Scoremanager: pdf_views
This commit is contained in:
parent
7ad5ec21fc
commit
5a3d739a9b
|
@ -160,7 +160,7 @@ INSTALLED_APPS = (
|
||||||
'eventplanner', # Event Management
|
'eventplanner', # Event Management
|
||||||
'simpleforum', # Messages ( Forum )
|
'simpleforum', # Messages ( Forum )
|
||||||
'location_field', # custom location field used in Event Management
|
'location_field', # custom location field used in Event Management
|
||||||
|
'scoremanager', # manager of scores, repertoire etc.
|
||||||
#'imagestore',
|
#'imagestore',
|
||||||
#'sorl.thumbnail',
|
#'sorl.thumbnail',
|
||||||
#'tagging'
|
#'tagging'
|
||||||
|
|
|
@ -8,7 +8,7 @@ import eventplanner.urls
|
||||||
import musicians.urls
|
import musicians.urls
|
||||||
#import imagestore.urls
|
#import imagestore.urls
|
||||||
import website.urls
|
import website.urls
|
||||||
|
import scoremanager.urls
|
||||||
|
|
||||||
import settings
|
import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
@ -19,6 +19,7 @@ urlpatterns = patterns('',
|
||||||
url(r'^', include(website.urls) ),
|
url(r'^', include(website.urls) ),
|
||||||
url(r'^events/', include( eventplanner.urls.urlpatterns) ),
|
url(r'^events/', include( eventplanner.urls.urlpatterns) ),
|
||||||
url(r'^musicians/', include( musicians.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'^messages/$', simpleforum.views.message_view ),
|
||||||
url(r'^admin/', include(admin.site.urls) ),
|
url(r'^admin/', include(admin.site.urls) ),
|
||||||
url(r'^location_field/', include('location_field.urls')),
|
url(r'^location_field/', include('location_field.urls')),
|
||||||
|
|
|
@ -48,7 +48,7 @@ class LocationWidget(widgets.TextInput):
|
||||||
}
|
}
|
||||||
# Use schemaless URL so it works with both, http and https websites
|
# Use schemaless URL so it works with both, http and https websites
|
||||||
js = (
|
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',
|
'//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js',
|
||||||
'//maps.google.com/maps/api/js?sensor=false&language=de',
|
'//maps.google.com/maps/api/js?sensor=false&language=de',
|
||||||
'/static/js/bindWithDelay.js',
|
'/static/js/bindWithDelay.js',
|
||||||
|
|
|
@ -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()
|
|
@ -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 )
|
|
@ -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<videoID>[-\w]*)' )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def embed_html( self ):
|
||||||
|
replacement = """
|
||||||
|
<div class="embed-container"><iframe src="//www.youtube.de/embed/\g<videoID>" frameborder="0" allowfullscreen></iframe> </div>
|
||||||
|
"""
|
||||||
|
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" ))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
|
@ -0,0 +1,133 @@
|
||||||
|
{% extends "website/base.html" %} {% load sekizai_tags staticfiles %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% addtoblock "css" %}
|
||||||
|
<style>
|
||||||
|
.pic-with-border {
|
||||||
|
border: 7px solid rgb(255, 255, 255);
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 1px 1px 2px 0px rgb(207, 207, 207);
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
padding-bottom: 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border-bottom: 1px solid rgb(216, 216, 216);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 40px 19px 40px 28px;
|
||||||
|
background: none repeat scroll 0% 0% rgb(255, 255, 255);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 1px 1px 2px 0px rgb(207, 207, 207);
|
||||||
|
float: right;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
.sidebar_menu {
|
||||||
|
margin: 0px;
|
||||||
|
padding-top: 30px;
|
||||||
|
padding-left: 10px;
|
||||||
|
list-style: none outside none;
|
||||||
|
}
|
||||||
|
.sidebar_menu ul{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.sidebar .sidebar_menu li {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
a.inverse_color {
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
-webkit-transition: color .2s;
|
||||||
|
-moz-transition: color .2s;
|
||||||
|
-ms-transition: color .2s;
|
||||||
|
transition: color .2s;
|
||||||
|
}
|
||||||
|
a.inverse_color:hover {
|
||||||
|
color: #1187D8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
{% endaddtoblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="span8" >
|
||||||
|
|
||||||
|
<h2>Repertoire</h2>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> # </th>
|
||||||
|
<th> Stück </th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for piece in repertoire %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ piece.repertoire_nr }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a class="inverse_color" href="{% url 'scoremanager.views.piece_view' piece.pk %}">
|
||||||
|
{{ piece.title }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{{ piece.booklocation }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="span3 offset1 sidebar">
|
||||||
|
<div class="box">
|
||||||
|
<h4>Notenverwaltung</h4>
|
||||||
|
|
||||||
|
<img class="pic-with-border" src="{{STATIC_URL}}/img/scoreSheet.jpg" />
|
||||||
|
|
||||||
|
<ul class="sidebar_menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a class="inverse_color" href="{% url 'scoremanager.pdf_views.repertoire_toc' %}"> Inhaltsverzeichnis herunterladen </a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a class="inverse_color" href="{% url 'scoremanager.pdf_views.repertoire_pdf' %}">Repertoire herunterladen</a>
|
||||||
|
</li>
|
||||||
|
{% if perms.scoremanager.manage_scores %}
|
||||||
|
<li>
|
||||||
|
<a class="inverse_color" href="{% url 'scoremanager.views.manage_repertoire' %}">
|
||||||
|
Repertoire verwalten
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,284 @@
|
||||||
|
{% extends "website/base.html" %} {% load sekizai_tags staticfiles %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
|
||||||
|
{% addtoblock "js" strip %} <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script> {% endaddtoblock %}
|
||||||
|
{% addtoblock "js" strip %} <script src="{{STATIC_URL}}/js/List.js"></script> {% endaddtoblock %}
|
||||||
|
{% addtoblock "js" strip %} <script src="{{STATIC_URL}}/js/List.pagination.js"></script> {% endaddtoblock %}
|
||||||
|
|
||||||
|
{% addtoblock "css" strip %} <link rel="stylesheet" href="{{STATIC_URL}}/css/jquery-ui-1.8.21.custom.css" type="text/css" media="screen" /> {% endaddtoblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% addtoblock "css" %}
|
||||||
|
<style>
|
||||||
|
.piecelist {
|
||||||
|
position: relative;
|
||||||
|
padding: 45px 15px 15px;
|
||||||
|
margin: 0 -15px 15px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
box-shadow: inset 0 3px 6px rgba(0,0,0,.05);
|
||||||
|
border-color: #e5e5e5 #eee #eee;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px 0;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist:after {
|
||||||
|
content: "Stückliste";
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #bbb;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist + .highlight {
|
||||||
|
margin: -15px -15px 15px;
|
||||||
|
border-radius: 0;
|
||||||
|
border-width: 0 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.piecelist {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #ddd;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.piecelist + .highlight {
|
||||||
|
margin-top: -16px;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
border-width: 1px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input[type='text'].pieceSearch {
|
||||||
|
float: right;
|
||||||
|
z-index: 2;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-right: 15px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
height: 12px;
|
||||||
|
width: 165px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.piecelist{
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol.piecelist {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom:0;
|
||||||
|
margin-left: 0px; /* Since 'ul,ol{}' setting in line 108 affects this selector, 'margin-left' is redefined. */
|
||||||
|
list-style-position: inside; /* This will place the number on top and inside of the current color of li */
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist .item {
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
height: 30px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align:text-top;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist .item img {
|
||||||
|
height: 40px;
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist > li {
|
||||||
|
border-radius: 17px;
|
||||||
|
background-color: #dff0d8;
|
||||||
|
padding:10px;
|
||||||
|
box-shadow: inset 0 1px 0 #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist .title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist .composer {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist .bookLocation {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piecelist li span {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
.pagination li {
|
||||||
|
display:inline-block;
|
||||||
|
padding:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveButton {
|
||||||
|
float: right;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endaddtoblock %}
|
||||||
|
|
||||||
|
{% addtoblock "js" %}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$( "#allPieces li" ).draggable({
|
||||||
|
connectToSortable: "#repertoire" ,
|
||||||
|
appendTo: "parent",
|
||||||
|
helper: function(){
|
||||||
|
return $(this).clone().width($(this).width());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( "#allPieces li.in-repertoire" ).draggable("disable");
|
||||||
|
|
||||||
|
$( "#repertoire" ).sortable( {
|
||||||
|
receive: function(ev, ui) {
|
||||||
|
$(ui.item).draggable("disable");
|
||||||
|
$('#saveButton').removeAttr('disabled');
|
||||||
|
},
|
||||||
|
update: function( event, ui ) {
|
||||||
|
$('#saveButton').removeAttr('disabled');
|
||||||
|
}
|
||||||
|
}).disableSelection();
|
||||||
|
|
||||||
|
$("#allPieces").droppable({
|
||||||
|
accept: "#repertoire li",
|
||||||
|
hoverClass: "ui-state-hover",
|
||||||
|
drop: function(ev, ui) {
|
||||||
|
var id = $(ui.draggable).data("pieceid");
|
||||||
|
$("#allPieces li[data-pieceid=" + id + "]" ).draggable("enable")
|
||||||
|
ui.draggable.remove();
|
||||||
|
$('#saveButton').removeAttr('disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( "#saveButton").click( function() {
|
||||||
|
var result = {};
|
||||||
|
$("#repertoire li").each( function(index,value) {
|
||||||
|
var id = parseInt( $(this).data("pieceid") );
|
||||||
|
result[id] = index +1;
|
||||||
|
});
|
||||||
|
$.ajax({
|
||||||
|
url: "{% url 'scoremanager.views.manage_repertoire_ajax_save' %} " ,
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
data: JSON.stringify(result),
|
||||||
|
dataType: 'text',
|
||||||
|
success: function(result) {
|
||||||
|
$('#saveButton').attr('disabled','disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
valueNames : [ 'title', 'composer' ],
|
||||||
|
page : 5,
|
||||||
|
plugins : [ ListPagination({ paginationClass: 'paginationListMarker'}) ]
|
||||||
|
};
|
||||||
|
var pieceList = new List('allPiecesList', options);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endaddtoblock %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span12">
|
||||||
|
<h2>Repertoire Manager</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="span6" id="allPiecesList">
|
||||||
|
<h4>Alle Stücke</h4>
|
||||||
|
|
||||||
|
|
||||||
|
<input class="search pieceSearch" type="text" placeholder="Suchen" />
|
||||||
|
|
||||||
|
<ul id="allPieces" class="piecelist list">
|
||||||
|
{% for piece in allPieces %}
|
||||||
|
<li data-pieceid="{{piece.pk}}" {% if not piece.repertoire_nr %} {% else %}class="in-repertoire"{% endif %}>
|
||||||
|
<p class="item">
|
||||||
|
<img src="{{STATIC_URL}}/img/score-icon.png" />
|
||||||
|
<span class="title"> {{ piece.title }} </span>
|
||||||
|
<br/>
|
||||||
|
<span class="composer">{{ piece.composer}} </span>
|
||||||
|
{% if piece.booklocation %} <span class="bookLocation"> ( {{piece.booklocation}} ) </span> {% endif %}
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="pagination">
|
||||||
|
<ul class="paginationListMarker"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="span6">
|
||||||
|
|
||||||
|
<h4>Repertoire</h4>
|
||||||
|
|
||||||
|
<ol id="repertoire" class="piecelist" >
|
||||||
|
{% for piece in repertoire %}
|
||||||
|
<li data-pieceid="{{piece.pk}}">
|
||||||
|
<p class="item">
|
||||||
|
<img src="{{STATIC_URL}}/img/score-icon.png" />
|
||||||
|
|
||||||
|
<span class="title"> {{ piece.title }} </span>
|
||||||
|
<br/>
|
||||||
|
<span class="composer">{{ piece.composer}} </span>
|
||||||
|
{% if piece.booklocation %} <span class="bookLocation"> ( {{piece.booklocation}} ) </span> {% endif %}
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
<button id="saveButton" class="btn btn-primary" disabled="true">Speichern</button>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,310 @@
|
||||||
|
{% extends "website/base.html" %}
|
||||||
|
|
||||||
|
{% load sekizai_tags staticfiles %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% addtoblock "css" %}
|
||||||
|
<style>
|
||||||
|
.piece-pic {
|
||||||
|
border: 7px solid rgb(255, 255, 255);
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 1px 1px 2px 1px rgb(207, 207, 207);
|
||||||
|
margin-top:10px;
|
||||||
|
margin-bottom:20px;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
margin: 10px 0px 0px 0px;
|
||||||
|
padding: 40px 19px 20px 28px;
|
||||||
|
background: none repeat scroll 0% 0% rgb(255, 255, 255);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0px 1px 3px 1px rgb(207, 207, 207);
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
margin:15px;
|
||||||
|
}
|
||||||
|
.upload_form {
|
||||||
|
margin-top:15px;
|
||||||
|
}
|
||||||
|
.upload-area {
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
.help-text {
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed-container {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 56.25%; /* 16/9 ratio */
|
||||||
|
padding-top: 30px; /* IE6 workaround*/
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed-container iframe,
|
||||||
|
.embed-container object,
|
||||||
|
.embed-container embed {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.youtubeWrapper {
|
||||||
|
width: 30%;
|
||||||
|
float: left;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.youtubeCaption {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#section1 {
|
||||||
|
|
||||||
|
padding-bottom:50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#section2 {
|
||||||
|
background: url("{{STATIC_URL}}/img/backgrounds/aqua.jpg") no-repeat scroll 0% 0% / cover transparent;
|
||||||
|
display: block;
|
||||||
|
padding-top:50px;
|
||||||
|
padding-bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#section2 h4 {
|
||||||
|
font-size: 38px;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
line-height: 38px;
|
||||||
|
text-align: center;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
#section2 .upload {
|
||||||
|
text-align: center;
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
{% endaddtoblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% addtoblock "js" %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function() {
|
||||||
|
$(".upload_form input:file").change(function (){
|
||||||
|
$(".upload_form").submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("a.score-label").click( function() {
|
||||||
|
var id = $(this).data("id");
|
||||||
|
$.ajax( {
|
||||||
|
url: "{% url 'scoremanager.views.score_user_mapping_save' %}",
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
data: ' { "myScore" : ' + id + ' } ',
|
||||||
|
dataType: 'text',
|
||||||
|
success: function(result) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".delete-button").click(function() {
|
||||||
|
var id = $(this).data("id");
|
||||||
|
var scoreUrl = "{% url 'scoremanager.views.score' '4242' %}"
|
||||||
|
|
||||||
|
if (confirm('Diesen Notensatz wirklich löschen?')) {
|
||||||
|
$.ajax( {
|
||||||
|
url: scoreUrl.replace( '4242', id.toString() ),
|
||||||
|
type: 'DELETE',
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
success: function(result) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#youtubeLinkInput").keypress(function(e){
|
||||||
|
if (e.keyCode != 13) { return }
|
||||||
|
e.preventDefault();
|
||||||
|
var data = { "link": this.value,
|
||||||
|
"pieceId": "{{ piece.id }}" };
|
||||||
|
|
||||||
|
$.ajax( {
|
||||||
|
url: "{% url 'scoremanager.views.youtube_link' %}",
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function(result) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".youtubeDeleteButton").click( function() {
|
||||||
|
var id = $(this).data("id");
|
||||||
|
$.ajax( {
|
||||||
|
url: "{% url 'scoremanager.views.youtube_link' %}",
|
||||||
|
type: 'DELETE',
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
data: ' { "linkid" : ' + id + ' } ',
|
||||||
|
dataType: 'text',
|
||||||
|
success: function(result) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endaddtoblock %}
|
||||||
|
|
||||||
|
<div id="section1">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
|
||||||
|
<h2>{{ piece.title }}</h2>
|
||||||
|
<div class="span7">
|
||||||
|
{% if pictureScore %}
|
||||||
|
<a href="{{MEDIA_URL}}/{{pictureScore.file}}">
|
||||||
|
<img class="piece-pic img-responsive" src="{% url 'scoremanager.views.score' pictureScore.id %}"> </img>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h3>Info</h3>
|
||||||
|
<table class="table table-striped">
|
||||||
|
{% if piece.composer %}
|
||||||
|
<tr>
|
||||||
|
<td>Komponist:</td>
|
||||||
|
<td>{{piece.composer}} </td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if piece.booklocation %}
|
||||||
|
<tr>
|
||||||
|
<td>Buch: </td>
|
||||||
|
<td>{{piece.booklocation }} </td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% if piece.scores.all|length > 0 %}
|
||||||
|
<h3>Alle Noten</h3>
|
||||||
|
<table class="table table-striped">
|
||||||
|
{% for score in piece.scores.all %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{MEDIA_URL}}/{{score.file}}">
|
||||||
|
{{ score.score_type }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if score == activeScore %}
|
||||||
|
<span data-id="{{score.id}}" class="label label-success score-label">Meine Noten</span>
|
||||||
|
{% else %}
|
||||||
|
<a data-id="{{score.id}}" class="btn btn-mini score-label">Meine Noten</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if score.uploaded_by == request.user or perms.scoremanager.manage_scores %}
|
||||||
|
<a class="btn btn-mini btn-danger delete-button" data-id="{{score.id}}">Löschen</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if piece.recordings.all|length > 0 %}
|
||||||
|
<h3>Aufnahmen</h3>
|
||||||
|
<ul>
|
||||||
|
{% for recording in piece.recordings.all %}
|
||||||
|
<li>
|
||||||
|
<a href="{{MEDIA_URL}}/{{ recording.file }}">
|
||||||
|
{{ recording.artist }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="span4 sidebar offset1">
|
||||||
|
|
||||||
|
<h4>Upload</h4>
|
||||||
|
|
||||||
|
<div class="upload-area">
|
||||||
|
<div class="help-text">
|
||||||
|
Hier kann jeder Noten oder Aufnahmen hochladen. Der Dateiname wird als Interpret (für Aufnahmen) bzw. Stimme (für Noten)
|
||||||
|
verwendet. Zip Dateien, die MP3s und/oder PDFs enthalten funktionieren auch.
|
||||||
|
</div>
|
||||||
|
<form class="upload_form" method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.file.errors|length > 0 %}
|
||||||
|
<div class="error">Problem beim Upload. Erlaubte Dateien: mp3,pdf und zip</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ form.file }}
|
||||||
|
<input type="submit" class="hidden" value="Ok"></input>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='section2'>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
Youtube<br>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="upload">
|
||||||
|
<input id="youtubeLinkInput" type="text" placeholder="Hier Youtube Link reinkopieren"></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if piece.youtubeLinks.all|length > 0 %}
|
||||||
|
<div>
|
||||||
|
{% for youtubeLink in piece.youtubeLinks.all %}
|
||||||
|
<div class="youtubeWrapper">
|
||||||
|
{{ youtubeLink.embed_html }}
|
||||||
|
|
||||||
|
{% if youtubeLink.uploaded_by == request.user or perms.scoremanager.manage_scores %}
|
||||||
|
<div class="youtubeCaption">
|
||||||
|
<button class="btn btn-mini btn-danger youtubeDeleteButton" data-id="{{youtubeLink.id}}">
|
||||||
|
Löschen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -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<pk>\d+)', scoremanager.views.piece_view ),
|
||||||
|
url(r'^score_usermapping_save', scoremanager.views.score_user_mapping_save ),
|
||||||
|
url(r'^score/(?P<pk>\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 ),
|
||||||
|
)
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
<li><a href="/events"> Termine</a></li>
|
<li><a href="/events"> Termine</a></li>
|
||||||
<li><a href="/messages">Forum</a></li>
|
<li><a href="/messages">Forum</a></li>
|
||||||
<li><a href="/musicians">Adressbuch</a></li>
|
<li><a href="/musicians">Adressbuch</a></li>
|
||||||
|
<li><a href="/scores"> Noten</a></li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
@ -70,7 +71,7 @@
|
||||||
<div class="span12">
|
<div class="span12">
|
||||||
<div class="row copyright">
|
<div class="row copyright">
|
||||||
<div class="span12">
|
<div class="span12">
|
||||||
© 2013 Blechreiz, Martin Bauer
|
© 2014 Blechreiz, Martin Bauer
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue