5

如何HSTORE使用 Flask-Admin 更新字段?

常规ModelView不会HSTORE在编辑视图中显示该字段。它什么也没显示。完全没有控制力。在列表视图中,它以 JSON 表示法显示包含数据的列。这对我来说没问题。

使用自定义ModelView,我可以将HSTORE字段更改为 TextAreaField。这将在编辑视图中以 JSON 表示法向我显示 HSTORE 字段。但我无法编辑/更新它。在列表视图中,它仍然以 JSON 表示法向我显示对象。在我看来很好。

class MyView(ModelView):
    form_overrides = dict(attributes=fields.TextAreaField)

当我尝试保存/编辑 JSON 时,收到此错误:

sqlalchemy.exc.InternalError
InternalError: (InternalError) Unexpected end of string
LINE 1: UPDATE mytable SET attributes='{}' WHERE mytable.id = ...
                                         ^
 'UPDATE mytable SET attributes=%(attributes)s WHERE mytable.id = %(mytable_id)s' {'attributes': u'{}', 'mytable_id': 14L}

现在 - 使用代码,我可以将一些东西保存到 HSTORE 字段中:

class MyView(ModelView):
    form_overrides = dict(attributes=fields.TextAreaField)
    def on_model_change(self, form, model, is_created):
        model.attributes = {"a": "1"}
        return

这基本上覆盖了模型并将这个对象放入其中。然后我可以在列表视图和编辑视图中看到该对象。仍然不够好——我想保存/编辑用户输入的对象。

我尝试将表单中的内容解析并保存到 JSON 中并退出。这不起作用:

class MyView(ModelView):
    form_overrides = dict(attributes=fields.TextAreaField)

    def on_model_change(self, form, model, is_created):
        x = form.data['attributes']
        y = json.loads(x)
        model.attributes = y
        return

json.loads(x) 是这样说的:

ValueError ValueError:期望属性名称:第 1 行第 1 列(字符 1)

以下是一些失败的示例输入:

{u's': u'ff'}
{'s':'ff'}

但是,此输入有效:

{}

空白也有效

这是我的 SQL 表:

CREATE TABLE mytable (
    id BIGSERIAL UNIQUE PRIMARY KEY,
    attributes hstore
);

这是我的 SQA 模型:

class MyTable(Base):
    __tablename__ = u'mytable'

    id = Column(BigInteger, primary_key=True)
    attributes = Column(HSTORE)

这是我将视图添加到管理对象的方式

admin.add_view(ModelView(models.MyTable, db.session))

使用自定义模型视图添加视图

admin.add_view(MyView(models.MyTable, db.session))

但我不会同时执行这些视图——我得到一个蓝图名称冲突错误——单独的问题)

我还尝试使用表单字段转换器。我无法让它真正命中代码。

class MyModelConverter(AdminModelConverter):
    def post_process(self, form_class, info):
        raise Exception('here I am') #but it never hits this
        return form_class

class MyView(ModelView):
    form_overrides = dict(attributes=fields.TextAreaField)
4

3 回答 3

7

答案比问的多一点

首先它“扩展”了 hstore 以便能够存储实际 JSON,而不仅仅是键值所以这个结构也可以:

{"key":{"inner_object_key":{"Another_key":"Done!","list":["no","problem"]}}}

所以,首先你的 ModelView 应该使用自定义转换器

class ExtendedModelView(ModelView):
   model_form_converter=CustomAdminConverter

转换器本身应该知道如何使用hstore方言:

class CustomAdminConverter(AdminModelConverter):
    @converts('sqlalchemy.dialects.postgresql.hstore.HSTORE')
    def conv_HSTORE(self, field_args, **extra):
        return DictToHstoreField(**field_args)

如您所见,这个使用自定义 WTForms 字段,可双向转换数据:

class DictToHstoreField(TextAreaField):
    def process_data(self, value):
        if value is None:
            value = {}
        else:
            for key,obj in value.iteritems():
                if (obj.startswith("{") and obj.endswith("}")) or (obj.startswith("[") and obj.endswith("]")):
                    try:
                        value[key]=json.loads(obj)
                    except:
                        pass #

        self.data=json.dumps(value)

    def process_formdata(self, valuelist):
        if valuelist:
            self.data = json.loads(valuelist[0])
            for key,obj in self.data.iteritems():
                if isinstance(obj,dict) or isinstance(obj,list):
                    self.data[key]=json.dumps(obj)
                if isinstance(obj,int):
                    self.data[key]=str(obj)

最后一步是在应用程序中实际使用这些数据

我没有为 SQLalchemy 以一种常见的好方法,因为它与 flask-restful 一起使用,所以我只在一个方向上采用了 flask-restful,但我认为从这里得到这个想法并完成剩下的工作很容易。

如果您的案例是简单的键值存储,所以不需要做任何额外的事情,就按原样使用它。但是如果你想在代码中的某个地方解包 JSON,只要你使用它就很简单,只需在函数中包装

 if (value.startswith("{") and value.endswith("}")) or (value.startswith("[") and value.endswith("]")):
        value=json.loads(value)

也可以通过扩展和添加一些用于添加/删除字段的 javascript 来为实际的非 JSON 数据编辑方式创建动态FormField字段,但这是完全不同的故事,在我的情况下,我需要实际的 json 存储,带有二十一点和列表 :)

于 2013-09-24T21:26:49.623 回答
6

正在研究 postgres JSON 数据类型。上述解决方案在稍作修改后效果很好。

试过了

'sqlalchemy.dialects.postgresql.json.JSON',
'sqlalchemy.dialects.postgresql.JSON',
'dialects.postgresql.json.JSON',
'dialects.postgresql.JSON'

上述版本不起作用。

最后,以下更改起作用

@converts('JSON')

并将类 DictToHstoreField更改为以下内容:

class DictToJSONField(fields.TextAreaField):
    def process_data(self, value):
        if value is None:
            value = {}

        self.data = json.dumps(value)

    def process_formdata(self, valuelist):
        if valuelist:
            self.data = json.loads(valuelist[0])
        else:
            self.data = '{}'
于 2014-10-07T08:59:48.060 回答
4

虽然,这可能不是您问题的答案,但默认情况下 SQLAlchemy 的 ORM 不会检测到HSTORE字段值的就地更改。但幸运的是有一个解决方案:SQLAlchemy 的MutableDict类型:

from sqlalchemy.ext.mutable import MutableDict

class MyClass(Base):
    __tablename__ = 'mytable'

    id = Column(Integer, primary_key=True)
    attributes = Column(MutableDict.as_mutable(HSTORE))

现在,当您就地更改某些内容时:

my_object.attributes.['some_key'] = 'some value'

hstore字段将在 之后更新session.commit()

于 2013-09-25T05:10:36.847 回答