导图社区 django思维导图
Django 基础最全解析,精心总结,望支持,Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,视图V和模版T。它最初是被开发来用于管理劳伦斯出版集团。
编辑于2021-06-16 17:08:14DNA修饰 DNA甲基化 RNA修饰 蛋白修饰 组蛋白修饰 3D基因组 及其技术。人类每个细胞的基因组一样,但功能、形态、不一样。
测序、转录组测序、单细胞测序、基因调控、NGS、RNA 世界,RNA:存在于生物细胞以及部分病毒、类病毒的遗传信息载体、一般单链。
首都师范大学课程笔记,包括古典精神分析和新精神分析(弗洛伊德、阿德勒、荣格、埃里克森),特质论(奥尔波特、卡特尔)行为主义和社会学习(巴普洛夫、华生、斯金纳、班杜拉),行为主义(罗杰斯、马斯洛),认知流派(凯利、威特金、米歇尔)以及生物流派(艾森克)进化心理学等
社区模板帮助中心,点此进入>>
DNA修饰 DNA甲基化 RNA修饰 蛋白修饰 组蛋白修饰 3D基因组 及其技术。人类每个细胞的基因组一样,但功能、形态、不一样。
测序、转录组测序、单细胞测序、基因调控、NGS、RNA 世界,RNA:存在于生物细胞以及部分病毒、类病毒的遗传信息载体、一般单链。
首都师范大学课程笔记,包括古典精神分析和新精神分析(弗洛伊德、阿德勒、荣格、埃里克森),特质论(奥尔波特、卡特尔)行为主义和社会学习(巴普洛夫、华生、斯金纳、班杜拉),行为主义(罗杰斯、马斯洛),认知流派(凯利、威特金、米歇尔)以及生物流派(艾森克)进化心理学等
Django
1. web开发模式
原生开发
敏捷开发
django, flask, sanic, tornado, FastAPI, twsited
二次开发
三者差异
开发速度
二次开发—> 敏捷开发—> 原生开发
开发难度
原生开发—> 二次开发—> 敏捷开发
项目性能
原生开发—> 敏捷开发—> 二次开发
2. 基本介绍
官网
http://www.djangoproject.com
文档
https://docs.djangoproject.com/zh-hans/3.2/
版本
普通发行版本
经常用于一些新功能,新特性,但是维护周期短,不稳定.
长线支持版本
LTS
维护周期长,稳定
软件版本号格式
大版本.小版本.修订号
说明
大版本一般是项目内容/软件的核心架构发生改动, 以前的代码已经不适用于新的版本
小版本一般是功能的删减, 删一个功能,小版本+1, 减一个功能,小版本+1
修订号一般就是原来的代码出现了bug, 会针对bug代码进行修复, 此时就会增加修订号的数值
3. 虚拟环境
作用
把项目中使用的第三方依赖模块进行隔离
种类
Virtualenvs
安装
1. 虚拟环境的依赖库
pip install virtualenv
2. 虚拟环境的外壳操作库
pip install virtualenvwrapper
兼容windows
pip install virtualenvwrapper-win
使用命令
列出当前系统中所有的虚拟环境
进入指定名称的虚拟环境中, 路径左边就会出现虚拟环境名称, 在有虚拟环境的名称时,所有的pip都是基于对应虚拟环境的目录才操作
workon <虚拟环境名称>
创建一个指定名称的虚拟环境,一个虚拟环境就是一个目录.
mkvirtualenv <虚拟环境名称>
-p 指定python解析器版本
在虚拟环境内部退出环境
deactivate
删除指定名称的虚拟环境[慎重使用,删除前建议备份]
rmvirtualenv <虚拟环境名称>
安装项目使用的第三方模块
pip
不能基于sudo或者su来调用pip,否则会变成全局安装
pip本身属于python解析器附带工具,和虚拟环境本身没有任何关系
pip源
豆瓣源
https://pypi.douban.com/simple/
清华源
https://pypi.tuna.tsinghua.edu.cn/simple
https://mirror.tuna.tsinghua.edu.cn/help/pypi/
常见命令操作
列出当前环境下开发者使用pip安装的包列表,作用类似于pip list,但是比pip list好用
pip freeze
pip freeze > ./requriments.txt
requriments.txt是瞎写的.不是固定,只是大家都叫这个名字
备份虚拟环境中的所有依赖包列表
安装模块
安装最新版本
pip install <包名>
例如:安装django
pip install django
安装指定版本
pip install <包名>==版本号
例如:安装django3.2.4版本
pip install django==3.2.4
指定版本条件安装
pip install <包名>==版本号
例如:安装django2最新版本
pip install django==3.2.*
pip install -r ./requriments.txt
还原备份的虚拟环境包列表
Anaconda
下载地址
https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/
Miniconda
一个 Anaconda 的轻量级替代,默认只包含了 python 和 conda,但是可以通过 pip 和 conda 来安装所需要的包。
常见命令操作
查看conda版本
conda -V
更新Anaconda的版本
conda update --prefix <anaconda安装目录> anaconda
可以先执行conda update,系统会自动提示完整并正确的命令
查看当前Anaconda的系统配置信息
conda info
列出当前系统中所有虚拟环境,环境列表左边*号表示当前所在环境
conda env list
也可以使用 conda info -e
新建虚拟环境
conda create -n <虚拟环境名称> python=<python版本号> <包名1>==<版本号> <包名2> ... <包名n>
例子
conda create -n python27 python=2.7
conda create -n python36 python=3.6 pymongo
conda create -n mofang python=3.8 flask celery
conda create -n renran python=3.6 django==2.2.0 pymysql
参数
设置当前虚拟环境的名称
-n <虚拟环境名称>
--name <虚拟环境名称>
python=<python版本号>
设置当前虚拟环境的python版本,如果本地没有会自动下载安装
<包名>==<版本号>
创建虚拟环境时同时安装一个或多个指定第三方包
可指定版本号,如果不指定版本,则安装当前python环境能支持的最新版本的包
指定包的版本时,有可能会因为没有这个版本或当前python环境不支持当前版本而导致虚拟环境创建失败。
建议指定包版本时,尽量使用*号表示小版本,例如:django==1.*
克隆虚拟环境
conda create -n <新的虚拟环境名称> --clone <旧的虚拟环境名称>
进入/切换到指定名称的虚拟环境
conda activate <虚拟环境名称>
如果不填写环境名称,则默认回到全局环境base中。
退出当前虚拟环境
conda deactivate
删除指定虚拟环境
conda remove -n <虚拟环境名称> --all
导出当前虚拟环境的Anaconda包信息到环境配置文件environment.yaml中
conda env export > environment.yaml
根据环境配置文件environment.yaml的包信息来创建新的虚拟环境
conda env create -f environment.yaml
给指定虚拟环境安装/或者更新一个或多个指定包
在当前虚拟环境中安装包
安装一个或多个最新版本的包
conda install <包名>
conda install <包名> <包名>
安装一个或多个指定版本的包
conda install <包名1>==<版本号>
给指定虚拟环境安装包
conda install -n <虚拟环境名称> <包名>
conda install -n <虚拟环境名称> <包名1>==<版本号> <包名2> ... <包名n>
建议使用conda install 代替原来的pip
虚拟环境卸载一个或多个指定包
在当前虚拟环境中卸载包
conda remove <包名1>==<版本号> <包名2> ... <包名n>
给指定虚拟环境卸载包
conda remove -n <虚拟环境名称> <包名1>==<版本号> <包名2> ... <包名n>
4. 基本安装
基于anaconda搭建python3.8环境
conda create -n djdemo python=3.8
进入虚拟环境
conda activate djdemo
安装django包
安装最新版本
pip install django
安装指定版本
pip install django==3.2
指定国内源,进行快速安装
pip install django -i https://pypi.douban.com/simple/
切换到工作目录
cd ~/Desktop
工作目录就是你希望在哪里保存自己的项目源代码的目录
生成一个具有基本目录结构的django项目
django-admin startproject demo
通过pycharm打开项目
在pychatm中配置运行当前项目的所在虚拟环境中的python解析器.
启动项目
直接直接下pycharm下面的终端terminal中使用命令运行django
python manage.py runserver
5. 目录结构
│─ manage.py # 终端脚本命令,提供了一系列用于生成文件或者目录的命令,也叫脚手架 └─ dome/ # 主应用开发目录,保存了项目中的所有开发人员编写的代码, 目录是生成项目时指定的 │- asgi.py # django3.0以后新增的,用于让django运行在异步编程模式的一个web应用对象 │- settings.py # 默认开发配置文件 │- urls.py # 路由列表目录,用于绑定视图和url的映射关系 │- wsgi.py # wsgi就是项目运行在wsgi服务器时的入口文件,本质上来说,manage.py runserver 内部调用的就是wsgi └- __init__.py
配置文件
全局默认配置文件
django.conf.global_settings.py
全局项目配置文件
主应用.setting.py
系统先加载了`global_settings.py`中的所有配置项, 接着加载setting.py配置的
settings.py中填写的配置项的优先级会高于`global_settings.py`默认配置
MVT
MVT来源于MVC
模型(Model)
是一个类或者函数,里面的代码就是用于完成操作数据库的
视图(View)
是一个保存了特殊语法的前端html内容文件,里面的代码就是用于展示给客户端的页面效果。
要在视图里面编写输出数据,需要掌握模板引擎提供的特殊语法
控制器(Controller)
是一个类或者函数,里面的代码就是用于项目功能逻辑的
一般用于调用模型来获取数据,获取到的数据通过调用视图返回给客户端
不会自动执行,当用户访问了绑定当前控制器的路由以后才会被调用
模型(Model)
是一个类或者函数,里面的代码就是用于完成操作数据库的
不会自动执行,都是由视图进行调用
视图(View
是一个类或者函数,里面的代码就是用于项目功能逻辑的
一般用于调用模型来获取数据,获取到的数据通过调用模板返回给客户端
不会自动执行,当用户访问了绑定当前视图的路由以后才会被调用
模板(Template)
是一个保存了特殊语法的前端html内容文件,里面的代码就是用于展示给客户端的页面效果。
要在模板里面编写输出数据,需要掌握模板引擎提供的特殊语法
执行顺序
客户端
发起http/websocket请求
web服务器
中间件
路由
视图
模型
数据库
模板
模板语法被转换成真实数据
路由
中间件
web服务器
客户端
6. 快速使用
1. 创建子应用
python manage.py startapp 子应用名称
子应用的名称将来会作为目录名而存在,所以不能出现特殊符号,不能出现中文等多字节的字符.
创建home子应用
python manage.py startapp home
2. 在子应用的视图文件中编写视图函数
home/view.py
from django.http.response import HttpResponse def index(request): print("视图运行了") return HttpResponse("hello world!")
3. 把视图和url进行绑定注册到django项目.
用户就可以通过url地址访问,用户访问的时候,django自动根据url地址执行对应的视图函数
demo/urls.py
from django.contrib import admin from django.urls import path from home.views import index urlpatterns = [ path('admin/', admin.site.urls), path("index", index), ]
4. 访问站点
保证项目正常运行
http://127.0.0.1:8000/index
web服务器
全称:http web 服务器
专门用于提供网页浏览服务器的一类软件
nginx,uwsgi,gunicorn,apache,tomcat,iis
可以通过各类语言使用代码开发出来
uwsgi和gunicorn就是使用python开发的web服务器软件
我们执行manage.py文件中, 之所以用户能使用浏览器访问就是web服务器的功劳. django内部用python代码写了一个测试级别的web服务器
大部分的项目框架都有内置的测试web服务器
测试web服务器,性能不好,不支持多线程.
django框架就内置了python解析器提供的wsgiref模块
是python官方提供给开发者进行学习测试使用的.不能用于项目的线上环境中
wsgi 就是python基于cgi标准实现的http通讯技术
asgi 就是wsgi的异步版本,async就是这个a
7. 路由
路由基础
Route路由, 是一种映射关系
路由是把客户端请求的url地址和用户请求的应用程序[这里意指django里面的视图]进行一对一绑定映射的一种关系
在项目中,我们常常说的路由,一般是一个类. 这个类完成了路由要做的事情
路由规则
用户访问视图的url路径 = 总路由拼接子路由
总路由
主应用下的urls.py
from django.contrib import admin from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path("student", include("home.urls")), ]
子路由
每一个子应用下的urlspy
需要自己手动创建urls.py子路由文件
from django.urls import path from .import views urlpatterns = [ path("index", views.index), path("login", views.login), ]
路由进阶
在django中所有的路由最终都被保存到一个 urlpatterns列表变量中.
注册路由
字符串路由
from django.urls import path
字符串路由也支持路由转换器
# path("路由url","视图函数","路由别名"), path("index.html4", views.index4,name="index4" ),
正则路由
from django.urls import re_path
from django.urls import path,re_path from . import views urlpatterns = [ # path("路由url","视图函数","路由别名"), re_path("^index7/(?P<id>\d+)/message/(?P<mobile>1[3-9]\d{9})$", views.index7), ]
path和re_path 使用参数一致.仅仅在url参数和接收参数时写法不一样
视图代码中接收路由的参数
def index7(request, id, mobile): print("id=%s, mobile=%s" % (id,mobile)) print("type(id)=%s, type(mobile)=%s" % (type(id),type(mobile))) return HttpResponse("ok")
路由转换器
也可以叫路由验证器
2个作用
把路由参数进行类型转换
简写正则路由
起到验证路由匹配的作用
内置转换器
str - 匹配除了 '/' 之外的非空字符串。如果表达式内不包含转换器,则会默认匹配字符串。
int - 匹配 0 或任何正整数。返回一个 int 。
slug - 匹配任意由 ASCII 字母或数字以及连字符和下划线组成的短标签。比如,building-your-1st-django-site 。
uuid - 匹配一个格式化的 UUID 。为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。比如,075194d3-6885-417e-a8a8-6c931e272f00。返回一个 UUID 实例。
path - 匹配非空字段,包括路径分隔符 '/' 。它允许你匹配完整的 URL 路径而不是像 str 那样匹配 URL 的一部分。
自定义转换器
编写自定义路由转换器
开发者要实现自定义转换器,需要编写的类必须符合官方要求的3个基本要求
regex
to_python
to_url
class MobileConverter(object): regex = "1[3-9]\d{9}" def to_python(self,value): """把路由上面的内容转换成python数据,并转换类型""" # 将匹配结果传递到视图内部时使用 return value def to_url(self,value): # 将匹配结果用于反向解析传值时使用 return value
注册路由转换器
from django.urls import register_converter # register_converter(路由转换器类名, "使用别名") register_converter(MobileConverter, "mob")
在路由中使用路由转换器
urlpatterns = [ # 匹配手机号码的路由参数 # re_path("^sms/(?P<mobile>1[3-9]\d{9})$", views.index7), # 把手机的正则路由匹配改写成路由转换器 # 一般负责的正则路由都会把正则路由编写成路由转换器,写在外部文件中,导包引入使用 path("sms/<mob:mobile>", views.index7), ]
8. 视图
视图编写
django中所有的函数视图必须编写在子应用的views.py文件中
视图从书写格式上分2类
函数视图
FBV
Function BaseView
from django.http.response import HttpResponse def 函数视图名称(request): # 代码 return HttpResponse("返回内容")
函数视图的函数名称,同一个模块下不能重复,同时采用变量命名规则
类视图
CBV
Class BaseView
基本定义
from django.shortcuts import render from django.views import View """ 1. 类视图的声明必须直接或者间接继承于django.views.View 2. 类视图中的视图方法名称是固定的.必须以http请求的小写方法名作为视图方法名!!!【get,post,put,patch,delete】 3. 每一个视图类都可以绑定一个路由, 而视图类中的所有视图方法共享这个路由地址 4. 类方法的名称虽然固定, 但是类视图并非每个方法都要添加.注意,同一个视图类下,类方法名不能重复. """ class IndexView(View): """类视图""" def get(self,request): """只允许通过get请求访问,建议编写读取数据的页面,一般例如:首页,列表页,详情页""" name = "get请求页面" return render(request,"index5.html",locals()) def post(self,request): """只允许通过post请求访问,一般用于上传,添加数据的页面""" name = "post请求页面" return render(request,"index5.html",locals()) def put(self,request): """只允许通过put请求访问,一般用于修改,更新数据的页面""" name = "put请求页面" return render(request,"index5.html",locals()) def patch(self,request): """只允许通过patch请求访问,一般用于修改,更新数据的页面""" name = "patch请求页面" return render(request,"index5.html",locals()) def delete(self,request): """只允许通过delete请求访问,一般用于处理删除数据的页面""" name = "delete请求页面" return render(request,"index5.html",locals())
类视图的路由绑定
from django.urls import path from . import views urlpatterns = [ # 给类视图绑定路由的格式固定如下: # path("访问路径", views.类名.as_view()), path("demo", views.IndexView.as_view()), ]
类视图的路由分发原理
django只识别函数视图,所以在as_views中定义了一个函数视图作为返回值,所以类视图的本质还是函数视图
在类视图基类中,提供了一个路由分发方法dispatch,这个方法的作用就是获取客户端的http请求,然后通过getattr获取视图类中与http请求对应名称的方法返回给as_views
子视图
from django.views.generic import ListView,DetailView,CreateView,UpdateView,DeleteView
ListView
列表视图,可以通过get请求访问,用于展示列表数据,内置了分页功能
DetailView
详情视图,可以通过get请求访问,用于展示单个数据
CreateView
添加视图,可以通过get/post请求访问,用于添加单个数据
UpdateView
更新视图,可以通过get/post请求访问,用于更新单个数据
DeleteView
删除视图,可以通过get请求访问,用于删除单个数据
http请求与响应
请求
web开发中,常用的http请求
POST
添加/上传
GET
获取/下载
PUT/PATCH
修改/更新
PUT表示修改整体数据
PATCH表示修改部分数据
DELETE
删除,废弃
限制http请求视图
django支持让客户端只能通过指定的Http请求来访问到项目的视图
编写视图,views.py
# 限制用户发送POST请求才能访问的页面 from django.views.decorators.http import require_http_methods @require_http_methods(["POST"]) def login(request): return HttpResponse("登录成功!")
绑定路由,urls.py
子应用下的路由文件urls.py
from django.urls import path from home.views import index,index2 urlpatterns = [ path("index", index), path("login", login), ]
总路由urls.py
"""总路由""" from django.contrib import admin from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), # path("公共url地址", include("子应用目录名.路由模块")) path("book/", include("home.urls")), ]
访问视图
http://127.0.0.1:8000/login
解决浏览器无法直接访问post请求的视图
postman软件
下载地址
https://www.postman.com/downloads/
解决django默认开启csrf防范机制导致无法通过postman直接访问post请求的视图
打开项目配置文件settings.py,并找到MIDDLEWARE配置项
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', # ctrl+/ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
视图接收http请求数据
django视图中提供了request对象用于获取客户端请求时发送过来的额数据
获取本次客户端的http请求动作
request.method
获取查询字符串的参数
request.GET
request.GET.get("name")
request.GET["name"]
request.GET.getlist("lve")
视图代码
def index3(request): print(request.GET) """ 访问地址: http://127.0.0.1:8000/home/index3 打印效果: <QueryDict: {}> 分析: 通过request.GET得到的客户端请求数据,基本都是会保存成QueryDict类字典给视图的,上面是空字典 QueryDict的声明位置: from django.http import QueryDict QueryDict的父类继承的就是dict字典,所以字典提供的方法或者操作, QueryDict都有 """ """ 访问地址: http://127.0.0.1:8000/home/index3?name=xiapming&mobile=13312345678 打印效果: <QueryDict: {'name': ['xiapming'], 'mobile': ['13312345678']}> 分析: 这次就获取到了name和mobile参数以及参数的值 注意: 因为客户端传递过来的参数有可能多个值的情况,所以查询字符串返回的数据值都是列表格式 """ """ 访问地址: http://127.0.0.1:8000/home/index3?name=xiapming&mobile=13312345678&lve=swimming&lve=shopping&lve=game 打印效果: <QueryDict: {'name': ['xiapming'], 'mobile': ['13312345678'], 'lve': ['swimming', 'shopping', 'game']}> 分析: 因为lve有多个值的存在, 所以值就是列表中有3个成员 """ """获取QueryDict对象中的参数值""" # 获取参数的一个值,或者第一个值 # 因为QueryDict是一个伪字典对象,所以可以通过get或者中括号获取到数据 # print(request.GET.get("name")) # print(request.GET["name"]) # 获取参数的所有值 print(request.GET.getlist("lve")) # ['swimming', 'shopping', 'game'] print(request.GET['lve']) # game,后面的值覆盖了前面的值 return HttpResponse("ok")
获取请求体数据
request.POST
得到的结果是QueryDict,操作类似request.GET的结果
request.body
得到的结果是bytes数据,需要格式转换
获取请求头数据
request.META
获取本次请求数据格式
request.META.get("CONTENT_TYPE")
获取自定义请求头
request.META.get("HTTP_COMPANY")
获取上传文件
request.FILES
request.FILES.get("avatar")
响应
数据响应
返回HTML数据
def index5(request): """响应对象""" """ return HttpResponse(content="正文内容",content_type="内容格式",status="http响应状态码") content_type 内容格式,默认是 text/html status 响应状态码,默认是 200 """ """返回html内容""" return HttpResponse(" hello world ")
返回Json
from django.http.response import JsonResponse import json def index5(request): """响应对象""" """返回json数据""" # data = { # "id":1, # "username":"张晓明", # "money": 22.50, # "sex":True, # "lve":["swimming","walk","game"] # } # goods_list = [ # {"id":1,"num":10,"name":"产品1"}, # {"id":2,"num":10,"name":"产品2"}, # {"id":3,"num":10,"name":"产品3"}, # {"id":4,"num":10,"name":"产品4"}, # ] # # 方式1,如果返回非字典数据,务必设置 sale=False 才行. # # return HttpResponse(json.dumps(data), content_type="application/json") # # 方式2,如果返回非字典数据,务必设置 sale=False 才行. # return JsonResponse(goods_list,safe=False)
返回图片信息
def index5(request): """响应对象""" """通过响应对象不仅可以返回HTML网页信息,还可以返回纯文本,或者图片,甚至完成文件下载的功能[这个过程只需要设置content_type即可]""" # content = open("release.png","rb").read() # return HttpResponse(content,content_type="image/jpeg")
提供下载支持
def index5(request): """响应对象""" """还可以是其他格式数据,用于提供下载""" # with open("./basic-2.2.1.zip","rb") as f: # content = f.read() # return HttpResponse(content,content_type="application/x-zip-compressed")
自定义响应头
def index5(request): """响应对象""" """返回数据的过程中设置响应头""" response = HttpResponse("ok") # 自定义响应头[值和属性都不能是多字节] response["company"] = "oldboyedu" return response
页面响应
站外跳转
# 跳转到站外 def index5(request): """响应对象""" from django.shortcuts import redirect # 方式1 # return HttpResponseRedirect("http://www.baidu.com") # 方式2 # return redirect("http://www.baidu.com")
站内跳转
# 跳转到站内 def center(request): # 方式1 # return redirect("/student/login") # 方式2 print( reverse("stu:user_login") ) # 路由反转,自动根据编写的视图方式名反解析成url地址 return redirect( reverse("stu:user_login") ) def login(request): return HttpResponse("登录页面")
路由反转解析
reverse("stu:user_login")
会话控制
为了能在多次请求过程中,识别客户端是否是同一个客户端,所以就出现了会话跟踪技术,就需要使用会话控制技术, 也叫会话保持或者会话跟踪技术.
会话控制技术,主要作用是为了识别和记录用户在web应用中的身份行为和操作历史.
实现会话控制的几种技术类型
url地址栏记录用户身份的参数[少见,很古老的技术了]
cookie
在浏览器中由浏览器自动读写保存用户信息的一种小文件. 能够存储的数据有限.10M左右
过时了,谷歌在2021开始慢慢关闭这个cookie技术了
采用token代替
保存cookie
from django.http.response import HttpResponse # Create your views here. def set_cookie(request): """先判断用户身份""" """身份正确, 生成cookie""" response = HttpResponse("设置cookie") # 生成cookie """ 参数列表: key, # 键/变量 value='', # 值/内容 max_age=None, # 设置cookie的有效时间,单位: 秒 expires=None, # 设置cookie的过期时间戳[时间戳表示从1970-01-01 00:00:00至今的总秒数] # datetime.now().timestamp() 获取时间戳 # int( time.time() * 1000 ) 获取毫秒时间戳 # datetime.now().timestamp() 获取毫秒时间戳 path=None, # 当前cookie是否只能在指定公共路径下使用,None表示在同一个域名下,任意路径都可以使用 domain=None, # 当前cookie是否只能在指定同一段域名下使用,None表示在当前服务器所在域名下使用 secure=False, # 当前cookie是否只能在https协议下使用,False表示在http协议下也能使用 httponly=False, # 当前cookie是否只能在http协议下使用,False表示在其他协议下也可以使用 """ # 设置cookie信息,并设置30秒后过期, # cookie会一直持续保存在浏览器直到过期时间来临,这个过程除了客户端自己删除以外,即便关闭浏览器也不会改变 response.set_cookie("uname", "xiaoming",max_age=5) response.set_cookie("uid", 100, max_age=180) # 设置cookie信息,可以不设置过期时间,默认cookie有效期的就是浏览器关闭时删除[会话结束时删除] response.set_cookie("is_login", True, ) return response
读取cookie
def get_cookie(request): """通过request.COOKIES可以获取客户端发送过来的cookie""" print(request.COOKIES) # 获取所有cookie print(request.COOKIES.get("uname")) # 获取指定名称cookie return HttpResponse("获取cookie")
删除cookie
def del_cookie(request): """删除cookie,在服务端是做不到的,因为cookie在客户端,所以我们需要通知客户端自己去删除""" # 告诉浏览器,cookie过期了 response = HttpResponse("告诉客户端,删除cookie") response.set_cookie("uid","",max_age=0) # 设置有效期为0秒,当浏览器接受响应内容时,0秒早就到了,所以会自动删除 return response
session
在服务端中保存用户信息的一种小文件.能够存储的数据有限.根据服务端配置而定
django中在2.0以后的版本默认采用了数据库保存session
相关配置
设置session存储方式,SESSION_ENGINE
保存到文件
django.contrib.sessions.backends.file
直接可以使用
保存到数据库
django.contrib.sessions.backends.db
需要配置数据库连接
保存到缓存
django.contrib.sessions.backends.cache
需要配置缓存连接
设置session的存储路径,SESSION_FILE_PATH
BASE_DIR / "session_path"
保存session
def set_session(request): """设置session""" # session保存在服务端,所以所有关于session的操作都是由request.session来完成的 # request.session["uname"] = "root" request.session["uid"] = 1 print(request.session.serializer) # b'{"uname":"root","uid":1}' return HttpResponse("设置session数据")
读取session
def get_session(request): """获取session""" print(f"uname={request.session.get('uname')}") # format string python3.6提供的 print(f"uid={request.session.get('uid')}") # 获取session数据的有效,默认值是:2周 ==> 60 * 60 * 24 * 7 * 2 print(request.session.get_session_cookie_age() ) return HttpResponse("获取session数据")
删除session
def del_session(request): """删除session数据""" # 删除单个指定名称的session try: del request.session["uid"] except KeyError: pass # 获取session所有的键值对 print(request.session.items()) # 删除所有的session,慎用 request.session.clear() return HttpResponse("删除session数据")
token令牌
就是一段可以记录和识别用户身份的字符串.通过客户端语言[js/安卓/ios]保存在客户端中一项技术
9. 模板引擎
基本概念
模板引擎是一种可以让开发者把服务端数据填充到html网页中完成渲染效果的技术
模板引擎实现了把前端代码和服务端代码分离的作用,让项目中的业务逻辑代码和数据表现代码分离,让前端开发者和服务端开发者可以更好的完成协同开发
Django框架中内置了web开发领域非常出名的一个DjangoTemplate模板引擎(DTL)
https://docs.djangoproject.com/zh-hans/3.2/topics/templates/
DTL模板文件与普通html文件的区别在哪里?
DTL模板文件是一种带有特殊语法的HTML文件,这个HTML文件可以被Django编译,可以传递参数进去,实现数据动态化。在编译完成后,生成一个普通的HTML文件,然后发送给客户端。
开发中,一般把开发中的文件分2种
静态文件
数据保存在当前文件,不需要经过任何处理就可以展示出去。
普通html文件,图片,视频,音频等这一类文件叫静态文件。
动态文件
数据并不在当前文件,而是要经过服务端或其他程序进行编译转换才可以展示出去。
编译转换的过程往往就是使用正则或其他技术把文件内部具有特殊格式的变量转换成真实数据。
动态文件,一般数据会保存在第三方存储设备,如数据库中。
django的视图,模板文件,就属于动态文件。
使用模板引擎的基本3个步骤
1. 在项目配置文件中指定保存模板文件的模板目录。
一般模板目录都是设置在项目根目录或者主应用目录下。
settings.py
# 模板引擎配置 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ BASE_DIR / "templates", # 路径拼接 ], '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', ], }, }, ]
2. 在视图中基于django提供的渲染函数绑定模板文件和需要展示的数据变量
from django.shortcuts import render def index(request): # 要显示到客户端的数据 name = "hello DTL!" # return render(request, "模板文件路径",context={字典格式:要在客户端中展示的数据}) return render(request, "index.html",context={"name":name})
3. 在模板目录下创建对应的模板文件,并根据模板引擎内置的模板语法,填写输出视图传递过来的数据。
templates/index.html
来自模板的内容 输出变量name的值:{{ name }}
模板语法
变量
输出基本类型数据
{{ 变量 }}
输出符合类型数据
列表
{{ 变量.下标 }}
字典
{{ 变量.键 }}
对象
{{变量.属性}}
标签
写法
单标签
{% 标签名 %}
双标签
{% 开始标签 %} {% 结束标签 %}
常用标签
注释
单行注释
{# 来自django模板引擎的注释~~~~ #}
多行注释
{% comment %} 多行注释,comment中的所有内容全部都不会被显示出去 {% endcomment %}
逻辑判断
{% if age < 18 %} <p>你还没成年,不能应聘我们公司的岗位!</p> {% endif %}
{% if name == "root" %} 超级用户,欢迎回家! {% else %} {{ name }},你好,欢迎来到xx网站! {% endif %}
{% if user_lve == lve.0 %} 那么巧,你喜欢游泳,海里也能见到你~ {% elif user_lve == lve.1 %} 那么巧,你也来收快递呀?~ {% elif user_lve == lve.2 %} 那么巧,你也在老男孩? {% else %} 看来我们没有缘分~ {% endif %}
循环遍历
遍历输出一个列表,列表成员是对象
{% for book in book_list1 %} {{ book.id }} {{ book.name }} {{ book.price }} {% endfor %}
遍历输出一个字典,不建议使用
{% for book in book_list1 %} {{ field }} == {{ value }} {% endfor %} {% endfor %}
逆向循环数据
{% for book in book_list1 reversed %} {{ book.id }} {{ book.name }} {% if book.price > 200 %} {{ book.price }} {% else %} {{ book.price }} {% endif %} {% endfor %}
循环过程中的迭代对象forloop
forloop.counter
显示循环的次数,从1开始
forloop.counter0
显示循环的次数,从0开始
forloop.revcounter0
倒数显示循环的次数,从0开始
forloop.revcounter
倒数显示循环的次数,从1开始
forloop.first
判断如果本次是循环的第一次,则结果为True
forloop.last
判断如果本次是循环的最后一次,则结果为True
forloop.parentloop
在嵌套循环中,指向当前循环的上级循环
{% for book in book_list1 %} {{ forloop.counter }} #} {# {{ forloop.counter0 }} #} {# {{ forloop.revcounter }} #} {# {{ forloop.revcounter0 }} #} {# {{ forloop.first }} #} {{ forloop.last }} {{ book.id }} {{ book.name }} {% if book.price > 200 %} {{ book.price }} {% else %} {{ book.price }} {% endif %} {% endfor %}
过滤器
本质就是函数
可以是python函数
也可以是开发者自定义函数
只能接收1个额外参数
写法
单个无参数过滤器,变量默认作为过滤器的第1个参数,过滤器中return的内容作为结果被输出
{{ 变量 | 过滤器 }}
单个有参数过滤器,django的过滤器只能额外接收1个参数。
{{ 变量 | 过滤器:参数2}}
多个无参数过滤器,每个 | (竖杠) 左边的内容作为右边过滤器的默认第1个参数
{{ 变量 | 过滤器1 | 过滤器2 | 过滤器3 | ..... }}
多个有参数过滤器,每个过滤器的参数1是 竖杠作为结果。最后一个过滤器返回的结果被输出
{{ 变量 | 过滤器1:参数2 | 过滤器2:参数2 | .... }}
内置过滤器
自定义过滤器
确保当前需要使用过滤器的子应用已经注册到了INSTALLED_APPS中
settings.py
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'tem', # 例如在tem中编写一个过滤器 ]
编写过滤器,过滤器必须在子应用下的templatetags包里面创建对应的python文件中
tem.templatetags.my_filters.py
from django import template # 返回值的变量名必须叫做register register = template.Library() # 自定义过滤器 @register.filter("sex") def check_sex(content): # 过滤器必须有一个以上的参数,提供给模板调用 if int(content): return "男" return "女"
在模板中通过load标签加载当前子应用已经声明的过滤器函数,load标签的使用最好写在模板的第一行
视图代码
def index11(request): user_list = [ {"id":10,"name":"xiaomin","money":1000,"sex":0}, {"id":12,"name":"xiaoming","money":800,"sex":1}, {"id":13,"name":"xiaohui","money":200.5,"sex":1}, {"id":14,"name":"xiaobai","money":180.75,"sex":1}, ] return render(request, 'index11.html', locals())
模板代码
{% load my_filters %} id 姓名 性别 钱包 {% for user in user_list %} {{ user.id }} {{ user.name }} {{ user.sex | sex }} {{ user.money }} {% endfor %}
模板分离
作用:模板页面代码复用
传统模板分离技术
django中提供了`{% include "模板文件名" %}`标签模板分离技术。
模板继承
视图
def index14(request): """模板继承""" return render(request, 'index14.html', locals()) def index15(request): """模板继承""" return render(request, 'index15.html', locals())
子模板
{% extends "common/base.html" %} {% block style %} {% endblock style %} {% block content %} index15的主体代码 {% endblock %} {% block header %} 15的公共头部 {% endblock header %}
父模板
{% block header %} 公共头部 {% endblock header %} {% block content %} 公共base.html->主体代码 {% endblock content %} 侧栏效果 公共脚部
10.静态文件
开发中在开启了debug模式时,django可以通过配置,允许用户通过对应的url地址访问django的静态文件。
settings.py
STATIC_URL = '/static/' # django模板中,可以引用{{STATIC_URL}}变量避免把路径写死。 STATICFILES_DIRS = [ BASE_DIR / 'static' ]
项目上线以后,关闭debug模式时,django默认是不提供静态文件的访问支持
项目部署的时候,我们会通过收集静态文件使用nginx这种web服务器来提供静态文件的访问支持。
11.中间件
MiddleWare,是 Django 请求/响应处理的钩子框架。
一个轻量级的、低级的插件系统,用于全局改变 Django 的输入或输出
输入指代的就是客户端像服务端django发送数据
输出指代django根据客户端要求处理数据的结果返回给客户端
内置中间件
django框架内部声明了很多的中间件,这些中间件有着各种各种的用途,有些没有被使用,有些被默认开启使用了。
被开启使用的中间件,都是在settngs.py的MIDDLEWARE中注册使用的
# 中间件列表 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', # 安全监测相关的中间件,防止页面过期, js跨站脚本攻击xss 'django.contrib.sessions.middleware.SessionMiddleware', # session加密和读取和保存session相关 'django.middleware.common.CommonMiddleware', # 通用中间件,用于给url进行重写 'django.middleware.csrf.CsrfViewMiddleware', # 防止网站遭到csrf攻击的 'django.contrib.auth.middleware.AuthenticationMiddleware', # 用户认证的中间件 'django.contrib.messages.middleware.MessageMiddleware', # 错误提示信息的中间件【提示错误信息,一次性提示】 'django.middleware.clickjacking.XFrameOptionsMiddleware', # 用于防止点击劫持攻击的 iframe标签 ]
Csrf攻击防范
GZIP压缩
自定义中间件
函数式中间件
在项目中找个目录创建一个存放中间件函数的模块
例如:主应用djdemo/my_middleware.py
def simple_middleware(get_response): # 自定义中间件 def middleware(request): # Code to be executed for each request before # the view (and later middleware) are called. print("--------------视图执行之前---------------") # 记录访问用户记录的信息,识别判断黑名单,白名单,判断用户是否登录, 判断用户是否拥有访问权限..... # 视图执行之前 response = get_response(request) # 视图调用 # 视图执行之后 print("-------------视图执行以后----------------") # 记录用户的操作历史,访问历史,日志记录, 资源的回收... return response return middleware
在settings.py下面注册中间件
# 中间件列表 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'middle.simple_middleware', # 没有特殊要求,一般自己定义的中间件写在最后 ]
类中间件
和函数式的中间件一样,我们一般都会保存在一个独立的文件中.把所有的中间件按不同的业务存放在一块,.
例如:主应用djdemo/my_middleware.py
from django.utils.deprecation import MiddlewareMixin from django.http.response import HttpResponse class CustomMiddleware(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("4. process_response在视图执行以后,才执行的") # 用途:记录操作历史, 记录访问历史,修改返回给客户端的数据, 建立缓存 # 必须返回response对象,否则报错!! return response def process_exception(self, request, exception): print(exception) # 用途:进行异常的处理或者记录错误日志 print("5. process_exception会在视图执行发生异常的时候才会执行") def process_template_response(self,request, response): # 用途:建立页面缓存 print("6. process_template_response只有在视图调用了模板以后,才会执行!!!") return response
中间件类和函数式中间件的注册方式雷同,也是在setting.py中直接注册.
# 中间件列表 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', # 安全监测相关的中间件,防止,页面过期, js跨站脚本攻击xss 'django.contrib.sessions.middleware.SessionMiddleware', # session加密和读取和保存session相关 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', # 'middle.simple_middleware', 'middle.CustomMiddleware', ]
中间件的执行顺序
回环式执行
在客户端请求时,中间件从settings文件中的MIDDLEWARE中从上往下执行
在服务端响应时,中间件从settings文件中的MIDDLEWARE中从下往上执行
12.文件上传处理
Django在处理文件上传的时候,文件数据被保存在request.FILES
FILES中的每个键为`<input type="file" name="" />`中的name
FILES只有在请求的方法为POST 且提交的`<form>`带有enctype="multipart/form-data" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象
使用模型处理上传文件:将属性定义成models.ImageField类型
客户端表单代码
django中保存上传文件有2种方式
手动保存
视图代码
from django.conf import settings def upload(request): if request.method == "POST": avatar = request.FILES['avatar'] fname = '%s/avatar/%s' % (settings.MEDIA_ROOT,avatar.name) with open(fname, 'w') as pic: for c in avatar.chunks(): pic.write(c) return HttpResponse("ok") else: return HttpResponse("error")
模型自动保存
模型代码中声明ImageField或者FileField
from django.db import models # Create your models here. class User(models.Model): name = models.CharField(max_length=50, verbose_name="姓名") avatar = models.ImageField(max_length=250, upload_to='avatar/%Y/%m/%d/') attachment = models.FileField(max_length=250, upload_to='atta/%Y/%m/%d/') class Meta: db_table = "home_user" verbose_name = "用户信息" verbose_name_plural = verbose_name
如果属性类型为ImageField需要安装PIL包
pip install Pillow
必须在项目配置中设置上传文件的存储根目录
settings
# 设置上传文件的保存根目录[此处指定的目录必须手动创建] MEDIA_ROOT = BASE_DIR / "uploads"
视图代码
from django.views import View from django.http.response import HttpResponse from .models import User class UserView(View): def get(self,request): print( request.FILES ) # 获取上传文件列表 print( request.FILES.get("avatar") ) return HttpResponse("上传文件") def post1(self,request): """request.FILES只会在post请求中接受上传文件,客户端中必须设置enctype=multipart/form-data""" print( request.FILES ) # 获取上传文件列表 print( request.FILES.get("avatar") ) print( request.FILES.get("avatar").content_type ) # 上传文件的MIME类型 if "image" in request.FILES.get("avatar").content_type: # 判断文件类型 if request.FILES.get("avatar").content_type.split("/")[-1] in ["png","jpg","jpeg","gif"]: # 图片类型 print("处理上传文件!") print( request.FILES.get("avatar").size ) # 上传文件的大小 print( type(request.FILES.get("avatar")) ) return HttpResponse("上传文件") def post(self,request): user = User.objects.create( name=request.POST.get("name"), avatar=request.FILES.get("avatar"), attachment=request.FILES.get("atta") ) return HttpResponse("上传文件处理完成!")
13.数据分页
Paginator对象
Paginator(列表,int):返回分页对象,参数1为要进行分页的列表数据,每面数据的条数(Limit)
属性
count:对象总数
num_pages:页面总数
page_range:页码列表,从1开始,例如[1, 2, 3, 4]
方法
page(num):num表示页码,以1开始
异常
InvalidPage
当向page()传入一个无效的页码时抛出
PageNotAnInteger
当向page()传入一个不是整数的值时抛出
EmptyPage
当向page()提供一个有效值,但是那个页面上没有任何对象时抛出
Page对象
Paginator对象的page()方法返回Page对象,不需要手动创建Page对象
属性
object_list
当前页上所有对象的列表
number
当前页的页码序号,从1开始
paginator
当前page对象相关的Paginator对象,方便找到父级对象
方法
has_next():如果有下一页返回True
has_previous():如果有上一页返回True
has_other_pages():如果有上一页或下一页返回True
next_page_number():返回下一页的页码,如果下一页不存在,抛出InvalidPage异常
previous_page_number():返回上一页的页码,如果上一页不存在,抛出InvalidPage异常
len():返回当前页面对象的个数
迭代页面对象:访问当前页面中的每个对象
ListView分页
class StudentListView(ListView): http_method_names = ['get'] # 限制当前视图类中提供哪些http请求视图方法 model = Student # 当前视图类中要操作的数据模型 context_object_name = 'students' # 模板变量 template_name = "students.html" # 模板路径 paginate_by = 5 # 每一页数据量[ListView内置视图类默认提供了分页器,分页对象在模板中通过page_obj来操作]
14.缓存
通过设置决定把数据缓存在哪里,是数据库中、文件系统还是在内存中,通过settings文件的CACHES配置来实现
本地缓存
CACHES={ 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'TIMEOUT': 60, } }
TIMEOUT:缓存的默认过期时间,以秒为单位,这个参数默认是300秒,即5分钟;设置TIMEOUT为None表示永远不会过期,值设置成0造成缓存立即失效
设置缓存到redis中
pip install django-redis-cache
CACHES = { "default": { "BACKEND": "redis_cache.cache.RedisCache", # django-redis-cache "LOCATION": "redis://:@127.0.0.1:6379/1", 'TIMEOUT': 60, # 缓存失效时间,这里60秒只是为了测试而已 }, }
pip install django_redis
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", # django_redis "LOCATION": "redis://:@127.0.0.1:6379/1", 'TIMEOUT': 60, # 缓存失效时间,这里60秒只是为了测试而已 }, }
存储操作
视图缓存
from django.views.decorators.cache import cache_page # 函数视图 @cache_page(60 * 15) def index(request): return HttpResponse('hello!') # 类视图 from django.views.decorators.cache import cache_page # 这个方法方法只能用于装饰函数视图 # 针对一些只能用于装饰函数视图的装饰器,想要在类视图方法中使用必须要使用 method_decorator 来改写 class Home(View): @method_decorator(cache_page(60 * 15)) def get(self,request): print("hello") # 执行了这里的hello,则表示缓存失效了。 return HttpResponse('Hello, async base view!')
cache_page接受一个参数:timeout,秒为单位,上例中缓存了15分钟
视图缓存与URL相关,如果多个URL指向同一视图,每个URL将会自动分别缓存
被缓存了的视图,在缓存失效之前,里面的代码不会被执行到,所以和缓存API是冲突的。
模板缓存
缓存API
from django.core.cache import cache
设置:cache.set(键,值,有效时间)
获取:cache.get(键)
删除:cache.delete(键)
清空:cache.clear() # 慎用
15.数据库
常用数据库2大阵营
关系型数据库[RDB]
数据库中存储数据的表之间存在某种内在的关联关系,因为这种关系,所以我们称这一类型的数据为关系型数据库
共同的特点:都使用了SQL语句进行数据库操作
常见的关系型数据库
mysql[MariaDB]
PostgreSQL
Oracle
MSServer
DB2. sqlite
Access
非关系型数据库[Nosql]
泛指那些不适用SQL语句进行数据库操作的所有其他数据库
常见的NoSQL数据库
Redis
MongoDB
Memcached
Elasticsearch/Sphinx/Solr
HBase/CouchDB
ORM框架
ORM框架会帮我们把类对象和数据表进行了一对一的映射,让我们可以通过类对象来操作对应的数据表。
O是object,也就类对象的意思。
R是relation,翻译成中文是关系,也就是关系数据库中数据表的意思。
M是mapping,是映射的意思。
优点
数据模型类都在一个地方定义,更容易更新和维护,也利于重用代码。
ORM 有现成的工具,很多功能都可以自动完成,比如数据消除、预处理、事务等等。
它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。
基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。
新手对于复杂业务容易写出性能不佳的 SQL,有了ORM不必编写复杂的SQL语句,只需要通过操作模型对象即可同步修改数据表中的数据.
开发中应用ORM将来如果要切换数据库.只需要切换ORM底层对接数据库的驱动【修改配置文件的连接地址即可】
缺点
ORM 库不是轻量级工具,需要花很多精力学习和设置,甚至不同的框架,会存在不同操作的ORM。
对于复杂的业务查询,ORM表达起来比原生的SQL要更加困难和复杂。
ORM操作数据库的性能要比使用原生的SQL差。
ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。【自己使用pymysql另外操作即可,用了ORM并不表示当前项目不能使用别的数据库操作工具了。】
使用步骤
1. 配置数据库连接信息
使用MySQL数据库首先需要安装驱动程序
pip install PyMySQL
主应用目录的__init__.py文件中添加如下语句
from pymysql import install_as_MySQLdb install_as_MySQLdb() # 让pymysql以MySQLDB的运行模式和Django的ORM对接运行
修改项目配置文件settings.py中DATABASES配置信息
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'HOST': '127.0.0.1', # 数据库主机 'PORT': 3306, # 数据库端口 'USER': 'root', # 数据库用户名 'PASSWORD': '123', # 数据库用户密码 'NAME': 'student' # 数据库名字 } }
在MySQL中创建数据库
create database student; # mysql8.0默认就是utf8mb4; create database student default charset=utf8mb4; # mysql8.0之前的版本
创建子应用并必须在settings.py中的INSTALLED_APPS中注册子应用
2. 在models.py中定义模型类
模型类被定义在"子应用/models.py"文件中。
模型类必须直接或者间接继承自django.db.models.Model类。
模型代码
from django.db import models from datetime import datetime # 模型类必须要直接或者间接继承于 models.Model class BaseModel(models.Model): """公共模型[公共方法和公共字段]""" # created_time = models.IntegerField(default=0, verbose_name="创建时间") created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") # auto_now_add 当数据添加时设置当前时间为默认值 # auto_now= 当数据添加/更新时, 设置当前时间为默认值 updated_time = models.DateTimeField(auto_now=True) class Meta(object): abstract = True # 设置当前模型为抽象模型, 当系统运行时, 不会认为这是一个数据表对应的模型. class Student(BaseModel): """Student模型类""" #1. 字段[数据库表字段对应] SEX_CHOICES = ( (0,"女"), (1,"男"), (2,"保密"), ) # 字段名 = models.数据类型(约束选项1,约束选项2, verbose_name="注释") # SQL: id bigint primary_key auto_increment not null comment="主键", # id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk # SQL: name varchar(20) not null comment="姓名" # SQL: key(name), name = models.CharField(max_length=20, db_index=True, verbose_name="姓名" ) # SQL: age smallint not null comment="年龄" age = models.SmallIntegerField(verbose_name="年龄") # SQL: sex tinyint not null comment="性别" # sex = models.BooleanField(verbose_name="性别") sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2) # SQL: class varchar(5) not null comment="班级" # SQL: key(class) classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班级") # SQL: description longtext default "" not null comment="个性签名" description = models.TextField(default="", verbose_name="个性签名") #2. 数据表结构信息 class Meta: db_table = 'tb_student' # 指明数据库表名,如果没有指定表明,则默认为子应用目录名_模型名称,例如: users_student verbose_name = '学生信息表' # 在admin站点中显示的名称 verbose_name_plural = verbose_name # 显示的复数名称 #3. 自定义数据库操作方法 def __str__(self): """定义每个数据对象的显示信息""" return "<User %s>" % self.name
数据库表名
可通过db_table 指明数据库表名。
模型类如果未指明表名db_table,Django默认以 小写app应用名_小写模型类名 为数据库表名。
主键
django会为表创建自动增长的主键列,每个模型只能有一个主键列。
如果使用选项设置某个字段的约束属性为主键列(primary_key)后,django不会再创建自动增长的主键列。
class Student(models.Model): # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # 设置主键
默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。
属性命名限制
不能是python的保留关键字。
不允许使用连续的2个下划线,这是由django的查询方式决定的。__ 是关键字来的,不能使用!!!
定义属性时需要指定字段类型,通过字段类型的参数指定选项
属性名 = models.字段类型(约束选项, verbose_name="注释")
字段类型
IntegerField
整数
子类型
AutoField
自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性
BooleanField
布尔字段,值为True或False
子类型
NullBooleanField
支持Null、True、False三种值
CharField
字符串
参数max_length表示最大字符个数,对应mysql中的varchar
子类型
TextField
大文本字段
一般大段文本(超过4000个字符)才使用。
DecimalField
十进制浮点数
参数
max_digits表示总位数,
decimal_places表示小数位数,常用于表示分数和价格
Decimal(max_digits=7, decimal_places=2) ==> 99999.99~ 0.00
FloatField
浮点数
DateTimeField
日期时间
参数
参数auto_now表示每次保存对象时,自动设置该字段为当前时间。
参数auto_now_add表示当对象第一次被创建时自动设置当前。
参数auto_now_add和auto_now是相互排斥的,一起使用会发生错误。
子类型
DateField
日期,参数同DateTimeField
TimeField
时间,参数同DateTimeField
FileField
上传文件字段,django在文件字段中内置了文件上传保存类,
django可以通过模型的字段存储自动保存上传文件, 但是在数据库中本质上保存的仅仅是文件在项目中的存储路径!!
子类型
ImageField
对上传的内容进行校验,确保是有效的图片
约束选项
null
若值为True,表示允许为空,默认值是False。相当于python的None
blank
若值为True,则该字段允许为空白,默认值是False。 相当于python的空字符串("")。
db_column
字段的名称,若未指定,则表示数据表中的字段名和模型中属性名一致。
一般在模型属性名和数据表字段名不一致的时候设置。
例如:表中的字段叫class,但是python中class是创建类的关键字,所以不能直接在模型中定义属性名为class。
db_index
若值为True, 则在表中会为此字段创建索引,默认值是False。 相当于SQL语句中的key
default
默认值,当不填写数据时,使用该选项的值作为数据的默认值。
primary_key
若值为True,则该字段会成为模型的主键,默认值是False,
django中一般不用设置,系统默认设置。
unique
若值为True,则该字段在表中必须有唯一值,默认值是False。相当于SQL语句中的unique
外键约束
在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理
CASCADE
级联,删除主表数据时连通一起删除外键表中数据
PROTECT
保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
SET_NULL
设置为NULL,仅在该字段null=True允许为null时可用
SET_DEFAULT
设置为默认值,仅在该字段设置了默认值时可用
SET()
设置为特定值或者调用特定方法
DO_NOTHING
不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常
3. 生成数据库迁移文件并执行迁文件[注意:数据迁移是一个独立的功能,这个功能在其他web框架未必和ORM一块的]
生成迁移文件
python manage.py makemigrations
同步到数据库中
python manage.py migrate
回滚迁移版本
python manage.py migrate appname name
appname代码子应用目录名
name就是版本号
具体需要查看数据库中的migrations这张表
4. 通过模型类对象提供的方法或属性完成数据表的增删改查操作
基本操作
增加数据
save
通过创建模型类对象,执行对象的save()方法保存到数据库中。
student = Student( name="刘德华", age=17, sex=True, clasmate=301, description="一手忘情水" ) student.save() print(student.id) # 判断是否新增有ID
create
通过模型类.objects.create()保存。
student = Student.objects.create( name="赵本山", age=50, sex=True, class_number=301, description="一段小品" ) print(student.id)
查询数据
查询单一结果
get
查询不到, 则返回模型类.DoesNotExist异常。
查询多个, 则返回模型类.MultipleObjectsReturned异常.
try: student = Student.objects.get(name="刘德华") print(student) print(student.description) except Student.MultipleObjectsReturned: print("查询得到多个结果!") except Student.DoesNotExist: print("查询结果不存在!")
first
查询不到,则返回None
查询多个,返回第一个
Student.objects.filter(name="刘德华").first()
查询多个结果
all
查询不到,则返回空列表对象
查询多个,返回QuerySet查询集
Student.objects.filter(classmate="000").all()
查询结果数量
count
Student.objects.filter(classmate="301").count()
更新数据
save
修改模型类对象的属性,然后执行save()方法
student = Student.objects.get(name='刘德华') student.name = '周星星' student.save()
update
使用模型类.objects.filter().update(),会返回受影响的行数
Student.objects.filter(name='赵华').update(name='赵晓华')
save之所以能提供给我们添加数据的同时,还可以更新数据的原因
save会找到模型的字段的主键id的值
主键id的值如果是none,则表示当前数据没有被数据库,所以save会自动变成添加操作
主键id有值,则表示当前数据在数据库中已经存在,所以save会自动变成更新数据操作
删除数据
模型类对象.delete()
student = Student.objects.get(id=13) student.delete()
模型类.objects.filter().delete()
Student.objects.filter(id=14).delete()
删除数据须谨慎
一般工作中不要进行物理删除,而是改用模型中的字段表示删除状态,这叫逻辑删除,或者软删除
物理删除时,建议加上limit作为结果限制
过滤操作
操作方法
filter
参数作为条件,返回符合条件的所有结果
exclude
参数作为条件,返回不符合条件的所有结果
get
参数作为条件,返回符合条件的一个结果
对于过滤条件的使用,上述三个方法相同,但是是互斥的,只能使用任意1个
表达语法
模型类.objects.filter(属性名称__运算符=值)
属性名称就是模型类中的属性名称
运算符默认都区分大小写
运算符前加上i表示不区分大小写
如iexact、icontains、istartswith、iendswith.
属性值与运算符之间必须是2个英文下划线
运算符
相等
exact
Student.objects.filter(id__exact=1) # 基本不使用
Student.objects.filter(id=1)
模糊查询
contains
是否包含
Student.objects.filter(name__contains='华')
startswith
以指定值开头
Student.objects.filter(name__startswith='文')
endswith
以指定值结尾
Student.objects.filter(name__endswith='文')
空查询
isnull
值是否为null
Student.objects.filter(description__isnull=True)
范围查询
in
是否包含在列表中列出的值范围内
Student.objects.filter(id__in=[1, 3, 5])
比较查询
gt
大于 (greater then)
Student.objects.filter(id__gt=3)
gte
大于等于 (greater then equal)
Student.objects.filter(id__gte=3)
lt
小于 (less then)
Student.objects.filter(id__lt=3)
lte
小于等于 (less then equal)
Student.objects.filter(id__lte=3)
不等于
Student.objects.exclude(id=3)
日期查询
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
Student.objects.filter(born_date__year=1980)
from django.utils import timezone as datetime student_list = Student.objects.filter(created_time__gte=datetime.datetime(2016,6,20),created_time__lt=datetime.datetime(2016,6,21)).all() print(student_list)
F对象
两个属性之间的值进行比较
F对象,被定义在django.db.models中
"""F对象:2个字段的值比较""" # 获取从添加数据以后被改动过数据的学生 from django.db.models import F # SQL: select * from db_student where created_time=updated_time; student_list = Student.objects.exclude(created_time=F("updated_time")) print(student_list)
Q对象
用于实现SQL语句中的or,and和not操作
Q对象被义在django.db.models中
语法
Q(属性名__运算符=值)
or
Q(属性名__运算符=值) | Q(属性名__运算符=值)
and
Q(属性名__运算符=值) & Q(属性名__运算符=值)
另一种写法
Student.objects.filter(age__gt=20,id__lt=30)
Student.filter(age__gt=20).filter(id__lt=30)
not
~Q(属性名__运算符=值)
另一种写法
Student.objects.exclude(pk=30)
from django.db.models import Q student_list = Student.objects.filter( Q(age__lt=19) | Q(age__gt=20) ).all()
结果排序
order_by
order_by("字段")
按指定字段正序显示,相当于 asc 从小到大
order_by("-字段")
按字段倒序排列,相当于 desc 从大到小
order_by("第一排序","第二排序",...)
限制查询
ORM中针对查询结果的数量限制,提供了一个查询集[QuerySet]
查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。
当调用如下过滤器方法时,Django会返回查询集
all()
返回所有数据。
filter()
返回满足条件的数据。filter会默认调用all方法
exclude()
返回满足条件之外的数据。exclude会默认调用all方法
order_by()
对结果进行排序。order_by会默认调用all方法
2大特性
惰性执行
QuerySet查询集在创建时是不会访问数据库执行SQL语句,直到调用模型对象或者调用模型对象的字段时,才会访问数据库执行SQL语句,调用模型的情况包括迭代、序列化、与if合用。
缓存
使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
限制结果数量
可以对查询集进行取下标或切片操作,等同于sql中的limit和offset子句。
不支持负数索引。
对查询集进行切片后返回一个新的查询集,不会立即执行查询。
qs = Student.objects.all()[0:2]
结果处理
exists()
判断查询集中是否有数据,如果有则返回True,没有则返回False。
values()
把结果集中的模型对象转换成字典,并可以设置转换的字段列表,达到减少内存损耗,提高性能
values_list()
把结果集中的模型对象转换成列表,并可以设置转换的字段列表(元祖),达到减少内存损耗,提高性能
聚合分组
聚合函数
使用aggregate()过滤器调用聚合函数
所有聚合函数全部被定义在django.db.models中
Avg
平均
Student.objects.aggregate(c=Avg("age"))
Count
数量
使用count时一般不使用aggregate()过滤器。除非分组查询下的统计
Max
最大
Min
最小
Sum
求和
Student.objects.aggregate(Sum("age"))
返回结果:{'age__sum': 2064}
Student.objects.aggregate(t=Sum("age"))
返回结果:{'t': 2064}
分组查询
django中提供了annotate()用于分组查询
annotate() 进行分组查询,按前面values的字段进行分组
模型对象.objects.values("id").annotate(course=Count('course__sid'))
位于annotate() 之后的values用于指定返回的字段列
模型对象.objects.values("id").annotate(course=Count('course__sid')).values('id','course')
查询指定模型, 按id分组 , 将course下的sid字段计数,返回结果是 name字段 和 course计数结果
SQL原生语句中分组之后可以使用having过滤,在django中并没有提供having对应的方法,但是可以使用filter对分组结果进行过滤
所以filter在annotate之前,表示where,在annotate之后代表having
同理,values在annotate之前,代表分组的字段,在annotate之后代表数据查询结果返回的字段列
原生查询
django提供了raw方法,可以用于执行原生SQL语句,返回结果是QuerySet,但是这个性能在操作字段时,会有额外损耗
ret = Student.objects.raw("SELECT id,name,age FROM tb_student") # student 可以是任意一个模型 for item in ret: print(item) # 此处执行SQL语句 print(item.description) # 当访问模型对象的属性时,会逐个依据ID主键进行遍历数据库,执行很多的SQl语句
关联模型
查询操作中涉及其他表字段的操作可以使用关联查询操作,在模型存在关联字段时,有2种方式获取其他表的数据
正向查询按字段,从主模型查询外键模型的数据
模型对象 = 主模型.filter(xxx).first() 模型对象.外键 # 外键_id
反向查询按表名,从外键模型查询主模型数据
外键对象 = 外键模型.filter(xxx).first() 外键对象.主模型表名_set
_set方法可以使用related_name代替
也就是说,模型中外键字段如果设置了related_name属性,则无法使用外键_set来获取关联模型的数据
_set操作在一对一里面是没有的
related_name依然有效
一对一关联
创建模型的关联关系
student = models.OneToOneField(Student, related_name="info", on_delete=models.DO_NOTHING, verbose_name="外键")
模型代码
from django.db import models # Create your models here. from django.db.models import Model class BaseModel(Model): # auto_now_add 当数据添加自动设置当前时间为默认值 # auto_now 当数据添加/更新时, 设置当前时间为默认值 # SQL: created_time datetime not null comment="添加时间" created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间") # SQL: updated_time datetime not null comment="更新时间" updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") class Meta(object): abstract = True # abstract = True # 设置当前模型为抽象模型, 当系统运行时, 不会认为这是一个数据表对应的模型. # 模型类必须要直接或者间接继承于 models.Model # SQL: create table db_student ( class Student(BaseModel): """学生信息表""" SEX_CHOICES = [ (0, '女'), (1, '男'), (2, '保密'), ] # 1. 字段声明, 声明字段和数据表对应 # 属性名 = models.数据类型(字段约束) # SQL: id bigint primary_key auto_increment not null comment="主键", # id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk # SQL: name varchar(20) not null comment="姓名" # SQL: key(name), name = models.CharField(max_length=20, db_index=True, verbose_name="姓名") # SQL: age smallint not null comment="年龄" age = models.SmallIntegerField(verbose_name="年龄") # SQL: sex tinyint not null comment="性别" # sex = models.BooleanField(verbose_name="性别") sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2) # SQL: class varchar(5) not null comment="班级" # SQL: key(class) classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班级") # SQL: description longtext default "" not null comment="个性签名" description = models.TextField(default=None, null=True, verbose_name="个性签名") # SQL: ) # 2. 表相关属性的设置 class Meta: db_table = "db_student" # db_table 指明数据库表名,如果没有指定表明,则默认为子应用目录名_模型名称,例如: student_student verbose_name = "学生信息表" # 在admin站点中显示的名称 verbose_name_plural = verbose_name # 显示的复数名称 # 3. 自定义数据库操作方法 def __str__(self): """定义每个数据对象的显示信息""" # 这里返回的内容只能是字符串 return f"<User {self.name}>" class StudentInfo(BaseModel): """学生信息附加表""" address = models.CharField(max_length=255, verbose_name="家庭地址") # 当设置字段为外键时,ORM会自动根据当前外键属性名拼接_id作为数据表中的字段名 # 所以在ORM操作外键时,也会多出了一个属性 xxx_id, # 例如:外键为student,则数据表中的真实的字段为student_id student = models.OneToOneField(Student, related_name="info", on_delete=models.DO_NOTHING, verbose_name="外键") # 2. 表相关属性的设置 class Meta: db_table = "db_student_info" verbose_name = "学生信息附加表" verbose_name_plural = verbose_name # 3. 自定义数据库操作方法 def __str__(self): """定义每个数据对象的显示信息""" # 这里返回的内容只能是字符串 return f"<UserInfo {self.student.name}>"
视图代码
""" 1对1:主表的一条记录,在从表中只对应最多一条记录,同理,附加表中的一条记录最多对应主表中的一条记录 用户表和用户附加表,商品表和商品详情表,文章表和文章详情表 """ """添加附表表数据""" # 1. 直接从数据库层面设置id的数值 # student_info = StudentInfo.objects.create( # student_id=4, # student=student ---> student = Student.objects.get(pk=4) # address="北京市昌平区沙河镇白沙路1003号" # ) # print(student_info) # 2. 直接按主模型添加 # student = Student.objects.get(pk=3) # student_info = StudentInfo.objects.create( # student=student, # address="北京市昌平区沙河镇白沙路1004号" # ) # print(student_info) """关联查询""" # 正向查询,从Student---->StudentInfo方向查询 # student = Student.objects.get(pk=4) # # 在外键中声明了 related_name 的情况下使用related_name的值直接可以获取外键对象 # print(student.info) # print(student.info.address) # 反向查询,从StudentInfo--->Student方向 # student_info = StudentInfo.objects.get(pk=1) # print(student_info.student.name) """更新和删除""" # 先获取主模型,然后从主模型中获取附加模型对象,通过附加模型对象修改数据 # student = Student.objects.get(pk=4) # print(student.info.address) # # 修改操作 # student.info.address = "北京市昌平区沙河镇白沙路1009号" # student.info.save() # print(student.info.address) # 先获取附加模型,然后通过附加模型获取主模型对象,通过主模型对象修改数据 # student_info = StudentInfo.objects.get(pk=4) # print(student_info.student.age) # st = student_info.student # st.age = 25 # st.save() # print(student_info.student.age) # 删除操作[先删除外键对应的记录,然后删除主键对应的记录] # student_info = StudentInfo.objects.filter(student_id=4).first() # student = student_info.student # print(student_info) # student_info.delete() # student.delete()
一对多关联
创建模型的关联关系
student = models.ForeignKey(Student, related_name="score_list", on_delete=models.DO_NOTHING, null=True)
模型代码
from django.db import models # Create your models here. from django.db.models import Model class BaseModel(Model): # auto_now_add 当数据添加自动设置当前时间为默认值 # auto_now 当数据添加/更新时, 设置当前时间为默认值 # SQL: created_time datetime not null comment="添加时间" created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间") # SQL: updated_time datetime not null comment="更新时间" updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") class Meta(object): abstract = True # abstract = True # 设置当前模型为抽象模型, 当系统运行时, 不会认为这是一个数据表对应的模型. # 模型类必须要直接或者间接继承于 models.Model # SQL: create table db_student ( class Student(BaseModel): """学生信息表""" SEX_CHOICES = [ (0, '女'), (1, '男'), (2, '保密'), ] # 1. 字段声明, 声明字段和数据表对应 # 属性名 = models.数据类型(字段约束) # SQL: id bigint primary_key auto_increment not null comment="主键", # id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk # SQL: name varchar(20) not null comment="姓名" # SQL: key(name), name = models.CharField(max_length=20, db_index=True, verbose_name="姓名") # SQL: age smallint not null comment="年龄" age = models.SmallIntegerField(verbose_name="年龄") # SQL: sex tinyint not null comment="性别" # sex = models.BooleanField(verbose_name="性别") sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2) # SQL: class varchar(5) not null comment="班级" # SQL: key(class) classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班级") # SQL: description longtext default "" not null comment="个性签名" description = models.TextField(default=None, null=True, verbose_name="个性签名") # SQL: ) # 2. 表相关属性的设置 class Meta: db_table = "db_student" # db_table 指明数据库表名,如果没有指定表明,则默认为子应用目录名_模型名称,例如: student_student verbose_name = "学生信息表" # 在admin站点中显示的名称 verbose_name_plural = verbose_name # 显示的复数名称 # 3. 自定义数据库操作方法 def __str__(self): """定义每个数据对象的显示信息""" # 这里返回的内容只能是字符串 return f"<User {self.name}>" class StudentInfo(BaseModel): """学生信息附加表""" address = models.CharField(max_length=255, verbose_name="家庭地址") # 当设置字段为外键时,ORM会自动根据当前外键属性名拼接_id作为数据表中的字段名 # 所以在ORM操作外键时,也会多出了一个属性 xxx_id, # 例如:外键为student,则数据表中的真实的字段为student_id student = models.OneToOneField(Student, related_name="info", on_delete=models.DO_NOTHING, verbose_name="外键") # 2. 表相关属性的设置 class Meta: db_table = "db_student_info" verbose_name = "学生信息附加表" verbose_name_plural = verbose_name # 3. 自定义数据库操作方法 def __str__(self): """定义每个数据对象的显示信息""" # 这里返回的内容只能是字符串 return f"<UserInfo {self.student.name}>" class Achievement(BaseModel): """成绩表""" student = models.ForeignKey(Student, related_name="score_list", on_delete=models.DO_NOTHING, null=True) # max_digits 总数值长度[有多少个字符,不包含小数点] # decimal_places 小数位数值的字符最多允许有几个 score = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="成绩") class Meta: db_table = 'db_student_achievement' verbose_name = '学生成绩表' verbose_name_plural = verbose_name
视图代码
""" 1对多:主表的一条记录,在从表中只对应1条或多条记录,同理,附加表中的一条记录最多对应主表中的一条记录 表达的是业务中的一种从属关系,用户表和地址信息,商品分类和商品表,作者和文章表 1用户表和m地址信息:一个用户可以有多个地址记录,一条地址记录只属于一个用户 """ """添加数据""" # student = Student.objects.get(pk=1) # # print(student.score_list) # 等同于 Achievement.objects # # print(student.score_list.all()) # 等同于 Achievement.objects.filter(student_id=1).all() # student.score_list.create( # student=student, # score=66.50 # ) """查询数据""" # 正向查询 # 设置related_name # student = Student.objects.get(pk=1) # score_list = student.score_list.values() # print(score_list) # 不设置related_name,可以基于"外键模型类名小写_set"来获取外键模型 # student = Student.objects.get(pk=1) # score_list = student.achievement_set.values() # print(score_list) # 反向查询 # achi = Achievement.objects.get(pk=3) # print(achi.student) # print(achi.student.name) """更新和删除数据: 更新和删除只能通过单个模型对象来完成""" student = Student.objects.get(pk=1) score_list = student.score_list.all() # 遍历或下标操作都可以 for score in score_list: score.delete()
多对多关联
创建模型的关联关系
teacher_list = models.ManyToManyField(Teacher, related_name="course_list")
模型代码
"""多对多""" # 一个老师可以授课多个课程 # 一个课程也可以由多个老师授课 class Teacher(models.Model): name = models.CharField(max_length=15,verbose_name="老师") class Meta: db_table = "tb_teacher" verbose_name = '老师信息表' verbose_name_plural = verbose_name class Course(models.Model): name = models.CharField(max_length=15,verbose_name="课程") teacher_list = models.ManyToManyField(Teacher, related_name="course_list") class Meta: db_table = "tb_course" verbose_name = '老师信息表' verbose_name_plural = verbose_name
视图代码
""" 范式理论:第一:不能重复,第二:必须唯一,第三:不能冗余 多对多:A表中的一条记录,在B中有多个对应的记录,反之,B表的一条记录也可以在A表中找到多个对应的记录 课程和学生的关系,读者与文章之间的浏览历史关系,购买者和商品的购买.... """ """添加数据""" # teacher1 = Teacher.objects.create(name="吴老师") # teacher2 = Teacher.objects.create(name="李老师") # teacher3 = Teacher.objects.create(name="张老师") # teacher4 = Teacher.objects.create(name="黄老师") # course1 = Course.objects.create(name="python终极") # course2 = Course.objects.create(name="python进阶") # course3 = Course.objects.create(name="python高级") # 通过老师添加课程,反过来也是类似 # 当B模型对象不存在,通过A模型进行关联添加,则使用create # teacher = Teacher.objects.get(pk=1) # 此处,不仅可以添加课程,还因为我们是从老师模型对象开始操作,所以还会自动把当前课程和老师的关系也同时进行绑定了。 # teacher.course_list.create( # name="javascript入门", # ) # 当B模型对象存在,通过A模型进行关联,则使用add # course = Course.objects.get(pk=3) # teacher.course_list.add(course) """查询数据""" # 查询老师授课的课程 # teacher = Teacher.objects.filter(name="吴老师").first() # print(teacher.course_list.values()) # 查询课程的授课老师 # course = Course.objects.get(pk=3) # print(course.name) # print(course.teacher_list.values()) """ 更新和删除数据: 更新和删除对应模型对象只能通过单个模型对象来完成 更新和删除关系记录,则直接可以通过外键进行删除 """ # 当希望删除2个模型之间的一条关联关系时,则使用remove # 当希望删除2个模型之间的清空关联关系时,则使用clear course = Course.objects.get(pk=3) teacher = Teacher.objects.filter(name="吴老师").first() course.teacher_list.remove(teacher)
自关联
自关联就是1张数据表,主键和外键都在一张表上。
一般会在菜单,权限列表,粉丝关注,好友关系,省市区行政区划这些业务中使用到。
创建模型的关联关系
parent = models.ForeignKey("self", on_delete=models.DO_NOTHING, related_name="son_list", null=True, blank=True)
模型代码
class Area(models.Model): """行政区划""" # 注意,django会自动创建主键,并默认字段名为:id name = models.CharField(max_length=20) parent = models.ForeignKey("self", on_delete=models.DO_NOTHING, related_name="son_list", null=True, blank=True) # 添加方法不算改变表的数据结构,所以不需要数据迁移 def __str__(self): return f"<Area {self.name}>"
视图代码
父级对象:area.pid 父级获取数据对象:area.areainfo_set.all()
"""自关联""" # 查询顶级单位 # province_list = Area.objects.filter(parent_id__isnull=True).values() province_list = Area.objects.filter(parent_id__isnull=True).all() print(province_list) province = province_list.filter(name="河南省").first() print(province) # city_list = province.son_list.values() city_list = province.son_list.all() print(city_list) city = city_list.filter(name="郑州市").first() print(city) # area_list = city.son_list.values() area_list = city.son_list.all() print(area_list) area = area_list[0] print(area) print(area.name) return JsonResponse({"msg":"ok"},safe=False)
虚拟外键
默认情况下,django中使用的外键都是数据库本身维护的物理外键,这在一定程度上会增加数据库的运行成本,消耗数据库性能
因为数据库中本身维护的物理外键原则上属于悲观锁,所以数据量大了之后DB在高并发情况会产生大量锁
因为django除了支持物理外键的关联以外,还提供了逻辑外键,也就是虚拟外键
虚拟外键本身对于数据库是不存在的,数据库中仅仅创建了一个普通索引,这个关联关系是通过模型声明来维护的。
如果没有数据库本身维护的物理外键,肯定也会存在对数据库一致性的风险
只需要在模型声明外键字段中设置属性`db_constraint=False`即可声明当前外键属于物理外键,模型中的其他设置和原来一样
创建模型的关联关系
name = models.ForeignKey('GameInfo',blank=False,null=False,db_constraint=False,on_delete=models.DO_NOTHING,verbose_name='名称')
查询优化
select_related
通过JOIN语句,在SQL查询内减少SQL查询数量
适用于一对一和一对多的关联方式中
使用
模型.objects.all().select_related()
模型.objects.all().select_related('外键字段')
模型.objects.all().select_related('外键字段__外键字段')
prefetch_related
通过 IN 语句分别查询关联的每个表的数据,然后用Python处理他们之间的关系,通过这种方式来达到减少SQL查询数量
适用于一对多和多对多的关联方式中
使用
模型.objects.prefetch_related('外键字段')
模型.objects.all().select_related('外键字段')
模型.objects.all().select_related('外键字段__外键字段')
模型管理器
自定义管理器类主要用于两种情况
重写objects中现有方法。
在管理器类中补充定义新的方法
是Django的模型进行数据库操作的接口,Django应用的每个模型类都拥有至少一个管理器。
我们在通过模型类的objects属性提供的方法操作数据库时,即是在使用一个管理器对象objects。当没有为模型类定义管理器时,Django会为每一个模型类生成一个名为objects的管理器,它是models.Manager类的对象。
自定义模型管理器
一旦为模型类指明自定义的过滤器后,Django不再生成默认管理对象objects。
#用户管理器 from django.db import models class StudentManager(models.Manager): def all(self): # 默认查询未删除的学生信息 # 工作中,一般不会存在真正的删除操作,因为用户的数据往往公司需要花费成本换回来的 # 所以往往在模型中都会存在一个代表虚拟删除的字段,一般都叫 is_delete 或者 softDelete # 调用父类的成员语法为:super().方法名 return super().filter(is_delete=False)
模型中注册
class Student(models.Model): ... my_objects = StudentManager()
视图中调用
Student.my_objects.all()
16.WSGI和ASGI
WSGI和ASGI,都是基于Python设计的网关接口(Gateway Interface,GI)。
网关接口
网关接口(Gateway Interface,GI)就是一种为了实现加载动态脚本而运行在Web服务器和Web应用程序中的通信接口,也可以理解为一份协议/规范。
只有Web服务器和Web应用程序都实现了网关接口规范以后,双方的通信才能顺利完成
常见的网关接口协议
CGI
公共网关接口(Common Gateway Interface,CGI)是最早的Web服务器主机提供信息服务的标准接口规范。
只要实现了CGI协议,Web服务器就能够获取并了解客户端提交的信息,转交给服务器端的web应用程序进行处理,最后返回结果给客户端
CGI程序是一种实现了CGI协议的程序。
FastCGI
快速通用网关接口(Fast Common Gateway Interface/FastCGI)
FastCGI是公共网关接口(CGI)的增强版本
WSGI
Web服务器网关接口(Python Web Server Gateway Interface,WSGI),是Python为了解决**Web服务器端与客户端之间的通信**基于CGI标准而设计的。
实现了WSGI协议的web服务器
uWSGI
一个快速的,自我驱动的,对开发者和系统管理员友好的应用容器服务器,完全由 C 编写,实现了WSGI协议,uwsgi,http等协议。
uwsgi 协议是一个 uWSGI服务器自有的协议,用于定义传输信息的类型
https://docs.djangoproject.com/zh-hans/3.2/howto/deployment/wsgi/uwsgi/
https://uwsgi-docs.readthedocs.io/
安装
conda config --add channels conda-forge conda install uWSGI
uwsgi配置文件
uwsgi.ini,代码样本
[uwsgi] #使用nginx连接时使用,Django程序所在服务器地址 # socket=0.0.0.0:8088 #直接做web服务器使用,Django程序所在服务器地址 http=0.0.0.0:8088 #项目根目录[绝对路径] chdir=/home/moluo/Desktop/demo #项目中wsgi.py文件的目录,相对于项目目录 wsgi-file=demo/wsgi.py # 进程数 CPU * 2 -1 processes=4 # 线程数 threads=2 # uwsgi服务器的角色 master=True # 存放进程编号的文件 pidfile=uwsgi.pid # 日志文件,因为uwsgi可以脱离终端在后台运行,日志看不见。我们以前的runserver是依赖终端的 daemonize=uwsgi.log # 指定依赖的虚拟环境 virtualenv=/home/moluo/anaconda3/envs/djdemo
操作
启动uwsgi服务器
uwsgi --ini uwsgi.ini
停止运行
uwsgi --stop uwsgi.pid
查看当前系统中的指定名称的进程
ps aux | grep uwsgi
uvicorn
gunicorn
。。。
ASGI
ASGI,是构建于WSGI接口规范之上的**异步服务器网关接口**,是WSGI的延伸和扩展。
实现了ASGI协议的web服务器
uvicorn
Uvicorn 是一个快速的 ASGI 服务器,Uvicorn 是基于 uvloop 和 httptools 构建的,是 Python 异步生态中重要的一员。
文档
https://www.uvicorn.org/
安装uvicorn
pip install uvicorn
运行
uvicorn homework.asgi:application --reload
Web服务器
Web服务器(Web Server)是一种运行于网站后台(物理服务器)的软件
主要用于提供网页浏览或文件下载服务
可以向浏览器等Web客户端提供html网页文档
可以提供其他类型的可展示文档,让客户端用户浏览
可以提供数据文件下载等
主流的Web服务器
Nginx
Apache
IIS
tomcat
gunicorn
UWSGI
Uvicorn
Web应用程序
Web应用程序(Web)是一种能完成web业务逻辑,能让用户基于web浏览器访问的应用程序
它可以是一个实现http请求和响应功能的函数或者类
也可以是Django、Flask、sanic等这样的web框架
也可以是其他语言的web程序或web框架
Web服务器和Web应用程序的区别
Web应用程序主要是完成web应用的业务逻辑的处理,Web服务器则主要是应对外部请求的接收、响应和转发。
需要使用web服务器启动运行,web应用程序才能被用户访问到。
17.异步编程
函数视图
"""同步视图""" 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!')
类视图
class Home3View(View): async def __call__(self, *args, **kwargs): return super().__call__(*args, **kwargs) def get(self, request): return HttpResponse("ok, get")
模型操作
适配器函数
async_to_sync()
异步转同步,参数就是同步函数
用法
from asgiref.sync import async_to_sync # 用法1 sync_function = async_to_sync(async_function)
from asgiref.sync import async_to_sync # 用法2 @async_to_sync async def async_function(...): pass
sync_to_async()
同步转异步,参数就是异步函数
用法
from asgiref.sync import sync_to_async # 用法1 async_function = sync_to_async(sync_function)
from asgiref.sync import sync_to_async # 用法2 @sync_to_async def sync_function(...): pass
# 先声明数据库同步转异步 @sync_to_async(thread_sensitive=True) def get_student_by_id(id): return Student.objects.get(pk=id) # 异步视图,直接调用异步操作 async def home5(request): ret = await get_student_by_id(id=10) print(ret.name) return HttpResponse('Hello, async world!')
第三方异步支持
redis异步库:aioredis
mysql异步库:aiomysql
mongoDB异步库:motor
http网络请求异步库:httpx
import asyncio from time import sleep import httpx from django.http import HttpResponse # 异步任务 import httpx async def async_task(): for num in range(1, 6): await asyncio.sleep(1) print(num) async with httpx.AsyncClient() as client: # 打开异步http请求客户端 r = await client.get("https://httpbin.org/") print(r) # 异步视图 - 调用异步任务 # aio, bio, nio async def async_view(request): loop = asyncio.get_event_loop() loop.create_task(async_task()) return HttpResponse("Async Non-Blocking HTTP request")
异步中间件
import asyncio from django.utils.decorators import sync_and_async_middleware @sync_and_async_middleware def simple_middleware(get_response): # One-time configuration and initialization goes here. if asyncio.iscoroutinefunction(get_response): # 判断是否是协程异步 async def middleware(request): # Do something here! response = await get_response(request) return response else: def middleware(request): # Do something here! response = get_response(request) return response return middleware