ฉันจะเข้าถึงออบเจ็กต์คำขอหรือตัวแปรอื่น ๆ ในเมธอด clean () ของฟอร์มได้อย่างไร


99

ฉันพยายาม request.user สำหรับเมธอด clean ของฟอร์ม แต่ฉันจะเข้าถึงอ็อบเจ็กต์การร้องขอได้อย่างไร ฉันสามารถแก้ไขเมธอด clean เพื่ออนุญาตให้ใส่ตัวแปรได้หรือไม่

คำตอบ:


158

คำตอบโดย Ber - เก็บไว้ใน threadlocals - เป็นความคิดที่แย่มาก ไม่มีเหตุผลที่จะทำเช่นนี้

วิธีที่ดีมากคือการแทนที่รูปแบบของวิธีการที่จะใช้ข้อโต้แย้งคำหลักพิเศษ__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
ไม่มีประโยชน์เมื่อคุณขยายฟอร์มผู้ดูแลระบบเนื่องจากคุณสามารถเริ่มแบบฟอร์มของคุณโดยส่งคำขอ var ความคิดใด ๆ ?
Mordi

14
ทำไมคุณถึงบอกว่าการใช้ที่จัดเก็บเธรดในเครื่องเป็นความคิดที่แย่มาก หลีกเลี่ยงการวางรหัสเพื่อส่งคำขอไปทั่วทุกแห่ง
Michael Mior

9
ฉันจะไม่ส่งออบเจ็กต์คำขอไปยังแบบฟอร์ม แต่เป็นฟิลด์คำขอที่คุณต้องการ (เช่นผู้ใช้) มิฉะนั้นคุณจะผูกตรรกะของฟอร์มเข้ากับวงจรคำขอ / การตอบกลับซึ่งทำให้การทดสอบยากขึ้น
Andrew Ingram

2
Chris Pratt มีทางออกที่ดีเช่นกันเมื่อจัดการกับแบบฟอร์มในผู้ดูแลระบบ ModelAdmin
radtek

35

อัปเดต 10/25/2011 : ตอนนี้ฉันใช้สิ่งนี้กับคลาสที่สร้างขึ้นแบบไดนามิกแทนวิธีการเนื่องจาก 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)

จากนั้นคุณสามารถเข้าถึงวัตถุคำขอจากวิธีการใด ๆกับModelFormself.request


1
คริสว่า "def __init __ (self, request = None, * args, ** kwargs)" ไม่ดีเพราะมันจะจบลงด้วยการร้องขอทั้งในตำแหน่งแรก arg และใน kwargs ฉันเปลี่ยนเป็น "def __init __ (self, * args, ** kwargs)" และได้ผล
slinkp

1
อ๊ะ. นั่นเป็นเพียงความผิดพลาดในส่วนของฉัน ฉันละเลยที่จะอัปเดตส่วนนั้นของโค้ดเมื่อฉันทำการอัปเดตอื่น ๆ ขอบคุณสำหรับการจับ อัปเดตแล้ว
Chris Pratt

4
นี่คือเมตาคลาสสิกจริงๆหรือ? ฉันคิดว่ามันเป็นเพียงการลบล้างตามปกติคุณเพิ่มคำขอไปยัง__new__kwargs ซึ่งหลังจากนั้นจะถูกส่งไปยัง__init__เมธอดของคลาส การตั้งชื่อชั้นเรียนผมคิดว่าชัดเจนมากขึ้นในความหมายของมันมากกว่าModelFormWithRequest ModelFormMetaClass
k4ml

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__ฟังก์ชันตัวสร้างแบบฟอร์ม ดังนั้นในการ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
สิ่งนี้ได้ผลสำหรับฉัน ฉันจดบันทึกเพราะฉันใช้ get_form_kwargs อยู่ดีเนื่องจากตรรกะ WizardForm ที่ซับซ้อน ไม่มีคำตอบอื่นใดที่ฉันเห็นสำหรับ WizardForm
datakid

2
มีใครนอกจากฉันคิดว่านี่เป็นเพียงเรื่องใหญ่ที่จะทำบางสิ่งที่ค่อนข้างเป็นพื้นฐานสำหรับเว็บเฟรมเวิร์ก Django นั้นยอดเยี่ยม แต่สิ่งนี้ทำให้ฉันไม่อยากใช้ CBV เลยเลยทีเดียว
trpt4him

1
IMHO ประโยชน์ของ CBV มีมากกว่าข้อเสียของ FBV โดยเฉพาะอย่างยิ่งหากคุณทำงานในโครงการขนาดใหญ่ที่มีโค้ดการเขียนมากกว่า 25+ devs ที่มีเป้าหมายเพื่อการครอบคลุมการทดสอบหน่วย 100% ไม่แน่ใจว่า Django เวอร์ชันใหม่กว่ารองรับการมีrequestวัตถุภายในget_form_kwargsโดยอัตโนมัติหรือไม่
Joseph Victor Zammit

ในหลอดเลือดดำที่คล้ายกันมีวิธีใดบ้างในการเข้าถึง ID ของอินสแตนซ์วัตถุใน get_form_kwargs
Hassan Baig

1
@HassanBaig อาจใช้self.get_object? ขยายCreateView SingleObjectMixinแต่จะได้ผลหรือเกิดข้อยกเว้นขึ้นอยู่กับว่าคุณกำลังสร้างวัตถุใหม่หรืออัปเดตวัตถุที่มีอยู่ คือทดสอบทั้งสองกรณี (และการลบแน่นอน)
Joseph Victor Zammit

17

aproach ตามปกติคือการจัดเก็บอ็อบเจ็กต์คำร้องขอในการอ้างอิงเธรดโลคัลโดยใช้มิดเดิลแวร์ จากนั้นคุณสามารถเข้าถึงสิ่งนี้ได้จากทุกที่ในแอพของคุณรวมถึงเมธอด 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ไม่สามารถส่งต่อไปยังเมธอดสะอาดได้อย่างน่าเชื่อถือในขณะที่การตั้งค่าเธรดโลคอลจะอนุญาตให้นำข้อมูลนี้ไปใช้
rplevy

4
@rplevy คุณส่งออบเจ็กต์คำขอจริงหรือไม่เมื่อคุณสร้างอินสแตนซ์ของแบบฟอร์ม ในกรณีที่คุณไม่ได้สังเกตเห็นมันใช้ข้อโต้แย้งคำหลักซึ่งหมายความว่าคุณจะต้องผ่านคำขอวัตถุเป็น**kwargs MyForm(request.POST, request=request)
เลิกใช้งาน

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 อันนี้ใช้ได้ผลและสั้นกว่ามาก ขอบคุณ!
Raik

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
François Constant

คำตอบที่ดีจริงๆฉันชอบมัน!
Luke Dupin

Django 1.9 มอบ: 'function' object has no attribute 'base_fields'. อย่างไรก็ตามคำตอบที่ง่ายกว่า (โดยไม่ต้องปิด) @ Françoisทำงานได้อย่างราบรื่น
raratiru

5

คุณไม่สามารถใช้วิธีนี้ได้เสมอไป (และอาจเป็นวิธีปฏิบัติที่ไม่ดี) แต่ถ้าคุณใช้แบบฟอร์มในมุมมองเดียวคุณสามารถกำหนดขอบเขตภายในเมธอดมุมมองได้

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 ถ้าฉันรู้ว่าฉันต้องทำหลายอย่างกับคำขอ อาจมีค่าใช้จ่ายบางส่วนในการสร้างคลาสซ้ำ ๆ แต่เพียงแค่ย้ายจากเวลานำเข้าเป็นรันไทม์
Matthew Schinckel

5

คำตอบของDaniel Rosemanยังคงดีที่สุด อย่างไรก็ตามฉันจะใช้อาร์กิวเมนต์ตำแหน่งแรกสำหรับคำขอแทนอาร์กิวเมนต์คำหลักด้วยเหตุผลบางประการ:

  1. คุณไม่เสี่ยงต่อการลบล้าง kwarg ที่มีชื่อเดียวกัน
  2. คำขอเป็นทางเลือกซึ่งไม่ถูกต้อง แอตทริบิวต์การร้องขอไม่ควรเป็นไม่มีในบริบทนี้
  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)

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

ตอนนี้คุณสามารถเข้าถึง self.instance ด้วยวิธีการที่สะอาดใน form.py


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

= ในระยะสั้นนี่คือวิธีการส่งผ่านrequestไปยังแบบฟอร์มของคุณด้วยมุมมองสร้าง / อัปเดตของ Django

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.