Django搭建个人博客:用户的注册

6898阅读 · 35评论 · 2018/11/03发布   前往评论

既然有登录登出,那么用户的注册肯定也是少不了的。

注册表单类

用户注册时会用到表单来提交账号、密码等数据,所以需要写注册用的表单/userprofile/forms.py

/userprofile/forms.py

...

# 注册用户表单
class UserRegisterForm(forms.ModelForm):
    # 复写 User 的密码
    password = forms.CharField()
    password2 = forms.CharField()

    class Meta:
        model = User
        fields = ('username', 'email')

    # 对两次输入的密码是否一致进行检查
    def clean_password2(self):
        data = self.cleaned_data
        if data.get('password') == data.get('password2'):
            return data.get('password')
        else:
            raise forms.ValidationError("密码输入不一致,请重试。")

上一章也讲过,对数据库进行操作的表单应该继承forms.ModelForm,可以自动生成模型中已有的字段。

这里我们覆写了password字段,因为通常在注册时需要重复输入password来确保用户没有将密码输入错误,所以覆写掉它以便我们自己进行数据的验证工作。def clean_password2()中的内容便是在验证密码是否一致了。def clean_[字段]这种写法Django会自动调用,来对单个字段的数据进行验证清洗。

覆写某字段之后,内部类class Meta中的定义对这个字段就没有效果了,所以fields不用包含password

需要注意:

  • 验证密码一致性方法不能写def clean_password(),因为如果你不定义def clean_password2()方法,会导致password2中的数据被Django判定为无效数据从而清洗掉,从而password2属性不存在。最终导致两次密码输入始终会不一致,并且很难判断出错误原因。
  • 从POST中取值用的data.get('password')是一种稳妥的写法,即使用户没有输入密码也不会导致程序错误而跳出。前面章节提取POST数据我们用了data['password'],这种取值方式如果data中不包含password,Django会报错。另一种防止用户不输入密码就提交的方式是在表单中插入required属性,后面会讲到。

视图函数

编写注册的视图/userprofile/views.py

/userprofile/views.py

# 引入 UserRegisterForm 表单类
from .forms import UserLoginForm, UserRegisterForm

# 用户注册
def user_register(request):
    if request.method == 'POST':
        user_register_form = UserRegisterForm(data=request.POST)
        if user_register_form.is_valid():
            new_user = user_register_form.save(commit=False)
            # 设置密码
            new_user.set_password(user_register_form.cleaned_data['password'])
            new_user.save()
            # 保存好数据后立即登录并返回博客列表页面
            login(request, new_user)
            return redirect("article:article_list")
        else:
            return HttpResponse("注册表单输入有误。请重新输入~")
    elif request.method == 'GET':
        user_register_form = UserRegisterForm()
        context = { 'form': user_register_form }
        return render(request, 'userprofile/register.html', context)
    else:
        return HttpResponse("请使用GET或POST请求数据")

逻辑上结合了发表文章视图用户登录视图,没有新的知识。

用户在注册成功后会自动登录并返回博客列表页面。

模板和url

表单有关的模板文件我们也很熟悉了,新建/templates/userprofile/register.html

/templates/userprofile/register.html

{% extends "base.html" %} {% load staticfiles %}
{% block title %} 登录 {% endblock title %}
{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
            <br>
            <form method="post" action=".">
                {% csrf_token %}
                <!-- 账号 -->
                <div class="form-group col-md-4">
                    <label for="username">昵称</label>
                    <input type="text" class="form-control" id="username" name="username" required>
                </div>
                <!-- 邮箱 -->
                <div class="form-group col-md-4">
                    <label for="email">Email</label>
                    <input type="text" class="form-control" id="email" name="email">
                </div>
                <!-- 密码 -->
                <div class="form-group col-md-4">
                    <label for="password">设置密码</label>
                    <input type="password" class="form-control" id="password" name="password" required>
                </div>
                <!-- 确认密码 -->
                <div class="form-group col-md-4">
                    <label for="password2">确认密码</label>
                    <input type="password" class="form-control" id="password2" name="password2" required>
                </div>
                <!-- 提交按钮 -->
                <button type="submit" class="btn btn-primary">提交</button>
            </form>
        </div>
    </div>
</div>
{% endblock content %}

上面的模板文件中,我们在昵称、密码input 标签中添加了required属性(前面提到过)。如果用户不填写带有required属性的字段,表单就不能提交,并提示用户填写。实际上前面学习的很多表单都可以添加required属性来提前验证数据的有效性。

注册的入口你可以放在任何喜欢的地方。本文放在登录页面中/templates/userprofile/login.html

/templates/userprofile/login.html

...
<div class="col-12">
    <br>
    <h5>还没有账号?</h5>
    <h5>点击<a href='{% url "userprofile:register" %}'>注册账号</a>加入我们吧!</h5>
    <br>
    <form method="post" action=".">
        ...
    </form>
</div>

...

最后就是在app中配置路由文件/userprofile/urls.py了:

/userprofile/urls.py

...

urlpatterns = [
    ...
    # 用户注册
    path('register/', views.user_register, name='register'),
]

测试

运行服务器,进入到登录页面,多了注册的提示:

点击注册账号进入注册页面:

填写好表单后提交(Email地址是可以为空的):

成功登录并返回了博客列表,功能完成。

总结

本章用到了表单类、对数据进行验证清洗等知识,完成了用户的注册功能。

接下来学习如何实现删除已有的用户。




本文作者: 杜赛
发布时间: 2018年11月03日 - 11:06
最后更新: 2019年01月28日 - 10:08
知识共享许可协议   转载请保留原文链接及作者


登录 后回复

共有35条评论

avatar
Superb来了 么么哒! 5

用户注册表单/userprofile/forms.py密码一致性校验的代码有个小错误~应该是if data.get('password') == data.get('password2')

1年前 回复


avatar
杜赛 [博主] Superb来了 么么哒! 4

已经改过来了。 感谢指出!

1年前 回复


avatar
Python 么么哒! 4

你好!突然想到一个需求:在用户注册成功后或正常登录,根据是否超级管理员来判定能否看见“后台管理”这个标签,规定除了管理员其余用户都不可见。同时在views视图里用内置的标签循环来实现?

{% if user.is_authenticated %}

这个标签是检验是否登录,那么还有同样的标签可以实现上面的需求吗?谢谢

11个月前 回复


avatar
杜赛 [博主] Python 么么哒! 4

user.is_superuser

或者:

user.is_staff

 

11个月前 回复


avatar
Python Python 么么哒! 4

好像找到了是 

{% if is_superuser %}

而且检验通过了,Stack Overflow看来还是有用,国内谷歌查不到,百度都是垃圾答案。

11个月前 回复


avatar
Python 杜赛 [博主] 么么哒! 4

谢谢,你真是秒回

 

11个月前 回复


avatar
杜赛 [博主] Python 么么哒! 4

搜索答案的能力是程序员一项很重要的技能哟laugh

11个月前 回复


avatar
Python 杜赛 [博主] 么么哒! 4

不好意思。感觉自己还是太菜了。但是决定和这个需求死磕下。虽然 is_superuser 可以解决权限检查,但是如果对方知道我的网址命名规律还是可以访问到“/article/article-column/”这个目录,还有办法阻止吗?谢谢

11个月前 回复


avatar
杜赛 [博主] Python 么么哒! 4

这就需要你在后台中对用户的身份进行检查了。

比如在视图中,同样可以用user.is_superuser对用户身份进行鉴权,把非法用户清洗掉。

教程中某一章应该是讲过这个内容的,你继续往下看。

11个月前 回复


avatar
Python 杜赛 [博主] 么么哒! 4

看了下好像可以用 

@login_required

装饰器来写,一直还没试出来,明天继续找。

11个月前 回复


avatar
waynecanfly 么么哒! 4

博主你好,为什么用户注册密码输入不一致时,并没有"密码输入不一致,请重试。"这样的提示,而是全部提示“注册表单输入有误,请重新输入。”呢?

11个月前 回复


avatar
杜赛 [博主] waynecanfly 么么哒! 4

你说得对,正常来说应该提示“密码不一致”更加人性化。

因为教程主要面向新手,为了避免内容繁杂,只重点设计了成功注册的流程,其他的逻辑分支一律返回“表单有误”。

form.py中的“密码不一致”返回的是错误处理,是给开发者看的,用户是看不到的。

读者可以考虑自己尝试去完善所有的逻辑。

11个月前 回复


avatar
waynecanfly 杜赛 [博主] 么么哒! 4

好的,谢谢博主~

11个月前 回复


avatar
ac1864 么么哒! 4

自己敲一遍没有过关,怎么也检查不出问题。

然后copy,paste,

代码又能运行了。

估计是一些空格的问题。先过关,然后再全部自己敲一遍。

谢谢作者大大的好教程。

10个月前 回复


avatar
pengshilin ac1864 么么哒! 4

估计是错字的问题

9个月前 回复


avatar
zhj 么么哒! 4

这里的复写密码是什么意思啊?cool

7个月前 回复


avatar
杜赛 [博主] zhj 么么哒! 4

复写密码?哪个位置?

7个月前 回复


avatar
zhj 杜赛 [博主] 么么哒! 4

# 注册用户表单
class UserRegisterForm(forms.ModelForm):
    # 复写 User 的密码
    password = forms.CharField()
    password2 = forms.CharField()

不好意思,我说的不详细,我是看到这里写的备注是复写密码

7个月前 回复


avatar
杜赛 [博主] zhj 么么哒! 5

这里可能表述有点问题。没有特别的含义,就是写了两个密码字段的意思,第二个用于校验

7个月前 回复


avatar
南霁姑娘 么么哒! 6

    elif request.method == 'GET':
        user_register_form = UserRegisterForm()
        context = { 'form': user_register_form }
        return render(request, 'userprofile/register.html', context)

博主你好,我想问一下,如果注册页面html不是自动获取表单内容展示,传不传context应该没啥影响吧

7个月前 回复


avatar
杜赛 [博主] 南霁姑娘 么么哒! 4

是的,这个地方是我的疏忽,不传也是可以的。有时间会更正过来。

7个月前 回复


avatar
团子大圆帅 杜赛 [博主] 么么哒! 2

刚想问同样的问题,翻了一遍留言找到答案了,谢谢博主和层主。

6个月前 回复


avatar
LXL 么么哒! 1

杜老师,Django中,登录时账号和密码错误后,怎么设置弹出一个提示框啊

4个月前 回复


avatar
LXL LXL 么么哒! 2

杜老师,有没有你现在这个博客的源代码啊,我碰到问题了,不会解决crying,你这个博客刚好有

4个月前 回复


avatar
杜赛 [博主] LXL 么么哒! 2

用 JS 好解决一点,你可能得补补 JS 的知识

4个月前 回复


avatar
杜赛 [博主] 杜赛 [博主] 么么哒! 2

什么问题,欢迎进群讨论

4个月前 回复


avatar
LXL 杜赛 [博主] 么么哒! 2

这个我单单的用js会做,但是加个python的判断代码就不会调用了

4个月前 回复


avatar
zhangmz1 么么哒! 2

本页面顶部:注册表单类 /userprofile/forms.py

class Meta:
    model = User
    fields = ('username', 'email')

其中 model = User,这里的 User 需要通过模块引入吗?

复制你的代码运行会报错,另外这个地方和Django版本有关系吗?

3个月前 回复


avatar
zhangmz1 zhangmz1 么么哒! 2

已解决cheeky

from django.contrib.auth.models import User

这个里面的,没注意到。

3个月前 回复


avatar
xusang 么么哒! 2

提一个可能比较小白的问题:

new_user = user_register_form.save(commit=False

这行代码执行的时候,user_register_form 中是有 password 和 password2 两个数据的,为什么 .save 方法不会将其中的 password 保存到 new_user 中。而需要在下方重新用 new_user.set_password 来设置密码呢?

2个月前 回复


avatar
493533667 么么哒! 2

请问一下博主,为什么编写clean_password的时候不能用函数名为clean_password而必须用clean_password2,还有self.cleaned_data在Django form类里面并不是初始变量为什么能被直接继承。这是为什么呢。跪求博主解答!

1个月前 回复


avatar
杜赛 [博主] 493533667 么么哒! 2

  • 因为 password2 在模型里是没有定义的,clean_password2 方法相当于告诉 django ,password2 这个变量是有用的,你别给我清洗掉。
  • cleaned_data 由父类提供,如何实现的不用深究,用就是了。实在感兴趣,读源码吧。

1个月前 回复


avatar
493533667 杜赛 [博主] 么么哒! 1

谢谢博主!

是不是这样理解,password字段在Django内部User里面已经被定义过了,而password2字段没有定义过,所以如果不定义clean_password2函数的话,就会被clean函数清洗掉。而clean_[field]这种函数会被Django自动调用,并且field会被保存下来。所以才一定要写成clean_password2。所以写成clean_password3也不行。field的名字必须要和新定义的字段名字相符合。

 

再次感谢博主

1个月前 回复


avatar
camel 么么哒! 1

我的forms.py已经写明了这个:

# 注册用户表单
class UserRegisterForm(forms.ModelForm):
    # 复写 User 的密码
    class UserRegisterForm(forms.ModelForm):
        # 复写 User 的密码
        password = forms.CharField()
        password2 = forms.CharField()

        class Meta:
            model = User  # 这里我指明了 User
            fields = ('username', 'email')

但是当我点击注册的时候报错:

  File "F:\my_blog\userprofile\views.py", line 55, in user_register
    user_login_form = UserRegisterForm()
  File "F:\ENV\djangoBlog\lib\site-packages\django\forms\models.py", line 285, in __init__
    raise ValueError('ModelForm has no model class specified.')
ValueError: ModelForm has no model class specified.
[03/Mar/2020 02:05:12] "GET /userprofile/register/ HTTP/1.1" 500 73217

我的views.py 的第55行如下所示:

    elif request.method == 'GET':
        user_login_form = UserRegisterForm()  # 第55行
        context = {'form': user_login_form}
        return render(request, 'userprofile/register.html', context)
    else:
        return HttpResponse('请使用GET或POST请求数据')

请老师指点一下谢谢(win10 64位  python3.6   django2.1  mysql5.7)

1个月前 回复


avatar
camel camel 么么哒! 1

找到哪里错了,晚上写的累了  forms.py表单写了两行 class  UserRegisterForm,哎

1个月前 回复