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.
 
 
 
 
 

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