Browse Source

Added auto-generated badges
Fix StringIO import to cStringIO
Added Blender Support from piernov
Added Badge Button on ListParticipant

tr4ck3ur 2 years ago
parent
commit
7e80490b17

+ 3 - 0
jm2l/__init__.py

@@ -147,6 +147,9 @@ def main(global_config, **settings):
147 147
     ## Users
148 148
     config.add_route('pict_user', '/user_picture')
149 149
     config.add_route('show_user', '/user/{user_slug:([\w-]+)?}')
150
+    config.add_route('badge_user', '/user/{user_slug:([\w-]+)?}/badge')
151
+    config.add_route('badge_user1', '/user/{user_slug:([\w-]+)?}/badge1')
152
+    config.add_route('badge_user2', '/user/{user_slug:([\w-]+)?}/badge2')
150 153
     
151 154
     # HTML Routes - Logged
152 155
     #config.add_route('profil', 'MesJM2L')    

+ 561 - 0
jm2l/badge.py

@@ -0,0 +1,561 @@
1
+# -*- coding: utf8 -*-
2
+from pyramid.httpexceptions import HTTPNotFound
3
+from pyramid.response import Response
4
+import cStringIO as StringIO
5
+from pyramid.view import view_config
6
+from .models import User
7
+
8
+from reportlab.pdfgen import canvas
9
+from reportlab.pdfbase import pdfmetrics  
10
+from reportlab.pdfbase.ttfonts import TTFont 
11
+from reportlab.lib.units import mm 
12
+import qrcode
13
+
14
+# Create PDF container
15
+WIDTH = 85 * mm
16
+HEIGHT = 60 * mm
17
+ICONSIZE = 8 * mm  
18
+
19
+def Ribbon35(DispUser, canvas):
20
+    canvas.saveState()
21
+    canvas.rotate(35)
22
+    offset_u=0        
23
+    if DispUser.Staff:
24
+        # Staff
25
+        canvas.setFillColorRGB(1,.2,.2)        
26
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
27
+        canvas.setFillColorRGB(1,1,1)
28
+        canvas.setFont('Liberation', 20)
29
+        canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+5, "STAFF")
30
+    elif DispUser.is_Intervenant: 
31
+        # Intervenant
32
+        canvas.setFillColorRGB(.3,.3,1)        
33
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
34
+        canvas.setFillColorRGB(1,1,1)
35
+        canvas.setFont('Liberation', 15)
36
+        canvas.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant")        
37
+    else:
38
+        # Visiteur
39
+        canvas.setFillColorRGB(.8,.8,.8)        
40
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
41
+        canvas.setFillColorRGB(0,0,0)
42
+        canvas.setFont('Liberation', 12)
43
+        canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+7, "Visiteur")
44
+    canvas.restoreState()        
45
+    return canvas
46
+
47
+def Ribbon45(DispUser, canvas):
48
+    canvas.saveState()
49
+    canvas.rotate(45)
50
+    offset_u=0        
51
+    if DispUser.Staff:
52
+        # Staff
53
+        canvas.setFillColorRGB(1,.2,.2)        
54
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
55
+        canvas.setFillColorRGB(1,1,1)
56
+        canvas.setFont('Liberation', 20)
57
+        canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+5, "STAFF")
58
+    elif DispUser.is_Intervenant: 
59
+        # Intervenant
60
+        canvas.setFillColorRGB(.3,.3,1)        
61
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
62
+        canvas.setFillColorRGB(1,1,1)
63
+        canvas.setFont('Liberation', 15)
64
+        canvas.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant")        
65
+    else:
66
+        # Visiteur
67
+        canvas.setFillColorRGB(.8,.8,.8)        
68
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
69
+        canvas.setFillColorRGB(0,0,0)
70
+        canvas.setFont('Liberation', 12)
71
+        canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+7, "Visiteur")
72
+    canvas.restoreState()        
73
+    return canvas
74
+
75
+def Ribbon90(DispUser, canvas):
76
+    canvas.saveState()
77
+    canvas.rotate(90)
78
+    offset_u=0        
79
+    if DispUser.Staff:
80
+        # Staff
81
+        canvas.setFillColorRGB(1,.2,.2)        
82
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
83
+        canvas.setFillColorRGB(1,1,1)
84
+        canvas.setFont('Liberation', 20)
85
+        canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+5, "STAFF")
86
+    elif DispUser.is_Intervenant: 
87
+        # Intervenant
88
+        canvas.setFillColorRGB(.3,.3,1)        
89
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
90
+        canvas.setFillColorRGB(1,1,1)
91
+        canvas.setFont('Liberation', 15)
92
+        canvas.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant")        
93
+    else:
94
+        # Visiteur
95
+        canvas.setFillColorRGB(.8,.8,.8)        
96
+        canvas.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
97
+        canvas.setFillColorRGB(0,0,0)
98
+        canvas.setFont('Liberation', 12)
99
+        canvas.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+7, "Visiteur")
100
+    canvas.restoreState()        
101
+    return canvas
102
+
103
+def JM2L_Logo(canvas, Position="Up"):
104
+    # Import font
105
+    ttfFile_Logo = "jm2l/static/fonts/PWTinselLetters.ttf"
106
+    pdfmetrics.registerFont(TTFont("Logo", ttfFile_Logo))
107
+    
108
+    if Position=="Up":
109
+        logoobject = canvas.beginText()
110
+        logoobject.setFont('Logo', 52)
111
+        logoobject.setFillColorRGB(.83,0,.33)
112
+        logoobject.setTextOrigin(55, HEIGHT-50)
113
+        logoobject.textLines("JM2L")
114
+        canvas.drawText(logoobject)
115
+        
116
+        yearobject = canvas.beginText()
117
+        yearobject.setFont("Helvetica-Bold", 15)
118
+        yearobject.setFillColorRGB(1,1,1)
119
+        yearobject.setTextOrigin(67, HEIGHT-20)
120
+        yearobject.setWordSpace(21)
121
+        yearobject.textLines("2 0 1 5")
122
+        canvas.drawText(yearobject)
123
+    elif Position=="Down":
124
+        logoobject = canvas.beginText()
125
+        logoobject.setFont('Logo', 32)
126
+        logoobject.setFillColorRGB(.83,0,.33)
127
+        logoobject.setTextOrigin(2, 17)
128
+        logoobject.textLines("JM2L")
129
+        canvas.drawText(logoobject)
130
+        
131
+        yearobject = canvas.beginText()
132
+        yearobject.setFont("Helvetica-Bold", 10)
133
+        yearobject.setFillColorRGB(1,1,1)
134
+        #yearobject.setLineWidth(.1)
135
+        #yearobject.setStrokeColorRGB(.5,.5,.5)
136
+        yearobject.setTextRenderMode(0)
137
+        #yearobject.setStrokeOverprint(.2)
138
+        yearobject.setTextOrigin(9 , 35)
139
+        yearobject.setWordSpace(13)
140
+        yearobject.textLines("2 0 1 5")
141
+        canvas.drawText(yearobject)
142
+
143
+def Tiers_Logo(canvas, DispUser, LWidth=4, StartPos=None):
144
+    Border = 1
145
+    if StartPos is None:
146
+        StartPos = ( WIDTH -70, 0 )
147
+    StartX, StartY = StartPos
148
+    num = 0
149
+    canvas.setStrokeColorRGB(0.5,0.5,0.5)
150
+    Logos = filter(lambda x:x.ThumbLinks, DispUser.tiers)
151
+    for tiers in Logos:
152
+        FileName = tiers.ThumbLinks.pop().split("/")[-1]
153
+        ImagePath = "jm2l/upload/images/tiers/%s/%s" % (tiers.slug, FileName)
154
+        # Should We compute a better positionning for logos ?
155
+        #PosX = StartX + (( (2.0*num+1) / 2*len(Logos) )*(ICONSIZE+Border) - ((ICONSIZE+Border)/2))
156
+        PosX = StartX + (num % LWidth) * (ICONSIZE+Border)
157
+        if len(Logos)<=LWidth:
158
+            # Middle of line
159
+            PosY = StartY + 0.5 * (ICONSIZE+Border)
160
+        else:
161
+            # in two or more lines
162
+            PosY = StartY + (num / LWidth) * (ICONSIZE+Border)
163
+        canvas.setLineWidth(.1)
164
+        canvas.drawImage(ImagePath, 
165
+            PosX, PosY, ICONSIZE, ICONSIZE,\
166
+            preserveAspectRatio=True, 
167
+            anchor='c',
168
+            mask=[0,3,0,3,0,3]
169
+            )
170
+        canvas.roundRect(PosX, PosY, ICONSIZE, ICONSIZE, radius=2, stroke=True)
171
+        num+=1
172
+
173
+def QRCode(DispUser):
174
+    qr = qrcode.QRCode(
175
+        version=1,
176
+        error_correction=qrcode.constants.ERROR_CORRECT_L,
177
+        box_size=10,
178
+        border=2,
179
+    )
180
+    # Data of QR code
181
+    qr.add_data('http://jm2l.linux-azur.org/user/%s' % DispUser.slug)
182
+    qr.make(fit=True)
183
+
184
+    return qr.make_image()
185
+
186
+@view_config(route_name='badge_user')
187
+def badge_user(request):
188
+    user_slug = request.matchdict.get('user_slug', None)
189
+    if user_slug is None or len(user_slug)==0:
190
+        raise HTTPNotFound(u"Cet utilisateur n'a pas été reconnu")
191
+    # Query database
192
+    DispUser = User.by_slug(user_slug)
193
+    if DispUser is None:
194
+        raise HTTPNotFound()
195
+
196
+    # Ok let's generate a PDF Badge
197
+    
198
+    # Register LiberationMono font
199
+    ttfFile = "jm2l/static/fonts/LiberationMono-Regular.ttf"
200
+    pdfmetrics.registerFont(TTFont("Liberation", ttfFile))
201
+
202
+    pdf = StringIO.StringIO()
203
+  
204
+    c = canvas.Canvas( pdf, pagesize=(WIDTH, HEIGHT) )
205
+    c.translate(mm, mm)
206
+    
207
+    # Feed some metadata
208
+    c.setCreator("linux-azur.org")
209
+    c.setTitle("Badge")
210
+
211
+    c.saveState()
212
+    # Logo on Top
213
+    JM2L_Logo(c, "Down")
214
+            
215
+    if DispUser.Staff:
216
+        # Staff
217
+        c.setFillColorRGB(.83,0,.33)
218
+        c.rect(-3, HEIGHT-30, WIDTH, HEIGHT, fill=1)
219
+        c.setFillColorRGB(1,1,1)
220
+        c.setFont('Liberation', 30)
221
+        c.drawCentredString(WIDTH/2, HEIGHT-26, "STAFF")
222
+    elif DispUser.is_Intervenant: 
223
+        # Intervenant
224
+        c.setFillColorRGB(.3,.3,1)        
225
+        c.rect(-3, HEIGHT-30, WIDTH, HEIGHT, fill=1)
226
+        c.setFillColorRGB(1,1,1)
227
+        c.setFont('Liberation', 30)
228
+        c.drawCentredString(WIDTH/2, HEIGHT-26, "Intervenant")        
229
+    else:
230
+        # Visiteur
231
+        c.setFillColorRGB(.8,.8,.8)        
232
+        c.rect(-3, HEIGHT-30, WIDTH, HEIGHT, fill=1)
233
+        c.setFillColorRGB(1,1,1)
234
+        c.setFont('Liberation', 30)
235
+        c.drawCentredString(WIDTH/2, HEIGHT-26, "Visiteur")            
236
+
237
+    c.restoreState()
238
+    
239
+    c.setFont('Liberation', 18)
240
+    c.setStrokeColorRGB(0,0,0)
241
+    c.setFillColorRGB(0,0,0)
242
+    # Feed Name and SurName
243
+    if len(DispUser.prenom) + len(DispUser.nom)>18:
244
+        if DispUser.pseudo:
245
+            c.drawCentredString(WIDTH/2, HEIGHT/2 + 4 * mm , "%s" % DispUser.prenom )
246
+            c.setFont('Courier', 17)        
247
+            c.drawCentredString(WIDTH/2, HEIGHT/2 - 2 * mm , "%s" % DispUser.nom )
248
+        else:
249
+            c.drawCentredString(WIDTH/2, HEIGHT/2 + 2 * mm , "%s" % DispUser.prenom )
250
+            c.setFont('Courier', 17)        
251
+            c.drawCentredString(WIDTH/2, HEIGHT/2 - 6 * mm , "%s" % DispUser.nom )            
252
+    else:
253
+        c.drawCentredString(WIDTH/2, HEIGHT/2 + 0 * mm , "%s %s" % (DispUser.prenom, DispUser.nom) )
254
+
255
+    if DispUser.pseudo:
256
+        c.setFont("Helvetica-Oblique", 14)
257
+        c.drawCentredString(WIDTH/2, HEIGHT/2 - 8 * mm , "%s" % DispUser.pseudo )
258
+    
259
+    # Put QR code to user profile 
260
+    c.drawInlineImage(QRCode(DispUser), \
261
+            WIDTH - 20 * mm - 7, 0, \
262
+            20 * mm, 20 * mm, \
263
+            preserveAspectRatio=True, \
264
+            anchor='s')
265
+    
266
+    Tiers_Logo(c, DispUser, 4,  ( 30 * mm, 2 ))   
267
+    
268
+    c.showPage()
269
+    c.save()
270
+    pdf.seek(0)    
271
+    return Response(app_iter=pdf, content_type = 'application/pdf' )
272
+
273
+@view_config(route_name='badge_user1')
274
+def badge_user1(request):
275
+    user_slug = request.matchdict.get('user_slug', None)
276
+    if user_slug is None or len(user_slug)==0:
277
+        raise HTTPNotFound(u"Cet utilisateur n'a pas été reconnu")
278
+    # Query database
279
+    DispUser = User.by_slug(user_slug)
280
+    if DispUser is None:
281
+        raise HTTPNotFound()
282
+
283
+    # Ok let's generate a PDF Badge
284
+    ttfFile = "jm2l/static/fonts/LiberationMono-Regular.ttf"
285
+    ttfFile_Logo = "jm2l/static/fonts/PWTinselLetters.ttf"
286
+    pdfmetrics.registerFont(TTFont("Liberation", ttfFile))
287
+    pdfmetrics.registerFont(TTFont("Logo", ttfFile_Logo))
288
+    pdf = StringIO.StringIO()
289
+  
290
+    c = canvas.Canvas( pdf, pagesize=(WIDTH, HEIGHT) )
291
+    c.translate(mm, mm)
292
+    
293
+    # Feed some metadata
294
+    c.setCreator("linux-azur.org")
295
+    c.setTitle("Badge")
296
+
297
+    c.saveState()
298
+
299
+    logoobject = c.beginText()
300
+    logoobject.setFont('Logo', 52)
301
+    logoobject.setFillColorRGB(.83,0,.33)
302
+    logoobject.setTextOrigin(55, HEIGHT-50)
303
+    logoobject.textLines("JM2L")
304
+    c.drawText(logoobject)
305
+    
306
+    
307
+    yearobject = c.beginText()
308
+    yearobject.setFont("Helvetica-Bold", 15)
309
+    yearobject.setFillColorRGB(1,1,1)
310
+    yearobject.setTextOrigin(67, HEIGHT-20)
311
+    yearobject.setWordSpace(21)
312
+    yearobject.textLines("2 0 1 5")
313
+    c.drawText(yearobject)
314
+        
315
+    num = 0
316
+    for tiers in DispUser.tiers:
317
+        if tiers.ThumbLinks:
318
+            FileName = tiers.ThumbLinks.pop().split("/")[-1]
319
+            num+=1
320
+            ImagePath = "jm2l/upload/images/tiers/%s/%s" % (tiers.slug, FileName)
321
+            if num<6:
322
+                c.drawImage(ImagePath, 
323
+                    WIDTH -5 - num * (ICONSIZE+5), 5, ICONSIZE, ICONSIZE,\
324
+                    preserveAspectRatio=True, 
325
+                    anchor='c',
326
+                    mask=[0,3,0,3,0,3])
327
+            else:
328
+                c.drawImage(ImagePath, 
329
+                    WIDTH -5 - (num-5) * (ICONSIZE+5), 5 + ICONSIZE, ICONSIZE, ICONSIZE,\
330
+                    preserveAspectRatio=True, 
331
+                    anchor='c',
332
+                    mask=[0,3,0,3,0,3])
333
+            
334
+    
335
+
336
+    
337
+    if 1:
338
+        Ribbon90(DispUser, c)
339
+        
340
+    if 0:
341
+        c.rotate(90)
342
+        offset_u=111
343
+        if DispUser.Staff:
344
+            # Staff
345
+            c.setFillColorRGB(1,.2,.2)        
346
+            c.rect(-5, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
347
+            c.setFillColorRGB(1,1,1)
348
+            c.setFont('Liberation', 30)
349
+            c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+5, "STAFF")
350
+        elif DispUser.is_Intervenant: 
351
+            # Intervenant
352
+            c.setFillColorRGB(.3,.3,1)        
353
+            c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
354
+            c.setFillColorRGB(1,1,1)
355
+            c.setFont('Liberation', 15)
356
+            c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant")        
357
+        else:
358
+            # Visiteur
359
+            c.setFillColorRGB(.8,.8,.8)        
360
+            c.rect(-5, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
361
+            c.setFillColorRGB(0,0,0)
362
+            c.setFont('Liberation', 19)
363
+            c.drawCentredString(WIDTH/2, HEIGHT/2-offset_u+7, "Visiteur")            
364
+    c.restoreState()
365
+    
366
+    c.setFont('Courier', 18)
367
+    c.setStrokeColorRGB(0,0,0)
368
+    c.setFillColorRGB(0,0,0)
369
+    # Feed Name and SurName
370
+    if len(DispUser.prenom) + len(DispUser.nom)>18:
371
+        if DispUser.pseudo:
372
+            c.drawCentredString(WIDTH/2, HEIGHT/2 + 4 * mm , "%s" % DispUser.prenom )
373
+            c.setFont('Courier', 17)        
374
+            c.drawCentredString(WIDTH/2, HEIGHT/2 - 2 * mm , "%s" % DispUser.nom )
375
+        else:
376
+            c.drawCentredString(WIDTH/2, HEIGHT/2 + 2 * mm , "%s" % DispUser.prenom )
377
+            c.setFont('Courier', 17)        
378
+            c.drawCentredString(WIDTH/2, HEIGHT/2 - 6 * mm , "%s" % DispUser.nom )            
379
+    else:
380
+        c.drawCentredString(WIDTH/2, HEIGHT/2 + 0 * mm , "%s %s" % (DispUser.prenom, DispUser.nom) )
381
+    #c.drawCentredString(WIDTH/2, HEIGHT - 22 * mm, )
382
+    if DispUser.pseudo:
383
+        #c.setFont('Liberation', 14)
384
+        c.setFont("Helvetica-Oblique", 14)
385
+        c.drawCentredString(WIDTH/2, HEIGHT/2 - 8 * mm , "\"%s\"" % DispUser.pseudo )
386
+    #c.restoreState()  
387
+    
388
+    # Put QR code to user profile 
389
+    c.drawInlineImage(QRCode(DispUser), \
390
+            WIDTH - 20 * mm - 7, 0, \
391
+            20 * mm, 20 * mm, \
392
+            preserveAspectRatio=True, \
393
+            anchor='s')
394
+   
395
+    c.showPage()
396
+    c.save()
397
+    pdf.seek(0)    
398
+    return Response(app_iter=pdf, content_type = 'application/pdf' )
399
+
400
+    
401
+@view_config(route_name='badge_user2')
402
+def badge_user2(request):
403
+    user_slug = request.matchdict.get('user_slug', None)
404
+    if user_slug is None or len(user_slug)==0:
405
+        raise HTTPNotFound(u"Cet utilisateur n'a pas été reconnu")
406
+    # Query database
407
+    DispUser = User.by_slug(user_slug)
408
+    if DispUser is None:
409
+        raise HTTPNotFound()
410
+
411
+    # Ok let's generate a PDF Badge
412
+    ttfFile = "jm2l/static/fonts/LiberationMono-Regular.ttf"
413
+    ttfFile_Logo = "jm2l/static/fonts/PWTinselLetters.ttf"
414
+    pdfmetrics.registerFont(TTFont("Liberation", ttfFile))
415
+    pdfmetrics.registerFont(TTFont("Logo", ttfFile_Logo))
416
+    pdf = StringIO.StringIO()
417
+
418
+    qr = qrcode.QRCode(
419
+        version=1,
420
+        error_correction=qrcode.constants.ERROR_CORRECT_L,
421
+        box_size=10,
422
+        border=2,
423
+    )
424
+    # Data of QR code
425
+    qr.add_data('http://jm2l.linux-azur.org/user/%s' % DispUser.slug)
426
+    qr.make(fit=True)
427
+
428
+    img = qr.make_image()    
429
+    # Create PDF container
430
+    WIDTH = 85 * mm
431
+    HEIGHT = 60 * mm
432
+    ICONSIZE = 10 * mm    
433
+    c = canvas.Canvas( pdf, pagesize=(WIDTH, HEIGHT) )
434
+    c.translate(mm, mm)
435
+    
436
+    # Feed some metadata
437
+    c.setCreator("linux-azur.org")
438
+    c.setTitle("Badge")
439
+
440
+    c.saveState()
441
+
442
+    logoobject = c.beginText()
443
+    logoobject.setFont('Logo', 52)
444
+    logoobject.setFillColorRGB(.83,0,.33)
445
+    logoobject.setTextOrigin(55, HEIGHT-50)
446
+    logoobject.textLines("JM2L")
447
+    c.drawText(logoobject)
448
+    
449
+    
450
+    yearobject = c.beginText()
451
+    yearobject.setFont("Helvetica-Bold", 15)
452
+    yearobject.setFillColorRGB(1,1,1)
453
+    yearobject.setTextOrigin(67, HEIGHT-20)
454
+    yearobject.setWordSpace(21)
455
+    yearobject.textLines("2 0 1 5")
456
+    c.drawText(yearobject)
457
+        
458
+    num = 0
459
+    for tiers in DispUser.tiers:
460
+        if tiers.ThumbLinks:
461
+            FileName = tiers.ThumbLinks.pop().split("/")[-1]
462
+            num+=1
463
+            ImagePath = "jm2l/upload/images/tiers/%s/%s" % (tiers.slug, FileName)
464
+            if num<6:
465
+                c.drawImage(ImagePath, 
466
+                    WIDTH -5 - num * (ICONSIZE+5), 5, ICONSIZE, ICONSIZE,\
467
+                    preserveAspectRatio=True, 
468
+                    anchor='c',
469
+                    mask=[0,3,0,3,0,3])
470
+            else:
471
+                c.drawImage(ImagePath, 
472
+                    WIDTH -5 - (num-5) * (ICONSIZE+5), 5 + ICONSIZE, ICONSIZE, ICONSIZE,\
473
+                    preserveAspectRatio=True, 
474
+                    anchor='c',
475
+                    mask=[0,3,0,3,0,3])
476
+            
477
+    
478
+
479
+    
480
+    if 1:
481
+        c.rotate(35)
482
+        offset_u=0        
483
+        if DispUser.Staff:
484
+            # Staff
485
+            c.setFillColorRGB(1,.2,.2)        
486
+            c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
487
+            c.setFillColorRGB(1,1,1)
488
+            c.setFont('Liberation', 20)
489
+            c.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+5, "STAFF")
490
+        elif DispUser.is_Intervenant: 
491
+            # Intervenant
492
+            c.setFillColorRGB(.3,.3,1)        
493
+            c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
494
+            c.setFillColorRGB(1,1,1)
495
+            c.setFont('Liberation', 15)
496
+            c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant")        
497
+        else:
498
+            # Visiteur
499
+            c.setFillColorRGB(.8,.8,.8)        
500
+            c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
501
+            c.setFillColorRGB(0,0,0)
502
+            c.setFont('Liberation', 12)
503
+            c.drawCentredString(WIDTH/2-10, HEIGHT/2-offset_u+7, "Visiteur")
504
+
505
+    if 0:
506
+        c.rotate(90)
507
+        offset_u=111
508
+        if DispUser.Staff:
509
+            # Staff
510
+            c.setFillColorRGB(1,.2,.2)        
511
+            c.rect(-5, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
512
+            c.setFillColorRGB(1,1,1)
513
+            c.setFont('Liberation', 30)
514
+            c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+5, "STAFF")
515
+        elif DispUser.is_Intervenant: 
516
+            # Intervenant
517
+            c.setFillColorRGB(.3,.3,1)        
518
+            c.rect(0, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
519
+            c.setFillColorRGB(1,1,1)
520
+            c.setFont('Liberation', 15)
521
+            c.drawCentredString(WIDTH/2-15, HEIGHT/2-offset_u+10, "Intervenant")        
522
+        else:
523
+            # Visiteur
524
+            c.setFillColorRGB(.8,.8,.8)        
525
+            c.rect(-5, HEIGHT/2-offset_u, WIDTH, 10*mm, fill=1)
526
+            c.setFillColorRGB(0,0,0)
527
+            c.setFont('Liberation', 19)
528
+            c.drawCentredString(WIDTH/2, HEIGHT/2-offset_u+7, "Visiteur")
529
+
530
+            
531
+    c.restoreState()
532
+    
533
+    c.setFont('Courier', 18)
534
+    c.setStrokeColorRGB(0,0,0)
535
+    c.setFillColorRGB(0,0,0)
536
+    # Feed Name and SurName
537
+    if len(DispUser.prenom) + len(DispUser.nom)>18:
538
+        if DispUser.pseudo:
539
+            c.drawCentredString(WIDTH/2, HEIGHT/2 + 4 * mm , "%s" % DispUser.prenom )
540
+            c.setFont('Courier', 17)        
541
+            c.drawCentredString(WIDTH/2, HEIGHT/2 - 2 * mm , "%s" % DispUser.nom )
542
+        else:
543
+            c.drawCentredString(WIDTH/2, HEIGHT/2 + 2 * mm , "%s" % DispUser.prenom )
544
+            c.setFont('Courier', 17)        
545
+            c.drawCentredString(WIDTH/2, HEIGHT/2 - 6 * mm , "%s" % DispUser.nom )            
546
+    else:
547
+        c.drawCentredString(WIDTH/2, HEIGHT/2 + 0 * mm , "%s %s" % (DispUser.prenom, DispUser.nom) )
548
+    #c.drawCentredString(WIDTH/2, HEIGHT - 22 * mm, )
549
+    if DispUser.pseudo:
550
+        #c.setFont('Liberation', 14)
551
+        c.setFont("Helvetica-Oblique", 14)
552
+        c.drawCentredString(WIDTH/2, HEIGHT/2 - 8 * mm , "\"%s\"" % DispUser.pseudo )
553
+    #c.restoreState()  
554
+    
555
+    # Put QR code to user profile 
556
+    c.drawInlineImage(img, WIDTH - 20 * mm - 7, 0, 20 * mm, 20 * mm, preserveAspectRatio=True, anchor='s')
557
+   
558
+    c.showPage()
559
+    c.save()
560
+    pdf.seek(0)    
561
+    return Response(app_iter=pdf, content_type = 'application/pdf' )

+ 198 - 0
jm2l/blenderthumbnailer.py

@@ -0,0 +1,198 @@
1
+#!/usr/bin/env python
2
+
3
+# ##### BEGIN GPL LICENSE BLOCK #####
4
+#
5
+#  This program is free software; you can redistribute it and/or
6
+#  modify it under the terms of the GNU General Public License
7
+#  as published by the Free Software Foundation; either version 2
8
+#  of the License, or (at your option) any later version.
9
+#
10
+#  This program is distributed in the hope that it will be useful,
11
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
+#  GNU General Public License for more details.
14
+#
15
+#  You should have received a copy of the GNU General Public License
16
+#  along with this program; if not, write to the Free Software Foundation,
17
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
+#
19
+# ##### END GPL LICENSE BLOCK #####
20
+
21
+# <pep8 compliant>
22
+
23
+"""
24
+Thumbnailer runs with python 2.7 and 3.x.
25
+To run automatically with a file manager such as Nautilus, save this file
26
+in a directory that is listed in PATH environment variable, and create
27
+blender.thumbnailer file in ${HOME}/.local/share/thumbnailers/ directory
28
+with the following contents:
29
+
30
+[Thumbnailer Entry]
31
+TryExec=blender-thumbnailer.py
32
+Exec=blender-thumbnailer.py %u %o
33
+MimeType=application/x-blender;
34
+"""
35
+
36
+import struct
37
+
38
+
39
+def open_wrapper_get():
40
+    """ wrap OS spesific read functionality here, fallback to 'open()'
41
+    """
42
+
43
+    class GFileWrapper:
44
+        __slots__ = ("mode", "g_file")
45
+
46
+        def __init__(self, url, mode='r'):
47
+            self.mode = mode  # used in gzip module
48
+            self.g_file = Gio.File.parse_name(url).read(None)
49
+
50
+        def read(self, size):
51
+            return self.g_file.read_bytes(size, None).get_data()
52
+
53
+        def seek(self, offset, whence=0):
54
+            self.g_file.seek(offset, [1, 0, 2][whence], None)
55
+            return self.g_file.tell()
56
+
57
+        def tell(self):
58
+            return self.g_file.tell()
59
+
60
+        def close(self):
61
+            self.g_file.close(None)
62
+
63
+    def open_local_url(url, mode='r'):
64
+        o = urlparse(url)
65
+        if o.scheme == '':
66
+            path = o.path
67
+        elif o.scheme == 'file':
68
+            path = unquote(o.path)
69
+        else:
70
+            raise(IOError('URL scheme "%s" needs gi.repository.Gio module' % o.scheme))
71
+        return open(path, mode)
72
+
73
+    try:
74
+        from gi.repository import Gio
75
+        return GFileWrapper
76
+    except ImportError:
77
+        try:
78
+            # Python 3
79
+            from urllib.parse import urlparse, unquote
80
+        except ImportError:
81
+            # Python 2
82
+            from urlparse import urlparse
83
+            from urllib import unquote
84
+        return open_local_url
85
+
86
+
87
+def blend_extract_thumb(path):
88
+    import os
89
+    open_wrapper = open_wrapper_get()
90
+
91
+    # def MAKE_ID(tag): ord(tag[0])<<24 | ord(tag[1])<<16 | ord(tag[2])<<8 | ord(tag[3])
92
+    REND = 1145980242  # MAKE_ID(b'REND')
93
+    TEST = 1414743380  # MAKE_ID(b'TEST')
94
+
95
+    blendfile = open_wrapper(path, 'rb')
96
+
97
+    head = blendfile.read(12)
98
+
99
+    if head[0:2] == b'\x1f\x8b':  # gzip magic
100
+        import gzip
101
+        blendfile.close()
102
+        blendfile = gzip.GzipFile('', 'rb', 0, open_wrapper(path, 'rb'))
103
+        head = blendfile.read(12)
104
+
105
+    if not head.startswith(b'BLENDER'):
106
+        blendfile.close()
107
+        return None, 0, 0
108
+
109
+    is_64_bit = (head[7] == b'-'[0])
110
+
111
+    # true for PPC, false for X86
112
+    is_big_endian = (head[8] == b'V'[0])
113
+
114
+    # blender pre 2.5 had no thumbs
115
+    if head[9:11] <= b'24':
116
+        blendfile.close()
117
+        return None, 0, 0
118
+
119
+    sizeof_bhead = 24 if is_64_bit else 20
120
+    int_endian_pair = '>ii' if is_big_endian else '<ii'
121
+
122
+    while True:
123
+        bhead = blendfile.read(sizeof_bhead)
124
+
125
+        if len(bhead) < sizeof_bhead:
126
+            return None, 0, 0
127
+
128
+        code, length = struct.unpack(int_endian_pair, bhead[0:8])  # 8 == sizeof(int) * 2
129
+
130
+        if code == REND:
131
+            blendfile.seek(length, os.SEEK_CUR)
132
+        else:
133
+            break
134
+
135
+    if code != TEST:
136
+        blendfile.close()
137
+        return None, 0, 0
138
+
139
+    try:
140
+        x, y = struct.unpack(int_endian_pair, blendfile.read(8))  # 8 == sizeof(int) * 2
141
+    except struct.error:
142
+        blendfile.close()
143
+        return None, 0, 0
144
+
145
+    length -= 8  # sizeof(int) * 2
146
+
147
+    if length != x * y * 4:
148
+        blendfile.close()
149
+        return None, 0, 0
150
+
151
+    image_buffer = blendfile.read(length)
152
+
153
+    if len(image_buffer) != length:
154
+        blendfile.close()
155
+        return None, 0, 0
156
+
157
+    blendfile.close()
158
+    return image_buffer, x, y
159
+
160
+
161
+def write_png(buf, width, height):
162
+    import zlib
163
+
164
+    # reverse the vertical line order and add null bytes at the start
165
+    width_byte_4 = width * 4
166
+    raw_data = b"".join(b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4))
167
+
168
+    def png_pack(png_tag, data):
169
+        chunk_head = png_tag + data
170
+        return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
171
+
172
+    return b"".join([
173
+        b'\x89PNG\r\n\x1a\n',
174
+        png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
175
+        png_pack(b'IDAT', zlib.compress(raw_data, 9)),
176
+        png_pack(b'IEND', b'')])
177
+
178
+
179
+def main():
180
+    import sys
181
+
182
+    if len(sys.argv) < 3:
183
+        print("Expected 2 arguments <input.blend> <output.png>")
184
+    else:
185
+        file_in = sys.argv[-2]
186
+
187
+        buf, width, height = blend_extract_thumb(file_in)
188
+
189
+        if buf:
190
+            file_out = sys.argv[-1]
191
+
192
+            f = open(file_out, "wb")
193
+            f.write(write_png(buf, width, height))
194
+            f.close()
195
+
196
+
197
+if __name__ == '__main__':
198
+    main()

+ 1 - 1
jm2l/captcha.py

@@ -3,7 +3,7 @@
3 3
 
4 4
 import random
5 5
 from PIL import Image, ImageDraw, ImageFont, ImageFilter
6
-import StringIO
6
+import cStringIO as StringIO
7 7
 import math
8 8
 from pyramid.view import view_config
9 9
 from .words import TabMots

+ 7 - 0
jm2l/models.py

@@ -170,6 +170,13 @@ class User(Base):
170 170
         return None
171 171
 
172 172
     @property
173
+    def is_Intervenant(self, year=2015):
174
+        """ This property will return if User do an event on specified year """
175
+        return DBSession.query(Event).join(User_Event) \
176
+            .filter(User_Event.user_uid==self.uid) \
177
+            .filter(Event.for_year==year).count()
178
+    
179
+    @property
173 180
     def my_hash(self):
174 181
         m = hashlib.sha1()
175 182
         m.update("Nobody inspects ")

BIN
jm2l/static/fonts/PWTinselLetters.ttf


BIN
jm2l/static/img/Blender_Thumb_Stamp.png


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

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

+ 116 - 31
jm2l/upload.py

@@ -2,8 +2,7 @@
2 2
 from pyramid.view import view_config, view_defaults
3 3
 from pyramid.response import Response
4 4
 from pyramid.exceptions import NotFound
5
-from pyramid.httpexceptions import HTTPNotFound
6
-from pyramid.request import Request
5
+from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest
7 6
 from PIL import Image
8 7
 import re, os, shutil
9 8
 from os import path
@@ -13,6 +12,7 @@ import subprocess
13 12
 import cStringIO as StringIO
14 13
 # Database access imports
15 14
 from .models import User, Place, Tiers, Event, SallePhy
15
+from .blenderthumbnailer import blend_extract_thumb, write_png
16 16
 
17 17
 CurrentYear = 2015
18 18
 MIN_FILE_SIZE = 1 # bytes
@@ -27,7 +27,8 @@ ACCEPTED_MIMES = ['application/pdf',
27 27
     'application/vnd.oasis.opendocument.presentation-template',
28 28
     'application/vnd.oasis.opendocument.spreadsheet',
29 29
     'application/vnd.oasis.opendocument.spreadsheet-template',
30
-    'image/svg+xml'
30
+    'image/svg+xml',
31
+    'application/x-blender'
31 32
 ]
32 33
 
33 34
         
@@ -136,6 +137,38 @@ class MediaPath():
136 137
         if mime=='application/pdf':
137 138
             return "/img/PDF.png"
138 139
 
140
+    def check_blend_file(self, fileobj):
141
+        head = fileobj.read(12)
142
+        fileobj.seek(0)
143
+
144
+        if head[:2] == b'\x1f\x8b':  # gzip magic
145
+            import zlib
146
+            head = zlib.decompress(fileobj.read(), 31)[:12]
147
+            fileobj.seek(0)
148
+
149
+        if head.startswith(b'BLENDER'):
150
+            return True
151
+
152
+    def get_mimetype_from_file(self, fileobj):
153
+        mimetype = magic.from_buffer(fileobj.read(1024), mime=True)
154
+        fileobj.seek(0)
155
+
156
+        # Check if the binary file is a blender file
157
+        if ( mimetype == "application/octet-stream" or mimetype == "application/x-gzip" ) and self.check_blend_file(fileobj):
158
+            return "application/x-blender", True
159
+        else:
160
+            return mimetype, False
161
+
162
+
163
+    def get_mimetype(self, name):
164
+        """ This function return the mime-type based on .type file """
165
+        try:
166
+            with open(self.mediapath(name) + '.type', 'r', 16) as f:
167
+                mime = f.read()
168
+            return mime
169
+        except IOError:
170
+            return None
171
+
139 172
 @view_defaults(route_name='media_upload')
140 173
 class MediaUpload(MediaPath):
141 174
 
@@ -152,19 +185,31 @@ class MediaUpload(MediaPath):
152 185
         return self.get_mediapath(self.media_table, self.linked_id, name)
153 186
 
154 187
     def validate(self, result, filecontent):
155
-        # let's say we don't trust the uploader
156
-        RealMime = magic.from_buffer( filecontent.read(1024), mime=True)
188
+        """ Do some basic check to the uploaded file before to accept it """
189
+        # Try to determine mime type from content uploaded
190
+        found_mime = magic.from_buffer(filecontent.read(1024), mime=True)
157 191
         filecontent.seek(0)
158
-        if RealMime!=result['type']:
159
-            result['error'] = 'l\'extension du fichier ne correspond pas à son contenu - '
160
-            result['error'] += "( %s vs %s )" % (RealMime, result['type'])
192
+        
193
+        # Do a special statement for specific detected mime type
194
+        if found_mime in ["application/octet-stream", "application/x-gzip"]:
195
+            # Lets see if it's a bender file
196
+            if self.check_blend_file(filecontent):
197
+                found_mime = "application/x-blender"
198
+                # MonKey Patch of content type
199
+                result['type'] = found_mime        
200
+        
201
+        # Reject mime type that don't match
202
+        if found_mime!=result['type']:
203
+            result['error'] = 'L\'extension du fichier ne correspond pas à son contenu - '
204
+            result['error'] += "( %s vs %s )" % (found_mime, result['type'])
161 205
             return False
206
+
162 207
         # Accept images and mime types listed
163
-        if not RealMime in ACCEPTED_MIMES:
164
-            if not (IMAGE_TYPES.match(RealMime)):
208
+        if not found_mime in ACCEPTED_MIMES:
209
+            if not (IMAGE_TYPES.match(found_mime)):
165 210
                 result['error'] = 'Ce type fichier n\'est malheureusement pas supporté. '
166
-                result['error'] += 'Les fichiers acceptées sont les images et pdf.'
167 211
                 return False
212
+
168 213
         if result['size'] < MIN_FILE_SIZE:
169 214
             result['error'] = 'le fichier est trop petit'
170 215
         elif result['size'] > MAX_FILE_SIZE:
@@ -173,12 +218,13 @@ class MediaUpload(MediaPath):
173 218
         #    file['error'] = u'les type de fichiers acceptés sont png, jpg et gif'
174 219
         else:
175 220
             return True
221
+
176 222
         return False
177 223
 
178
-    def get_file_size(self, file):
179
-        file.seek(0, 2) # Seek to the end of the file
180
-        size = file.tell() # Get the position of EOF
181
-        file.seek(0) # Reset the file position to the beginning
224
+    def get_file_size(self, fileobj):
225
+        fileobj.seek(0, 2) # Seek to the end of the file
226
+        size = fileobj.tell() # Get the position of EOF
227
+        fileobj.seek(0) # Reset the file position to the beginning
182 228
         return size
183 229
 
184 230
     def thumbnailurl(self,name):
@@ -254,9 +300,6 @@ class MediaUpload(MediaPath):
254 300
 
255 301
     def docthumbnail(self, filename):
256 302
         TargetFileName = self.thumbnailpath(filename)
257
-        # unoconv need a libre office server to be up
258
-        Command = ["unoconv", "-f", "pdf", "-e", "PageRange=1", "--output=%s" % TargetFileName, \
259
-            "%s[0]" % self.mediapath(filename) ]
260 303
         # let's take the thumbnail generated inside the document
261 304
         Command = ["unzip", "-p", self.mediapath(filename), "Thumbnails/thumbnail.png"]
262 305
         ThumbBytes = subprocess.check_output(Command)
@@ -284,6 +327,40 @@ class MediaUpload(MediaPath):
284 327
         timage.convert('RGB').save( TargetFileName+".jpg", 'JPEG')
285 328
         return self.thumbnailurl( os.path.basename(TargetFileName+".jpg") )
286 329
 
330
+    def blendthumbnail(self, filename):
331
+        blendfile = self.mediapath(filename)
332
+        # Extract Thumb
333
+        if 0:
334
+            head = fileobj.read(12)
335
+            fileobj.seek(0)
336
+    
337
+            if head[:2] == b'\x1f\x8b':  # gzip magic
338
+                import zlib
339
+                head = zlib.decompress(fileobj.read(), 31)[:12]
340
+                fileobj.seek(0)        
341
+        
342
+        buf, width, height = blend_extract_thumb(blendfile)
343
+        if buf:
344
+            png = write_png(buf, width, height)
345
+            TargetFileName = self.thumbnailpath(filename)
346
+            image = Image.open(StringIO.StringIO(png))
347
+            blender_indicator = Image.open( "jm2l/static/img/Blender_Thumb_Stamp.png" )
348
+            image.thumbnail((THUMBNAIL_SIZE, THUMBNAIL_SIZE), Image.ANTIALIAS)
349
+            timage = Image.new('RGBA', (THUMBNAIL_SIZE, THUMBNAIL_SIZE), (255, 255, 255, 0))
350
+            # Add thumbnail
351
+            timage.paste(
352
+                image,
353
+                ((THUMBNAIL_SIZE - image.size[0]) / 2, (THUMBNAIL_SIZE - image.size[1]) / 2))
354
+            # Stamp with Blender file type
355
+            timage.paste(
356
+                blender_indicator,
357
+                (timage.size[0]-30, timage.size[1]-30),
358
+                blender_indicator,
359
+                )
360
+            timage.save( TargetFileName+".png")
361
+            return self.thumbnailurl( os.path.basename(TargetFileName+".png") )
362
+        return self.ExtMimeIcon('application/x-blender')
363
+
287 364
     def fileinfo(self,name):
288 365
         filename = self.mediapath(name) 
289 366
         f, ext = os.path.splitext(name)
@@ -295,13 +372,17 @@ class MediaUpload(MediaPath):
295 372
                         name=name,
296 373
                         media_table=self.media_table,
297 374
                         uid=self.linked_id)
298
-            mime = mimetypes.types_map.get(ext)
299
-            if (IMAGE_TYPES.match(mime)):
375
+
376
+            mime = self.get_mimetype(name)
377
+            if IMAGE_TYPES.match(mime):
300 378
                 info['thumbnailUrl'] = self.thumbnailurl(name)
301 379
             elif mime in ACCEPTED_MIMES:
302 380
                 thumb = self.thumbnailpath("%s%s" % (f, ext))
303
-                if os.path.exists( thumb +'.jpg' ):
304
-                    info['thumbnailUrl'] = self.thumbnailurl(name)+'.jpg'
381
+                thumbext = ".jpg"
382
+                if mime == "application/x-blender":
383
+                    thumbext = ".png"
384
+                if os.path.exists( thumb + thumbext ):
385
+                    info['thumbnailUrl'] = self.thumbnailurl(name)+thumbext
305 386
                 else:
306 387
                     info['thumbnailUrl'] = self.ExtMimeIcon(mime)
307 388
             else:
@@ -343,7 +424,6 @@ class MediaUpload(MediaPath):
343 424
 
344 425
     @view_config(request_method='DELETE', xhr=True, accept="application/json", renderer='json')
345 426
     def delete(self):
346
-        import json
347 427
         filename = self.request.matchdict.get('name')
348 428
         try:
349 429
             os.remove(self.mediapath(filename) + '.type')
@@ -381,11 +461,17 @@ class MediaUpload(MediaPath):
381 461
             result['name'] = os.path.basename(fieldStorage.filename)
382 462
             result['type'] = fieldStorage.type
383 463
             result['size'] = self.get_file_size(fieldStorage.file)
464
+
384 465
             if self.validate(result, fieldStorage.file):
385
-                with open( self.mediapath(result['name'] + '.type'), 'w') as f:
466
+                # Keep mime-type in .type file
467
+                with open( self.mediapath( result['name'] ) + '.type', 'w') as f:
386 468
                     f.write(result['type'])
387
-                with open( self.mediapath(result['name']), 'wb') as f:
469
+                
470
+                # Store uploaded file
471
+                fieldStorage.file.seek(0)
472
+                with open( self.mediapath(result['name'] ), 'wb') as f:
388 473
                     shutil.copyfileobj( fieldStorage.file , f)
474
+
389 475
                 if re.match(IMAGE_TYPES, result['type']):
390 476
                     result['thumbnailUrl'] = self.createthumbnail(result['name'])
391 477
                 elif result['type']=='application/pdf':
@@ -394,8 +480,11 @@ class MediaUpload(MediaPath):
394 480
                     result['thumbnailUrl'] = self.svgthumbnail(result['name'])
395 481
                 elif result['type'].startswith('application/vnd'):
396 482
                     result['thumbnailUrl'] = self.docthumbnail(result['name'])
483
+                elif result['type']=='application/x-blender':
484
+                    result['thumbnailUrl'] = self.blendthumbnail(result['name'])
397 485
                 else:
398 486
                     result['thumbnailUrl'] = self.ExtMimeIcon(result['type'])
487
+
399 488
                 result['deleteType'] = DELETEMETHOD
400 489
                 result['deleteUrl'] = self.request.route_url('media_upload',
401 490
                             sep='',
@@ -425,12 +514,8 @@ class MediaView(MediaPath):
425 514
     @view_config(request_method='GET', http_cache = (EXPIRATION_TIME, {'public':True}))
426 515
     def get(self):
427 516
         name = self.request.matchdict.get('name')
428
-        try:
429
-            with open( self.mediapath( os.path.basename(name) ) + '.type', 'r', 16) as f:
430
-                test = f.read()
431
-                self.request.response.content_type = test
432
-        except IOError:
433
-            pass
517
+        self.request.response.content_type = self.get_mimetype(name)
518
+
434 519
         try:
435 520
             self.request.response.body_file = open( self.mediapath(name), 'rb', 10000)
436 521
         except IOError:

+ 3 - 1
setup.py

@@ -30,7 +30,9 @@ requires = [
30 30
     'pyramid_exclog',
31 31
     'repoze.sendmail==4.1',
32 32
     'pyramid_mailer',
33
-    'apscheduler'
33
+    'apscheduler',
34
+    'qrcode',
35
+    'reportlab'
34 36
     ]
35 37
 
36 38
 setup(name='JM2L',