I am trying to build a Flask app with Flask_security_too and Flask_admin. I need to have 1 user linked to 1 Device, but 1 device can have multiple releases:
class Users(db.Model, UserMixin):
__tablename__ = "users"
user_id = Column(Integer, primary_key=True)
username = Column(String(100), unique=True, index=True)
password = Column(String(80))
device_name = Column(String, ForeignKey("devices.name"))
active = Column(Boolean())
roles = relationship(
"Roles", secondary="roles_users", backref=backref("users", lazy=True)
)
fs_uniquifier = Column(
String(255),
unique=True,
nullable=False,
# Line below necessary to avoid "ValueError: Constraint must have a name"
name="unique_fs_uniquifier_constraint",
)
devices = relationship("Devices", backref=backref("users", lazy=True))
def versions(self):
if self.devices:
return ", ".join([release.version for device in self.devices for release in device.releases])
else:
return ""
def __repr__(self):
return self.username
class Roles(db.Model, RoleMixin):
...
class Devices(db.Model):
__tablename__ = "devices"
device_id = Column(Integer, primary_key=True)
name = Column(String(20), unique=True)
country = Column(String(3), nullable=True)
releases = relationship("Releases", backref=backref("devices", lazy=True))
def __repr__(self):
return f"{self.name.capitalize()}, country: ({self.country})"
class Releases(db.Model):
__tablename__ = "releases"
release_id = Column(Integer, primary_key=True)
device_id = Column(Integer, ForeignKey("devices.device_id"))
version = Column(String(20)) # e.g. 8.0.122
flag_visible = Column(Boolean())
def __repr__(self):
return f"{self.version}"
Then I have coded the visualization of the list of Users for Flas_admin:
class UserAdminView(ModelView):
# Actual columns' title as seen in the website
column_list = ("username", "versions", "active", "roles")
# Link the columns' title and the model class attribute, so to make data sortable
column_sortable_list = (
"username",
("versions", "device_name"),
"active",
("roles", "roles.name"),
)
def is_accessible(self):
return (
current_user.is_active
and current_user.is_authenticated
and any(role.name == "administrator" for role in current_user.roles)
)
def _handle_view(self, name):
if not self.is_accessible():
return redirect(url_for("security.login"))
@staticmethod
def _display_roles(view, context, model, name):
return ", ".join([role.name.capitalize() for role in model.roles])
@staticmethod
def _display_versions(view, context, model, name):
return model.versions()
column_formatters = {"roles": _display_roles, "versions": _display_versions}
admin.add_view(UserAdminView(Users, db.session))
However, I get this (double) error:
Traceback (most recent call last):
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/base.py", line 369, in _run_view
return fn(self, *args, **kwargs)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/model/base.py", line 2029, in index_view
return self.render(
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/base.py", line 308, in render
return render_template(template, **kwargs)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask/templating.py", line 152, in render_template
return _render(app, template, context)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask/templating.py", line 133, in _render
rv = template.render(context)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/jinja2/environment.py", line 1301, in render
self.environment.handle_exception()
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/jinja2/environment.py", line 936, in handle_exception
raise rewrite_traceback_stack(source=source)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/templates/bootstrap3/admin/model/list.html", line 6, in top-level template code
{% import 'admin/model/row_actions.html' as row_actions with context %}
File "/home/arpaia/customer-webpage-rewrite/app/templates/admin/master.html", line 1, in top-level template code
{% extends 'admin/base.html' %}
File "/home/arpaia/customer-webpage-rewrite/app/templates/admin/base.html", line 38, in top-level template code
{% block page_body %}
File "/home/arpaia/customer-webpage-rewrite/app/templates/admin/base.html", line 78, in block 'page_body'
{% block body %}{% endblock %}
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/templates/bootstrap3/admin/model/list.html", line 68, in block 'body'
{% block model_list_table %}
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/templates/bootstrap3/admin/model/list.html", line 116, in block 'model_list_table'
{% block list_row scoped %}
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/templates/bootstrap3/admin/model/list.html", line 146, in block 'list_row'
{{ get_value(row, c) }}
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/model/base.py", line 1877, in get_list_value
return self._get_list_value(
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/model/base.py", line 1836, in _get_list_value
value = column_fmt(self, context, model, name)
File "/home/arpaia/customer-webpage-rewrite/app/__init__.py", line 118, in _display_versions
return model.versions()
File "/home/arpaia/customer-webpage-rewrite/app/models.py", line 37, in versions
return ", ".join([release.version for device in self.devices for release in device.releases])
TypeError: 'Devices' object is not iterable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask/app.py", line 1478, in __call__
return self.wsgi_app(environ, start_response)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask/app.py", line 1458, in wsgi_app
response = self.handle_exception(e)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask/app.py", line 1455, in wsgi_app
response = self.full_dispatch_request()
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask/app.py", line 869, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/base.py", line 69, in inner
return self._run_view(f, *args, **kwargs)
File "/home/arpaia/customer-webpage-rewrite/.venv/lib/python3.10/site-packages/flask_admin/base.py", line 371, in _run_view
return fn(cls=self, **kwargs)
TypeError: BaseModelView.index_view() got an unexpected keyword argument 'cls'
Can someone help me, please?
I was expecting to see an empty field on the front-end or at least None, due to the following code in my Users model:
def versions(self):
if self.devices:
return ", ".join([release.version for device in self.devices for release in device.releases])
else:
return ""
Your database models should have
__str__methods, Flask-Admin will use this to display model instances in the UI.Your formatters should be regular method instances and not static methods. e.g.
Here, the
viewparameter is the view instance, normally written asself.