これはDjango 1.9 with 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を個々のフォームにこのように渡すことができますか?
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には何の影響も与えないと思います(つまり、サブクラス化を困難にすることによって)。
Django 2.0:
ArticleFormSet = formset_factory(MyArticleForm)
formset = ArticleFormSet(form_kwargs={'user': request.user})
関数でフォームクラスを動的に構築し、閉鎖を介してアフィリエイトにアクセスできるようにします。
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):
それが誰かを助けることを願って、それを理解するのに十分長い時間がかかりました;)
これを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))
「クリーナー」でよりPythonicなクロージャーソリューションが好きです(つまり、mmarshallへの+1)が、Djangoフォームには、フォームセットでクエリセットをフィルタリングするために使用できるコールバックメカニズムもあります。
また、文書化されていませんが、これはDjango devsがあまり気に入らないかもしれない指標です。
したがって、基本的に同じフォームセットを作成しますが、コールバックを追加します。
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
これにより、一般的なアイデアが得られます。コールバックをこのようなオブジェクトメソッドにするのは少し複雑ですが、単純な関数コールバックを実行するのとは対照的に、もう少し柔軟性があります。
コミットe091c18f50266097f648efc7cac2503968e9d217(Tue Aug 14 23:44:46 2012 +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)
これは、フォームの代わりに渡されたcurry
edまたはpartial
オブジェクトでさえ、いわば「アヒルに噛み付かせる」ことを意味します。つまり、ModelFormClass
の構築パラメーターで関数を呼び出します。エラーメッセージを返すオブジェクト::
function() argument 1 must be code, not str
これを回避するために、クロージャーを使用して最初のパラメーターとして指定されたクラスのサブクラスを返すジェネレーター関数を作成し、ジェネレーター関数の呼び出しで提供されたものでkwargsをupdate
ingした後に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))
警告:
Carl Meyerのソリューションは非常にエレガントに見えます。 modelformsetsに実装してみました。クラス内で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)
次に、「request」キーワードが、フォームセットのすべてのメンバーフォームに伝達されます。喜んでいますが、なぜこれが機能しているのか分かりません-間違っているようです。助言がありますか?
この投稿を見る前に、私はこの問題を理解しようといくつかの時間を費やしました。
私が思いついた解決策はクロージャーの解決策でした(そしてこれは、以前に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), ... )
私はここに初心者なので、コメントを追加できません。このコードも機能することを願っています:
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))