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 131 132 133
|
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.forms import ModelForm, modelformset_factory
from django.forms.models import BaseModelFormSet
class BaseGenericInlineFormSet(BaseModelFormSet):
"""
A formset for generic inline objects to a parent.
"""
def __init__(
self,
data=None,
files=None,
instance=None,
save_as_new=False,
prefix=None,
queryset=None,
**kwargs,
):
opts = self.model._meta
self.instance = instance
self.rel_name = (
opts.app_label
+ "-"
+ opts.model_name
+ "-"
+ self.ct_field.name
+ "-"
+ self.ct_fk_field.name
)
self.save_as_new = save_as_new
if self.instance is None or not self.instance._is_pk_set():
qs = self.model._default_manager.none()
else:
if queryset is None:
queryset = self.model._default_manager
qs = queryset.filter(
**{
self.ct_field.name: ContentType.objects.get_for_model(
self.instance, for_concrete_model=self.for_concrete_model
),
self.ct_fk_field.name: self.instance.pk,
}
)
super().__init__(queryset=qs, data=data, files=files, prefix=prefix, **kwargs)
def initial_form_count(self):
if self.save_as_new:
return 0
return super().initial_form_count()
@classmethod
def get_default_prefix(cls):
opts = cls.model._meta
return (
opts.app_label
+ "-"
+ opts.model_name
+ "-"
+ cls.ct_field.name
+ "-"
+ cls.ct_fk_field.name
)
def save_new(self, form, commit=True):
setattr(
form.instance,
self.ct_field.attname,
ContentType.objects.get_for_model(self.instance).pk,
)
setattr(form.instance, self.ct_fk_field.attname, self.instance.pk)
return form.save(commit=commit)
def generic_inlineformset_factory(
model,
form=ModelForm,
formset=BaseGenericInlineFormSet,
ct_field="content_type",
fk_field="object_id",
fields=None,
exclude=None,
extra=3,
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,
absolute_max=None,
can_delete_extra=True,
):
"""
Return a ``GenericInlineFormSet`` for the given kwargs.
You must provide ``ct_field`` and ``fk_field`` if they are different from
the defaults ``content_type`` and ``object_id`` respectively.
"""
opts = model._meta
# if there is no field called `ct_field` let the exception propagate
ct_field = opts.get_field(ct_field)
if (
not isinstance(ct_field, models.ForeignKey)
or ct_field.remote_field.model != ContentType
):
raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field)
fk_field = opts.get_field(fk_field) # let the exception propagate
exclude = [*(exclude or []), ct_field.name, fk_field.name]
FormSet = modelformset_factory(
model,
form=form,
formfield_callback=formfield_callback,
formset=formset,
extra=extra,
can_delete=can_delete,
can_order=can_order,
fields=fields,
exclude=exclude,
max_num=max_num,
validate_max=validate_max,
min_num=min_num,
validate_min=validate_min,
absolute_max=absolute_max,
can_delete_extra=can_delete_extra,
)
FormSet.ct_field = ct_field
FormSet.ct_fk_field = fk_field
FormSet.for_concrete_model = for_concrete_model
return FormSet
|