Làm cách nào để truy cập đối tượng yêu cầu hoặc bất kỳ biến nào khác trong phương thức clean () của biểu mẫu?


99

Tôi đang cố gắng truy cập request.user cho phương thức sạch của biểu mẫu, nhưng làm cách nào tôi có thể truy cập đối tượng request? Tôi có thể sửa đổi phương thức sạch để cho phép nhập biến không?

Câu trả lời:


157

Câu trả lời của Ber - lưu trữ nó trong threadlocals - là một ý kiến ​​rất tồi. Hoàn toàn không có lý do gì để làm theo cách này.

Một cách tốt hơn nhiều là ghi đè phương thức của biểu mẫu __init__để lấy một đối số từ khóa bổ sung request,. Điều này lưu trữ yêu cầu trong biểu mẫu , nơi yêu cầu và từ đó bạn có thể truy cập yêu cầu đó trong phương pháp sạch của mình.

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

và theo quan điểm của bạn:

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

4
Bạn đúng trong trường hợp này. Tuy nhiên, nó có thể không được mong muốn để sửa đổi Biểu mẫu / Chế độ xem trong này. Ngoài ra, có những trường hợp sử dụng cho cửa hàng cục bộ luồng nơi không thể thêm các tham số phương thức hoặc biến cá thể. Hãy nghĩ về đối số có thể gọi đến bộ lọc truy vấn cần quyền truy cập để yêu cầu dữ liệu. Bạn không thể thêm một tham số vào cuộc gọi, cũng như không có bất kỳ trường hợp nào để tham chiếu.
Ber

4
Nó không hữu ích khi bạn mở rộng biểu mẫu quản trị viên, vì bạn có thể init biểu mẫu của mình thông qua var yêu cầu. Bất kỳ ý tưởng?
Mordi

13
Tại sao bạn nói sử dụng lưu trữ cục bộ luồng là một ý tưởng rất tồi? Nó tránh phải bỏ mã để chuyển yêu cầu ở khắp mọi nơi.
Michael Mior

9
Tôi sẽ không chuyển chính đối tượng yêu cầu vào biểu mẫu mà thay vào đó là các trường yêu cầu mà bạn cần (tức là người dùng), nếu không, bạn buộc logic biểu mẫu của mình với chu kỳ yêu cầu / phản hồi, điều này làm cho việc kiểm tra khó hơn.
Andrew Ingram

2
Chris Pratt có một giải pháp tốt quá, khi giao dịch với các hình thức trong admin.ModelAdmin
radtek

34

CẬP NHẬT 25/10/2011 : Tôi hiện đang sử dụng điều này với một lớp được tạo động thay vì phương thức, vì Django 1.3 hiển thị một số điều kỳ lạ.

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

Sau đó ghi đè MyCustomForm.__init__như sau:

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

Sau đó, bạn có thể truy cập đối tượng yêu cầu từ bất kỳ phương thức nào của ModelFormwith self.request.


1
Chris, rằng "def __init __ (self, request = None, * args, ** kwargs)" là không tốt, vì nó sẽ kết thúc bằng yêu cầu trong cả arg vị trí đầu tiên và trong kwargs. Tôi đã thay đổi nó thành "def __init __ (self, * args, ** kwargs)" và điều đó hoạt động.
slinkp 19/12/11

1
Rất tiếc. Đó chỉ là một sai lầm của tôi. Tôi đã sơ ý cập nhật phần mã đó khi tôi thực hiện bản cập nhật khác. Cảm ơn vì đã bắt được. Đã cập nhật.
Chris Pratt

4
Đây có thực sự là một siêu thủy tinh? Tôi nghĩ nó chỉ là ghi đè bình thường, bạn thêm yêu cầu vào __new__kwargs của nó, sau này sẽ được chuyển đến __init__phương thức của lớp . Đặt tên cho lớp ModelFormWithRequesttôi nghĩ rõ ràng hơn nhiều về ý nghĩa của nó hơn ModelFormMetaClass.
k4ml Ngày

2
Điều này KHÔNG PHẢI là một siêu kính! Xem stackoverflow.com/questions/100003/…
frnhr

32

Đối với những gì nó đáng giá, nếu bạn đang sử dụng Chế độ xem dựa trên lớp , thay vì chế độ xem dựa trên chức năng, hãy ghi đè get_form_kwargstrong chế độ xem chỉnh sửa của bạn. Mã mẫu cho một CreateView tùy chỉnh :

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

Mã chế độ xem ở trên sẽ requestsẵn dùng làm một trong các đối số từ khóa cho hàm khởi tạo của biểu mẫu __init__. Do đó trong việc của bạn 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
Điều này đã làm việc cho tôi. Tôi ghi chú vì dù sao thì tôi cũng đang sử dụng get_form_kwargs do logic WizardForm phức tạp. Không có câu trả lời nào khác mà tôi đã thấy tính đến WizardForm.
datakid

2
Có ai ngoài tôi nghĩ rằng đây chỉ là một mớ hỗn độn để làm một cái gì đó khá thô sơ cho một khuôn khổ web không? Django rất tuyệt nhưng điều này khiến tôi không muốn sử dụng CBV chút nào.
trpt4him 20/07/2017

1
IMHO lợi ích của CBV lớn hơn những hạn chế của FBV cho đến nay, đặc biệt nếu bạn làm việc trong một dự án lớn với hơn 25 nhà phát triển viết mã nhằm mục đích bao phủ 100% thử nghiệm đơn vị. Không chắc liệu các phiên bản Django mới hơn có phục vụ cho việc có requestđối tượng trong get_form_kwargstự động hay không.
Joseph Victor Zammit,

Theo cách tương tự, có cách nào để truy cập ID của cá thể đối tượng trong get_form_kwargs không?
Hassan Baig

1
@HassanBaig Có thể sử dụng self.get_objectkhông? Các CreateViewmở rộng SingleObjectMixin. Nhưng điều này có hiệu quả hay tăng một ngoại lệ hay không phụ thuộc vào việc bạn đang tạo một đối tượng mới hay cập nhật một đối tượng hiện có; tức là kiểm tra cả hai trường hợp (và xóa tất nhiên).
Joseph Victor Zammit

17

Aproach thông thường là lưu trữ đối tượng yêu cầu trong một tham chiếu cục bộ luồng sử dụng phần mềm trung gian. Sau đó, bạn có thể truy cập điều này từ bất kỳ đâu trong ứng dụng của mình, bao gồm cả phương thức Form.clean ().

Thay đổi chữ ký của phương thức Form.clean () có nghĩa là bạn có phiên bản Django đã sửa đổi, sở hữu của bạn, phiên bản này có thể không phải là những gì bạn muốn.

Cảm ơn số lượng phần mềm trung gian trông giống như sau:

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

Đăng ký phần mềm trung gian này như được mô tả trong tài liệu Django


2
Mặc dù có những nhận xét trên, phương pháp này hoạt động trong khi phương pháp kia thì không. Việc đặt một thuộc tính của đối tượng biểu mẫu trong init không chuyển sang các phương thức sạch một cách đáng tin cậy, trong khi việc đặt địa phương luồng cho phép dữ liệu này được chuyển sang.
rplevy

4
@rplevy bạn đã thực sự chuyển đối tượng yêu cầu khi bạn tạo một phiên bản của biểu mẫu chưa? Trong trường hợp bạn không nhận thấy nó sử dụng các đối số từ khóa **kwargs, có nghĩa là bạn sẽ phải chuyển đối tượng yêu cầu là MyForm(request.POST, request=request).
unide

13

Đối với quản trị viên Django, trong 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
Phương pháp được xếp hạng cao nhất ở trên thực sự dường như đã ngừng hoạt động ở đâu đó giữa Django 1.6 và 1.9. Cái này hoạt động và ngắn hơn nhiều. Cảm ơn!
Raik

9

Tôi đã gặp phải vấn đề cụ thể này khi tùy chỉnh quản trị viên. Tôi muốn một trường nhất định được xác thực dựa trên thông tin đăng nhập của quản trị viên cụ thể.

Vì tôi không muốn sửa đổi dạng xem để chuyển yêu cầu làm đối số cho biểu mẫu, nên sau đây là những gì tôi đã làm:

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

Cảm ơn vì điều đó. Typo nhanh: obj=objkhông obj=Nonetrên dòng 11.
François liên tục

Câu trả lời thực sự hay, tôi thích nó!
Luke Dupin

Django 1.9 cung cấp: 'function' object has no attribute 'base_fields'. Tuy nhiên, câu trả lời @ François đơn giản hơn (không đóng) hoạt động trơn tru.
raratiru

5

Bạn không phải lúc nào cũng sử dụng phương pháp này (và có thể là phương pháp không tốt của nó), nhưng nếu bạn chỉ sử dụng biểu mẫu trong một dạng xem, bạn có thể đưa nó vào bên trong chính phương thức xem.

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

Đây đôi khi là một giải pháp thực sự tốt: Tôi thường làm điều này theo get_form_classphương pháp CBV , nếu tôi biết mình cần phải làm nhiều thứ với yêu cầu. Có thể có một số chi phí trong việc liên tục tạo lớp, nhưng điều đó chỉ chuyển nó từ thời gian nhập sang thời gian chạy.
Matthew Schinckel

5

Câu trả lời của Daniel Roseman vẫn là tốt nhất. Tuy nhiên, tôi sẽ sử dụng đối số vị trí đầu tiên cho yêu cầu thay vì đối số từ khóa vì một vài lý do:

  1. Bạn không có nguy cơ ghi đè một con kwarg có cùng tên
  2. Yêu cầu là tùy chọn là không đúng. Thuộc tính yêu cầu không bao giờ được là Không trong ngữ cảnh này.
  3. Bạn có thể chuyển rõ ràng các args và kwargs đến lớp cha mà không cần phải sửa đổi chúng.

Cuối cùng, tôi sẽ sử dụng một tên độc đáo hơn để tránh ghi đè một biến hiện có. Do đó, câu trả lời đã sửa đổi của tôi trông giống như:

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

Tôi có một câu trả lời khác cho câu hỏi này theo yêu cầu của bạn, bạn muốn truy cập người dùng vào phương thức sạch của biểu mẫu. Bạn có thể thử điều này. 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)

Bây giờ bạn có thể truy cập self.instance bằng bất kỳ phương pháp rõ ràng nào trong form.py


0

Khi bạn muốn truy cập nó thông qua các khung nhìn lớp Django "đã chuẩn bị" giống như CreateViewcó một mẹo nhỏ cần biết (= giải pháp chính thức không hoạt động ngoài hộp). Riêng bạn, CreateView bạn sẽ phải thêm mã như sau:

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

= trong ngắn hạn, đây là giải pháp để chuyển requestđến biểu mẫu của bạn với các chế độ xem Tạo / Cập nhật của Django.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.