Django表单通过get_form_kwargs传递请求无法使表单访问self.request.user。

huangapple go评论66阅读模式
英文:

Django forms passing request via get_form_kwargs fails to give form access to self.request.user

问题

Goal

我需要使用户能够通过表单进行验证(即通过 self.request.user)。

Approach Taken

我在我的视图中使用 get_form_kwargs() 函数,将请求传递给我的表单(建议参考这里:非常相似的问题)。

Problem

尽管遵循了上面链接的StackOverflow答案中概述的步骤,但我仍然收到错误消息 'NoneType' object has no attribute 'user'

Code

views.py

class MemberDetailView(LoginRequiredMixin, generic.DetailView):
    """View class for member profile page"""
    model = User
    context_object_name = 'user'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs,)
        context['form'] = JoinCommunityForm()
        return context

class JoinCommunityFormView(FormView):
    """View class for join code for users to join communities"""
    form_class = JoinCommunityForm

    def get_form_kwargs(self, *args, **kwargs):
        form_kwargs = super().get_form_kwargs(*args, **kwargs)
        form_kwargs['request'] = self.request
        return form_kwargs

    def post(self, request, *args, **kwargs):
        """posts the request to join community only if user is logged in"""
        if not request.user.is_authenticated:
            return HttpResponseForbidden
        return super().post(request, *args, **kwargs)

class MemberDashboardView(View):
    """View class to bring together the MemberDetailView and the JoinCommunityFormView"""
    def get(self, request, *args, **kwargs):
        view = MemberDetailView.as_view()
        return view(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        view = JoinCommunityFormView.as_view()
        form = JoinCommunityForm(request.POST)
        if form.is_valid():

forms.py

引发错误的行是 user=self.request.user

from communities.models import Community, UserRoleWithinCommunity

class JoinCommunityForm(forms.Form):
    pin = forms.RegexField(
                           '^[A-HJ-NP-Z1-9]$',
                           max_length=6, 
                           label='',
                           widget=forms.TextInput(attrs={
                               'oninput': 'this.value = this.value.toUpperCase()'
                                                        }
                                                  )
                           )

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(JoinCommunityForm, self).__init__(*args, **kwargs)
    
    def clean(self):
        cleaned_data = super().clean()
        pin = self.cleaned_data['pin']
        test_join_code = f'{pin}'
        if Community.objects.filter(join_code=test_join_code).exists():
            # Make sure user is not already a member of the community
            communityToJoin = Community.objects.get(join_code=test_join_code)
            if UserRoleWithinCommunity.objects.filter(
                community=communityToJoin,
                user=self.request.user
            ).exists():
                raise forms.ValidationError("You're already a member of that Community")
            else:
                return cleaned_data
        else:
            raise forms.ValidationError("Join code is incorrect")

Edit 1:

我尝试了Willem建议的方法,但仍然遇到相同的错误 'NoneType' object has no attribute 'user',这个错误追溯到我的表单中需要通过 self.request.user 访问用户的那一行。

所有 forms.py 代码仍然保持不变。我的 views.py 代码如下更改(现在我只有一个视图,而不是两个视图(表单视图和详细视图),它们合并到另一个视图中):

views.py

# 所有必要的导入语句

class MemberDetailView(
        LoginRequiredMixin, FormMixin,
        generic.DetailView, ProcessFormView
    ):
    """View class for member profile page"""
    model = User
    context_object_name = 'user'
    form_class = JoinCommunityForm
    success_url = '#communities-card'
    template_name = 'home/terpuser_detail.html'

    def get_queryset(self, *args, **kwargs):
        qs = super().get_queryset(*args, **kwargs)
        if not self.request.user.is_superuser:
            qs = qs.filter(pk=self.request.user.pk)
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(
            **kwargs,
            demo=get_object_or_404(Scenario.objects, scenario_id='demo'),
            today=date.today(),
        )
        return context
    
    def form_valid(self, form):
        # 这是我放置创建新的Community membership对象的代码的地方
        return super().form_valid(self, form)

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('member/<slug:slug>/', views.MemberDetailView.as_view(), name='member-detail')
]

error message

  • 请求方法:POST
  • 请求URL:http://127.0.0.1:8000/member/tron/
  • Django版本:4.1.5
  • 异常类型:AttributeError
  • 异常值:'NoneType' object has no attribute 'user'
  • 异常位置:../projDir/home/forms.py, 第53行,在clean方法中
  • 引发位置:home.views.MemberDetailView
  • Python可执行文件:../projDir/django-terp-venv/bin/python
  • Python版本:3.8.2
英文:

Goal

I need to make the user accessible to a form for validation (i.e., through self.request.user)

Approach Taken

I use the get_form_kwargs() function in my view to make the request available to my form (as suggested here: Very similar problem).

Problem

Despite following the steps outlined in the StackOverflow answer linked above, I'm getting the error &#39;NoneType&#39; object has no attribute &#39;user&#39;

Code

views.py

class MemberDetailView(LoginRequiredMixin, generic.DetailView):
    &quot;&quot;&quot;View class for member profile page&quot;&quot;&quot;
    model = User
    context_object_name = &#39;user&#39;

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs,)
        context[&#39;form&#39;] = JoinCommunityForm()
        return context
    
class JoinCommunityFormView(FormView):
    &quot;&quot;&quot;View class for join code for users to join communities&quot;&quot;&quot;
    form_class = JoinCommunityForm

    def get_form_kwargs(self, *args, **kwargs):
        form_kwargs = super().get_form_kwargs(*args, **kwargs)
        form_kwargs[&#39;request&#39;] = self.request
        return form_kwargs

    def post(self, request, *args, **kwargs):
        &quot;&quot;&quot;posts the request to join community only if user is logged in&quot;&quot;&quot;
        if not request.user.is_authenticated:
            return HttpResponseForbidden
        return super().post(request, *args, **kwargs)
    
class MemberDashboardView(View):
    &quot;&quot;&quot;View class to bring together the MemberDetailView and the JoinCommunityFormView&quot;&quot;&quot;
    def get(self, request, *args, **kwargs):
        view = MemberDetailView.as_view()
        return view(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        view = JoinCommunityFormView.as_view()
        form = JoinCommunityForm(request.POST)
        if form.is_valid():

forms.py

*Line where error is being raised is user=self.request.user *

from communities.models import Community, UserRoleWithinCommunity

class JoinCommunityForm(forms.Form):
    pin = forms.RegexField(
                           &#39;^[A-HJ-NP-Z1-9]$&#39;,
                           max_length=6, 
                           label=&#39;&#39;,
                           widget=forms.TextInput(attrs={
                               &#39;oninput&#39;: &#39;this.value = this.value.toUpperCase()&#39;
                                                        }
                                                  )
                           )

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop(&#39;request&#39;, None)
        super(JoinCommunityForm, self).__init__(*args, **kwargs)
    
    def clean(self):
        cleaned_data = super().clean()
        pin = self.cleaned_data[&#39;pin&#39;]
        test_join_code = f&#39;{pin}&#39;
        if Community.objects.filter(join_code=test_join_code).exists():
            # Make sure user is not already a member of the community
            communityToJoin = Community.objects.get(join_code=test_join_code)
            if UserRoleWithinCommunity.objects.filter(
                community=communityToJoin,
                user=self.request.user
            ).exists():
                raise forms.ValidationError(&quot;You&#39;re already a member 
                                             of that Community&quot;)
            else:
                return cleaned_data
        else:
            raise forms.ValidationError(&quot;Join code is incorrect&quot;)

Edit 1:

I've tried the approach suggested by Willem but am still butting up against the same error &#39;NoneType&#39; object has no attribute &#39;user&#39;, which is tracing back to that one line in my form where I need to access the user through self.request.user.

All forms.py code remains the exact same as above. My views.py code has changed as follows (I now only have a single view rather than two views (form view and detail view) that feed into another view):

views.py

# all necessary import statements

class MemberDetailView(
        LoginRequiredMixin, FormMixin,
        generic.DetailView, ProcessFormView
    ):
    &quot;&quot;&quot;View class for member profile page&quot;&quot;&quot;
    model = User
    context_object_name = &#39;user&#39;
    form_class = JoinCommunityForm
    success_url = &#39;#communities-card&#39;
    template_name = &#39;home/terpuser_detail.html&#39;

    def get_queryset(self, *args, **kwargs):
        qs = super().get_queryset(*args, **kwargs)
        if not self.request.user.is_superuser:
            qs = qs.filter(pk=self.request.user.pk)
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(
            **kwargs,
            demo=get_object_or_404(Scenario.objects, scenario_id=&#39;demo&#39;),
            today=date.today(),
        )
        return context
    
    def form_valid(self, form):
        # this is where I put my code to create a new Community 
        # membership object
        return super().form_valid(self, form)

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path(&#39;member/&lt;slug:slug&gt;/&#39;, views.MemberDetailView.as_view(), name=&#39;member-detail&#39;)
]

error message

  • Request Method: POST
  • Request URL: http://127.0.0.1:8000/member/tron/
  • Django Version: 4.1.5
  • Exception Type: AttributeError
  • Exception Value: 'NoneType' object has no attribute 'user'
  • Exception Location: ../projDir/home/forms.py, line 53, in clean
  • Raised during: home.views.MemberDetailView
  • Python Executable: ../projDir/django-terp-venv/bin/python
  • Python Version: 3.8.2

答案1

得分: 1

以下是翻译好的部分:

这没有起作用的原因是因为您使用 JoinCommunityForm(request.POST) 构建了表单,而 Django 的 FormMixin 将调用 get_form_kwargs() 然后将这些参数传递给表单,正如我们在 源代码 [GitHub] 中所看到的:

def get_form(self, form_class=None):
    """返回在此视图中要使用的表单的实例。"""
    if form_class is None:
        form_class = self.get_form_class()
    return form_class(**self.get_form_kwargs())

在您的视图中,您可以使用以下方法轻松处理这个问题:

from django.views.generic.edit import FormMixin, ProcessFormView

class MemberDetailView(
    LoginRequiredMixin, FormMixin, generic.DetailView, ProcessFormView
):
    model = User
    context_object_name = 'user'
    form_class = JoinCommunityForm
    template_name = '...'  # 在 MemberDetailView 中使用的模板

    def form_valid(self, form):
        # 当表单有效时执行某些操作
        return super().form_valid(form)
英文:

The reason this did not work is because you constructed the form with JoinCommunityForm(request.POST), whereas Django's FormMixin will call get_form_kwargs() and then pass these to the form, as we can see in the source code [GitHub]:

> <pre><code>def get_form(self, form_class=None):
> &quot;&quot;&quot;Return an instance of the form to be used in this view.&quot;&quot;&quot;
> if form_class is None:
> form_class = self.get_form_class()
> return <b>form_class(**self.get_form_kwargs())</b></code></pre>

In your view you can however easily handle this with:

<pre><code>from django.views.generic.edit import FormMixin, ProcessFormView

class MemberDetailView(
LoginRequiredMixin, <b>FormMixin</b>, generic.DetailView, <b>ProcessFormView</b>
):
model = User
context_object_name = 'user'
form_class = JoinCommunityForm
template_name = '&hellip;' # template used in MemberDetailView

def form_valid(self, form):
    # do something when the form is valid
    return super().form_valid(self, form)&lt;/code&gt;&lt;/pre&gt;

答案2

得分: 0

以下是翻译好的部分:

看起来我需要在表单类的 `__init__` 方法中获取当前用户(而不仅仅是 `request`)。下面是最终有效的 `views.py` 和 `forms.py` 代码:

## views.py ##
```python
# 所有必要的导入语句

class MemberDetailView(
        LoginRequiredMixin, FormMixin,
        generic.DetailView, ProcessFormView
    ):
    """成员个人资料页面的视图类"""
    model = User
    context_object_name = 'user'
    form_class = JoinCommunityForm
    success_url = '#communities-card'
    template_name = 'home/terpuser_detail.html'

    def get_queryset(self, *args, **kwargs):
        qs = super().get_queryset(*args, **kwargs)
        if not self.request.user.is_superuser:
            qs = qs.filter(pk=self.request.user.pk)
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(
            **kwargs,
            demo=get_object_or_404(Scenario.objects, scenario_id='demo'),
            today=date.today(),
        )
        return context
    
    def form_valid(self, form):
        # 这是我放置创建新的社区成员对象的代码的地方
        return super().form_valid(self, form)

forms.py

解决我的问题的代码行位于表单类的 __init__ 方法中:self.user = self.request.user。然后,在表单类的 clean 方法中调用 self.user 而不是 self.request.user

from communities.models import Community, UserRoleWithinCommunity

class JoinCommunityForm(forms.Form):
    pin = forms.RegexField(
                           '^[A-HJ-NP-Z1-9]$',
                           max_length=6, 
                           label='',
                           widget=forms.TextInput(attrs={
                               'oninput': 'this.value = this.value.toUpperCase()'
                                                        }
                                                  )
                           )

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None) # 将 request kwarg 实例化,如Willem建议的
        self.user = self.request.user # 获取用户,这是解决错误的关键
        super(JoinCommunityForm, self).__init__(*args, **kwargs)
    
    def clean(self):
        cleaned_data = super().clean()
        pin = self.cleaned_data['pin']
        test_join_code = f'{pin}'
        if Community.objects.filter(join_code=test_join_code).exists():
            # 确保用户不是该社区的成员
            communityToJoin = Community.objects.get(join_code=test_join_code)
            if UserRoleWithinCommunity.objects.filter(
                community=communityToJoin,
                user=self.user # 这里我调用 self.user 而不是 self.request.user
            ).exists():
                raise forms.ValidationError("您已经是该社区的成员")
            else:
                return cleaned_data
        else:
            raise forms.ValidationError("加入代码不正确")

请注意,以上代码中的注释已被保留在翻译中。

<details>
<summary>英文:</summary>

It appears I needed to get the current user (not just the `request`) using the form class&#39;s `__init__` method. Below is the `views.py` and `forms.py` code that ended up working:
## views.py ##

all necessary import statements

class MemberDetailView(
LoginRequiredMixin, FormMixin,
generic.DetailView, ProcessFormView
):
"""View class for member profile page"""
model = User
context_object_name = 'user'
form_class = JoinCommunityForm
success_url = '#communities-card'
template_name = 'home/terpuser_detail.html'

def get_queryset(self, *args, **kwargs):
    qs = super().get_queryset(*args, **kwargs)
    if not self.request.user.is_superuser:
        qs = qs.filter(pk=self.request.user.pk)
    return qs

def get_context_data(self, **kwargs):
    context = super().get_context_data(
        **kwargs,
        demo=get_object_or_404(Scenario.objects, scenario_id=&#39;demo&#39;),
        today=date.today(),
    )
    return context

def form_valid(self, form):
    # this is where I put my code to create a new Community membership object
    return super().form_valid(self, form)

## forms.py ##
The line that ended up solving my problems is in the form class&#39;s `__init__` method: `self.user = self.request.user`. Then, I call `self.user` in the form class&#39;s `clean` method rather than `self.request.user`

from communities.models import Community, UserRoleWithinCommunity

class JoinCommunityForm(forms.Form):
pin = forms.RegexField(
'^[A-HJ-NP-Z1-9]$',
max_length=6,
label='',
widget=forms.TextInput(attrs={
'oninput': 'this.value = this.value.toUpperCase()'
}
)
)

def __init__(self, *args, **kwargs):
    self.request = kwargs.pop(&#39;request&#39;, None) # instantiate request kwarg as Willem suggests
    self.user = self.request.user # get user, this was key to resolving error
    super(JoinCommunityForm, self).__init__(*args, **kwargs)

def clean(self):
    cleaned_data = super().clean()
    pin = self.cleaned_data[&#39;pin&#39;]
    test_join_code = f&#39;{pin}&#39;
    if Community.objects.filter(join_code=test_join_code).exists():
        # Make sure user is not already a member of the community
        communityToJoin = Community.objects.get(join_code=test_join_code)
        if UserRoleWithinCommunity.objects.filter(
            community=communityToJoin,
            user=self.user # here I call self.user instead of self.request.user
        ).exists():
            raise forms.ValidationError(&quot;You&#39;re already a member 
                                         of that Community&quot;)
        else:
            return cleaned_data
    else:
        raise forms.ValidationError(&quot;Join code is incorrect&quot;)

</details>



huangapple
  • 本文由 发表于 2023年6月28日 23:49:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76574805.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定