カスタムフォームパラメーターをFormsetに渡すDjango


150

これは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ログインしているユーザーはどこですか。これは意図したとおりに機能します。

私の問題は、この単一のフォームをフォームセットにしたいということです。私が理解できないのは、フォームセットの作成時にアフィリエイト情報を個々のフォームに渡す方法です。ドキュメントからこれからフォームセットを作成するために、私はこのようなことをする必要があります:

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

そして、私はこのようにそれを作成する必要があります:

formset = ServiceFormSet()

このようにして、どうすればaffiliate = request.affiliateを個々のフォームに渡すことができますか?

回答:


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)

これは最もクリーンなアプローチであり、ServiceFormに影響を与えない(つまり、サブクラス化を困難にする)と思います。


うまくいきません。エラーが表示されます:AttributeError: '_curriedFormSet' object has no attribute 'get'
Paolo Bergantino

このエラーは再現できません。formsetは通常 'get'属性を持たないため、これは奇妙なことでもあり、コードで奇妙なことをしているようです。(また、 '_ curriedFormSet'などの奇妙な要素を取り除く方法で回答を更新しました)。
カールマイヤー

私はあなたのソリューションを機能させたいので、これを再訪します。formsetを細かく宣言することはできますが、印刷しようとすると{{formset}}を実行すると「has no attribute 'get'」エラーが発生します。あなたが提供したどちらのソリューションでも起こります。formsetをループしてフォームを{{form}}として出力すると、エラーが再び発生します。たとえば、ループして{{form.as_table}}として出力すると、空のフォームテーブルが取得されます。フィールドは印刷されません。何か案は?
Paolo Bergantino、

あなたは正しい、ごめんなさい。私の以前のテストは十分に行きませんでした。私はこれを追跡しましたが、FormSetsが内部で動作する方法のいくつかの奇妙さのために壊れています。問題を回避する方法はありますが、それは元の優雅さを失い始めます...
カールマイヤー

5
ここのコメントスレッドが意味をなさない場合はfunctools.partial、Djangoの代わりにPythonを使用するように回答を編集しただけdjango.utils.functional.curryです。それらは同じことを行いfunctools.partialますが、通常のPython関数の代わりに呼び出し可能な個別の型を返し、partial型はインスタンスメソッドとしてバインドされないため、このコメントスレッドが主にデバッグに費やされていた問題が適切に解決されます。
Carl Meyer、

81

公式ドキュメントウェイ

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


8
これは今それを行う正しい方法であるべきです。受け入れられた回答は機能し、すばらしいですがハックです
Junchao Gu

間違いなく最良の答えとそれを行う正しい方法です。
yaniv14


46

フォームクラスを関数内で動的に構築し、クロージャーを介してアフィリエイトにアクセスできるようにします。

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

ありがとう-うまくいきました。この方法で実行することは間違いなくファンキーな感じがするので、よりクリーンなオプションがあることを望んでいるので、これを承認済みとしてマークすることを控えています。
Paolo Bergantino

明らかにこれがそれを行う最良の方法であるため、承認済みとしてマークします。奇妙に感じますが、トリックを行います。:) ありがとうございました。
Paolo Bergantino

カール・マイヤーはあなたが探していたよりきれいな方法を持っていると思います。
ジャレットハーディー

このメソッドをDjango ModelFormsで使用しています。
chefsmart 2009年

私はこのソリューションが好きですが、フォームセットのようなビューでそれをどのように使用するかわかりません。これをビューで使用する方法の良い例はありますか?任意の提案をいただければ幸いです。
ジョーJ

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

それが誰かを助けることを願って、それを理解するのに十分な時間がかかった;)


1
staticmethodここでなぜ必要なのか説明してくれませんか?
fpghost

9

「よりクリーン」でPythonic(+1からmmarshallへの回答)のクロージャーソリューションが好きですが、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

これはあなたに一般的な考えを与えるはずです。コールバックをこのようなオブジェクトメソッドにすることは少し複雑ですが、単純な関数のコールバックを行うのとは対照的に、少し柔軟性があります。


1
答えてくれてありがとう。私は今mmarshallのソリューションを使用していますが、あなたが同意するのはそれがよりPythonicである(これは私の最初のPythonプロジェクトなので知らないものです)私はそれにこだわっていると思います。ただし、コールバックについて知っていることは間違いなく良いことです。再度、感謝します。
Paolo Bergantino

1
ありがとうございました。この方法は、modelformset_factoryでうまく機能します。他の方法でモデルフォームセットを適切に操作することはできませんでしたが、この方法は非常に簡単でした。
スパイク

カレーファンクショナルは本質的にクロージャーを作りますね?@mmarshallのソリューションがよりPythonicであると言うのはなぜですか?ところで、あなたの答えをありがとう。私はこのアプローチが好きです。
Josh

9

カールマイヤーズの回答に対するコメントとしてこれを配置したかったのですが、ポイントが必要なので、ここに配置しました。これが理解するのに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))

同じことが当てはまりmodelformset_factoryます。この回答をありがとう!
2014年

9

コミットe091c18f50266097f648efc7cac2503968e9d217火曜日8月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)

これは、フォームの代わりに渡されたcurryedまたはpartialオブジェクトでも、いわば「アヒルがあなたを噛ませる」ことを意味します。ModelFormClassオブジェクトの構築パラメーターを使用して関数を呼び出し、エラーメッセージを返します::

function() argument 1 must be code, not str

これを回避するために、クロージャーを使用して、最初のパラメーターとして指定された任意のクラスのサブクラスを返すジェネレーター関数を作成し、ジェネレーター関数の呼び出しで提供されたもので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

次に、コードでフォームファクトリを次のように呼び出します。

MyFormSet = inlineformset_factory(ParentModel, Model,form = class_gen_with_kwarg(MyForm, user=self.request.user))

警告:

  • これは少なくとも今のところほとんどテストを受けていません
  • 提供されたパラメーターは、コンストラクターによって返されたオブジェクトを使用するコードが使用するパラメーターと衝突して上書きする可能性があります

ありがとう、Django 1.10.1では、他のいくつかのソリューションとは異なり、非常にうまく機能しているようです。
fpghost 2016

1
@fpghostは、少なくとも1.9まで(いくつかの理由により、私はまだ1.10を使用していません)フォームを作成するクエリセットを変更する必要がある場合は、フォームでフォームを更新できます使用する前に.queryset属性を変更してMyFormSetを返しました。この方法ほど柔軟性はありませんが、読み取り/理解がはるかに簡単です。
RobM 2016

3

カールマイヤーのソリューションは非常にエレガントに見えます。私はそれをモデルフォームセットに実装してみました。クラス内で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)

次に、「リクエスト」キーワードがフォームセットのすべてのメンバーフォームに反映されます。嬉しいですが、なぜこれが機能するのかわかりません-間違っているようです。助言がありますか?


うーん... MyFormSetのインスタンスのform属性にアクセスしようとすると、(正しく)<MyForm>ではなく<function _curried>が返されます。実際のフォームにアクセスする方法について何か提案はありますか?試しましたMyFormSet.form.Meta.model
trubliphone

フォームにアクセスするには、カレー関数を呼び出さなければなりません。 MyFormSet.form().Meta.model。本当に明らかです。
trubliphone

私はあなたの解決策を私の問題に適用しようとしましたが、私はあなたの答え全体を完全に理解していないと思います。ここで私の問題にあなたのアプローチを適用できるかどうかのアイデアはありますか?stackoverflow.com/questions/14176265/...
finspin

1

この投稿を見る前に、この問題を理解するために少し時間を費やしました。

私が思いついたソリューションは、クロージャーソリューションでした(これは、以前Djangoモデルフォームで使用したソリューションです)。

上記のようにcurry()メソッドを試しましたが、Django 1.0で動作させることができなかったので、最終的にクロージャーメソッドに戻りました。

クロージャーメソッドは非常に整然としており、わずかに奇妙な点は、クラス定義がビューまたは別の関数内にネストされていることです。これが私には奇妙に見えるという事実は、私の以前のプログラミング経験からのハングアップであり、より動的な言語のバックグラウンドを持つ誰かがまぶたを打つことはないと思います!


1

私も同じようなことをしなければなりませんでした。これは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), ... )

1

この答えに基づいて私はより明確な解決策を見つけました:

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

6
Django 1.9はこれを不要にしました。代わりにform_kwargsを使用してください。
Paolo Bergantino 2016

私の現在の作業では、レガシーdjango 1.7(((
alexey_efimov

0

私はここの初心者なので、コメントを追加できません。このコードがうまくいくことを願っています:

ServiceFormSet = formset_factory(ServiceForm, extra=3)

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

BaseFormSetフォームの代わりにフォームセットに追加のパラメータを追加することに関しては。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.