From 5a3d739a9b7c602af33a6da83a3081faa5bd75a4 Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Sat, 15 Feb 2014 18:07:00 +0100 Subject: [PATCH] Scoremanager: pdf_views --- blechreiz/settings.py | 2 +- blechreiz/urls.py | 3 +- location_field/widgets.py | 2 +- scoremanager/PdfImage.py | 123 +++++++ scoremanager/__init__.py | 0 scoremanager/admin.py | 22 ++ scoremanager/models.py | 183 +++++++++++ scoremanager/pdf_views.py | 221 +++++++++++++ scoremanager/static/img/score-icon.png | Bin 0 -> 14535 bytes scoremanager/static/img/scoreSheet.jpg | Bin 0 -> 31907 bytes .../scoremanager/list_repertoire.html | 133 ++++++++ .../scoremanager/manage_repertoire.html | 284 ++++++++++++++++ .../templates/scoremanager/piece_view.html | 310 ++++++++++++++++++ scoremanager/urls.py | 17 + scoremanager/views.py | 154 +++++++++ website/templates/website/base.html | 3 +- 16 files changed, 1453 insertions(+), 4 deletions(-) create mode 100644 scoremanager/PdfImage.py create mode 100644 scoremanager/__init__.py create mode 100644 scoremanager/admin.py create mode 100644 scoremanager/models.py create mode 100644 scoremanager/pdf_views.py create mode 100644 scoremanager/static/img/score-icon.png create mode 100644 scoremanager/static/img/scoreSheet.jpg create mode 100644 scoremanager/templates/scoremanager/list_repertoire.html create mode 100644 scoremanager/templates/scoremanager/manage_repertoire.html create mode 100644 scoremanager/templates/scoremanager/piece_view.html create mode 100644 scoremanager/urls.py create mode 100644 scoremanager/views.py diff --git a/blechreiz/settings.py b/blechreiz/settings.py index e072f95..49c03d7 100644 --- a/blechreiz/settings.py +++ b/blechreiz/settings.py @@ -160,7 +160,7 @@ INSTALLED_APPS = ( 'eventplanner', # Event Management 'simpleforum', # Messages ( Forum ) 'location_field', # custom location field used in Event Management - + 'scoremanager', # manager of scores, repertoire etc. #'imagestore', #'sorl.thumbnail', #'tagging' diff --git a/blechreiz/urls.py b/blechreiz/urls.py index fcfb313..9640e25 100644 --- a/blechreiz/urls.py +++ b/blechreiz/urls.py @@ -8,7 +8,7 @@ import eventplanner.urls import musicians.urls #import imagestore.urls import website.urls - +import scoremanager.urls import settings from django.conf.urls.static import static @@ -19,6 +19,7 @@ urlpatterns = patterns('', url(r'^', include(website.urls) ), url(r'^events/', include( eventplanner.urls.urlpatterns) ), url(r'^musicians/', include( musicians.urls.urlpatterns) ), + url(r'^scores/', include( scoremanager.urls.urlpatterns) ), url(r'^messages/$', simpleforum.views.message_view ), url(r'^admin/', include(admin.site.urls) ), url(r'^location_field/', include('location_field.urls')), diff --git a/location_field/widgets.py b/location_field/widgets.py index c55c6f2..304a31f 100644 --- a/location_field/widgets.py +++ b/location_field/widgets.py @@ -48,7 +48,7 @@ class LocationWidget(widgets.TextInput): } # Use schemaless URL so it works with both, http and https websites js = ( - '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js', # jquery + '//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js', # jquery '//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js', '//maps.google.com/maps/api/js?sensor=false&language=de', '/static/js/bindWithDelay.js', diff --git a/scoremanager/PdfImage.py b/scoremanager/PdfImage.py new file mode 100644 index 0000000..17978d2 --- /dev/null +++ b/scoremanager/PdfImage.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# encoding: utf-8 +"""matplotlib_example.py + An simple example of how to insert matplotlib generated figures + into a ReportLab platypus document. +""" + +import matplotlib +matplotlib.use('PDF') +import matplotlib.pyplot as plt +import cStringIO + +from pdfrw import PdfReader +from pdfrw.buildxobj import pagexobj +from pdfrw.toreportlab import makerl + +from reportlab.platypus import Flowable +from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.rl_config import defaultPageSize +from reportlab.lib.units import inch + +PAGE_HEIGHT=defaultPageSize[1]; PAGE_WIDTH=defaultPageSize[0] +styles = getSampleStyleSheet() + +class PdfImage(Flowable): + """PdfImage wraps the first page from a PDF file as a Flowable +which can be included into a ReportLab Platypus document. +Based on the vectorpdf extension in rst2pdf (http://code.google.com/p/rst2pdf/)""" + + def __init__(self, filename_or_object, width=None, height=None, kind='direct'): + from reportlab.lib.units import inch + # If using StringIO buffer, set pointer to begining + if hasattr(filename_or_object, 'read'): + filename_or_object.seek(0) + page = PdfReader(filename_or_object, decompress=False).pages[0] + self.xobj = pagexobj(page) + self.imageWidth = width + self.imageHeight = height + x1, y1, x2, y2 = self.xobj.BBox + + self._w, self._h = x2 - x1, y2 - y1 + if not self.imageWidth: + self.imageWidth = self._w + if not self.imageHeight: + self.imageHeight = self._h + self.__ratio = float(self.imageWidth)/self.imageHeight + if kind in ['direct','absolute'] or width==None or height==None: + self.drawWidth = width or self.imageWidth + self.drawHeight = height or self.imageHeight + elif kind in ['bound','proportional']: + factor = min(float(width)/self._w,float(height)/self._h) + self.drawWidth = self._w*factor + self.drawHeight = self._h*factor + + def wrap(self, aW, aH): + return self.drawWidth, self.drawHeight + + def drawOn(self, canv, x, y, _sW=0): + if _sW > 0 and hasattr(self, 'hAlign'): + a = self.hAlign + if a in ('CENTER', 'CENTRE', TA_CENTER): + x += 0.5*_sW + elif a in ('RIGHT', TA_RIGHT): + x += _sW + elif a not in ('LEFT', TA_LEFT): + raise ValueError("Bad hAlign value " + str(a)) + + xobj = self.xobj + xobj_name = makerl(canv._doc, xobj) + + xscale = self.drawWidth/self._w + yscale = self.drawHeight/self._h + + x -= xobj.BBox[0] * xscale + y -= xobj.BBox[1] * yscale + + canv.saveState() + canv.translate(x, y) + canv.scale(xscale, yscale) + canv.doForm(xobj_name) + canv.restoreState() + +Title = "Hello world" +pageinfo = "platypus example" +def myFirstPage(canvas, doc): + canvas.saveState() + canvas.setFont('Times-Bold',16) + canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, Title) + canvas.setFont('Times-Roman',9) + canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo) + canvas.restoreState() + + +def myLaterPages(canvas, doc): + canvas.saveState() + canvas.setFont('Times-Roman',9) + canvas.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo)) + canvas.restoreState() + +def go(): + fig = plt.figure(figsize=(4, 3)) + plt.plot([1,2,3,4]) + plt.ylabel('some numbers') + imgdata = cStringIO.StringIO() + fig.savefig(imgdata,format='PDF') + doc = SimpleDocTemplate("document.pdf") + Story = [Spacer(1,2*inch)] + style = styles["Normal"] + for i in range(5): + bogustext = ("This is Paragraph number %s. " % i) *20 + p = Paragraph(bogustext, style) + Story.append(p) + Story.append(Spacer(1,0.2*inch)) + pi = PdfImage(imgdata) + Story.append(pi) + Story.append(Spacer(1,0.2*inch)) + doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages) + + +if __name__ == '__main__': + go() \ No newline at end of file diff --git a/scoremanager/__init__.py b/scoremanager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scoremanager/admin.py b/scoremanager/admin.py new file mode 100644 index 0000000..f9662f3 --- /dev/null +++ b/scoremanager/admin.py @@ -0,0 +1,22 @@ +from django.contrib import admin +from scoremanager.models import Piece, Score, Recording, BookLocation + + +class ScoreInline( admin.StackedInline ): + model = Score + extra = 1 + + +class RecordingInline( admin.StackedInline ): + model = Recording + extra = 1 + +class BookLocationInline( admin.StackedInline ): + model = BookLocation + extra = 1 + +class PieceAdmin (admin.ModelAdmin): + inlines = (BookLocationInline, ScoreInline, RecordingInline ) + + +admin.site.register( Piece, PieceAdmin ) \ No newline at end of file diff --git a/scoremanager/models.py b/scoremanager/models.py new file mode 100644 index 0000000..d1d920d --- /dev/null +++ b/scoremanager/models.py @@ -0,0 +1,183 @@ +import os +import re + +from django.db import models +from django.db.models.fields.related import ForeignKey +from django.utils.translation import ugettext as _ +from django.contrib.auth.models import User +from django.utils.safestring import mark_safe + + +############################# Helper Functions ####################################################### + + +def space_to_camelcase(value): + def camelcase(): + yield type(value).lower + while True: + yield type(value).capitalize + + c = camelcase() + return "".join(c.next()(x) if x else '_' for x in value.split() ) + + +def score_filename(score,original_name): + fileExtension = os.path.splitext(original_name)[1] + filename = "scores/" + filename += space_to_camelcase( score.piece.title ) + "/" + filename += space_to_camelcase( score.score_type ) + filename += fileExtension + return filename + + +def recordingFileName( recording, originalName ): + fileExtension = os.path.splitext(originalName)[1] + filename = "recordings/" + filename += space_to_camelcase( recording.piece.title ) + "/" + filename += space_to_camelcase( recording.artist ) + filename += fileExtension + return filename + +_ +####################################################################################################### + + +class Piece (models.Model): + title = models.CharField( max_length = 255, verbose_name = _("title"), unique=True ) + composer = models.CharField( max_length = 255, blank = True, verbose_name = _("composer") ) + repertoire_nr = models.IntegerField( null = True, blank=True, unique=True, default = None ) + + def __unicode__(self): + res = self.title + return res + + def isInRepertoire(self): + return self.repertoire_nr is not None + + def get_score_for_user(self, user): + try: + return ScoreUserMapping.objects.get( user=user, score__in = self.scores.all() ).score + except Piece.DoesNotExist: + if len( self.scores.all() ) > 0: + return self.scores.all()[0] + else: + return None + + + @staticmethod + def getRepertoire(): + return Piece.objects.filter( repertoire_nr__isnull = False ).order_by( 'repertoire_nr' ) + + class Meta: + permissions = ( + ("manage_scores", "Administrate and manage scores"), + ) + + + +class BookLocation ( models.Model ): + piece = models.ForeignKey ( 'Piece' ) + book = models.CharField( max_length = 100, blank = False, verbose_name = _("Buch") ) + page = models.IntegerField( verbose_name = _("page") ) + + def __unicode__(self): + return "%s, %d" % ( self.book, self.page ) + + + +class Score( models.Model ): + piece = ForeignKey('Piece', related_name="scores") + score_type = models.CharField(max_length=100, verbose_name="score type" ) # for example partitur, unterstimmen ... + file = models.FileField(upload_to=score_filename, verbose_name = _("file") ) + uploaded_by = ForeignKey( User, verbose_name=_("uploaded_by") ) + + @property + def image_file_name(self): + return os.path.splitext("image_cache/" + str(self.file))[0] + ".jpg" + + @staticmethod + def pdf2jpg(source_file, target_file, resolution = 100, crop = 15): + from wand.image import Image + ret = True + try: + with Image(filename=source_file, resolution=(resolution,resolution)) as img: + img.crop( crop,crop, width= img.width - 2*crop, height = int( 0.5 * img.height) - 2*crop ) + img.format = 'jpeg' + img.save(filename = target_file) + except Exception as e: + print(e) + ret = False + + return ret + + def get_image_file(self): + from django.conf import settings + + inputFile = settings.MEDIA_ROOT + "/" + str(self.file) + cacheFile = settings.MEDIA_ROOT + "/" + str(self.image_file_name) + # Create a jpg for this score, if it does not exist yet + if not os.path.exists( cacheFile ): + if not os.path.exists( os.path.dirname( cacheFile )): + os.makedirs( os.path.dirname(cacheFile) ) + Score.pdf2jpg ( inputFile, cacheFile ) + + return self.image_file_name + + def is_active_score(self,user): + return len( ScoreUserMapping.objects.filter( score= self, user=user ) ) > 0 + + class Meta: + unique_together = (("piece", "score_type"),) + ordering = ['score_type'] + + + + +class Recording( models.Model ): + piece = ForeignKey( 'Piece', related_name='recordings' ) + artist = models.CharField( max_length = 100, verbose_name = _("Artist") ) + file = models.FileField( upload_to = recordingFileName, verbose_name = _("file") ) + uploaded_by = ForeignKey( User, verbose_name=_("uploaded_by") ) + + class Meta: + unique_together = (("piece", "artist"),) + ordering = ['artist'] + + + +class YoutubeRecording( models.Model ): + piece = models.ForeignKey('Piece', related_name="youtubeLinks" ) + link = models.CharField( max_length = 300, blank=False) + uploaded_by = ForeignKey( User, verbose_name=_("uploaded_by") ) + + youtubeRegex = re.compile( u'(?:https://)?(?:http://)?www.youtube.(?:com|de)/watch\?v=(?P[-\w]*)' ) + + @property + def embed_html( self ): + replacement = """ +
+ """ + return mark_safe( YoutubeRecording.youtubeRegex.sub( replacement, self.link ) ) + + class Meta: + unique_together = (("link", "piece" )) + + + + +class ScoreUserMapping( models.Model): + score = ForeignKey( 'Score' ) + user = models.OneToOneField( User ) + piece = ForeignKey( 'Piece' ) + + @staticmethod + def add_user_score_mapping( score, user ): + piece = score.piece + ScoreUserMapping.objects.filter( user=user, piece=piece ).delete() + ScoreUserMapping.objects.create( score=score, user=user, piece= piece ) + + + class Meta: + unique_together = (("piece", "user" )) + + diff --git a/scoremanager/pdf_views.py b/scoremanager/pdf_views.py new file mode 100644 index 0000000..974fefc --- /dev/null +++ b/scoremanager/pdf_views.py @@ -0,0 +1,221 @@ +# ----------------------------- Pdf Views ------------------------------------------ + +from django.http import HttpResponse +from scoremanager.models import Piece + +from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Flowable, Table, PageBreak, NextPageTemplate +from reportlab.lib import pagesizes, units, utils +from reportlab.lib.colors import Color + +import os + +class RepertoireDocTemplate( BaseDocTemplate ): + def __init__(self, *args, **kwargs ): + BaseDocTemplate.__init__(self,*args, **kwargs) + + self.pagesize = kwargs['pagesize'] + + leftBorder = 1 * units.cm + rightBorder = 1 * units.cm + topBorder = 3 * units.cm + bottomBorder = 1 * units.cm + + frameWidth = (self.pagesize[0] - rightBorder - leftBorder ) /2 + frameHeight = (self.pagesize[1] - topBorder - bottomBorder ) + + leftColumn = Frame( leftBorder, bottomBorder, frameWidth, frameHeight, showBoundary=0 ) + rightColumn = Frame( leftBorder+frameWidth , bottomBorder, frameWidth, frameHeight, showBoundary=0 ) + + tocTemplate = PageTemplate( id='TOC', frames=[leftColumn,rightColumn], onPage=RepertoireDocTemplate._drawHeader ) + fullPageTemplate = PageTemplate( id='FullPage', frames = [ Frame(0,0, self.pagesize[0], self.pagesize[1] ) ] ) + + self.addPageTemplates( [ tocTemplate, fullPageTemplate ] ) + + @staticmethod + def _drawHeader(canvas,document): + + currentPath = os.path.dirname(os.path.realpath(__file__)) + logoImageFile = currentPath + "/static/pdfImg/logo.png" + bgImageFile = currentPath + "/static/pdfImg/body_bg.jpg" + + #Gradient + titleAreaHeight = 2 * units.cm + gradientColors = [ Color(0.1,0.1,0.1), Color(0.2,0.2,0.2)] + ps = document.pagesize + gradientStart = ( ps[0]/2, ps[1] ) + gradientEnd = ( ps[0]/2, ps[1] - titleAreaHeight) + canvas.linearGradient( gradientStart[0],gradientStart[1], gradientEnd[0], gradientEnd[1],gradientColors, extend=False ) + titleAreaMidY = 0.5 * ( gradientStart[1] + gradientEnd[1] ) + + # Draw Logo + logoImg = utils.ImageReader( logoImageFile ) + logoSize = logoImg.getSize() + logoFraction = 0.6 + logoSize = (logoFraction * logoSize[0], logoFraction*logoSize[1]) + logoPosition = ( ps[0] - 5*units.cm, titleAreaMidY - 0.5*logoSize[1] ) + canvas.drawImage( logoImg, logoPosition[0], logoPosition[1], width=logoSize[0], height=logoSize[1], mask=Color(0,0,0) ) + + # Draw Title + text = canvas.beginText() + text.setTextOrigin( 1 * units.cm, titleAreaMidY-25/2 ) + text.setFillColorRGB( 0.95,0.95,0.95 ) + text.setFont( 'Helvetica', 25 ) + text.textLine( "Inhaltsverzeichnis" ) + canvas.drawText( text ) + + # Draw Background + bgImage = utils.ImageReader( bgImageFile ) + bgImageSize = bgImage.getSize() + curPos = [0, gradientEnd[1]-bgImageSize[1] ] + while curPos[1] > -bgImageSize[1]: + curPos[0] = 0 + while curPos[0] < ps[0]: + canvas.drawImage( bgImage, curPos[0], curPos[1] ) + curPos[0] += bgImageSize[0] + + curPos[1] -= bgImageSize[1] + + +class PdfImage(Flowable): + """PdfImage wraps the first page from a PDF file as a Flowable + which can be included into a ReportLab Platypus document. + Based on the vectorpdf extension in rst2pdf (http://code.google.com/p/rst2pdf/)""" + + def __init__(self, filename_or_object, page=0, width=None, height=None, kind='direct'): + from pdfrw import PdfReader + from pdfrw.buildxobj import pagexobj + + # If using StringIO buffer, set pointer to beginning + if hasattr(filename_or_object, 'read'): + filename_or_object.seek(0) + page = PdfReader(filename_or_object, decompress=False).pages[page] + self.xobj = pagexobj(page) + self.imageWidth = width + self.imageHeight = height + x1, y1, x2, y2 = self.xobj.BBox + + self._w, self._h = x2 - x1, y2 - y1 + if not self.imageWidth: + self.imageWidth = self._w + if not self.imageHeight: + self.imageHeight = self._h + self.__ratio = float(self.imageWidth)/self.imageHeight + if kind in ['direct','absolute'] or width==None or height==None: + self.drawWidth = width or self.imageWidth + self.drawHeight = height or self.imageHeight + elif kind in ['bound','proportional']: + factor = min(float(width)/self._w,float(height)/self._h) + self.drawWidth = self._w*factor + self.drawHeight = self._h*factor + + def wrap(self, aW, aH): + return self.drawWidth, self.drawHeight + + def drawOn(self, canv, x, y, _sW=0): + from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT + from pdfrw.toreportlab import makerl + + if _sW > 0 and hasattr(self, 'hAlign'): + a = self.hAlign + if a in ('CENTER', 'CENTRE', TA_CENTER): + x += 0.5*_sW + elif a in ('RIGHT', TA_RIGHT): + x += _sW + elif a not in ('LEFT', TA_LEFT): + raise ValueError("Bad hAlign value " + str(a)) + + xobj = self.xobj + xobj_name = makerl(canv._doc, xobj) + + xscale = self.drawWidth/self._w + yscale = self.drawHeight/self._h + + x -= xobj.BBox[0] * xscale + y -= xobj.BBox[1] * yscale + + canv.saveState() + canv.translate(x, y) + canv.scale(xscale, yscale) + canv.doForm(xobj_name) + canv.restoreState() + + +class Bookmark(Flowable): + """ Utility class to display PDF bookmark. """ + def __init__(self, title, key): + self.title = title + self.key = key + Flowable.__init__(self) + + def wrap(self, availWidth, availHeight): + """ Doesn't take up any space. """ + return (0, 0) + + def draw(self): + # set the bookmark outline to show when the file's opened + self.canv.showOutline() + # step 1: put a bookmark on the + self.canv.bookmarkPage(self.key) + # step 2: put an entry in the bookmark outline + self.canv.addOutlineEntry(self.title, self.key, 0, 0) + + +def repertoire_pdf( request ): + response = HttpResponse(content_type='application/pdf') + response['Content-Disposition'] = 'attachment; filename="Repertoire.pdf"' + + # Create the PDF object, using the response object as its "file. + doc = RepertoireDocTemplate(response, pagesize=pagesizes.A4) + elements = [] + + elements.append( Bookmark("Inhaltsverzeichnis", "Contents") ) + #TOC + data = [] + for piece in Piece.getRepertoire(): + data.append( [ "%d %s" % ( piece.repertoire_nr, piece.title ) ] ) + table = Table(data) + table.hAlign = "LEFT" + elements.append(table) + + + elements.append( NextPageTemplate('FullPage') ) + elements.append(PageBreak()) + pagesize=pagesizes.A4 + for piece in Piece.getRepertoire(): + score = piece.get_score_for_user( request.user ) + filename = score.file + bookmarkTitle = "%02d %s - %s " % ( piece.repertoire_nr, piece.title, score.score_type ) + elements.append( Bookmark( bookmarkTitle, str(piece.id) ) ) + + #TODO support also multiple pages!! + image_flowable = PdfImage( filename, 0, width=pagesize[0]*0.98, height=pagesize[1] *0.98 ) + image_flowable.hAlign = "CENTER" + elements.append( image_flowable ) + + + doc.build(elements) + + return response + + + +def repertoire_toc( request ): + response = HttpResponse(content_type='application/pdf') + response['Content-Disposition'] = 'attachment; filename="Inhaltsverzeichnis.pdf"' + + # Create the PDF object, using the response object as its "file. + doc = RepertoireDocTemplate(response,pagesize=pagesizes.A4 ) + elements = [] + + data = [] + for piece in Piece.getRepertoire(): + data.append( [ "%d %s" % ( piece.repertoire_nr, piece.title ) ] ) + + table = Table(data) + table.hAlign = "LEFT" + elements.append(table) + + doc.build(elements) + + return response + diff --git a/scoremanager/static/img/score-icon.png b/scoremanager/static/img/score-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..72c63ef041bcaa48d08c4786da0daab3e9d83eff GIT binary patch literal 14535 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%0A*;a^*3~NGGgao^6VhB<=FFKh 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 literal 0 HcmV?d00001 diff --git a/scoremanager/static/img/scoreSheet.jpg b/scoremanager/static/img/scoreSheet.jpg new file mode 100644 index 0000000000000000000000000000000000000000..19cd0e19480d07ce4544ebbf62e0f3041ae20ae1 GIT binary patch literal 31907 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 literal 0 HcmV?d00001 diff --git a/scoremanager/templates/scoremanager/list_repertoire.html b/scoremanager/templates/scoremanager/list_repertoire.html new file mode 100644 index 0000000..b34f115 --- /dev/null +++ b/scoremanager/templates/scoremanager/list_repertoire.html @@ -0,0 +1,133 @@ + {% extends "website/base.html" %} {% load sekizai_tags staticfiles %} + + + {% block content %} + + {% addtoblock "css" %} + +{% endaddtoblock %} + + + +
+
+ +
+ +
+ +

Repertoire

+ + + + + + + + + + {% for piece in repertoire %} + + + + + + + + {% endfor %} +
# Stück
+ {{ piece.repertoire_nr }} + + + {{ piece.title }} + + + {{ piece.booklocation }} +
+
+ + +
+ +
+ +
+ + +{% endblock %} \ No newline at end of file diff --git a/scoremanager/templates/scoremanager/manage_repertoire.html b/scoremanager/templates/scoremanager/manage_repertoire.html new file mode 100644 index 0000000..57242fd --- /dev/null +++ b/scoremanager/templates/scoremanager/manage_repertoire.html @@ -0,0 +1,284 @@ + {% extends "website/base.html" %} {% load sekizai_tags staticfiles %} + + + + +{% block content %} + + +{% addtoblock "js" strip %} {% endaddtoblock %} +{% addtoblock "js" strip %} {% endaddtoblock %} +{% addtoblock "js" strip %} {% endaddtoblock %} + +{% addtoblock "css" strip %} {% endaddtoblock %} + + + +{% addtoblock "css" %} + +{% endaddtoblock %} + +{% addtoblock "js" %} + +{% endaddtoblock %} + +
+
+
+
+

Repertoire Manager

+
+
+ +
+ +
+

Alle Stücke

+ + + + +
    + {% for piece in allPieces %} +
  • +

    + + {{ piece.title }} +
    + {{ piece.composer}} + {% if piece.booklocation %} ( {{piece.booklocation}} ) {% endif %} + +

    +
  • + {% endfor %} +
+ + +
+ +
+ +

Repertoire

+ +
    + {% for piece in repertoire %} +
  1. +

    + + + {{ piece.title }} +
    + {{ piece.composer}} + {% if piece.booklocation %} ( {{piece.booklocation}} ) {% endif %} + +

    +
  2. + {% endfor %} +
+ + + + + +
+
+ +
+ +
+ + +{% endblock %} \ No newline at end of file diff --git a/scoremanager/templates/scoremanager/piece_view.html b/scoremanager/templates/scoremanager/piece_view.html new file mode 100644 index 0000000..552fc68 --- /dev/null +++ b/scoremanager/templates/scoremanager/piece_view.html @@ -0,0 +1,310 @@ +{% extends "website/base.html" %} + +{% load sekizai_tags staticfiles %} + +{% block content %} + +{% addtoblock "css" %} + +{% endaddtoblock %} + + +{% addtoblock "js" %} + +{% endaddtoblock %} + +
+
+
+ + +

{{ piece.title }}

+
+ {% if pictureScore %} + + + + {% endif %} + +

Info

+ + {% if piece.composer %} + + + + + {% endif %} + + + {% if piece.booklocation %} + + + + + {% endif %} +
Komponist:{{piece.composer}}
Buch: {{piece.booklocation }}
+ + + + {% if piece.scores.all|length > 0 %} +

Alle Noten

+ + {% for score in piece.scores.all %} + + + + + + {% endfor %} +
+ + {{ score.score_type }} + + + {% if score == activeScore %} + Meine Noten + {% else %} + Meine Noten + {% endif %} + + {% if score.uploaded_by == request.user or perms.scoremanager.manage_scores %} + Löschen + {% endif %} +
+ {% endif %} + + {% if piece.recordings.all|length > 0 %} +

Aufnahmen

+ + {% endif %} + + +
+ + + + +
+ + +
+
+ +
+
+
+
+ +

+ Youtube
+

+ +
+ +
+ + {% if piece.youtubeLinks.all|length > 0 %} +
+ {% for youtubeLink in piece.youtubeLinks.all %} +
+ {{ youtubeLink.embed_html }} + + {% if youtubeLink.uploaded_by == request.user or perms.scoremanager.manage_scores %} +
+ +
+ {% endif %} +
+ {% endfor %} +
+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/scoremanager/urls.py b/scoremanager/urls.py new file mode 100644 index 0000000..1645cde --- /dev/null +++ b/scoremanager/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls import patterns, url + +import scoremanager.views +import scoremanager.pdf_views + +urlpatterns = patterns('', + url(r'^repertoireManager$', scoremanager.views.manage_repertoire ), + url(r'^repertoireAjaxSave$$', scoremanager.views.manage_repertoire_ajax_save ), + url(r'^$', scoremanager.views.list_repertoire ), + url(r'^piece/(?P\d+)', scoremanager.views.piece_view ), + url(r'^score_usermapping_save', scoremanager.views.score_user_mapping_save ), + url(r'^score/(?P\d+)', scoremanager.views.score ), + url(r'^youtubeLink$', scoremanager.views.youtube_link ), + url(r'^inhaltsverzeichnis.pdf$',scoremanager.pdf_views.repertoire_toc), + url(r'^repertoire.pdf', scoremanager.pdf_views.repertoire_pdf ), +) + diff --git a/scoremanager/views.py b/scoremanager/views.py new file mode 100644 index 0000000..6fbff7c --- /dev/null +++ b/scoremanager/views.py @@ -0,0 +1,154 @@ + +from django.shortcuts import render +from django.http import HttpResponse +from django import forms +from django.conf import settings +from django.utils.translation import ugettext as _ +from django.core.exceptions import PermissionDenied + +from scoremanager.models import Piece, Score, Recording, ScoreUserMapping, YoutubeRecording +import json +import os + +def manage_repertoire( request ): + context = {} + context['repertoire'] = Piece.getRepertoire() + context['allPieces' ] = Piece.objects.all().order_by( 'title' ) + return render ( request, 'scoremanager/manage_repertoire.html', context ) + + +def manage_repertoire_ajax_save( request ): + if request.is_ajax(): + if request.method == 'POST': + result = json.loads( request.body ) + + parsedResult = { int(key): value for ( key,value ) in result.items() } + Piece.objects.all().update( repertoire_nr = None) + for piece in Piece.objects.all(): + if piece.pk in parsedResult: + piece.repertoire_nr = parsedResult[piece.pk] + piece.save() + + return HttpResponse("OK") + + + + +def list_repertoire( request ): + context = { 'repertoire': Piece.getRepertoire() } + return render( request, 'scoremanager/list_repertoire.html' , context ) + + + + + + + + +# ----------------------------- Piece View + Ajax views + Forms ------------------------------------------ + + + +def score_user_mapping_save( request ): + if request.is_ajax(): + if request.method == 'POST': + result = json.loads( request.body ) + print ( "Result " + str( result ) ) + print ( "Resulting score: " + str( result['myScore'] ) ) + result = int(result['myScore']) + ScoreUserMapping.add_user_score_mapping( user=request.user, score= Score.objects.get(pk=result) ) + + return HttpResponse("OK") + +def score( request, pk ): + requestedScore = Score.objects.get(pk=pk) + + if request.method == 'GET': + imageFile = requestedScore.get_image_file() + image_data = open( settings.MEDIA_ROOT + imageFile, "rb").read() + return HttpResponse(image_data, mimetype="image/jpeg") + if request.method == "DELETE": + if requestedScore.uploaded_by != request.user or request.user.has_perm('scoremanager.manage_scores'): + raise PermissionDenied + requestedScore.delete() + return HttpResponse("OK") + + +def youtube_link( request ): + result = json.loads( request.body ) + print("Youtube link: " + str(result) ) + + if request.method == 'DELETE': + linkId = int( result['linkid'] ) + youtubeRecording = YoutubeRecording.objects.get( pk = linkId ) + if youtubeRecording.uploaded_by == request.user or request.user.has_perm('scoremanager.manage_scores'): + youtubeRecording.delete() + else: + raise PermissionDenied + + return HttpResponse("OK") + + elif request.method == 'POST': + link = str( result['link'] ) + pieceId = int( result['pieceId'] ) + + newRecording = YoutubeRecording(piece=Piece.objects.get(pk=pieceId), link=link) + newRecording.uploaded_by = request.user + newRecording.save() + return HttpResponse("OK") + + + +class UploadFileForm(forms.Form): + file = forms.FileField( max_length="80") + + def clean_file(self): + f = self.cleaned_data['file'] + extension = os.path.splitext( f.name )[1] + if extension != ".pdf" and extension !=".mp3" and extension != ".zip": + raise forms.ValidationError( _("Unknown extension. Allowed extension are mp3, pdf and zip")) + + +def piece_view( request, pk ): + currentPiece = Piece.objects.get( pk=pk ) + context = {'piece': currentPiece } + + for score in currentPiece.scores.all(): + if score.is_active_score( request.user ): + context['activeScore'] = score + context['pictureScore'] = score + + + if not 'pictureScore' in context.keys() and len( currentPiece.scores.all()) > 0: + context['pictureScore'] = currentPiece.scores.all()[0] + + if request.method == 'POST': + form = UploadFileForm(request.POST, request.FILES) + + if form.is_valid(): + f = request.FILES['file'] + [basename,extension] = os.path.splitext( f.name ) + print ("extension " + extension + " basename " + basename) + if extension == ".mp3": + print("Uploaded Recording") + recording = Recording( piece=currentPiece, artist=basename, file=f ) + recording.uploaded_by=request.user + recording.save() + elif extension == ".pdf": + print("Uploaded Score") + score = Score( piece=currentPiece, score_type=basename, file=f ) + score.uploaded_by=request.user + score.save() + elif extension == ".zip": + #TODO + print ("uploaded zip - not yet supported") + + else: + form = UploadFileForm() + + context['form'] = form + + return render( request, 'scoremanager/piece_view.html', context ) + + + diff --git a/website/templates/website/base.html b/website/templates/website/base.html index 570d70b..5809ecb 100644 --- a/website/templates/website/base.html +++ b/website/templates/website/base.html @@ -37,6 +37,7 @@
  • Termine
  • Forum
  • Adressbuch
  • +
  • Noten
  • {% endblock %} {% if user.is_authenticated %} @@ -70,7 +71,7 @@