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

时间:2013-09-22 17:24:26

标签: json postgresql flask hstore flask-admin

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

常规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列(char   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)

3 个答案:

答案 0 :(得分:6)

答案会让你多一点,然后问

所有它的拳头“扩展”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做出共同的好方法,因为它与烧瓶一起使用,所以我只采用了一个方向的烧瓶,但是我觉得很容易从这里得到这个想法并且做到了其余部分。

如果您的案例是简单的键值存储,那么就不应该另外添加,只需按原样使用即可。 但是如果你想在代码中的某个地方展开JSON,那么只要你使用它就像这样简单,只需包装在函数中

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

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

答案 1 :(得分:4)

正在使用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 = '{}'

答案 2 :(得分:3)

尽管如此,这可能不是您问题的答案,但默认情况下,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()之后更新。