简体   繁体   English

Django 将自定义表单参数传递给 Formset

[英]Django Passing Custom Form Parameters to Formset

This was fixed in Django 1.9 with form_kwargs .这已在 Django 1.9 中使用form_kwargs 修复

I have a Django Form that looks like this:我有一个 Django 表格,如下所示:

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.其中request.affiliate是登录用户。 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.我想不通的是如何在创建表单集时将附属信息传递给个人 forms。 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?现在我怎样才能以这种方式将affiliate=request.affiliate 传递给个人forms?

I would use functools.partial and functools.wraps :我会使用functools.partialfunctools.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 (ie by making it difficult to subclass).我认为这是最干净的方法,并且不会以任何方式影响 ServiceForm(即通过使其难以子类化)。

Official Document Way公文方式

Django 2.0: 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 https://docs.djangoproject.com/en/2.0/topics/forms/formsets/#passing-custom-parameters-to-formset-forms

I would build the form class dynamically in a function, so that it has access to the affiliate via closure:我将在 function 中动态构建 class 表格,以便它可以通过关闭访问会员:

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:作为对评论的回应,您可以在任何使用 class 名称的地方调用此 function:

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

This is what worked for me, Django 1.7:这对我有用,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;)希望它对某人有所帮助,我花了足够长的时间才弄清楚;)

As of commit e091c18f50266097f648efc7cac2503968e9d217 on Tue Aug 14 23:44:46 2012 +0200 the accepted solution can't work anymore.截至 2012 年 8 月 14 日星期二 23:44:46 +0200 提交 e091c18f50266097f648efc7cac2503968e9d217 +0200 时,已接受的解决方案不再起作用。

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:: 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即时输入::

# 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 curry ed 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::这意味着即使是curry ed 或partial object 传递,而不是“导致鸭子咬你”的形式,可以这么说:它会调用 function 和ModelFormClass的构造参数,返回错误消息:

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 update ing the kwargs with the ones supplied on the generator function's call::为了解决这个问题,我编写了一个生成器 function ,它使用闭包返回指定为第一个参数的任何 class 的子类,然后在update使用生成器函数调用中提供的 kwargs 之后调用super.__init__

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提供的参数可能会发生冲突并覆盖那些将使用构造函数返回的 object 的代码

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.我喜欢闭包解决方案,因为它“更干净”并且更 Pythonic(所以 +1 到 mmarshall 答案),但是 Django forms 也有一个回调机制,可用于过滤表单集中的查询集。

It's also not documented, which I think is an indicator the Django devs might not like it as much.它也没有记录,我认为这表明 Django 开发人员可能不太喜欢它。

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 的实例,如下所示:

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.将回调设置为像这样的 object 方法会稍微复杂一些,但与执行简单的 function 回调相比,它给您更多的灵活性。

I wanted to place this as a comment to Carl Meyers answer, but since that requires points I just placed it here.我想将此作为对 Carl Meyers 回答的评论,但由于这需要积分,所以我将其放在这里。 This took me 2 hours to figure out so I hope it will help someone.这花了我2个小时才弄清楚,所以我希望它会对某人有所帮助。

A note about using the inlineformset_factory.关于使用 inlineformset_factory 的说明。

I used that solution my self and it worked perfect, until I tried it with the inlineformset_factory.我自己使用了该解决方案,并且效果很好,直到我使用 inlineformset_factory 进行了尝试。 I was running Django 1.0.2 and got some strange KeyError exception.我正在运行 Django 1.0.2 并得到一些奇怪的 KeyError 异常。 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))

Carl Meyer's solution looks very elegant. Carl Meyer 的解决方案看起来非常优雅。 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 中调用 staticmethods,但以下莫名其妙的工作:

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.然后“请求”关键字被传播到我的表单集的所有成员 forms。 I'm pleased, but I have no idea why this is working - it seems wrong.我很高兴,但我不知道为什么会这样——这似乎是错误的。 Any suggestions?有什么建议么?

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).我想出的解决方案是闭包解决方案(这是我之前使用 Django model 表格的解决方案)。

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.我尝试了如上所述的 curry() 方法,但我无法让它与 Django 1.0 一起使用,所以最后我恢复到了闭包方法。

The closure method is very neat and the only slight oddness is that the class definition is nested inside the view or another function.关闭方法非常简洁,唯一有点奇怪的是 class 定义嵌套在视图或另一个 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!我认为这对我来说看起来很奇怪的事实是我以前的编程经验的一个挂断,我认为具有更动态语言背景的人不会眨眼!

I had to do a similar thing.我不得不做类似的事情。 This is similar to the curry solution:这类似于curry解决方案:

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

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.至于将附加参数添加到表单集的BaseFormSet而不是表单。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM