Le repo des sources pour le site web des JM2L
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

440 lines
18 KiB

  1. # -*- coding: utf8 -*-
  2. from pyramid.view import view_config, view_defaults
  3. from pyramid.response import Response
  4. from pyramid.exceptions import NotFound
  5. from pyramid.httpexceptions import HTTPNotFound
  6. from pyramid.request import Request
  7. from PIL import Image
  8. import re, os, shutil
  9. from os import path
  10. import mimetypes
  11. import magic
  12. import subprocess
  13. import cStringIO as StringIO
  14. # Database access imports
  15. from .models import User, Place, Tiers, Event, SallePhy
  16. CurrentYear = 2015
  17. MIN_FILE_SIZE = 1 # bytes
  18. MAX_FILE_SIZE = 500000000 # bytes
  19. IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)')
  20. ACCEPTED_MIMES = ['application/pdf',
  21. 'application/vnd.oasis.opendocument.text',
  22. 'application/vnd.oasis.opendocument.text-template',
  23. 'application/vnd.oasis.opendocument.graphics',
  24. 'application/vnd.oasis.opendocument.graphics-template',
  25. 'application/vnd.oasis.opendocument.presentation',
  26. 'application/vnd.oasis.opendocument.presentation-template',
  27. 'application/vnd.oasis.opendocument.spreadsheet',
  28. 'application/vnd.oasis.opendocument.spreadsheet-template',
  29. 'image/svg+xml'
  30. ]
  31. ACCEPT_FILE_TYPES = IMAGE_TYPES
  32. THUMBNAIL_SIZE = 80
  33. EXPIRATION_TIME = 300 # seconds
  34. IMAGEPATH = [ 'images' ]
  35. DOCPATH = [ 'document' ]
  36. THUMBNAILPATH = [ 'images', 'thumbnails' ]
  37. # change the following to POST if DELETE isn't supported by the webserver
  38. DELETEMETHOD="DELETE"
  39. mimetypes.init()
  40. class MediaPath():
  41. def get_list(self, media_table, linked_id, MediaType=None):
  42. filelist = list()
  43. curpath = self.get_mediapath(media_table, linked_id, None)
  44. if not os.path.isdir(curpath):
  45. return list()
  46. for f in os.listdir(curpath):
  47. if os.path.isdir(os.path.join(curpath,f)):
  48. continue
  49. if f.endswith('.type'):
  50. continue
  51. if f:
  52. filename, ext = os.path.splitext( f )
  53. tmpurl = '/image/%s/%d/%s' % (media_table, linked_id, f)
  54. if MediaType is None:
  55. filelist.append(tmpurl)
  56. elif MediaType=='Image' and ext.lower() in ['.gif','.jpg','.png','.svg','.jpeg']:
  57. filelist.append(tmpurl)
  58. elif MediaType=='Other' and ext.lower() not in ['.gif','.jpg','.png','.svg','.jpeg']:
  59. filelist.append(tmpurl)
  60. return filelist
  61. def get_thumb(self, media_table, linked_id, MediaType=None):
  62. filelist = list()
  63. curpath = self.get_mediapath(media_table, linked_id, None)
  64. curpath = os.path.join( curpath, 'thumbnails')
  65. if not os.path.isdir(curpath):
  66. return list()
  67. for f in os.listdir(curpath):
  68. filename, ext = os.path.splitext( f )
  69. if os.path.isdir(os.path.join(curpath,f)):
  70. continue
  71. if f.endswith('.type'):
  72. continue
  73. if f:
  74. tmpurl = '/image/%s/%d/thumbnails/%s' % (media_table, linked_id, f)
  75. if MediaType is None:
  76. filelist.append(tmpurl)
  77. elif MediaType=='Image' and len( os.path.splitext(filename)[1] )==0:
  78. filelist.append(tmpurl)
  79. elif MediaType=='Other' and len( os.path.splitext(filename)[1] ):
  80. filelist.append(tmpurl)
  81. return filelist
  82. def get_mediapath(self, media_table, linked_id, name):
  83. linked_id = str(linked_id)
  84. if media_table in ['tiers', 'place', 'salle']:
  85. # Retrieve Slug
  86. if media_table=='tiers':
  87. slug = Tiers.by_id(linked_id).slug
  88. if media_table=='place':
  89. slug = Place.by_id(linked_id).slug
  90. if media_table=='salle':
  91. slug = SallePhy.by_id(linked_id).slug
  92. p = IMAGEPATH + [ media_table ] + [ slug ]
  93. elif media_table=='presse':
  94. # Use Year in linked_id
  95. p = IMAGEPATH + [ media_table ] + [ linked_id ]
  96. elif media_table=='tasks':
  97. # Use Current Year
  98. p = IMAGEPATH + [ str(CurrentYear), media_table ] + [ linked_id ]
  99. elif media_table=='poles':
  100. # Use Current Year
  101. p = IMAGEPATH + [ str(CurrentYear), media_table ] + [ linked_id ]
  102. elif media_table in ['RIB', 'Justif']:
  103. slug = User.by_id(linked_id).slug
  104. p = IMAGEPATH + ['users'] + [ slug ] + [ self.media_table ]
  105. elif media_table=='users':
  106. user = User.by_id(linked_id)
  107. if not user:
  108. raise HTTPNotFound()
  109. else:
  110. slug = user.slug
  111. p = IMAGEPATH + ['users'] + [ slug ]
  112. elif media_table=='event':
  113. ev = Event.by_id(linked_id)
  114. slug = ev.slug
  115. year = ev.for_year
  116. p = IMAGEPATH + ['event'] + [ str(year) ] + [ slug ]
  117. if name:
  118. p += [ name ]
  119. TargetPath = os.path.join('jm2l/upload', *p)
  120. if not os.path.isdir(os.path.dirname(TargetPath)):
  121. os.makedirs(os.path.dirname(TargetPath))
  122. return os.path.join('jm2l/upload', *p)
  123. def ExtMimeIcon(self, mime):
  124. if mime=='application/pdf':
  125. return "/img/PDF.png"
  126. @view_defaults(route_name='media_upload')
  127. class MediaUpload(MediaPath):
  128. def __init__(self, request):
  129. self.request = request
  130. self.media_table = self.request.matchdict.get('media_table')
  131. self.linked_id = self.request.matchdict.get('uid')
  132. if not self.linked_id.isdigit():
  133. raise HTTPBadRequest('Wrong Parameter')
  134. request.response.headers['Access-Control-Allow-Origin'] = '*'
  135. request.response.headers['Access-Control-Allow-Methods'] = 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
  136. def mediapath(self, name):
  137. return self.get_mediapath(self.media_table, self.linked_id, name)
  138. def validate(self, result, filecontent):
  139. # let's say we don't trust the uploader
  140. RealMime = magic.from_buffer( filecontent.read(1024), mime=True)
  141. filecontent.seek(0)
  142. if RealMime!=result['type']:
  143. result['error'] = 'l\'extension du fichier ne correspond pas à son contenu - '
  144. result['error'] += "( %s vs %s )" % (RealMime, result['type'])
  145. return False
  146. # Accept images and mime types listed
  147. if not RealMime in ACCEPTED_MIMES:
  148. if not (IMAGE_TYPES.match(RealMime)):
  149. result['error'] = 'Ce type fichier n\'est malheureusement pas supporté. '
  150. result['error'] += 'Les fichiers acceptées sont les images et pdf.'
  151. return False
  152. if result['size'] < MIN_FILE_SIZE:
  153. result['error'] = 'le fichier est trop petit'
  154. elif result['size'] > MAX_FILE_SIZE:
  155. result['error'] = 'le fichier est trop voluminueux'
  156. #elif not ACCEPT_FILE_TYPES.match(file['type']):
  157. # file['error'] = u'les type de fichiers acceptés sont png, jpg et gif'
  158. else:
  159. return True
  160. return False
  161. def get_file_size(self, file):
  162. file.seek(0, 2) # Seek to the end of the file
  163. size = file.tell() # Get the position of EOF
  164. file.seek(0) # Reset the file position to the beginning
  165. return size
  166. def thumbnailurl(self,name):
  167. return self.request.route_url('media_view',name='thumbnails',
  168. media_table=self.media_table,
  169. uid=self.linked_id) + '/' + name
  170. def thumbnailpath(self,name):
  171. origin = self.mediapath(name)
  172. TargetPath = os.path.join( os.path.dirname(origin), 'thumbnails', name)
  173. if not os.path.isdir(os.path.dirname(TargetPath)):
  174. os.makedirs(os.path.dirname(TargetPath))
  175. return TargetPath
  176. def createthumbnail(self, filename):
  177. image = Image.open( self.mediapath(filename) )
  178. image.thumbnail((THUMBNAIL_SIZE, THUMBNAIL_SIZE), Image.ANTIALIAS)
  179. timage = Image.new('RGBA', (THUMBNAIL_SIZE, THUMBNAIL_SIZE), (255, 255, 255, 0))
  180. timage.paste(
  181. image,
  182. ((THUMBNAIL_SIZE - image.size[0]) / 2, (THUMBNAIL_SIZE - image.size[1]) / 2))
  183. TargetFileName = self.thumbnailpath(filename)
  184. timage.save( TargetFileName )
  185. return self.thumbnailurl( os.path.basename(TargetFileName) )
  186. def pdfthumbnail(self, filename):
  187. TargetFileName = self.thumbnailpath(filename)
  188. Command = ["convert","./%s[0]" % self.mediapath(filename),"./%s_.jpg" % TargetFileName]
  189. Result = subprocess.call(Command)
  190. if Result==0:
  191. image = Image.open( TargetFileName+"_.jpg" )
  192. pdf_indicator = Image.open( "jm2l/static/img/PDF_Thumb_Stamp.png" )
  193. image.thumbnail((THUMBNAIL_SIZE, THUMBNAIL_SIZE), Image.ANTIALIAS)
  194. timage = Image.new('RGBA', (THUMBNAIL_SIZE, THUMBNAIL_SIZE), (255, 255, 255, 0))
  195. # Add thumbnail
  196. timage.paste(
  197. image,
  198. ((THUMBNAIL_SIZE - image.size[0]) / 2, (THUMBNAIL_SIZE - image.size[1]) / 2))
  199. # Stamp with PDF file type
  200. timage.paste(
  201. pdf_indicator,
  202. (timage.size[0]-30, timage.size[1]-30),
  203. pdf_indicator,
  204. )
  205. timage.convert('RGB').save( TargetFileName+".jpg", 'JPEG')
  206. os.unlink(TargetFileName+"_.jpg")
  207. return self.thumbnailurl( os.path.basename(TargetFileName+".jpg") )
  208. return self.ExtMimeIcon('application/pdf')
  209. def svgthumbnail(self, filename):
  210. TargetFileName = self.thumbnailpath(filename)
  211. Command = ["convert","./%s[0]" % self.mediapath(filename),"./%s_.jpg" % TargetFileName]
  212. Result = subprocess.call(Command)
  213. if Result==0:
  214. image = Image.open( TargetFileName+"_.jpg" )
  215. pdf_indicator = Image.open( "jm2l/static/img/svg-icon.png" )
  216. image.thumbnail((THUMBNAIL_SIZE, THUMBNAIL_SIZE), Image.ANTIALIAS)
  217. timage = Image.new('RGBA', (THUMBNAIL_SIZE, THUMBNAIL_SIZE), (255, 255, 255, 0))
  218. # Add thumbnail
  219. timage.paste(
  220. image,
  221. ((THUMBNAIL_SIZE - image.size[0]) / 2, (THUMBNAIL_SIZE - image.size[1]) / 2))
  222. # Stamp with PDF file type
  223. timage.paste(
  224. pdf_indicator,
  225. (timage.size[0]-30, timage.size[1]-30),
  226. pdf_indicator,
  227. )
  228. timage.convert('RGB').save( TargetFileName+".jpg", 'JPEG')
  229. os.unlink(TargetFileName+"_.jpg")
  230. return self.thumbnailurl( os.path.basename(TargetFileName+".jpg") )
  231. return self.ExtMimeIcon('image/svg+xml')
  232. def docthumbnail(self, filename):
  233. TargetFileName = self.thumbnailpath(filename)
  234. # unoconv need a libre office server to be up
  235. Command = ["unoconv", "-f", "pdf", "-e", "PageRange=1", "--output=%s" % TargetFileName, \
  236. "%s[0]" % self.mediapath(filename) ]
  237. # let's take the thumbnail generated inside the document
  238. Command = ["unzip", "-p", self.mediapath(filename), "Thumbnails/thumbnail.png"]
  239. ThumbBytes = subprocess.check_output(Command)
  240. image = Image.open( StringIO.StringIO(ThumbBytes) )
  241. image.thumbnail((THUMBNAIL_SIZE, THUMBNAIL_SIZE), Image.ANTIALIAS)
  242. # Use the correct stamp
  243. f, ext = os.path.splitext( filename )
  244. istamp = [ ('Writer','odt'),
  245. ('Impress','odp'),
  246. ('Calc','ods'),
  247. ('Draw','odg')]
  248. stampfilename = filter(lambda (x,y): ext.endswith(y), istamp)
  249. stamp = Image.open( "jm2l/static/img/%s-icon.png" % stampfilename[0][0])
  250. timage = Image.new('RGBA', (THUMBNAIL_SIZE, THUMBNAIL_SIZE), (255, 255, 255, 0))
  251. # Add thumbnail
  252. timage.paste(
  253. image,
  254. ((THUMBNAIL_SIZE - image.size[0]) / 2, (THUMBNAIL_SIZE - image.size[1]) / 2))
  255. # Stamp with PDF file type
  256. timage.paste(
  257. stamp,
  258. (timage.size[0]-30, timage.size[1]-30),
  259. stamp,
  260. )
  261. timage.convert('RGB').save( TargetFileName+".jpg", 'JPEG')
  262. return self.thumbnailurl( os.path.basename(TargetFileName+".jpg") )
  263. def fileinfo(self,name):
  264. filename = self.mediapath(name)
  265. f, ext = os.path.splitext(name)
  266. if ext!='.type' and os.path.isfile(filename):
  267. info = {}
  268. info['name'] = name
  269. info['size'] = os.path.getsize(filename)
  270. info['url'] = self.request.route_url('media_view',
  271. name=name,
  272. media_table=self.media_table,
  273. uid=self.linked_id)
  274. mime = mimetypes.types_map.get(ext)
  275. if (IMAGE_TYPES.match(mime)):
  276. info['thumbnailUrl'] = self.thumbnailurl(name)
  277. elif mime in ACCEPTED_MIMES:
  278. thumb = self.thumbnailpath("%s%s" % (f, ext))
  279. if os.path.exists( thumb +'.jpg' ):
  280. info['thumbnailUrl'] = self.thumbnailurl(name)+'.jpg'
  281. else:
  282. info['thumbnailUrl'] = self.ExtMimeIcon(mime)
  283. else:
  284. info['thumbnailUrl'] = self.ExtMimeIcon(mime)
  285. info['deleteType'] = DELETEMETHOD
  286. info['deleteUrl'] = self.request.route_url('media_upload',
  287. sep='',
  288. name='',
  289. media_table=self.media_table,
  290. uid=self.linked_id) + '/' + name
  291. if DELETEMETHOD != 'DELETE':
  292. info['deleteUrl'] += '&_method=DELETE'
  293. return info
  294. else:
  295. return None
  296. @view_config(request_method='OPTIONS')
  297. def options(self):
  298. return Response(body='')
  299. @view_config(request_method='HEAD')
  300. def options(self):
  301. return Response(body='')
  302. @view_config(request_method='GET', renderer="json")
  303. def get(self):
  304. p = self.request.matchdict.get('name')
  305. if p:
  306. return self.fileinfo(p)
  307. else:
  308. filelist = []
  309. content = self.mediapath('')
  310. if content and path.exists(content):
  311. for f in os.listdir(content):
  312. n = self.fileinfo(f)
  313. if n:
  314. filelist.append(n)
  315. return { "files":filelist }
  316. @view_config(request_method='DELETE', xhr=True, accept="application/json", renderer='json')
  317. def delete(self):
  318. import json
  319. filename = self.request.matchdict.get('name')
  320. try:
  321. os.remove(self.mediapath(filename) + '.type')
  322. except IOError:
  323. pass
  324. except OSError:
  325. pass
  326. try:
  327. os.remove(self.thumbnailpath(filename))
  328. except IOError:
  329. pass
  330. except OSError:
  331. pass
  332. try:
  333. os.remove(self.thumbnailpath(filename+".jpg"))
  334. except IOError:
  335. pass
  336. except OSError:
  337. pass
  338. try:
  339. os.remove(self.mediapath(filename))
  340. except IOError:
  341. return False
  342. return True
  343. @view_config(request_method='POST', xhr=True, accept="application/json", renderer='json')
  344. def post(self):
  345. if self.request.matchdict.get('_method') == "DELETE":
  346. return self.delete()
  347. results = []
  348. for name, fieldStorage in self.request.POST.items():
  349. if isinstance(fieldStorage,unicode):
  350. continue
  351. result = {}
  352. result['name'] = os.path.basename(fieldStorage.filename)
  353. result['type'] = fieldStorage.type
  354. result['size'] = self.get_file_size(fieldStorage.file)
  355. if self.validate(result, fieldStorage.file):
  356. with open( self.mediapath(result['name'] + '.type'), 'w') as f:
  357. f.write(result['type'])
  358. with open( self.mediapath(result['name']), 'wb') as f:
  359. shutil.copyfileobj( fieldStorage.file , f)
  360. if re.match(IMAGE_TYPES, result['type']):
  361. result['thumbnailUrl'] = self.createthumbnail(result['name'])
  362. elif result['type']=='application/pdf':
  363. result['thumbnailUrl'] = self.pdfthumbnail(result['name'])
  364. elif result['type']=='image/svg+xml':
  365. result['thumbnailUrl'] = self.svgthumbnail(result['name'])
  366. elif result['type'].startswith('application/vnd'):
  367. result['thumbnailUrl'] = self.docthumbnail(result['name'])
  368. else:
  369. result['thumbnailUrl'] = self.ExtMimeIcon(result['type'])
  370. result['deleteType'] = DELETEMETHOD
  371. result['deleteUrl'] = self.request.route_url('media_upload',
  372. sep='',
  373. name='',
  374. media_table=self.media_table,
  375. uid=self.linked_id) + '/' + result['name']
  376. result['url'] = self.request.route_url('media_view',
  377. media_table=self.media_table,
  378. uid=self.linked_id,
  379. name=result['name'])
  380. if DELETEMETHOD != 'DELETE':
  381. result['deleteUrl'] += '&_method=DELETE'
  382. results.append(result)
  383. return {"files":results}
  384. @view_defaults(route_name='media_view')
  385. class MediaView(MediaPath):
  386. def __init__(self,request):
  387. self.request = request
  388. self.media_table = self.request.matchdict.get('media_table')
  389. self.linked_id = self.request.matchdict.get('uid')
  390. def mediapath(self,name):
  391. return self.get_mediapath(self.media_table, self.linked_id, name)
  392. @view_config(request_method='GET', http_cache = (EXPIRATION_TIME, {'public':True}))
  393. def get(self):
  394. name = self.request.matchdict.get('name')
  395. try:
  396. with open( self.mediapath( os.path.basename(name) ) + '.type', 'r', 16) as f:
  397. test = f.read()
  398. self.request.response.content_type = test
  399. except IOError:
  400. pass
  401. try:
  402. self.request.response.body_file = open( self.mediapath(name), 'rb', 10000)
  403. except IOError:
  404. raise NotFound
  405. return self.request.response
  406. ##return Response(app_iter=ImgHandle, content_type = 'image/png')