Ошибка Flask-admin при редактировании записи, в которой первичный ключ имеет enum - PullRequest
1 голос
/ 25 сентября 2019

Использование postgres и попытка редактировать запись с первичным ключом, содержащим перечисление, будет ошибка .

Это потому, что идентификатор содержит имя класса перечисления, то есть MyEnum..ONE, в то время как это должно быть только ONE: http://127.0.0.1:5000/admin/mymodel/edit/?id=1%2CMyEnum..ONE&url=%2Fadmin%2Fmymodel%2F

test.py

from enum import Enum

from flask import Flask
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import PrimaryKeyConstraint

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)


class MyEnum(Enum):
    ONE = 1


class MyModel(db.Model):
    id = db.Column(db.Integer)
    enum = db.Column(db.Enum(MyEnum))

    __table_args__ = (
        PrimaryKeyConstraint('id', 'enum', name='pk_mymodel'),
    )


class MyAdminView(ModelView):
    column_display_pk = True


admin = Admin(app)
admin.add_view(MyAdminView(MyModel, db.session))

if __name__ == '__main__':

    # Start app
    db.drop_all()
    db.create_all()
    db.session.add(MyModel(id=1, enum=MyEnum.ONE))
    db.session.commit()

    app.run(debug=True)

Запустите с python test.py, затем перейдите к http://127.0.0.1:5000/admin/mymodel и нажмите на правкузапись:

enter image description here

2019-09-25 01:49:22,685 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2019-09-25 01:49:22,686 INFO sqlalchemy.engine.base.Engine SELECT my_model.id AS my_model_id, my_model.enum AS my_model_enum 
FROM my_model 
WHERE my_model.id = %(param_1)s AND my_model.enum = %(param_2)s
2019-09-25 01:49:22,687 INFO sqlalchemy.engine.base.Engine {'param_1': '1', 'param_2': 'MyEnum.ONE'}
127.0.0.1 - - [25/Sep/2019 01:49:22] "GET /admin/mymodel/edit/?id=1%2CMyEnum..ONE&url=%2Fadmin%2Fmymodel%2F HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1249, in _execute_context
    cursor, statement, parameters, context
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/engine/default.py", line 552, in do_execute
    cursor.execute(statement, parameters)
psycopg2.errors.InvalidTextRepresentation: invalid input value for enum myenum: "MyEnum.ONE"
LINE 3: WHERE my_model.id = '1' AND my_model.enum = 'MyEnum.ONE'
                                                    ^

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask_admin/base.py", line 69, in inner
    return self._run_view(f, *args, **kwargs)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask_admin/base.py", line 368, in _run_view
    return fn(self, *args, **kwargs)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask_admin/model/base.py", line 2119, in edit_view
    model = self.get_one(id)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/flask_admin/contrib/sqla/view.py", line 1089, in get_one
    return self.session.query(self.model).get(tools.iterdecode(id))
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/orm/query.py", line 959, in get
    return self._get_impl(ident, loading.load_on_pk_identity)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/orm/query.py", line 1069, in _get_impl
    return db_load_fn(self, primary_key_identity)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/orm/loading.py", line 282, in load_on_pk_identity
    return q.one()
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/orm/query.py", line 3292, in one
    ret = self.one_or_none()
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/orm/query.py", line 3261, in one_or_none
    ret = list(self)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/orm/query.py", line 3334, in __iter__
    return self._execute_and_instances(context)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/orm/query.py", line 3359, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 988, in execute
    return meth(self, multiparams, params)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/sql/elements.py", line 287, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1107, in _execute_clauseelement
    distilled_params,
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1253, in _execute_context
    e, statement, parameters, cursor, context
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1473, in _handle_dbapi_exception
    util.raise_from_cause(sqlalchemy_exception, exc_info)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/util/compat.py", line 398, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/util/compat.py", line 152, in reraise
    raise value.with_traceback(tb)
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1249, in _execute_context
    cursor, statement, parameters, context
  File "/Users/okomarov/.virtualenvs/testing/lib/python3.7/site-packages/sqlalchemy/engine/default.py", line 552, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.DataError: (psycopg2.errors.InvalidTextRepresentation) invalid input value for enum myenum: "MyEnum.ONE"
LINE 3: WHERE my_model.id = '1' AND my_model.enum = 'MyEnum.ONE'
                                                    ^

[SQL: SELECT my_model.id AS my_model_id, my_model.enum AS my_model_enum 
FROM my_model 
WHERE my_model.id = %(param_1)s AND my_model.enum = %(param_2)s]
[parameters: {'param_1': '1', 'param_2': 'MyEnum.ONE'}]
(Background on this error at: http://sqlalche.me/e/9h9h)

Ответы [ 2 ]

2 голосов
/ 28 сентября 2019

Если вы не хотите переопределять метод __str__ MyEnum, вы можете переопределить метод get_pk_value MyAdminView, чтобы создать правильное значение первичного ключа для администратора фляги.

from enum import Enum
from flask import Flask
from flask_admin import Admin,tools
from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import PrimaryKeyConstraint

class MyAdminView(ModelView):
    column_display_pk = True
    form_columns = ('id', 'enum')

    def get_pk_value(self, model):
        if isinstance(self._primary_key, tuple):
            pk_values = []
            for attr in self._primary_key:
                value = getattr(model, attr)
                if type(value) is MyEnum:
                    pk_values.append(value.name)
                else:
                    pk_values.append(value)
            return tools.iterencode(pk_values)
        else:
            return tools.escape(getattr(model, self._primary_key))

Теперь URL дляизменить: /admin/mymodel/edit/?id=1%2CONE&url=%2Fadmin%2Fmymodel%2F, как и ожидалось.

1 голос
/ 25 сентября 2019

Это из-за того, как flask_admin создает значение первичного ключа для данного экземпляра в URL.

Через некоторое время игры с отладчиком и копания в исходного кода администратора фляги на Github Iобнаружил, что это связано с тем, что строковое представление свойства enum в экземпляре MyModel фактически равно 'MyEnum.ONE'

str(my_model_instance.enum) == 'MyEnum.ONE'

str и в конечном итоге вызывается as_unicode , который вызывается iterencode , который вызывается в свою очередь, вызывается get_pk_value , результат которого используется в URL в шаблонах администратора.

Пока они, возможно, не исправят это вВ следующем выпуске вы можете переопределить метод __str__ MyEnum, чтобы он возвращал его имя, а затем str(my_model_instance.enum) == 'ONE'.

class MyEnum(Enum):
    ONE = 1

    def __str__(self):
        return self.name

Теперь URL для редактирования: http://127.0.0.1:5000/admin/mymodel/edit/?id=1%2CONE&url=%2Fadmin%2Fmymodel%2F, как и ожидалось.

...