upload.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. from flask_babel import gettext
  2. from markupsafe import Markup
  3. from werkzeug.datastructures import FileStorage
  4. from wtforms import fields, ValidationError
  5. from wtforms.widgets import html_params
  6. from .filemanager import FileManager, ImageManager
  7. try:
  8. from wtforms.fields.core import _unset_value as unset_value
  9. except ImportError:
  10. from wtforms.utils import unset_value
  11. """
  12. Based and thanks to
  13. https://github.com/mrjoes/flask-admin/blob/master/flask_admin/form/upload.py
  14. """
  15. class BS3FileUploadFieldWidget(object):
  16. empty_template = (
  17. '<div class="input-group">'
  18. '<span class="input-group-addon"><i class="fa fa-upload"></i>'
  19. "</span>"
  20. '<input class="form-control" %(file)s/>'
  21. "</div>"
  22. )
  23. data_template = (
  24. "<div>"
  25. " <input %(text)s>"
  26. ' <input type="checkbox" name="%(marker)s">Delete</input>'
  27. "</div>"
  28. '<div class="input-group">'
  29. '<span class="input-group-addon"><i class="fa fa-upload"></i>'
  30. "</span>"
  31. '<input class="form-control" %(file)s/>'
  32. "</div>"
  33. )
  34. def __call__(self, field, **kwargs):
  35. kwargs.setdefault("id", field.id)
  36. kwargs.setdefault("name", field.name)
  37. template = self.data_template if field.data else self.empty_template
  38. return Markup(
  39. template
  40. % {
  41. "text": html_params(type="text", value=field.data),
  42. "file": html_params(type="file", **kwargs),
  43. "marker": "_%s-delete" % field.name,
  44. }
  45. )
  46. class BS3ImageUploadFieldWidget(object):
  47. empty_template = (
  48. '<div class="input-group">'
  49. '<span class="input-group-addon"><span class="glyphicon glyphicon-upload"></span>'
  50. "</span>"
  51. '<input class="form-control" %(file)s/>'
  52. "</div>"
  53. )
  54. data_template = (
  55. '<div class="thumbnail">'
  56. " <img %(image)s>"
  57. ' <input type="checkbox" name="%(marker)s">Delete</input>'
  58. "</div>"
  59. '<div class="input-group">'
  60. '<span class="input-group-addon"><span class="glyphicon glyphicon-upload"></span>'
  61. "</span>"
  62. '<input class="form-control" %(file)s/>'
  63. "</div>"
  64. )
  65. def __call__(self, field, **kwargs):
  66. kwargs.setdefault("id", field.id)
  67. kwargs.setdefault("name", field.name)
  68. args = {
  69. "file": html_params(type="file", **kwargs),
  70. "marker": "_%s-delete" % field.name,
  71. }
  72. if field.data:
  73. url = self.get_url(field)
  74. args["image"] = html_params(src=url)
  75. template = self.data_template
  76. else:
  77. template = self.empty_template
  78. return Markup(template % args)
  79. def get_url(self, field):
  80. im = ImageManager()
  81. return im.get_url(field.data)
  82. # Fields
  83. class FileUploadField(fields.StringField):
  84. """
  85. Customizable file-upload field.
  86. Saves file to configured path, handles updates and deletions.
  87. Inherits from `StringField`, resulting filename will be stored as string.
  88. """
  89. widget = BS3FileUploadFieldWidget()
  90. def __init__(self, label=None, validators=None, filemanager=None, **kwargs):
  91. """
  92. Constructor.
  93. :param label:
  94. Display label
  95. :param validators:
  96. Validators
  97. """
  98. self.filemanager = filemanager or FileManager()
  99. self._should_delete = False
  100. super().__init__(label, validators, **kwargs)
  101. def process_on_delete(self, obj):
  102. """Override this method to make customised updates to the object
  103. when the stored file is going to be deleted."""
  104. pass
  105. def process_on_store(self, obj, byte_stream):
  106. """Override this method to make customised updates to the object
  107. when a file is going to be stored.
  108. This may be used to parse file content and extract values for
  109. additional fields.
  110. Note: as populate_obj() on form fields my be called in an arbitrary
  111. order, do not assume that other fields in obj have been correctly set.
  112. If an extra information (from other fields) is necessary for parsing
  113. the supplied file content, a form-field validator may be used to copy
  114. it directly from the form to this field.
  115. :param obj: model object
  116. :param byte_stream: file contents
  117. """
  118. pass
  119. def pre_validate(self, form):
  120. if (
  121. self.data
  122. and isinstance(self.data, FileStorage)
  123. and not self.filemanager.is_file_allowed(self.data.filename)
  124. ):
  125. raise ValidationError(gettext("Invalid file extension"))
  126. def process(self, formdata, data=unset_value, **kwargs):
  127. if formdata:
  128. marker = "_%s-delete" % self.name
  129. if marker in formdata:
  130. self._should_delete = True
  131. return super().process(formdata, data, **kwargs)
  132. def populate_obj(self, obj, name):
  133. field = getattr(obj, name, None)
  134. if field:
  135. # If field should be deleted, clean it up
  136. if self._should_delete:
  137. self.process_on_delete(obj)
  138. self.filemanager.delete_file(field)
  139. setattr(obj, name, None)
  140. return
  141. if self.data and isinstance(self.data, FileStorage):
  142. if field:
  143. self.process_on_delete(obj)
  144. self.filemanager.delete_file(field)
  145. position = self.data.stream.tell()
  146. self.process_on_store(obj, self.data.stream)
  147. self.data.stream.seek(position)
  148. filename = self.filemanager.generate_name(obj, self.data)
  149. filename = self.filemanager.save_file(self.data, filename)
  150. setattr(obj, name, filename)
  151. class ImageUploadField(fields.StringField):
  152. """
  153. Image upload field.
  154. """
  155. widget = BS3ImageUploadFieldWidget()
  156. def __init__(self, label=None, validators=None, imagemanager=None, **kwargs):
  157. self.imagemanager = imagemanager or ImageManager()
  158. self._should_delete = False
  159. super().__init__(label, validators, **kwargs)
  160. def pre_validate(self, form):
  161. if (
  162. self.data
  163. and isinstance(self.data, FileStorage)
  164. and not self.imagemanager.is_file_allowed(self.data.filename)
  165. ):
  166. raise ValidationError(gettext("Invalid file extension"))
  167. def process(self, formdata, data=unset_value, **kwargs):
  168. if formdata:
  169. marker = "_%s-delete" % self.name
  170. if marker in formdata:
  171. self._should_delete = True
  172. return super().process(formdata, data, **kwargs)
  173. def populate_obj(self, obj, name):
  174. field = getattr(obj, name, None)
  175. size = obj.__mapper__.columns[name].type.size
  176. thumbnail_size = obj.__mapper__.columns[name].type.thumbnail_size
  177. if field:
  178. # If field should be deleted, clean it up
  179. if self._should_delete:
  180. self.imagemanager.delete_file(field)
  181. setattr(obj, name, None)
  182. return
  183. if self.data and isinstance(self.data, FileStorage):
  184. if field:
  185. self.imagemanager.delete_file(field)
  186. filename = self.imagemanager.generate_name(obj, self.data)
  187. filename = self.imagemanager.save_file(
  188. self.data, filename, size, thumbnail_size
  189. )
  190. setattr(obj, name, filename)