簡體   English   中英

Django 將自定義表單參數傳遞給 Formset

[英]Django Passing Custom Form Parameters to 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是登錄用戶。 這按預期工作。

我的問題是我現在想把這個單一的表單變成一個表單集。 我想不通的是如何在創建表單集時將附屬信息傳遞給個人 forms。 根據文檔來制作一個表單集,我需要做這樣的事情:

ServiceFormSet = forms.formsets.formset_factory(ServiceForm, extra=3)

然后我需要像這樣創建它:

formset = ServiceFormSet()

現在我怎樣才能以這種方式將affiliate=request.affiliate 傳遞給個人forms?

我會使用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(即通過使其難以子類化)。

公文方式

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

我將在 function 中動態構建 class 表格,以便它可以通過關閉訪問會員:

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

作為獎勵,您不必在選項字段中重寫查詢集。 缺點是子類化有點時髦。 (任何子類都必須以類似的方式制作。)

編輯:

作為對評論的回應,您可以在任何使用 class 名稱的地方調用此 function:

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

希望它對某人有所幫助,我花了足夠長的時間才弄清楚;)

截至 2012 年 8 月 14 日星期二 23:44:46 +0200 提交 e091c18f50266097f648efc7cac2503968e9d217 +0200 時,已接受的解決方案不再起作用。

The current version of django.forms.models.modelform_factory() function uses a "type construction technique", calling the type() function on the passed form to get the metaclass type, then using the result to construct a class-object of its即時輸入::

# Instatiate type(form) in order to use the same metaclass as form.
return type(form)(class_name, (form,), form_class_attrs)

這意味着即使是curry ed 或partial object 傳遞,而不是“導致鴨子咬你”的形式,可以這么說:它會調用 function 和ModelFormClass的構造參數,返回錯誤消息:

function() argument 1 must be code, not str

為了解決這個問題,我編寫了一個生成器 function ,它使用閉包返回指定為第一個參數的任何 class 的子類,然后在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))

警告:

  • 這幾乎沒有得到任何測試,至少目前是這樣
  • 提供的參數可能會發生沖突並覆蓋那些將使用構造函數返回的 object 的代碼

我喜歡閉包解決方案,因為它“更干凈”並且更 Pythonic(所以 +1 到 mmarshall 答案),但是 Django forms 也有一個回調機制,可用於過濾表單集中的查詢集。

它也沒有記錄,我認為這表明 Django 開發人員可能不太喜歡它。

因此,您基本上創建了相同的表單集,但添加了回調:

ServiceFormSet = forms.formsets.formset_factory(
    ServiceForm, extra=3, formfield_callback=Callback('option', affiliate).cb)

這是創建一個 class 的實例,如下所示:

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

這應該給你一個大致的想法。 將回調設置為像這樣的 object 方法會稍微復雜一些,但與執行簡單的 function 回調相比,它給您更多的靈活性。

我想將此作為對 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))

Carl Meyer 的解決方案看起來非常優雅。 我嘗試為模型集實現它。 我的印象是我不能在 class 中調用 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)

然后“請求”關鍵字被傳播到我的表單集的所有成員 forms。 我很高興,但我不知道為什么會這樣——這似乎是錯誤的。 有什么建議么?

在看到這個帖子之前,我花了一些時間試圖找出這個問題。

我想出的解決方案是閉包解決方案(這是我之前使用 Django model 表格的解決方案)。

我嘗試了如上所述的 curry() 方法,但我無法讓它與 Django 1.0 一起使用,所以最后我恢復到了閉包方法。

關閉方法非常簡潔,唯一有點奇怪的是 class 定義嵌套在視圖或另一個 function 內。 我認為這對我來說看起來很奇怪的事實是我以前的編程經驗的一個掛斷,我認為具有更動態語言背景的人不會眨眼!

我不得不做類似的事情。 這類似於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), ... )

我是這里的新手,所以我無法添加評論。 我希望這段代碼也能工作:

ServiceFormSet = formset_factory(ServiceForm, extra=3)

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

至於將附加參數添加到表單集的BaseFormSet而不是表單。

基於這個答案,我找到了更清晰的解決方案:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM