【问题标题】:How do I access the request object or any other variable in a form's clean() method?如何在表单的 clean() 方法中访问请求对象或任何其他变量?
【发布时间】:2010-11-06 15:13:13
【问题描述】:

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

【问题讨论】:

    标签: python django


    【解决方案1】:

    Ber 的答案 - 将其存储在 threadlocals 中 - 是一个非常糟糕的主意。绝对没有理由这样做。

    更好的方法是覆盖表单的__init__ 方法以获取额外的关键字参数request。这会将请求存储在 form 中,在需要的位置以及您可以在 clean 方法中访问它的位置。

    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)
    

    【讨论】:

    • 在这种情况下你是对的。但是,可能不希望在此修改表单/视图。此外,在线程本地存储的一些用例中,添加方法参数或实例变量是不可能的。考虑需要访问请求数据的查询过滤器的可调用参数。既不能给调用添加参数,也不能引用任何实例。
    • 在扩展管理表单时没有用,因为您可以通过请求变量初始化表单。有什么想法吗?
    • 为什么说使用线程本地存储是一个非常糟糕的主意?它避免了不得不丢弃到处传递请求的代码。
    • 我不会将请求对象本身传递给表单,而是传递您需要的请求字段(即用户),否则您会将表单逻辑与进行测试的请求/响应周期联系起来更难。
    • Chris Pratt 也有一个很好的解决方案,用于在 admin.ModelAdmin 中处理表单时
    【解决方案2】:

    不管怎样,如果您使用的是基于类的视图,而不是基于函数的视图,请在您的编辑视图中覆盖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)
    

    【讨论】:

    • 这对我有用。我做笔记是因为由于复杂的 WizardForm 逻辑,我一直在使用 get_form_kwargs。我见过没有其他答案可以解释 WizardForm。
    • 除了我之外,还有没有人认为做一些对于 Web 框架来说非常初级的事情只是一团糟? Django 很棒,但这让我根本不想使用 CBV。
    • 恕我直言,到目前为止,CBV 的好处远远超过 FBV 的缺点,特别是如果您在一个有 25 位以上开发人员编写旨在 100% 单元测试覆盖率的代码的大型项目中工作。不确定较新版本的 Django 是否能够自动在 get_form_kwargs 中包含 request 对象。
    • 类似地,有什么方法可以在 get_form_kwargs 中访问对象实例的 ID?
    • @HassanBaig 可能使用self.get_objectCreateView 扩展了 SingleObjectMixin。但这是否有效或引发异常取决于您是创建新对象还是更新现有对象。即测试两种情况(当然还有删除)。
    【解决方案3】:

    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)
    

    然后您可以通过ModelFormself.request 的任何方法访问请求对象。

    【讨论】:

    • 克里斯,“def __init__(self, request=None, *args, **kwargs)” 不好,因为它最终会在第一个位置 arg 和 kwargs 中出现请求。我将其更改为“def __init__(self, *args, **kwargs)”并且有效。
    • 哎呀。那只是我的一个错误。当我进行其他更新时,我忽略了更新那部分代码。谢谢你的收获。已更新。
    • 这真的是一个元类吗?我认为这只是正常的覆盖,您将请求添加到__new__ 的kwargs,稍后将传递给该类的__init__ 方法。将课程命名为ModelFormWithRequest 我认为它的含义比ModelFormMetaClass 更清楚。
    【解决方案4】:

    通常的方法是使用中间件将请求对象存储在线程本地引用中。然后,您可以从应用程序中的任何位置访问它,包括 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 docs中的描述注册这个中间件

    【讨论】:

    • 尽管有上述 cmets,但此方法有效,而另一种方法无效。在 init 中设置表单对象的属性并不能可靠地传递给清理方法,而设置线程局部变量确实允许传递此数据。
    • @rplevy 当您创建表单实例时,您实际上是否传递了请求对象?如果您没有注意到它使用关键字参数**kwargs,这意味着您必须将请求对象作为MyForm(request.POST, request=request) 传递。
    【解决方案5】:

    对于 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
    

    【讨论】:

    • 上述最受好评的方法确实似乎在 Django 1.6 和 1.9 之间的某个地方停止工作。这个确实有效,而且要短得多。谢谢!
    【解决方案6】:

    我在自定义管理员时遇到了这个特殊问题。我希望根据特定管理员的凭据验证某个字段。

    由于我不想修改视图以将请求作为参数传递给表单,因此我做了以下操作:

    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 不是第 11 行的 obj=None
    • 非常好的答案,我喜欢它!
    • Django 1.9 提供:'function' object has no attribute 'base_fields'。但是,更简单的(没有关闭)@François 答案运行顺利。
    【解决方案7】:

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

    1. 您不会冒覆盖同名 kwarg 的风险
    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 ...
    

    【讨论】:

      【解决方案8】:

      您不能总是使用这种方法(这可能是不好的做法),但如果您只在一个视图中使用表单,您可以在视图方法本身内限定它。

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

      【讨论】:

      • 这有时是一个非常好的解决方案:如果我知道我需要对请求做很多事情,我经常在 CBV get_form_class 方法中执行此操作。重复创建类可能会有一些开销,但这只是将它从导入时移到运行时。
      【解决方案9】:

      来自 cheesebaker@pypi 的新鲜奶酪:django-requestprovider

      【讨论】:

        【解决方案10】:

        根据您希望将用户访问到表单的 clean 方法的要求,我对此问题有另一个答案。 你可以试试这个。 查看.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

        【讨论】:

          【解决方案11】:

          当您想通过“准备好的”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
          

          = 简而言之,这是使用 Django 的创建/更新视图将 request 传递给表单的解决方案。

          【讨论】:

            猜你喜欢
            • 2018-04-25
            • 2022-01-25
            • 1970-01-01
            • 2018-10-18
            • 2014-11-29
            • 2012-09-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多