12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182 |
- 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):
- """
- 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,
- )
- FormSet.ct_field = ct_field
- FormSet.ct_fk_field = fk_field
- FormSet.for_concrete_model = for_concrete_model
- return FormSet
|