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))
' % 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))
                                
                             Anton Volkov
            20.02.2020 в 23:10
            Anton Volkov
            20.02.2020 в 23:10
         LegGnom
            21.02.2020 в 22:07
            LegGnom
            21.02.2020 в 22:07
         Spirit412
            03.09.2020 в 14:25
            Spirit412
            03.09.2020 в 14:25
         Valera Chornenkiiy
            27.04.2021 в 22:24
            Valera Chornenkiiy
            27.04.2021 в 22:24