pythonsqlalchemyflask-appbuilder

Creating a model in flask-appbuilder using JSON


Trying to create this model for my table in my database however I’m getting this key error when viewing the table, I then get this error, I suspect this is link to an issue of adding my data into the table, any help is greatly appreciated. I also created a view in my view module but its just a simple connection between the model and I list the fields, I'll include my view which I need for appbuilder,

views.py:

class PView(ModelView):
    datamodel = SQLAInterface(Profile)
    list_columns = ['id', 'name', 'properties']

appbuilder.add_view(PView, "X", icon="fa-envelope", category="X")

models.py:

class Profile(Model):
    __tablename__ = 'profile'
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    properties = Column(JSONB, nullable=True)

    def __repr__(self):
        return self.name

Traceback:

Traceback (most recent call last):
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/security/decorators.py", line 26, in wraps
    return f(self, *args, **kwargs)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/views.py", line 478, in list
    widgets=widgets)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/baseviews.py", line 160, in render_template
    return render_template(template, **dict(list(kwargs.items()) + list(self.extra_args.items())))
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask/templating.py", line 134, in render_template
    context, ctx.app)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask/templating.py", line 116, in _render
    rv = template.render(context)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/jinja2/asyncsupport.py", line 76, in render
    return original_render(self, *args, **kwargs)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/jinja2/environment.py", line 1008, in render
    return self.environment.handle_exception(exc_info, True)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/general/model/list.html", line 2, in top-level template code
    {% import 'appbuilder/general/lib.html' as lib %}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/base.html", line 1, in top-level template code
    {% extends base_template %}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/baselayout.html", line 2, in top-level template code
    {% import 'appbuilder/baselib.html' as baselib %}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/init.html", line 46, in top-level template code
    {% block body %}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/baselayout.html", line 19, in block "body"
    {% block content %}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/general/model/list.html", line 7, in block "content"
    {% block list_search scoped %}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/general/model/list.html", line 8, in block "list_search"
    {% call lib.accordion_tag("accordion1",_("Search"), False) %}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/jinja2/runtime.py", line 579, in _invoke
    rv = self._func(*arguments)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/general/lib.html", line 254, in template
    {{ caller() }}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/jinja2/runtime.py", line 579, in _invoke
    rv = self._func(*arguments)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/templates/appbuilder/general/model/list.html", line 9, in template
    {{ widgets.get('search')()|safe }}
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/flask_appbuilder/widgets.py", line 107, in __call__
    label_columns[col] = as_unicode(self.template_args['form'][col].label.text)
  File "/Users/adam/myapp/venv/lib/python3.6/site-packages/wtforms/form.py", line 65, in __getitem__
    return self._fields[name]
KeyError: 'properties'

Solution

  • It'd seem like Flask-AppBuilder lacks support for SQLAlchemy's implementation of Postgresql JSONB type. On the other hand it's challenging to imagine a generic input widget for such a flexible format as JSON, other than a text field.

    The traceback shows that the exception is raised during rendering of a search widget, and sure enough if you inspect the fields in the debugger, 'properties' is missing:

    [console ready]
    >>> self._fields
    collections.OrderedDict({'name': <wtforms.fields.core.StringField object at 0x7febfe492668>, 'csrf_token': <wtforms.csrf.core.CSRFTokenField object at 0x7febfe492978>})
    >>> 
    

    A "solution" is to exclude the problematic field from forms entirely:

    class PView(ModelView):
        datamodel = SQLAInterface(Profile)
        list_columns = ['id', 'name', 'properties']
        add_exclude_columns = ['properties']
        edit_exclude_columns = ['properties']
        search_exclude_columns = ['properties']
    

    Of course this isn't a solution at all in the sense that it leaves the properties effectively unusable.