157

这已在 Django 1.9 中使用form_kwargs 修复

我有一个看起来像这样的 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)

我用这样的方式调用这个表单:

form = ServiceForm(affiliate=request.affiliate)

request.affiliate登录用户在哪里。这按预期工作。

我的问题是我现在想把这个单一的表单变成一个表单集。我想不通的是如何在创建表单集时将附属信息传递给各个表单。根据文档来制作一个表单集,我需要做这样的事情:

ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)

然后我需要像这样创建它:

formset = ServiceFormSet()

现在我怎样才能以这种方式将affiliate=request.affiliate 传递给个人表单?

4

12 回答 12

107

我会使用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)

我认为这是最干净的方法,并且不会以任何方式影响 ServiceForm(即通过使其难以子类化)。

于 2009-03-08T18:00:26.520 回答
103

公文方式

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

于 2016-03-05T06:41:12.803 回答
47

我将在函数中动态构建表单类,以便它可以通过闭包访问附属机构:

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

作为奖励,您不必在选项字段中重写查询集。缺点是子类化有点时髦。(任何子类都必须以类似的方式制作。)

编辑:

作为对评论的回应,您可以在任何使用类名的地方调用此函数:

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)
    ...
于 2009-03-08T04:24:10.023 回答
16

这对我有用,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):

希望它对某人有所帮助,我花了足够长的时间才弄清楚;)

于 2014-09-18T14:29:59.323 回答
10

我喜欢闭包解决方案,因为它“更干净”并且更 Pythonic(所以 +1 到 mmarshall 答案),但 Django 表单也有一个回调机制,您可以使用它来过滤表单集中的查询集。

它也没有记录在案,我认为这表明 Django 开发人员可能不太喜欢它。

因此,您基本上创建了相同的表单集,但添加了回调:

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

这是创建一个类的实例,如下所示:

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

这应该给你一个大致的想法。使回调成为像这样的对象方法会稍微复杂一些,但与执行简单的函数回调相比,它为您提供了更多的灵活性。

于 2009-03-08T07:08:41.720 回答
10

我想将此作为对 Carl Meyers 回答的评论,但由于这需要积分,所以我将其放在这里。这花了我 2 个小时才弄清楚,所以我希望它会对某人有所帮助。

关于使用 inlineformset_factory 的说明。

我自己使用了该解决方案,并且效果很好,直到我使用 inlineformset_factory 进行了尝试。我正在运行 Django 1.0.2 并遇到了一些奇怪的 KeyError 异常。我升级到最新的主干,它直接工作。

我现在可以像这样使用它:

BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
BookFormSet.form = staticmethod(curry(BookForm, user=request.user))
于 2009-05-01T23:00:06.300 回答
10

截至 2012 年 8 月 14 日星期二 23:44:46 +0200 提交 e091c18f50266097f648efc7cac2503968e9d217 +0200 时,已接受的解决方案不再起作用。

当前版本的 django.forms.models.modelform_factory() 函数使用“类型构造技术”,在传递的表单上调用 type() 函数来获取元类类型,然后使用结果构造它的类对象即时输入::

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

这意味着即使传递了一个curryed 或partial对象而不是一个表单“导致鸭子咬你”,可以这么说:它会使用对象的构造参数调用一个函数ModelFormClass,返回错误消息::

function() argument 1 must be code, not str

为了解决这个问题,我编写了一个生成器函数,它使用闭包返回指定为第一个参数的任何类的子类,然后在使用生成器函数调用中提供的 kwargssuper.__init__之后调用:update

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

然后在您的代码中,您将表单工厂称为:

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

警告:

  • 这几乎没有得到任何测试,至少目前是这样
  • 提供的参数可能会发生冲突并覆盖那些将使用构造函数返回的对象的任何代码所使用的参数
于 2014-09-10T13:09:20.300 回答
3

Carl Meyer 的解决方案看起来非常优雅。我尝试为模型集实现它。我的印象是我不能在一个类中调用 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))

在我看来,如果我这样做:

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

然后“请求”关键字被传播到我的表单集的所有成员表单。我很高兴,但我不知道为什么会这样——这似乎是错误的。有什么建议么?

于 2012-02-09T07:16:01.840 回答
1

在看到这个帖子之前,我花了一些时间试图找出这个问题。

我想出的解决方案是闭包解决方案(这是我以前使用过的 Django 模型表单的解决方案)。

我尝试了上面描述的 curry() 方法,但我无法让它与 Django 1.0 一起工作,所以最后我恢复到了闭包方法。

闭包方法非常简洁,唯一有点奇怪的是类定义嵌套在视图或其他函数中。我认为这对我来说看起来很奇怪的事实是我以前的编程经验的一个挂断,我认为具有更动态语言背景的人不会眨眼!

于 2009-08-27T14:38:38.377 回答
1

我不得不做类似的事情。这类似于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), ... )
于 2010-10-27T12:12:55.947 回答
0

我是这里的新手,所以我无法添加评论。我希望这段代码也能工作:

ServiceFormSet = formset_factory(ServiceForm, extra=3)

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

至于向BaseFormSet表单集而不是表单添加其他参数。

于 2013-08-21T04:36:35.120 回答
0

基于这个答案,我找到了更清晰的解决方案:

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

并在视图中运行它

formset_factory(form=ServiceForm.make_service_form(affiliate))
于 2016-08-30T13:59:30.253 回答