162

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
  • 480,997
  • 81
  • 517
  • 436

12 Answers12

110

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

Bigair
  • 1,452
  • 3
  • 15
  • 42
srgi0
  • 3,319
  • 1
  • 23
  • 20
106

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

Carl Meyer
  • 122,012
  • 20
  • 106
  • 116
  • It's not working for me. I get the error: AttributeError: '_curriedFormSet' object has no attribute 'get' – Paolo Bergantino Mar 09 '09 at 00:35
  • I can't duplicate this error. It's also an odd one because a formset usually does not have a 'get' attribute, so it seems you might be doing something strange in your code. (Also, I updated the answer with a way to get rid of oddities like '_curriedFormSet'). – Carl Meyer Mar 09 '09 at 12:50
  • I'm revisiting this because I'd like to get your solution working. I can declare the formset fine, but if I try to print it doing {{ formset }} is when I get the "has no attribute 'get'" error. It happens with either solution you provided. If I loop through the formset and print the forms as {{ form }} I get the error again. If I loop and print as {{ form.as_table }} for example, I get empty form tables, ie. no fields are printed. Any ideas? – Paolo Bergantino Apr 19 '09 at 07:01
  • You're right, I'm sorry; my earlier testing didn't go far enough. I tracked this down, and it breaks due to some oddities in the way FormSets work internally. There is a way to work around the problem, but it begins to lose the original elegance... – Carl Meyer Apr 20 '09 at 22:31
  • I'm working on implementing this solution. However, I'm getting a 'ManagementForm data is missing or has been tampered with' error upon the POST. Has anyone tried this solution with django 1.1? The docs seem to indicate a ManagementForm construct was added then that might be causing this error. – Joe J Jun 09 '10 at 04:45
  • @Joe J - I doubt that error is related to using the above solution; it's probably just a general matter of using formsets correctly. Have you tried using a formset normally (without currying) to see if you still get the error? How are you displaying the formset in the template (if you are not using as_* you have to manually display {{ formset.management_form }} somewhere in your template. – Carl Meyer Jun 10 '10 at 14:37
  • Does this technique still work/is there now a better way to do this? I tried the after the edit solution above, and when I try to do formset.is_valid() afterwards it says `unbound method is_valid() must be called with ServiceFormFormSet instance as first argument (got nothing instead)` – Murph Jul 30 '12 at 20:47
  • @Murph the variable ``formset`` (returned from ``formset_factory``) in the above is actually a class, not an instance. (It might be clearer if I named it ``FormSet``). In your view you still need to instantiate that class with the specific data from the request (e.g. ``formset = FormSet(request.POST)``). I've edited the answer to use clearer variable naming. – Carl Meyer Dec 04 '12 at 16:58
  • Don't forget `from django.forms.formsets import formset_factory` – Ashley Dec 19 '12 at 12:09
  • 6
    If the comment thread here doesn't make sense, it's because I just edited the answer to use Python's `functools.partial` instead of Django's `django.utils.functional.curry`. They do the same thing, except that `functools.partial` returns a distinct callable type instead of a regular Python function, and the `partial` type does not bind as an instance method, which neatly solves the problem this comment thread was largely devoted to debugging. – Carl Meyer Feb 12 '14 at 23:24
  • @boldnik Do you have any reason for preferring Ajax to this solution? Doesn't make much sense to me. – Carl Meyer Feb 12 '14 at 23:25
  • @Carl Meyer My opinion is that the code should be *clean* to read and to *understand*. That's why using `django.utils.functional.curry` might not be obvious. But recently I implemented a custom arg (reauest.user) in each form in the formset. I did it by creating a CustomFormset(BaseModelFormSet). In `__init__` it `pop`s a dict of additional args and attaches to the body of the formset *or* to each form in `_construct_form` method. Using a `setattr` this CustomFormSet might be used everywhere. But I agree that it is also not an obvious way... – boldnik Feb 13 '14 at 11:39
  • Your last edit broke the example, `partial` is not used anywhere and You left the `curry` in place after removing the import. – Martin Jan 06 '15 at 14:29
  • Wow thanks! Does this also works for modelform_factory? Are alle parentheses right? I can't get this to work unfortunately. – Wim Feijen Mar 05 '16 at 11:14
  • @WimFeijen should work with modelformset_factory. I think all the parens are right. What kind of trouble are you having? – Carl Meyer Mar 05 '16 at 14:56
  • @CarlMeyer How would I look if I used modelformset_factory? For example: _WellSettingFormSet = modelformset_factory(WellSetting, form=WellSettingForm)_ – Pereira Jul 05 '17 at 20:52
  • 2
    It's a hack!! Use roach's answer which follows official doc way – Bigair Jun 29 '18 at 02:29
47

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)
    ...
Matthew Marshall
  • 5,793
  • 2
  • 21
  • 14
16

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

rix
  • 10,104
  • 14
  • 65
  • 92
10

As of commit e091c18f50266097f648efc7cac2503968e9d217 on Tue Aug 14 23:44:46 2012 +0200 the accepted solution can't work anymore.

The current version of django.forms.models.modelform_factory() function uses a "type construction technique", calling the type() function on the passed form to get the metaclass type, then using the result to construct a class-object of its type on the fly::

# Instatiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)

This means even a curryed or partial object passed instead of a form "causes the duck to bite you" so to speak: it'll call a function with the construction parameters of a ModelFormClass object, returning the error message::

function() argument 1 must be code, not str

To work around this I wrote a generator function that uses a closure to return a subclass of any class specified as first parameter, that then calls super.__init__ after updateing the kwargs with the ones supplied on the generator function's call::

def class_gen_with_kwarg(cls, **additionalkwargs):
  """class generator for subclasses with additional 'stored' parameters (in a closure)
     This is required to use a formset_factory with a form that need additional 
     initialization parameters (see http://stackoverflow.com/questions/622982/django-passing-custom-form-parameters-to-formset)
  """
  class ClassWithKwargs(cls):
      def __init__(self, *args, **kwargs):
          kwargs.update(additionalkwargs)
          super(ClassWithKwargs, self).__init__(*args, **kwargs)
  return ClassWithKwargs

Then in your code you'll call the form factory as::

MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user))

caveats:

  • this received very little testing, at least for now
  • supplied parameters could clash and overwrite those used by whatever code will use the object returned by the constructor
RobM
  • 1,005
  • 11
  • 13
  • Thanks, seems to work very well in Django 1.10.1 unlike some of the other solutions here. – fpghost Sep 14 '16 at 18:38
  • 1
    @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. – RobM Sep 16 '16 at 19:14
  • @RobM - I am getting error _NameError: name 'self' is not defined_ – shaan Nov 12 '20 at 05:51
  • @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 – RobM Nov 18 '20 at 02:34
10

I like the closure solution for being "cleaner" and more Pythonic (so +1 to mmarshall answer) but Django forms also have a callback mechanism you can use for filtering querysets in formsets.

It's also not documented, which I think is an indicator the Django devs might not like it as much.

So you basically create your formset the same but add the callback:

ServiceFormSet = forms.formsets.formset_factory(
    ServiceForm, extra=3, formfield_callback=Callback('option', affiliate).cb)

This is creating an instance of a class that looks like this:

class Callback(object):
    def __init__(self, field_name, aff):
        self._field_name = field_name
        self._aff = aff
    def cb(self, field, **kwargs):
        nf = field.formfield(**kwargs)
        if field.name == self._field_name:  # this is 'options' field
            nf.queryset = ServiceOption.objects.filter(affiliate=self._aff)
        return nf

This should give you the general idea. It's a little more complex making the callback an object method like this, but gives you a little more flexibility as opposed to doing a simple function callback.

Van Gale
  • 43,536
  • 9
  • 71
  • 81
  • 1
    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. – Paolo Bergantino Mar 08 '09 at 07:17
  • 1
    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. – Spike Mar 07 '10 at 19:16
  • 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. – Josh Aug 23 '12 at 20:03
10

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))
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Johan Berg Nilsson
  • 1,634
  • 1
  • 12
  • 11
3

Carl Meyer's solution looks very elegant. I tried implementing it for modelformsets. I was under the impression that I could not call staticmethods within a class, but the following inexplicably works:

class MyModel(models.Model):
  myField = models.CharField(max_length=10)

class MyForm(ModelForm):
  _request = None
  class Meta:
    model = MyModel

    def __init__(self,*args,**kwargs):      
      self._request = kwargs.pop('request', None)
      super(MyForm,self).__init__(*args,**kwargs)

class MyFormsetBase(BaseModelFormSet):
  _request = None

def __init__(self,*args,**kwargs):
  self._request = kwargs.pop('request', None)
  subFormClass = self.form
  self.form = curry(subFormClass,request=self._request)
  super(MyFormsetBase,self).__init__(*args,**kwargs)

MyFormset =  modelformset_factory(MyModel,formset=MyFormsetBase,extra=1,max_num=10,can_delete=True)
MyFormset.form = staticmethod(curry(MyForm,request=MyFormsetBase._request))

In my view, if I do something like this:

formset = MyFormset(request.POST,queryset=MyModel.objects.all(),request=request)

Then the "request" keyword gets propagated to all of the member forms of my formset. I'm pleased, but I have no idea why this is working - it seems wrong. Any suggestions?

trubliphone
  • 4,132
  • 3
  • 42
  • 66
  • Hmmm... Now if I try to access the form attribute of an instance of MyFormSet it (correctly) returns instead of . Any suggestions on how to access the actual form, though? I've tried `MyFormSet.form.Meta.model`. – trubliphone Feb 09 '12 at 07:41
  • Whoops... I have to _call_ the curried function in order to access the form. `MyFormSet.form().Meta.model`. Obvious really. – trubliphone Feb 09 '12 at 07:46
  • 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? http://stackoverflow.com/questions/14176265/limit-values-in-the-modelformset-field – finspin Jan 08 '13 at 11:29
1

I spent some time trying to figure out this problem before I saw this posting.

The solution I came up with was the closure solution (and it is a solution I've used before with Django model forms).

I tried the curry() method as described above, but I just couldn't get it to work with Django 1.0 so in the end I reverted to the closure method.

The closure method is very neat and the only slight oddness is that the class definition is nested inside the view or another function. I think the fact that this looks odd to me is a hangup from my previous programming experience and I think someone with a background in more dynamic languages wouldn't bat an eyelid!

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
1

I had to do a similar thing. This is similar to the curry solution:

def form_with_my_variable(myvar):
   class MyForm(ServiceForm):
     def __init__(self, myvar=myvar, *args, **kwargs):
       super(SeriveForm, self).__init__(myvar=myvar, *args, **kwargs)
   return MyForm

factory = inlineformset_factory(..., form=form_with_my_variable(myvar), ... )
Amandasaurus
  • 58,203
  • 71
  • 188
  • 248
0

I'm a newbie here so I can't add comment. I hope this code will work too:

ServiceFormSet = formset_factory(ServiceForm, extra=3)

ServiceFormSet.formset = staticmethod(curry(ServiceForm, affiliate=request.affiliate))

as for adding additional parameters to the formset's BaseFormSet instead of form.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
0

based on this answer I found more clear solution:

class ServiceForm(forms.Form):
    option = forms.ModelChoiceField(
            queryset=ServiceOption.objects.filter(affiliate=self.affiliate))
    rate = forms.DecimalField(widget=custom_widgets.SmallField())
    units = forms.IntegerField(min_value=1, 
            widget=custom_widgets.SmallField())

    @staticmethod
    def make_service_form(affiliate):
        self.affiliate = affiliate
        return ServiceForm

And run it in view like

formset_factory(form=ServiceForm.make_service_form(affiliate))
Community
  • 1
  • 1
alexey_efimov
  • 1,541
  • 2
  • 12
  • 16