# 基础知识

# 认识django

# 安装运行

# 使用中文镜像源安装
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Django==4.1.7

# 安装Django包后,就可以使用全局命令django-admin快速创建项目
django-admin startproject djdemo

# 直接在终端运行命令,只允许当前操作系统通过本地IP/域名访问
python manage.py runserver <127.0.0.1:8888>
# 允许其他的操作系统通过IP/域名访问
python manage.py runserver 0.0.0.0:8888
ALLOWED_HOSTS = ['192.168.1.3'] # 添加本机IP

PyCharm中的设置

# 目录结构

manage.py # 终端脚本命令,提供了一系列用于生成文件或者目录的命令,也叫脚手架
djdemo/   # 主应用开发目录,保存了项目中的所有开发人员编写的代码,目录是生成项目时指定的
	wsgi.py  # 项目运行在wsgi服务器时的入口文件,manage.py runserver 内部调用的就是wsgi
	asgi.py  # djang03.0以后新增的,wsgi的异步版本,用于让django运行在异步编程模式的web应用对象
	urls.py  # 总路由文件,用于绑定django应用程序和uri的映射关系
	settings.py # 默认开发配置文件,将来填写数据库账号,密码等相关配置
	_init.py   # 包初始化文件

# 相关命令

# 可以查看所有命令
python manage.py

# 添加模块
python manage.py startapp test
# django-admin startapp test 或者
# 模块内添加模块
python ../manage.py startapp users
# django-admin startapp users 或者

# 创建总管理员账号
python manage.py createsuperuser
# 可以将 Student 模块添加到系统管理中 Student -> admin.py
admin.site.register(models.Student, admin.ModelAdmin)

# 检测模型的更改,创建数据库迁移文件,存储在 migrations 目录
python manage.py makemigrations 
# 将迁移文件中描述的变更应用到实际数据库
python manage.py migrate
# 把当前项目中的数据迁移历史记录回滚指定版本
python manage.py migrate <app> <name>
# --fake 选项,可以将迁移记录标记为未应用,然后重新应用迁移
python manage.py migrate detail zero --fake

# 配置文件

# Django核心包里的全局默认配置

核心配置 (opens new window)

# 位置:site-packages -> django -> conf -> global_settings.py
# django项目运行时会先加载 global_settings.py 接着加载应用目录下 setting.py 的配置项
# 所以 settings.py 中填写的配置项的优先级会高于 global_settings.py的默认

# 项目的配置文件 settings.py

# 自动生成的当前项目的绝对路径
BASE_DIR = Path(__file__).resolve().parent.parent
pint(Path(__file__)) # setting文件的绝对路径
pint(Path(__file__).resolve()) # setting文件的绝对路径(规范化)

# urls.py总路由的位置
ROOT_URLCONF = "Django.urls"  

# 允许访问该项目的地址列表
ALLOWED_HOSTS
# []:仅127.0.0.1和localhost可访问
# ['*']:所有网络地址均可访问
# ['192.168.11.11', '192.168.22.22']:仅列表中的两个地址可访问

# django注册的子应用列表[用于数据库操作,缓存,日志,admin管理]
INSTALLED_APPS = [
    "django.contrib.admin",        # admin站点的子应用
    "django.contrib.auth",         # 内置的登录认证功能
    "django.contrib.contenttypes", # 内容类型管理
    "django.contrib.sessions",     # session功能
    "django.contrib.messages",     # 信号、消息功能的实现
    "django.contrib.staticfiles",  # 志文件浏览服务
	
	"user"                         #子应用的字符串导包路径
]

# 认证后端
AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
]

# 中间件,用于进行拦截请求,或者数据格式转换,权限判断
MIDDLEWARE = [
	# 安全监测相关的中间件,防止页面过期,js跨站脚本攻击xss
	"django.middleware.security.SecurityMiddleware", 
	# session加密和读取和保存session相关
    "django.contrib.sessions.middleware.SessionMiddleware",
	# 通用中间件,用于给ur进行重写,自动给ur后面加上/
    "django.middleware.common.CommonMiddleware", 
	# 防止跨站请求伪造的功能
    "django.middleware.csrf.CsrfViewMiddleware", 
	# 用户认证的中间件
    "django.contrib.auth.middleware.AuthenticationMiddleware", 
	# 错误提示信息的中间件【提示错误信息,一次性提示】
    "django.contrib.messages.middleware.MessageMiddleware", 
	# 用于防止点击劫持攻击的 iframe标签
    "django.middleware.clickjacking.XFrameOptionsMiddleware", 
]

# 模板配置
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

# web应用程序的模块
WSGI_APPLICATION = "djdemo.wsgi.application"

# 数据库配置
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}

# 密码验证类
AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]

# 缓存配置
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
		# 数据源格式连接写法  mysql://账号:密码@IP:端口/数据库名称
        "LOCATION": "redis://127.0.0.1:6379/0",
		# 以秒为单位,这个参数默认是300秒,即5分钟,为None表示永远不会过期,值设置成0立即失效
        "TIMEOUT": 300, 
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

# 语言配置
LANGUAGE_CODE # 中文'zh-Hans' 英文'en-us'

# 时区配置
TIME_ZONE  # 'UTC'为格林威治时间,中国时区为'Asia/Shanghai'

# 是否开启国际化本地化功能
USE_I18N = True

# 是否启用时区转换
USE_TZ = True # False:django会基于TIME_ZONE的时区来转换时间,True:则采用基于系统时间来转换时间

# 静态文件的访问url路径
STATIC_URL = "static/"

# 默认情况下,django中的数据表的主键ID的数据类型 bigint
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

注意

jango中的配置被强制要求一定要大写!否则django不识别。

# 路由使用

# 路由分层

把路由代码放回到对应的各个子应用目录下,单独存放

  • 在子应用home下创建子路由文件,一般路由文件名建议是 urls.py
  • 把子应用home下面的视图绑定代码转到 home/urls.py
# home/urls.py
from django.urls import path
from goods import views

urlpatterns = [
    path("home/", views.home, name="home"),
]
  • 在总路由 djdemo/urls.py 中通过include加载路由文件到django项目中
# djdemo/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("goods/", include("goods.urls"))
]

# 访问地址:http://127.0.0.1:8000/goods/home/

# 路由匹配

django一共提供了2个函数用于绑定路由与视图关系

from django.urls import path # 普通路由
from django.urls import re_path # 正则路由
urlpatterns = [
    # 正则路由
    re_path(r"^info/(?P<id>\d+)/(?P<page>0[1-9]+)$", views.info),
    re_path(r"^mobile/(?P<mobile>1[3-9]\d{9})$", views.mobile),
	re_path(r"^student/(?P<pk>\d+)/$", views.student), # 匹配 student/1 

    # 路由转换器
    path("detail/<int:id>/", views.detail, name="detail"),
    path("detail/<str:id>/", views.detail, name="detail"),
]

def detail(request, id):
    # http://127.0.0.1:8000/goods/detail/11
    # http://127.0.0.1:8000/goods/detail/aa/
    return HttpResponse(f"商品id为{id}")

def info(request, id, page):
    # http://127.0.0.1:8000/goods/info/1233/01
    return HttpResponse(f"商品id为{id}, 页码为{page}")

def mobile(request, mobile):
    # http://127.0.0.1:8000/goods/mobile/13245678912
    return HttpResponse(f"手机号为{mobile}")

# 视图介绍

django中所有的视图都建议编写在子应用的views.py文件中

# 基本使用

  • 函数视图(Function Base View,FBV)
from django.http.response import HttpResponse
from django.views.decorators.http import require_http_methods

# 注意,中括号中的清求方法名务必大写!!!否则无法正常显示
# 但可以不用加 @require_http_methods
@require_http_methods(["GET"])
def home(request):
    return HttpResponse("Hello World",content_type="text/plain")
  • 类视图(Class Base View, CBV)
# 1、urls.py
urlpatterns = [
    # as_view 获取客户端本次HTTP请求 POST,GET,PUT,DELETE,HEAD
    path('index', views.IndexView.as_view(), name='index'),
]

# 2、Views.py
from django.http import HttpResponse
from django.shortcuts import render
from django.views import View

class IndexView(View):

    # 类视图中的公共方法/公共属性
    def res(self,data):
        print(self.request.method, data)
        return HttpResponse(data)

    def get(self, request):
        return self.res(request.method)

    def post(self, request):
        return self.res(request.method)

    def put(self, request):
        return self.res(request.method)

    def delete(self, request):
        return self.res(request.method)

    def head(self, request):
        return self.res(request.headers.get('User-Agent'))

# 视图基类

# 1、视图基类
import json
from django.views import View

class APIViews(View):
    # 可以把没有参数的类方法,转换成普通属性来调用
    @property
    def data(self):
        ct = self.request.META.get('CONTENT_TYPE')
        if ct == 'application/json':
            return  json.loads(self.request.body)
        elif ct == 'multipart/form-data':
            return self.request.FILES.dict()
        else:
            return self.request.method

# 2、使用视图基类	
from django.http import HttpResponse
from cbv.apiviews import APIViews

class IndexView(APIViews):

    def get(self, request):
        print(self.data)
        return HttpResponse(request.method)

    def post(self, request):
        print(self.data)
        return HttpResponse(request.method)

    def put(self, request):
        print(self.data)
        return HttpResponse(request.method)

    def delete(self, request):
        print(self.data)
        return HttpResponse(request.method)

# 视图子类

视图子类是django为了方便开发者快速提供基于不同http请求视图而提供的

from django.views import ListView, DetailView, CreateView, UpdateView, DeleteView

ListView # 列表视图,可以通过get请求访问,用于展示列表数据,内置了分页功能
DetailView # 详情视图,可以通过get请求访问,用于展示单个数据
CreateView # 添加视图,可以通过get/post请求访问,用于添加单个数据
UpdateView # 更新视图,可以通过get/post请求访问,用于更新单个数据
DeleteView # 删除视图,可以通过get请求访问,用于删除单个数据

# 请求响应

# request 请求

request.GET 获取地址栏上的所有的査询字符串,组成一个QueryDic查询字典对象

  • GET请求
from django.http.response import HttpResponse
from django.views.decorators.http import require_http_methods

# http://127.0.0.1:8000/goods/home/?a=1&b=2&c=3&c=4
@require_http_methods(["GET"])
def home(request):
    print(request.path) # /home/
    print(request.method) # GET
    print(request.GET) # <QueryDict: {'a': ['1'], 'b': ['2'], 'c': ['3', '4']}>
	print(request.GET.get("a")) # 1
    print(request.GET.getlist("c")) # ['3', '4']
	# 当客户端没有传通参数时,可以使用get或者getlist的第二个参数default设置默认值
    print(request.GET.get("d", "123")) # 123
    return HttpResponse("Hello World",content_type="text/plain")
  • POST请求
# 请求体:name=xiaoming&age=16&hobby=run&hobby=swimming
@require_http_methods(["POST"])
def home(request):
    # request.POST只能获取POST的请求体,不能获取PUT/PATCH的请求体
    print(request.POST) # <QueryDict:{'name':['xiaohui'],'age':['17']}>
	print(request.POST.get("name")) # xiaohui
	print(request.POST.get("age")) # 17
	print(request.POST.get("hobby")) # ['run', 'swimming']
    return HttpResponse("Hello World",content_type="text/plain")
  • PUT请求
# 请求体json:name=xiaoming&age=16&hobby=run&hobby=swimming
@require_http_methods(["PUT"])
def home(request):
    # request.POST只能获取POST的请求体,不能获取PUT/PATCH的请求体
    print(request.body) #b'{\n "name": "xiaobai",\n "age": 16\n}"
	print(json.loads(request.body)) # ['name':'xiaobai', "age’: 16]
    return HttpResponse("Hello World",content_type="text/plain")
  • 获取请求头
def home(request):
    print(request.META) # 获取原生请求头
	print(request.META.get("SERVER_NAME")) # 服务端系统名称
	print(request.META.get("SERVER_PORT")) # 服务端的运行端口
	print(request.META.get("REMOTE_ADDR")) # 客户端的所在IP地址
	print(request.META.get("SERVER_SOFTWARE")) # 服务端运行web服务器的软件打印信息
	print(request.META.get("PATH_INFO")) # 客户端本次请求时的ur路径
	
	print(request.headers) # 获取http请求头
	print(request.headers.get("Content-Type"))
    return HttpResponse("Hello World",content_type="text/plain")
  • 获取上传文件
def home(request):
    # 只能接受POST请求的数据
    print(request.FILES) 
	# <MultiValueDict:{'avatar':[<InMemoryUploadedFile:demo.py (application/octet-stream)>]}
	
	print(request.FILES.get("avatar")) # demo.py 文件对象
	print(request.FILES.getlist("avatars")) # 获取多个文件对象
	for file in request.FILE.getlist("avatars"):
	    with open(f"{os.path.dirname(__file__)}/{file.name}","wb") as f:
		    f.write(file.read())
			
    return HttpResponse("Hello World",content_type="text/plain")

# response 响应

针对http的响应,提供了2种不同的响应方式

  • 响应html内容【一般用于web前后端不分离的项目】
@require_http_methods(["GET"])
def home(request):
	# content响应内容
    # content_type响应类型
    # status响应状态码
    response = HttpResponse("<h1>你好, django</h1>",
	                         content_type="text/plain; charset=utf-8",
							 status=200, 
							 headers={"token": "123456"}
						    )
	# 自定义响应头值和属性都不能是多字节
    response["company"] = "Django"
    return response
  • 响应ison内容【一般用于开发web前后端分离的项目的api接口开发】
@require_http_methods(["GET"])
def home(request):
    data = {
        "name": "django",
        "age": 18,
        "sex": "男"
    }
	
    return JsonResponse(data, json_dumps_params={"ensure_ascii": False})
	
@require_http_methods(["GET"])
def home(request):
    list = [
        {"name": "django", "age": 18, "sex": "男"},
        {"name": "flask", "age": 18, "sex": "男"},
        {"name": "tornado", "age": 18, "sex": "男"},
    ]
	# JsonResponse返回的数据如果不是字典,则必须要加上safe参数声明,并且值为False
    return JsonResponse(list, safe=False, json_dumps_params={"ensure_ascii": False})
  • 图片与下载文件
@require_http_methods(["GET"])
def home(request):
    # 当前目录
    # with open(f"{os.path.dirname(__file__)}/0,jpg", "rb") as f:
    with open("./0.jpg", "rb") as f:
        img = f.read()
    return HttpResponse(content=img, content_type="image/png")
	
@require_http_methods(["GET"])
def home(request):
    with open("./0.zip", "rb") as f:
        img = f.read()
    return HttpResponse(content=zip, content_type="application/x-gzip")
  • 站外跳转
@require_http_methods(["GET"])
def home(request):
    response = HttpResponse(status=301)
    response["Location"] = "http://www.baidu.com"
    return response
	
@require_http_methods(["GET"])
def home(request):
    return HttpResponseRedirect("http://www.baidu.com")
	
@require_http_methods(["GET"])
def home(request):
    return redirect("http://www.baidu.com")
  • 站内跳转
# 在站内跳转时,使用diango.uris.reverse函数根据路由的别名反向生成路由的URL地址
# 则必须在总路由文件和子路由文件中,对路由的前缀和子路由后缀进行别名绑定

# 1、总路由文件
urlpatterns = [
    path("admin/", admin.site.urls),
    path("goods/", include("goods.urls", namespace="goods"))
]

# 2、子路由文件,使用app_name可以避免url反向解析时出现的错误
app_name = "goods"
urlpatterns = [
    path("home/", views.home, name="home"),
    path("index/", views.index, name="index"),
]

# 3、使用
from django.urls import reverse
@require_http_methods(["GET"])
def home(request):
    url = reverse("goods:index")
    return  redirect(url)

def index(request):
    return HttpResponse("hello world")

# Cookie的使用

def set_cookie(request):
    response = HttpResponse("set cookie")
    response.set_cookie("username", "zhangsan", max_age=3600)
    return response

def get_cookie(request):
    username = request.COOKIES.get("username")
    return HttpResponse(f"username: {username}")

def del_cookie(request):
    response = HttpResponse("delete cookie")
    response.delete_cookie("username")

# Session的使用

# 保存到文件
SESSION_ENGINE = "django.contrib.sessions.backends.file"
# 保存到数据库,需要配置数据库信息
SESSION_ENGINE = "django.contrib.sessions.backends.db"
# 保存到缓存,需要配置缓存信息
SESSION_ENGINE = "django.contrib.sessions.backends.cache"

# session存储目录[如果不设置,则默认是系统的缓存目录]
# 路径拼接,如果当前目录不存在,必须手动创建,否则报错,
SESSION_FILE_PATH = BASE_DIR/"session_path"

def set_session(request):
    # 1.设置session
    request.session["username"] = "zhangsan"
    request.session["age"] = 18
    request.session.set_expiry(30)
    # 2.返回响应
    return HttpResponse("设置session成功")

def get_session(request):
    # 1.获取session
    username = request.session.get("username")
    age = request.session.get("age")
	items = request.session.items() # 所有session
	keys = request.session.keys() # 所有keys
    values = request.session.values() # 所有values
	request.session.get_expiry_age()  # 获取session的过期时间,默认14天
    # 2.返回响应
    return HttpResponse(f"username:{username},age:{age}")
    
def del_session(request):
    # 1.删除session
    if "username" in request.session:
        request.session.pop("username")
        
    request.session.clear() # 删除所有session
    # 2.返回响应
    return HttpResponse("删除session成功")

# Base64编码

data = { "uname":"root","uid":1 }
# 先转换成bytes类型数据
data_bytes = json.dumps(data).encode()
# 编码
data_base64 = base64.b64encode(data_bytes)
# 解码
data_bytes = base64.b64decode(data_base64)
decode = data_bytes.decode()

# 中间件的

MiddleWare (opens new window) 是 Django 请求/响应处理的钩子框架。它是一个轻量级的、低级的"插件"系统,用于全局改变 Django 的输入或输出。

# 内置中间件

  • 执行顺序:在http请求阶段,从上往下执行,在http响应阶段,从下往上执行
def process_request(view):
	print("执行1")
	view()
	print("执行2")
  • 解决跨站点请求伪造的方式
# 方式一:在form表单中加入{% csrf_token %}
<form action="/fm001/myCSRF/"  method="post">
	name: <input type="text">
	<input type="submit" value="提交">
	{% csrf_token %}
</form>

# 方式二:在请求头里面加上:headers:{"X-CSRFToken":Cookies.get('csrftoken')}
request.headers.get('Cookie') 
# i18n_redirected=zh;
# csrftoken=58oYo19Lvd1VTXUAqVqpCVwRkYQJtnDU;
# sessionid=sbtobek4s3d4hkk0m3ax4ymj2go6mxcm

# 方式三:django提供了装饰器可以实现那些请求需要token,那些不需要。加在views函数上
from dajngo.views.decorators.csrf import csrf_protect,csrf_exempt
@csrf_protect # 为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件
@csrf_exempt # 取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件

注意

csrf_token 是每次都是基于服务端的秘钥进行随机生成的,客户端每次提交数据操作时,中间件则会判断这个随机字符串是否是由服务端提供

# 自定义中间件

  • 函数中间件
# 1、创建一个专门存放中间件函数的模块:djdemo/func_middleware/func_middleware.py
# 2、setting.py 中 MIDDLEWARE 添加:Django.func_middleware.func_middleware

def func_middleware(get_respose):
    # 自定义中间件
    def middleware(request):
        print("-------视图执行之前-------")
        # 记录用户记录的信息,识别判断黑名单,白名单,判断用户是否登录, 判断用户是否拥有访问权限等
		
        respose = get_respose(request)
		
        print("-------视图执行之后-------")
        # 记录用户的操作历史,访问历史,日志记录, 资源的回收等
		
        return respose
    return middleware
  • 类中间件
from django.utils.deprecation import MiddlewareMixin
from django.http.response import HttpResponse

# Mixin 表示当前类是一个混人类,扩展类,混入类的作用就是保存一些类的公共方法
# 类中间件里面提供的固定钩子方法有5个,但是最常用的是 process_request 与 process_response

class class_middleware(MiddlewareMixin):
    # 方法名是固定的,该方法会在用户请求访问路由解析完成以后,调用视图之前自动执行
    def process_request(self,request):
        print("1. process_request在路由解析以后,产生request对象,视图执行之前,会执行这个方法")
        # 用途:权限,路由分发,cdn,用户身份识别,白名单,黑名单...
        # 注意,此方法不能使用return,使用则报错!!!

    def process_view(self,request,view_func,view_args,view_kwargs):
        print("2. process_view在视图接受了参数以后,没有执行内部代码之前,会执行这个方法")
        # 用途:进行缓存处理,识别参数,根据参数查询是否建立缓存
        # 可以返回response对象, 如果返回response对象以后,则当前对应的视图函数将不会被执行
        # return HttpResponse("ok")
        # 也可以不返回response,则默认返回None,django就会自动执行视图函数

    def process_response(self,request,response):
        print("3. process_response在视图执行以后,才执行的")
        # 用途:记录操作历史, 记录访问历史,修改返回给客户端的数据, 建立缓存
        # 必须返回response对象,否则报错!!
        return response

    def process_exception(self,request,exception):
        print(exception)
        print("4. process_exception会在视图执行发生异常的时候才会执行")
        # 用途:进行异常的处理或者记录错误日志

    def process_template_response(self,request,response):
        print("5. process_template_response只有在视图调用了模板以后,才会执行!!!")
        # 用途:建立页面缓存,可以修改模板的内容
        return response

# 模板引擎

# 简单介绍

Django框架中内置了web开发领域非常出名的一个 DjangoTemplate (opens new window) 模板引擎(简称:DTL),使用步骤:

  • 在项目配置文件settings.py中指定保存模板文件的模板目录
"DIRS": [BASE_DIR / "templates"],
  • 在视图中基于dianqo提供的渲染函数绑定模板文件和需要展示的数据变量
def index(request):
    data = {
        'title': 'django开发',
        'author': '张三',
    }
    # render函数实现3个功能:
    # 1. 识别查找模板目录下对应的HTML文件
    # 2. 读取HTML文件内容,替换特殊的模板语法
    # 3. 返回一个封装好的HttpResponse对象
    response = render(request, 'index.html', context=data)

    # 或者以下的形式,直接在render函数中传递数据
    title ='django二次开发'
    author = '李四'
    response = render(request, 'index.html', locals())

    print(response)  # <HttpResponse status_code=200, "text/html; charset=utf-8">
    # render读取模板文件以后,生成的HTML文档内容
    print(response.content.decode('utf-8'))
    return response
  • 在模板目录下创建对应的模板文件,并根据模板引擎内置的模板语法,填写输出视图传递过来的数据
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ title }}</title>
</head>
<body>
	{# django模板中的注释内容,提供给开发者看的 #}
	作者:{{author}}
</body>
</html>

# 静态文件

开发中在开启了debug模式时,django可以通过配置,允许用户通过对应的url地址访问django的静态文件

STATIC_URL = "static/"
STATICFILES_DIRS = [
    BASE_DIR / "static",
]
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>静态文件</title>
</head>
<body>
	{% load static %}
    <img src= "{% static '/test.png' %}" alt="My image">
</body>
</html>

项目上线以后,关闭debug模式时,django默认是不提供静态文件的访问支持,项目部署的时候,可以使用nginx这种web服务器来提供静态文件的访问支持

# 模型管理器

# 定义模型类

  • 模型类被定义在"子应用/models.py"文件中。
  • 模型类必须直接或者间接继承于django.db.models.Model类
  • 定义属性的语法:
属性名 = models.字段类型(约束选项, verbose_name="注释")

字段类型 (opens new window) 字段类型 约束选项 (opens new window) 约束选项

from django.db import models

class BaseModel(models.Model):
    # auto_now_add 设置新建数据时,把当前时间戳作为默认值保存到当前字段中
    create_time = models.DateTimeField(auto_now_add=True, null=True, verbose_name="创建时间")
    # auto_now 设置更新数据时,把当前时间戳作为默认值保存到当前字段中
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    class Meta:
        # 设置为抽象模型类,用于继承,不会创建对应的表
        abstract = True

class Student(BaseModel):
    STATUS_CHOICES = (
        (0, "正常"),
        (1, "休学"),
        (2, "退学"),
        (3, "毕业"),
    )
    # django模型中不需要自己单独声明主键,模型会自动创建主键ID
    # 将来直接可以通过模型对象.id 或者 模型对象.pk就可以调用主键了
    # 如果设置某个字段的约束属性为主键列(primary_key)后,django不会再创建自动增长的主键列
    id = models.BigAutoField(primary_key=True, verbose_name="主键")
    name = models.CharField(max_length=15, db_index=True, verbose_name="姓名")
    age = models.SmallIntegerField(default=0, verbose_name="年龄")
    sex = models.BooleanField(default=True, verbose_name="性别")
    # dbcolumn属性如果不写则默认使用属性名作为表的字段名进行对应
    # 字段名如果在python是一个关键字/保留字。则选项中需要通过db_column()来进行关联绑定
    classmate = models.CharField(max_length=50, db_column="class", default="", db_index=True, verbose_name="班级编号")
    mobile = models.CharField(max_length=10, unique=True, verbose_name="手机号")
    description = models.TextField(blank=True, null=True, verbose_name="个性签名")
    status = models.IntegerField(choices=STATUS_CHOICES, default=1, null=True, verbose_name="状态")

    class Meta:
        # 指明数据库表名
        # 不指定的话,Django会自动生成表名,格式为:应用目录名_模型类名(全小写)
        db_table = "student"
        verbose_name = "学生信息"
        # 让模型的复数显示名称和单数显示名称相同
        verbose_name_plural = verbose_name

    def __str__(self):
        # 当使用print打印django模型对象时的输出内容
        return self.name
		
	@classmethod
	def get_user(cls):
		"""获取成年人列表"""
		return cls.objects.filter(age__gte=18).all()
		
class Female(User):
    class Meta:
		# 设置当前模型为代理模型,共享父模型的数据和操作方法
        proxy = True@classmethod
    def all(cls):
        return cls.objects2.filter(sex=False).all()

# 模型管理器

模型类.objects.操作方法()  
# objects 就是模型管理器(Manager)的实例对象,它提供了关于数据库的所有操作

"""
源码解释
"""
# 定义了一个名为 Manager 的类,该类继承自 BaseManager.from_queryset(QuerySet) 的返回结果
# from_queryset 方法接收一个 QuerySet 类作为参数,会动态创建一个新的管理器类。
# 新创建的管理器类会继承 BaseManager,并且将传入的 QuerySet 类赋值给 _queryset_class 属性
# 该方法还会把 QuerySet 类的公共方法复制到新的管理器类上,这样管理器就能直接调用 QuerySet 的方法
class Manager(BaseManager.from_queryset(QuerySet)):
    pass

def from_queryset(cls, queryset_class, class_name=None):
    # 检查是否提供了新管理器类的名称,如果没有,则自动生成一个
    if class_name is None:
        class_name = "%sFrom%s" % (cls.__name__, queryset_class.__name__)
    # 使用 type 函数动态创建一个新的类
    return type(
        class_name,  # 新类的名称
        (cls,),  # 新类的父类,这里是调用该方法的类,通常是 BaseManager 或其子类
        {
			# 将传入的 QuerySet 类赋值给新类的 _queryset_class 属性
            "_queryset_class": queryset_class,  
			# 将 QuerySet 类的公共方法添加到新类中
            **cls._get_queryset_methods(queryset_class),  
        },
    )

# 自定义模型管理器

一旦为模型类指明自定义模型管理器以后,Django不再提供默认的模型管理器对象objects了

"""
1. 自定义模型管理器
模型管理器,必须直接或间接继承于 Manager
注意:filter的返回值并非QuerySet,所以跟在filter后面的调用的方法无法重写。
"""
# 打开orm/models.py文件,定义类UserManager
from django.db.models import Manager
​
class UserManager(Manager):
    def create(self, **kwargs):
        if "password" in kwargs:
            from hashlib import sha256
            hash = sha256()
            hash.update(kwargs["password"].encode())
            kwargs["password"] = str(hash.hexdigest())
        return super().create(**kwargs)def all(self):
        return super().filter(deleted_time__isnull=True).all()
		
	def soft_delete(self, **kwargs):
			from datetime import datetime
			# 逻辑删除,并非真实删除数据,而是给数据设置了一个删除时间
			return self.filter(**kwargs).update(deleted_time=datetime.now())

"""
2. 在模型类User中注册模型管理器
"""
class User(models.Model):
    nickname = models.CharField(max_length=50, verbose_name="昵称")
    username = models.CharField(max_length=50, verbose_name="用户名")
    password = models.CharField(max_length=255, verbose_name="密码")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")
    updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
    deleted_time = models.DateTimeField(null=True, blank=True, verbose_name="删除时间")# 覆盖设置了django默认的模型管理器,是否叫objects都不影响django取消内部的objects
    objects = UserManager()class Meta:
        db_table = "orm_user"
        verbose_name = "用户信息"
        verbose_name_plural = verbose_name
​
    def __str__(self):
        return str({"name": self.nickname, "deleted_time": self.deleted_time})"""
3. 视图中调用模型管理器
"""
class StudentView(View):
    def get(self, request):
        user = User.objects.create(username="root", password="123456")
        # 同上面的方法
        from hashlib import sha256
        hash = sha256()
        hash.update("123456".encode())
        user = User.objects.create(username="root",password=str(hash.hexdigest()))
​
        user_list = User.objects.all()
        print(user_list)
		
		# 新增objects没有的方法
		User.objects.soft_delete(username="root")return HttpResponse("ok")

# 模型序列化

Django 模型实例对象无法直接被序列化为 JSON 格式,可以把模型实例转换为字典

  • 使用 model_to_dict 函数
data_list = []
for i in page.object_list:
	# exclude这个是转字典的时候去掉,哪个字段,就是不给哪个字段转成字典
	mode_to = model_to_dict(i, exclude='img')
	data_list.append(mode_to)
	
# object_list = [model_to_dict(student) for student in page.object_list]

data = {'code': 0, "msg": '操作成功', "data": data_list }
return JsonResponse(data, json_dumps_params={'ensure_ascii': False})
  • 使用自定义序列化函数
data_list = []
def student_to_dict(student):
	return {
		'id': student.id,
		'name': student.name,
		'age': student.age,
		# 按需添加更多字段
	}

object_list = [student_to_dict(student) for student in page.object_list]

data = {'code': 0, "msg": '操作成功', "data": data_list }
return JsonResponse(data, json_dumps_params={'ensure_ascii': False})

# ORM使用

# 使用MySQL数据库

  • 安装驱动程序
pip install PyMySQL
# 如果上面命令安装失败,则可以使用以下命令安装:
# conda install -c conda-forge pymysql
  • 在Django的主应用目录的__init__.py文件中添加如下语句
from pymysql import install_as_MySQLdb
​
 # 作用是让Django的ORM能以mysqldb的方式来调用PyMySQL
install_as_MySQLdb() 
  • 修改DATABASES配置信息
DATABASES = {
    'default': {
        # 'ENGINE': 'django.db.backends.sqlite3',
        # 'NAME': BASE_DIR / 'db.sqlite3',
        'ENGINE': 'django.db.backends.mysql',  # ORM的底层对接pymysql的核心引擎类
        'NAME': 'school',          # 数据库名
        'PORT': 3306,              # 端口
        'HOST': '127.0.0.1',       # 数据库IP
        'USER': 'root',            # 账号
        'PASSWORD': '123',         # 密码
		
		# pool表示数据库连接池配置,主要为了节省连接数据库的开销,临时存储数据库连接对象
        'POOL_OPTIONS': {  
            'POOL_SIZE': 10,     # 默认情况下,打开的数据库连接对象的数量
            'MAX_OVERFLOW': 30,  # 负载情况下,允许溢出的连接数量
        },
		
		"OPTIONS": {
            # STRICT_TRANS_TABLES 模式是一种严格模式,其主要作用如下:
            # 1. 插入数据时的严格检查:当向表中插入数据时,若数据不符合列的定义
			#    MySQL 会抛出错误而不是进行隐式转换或截断
            # 2. 事务安全:对于支持事务的存储引擎(如 InnoDB),若插入或更新数据时发生错误
			#    事务会回滚,保证数据的一致性
            "init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
        }
    }
}

# 查询单条数据

  • get 查询单一结果
# 查询不到, 则返回模型类.DoesNotExist异常
# 查询多个, 则返回模型类.MultipleObjectsReturned异常

try:
	student = Student.objects.get(name="小黄人")
	print(student.id, student.pk)  # 获取主键值
	print(student.name, student.description)  # 获取其他属性
	print(student.created_time.strftime("%Y-%m-%d %H:%M:%S"))  # 获取日期格式的内容
	print(student.get_status_display())  # 获取枚举类型的内容
	print(student.sex)  # 获取布尔类型的内容
except Student.DoesNotExist:
	print("没有查询结果!")
except Student.MultipleObjectsReturned:
	print("当前数据不是唯一的结果!")
  • first 查询单一结果
# 查询不到,则返回None,查询多个,返回查询结果列表的第一个
# last方法,可以获取结果列表中最后一个成员

student = Student.objects.first()

# 获取全部数据

from django.http import HttpResponse, JsonResponse
from django.views import View
from student import models

class StudentView(View):
    def get(self, request):
        # 获取模型对应的数据表的模型类对象
        objects_all = models.Student.objects.all()
		
        # <class 'django.db.models.query.QuerySet'>
        print(type(objects_all))
		
        # QuerySet是django的ORM中提供的查询集对象【伪列表】,支持使用索引来查询
        print(objects_all[0])
		
	return HttpResponse("ok")
  • QuerySet 转换对象为字典方法一
# QuerySet里面的成员是模型对象,不能直接被json转换成数据,需要先转换对象为字典

student_list = []
for students in objects_all:
	student_dict = {
		"id": student.id,
		"name": student.name,
		"age": student.age,
		"sex": student.sex,
		"classmate": student.classmate,
		"mobile": student.mobile,
		"description": student.description,
		"status": student.get_status_display(),
		"created_time": student.created_time.strftime("%Y-%m-%d %H:%M:%S"),
		"updated_time": student.updated_time.strftime("%Y-%m-%d %H:%M:%S")
	}
	student_list.append(student_dict)

return JsonResponse(list(student_list), safe=False)
  • QuerySet 转换对象为字典方法二
# all()返回的是模型对象列表,如果要获取字典列表,则可以使用values()
objects_all = models.Student.objects.values("id", "name")
print(objects_all)
# values() 调用时没有传递参数,则默认获取所有字段内容
objects_all = models.Student.objects.values()
print(objects_all)

# 把结果列表中的所有模型对象转化成字典结构
student_list = Student.objects.all().values()
# 把结果列表中的所有模型对象转换成元组结构
student_list = Student.objects.all().values_list()

return JsonResponse(list(objects_all),safe=False,json_dumps_params={"ensure_ascii": False})

# 新增数据

  • 通过创建模型类对象,执行对象的save()方法保存到数据库中
student = Student(
	name="张三",
	age=18,
	sex="True",
	classmate=301,
	mobile="13800138011",
	description="这是一个学生"
)
# 从request中接收
# data = json.loads(request.body)
# student = Student(**data)  # 转为字典
student.save()

print(student.id) # 打印出主键id
print(student.pk) # 打印出主键id

# 将模型对象转换为字典
student_dict = model_to_dict(student)
return JsonResponse(student_dict, safe=False, json_dumps_params={"ensure_ascii": False})
  • 通过模型类.objects.create()保存
student = Student.objects.create(
	name="张三",
	age=18,
	sex="True",
	classmate=301,
	mobile="13800138011",
	description="这是一个学生"
)
# 从request中接收
# data = json.loads(request.body)
# student = Student.objects.create(**data) # 转为字典
print(student.pk) # 打印出主键id

# 将模型对象转换为字典
student_dict = model_to_dict(student)
return JsonResponse(student_dict, safe=False, json_dumps_params={"ensure_ascii": False})
  • 通过模型类.objects.bulk_create()批量添加数据
student1 = Student.objects.create(
	name="张三2",
	age=18,
	sex="True",
	classmate=301,
	mobile="13800138111",
	description="这是一个学生"
)
student2 = Student.objects.create(
	name="李四2",
	age=18,
	sex="True",
	classmate=301,
	mobile="13800138112",
	description="这是一个学生"
)
stu_list = [student1, student2]
ret = Student.objects.bulk_create(stu_list, ignore_conflicts=True)
return HttpResponse("ok")

# 更新数据

  • 修改模型类对象的属性,然后执行save()方法同步到数据库中
student = Student.objects.filter(name="小白").first()
if student:
		student.name = "小黑"
		student.age = 18
		student.save()  # 把当前模型的中字段值同步到数据库
  • 基于update来完成更新满足条件的所有数据,结果是受影响的行数
# update操作的执行效率比save要高!

Student.objects.filter(name="刘德华").update(name="刘福荣")

# 删除数据

  • 模型类对象.delete()
student = Student.objects.filter(name="小白").first()
if student:
	# 调用模型对象的delete方法进行删除
	student.delete()
  • 模型类对象.delete(),返回值是删除的数量
Student.objects.filter(name="小黄人").delete()

# 文件上传

# 当 Django 在处理文件上传的时候,文件数据被保存在 request.FILES
# FILES 中的每个键为 <input type="file" name="字段名" /> 中的 name
# 请求的方法为 POST 且提交的 <form> 带有 enctype="multipart/form-data" 的情况下才会包含文件
# 如果属性的字段类型为图片类型ImageField,需要在python安装PIL包:pip install Pillow
# 设置保存上传文件的公共路径:MEDIA_ROOT = BASE_DIR / "uploads"
  • 使用模型处理上传文件:将属性定义成models.ImageField或者models.FileField类型
class Soft(models.Model):
    name = models.CharField(max_length=150, verbose_name="软件名称")
    version = models.CharField(max_length=50, verbose_name="版本号")
    # upload_to 用于设置保存上传文件的存储子路径,主目录为settings.py中配置项 MEDIA_ROOT
    # ImageField是FileField的子类,FileField内部实现了基于日期时间格式生成目录的功能
    # 当同一目录下文件同名了,FileField会自动把后面重复的文件名追加补充随机字符串防止重名
    picture = models.ImageField(upload_to="pic/%Y/%m/%d/", verbose_name="软件图片")
    download = models.FileField(upload_to="soft/%Y/%m/%d/", verbose_name="软件下载")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    class Meta:
        db_table = "soft"
        verbose_name = "软件信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name
  • 接收文件和删除文件
class IndexView(View):
    def post(self, request):
        name = request.POST.get("name")
        version = request.POST.get("version")
        website = request.POST.get("website")
        picture = request.FILES.get("picture")
        download = request.FILES.get("download")
        software = Soft.objects.create(
            name=name,
            version=version,
            picture=picture,
            download=download
        )
        return JsonResponse({
            "id": software.id,
            "name": software.name,
            "version": software.version,
            # 模型字段是图片或者文件的,当前属性是文件对象,需要调用url属性才能获取url访问路径
            "picture": f"//{request.META.get('HTTP_HOST')}{software.picture.url}",
            "download": f"//{request.headers.get('host')}{software.download.url}",
        })

    def delete(self, request):
        """删除操作"""
        id = request.GET.get("id")
        software = Soft.objects.filter(pk=id).first()
        # 通过path可以获取当前上传的绝对路径,通过绝对路径可以删除文件
        print(software.picture.path)
        # 删除操作代码:
        os.remove(software.picture.path)
        os.remove(software.download.path)
        return JsonResponse({})
  • 上传文件提供外界访问
# settings.py 配置访问上传文件的url地址前缀,可以不用配置
MEDIA_URL = "/uploads/"

# Django/urls.py 中添加
from django.views.static import serve # 静态文件代理访问模块

urlpatterns = [
    re_path(r'uploads/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT})
]

# 数据分页

Paginator(要进行分页的列表数据,每一页数据的条数):返回分页器对象

class StudentView(View):
    def get(self, request):
        page = request.GET.get('page', 1)  # 获取第几页
		limit = request.GET.get('limit', 2)  # 每页有多少条数据
		keyword = request.GET.get('keyword', "")
		
		student_list = Student.objects.filter(name__icontains=keyword)
		
        # Paginator(数据对象列表, limit)
        paginator = Paginator(list(student_list), limit)
        # 数据列表的长度
        # print(paginator.count)
        # 页码总数
        # print(paginator.num_pages)
        # 页面列表
        # print(paginator.page_range)
​
        page = paginator.page(page)
        # 当前页要展示给外界的数据对象列表
        # print(page.object_list)
        # 当前页码
        # print(page.number)
        # 逆向查找当前Page分页对象的父级分页器对象
        # print(page.paginator)

        data_list = []
        for i in page.object_list:
            # exclude这个是转字典的时候去掉,哪个字段,就是不给哪个字段转成字典
            mode_to = model_to_dict(i, exclude='img', )
            data_list.append(mode_to)
        
		count = paginator.count
		data = {'code': 0, "msg": '操作成功', "data": data_list, 'count': count}
		return JsonResponse(data)
		
# 使用ListView
class Student2View(ListView):
    # # 设置当前视图提供哪些方法,默认支持get
    # http_method_names = ["get"]
    # 设置当前视图类中使用模板文件名
    template_name = "index2.html"
    # 设置当前视图类中使用的模型
    model = models.Student
    # 设置分页的数据量
    paginate_by = 5
    # # 设置分页的页码,默认是"page"
    # page_kwarg = "page"
    # 在HTML模板中,代表page对象的object_list变量名
    # context_object_name = "student_list"

# 过滤条件

  • exact:表示判断值是否相等
# student_list = Student.objects.filter(name__exact="吴杰")
student_list = Student.objects.filter(name="吴杰")
  • contains:是否包含,模糊查询
student_list1 = Student.objects.filter(name__contains='华')
student_list2 = Student.objects.filter(name__startswith="江")
student_list3 = Student.objects.filter(name__endswith="江")
  • isnull:字段值是否为null
student_list = Student.objects.filter(description__isnull=True)
  • in:是否包含在范围内
student_list = Student.objects.filter(classmate__in=[301, 302, 303])
  • range:取值范围
student_list = Student.objects.filter(id__range=(51, 67)).values("id", "name")
  • 比较查询
student_list = Student.objects.filter(age__gt=22).values("name", "age") # 大于
student_list = Student.objects.filter(age__gte=22).values("name", "age") # 大于等于
student_list = Student.objects.filter(age__lt=22).values("name", "age") # 小于
student_list = Student.objects.filter(age__lte=22).values("name", "age") # 小于等于
student_list = Student.objects.exclude(age=22).values("name", "age") # 不等于
  • 日期查询
student_list = Student.objects.filter(created_time__year=2017)
student_list = Student.objects.filter(created_time__month=7)
student_list = Student.objects.filter(created_time__day=20)
student_list = Student.objects.filter(created_time__year=2022, created_time__month=7)
student_list = Student.objects.filter(created_time="2021-08-18 16:19:38")

# 把字符窜格式的时间转换成datetime对象
from django.utils.timezone import datetime
timestamp = datetime.strptime("2021-08-18 16:19:38", "%Y-%m-%d %H:%M:%S")
student_list = Student.objects.filter(created_time=timestamp)

# 判断两个时间范围
time1 = "2020-11-20 9:00:00"
time2 = "2020-11-20 11:00:00"
student_list = Student.objects.filter(created_time__gte=time1,created_time__lte=time2)
  • 多个过滤条件
students = Student.objects.filter(age__gt=20,id__lt=30)
# 或者
students = Student.filter(age__gt=20).filter(id__lt=30)

# F对象和Q对象

  • F对象:用于在SQL语句中针对字段之间的值进行比较的查询
from django.db.models import F

students = Student.objects.filter(created_time=F("updated_time"))
  • Q对象:用于实现逻辑或or的查询,需要使用Q()对象结合|运算符
from django.db.models import Q

# 可以使用 & 表示逻辑与(and) | 表示逻辑或(or) ~ 表示逻辑非(not)
Student.objects.filter(Q(classmate=301, xingbie=1) | Q(classmate=302, xingbie=1))
Student.objects.filter(Q(classmate=301, xingbie=1) & Q(classmate=302, xingbie=1))
Student.objects.filter(~Q(age=20))

# 结果排序

order_by("id")   # 表示按id字段的值进行升序排序,id数值从小到大
order_by("-id")  # 表示按id字段的值进行降序排序,id数值从大到小

# 先按班级进行第一排序降序处理,当班级数值一样时,再按id进行第二排序升序处理
student = Student.objects.order_by("-classmate","id")

# QuerySet的特性

  • 惰性执行
# QuerySet查询集在创建时是不会访问数据库执行SQL语句,直到模型对象被调用或者调用模型对象的属性时
# 才会真正的访问数据库执行SQL语句,调用模型的情况包括循环迭代、序列化、与if合用,print的时候
# 当执行如下语句时,并未进行数据库查询,只是创建了一个查询集对象student_list
student_list = Student.objects.all()

# 执行遍历迭代、或打印操作之后操作后,才真正的进行了数据库的查询
for student in student_list:
    pass
  • 缓存结果
# 使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来
# 再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数
student_list=Student.objects.all()
# 因为上面保存到查询到变量中,所以此处执行了SQL语句
[student.id for student in student_list]
# 此处调用了之前的缓存数据
[student.id for student in student_list]
  • 限制结果数量
# 对查询集QuerySet进行切片后返回一个新的查询集,但还是不会立即执行数据库查询
qs = Student.objects.all()
# print(qs[0])  # 第1条数据 
# print(qs[2])  # 第3条数据 
# print(qs[:2])   # 前2条数据 
# print(qs[1:4])  # 第1,2,3 数据 
# print( qs[-1] )   # 报错!!!不能使用负数

# 聚合分组

  • 聚合函数
# 可以使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg、Count、Max、Min、Sum
# aggregate的返回值是一个字典类型
from django.db.models import Avg,Max,Min,Sum,Count

ret = Student.objects.filter(classmate=301).aggregate(Avg("age")) # {'age__avg': 20.4444}
ret = Student.objects.filter(classmate=301).aggregate(Max("age")) # {'age__max': 100}
ret = Student.objects.filter(classmate=301).aggregate(c=Min("id")) # {'c': 2}
  • 分组查询
QuerySet对象.annotate()
# annotate() 进行分组统计,按前面values的字段进行 group by
# annotate() 返回值依然是 queryset对象,增加了分组统计后的键值对
# 针对单个字段进行分组,按班级统计人数
ret = Student.objects.values("classmate").annotate(total=Count("id"))
# <QuerySet [{'classmate': '301', 'total': 9}, {'classmate': '302', 'total': 3}>

# 针对多个字段进行分组,按班级和性别统计人数
ret = Student.objects.values("classmate","xingbie").annotate(total=Count("id"))
# <QuerySet [{'classmate': '307', 'sex': True, 'total': 3}, {'classmate': '301', 'sex': True, 'total': 7}>

# 查询出每一个班级中年龄最小的学生信息
ret = Student.objects.values("classmate").annotate(min=Min("age"))
# <QuerySet [{'classmate': '301', 'min': 18}, {'classmate': '302', 'min': 21}>

# 查询出女生数量在2个以上的班级
ret = Student.objects.filter(sex=1).values("classmate").annotate(total=Count("id")).filter(total__gte=2)
# <QuerySet [{'classmate': '301', 'total': 7}, {'classmate': '302', 'total': 2}>

# 原生查询

可以调用ORM提供的raw方法来执行SQL语句,返回QuerySet,这个结果在操作字段时会有额外性能损耗

sql = "SELECT id,name,sex,age,class FROM `db_student`"
ret = Student.objects.raw(sql)

# 针对原生SQL语句中已经查询出来的字段,只会查询一遍
# SQL语句没有查询出来的字段,而在模型中调用,则会由ORM再次调用数据库查询,把数据临时查询出来
for student in ret:
    print(student)
    print(student.description)

# 多库共存

在django中,settings.py配置的DATABASES配置项允许注册多个数据库

student_objs = models.Student.objects.using("default").values("name", "age")

# 关联模型

# 外键约束选项

在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:

CASCADE  # 级联/株连,删除主表数据时连通一起删除外键表中数据
PROTECT  # 删除保护,通过抛出ProtectedError异常,就是必须先删除外键数据以后才能删除主键数据
SET_NULL  # 设置为NULL,仅在该字段null=True允许为null时可用
SET_DEFAULT  # 设置为默认值,仅在该字段设置了默认值时可用
DO_NOTHING  # 不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常
SET()  # 设置为特定值或者调用特定方法,例如:
rom django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
​
def get_sentinel_user():
    return get_user_model().objects.get_or_create(username='deleted')[0]class UserModel(models.Model):
    user = models.ForeignKey(
	    # settings.AUTH_USER_MODEL:指定外键关联的目标模型为 Django 的用户模型
		# settings.AUTH_USER_MODEL 是 Django 里引用用户模型的推荐方式
		# 这样能兼容自定义的用户模型,而非硬编码为默认的 auth.User 模型
        settings.AUTH_USER_MODEL,
        on_delete=models.SET(get_sentinel_user),
    )

# 一对一关联:OneToOneField

  • 创建模型的关联关系
class Detail(models.Model):
    address = models.CharField(max_length=100, verbose_name="地址")
    school = models.CharField(max_length=100, verbose_name="学校")
    # 设置外键1对1关联,models.OneToOneField("主模型类名", related_name="从模型类名小写")
    # 设置外键后,在数据库中会自动创建外键列,列名为:从模型类名小写_id 例如:student_id
    # related_name属性用于反向查询,即通过主模型类对象,反向查询从模型类对象
    # 例如:Student.detail_list.address 表示通过学生对象,反向查询学生的详细信息
    # 例如:Detail.student.name 表示通过学生的详细信息,反向查询学生的姓名
    student = models.OneToOneField("student.student", 
	                                related_name="detail_list", 
									on_delete=models.CASCADE, 
									verbose_name="学生")

    class Meta:
        db_table = "detail"
        verbose_name = "学生详细信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.address
  • 一对一关联模型的增删改查
class StudentView(View):
    def get1(self, request):
        # 添加数据
		# 方式一:先加主模型,根据主模型添加外键模型
        stu = Student.objects.create(
            name="张三2",
            age=18,
            sex=True,
            mobile="13800138025",
            classmate="1班"
        )
        detail = Detail.objects.create(
            # student=stu, # 关联主模型数据
            student_id=stu.id,  # 关联主模型数据,等价于上面的方式
            address="北京",
            school="北京大学"
        )
		
		# 方式二:添加主模型,再添加外键模型的另一种写法,没有事物问题,推荐使用
		Detail.objects.create(
            address="北京",
            school="清华大学"
            student=Student.objects.create(
                name="李四",
                age=18,
                sex=True,
                mobile="13800138026",
                classmate="1班"
            )
        )
        return HttpResponse("get请求")

    def get2(self, request):
        # 查询数据
        stu = Student.objects.filter(id=125).first()
        # 方式1
        if stu:
		    # detail_list 就是Detail中related_name定义,提供给Student反向查询使用的
            address = stu.detail_list.address
            print(address)
			
        # 方式2
        detail = Detail.objects.filter(student=stu).first()
		detail = Detail.objects.filter(student__name='张三').first()
        print(detail.address)
		
        # 方式3
        detail = Detail.objects.filter(address="北京").first()
        if detail:
            stu = detail.student
            print(stu.name)
		# 或者
        stu = Student.objects.filter(detail_list__address="北京").first()
        if stu:
            print(stu.name)

        return HttpResponse("get请求")

    def get3(self, request):
        # 更新数据
        stu = Student.objects.filter(id=125).first()
        # 方式1
        if stu:
            stu.detail.address = "上海"
            stu.detail.save()
			
        # 方式2
        ret1 = Detail.objects.filter(student=stu).update(address="上海")
		ret1 = Detail.objects.filter(student__name="张三").update(address="上海")
        print(ret1)
		
        # 方式3
        detail = Detail.objects.filter(address="北京").first()
        if detail:
            detail.student.name = "李四"
            detail.student.save()
			
        # 方式4
        ret2 = Student.objects.filter(detail_list__address="上海").update(age=50)
        print(ret2)
		
		# 方式5
        Detail.objects.filter(address="上海").update(
            student=Student.objects.filter(name="李四").first()
        )
		
        # 这样是不行滴!!!
        ret3 = Student.objects.filter(id=125).update(detail__address="上海")
        print(ret3)
        return HttpResponse("get请求")

    def get(self, request):
        # 删除数据
        # 当on_delete=models.CASCADE时,删除主模型会级联删除关联的从模型
        Student.objects.filter(id=125).delete()
		
        # 当on_delete=models.CASCADE时,删除联的从模型不会级联删除主模型
        Detail.objects.filter(student_id=126).delete()
        return HttpResponse("get请求")

# 一对多关联:ForeignKey

  • 创建模型的关联关系
class Detail(models.Model):
    address = models.CharField(max_length=100, verbose_name="地址")
    school = models.CharField(max_length=100, verbose_name="学校")
    job = models.CharField(max_length=100, verbose_name="工作")
    # 关联主模型,在数据库中会自动创建外键列,列名为:从模型类名小写_id 例如:student_id
    student = models.ForeignKey("student.student", 
	                             related_name="detail_list", 
								 on_delete=models.DO_NOTHING,
								 verbose_name="学生")

    class Meta:
        db_table = "detail"
        verbose_name = "学生详细信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.address
  • 一对多关联模型的增删改查
class StudentView(View):
    def get1(self, request):
        # 添加数据
        # 方式一:先加主模型,根据主模型添加外键模型
        stu = Student.objects.create(
            name="张三",
            age=18,
            sex=True,
            mobile="13800138025",
            classmate="1班"
        )
        detail_list = [
            # 关联模型的2种形式
            Detail(address="北京", school="清华大学", job="教师", student=stu),
            Detail(address="上海", school="复旦大学", job="医生", student_id=stu.id)
        ]
        # 添加外键模型数据
        Detail.objects.bulk_create(detail_list)

        # 方式二:添加主模型,再添加外键模型的另一种写法,没有事物问题,推荐使用
        Detail.objects.create(
            address="北京",
            school="清华大学",
            job="教师",
            student=Student.objects.create(
                name="李四",
                age=18,
                sex=True,
                mobile="13800138026",
                classmate="1班"
            )
        )
        return HttpResponse("get请求")

    def get2(self, request):
        # 查询数据
        stu = Student.objects.filter(id=130).first()
        # 方式1
        if stu:
            # detail_list 就是Detail中related_name定义,提供给Student反向查询使用的
            address = stu.detail_list.all()
            print(address)

        # 方式2
        list = Detail.objects.filter(student=stu).all()
        list = Detail.objects.filter(student__name='张三').all()
        print(list)

        # 方式3
        detail = Detail.objects.filter(address="北京").first()
        if detail:
            stu = detail.student
            print(stu.name)
        # 或者
        stu = Student.objects.filter(detail_list__address="北京").first()
        if stu:
            print(stu.name)

        return HttpResponse("get请求")

    def get3(self, request):
        # 更新数据
        stu = Student.objects.filter(id=130).first()
        # 方式1
        for detail in stu.detail_list.all():
            detail.address = "上海"
            detail.save()

        # 方式2
        ret1 = Detail.objects.filter(student=stu).update(address="上海")
        ret1 = Detail.objects.filter(student__name="张三").update(address="上海")
        print(ret1)

        # 方式3
        detail = Detail.objects.filter(address="北京").first()
        if detail:
            detail.student.name = "李四"
            detail.student.save()

        # 方式4
        ret2 = Student.objects.filter(detail_list__address="上海").update(age=50)
        print(ret2)

        # 方式5
        Detail.objects.filter(address="上海").update(
            student=Student.objects.filter(name="李四").first()
        )
        return HttpResponse("get请求")

    def get(self, request):
        # 删除数据,先删除子模型数据,再删除主模型数据
        Detail.objects.filter(student_id=130).delete()

        Student.objects.filter(id=130).delete()
        return HttpResponse("get请求")

# 多对多关联:ManyToManyField

  • 创建模型的关联关系
class Detail(models.Model):
    address = models.CharField(max_length=100, verbose_name="地址")
    school = models.CharField(max_length=100, verbose_name="学校")
    job = models.CharField(max_length=100, verbose_name="工作")
    # 关联主模型,在数据库中会自动创建关系表:detail_student
    # 注意:Detail模型中设置了Student外键,就不要在Student模型中设置detail的外键,否则会冲突
    # 多对多关系中不需要设置on_delete属性,默认是models.CASCADE级联删除
    student = models.ManyToManyField("student.student", 
	                                  related_name="to_detail", 
									  verbose_name="学生")

    class Meta:
        db_table = "detail"
        verbose_name = "学生详细信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.address
  • 多对多关联模型的增删改查
class StudentView(View):
    def get1(self, request):
        # 添加数据
        # 方式一:先加模型,再通过外键使用add绑定关系
        stu = Student.objects.create(
            name="张三3",
            age=18,
            sex=True,
            mobile="13800131025",
            classmate="1班"
        )
        detail = Detail.objects.create(
            address="北京",
            school="清华大学",
            job="教师"
        )
        detail.student.add(stu)

        # 方式二:绑定多个模型
        stu1 = Student.objects.create(
            name="张三1",
            age=18,
            sex=True,
            mobile="13800138125",
            classmate="1班",
        )
        stu2 = Student.objects.create(
            name="张三2",
            age=18,
            sex=True,
            mobile="13800138225",
            classmate="1班",
        )
        detail = Detail.objects.create(
            address="上海",
            school="复旦大学",
            job="医生"
        )
        detail.student.add(stu1, stu2)
        return HttpResponse("get请求")

    def get2(self, request):
        # 查询数据
        stu = Student.objects.filter(id=133).first()
        # 方式1
        if stu:
            # to_detail 就是Detail中related_name定义,提供给Student反向查询使用的
            address = stu.to_detail.all()
            print(address)

        # 方式2
        list = Detail.objects.filter(student=stu).all()
        list = Detail.objects.filter(student__name='张三').all()
        print(list)

        # # 方式3
        detail = Detail.objects.filter(address="北京").first()
        if detail:
            stu = detail.student
            print(stu.name)
        # 或者
        stu = Student.objects.filter(detail_list__address="北京").first()
        if stu:
            print(stu.name)

        return HttpResponse("get请求")

    def get3(self, request):
        # 更新数据
        detail = Detail.objects.filter(address="上海").first()
        # 方式1
        for stu in detail.student.all():
            print(stu.name)
            stu.mobile = "130123456780" # 修改为已存在的主键时会报错
            stu.save()

        # 方式2
        stu = Student.objects.filter(id=133).first()
        ret1 = Detail.objects.filter(student=stu).update(address="上海")
        ret1 = Detail.objects.filter(student__classmate="1班").update(address="上海")
        print(ret1)

        # 方式3
        ret2 = Student.objects.filter(to_detail__address="上海").update(age=50)
        print(ret2)

        return HttpResponse("get请求")

    def get(self, request):
        # 删除数据
        # 删除模型记录时,对应的关系也会被删除,主模型不会被删除
        Detail.objects.filter(student__id=133).delete()

        # 删除主模型时,对应的关系也会被删除,子模型不会被删除
        Student.objects.filter(id=134).delete()

        # 删除绑定关系
        stu = Student.objects.filter(id=133).first()
        detail = Detail.objects.filter(address="上海").first()
        detail.student.remove(stu)
        detail.student.clear() # 清空所有绑定关系
        return HttpResponse("get请求")

# 自关联

自关联就是主键和外键都在一张表上。一般会在多级部门,多级菜单,多级权限,省市区行政区划

  • 一对多的自关联
# 创建模型的关联关系
class Area(models.Model):
    name = models.CharField(max_length=20, verbose_name="名称")
    # 自关联,建立一个自关联的外键
    parent = models.ForeignKey("self", on_delete=models.SET_NULL, 
	                                   related_name="subs", 
									   null=True, 
									   blank=True, 
									   verbose_name="上级行政区划")

    class Meta:
        db_table = "tb_areas"
        verbose_name = "行政区划"
        verbose_name_plural = verbose_name
    def __str__(self):
        return self.name

# 一对多的自关联的数据操作
class StudentView(View):
    def get1(self, request):
        # 添加数据
        # 添加省份数据
        area1 = Area.objects.create(name="山东省")
        area2 = Area.objects.create(name="江苏省")
        area3 = Area.objects.create(name="河南省")

        # 添加子数据,方式一
        area11 = Area.objects.create(name="济南市", parent=area1)
        area12 = Area.objects.create(name="青岛市", parent_id=area1.id)

        # 添加子数据,方式二
        area2.subs.add(
            Area.objects.create(name="南京市"),
            Area.objects.create(name="苏州市")
        )

        # 添加子数据,方式三
        area_list = [
            Area(name="郑州市"),
            Area(name="洛阳市"),
            Area(name="开封市")
        ]
        # bulk=False表示不使用批量插入,而是一条一条插入,默认是True
        # bulk=True时,会先将数据保存到内存中,然后一次性插入数据库,提高效率
        # bulk属性只有在一对多关系中才有效,一对一和多对多关系中无效
        area3.subs.add(*area_list, bulk=False)

        return HttpResponse("get请求")

    def get2(self, request):
        # 查询数据
        area = Area.objects.filter(name="济南市").first()
        print(area.parent.name)

        areas = Area.objects.filter(name="山东省").first()
        print(areas.subs.all())

        # 查找子级包含济南市的
        area = Area.objects.filter(subs__name__in=["济南市"]).all()
        print(area)

        # 使用父级记录作为条件
        areas = Area.objects.filter(parent__name="山东省").all()
        print(areas)

        return HttpResponse("get请求")
  • 多对多的自关联
# 创建模型的关联关系
class Member(models.Model):
    name = models.CharField(max_length=20, verbose_name="姓名")
    age = models.IntegerField(verbose_name="年龄")
    # symmetrical=True 表示对称关系,即A和B是朋友,B和A也是朋友
    friends = models.ManyToManyField("self", symmetrical= True, verbose_name="朋友")
    # symmetrical=False 表示非对称关系,即A和B是朋友,B和A不一定是朋友
    focus = models.ManyToManyField("self", symmetrical=False, 
	                                       related_name="fans_list", 
										   verbose_name="关注")

    class Meta:
        db_table = "member"
        verbose_name = "会员信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name
		
# 双向多对多的自关联的数据操作
class StudentView(View):
    def get1(self, request):
        # 添加数据
        Member.objects.create(name="张三", age=18)
        Member.objects.create(name="李四", age=20)
        Member.objects.create(name="王五", age=22)
        Member.objects.create(name="赵六", age=24)

        # 张三添加好友
        member1 = Member.objects.filter(name="张三").first()
        member2 = Member.objects.filter(name="李四").first()
        member3 = Member.objects.filter(name="王五").first()
        member4 = Member.objects.filter(name="赵六").first()
        member1.friends.add(member2, member3, member4)

        # 李四添加好友
        member2.friends.add(member1, member3, member4)

        return HttpResponse("get请求")

    def get2(self, request):
        # 查询数据
        member = Member.objects.filter(name="张三").first()
        # 张三的所有好友
        print(member.friends.all())

        return HttpResponse("get请求")
		
# 单向多对多的自关联的数据操作
class StudentView(View):
    def get5(self, request):
        # 添加数据
        member1 = Member.objects.get(name="张三")
        member2 = Member.objects.get(name="李四")
        member3 = Member.objects.get(name="王五")
        member4 = Member.objects.get(name="赵六")

        # 张三的关注
        member1.focus.add(member2, member3, member4)
        # 李四的关注
        member2.focus.add(member1, member3, member4)

        return HttpResponse("get请求")

    def get(self, request):
        # 查询数据
        member = Member.objects.get(name="张三")
        # 张三的关注列表
        print(member.focus.all()) # 李四 王五 赵六
        # 张三的粉丝列表
        print(member.fans_list.all()) # 李四

        return HttpResponse("get请求")

# 虚拟外键

  • 外键关系是由ORM代码来维护并进行关联查询操作,不依靠数据库维护的物理外键
  • 使用虚拟外键后,生成的数据表中则不会出现外键的约束声明,只会出现普通索引的声明
  • 要在Django中使用虚拟外键,只需要在模型声明外键字段中设置属性 db_constraint=False 即可
# 一对一的逻辑外键
student = models.OneToOneField("Student", db_constraint=False, related_name="profile")# 一对多的逻辑外键
student = models.ForeignKey("Student", db_constraint=False, on_delete=models.CASCADE)# 多对多的逻辑外键
teacher = models.ManyToManyField("Teacher", db_constraint=False, related_name="to_course")

# 查询优化

在关联查询中为了减少SQL查询的数量,提供了2个优化方法:

select_related() # 是通过JOIN语句,在查询时减少SQL查询数量,适用于一对一和多对一
prefetch_related() # 是通过IN语句分别查询每个表,然后在代码中处理逻辑关系,适用于多对多和一对多
  • 模型类
class Person(models.Model):
    firstname = models.CharField(max_length=10, verbose_name="姓")
    lastname = models.CharField(max_length=10, verbose_name="名")
    hometown = models.ForeignKey("City", 
	                              on_delete=models.DO_NOTHING, 
								  related_name="hometown_peoples", 
								  verbose_name="家乡")
    living = models.ForeignKey("City", 
	                            on_delete=models.DO_NOTHING, 
								related_name="living_peoples", 
								verbose_name="现居地")
    visitation = models.ManyToManyField("City", 
	                                    related_name="visit_peoples", 
										verbose_name="旅游地")class Meta:
        db_table = "tb_person"
        verbose_name = "用户信息"
        verbose_name_plural = verbose_name
​
    def __str__(self):
        return self.firstname + self.lastname
​
​
class PersonProfile(models.Model):
    mobile = models.CharField(max_length=20, verbose_name="联系电话")
    wechat = models.CharField(max_length=50, verbose_name="微信号")
    person = models.OneToOneField("Person", 
	                               on_delete=models.CASCADE, 
								   related_name="profile")class Meta:
        db_table = "tb_person_profile"
        verbose_name = "用户详细信息"
        verbose_name_plural = verbose_name
​
    def __str__(self):
        return self.person.firstname + self.person.lastname
		

class Province(models.Model):
    name = models.CharField(max_length=50, verbose_name="省份")
    class Meta:
        db_table = "tb_province"
        verbose_name = "省份信息"
        verbose_name_plural = verbose_name
​
    def __str__(self):
        return self.name
​
​
class City(models.Model):
    name = models.CharField(max_length=50, verbose_name="城市")
    province = models.ForeignKey("Province", 
	                              on_delete=models.DO_NOTHING, 
								  verbose_name="省份")class Meta:
        db_table = "tb_city"
        verbose_name = "城市信息"
        verbose_name_plural = verbose_name
​
    def __str__(self):
        return self.name
  • select_related 的用法
# 获取主表信息的同时,也把外键表数据也获取到
模型.objects.all().select_related() # 默认主表数据时,获取全部的外键字段
模型.objects.all().select_related('外键字段')
模型.objects.all().select_related('外键字段1').select_related('外键字段2').... #性能低
模型.objects.all().select_related('外键字段__外键字段')    #一次查询,连表操作性能低# select_related 全外键关联优化
person = Person.objects.filter(firstname="张", lastname="三").select_related()
# SELECT * FROM `orm_person`
# INNER JOIN `orm_city` ON (`orm_person`.`hometown_id` = `orm_city`.`id`)
# INNER JOIN `orm_province` ON (`orm_city`.`province_id` = `orm_province`.`id`)
# INNER JOIN `orm_city` T4 ON (`orm_person`.`living_id` = T4.`id`)
# INNER JOIN `orm_province` T5 ON (T4.`province_id` = T5.`id`)
# WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三')
# ORDER BY `orm_person`.`id`

# 限定外键的优化查找
person = Person.objects.filter(firstname="张", lastname="三").select_related("living")
# SELECT * FROM `orm_person`
# INNER JOIN `orm_city` ON (`orm_person`.`living_id` = `orm_city`.`id`)
# WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三')
# ORDER BY `orm_person`.`id` 

# 限定外键的优化查找
person = Person.objects.filter(firstname="张", lastname="三")
                        .select_related("hometown")
                        .select_related("living__province")
print(person.living)
print(person.living.province)
print(person.living.hometown)

# SELECT * FROM `orm_person` 
# INNER JOIN `orm_city` ON (`orm_person`.`living_id` = `orm_city`.`id`) 
# INNER JOIN `orm_province` ON (`orm_city`.`province_id` = `orm_province`.`id`) 
# WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三') 
# ORDER BY `orm_person`.`id`
  • prefetch_related 的用法
模型.objects.all().prefetch_related('外键字段')    #不连表,一次性多次查询
模型.objects.all().prefetch_related('外键字段__外键字段')

# 多对多中使用prefetch_related来减少SQL语句
person_list = Person.objects.prefetch_related("visitation").all()
for person in person_list:
	print(person.visitation.all())# SELECT * FROM `orm_person` 
# WHERE (`orm_person`.`firstname` = '张' AND `orm_person`.`lastname` = '三') 
# ORDER BY `orm_person`.`id` ASC# SELECT (`orm_person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`, 
# `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` 
# INNER JOIN `orm_person_visitation` ON (`orm_city`.`id`=`orm_person_visitation`.`city_id`) 
# WHERE `orm_person_visitation`.`person_id` IN (1, 2, 3, 4, 5)# prefetch_related也支持多级外键
person_list = Person.objects.prefetch_related("visitation__province").all()
for person in person_list:
	print(person.visitation.all())# SELECT  *  FROM `orm_person`
# SELECT (`orm_person_visitation`.`person_id`) AS `_prefetch_related_val_person_id`, 
# `orm_city`.`id`, `orm_city`.`name`, `orm_city`.`province_id` FROM `orm_city` 
# INNER JOIN `orm_person_visitation` ON (`orm_city`.`id`=`orm_person_visitation`.`city_id`) 
# WHERE `orm_person_visitation`.`person_id` IN (1, 2, 3, 4, 5)

# SELECT `orm_province`.`id`, `orm_province`.`name` FROM `orm_province` 
# WHERE `orm_province`.`id` IN (1, 2)

# 权限认证

  • 用户认证机制:基于django.contrib.auth子应用对外提供的。里面有内置的视图,模板,模型等等
  • 权限认证系统:基于django.contrib.auth子应用对外提供的。里面基于模型完成了基于RBAC的权限认证

# 配置用户模型

  • 使用默认用户模型
# Django 自带用户认证系统,用户模型默认是 django.contrib.auth.models.User 包含用户认证字段
# 在settings.py 中,AUTH_USER_MODEL 的默认是auth.User,这意味着 Django 使用默认用户模型进行认证
  • 自定义用户模型
# 1、创建自定义用户模型: 在应用的models.py文件中, 可以继承:
    # AbstractUser:如果你需要Django默认提供的所有字段
    # AbstractBaseUser:如果你只需要自定义字段
    # PermissionsMixin:如果你需要权限系统
	
	from django.contrib.auth.models import AbstractUser  
	 
	class MyUser(AbstractUser):  
		# 添加自定义字段  
		some_field = models.CharField(max_length=100)

# 2、然后在settings.py文件中,将AUTH_USER_MODEL设置为自定义的用户模型:
	INSTALLED_APPS = [  
		# ...  
		'your_app_name',  # 确保应用名称已经被添加进去
		# ...  
		'django.contrib.auth',  
		# ...  
	]  
	 
	AUTH_USER_MODEL = 'your_app_name.CustomUser' # 这里your_app_name是应用的名称
	
# 3、需要引用用户模型时, 使用get_user_model()函数, 会返回在settings中AUTH_USER_MODEL指向的模型
	from django.contrib.auth import get_user_model  
	 
	User = get_user_model()  
	user = User.objects.get(username='some_username')

# 配置认证后端

  • 使用默认认证后端
# 默认使用 django.contrib.auth.backends.ModelBackend 作为认证后端
# 这个后端通过检查用户模型(默认或自定义)中的用户名和密码来进行认证
  • 自定义认证后端
# 1、创建一个新的认证后端类,实现authenticate方法来定义认证逻辑
from django.contrib.auth.backends import ModelBackend
from.models import CustomUser

class CustomAuthBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = CustomUser.objects.get(username=username)
            if user.check_password(password):
                return user
        except CustomUser.DoesNotExist:
            return None

# 2、然后在settings.py文件中,将自定义的认证后端添加到AUTHENTICATION_BACKENDS列表中
AUTHENTICATION_BACKENDS = [
    'your_app_name.backends.CustomAuthBackend',
    'django.contrib.auth.backends.ModelBackend'
]

# 在视图中使用用户认证和权限控制

  • 用户登录检查
# 使用login_required装饰器来确保视图只能被登录用户访问
# 当未登录用户访问该视图时,会被重定向到settings.py中配置的登录页面(LOGIN_URL)
from django.contrib.auth.decorators import login_required
from django.shortcuts import render

@login_required
def protected_view(request):
    return render(request, "protected.html")

  • 权限检查
# 使用permission_required装饰器来检查用户是否具有特定的权限
# 如果用户没有该权限,会返回一个403 Forbidden错误页面
from django.contrib.auth.decorators import permission_required
from django.shortcuts import render

@permission_required('blog.can_edit_post')
def edit_post_view(request):
    # 编辑文章的逻辑
    return render(request, "edit_post.html")

# 对于基于类的视图,可以使用LoginRequiredMixin和PermissionRequiredMixin来实现类似的权限控制

# 认证方式

默认情况下,Django 使用 SessionAuthentication,但可以通过 settings.py 配置其他认证方式:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',  # Session 认证
        'rest_framework.authentication.TokenAuthentication',    # Token 认证
        'rest_framework_simplejwt.authentication.JWTAuthentication',  # JWT 认证
    ],
}

# 无论使用哪种认证方式,self.request.user 的底层实现都是通过
# django.contrib.auth.middleware.AuthenticationMiddleware 中间件完成的
# 该中间件会在每个请求中调用认证后端,将用户信息赋值给 request.user

# RBAC权限认证机制

3表RBAC认证 # 用户表、角色表、权限表
5表RBAC认证 # 用户表、角色表、权限表、用户与角色之间的关系表、角色与权限的关系表
Django的RBAC # 增加了用户与权限之间的关系表,允许针对某一个用户,可以单独分配权限的
  • admin.py 里面允许我们编写的代码一共可以分成2部分:列表页配置与详情页配置
from django.contrib import admin
from . import models
​ 
class StudentModelAdmin(admin.ModelAdmin):
    """列表页功能配置"""
    # 列表页字段显示[可以使用模型的字段,也可以是模型/管理器的方法]
    list_display = ("id", "name", "age", "sex_display", "sex_display2", "mobile")
    @admin.display(description="性别", ordering="sex")
    def sex_display2(self, obj):
        return "男" if obj.sex else "女"# 列表页默认排序字段[写法与ORM的order_by一样]
    ordering = ["-age", "id"]
    # 设置允许点击指定字段可以跳转到详情页[默认是主键]
    list_display_links = ["id", "name"]
    # 设置列表页的过滤字段[只支持模型字段,不支持模型/管理器的方法]
    list_filter = ["sex", "age"]
    # 设置列表页单页数据量
    list_per_page = 10
    # 设置允许在列表页直接编辑的字段[只支持模型字段]
    list_editable = ["age", "mobile"]
    # 设置允许在列表中搜索的字段
    search_fields = ["name", "classmate"]
    # 设置列表页中的日期过滤器
    date_hierarchy = "updated_time"# 动作栏是否在上下方显示
    actions_on_top = False     # 上方控制栏是否显示,默认False表示隐藏
    actions_on_bottom = True  # 下方控制栏是否显示,默认False表示隐藏"""详情页功能配置"""
	# 设置添加页面和更新页面,显示表单中的字段列表
	# fields = ["name", "sex", "age", "classmate", "description", "mobile", "status"]# 设置添加页面和更新页面,显示表单中所报的字段分组列表
	fieldsets = (
		# ["组名", {
		#   "fields": ["字段1","字段2",...] # 当前组出现的字段
		#   "classes": ["collapse", "wide", "extrapretty"] # 折叠样式
		# }],
		["必填项", {
			"fields": ["name", "age", "mobile", "classmate", "status"],
			"classes": ["wide"],
		}],
		["可选项", {
			"fields": ["sex", "description"],
			"classes": ["collapse", "wide"],
		}],
	)# 添加数据的字段列配置
	add_fieldsets = (
		("必填项", {
			'fields': ('name', 'age', 'classmate', "mobile"),
			'classes': ('wide',),
		}),
		('可选项', {
			'fields': ('sex', 'description'),
			'classes': ('collapse',),  # 折叠样式
		}),
	)def get_fieldsets(self, request, obj=None):
		"""
		:request 本次客户端的请求处理对象
		:obj     本次客户端更新的模型对象[如果本次操作属于添加数据操作,则obj的值为None]
		"""
		if obj is None:
			"""添加页面的字段列表"""
			return self.add_fieldsets
		else:
			"""更新页面的字段列表"""
			return self.fieldsets
​
	# 钩子方法
	def save_model(self, request, obj, form, change):
		"""
		钩子方法:客户端提交保存数据[添加/更新]的表单后自动执行这个方法,
		:request 客户端本次提交的请求对象
		:obj     客户端本次操作的模型对象[如果是添加对象,则当前obj属于刚创建的没有ID的对象]
		:form    客户端本次显示的表单对象[开始]
		:change  客户端本次提交的表单对象[提交]
		"""
		# print("客户端提交的数据", request.POST)
		# 更新页面提交的obj.id是数字,添加页面提交obj.id是None
		# print("客户端提交的数据", obj.id, type(obj))
		if obj.id:
			print("更新模型操作")
		else:
			print("添加模型操作")# 这里一般用于写验证
		if obj.age > 100:
			raise TypeError("年龄太大了!")
​
		obj.save()  # 这里模型添加# 这里一般写关联模型[关联业务的代码,注册用户发送通知邮件,赠送数据]def delete_model(self, request, obj):
		"""
		钩子方法:客户端在详情页删除数据时自动执行这个方法,
		:request 客户端本次提交的请求对象
		:obj     客户端本次操作的模型对象
		"""
		# 删除前的代码[例如,判断权限,]
		print("详情页删除模型前的代码操作")
		obj.delete()  # 删除代码
		# 删除后的代码[例如,记录操作日志,删除相关其他的数据]
		print("详情页删除模型后的代码操作")def delete_queryset(self, request, queryset):
		"""
		钩子方法:  客户端在列表页删除数据时自动执行这个方法,
		:request  客户端本次提交的请求对象
		:queryset 客户端本次操作的QuerySet对象[这个QuerySet表示可以1个对象,也可以多个对象]
		"""
		# 删除前的代码[例如,判断权限,]
		print("列表页删除模型前的代码操作")
		queryset.delete()  # 删除代码
		# 删除后的代码[例如,记录操作日志,删除相关其他的数据]
		print("列表页删除模型后的代码操作")
​
​
# admin.site.register(模型类, 模型管理类)
admin.site.register(models.Student, StudentModelAdmin)
  • 子应用的英文名改成中文显示 apps.py
from django.apps import AppConfig
​
class StuapiConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'stuapi'
    verbose_name = "学生管理"
    verbose_name_plural = verbose_name
  • 子应用的英文名改成中文显示 apps.py
from django.apps import AppConfig
​
class StuapiConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'stuapi'
    verbose_name = "学生管理"
    verbose_name_plural = verbose_name
  • 模型中的设置 models.py
from django.db import models
​
class Student(models.Model):
    STATUS = (
        (0, "正常入学"),
        (1, "正常毕业"),
        (2, "已经辍学"),
    )
    name = models.CharField(max_length=15, verbose_name="学生名字")
    age = models.SmallIntegerField(verbose_name="年龄")
    sex = models.BooleanField(default=True, verbose_name="性别")
    classmate = models.CharField(db_column="class", max_length=50, verbose_name="班级")
    mobile = models.CharField(max_length=20, unique=True, verbose_name="手机号码", 
	                          help_text="请输入一个唯一的手机号码")
    description = models.TextField(null=True, verbose_name="个性签名")
    status = models.SmallIntegerField(null=True, verbose_name="状态码")
    created_time = models.DateTimeField(auto_now_add=True)
    updated_time = models.DateTimeField(auto_now=True)class Meta:
        db_table = "ww_student"
        verbose_name = "学生信息"
        verbose_name_plural = verbose_name
​
    def __str__(self):
        return f"{self.name}"def sex_display(self):
        return "男" if self.sex else "女"# 设置方法名在列表页中的文字提示
    sex_display.short_description = "性别"
    # 设置方法名在列表页中的排序字段
    sex_display.admin_order_field = "sex"
  • 数据库和本地Django的时区有冲突,所以需要根据实际开发过程中的业务来考虑是否要修改时区关闭USE_TZ
USE_TZ = False

# 缓存Cache

# 设置缓存

  • 设置 settings.py 中的 CACHES 配置项决定把数据缓存在哪里,是数据库中、文件系统还是在内存中
  • 可以将 cache 存到 redis 中,默认采用0号数据库
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
		# 数据源格式连接写法  mysql://账号:密码@IP:端口/数据库名称
        "LOCATION": "redis://127.0.0.1:16379",
		# 以秒为单位,这个参数默认是300秒,即5分钟,为None表示永远不会过期,值设置成0立即失效
        "TIMEOUT": 60,
		"OPTIONS": {
			"CLIENT_CLASS": "django_redis.client.DefaultClient",
			"CONNECTION_POOL_KWARGS": {
				"max_connections": 1000,
				"encoding": 'utf-8'
			},
			"PASSWORD": "qwe123"  # redis密码
		}
    }
}
  • 在python中连接并操作redis,需要安装django-redis包并配置如下:
# pip install cryptography  # 这个是连接数据库有时候涉及加密时使用的模块
pip install django-redis

# 视图缓存

django.views.decorators.cache 定义了 cache_page 装饰器,用于对视图的输出进行缓存

"""缓存函数视图"""
from django.http.response import HttpResponse
from django.views.decorators.cache import cache_page
import random
​
​# 一为了避免所有视图缓存同一时间过期,一般是设置随机数过期时间
@cache_page(timeout=60 * 60 * 20 + random.randint(1, 9999)) 
def index(request):
    print("执行视图代码了!")
    return HttpResponse('hello!')
​
​
"""缓存类视图"""
from django.utils.decorators import method_decorator
​
class IndexView(View):
    # cache_page 是基于函数视图进行缓存的,所以无法直接给类视图使用
	# 需要使用method_decorator进行类视图转换
    @method_decorator(cache_page(timeout=60))
    def get(self, request):
        print("执行视图代码了!")
        return HttpResponse('hello! IndexView.get')

# 缓存API

缓存API是针对于某个变量数据进行单独缓存的,使用上比视图缓存更为灵活

from django.http.response import JsonResponse
from django.core.cache import cache

# 设置:cache.set(键, 值, 有效时间)
# 获取:cache.get(键)
# 删除:cache.delete(键)
# 清空:cache.clear() # 慎用,这个会把整个库所有的 缓存数据全部清空!class HomeView(View):
    def get(self, request):
        # 读取缓存
        student_list = cache.get("student_list")
        # 判断缓存结果,如果没有,则读取数据库并写入缓存
        if not student_list:
            print("读取数据库!")
            student_list = list(models.Student.objects.values_list())
            cache.set("student_list", student_list, 10)return JsonResponse(student_list, safe=False)def delete(self,request):
        # 删除/更新数据时,先删缓存,再删除/更新数据库的数据
        cache.delete("student_list")
        return JsonResponse({}, safe=False)

# WSGI和ASGI

WSGI和ASGI,都是基于Python设计的网关接口(Gateway Interface,GI)

# 网关接口(Gateway Interface,GI)

# 网关接口就是一种为了实现加载动态脚本而运行在Web服务器和Web应用程序中的通信接口,是一种协议/规范
# 只有Web服务器和Web应用程序都实现了网关接口规范以后,双方的通信才能顺利完成

# 常见的网关接口协议:
CGI # 公共网关接口(Common Gateway Interface)是最早的Web服务器主机提供信息服务的标准接口规范
FastCGI # 快速通用网关接口(Fast Common Gateway Interface)是公共网关接口(CGI)的增强版本

WSGI # Web服务器网关接口(Web Server Gateway Interface)
# 是Python解决Web服务器与客户端间的通信基于CGI标准而设计的
# django运行runserver命令,其内部就启动了wsgiref模块。wsgiref是遵循wsgi接口规范的web服务器
# django项目线上运行不使用runserver,一般使用 uWSGI 或者 Gunicorn 作为web服务器运行django

ASGI
# 是构建于WSGI接口规范之上的异步服务器网关接口,是WSGI的延伸和扩展
协议规范 支持的请求协议 同步/异步 支持的框架
WSGI HTTP 同步 django3.0以前/Flask1.0
ASGI HTTP/HTTP2/WebSocket等 同步/异步 FastAPI/Tornado/django3.1以后/flask2.0

# uWSGI 的使用

如何用uWSGI托管Django (opens new window)配置 (opens new window)

# 1、安装 uWSGI
conda config --add channels conda-forge
conda install uWSGI

# 2、配置 uWSGI,项目根目录下创建uwsgi配置文件:uwsgi.ini
[uwsgi]
# 使用nginx连接时使用,Django程序所在服务器地址
# socket=0.0.0.0:8000
# 直接做web服务器使用,Django程序所在服务器地址
http=0.0.0.0:8000
# 项目目录
# chdir=项目根目录,务必使用绝对路径
chdir=/home/moluo/Desktop/djdemo
# 项目中wsgi.py文件的目录,相对于项目根目录
wsgi-file=djdemo/wsgi.py
# 进程数 CPU * 2 - 1 , 也可以不减1
processes= 4
# 线程数 CPU数量
threads=2
# uwsgi服务器的角色
master=True
# 存放进程编号的文件
pidfile=uwsgi.pid
# 日志文件,因为uwsgi可以脱离终端在后台运行,日志看不见。我们以前的runserver是依赖终端的
daemonize=uwsgi.log
# 指定依赖的虚拟环境
# virtualenv=/root/.virtualenvs/环境名称
virtualenv=/home/moluo/anaconda3/envs/djdemo

# 3、项目根目录下,启动uwsgi服务器
uwsgi --ini uwsgi.ini
​# 停止运行
uwsgi --stop uwsgi.pid  # 调用系统的 kill -9 uwsgi.pid中的进程号# 查看当前系统中的指定名称的进程
ps aux | grep uwsgi

# Uvicorn 的使用

  • Uvicorn (opens new window) 是一个快速的 ASGI 服务器,基于 uvloop 和 httptools 构建,是 Python 异步生态中的一员
  • Uvicorn 当前支持 HTTP / 1.1 和 WebSockets,将来计划支持HTTP/2.0
# 安装uvicorn
pip install uvicorn

# 项目根目录下,运行django项目
uvicorn djdemo.asgi:application --reload

# 使用gunicorn来管理uvicorn
pip install gunicorn

# 运行
gunicorn -w 4 djdemo.asgi:application -k uvicorn.workers.UvicornWorker --reload

# 异步视图

文档 (opens new window)

  • 函数视图:在Django3.1后的版本中,可以通过async def语法,将任何函数视图定义为异步视图
"""同步视图"""
import time
def home1(request):
    time.sleep(5)
    return HttpResponse('Hello, sync view!')"""异步视图"""
import asyncio
async def home2(request):
    # asyncio.sleep(5)
    await asyncio.sleep(5)  
    return HttpResponse('Hello, async view!')
  • 类视图:django内部是将它的__call__()方法定义为async def,成为异步视图
class Home3View(View):
    async def __call__(self, *args, **kwargs): # 当把一个类当函数去调用,就会触发__call__方法
        return super().__call__(*args, **kwargs)async def get(self, request):
        await asyncio.sleep(5)
        return HttpResponse("ok, get")

# 模型异步操作

  • django4.2版本的ORM对数据库的访问这块还没有全面实现异步处理,默认还是同步的
  • django提供了2个适配函数,它们用于同步和异步之间调用风格的转换
async_to_sync() # 异步转同步,参数就是同步函数
sync_to_async() # 同步转异步,参数就是异步函数
  • 适配函数既可以当包装函数使用,也可以作为装饰器使用
from asgiref.sync import async_to_sync

# 用法1
sync_function = async_to_sync(async_function)# 用法2
@async_to_sync
async def async_function(...):
    pass
from django.views import View
from django.http.response import JsonResponse
from component import models
from asgiref.sync import sync_to_async
​
class User1View(View):
    async def __call__(self, *args, **kwargs):
        return super().__call__(*args, **kwargs)async def get(self, request):
        """因为是异步视图,无法直接使用同步代码,所以报错:SynchronousOnlyOperation"""
        # student = models.Student.objects.get(id=12)
        # print(student)"""在异步视图中,必须异步操作模型"""
        # sync_to_async(models.Student.objects.get, thread_sensitive=True)
        # 上面就是把 models.Student.objects.get 进行异步转换,在线程安全模式运行"""异步获取一条数据"""
        # aget = sync_to_async(models.Student.objects.get, thread_sensitive=True)
        # student = await aget(id=12)  # # aget就是get的异步方法,调用方法与原来的get一样
        # return JsonResponse({"msg": "ok, get", "data": {
        #     "id": student.id,
        #     "name": student.name,
        # }})"""异步获取多条数据"""
        # # QuerySet 惰性查询,all执行的时候,根本没发生数据操作,自然也就没有IO
        # student_objs = models.Student.objects.all()
        # student_list = []
        # async for student in student_objs:
        #     student_list.append({
        #         "id": student.id,
        #         "name": student.name,
        #     })
        # return JsonResponse({"msg": "ok, get", "data": student_list})"""异步添加数据"""
        acreate = sync_to_async(models.Student.objects.create, thread_sensitive=False)
        student = await acreate(
            name="小柏",
            age=13,
            sex=True,
            mobile="13956567878",
            status=1,
            classmate="307",
            description="话不多数,上就完事了!")
        return JsonResponse({"msg": "ok, get", "data": {
            "id": student.id,
            "name": student.name,
        }}, status=201)

# 异步HTTP请求

import httpx
from django.views import View
from django.http.response import JsonResponse
from component import models
from asgiref.sync import sync_to_async
​ 
class User1View(View):
    async def __call__(self, *args, **kwargs):
        return super().__call__(*args, **kwargs)async def get(self, request):
        """异步网络请求"""
        # 同步代码在遇到IO操作时就会出现阻塞,所以异步代码在IO时需要交出程序执行权
        async with httpx.AsyncClient() as client:
            response = await client.get("https://httpbin.org/get")
        return JsonResponse(response.json())

# 异步中间件

"""
wsgiref 是python内置模块,提供给开发者在开发时,用于创建同步web服务器
asgiref 是python内置模块,提供给开发者在开发时,用于创建异步web服务器
"""

"""异步函数中间件"""
from asgiref.sync import iscoroutinefunction
# iscoroutinefunction 是python内置 判断当前参数是否是协程函数
# 如果是协程函数,则返回值为True,否则Falsedef simple_middleware(get_response):
    if iscoroutinefunction(get_response):
        async def middleware(request):
            response = await get_response(request)
            return response
    else:
        def middleware(request):
            response = get_response(request)
            return response
    return middleware
​
​
"""异步类中间件"""
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
# markcoroutinefunction 把当前参数标记为协程函数class SimpleMiddleware2:
    """异步中间件"""
    async_capable = True
    sync_capable = Falsedef __init__(self, get_response):
        self.get_response = get_response
        if iscoroutinefunction(self.get_response):
            markcoroutinefunction(self)async def __call__(self, request):
        response = await self.get_response(request)
        return response
​
    async def process_request(self, request):
        print("视图执行之前!!")async def process_response(self, request, response):
        print("视图执行以后!!")
        return response

# DRF简单介绍

Django REST framework (opens new window)是建立在 Django 基础上的 Web 开发子框架,可快速开发REST API接口应用

  • 提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程
  • 提供丰富的类视图、扩展类、视图集来简化视图的编写工作
  • 提供了认证、权限、限流、过滤、分页、接口文档等功能支持
  • 提供了一个调试API接口 的Web可视化界面来方便查看测试接口

# 环境安装与配置

  • 安装DRF
# DRF需要以下依赖:
# Python 3.6+
# Django 3.0+

pip install djangorestframework -i https://pypi.tuna.tsinghua.edu.cn/simple 
  • 添加rest_framework应用
# 在settings.py的INSTALLED_APPS中添加'rest_framework'
INSTALLED_APPS = [
    ...
    'rest_framework',
]

# DRF 简写代码的过程

  • 创建模型操作类
from django.db import models

class DRFStudent(models.Model):
    STATUS = (
        (0, "正常入学"),
        (1, "正常毕业"),
        (2, "已经辍学"),
    )
    name = models.CharField(max_length=15, verbose_name="学生名字")
    age = models.SmallIntegerField(verbose_name="年龄")
    sex = models.BooleanField(default=True, verbose_name="性别")
    classmate = models.CharField(db_column="class", max_length=50, verbose_name="班级")
    mobile = models.CharField(max_length=20, unique=True, verbose_name="下载地址")
    description = models.TextField(null=True, verbose_name="手机号码")
    status = models.SmallIntegerField(null=True, choices=STATUS, verbose_name="状态码")
    created_time = models.DateTimeField(auto_now_add=True)
    updated_time = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "student"
        verbose_name = "学生信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return f"{self.name}"
  • 创建序列化器
from rest_framework import serializers
from student.models import Student

class StudentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = '__all__' # 序列化所有字段
        read_only_fields = ['id']  # 只读字段
        extra_kwargs = {
            
        }
  • 创建视图
from rest_framework.viewsets import ModelViewSet
from stuapi import models
from . import serializers
​
class StudentModelViewSet(ModelViewSet):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentSerializer
  • 定义路由
from django.urls import path
from rest_framework import routers
from drfstu import views

# DefaultRouter 是 Django REST framework 提供的一个实用工具
# 能依据视图集自动生成一系列 RESTful 风格的 URL 路由规则
# 这些规则会涵盖常见的 CRUD(创建、读取、更新、删除)操作
router = routers.DefaultRouter()
# 把视图集注册到该路由器上,从而自动生成与学生信息相关的 RESTful API 的 URL 路由规则
router.register('student', views.StudentModelViewSet, basename='student')
# router.register('base', views.StudentBaseViewSet, basename='base')

# 将 DefaultRouter 自动生成的 URL 路由规则整合到项目的主 URL 模式里
# 项目就会同时支持手动定义的 URL 规则和 DefaultRouter 自动生成的规则
urlpatterns = [
    # 普通视图类(如 APIView、IView)可以直接调用 .as_view() 方法
    # path("base/", views.StudentBaseViewSet.as_view())
    # 视图集(ViewSet)因为支持多种 HTTP 方法对应不同的处理动作(像 list、create等)
    # 所以调用 .as_view() 时需要明确指定每个 HTTP 方法对应的处理动作
    # path("base/", views.StudentBaseViewSet.as_view({'get': 'list'})),
] + router.urls
  • 加载到总路由文件中
from django.contrib import admin
from django.urls import path, include
​
urlpatterns = [
    path("admin/", admin.site.urls),
    path("drfstu/", include("students.urls")),
]
  • 在浏览器中输入网址 http://127.0.0.1:8000/drfstu/,可以看到 DRF 提供的 API Web 浏览页面 DRF提供的APIWeb
# 点击链接 http://127.0.0.1:8000/drfstu/student/ 可以访问获取所有数据的接口
# 在页面底下表单部分填写学生信息,可以访问添加新学生的接口,保存学生信息
# http://127.0.0.1:8000/drfstu/student/1/ 可以访问获取单一学生信息的接口
# 在页面底部表单中填写学生信息,可以访问修改学生的接口
# 点击DELETE按钮,可以访问删除学生的接口

# DRF序列化器

1. 序列化:在服务器响应时,使用序列化器可以完成对数据的序列化
#  序列化器会把模型对象转换成字典,经过视图中response对象以后变成json字符串

1. 反序列化:在客户端请求时,使用序列化器可以完成对数据的反序列化
#  视图中request会把客户端发送过来的数据转换成字典,序列化器可以把字典转成模型
#  把客户端发送过来的数据进行校验

注意

  • serializer不是只能对模型类转换数据格式,也可以为非数据库模型类的转换数据格式
  • serializer是独立于数据库之外的存在,并不是和模型绑定的,具体要看视图实现的业务逻辑来决定

# 常用字段类型

  • serailziers类中的字段声明是提供给客户端的

常用字段类型

  • 字段的选项参数
参数名称 作用
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空字符串
trim_whitespace 是否移除字符串两边的空白字符
max_value 最小数值
min_value 最大数值
  • 字段的通用选项参数
参数名称 说明
read_only 该字段仅用于序列化输出,默认False,例如:id,created_time,updated_time
write_only 该字段仅用于反序列化输入,默认False,例如:password,
required 该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 反序列化时该字段是否允许传入None,默认False
validators 反序列化时该字段使用的验证器函数(是定义在序列化器类外部的校验函数)
error_messages 表明反序列化时如果验证出错了,返回错误错误信息的字典
label 用于HTML展示API页面时,显示的字段名称。如果不写,则默认采用模型的verbose_name,但是前提是当前序列化器继承ModelSerializer
help_text 用于HTML展示API页面时,显示的字段帮助提示信息。如果不写,则默认采用模型的help_text,但是前提是当前序列化器继承ModelSerializer

# 定义序列化器

Serializer使用类来定义,必须直接或间接继承于rest_framework.serializers.Serializer

def check_mobile(data):
    """定义在序列化器类外部的校验函数"""
    print("check_mobile, data=", data)
    ret = DRFStudent.objects.filter(mobile=data).first()
    if ret:
        raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!")
    return data  # 必须把数据返回,否则后续操作中序列化器无法获取校验后的结果

class StudentBaseSerializer(Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(max_length=100)
    age = serializers.IntegerField()
    sex = serializers.BooleanField
    classmate = serializers.CharField(max_length=100)
	# 在字段中添加 validators 选项参数,也可以补充验证行为
    mobile = serializers.CharField(max_length=20, validators=[check_mobile])
    description = serializers.CharField(max_length=2000)
    status = serializers.IntegerField()
    created_time = serializers.DateTimeField(read_only=True)
    updated_time = serializers.DateTimeField(read_only=True)

    class Meta:
        model = DRFStudent
        fields = ['name', 'age', 'sex', 'classmate', 'mobile', 'description', 'status']
        #     fields = "__all__"

    # 全部数据的校验,attrs等价于 request.body
    def validate(self, attrs):
        ret = DRFStudent.objects.filter(mobile=attrs.get("mobile")).first()
        if ret:
            raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!")
        # 可以在这里添加自定义的验证逻辑
        return attrs  # 必须有返回值

    # 校验单个指定字段的数据
    def validate_mobile(self, value):
        ret = DRFStudent.objects.filter(mobile=value).first()
        if ret:
            raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!")
        # 必须把数据返回,否则后续操作中序列化器无法获取校验后的结果
        return value

    # validated_data 就是校验数据后的返回结果
    def create(self, validated_data):
        # 可以在这里添加自定义的创建逻辑
		# 添加模型操作
        # :validated_data 校验后的客户端数据,字典格式
        # 视图中使用序列化器对象的save方法会自动调用这个方法
        return DRFStudent.objects.create(**validated_data)

    # instance 表示要修改的模型对象,validated_data 就是校验数据的返回结果,字典格式
    def update(self, instance, validated_data):
        # 可以在这里添加自定义的更新逻辑
        instance.name = validated_data.get('name', instance.name)
        instance.age = validated_data.get('age', instance.age)
		# 或者
		for field, value in validated_data.items():
		    setattr(instance, field, value)
			
		instance.save()  # 保存实例到数据库
        return instance

# 创建序列化器对象

# instance:用于序列化时实例化,则需要将模型类对象传入instance参数
# data:用于反序列化时实例化,将要被反序列化的数据传入data参数
# many:用于序列化时实例化,当instance的值是一个QuerySet类型,则需要声明many=True
# context:创建Serializer对象时,还可通过context参数【字典类型】额外添加数据传入到序列化器中
serializer = 序列化器类(instance=None, data=empty, many=False, context=None, **kwargs)


# 序列化单个模型对象,当instance是模型对象
serializer = StudentSerializer(instance=student)# 序列化多个模型对象,当instance是QuerySet对象
# 序列化器内部会根据many的值判断是否需要for循环
serializer = StudentSerializer(instance=student_list, many=True)# 如果传递数据到序列化器中,可以使用context
serializer = StudentSerializer(其他参数..., context={'request': request})

# 通过context参数附加的数据,可以通过Serializer对象的context属性获取

视图调用序列化器

class StudentBaseViewSet(View):
    def get(self, request):
        sudent = DRFStudent.objects.all()
        # 序列化转换数据
        # many=True 表示instance参数是一个列表,序列化器内部会根据many的值判断是否需要for循环
        serializer = StudentBaseSerializer(sudent, many=True)
        student_list = serializer.data
        # 类型:rest_framework.utils.serializer_helpers.ReturnList 继承自 list
        print(type(student_list))
        return JsonResponse(student_list, safe=False)
		
	def post(self, request):
		data = json.loads(request.body)
		
		# 实例化序列化器,用于反序列化必须填写data参数
        serializer = StudentSerializer(data = data)
		 # 调用序列化器的校验流程
        if not serializer.is_valid():
		    # 获取校验失败后的错误(validate/validate_mobile/error_messages/validators)提示信息
            print(serializer.errors) 
            return JsonResponse(serializer.errors, status=400)
			
		# 除了使用is_valid的结果进行判断,手动返回错误信息以外,还可以由drf帮我们返回错误信息
		# raise_exception=True 表示校验失败时
		# 让drf自行返回错误(validate/validate_mobile/error_messages/validators)给客户端
        serializer.is_valid(raise_exception=True) 
			
		# 模型操作
        student = models.Student.objects.create(
            name=data.get("name"),
            mobile=data.get("mobile"),
            age=data.get("age"),
            status=data.get("status"),
            classmate=data.get("classmate"),
            description=data.get("description"),
        )# 序列化
        serializer = StudentSerializer(student)
        # 返回结果
        instance = serializer.data
        return JsonResponse(instance, status=201)

# 模型操作

# 反序列化的过程可以把数据转成模型类对象,通过在序列化器中的create()和update()两个方法来实现
# 实现了上述两个方法后,在反序列化数据的时候,就可以通过save()方法返回一个数据对象实例

# 如果创建序列化器对象时没有设置instance参数,则序列化器的create()被调用
# 反之,如果设置instance参数,则序列化器的update()被调用
instance = serializer.save()

# 源代码 rest_framework/serializers.py
def save(self, **kwargs):
	
	validated_data = {**self.validated_data, **kwargs}

	if self.instance is not None:
		self.instance = self.update(self.instance, validated_data)
		assert self.instance is not None, (
			'`update()` did not return an object instance.'
		)
	else:
		self.instance = self.create(validated_data)
		assert self.instance is not None, (
			'`create()` did not return an object instance.'
		)

	return self.instance

视图操作代码

from . import serializers

def post3(self, request):
        """添加数据操作"""
        data = json.loads(request.body)
        serializer = serializers.StudentSerializer(data = data)
        serializer.is_valid(raise_exception=True)
        # 因为上面实例化序列化器时没有传递instance参数,所以save自动调用模型类中的create方法
        serializer.save() 
        instance = serializer.data
        return JsonResponse(instance, status=201)def put(self, request):
        """更新数据操作"""
        id = request.GET.get("id")
        try:
            student = models.Student.objects.get(id=id)
        except models.Student.DoesNotExist:
            return JsonResponse({}, status=400)
​
        data = json.loads(request.body)
        serializer = serializers.StudentSerializer(instance = student, data = data)
        serializer.is_valid(raise_exception=True)
        # 因为上面实例化序列化器时有传递instance参数,所以save自动调用模型类中的update方法
        serializer.save()
        instance = serializer.data
        return JsonResponse(instance, status=201)

# 部分字段跳过验证阶段的方式说明

  • 在对序列化器进行save()保存时,可以传递额外数据,这些数据可以在create()和update()中的validated_data参数获取到
# 把 request 对象作为参数传入后,能在序列化器的 create() 或 update() 方法里访问该请求对象
# 这样在创建或更新对象时,就可依据请求信息(如用户认证信息、请求头)做额外处理
serializer.save(request=request) # 可以在save中,传递1个或多个不需要验证的数据到模型里面
  • 默认序列化器必须传递所有required的字段,否则会抛出验证异常。但是我们可以使用partial参数来允许部分字段更新
# 在开发中可能只需要验证当前被修改的字段,而不想验证其他的字段时,可以设置partial=True
# 这种情况有很多,例如,手机号更新绑定,修改密码或头像等等。
# 例如,当前学生模型中,我们只修改mobile字段,但是不提供其他的字段修改。则代码如下:
serializer = StudentSerializer(student, data={'mobile': '13000000000'}, partial=True)

# 模型类序列化器

ModelSerializer模型类序列化器与Serializer序列化器基类声明方式与调用方式一致,但额外提供了:

  • 基于模型类自动生成一系列字段声明
  • 基于模型类自动为Serializer生成unique、validators、等校验规则
  • 默认提供了create()和update()的实现
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer
from drfstu.models import DRFStudent

def check_mobile(data):
    """定义在序列化器类外部的校验函数"""
    print("check_mobile, data=", data)
    ret = DRFStudent.objects.filter(mobile=data).first()
    if ret:
        raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!")
    return data  # 必须把数据返回,否则后续操作中序列化器无法获取校验后的结果

class StudentBaseSerializer(ModelSerializer):
    # 仅保留有自定义验证逻辑的字段
    mobile = serializers.CharField(max_length=20, validators=[check_mobile])

    class Meta:
        model = DRFStudent
        fields = ['id', 'name', 'age', 'sex', 'classmate', 'mobile', 'description', 'status', 'created_time', 'updated_time']
        # 排除,少用,与fields互斥的
        exclude = ['created_time', 'updated_time']
        read_only_fields = ['id', 'created_time', 'updated_time']  # 只读字段
        # 选填,字段额外选项声明
        extra_kwargs = {
            # "字段名": {
            #     "选项": 值,
            #     "选项": 值,
            #      ...
            # },

            # "name": {
            #     "error_messages": {"required": "姓名不能为空!"}
            # },
            #
            # "age": {
            #     "max_value": 99,
            #     "min_value": 1,
            # }
        }

    # 全部数据的校验,attrs等价于 request.body
    def validate(self, attrs):
        ret = DRFStudent.objects.filter(mobile=attrs.get("mobile")).first()
        if ret:
            raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!!!!!")
        # 可以在这里添加自定义的验证逻辑
        return attrs  # 必须有返回值

    # 校验单个指定字段的数据
    def validate_mobile(self, value):
        ret = DRFStudent.objects.filter(mobile=value).first()
        if ret:
            raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!!!")
        # 必须把数据返回,否则后续操作中序列化器无法获取校验后的结果
        return value

    # validated_data 就是校验数据后的返回结果
    def create(self, validated_data):
        # 可以在这里添加自定义的创建逻辑
        return DRFStudent.objects.create(**validated_data)

    # # instance 表示要修改的模型对象,validated_data 就是校验数据的返回结果
    def update(self, instance, validated_data):
        # 可以在这里添加自定义的更新逻辑
        # instance.name = validated_data.get('name', instance.name)
        # instance.age = validated_data.get('age', instance.age)
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        return instance

视图代码

import json
from django.views import View
from django.http.response import JsonResponse
from stuapi import models
from . import serializers
​
class Student3View(View):
    def get1(self,request):
        """基于模型类序列化器序列化单个模型数据"""
        # 模型读取数据
        student = models.Student.objects.first()
        # 序列化
        serializer = serializers.StudentModelSerializer(instance=student)
        student_dict = serializer.data
        # 响应数据
        return JsonResponse(student_dict, safe=False)def get(self,request):
        """基于模型类序列化器序列化QuerySet"""
        # 模型读取数据
        student_objs = models.Student.objects.all()
        # 序列化
        serializer = serializers.StudentModelSerializer(instance=student_objs, many=True)
        student_list = serializer.data
        # 响应数据
        return JsonResponse(student_list, safe=False)def post(self,request):
        """基于模型类序列化器实现添加数据的校验与保存"""
        # 接收数据
        data = json.loads(request.body)
        # 实例化序列化器,用于反序列化必须填写data参数
        serializer = serializers.StudentModelSerializer(data=data)
        # 校验数据
        serializer.is_valid(raise_exception=True)
        # 模型操作
        serializer.save()  # 自动调用模型类中的create方法
        # 返回结果
        instance = serializer.data
        return JsonResponse(instance, status=201)def put(self,request):
        """基于模型类序列化器实现更新数据的校验与保存"""
        # 此处为了方便演示,让客户端通过查询字符串传递id,指定要修改的模型对象
        id = request.GET.get("id")
        try:
            student = models.Student.objects.get(id=id)
        except models.Student.DoesNotExist:
            return JsonResponse({"errors": "当前数据不存在!"}, status=400)# 接收数据
        data = json.loads(request.body)
        # 实例化序列化器
        serializer = serializers.StudentModelSerializer(instance=student, data=data)
        print(serializer)
        # 校验数据
        serializer.is_valid(raise_exception=True)
        # 模型操作
        serializer.save()  # 自动调用模型类中的update方法
        # 返回结果
        instance = serializer.data
        return JsonResponse(instance, status=201)
  • 路由代码
from django.urls import path, re_path
from . import views
​
urlpatterns = [
    path("student/", views.StudentView.as_view()),
    path("s1/", views.Student1View.as_view()),
    path("s2/", views.Student2View.as_view()),
    path("s3/", views.Student3View.as_view()),
]

# DRF请求响应

# 内容协商

  • drf在django原有的基础上,新增了一个request对象内置到了drf提供的APIVIew视图类里面
  • 并在django原有的HttpResponse响应类的基础上实现了一个子类rest_framework.response.Response
  • 这两个类,都是基于内容协商来完成数据的格式转换的

# Request 请求

drf传入视图的request对象不再是Django默认的HttpRequest对象,而是drf自己声明的Request类的对象

# drf 提供了 Parser (http请求解析器类) 在接收到客户端请求后会自动根据 Content-Type 的请求类型
# 如JSON、html等,将数据解析为类字典 [QueryDict] 对象保存到 drf 的 Request 对象的 data 属性中
  • 常用属性
request.data
# 包含了解析之后的文件和非文件数据
# 包含了对POST、PUT、PATCH请求方式解析后的数据
# 利用了REST framework的parser解析器,不仅支持表单类型数据,也支持JSON数据
# 提供了Parse解析之后的请求体数据。类似于Django中request.POST和 request.FILES、request.body属性

request.query_params
# 只要是地址栏 ? 号后面的查询字符串都可以接收,结果是QueryDict格式
  • 视图代码
class StudentBaseViewSet(APIView):
    def get(self, request):
        # 获取地址栏上的查询字符串
        print(request.query_params)  # <QueryDict: {'url': ['baidu.com']}>
        print(request.query_params.dict())  # {'url': 'baidu.com'}
        return Response({"mgs":"get"})

    def post(self, request):
        # <QueryDict: {'name': ['sylone'], 'age': ['32']}>
        print(request.data)
        # 如果客户端提交的是表单数据,则可以使用dict方法,把QueryDict转换成普通字典
        # {'name': 'sylone', 'age': '32'}
        print(request.data.dict())
        return Response({"mgs":"get"})

使用了drf的视图类,必须使用drf的请求与响应。同理,使用了django的视图了则使用Django的请求与响应

# Django --> View       --> HttpRequest/HttpResponse/JsonResponse
# Drf    --> APIView    --> Request/Response

# Response 响应

REST framework 提供了Renderer 渲染器,用来根据客户端的请求头中的 Accept(接收数据类型声明)来自动转换响应数据格式

# 可以在rest_framework/settings.py查找所有的drf默认配置项
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (  # 默认http响应渲染类
        'rest_framework.renderers.JSONRenderer',  # json渲染器,返回json数据
        'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览器API渲染器,返回api调试界面
    )
}
  • Response类的构造方式
from rest_framework.response import Response
​
# data不能是复杂结构的数据,如Django的模型类对象,可以使用Serializer序列化处理后再传递给data参数
# REST framewrok在rest_framework.status模块中提供了常用http状态码的常量:HTTP_200_OK 等
Response(data, status=None, template_name=None, headers=None, content_type=None)

# DRF的视图类

# 视图类 APIView

  • APIView 是 REST framework 所有视图类的基类,继承自Django的View视图类,APIView与View不同:
# 传入到视图方法中的是 REST framework 的 Request 对象,而不是 Django 的 HttpRequeset 对象
# 视图方法可以返回 REST framework 的 Response 对象,视图会为响应数据设置符合前端期望要求的格式

# 任何可以被 APIException 捕获到异常,都将会被 APIView 处理成合适格式的响应信息返回给客户端
  # django 的 View 中所有异常全部以 HTML 格式显示,不会返回 json 格式
  # drf 的 APIVIew 或者 APIView 的子类会自动根据客户端的 Accept 进行错误信息的格式转换

# drf的as_view方法在dispatch()进行路由分发前,会对请求的客户端进行身份认证、权限检查、流量控制
  # APIView为了实现上面说的3个功能除了继承了View原有的属性方法意外,还新增了类属性:
    # authentication_classes 值是列表或元组,成员是身份认证类
    # permissoin_classes 值是列表或元组,成员是权限检查类
    # throttle_classes 值是列表或元祖,成员是流量控制类
  • 视图代码
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from stuapi import models
from . import serializers

class StudentAPIView(APIView):
    def get(self, request):
        """获取所有学生信息"""
        # 获取数据
        student_objs = models.Student.objects.all()
        # 序列化,instance参数是QuerySet,必须声明many参数
        serializer = serializers.StudentModelSerializer(instance=student_objs, many=True)
        # 返回结果
        return Response(serializer.data)def post(self,request):
        """添加一个学生信息"""
        # 接受客户端数据,并反序列化【验证、保存】
        serializer = serializers.StudentModelSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        # 返回结果
        return Response(serializer.data, status=status.HTTP_201_CREATED)
​
​
class StudentInfoAPIView(APIView):
    def get(self, request, pk):
        """获取一个学生信息"""
        # 读取数据
        try:
            instance = models.Student.objects.get(pk=pk)
        except models.Student.DoesNotExist:
            return Response({"errors": "当前学生信息不存在!"}, 
			                status=status.HTTP_400_BAD_REQUEST)
        # 序列化
        serializer = serializers.StudentModelSerializer(instance=instance)
        # 返回结果
        return Response(serializer.data)def delete(self, request, pk):
        """删除一个学生信息"""
        # 删除数据
        models.Student.objects.filter(pk=pk).delete()
        # 返回结果
        return Response({}, status=status.HTTP_204_NO_CONTENT)def put(self, request, pk):
        """更新一个学生信息"""
        # 查询要更新的学生信息
        try:
            instance = models.Student.objects.get(pk=pk)
        except models.Student.DoesNotExist:
            return Response({"errors": "当前学生信息不存在!"}, 
			                status=status.HTTP_400_BAD_REQUEST)# 反序列化【校验、保存】,如果允许修改部分字段,则可以设置partial=True
        serializer = serializers.StudentModelSerializer(instance=instance, 
		                                                data=request.data, 
														partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()# 返回序列化结果
        return Response(serializer.data, status=status.HTTP_201_CREATED)
  • 路由代码
from django.urls import path,re_path
from . import views
​
urlpatterns = [
    path("student/", views.StudentAPIView.as_view()),
    re_path(r"^student/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
]

# GenericAPIView 通用视图类

  • 继承自APIView,主要增加了操作序列化器的1个属性与2个方法
# 属性:
serializer_class=序列化器 # 指明视图使用的序列化器类

# 方法1:		
get_serializer(self, args, *kwargs) # 返回序列化器对象

# 方法2:
get_serializer_class(self) # 返回序列化器类,默认返回serializer_class

# 一个视图类中调用多个序列化器时,可以返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象
class Student2GenericAPIView(GenericAPIView):
	queryset = Student.objects.all()
	
    # 整个视图类只使用一个序列化器的情况
    serializer_class = StudentModelSerializert
	
    # 整个视图类中使用多个序列化器的情况
    def get_serializer_class(self):
      if self.request.method.lower() == "put":
            return StudentModelSerializer
      else:
            return Student2ModelSerializer
​
    def get(self, request, pk):
        """获取一个模型信息"""
        serializer = self.get_serializer(instance=self.get_object())
        return Response(serializer.data)def put(self, request, pk):
        """更新一个模型信息"""
        serializer = self.get_serializer(instance=self.get_object(), data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)
  • 增加了关于数据库查询的1个属性与2个方法
# 属性:
queryset  # 指明使用的数据查询的结果集

# 方法1:
get_queryset(self) # 返回视图使用的查询集QuerySet

# 方法2:
get_object(self) # 返回详情视图所需的1个模型类数据对象
  • 其他可以设置的属性
pagination_class  # 指明分页控制类
filter_backends   # 指明数据过滤控制后端,允许客户端通过地址栏传递过滤参数
  • 视图代码
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework import status
from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent

class StudentBaseViewSet(GenericAPIView):
    queryset = DRFStudent.objects.all()
    serializer_class = StudentBaseSerializer

    def get(self, request):
        # 获取所有数据
        instance = self.get_queryset()
        # # 本质上 self.serializer_class(instance, many=True)
        serializer = self.get_serializer(instance, many=True)
        return Response(serializer.data)

    def post(self, request):
        # 新增数据,接受客户端数据,并反序列化【验证、保存】
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors)

class StudentInfoViewSet(GenericAPIView):
    queryset = DRFStudent.objects
    serializer_class = StudentBaseSerializer

    def get(self, request, pk):
        # 获取指定数据
        # instance = DRFStudent.objects.get(pk=pk)
        # instance = self.get_queryset().get(pk=pk)
        instance = self.get_object() # 本质上就是 self.queryset.get(pk=pk)
        serializer = self.get_serializer(instance) # 本质上 self.serializer_class(instance)
        return Response(serializer.data)

    # 注意请求地址最后一定要加 /   http://127.0.0.1:8000/drfstu/stu/1/
    def put(self, request, pk):
        # 修改指定数据
        instance = self.get_object()
        # 反序列化【校验、保存】,如果允许修改部分字段,则可以设置partial=True
        serializer = self.get_serializer(instance, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors)
        
    def delete(self, request, pk):
        # 删除指定数据
        instance = self.get_object()
        instance.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
  • 路由代码
from django.urls import path,re_path
from . import views
​
urlpatterns = [
    path("student/", views.StudentAPIView.as_view()),
    re_path(r"^student/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
]

# 五个视图扩展类

  • 也叫视图混入类,提供了对数据资源进行增删改查的视图方法处理流程的实现
  • 如果需要编写的视图方法属于这五种,可以通过继承相应的扩展类来减少自己编写的代码量
  • 需要搭配GenericAPIView通用视图类,因为需要调用GenericAPIView提供的序列化器与数据查询的方法
ListModelMixin      -> list                                       # 获取多条数据,可以过滤和分页
CreateModelMixin    -> create -> perform_create                   # 添加一条数据
RetrieveModelMixin  -> retrieve                                   # 获取一条数据
UpdateModelMixin    -> update/partial_update -> perform_update    # 更新一条数据/局部更新
DestroyModelMixin   -> destroy -> perform_update                  # 删除一条数据
  • 使用GenericAPIView结合视图扩展类,实现5个基本api接口,视图代码:
from rest_framework import mixins
​

class StudentMixinAPIView(GenericAPIView, mixins.ListModelMixin, mixins.CreateModelMixin):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
​
    def get(self, request):
        """获取所有数据"""
        return self.list(request)def post(self, request):
        """添加一条数据"""
        return self.create(request)
​
​
class StudentInfoMixinAPIView(GenericAPIView, mixins.RetrieveModelMixin, 
                                              mixins.UpdateModelMixin, 
											  mixins.DestroyModelMixin):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
​
    def get(self, request, pk):
        """获取一条数据"""
        return self.retrieve(request, pk)def put(self, request, pk):
        """更新一条数据"""
        return self.update(request, pk)def delete(self, request, pk):
        """删除一条数据"""
        return self.destroy(request, pk)
  • 路由代码:
from django.urls import path, re_path
from . import views
​
urlpatterns = [
    # GenericAPIView+mixins
    path("student3/", views.StudentMixinAPIView.as_view()),
    re_path(r"^student3/(?P<pk>\d+)/$", views.StudentInfoMixinAPIView.as_view()),
]

# 九个视图子类

1)ListAPIView
# 提供了get视图方法,内部调用了模型扩展类的list方法
# 继承自:GenericAPIView、ListModelMixin

2)CreateAPIView
# 提供了post视图方法,内部调用了模型扩展类的create方法
# 继承自: GenericAPIView、CreateModelMixin

3)RetrieveAPIView
# 提供了get视图方法,内部调用了模型扩展类的retrieve方法
# 继承自: GenericAPIView、RetrieveModelMixin

4)DestroyAPIView
# 提供了delete视图方法,内部调用了模型扩展类的destroy方法
# 继承自:GenericAPIView、DestoryModelMixin

5)UpdateAPIView
# 提供了put和patch视图方法,内部调用了模型扩展类的update和partial_update方法
# 继承自:GenericAPIView、UpdateModelMixin

6)ListCreateAPIView
# 提供了get和post方法,内部调用了list和create方法
# 继承自:GenericAPIView、ListModelMixin、CreateModelMixin

7)RetrieveUpdateAPIView
# 提供 get、put、patch方法
# 继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin

8)RetrieveDestroyAPIView
# 提供 get、delete方法
# 继承自:GenericAPIView、RetrieveModelMixin、DestroyModelMixin

9)RetrieveUpdateDestroyAPIView
# 提供 get、put、patch、delete方法
# 继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin
  • 视图代码
from rest_framework import generics
​
# class StudentSonAPIView(generics.ListAPIView, generics.CreateAPIView):
class StudentSonAPIView(generics.ListCreateAPIView):
    """
    查询所有数据
    添加一条数据
    """
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
​
# class StudentInfoSonAPIView(generics.RetrieveAPIView, generics.UpdateAPIView, generics.DestroyAPIView):
# class StudentInfoSonAPIView(generics.RetrieveUpdateAPIView, generics.DestroyAPIView):
# class StudentInfoSonAPIView(generics.RetrieveDestroyAPIView, generics.UpdateAPIView):
class StudentInfoSonAPIView(generics.RetrieveUpdateDestroyAPIView):
    """
    查询一条数据
    更新一条数据
    删除一条数据
    """
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
  • 视图代码
from django.urls import path, re_path
from . import views
​
urlpatterns = [
    path("student4/", views.StudentSonAPIView.as_view()),
    re_path(r"^student4/(?P<pk>\d+)/$", views.StudentInfoSonAPIView.as_view()),
]

# DRF的视图集

使用视图集可以将视图相关的逻辑代码和http请求封装到一个类中,不再限制必须使用http请求的get/post...等作为视图方法名了,而是允许自定义视图方法名。

# ViewSet

ViewSet主要通过继承ViewSetMixin来重写了APIView的as_view()方法,允许开发者在调用as_view时传递字典参数,把视图方法名与HTTP请求动作进行关联映射

from rest_framework.response import Response
from rest_framework import status
from rest_framework.viewsets import ViewSet

from drfstu import baseserializer
from drfstu.models import DRFStudent


class StudentBaseViewSet(ViewSet):
    
    def getlist(self, request):
        instance = DRFStudent.objects.all()
        serializer = baseserializer.StudentBaseSerializer(instance, many=True)
        return Response(serializer.data)

    def getone(self, request, pk):
        instance = DRFStudent.objects.get(id=pk)
        serializer = baseserializer.StudentBaseSerializer(instance)
        return Response(serializer.data)

    def gettop(self, request):
        instance = DRFStudent.objects.all().order_by("-age")[:3]
        serializer = baseserializer.StudentBaseSerializer(instance, many=True)
        return Response(serializer.data)

    def add(self, request):
        serializer = baseserializer.StudentBaseSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors)

    def update(self, request, pk):
        instance = DRFStudent.objects.get(id=pk)
        serializer = baseserializer.StudentBaseSerializer(instance, 
		                                                  data=request.data, 
														  partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors)

    def delete(self, request, pk):
        instance = DRFStudent.objects.get(id=pk)
        instance.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
urlpatterns = [
    path("stu/", views.StudentBaseViewSet.as_view({
        'get': 'getlist',
        'post': 'add',
    })),
    re_path("^stu/(?P<pk>\d+)/$", views.StudentBaseViewSet.as_view({
        'get': 'getone',
        'put': 'update',
        'delete': 'delete'
    })),
    path("stu/top/", views.StudentBaseViewSet.as_view({
        'get': 'gettop',
    })),
]

# GenericViewSet

GenericViewSet(通用视图集)继承自GenericAPIView和ViewSetMixin

from rest_framework.response import Response
from rest_framework import status
from rest_framework.viewsets import GenericViewSet

from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent


class StudentBaseViewSet(GenericViewSet):
    queryset = DRFStudent.objects.all()
    serializer_class = StudentBaseSerializer

    def getlist(self, request):
        instance = self.get_queryset()
        serializer = self.get_serializer(instance, many=True)
        return Response(serializer.data)

    def getone(self, request, pk):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

    def gettop(self, request):
        instance = self.get_queryset().order_by('-age')[:5]
        serializer = self.get_serializer(instance, many=True)
        return Response(serializer.data)

    def add(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors)

    def update(self, request, pk):
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors)

    def delete(self, request, pk):
        instance = self.get_object()
        instance.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
urlpatterns = [
    path("stu/", views.StudentBaseViewSet.as_view({
        'get': 'getlist',
        'post': 'add',
    })),
    re_path("^stu/(?P<pk>\d+)/$", views.StudentBaseViewSet.as_view({
        'get': 'getone',
        'put': 'update',
        'delete': 'delete'
    })),
    path("stu/top/", views.StudentBaseViewSet.as_view({
        'get': 'gettop',
    })),
]

# GenericViewSet+Mixins

from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet

from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent
from rest_framework import mixins


class StudentBaseViewSet(GenericViewSet,
                         mixins.ListModelMixin,
                         mixins.RetrieveModelMixin,
                         mixins.CreateModelMixin,
                         mixins.UpdateModelMixin,
                         mixins.DestroyModelMixin):

    queryset = DRFStudent.objects.all()
    serializer_class = StudentBaseSerializer
	
    def gettop(self, request):
        instance = self.get_queryset().order_by('-age')[:5]
        serializer = self.get_serializer(instance, many=True)
        return Response(serializer.data)

    # 避免与 self.update 方法重名,不建议用 update
    def put(self, request, pk):
        return self.update(request, pk, partial=True)
urlpatterns = [
    path("stu/", views.StudentBaseViewSet.as_view({
        'get': 'getlist',
        'post': 'add',
    })),
    re_path("^stu/(?P<pk>\d+)/$", views.StudentBaseViewSet.as_view({
        'get': 'getone',
        'put': 'put',
        'delete': 'delete'
    })),
    path("stu/top/", views.StudentBaseViewSet.as_view({
        'get': 'gettop',
    })),
]

# ModelViewSet 模型视图集

继承自GenericViewSet,同时还继承了5个视图扩展类(ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin)

from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent


class StudentBaseViewSet(ModelViewSet):

    queryset = DRFStudent.objects.all()
    serializer_class = StudentBaseSerializer

    def gettop(self, request):
        instance = self.get_queryset().order_by('-age')[:5]
        serializer = self.get_serializer(instance, many=True)
        return Response(serializer.data)


    def put(self, request, pk):
        return self.update(request, pk, partial=True)
urlpatterns = [
    path("stu/", views.StudentBaseViewSet.as_view({
        'get': 'getlist',
        'post': 'add',
    })),
    re_path("^stu/(?P<pk>\d+)/$", views.StudentBaseViewSet.as_view({
        'get': 'getone',
        'put': 'put',
        'delete': 'delete'
    })),
    path("stu/top/", views.StudentBaseViewSet.as_view({
        'get': 'gettop',
    })),
]

# DRF的路由集

对于视图集ViewSet,除了手动指明请求方式与视图方法之间的对应关系外,还可以使用Routers来快速实现路由信息。如果是非视图集,不需要使用路由集routers

# SimpleRouter 和 DefaultRouter

# REST framework提供了两个router类,使用方式基本一致 SimpleRouter、DefaultRouter

# 区别是:SimpleRouter用于线上运营项目,DefaultRouter 本地开发,项目上线前
# DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据
from django.urls import path, re_path
from rest_framework import routers
from drfstu import views

router = routers.SimpleRouter()
# router = routers.DefaultRouter()

# prefix 该视图集的路由前缀
# viewset 视图集
# basename 路由别名的前缀
router.register('stu', views.StudentBaseViewSet, basename='stu')

urlpatterns = [
    path("stu/top/", views.StudentBaseViewSet.as_view({
        'get': 'gettop',
    })),
] + router.urls

print("simple_router=",  router.urls)
print("urlpatterns=", urlpatterns)
print('simple_router=', router.urls)

# action 装饰器自动根据视图自定义方法生成路由

action装饰器可以接收三个参数:

methods  # 声明被action装饰的视图方法允许外界通过哪些HTTP请求访问,默认是get

detail   # 声明被action装饰的视图方法名生成自定义路径,是否使用当前类作为路径的尾缀
         # detail=True 表示将来生成url格式是<prefix>/<pk>/<url_path>/
         # detail=False 表示将来生成url格式是<prefix>/<url_path>/
		 
url_path # 声明该action的路由尾缀。默认就是视图方法名
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent


class StudentBaseViewSet(ModelViewSet):

    queryset = DRFStudent.objects.all()
    serializer_class = StudentBaseSerializer

    # 用于告诉路由集该如何生成url路径的,是否要加上pk值等等
    @action(methods=['GET'], detail=False, url_path='top')
    def gettop(self, request):
        instance = self.get_queryset().order_by('-age')[:5]
        serializer = self.get_serializer(instance, many=True)
        return Response(serializer.data)
from rest_framework import routers

router = routers.SimpleRouter()
# router = routers.DefaultRouter()

router.register('stu', views.StudentBaseViewSet, basename='stu')

# DRF权限认证

# 认证 Authentication

  • 可以在配置文件中配置全局默认的认证方式/认证流程,见的认证方式:cookie、session、token
  • 在drf的默认配置文件中,提供了DEFAULT_AUTHENTICATION_CLASSES配置项进行认证类的注册
# rest_framework/settings.py

REST_FRAMEWORK = {
    # 配置认证方式的选项
    'DEFAULT_AUTHENTICATION_CLASSES': (
	    # session认证,从request.user提取用户认证
        'rest_framework.authentication.SessionAuthentication',
		# basic认证[基于请求头,使用用户ID与密码来完成用户认证]
        'rest_framework.authentication.BasicAuthentication', 
    )
}
  • 自定义全局认证类 settings.py
# user/authentication.py

from django.contrib.auth import get_user_model
from rest_framework import authentication

# drf中所有的认证类必须直接或间接继承于authentication.BaseAuthentication类
class CustomAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        User = get_user_model()
        # request._request.headers.get("键")     drf从django的请求头中提取数据
        user_id = request.query_params.get('user')
        password = request.query_params.get('password')

        if user_id is None or password is None:
            return None
        try:
            user = User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None
        
        ret = user.check_password(password)
        if ret is False:
            return None
        return (user, None)
		
# 在项目中注册并使用上面自定义的认证类 settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        "user.authentication.CustomAuthentication",
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
}
  • 自定义局部认证类 authentication_classes
from django.http import HttpResponse
from django.views import View

from user import authentication

# 注意一定要继承 APIView !!!才有校验功能
class UserView(View):
    authentication_classes = [authentication.CustomAuthentication]

    def get(self, request):
        user = request.user
        # 没有登陆,或request.user无法获取认证信息,则request.user是匿名用户:AnonymousUser
        # 已经登陆,经过中间件的处理以后,request.user的值是当前登陆用户的模型对象
        print(user)
        if user.id:
            print(user.username, user.first_name, user.last_name)
        return HttpResponse("get请求")

# 权限 Permissions

  • 权限控制可以限制用户对于视图的访问和对于具有模型对象的访问
# 在APIView视图的dispatch()方法中调用initial方法中先进行视图访问权限的判断
self.check_permissions(request)

# 在GenericAPIView通过get_object()获取具体模型对象时,会进行模型对象访问权限的判断
self.check_object_permissions(self.request, obj)
  • 可以在配置文件restframework/settings.py中默认的全局设置了权限管理类,源码:
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
}
  • 如果要在项目覆盖默认配置rest_framework/settings.py的设置,则可以在项目配置文件中,settings.py
# AllowAny 表示允许任何用户访问站点视图
# IsAuthenticated 只允许登陆用户[包括管理员]访问
# IsAdminUser 只允许管理员访问【游客、普通用户不能访问】
# IsAuthenticatedOrReadOnly 登录用户可以操作数据,但是游客只能看不能修改
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}
  • 也可以在视图类中通过类属性permission_classes来进行局部设置
from rest_framework import permissions
from rest_framework.views import APIView

class UserView(APIView):
    # permission_classes = [permissions.AllowAny]
    # permission_classes = [permissions.IsAuthenticated]
    # permission_classes = [permissions.IsAdminUser]
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]  

# 自定义权限类

需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部

class CustomPermission(BasePermission):
    def has_permission(self, request, view):
        """
            判断视图访问权限
            request drf提供的HTTP请求对象
            view    当前被访问的视图对象
            return  必须是True或者False,True表示允许访问 False不允许访问
        """
        if int(request.query_params.get("level", 0)) >= 10:
            return True

    def has_object_permission(self, request, view, obj):
        """
            判断对象访问权限
            request drf提供的HTTP请求对象
            view    当前被访问的视图对象
            obj     当前视图中基于get_object调用的模型对象
            return  必须是True或者False,True表示允许访问 False不允许访问
        """
        return True
class LevelAPIView(APIView):
    permission_classes = [CustomPermission]

    def get(self, request):
        return Response({"msg": "VIP10->专属功能"})

# 限流 Throttling

  • 可以在配置文件中进行全局配置,针对整个站点所有视图
# 注意缓存的配置
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/0",
        "TIMEOUT": 300,
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

REST_FRAMEWORK = {
    # 限流全局配置类
    'DEFAULT_THROTTLE_CLASSES': [
        # 'rest_framework.throttling.AnonRateThrottle', # 未登录认证的用户访问
        # 'rest_framework.throttling.UserRateThrottle' # 已登录认证的用户访问
		# 'rest_framework.throttling.ScopedRateThrottle' # 针对限流命名空间进行限制
    ],
    # 访问频率的全局配置,默认是None,即不限制,支持使用 s(秒) m(分) h(时) d(天)来指明限流周期
    'DEFAULT_THROTTLE_RATES': {
        # 'anon': '2/day', # 针对未登录用户的访问频率进行限制
        # 'user': '5/day',  # 针对登录用户的访问频率进行限制
		'member': '2/min', # 针对登录用户的访问频率进行限制
    },
}
  • 也可以在具体视图类中通过类属性throttle_classess来局部配置
class UserAPIView(APIView):
    # AnonRateThrottle
    # 限制未登录用户,使用IP区分用户
	# 使用配置项DEFAULT_THROTTLE_RATES['anon'] 来设置频次
	
    # UserRateThrottle
    # 限制登录用户,使用User模型的id主键来区分
	# 使用配置项DEFAULT_THROTTLE_RATES['user'] 来设置频次
	
    # throttle_classes = [UserRateThrottle, AnonRateThrottle]

    # ScopedRateThrottle
    # 限制用户对于每个视图类的访问频次,使用ip或user id
	# 使用视图类中的throttle_scope设置限流频次的变量名
    # 假设是member,则可以使用配置项DEFAULT_THROTTLE_RATES['member']来设置频次
    throttle_classes = [ScopedRateThrottle]
    throttle_scope = 'member'

    def get(self, request):
        return Response("ok")

# DRF过滤查询排序

pip install django-filter

# 使用第三方过滤类django-filters一定要在配置文件中配置
INSTALLED_APPS = [
    # ....
    'django_filters',
]
  • 全局配置
# 1、在配置文件中增加过滤器类的全局设置
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend', # 过滤
        'rest_framework.filters.OrderingFilter', # 排序
        'rest_framework.filters.SearchFilter' # 查询
    ],
}

# 2、在具体的视图类中添加类属性 filterset_fields,指定可以过滤的字段
class StudentBaseViewSet(ModelViewSet):
    queryset = DRFStudent.objects.all()
    serializer_class = StudentBaseSerializer
    filterset_fields = ['mobile', 'age'] # 用于 list 中 !!!
	search_fields = ['name', 'mobile'] # 用于 list 中 !!!
	ordering_fields = ['age', 'id'] # 用于 list 中 !!!
	
	# 单个字段过滤
	# http://127.0.0.1:8000/component/list/?age=18
	# http://127.0.0.1:8000/component/list/?sex=1
	# 多个字段过滤
	# http://127.0.0.1:8000/component/list/?age=18&sex=1

    # 查询
	# http://127.0.0.1:8000/component/order/?search=example
	# 会在 search_fields 指定的字段中搜索包含 example 的记录
	
	# 排序
	# http://127.0.0.1:8000/component/order/?ordering=-id
	# -id 表示针对id字段进行倒序排序
	# id  表示针对id字段进行升序排序
  • 局部设置
# 在视图类中设置类属性filter_backends调用的过滤器类

from rest_framework.generics import ListAPIView
from django_filters.rest_framework import DjangoFilterBackend
​
class FilterAPIView(ListAPIView):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
	
    # 在当前视图中设置过滤器、排序器、查询器
    filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
	
    filterset_fields = ["sex", "age"] # 过滤
	search_fields = ['name', 'mobile'] # 查询
	ordering_filters = ["id", "age"] # 排序

注意

  • 过滤查询排序共用配置项filter_backends,所以要么一起全局配置,要么一起局部配置
  • 局部配置项 filter_backends 会自动覆盖全局配置的 DEFAULT_FILTER_BACKENDS

# DRF的分页器

# 全局配置分页器

  • 在配置文件settings.py中进行全局分页配置
REST_FRAMEWORK = {
	# 以page参数作为分页参数
	'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
	# 以limit参数作为分页参数
	'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.LimitOffsetPagination',
	'PAGE_SIZE': 5, # 每页数目,如果不设置,则没有进行分配
}
  • 如果在settings.py配置文件中设置了全局分页,那么凡是调用了ListModelMixin的list()都会自动分页
{
    "count": 16,   // 本次分页的总数据量
    "next": "http://127.0.0.1:8000/component/page/?page=3",  // 下一页数据所在的地址
    "previous": "http://127.0.0.1:8000/component/page/",         // 上一页数据所在的地址
    "results": [    // 当前页数据的列表项
        {
            "id": 6,
            "name": "xiaoming",
            "sex": true,
            "age": 20,
            "classmate": "303",
            "description": "hello world"
        },
       // .....
    ]
}
  • 如果项目中只有少数部分的不需要分页,则可以在少部分的视图类中关闭分页功能
class PageAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    pagination_class = None  # 关闭来自全局配置的分页设置,设置当前列表视图不需要分页

# 自定义分页器 PageNumberPagination

  • 创建一个自定义分页器类
from rest_framework.pagination import PageNumberPagination
​
class StudentPagination(PageNumberPagination):
    page_size = 10 # 每页数据量
    page_query_param = "page" # 地址栏代表页码的参数
    page_size_query_param = "size"  # 地址栏代表数据量的参数
    max_page_size = 10  # 限制客户端修改的每页数据量的最大值
  • 视图中使用自定义分页类
from . import paginations
​
class studentAPIView(ListAPIView):
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
	
    # pagination_class的值只能是一个分页对象,
    pagination_class = paginations.StudentPagination
   
	# 访问地址:http://127.0.0.1:8000/component/student/?page=3
	# 访问地址:http://127.0.0.1:8000/component/student/?page=3&size=5

# 自定义分页器 LimitOffsetPagination

  • 创建一个自定义分页器类
rom rest_framework.pagination import LimitOffsetPagination
# LimitOffsetPagination,以数据库查询的limit和offset数值作为分页条件
# limit=10&offset=0   第1页
# limit=10&offset=10  第2页
​
​
class StudentLimitPagination(LimitOffsetPagination):
    default_limit = 10 # 默认的每页数据量
    offset_query_param = "offset"  # 地址栏代表偏移量的参数,类似上面的page
    limit_query_param = "limit" # 地址栏代表数据量的参数,类似上面的size
    max_limit = 10  # 限制客户端修改的每页数据量的最大值
  • 视图中使用自定义分页类
from . import paginations
​
class Student2APIView(ListAPIView):
    """数据分页: LimitOffsetPagination"""
    queryset = models.Student.objects.all()
    serializer_class = serializers.StudentModelSerializer
    pagination_class = paginations.StudentLimitPagination
    
  # 访问地址:http://127.0.0.1:8000/component/student2/?limit=3

# DRF异常处理

  • REST framework本身在APIView提供了异常处理,也可以额外添加属于开发者自己的逻辑代码
# exception.py

from django.core.exceptions import ObjectDoesNotExist
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import exception_handler

"""
全局异常处理
    1. 全局异常处理函数的参数列表必须是两个,分别是exc和context
    2. 全局异常处理函数的返回值必须是一个Response对象
    3. 全局异常处理函数必须要在settings.py文件中进行注册
    4. 全局异常处理函数的优先级要高于局部异常处理函数
    5. exc 本次发生的异常实例对象
    6. context 本次发生时的执行上下文, 包含了本次请求的request对象
"""

def custom_exception_handler(exc, context):
    # 自定义异常处理函数
    print(context)
    print(context['request'].method)
    # 先调用drf的异常处理函数,让drf先处理
    response = exception_handler(exc, context)
    # 如果drf无法处理,那么我们自己处理
    if response is None:
        if isinstance(exc, ZeroDivisionError):
            response = Response({"error": "除数不能为0"}, 
			                     status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        elif isinstance(exc, ObjectDoesNotExist):
            response = Response({"error": "数据不存在"}, 
			                    status=status.HTTP_404_NOT_FOUND)
    return response

  • 在配置文件中声明自定义的异常处理
REST_FRAMEWORK = {
	# 如果未声明自定义异常的话,drf会采用默认的方式,使用自己封装的异常处理函数
    # "EXCEPTION_HANDLER": "rest_framework.views.exception_handler",
    "EXCEPTION_HANDLER": "Django.exceptions.custom_exception_handler", # 自定义异常处理函数
}
  • 视图代码
def get(self, request):
	# print(1/0) # 这句代码会抛出python异常 ZeroDivisionError
	DRFStudent.objects.get(pk=60000)
	return Response({"msg": "ok"})
  • REST framework内置异常处理类
异常类 描述
APIException drf的所有异常的父类
ParseError 解析错误
AuthenticationFailed 认证失败
NotAuthenticated 尚未认证
PermissionDenied 权限拒绝
NotFound 404 未找到
MethodNotAllowed 请求方式不支持
NotAcceptable 要获取的数据格式不支持
UnsupportedMediaType 不支持的媒体格式
Throttled 超过限流次数
ValidationError 校验失败

# DRF接口文档

# coreapi 的使用

  • 设置接口文档访问路径
pip install coreapi


INSTALLED_APPS = [
    'coreapi',
]
REST_FRAMEWORK = {
    # 配置自动生成接口文档的模式
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}
  • 在总路由中添加接口文档路径,访问地址:http://127.0.0.1:8000/docs/
pip install coreapi

REST_FRAMEWORK = {
    # 配置自动生成接口文档的模式
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}
  • 文档描述说明的定义位置
# 单一方法的视图,可直接使用类视图的文档字符串
class StudentListView(ListAPIView):
    """
    返回所有学生信息
    """
	
# 包含多个方法的视图,在类视图的文档字符串中,分开方法定义
from rest_framework.generics import ListCreateAPIView
class DocumentAPIView(ListCreateAPIView):
    """
    get: 获取所有学生信息
    post: 添加学生信息
    """
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    
from rest_framework.generics import RetrieveUpdateDestroyAPIView
class Document1APIView(RetrieveUpdateDestroyAPIView):
    """
    get: 查询一个学生信息
    put: 更新一个学生信息
    patch: 更新一个学生信息[部分字段]
    delete: 删除一个学生信息
    """
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
	
# 对于视图集ViewSet,在类视图的文档字符串中定义,但是应使用action名称区分
from rest_framework.decorators import action
from demo.serializers import StudentLoginModelSerializer
​
class DocumentAPIView(ModelViewSet):
    """
    list: 获取所有学生信息
    create: 添加学生信息
    read: 查询一个学生信息
    update: 更新一个学生信息
    partial_update: 更新一个学生信息[部分字段]
    delete: 删除一个学生信息
    login: 学生登录
    """
    queryset = Student.objects.all()def get_serializer_class(self):
        if self.action == "login":
            return StudentLoginModelSerializer
        return StudentModelSerializer
    
    @action(methods=["POST"], detail=False)
    def login(self, request):
        return Response("ok")

注意

  • 视图集ViewSet中的retrieve名称,在接口文档网站中叫做read
  • 参数的Description需要在模型类或序列化器类的字段中以help_text选项定义
class Student(models.Model):
    ...
    age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
    ...
	
class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"
        extra_kwargs = {
            'classmate': {
                'help_text': '班级'
            }
        }

# drf-yasg 的使用

drf-yasg (opens new window)是生成Swagger风格的一个drf模块,Swagger在线编辑器 (opens new window)

  • 设置接口文档访问路径
pip install drf-yasg


INSTALLED_APPS = [
    'drf_yasg',
]
REST_FRAMEWORK = {
    # 配置自动生成接口文档的模式
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}
  • 在总路由设置,访问地址:http://127.0.0.1:8000/doc/

from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls
​
​
# yasg的视图配置类,用于生成api
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
​
# 接口文档的视图配置
schema_view = get_schema_view(
    openapi.Info(
        title="drf接口文档",  # 站点标题,必填
        default_version='v1.0,0',  # api版本,必填
        description="描述信息",  # 站点描述
        terms_of_service='htttp://www.1ge0.com/',   # 团队博客网址
        contact=openapi.Contact(name="zero", url="http://www.1ge0.com/", email=""),
        license=openapi.License(name="开源协议名称", url="开源协议网地") # 协议
    ),
    public=True, # 是否外部站点
    # permission_classes=(rest_framework.permissions.AllowAny)  # 权限类
)
​
urlpatterns = [
    path('admin/', admin.site.urls),
    path('doc/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger'),
]
  • yasg提供给开发者编写视图接口的描述信息方式与前面学习的coreapi一样
class UserAPIView(ModelViewSet):
    """
    list: 返回所有学生信息
    create: 创建一个学生信息
    read: 读取一个学生信息
    update: 更新一个学生信息
    partial_update: 更新一个学生信息的单个字段
    delete: 删除一个学生
    """
    queryset = models.UserModel.objects.all()
    serializer_class = serializers.UserModelSerializer

# 代码解读

# 序列化器(serializer)的作用是把模型实例转换为可传输的数据格式,像 JSON
class ExampleSerializer(serializers.ModelSerializer):
    # SerializerMethodField:自定义字段方法,给序列化结果添加额外字段
    is_confirmed = serializers.SerializerMethodField()
	
	# self 代表类的实例对象
	# 在序列化模型实例时,序列化器会自动调用 get_is_confirmed 方法,把计算结果添加到序列化数据里
	def get_is_confirmed(self, instance):
	
	    # 从序列化器的上下文获取当前请求的用户对象
		# self.context:DRF 的序列化器有一个 context 属性,这是一个字典
		# 用于在序列化器和视图之间传递额外的数据。视图通常会把请求对象、视图实例等信息放到 context 里
	    user = self.context.get("request").user
		# instance.states 是 Django 模型关联查询的一部分
	    states = instance.states.all()
	    return states.count() > 0
# 这是一个 ListCreateAPIView,用于处理 Example 模型的列表和创建操作
class ExampleList(generics.ListCreateAPIView):
    # 指定视图使用的序列化器类。它定义了如何将模型实例
	# 如 Example 模型转换为 JSON 或其他格式(序列化)
	# 以及如何将客户端发送的数据转换为模型实例(反序列化)
    serializer_class = ExampleSerializer
	# 权限控制,来确保只有经过身份验证的用户才能访问,并且用户必须是项目管理员或项目成员(只读)
	# 是 自动调用 的,你不需要手动调用它
	# DRF 的视图类(如 APIView、GenericAPIView 等)会在处理请求时自动检查 permission_classes 中定义的权限
    permission_classes = [IsAuthenticated & (IsProjectAdmin | IsProjectStaffAndReadOnly)]
	# 是一个元组,用于指定视图使用的过滤后端。它定义了哪些过滤功能可用
	# DjangoFilterBackend:提供基于字段的精确过滤功能,允许客户端通过 URL 参数对数据进行过滤,例如:?text=example 会过滤 text 字段等于 example 的记录
	# filters.SearchFilter:提供全文搜索功能,允许客户端通过 search 参数对指定字段进行搜索,例如:/examples/?search=example 会在 search_fields 指定的字段中搜索包含 example 的记录
    # filters.OrderingFilter:提供排序功能,允许客户端通过 ordering 参数对数据进行排序,例如:/examples/?ordering=created_at 会按 created_at 字段升序排序
	filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
	# 是一个元组,用于指定允许排序的字段
    ordering_fields = ("created_at", "updated_at", "score")
	# 是一个元组,用于指定允许搜索的字段
    search_fields = ("text", "filename")
	# filter_backends、ordering_fields 和 search_fields 的调用是由 DRF 的 视图类 和 过滤后端 自动处理的。它们会在 请求处理过程中 被调用
    # 具体来说,ListCreateAPIView 的父类 GenericAPIView 会在 get_queryset 方法返回查询集后,自动应用 filter_backends 中定义的过滤后端

    model = Example
	
	# 指定视图使用的自定义过滤器类,通常与 DjangoFilterBackend 一起使用,允许客户端通过 URL 参数对数据进行过滤
	# 通过 filterset_class,你可以定义更复杂的过滤逻辑,而不仅仅是简单的字段匹配
    filterset_class = ExampleFilter
	
	   @property
	    def project(self):
		    # 通过 project 属性获取当前项目,并在 get_queryset 方法中根据用户的角色返回不同的查询集
			# pk 是主键(Primary Key)的缩写,通常对应数据库表中的 id 字段
			# 从 URL 中提取的参数。例如,如果 URL 是 /projects/123/examples/,那么 project_id 就是 123
			# 用户访问某个 URL 时,Django 会解析 URL 并将参数存储在 self.kwargs 中
	        return get_object_or_404(Project, pk=self.kwargs["project_id"])
	
	    def get_queryset(self):
		    # self.project:是一个 属性,通过 @property 装饰器定义
			# self.request.user:一个非常重要的属性,它表示当前请求的用户对象。它的作用是根据用户的身份和权限来控制访问和操作
			# self.request.user.is_authenticated:检查用户是否登录
			# self.request.user.username:获取用户信息
			# self.request.user.has_perm('example.can_edit'):权限检查
			
	        member = get_object_or_404(Member, project=self.project, user=self.request.user)
	        if member.is_admin():
	            return self.model.objects.filter(project=self.project)
	        # assignments__assignee=self.request.user 是一个 跨关系查询 的语法
			# 它表示通过 assignments 关系查找 assignee 字段等于当前用户的记录
	        queryset = self.model.objects.filter(project=self.project, assignments__assignee=self.request.user)
	        if self.project.random_order:
	            queryset = queryset.order_by("assignments__id")
	        return queryset
	
	    # 创建操作:自动将新创建的 Example 实例与当前项目关联
	    def perform_create(self, serializer):
	        serializer.save(project=self.project)
	
	    # 批量删除:支持通过 ids 参数指定要删除的实例
	    def delete(self, request, *args, **kwargs):
	        queryset = self.project.examples
	        delete_ids = request.data["ids"]
	        if delete_ids:
	            queryset.filter(pk__in=delete_ids).delete()
	        else:
	            queryset.all().delete()
	        return Response(status=status.HTTP_204_NO_CONTENT)

注意

Project 是一个 Django 模型类,而不是对象或数据列表。它表示数据库中的 Project 表

# ExampleFilter 的定义

import django_filters
from examples.models import Example

class ExampleFilter(django_filters.FilterSet):
    # 定义一个基于 text 字段的过滤器,支持 icontains 查找(不区分大小写的包含匹配)
    text = django_filters.CharFilter(lookup_expr='icontains')
	# 定义一个基于 created_at 字段的过滤器
	# 支持日期范围过滤(如 ?created_at_after=2023-01-01&created_at_before=2023-12-31)
    created_at = django_filters.DateFromToRangeFilter()
    confirmed = BooleanFilter(field_name="states", method="filter_by_state")
	# 用于处理布尔值(True/False)的过滤,通常用于过滤字段值为布尔类型的数据
	# field_name 参数指定要过滤的字段,method 参数指定用于过滤的 自定义方法
    confirmed = BooleanFilter(field_name="states", method="filter_by_state")
	# 用于处理字符串类型的过滤,通常用于过滤字段值为字符串类型的数据
	# field_name 参数指定要过滤的字段,method 参数指定用于过滤的 自定义方法
    label = CharFilter(method="filter_by_label")

    # queryset:当前的查询集,field_name:过滤器的字段名,value:客户端传递的过滤值
    def filter_by_state(self, queryset, field_name, is_confirmed: bool):
        queryset = queryset.annotate(
            num_confirm=Count(
                expression=field_name,
                filter=Q(**{f"{field_name}__confirmed_by": self.request.user})
                | Q(project__collaborative_annotation=True),
            )
        )
        if is_confirmed:
            queryset = queryset.filter(num_confirm__gte=1)
        else:
            queryset = queryset.filter(num_confirm__lte=0)
        return queryset

    def filter_by_label(self, queryset: QuerySet, field_name: str, label: str) -> QuerySet:
        queryset = queryset.filter(
            Q(categories__label__text=label)
            | Q(spans__label__text=label)
            | Q(relations__type__text=label)
            | Q(bboxes__label__text=label)
            | Q(segmentations__label__text=label)
        )
        return queryset
	# 指定过滤器关联的模型和字段
    class Meta:
        model = Example
        fields = ['text', 'created_at']
		
# 使用
# 建了一个 ExampleFilter 的对象
# 这个对象用于对查询集(queryset)进行过滤,过滤规则由 data 参数指定,而 request 参数则提供了请求的上下文信息
# ExampleFilter 类确实没有显式定义三个参数的构造方法(__init__)
# 但它的父类 django_filters.FilterSet 提供了这些参数的默认处理逻辑
# 因此,即使 ExampleFilter 类中没有显式定义构造方法
# 你仍然可以传递 data、queryset 和 request 参数,这些参数会被父类 FilterSet 处理
ExampleFilter(data=data, queryset=self.queryset, request=self.request)

# SimpleUI

# 安装注册

  • pip安装
pip install django-simpleui
  • 将simpleui加入到INSTALLED_APPS里去,放在第一行,也就是django自带admin的前面
INSTALLED_APPS = [
      'simpleui', # 注意这里
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
      ...     
]

# 常用配置

  • 设置语言,替换logo,关闭第三方广告,设置主题
# 更改默认语言为中文
LANGUAGE_CODE = 'zh-hans'

# 去掉默认Logo或换成自己Logo链接
SIMPLEUI_LOGO = 'https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/globe-512.png'

# 隐藏右侧SimpleUI广告链接和使用分析
SIMPLEUI_HOME_INFO = False 
SIMPLEUI_ANALYSIS = False 

# 设置默认主题,指向主题css文件名。Admin Lte风格
SIMPLEUI_DEFAULT_THEME = 'admin.lte.css'

# 设置默认主题,指向主题css文件名。Element-ui风格
SIMPLEUI_DEFAULT_THEME = 'element.css'

# 设置默认主题,指向主题css文件名。layui风格
SIMPLEUI_DEFAULT_THEME = 'layui.css'

# 设置默认主题,指向主题css文件名。紫色风格
SIMPLEUI_DEFAULT_THEME = 'purple.css'
  • 修改管理后台的名称 admin.py
admin.site.site_header = '自动化运维后台管理'
admin.site.site_title = '自动化运维后台管理'
admin.site.index_title = '自动化运维后台管理'
admin.site.register(NormalUser, UserAdmin)
  • 自定义或第三方APP名和模型名修改成中文
# app.py
from django.apps import AppConfig

class App01Config(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'app01'
    # 定义这个配置即可
    verbose_name = '超级用户管理'
	
# models.py
from django.db import models

class NormalUser(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type = models.IntegerField(choices=((1, '注册用户'), (2, '普通管理员'), (3, '超级管理员')), default=1)

    class Meta:
        # 定义这个配置
        verbose_name_plural = '用户表'

    def __str__(self):
        return self.username

# 自定义菜单

左侧可折叠菜单是Simple UI系统默认菜单,根据已注册的应用和模型自动生成,其中父级菜单是App名,子菜单一般是所属App的各个模型名。SimpleUI甚至会自动为你分配默认图标,比如本例的tasks的应用使用了font-awsome的fa fa-tasks。在大多数情况下,Simple UI系统默认菜单不能满足需求,这时你就需要自定义菜单了,比如添加新的选项或给菜单选项分配新的图标。

# 修改settings.py
SIMPLEUI_CONFIG = {
     # 是否使用系统默认菜单。
    'system_keep': False,
    
     # 用于菜单排序和过滤, 不填此字段为默认排序和全部显示。 空列表[] 为全部不显示.
    'menu_display': ['任务管理', '权限认证'],
    
    # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时刷新展示菜单内容。
    # 一般建议关闭。
    'dynamic': False,
    'menus': [
        {
            'app': 'auth',
            'name': '权限认证',
            'icon': 'fas fa-user-shield',
            'models': [
                {
                'name': '用户列表',
                'icon': 'fa fa-user',
                'url': 'auth/user/'
                },
                {
                    'name': '用户组',
                    'icon': 'fa fa-th-list',
                    'url': 'auth/group/'
                }
            ]
        },

        {
            'name': '任务管理',
            'icon': 'fa fa-th-list',
            'models': [
                {
                'name': '任务列表',
                # 注意url按'/admin/应用名小写/模型名小写/'命名。  
                'url': '/admin/tasks/task/',
                'icon': 'fa fa-tasks'
                },
            ]
        },
    ]
}

# 自定义首页

SimpleUI默认首页由快捷链接和最近动作组成,我们可以将其隐藏,并将其链接到其它url

# settings.py

# 隐藏首页的快捷操作和最近动作
SIMPLEUI_HOME_QUICK = False 
SIMPLEUI_HOME_ACTION = False

# 修改左侧菜单首页设置
SIMPLEUI_HOME_PAGE = 'https://www.baidu.com'  # 指向页面
SIMPLEUI_HOME_TITLE = '百度欢迎你!' # 首页标题
SIMPLEUI_HOME_ICON = 'fa fa-code' # 首页图标

# 设置右上角Home图标跳转链接,会以另外一个窗口打开
SIMPLEUI_INDEX = 'https://www.baidu.com'

# 自定义成自己设计的首页

实际应用中后台首页通常是控制面板,需要用图表形式展示各种关键数据,这时就需要重写首页了。这里主要有两种实现方法。第一种是重写simpleui自带的home.html, 另一种自己编写一个控制面板的页面,然后设置首页指向它, 个人倾向于第二种, 因为它完全不涉及改动simpleui的源码。

我们现在开始使用Django编写一个用于显示控制面板的页面,用于在首页显示注册用户数量及任务数量。

  • URL路由及对应的视图函数如下所示:
# app01/urls.py
urlpatterns = [
    path('dashboard/', dashboard, name='dashboard'),
]

# app01/views.py
def dashboard(request):
    user_count = User.objects.count()
    book_count = Book.objects.count()

    context = {'user_count': user_count, 'book_count': book_count}
    return render(request, 'dashboard.html', context)
# app01/urls.py
urlpatterns = [
    path('dashboard/', dashboard, name='dashboard'),
]

# app01/views.py
def dashboard(request):
    user_count = User.objects.count()
    book_count = Book.objects.count()

    context = {'user_count': user_count, 'book_count': book_count}
    return render(request, 'dashboard.html', context)
  • 模板文件
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>控制面板</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Theme style -->
    <link rel="stylesheet"
          href="https://adminlte.io/themes/AdminLTE/bower_components/bootstrap/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://adminlte.io/themes/AdminLTE/dist/css/AdminLTE.min.css">
</head>

<body>
<div class="wrapper">
    <!-- Main content -->
    <section class="content">
        <div class="container-fluid">
            <!-- Small boxes (Stat box) -->
            <div class="row">

                <div class="col-sm-3">
                    <!-- small box -->
                    <div class="small-box bg-info">
                        <div class="inner">
                            <h3>{{ user_count }}</h3>

                            <p>用户总数</p>
                        </div>
                        <div class="icon">
                            <i class="ion ion-bag"></i>
                        </div>
                        <a href="#" class="small-box-footer">更多信息 <i class="fas fa-arrow-circle-right"></i></a>
                    </div>
                </div>
                <!-- ./col -->
                <div class="col-sm-3">
                    <!-- small box -->
                    <div class="small-box bg-success">
                        <div class="inner">
                            <h3>{{ book_count }}</h3>

                            <p>图书总数</p>
                        </div>
                        <div class="icon">
                            <i class="ion ion-stats-bars"></i>
                        </div>
                        <a href="#" class="small-box-footer">更多信息 <i class="fas fa-arrow-circle-right"></i></a>
                    </div>
                </div>
                <!-- ./col -->

            </div>
        </div>
    </section>
</div>
</body>
  • 修改settings.py
# 修改首页设置, 指向新创建的控制面板
SIMPLEUI_HOME_PAGE = '/app01/dashboard/'
SIMPLEUI_HOME_TITLE = '控制面板!' 
SIMPLEUI_HOME_ICON = 'fa fa-eye'