Browse Source

Added auto-generated badges

Fix StringIO import to cStringIO
Added Blender Support from piernov
Added Badge Button on ListParticipant
master
tr4ck3ur des JM2L 9 years ago
parent
commit
7e80490b17
10 changed files with 893 additions and 34 deletions
  1. +3
    -0
      jm2l/__init__.py
  2. +561
    -0
      jm2l/badge.py
  3. +198
    -0
      jm2l/blenderthumbnailer.py
  4. +1
    -1
      jm2l/captcha.py
  5. +7
    -0
      jm2l/models.py
  6. BIN
      jm2l/static/fonts/PWTinselLetters.ttf
  7. BIN
      jm2l/static/img/Blender_Thumb_Stamp.png
  8. +4
    -1
      jm2l/templates/Participant/list.mako
  9. +116
    -31
      jm2l/upload.py
  10. +3
    -1
      setup.py

+ 3
- 0
jm2l/__init__.py View File

@@ -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')


+ 561
- 0
jm2l/badge.py View File

@@ -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' )

+ 198
- 0
jm2l/blenderthumbnailer.py View File

@@ -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 #####

# <pep8 compliant>

"""
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 '<ii'

while True:
bhead = blendfile.read(sizeof_bhead)

if len(bhead) < sizeof_bhead:
return None, 0, 0

code, length = struct.unpack(int_endian_pair, bhead[0:8]) # 8 == sizeof(int) * 2

if code == REND:
blendfile.seek(length, os.SEEK_CUR)
else:
break

if code != TEST:
blendfile.close()
return None, 0, 0

try:
x, y = struct.unpack(int_endian_pair, blendfile.read(8)) # 8 == sizeof(int) * 2
except struct.error:
blendfile.close()
return None, 0, 0

length -= 8 # sizeof(int) * 2

if length != x * y * 4:
blendfile.close()
return None, 0, 0

image_buffer = blendfile.read(length)

if len(image_buffer) != length:
blendfile.close()
return None, 0, 0

blendfile.close()
return image_buffer, x, y


def write_png(buf, width, height):
import zlib

# reverse the vertical line order and add null bytes at the start
width_byte_4 = width * 4
raw_data = b"".join(b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4))

def png_pack(png_tag, data):
chunk_head = png_tag + data
return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))

return b"".join([
b'\x89PNG\r\n\x1a\n',
png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
png_pack(b'IDAT', zlib.compress(raw_data, 9)),
png_pack(b'IEND', b'')])


def main():
import sys

if len(sys.argv) < 3:
print("Expected 2 arguments <input.blend> <output.png>")
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()

+ 1
- 1
jm2l/captcha.py View File

@@ -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


+ 7
- 0
jm2l/models.py View File

@@ -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()


BIN
jm2l/static/fonts/PWTinselLetters.ttf View File


BIN
jm2l/static/img/Blender_Thumb_Stamp.png View File

Before After
Width: 27  |  Height: 27  |  Size: 2.1 KiB

+ 4
- 1
jm2l/templates/Participant/list.mako View File

@@ -162,7 +162,10 @@ now = datetime.datetime.now()
<a href="javascript:alert('${u.nom}, ${u.prenom}\n${u.phone}');">
<i class="icon-headphones"></i>
</a>
% endif
% endif
<a href="/user/${u.slug}/badge">
<i class="icon-qrcode"></i>
</a>
</span>
</td>
<td style="text-align:center;">


+ 116
- 31
jm2l/upload.py View File

@@ -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:


+ 3
- 1
setup.py View File

@@ -30,7 +30,9 @@ requires = [
'pyramid_exclog',
'repoze.sendmail==4.1',
'pyramid_mailer',
'apscheduler'
'apscheduler',
'qrcode',
'reportlab'
]

setup(name='JM2L',


Loading…
Cancel
Save