Django Passing Custom Form Parameters to Formset

47,629

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})

https://docs.djangoproject.com/en/2.0/topics/forms/formsets/#passing-custom-parameters-to-formset-forms

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))
Share:
47,629

Related videos on Youtube

Paolo Bergantino
Author by

Paolo Bergantino

DISCLAIMER: I have no idea what I am talking about.

Updated on June 29, 2020

Comments

  • Paolo Bergantino
    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
    Paolo Bergantino about 15 years
    Thank 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
    Spike about 14 years
    Thank 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
    trubliphone over 12 years
    Hmmm... 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
    trubliphone over 12 years
    Whoops... I have to call the curried function in order to access the form. MyFormSet.form().Meta.model. Obvious really.
  • Yabi Abdou
    Yabi Abdou over 11 years
    The 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
    finspin over 11 years
    I'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
    thnee over 9 years
    Same thing goes for modelformset_factory. Thanks for this answer!
  • fpghost
    fpghost over 7 years
    Could you explain to me why staticmethod is needed here?
  • Paolo Bergantino
    Paolo Bergantino over 7 years
    Django 1.9 made any of this unnecessary, use form_kwargs instead.
  • alexey_efimov
    alexey_efimov over 7 years
    In my current work we need to use legacy django 1.7((
  • fpghost
    fpghost over 7 years
    Thanks, seems to work very well in Django 1.10.1 unlike some of the other solutions here.
  • RobM
    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
    Junchao Gu over 6 years
    this should be correct way of doing it now. the accepted answer works and is nice but is a hack
  • yaniv14
    yaniv14 over 4 years
    definitely the best answer and correct way to do it.
  • ruohola
    ruohola almost 4 years
  • shaan
    shaan over 3 years
    @RobM - I am getting error NameError: name 'self' is not defined
  • RobM
    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