【问题标题】:Django Passing Custom Form Parameters to FormsetDjango 将自定义表单参数传递给 Formset
【发布时间】:2010-10-12 00:00:16
【问题描述】:

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

【问题讨论】:

    标签: python django forms django-forms


    【解决方案1】:

    我会使用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(即通过使其难以子类化)。

    【讨论】:

    • 这对我不起作用。我收到错误:AttributeError: '_curriedFormSet' 对象没有属性 'get'
    • 我无法复制此错误。这也是一个奇怪的问题,因为表单集通常没有“get”属性,所以看起来你可能在代码中做了一些奇怪的事情。 (另外,我更新了答案,以摆脱像“_curriedFormSet”这样的怪事)。
    • 我正在重新审视这个问题,因为我想让您的解决方案发挥作用。我可以很好地声明表单集,但是如果我尝试打印它 {{ formset }} 是当我得到“没有属性'get'”错误时。您提供的任一解决方案都会发生这种情况。如果我遍历表单集并将表单打印为 {{ form }} 我再次收到错误。例如,如果我循环并打印为 {{ form.as_table }},我会得到空表单表,即。不打印任何字段。有什么想法吗?
    • 如果这里的评论线程没有意义,那是因为我刚刚编辑了答案以使用 Python 的 functools.partial 而不是 Django 的 django.utils.functional.curry。他们做同样的事情,除了 functools.partial 返回一个独特的可调用类型而不是常规 Python 函数,并且 partial 类型不绑定为实例方法,这巧妙地解决了这个评论线程主要致力于调试的问题.
    • 这是一个黑客!!使用 roach 遵循官方文档方式的答案
    【解决方案2】:

    公文方式

    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

    【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 谢谢——这有效。我推迟将其标记为已接受,因为我有点希望有一个更清洁的选择,因为这样做肯定感觉很时髦。
    • 标记为已接受,因为显然这是最好的方法。感觉怪怪的,但效果不错。 :) 谢谢。
    • Carl Meyer 有,我认为,你正在寻找的更清洁的方式。
    • 我在 Django ModelForms 中使用这个方法。
    • 我喜欢这个解决方案,但我不确定如何在表单集之类的视图中使用它。你有什么好的例子来说明如何在视图中使用它吗?任何建议表示赞赏。
    【解决方案4】:

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

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

    【讨论】:

    • 你能解释一下为什么这里需要staticmethod吗?
    【解决方案5】:

    我喜欢闭包解决方案,因为它“更干净”且更 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
    

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

    【讨论】:

    • 感谢您的回答。我现在正在使用 mmarshall 的解决方案,既然你同意它更像 Pythonic(我不知道这是我的第一个 Python 项目),我想我会坚持下去。不过,了解回调绝对是件好事。再次感谢。
    • 谢谢。这种方式适用于 modelformset_factory。我无法正确使用模型表单集的其他方式,但这种方式非常简单。
    • curry 函数本质上创建了一个闭包,不是吗?为什么你说@mmarshall 的解决方案更 Pythonic?顺便说一句,谢谢你的回答。我喜欢这种方法。
    【解决方案6】:

    我想将此作为对 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))
    

    【讨论】:

    • modelformset_factory 也是如此。感谢您的回答!
    【解决方案7】:

    截至 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
    

    为了解决这个问题,我编写了一个生成器函数,它使用闭包返回指定为第一个参数的任何类的子类,然后在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
    

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

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

    注意事项:

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

    【讨论】:

    • 谢谢,与这里的其他一些解决方案不同,似乎在 Django 1.10.1 中运行良好。
    • @fpghost 请记住,如果您需要做的就是更改表单所在的 QuerySet,至少要达到 1.9(由于多种原因,我仍然不在 1.10 上)构造后,您可以在返回的 MyFormSet 上更新它,方法是在使用它之前更改其 .queryset 属性。不如这种方法灵活,但更容易阅读/理解。
    • @RobM - 我收到错误 NameError: name 'self' is not defined
    • @shaan 在哪一行?对 super() 的调用(你可以简单地写为 super().__init__(*args, **kwargs) 现在你正在使用 Python 3)或对 inlineformset_factory 的调用?如果是调用工厂,则需要将 self.request.user 替换为代码中包含用户的变量。您可能没有使用基于类的视图,因此您没有 self,而是 request 作为参数:在这种情况下,它是 request.user
    【解决方案8】:

    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)
    

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

    【讨论】:

    • 嗯...现在,如果我尝试访问 MyFormSet 实例的表单属性,它(正确地)返回 而不是 。但是,关于如何访问实际表单的任何建议?我试过MyFormSet.form.Meta.model
    • 哎呀...我必须 调用 curried 函数才能访问表单。 MyFormSet.form().Meta.model。确实很明显。
    • 我一直在尝试将您的解决方案应用于我的问题,但我认为我并不完全理解您的全部答案。如果您的方法可以在这里应用于我的问题,有什么想法吗? stackoverflow.com/questions/14176265/…
    【解决方案9】:

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

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

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

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

    【讨论】:

      【解决方案10】:

      我不得不做类似的事情。这类似于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】:

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

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

        至于给formset的BaseFormSet添加额外的参数而不是form。

        【讨论】:

          【解决方案12】:

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

          【讨论】:

          • Django 1.9 使这一切变得不必要,请改用 form_kwargs。
          • 在我目前的工作中我们需要使用legacy django 1.7((
          猜你喜欢
          • 2010-11-15
          • 2014-08-23
          • 2023-03-20
          • 2023-03-19
          • 2023-03-24
          • 2016-12-13
          • 2020-04-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多