123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- from flask_babel import gettext
- from markupsafe import Markup
- from werkzeug.datastructures import FileStorage
- from wtforms import fields, ValidationError
- from wtforms.widgets import html_params
- from .filemanager import FileManager, ImageManager
- try:
- from wtforms.fields.core import _unset_value as unset_value
- except ImportError:
- from wtforms.utils import unset_value
- """
- Based and thanks to
- https://github.com/mrjoes/flask-admin/blob/master/flask_admin/form/upload.py
- """
- class BS3FileUploadFieldWidget(object):
- empty_template = (
- '<div class="input-group">'
- '<span class="input-group-addon"><i class="fa fa-upload"></i>'
- "</span>"
- '<input class="form-control" %(file)s/>'
- "</div>"
- )
- data_template = (
- "<div>"
- " <input %(text)s>"
- ' <input type="checkbox" name="%(marker)s">Delete</input>'
- "</div>"
- '<div class="input-group">'
- '<span class="input-group-addon"><i class="fa fa-upload"></i>'
- "</span>"
- '<input class="form-control" %(file)s/>'
- "</div>"
- )
- def __call__(self, field, **kwargs):
- kwargs.setdefault("id", field.id)
- kwargs.setdefault("name", field.name)
- template = self.data_template if field.data else self.empty_template
- return Markup(
- template
- % {
- "text": html_params(type="text", value=field.data),
- "file": html_params(type="file", **kwargs),
- "marker": "_%s-delete" % field.name,
- }
- )
- class BS3ImageUploadFieldWidget(object):
- empty_template = (
- '<div class="input-group">'
- '<span class="input-group-addon"><span class="glyphicon glyphicon-upload"></span>'
- "</span>"
- '<input class="form-control" %(file)s/>'
- "</div>"
- )
- data_template = (
- '<div class="thumbnail">'
- " <img %(image)s>"
- ' <input type="checkbox" name="%(marker)s">Delete</input>'
- "</div>"
- '<div class="input-group">'
- '<span class="input-group-addon"><span class="glyphicon glyphicon-upload"></span>'
- "</span>"
- '<input class="form-control" %(file)s/>'
- "</div>"
- )
- def __call__(self, field, **kwargs):
- kwargs.setdefault("id", field.id)
- kwargs.setdefault("name", field.name)
- args = {
- "file": html_params(type="file", **kwargs),
- "marker": "_%s-delete" % field.name,
- }
- if field.data:
- url = self.get_url(field)
- args["image"] = html_params(src=url)
- template = self.data_template
- else:
- template = self.empty_template
- return Markup(template % args)
- def get_url(self, field):
- im = ImageManager()
- return im.get_url(field.data)
- # Fields
- class FileUploadField(fields.StringField):
- """
- Customizable file-upload field.
- Saves file to configured path, handles updates and deletions.
- Inherits from `StringField`, resulting filename will be stored as string.
- """
- widget = BS3FileUploadFieldWidget()
- def __init__(self, label=None, validators=None, filemanager=None, **kwargs):
- """
- Constructor.
- :param label:
- Display label
- :param validators:
- Validators
- """
- self.filemanager = filemanager or FileManager()
- self._should_delete = False
- super().__init__(label, validators, **kwargs)
- def process_on_delete(self, obj):
- """Override this method to make customised updates to the object
- when the stored file is going to be deleted."""
- pass
- def process_on_store(self, obj, byte_stream):
- """Override this method to make customised updates to the object
- when a file is going to be stored.
- This may be used to parse file content and extract values for
- additional fields.
- Note: as populate_obj() on form fields my be called in an arbitrary
- order, do not assume that other fields in obj have been correctly set.
- If an extra information (from other fields) is necessary for parsing
- the supplied file content, a form-field validator may be used to copy
- it directly from the form to this field.
- :param obj: model object
- :param byte_stream: file contents
- """
- pass
- def pre_validate(self, form):
- if (
- self.data
- and isinstance(self.data, FileStorage)
- and not self.filemanager.is_file_allowed(self.data.filename)
- ):
- raise ValidationError(gettext("Invalid file extension"))
- def process(self, formdata, data=unset_value, **kwargs):
- if formdata:
- marker = "_%s-delete" % self.name
- if marker in formdata:
- self._should_delete = True
- return super().process(formdata, data, **kwargs)
- def populate_obj(self, obj, name):
- field = getattr(obj, name, None)
- if field:
- # If field should be deleted, clean it up
- if self._should_delete:
- self.process_on_delete(obj)
- self.filemanager.delete_file(field)
- setattr(obj, name, None)
- return
- if self.data and isinstance(self.data, FileStorage):
- if field:
- self.process_on_delete(obj)
- self.filemanager.delete_file(field)
- position = self.data.stream.tell()
- self.process_on_store(obj, self.data.stream)
- self.data.stream.seek(position)
- filename = self.filemanager.generate_name(obj, self.data)
- filename = self.filemanager.save_file(self.data, filename)
- setattr(obj, name, filename)
- class ImageUploadField(fields.StringField):
- """
- Image upload field.
- """
- widget = BS3ImageUploadFieldWidget()
- def __init__(self, label=None, validators=None, imagemanager=None, **kwargs):
- self.imagemanager = imagemanager or ImageManager()
- self._should_delete = False
- super().__init__(label, validators, **kwargs)
- def pre_validate(self, form):
- if (
- self.data
- and isinstance(self.data, FileStorage)
- and not self.imagemanager.is_file_allowed(self.data.filename)
- ):
- raise ValidationError(gettext("Invalid file extension"))
- def process(self, formdata, data=unset_value, **kwargs):
- if formdata:
- marker = "_%s-delete" % self.name
- if marker in formdata:
- self._should_delete = True
- return super().process(formdata, data, **kwargs)
- def populate_obj(self, obj, name):
- field = getattr(obj, name, None)
- size = obj.__mapper__.columns[name].type.size
- thumbnail_size = obj.__mapper__.columns[name].type.thumbnail_size
- if field:
- # If field should be deleted, clean it up
- if self._should_delete:
- self.imagemanager.delete_file(field)
- setattr(obj, name, None)
- return
- if self.data and isinstance(self.data, FileStorage):
- if field:
- self.imagemanager.delete_file(field)
- filename = self.imagemanager.generate_name(obj, self.data)
- filename = self.imagemanager.save_file(
- self.data, filename, size, thumbnail_size
- )
- setattr(obj, name, filename)
|