代码之家  ›  专栏  ›  技术社区  ›  Paolo Bergantino

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

  •  135
  • Paolo Bergantino  · 技术社区  · 15 年前

    这是在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传递给各个表单?

    12 回复  |  直到 6 年前
        1
  •  102
  •   Carl Meyer    10 年前

    我会用 functools.partial 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)
    

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

        2
  •  45
  •   Matthew Marshall    14 年前

    我将在一个函数中动态地构建表单类,这样它就可以通过闭包访问关联:

    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)
        ...
    
        3
  •  37
  •   Bigair    6 年前

    公文方式

    Django 2:

    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

        4
  •  16
  •   rix    10 年前

    这就是我的工作,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):
    

    希望它能帮助别人,花了我足够长的时间才弄明白;)

        5
  •  9
  •   Van Gale    15 年前

    我喜欢闭包解决方案,因为它“更干净”和更多的pythonic(so+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
    

    这应该给你一个大概的想法。使回调成为这样的对象方法有点复杂,但是与执行简单的函数回调相比,它给了您更多的灵活性。

        6
  •  9
  •   Peter Mortensen Len Greski    15 年前

    我想把这句话作为对卡尔·梅耶斯答案的评论,但因为这需要点,所以我把它放在这里。我花了2个小时才弄明白,所以我希望它能帮助别人。

    关于使用inlineformset_工厂的说明。

    我用这个方法解决了我自己的问题,而且效果很好,直到我在inlineformset工厂尝试过。我在运行django 1.0.2,得到了一些奇怪的keyerror异常。我升级到最新的行李箱,它直接工作。

    我现在可以这样使用它:

    BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
    BookFormSet.form = staticmethod(curry(BookForm, user=request.user))
    
        7
  •  9
  •   RobM    10 年前

    截止到2012年8月14日星期二23:44:46+0200提交e091c18f50266097f648efc7cac2503968e9d217,已接受的解决方案将无法再工作。

    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)
    

    这意味着即使是 curry ED或 partial 对象传递而不是窗体“导致鸭子咬你”,可以这么说:它将使用 ModelFormClass 对象,返回错误消息::

    function() argument 1 must be code, not str
    

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

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

    注意事项:

    • 至少目前为止,这还没有得到多少测试。
    • 提供的参数可能会冲突并覆盖由任何代码使用构造函数返回的对象所使用的参数。
        8
  •  3
  •   trubliphone    13 年前

    卡尔·迈耶的解决方案看起来很优雅。我试着为模型集实现它。我觉得我不能在类中调用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)
    

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

        9
  •  1
  •   Peter Mortensen Len Greski    15 年前

    在看到这篇文章之前,我花了一些时间想弄清楚这个问题。

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

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

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

        10
  •  1
  •   Amandasaurus    14 年前

    我也不得不做类似的事情。这和 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), ... )
    
        11
  •  0
  •   Brian Tompsett - 汤莱恩 andrewwong97    9 年前

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

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

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

        12
  •  0
  •   Community Tales Farias    7 年前

    基于 this answer 我找到了更清晰的解决方案:

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