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.
 
 
 
 
 

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