1.cookie
-
HTTP被设计为”⽆态“,这⼀次请求和下⼀次请求 之间没有任何状态保持,我们⽆法根据请求的任何⽅⾯(IP地址,⽤户代理等)来识别来⾃同⼀⼈的连续请求。实现状态保持的⽅式:在客户端或服务器端存储与会话有关的数据(客户端与服务器端的⼀次通信,就是⼀次会话)
-
方法
set_cookie(self, key, value='', max_age=None, expires=None, path='/',domain=None, secure=False, httponly=False, samesite=None):
- key:cookie的名称
- value:cookie的值
- max_age:cookie的有效时间,设置为None,关闭浏览器时关闭
- expires:过期时间
- path:cookie的作用路径
- domain:cookie作用的域名
- secure:需要https传递cookie
- httponly:仅http传输,不允许js获取
set_signed_cookie(self, key, value, salt='', **kwargs)
- 对cookie的value加密,其他的一样
HttpResponse.delete_cookie(key, path='/', domain=None)
- 删除cookie
1.1.案例
- login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="{% url 'app:login' %}" method="post">
{% csrf_token %}
用户名:<input type="text" name="username"> <br>
密码: <input type="password" name="password"> <br>
<input type="submit">
</form>
</body>
</html>
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
{% if username %}
{{ username }} <a href="{% url 'app:logout' %}">退出登录</a>
{% else %}
你还有登录,<a href="{% url 'app:login' %}">点击登录</a>
{% endif %}
</body>
</html>
- urls.py
from django.urls import path
from app import views
app_name = 'app'
urlpatterns = [
path('login/', views.login, name="login"),
path('logout/', views.logout, name="logout"),
path('', views.home, name="home")
]
- views.py
from datetime import datetime, timedelta
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from app.models import Account
def login(request: HttpRequest):
if request.method == "POST":
username = request.POST.get('username', "")
password = request.POST.get('password', "")
user = Account.objects.get(username=username, password=password)
if user:
response = render(request, 'app/index.html', locals())
response.set_cookie('username', username, expires=datetime.now() + timedelta(days=3))
return response
return render(request, 'app/login.html')
def logout(request: HttpRequest):
res = render(request, 'app/index.html')
res.delete_cookie('username')
return res
def home(request: HttpRequest):
username = request.COOKIES.get('username')
return render(request, 'app/index.html', locals())
2.session
-
cookie看似解决了HTTP(短连接、⽆状态)的会话保持问题,但把全部⽤户数据保存在客户端,存在安全隐患,于是session出现了。我们可以 把关于⽤户的数据保存在服务端,在客户端cookie⾥加⼀个sessionID(随机字符串)。
-
其⼯作流程:
-
(1)、当⽤户来访问服务端时,服务端会⽣成⼀个随机字符串;
-
(2)、当⽤户登录成功后 把 {sessionID :随机字符串} 组织成键值对加到cookie⾥发送给⽤户;
-
(3)、服务器以发送给客户端 cookie中的随机字符串做键,⽤户信息做值,保存⽤户信息;
-
(4)、再访问服务时客户端会带上sessionid,服务器根据sessionid来确认⽤户是否访问过⽹站
2.1.案例
- login.html、index.html、urls.py和前面cookie一样
- views.py
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
# Create your views here.
from app.models import Account
def login(request: HttpRequest):
if request.method == "POST":
username = request.POST.get('username', "")
password = request.POST.get('password', "")
user = Account.objects.get(username=username, password=password)
if user:
response = render(request, 'app/index.html', locals())
# response.set_cookie('username', username, expires=datetime.now() + timedelta(days=3))
request.session['username'] = username
return response
return render(request, 'app/login.html')
def logout(request: HttpRequest):
res = render(request, 'app/index.html')
# res.delete_cookie('username')
del request.session['username']
return res
def home(request: HttpRequest):
# username = request.COOKIES.get('username')
username = request.session.get('username', "")
return render(request, 'app/index.html', locals())
3.cookie和session简单区别
-
session将数据存储与服务器端 cookie存储在客户端
-
cookie 存储在客户端,不安全,sess存储在服务器端,客户端只存sesseionid,安全
-
cookie在客户端存储值有⼤⼩的限制,⼤约⼏kb。session没有限制
4.分页
-
Paginator分页器
-
实例化:Paginator(<query_set查询集>,每⻚显示数据的条数)
-
方法
- page(num) 返回page对象 如果给定的⻚码不存在 则抛出异常
-
属性
-
count 分⻚对象的个数
-
num_pages 总⻚数
-
page_range ⻚码的列表
-
-
page对象
| 类别 | 名称 | 说明 |
|---|---|---|
| 属性 | object_list | 当前⻚码上的所有数据 |
| 属性 | number | 当前⻚码值 |
| 属性 | paginator | 返回Paginator的对象 |
| ⽅法 | has_next | 是否有下⼀⻚ |
| ⽅法 | has_previous | 是否有上⼀⻚ |
| ⽅法 | has_other_pages | 是否有上⼀⻚ 或者下⼀⻚ |
| ⽅法 | next_page_number | 返回下⼀⻚的⻚码 |
| ⽅法 | previous_page_number | 返回上⼀⻚的⻚码 |
| ⽅法 | len | 返回当前⻚数据的个数 |
4.1.实例
- url.py
path('page/<int:page>', views.page_test, name='page')
- views.py
def page_test(request: HttpRequest, page=1):
accounts = Account.objects.all()
paginator = Paginator(accounts, 10)
pageobj = paginator.page(page)
return render(request, 'list.html', locals())
- list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1" cellspacing="0" width="80%">
<tr>
<td>用户名</td>
<td>密码</td>
</tr>
{% for account in pageobj.object_list %}
<tr>
<td>{{ account.username }}</td>
<td>{{ account.password }}</td>
</tr>
{% endfor %}
</table>
<div>
{# paginator.page_range 页码列表 #}
{% for page in paginator.page_range %}
<a href="{% url 'app:page' page %}">{{ page }}</a>
{% endfor %}
</div>
</body>
</html>
5.form表单
- 概要
通常情况下,我们需要⾃⼰⼿动在HTML⻚⾯中,编写form标签和其内的其它元
素。但这费时费⼒,⽽且有可能写得不太恰当,数据验证也⽐较麻烦。有鉴于此,
Django在内部集成了⼀个表单模块,专⻔帮助我们快速处理表单相关的内容。
Django的表单模块给我们提供了下⾯三个主要功能:
- 准备和重构数据⽤于⻚⾯渲染
- 为数据创建HTML表单元素
- 接收和处理⽤户从表单发送过来的数据
5.1.使用
- 有2种实用方式,一种是继承django.forms.Form,和模型类相似,第二种是集成django.froms.ModelForm,指定模型类生成form表单
- ModelForm使用
class Shop(models.Model):
title = models.CharField('标题', max_length=30)
content = models.CharField('内容', max_length=20)
class Meta:
db_table = 'T_SHOP'
class ShopForm(ModelForm):
class Meta:
model = Shop
fields = "__all__"
- Form使用
class RegisterForm(forms.Form):
username = forms.CharField(min_length=3,required=True,error_messages={
'required':'用户名必须输入',
'min_length':'用户名至少3个字符'
})
password = forms.CharField(min_length=3,required=True,error_messages={
'required': '密码名必须输入',
'min_length': '密码至少3个字符'
})
confirm = forms.CharField(min_length=3,required=True,error_messages={
'required': '密码名必须输入',
'min_length': '密码至少3个字符'
})
regtime = forms.DateTimeField(required=False,error_messages={
'invalid':'日期格式错误',
})
sex = forms.BooleanField(required=False)
5.2.Form字段和字段属性
5.3.重写验证
单个字段重写验证
-
函数名就必须为clean_字段名
-
必须有返回值
-
只能拿⾃⼰当前字段值
-
错误必须是raise ValidationError('xxx')
def clean_password(self):
if self.cleaned_data.get('password').isdigit() or self.cleaned_data.get('password').isalpha():
raise ValidationError('密码必须包含数字和字⺟')
else:
return self.cleaned_data['password']
所有字段重写验证
def clean(self):
if self.cleaned_data.get('password') != self.cleaned_data.get('password2'):
raise ValidationError('密码不⼀致')
else:
return self.cleaned_data
5.4.事例
- models.py
class User(AbstractUser):
uid = models.AutoField(primary_key=True)
username = models.CharField(unique=True, max_length=30)
password = models.CharField(max_length=128)
regtime = models.DateTimeField()
sex = models.IntegerField(blank=True, null=True)
class Meta:
db_table = 'user'
- forms.py
class RegisterForm(forms.Form):
username = forms.CharField(min_length=3, required=True, error_messages={
'required': '用户名必须输入',
'min_length': '用户名至少3个字符'
})
password = forms.CharField(min_length=3, required=True, error_messages={
'required': '密码名必须输入',
'min_length': '密码至少3个字符'
})
confirm = forms.CharField(min_length=3, required=True, error_messages={
'required': '密码名必须输入',
'min_length': '密码至少3个字符'
})
regtime = forms.DateTimeField(required=False, error_messages={
'invalid': '日期格式错误',
})
sex = forms.BooleanField(required=False)
# 单个字段验证: clean_xxxx
def clean_password(self):
password = self.cleaned_data.get('password')
print(password)
if password and password.isdigit():
raise ValidationError("密码不能是纯数字")
return password
# 全局验证
def clean(self):
password = self.cleaned_data.get('password', None)
confirm = self.cleaned_data.get('confirm', '')
print(password, confirm)
if password != confirm:
raise ValidationError({'confirm': "两次密码输入不一致"})
return self.cleaned_data
- urls.py
app_name = 'user'
urlpatterns = [
path('userregister/', views.user_register, name='user_register'),
]
- views.py
def user_register(request):
if request.method == "POST":
print(request.POST)
form = RegisterForm(request.POST)
if form.is_valid():
data = form.cleaned_data
data.pop("confirm")
# 把用户写入数据库
# 密码会做签名,不能手动签名加密password
user = User.objects.create_user(**data)
if user:
return HttpResponse("注册成功")
else:
return render(request, "register.html", {'form': form})
else:
return render(request, "register.html", {'form': form})
return render(request, "register.html")
- register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<form action="{% url 'user:user_register' %}" method="post">
{% csrf_token %}
用户名:<input type="text" name="username">
{% for error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %}
<br>
密码:<input type="password" name="password">
{{ form.password.errors }}
<br>
确认密码:<input type="password" name="confirm">
{{ form.confirm.errors }}
<br>
注册时间:<input type="text" name="regtime">
{{ form.regtime.errors }}
<br>
性别:<input type="radio" name="sex" value="0">女
<input type="radio" name="sex" value="1"> 男
{{ form.sex.errors }}
<br>
<input type="submit" value="注册">
</form>
</body>
</html>
- settings.py
AUTH_USER_MODEL = 'app.User' # appname.modelname
6.用户认证
6.1.概要
- auth模块是Django提供的标准权限管理系统,可以提供⽤户身份认证, ⽤户组和权限管理。在INSTALLED_APPS中添加'django.contrib.auth'使⽤该APP
- 主要操作
- create_user 创建⽤户
- authenticate验证登录
- login记录用户登录状态
- logout退出登录
- is_authenticated判断是否登录
- @login_required保护路由
6.2.django user对象
| 说明 | 说明 | 备注 |
|---|---|---|
| username | 少于等于30个字符。 ⽤户名可以包含字⺟、数字、_、@、+、.和- 字符 | 必选 |
| password | 密码的哈希及元数据。(Django 不保存原始密码)。原始密码可以⽆限⻓⽽且可以包含任意字符。 | 必须 |
| is_active | 账户是否激活,默认True | 非必选 |
| first_name | 少于30个字符 | 非必选 |
| last_name | 少于30个字符 | 非必选 |
| 邮箱地址 | 非必选 | |
| groups | 与Group之间多对多 | 非必选 |
| is_staff | 是否可以登录admin站点 | 非必选 |
| is_superuser | 是否是管理员 | 非必选 |
| last_login | 用户最后一次登录 | 非必选 |
| date_joined | 用户创建时间 | 非必选 |
- is_active为false可以禁止用户登录
6.3.扩展User模型
-
django自带的user有时候不满足业务需求,django.contrib.auth.models.User也是继承⾃AbstractUser抽象基类,⽽且仅仅就是继承了AbstractUser ,没有对AbstractUser做任何的拓展
-
在自己的app/models.py中定义用户类
class User(AbstractUser):
phone = models.CharField(max_length=12,null=True,verbose_name="⼿机号")
class Meta(AbstractUser.Meta):
db_table='user'
- 在setttings.py定义AUTH_USER_MODEL,'app_name.user_model_name'
AUTH_USER_MODEL='app.user'
- 迁移
python manage.py makemigrations
python manage.py migrate
6.4.事例
- urls.py
from django.urls import path
from . import views
app_name = 'user'
urlpatterns = [
path('userregister/', views.user_register, name='user_register'),
path('login/', views.user_login, name='login'),
path('user_info/', views.user_info, name='login'),
path('logout/', views.user_logout, name='logout'),
]
- forms.py
from django import forms
from django.core.exceptions import ValidationError
class RegisterForm(forms.Form):
username = forms.CharField(min_length=3, required=True, error_messages={
'required': '用户名必须输入',
'min_length': '用户名至少3个字符'
})
password = forms.CharField(min_length=3, required=True, error_messages={
'required': '密码名必须输入',
'min_length': '密码至少3个字符'
})
confirm = forms.CharField(min_length=3, required=True, error_messages={
'required': '密码名必须输入',
'min_length': '密码至少3个字符'
})
regtime = forms.DateTimeField(required=False, error_messages={
'invalid': '日期格式错误',
})
sex = forms.BooleanField(required=False)
# 单个字段验证: clean_xxxx
def clean_password(self):
password = self.cleaned_data.get('password')
print(password)
if password and password.isdigit():
raise ValidationError("密码不能是纯数字")
return password
# 全局验证
def clean(self):
password = self.cleaned_data.get('password', None)
confirm = self.cleaned_data.get('confirm', '')
print(password, confirm)
if password != confirm:
raise ValidationError({'confirm': "两次密码输入不一致"})
return self.cleaned_data
- views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpRequest
from django.shortcuts import render
# Create your views here.
from app.forms import RegisterForm
from app.models import User
def user_register(request):
if request.method == "POST":
print(request.POST)
form = RegisterForm(request.POST)
if form.is_valid():
data = form.cleaned_data
data.pop("confirm")
# 把用户写入数据库
# 密码会做签名,不能手动签名加密password
user = User.objects.create_user(**data)
if user:
return HttpResponse("注册成功")
else:
return render(request, "register.html", {'form': form})
else:
return render(request, "register.html", {'form': form})
return render(request, "register.html")
def user_login(request):
if request.method == "POST":
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = authenticate(request, username=username, password=password)
if user:
login(request, user)
return HttpResponse('登录成功')
return render(request, 'login.html', {'msg': "用户名和密码错误"})
return render(request, "login.html")
def user_logout(request: HttpRequest):
logout(request)
return HttpResponse('退出登录')
@login_required(login_url='/login')
def user_info(request):
return HttpResponse('用户中心')
- login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>{{ msg }}</h2>
<form action="{% url 'user:login' %}" method="post">
{% csrf_token %}
用户名:<input type="text" name="username"> <br>
密码:<input type="password" name="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>
- register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<form action="{% url 'user:user_register' %}" method="post">
{% csrf_token %}
用户名:<input type="text" name="username">
{% for error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %}
<br>
密码:<input type="password" name="password">
{{ form.password.errors }}
<br>
确认密码:<input type="password" name="confirm">
{{ form.confirm.errors }}
<br>
注册时间:<input type="text" name="regtime">
{{ form.regtime.errors }}
<br>
性别:<input type="radio" name="sex" value="0">女
<input type="radio" name="sex" value="1"> 男
{{ form.sex.errors }}
<br>
<input type="submit" value="注册">
</form>
</body>
</html>