Why does Django custom User Registration Form returns None instead of HttpResponse and no Form errors are showing?

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

Why does Django custom User Registration Form returns None instead of HttpResponse and no Form errors are showing?

问题

我正在学习Django,试图使用一个简单的自定义用户表单构建用户注册页面,该表单比默认的UserCreationForm多了一些字段。当我提交注册表单时,我收到一个Django调试错误消息,其中显示:

视图catalog.views.librarian_add_user没有返回HttpResponse对象。它返回了None。

我正在使用Python 3.8.15和Django 4.1.4。

我在forms.py中创建了一个UserRegistrationForm

from django.contrib.auth.forms import UserCreationForm
from .models import User

class UserRegistrationForm(UserCreationForm):
    first_name = forms.CharField(max_length=101, required=True)
    last_name = forms.CharField(max_length=101, required=True)
    email = forms.EmailField(required=True)

    class Meta:
        model = User
        fields = (
            'username',
            'first_name',
            'last_name',
            'email',
            'password1',
            'password2',
        )

views.py中有一个函数视图librarian_add_user

from django.contrib.auth import authenticate, login
from .forms import UserRegistrationForm

def librarian_add_user(request):
    if request.method == 'POST':
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data['username']
            password = form.cleaned_data['password1']
            user = authenticate(username=username, password=password)
            login(request, user)
            messages.success(request, ("User added successfully."))
            return HttpResponseRedirect(reverse('catalog:index'))
    else:
        form = UserRegistrationForm()
        context = {
            'user_form': form,
        }
        return render(
            request,
            'catalog/librarian_add_user.html',
            context,
        )

还有一个位于我的模板中的librarian_add_user.html

{% extends 'catalog/base_generic.html' %}

{% block content %}
{% if perms.catalog.can_mark_returned %}

    {% if user_form.errors %}
        {{ user_form.errors }}
    {% endif %}

    <h1>Add a new user</h1>
    <form action="" method="post">
        {% csrf_token %}
        <table>
            {{ user_form.as_p }}
        </table>
        <input type="submit" value="Register">
    </form>

{% endif %}

出于简化的原因,我将省略urls.py中的路径。

根据views.py中的内联注释,我尝试添加一个嵌套的else子句到librarian_add_user函数视图,并且可以看到我的终端中打印出了form.errors,这意味着form.is_valid()失败。

我还尝试使用相同的嵌套的else子句来创建一个“else”情景,其中显示一个未绑定的表单以及一个错误消息。像这样:

def librarian_add_user(request):
    if request.method == 'POST':
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            form.save()
            ...
            return HttpResponseRedirect(reverse('catalog:index'))
        else:
            form = UserRegistrationForm()  # &lt;--- HERE!
            context = {
                'user_form': form
            }
    else:
        form = UserRegistrationForm()
        ...
        return render(
            request,
            'catalog/librarian_add_user.html',
            context,
        )

我知道这是多余的,但仍然无法在新呈现的未绑定表单中看到user_form.errors

我还尝试根据Stackoverflow建议的帖子在这里将表单验证工作放在forms.py文件中,具体更改如下:

class UserRegistrationForm(UserCreationForm):
    first_name = forms.CharField(max_length=101, required=True)
    last_name = forms.CharField(max_length=101, required=True)
    email = forms.EmailField(required=True)

    class Meta:
        model = User
        fields = (
            ...   # 省略与上面相同的部分以简化
        )
    
    def save(self, commit=True):
        user = super(UserRegistrationForm, self).save(commit=False)
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        user.email = self.cleaned_data['email']
        
        if commit:
            user.save()
        
        return user

以及对views.py的这些更改:

def librarian_add_user(request):
    if request.method == 'POST':
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request, user)
            messages.success(request, ("User added successfully."))
            return HttpResponseRedirect(reverse('catalog:index'))
    else:
        form = UserRegistrationForm()
        ...
        return render(
            request,
            'catalog/librarian_add_user.html',
            context,
        )

这可能是一些简单且愚蠢的问题,但我花了整个下午、晚上以及今天一整天的时间来查看它,都没有任何效果。是否有人可以帮助查看并回答以下问题?

  1. 是否需要嵌套的else子句,用于处理form.is_valid()失败的情况?即使MDN和Django的教程也没有这样做。
  2. 为什么我的form.errors不显示在模板中,以及这与form.is_valid()失败并返回None有什么关系(我认为有关)?
  3. 如何修复我的代码,以便我可以注册一个新的测试用户?

非常感谢帮助新手!

英文:

I'm learning Django and trying to build a user registration page with a simple custom user form with just a few more fields then the default UserCreationForm. When I submit the registration form, I get a Django debug error message saying:

The view catalog.views.librarian_add_user didn&#39;t return an HttpResponse object. It returned None instead.

I'm using python 3.8.15 with Django 4.1.4.

I've created a UserRegistrationForm in forms.py:

from django.contrib.auth.forms import UserCreationForm
from .models import User  # I import from .models and not from django.contrib.auth.models for consistency&#39;s sake

class UserRegistrationForm(UserCreationForm):
    first_name = forms.CharField(max_length=101, required=True)
    last_name = forms.CharField(max_length=101, required=True)
    email = forms.EmailField(required=True)

    class Meta:
        model = User
        fields = (
            &#39;username&#39;,
            &#39;first_name&#39;,
            &#39;last_name&#39;,
            &#39;email&#39;,
            &#39;password1&#39;,
            &#39;password2&#39;,
        )

A function view librarian_add_user in views.py:

from django.contrib.auth import authenticate, login
from .forms import UserRegistrationForm

def librarian_add_user(request):
    if request.method == &#39;POST&#39;:
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data[&#39;username&#39;]
            password = form.cleaned_data[&#39;password1&#39;]
            user = authenticate(username=username, password=password)
            login(request, user)
            messages.success(request, (&quot;User added successfully.&quot;))
            return HttpResponseRedirect(reverse(&#39;catalog:index&#39;))
        # else:
        #     print(form.errors)  # I can see the errors printed in my terminal, which means `form.is_valid()` failed
    else:
        form = UserRegistrationForm()
        context = {
            &#39;user_form&#39;: form,
        }
        return render(
            request,
            &#39;catalog/librarian_add_user.html&#39;,
            context,
        )

and the librarian_add_user.html in my templates:

{% extends &#39;catalog/base_generic.html&#39; %}

{% block content %}
{% if perms.catalog.can_mark_returned %}

    {% if user_form.errors %}
        {{ user_form.errors }}
    {% endif %}

    &lt;h1&gt;Add a new user&lt;/h1&gt;
    &lt;form action=&quot;&quot; method=&quot;post&quot;&gt;
        {% csrf_token %}
        &lt;table&gt;
            {{ user_form.as_p }}
        &lt;/table&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Register&quot;&gt;
    &lt;/form&gt;

{% endif %}

I will omit the path in urls.py for simplicity.

As commented inline in views.py, I've tried adding a nested else clause in librarian_add_user function view and can see the form.errors printed in my terminal in an HTML unordered list. I have hypothesized maybe this is due to UserRegistrationForm in forms.py did not validate whether password1 equaled password2. I was able to rule that out by deliberately typing different passwords between password1 and password2, and getting the appropriate form error message in my terminal.

I have also tried using the same nested else clause to create an "else" scenario where a unbounded form is shown along with an error message. Like this:

def librarian_add_user(request):
    if request.method == &#39;POST&#39;:
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            form.save()
            ...
            return HttpResponseRedirect(reverse(&#39;catalog:index&#39;))
        else:
            form = UserRegistrationForm()  # &lt;--- HERE!
            context = {
                &#39;user_form&#39;: form
            }
    else:
        form = UserRegistrationForm()
        ...
        return render(
            request,
            &#39;catalog/librarian_add_user.html&#39;,
            context,
        )

I know this is redundant but still the user_form.errors don't show up in the newly rendered unbounded form.

I have also tried putting the form validation work in the forms.py file according to the Stackoverflow suggested post here, with the following changes to forms.py:

class UserRegistrationForm(UserCreationForm):
    first_name = forms.CharField(max_length=101, required=True)
    last_name = forms.CharField(max_length=101, required=True)
    email = forms.EmailField(required=True)

    class Meta:
        model = User
        fields = (
            ...   # same as above and omitted for simplicity
        )
    
    def save(self, commit=True):   # &lt;--- ENTIRE METHOD HERE
        user = super(UserRegistrationForm, self).save(commit=False)
        user.first_name = self.cleaned_data[&#39;first_name&#39;]
        user.last_name = self.cleaned_data[&#39;last_name&#39;]
        user.email = self.cleaned_data[&#39;email&#39;]
        
        if commit:
            user.save()
        
        return user

And these changes to views.py:

def librarian_add_user(request):
    if request.method == &#39;POST&#39;:
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            user = form.save()   # &lt;--- HERE
            login(request, user)    # &lt;--- HERE    
            messages.success(request, (&quot;User added successfully.&quot;))   # &lt;--- HERE
            return HttpResponseRedirect(reverse(&#39;catalog:index&#39;))
    else:
        form = UserRegistrationForm()
        ...
        return render(
            request,
            &#39;catalog/librarian_add_user.html&#39;,
            context,
        )

This is probably some thing simple and really stupid but I have spend last afternoon, evening, and whole day today looking at it with no avail. Could someone help take a look and help answer the following questions?

  1. Do I need a nested else clause for the case when form.is_valid() fails? Even the tutorial on MDN and Django doesn't do this.
  2. Why is it that my form.errors does not show in the template and how is this related (which I think it is) to form.is_valid() failing and returning None?
  3. How can I fix my code so I can register a new test user?

Thanks so much for helping a new comer!

SOLUTION:

I was able to solve the issue thanks to the suggestion from @srn, through the final result is a little different.

There are two ways to solve this. One way is to build a nested else clause that deals with the case when form.is_valid() is false. It keeps the data already entered (but not yet bounded to the form object) and just renders the form with the data.*

*For those who are confused about this, I learned from this error that althought I have filled out the form and pressed the "register" button, data binding to the form object DOES NOT HAPPEN until form.is_valid().

The views.py function view looks like this:

def librarian_add_user(request):
    if request.method == &#39;POST&#39;:
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            user = form.save()
            messages.success(request, (&quot;User added successfully.&quot;))
            return HttpResponseRedirect(reverse(&#39;catalog:librarian-manage-member&#39;, kwargs={&#39;username&#39;: user.username}))
        else:   # &lt;--- HERE!
            return render(request, &#39;catalog/librarian_add_user.html&#39;, {&#39;user_form&#39;: form})
    else:
        form = UserRegistrationForm()

        context = {
            &#39;user_form&#39;: form,
        }
        return render(
            request,
            &#39;catalog/librarian_add_user.html&#39;,
            context,
        )

The second way is more elegant IMO and is where my code deviated from all the examples in the official Django documentation. All I needed to do is to fix an indentation error after the outer else clause so that the context variable and return command covers the scenario where form.is_valid() is false. This fixed all my issues as shown below:

def librarian_add_user(request):
    if request.method == &#39;POST&#39;:
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            user = form.save()
            messages.success(request, (&quot;User added successfully.&quot;))
            return HttpResponseRedirect(reverse(&#39;catalog:librarian-manage-member&#39;, kwargs={&#39;username&#39;: user.username}))
    else:
        form = UserRegistrationForm()

    context = {   # &lt;--- Outdented from here to the end
        &#39;user_form&#39;: form,
    }
    return render(
        request,
        &#39;catalog/librarian_add_user.html&#39;,
        context,
    )

As shown above, the outer if/else clause now only deals with whether the request is POST or GET, and the inner if clause only returns and exits the program if form.is_valid() is true. I can't believe I was on this for two days...(facepalm)

So to answer my own questions:

  1. Yes, there needs to be coverage for the scenario where form.is_valid() is false. It could be explicit with an else clause, or outdenting the code following the if/else conditions to catch that scenario.
  2. Since form.is_valid() case was not covered, there was no page rendered and hence no error information.
  3. See the two approaches above.

At the end, this goes to show after three years of programming and building apps (non-production) as a hobby, I can still make simple mistakes. So don't ever get discouraged, stupid mistakes will happen years down the road...lol

答案1

得分: 0

如果某些情况下返回 None,通常表示没有返回任何内容。

在你的情况下,这可能会发生在 librarian_add_user() 函数中。

如果 form.is_valid() 不为 True,该函数将根本不返回任何内容,而是返回 None。

这是因为你只提供了它在为 True 时应该执行的操作。如果不是 True,代码不会自动跳回到"外部"的 if/else 语句。

示例:

def foo(baz):
    if baz == 1:
        return(1)

>>> x = foo(2)
>>> type(x)
<class 'NoneType'>

所以,如果表单无效,你应该尝试返回一些东西,以提高在与匹配的 if 语句位于同一级别的 else 条件中的可读性。

英文:

If something returns None, it usually means that nothing was returned.

In your case this can happen in librarian_add_user().

if form.is_valid() is not True, the function will return nothing at all, None.

This happens because you only provide what it shall do in case it is True. If it isn't, the code won't magically jump back to the "outer" if/else clause.

Example:

def foo(baz):
    if baz == 1:
        return(1)

&gt;&gt;&gt; x = foo(2)
&gt;&gt;&gt; type(x)
&lt;class &#39;NoneType&#39;&gt;

So yes, you should try to return something if the form is not valid, for readability from within an else-condition on the same level as the matching 'if'.

huangapple
  • 本文由 发表于 2023年5月29日 04:26:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76353477.html
匿名

发表评论

匿名网友

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

确定