django练手( 十二):实现网站的登录功能
一、编写登录页面的form类。
-
在app-form-app-account.py 文件中导入auth组件的authenticate方法和login方法。authenticate方法的作用是”验证用户名密码是否匹配。若匹配,会返回user对象“,这个对象会在接下来的login方法中使用。login方法接收一个request对象,一个经过认证的user对象。该方法执行后,会在后端为该用户产生session数据。
-
导入authenticate方法和login方法的代码是:
from django.contrib.auth import authenticate, login
-
代码如下:
class LoginForm(forms.Form): """ 登录Form的功能: 1、生成登录表单。该表单包含用户名和密码两个字段; 2、对表单数据进行验证。 3、重写父类的__init__方法,增加request参数,从视图获取request。 4、用户名,密码验证通过后,通过auth组件的login方法,实现用户登录。 """ username = forms.CharField(max_length=25, label='用户名', required=True, error_messages={"required": "用户名不能为空", "max_length": "用户名不能超过25个字符"}) password = forms.CharField(widget=forms.PasswordInput(), label='密码', required=True, min_length=8, error_messages={"required": "密码不能为空", "min_length": "密码小于8个字符,请重新输入"}) # 重新父类的__init__方法,从视图获取request数据,是LoginForm可以传进来request def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) self.request = request # 给表单加上bootstrap的样式 for name, field in self.fields.items(): field.widget.attrs['class'] = "form-control" field.widget.attrs['placeholder'] = '请输入{}'.format(field.label) # 验证用户名是否存在 def clean_username(self): username = self.cleaned_data['username'] exists = MyUser.objects.filter(username=username).exists() if not exists: raise ValidationError('用户名不存在,请重新输入') return username # 验证密码 def clean_password(self): password = self.cleaned_data['password'] username = self.cleaned_data.get('username', '') # 使用auth组件的authenticate功能,验证用户名密码是否匹配。若匹配,会返回user对象 user = authenticate(username=username, password=password) # 如果user对象为空,则返回错误信息”用户名或密码不正确“ if user is None: raise ValidationError('用户名或密码不正确') # 如果user对象存在,使用auth组件的login方法,进行登录。该方法接收一个request对象,一个经过认证的user对象。 # login方法执行后,会在后端为该用户产生session数据 else: login(request=self.request, user=user) return password
二、编写登录页面的视图。
-
登录视图的代码如下:
# 登录视图 def login_view(request): """ 功能描述: 1、如果是GET方法访问该视图,返回登录表单。 2、如果是POST方法访问该视图,则用LoginForm进行数据验证,如果验证通过,进入登录状态。如果验证不通过,返回错误信息。 """ if request.method == 'GET': fields = LoginForm(request) return render(request, 'app/login.html', {'fields': fields}) if request.method == 'POST': data = request.POST fields = LoginForm(request, data) if fields.is_valid(): return JsonResponse({"status": True}) else: return JsonResponse({"status": False, "errors": fields.errors})
三、登录页面的模板设计。
-
登录页面的模板与注册页面的模板几乎完全一样,不做详细解释,代码如下:
{% extends 'app/layout/basic.html' %} {% load static %} {% block css %} <link rel="stylesheet" href="{% static 'css/account.css' %}"> {% endblock %} {% block content %} <div class="account"> <div class="title"> 登录</div> <form id="login_form" method="post"> {% csrf_token %} <div class="form-group"> {% for field in fields %} <div class="form-group"> <label for={{ field.id_for_label }}>{{ field.label }}</label> {{ field }} <span class="error-msg"></span> </div> {% endfor %} </div> <input id="btnSubmit" type="button" class="btn btn-primary btn-block " value="登录"> </form> </div> {% endblock %} {% block js %} <script> $(document).ready(function () { btnSubmitClick() }) function btnSubmitClick() { $("#btnSubmit").click(function () { let data = $("#login_form").serialize(); $.ajax({ url: "{% url 'app:login' %}", type: "POST", data: data, async: true, dataType: "json", success: function (res) { if (res.status) { location.href='/'; } else { $.each(res.errors, function (key, value) { $("#id_" + key).next().text(value); }) } }, errors: function () { alert("请求失败"); } }) }) } </script> {% endblock %}
四、登录页面的路由。
-
登录页面的路由写在app-urls.py 文件中,代码如下:
from django.urls import path from app.views.app.views import * urlpatterns = [ path('register/', register, name='register'), # 注册的路由 path('login/', login_view, name='login'), # 登录的路由 ]
五、在导航栏上体现登录状态。
- 导航栏的登录状态与非登录状态对比如下面两张图:
- 代码在app-templates-app-layout-basic.html里编写。实现原理是,如果能获取请求网页的用户对象的用户名,则判断用户为登录状态,则在导航栏显示用户名和用户中心。如果不能获取请求用户对象的用户名,则判断用户为非登录状态,导航栏显示登录和注册链接。具体代码的位置如下图:
- 具体的代码如下:
<ul class="nav navbar-nav navbar-right">
{% if request.user.username %}
<li><a href="#">欢迎你:{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">用户中心 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'app:login' %}">登录</a></li>
<li><a href="{% url 'app:register' %}">注册</a></li>
{% endif %}
</ul>
六、优化注册功能,实现注册完自动登录。
- 上一节的注册功能,只实现了用户注册信息写入数据库。在这里,我在注册视图里加如登录功能。具体的思路是:如果用户的注册的结果不为空,则使用auth组件的authenticate方法,验证用户注册时使用的用户名和密码,并返回用户对象。接下来,使用auth组件的login方法,进行用户登录。
- 需要优化代码的位置:代码在app-views-views.py 文件的register方法下。优化后的register方法的代码如下:
# 注册视图
def register(request):
"""
1、如果是GET请求,向注册模板返回表单数据;
2、如果是POST请求,执行注册动作;如果注册成功,新用户登录,向模板返回status的值是True。否则,返回status返回的值是False,并把错误信息返回给模板。
"""
if request.method == 'GET':
fields = RegisterForm()
return render(request, 'app/register.html', {'fields': fields})
if request.method == 'POST':
data = request.POST
fields = RegisterForm(data)
if fields.is_valid():
username = request.POST.get('username', '')
password = request.POST.get('password', '')
email = request.POST.get('email', '')
phone_num = request.POST.get('phone_num', '')
address = request.POST.get('address', '')
# 注册
reg_ok = MyUser.objects.create_user(username=username, password=password, email=email, phone_num=phone_num,
address=address)
# 如果注册成功,验证用户名密码,登录系统
if reg_ok:
# 验证用户名密码,获取验证后的用户对象
user = authenticate(username=username, password=password)
# 登录系统
login(request=request,user=user)
return JsonResponse({"status": True})
else:
return JsonResponse({"status": False, "errors": fields.errors})