英文:
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() # <--- 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,
)
这可能是一些简单且愚蠢的问题,但我花了整个下午、晚上以及今天一整天的时间来查看它,都没有任何效果。是否有人可以帮助查看并回答以下问题?
- 是否需要嵌套的
else
子句,用于处理form.is_valid()
失败的情况?即使MDN和Django的教程也没有这样做。 - 为什么我的
form.errors
不显示在模板中,以及这与form.is_valid()
失败并返回None
有什么关系(我认为有关)? - 如何修复我的代码,以便我可以注册一个新的测试用户?
非常感谢帮助新手!
英文:
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'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'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 = (
'username',
'first_name',
'last_name',
'email',
'password1',
'password2',
)
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 == '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:
# print(form.errors) # I can see the errors printed in my terminal, which means `form.is_valid()` failed
else:
form = UserRegistrationForm()
context = {
'user_form': form,
}
return render(
request,
'catalog/librarian_add_user.html',
context,
)
and the librarian_add_user.html
in my templates:
{% 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 %}
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 == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
form.save()
...
return HttpResponseRedirect(reverse('catalog:index'))
else:
form = UserRegistrationForm() # <--- HERE!
context = {
'user_form': form
}
else:
form = UserRegistrationForm()
...
return render(
request,
'catalog/librarian_add_user.html',
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): # <--- ENTIRE METHOD HERE
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
And these changes to views.py
:
def librarian_add_user(request):
if request.method == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
user = form.save() # <--- HERE
login(request, user) # <--- HERE
messages.success(request, ("User added successfully.")) # <--- HERE
return HttpResponseRedirect(reverse('catalog:index'))
else:
form = UserRegistrationForm()
...
return render(
request,
'catalog/librarian_add_user.html',
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?
- Do I need a nested
else
clause for the case whenform.is_valid()
fails? Even the tutorial on MDN and Django doesn't do this. - Why is it that my
form.errors
does not show in the template and how is this related (which I think it is) toform.is_valid()
failing and returningNone
? - 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 == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
user = form.save()
messages.success(request, ("User added successfully."))
return HttpResponseRedirect(reverse('catalog:librarian-manage-member', kwargs={'username': user.username}))
else: # <--- HERE!
return render(request, 'catalog/librarian_add_user.html', {'user_form': form})
else:
form = UserRegistrationForm()
context = {
'user_form': form,
}
return render(
request,
'catalog/librarian_add_user.html',
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 == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
user = form.save()
messages.success(request, ("User added successfully."))
return HttpResponseRedirect(reverse('catalog:librarian-manage-member', kwargs={'username': user.username}))
else:
form = UserRegistrationForm()
context = { # <--- Outdented from here to the end
'user_form': form,
}
return render(
request,
'catalog/librarian_add_user.html',
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:
- Yes, there needs to be coverage for the scenario where
form.is_valid()
is false. It could be explicit with anelse
clause, or outdenting the code following theif/else
conditions to catch that scenario. - Since
form.is_valid()
case was not covered, there was no page rendered and hence no error information. - 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)
>>> x = foo(2)
>>> type(x)
<class 'NoneType'>
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'.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论