134 lines
3.9 KiB
Python
134 lines
3.9 KiB
Python
|
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 self.instance.pk is None:
|
||
|
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.get_attname(),
|
||
|
ContentType.objects.get_for_model(self.instance).pk,
|
||
|
)
|
||
|
setattr(form.instance, self.ct_fk_field.get_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
|