Skip to content

Commit

Permalink
Support passing formsets / exclude_formsets to childformset_factory
Browse files Browse the repository at this point in the history
This then makes it possible to specify these in a nested 'formsets' dict in ClusterForm.Meta.
  • Loading branch information
gasman committed Mar 14, 2022
1 parent ac3ff71 commit 3eba3c8
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 3 deletions.
43 changes: 42 additions & 1 deletion modelcluster/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def childformset_factory(
formset=BaseChildFormSet, fk_name=None, fields=None, exclude=None,
extra=3, can_order=False, can_delete=True, max_num=None, validate_max=False,
formfield_callback=None, widgets=None, min_num=None, validate_min=False,
inherit_kwargs=None,
inherit_kwargs=None, formsets=None, exclude_formsets=None
):

fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
Expand All @@ -187,6 +187,22 @@ def childformset_factory(
exclude = []
exclude += [fk.name]

if issubclass(form, ClusterForm) and (formsets is not None or exclude_formsets is not None):
# the modelformset_factory helper that we ultimately hand off to doesn't recognise
# formsets / exclude_formsets, so we need to prepare a specific subclass of our `form`
# class, with these pre-embedded in Meta, to use as the base form

# If parent form class already has an inner Meta, the Meta we're
# creating needs to inherit from the parent's inner meta.
bases = (form.Meta,) if hasattr(form, "Meta") else ()
Meta = type("Meta", bases, {
'formsets': formsets,
'exclude_formsets': exclude_formsets,
})

# Instantiate type(form) in order to use the same metaclass as form.
form = type(form)("_ClusterForm", (form,), {"Meta": Meta})

kwargs = {
'form': form,
'formfield_callback': formfield_callback,
Expand Down Expand Up @@ -404,3 +420,28 @@ def has_changed(self):
if form.has_changed():
return True
return bool(self.changed_data)


def clusterform_factory(model, form=ClusterForm, **kwargs):
# Same as Django's modelform_factory, but arbitrary kwargs are accepted and passed on to the
# Meta class.

# Build up a list of attributes that the Meta object will have.
meta_class_attrs = kwargs
meta_class_attrs["model"] = model

# If parent form class already has an inner Meta, the Meta we're
# creating needs to inherit from the parent's inner meta.
bases = (form.Meta,) if hasattr(form, "Meta") else ()
Meta = type("Meta", bases, meta_class_attrs)
formfield_callback = meta_class_attrs.get('formfield_callback')
if formfield_callback:
Meta.formfield_callback = staticmethod(formfield_callback)
# Give this new form class a reasonable name.
class_name = model.__name__ + "Form"

# Class attributes for the new form class.
form_class_attrs = {"Meta": Meta, "formfield_callback": formfield_callback}

# Instantiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)
2 changes: 0 additions & 2 deletions tests/tests/test_cluster_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,6 @@ class Meta:
self.assertTrue(Song.objects.filter(name='Misery').exists())
self.assertFalse(Song.objects.filter(name='I Saw Her Standing There').exists())

@unittest.skip('Explicit nested formsets not yet enabled')
def test_explicit_nested_formset_list(self):
class BandForm(ClusterForm):
class Meta:
Expand All @@ -968,7 +967,6 @@ class Meta:
self.assertTrue('albums' in form.as_p())
self.assertTrue('songs' in form.as_p())

@unittest.skip('Excluded nested formsets not yet enabled')
def test_excluded_nested_formset_list(self):
class BandForm(ClusterForm):
class Meta:
Expand Down

0 comments on commit 3eba3c8

Please sign in to comment.