1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
from django.contrib.contenttypes.forms import (
BaseGenericInlineFormSet,
generic_inlineformset_factory,
)
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.forms.models import ModelForm
from .models import (
BasePolymorphicModelFormSet,
PolymorphicFormSetChild,
polymorphic_child_forms_factory,
)
class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
"""
Formset child for generic inlines
"""
def __init__(self, *args, **kwargs):
self.ct_field = kwargs.pop("ct_field", "content_type")
self.fk_field = kwargs.pop("fk_field", "object_id")
super().__init__(*args, **kwargs)
def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs):
"""
Construct the form class for the formset child.
"""
exclude = list(self.exclude)
extra_exclude = kwargs.pop("extra_exclude", None)
if extra_exclude:
exclude += list(extra_exclude)
# Make sure the GFK fields are excluded by default
# This is similar to what generic_inlineformset_factory() does
# if there is no field called `ct_field` let the exception propagate
opts = self.model._meta
ct_field = opts.get_field(self.ct_field)
if (
not isinstance(ct_field, models.ForeignKey)
or ct_field.remote_field.model != ContentType
):
raise Exception(f"fk_name '{ct_field}' is not a ForeignKey to ContentType")
fk_field = opts.get_field(self.fk_field) # let the exception propagate
exclude.extend([ct_field.name, fk_field.name])
kwargs["exclude"] = exclude
return super().get_form(**kwargs)
class BaseGenericPolymorphicInlineFormSet(BaseGenericInlineFormSet, BasePolymorphicModelFormSet):
"""
Polymorphic formset variation for inline generic formsets
"""
def generic_polymorphic_inlineformset_factory(
model,
formset_children,
form=ModelForm,
formset=BaseGenericPolymorphicInlineFormSet,
ct_field="content_type",
fk_field="object_id",
# Base form
# TODO: should these fields be removed in favor of creating
# the base form as a formset child too?
fields=None,
exclude=None,
extra=1,
can_order=False,
can_delete=True,
max_num=None,
formfield_callback=None,
validate_max=False,
for_concrete_model=True,
min_num=None,
validate_min=False,
child_form_kwargs=None,
):
"""
Construct the class for a generic inline polymorphic formset.
All arguments are identical to :func:`~django.contrib.contenttypes.forms.generic_inlineformset_factory`,
with the exception of the ``formset_children`` argument.
:param formset_children: A list of all child :class:`PolymorphicFormSetChild` objects
that tell the inline how to render the child model types.
:type formset_children: Iterable[PolymorphicFormSetChild]
:rtype: type
"""
kwargs = {
"model": model,
"form": form,
"formfield_callback": formfield_callback,
"formset": formset,
"ct_field": ct_field,
"fk_field": fk_field,
"extra": extra,
"can_delete": can_delete,
"can_order": can_order,
"fields": fields,
"exclude": exclude,
"min_num": min_num,
"max_num": max_num,
"validate_min": validate_min,
"validate_max": validate_max,
"for_concrete_model": for_concrete_model,
# 'localized_fields': localized_fields,
# 'labels': labels,
# 'help_texts': help_texts,
# 'error_messages': error_messages,
# 'field_classes': field_classes,
}
if child_form_kwargs is None:
child_form_kwargs = {}
child_kwargs = {
# 'exclude': exclude,
"ct_field": ct_field,
"fk_field": fk_field,
}
if child_form_kwargs:
child_kwargs.update(child_form_kwargs)
FormSet = generic_inlineformset_factory(**kwargs)
FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs)
return FormSet
|