# 基础知识

# 认识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