Guide: how to customzie SQLA related model appearances in ModelView
See original GitHub issueI had to go from this:
To this:
Here’s how to do it.
The representation is defined by this method of flask_admin.contrib.sqla.ajax.QueryAjaxModelLoader
:
class QueryAjaxModelLoader(AjaxModelLoader):
#skipped lines
def format(self, model):
if not model:
return None
return (getattr(model, self.pk), as_unicode(model))
There is no way around subclassing a loader. Subclass it:
class CustomFormatAjaxLoader(QueryAjaxModelLoader):
def format(self, model):
if not model:
return None
format_func = self.options['format_func']
return (getattr(model, self.pk), format_func(model))
Notice I am passing a new function using the options
dict. It’s base I want to reuse the loader, and apply different formatting to different models.
Now that we have a custom loader, we need to make the ModelView
use it.
ModelView
has a method _create_ajax_loader
that calls create_ajax_loader
.
We have to override the process with a custom create_ajax_loader
function.
Unfortunately the only way to do this that I found was to copy/paste the original create_ajax_loader
and replace the loader class because the loader class is hardcoded.
def override_create_ajax_loader(model, session, name, field_name, options, format_func=lambda x: str(x)):
attr = getattr(model, field_name, None)
if attr is None:
raise ValueError('Model %s does not have field %s.' % (model, field_name))
if not is_relationship(attr) and not is_association_proxy(attr):
raise ValueError('%s.%s is not a relation.' % (model, field_name))
if is_association_proxy(attr):
attr = attr.remote_attr
remote_model = attr.prop.mapper.class_
options['format_func'] = format_func
options['order_by'] = 'name'
return CustomFormatAjaxLoader(name, session, remote_model, **options)
I am also passing a format_func
here.
order_by
is hardcoded as a quick fix, without it AJAX queries trigger errors on MSSQL. You can ignore it if you are not using MSSQL.
Finally subclass ModelView and override loader creation:
def format_generation(gen):
return 'Generation ({}) of serie {}'.format(gen.name, gen.serie.name)
class SerieModelView(ModelView):
form_ajax_refs = {
'generations': {
'fields' :['id', 'name']
},
}
def _create_ajax_loader(self, name, options):
return override_create_ajax_loader(self.model, self.session, name, name, options, format_func=format_generation)
That’s it. Now you control the representation using the format_func
. You can pass any callable that returns a string, it should work.
Whole code:
from flask_admin.contrib.sqla.ajax import QueryAjaxModelLoader
from flask_admin.contrib.sqla.tools import is_relationship, is_association_proxy
class CustomFormatAjaxLoader(QueryAjaxModelLoader):
def format(self, model):
if not model:
return None
format_func = self.options['format_func']
return (getattr(model, self.pk), format_func(model))
def override_create_ajax_loader(model, session, name, field_name, options, format_func=lambda x: str(x)):
attr = getattr(model, field_name, None)
if attr is None:
raise ValueError('Model %s does not have field %s.' % (model, field_name))
if not is_relationship(attr) and not is_association_proxy(attr):
raise ValueError('%s.%s is not a relation.' % (model, field_name))
if is_association_proxy(attr):
attr = attr.remote_attr
remote_model = attr.prop.mapper.class_
options['format_func'] = format_func
options['order_by'] = 'name'
return CustomFormatAjaxLoader(name, session, remote_model, **options)
format_generation(gen):
return 'Generation ({}) of serie {}'.format(gen.name, gen.serie.name)
class SerieModelView(ModelView):
form_ajax_refs = {
'generations': {
'fields' :['id', 'name']
},
}
def _create_ajax_loader(self, name, options):
return override_create_ajax_loader(self.model, self.session, name, name, options, format_func=format_generation)
If you know an easier way to do it - please let me know!
I would like this whole process to be less hacky. Suggestions:
- Remove loader hardcoding, add ability to pass loader class somehow.
- Add ability to pass a function to format output, for example via
form_ajax_refs
:
form_ajax_refs = {
'generations': {
'fields' :['id', 'name'],
'format': lambda x: str(x),
},
}
Issue Analytics
- State:
- Created 6 years ago
- Comments:5 (2 by maintainers)
@mrjoes I have a
__str__
property on my model. The problem is my__str__
property is used elsewhere. To be precise I use it to create a string representation of models for logs. That is my__str__
generates a programmer-readable representation. But in the admin I want to generate a human-readable representation.So my models are used not only in the
flask_admin
utilizing project. If I changed__str__
method of the model to fitflask_admin
conventions then I would have to change all of my other projects.It makes no sense to not be able to control the output format. It makes it impossible to tailor the representation to a single project (or single view even).
Using
__str__
is a reasonable default, but I would like a way to override it.@Euphe Isn’t
__repr__
used for programmer-readable (and also unique) representation of object?