Flask Admin: загрузка файлов и обработка формы в модели
Мне довольно часто требуется кастомизировать базовую модель Flask Admin для того чтобы например загружать изображения. В интернете есть довольно много примеров как сделать загрузку файлов через `flask_admin.form.upload`, но он не дает полного контроля над происходящим и если потребуется сделать что-то более сложное, придется импровизировать.
Поиграв с дебагером и почитав документацию, я пришел к выводу, что в моем случае хорошо подойдет 2 функции модели edit_form и create_form. Они вызываются при создании формы из реквеста, и до того как произойдет валидация их можно переопределить.
Начнем
Есть модель:
class StorageModel(db.Model): __tablename__ = 'storage' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Unicode(64)) path = db.Column(db.Unicode(128)) type = db.Column(db.Unicode(3)) create_date = db.Column(db.DateTime, default=datetime.datetime.now)
Создаем view и переопределяем обработчики форм
from flask_admin.contrib import sqla from flask_admin import form import random import os class StorageAdminModel(sqla.ModelView): form_extra_fields = { 'file': form.FileUploadField('file') } def _change_path_data(self, _form): try: storage_file = _form.file.data if storage_file is not None: hash = random.getrandbits(128) ext = storage_file.filename.split('.')[-1] path = '%s.%s' % (hash, ext) storage_file.save( os.path.join(app.config['STORAGE'], path) ) _form.name.data = _form.name.data or storage_file.filename _form.path.data = path _form.type.data = ext del _form.file except Exception as ex: pass return _form def edit_form(self, obj=None): return self._change_path_data( super(StorageAdminModel, self).edit_form(obj) ) def create_form(self, obj=None): return self._change_path_data( super(StorageAdminModel, self).create_form(obj) ) admin.add_view(StorageAdminModel(StorageModel, db.session))
- edit_form - вызывается при редактировании записи
- create_form - соответственно при создании
Мы перехватываем формирование формы и обрабатываем данные до того как они попадут в модель. Здесь важно отметить, ключи формы должны соответствовать вашей модели данных в базе данных, чтобы не свалились ошибки.
Через form_extra_fields мы добавляем новое поле file и задаем ему тип FileUploadField. Это добавит в форму новый field с возможностью загрузки файла. После обработки данных удаляем его из формы del _form.file
Вот вообщем и все, мы получили дополнительные данные, записали их в нужные нам поля.
В результате у вас должно получится как ниже на скриншоте:
Бонус
На списке объектов, было бы неплохо сразу видеть то что мы загрузи, если это картинка, показать картинку, если это запись, дать возможность ее воспроизвести. С этим нам поможет column_formatters. Добавим в наш view еще немного магии
def _list_thumbnail(view, context, model, name): if not model.path: return '' url = url_for('static', filename=os.path.join('storage/', model.path)) if model.type in ['jpg', 'jpeg', 'png', 'svg', 'gif']: return Markup('<img src="%s" width="100">' % url) if model.type in ['mp3']: return Markup('<audio controls="controls"><source src="%s" type="audio/mpeg" /></audio>' % url) column_formatters = { 'path': _list_thumbnail }
И смотрим что получилось:
Весь файл целиком:
from app import app from app import admin from app import db from app.models.StorageModel import StorageModel from jinja2 import Markup from flask import url_for from flask_admin import form from flask_admin.contrib import sqla import random import os class StorageAdminModel(sqla.ModelView): def _list_thumbnail(view, context, model, name): if not model.path: return '' url = url_for('static', filename=os.path.join('storage/', model.path)) if model.type in ['jpg', 'jpeg', 'png', 'svg', 'gif']: return Markup('' % url) if model.type in ['mp3']: return Markup('' % url) column_formatters = { 'path': _list_thumbnail } form_extra_fields = { 'file': form.FileUploadField('file') } def _change_path_data(self, _form): try: storage_file = _form.file.data if storage_file is not None: hash = random.getrandbits(128) ext = storage_file.filename.split('.')[-1] path = '%s.%s' % (hash, ext) storage_file.save( os.path.join(app.config['STORAGE'], path) ) _form.name.data = _form.name.data or storage_file.filename _form.path.data = path _form.type.data = ext del _form.file except Exception as ex: pass return _form def edit_form(self, obj=None): return self._change_path_data( super(StorageAdminModel, self).edit_form(obj) ) def create_form(self, obj=None): return self._change_path_data( super(StorageAdminModel, self).create_form(obj) ) admin.add_view(StorageAdminModel(StorageModel, db.session))