【问题标题】:Flask-admin error when editing record where primay key has enum编辑主键为枚举的记录时出现 Flask-admin 错误
【发布时间】:2019-09-25 00:56:27
【问题描述】:

使用 postgres 并尝试使用包含枚举的主键编辑记录将错误

这是因为 ID 包含枚举的类名,即MyEnum..ONE,而它应该只是ONEhttp://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 并单击编辑记录:

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)

【问题讨论】:

    标签: python postgresql flask-sqlalchemy flask-admin


    【解决方案1】:

    如果您不想重写 MyEnum 的 __str__ 方法,您可以重写 MyAdminView 的 get_pk_value 方法,以便为 flask admin 构建正确的主键值。

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

    现在编辑的网址是: /admin/mymodel/edit/?id=1%2CONE&url=%2Fadmin%2Fmymodel%2F 正如预期的那样。

    【讨论】:

      【解决方案2】:

      这是因为 flask_admin 如何在 urls 中为给定实例构建主键值。

      在使用调试器和挖掘flask admin source code on Github 一段时间后,我发现这是因为MyModel 实例上enum 属性的字符串表示实际上等于'MyEnum.ONE'

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

      str 最终被as_unicode 调用,iterencode 又被get_pk_value 调用,结果在管理模板的 url 中使用。

      在他们可能在下一个版本中解决此问题之前,您可以覆盖 MyEnum 的 __str__ 方法,以便返回其名称,然后返回 str(my_model_instance.enum) == 'ONE'

      class MyEnum(Enum):
          ONE = 1
      
          def __str__(self):
              return self.name
      

      现在编辑的网址是: http://127.0.0.1:5000/admin/mymodel/edit/?id=1%2CONE&url=%2Fadmin%2Fmymodel%2F 符合预期。

      【讨论】:

      • 感谢您对此进行调查。这是我使用的解决方法,但希望有替代 __str__ 的替代方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多