Django将自定义窗体parameter passing给Formset
这是在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
是login的用户。 这按预期工作。
我的问题是,我现在想把这个单一的forms变成一个formset。 我无法弄清楚的是如何在创buildformset时将联盟信息传递给各个表单。 根据这个文档,我需要做这样的事情:
ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)
然后我需要像这样创build它:
formset = ServiceFormSet()
现在我怎么可以通过这种方式将affiliate = request.affiliate传递给个人表单?
我会使用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(即通过使子类很难)。
我将在函数中dynamic构build表单类,以便通过closures访问联属机构:
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) ...
这是我的工作,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):
希望它能帮助别人,花了我很长时间才弄清楚;)
Django 1.9:
ArticleFormSet = formset_factory(MyArticleForm) formset = ArticleFormSet(form_kwargs={'user': request.user})
我喜欢封闭的解决scheme是“更干净”,更Pythonic(所以+1到mmarshall答案),但Django窗体也有一个callback机制,你可以用来过滤查询集在formset。
这也没有logging,我认为这是Django的开发者可能不太喜欢它的一个指标。
所以你基本上创build你的formset相同,但添加callback:
ServiceFormSet = forms.formsets.formset_factory( ServiceForm, extra=3, formfield_callback=Callback('option', affiliate).cb)
这是创build一个类的实例,如下所示:
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
这应该给你一个总的想法。 这是一个更复杂一点的callback一个像这样的对象方法,但给你更多的灵活性,而不是做一个简单的函数callback。
我想把这个作为对卡尔·迈耶斯回答的评论,但是因为这需要点,我只是把它放在这里。 这花了我2个小时弄清楚,所以我希望它会帮助别人。
关于使用inlineformset_factory的说明。
我用自己的解决scheme,它完美的工作,直到我用inlineformset_factory试试。 我正在运行Django 1.0.2,并得到了一些奇怪的KeyErrorexception。 我升级到最新的主干,它直接工作。
我现在可以使用它类似于这样的:
BookFormSet = inlineformset_factory(Author, Book, form=BookForm) BookFormSet.form = staticmethod(curry(BookForm, user=request.user))
自从8月14日星期二23:44:46 2012 +0200提交e091c18f50266097f648efc7cac2503968e9d217之后,接受的解决scheme就不能工作了。
当前版本的django.forms.models.modelform_factory()函数使用“types构造技术”,在传递的表单上调用type()函数以获取元types,然后使用结果构造其类对象在飞行中input::
# Instatiate type(form) in order to use the same metaclass as form. return type(form)(class_name, (form,), form_class_attrs)
这意味着即使是一个curry
ed或partial
对象,而不是一个forms“让鸭子咬你”,可以这么说:它将调用一个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))
注意事项:
- 这至less在现在收到了很less的testing
- 提供的参数可能会冲突并覆盖那些使用由构造函数返回的对象的代码
卡尔迈尔的解决scheme看起来非常优雅。 我试图实现它modelformsets。 我的印象是,我不能在一个类中调用静态方法,但是下面这个莫名其妙的作品:
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)
然后“请求”关键字传播到我的formset的所有成员forms。 我很高兴,但我不知道为什么这个工作 – 这似乎是错误的。 有什么build议么?
在我看到这个post之前,我花了一些时间想弄清楚这个问题。
我提出的解决scheme是封闭解决scheme(这是我以前用Django模型表单使用的解决scheme)。
我尝试了如上所述的curry()方法,但是我只是无法让它与Django 1.0一起工作,所以最后我还是回到了closures方法。
闭包方法非常简洁,唯一的一个小问题就是类定义嵌套在视图或其他函数中。 我觉得这个看起来很奇怪的事实是我之前编程经验的一个悬念,我认为有更多dynamic语言背景的人不会眨眼睛!
我不得不做类似的事情。 这与curry
解决scheme类似:
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), ... )
我在这里是新手,所以我不能添加评论。 我希望这个代码也能工作:
ServiceFormSet = formset_factory(ServiceForm, extra=3) ServiceFormSet.formset = staticmethod(curry(ServiceForm, affiliate=request.affiliate))
至于添加额外的参数到formset的BaseFormSet
而不是form。
基于这个答案,我发现更明确的解决scheme:
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))