[Django]-Django — Generate form based on queryset

0👍

Dynamically generating Forms using FormSets

So I figured this out (after much googling and swearing and gnashing of teeth). Malcolm Tredinnick made a blog post about exactly what I wanted to do, which a kind soul preserved on Django Snippets

Using Malcom’s code as a model, I solved my problem and it works like a charm

class PressureReadingUpdate(forms.Form):
    """
    Form that asks for the pressure of a line given some attributes of that line.
    """
    psi = forms.IntegerField(widget=forms.NumberInput)

    def __init__(self, *args, **kwargs):
        self.line = kwargs.pop('line')
        kwargs['auto_id'] = "%s".format(self.line.number)
        super(PressureReadingUpdate, self).__init__(*args, **kwargs)

class BasePsiReadingFormset(BaseFormSet):
    """
    Formset that constructs a group of PressureReadingUpdate forms by taking a queryset
    of Line objects and passing each one in turn to a PressureReadingUpdate form as it
    gets constructed
    """
    def __init__(self, *args, **kwargs):
        self.lines = kwargs.pop('lines')
        super(BasePsiReadingFormset, self).__init__(*args, **kwargs)
        self.extra = len(self.lines)
        self.max_num = len(self.lines)

    def _construct_form(self, i, **kwargs):
        kwargs['line'] = self.lines[i]
        form = super(BasePsiReadingFormset, self)._construct_form(i, **kwargs)
        return form

PsiReadingFormset = formset_factory(form=PressureReadingUpdate, formset=BasePsiReadingFormset)

This gives you a Formset with an extra kwarg you can pass down the chain of constructors. You can use it in a view (along with a more typical initial= kwarg) with:

formset = PsiReadingFormset(lines=lines,
                            initial=[{'psi': l.current().psi} for l in lines],

So here’s the best explanation I can think of:

Any kwargs passed to a FormSet like (which gets made by the formset_factory function using a non-default ‘BaseFormSet’ as a blueprint) get passed along — mostly unaltered — to the __init__ method of whatever BaseFormSet the FormSet is based on.

This means you can define custom behavior in BaseFormSet.__init__, and you can relay runtime data to the BaseFormSet.__init__ method by passing it as a keyword argument to FormSet (that’s the lines= kwarg in my example above). I use it to set an attribute on on formset (an instance of a FormSet based on ‘BasePsiReadingFormset’).

Confused yet? I was too at first.

But the real magic is understanding how _construct_forms works: A FormSet calls this function each time it wants to make a new Form in the set. It will relay any unrecognized kwargs to to the constructor of the Form it is supposed to manage a set of.

So you just need to overload your custom BaseFormSet‘s ._construct_forms to wrap the original _construct_forms of the superclass and inject a new kwarg. This kwarg will then be passed through to the constructor of your custom Form class, and shape the new Form instance according to the Form‘s initialization function.

And that, ladies and gentlemen, is how you can have a FormSet that has a bunch of similarly-defined but dynamically-generated-and-slightly different forms in it.

After understanding this, I can see the elegance of it. However, it uses some intermediate-to-advanced python, and is neither immediately obvious nor well documented. If you are struggling with this like I did, feel free to message me.

Leave a comment