拿我们平时的操作打比方,我们平时发送的请求都是下图这样的,是http请求,一般为无状态的短链接。
那无状态是什么?短链接又是什么?
我们知道浏览器会向服务端发起请求,服务端在接收到请求之后经过一系列的处理最后返回一条响应。短链接就是用来断开这个请求与响应的。无状态就意味着每次浏览器向服务器发出请求都不带有以前的状态,不管浏览器是第几次发出请求,服务器都按照第一次收到请求对待。
与http请求的无状态不一样,cookie就是我们的一种状态。在访问网站时,浏览器会向网站发送一个请求,网站的回复的响应包括响应头和响应体两部分,其中响应体就是展现在网页上的东西,响应头中就包括了cookie。这个cookie在被浏览器接收后就会被保存到浏览器内,在下一次访问这个网站时,浏览器发送的请求中就会包含这个cookie。由此我们知道cookie的两个特点:
我们的想法由此而来:我们可以通过cookie做一个浏览器的凭证,代表这个浏览器是否是第一次访问我们网站,再基于这个完成接下来一系列的用户认证操作。
我们知道网页会给用户发放cookie,那网页自己是怎么确定这个cookie是不是自己发放的呢?网页拿这个cookie有什么用呢?这个就涉及到了我们接下来用的session。
session是一类东西的统称,可以是数据库,也可以是redis,也可以是文件等等。里面的内容就是cookie以及这个cookie对应的变量值。
举一个例子,我的浏览器访问了这个网页,网页给浏览器发放了一个cookie:k=6666
,并在session中给这个cookie保存了name=Olsen,VIP=5
的值。下一次我访问的时候,浏览器告诉网页我的cookie是k=6666
,网页就可以通过session中的键值对确定我的身份信息和VIP等级,从而提供相应的权限和服务。
知道cookie和session的用法之后,我们就可以完成这么一个功能了。
首先是需要做出前端界面,还是三步走(urls.py
, views.py
, html文件
):
在诸多已有的界面中加入我们的登录界面(urls.py)
"""Employee_Management URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from application01.views import depart, num, user, admin, account
urlpatterns = [
# path('admin/', admin.site.urls),
path('depart/list', depart.depart_list),
path('depart/add', depart.depart_add),
path('depart/del', depart.depart_del),
path('depart//edit' , depart.depart_edit),
path('user/list', user.user_list),
path('user/add', user.user_add),
path('user/madd', user.user_madd),
path('user//edit' , user.user_edit),
path('user//del' , user.user_del),
path('num/list', num.num_list),
path('num/add', num.num_add),
path('num/a', num.num_a),
path('num//edit' , num.num_edit),
path('num//del' , num.num_del),
path('admin/list', admin.admin_list),
path('admin/add', admin.admin_add),
path('admin//edit' , admin.admin_edit),
path('admin//del' , admin.admin_del),
path('admin//reset' , admin.admin_reset),
path('login/', account.login), # 登录界面
]
因为views太多了,我将原来的views.py
文件拆成了几部分,我们这次对views的操作都是在account.py
中进行的。
放个最简单的展示操作,够用就行。
# -*- coding:utf-8 -*-
from django.shortcuts import render
# 登录界面
def login(request):
return render(request, 'login.html')
html界面就随便在网上剽一个不要钱的登录界面,放上奥妹美美的照片即可。
因为用户登录的操作仅仅是将用户写入的数据与数据库中已有的数据进行比较,并不是写入数据,所以这里我们就不用大动干戈地使用ModelForm了,直接用Form完成即可。当然ModelForm也可以完成,如果想了解具体的用法可以看03 学习笔记 4.3Django组件优化新建用户。
当然,我们这部分代码主要以学习知识点为目的,在真正开发的时候还是有很多优化的地方的。比如说有的事情能让客户端自己做的就没必要让服务器来干。
account.py
部分需要改一波,这里我们暂时不像以前那样吧LoginForm放到models文件夹中了。这段代码主要完成的工作有:
from django.shortcuts import render, HttpResponse
from django import forms
from application01.utils.encrypt import md5
from application01 import models
class LoginForm(forms.Form):
username = forms.CharField(label='USERNAME',
widget=forms.TextInput(attrs={
'name': "username",
'class': "border-item",
'autocomplete': "off"}),
required=True # 这是默认的,代表必填
)
password = forms.CharField(label='PASSWORD',
widget=forms.PasswordInput( # 用我们白嫖过来的样式
{'name': "password",
'class': "border-item",
'autocomplete': "off"},
render_value=True # 保留密码
),
required=True # 这是默认的,代表必填
)
# 返回密文
def clean_password(self):
pwd = self.cleaned_data.get('password')
return md5(pwd)
# 这部分纯粹就是尝试这个用法,在这个小问题上没有特别大的实际作用
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs["placeholder"] = field.label
# 登录界面
def login(request):
if request.method == 'GET':
form = LoginForm()
return render(request, 'login.html', {"form": form})
form = LoginForm(data=request.POST)
if form.is_valid():
# 这里是没有models.save的,但是form.cleaned_data
# 直接用字典形式,不用一个一个写
admin_exist = models.Admin.objects.filter(**form.cleaned_data).first()
# 密码错误
if not admin_exist:
form.add_error('password', '用户名或密码错误')
return render(request, 'login.html', {'form': form})
return HttpResponse(form.cleaned_data['username'] + '登录成功')
return render(request, 'login.html', {"form": form})
login.html的部分代码如下。主要注意Django在html中的应用。
<body>
<div class="form-wrapper">
<div class="header">
login
div>
<form method="post">
{% csrf_token %}
<div class="input-wrapper">
<div class="border-wrapper">
{{ form.username }}
div>
<div style="text-align: center; width: calc(100% - 4px);">{{ form.username.errors.0 }}div>
<div class="border-wrapper">
{{ form.password }}
div>
<div style="text-align: center; width: calc(100% - 4px);">{{ form.password.errors.0 }}div>
div>
<div class="action">
<input type="submit" class="btn block" value="LOGIN">input>
div>
form>
<div class="icon-wrapper">
<i class="iconfont icon-weixin">i>
<i class="iconfont icon-qq">i>
<i class="iconfont icon-git">i>
div>
div>
body>
效果如下:
之前多次提到cookie,在这里终于用到了。原来是件挺麻烦的事情的,但是Django内部都帮我们封装好了,我们最后只需要一句话就可以完成:
# 登录界面
def login(request):
if request.method == 'GET':
form = LoginForm()
return render(request, 'login.html', {"form": form})
form = LoginForm(data=request.POST)
if form.is_valid():
# 这里是没有models.save的,但是form.cleaned_data
# 直接用字典形式,不用一个一个写
admin_exist = models.Admin.objects.filter(**form.cleaned_data).first()
# 密码错误
if not admin_exist:
form.add_error('password', '用户名或密码错误')
return render(request, 'login.html', {'form': form})
# 生成随机cookie原来还是比较麻烦的事情,但是Django帮我们简化了
# 这一行代码直接完成了cookie的随机字符串的生成,将随机字符串保存到session中并附上响应的值
request.session['info'] = {'id': admin_exist.id, 'username': admin_exist.username}
return redirect("/admin/list")
return render(request, 'login.html', {"form": form})
查看下我们操作的效果:
session的数据是被存在数据库中的,我们访问数据库查看。
在2. 用户登录中我们完成了用户登录界面的制作,但是在实际应用中这样显然是不够的,因为用户完全可以绕开我们的登录界面,从而直接访问其他的界面。因此,我们需要加入中间件来来解决这个问题。
我们当然不可能每个页面之前加入一个判断,确定访问的浏览器是否已经登录,这样非常的机械,显然页太low了。这里我们利用Django中的另一个组件完成这项功能,这个东西就是中间件。
中间件有点类似于servlet里的filter或者是vue里面的路由守卫,我们发出的指令在Djongo中如果想要被执行,就需要通过一个又一个的中间件才能够完成,就像一个食品想要被摆上货架,需要经过层层检查。
当然不止是进入网站需要经过中间件,出网站也需要经过中间件。在Django中,中间件就是一个类,进入是对应的就是process_request
方法,出网站的时候对应的就是process_response
方法。
这么看来,我们确认登录的环节确实很适合用中间件来检验。
先熟悉一下Django中的中间件应用(定义 -> 应用 -> ):
process_request
和process_response
方法。urls.py
中添加网页路径,中间件需要在settings.py
中注册中间件,在已有的MIDDLEWARE
列表中加入新的中间件。MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
首先我们需要先定义一个中间件,我把它放在新的文件夹中:
user_in.py
# -*- coding:utf-8 -*-
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class UserInMiddleware(MiddlewareMixin):
def process_request(self, request):
# 获取当前用户请求的url,如果访问的是login直接放行,不然会死递归
if request.path_info == '/login':
return
# 读取当前用户的session信息
info_dict = request.session.get('info')
# 证明用户已经登录了
if info_dict:
return
# 证明用户没登录
return redirect('/login')
再将它放入到settings.py
里面的路径中
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'application01.middleware.user_in.UserInMiddleware'
]
效果如下:
很自然的,有登录就有注销,我们显然不能要求用户在每次退出的时候都进入到浏览器的设置中,手动消除浏览器的cookie。
Django的封装做的很不错,这里只需要一句话就可以完成注销。
# 注销
def logout(request):
request.session.clear()
return redirect('/login')
我们再把写好的注销函数关联到模板layout.html
中的注销链接中。
{% load static %}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Olsentitle>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1-dist/css/bootstrap-datetimepicker.min.css' %}">
<style>
.navbar{
border-radius: 0;
}
style>
head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigationspan>
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
<a class="navbar-brand" href="#">Olsen用户管理系统a>
div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/admin/list">管理员账户a>li>
<li><a href="/depart/list">部门管理a>li>
<li><a href="/user/list">用户管理a>li>
<li><a href="/num/list">靓号管理a>li>
ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">登录a>li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">ClarkHu <span class="caret">span>a>
<ul class="dropdown-menu">
<li><a href="#">个人资料a>li>
<li><a href="#">我的信息a>li>
<li role="separator" class="divider">li>
<li><a href="/logout">注销a>li>
ul>
li>
ul>
div>
div>
nav>
<div>
{% block content %}
{% endblock %}
div>
<script src="{% static 'js/jquery.js' %}">script>
<script src="{% static 'plugins/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}">script>
body>
html>
效果如下:
在完成上一步的时候我偶然发现,我们之前模板中右上角的用户信息直接被写死了。在效果展示中我们可以看到,明明是Taylor登录的系统,但是左上角仍然显示ClarkHu。
当然这也非常简单啊。之前我们都端往前端穿request或者是form,其实我们可以直接在html中使用request,还是拿模板文件layout.html
示例。只需要把之前的下拉框代码:
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">ClarkHu <span class="caret">span>a>
改成现在这样的:
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{ request.session.info.username }} <span class="caret">span>a>
就可以完成这个效果: