# builtins import os import mimetypes # third-party import markupsafe import werkzeug import flask import peewee # internals from application import app import form import database import admin import markdown @app.abstract class Upload(admin.Named): subdirectory = None published = peewee.BooleanField(null=False, default=False, verbose_name='Published') filename = peewee.CharField(null=False, unique=True) description = markdown.MarkdownTextField(null=True) allowed_extensions = '*' @property def path(self): return f"upload/{self.subdirectory}/{self.filename}" @property def media_type(self): # essentially the same logic werkzeug.utils.send_file uses mimetype, _ = mimetypes.guess_type(self.filename) if mimetype: return mimetype return 'application/octet-stream' @property def filesize(self): try: return os.path.getsize(self.path) except OSError: return float('nan') # not a number def render(self, mode='full', format='html'): if not (self.published or flask.g.user): return markupsafe.Markup('
Unpublished embedded content
') return super().render(mode=mode, format=format) def form(self, mode, **kwargs): f = super().form(mode, **kwargs) if self.is_in_db: teaser = self.render('inline') if f.intro: f.intro = teaser + f.intro else: f.intro = teaser return f def form_field(self, field_name): if field_name == 'filename': db_field = getattr(self.__class__, field_name) field_label = 'File' field_help = db_field.help_text field_value = getattr(self, field_name) field_required = not self.id return form.File(label=field_label, value=field_value, help=field_help, required=field_required) return super().form_field(field_name) def form_field_handle(self, field, mode): if field.name == 'filename': if field.valid and field.value: self.write_file(field.value) flask.flash("Stored new file.", 'info') def write_file(self, file: werkzeug.datastructures.file_storage.FileStorage): extension = file.filename.split('.')[-1] filename = f'{self.name}.{extension}' filename_clean = werkzeug.utils.secure_filename(filename) if self.filename: try: os.unlink(self.path) except FileNotFoundError as e: app.logger.warning("Didn't find associated file '{self.filename}' for {self.__class__.__name__} '{self.name}', storing new file.") self.filename = filename_clean file.save(self.path) def url(self, mode='raw'): if mode in ('raw', 'download'): url = f'/upload/{self.subdirectory}/{self.name}/' # FIXME: make this less hacky, if possible if mode == 'download': url += '?download' return url return super().url(mode=mode) def delete_instance(self, **kwargs): try: os.unlink(self.path) except FileNotFoundError as e: # all other exception types not caught so db record won't be deleted app.logger.warning("Didn't find associated file '{self.filename}' for {self.__class__.__name__} '{self.name}', proceeding with record deletion.") flask.flash(f"Didn't find associated file '{self.filename}', deleting record anyhow.", 'warning') super().delete_instance(**kwargs) class File(Upload): # NOTE: collides with form.File in ClassAware attributes # this is fine tho, because it's defined later (i.e. overwrites) # references to form.File which you never want to look up through # ClassAware anyhow. And if you do, you can still go through a # non-common Ancestor, i.e. form.Field subdirectory = 'file' class Image(Upload): subdirectory = 'image' class Audio(Upload): subdirectory = 'audio' class Video(Upload): subdirectory = 'video' class GalleryForm(admin.AutoForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.mode == 'edit': self['items'] = form.Fieldset() for item in self.administerable.items_ordered: fieldset_item = form.ProxyFieldset(item.form('edit'), name=f'item-{item.id}') fieldset_item['gallery_id'] = form.ValueField(value=self.administerable.id) del(fieldset_item['order_pos']) fieldset_delete = form.ProxyFieldset(item.form('delete'), name=f'item-{item.id}-delete') fieldset_delete.intro = None fieldset_delete.buttons['delete'].label = '⊗' fieldset_order = form.Fieldset(name='order') fieldset_order.buttons['up'] = form.Button(name='up', label='⌃') fieldset_order.buttons['down'] = form.Button(name='down', label='⌄') # HERE fieldset = form.Fieldset(name=f'{fieldset_item.name}-wrapper', extra_classes=['galleryitem-wrapper']) fieldset['item'] = fieldset_item fieldset['delete'] = fieldset_delete fieldset['order'] = fieldset_order self['items'][fieldset.name] = fieldset fieldset = form.ProxyFieldset(GalleryItem().form('create'), name='item-new') fieldset['gallery_id'] = form.ValueField(value=self.administerable.id) del(fieldset['order_pos']) self['items']['new'] = fieldset def handle(self, skip_bind=False): if 'submit' not in flask.request.form: self.errors.append(form.ValidationError("Missing submit information.")) return submit = flask.request.form.get('submit') if not skip_bind: self.bind(flask.request.form, flask.request.files) fieldset = self.submitted_fieldset(submit) if fieldset is not None: if submit.endswith('.order.up') or submit.endswith('.order.down'): # one of the arrow buttons to reorder propagandapieceitems was pressed self.process_order(submit, fieldset) return flask.redirect('') fieldset.handle(skip_bind=True) # execute fieldset handle, but ignore any returned redirects return flask.redirect('') # instead reload current page to update form else: submit_relative = self.submit_relative(submit) self.validate(submit_relative) return self.process(submit_relative) def process_order(self, submit, fieldset): inner_fieldset = fieldset.submitted_fieldset(submit) item = inner_fieldset['item'].form.administerable # grab PropagandaPieceItem from ProxyFieldset if submit.endswith('.up'): item.order_pos -= 1 elif submit.endswith('.down'): item.order_pos += 1 flask.flash("Moved gallery item.", 'success') item.save() class Gallery(admin.Named): autoform_class = GalleryForm @property def items_ordered(self): return self.items.order_by(GalleryItem.order_pos) class GalleryItemForm(admin.AutoForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.mode == 'edit': self['file'] = form.File() class GalleryItem(admin.Administerable): autoform_class = GalleryItemForm gallery = peewee.ForeignKeyField(Gallery, null=False, backref='items', verbose_name='Gallery', on_delete='CASCADE') order_pos = peewee.IntegerField(null=False, default=0, verbose_name='Order position') image = peewee.ForeignKeyField(Image, null=False, verbose_name='Image', backref='in_galleries', on_delete='CASCADE') description = markdown.MarkdownTextField(null=True, verbose_name='Description') def form(self, mode): f = super().form(mode) if self.is_in_db: teaser = self.image.render('teaser') if f.intro: f.intro = teaser + f.intro else: f.intro = teaser return f upload_class_lookup = { 'file': File, 'image': Image, 'audio': Audio, 'video': Video, } @app.route('/upload///') def serve_upload(class_key, name): if class_key in upload_class_lookup: cls = upload_class_lookup[class_key] instance = cls.load(name) # this failing is handled by error_catchall if flask.g.user or instance.published: download = 'download' in flask.request.args return flask.send_from_directory(f'upload/{cls.subdirectory}', instance.filename, as_attachment=download) flask.abort(404)