Django32session登录验证操作33缓存操作34分页操作
Django笔记三十二之session登录验证操作
合集 - Django笔记(19) 1.Django笔记二十四之数据库函数之比较和转换函数2023-04-182.Django笔记二十五之数据库函数之日期函数2023-04-193.Django笔记二十六之数据库函数之数学公式函数2023-04-204.Django笔记二十八之数据库查询优化汇总2023-04-225.Django笔记二十九之中间件介绍2023-04-236.Django笔记三十之log日志记录详解2023-04-25 7.Django笔记三十二之session登录验证操作2023-04-27 8.Django笔记三十三之缓存操作2023-04-299.Django笔记三十四之分页操作2023-04-3010.Django笔记三十五之admin后台界面介绍2023-05-0411.Django笔记三十六之单元测试汇总介绍2023-05-0612.Django笔记三十七之多数据库操作(补充版)2023-05-0713.Django笔记三十八之发送邮件2023-05-0814.Django笔记三十九之settings配置介绍2023-05-0915.Django笔记四十之运行Django环境的python脚本2023-05-1216.Django笔记四十一之Django中使用es2023-11-1917.Django笔记四十二之model使用validator验证器2023-11-2218.Django笔记四十三之使用uWSGI部署Django系统2023-11-2619.Django笔记四十四之Nginx+uWSGI部署Django以及Nginx负载均衡操作2023-12-10 收起本文首发于公众号:Hunter后端
这一篇笔记将介绍 session 相关的内容,包括如何在系统中使用 session,以及利用 session 实现登录认证的功能。
这篇笔记将分为以下几个内容:
- session 的使用流程
- session 的配置和相关方法
- users 模块的准备
- session 验证的的实现
- Session 表介绍
- 登录验证的几种实现形式
1、session 的使用流程
cookie 和 session 的基本概念这里不做赘述,这里简单讲一下在 Django 中如何使用自定义的模块来实现登录、登出以及仅允许登录用户访问某些接口的操作。
Django 有一套自带的 auth 验证模块,包括用户以及用户及相应的权限的表和操作,我们这里没有用,而是单独自定义一个 user 模块以及相应的功能函数用来实现用户的注册、登录和登出功能。
session 在这里的使用流程大致如下:
1、通过 login 接口,验证成功后,将某些信息写入 session,可以是 user_id,或者是某个你自定义的特定的字段,反正是后续需要进行验证是否登录成功的数据
2、在访问特定的、需要登录才可查看的接口前,先检查前端返回的数据中是否包含我们在上一步中写入的数据来确保用户是处于登录状态,如果是,则允许继续访问,否则返回未登录的信息,提示用户需要先进行登录操作
3、通过 logout 接口,将用户在 login 接口里写入的登录信息抹除,返回登出成功信息
在 Django 中,系统自动为我们准备好了 session 的所有相关的操作,我们只需要在后续的登录操作中往里面写入我们需要验证的数据即可。
Django 这部分为我们准备好的 session 操作也是通过中间件的形式存在的,是 settings.py 的 MIDDLEWARE 的 'django.contrib.sessions.middleware.SessionMiddleware'
如果不指定其他存储方式,session 的数据默认存在于我们的后端表中,这个我们在第一次执行 migrate 的时候已经自动为我们创建了该表,名为 django_session
。
表数据的操作和查看我们在后面再详细介绍。
2、session 的配置和相关方法
前面已经介绍了 session 的操作流程,这里我们介绍一下 session 的相关配置和方法。
session 配置
以下设置都在 settings.py 中设置,事实上,这些 session 的默认配置就差不多可以使用,后续有特殊需求我们可以再来查看,这里只介绍几个我觉得方便我们使用的。
这个地方的官方文档地址在:https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#sessions
SESSION_COOKIE_AGE
session 过期时间,默认为 1209600,即 14 * 24 * 60 * 60,为 14天。
我们可以在 settings.py 中配置 session 的过期时长,也可以在程序中使用方法手动配置过期时长,方法的使用我们后面再介绍。
SESSION_COOKIE_NAME
默认值为 sessionid,在用户登录之后,请求我们系统,请求的 cookie 里会带上 session key-value 的参数,这个 key 就是我们这里的 SESSION_COOKIE_NAME,默认为 sessionid。
如果想改成其他的名称直接定义即可。
SESSION_ENGING
Django 存储 session 具体数据的地方,默认值为 django.contrib.sessions.backends.db
,表示存在于数据库,也就是我们前面说的在 django_session 这张表。
也可以存储在文件或者缓存里。
session 方法
这里接着介绍一下 session 相关的方法,这些方法的调用一般是在接口里通过 request.session 来操作。
这里我们只是做一下方法的作用和效果的介绍,具体用途我们在之后的示例中再详细说明。
dict 操作
我们可以将 request.session 视作一个 dict,往里面添加 user_id,is_login 等用于标识用户是否登录的信息的时候可以直接操作,比如:
request.session["user_id"] = 1
request.session["is_login"] = True
keys()
输出 request.session.keys() 返回的就是我们在前面往 session 里添加的数据。
同理,request.session.items() 输出的也是我们往里添加的数据的 key-value 的值。
del 操作
当我们使用登出操作时,可以直接使用:
del request.session["user_id"]
这种方式会删除 session 中我们保存的 user_id 信息,这样用户在访问我们的接口的时候,如果我们做登录验证的操作,就会找不到已经登录的信息。
之前我们说过,我们的 session 数据会保存在数据库里,这种方式仅仅是删除 session 中某个特定的 key-value,并不会删除 django_session 表中这条数据
而如果想要直接删除这一条 session 数据,则可以使用 flush() 方法
flush()
下面的操作则会直接操作数据库删除这条 session 数据:
request.session.flush()
flush() 和 前面的 del 方法都可以用作我们 logout 过程中的操作。
get_expiry_age()
获取 session 过期秒数,这个值就是前面我们在 settings.py 中设置的 SESSION_COOKIE_AGE 的值。
clear_expired()
从 django_session 中移除过期的会话,下面会介绍 Session 这个 model 的相关操作,这里提前说一下这个函数。
django_session 会有一个 expire_date 字段,clear_expired() 这个操作就会删除表里 expire_date 小于当前时间的数据。
3、users 模块的准备
前面介绍了 session 的相关配置和方法以及 session 的基本使用流程。接下来我们将介绍如何在系统中使用上 session。
在介绍 session 使用前,我们自定义一个 users application 来做一下相关准备。
新建一个 application 和 相关的配置,在前面的笔记中都有介绍,这里不再做赘述,比如 app 的创建、在 settings.py 里 INSTALLED_APPS 里的定义,和 hunter/urls.py 的 patterns 里新建一条数据,指向 users/urls.py 等操作。
其中,在 hunter/urls.py 中对 users app 的 url 前缀我们定义为 users,如下:
# hunter/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('users/', include('users.urls')),
]
我们这里在 users/models.py 下新建一个 User model,然后对其进行相关的 migration 操作,使其表添加到数据库中。
# users/models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=20, verbose_name="登录用户名", unique=True)
password = models.CharField(max_length=256, verbose_name="加密密码")
4、session 验证的的实现
接下来,我们将新建几个接口:
- 用户注册接口
- 用户登录接口
- 用户注销接口
- 用户信息接口
可以先看下这几个接口的代码总揽,接着我们详细介绍一下接口的操作。
users/urls.py
from django.urls import path
from users.views import LoginView, RegisterView, LogoutView, UserInfoView
urlpatterns = [
path("register", RegisterView.as_view()),
path("login", LoginView.as_view()),
path("logout", LogoutView.as_view()),
path("user/info", UserInfoView.as_view()),
]
users/views.py
from django.contrib.auth.hashers import make_password, check_password
from django.http import JsonResponse
from django.views import View
from users.models import User
import json
# 用户注册
class RegisterView(View):
def post(self, request):
request_json = json.loads(request.body)
username = request_json.get("username")
password = request_json.get("password")
if not username or not password:
result = {"code": -1, "msg": "username or password not valid"}
else:
if User.objects.filter(username=username).exists():
result = {"code": -1, "msg": "username exists"}
else:
User.objects.create(username=username, password=make_password(password))
result = {"code": 0, "msg": "success"}
return JsonResponse(result, safe=False)
# 用户登录
class LoginView(View):
def post(self, request):
request_json = json.loads(request.body)
username = request_json.get("username")
password = request_json.get("password")
if not username or not password:
result = {"code": -1, "msg": "login info error"}
else:
user = User.objects.filter(username=username).first()
if not user:
result = {"code": -1, "msg": "username not found"}
else:
if check_password(password, user.password):
result = {"code": 0, "msg": "success"}
request.session["username"] = username
else:
result = {"code": -1, "msg": "password error"}
return JsonResponse(result, safe=False)
# 用户登出
class LogoutView(View):
def post(self, request):
if request.session.get("username"):
del request.session["username"]
# request.session.flush()
return JsonResponse({"code": 0, "msg": "登出成功"})
# 用户信息
class UserInfoView(View):
def post(self, request):
username = request.session.get("username")
if username:
result = {"code": 0, "msg": f"登录用户为{username}"}
status = 200
else:
result = {"code": -1, "msg": "用户未登录"}
status = 401
return JsonResponse(result, status=status)
首先介绍一下,所有请求的参数都是放在 body 里以 json 格式传递,我这里都是通过 postman 来请求测试的。
其次,在请求里,session 的处理可以直接通过 request.session 的方式进行,以下见示例。
用户注册接口
在注册接口里,这里做了参数校验的简化,直接 json.loads() 处理 body 的内容,然后通过 Django 自带的加密函数 make_password 将密码以加密的形式保存。
用户登录接口
登录接口里,首先是校验账号密码是否正确,判断正确后我们将登录用户的 username 字段写入 session,然后在用户下一次请求的时候就会自动获取该 session。
或者更正确的来说,用户登录在操作 request.session 之后,在返回 response 的时候,系统会在 django_session 里新增或者更新该用户的记录,这条数据有包含 session_key,session_data 和 expire_date 这几个字段。
session_key,在 cookie 的名称是 sessionid,postman 中第一次登录之后,在之后的每一次接口请求都会将sessionid=xx 传给后端,后端就会根据这个 session_key 的值去 django_session 表里查询相应的记录
如果这个 session_key 在表里不存在记录,或者 expire_date 过期了,那么后端系统会自动给其值赋为 None,即认定此次接口请求是未登录状态。
expire_date 字段则是一个时间字段,主要用于判断数据是否过期。
session_data 则是会包含我们写入的数据,比如我们在用户登录的时候,通过 request.session["username"] = username
的方式写入了一些特殊的标识,然后将其编码成 session_data 的值存入数据库,那么用户在下次请求接口的时候我们就可以通过解码 session_data,将值取出来用于判断用户是否登录。
将 session_data 解码的方式可以单独通过获取 django_session 的记录然后获取,但是在请求中,Django 为我么做了这些解码工作,我们可以直接通过前面介绍的 request.session.items()
的方式来查看在当前登录的 session_data 里写入的 key-value 数据。
注意: 前后端并不直接将 session_data 作为值传递,而是会传递 session_key 这个参数,一些校验的数据也都是放在 session_key 对应记录的 session_data 中存在后台的数据库中。
用户信息接口
我们假定获取用户信息接口要求用户必须处于登录状态,实际上也是,因为用户不登录无法定位到用户,然后获取用户的信息。
那么我们在进行下一步的实际操作前,我们肯定需要尝试从 session 中获取用户相应的信息,如果获取到了,则判断是处于登录状态,否则是处于未登录状态,无法获取用户信息。
所以我们这里的判断是从 session 中获取 username 字段,通过判断 username 是否有值来判断用户是否处于登录状态。
用户注销接口
用户注销,也就是登出接口,我们这里用的是 del 的方式,这个主要是看我们验证用户登录的方式,比如我们是通过向 session 中取值 username 来判断用户是否登录,那么 del request.session["username"]
的操作即可实现注销的功能。
注意: 这里执行的 del 操作仅仅是删除 session_data 中的 {"username": "xxx"} 的数据,这条 session_key 对应的数据还存在。
可以看到,在这条代码的下一行还有一条是执行的 flush() 操作,这个操作是直接在数据库里删除这条 session 记录,这是一种更为彻底的登出操作。
这里还需要注意的一点是,del 操作的前提是 session 数据里必须要有 username 这个 key,否则会引起报错,所以我们这里用了一个 if 判断逻辑,我们还可以使用 try-except 操作,或者更为彻底的操作是直接使用 flush() 操作。
至此,用户登录登出以及 session 数据的基本使用操作就介绍完毕了,下面我们额外介绍一些操作。
5、Session 表介绍
django_session 表的单独获取查看操作一般在程序里不会出现,因为前后端都是通过 cookie 中 sessionid 直接获取到对应的数据,但为了以防万一,或者你对这张表有一些兴趣,这里额外介绍一下如何单独操作这张表里的数据。
django_session 表的引入方式如下:
from django.contrib.sessions.models import Session
然后通过 session_key 来获取这条数据,比如 session_key 为 nqu3s71e38279bl5cbgju6sut64tnqmx
,就可以:
session_key = "nqu3s71e38279bl5cbgju6sut64tnqmx"
session = Session.objects.get(pk=session_key)
# session = Session.objects.get(session_key=session_key)
其中,我们向 session 里写入的数据都包含在 session.session_data 里,我么可以直接通过 get_decoded() 方法来获取:
session.get_decoded()
# {'username': 'root'}
6、登录验证的几种实现形式
获取用户信息这个接口需要用户登录才可以接着获取用户信息,我们这里的操作是直接判断 session 里是否含有 username 字段。
但是如果我们系统里大部分接口都是需要用户先登录才可访问,这样在每个 views 里都要先加这个判断的操作,这样的显然是不实际的。
那么我们可以怎么操作来实现这个重复性的操作呢?
这里提供两个方式,一个是装饰器,一个是写在中间件里。
装饰器实现登录验证
其实如果直接使用 Django 自带的登录验证的功能,是可以直接使用系统自带的装饰器的,但是我们这里的表都是手动操作的,所以这个功能的装饰器我这里就自己实现了一个,相关代码如下:
def login_required_manual(func):
def wrapper(*args, **kwargs):
request = args[1]
if not request.session.get("username"):
return JsonResponse({"code": -1, "msg": "not login"}, status=401)
return func(*args, **kwargs)
return wrapper
class UserInfoView(View):
@login_required_manual
def post(self, request):
username = request.session.get("username")
return JsonResponse({"code": 0, "msg": f"登录用户{username}"})
可以看到,使用了登录验证的装饰器之后,我们的代码都简洁了很多。
我们可以尝试在调用登出接口后,再调用用户信息接口,可以看到系统就自动返回了未登录的信息了。
中间件实现登录验证
这里我们假定目前仅仅是注册和登录不需要登录即可访问,然后我们创建一个中间件如下:
# hunter/middlewares/auth_middleware.py
from django.http import JsonResponse
class AuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
path = request.path
# url 路径为 /users/register 和 /users/login 的接口不需要进行判断验证
if path not in [
"/users/register",
"/users/login",
]:
session = request.session
if not session.get("username"):
return JsonResponse({"code": -1, "msg": "not login"}, status=401)
response = self.get_response(request)
return response
然后在 hunter/settings.py 里加上这个中间件:
# hunter/settings.py
INSTALLED_APPS = [
...
'hunter.middlewares.auth_middleware.AuthMiddleware',
...
]
PYTHON 复制 全屏
这样,在每个接口请求到达 views 视图前,都会经历这个验证的中间件,这里将接口路径的判断简化成注册接口和登录接口,这两个接口不需要登录即可访问,其他接口都设置成需要登录才可访问。
相比于装饰器的做法,这里更推荐中间件的操作方式,这样首先就不用在每个 views 前加上装饰器,另外,需要登录才可访问的接口都可以在中间件部分统一列举出来,方便查看。
以上就是本篇笔记关于 session 的全部内容。
Django笔记三十三之缓存操作
合集 - Django笔记(19) 1.Django笔记二十四之数据库函数之比较和转换函数2023-04-182.Django笔记二十五之数据库函数之日期函数2023-04-193.Django笔记二十六之数据库函数之数学公式函数2023-04-204.Django笔记二十八之数据库查询优化汇总2023-04-225.Django笔记二十九之中间件介绍2023-04-236.Django笔记三十之log日志记录详解2023-04-257.Django笔记三十二之session登录验证操作2023-04-27 8.Django笔记三十三之缓存操作2023-04-29 9.Django笔记三十四之分页操作2023-04-3010.Django笔记三十五之admin后台界面介绍2023-05-0411.Django笔记三十六之单元测试汇总介绍2023-05-0612.Django笔记三十七之多数据库操作(补充版)2023-05-0713.Django笔记三十八之发送邮件2023-05-0814.Django笔记三十九之settings配置介绍2023-05-0915.Django笔记四十之运行Django环境的python脚本2023-05-1216.Django笔记四十一之Django中使用es2023-11-1917.Django笔记四十二之model使用validator验证器2023-11-2218.Django笔记四十三之使用uWSGI部署Django系统2023-11-2619.Django笔记四十四之Nginx+uWSGI部署Django以及Nginx负载均衡操作2023-12-10 收起本文首发于公众号:Hunter后端
原文链接:Django笔记三十三之缓存操作
这一节介绍一下如何在 Django 中使用 redis 做缓存操作。
在 Django 中可以有很多种方式做缓存,比如数据库,比如服务器文件,或者内存,这里介绍用的比较多的使用 redis 作为缓存。
这篇笔记主要内容如下:
- 依赖安装
- settings.py 配置
- 缓存操作用法
- 缓存版本控制
- cache 用作 session backend
- 清除 redis 里全部数据
- 批量查询与删除
其中,redis 的安装我们在 celery 系列笔记的第一篇已经介绍过了,可以直接使用 docker 来操作,这里不做赘述了。
1、依赖安装
Django 连接 redis 这里用到一个模块,django-redis,接下来我们用 pip 来安装:
pip3 install django-redis
2、settings.py 配置
然后在 settings.py 里设置 CACHES 参数即可使用:
# hunter/settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://:[email protected]:6380/2",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
在这里 redis 的端口我设置成了 6380,密码我设为了 123456。
如果没有密码,LOCATION 的参数为 redis://127.0.0.1:6380/2
当然,如果密码也可以和 url 分离配置,我们可以放到 OPTIONS 参数里:
# hunter/settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6380/2",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "123456",
}
}
}
3、缓存操作用法
在上面的配置都设置好之后,可以正式开始我们的缓存操作了。
其实关于缓存,我们可以缓存视图,模板等,但是一般来说,都是缓存更细粒度的数据,比如某个需要经常被访问到的、或者需要经过一些时间进行计算得出结果的数据,可以将其存入缓存来提高接口的访问效率。
以下是缓存操作的一些用法介绍:
#引入 cache 缓存模块
from django.core.cache import cache
#创建一条缓存信息
cache.set("key", "value", 60)
###上述语句释义:Redis 是一种以 key-value 形式存储的非关系型数据库,
###所以上述语句表示的是向 Redis 中存入一条记录,到期时间是60秒后,以秒为单位
#查看是否有某条缓存信息
cache.has_key("key")
###返回的结果是布尔型 True or False
#如果没有就创建一条缓存信息
cache.get_or_set("k", 3, 60)
### 返回的是 k 这条记录的 value 值
#查询某条缓存记录
cache.get("k")
### 如果没有这条缓存信息 则不返回
#查询某条记录,没有则返回特定值
cache.get("k", False)
#同时创建多条记录
cache.set_many({"d":1, "e": 3, "f": 6})
### 输入参数为一个 字典
#同时查询多条记录
cache.get_many(["a", "v", "e"])
### 返回的结果是一个有序字典 OrderedDict
#删除某条缓存记录
cache.delete("a")
### 输入参数为该记录的 key
#删除多条缓存记录
cache.delete(["a", "b", "c"])
#清除所有缓存记录
cache.clear()
#对缓存value为数字的记录进行操作
cache.set("num", 1)
#对缓存记录+1
cache.incr("num")
#对缓存记录 +n
cache.incr("num", 5)
#对缓存记录-1
cache.decr("num")
#对缓存记录-n
cache.decr("num", 8)
# 对 key 设置新的过期时间为 20s
cache.touch("num", 20)
# 除了 touch 还有 expire 函数可以设置过期时间
cache.expire("num", 10)
# 设置永不超时,timeout=None
cache.set("a", 1, timeout=None)
# 设置永不过期,还可以使用 persist
cache.persist("a")
# 获取 key 的剩余时间,返回的是0 表示已过期或者不存在key,否则返回的是剩余的秒数,如果返回的是 None 表示该数据永不过期
cache.ttl("a")
4、缓存版本控制
如果我们执行 cache.set("a", 1)
这条命令,再去 redis 的命令行通过 keys * 查看所有数据,可以看到一条 key 为 :1:a
的数据。
但是我们去通过 cache.get("a")
的时候,发现可以直接获取到数据,这就是后台为我们自动处理的版本控制,前面的 :1
就表示版本数是 1。
当我们执行 cache.set()
命令时,其实后面还有一个 version 参数,默认为 1,所以下面两个命令是等效的:
cache.set("a", 1)
cache.set("a", 1, version=1)
所以如果有版本的需求我们可以通过这个参数来控制。
缓存版本递增或递减
比如对于一个 version=2 的数据,我们可以这样操作:
cache.set("a", 1, version=2)
# 版本数 +1
cache.incr_version("a")
# 版本数 -1
cache.decr_version("a")
但是注意,当 version 可以减少到 0,但是不能再往下减少了,再减少的话就会报错了。
5、cache 用作 session backend
在上一节中,我们介绍了默认使用数据库表作为 session 的存储形式,我们还可以使用 cache 来用作存储。
只需要在 settings.py 中加入下面这两条命令:
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
重新运行系统,使用前一篇笔记的代码,调用登录接口,然后就可以在 redis 的交互界面,通过 keys * ,就可以看到这条 session 记录在 redis 里已经有保存了。
6、清除 redis 里全部数据
删除 redis 中 cache 里全部 key-value 数据,可以使用下面的命令:
from django_redis import get_redis_connection
get_redis_connection("default").flushall()
7、批量查询与删除
我们可以通过通配符的方式来查询或者删除指定的键。
比如我们创建下面几条数据:
cache.set("a", 1)
cache.set("a_1", 1)
cache.set("a_2", 1)
然后可以通过 a* 的方式来获取这几条数据的 key:
cache.keys("a*")
# 返回数组:["a", "a2", "a3"]
但是官方文档不推荐这种方式,尤其是在 redis 数据量大的情况下,推荐的是 iter_keys() 函数,返回一个迭代器
for key in cache.iter_keys("a*"):
print(key)
删除 key
批量删除的话使用的是 delete_pattern() 函数。
cache.delete_pattern("a*")
以上就是本篇笔记全部内容,来源于两篇官方文档:
https://django-redis-chs.readthedocs.io/zh_CN/latest/#
https://docs.djangoproject.com/zh-hans/3.2/topics/cache/
如果想获取更多后端相关文章.
Django笔记三十四之分页操作
合集 - Django笔记(19) 1.Django笔记二十四之数据库函数之比较和转换函数2023-04-182.Django笔记二十五之数据库函数之日期函数2023-04-193.Django笔记二十六之数据库函数之数学公式函数2023-04-204.Django笔记二十八之数据库查询优化汇总2023-04-225.Django笔记二十九之中间件介绍2023-04-236.Django笔记三十之log日志记录详解2023-04-257.Django笔记三十二之session登录验证操作2023-04-278.Django笔记三十三之缓存操作2023-04-29 9.Django笔记三十四之分页操作2023-04-30 10.Django笔记三十五之admin后台界面介绍2023-05-0411.Django笔记三十六之单元测试汇总介绍2023-05-0612.Django笔记三十七之多数据库操作(补充版)2023-05-0713.Django笔记三十八之发送邮件2023-05-0814.Django笔记三十九之settings配置介绍2023-05-0915.Django笔记四十之运行Django环境的python脚本2023-05-1216.Django笔记四十一之Django中使用es2023-11-1917.Django笔记四十二之model使用validator验证器2023-11-2218.Django笔记四十三之使用uWSGI部署Django系统2023-11-2619.Django笔记四十四之Nginx+uWSGI部署Django以及Nginx负载均衡操作2023-12-10 收起本文首发于公众号:Hunter后端
原文链接:Django笔记三十四之分页操作
这一篇笔记介绍一下如何在 Django 使用分页。
Django 自带一个分页的模块:
from django.core.paginator import Paginator
主要用途是列表数据的切割,比如说有 3000 条用户数据,前端需要一个列表接口用于展示这些数据,但是一次性展现这么多数据不合适,所以打算用分页的方式来操作。
比如一页20条数据,前端通过按钮控制 page_num 和 size 参数用于后端返回数据。
以下是本篇笔记目录:
- 直接分页操作
- Paginator 分页操作
- Paginator 其他函数
- Page 的其他操作
1、直接分页操作
在介绍 Django 的分页模块前,我们一般如果要分页的话会如何操作呢,这里我们定义 page_num 参数为 页数,size 参数为一页返回的数据量。
假设有这样一个长度为 20 的列表:
data_list = list(range(20))
我们想要实现每页三条数据,也就是 size = 3,我们根据 page_num 和 size 参数可以这样操作:
target_list = data_list[(page_num - 1) * size: page_num * size]
因为页数是从 1 开始的,而列表的下标是从 0 开始的,所以这里是 page_num - 1。
以这个为例,我们接下来介绍一下如何使用 Django 的模块来操作分页。
2、Paginator 分页操作
Paginator 不仅可以用于 model 的 queryset 数据,也可以用于我们上面这种列表数据 data_list,我们这里使用 data_list 作为示例。
以下是一个简单的使用 Paginator 的示例:
from django.core.paginator import Paginator
data_list = list(range(20))
page_num = 1
size = 3
paginator = Paginator(data_list, size)
target_page_data = paginator.page(page_num)
# <Page 1 of 7>
for item in target_page_data:
print(item)
count = paginator.count
在上面的示例中,Paginator() 方法接收需要分页的可迭代数据,可以是这里的列表,也可以是 Django 里的 QuerySet 类型,然后通过 .page() 函数指定 page_num 数就可以获取指定页数的数据。
另外,如果需要获取总数,可以直接 .count 获取接收的可迭代数据的总数。
分页超出总页数
比如前面我们根据 size 大小对数据进行了分页,最多只能分为 7 页,但是后面我们的 page 数传入的是 7,会怎么办呢?会报错:
raise EmptyPage(_('That page contains no results'))
django.core.paginator.EmptyPage: That page contains no results
如何规避这种情况呢,当然,前端在传入的时候可以做一定的限制,但是后端也要有这样的控制,可以在传入 page_num 参数前就对数据做一个校验,发现 page_num 超出总页数则直接 raise 报错返回前端,或者直接传入 page_num,通过 try except 来控制,发现报错的话,直接返回空列表,比如:
data_list = list(range(20))
page_num = 10
size = 3
paginator = Paginator(data_list, size)
try:
target_page_data = paginator.page(page_num)
except:
target_page_data = []
count = paginator.count
3、Paginator 其他函数
get_page(number)
前面我们对于每页数据的获取有一个 try except 的操作:
try:
target_page_data = paginator.page(page_num)
except:
target_page_data = []
假设说我们的数据只能分 7 页数据,那么 paginator.page(page_num) 的 page_num 参数就只能在 1-7 之间,可以是 int,也可以是字符串的 1-7,比如 "2",除此之外输入的其他参数,比如 0, -1,或者其他非法字符串都会引发报错。
所以我们使用了一个 try except 操作来捕获异常,当发生异常时,我们返回的是空列表。
get_page() 函数相当于是基于 page() 函数做了异常处理,当我们输入的数据是非法整数时,比如页数在 1-7 之间,我们输入的是 0,或者 -1,或者 10,返回的则是最后一页数据:
>>> paginator.get_page(99)
<Page 7 of 7>
如果我们输入的是其他的非法数据的时候,返回的则是第一页数据:
>>> paginator.get_page('a')
<Page 1 of 7>
count 属性
前面介绍了,可以通过 paginator.count 的方式来拿到待分页的数据的总数,这里介绍一下 .count 实现的方式。
因为 Paginator 是既可以对列表类型数据进行分页,也可以对 QuerySet 进行分页,但是 QuerySet 有 .count() 函数,而列表数据是没有这个操作的。
但是如果统一都用 len() 函数来对输入的数据进行取长度,这又是不现实的,因为 len() 函数的操作流程会将 QuerySet 数据都加载然后取值,在 QuerySet 无比大的时候这又是不现实的,这一点在之前的 Django 查询优化笔记中有记录。
所以这里的 count 背后的方法是先去查看这个数据有没有 count() 方法,有的话就执行,比如一个 QuerySet,没有的话就执行 len() 函数,比如列表数据。
num_pages 属性
返回总页数,比如我们前面的示例返回的数据是 7:
paginator.num_pages
# 7
page_range 属性
返回页数范围,是一个 range() 类型:
paginator.page_range
4、Page 的其他操作
这里的 Page 指的是分页后的一页数据的 Page 类型,也就是前面我们定义的 target_page_data 数据:
target_page_data = paginator.page(page_num)
是否有前一页
>>> target_page_data.has_previous()
# True
是否有后一页
>>> target_page_data.has_next()
# True
获取下一页的页数
>>> target_page_data.next_page_number()
# 2
获取前一页的页数
target_page_data.next_page_number()
注意:如果当前页在第一页或者最后一页,当我们使用获取前一页或者下一页的页数时会报错。
当前页的开始和结束索引
对于某页数据,如果想获取该页数据在全部数据中的索引,比如说,对于一个长度为 20 的列表进行分页,每页数量为 4,获取的是第 1 页的数据,那么这页数据的开始和结束索引就在 1 和 4,因为这里定义的索引是从 1 开始计算的。
>>> target_page_data = paginator.page(1)
>>>
>>> target_page_data.start_index()
# 1
>>> target_page_data.end_index()
# 4
当前页数
获取当前页数:
target_page_data.number
获取当前页数据列表
>>> target_page_data.object_list
[12, 13, 14]
如果想获取更多后端相关文章
标签:33,cache,笔记,Django,session,2023,操作,34,page From: https://www.cnblogs.com/xinxihua/p/18164724