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.
 
 
 
 
 

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