Bagaimana cara mengakses objek permintaan atau variabel lain dalam metode clean () formulir?

99

Saya mencoba request.user untuk metode bersih formulir, tapi bagaimana saya bisa mengakses objek permintaan? Dapatkah saya mengubah metode bersih untuk mengizinkan input variabel?

nubela
sumber

Jawaban:

157

Jawaban oleh Ber - menyimpannya di threadlocals - adalah ide yang sangat buruk. Sama sekali tidak ada alasan untuk melakukannya dengan cara ini.

Cara yang jauh lebih baik adalah mengganti metode formulir __init__untuk mengambil argumen kata kunci tambahan request,. Ini menyimpan permintaan dalam bentuk , jika diperlukan, dan dari mana Anda dapat mengaksesnya dalam metode bersih Anda.

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

dan menurut pandangan Anda:

myform = MyForm(request.POST, request=request)
Daniel Roseman
sumber
4
Anda benar dalam kasus ini. Namun, mungkin tidak diinginkan untuk mengubah Formulir / Tampilan di sini. Juga, ada kasus penggunaan untuk penyimpanan lokal utas di mana menambahkan parameter metode atau variabel contoh tidak mungkin. Pikirkan tentang argumen yang dapat dipanggil ke filter kueri yang membutuhkan akses untuk meminta data. Anda tidak dapat menambahkan parameter ke panggilan, juga tidak ada contoh untuk referensi.
Ber
4
Ini tidak berguna ketika Anda memperluas formulir admin, karena Anda dapat memasukkan formulir Anda melalui request var. Ada ide?
Mordi
13
Mengapa Anda mengatakan menggunakan penyimpanan lokal-thread adalah ide yang sangat buruk? Ini menghindari keharusan menjatuhkan kode untuk meneruskan permintaan di mana-mana.
Michael Mior
9
Saya tidak akan meneruskan objek permintaan itu sendiri ke formulir, melainkan bidang permintaan yang Anda butuhkan (yaitu pengguna), jika tidak, Anda mengikat logika formulir Anda ke siklus permintaan / respons yang membuat pengujian lebih sulit.
Andrew Ingram
2
Chris Pratt juga memiliki solusi yang baik, karena saat menangani formulir di admin.ModelAdmin
radtek
34

DIPERBARUI 25/10/2011 : Saya sekarang menggunakan ini dengan kelas yang dibuat secara dinamis daripada metode, karena Django 1.3 menampilkan beberapa keanehan sebaliknya.

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

Kemudian timpa MyCustomForm.__init__sebagai berikut:

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

Anda kemudian dapat mengakses objek permintaan dari metode apa pun ModelFormdengan self.request.

Chris Pratt
sumber
1
Chris, bahwa "def __init __ (self, request = None, * args, ** kwargs)" buruk, karena akan berakhir dengan permintaan di kedua posisi pertama dan di kwargs. Saya mengubahnya menjadi "def __init __ (self, * args, ** kwargs)" dan berhasil.
slinkp
1
Ups. Itu hanya kesalahan di pihak saya. Saya lalai memperbarui bagian kode itu ketika saya membuat pembaruan lainnya. Terima kasih untuk tangkapannya. Diperbarui.
Chris Pratt
4
Apakah ini benar-benar sebuah metaclass? Saya pikir itu hanya menimpa biasa, Anda menambahkan permintaan ke __new__kwargs yang nantinya akan diteruskan ke metode kelas __init__. Penamaan kelas ModelFormWithRequestmenurut saya jauh lebih jelas dalam maknanya daripada ModelFormMetaClass.
k4ml
2
Ini BUKAN metaclass! Lihat stackoverflow.com/questions/100003/...
frnhr
32

Untuk apa nilainya, jika Anda menggunakan Tampilan Berbasis Kelas , alih-alih tampilan berbasis fungsi, timpa get_form_kwargsdalam tampilan pengeditan Anda. Contoh kode untuk CreateView kustom :

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

Kode tampilan di atas akan requesttersedia sebagai salah satu argumen kata kunci untuk __init__fungsi konstruktor formulir . Karena itu dalam pekerjaan Anda 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)
Joseph Victor Zammit
sumber
1
Ini berhasil untuk saya. Saya membuat catatan karena saya tetap menggunakan get_form_kwargs karena logika WizardForm yang kompleks. Tidak ada jawaban lain yang pernah saya lihat untuk WizardForm.
datakid
2
Apakah ada orang selain saya yang menganggap ini hanya kekacauan besar untuk melakukan sesuatu yang belum sempurna untuk kerangka web? Django hebat tapi ini membuat saya tidak ingin menggunakan CBV sama sekali.
trpt4him
1
IMHO, manfaat CBV lebih besar daripada kelemahan FBV sejauh ini, terutama jika Anda bekerja pada proyek besar dengan 25+ kode penulisan pengembang yang bertujuan untuk cakupan pengujian unit 100%. Tidak yakin apakah versi terbaru dari Django memenuhi untuk memiliki requestobjek get_form_kwargssecara otomatis.
Joseph Victor Zammit
Dengan nada yang sama, apakah ada cara untuk mengakses ID instance objek di get_form_kwargs?
Hassan Baig
1
@HassanBaig Mungkin menggunakan self.get_object? The CreateViewmemperpanjang SingleObjectMixin. Tetapi apakah ini berfungsi atau memunculkan pengecualian bergantung pada apakah Anda membuat objek baru atau memperbarui yang sudah ada; yaitu menguji kedua kasus (dan tentu saja penghapusan).
Joseph Victor Zammit
17

Pendekatan biasa adalah menyimpan objek permintaan dalam referensi lokal-thread menggunakan middleware. Kemudian Anda bisa mengakses ini dari mana saja di aplikasi Anda, termasuk metode Form.clean ().

Mengubah tanda tangan dari metode Form.clean () berarti Anda memiliki versi Django yang telah Anda modifikasi, yang mungkin bukan yang Anda inginkan.

Terima middleware count terlihat seperti ini:

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

Daftarkan middleware ini seperti yang dijelaskan dalam dokumen Django

Ber
sumber
2
Terlepas dari komentar di atas, metode ini berfungsi sedangkan metode lainnya tidak. Menetapkan atribut objek formulir di init tidak dapat diandalkan untuk dibawa ke metode pembersihan, sedangkan pengaturan thread lokal memungkinkan data ini dibawa-bawa.
rplevy
4
@rplevy pernahkah Anda mengirimkan objek permintaan saat Anda membuat instance formulir? Jika Anda tidak memperhatikan itu menggunakan argumen kata kunci **kwargs, yang berarti Anda harus meneruskan objek permintaan sebagai MyForm(request.POST, request=request).
unode
13

Untuk admin Django, di 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
François Constant
sumber
1
Metode peringkat teratas lebih jauh di atas memang tampaknya telah berhenti bekerja di suatu tempat antara Django 1.6 dan 1.9. Yang ini berhasil dan jauh lebih pendek. Terima kasih!
Raik
9

Saya mengalami masalah khusus ini saat menyesuaikan admin. Saya ingin bidang tertentu divalidasi berdasarkan kredensial admin tertentu.

Karena saya tidak ingin mengubah tampilan untuk meneruskan permintaan sebagai argumen ke formulir, berikut ini yang saya lakukan:

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
entropi
sumber
Terima kasih untuk itu. Kesalahan ketik cepat: obj=objtidak obj=Nonedi baris 11.
François Constant
Jawaban yang sangat bagus, saya menyukainya!
Luke Dupin
Django 1,9 memberikan: 'function' object has no attribute 'base_fields'. Namun, jawaban @ François yang lebih sederhana (tanpa penutupan) bekerja dengan lancar.
raratiru
5

Anda tidak dapat selalu menggunakan metode ini (dan mungkin praktiknya yang buruk), tetapi jika Anda hanya menggunakan formulir dalam satu tampilan, Anda dapat mencakupnya di dalam metode tampilan itu sendiri.

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")
Chris
sumber
Terkadang ini adalah solusi yang sangat bagus: Saya sering melakukan ini dengan get_form_classmetode CBV , jika saya tahu saya perlu melakukan banyak hal dengan permintaan tersebut. Mungkin ada beberapa overhead dalam membuat kelas berulang kali, tetapi itu hanya memindahkannya dari waktu impor ke waktu proses.
Matthew Schinckel
5

Jawaban Daniel Roseman masih yang terbaik. Namun, saya akan menggunakan argumen posisi pertama untuk permintaan tersebut, bukan argumen kata kunci karena beberapa alasan:

  1. Anda tidak berisiko menimpa kwarg dengan nama yang sama
  2. Permintaan itu bersifat opsional yang tidak benar. Atribut permintaan tidak boleh ada dalam konteks ini.
  3. Anda dapat meneruskan args dan kwargs ke kelas induk dengan rapi tanpa harus mengubahnya.

Terakhir, saya akan menggunakan nama yang lebih unik untuk menghindari menimpa variabel yang ada. Jadi, jawaban saya yang dimodifikasi terlihat seperti:

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 ...
Andres Restrepo
sumber
3

Saya punya jawaban lain untuk pertanyaan ini sesuai kebutuhan Anda, Anda ingin mengakses pengguna ke metode bersih formulir. Anda bisa mencoba ini. 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)

Sekarang Anda dapat mengakses self.instance dalam metode bersih apa pun di form.py

Nishant Kashyap
sumber
0

Ketika Anda ingin mengaksesnya melalui tampilan kelas Django yang "disiapkan" seperti CreateViewada trik kecil untuk diketahui (= solusi resmi tidak bekerja di luar kotak). Di Anda sendiri, CreateView Anda harus menambahkan kode seperti ini:

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

= singkatnya ini adalah solusi untuk meneruskan requestke formulir Anda dengan tampilan Buat / Perbarui Django.

Olivier Pons
sumber