フォームのclean()メソッドでリクエストオブジェクトまたはその他の変数にアクセスするにはどうすればよいですか?


99

フォームのクリーンメソッドのrequest.userを試行していますが、リクエストオブジェクトにアクセスするにはどうすればよいですか?変数の入力を許可するようにcleanメソッドを変更できますか?

回答:


157

Berによる答え-スレッドローカルに保存することは非常に悪い考えです。このようにする理由はまったくありません。

より良い方法は、フォームの__init__メソッドをオーバーライドして、追加のキーワード引数を取ることrequestです。これは、リクエスト格納フォームこれは必須です、そして、あなたのきれいな方法でアクセスできる場所から。

class MyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(MyForm, self).__init__(*args, **kwargs)


    def clean(self):
        ... access the request object via self.request ...

そしてあなたの見解では:

myform = MyForm(request.POST, request=request)

4
あなたはこの場合正しいです。ただし、これはフォーム/ビューの変更が望ましくない場合がありました。また、メソッドローカルパラメータまたはインスタンス変数を追加できないスレッドローカルストアの使用例もあります。リクエストデータへのアクセスを必要とするクエリフィルターへの呼び出し可能な引数について考えます。呼び出しにパラメーターを追加することも、参照するインスタンスを追加することもできません。
Ber

4
リクエスト変数を渡してフォームを初期化できるので、管理フォームを拡張しているときは役に立ちません。何か案が?
モルディ

13
スレッドローカルストレージを使用することが非常に悪い考えであると言うのはなぜですか?どこにでもリクエストを渡すためにコードをドロップする必要がなくなります。
Michael Mior

9
リクエストオブジェクト自体をフォームに渡すのではなく、必要なリクエストのフィールド(つまりユーザー)を渡します。それ以外の場合は、フォームロジックをリクエスト/レスポンスサイクルに関連付けて、テストを困難にします。
Andrew Ingram

2
Chris Prattも、admin.ModelAdminでフォームを処理する場合に適切な解決策を持っています
radtek

34

更新日2011/10/25:Django 1.3は奇妙さを表示するため、メソッドの代わりに動的に作成されたクラスでこれを使用しています。

class MyModelAdmin(admin.ModelAdmin):
    form = MyCustomForm
    def get_form(self, request, obj=None, **kwargs):
        ModelForm = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
        class ModelFormWithRequest(ModelForm):
            def __new__(cls, *args, **kwargs):
                kwargs['request'] = request
                return ModelForm(*args, **kwargs)
        return ModelFormWithRequest

次にMyCustomForm.__init__、次のようにオーバーライドします。

class MyCustomForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(MyCustomForm, self).__init__(*args, **kwargs)

その後、ModelFormwithの任意のメソッドからリクエストオブジェクトにアクセスできますself.request


1
クリス、「def __init __(self、request = None、* args、** kwargs)」は、最初の定位置引数とkwargsの両方でリクエストになってしまうため、不正です。私はそれを「def __init __(self、* args、** kwargs)」に変更し、それは機能します。
slinkp 2011

1
おっと。それは私の間違いです。他の更新を行ったとき、コードのその部分を更新するのを怠っていました。キャッチありがとうございます。更新しました。
Chris Pratt、

4
これは本当にメタクラスですか?普通のオーバーライドだと思います__new__。後でクラスの__init__メソッドに渡されるのkwargsにリクエストを追加します。クラスModelFormWithRequestに名前を付けると、の意味よりもはるかに明確になりModelFormMetaClassます。
k4ml 2012年

2
これはメタクラスではありません!stackoverflow.com/questions/100003/…を
frnhr

32

価値があるのは、関数ベースのビューではなくクラスベースのビューを使用get_form_kwargsしている場合は、編集ビューでオーバーライドすることです。カスタムCreateViewのコード例:

from braces.views import LoginRequiredMixin

class MyModelCreateView(LoginRequiredMixin, CreateView):
    template_name = 'example/create.html'
    model = MyModel
    form_class = MyModelForm
    success_message = "%(my_object)s added to your site."

    def get_form_kwargs(self):
        kw = super(MyModelCreateView, self).get_form_kwargs()
        kw['request'] = self.request # the trick!
        return kw

    def form_valid(self):
        # do something

上記のビューコードはrequest、フォームの__init__コンストラクター関数へのキーワード引数の1つとして利用可能になります。したがって、あなたのModelForm行うこと:

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__(self, *args, **kwargs):
        # important to "pop" added kwarg before call to parent's constructor
        self.request = kwargs.pop('request')
        super(MyModelForm, self).__init__(*args, **kwargs)

1
これでうまくいきました。とにかく、複雑なWizardFormロジックのためにget_form_kwargsを使用していたので注意します。WizardFormについて説明した他の回答はありません。
datakid 2014年

2
私以外の誰かが、これはWebフレームワークにとってかなり初歩的なことをするための大きな混乱だと思いますか?Djangoは素晴らしいですが、これにより、CBVをまったく使用したくありません。
trpt4him 2017

1
特に、25%以上の開発者が100%の単体テストカバレッジを目指すコードを記述している大規模なプロジェクトで作業している場合は、CBVの利点がFBVの欠点をはるかに上回ります。Djangoの新しいバージョンがrequestオブジェクトをget_form_kwargs自動的に内部に収めることができるかどうかはわかりません。
ジョセフビクターザミット2017

同様に、get_form_kwargsでオブジェクトインスタンスのIDにアクセスする方法はありますか?
Hassan Baig 2018年

1
@HassanBaigおそらく使用していself.get_objectますか?はをCreateView拡張しSingleObjectMixinます。ただし、これが機能するか例外が発生するかは、新しいオブジェクトを作成するか、既存のオブジェクトを更新するかによって異なります。つまり、両方のケースをテストします(もちろん削除します)。
ジョセフビクターザミット2018年

17

通常のアプローチは、ミドルウェアを使用して、リクエストオブジェクトをスレッドローカル参照に格納することです。その後、Form.clean()メソッドを含め、アプリのどこからでもこれにアクセスできます。

Form.clean()メソッドのシグネチャを変更するということは、Djangoの修正バージョンを所有していることを意味します。

ミドルウェアの数は次のようになります。

import threading
_thread_locals = threading.local()

def get_current_request():
    return getattr(_thread_locals, 'request', None)

class ThreadLocals(object):
    """
    Middleware that gets various objects from the
    request object and saves them in thread local storage.
    """
    def process_request(self, request):
        _thread_locals.request = request

Djangoのドキュメントで説明されているように、このミドルウェアを登録します。


2
上記のコメントにもかかわらず、この方法は機能しますが、他の方法は機能しません。initでフォームオブジェクトの属性を設定しても、cleanメソッドに確実に引き継がれませんが、スレッドローカルを設定すると、このデータを引き継ぐことができます。
rplevy

4
@rplevyフォームのインスタンスを作成するときに実際にリクエストオブジェクトを渡しましたか?気付かない場合は、キーワード引数を使用してい**kwargsますMyForm(request.POST, request=request)。つまり、リクエストオブジェクトをとして渡す必要があります。
unode

13

Django管理者向け、Django 1.8

class MyModelAdmin(admin.ModelAdmin):
    ...
    form = RedirectForm

    def get_form(self, request, obj=None, **kwargs):
        form = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
        form.request = request
        return form

1
実際、上記の上位のメソッドは、Django 1.6と1.9の間のどこかで機能しなくなったようです。これは機能し、はるかに短いです。ありがとう!
ライク

9

管理者をカスタマイズするときにこの特定の問題に遭遇しました。特定の管理者の資格情報に基づいて特定のフィールドを検証する必要がありました。

リクエストをフォームの引数として渡すようにビューを変更したくなかったので、次のようにしました。

class MyCustomForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def clean(self):
        # make use of self.request here

class MyModelAdmin(admin.ModelAdmin):
    form = MyCustomForm
    def get_form(self, request, obj=None, **kwargs):
        ModelForm = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
        def form_wrapper(*args, **kwargs):
            a = ModelForm(*args, **kwargs)
            a.request = request
            return a
    return form_wrapper

それをありがとう。クイックタイプミス:obj=objないobj=Noneライン11上の
フランソワ・定数

本当にいい答え、大好きです!
ルークデュピン、2016年

Django 1.9は以下を提供します'function' object has no attribute 'base_fields'。ただし、より単純な(クロージャーなしの)@Françoisの回答はスムーズに機能します。
raratiru 2016年

5

常にこのメソッドを使用できるわけではありません(そしておそらくその悪い習慣です)が、1つのビューでのみフォームを使用している場合は、viewメソッド自体の中にスコープを設定できます。

def my_view(request):

    class ResetForm(forms.Form):
        password = forms.CharField(required=True, widget=forms.PasswordInput())

        def clean_password(self):
            data = self.cleaned_data['password']
            if not request.user.check_password(data):
                raise forms.ValidationError("The password entered does not match your account password.")
            return data

    if request.method == 'POST':
        form = ResetForm(request.POST, request.FILES)
        if form.is_valid():

            return HttpResponseRedirect("/")
    else:
        form = ResetForm()

    return render_to_response(request, "reset.html")

これは本当に素晴らしい解決策になることがget_form_classあります。リクエストで多くのことを行う必要があることがわかっている場合は、CBV メソッドでこれを行うことがよくあります。クラスを繰り返し作成するのに多少のオーバーヘッドがあるかもしれませんが、それは単にインポート時から実行時に移動するだけです。
マシュー・シンケル2017

5

ダニエル・ローズマンの答えはまだ最高です。ただし、いくつかの理由により、キーワード引数ではなくリクエストの最初の位置引数を使用します。

  1. 同じ名前のクワーグを上書きするリスクはありません
  2. リクエストはオプションですが、正しくありません。このコンテキストでは、リクエスト属性をNoneにしないでください。
  3. argsとkwargsを変更することなく、親クラスにクリーンに渡すことができます。

最後に、既存の変数を上書きしないように、より一意の名前を使用します。したがって、私の変更された回答は次のようになります。

class MyForm(forms.Form):

  def __init__(self, request, *args, **kwargs):
      self._my_request = request
      super(MyForm, self).__init__(*args, **kwargs)


  def clean(self):
      ... access the request object via self._my_request ...


3

フォームのクリーンメソッドにユーザーをアクセスする必要があるという要件に従って、この質問に対する別の回答があります。これを試すことができます。View.py

person=User.objects.get(id=person_id)
form=MyForm(request.POST,instance=person)

forms.py

def __init__(self,*arg,**kwargs):
    self.instance=kwargs.get('instance',None)
    if kwargs['instance'] is not None:
        del kwargs['instance']
    super(Myform, self).__init__(*args, **kwargs)

これで、form.pyのすべてのクリーンメソッドでself.instanceにアクセスできます。


0

「準備済み」のDjangoクラスビューからアクセスする場合はCreateView、知っておくべき小さなトリックがあります(=公式のソリューションはそのままでは機能しません)。あなた自身でCreateView あなたはこのようなコードを追加する必要があります:

class MyCreateView(LoginRequiredMixin, CreateView):
    form_class = MyOwnForm
    template_name = 'my_sample_create.html'

    def get_form_kwargs(self):
        result = super().get_form_kwargs()
        result['request'] = self.request
        return result

=つまり、これはrequestDjangoのCreate / Updateビューでフォームに渡すソリューションです。

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