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

这是用 Form _ kwargs在 Django 1.9中修复的。

我有一个姜戈表格,看起来像这样:

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

现在,我如何以这种方式向各个表单传递子公司 = request. 子公司?

49715 次浏览

我将在函数中动态构建表单类,这样它就可以通过闭包访问子公司:

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

我喜欢闭包解决方案,因为它更“简洁”,更 Python 化(所以 mmarshall 的回答是 + 1) ,但是 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

这应该能给你一个大概的概念。使回调成为类似这样的对象方法要复杂一些,但是与简单的函数回调相比,它提供了更多的灵活性。

我会使用 Functools.partWraps:

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 (比如,使它难以子类化)。

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

关于使用 inlineformset _ Factory 的注意事项。

我自己使用了这个解决方案,它工作得很好,直到我用 inlineformset _ Factory 尝试了它。我正在运行 Django 1.0.2,得到了一些奇怪的 KeyError 异常。我升级到最新的主干,它直接工作。

我现在可以使用类似于这样的方法:

BookFormSet = inlineformset_factory(Author, Book, form=BookForm)
BookFormSet.form = staticmethod(curry(BookForm, user=request.user))

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

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

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

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

我不得不做一件类似的事情,这与 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), ... )

卡尔•迈耶(Carl Meyer)的解决方案看上去非常优雅。我尝试为模型集实现它。我的印象是,我不能在一个类中调用 staticmethod,但是下面的方法令人费解地起作用:

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”关键字将被传播到我的格式集的所有成员表单。我很高兴,但我不知道为什么这是工作-它似乎是错误的。有什么建议吗?

我在这里是个新手,所以我不能添加注释。我希望这段代码也能起作用:

ServiceFormSet = formset_factory(ServiceForm, extra=3)


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

为格式集的 BaseFormSet而不是 form 添加额外的参数。

截至提交 e091c18f50266097f648efc7cac2503968e9d217于8月14日星期二23:44:462012 + 0200接受的解决方案不能再工作了。

当前版本的 django.forms.mods.model _ 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

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

注意事项:

  • 至少目前来说,这种做法几乎没有受到什么考验
  • 所提供的参数可能会冲突并覆盖那些由任何代码使用构造函数返回的对象所使用的参数

姜戈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):

希望它能帮到别人,我花了很长时间才弄明白;)

正式文件方式

姜戈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

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

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