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

nub*_*ela 92 python django

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

Dan*_*man 147

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 ...
Run Code Online (Sandbox Code Playgroud)

在你看来:

myform = MyForm(request.POST, request=request)
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*att 31

更新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 ModelFormMetaClass(ModelForm):
            def __new__(cls, *args, **kwargs):
                kwargs['request'] = request
                return ModelForm(*args, **kwargs)
        return ModelFormMetaClass
Run Code Online (Sandbox Code Playgroud)

然后覆盖MyCustomForm.__init__如下:

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

然后,您可以从任何方法来访问请求对象ModelFormself.request.

  • 这真的是一个元类吗?我认为它只是正常的覆盖,你向`__new__`的kwargs添加请求,稍后将把它传递给类的`__init__`方法.命名类ModelFormWithRequest`我认为它的含义比`ModelFormMetaClass`更清楚. (3认同)
  • 这不是元类!见 http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python (2认同)

Jos*_*mit 27

对于它的价值,如果您使用基于类的视图而不是基于函数的视图,请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
Run Code Online (Sandbox Code Playgroud)

上面的视图代码将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)
Run Code Online (Sandbox Code Playgroud)

  • 除了我之外,有没有人认为做一些对于 Web 框架来说非常基本的事情只是一团糟?Django 很棒,但这让我根本不想使用 CBV。 (2认同)

Ber*_*Ber 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
Run Code Online (Sandbox Code Playgroud)

按照Django文档中的描述注册此中间件

  • @rplevy你在创建表单实例时是否实际传递了请求对象?如果您没有注意到它使用关键字参数`**kwargs`,这意味着您必须将请求对象作为`MyForm(request.POST,request = request)`传递. (4认同)
  • 尽管有上述评论,但这种方法可行,而另一种方法则不然.在__init__中设置表单对象的属性不能可靠地转移到干净的方法,而设置线程本地包确实允许这些数据被转移. (2认同)

Fra*_*ant 12

对于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
Run Code Online (Sandbox Code Playgroud)


ent*_*opy 8

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

由于我不想修改视图以将请求作为参数传递给表单,以下是我所做的:

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
Run Code Online (Sandbox Code Playgroud)


小智 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")
Run Code Online (Sandbox Code Playgroud)


And*_*epo 5

丹尼尔罗斯曼的答案仍然是最好的.但是,我会使用请求的第一个位置参数而不是关键字参数,原因如下:

  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 ...
Run Code Online (Sandbox Code Playgroud)