From 7e80490b17e5000d7dc6c8feced9fba04dd15436 Mon Sep 17 00:00:00 2001 From: tr4ck3ur Date: Tue, 21 Jul 2015 00:35:09 +0200 Subject: [PATCH] Added auto-generated badges Fix StringIO import to cStringIO Added Blender Support from piernov Added Badge Button on ListParticipant --- jm2l/__init__.py | 3 + jm2l/badge.py | 561 ++++++++++++++++++++++++ jm2l/blenderthumbnailer.py | 198 +++++++++ jm2l/captcha.py | 2 +- jm2l/models.py | 7 + jm2l/static/fonts/PWTinselLetters.ttf | Bin 0 -> 111908 bytes jm2l/static/img/Blender_Thumb_Stamp.png | Bin 0 -> 2157 bytes jm2l/templates/Participant/list.mako | 5 +- jm2l/upload.py | 147 +++++-- setup.py | 4 +- 10 files changed, 893 insertions(+), 34 deletions(-) create mode 100644 jm2l/badge.py create mode 100644 jm2l/blenderthumbnailer.py create mode 100644 jm2l/static/fonts/PWTinselLetters.ttf create mode 100644 jm2l/static/img/Blender_Thumb_Stamp.png diff --git a/jm2l/__init__.py b/jm2l/__init__.py index 40e9f00..ce22a61 100644 --- a/jm2l/__init__.py +++ b/jm2l/__init__.py @@ -147,6 +147,9 @@ def main(global_config, **settings): ## Users config.add_route('pict_user', '/user_picture') config.add_route('show_user', '/user/{user_slug:([\w-]+)?}') + config.add_route('badge_user', '/user/{user_slug:([\w-]+)?}/badge') + config.add_route('badge_user1', '/user/{user_slug:([\w-]+)?}/badge1') + config.add_route('badge_user2', '/user/{user_slug:([\w-]+)?}/badge2') # HTML Routes - Logged #config.add_route('profil', 'MesJM2L') diff --git a/jm2l/badge.py b/jm2l/badge.py new file mode 100644 index 0000000..7f620e2 --- /dev/null +++ b/jm2l/badge.py @@ -0,0 +1,561 @@ +# -*- coding: utf8 -*- +from pyramid.httpexceptions import HTTPNotFound +from pyramid.response import Response +import cStringIO as StringIO +from pyramid.view import view_config +from .models import User + +from reportlab.pdfgen import canvas +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.lib.units import mm +import qrcode + +# Create PDF container +WIDTH = 85 * mm +HEIGHT = 60 * mm +ICONSIZE = 8 * mm + +def Ribbon35(DispUser, canvas): + canvas.saveState() + canvas.rotate(35) + offset_u=0 + if DispUser.Staff: + # Staff + canvas.setFillColorRGB(1,.2,.2) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(1,1,1) + canvas.setFont('Liberation', 20) + canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+5, "STAFF") + elif DispUser.is_Intervenant: + # Intervenant + canvas.setFillColorRGB(.3,.3,1) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(1,1,1) + canvas.setFont('Liberation', 15) + canvas.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant") + else: + # Visiteur + canvas.setFillColorRGB(.8,.8,.8) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(0,0,0) + canvas.setFont('Liberation', 12) + canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+7, "Visiteur") + canvas.restoreState() + return canvas + +def Ribbon45(DispUser, canvas): + canvas.saveState() + canvas.rotate(45) + offset_u=0 + if DispUser.Staff: + # Staff + canvas.setFillColorRGB(1,.2,.2) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(1,1,1) + canvas.setFont('Liberation', 20) + canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+5, "STAFF") + elif DispUser.is_Intervenant: + # Intervenant + canvas.setFillColorRGB(.3,.3,1) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(1,1,1) + canvas.setFont('Liberation', 15) + canvas.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant") + else: + # Visiteur + canvas.setFillColorRGB(.8,.8,.8) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(0,0,0) + canvas.setFont('Liberation', 12) + canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+7, "Visiteur") + canvas.restoreState() + return canvas + +def Ribbon90(DispUser, canvas): + canvas.saveState() + canvas.rotate(90) + offset_u=0 + if DispUser.Staff: + # Staff + canvas.setFillColorRGB(1,.2,.2) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(1,1,1) + canvas.setFont('Liberation', 20) + canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+5, "STAFF") + elif DispUser.is_Intervenant: + # Intervenant + canvas.setFillColorRGB(.3,.3,1) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(1,1,1) + canvas.setFont('Liberation', 15) + canvas.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant") + else: + # Visiteur + canvas.setFillColorRGB(.8,.8,.8) + canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + canvas.setFillColorRGB(0,0,0) + canvas.setFont('Liberation', 12) + canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+7, "Visiteur") + canvas.restoreState() + return canvas + +def JM2L_Logo(canvas, Position="Up"): + # Import font + ttfFile_Logo = "jm2l/static/fonts/PWTinselLetters.ttf" + pdfmetrics.registerFont(TTFont("Logo", ttfFile_Logo)) + + if Position=="Up": + logoobject = canvas.beginText() + logoobject.setFont('Logo', 52) + logoobject.setFillColorRGB(.83,0,.33) + logoobject.setTextOrigin(55, HEIGHT-50) + logoobject.textLines("JM2L") + canvas.drawText(logoobject) + + yearobject = canvas.beginText() + yearobject.setFont("Helvetica-Bold", 15) + yearobject.setFillColorRGB(1,1,1) + yearobject.setTextOrigin(67, HEIGHT-20) + yearobject.setWordSpace(21) + yearobject.textLines("2 0 1 5") + canvas.drawText(yearobject) + elif Position=="Down": + logoobject = canvas.beginText() + logoobject.setFont('Logo', 32) + logoobject.setFillColorRGB(.83,0,.33) + logoobject.setTextOrigin(2, 17) + logoobject.textLines("JM2L") + canvas.drawText(logoobject) + + yearobject = canvas.beginText() + yearobject.setFont("Helvetica-Bold", 10) + yearobject.setFillColorRGB(1,1,1) + #yearobject.setLineWidth(.1) + #yearobject.setStrokeColorRGB(.5,.5,.5) + yearobject.setTextRenderMode(0) + #yearobject.setStrokeOverprint(.2) + yearobject.setTextOrigin(9 , 35) + yearobject.setWordSpace(13) + yearobject.textLines("2 0 1 5") + canvas.drawText(yearobject) + +def Tiers_Logo(canvas, DispUser, LWidth=4, StartPos=None): + Border = 1 + if StartPos is None: + StartPos = ( WIDTH -70, 0 ) + StartX, StartY = StartPos + num = 0 + canvas.setStrokeColorRGB(0.5,0.5,0.5) + Logos = filter(lambda x:x.ThumbLinks, DispUser.tiers) + for tiers in Logos: + FileName = tiers.ThumbLinks.pop().split("/")[-1] + ImagePath = "jm2l/upload/images/tiers/%s/%s" % (tiers.slug, FileName) + # Should We compute a better positionning for logos ? + #PosX = StartX + (( (2.0*num+1) / 2*len(Logos) )*(ICONSIZE+Border) - ((ICONSIZE+Border)/2)) + PosX = StartX + (num % LWidth) * (ICONSIZE+Border) + if len(Logos)<=LWidth: + # Middle of line + PosY = StartY + 0.5 * (ICONSIZE+Border) + else: + # in two or more lines + PosY = StartY + (num / LWidth) * (ICONSIZE+Border) + canvas.setLineWidth(.1) + canvas.drawImage(ImagePath, + PosX, PosY, ICONSIZE, ICONSIZE,\ + preserveAspectRatio=True, + anchor='c', + mask=[0,3,0,3,0,3] + ) + canvas.roundRect(PosX, PosY, ICONSIZE, ICONSIZE, radius=2, stroke=True) + num+=1 + +def QRCode(DispUser): + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=2, + ) + # Data of QR code + qr.add_data('http://jm2l.linux-azur.org/user/%s' % DispUser.slug) + qr.make(fit=True) + + return qr.make_image() + +@view_config(route_name='badge_user') +def badge_user(request): + user_slug = request.matchdict.get('user_slug', None) + if user_slug is None or len(user_slug)==0: + raise HTTPNotFound(u"Cet utilisateur n'a pas été reconnu") + # Query database + DispUser = User.by_slug(user_slug) + if DispUser is None: + raise HTTPNotFound() + + # Ok let's generate a PDF Badge + + # Register LiberationMono font + ttfFile = "jm2l/static/fonts/LiberationMono-Regular.ttf" + pdfmetrics.registerFont(TTFont("Liberation", ttfFile)) + + pdf = StringIO.StringIO() + + c = canvas.Canvas( pdf, pagesize=(WIDTH, HEIGHT) ) + c.translate(mm, mm) + + # Feed some metadata + c.setCreator("linux-azur.org") + c.setTitle("Badge") + + c.saveState() + # Logo on Top + JM2L_Logo(c, "Down") + + if DispUser.Staff: + # Staff + c.setFillColorRGB(.83,0,.33) + c.rect(-3, HEIGHT-30, WIDTH, HEIGHT, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 30) + c.drawCentredString(WIDTH/2, HEIGHT-26, "STAFF") + elif DispUser.is_Intervenant: + # Intervenant + c.setFillColorRGB(.3,.3,1) + c.rect(-3, HEIGHT-30, WIDTH, HEIGHT, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 30) + c.drawCentredString(WIDTH/2, HEIGHT-26, "Intervenant") + else: + # Visiteur + c.setFillColorRGB(.8,.8,.8) + c.rect(-3, HEIGHT-30, WIDTH, HEIGHT, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 30) + c.drawCentredString(WIDTH/2, HEIGHT-26, "Visiteur") + + c.restoreState() + + c.setFont('Liberation', 18) + c.setStrokeColorRGB(0,0,0) + c.setFillColorRGB(0,0,0) + # Feed Name and SurName + if len(DispUser.prenom) + len(DispUser.nom)>18: + if DispUser.pseudo: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 4 * mm , "%s" % DispUser.prenom ) + c.setFont('Courier', 17) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 2 * mm , "%s" % DispUser.nom ) + else: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 2 * mm , "%s" % DispUser.prenom ) + c.setFont('Courier', 17) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 6 * mm , "%s" % DispUser.nom ) + else: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 0 * mm , "%s %s" % (DispUser.prenom, DispUser.nom) ) + + if DispUser.pseudo: + c.setFont("Helvetica-Oblique", 14) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 8 * mm , "%s" % DispUser.pseudo ) + + # Put QR code to user profile + c.drawInlineImage(QRCode(DispUser), \ + WIDTH - 20 * mm - 7, 0, \ + 20 * mm, 20 * mm, \ + preserveAspectRatio=True, \ + anchor='s') + + Tiers_Logo(c, DispUser, 4, ( 30 * mm, 2 )) + + c.showPage() + c.save() + pdf.seek(0) + return Response(app_iter=pdf, content_type = 'application/pdf' ) + +@view_config(route_name='badge_user1') +def badge_user1(request): + user_slug = request.matchdict.get('user_slug', None) + if user_slug is None or len(user_slug)==0: + raise HTTPNotFound(u"Cet utilisateur n'a pas été reconnu") + # Query database + DispUser = User.by_slug(user_slug) + if DispUser is None: + raise HTTPNotFound() + + # Ok let's generate a PDF Badge + ttfFile = "jm2l/static/fonts/LiberationMono-Regular.ttf" + ttfFile_Logo = "jm2l/static/fonts/PWTinselLetters.ttf" + pdfmetrics.registerFont(TTFont("Liberation", ttfFile)) + pdfmetrics.registerFont(TTFont("Logo", ttfFile_Logo)) + pdf = StringIO.StringIO() + + c = canvas.Canvas( pdf, pagesize=(WIDTH, HEIGHT) ) + c.translate(mm, mm) + + # Feed some metadata + c.setCreator("linux-azur.org") + c.setTitle("Badge") + + c.saveState() + + logoobject = c.beginText() + logoobject.setFont('Logo', 52) + logoobject.setFillColorRGB(.83,0,.33) + logoobject.setTextOrigin(55, HEIGHT-50) + logoobject.textLines("JM2L") + c.drawText(logoobject) + + + yearobject = c.beginText() + yearobject.setFont("Helvetica-Bold", 15) + yearobject.setFillColorRGB(1,1,1) + yearobject.setTextOrigin(67, HEIGHT-20) + yearobject.setWordSpace(21) + yearobject.textLines("2 0 1 5") + c.drawText(yearobject) + + num = 0 + for tiers in DispUser.tiers: + if tiers.ThumbLinks: + FileName = tiers.ThumbLinks.pop().split("/")[-1] + num+=1 + ImagePath = "jm2l/upload/images/tiers/%s/%s" % (tiers.slug, FileName) + if num<6: + c.drawImage(ImagePath, + WIDTH -5 - num * (ICONSIZE+5), 5, ICONSIZE, ICONSIZE,\ + preserveAspectRatio=True, + anchor='c', + mask=[0,3,0,3,0,3]) + else: + c.drawImage(ImagePath, + WIDTH -5 - (num-5) * (ICONSIZE+5), 5 + ICONSIZE, ICONSIZE, ICONSIZE,\ + preserveAspectRatio=True, + anchor='c', + mask=[0,3,0,3,0,3]) + + + + + if 1: + Ribbon90(DispUser, c) + + if 0: + c.rotate(90) + offset_u=111 + if DispUser.Staff: + # Staff + c.setFillColorRGB(1,.2,.2) + c.rect(-5, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 30) + c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+5, "STAFF") + elif DispUser.is_Intervenant: + # Intervenant + c.setFillColorRGB(.3,.3,1) + c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 15) + c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant") + else: + # Visiteur + c.setFillColorRGB(.8,.8,.8) + c.rect(-5, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(0,0,0) + c.setFont('Liberation', 19) + c.drawCentredString(WIDTH/2, HEIGHT/2-offset_u+7, "Visiteur") + c.restoreState() + + c.setFont('Courier', 18) + c.setStrokeColorRGB(0,0,0) + c.setFillColorRGB(0,0,0) + # Feed Name and SurName + if len(DispUser.prenom) + len(DispUser.nom)>18: + if DispUser.pseudo: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 4 * mm , "%s" % DispUser.prenom ) + c.setFont('Courier', 17) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 2 * mm , "%s" % DispUser.nom ) + else: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 2 * mm , "%s" % DispUser.prenom ) + c.setFont('Courier', 17) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 6 * mm , "%s" % DispUser.nom ) + else: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 0 * mm , "%s %s" % (DispUser.prenom, DispUser.nom) ) + #c.drawCentredString(WIDTH/2, HEIGHT - 22 * mm, ) + if DispUser.pseudo: + #c.setFont('Liberation', 14) + c.setFont("Helvetica-Oblique", 14) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 8 * mm , "\"%s\"" % DispUser.pseudo ) + #c.restoreState() + + # Put QR code to user profile + c.drawInlineImage(QRCode(DispUser), \ + WIDTH - 20 * mm - 7, 0, \ + 20 * mm, 20 * mm, \ + preserveAspectRatio=True, \ + anchor='s') + + c.showPage() + c.save() + pdf.seek(0) + return Response(app_iter=pdf, content_type = 'application/pdf' ) + + +@view_config(route_name='badge_user2') +def badge_user2(request): + user_slug = request.matchdict.get('user_slug', None) + if user_slug is None or len(user_slug)==0: + raise HTTPNotFound(u"Cet utilisateur n'a pas été reconnu") + # Query database + DispUser = User.by_slug(user_slug) + if DispUser is None: + raise HTTPNotFound() + + # Ok let's generate a PDF Badge + ttfFile = "jm2l/static/fonts/LiberationMono-Regular.ttf" + ttfFile_Logo = "jm2l/static/fonts/PWTinselLetters.ttf" + pdfmetrics.registerFont(TTFont("Liberation", ttfFile)) + pdfmetrics.registerFont(TTFont("Logo", ttfFile_Logo)) + pdf = StringIO.StringIO() + + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=2, + ) + # Data of QR code + qr.add_data('http://jm2l.linux-azur.org/user/%s' % DispUser.slug) + qr.make(fit=True) + + img = qr.make_image() + # Create PDF container + WIDTH = 85 * mm + HEIGHT = 60 * mm + ICONSIZE = 10 * mm + c = canvas.Canvas( pdf, pagesize=(WIDTH, HEIGHT) ) + c.translate(mm, mm) + + # Feed some metadata + c.setCreator("linux-azur.org") + c.setTitle("Badge") + + c.saveState() + + logoobject = c.beginText() + logoobject.setFont('Logo', 52) + logoobject.setFillColorRGB(.83,0,.33) + logoobject.setTextOrigin(55, HEIGHT-50) + logoobject.textLines("JM2L") + c.drawText(logoobject) + + + yearobject = c.beginText() + yearobject.setFont("Helvetica-Bold", 15) + yearobject.setFillColorRGB(1,1,1) + yearobject.setTextOrigin(67, HEIGHT-20) + yearobject.setWordSpace(21) + yearobject.textLines("2 0 1 5") + c.drawText(yearobject) + + num = 0 + for tiers in DispUser.tiers: + if tiers.ThumbLinks: + FileName = tiers.ThumbLinks.pop().split("/")[-1] + num+=1 + ImagePath = "jm2l/upload/images/tiers/%s/%s" % (tiers.slug, FileName) + if num<6: + c.drawImage(ImagePath, + WIDTH -5 - num * (ICONSIZE+5), 5, ICONSIZE, ICONSIZE,\ + preserveAspectRatio=True, + anchor='c', + mask=[0,3,0,3,0,3]) + else: + c.drawImage(ImagePath, + WIDTH -5 - (num-5) * (ICONSIZE+5), 5 + ICONSIZE, ICONSIZE, ICONSIZE,\ + preserveAspectRatio=True, + anchor='c', + mask=[0,3,0,3,0,3]) + + + + + if 1: + c.rotate(35) + offset_u=0 + if DispUser.Staff: + # Staff + c.setFillColorRGB(1,.2,.2) + c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 20) + c.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+5, "STAFF") + elif DispUser.is_Intervenant: + # Intervenant + c.setFillColorRGB(.3,.3,1) + c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 15) + c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant") + else: + # Visiteur + c.setFillColorRGB(.8,.8,.8) + c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(0,0,0) + c.setFont('Liberation', 12) + c.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+7, "Visiteur") + + if 0: + c.rotate(90) + offset_u=111 + if DispUser.Staff: + # Staff + c.setFillColorRGB(1,.2,.2) + c.rect(-5, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 30) + c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+5, "STAFF") + elif DispUser.is_Intervenant: + # Intervenant + c.setFillColorRGB(.3,.3,1) + c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(1,1,1) + c.setFont('Liberation', 15) + c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant") + else: + # Visiteur + c.setFillColorRGB(.8,.8,.8) + c.rect(-5, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1) + c.setFillColorRGB(0,0,0) + c.setFont('Liberation', 19) + c.drawCentredString(WIDTH/2, HEIGHT/2-offset_u+7, "Visiteur") + + + c.restoreState() + + c.setFont('Courier', 18) + c.setStrokeColorRGB(0,0,0) + c.setFillColorRGB(0,0,0) + # Feed Name and SurName + if len(DispUser.prenom) + len(DispUser.nom)>18: + if DispUser.pseudo: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 4 * mm , "%s" % DispUser.prenom ) + c.setFont('Courier', 17) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 2 * mm , "%s" % DispUser.nom ) + else: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 2 * mm , "%s" % DispUser.prenom ) + c.setFont('Courier', 17) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 6 * mm , "%s" % DispUser.nom ) + else: + c.drawCentredString(WIDTH/2, HEIGHT/2 + 0 * mm , "%s %s" % (DispUser.prenom, DispUser.nom) ) + #c.drawCentredString(WIDTH/2, HEIGHT - 22 * mm, ) + if DispUser.pseudo: + #c.setFont('Liberation', 14) + c.setFont("Helvetica-Oblique", 14) + c.drawCentredString(WIDTH/2, HEIGHT/2 - 8 * mm , "\"%s\"" % DispUser.pseudo ) + #c.restoreState() + + # Put QR code to user profile + c.drawInlineImage(img, WIDTH - 20 * mm - 7, 0, 20 * mm, 20 * mm, preserveAspectRatio=True, anchor='s') + + c.showPage() + c.save() + pdf.seek(0) + return Response(app_iter=pdf, content_type = 'application/pdf' ) diff --git a/jm2l/blenderthumbnailer.py b/jm2l/blenderthumbnailer.py new file mode 100644 index 0000000..32d5567 --- /dev/null +++ b/jm2l/blenderthumbnailer.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# + +""" +Thumbnailer runs with python 2.7 and 3.x. +To run automatically with a file manager such as Nautilus, save this file +in a directory that is listed in PATH environment variable, and create +blender.thumbnailer file in ${HOME}/.local/share/thumbnailers/ directory +with the following contents: + +[Thumbnailer Entry] +TryExec=blender-thumbnailer.py +Exec=blender-thumbnailer.py %u %o +MimeType=application/x-blender; +""" + +import struct + + +def open_wrapper_get(): + """ wrap OS spesific read functionality here, fallback to 'open()' + """ + + class GFileWrapper: + __slots__ = ("mode", "g_file") + + def __init__(self, url, mode='r'): + self.mode = mode # used in gzip module + self.g_file = Gio.File.parse_name(url).read(None) + + def read(self, size): + return self.g_file.read_bytes(size, None).get_data() + + def seek(self, offset, whence=0): + self.g_file.seek(offset, [1, 0, 2][whence], None) + return self.g_file.tell() + + def tell(self): + return self.g_file.tell() + + def close(self): + self.g_file.close(None) + + def open_local_url(url, mode='r'): + o = urlparse(url) + if o.scheme == '': + path = o.path + elif o.scheme == 'file': + path = unquote(o.path) + else: + raise(IOError('URL scheme "%s" needs gi.repository.Gio module' % o.scheme)) + return open(path, mode) + + try: + from gi.repository import Gio + return GFileWrapper + except ImportError: + try: + # Python 3 + from urllib.parse import urlparse, unquote + except ImportError: + # Python 2 + from urlparse import urlparse + from urllib import unquote + return open_local_url + + +def blend_extract_thumb(path): + import os + open_wrapper = open_wrapper_get() + + # def MAKE_ID(tag): ord(tag[0])<<24 | ord(tag[1])<<16 | ord(tag[2])<<8 | ord(tag[3]) + REND = 1145980242 # MAKE_ID(b'REND') + TEST = 1414743380 # MAKE_ID(b'TEST') + + blendfile = open_wrapper(path, 'rb') + + head = blendfile.read(12) + + if head[0:2] == b'\x1f\x8b': # gzip magic + import gzip + blendfile.close() + blendfile = gzip.GzipFile('', 'rb', 0, open_wrapper(path, 'rb')) + head = blendfile.read(12) + + if not head.startswith(b'BLENDER'): + blendfile.close() + return None, 0, 0 + + is_64_bit = (head[7] == b'-'[0]) + + # true for PPC, false for X86 + is_big_endian = (head[8] == b'V'[0]) + + # blender pre 2.5 had no thumbs + if head[9:11] <= b'24': + blendfile.close() + return None, 0, 0 + + sizeof_bhead = 24 if is_64_bit else 20 + int_endian_pair = '>ii' if is_big_endian else ' ") + else: + file_in = sys.argv[-2] + + buf, width, height = blend_extract_thumb(file_in) + + if buf: + file_out = sys.argv[-1] + + f = open(file_out, "wb") + f.write(write_png(buf, width, height)) + f.close() + + +if __name__ == '__main__': + main() diff --git a/jm2l/captcha.py b/jm2l/captcha.py index 65ab5db..f7dd768 100644 --- a/jm2l/captcha.py +++ b/jm2l/captcha.py @@ -3,7 +3,7 @@ import random from PIL import Image, ImageDraw, ImageFont, ImageFilter -import StringIO +import cStringIO as StringIO import math from pyramid.view import view_config from .words import TabMots diff --git a/jm2l/models.py b/jm2l/models.py index 52402ac..9e3db09 100644 --- a/jm2l/models.py +++ b/jm2l/models.py @@ -169,6 +169,13 @@ class User(Base): return u return None + @property + def is_Intervenant(self, year=2015): + """ This property will return if User do an event on specified year """ + return DBSession.query(Event).join(User_Event) \ + .filter(User_Event.user_uid==self.uid) \ + .filter(Event.for_year==year).count() + @property def my_hash(self): m = hashlib.sha1() diff --git a/jm2l/static/fonts/PWTinselLetters.ttf b/jm2l/static/fonts/PWTinselLetters.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7dfd99e16ba38202de1a7343e6447ce858078803 GIT binary patch literal 111908 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 TAzW$@mjz(094u=s{8|465#Y)Z literal 0 HcmV?d00001 diff --git a/jm2l/templates/Participant/list.mako b/jm2l/templates/Participant/list.mako index 16c99c8..46eebb5 100644 --- a/jm2l/templates/Participant/list.mako +++ b/jm2l/templates/Participant/list.mako @@ -162,7 +162,10 @@ now = datetime.datetime.now() - % endif + % endif + + + diff --git a/jm2l/upload.py b/jm2l/upload.py index 9c4c7e9..d68cc4e 100644 --- a/jm2l/upload.py +++ b/jm2l/upload.py @@ -2,8 +2,7 @@ from pyramid.view import view_config, view_defaults from pyramid.response import Response from pyramid.exceptions import NotFound -from pyramid.httpexceptions import HTTPNotFound -from pyramid.request import Request +from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest from PIL import Image import re, os, shutil from os import path @@ -13,6 +12,7 @@ import subprocess import cStringIO as StringIO # Database access imports from .models import User, Place, Tiers, Event, SallePhy +from .blenderthumbnailer import blend_extract_thumb, write_png CurrentYear = 2015 MIN_FILE_SIZE = 1 # bytes @@ -27,7 +27,8 @@ ACCEPTED_MIMES = ['application/pdf', 'application/vnd.oasis.opendocument.presentation-template', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.spreadsheet-template', - 'image/svg+xml' + 'image/svg+xml', + 'application/x-blender' ] @@ -136,6 +137,38 @@ class MediaPath(): if mime=='application/pdf': return "/img/PDF.png" + def check_blend_file(self, fileobj): + head = fileobj.read(12) + fileobj.seek(0) + + if head[:2] == b'\x1f\x8b': # gzip magic + import zlib + head = zlib.decompress(fileobj.read(), 31)[:12] + fileobj.seek(0) + + if head.startswith(b'BLENDER'): + return True + + def get_mimetype_from_file(self, fileobj): + mimetype = magic.from_buffer(fileobj.read(1024), mime=True) + fileobj.seek(0) + + # Check if the binary file is a blender file + if ( mimetype == "application/octet-stream" or mimetype == "application/x-gzip" ) and self.check_blend_file(fileobj): + return "application/x-blender", True + else: + return mimetype, False + + + def get_mimetype(self, name): + """ This function return the mime-type based on .type file """ + try: + with open(self.mediapath(name) + '.type', 'r', 16) as f: + mime = f.read() + return mime + except IOError: + return None + @view_defaults(route_name='media_upload') class MediaUpload(MediaPath): @@ -152,19 +185,31 @@ class MediaUpload(MediaPath): return self.get_mediapath(self.media_table, self.linked_id, name) def validate(self, result, filecontent): - # let's say we don't trust the uploader - RealMime = magic.from_buffer( filecontent.read(1024), mime=True) + """ Do some basic check to the uploaded file before to accept it """ + # Try to determine mime type from content uploaded + found_mime = magic.from_buffer(filecontent.read(1024), mime=True) filecontent.seek(0) - if RealMime!=result['type']: - result['error'] = 'l\'extension du fichier ne correspond pas à son contenu - ' - result['error'] += "( %s vs %s )" % (RealMime, result['type']) + + # Do a special statement for specific detected mime type + if found_mime in ["application/octet-stream", "application/x-gzip"]: + # Lets see if it's a bender file + if self.check_blend_file(filecontent): + found_mime = "application/x-blender" + # MonKey Patch of content type + result['type'] = found_mime + + # Reject mime type that don't match + if found_mime!=result['type']: + result['error'] = 'L\'extension du fichier ne correspond pas à son contenu - ' + result['error'] += "( %s vs %s )" % (found_mime, result['type']) return False + # Accept images and mime types listed - if not RealMime in ACCEPTED_MIMES: - if not (IMAGE_TYPES.match(RealMime)): + if not found_mime in ACCEPTED_MIMES: + if not (IMAGE_TYPES.match(found_mime)): result['error'] = 'Ce type fichier n\'est malheureusement pas supporté. ' - result['error'] += 'Les fichiers acceptées sont les images et pdf.' return False + if result['size'] < MIN_FILE_SIZE: result['error'] = 'le fichier est trop petit' elif result['size'] > MAX_FILE_SIZE: @@ -173,12 +218,13 @@ class MediaUpload(MediaPath): # file['error'] = u'les type de fichiers acceptés sont png, jpg et gif' else: return True + return False - def get_file_size(self, file): - file.seek(0, 2) # Seek to the end of the file - size = file.tell() # Get the position of EOF - file.seek(0) # Reset the file position to the beginning + def get_file_size(self, fileobj): + fileobj.seek(0, 2) # Seek to the end of the file + size = fileobj.tell() # Get the position of EOF + fileobj.seek(0) # Reset the file position to the beginning return size def thumbnailurl(self,name): @@ -254,9 +300,6 @@ class MediaUpload(MediaPath): def docthumbnail(self, filename): TargetFileName = self.thumbnailpath(filename) - # unoconv need a libre office server to be up - Command = ["unoconv", "-f", "pdf", "-e", "PageRange=1", "--output=%s" % TargetFileName, \ - "%s[0]" % self.mediapath(filename) ] # let's take the thumbnail generated inside the document Command = ["unzip", "-p", self.mediapath(filename), "Thumbnails/thumbnail.png"] ThumbBytes = subprocess.check_output(Command) @@ -284,6 +327,40 @@ class MediaUpload(MediaPath): timage.convert('RGB').save( TargetFileName+".jpg", 'JPEG') return self.thumbnailurl( os.path.basename(TargetFileName+".jpg") ) + def blendthumbnail(self, filename): + blendfile = self.mediapath(filename) + # Extract Thumb + if 0: + head = fileobj.read(12) + fileobj.seek(0) + + if head[:2] == b'\x1f\x8b': # gzip magic + import zlib + head = zlib.decompress(fileobj.read(), 31)[:12] + fileobj.seek(0) + + buf, width, height = blend_extract_thumb(blendfile) + if buf: + png = write_png(buf, width, height) + TargetFileName = self.thumbnailpath(filename) + image = Image.open(StringIO.StringIO(png)) + blender_indicator = Image.open( "jm2l/static/img/Blender_Thumb_Stamp.png" ) + image.thumbnail((THUMBNAIL_SIZE, THUMBNAIL_SIZE), Image.ANTIALIAS) + timage = Image.new('RGBA', (THUMBNAIL_SIZE, THUMBNAIL_SIZE), (255, 255, 255, 0)) + # Add thumbnail + timage.paste( + image, + ((THUMBNAIL_SIZE - image.size[0]) / 2, (THUMBNAIL_SIZE - image.size[1]) / 2)) + # Stamp with Blender file type + timage.paste( + blender_indicator, + (timage.size[0]-30, timage.size[1]-30), + blender_indicator, + ) + timage.save( TargetFileName+".png") + return self.thumbnailurl( os.path.basename(TargetFileName+".png") ) + return self.ExtMimeIcon('application/x-blender') + def fileinfo(self,name): filename = self.mediapath(name) f, ext = os.path.splitext(name) @@ -295,13 +372,17 @@ class MediaUpload(MediaPath): name=name, media_table=self.media_table, uid=self.linked_id) - mime = mimetypes.types_map.get(ext) - if (IMAGE_TYPES.match(mime)): + + mime = self.get_mimetype(name) + if IMAGE_TYPES.match(mime): info['thumbnailUrl'] = self.thumbnailurl(name) elif mime in ACCEPTED_MIMES: thumb = self.thumbnailpath("%s%s" % (f, ext)) - if os.path.exists( thumb +'.jpg' ): - info['thumbnailUrl'] = self.thumbnailurl(name)+'.jpg' + thumbext = ".jpg" + if mime == "application/x-blender": + thumbext = ".png" + if os.path.exists( thumb + thumbext ): + info['thumbnailUrl'] = self.thumbnailurl(name)+thumbext else: info['thumbnailUrl'] = self.ExtMimeIcon(mime) else: @@ -343,7 +424,6 @@ class MediaUpload(MediaPath): @view_config(request_method='DELETE', xhr=True, accept="application/json", renderer='json') def delete(self): - import json filename = self.request.matchdict.get('name') try: os.remove(self.mediapath(filename) + '.type') @@ -381,11 +461,17 @@ class MediaUpload(MediaPath): result['name'] = os.path.basename(fieldStorage.filename) result['type'] = fieldStorage.type result['size'] = self.get_file_size(fieldStorage.file) + if self.validate(result, fieldStorage.file): - with open( self.mediapath(result['name'] + '.type'), 'w') as f: + # Keep mime-type in .type file + with open( self.mediapath( result['name'] ) + '.type', 'w') as f: f.write(result['type']) - with open( self.mediapath(result['name']), 'wb') as f: + + # Store uploaded file + fieldStorage.file.seek(0) + with open( self.mediapath(result['name'] ), 'wb') as f: shutil.copyfileobj( fieldStorage.file , f) + if re.match(IMAGE_TYPES, result['type']): result['thumbnailUrl'] = self.createthumbnail(result['name']) elif result['type']=='application/pdf': @@ -394,8 +480,11 @@ class MediaUpload(MediaPath): result['thumbnailUrl'] = self.svgthumbnail(result['name']) elif result['type'].startswith('application/vnd'): result['thumbnailUrl'] = self.docthumbnail(result['name']) + elif result['type']=='application/x-blender': + result['thumbnailUrl'] = self.blendthumbnail(result['name']) else: result['thumbnailUrl'] = self.ExtMimeIcon(result['type']) + result['deleteType'] = DELETEMETHOD result['deleteUrl'] = self.request.route_url('media_upload', sep='', @@ -425,12 +514,8 @@ class MediaView(MediaPath): @view_config(request_method='GET', http_cache = (EXPIRATION_TIME, {'public':True})) def get(self): name = self.request.matchdict.get('name') - try: - with open( self.mediapath( os.path.basename(name) ) + '.type', 'r', 16) as f: - test = f.read() - self.request.response.content_type = test - except IOError: - pass + self.request.response.content_type = self.get_mimetype(name) + try: self.request.response.body_file = open( self.mediapath(name), 'rb', 10000) except IOError: diff --git a/setup.py b/setup.py index 8bd9095..5d0df3d 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,9 @@ requires = [ 'pyramid_exclog', 'repoze.sendmail==4.1', 'pyramid_mailer', - 'apscheduler' + 'apscheduler', + 'qrcode', + 'reportlab' ] setup(name='JM2L',