Last active
April 9, 2021 07:16
-
-
Save sykire/398a05e4945805bc09d1 to your computer and use it in GitHub Desktop.
Usually in flask-admin inline-models use the same converter. You can set a different converter setting 'inline_converter' in the ModelView but this will apply the same converter for every Model in the inline_models list.So I've overridden the 'scaffold_inline_form_models' method from ModelView just to check for every inline model in the list if …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask_admin.form import FormOpts | |
from flask_admin.contrib.sqla.form import InlineModelFormList, \ | |
InlineModelConverter, \ | |
get_form | |
from flask_admin.contrib.sqla.tools import get_primary_key | |
from flask_admin.contrib.sqla import ModelView | |
from flask_admin.model.fields import InlineModelFormField | |
from flask_admin.model.form import InlineFormAdmin | |
from flask_admin._compat import iteritems | |
class ForwardInlineModelForm(InlineModelFormField): | |
def __init__(self, form, session, model, prop, inline_view, **kwargs): | |
""" | |
Default constructor. | |
:param form: | |
Form for the related model | |
:param session: | |
SQLAlchemy session | |
:param model: | |
Related model | |
:param prop: | |
Related property name | |
:param inline_view: | |
Inline view | |
""" | |
self.form = form | |
self.session = session | |
self.model = model | |
self.prop = prop | |
self.inline_view = inline_view | |
self._pk = get_primary_key(model) | |
# Generate inline form field | |
form_opts = FormOpts(widget_args=getattr(inline_view, 'form_widget_args', None), | |
form_rules=inline_view._form_rules) | |
super().__init__(form, self._pk, form_opts=form_opts, **kwargs) | |
def populate_obj(self, obj, name): | |
value = getattr(obj, name, None) | |
inline_form = self.form | |
if value: | |
model = value | |
else: | |
model = self.model() | |
setattr(obj, name, model) | |
for name, field in iteritems(self.form._fields): | |
if name != self._pk: | |
field.populate_obj(model, name) | |
self.inline_view.on_model_change(inline_form, model) | |
class ForwardInlineModelConverter(InlineModelConverter): | |
inline_field_list_type = ForwardInlineModelForm | |
def contribute(self, model, form_class, inline_model): | |
mapper = model._sa_class_manager.mapper | |
info = self.get_info(inline_model) | |
# Find property from target model to current model | |
target_mapper = info.model._sa_class_manager.mapper | |
forward_prop = None | |
for prop in mapper.iterate_properties: | |
if hasattr(prop, 'direction') and prop.direction.name == 'MANYTOONE': | |
if prop.mapper.class_ == target_mapper.class_: | |
forward_prop = prop | |
break | |
else: | |
raise Exception('Cannot find forward relation for model %s' % info.model) | |
reverse_prop = None | |
for prop in target_mapper.iterate_properties: | |
if hasattr(prop, 'direction') and prop.direction.name == 'ONETOMANY': | |
if prop.mapper.class_ == mapper.class_: | |
reverse_prop = prop | |
break | |
else: | |
raise Exception('Cannot find reverse relation for model %s' % info.model) | |
# Remove reverse property from the list | |
ignore = [reverse_prop.key] | |
if info.form_excluded_columns: | |
exclude = ignore + list(info.form_excluded_columns) | |
else: | |
exclude = ignore | |
# Create converter | |
converter = self.model_converter(self.session, info) | |
# Create form | |
child_form = info.get_form() | |
if child_form is None: | |
child_form = get_form(info.model, | |
converter, | |
only=info.form_columns, | |
exclude=exclude, | |
field_args=info.form_args, | |
hidden_pk=True) | |
# Post-process form | |
child_form = info.postprocess_form(child_form) | |
kwargs = dict() | |
label = self.get_label(info, forward_prop.key) | |
if label: | |
kwargs['label'] = label | |
if self.view.form_args: | |
field_args = self.view.form_args.get(forward_prop.key, {}) | |
kwargs.update(**field_args) | |
setattr(form_class, | |
forward_prop.key, | |
self.inline_field_list_type(child_form, | |
self.session, | |
info.model, | |
reverse_prop.key, | |
info, | |
**kwargs)) | |
return form_class | |
#This is to use a different inline converter, in this case a ForwardInlineModelConverter | |
class ManyToOneInlineForm(InlineFormAdmin): | |
inline_converter = ForwardInlineModelConverter | |
#This is to use to default inline converter | |
class OneToManyInlineForm(InlineFormAdmin): | |
pass | |
class PerInlineModelConverterView(ModelView): | |
def scaffold_inline_form_models(self, form_class): | |
""" | |
Contribute inline models to the form | |
:param form_class: | |
Form class | |
""" | |
inline_converter = self.inline_model_form_converter(self.session, | |
self, | |
self.model_form_converter) | |
for m in self.inline_models: | |
if hasattr(m, 'inline_converter'): | |
custom_converter = m.inline_converter(self.session, | |
self, | |
self.model_form_converter) | |
form_class = custom_converter.contribute(self.model, form_class, m) | |
else: | |
form_class = inline_converter.contribute(self.model, form_class, m) | |
return form_class | |
#EXAMPLE | |
class PackagesView(PerInlineModelConverterView): | |
inline_models = ( OneToManyInlineForm(Place), SomeModelUsingDefaultConverter, ManyToOneInlineForm(Content)) |
Thank you very much for doing that :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
pallets-eco/flask-admin#2091
also, here is a pull request