Django - Passing Extra Params To Formsets
The scenario is that we want to use formsets on a model, but the model has a foreign key, and we only want a subset of the elements referenced by the foreign key.
i.e. We don’t want all of the options returned by the foreign key to be shown.
I really like the Django class based views and use them as much as possible. I find that they make my life a lot easier. So when I needed to make use of some formsets in a particular view, but need the formsets to only display a subset of the elements referenced by the foreign key in the model, and tackled it in the following manner;
Here is our sample model, in this example when creating a transaction I only want to show those Accounts, referenced by account_debit / account_debit that are applicable / associated to the user making the request;
#!python
class Transaction(models.Model):
account_debit = models.ForeignKey(Account, related_name="debit",
verbose_name="debit")
account_credit = models.ForeignKey(Account, related_name="credit",
verbose_name="credit")
amount = models.DecimalField(decimal_places=2, max_digits=8)
summary = models.CharField(max_length=50)
description = models.CharField(max_length=250, blank=True)
date = models.DateField()
We create our normal model form, and the work happens in the TransactionBaseFormSet
, we override BaseFormSet
and in the _construct
method we implement the logic that determines the account_debit/account_credit choices;
#!python
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
exclude = ["description"]
class TransactionBaseFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super(TransactionBaseFormSet, self).__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
form = super(TransactionBaseFormSet, self)._construct_form(i, **kwargs)
account_choices = get_account_choices(self.user)
form.fields["account_debit"].choices = account_choices
form.fields["account_credit"].choices = account_choices
form.fields["DELETE"].label = "Duplicate"
return form
TransactionFormSet = formset_factory(TransactionForm, can_delete=True, extra=0,
formset=TransactionBaseFormSet)
And to get the user object into the formset we create a class based view normally and in the get_form_kwargs method we pass in the relevant user object we need/want;
#!python
class TransactionImportConfirmView(FormView):
template_name = "import.html"
form_class = TransactionFormSet
success_url = reverse_lazy("accounts.transaction.list")
def get_form_kwargs(self):
kwargs = super(TransactionImportConfirmView, self).get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
And there we have it, we just had to really override the BaseFormSet
and weave what magic we wanted in the _construct
method and ensure we pass in any parameters we needed with the logic as we’re all set.