如何访问表单的clean()方法中的请求对象或任何其他变量?


99

我试图在request.user中获取表单的clean方法,但是如何访问请求对象?我可以修改clean方法以允许输入变量吗?

Answers:


157

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
在这种情况下,您是对的。但是,可能不需要修改表单/视图。此外,在线程本地存储的用例中,无法添加方法参数或实例变量。考虑需要访问请求数据的查询过滤器的可调用参数。您既不能在调用中添加参数,也不能引用任何实例。
小檗碱

4
扩展管理表单时,它没有用,因为您可以通过请求var来初始化表单。任何想法?
莫迪

13
为什么说使用线程本地存储是一个非常糟糕的主意?它避免了丢弃用于将请求传递到任何地方的代码。
Michael Mior

9
我不会将请求对象本身传递给表单,而是将您需要的请求字段(即用户)传递给表单,否则,您会将表单逻辑绑定到请求/响应周期,这会使测试更加困难。
Andrew Ingram 2013年

2
克里斯·普拉特(Chris Pratt)对于在admin.ModelAdmin中处理表单时也有一个很好的解决方案
radtek 2015年

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
克里斯(Chris),“ def __init __(self,request = None,* args,** kwargs)”是不好的,因为它将在第一个位置arg和kwargs中都以请求结尾。我将其更改为“ def __init __(self,* args,** kwargs)”,并且可以正常工作。
slinkp 2011年

1
哎呀 那只是我的一个错误。进行其他更新时,我忽略了更新代码的那部分。谢谢你的收获。更新。
克里斯·普拉特

4
这真的是一个元类吗?我认为这只是正常的重载,您将请求添加到__new__的kwargs中,稍后将传递给该类的__init__方法。ModelFormWithRequest我认为命名该类的含义比清楚得多ModelFormMetaClass
k4ml 2012年


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
这对我有用。我做笔记是因为由于复杂的WizardForm逻辑,无论如何我都在使用get_form_kwargs。我没有看到其他答案说明了WizardForm。
2014年

2
除了我之外,是否有人认为这对于Web框架来说非常简单呢?Django非常棒,但这使我永远都不想使用CBV。
trpt4him

1
恕我直言,CBV的好处远远超过了FBV的缺点,尤其是当您从事一个大型项目且有25个以上的开发人员编写旨在实现100%单元测试覆盖率的代码时。不知道较新版本的Django是否request能够get_form_kwargs自动容纳该对象。
约瑟夫·维克多·扎米特

同样,是否可以通过get_form_kwargs访问对象实例的ID?
哈桑·拜格

1
@HassanBaig可能使用self.get_object?该CreateView扩展了SingleObjectMixin。但是,这是可行还是引发异常取决于您是要创建一个新对象还是更新一个现有对象。即测试两种情况(当然也要删除)。
约瑟夫·维克多·扎米特

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中设置表单对象的属性不能可靠地传递给干净方法,而设置线程局部变量的确可以使该数据被传递。
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=None11号线上
弗朗索瓦·常量

答案非常好,我喜欢!
卢克·杜平

Django 1.9提供:'function' object has no attribute 'base_fields'。但是,较简单的(不带闭包)@François答案可以正常运行。
raratiru's

5

您不能总是使用此方法(及其可能的不良做法),但是如果仅在一个视图中使用表单,则可以将其范围限定在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 方法执行此操作。重复创建类可能会有一些开销,但这只是将其从导入时间移到运行时。
马修·申克尔

5

Daniel Roseman的答案仍然是最好的。但是,出于某些原因,我将第一个位置参数用于请求而不是关键字参数:

  1. 您不存在覆盖同名kwarg的风险
  2. 该请求是可选的,不正确。在这种情况下,request属性绝不能为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

根据您的要求,我想针对该问题提供另一个答案,您希望将用户访问表单的clean方法。你可以试试看。View.py

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

表格

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的创建/更新视图传递到表单的解决方案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.