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

Pao*_*ino 145 python forms django django-forms

这是使用form_kwargs在Django 1.9中修复的.

我有一个看起来像这样的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)
Run Code Online (Sandbox Code Playgroud)

我用这样的方式称这个形式:

form = ServiceForm(affiliate=request.affiliate)
Run Code Online (Sandbox Code Playgroud)

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

我的问题是我现在想把这个单一的表单变成一个formset.我无法弄清楚的是,在创建formset时,我如何将联盟信息传递给各个表单.根据文档制作一个formset,我需要做这样的事情:

ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)
Run Code Online (Sandbox Code Playgroud)

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

formset = ServiceFormSet()
Run Code Online (Sandbox Code Playgroud)

现在,我如何通过这种方式将affiliate = request.affiliate传递给单个表单?

Car*_*yer 105

我会使用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)
Run Code Online (Sandbox Code Playgroud)

我认为这是最干净的方法,并且不会以任何方式影响ServiceForm(即难以进行子类化).

  • 如果这里的评论主题没有意义,那是因为我刚编辑了使用Python的`functools.partial`而不是Django的`django.utils.functional.curry`的答案.他们做同样的事情,除了`functools.partial`返回一个不同的可调用类型而不是常规的Python函数,并且`partial`类型不作为实例方法绑定,这整齐地解决了这个注释线程主要投入的问题调试. (4认同)
  • 这是一个黑客!使用 roach 的答案,遵循官方文档方式 (2认同)

roa*_*ach 61

正式文件方式

Django 2.0:

ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})
Run Code Online (Sandbox Code Playgroud)

https://docs.djangoproject.com/en/2.0/topics/forms/formsets/#passing-custom-parameters-to-formset-forms

  • 这应该是现在这样做的正确方法.接受的答案是有效的,但很好但是是一个黑客 (6认同)
  • 绝对是最好的答案和正确的方法。 (2认同)
  • 也适用于 Django 1.11 https://docs.djangoproject.com/en/1.11/topics/forms/formsets/#passing-custom-parameters-to-formset-forms (2认同)

Mat*_*all 45

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

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
Run Code Online (Sandbox Code Playgroud)

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

编辑:

在回复评论时,您可以在任何使用类名的地方调用此函数:

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)
    ...
Run Code Online (Sandbox Code Playgroud)


rix*_*rix 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):
Run Code Online (Sandbox Code Playgroud)

希望它对某人有所帮助,带我走了很长时间才弄明白;)


Van*_*ale 9

我喜欢封闭解决方案是"更干净"和更Pythonic(所以+1到mmarshall答案)但Django表单也有一个回调机制,你可以用来过滤formsets中的查询集.

它也没有记录,我认为这是Django开发者可能不喜欢它的一个指标.

所以你基本上创建你的formset相同,但添加回调:

ServiceFormSet = forms.formsets.formset_factory(
    ServiceForm, extra=3, formfield_callback=Callback('option', affiliate).cb)
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

这应该给你一般的想法.使回调成为这样的对象方法有点复杂,但与简单的函数回调相比,它给你一点灵活性.


Joh*_*son 9

我想把它作为对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))
Run Code Online (Sandbox Code Playgroud)


Rob*_*obM 9

截至2012年8月14日23:44:46 + 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)
Run Code Online (Sandbox Code Playgroud)

这意味着即使是传递的curryed或partial对象而不是表单"导致鸭子咬你"这样说:它将使用ModelFormClass对象的构造参数调用函数,返回错误消息::

function() argument 1 must be code, not str
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我编写了一个生成器函数,它使用一个闭包来返回指定为第一个参数的任何类的子类,然后在使用生成器函数调用的提供的kwargs super.__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
Run Code Online (Sandbox Code Playgroud)

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

MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user))
Run Code Online (Sandbox Code Playgroud)

注意事项:

  • 到目前为止,这几乎没有得到任何测试
  • 提供的参数可以冲突并覆盖由任何代码使用的那些将使用构造函数返回的对象