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))