Django Passing Custom Form Parameters to Formset
Solution 1
I would use functools.partial and functools.wraps:
from functools import partial, wraps
from django.forms.formsets import formset_factory
ServiceFormSet = formset_factory(wraps(ServiceForm)(partial(ServiceForm, affiliate=request.affiliate)), extra=3)
I think this is the cleanest approach, and doesn't affect ServiceForm in any way (i.e. by making it difficult to subclass).
Solution 2
Official Document Way
Django 2.0:
ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})
Solution 3
I would build the form class dynamically in a function, so that it has access to the affiliate via closure:
def make_service_form(affiliate):
class ServiceForm(forms.Form):
option = forms.ModelChoiceField(
queryset=ServiceOption.objects.filter(affiliate=affiliate))
rate = forms.DecimalField(widget=custom_widgets.SmallField())
units = forms.IntegerField(min_value=1,
widget=custom_widgets.SmallField())
return ServiceForm
As a bonus, you don't have to rewrite the queryset in the option field. The downside is that subclassing is a little funky. (Any subclass has to be made in a similar way.)
edit:
In response to a comment, you can call this function about any place you would use the class name:
def view(request):
affiliate = get_object_or_404(id=request.GET.get('id'))
formset_cls = formset_factory(make_service_form(affiliate))
formset = formset_cls(request.POST)
...
Solution 4
This is what worked for me, Django 1.7:
from django.utils.functional import curry
lols = {'lols':'lols'}
formset = modelformset_factory(MyModel, form=myForm, extra=0)
formset.form = staticmethod(curry(MyForm, lols=lols))
return formset
#form.py
class MyForm(forms.ModelForm):
def __init__(self, lols, *args, **kwargs):
Hope it helps someone, took me long enough to figure it out ;)
Solution 5
I wanted to place this as a comment to Carl Meyers answer, but since that requires points I just placed it here. This took me 2 hours to figure out so I hope it will help someone.
A note about using the inlineformset_factory.
I used that solution my self and it worked perfect, until I tried it with the inlineformset_factory. I was running Django 1.0.2 and got some strange KeyError exception. I upgraded to latest trunk and it worked direct.
I can now use it similar to this:
BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
BookFormSet.form = staticmethod(curry(BookForm, user=request.user))
Related videos on Youtube
Paolo Bergantino
DISCLAIMER: I have no idea what I am talking about.
Updated on June 29, 2020Comments
-
Paolo Bergantino almost 4 years
This was fixed in Django 1.9 with form_kwargs.
I have a Django Form that looks like this:
class ServiceForm(forms.Form): option = forms.ModelChoiceField(queryset=ServiceOption.objects.none()) rate = forms.DecimalField(widget=custom_widgets.SmallField()) units = forms.IntegerField(min_value=1, widget=custom_widgets.SmallField()) def __init__(self, *args, **kwargs): affiliate = kwargs.pop('affiliate') super(ServiceForm, self).__init__(*args, **kwargs) self.fields["option"].queryset = ServiceOption.objects.filter(affiliate=affiliate)
I call this form with something like this:
form = ServiceForm(affiliate=request.affiliate)
Where
request.affiliate
is the logged in user. This works as intended.My problem is that I now want to turn this single form into a formset. What I can't figure out is how I can pass the affiliate information to the individual forms when creating the formset. According to the docs to make a formset out of this I need to do something like this:
ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)
And then I need to create it like this:
formset = ServiceFormSet()
Now how can I pass affiliate=request.affiliate to the individual forms this way?
-
Paolo Bergantino about 15 yearsThank you for the answer. I'm using mmarshall's solution right now and since you agree it is more Pythonic (something I wouldn't know as this is my first Python project) I guess I am sticking with that. It's definitely good to know about the callback, though. Thanks again.
-
Spike about 14 yearsThank you. This way works great with modelformset_factory. I could not get the other ways working with modelformsets properly but this way was very straightforward.
-
trubliphone over 12 yearsHmmm... Now if I try to access the form attribute of an instance of MyFormSet it (correctly) returns <function _curried> instead of <MyForm>. Any suggestions on how to access the actual form, though? I've tried
MyFormSet.form.Meta.model
. -
trubliphone over 12 yearsWhoops... I have to call the curried function in order to access the form.
MyFormSet.form().Meta.model
. Obvious really. -
Yabi Abdou over 11 yearsThe curry functional essentially creates a closure, doesn't it? Why do you say that @mmarshall's solution is more Pythonic? Btw, thanks for your answer. I like this approach.
-
finspin over 11 yearsI've been trying to apply your solution to my problem but I think I don't fully understand your whole answer. Any ideas if your approach can be applied to my issue here? stackoverflow.com/questions/14176265/…
-
thnee over 9 yearsSame thing goes for
modelformset_factory
. Thanks for this answer! -
fpghost over 7 yearsCould you explain to me why
staticmethod
is needed here? -
Paolo Bergantino over 7 yearsDjango 1.9 made any of this unnecessary, use form_kwargs instead.
-
alexey_efimov over 7 yearsIn my current work we need to use legacy django 1.7((
-
fpghost over 7 yearsThanks, seems to work very well in Django 1.10.1 unlike some of the other solutions here.
-
RobM over 7 years@fpghost keep in mind that, at least up to 1.9 (I'm still not on 1.10 for a number of reasons) if all you need to do is to change the QuerySet upon which the form is constructed, you can update it on the returned MyFormSet by changing its .queryset attribute before using it. Less flexible than this method, but much simpler to read/understand.
-
Junchao Gu over 6 yearsthis should be correct way of doing it now. the accepted answer works and is nice but is a hack
-
yaniv14 over 4 yearsdefinitely the best answer and correct way to do it.
-
ruohola almost 4 yearsAlso works in Django 1.11 docs.djangoproject.com/en/1.11/topics/forms/formsets/…
-
shaan over 3 years@RobM - I am getting error NameError: name 'self' is not defined
-
RobM over 3 years@shaan on which line? The call to super() (that you can simply write as super().__init__(*args, **kwargs) now that you're using Python 3) or the call to inlineformset_factory? If it's call to factory, you need to replace self.request.user with the variable that contains the user in your code. You probably aren't using a class based view so you don't have self, but request as a parameter: in that case it's request.user