Загрузка изображения Flask-Admin + SQLAlchemy работает в производственной среде MySQL, но не в тестовом примере SQLite: InterfaceError

Я создал веб-приложение с использованием Flask, Flask-Admin и Flask-SQLAlchemy, в которое администратор может загружать изображения. Функциональность загрузки изображений была в значительной степени имитирована из этого примера Flask-Admin . Для производственного веб-сайта я использую MySQL, и загрузка работает отлично. Однако в тестовом наборе я использую базу данных SQLite с отображением памяти, и любая попытка загрузить изображение через ту же форму терпит неудачу с InterfaceError. См. этот Свод для получения полной информации и сокращенного тестового примера.

Похоже, это может иметь какое-то отношение к сопоставлению типов, когда серверная часть MySQL SQLAlchemy, похоже, понимает, что имя файла загруженного изображения должно быть вставлено в оператор SQL, а серверная часть SQLite - нет. Однако пример Flask-Admin, на который я ссылался выше, работает абсолютно нормально и также основан на SQLite.

Кто может сказать мне, что не так и что нужно сделать, чтобы тест прошел?

Отредактируйте, чтобы добавить: оказалось, что проблема уже была известна разработчикам Flask-Admin. См. тикет на GitHub.


person Julian    schedule 08.07.2015    source источник
comment
Можете ли вы распечатать свой класс, содержащий SelectField () и QuerySelectField ()?   -  person alagu    schedule 27.07.2015
comment
@kuttyraj Я не понимаю вашего запроса, не могли бы вы уточнить?   -  person Julian    schedule 27.07.2015


Ответы (1)


Да, ты прав. В этом причина вашей проблемы. Проблема в различии бэкэндов SQLite и MySQL.

Как вы можете видеть в трассировке стека, он пытается привязать параметр типа FileStorage и терпит неудачу.

InterfaceError: (sqlite3.InterfaceError) Error binding parameter 0 - probably unsupported type. [SQL: u'SELECT picture.id AS picture_id, picture.name AS picture_name, picture.path AS picture_path \nFROM picture \nWHERE picture.path = ?'] [parameters: (<FileStorage: u'openclipart_hector_gomez_landscape.png' ('image/png')>,)]

Место, где вы хотите поставить точку останова, будет в методе do_execute() в модуле sqlalchemy.engine.default.

Серверная часть SQLite - это двоичное расширение, а cursor происходит от двоичного расширения (_sqlite3.so). Это двоичное расширение получает параметр типа FileStorage и пытается преобразовать его в представление SQL, вызывая метод FileStorage.__conform__(). Но у класса нет такого метода. Вот почему это не удается.

С другой стороны, серверная часть MySQL происходит из чистого модуля Python с именем MySQLdb. Таким образом, он вызывает метод MySQLdb.cursors.BaseCursor.execute(), который, в частности, преобразует параметр типа FileStorage в представление SQL путем вызова db.literal(), который завершится вызовом FileStorage.__repr__(). И вы получите следующий запрос:

'SELECT picture.id AS picture_id, picture.name AS picture_name, picture.path AS picture_path FROM picture WHERE picture.path = \\'<FileStorage: u\\\\'images.jpeg\\\\' (\\\\'image/jpeg\\\\')>\\''

Неожиданно, правда? Теперь вы не уверены, что он правильно работает с MySQL. Ты? Просто попробуйте создать два изображения с одним и тем же файлом, и вы получите Integrity error. (_mysql_exceptions.IntegrityError) (1062, "Duplicate entry 'images.jpeg' for key 'path'") [SQL: u'INSERT INTO picture (name, path) VALUES (%s, %s)'] [parameters: ('Test', 'images.jpeg')] вместо значимого сообщения об ошибке.

Почему это работает в примере от Flask-Admin?

Вы устанавливаете ограничение unique на path столбец вашей модели. Именно отсюда и возникает сбойный запрос SQL. Прежде чем пытаться вставить новый, он проверяет, существует ли уже изображение с таким же путем в базе данных или нет.

Как исправить

Проблема заключается в ошибке в flask_admin.contrib.sqla.validators.Unique валидаторе или flask_admin.form.upload.FileUploadField (они несовместимы). Валидатор должен использовать то же значение, которое было введено в модель методом flask_admin.form.upload.FileUploadField.populate_obj(), вместо прямой передачи FileStorage в запрос к базе данных. Просто поднимите проблему на GitHub и укажите этот вопрос.

Я не думаю, что это можно легко исправить в вашем тестовом примере, поскольку это довольно серьезная ошибка в библиотеке, на которую вы полагаетесь. Конечно, при условии, что вы хотите сохранить ограничение unique на поле path.

person Yaroslav Admin    schedule 28.07.2015
comment
Отличный ответ, очень хорошо исследованный. Я отправлю билет, как вы предложили. - person Julian; 28.07.2015