# 基础知识
# 认识django
- 官网 (opens new window)
- 中文文档 (opens new window)
- 历史版本 (opens new window)
- Django是Python中重量级选手中最有代表性的一位。许多成功的网站和APP都基于Django
- Django 采用了 MVT的软件设计模式,即模型(Model),视图(View)和模板(Template)
# 安装运行
# 使用中文镜像源安装
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

# 目录结构
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核心包里的全局默认配置
# 位置: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
# 异步视图
- 函数视图:在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,否则False
def 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 = False
def __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框架 →