Django 框架是一个基于 python 的重量级的 web 开发框架,现今很多大公司大项目都是使用 Django 框架。采用了 MVC(model view controller) 的框架模式,python 中此模式也被称为 MTV(model template view) 模式。
可以使用 pip install django
来安装 Django 库
Django官方文档
可以通过终端和 pycharm 两种方式来创建项目,区别是 pycharm 创建的项目会额外添加了一些模板信息。
Django 创建项目有点类似于 Scrapy ,在要创建项目的文件夹下,使用终端(命令行)输入命令。django_admin.exe 是在 python 下的 Scripts 下,默认添加到了环境变量中,所以可以直接执行。
django-admin startproject 项目名称
执行完成后,会在当前目录下创建一个项目名称命名的文件夹。这样项目就创建成功了。
在 pycharm 中新建项目时,选择 Django 项目,即可创建 Django 项目了。需要注意的是,需使用企业版的 pycharm。
创建完成的项目目录结构为(learnDjango是项目名称,也是主工程目录):
learnDjango
│ manage.py
│
└─ learnDjango
asgi.py
settings.py
urls.py
wsgi.py
__init__.py
可以在主工程目录下的 settings.py 中进行项目配置
en-us
,中文使用 zh-hans
Asia/Shanghai
)os.path.join(BASE_DIR, 'media')
一个项目下可以有 N 个独立的 app 来处理不同的功能,每个 app 可以拥有独立的、不同的表结构、函数、HTML模板、CSS等。
终端里,创建好的项目文件夹下,带参数运行 manage.py 即可创建一个新的 app:
python manage.py startapp app名称
app 的目录结构为(app01是app的名称)
app01
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ views.py
│ __init__.py
│
└─migrations
__init__.py
当创建好 app 后,在 app 的目录下有 apps.py ,这里注册了 app 的一些配置信息。例如
当创建了项目和app之后,到应用启动还需要注意以下几点:
在 app 目录下的 apps.py 中能看到类名称,通常是 app项目名+Config。将此类名称添加到 settings.py 里的 INSTALLED_APPS 列表中即可。需注意的是添加目录名、文件名和启动类名,例如 ‘app01.apps.App01Config’
在 urls.py 中编写对应关系。添加到 urlpatterns 列表中,格式为
path('路由路径', 函数名)
。例如 path(‘index/’, views.index)。另外注意的是,需要导入 views.py 文件。
在 views.py 中编写视图函数。需注意的是,视图函数默认需有形参 request。例如:
from django.shortcuts import render,HttpResponse
# Create your views here.
def index(request):
return HttpResponse("初学 Django")
在终端中输入 python manage.py runserver
来启动 django 服务,使用 ctrl + c 来结束服务。也可以添加参数,来确定访问地址。例如
python manage.py runserver 0:8000
python manage.py runserver 0.0.0.0:8000
就表示可用IP为 0.0.0.0 ,端口为 8000
使用 pycharm 建立的 django 项目可以通过默认启动项直接启动,默认快捷键是 shift + F10
如果需要由其他的 python 脚本调用启动 django 服务,可以使用这种方法:
# 创建文件和 manage.py 放在一起,可以参考 manage.py 的内容。调用 run_django 即可启动 django 服务
from django.core.management import execute_from_command_line
def run_django():
# 添加 Django 的设置环境,第二个参数指向工程目录的 settings.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Django.settings')
# 执行运行的指令,是个列表,可以更改 ip 和端口
execute_from_command_line(['manage.py', 'runserver', '0.0.0.0:9600'])
路由及相应的视图处理函数在工程目录(与项目同名的文件夹)下的 urls.py 中注册(或在 app 目录下的 urls.py 中进行注册,这种叫做子路由,方式及语法同主路由,只是需要在主路由中进行注册)主路由,注册到 urlpatterns 下。 urlpatterns 是一个列表,每个成员是一个 partial 类。
可以由 path()
方法创建,路由地址及视图处理函数作为参数,注意 django 中的路由都不用反斜杠(/)起始
from django.urls import include
urlpatterns = [
path('', views.login),
# 子路由需要在主路由中进行注册
path('user/', include('app.urls')) # 所有 user 下的路由使用子路由,子路由不需要注册上级路由
path('index', views.index)
]
也可以使用正则表达式
# 此种方法为老版本的 django,4.0以后已经废除!!!!!
from django.conf.urls import url
urlpatterns = [
url(r'^hi/(?P\w+)$' , views.hello),
]
# 此种方式会使用路径传参,将匹配正则的参数以关键参数 name 传入视图处理函数
# 如果不指定关键参数传值,则会按照括号的顺序传值
# 需注意的是传参必须使用括号,视图处理函数必须接收参数
或者
# 新版本 django 支持的方式,使用 re_path 方法
from django.urls import path, re_path
urlpatterns = [
path('', views.login),
re_path(r'hi/(\w+)/(\d{4})', views.hello),
]
注册路由时,使用 path 方法注册视图处理函数(或注册子路由)。在注册时其实有一个参数来定义名称(或命名空间):
from django.urls import include
app_name = 'app' # 注意将当前 app 注册名称
urlpatterns = [
path('', views.login, name=None),
# 子路由需要在主路由中进行注册
path('user/', include('app.urls', namespace='user')) # 所有 user 下的路由使用子路由,子路由不需要注册上级路由
path('index', views.index, name='index')
]
可以通过定义的名称或命名空间来反向解析路由路径。需要注意的是,使用子路由时,需要定义 app_name (一般在app 的 urls.py 中),否则会报错。反向解析路由字符串为 namespace:name
或 app_name:name
在模板中,可以使用 {% url %} 结构来获取反向解析路由,语法为 {% url 反向解析路由字符串 参数1 参数2 %},参数可以是多个,使用空格分隔,默认位置传参,也可以使用关键参数传参。例如
<a href="{% url 'user:list' id=1 5 %}">链接a>
需注意的是,反向解析路由字符串是由 urls.py 中的 app_name、namespace、和 name 定义的字符串组成的。传递的参数是路径传参的形式,不是 GET 参数形式。
在视图函数中,使用 reverse()
函数来反向解析路由路径。然后可以使用 redirect 或 RedirectHttpResponse 来重定向。传递的参数也是路径传参
from django.urls import reverse
url = reverse('user:list',args=('id',5))
也可以使用字典传递关键字参数
from django.urls import reverse
url = reverse('user:list', kwargs=dict(id=1, page=5))
在视图函数中使用 render()
方法可以返回 html 页面
def index(request):
return render(request, 'index.html')
默认情况下 django 会在 app 目录下的 templates 目录下来查找需要使用的 html 页面文件。
需要注意的是,django 并不是在当前 app 的 templates 目录下寻找,而是根据 app 的注册顺序,查找相应 app 目录下的 templates 目录下查找 html 文件。
另外,可以在项目 settings.py 文件中的 TEMPLATES 字段下的 ‘DIRS’ 添加参数 [os.path.join(BASE_DIR, 'templates')]
,则优先在项目根目录下的 templates 目录下查找 html 文件。
在开发过程中,一般将图片、CSS、js 等作为静态文件处理。django 会将静态文件存放在 app 目录下的 static 文件夹下。html 指向静态文件时使用相对路径,例如 src="/static/1.png"
。所以一般 static 目录下会创建 img、js、css、plugins 等文件夹存放相应的静态文件。
不过 django 推荐这样使用静态资源:
{% load static %}
<link rel="stylesheet" href="{% static 'plugins/bootstrap-5.1.3-dist/css/bootstrap.css' %}">
如果需要更改 static 目录,则在 settings.py 文件里的 STATIC_URL(静态文件的 url 地址) 和 STATICFILES_DIRS(静态文件的物理地址) 里更改。
# setting.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
浏览器会经常访问django获取 favicon.ico
,用于显示浏览器标签图标。设置好静态路径后,可以保存一个图标到静态目录(可以是非 app 的静态文件夹)例如项目目录下的 static 下,然后在 urls.py 中添加路由:
# urls.py
from django.contrib.staticfiles import views
urlpatterns = [
...
path('favicon.ico', views.serve, {'path': 'img/favicon.ico'}), # 即ico文件为 static/img/favicon.ico
]
这种在开发模式中可以使用,生产模式中因为通常静态资源是由 nginx 之类的负责,所以 django 没有相应配置。但是可以通过修改页面模板的方式指定所要访问的静态资源。
{% load static %}
<head>
<link rel="shortcut icon" href="{% static 'img/favicon.ico' %}" type="image/x-icon">
head>
在开发环境中(DEBUG=True)
,django.contrib.staticfiles 会自动帮助寻找静态路径。在生产环境中(DEBUG=False)
,django.contrib.staticfiles就失效了,需要我们自己设置静态路径。
# settings.py
DEBUG = False
STATIC_URL = '/static/' # 静态资源 url 地址
if DEBUG: # 开发模式
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), # 配置非 app 绑定的静态资源路径,后面逗号一定要加
]
else: # 生产模式起作用
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
from django.views.static import serve
urlpatterns.append(re_path(r"^static/(?P.*)$" , serve, {'document_root': settings.STATIC_ROOT}))
python manage.py collectstatic
如果使用了自定义的静态资源,例如用户上传文件 MEDIA ,则除了需要在 settings.py 中进行配置(例如 MEDIA_URL 和 MEDIA_ROOT)外,还需要在 urls.py 中进行注册。
# setting.py
MEDIA_URL = '/upload/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'upload'),
# urls.py
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
生产环境下的路径配置也类似于静态文件
urlpatterns.append(re_path(r"^media/(?P.*)$" , serve, {'document_root': settings.MEDIA_ROOT}))
在前端中,可以使用 {{ MEDIA_URL }}
来获取 MEDIA 的url路径。例如
<img id="Img" class="Img" src="{{ MEDIA_URL }}img/img.jpg"/>
在视图函数中,可以使用 from django.conf import settings
包的 settings.MEDIA_ROOT 来获取自定义文件绝对路径。例如保存一个上传的图像文件:
def upload(request):
if request.method == 'POST':
'''获取图片,此时文件格式为二进制'''
pic = request.FILES.get('file')
'''拼接图片路径'''
url = settings.MEDIA_ROOT + 'images/' + pic.name
with open(url, 'wb') as f:
for data in pic.chunks():
f.write(data)
'''健康码图片名字'''
name = pic.name
在 settings.py 中注册路由时,会绑定相应的视图(view)处理函数,即使用指定的函数处理路由请求。视图处理函数默认有一个参数 request 即请求对象,返回 HttpResponse 对象即响应对象。
在面向对象的设计思想中,能够通过继承、重写等相关特性实现业务功能。所以除了使用函数处理视图(FBV Function-based View)外,还能够使用类处理视图(Class-based View)。
Django 接收到一个请求时,一般会经过以下基本流程
需注意的是,这个是基本流程,在实际使用中可能会不太一样。
视图函数中有一个默认形参 request,它是一个对象,封装了用户发送过来的所有请求数据。
request 对象的常用属性和方法有:
def index(request):
if request.method == "GET":
return render(request, "login.html")
可以使用 request.GET.get()
方法获取查询参数
username = request.GET.get("user")
password = request.GET.get("pwd")
可以使用 request.POST.get()
方法获取正文参数
username = request.POST.get("user")
password = request.POST.get("pwd")
get 请求和 post 请求可以附带很多参数,且参数名可以重复。对于重复的参数名,可以使用 request.GET.getlist()
或 request.POST.getlist()
方法获取所有的参数值列表。
userlist = request.GET.getlist('user')
在路由路径中,除了使用正则可以获取路径中的参数外,可以使用 <>
直接指定参数类型和名称,来传递参数到视图处理函数中。注意视图处理函数中需使用形参获取参数。
# urls.py
urlpatterns = [
path('depart//edit/' , views.depart_edit)
]
# views.py
def depart_edit(request, nid):
pass
django 支持类型转换器 str 、int、slug(ASCII字母数字下划线和连字符)、uuid 等
响应使用的 render、HttpResponse、redirect 都在 django.shortcuts 里
响应对象可以直接创建,一般有 HttpResponse、HttpResponseRedirect、JsonResponse 三个类,当然也可以使用 render()
、redirect()
等函数快速创建。
使用 HttpResponse 对象可以直接返回响应包,例如 HttpResponse(‘String’) 的响应包返回内容就是字符串。
def index(request):
return HttpResponse('hi, Django
')
使用 render() 方法通常返回一个 html 页面,也可以使用此方法向页面中传递数据(渲染模板)。
def index(request: HttpRequest):
user={'id': 1, 'name': '张三'}
return render(request, 'index.html', user=user)
需注意 render()
方法必须返回 request 对象。
redirect() 方法会重定向到另一个 url ,使用方法为:return redirect("https://www.baidu.com")
可以直接返回一个 JsonResponse 对象来返回 json 数据
可以通过设置响应包的 content 和 content_type 返回其他类型的数据,例如图片:
def pic(request):
with open('images/001.jpg', 'rb') as f:
content_bytes = f.read()
resp = HttpResponse(content=content_bytes, content_type='image/jpeg', status=200)
return resp
在 django 中,response 对象具有 dict 的特性。所以可以使用 response[key]=value
的方法设置响应头。
除了在 Response 对象中添加状态码外,还可以自定义错误响应模板。例如自定义一个 html 页面,表示 404 错误,只要在工程的模板目录中,创建 404.html 文件即可。Django 在遇到 404 错误时会直接返回此页面,使用 {{ request_path }} 则可以获取错误请求的 url 地址。
模板语法本质上是写 HTML 时写一些占位符,由数据对占位符进行替换和处理。django 的模板语言(DTL 即 Django Template Language)使用的是 Mustache 语法,这个语法很多地方都在使用,例如 Flask、Vue 等。
模板主要分为加载和渲染两部,加载就是读取模板文件,渲染是处理需要的数据语句等,修改加载的模板文件,称为最终我们想要呈现给用户的页面文件。
from django.template import loader
# 加载模板文件
template = loader.get_template('a.html')
# 以 context 设置的数据处理加载的模板文件,替换相应语句或变量等,生成呈现的页面文件
result = template.render(context={'msg': 'haha'})
为了方便使用,可以将加载和渲染进行结合,同时使用
result = loader.render_to_string('a.html', context={})
此时,可以将渲染好的页面进行缓存,在需要时添加到响应中返回(使用 HttpResponse(result)
)。
在使用中,可以进一步简化,使用 render()
方法,直接渲染模板并返回响应。
def index(request):
text = '初学 Django'
return render(request, 'index.html', n1=text)
flask 的模板语法都是包含在 {}
中的。
在模板语言中,不支持方括号式获取索引下标、元素等,而是使用 “.” 。这个点使用在很多地方获取数据
注释内容不会被渲染。使用{# 注释内容 #}
进行单行注释,使用 {% comment %}
和 {% endcomment %}
进行多行注释。
使用双花括号可以接收数据或使用过滤器
使用 render()
方法,将数据发送给页面相应的变量:
def index(request):
text = '初学 Django'
return render(request, 'index.html', {'n1': text})
render()
方法的第三个参数就是传递数据,它是个上下文参数,也可以使用字典对象来传递多个数据。
在页面中使用 {{ 变量名 }} 来接送 render()
方法传递的数据。
<h1> {{ n1 }} h1>
可以通过 with 标签在模板中创建变量,此变量可以使用视图传递的数据,也可以使用常量或表达式
{% with aaa = n1 %}
<h1> {{ aaa }} </h1>
{% endwith %}
{% with bbb = n1 + '111' %}
<h1> {{ bbb }} </h1>
{% endwith %}
过滤器,就是将传入的数据以某些方法进行过滤或修改。使用管道符分隔数据和方法,冒号添加方法的参数(部分参数也可以写到括号内,类似于调用 python 的相应方法传参),可以类似链式调用的方式多次过滤。其格式是
{{ 数据变量 | 过滤方法1:参数 | 过滤方法2:参数 | 过滤方法3:参数 }}
例如:
{{ 日期数据 | date:“Y-m-d H:i:s” }}
使用方法上大部分都类似于 python 的相应方法。
常用的过滤器有
add : 算术运算加法
divisibleby : 能够被整除
capfirst : 整个字符串首字母大写
lower : 全小写
upper : 全大写
title : 字符串中每个单词首字母大写
trim : 去除前后空格
reverse : 逆序排列(反转)
format : 格式化字符串
tojson : 转换为 json 对象
striptags : 渲染之前将值中的标签去掉(如果不去掉则直接以文本显示)
addslashes : 添加反斜杠到任何反斜杠、单引号或者双引号前面。不适用,则这些符号可能会因为转义而不被输出到模板中
safe : 确定值中的标签是安全的,可以渲染出来
escape : 标签不安全,渲染为 html 字符而不是标签
default : 如果值未定义,则返回默认值
last : 返回序列的最后一项
first : 返回序列的第一项
sum : 返回序列数字之和
sort : 排序,类似于 python 的 sort 方法
unique : 序列中去重,以迭代器方式返回,通常和 join 一起使用
join : 类似 python 的 join 方法,使用指定连字符连接可迭代对象中每一个元素,返回字符串
list : 将生成器、可迭代对象转为列表
floatformat : 小数格式化
filesizeformat : 文件大小格式化
也可以自定义过滤器,一般写在 app 的 init.py 中。例如写一个格式化日期的过滤器
from django.template.defaultfilters import register
@register.filter('datefmt') # 定义一个名称为 datefmt 的过滤器
def datefmt(value, *args): # value 是获取的值,args 是获取的参数
if type(value) == datetime:
return value.strftime(args[0])
else:
from datetime imoprt datetime
return datetime.strptime(value, args[0])
列表数据的传递和单个数据是一样的。注意下标使用 . 而不是方括号
def index(request):
text=["Python", "C", "Java", "Basic"]
return render(request, 'index.html', n1=text)
<h1>计算机编程语言有 {{ n1.0 }},{{ n1.1 }},{{ n1.2 }},{{ n1.3 }} 等h1>
字典数据的传递是一样的。接收时,使用 字典名.key
来获取字典数据。
def index(request):
text ={"y1": "Python", "y2": "C", "y3": "Java", "y4": "Basic"}
return render(request, 'index.html', n1=text)
<h1>第一种语言是 {{ n1.y1 }},第二种语言是 {{ n1.y2 }},第三种语言是 {{ n1.y3 }},第四种语言是 {{ n1.y4 }}h1>
{% %} 这个方法主要用在结构标签、宏定义和循环及流程控制中。
结构标签主要包含 block、extends和include
block 和 extends 主要用于母版。有些页面内容可以作为母版使用,在母版页面中填充子页面,可以提高代码复用,减少工作量。
在母版页中,需要更改内容(放置子页面)的地方,使用 block 进行标记,即制作好了母版页。
{% load static %}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>部门列表title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
head>
<body>
<script src="{% static 'js/jquery-3.6.0.min.js' %}">script>
<script src="{% static 'plugins/bootstrap-3.3.7-dist/js/bootstrap.min.js' %}">script>
{# 导航条 #}
<nav class="navbar navbar-default">
...
nav>
{# 内容 #}
<div>
{% block content %}{% endblock %}
div>
body>
html>
在子页面的第一行,写入要继承的母版页,然后将需要填充到母版页的代码同样使用 block 标记即可。
{% extends 'layout.html' %}
{% block content %}
{#内容#}
<div class="container">
<div class="panel panel-default">
...
div>
div>
{% endblock %}
需注意的是,使用相同的 block 标记(即 block 后的名称相同,通常用在子页面被其它子页面继承的情况),后者会覆盖前者。如果不想覆盖, 需要后者在 block 块内部添加 {{ block.super }} 继承父模板的内容。
在当前页面中,导入目标页面内容,则使用 {% include '页面文件’ %}
。这种方法将导入页面的 head 标签和 body 标签去掉(但是保留其内容),然后再进行整体导入,例如广告栏等。
此方法可能会因 id 冲突、脚本冲突等原因引起页面混乱,所以不推荐使用。一般使用在引入 css 文件等地方。
在 jinjia 中有宏的概念,即模板中的函数。django 不支持使用宏,但是可以另类的使用 {% include %}
标签达到宏的效果。
{%if partial_name == 'partial1'%}
{% for item in list %}
<li>{{ item }}li>
{% endfor %}
{%endif%}
{%if partial_name == 'partial2'%}
{% for item in list %}
<li>{{ item }}li>
{% endfor %}
{%endif%}
{%if partial_name == 'partial3'%}
{% for item in list %}
<li>{{ item }}li>
{% endfor %}
{%endif%}
{% include 'partial.html' with list=list1 partial_name="partial1"%}
{% include 'partial.html' with list=list2 partial_name="partial2"%}
{% include 'partial.html' with list=list3 partial_name="partial3"%}
在这种调用方式中,也可以进行传参。
如果需要在模板中临时定义变量,可以使用 {% with %}
标签。例如
{% with age=user.age %}
{{ age|add:"2" }}
{% endwith %}
{% with age=20 %}
{{ age|add:"2" }}
{% endwith %}
使用循环语句进行循环控制,使用判断语句进行流程控制
循环的语法如下:
{% for item in n1 %}
<span>{{ item }}span>
{% empty %}
<span> 没有数据 span>
{% endfor %}
可以添加 reversed 来反向循环一个列表
{% for item in n1 reversed %}
<span>{{ item }}span>
{% endfor %}
字典也可以使用循环
{% for k, v in n1.items %}
<li>{{ k }} = {{ v }} li>
{% endfor %}
还可以使用 forloop 对象来获取一些循环的数据信息,例如
{% for item in n1 %}
<span>{{ forloop.counter }} - {{ item }}span>
{% endfor %}
forloop 的一些常用的方法有
forloop.counter : 当前循环次数(从1开始循环)
forloop.counter0 : 当前循环次数(从0开始循环)
forloop.revcounter : 循环剩余的元素数量(counter的逆序)
forloop.revcounter0 : 同 revcounter ,只是索引从0开始(counter0的逆序)
forloop.first : 是否为循环的第一个元素
forloop.last : 是否为循环最后一个元素
forloop.parentloop : 另因为 forloop 变量只在循环内部可用。模板解析器遇到 {% endfor %} 时, forloop 随之消失。所以在嵌套的循环中, 使用 forloop.parentloop 引用父级循环的 forloop 对象。
cycle 会循环它后面的每一个出现的东西,例如
{% for foo in list %}
{% cycle 'row1' 'row2' %}
{% endfor %}
则第一次循环到 cycle ,则输出 row1,第二次就是 row2 ,依此类推。也可以将循环选择器设置为变量
{% cycle 'row1' 'row2' as cbc %}
{% cycle 'row3' 'row3' as aaa%}
使用的时候直接使用变量就可以了,就像 {{cbc}} 和 {{aaa}}。但这样写在定义的时候会显示当前的输出:
{% for foo in list %}
我是定义1:{% cycle 'row1' 'row2' as cbc %}
我是定义2:{% cycle 'row3' 'row4' as aaa %}
变量1:{{ cbc }}
变量2:{{ aaa }}
{% endfor %}
如果不想在定义时输出,可以添加 silent 使其沉默
{% for foo in list %}
我是定义1:{% cycle 'row1' 'row2' as cbc silent%}
我是定义2:{% cycle 'row3' 'row4' as aaa silent%}
变量1:{{ cbc }}
变量2:{{ aaa }}
{% endfor %}
使用变量时,可以不使用 for 标签进行循环,而是直接调用变量,也可以达到循环输出的效果
我是定义1:{% cycle 'row1' 'row2' as cbc %}
变量1:{{ cbc }}
循环:{% cycle cbc %}
循环:{% cycle cbc %}
循环:{% cycle cbc %}
restecycle 标签可以重置之前的 cycle 标签,使其从第一项开始重新启动。
{% for coach in coach_list %}
<h1>{{ coach.name }}h1>
{% for athlete in coach.athlete_set.all %}
<p class="{% cycle 'odd' 'even' %}">{{ athlete.name }}p>
{% endfor %}
{% resetcycle %}
{% endfor %}
判断语句语法如下:
{% if text == "Django老手" %}
<h1> 高手高手高高手 h1>
{% elif text == "初学 Django" %}
<h1> 菜鸟一个 h1>
{% else %}
<h1> 无法识别 h1>
{% endif %}
{% if x > 0 %}
...
{% endif %}
也可以在判断中使用逻辑判断
{% if condition1 and condition2 %}
<h1>同时满足两个判断条件h1>
{% endif %}
{% if condition1 or condition2 %}
<h1>满足两个条件其中之一h1>
{% endif %}
firstof 标签可以输出第一个不是 false 的变量,如果所有传递的变量都是 false,则不输出任何内容,也可以添加一个后备值。
{% firstof var1 var2 var3 'fallback value' %}
# 相当于
{% if var1 %}
{{ var1 }}
{% elif var2 %}
{{ var2 }}
{% elif var3 %}
{{ var3 }}
{% else %}
fallback value
{% endif %}
如果将一段 HTML 代码以变量形式渲染到页面上,为了安全起见,渲染结果是作为字符串进行渲染,而不是作为 HTML 代码渲染。如果需要关闭这个功能,则可以使用 autoescape 标签
{% autoescape off %}
{{ info }}
{% endautoescape %}
此时通过变量 info 传递的文本就可以作为 html 代码使用了。而使用过滤器 escape 则可以在关闭区域将某个变量设置为 escape ,将符号等渲染为 html 字符。
默认在DTL模板中是会去解析那些特殊字符的。比如{%
和%}
以及{{
等。如果你在某个代码片段中不想使用DTL的解析引擎。那么你可以把这个代码片段放在verbatim标签中。例如使用 vue 等框架的时候。
{% verbatim %}
{{if dying}}Still alive.{{/if}}
{% endverbatim %}
django 为了防止 XSS 攻击,每当收到 post 请求时会验证其 csrf_token 是否和 cookie 中的数据一致,使用django.middleware.csrf.CsrfViewMiddleware
中间件来验证 csrf_token ,如果没有或不一致就会报错。这就需要模板在所有 form 表单中加上 {% csrf_token %}
标签,会在渲染时生成一个 input 标签。这样在每次客户在请求 csrf_token 时,会在服务器端生成一个 token,渲染至模板并存放在 cookie 里面,提交时也会将此 token 随表单一起提交以便验证。
在 ajax 等不方便加 {% csrf_token %}
标签的地方,由2种处理方式:关闭 csrf_token 验证和手动获取验证 token。
虽然可以通过在 settings.py 中取消使用中间件的方式关闭 csrf_token 验证,但是不建议这么做。通常会使用在视图处理函数中使用装饰器 @csrf_exempt
声明此函数不进行 csrf_token 校验。
如果需要手动使用 csrf_token,则有三种方法:
{{ csrf_token }}
直接渲染数据,也可以使用 {% csrf_token %}
标签,并获取 name=csrfmiddlewaretoken
的 input 标签元素的 value 值即可。X-CSRFToken
项中。注意需要使用 jQuery 等能够修改请求头的方法,来添加 X-CSRFToken
项。这种方法$.ajax({
url:"/app03/ajax/",
type:'post',
headers:{"X-CSRFToken":$.cookie('csrftoken')}, // 在请求头中添加 csrf_token 信息
data:{
uanme:$('#username').val(),
//csrfmiddlewaretoken:$("[name=csrfmiddlewaretoken]").val(), 请求体中加 {% csrf_token %} 渲染的数据
//csrfmiddlewaretoken:"{{ csrf_token }}", 请求体中加直接渲染的数据
},
success:(res) => {}
})
from django.middleware.csrf import get_token
# 获取cstftoken
def getToken(request):
token = get_token(request)
return JsonResponse({'token': token})
在测试中,cookie 中保存的 csrf_token 和通过视图处理函数方法返回的 token 值不一样,但是无论是在数据体中还是请求头中使用哪个值都是可以的。另外验证的时候需要 cookie 中保存的有 csrftoken 值,如果 post 请求 cookie 中没有 csrftoken 值,无论请求体中验证还是请求头中验证都无法通过。
如果模板中有部分内容需要根据某些数据动态生成,但是生成后基本不会改变,并且会频繁的调用。如果每次调用就重新获取数据进行动态渲染会浪费很多计算资源,这时可以使用模板片段缓存,将渲染的片段部分进行缓存,需要的时候直接调用。
使用 {% load cache %}
和 {% endcache %}
来表明此模板片段使用缓存。缓存片段中需要显示确定缓存时间(单位为秒)和片段名称。
{% load cache %}
{% cache 500 sidebar %}
{% endcache %}
根据需要,可以将这个片段内的动态缓存多个版本,例如上个例子可以根据站点每个用户生成不同版本的缓存。需要给 {% cache %}
再传递一个参数来标识区分这个缓存片段
{% load cache %}
{% cache 500 sidebar request.user.username %}
{% endcache %}
缓存超时时间可以使用模板变量。
具体的缓存机制后面缓存部分再分析。模板片段缓存默认尝试使用名称为 template_fragments 的缓存,如果没有则使用默认缓存。也可以使用关键字 using
指定高速缓存,该参数必须是标记的组后一个参数。如果该参数指定的缓存名称未配置则会报错。
{% cache 500 sidebar ...一些参数 using="localcache" %}
如果需要手动删除模板片段缓存,则可以获得该缓存名称,再进行删除
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
# 模板中的缓存片段为 {% cache 300 sidebar username %}
key = make_template_fragment_key('sidebar', [username])
cache.delete(key)
Django 开发操作数据库更简单,其内部提供了 ORM 框架,可以轻松的访问数据库。Django 官方支持 PostgreSQL、MariaDB、MySQL、Oracle、SQLite,还支持 MS SQL Server 等提供的后端。
django 数据库是基于其他的第三方库,所以需要先安装。 这里使用 MySQL 数据库。因为 django 对 pymysql 的支持不是很好,据说有错误,所以使用 mysqlclient 库。pip install mysqlclient
如果需要使用 pymysql 的话,可以执行 pymysql.install_as_MySQLdb()
,然后就可以使用 pymysql 了。这种默认执行一般可以放在 init.py 文件中。
ORM(Object Relational Mapping) 即 对象关系映射 ,可以方便不会数据库的开发人员快速操作数据库,另外还有防止注入攻击等优点。
将实体类(Model 或 Entries)和数据库表直接建立关联关系,即 类->表,类对象->表记录集,类对象的属性->表字段。当 ORM 的关系映射成功后,直接操作类或对象,就能够操作数据库中表或记录。
django 连接数据库需要在 setting.py 中对 DATABASES 列表字段进行设置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'NAME': 'mytest', # 数据库名
'USER': 'root', # 访问数据库用户名
'PASSWORD': '123456', # 访问数据库用户的密码
'HOST': 'localhost', # 数据库主机
'PORT': 3306 # 访问数据库的端口
}
}
因为每个表在数据模型中是一个类,所以可以将共性的字段提取出来形成一个抽象的基类,由实体数据模型类继承。在模型类中的元信息中,声明了 abstract=True
则声明此类为抽象类,不会产生实体数据模型表。需注意的是,抽象基类也必须继承自 models.Model 类。
django 对于表的操作是在 models.py 文件中进行。
在此文件中,创建一个类,继承 models.Model 类,即可创建表。然后在内的成员中定义表结构。
class UserInfo(models.Model):
name = models.CharField(max_length=16, verbose_name='姓名')
password = models.CharField(max_length=64, verbose_name='密码')
age = models.IntegerField(verbose_name='年龄')
account = models.DecimalField(verbose_name='工资账户金额', max_digits=10, decimal_places=2, default=0) # 精准小数,最大位数10位,小数位2位,默认值为0
create_time = models.DateTimeField(verbose_name='入职时间')
depart = models.ForeignKey(to='Department', to_field='id', on_delete=models.CASCADE) # 外键,关联到 Department 表的 id 字段,级联删除
gender_choices = ((1, '男'), (2, '女')) # 使用了 choices 参数,取值时能够使用 get_xxx_display() 方法获取对应值
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices) # xxx 为字段名
class Meta: # 元信息
db_table = 'user_info' # 如果不设置表名,则表名为 app名称 + 下划线 + 小写类名
verbose_name = '用户信息' # 数据模型中使用的表的别名
verbose_name_plural = verbose_name # 数据模型中使用的复数别名
ordering = ['-age'] # 设置排序字段,+升序 -降序
abstract = False # 是否为抽象类,默认 False
app_label = 'app' # 指定了当前模型的应用名称,可以省略
# permissions = (('定义权限','权限说明'))
数据库中,字段名称就是定义的变量名,另外会自动添加一个名称为 id 的 bigint 型的自增字段作为主键。
常用的类型有:
# 使用 UUID
# 模型类
class Order(models.Model):
# 默认情况下,会自动创建主键 id,但是也可以显式创建主键
no = models.UUIDField(verbose_name='订单编号',primary_key=True)
price = models.DecimalField(verbose_name='订单金额',max_digits=6,decimal_places=2)
pay_state = models.BooleanField(defautl=False)
# no 的值默认取uuid.uuid4().hex,但是如果使用default参数可能会出现取到上一个 uuid 的情况,所以在 save 方法中获取
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if not self.no: # 如果是新添加的数据
self.no = uuid.uuid4().hex # 在保存时获取 uuid
super().save()
# 数据对象
import uuid
Order.objects.create(price=1819.56,pay_state=True)
# 使用 FileField 和 ImageField
# 需要注意的是,文件字段和图片字段都属于MEDIA的多媒体类型文件,需要在settings.py中指定
# MEDIA_ROOT、MEDIA_URL、STATIC_URL、STATICFILES_DIRS
# 另外使用 ImageField 时需要安装 Pillow 库
# 模型类
# 参数 storage 是存储组件,默认值是 None,使用 django.core.files.storage.FileSystemStorage
# 参数 upload_to 是指定上传路径,是相对于设置好的静态资源路径的
class Upload(models.Model):
f = models.FileField()
# 图片比文件多了宽、高存储字段两个参数
img = models.ImageField(verbose_name='图片',upload_to='img',width_field='width',height_field='height')
width = models.IntegerField(verbose_name='图片宽度')
height = models.IntegerField(verbose_name='图片高度')
# 数据对象
Upload.objects.create()
# 使用 DateField
# 格式为 YYYY-MM-DD
# 参数 auto_now_add 对象第一次保存时,自动设置为当前时间,用于“创建时间”
# 参数 auto_now 对象每次保存时,自动设置为当前时间,用于“最后一次修改时间”
create_time = models.DateField(verbose_name='创建时间',auto_now_add=True)
last_time = models.DateField(verbose_name='最后保存时间',auto_now=True)
常用的参数(约束)有:
# choices 选择约束
gender_choices = ((1, '男'), (2, '女'))
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
只需要在 models.py 文件中,将需要删除的表相对的类删除(或注释),即可删除表。同理删除字段就是在类定义中将相应字段定义的成员删除。
更改表或字段和删除表或字段类似,更改 models.py 文件中相应的类或类的成员即可。需要注意的是更改时数据库对数据的约束条件。例如没有设置可为空或具有默认值的字段,添加时要输入默认值等。
在终端执行命令
python manage.py makemigrations
就会读取并处理 models.py,如果第一次则会在 migrations 文件夹下建立索引为 1 的初始化文件 initial.py 。之后每次执行此命令会根据当时的 models.py 和 migrations 下的历史文件,对数据库表进行比较,创建结构变更记录文件(生成迁移文件)。
然后在终端执行
python manage.py migrate
就会根据 migratrons 下最后的变更文件,对数据库相应表的结构进行变动(进行迁移)。
另外需要注意的是,需要在 setting.py 文件中注册相应的 app。
使用验证器,可以对数据模型字段的内容进行验证,如果验证不通过可以抛出 ValidationError 信息。
from django.core.exceptions import ValidationError
# 自定义验证类和验证方法
class UserValidator:
# 必须是类方法而不是对象方法
@classmethod
def valid_phone(cls, value):
if not re.match(r'1[1-57-9]\d{9}',value): # 没有通过正则验证
raise ValidationError('手机号格式不正确')
# 可以不返回,验证器只要不抛出异常就算通过
return True
# 在数据模型字段中添加验证
class UserEntity(models.Model):
phone = models.CharField(max_length=11,
verbose_name='手机号',
blank=True,
null=True,
# 验证器是个列表,可以添加多个验证函数或方法
validators=[UserValidator.valid_phone])
使用 ModelForm 组件可以将抛出的错误信息直接呈现给用户。
Django 的数据模型操作涉及一个对象 objects
,它是查询结果集对象,所有的 CURD 操作都会围绕这个对象来进行。
在实际使用中,会在视图函数中进行数据操作,所以在 views.py 中需要引入 models.py 文件。
在视图函数中,可以使用表相应类的 create 方法添加数据
from app01 import models
# 添加数据,使用 models.表相应的类名称.objects.create(字段=值,字段=值...),返回值是新创建的数据对象实例
obj = models.Department.objects.create(title='销售部')
# 为了避免重复创建数据表中已存在的条目,Django还提供了get_or_create()。它会返回查询到的或新建的模型对象实例,还会返回这个对象实例是否是刚刚创建的
obj, create = models.UserInfo.objects.get_or_create(name='张三', password='123456', age=19)
或者以创建数据对象的方式添加数据
u = models.Department()
u.title = '公共关系部'
u.save()
当创建多个数据对象时可以批量保存
u1 = models.Department()
u1.title = '公共关系部'
u2 = models.Department()
u2.title = '研发部'
u3 = models.Department()
u3.title = '技术部'
models.Department.objects.bulk_create([u1, u2, u3])
可以使用 all 方法获取所有数据,得到的是可迭代的 QuarySet 类型的对象,可以当成数据列表。列表中的每个元素都是一个数据对象,可以使用 对象.字段 的方式获取数据。
data_list = models.UserInfo.objects.all()
for data in data_list:
print(data.id,data.name,data.password,data.age)
需要注意的是,查询的数据哪怕只有一条,获取的也是 QuarySet 对象。不过可以使用 first 方法从 QuarySet 中获取第一条数据,或使用 last 方法获取最后一条数据。此时获取的是数据模型对象。
可以使用 filter 方法进行条件筛选(过滤),参数即为过滤条件
data = models.UserInfo.objects.filter(id=1).first()
过滤条件的语法为 属性名__运算符=临界值
对于查询条件,除了等于外运算符还有
示例 | 说明 |
---|---|
id=12 | 数值字段id的值等于12 |
id__gt=12 | 数值字段id的值大于12 |
id__gte=12 | 数值字段id的值大于等于12 |
id__lt=12 | 数值字段id的值小于12 |
id__lte=12 | 数值字段id的值小于等于12 |
name__startswith=“张” | 字符串字段name以 “张” 开始 |
name__endswith=“三” | 字符串字段name以 “三” 结尾 |
name__contains=“老” | 字符串字段name包含了字符串 “老” |
istartswith/iendswith/icontains | 类似于 startswith 等,忽略大小写 |
name__isnull | 字段为空 |
name__isnotnull | 字段不为空 |
name__exact | 精确内容查找,区分大小写 |
name__iexact | 精确内容查找,忽略大小写(i 代表 ignore,即忽略) |
id__in=(3,4,5,6) | 字段值包含在元组中 |
notin | 不包含 |
除了这些过滤条件外,还支持时间过滤,使用 属性名__时间__ 操作符=值,时间包括 year、month、day、hour、minute、second
当使用多个参数作为过滤条件时,则认为取其交集,即类似逻辑运算“且”。
当使用过滤器 filter 时,可以将解包的字典作为查询条件
data = models.UserInfo.objects.filter(**query).first()
另外可以使用 exclude 方法来排除筛选条件(即对 filter 取反)
data = models.UserInfo.objects.filter(age=19).exclude(id=2).first()
过滤器 filter 和 exclude 可以链式调用,且得到的结果均是类似于 all 方法获取的 QuerySet 对象。
使用 filter
方法和 all
方法返回的是一个查询的结果对象,是无法被 json 序列化的。有时候我们需要返回 json 结果,则可以使用 values
方法,参数可以指定返回的列。如果 values
方法没有参数,则返回全部列。
row_dict = models.UserInfo.objects.filter(id=uid).values('id', 'name', 'password', 'age').first()
需注意的是,values 方法获取的是个 QuerySet 类型的字典列表,每个成员是字典类型,但是整体还是 QuerySet,是不可序列化的,当要序列化时,还需进一步处理。可以使用 first 方法获取第一个值,或使用 list 方法将全部数据转为列表。另外可以使用 values_list 方法获取元组列表,元组顺序为参数指定的列顺序。
row_dict = models.UserInfo.objects.filter(id=uid).values_list('id', 'name', 'password', 'age').first()
F对象其实就是一条数据对象,使用F对象可以将自己(数据)的属性作为查询条件。
from django.db.models import F
# 查询 Store 表中 id 的值和 years 字段的值相等的数据
Store.objects.filter(id=F('years'))
# F对象还支持运算
Store.objects.filter(id=1).update(years=F('years')+5)
Q 对象是将多个查询条件,以一定的逻辑进行封装。简单说就是多条件的逻辑组合查询(filter 并没有逻辑组合,尽管可以使用多参数达到且查询的目的)。
from django.db.models import Q
# & 为与,| 为或, ~ 为非
Store.objects.filter(Q(years=1)|Q(years=2))
Stroe.objects.filter(~Q(years=1))
Store.objects.filter(Q(years__gt=1)&Q(years__lt=3))
也可以动态构造Q对象
filterBy = [{'username': 'lilei'}, {'age': 19}, {'sex': 'male'}]
q = Q()
for item in filterBy:
q.add(Q(**item), Q.OR)
User.objects.filter(q)
如果动态构造的Q对象比较复杂,可以这样构造:
q1 = Q()
q1.connector = 'OR'
q1.children.append(('id', 1))
q1.children.append(('id', 2))
q2 = Q()
q2.connector = 'OR'
q2.children.append(('status', 1))
q2.children.append(('status', 2))
con = Q()
con.add(q1,'AND')
con.add(q2,'AND')
User.objects.filter(con)
针对一些复杂查询,可能通过数据模型 QuerySet 查询不方便,这时就需要使用原生 SQL 查询。
直接将原生 sql 语句字符串作为参数传给 raw 方法
Store.objects.raw('select * from t_store')
此方法返回的查询结果是一个 RawQuerySet 对象,此对象包含了一些属性,例如 query 属性是查询的原生 sql 字符串,columns 是查询结果列,model 是查询的数据模型对象,model_fields 是模型的字段对象。获取的结果可以进行遍历直接输出(输出的是数据模型对象,所以需要重写该对象的 str 方法)。需要注意的是,使用 raw 方法查询,查询的字段必须是数据模型类中声明的字段,且必须包含主键列,否则会报错。
extra 方法是扩展查询,针对 QuerySet 查询结果集额外增加查询条件或排序等相关操作。查询结果是 QuerySet 对象。
使用 extra 方法,则将 select 、 where 等 sql 原生语句的条件作为参数传入,需注意的是,对比方法、字段和对比值分开。
Store.objects.extra(where=['price<%s or name like %s'],params=['10', '果'])
可以使用 django.db.connection 数据库连接对象直接连接到数据库进行原生 SQL 查询。可以通过 connection 连接对象获取游标 cursor 对象,再通过对 cursor 的 execute()
、fetchall()
、 rowcount
等相关方法或函数来执行原生 SQL 和获取执行结果。需注意的是,返回的数据是一个元组,元组中每一个元素就是一条数据,每条数据也是个元组,每个元素是相应字段数据。
from django.db import connection
cursor = connection.cursor()
cursor.execute('select * from t_store')
for row in cursor.fetchall():
print(row)
# row = cursor.fetchone() # 读取一条数据
cursor.execute('update t_fruit set price=2 where id=1')
print(cursor.rowcount)
connection.commit() # 提交更新
找到了需要处理的数据后,可以使用 delete 方法进行删除
models.Department.objects.all().delete()
models.UserInfo.objects.filter(id=3).delete()
类似于删除数据,可以使用 update 方法对数据进行更新
models.Department.objects.all().update(password=999) # 将所有数据的 password 字段值改为 999
models.UserInfo.objests.filter(id=3).update(name='张三',password='5445',age=20)
或者更改查询结果数据对象的相应成员,然后使用 save 方法进行保存
data = models.Department.objects.get(pk=1)
data.password = 999
data.save()
django 可以控制返回查询记录的起始索引和结束索引,有点类似于切片。即,使用 [ 起始记录索引号 : 结束记录索引号 ] 的方式,并且也遵循左闭右开的原则。
models.UserInfo.objects.all()[0:10] # 返回查询结果索引为 0 - 9 的记录
models.UserInfo.objects.filter(age__gte=22)[10:20] # 返回查询结果索引为 10 - 19 的记录
这样就可以进行分页了。起始记录索引号的值为 (页码 - 1) * 每页大小 , 结束记录索引号的值为 页码 * 每页大小。
可以使用 order_by()
方法进行排序,需要注意的是,参数是字段名,而不是数据库中的列名。如果需要使用逆序,则可以在字段前添加 ‘-’ 或者 ‘?’ 符号。可以添加多个字段进行排序,作为多个参数添加即可。
num = models.UserInfo.objects.filter(age__gt=19).order_by('-id')
可以使用 distinct 方法来去重查询
ages = models.UserInfo.objects.values('age').distinct()
可以使用 count 方法来返回查询记录总数
num = models.UserInfo.objects.filter(age__gt=19).count()
可以使用 exists 方法来判断查询结果集中是否有数据
num = models.UserInfo.objects.filter(age__gt=19).exists()
可以使用 aggregate
方法来进行一些复杂的聚合运算,例如求和
# 使用 aggregate 可以进行 Avg、Max、Min、Sum、Count 等操作,注意需要先导入包
# 参数为需要进行的聚合运算,参数名称为返回数据的字典中的键,可以传入多个参数进行多次聚合运算
from django.db.models import Sum, Avg
num = models.UserInfo.objects.all().aggregate(nums=Sum('age'), avg_age=Avg('age'))
aggregate
方法返回的是一个字典,字典里包含了聚合运算的数据。
模型的关系分为一对一、一对多和多对多的关系,django 提供了各种关系下使用的字段。使用关系字段关联时,字段参数第一个值为需要关联的模型类,需注意的是如果定义关系时该类并未创建,或未完成创建(例如需要自关联),则可以模型的名称(字符串),自关联则使用 self 。如果使用的是另一个应用中的模型,可以明确的使用完整的应用标签指定一个模型,格式为:应用名称.模型名称。
建立第二个数据模型(从表),并创建关联字段,类型为 OneToOneField
绑定到关联表(主表)中,则两个表建立了一对一的关联关系。
class UserEntity(models.Model):
name = models.CharField(max_length=20)
pass
class RealProfile(models.Model):
# 声明一对一的关系,默认绑定到目标模型的主键
# 也可以使用 to_field 参数绑定到目标字段
# 注意必须有 on_delete 级联操作,或者使用 models.SET_NULL 将级联设置为null
user = models.OneToOneField(UserEntity, on_delete=models.CASCADE)
pass
使用一对一关系,需要从表主动声明关系。从表获取主表使用的是一个字段(使用 数据对象.关联字段.字段名),主表获取从表是隐性的(使用 数据对象.模型名.字段名注意小写,或定义 related_name
使用 数据对象.反向引用名 访问)。
关联关系必须指定 on_delete
参数,删除的约束行为,值在 django.db.models
中引用。含义为:
ProtectedError
防止删除被关联引用的对象default
参数可选参数 related_name
是反查名称,用于从相关对象到此对象的关系名称,也是 related_query_name
的默认值(用于从目标模型反向过滤名称的名称)。如果不想创建反向关系,即不进行反查,可以将此参数设置为 +
或以 +
结束。
可选参数 related_query_name
反向过滤器的名称,一般不设置,使用默认值。
可选参数 to_field
关联对象的字段。默认情况下,django 会关联对象的主键,如果引用了一个不同的字段,则这个字段必须唯一(即 unique=True
)
一对多的关系通常在从表中使用外键来关联主表
class User(models.Model):
pass
class Order(models.Model):
# 声明外键,即一对多的关系,默认为目标模型的主键,也可以使用 to_field 绑定到目标字段
# 注意必须有 on_delete 级联操作,或者使用 models.SET_NULL 将级联设置为null
# 可以使用 related_name 来定义反向引用的名称(否则默认为 模型名称_set )
order_from = models.ForeignKey(User, on_delete=models.CASCADE)
使用一对多关系,从表声明外键关联主表。从表获取主表是通过字段属性(即 数据对象.外键字段.字段名),主表获取从表使用 数据对象.模型名_set (或 数据对象.反向引用名),获取到的是数据集合,不是具体的数据或表。是 QuerySet 的子类,可以进行筛选排序等操作。
一对多的关系参数和用法同一对一关系
多对多关系也是由外键实现的,对于原表没有任何变化。而是多创建一个表,分别对原两张表产生多对一的关系,来维护两个原表的多对多的关系。即关系表中至少需要2个外键,分别关联至原表中。
或者创建关系字段,使用 ManyToManyField
类型来绑定数据模型表,此时 django 会创建关系维护表。
class User(models.Model):
pass
class Goods(models.Model):
# 声明多对多关系
from_user = models.ManyToManyField(User, db_table='t_collect', related_name='goods', verbose_name='收藏的用户列表')
使用 ManyToManyField
来创建多对多关系,会自动创建关系表,并自动进行维护。而使用外键则需要手动维护关系表。使用时需在从表定义多对多关系。
主表获取从表的数据,可以使用 数据对象.模型名_set 或 数据对象.反向引用名 来获取;从表获取主表的数据,可以使用 数据对象.关系字段 来获取。获取到的数据均是一个数据集合。
和一对一、一对多关系关联数据的不同是,关联数据其实是向多端数据集合添加数据实体:
u1 = User.objects.get(pk=1)
# 因为 Goods 模型中进行了多对多关联,且声明了反向引用名称,所以 User 模型中多了一个 goods 字段
# 向 u1 关联的集合中添加数据实体,则实现了2个数据实体的关联
u1.goods.add(Goods.objects.get(pk=2))
相应的,取消多对多关系,则是将数据实体从数据集合中移除
u1 = User.objects.get(pk=1)
u1.goods.remove(Goods.objects.get(pk=2))
多段多的关系参数中,部分与一对一相同,包括 : related_name
related_query_name
,另外还有一些独有的参数:
django 在使用 ManyToManyField
定义了多对多字段时,会自动创建并维护一个关系表,名称可以由字段参数 db_table
指定,默认使用定义关系的模型表和字段本身名称。
如果在自身定义多对多关系时,例如
class Person(models.Model):
friends = models.ManyToManyField('self')
当 django 处理这个模型时,会识别出它本身有一个 ManyToManyField,因此,它没有给 Person 类添加 person_set 属性。相反, ManyToManyField 被认为是对称的,也就是说,如果我是你的朋友,那么你就是我的朋友。如果你不想让 self 的多对多关系对称,可以将 symmetrical 设置为 False。这样会强制 Django 添加反向关系的描述符,允许 ManyToManyField 关系是非对称的。
定义多对多关系时,如果想手动指定中间模型,而不使用默认生成的关系表,可以使用 through
参数来指定。through_fields
用来指定自定义中间模型的关系字段,具体的操作后面详细说明。
使用自定义关系模型,可以在 ManyToManyField
字段中使用 through
参数来指定中间表的模型。最常用的地方是想把额外的数据与多对多关系起来。例如给多对多关系加上一个时间、标识、说明等等。
如果没有显式的指定 through
模型,会隐式的创建 through
模型,此模型有三个字段:
如果需要查询关联记录,可以这样使用:
# Model 即定义的数据模型
# m2mfield 是多对多字段
Model.m2mfield.through.objects.all()
需要注意的是,through
获取的是关联表,和 Model 的模型实例和 m2mfield 关联实例并没有关系。需要查询指定的数据还要进行筛选。
显式的指定 though
模型,则可以这样使用:
class ModelA(models.Model):
name = models.CharField(max_length=100)
class ModelB(models.Model):
name = models.CharField(max_length=100)
models_a = models.ManyToManyField(ModelA, through='CustomModel')
class CustomModel(models.Model):
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
model_b = models.ForeignKey(ModelB, on_delete=models.CASCADE)
custom_field = models.CharField(max_length=100)
可以像操作其他模型一样,通过中间模型来访问和操作关联关系表:
model_a = ModelA.objects.create(name='Model A')
model_b = ModelB.objects.create(name='Model B')
custom_model = CustomModel.objects.create(model_a=model_a, model_b=model_b, custom_field='Custom Field')
custom_models = CustomModel.objects.filter(model_a=model_a)
# 通过关联模型访问中间模型的数据
custom_data = model_b.models_a.through.objects.filter(modela=model_a, modelb=model_b).first().custom_field
当自定义的关系模型中,有两个外键字段同时指向一个模型,例如:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through="Membership", through_fields=("group", "person"))
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(Person, on_delete=models.CASCADE, related_name="membership_invites")
invite_reason = models.CharField(max_length=64)
Membership 对 Person 有 两个 外键( person 和 inviter ),这就使得两者的关系变得模糊不清,Django 无法知道应该使用哪个外键。在这种情况下,必须使用 through_fields
明确指定 Django 应该使用哪个外键。through_fields
接收一个二元元组 ('field1', 'field2')
其中 field1 是定义在多对多字段上的模型的外键名称,field2 是目标模型的外键名称。
自关联根据需要可以使用一对一、一对多、多对多的类型,用法是一样的。只是在声明关系时,参数无法使用实体数据类(因为实体数据类是自己本身,还没有完成定义),可以使用 to='self'
参数传入。
在查询条件中,多端外键字段的值可以是一端相应字段的值,也可以是一端的数据对象。
book = BookInfo.objects.get(id=1)
person = PeopleInfo.objects.get(id=1)
book = BookInfo.objects.filter(peopleinfo__name='郭靖')
book = BookInfo.objects.filter(peopleinfo__description__contains='八')
获取数据方面,从多端可以使用外键字段获取一端数据结果对象,从一端可以使用反向引用字段或多端数据模型(小写)_set来获取多端的数据结果集。
queryset 结果集可以使用 values()
方法获取所有列的数据,但是数据对象不行,这时可以使用 model._meta.fields
来获取全部的列对象,列对象的属性 name
就是列名了。使用遍历的方式可以获取全部列名,根据列名可以使用 getattr()
将数据对象的相应属性得到,也就变相遍历了数据对象。
django 使用了 migrations 来管理和处理相应的数据模型的迁移,每次迁移会产生记录(在 django_migrations 表中),下次迁移则会根据历史记录和变化产生迁移。如果没有使用 migrations 来进行迁移,而是直接对数据表或迁移记录等进行操作,则会影响到迁移的历史检索个比对。所以复制数据库时,也需要注意迁移记录。
获取模型数据后,可以直接传递到模板使用。如果是多个数据,则可以作为数据列表使用。单个数据可以当成字典使用。
原子性操作即不可分割的操作,使得若干操作要么全部成功,要么全部失败。django orm 中使用原子性操作需要使用到事务
from django.db import transaction
try:
with transaction.atomic():
orm 操作
orm 操作
except:
pass
django orm 在每次获取结果集会进行一次查询,如果有类似于遍历比对的操作,会对数据库产生大量的查询操作。
例如通过结果集1的某个字段值获取结果集2的数据,一般会使用查询结果集2筛选条件为结果集2某字段等于结果集1(外键)的方式。如果结果集1数量很多,则遍历结果集1会产生大量的数据库查询访问。
django orm 会在获取结果集时,会进行查询,并缓存至本地。如果不使用筛选的方式,则可以减少数据库查询访问。例如结果集1和结果集2是一对一关系,遍历结果集1产生条件字段列表(此操作只产生一条查询,即获取结果集1),遍历结果集2产生筛选数据字段列表(此操作只产生一条查询,即获取结果集2)。假设两个结果集顺序也是对应的(可以提前进行排序),则使用查询条件在结果集1中使用 index()
方法获取索引,使用索引可以在结果集2数据字段列表中获取需要的数据(也可以使用索引对结果集2进行切片,不会产生查询)。
简而言之,就是尽量少用筛选等结果集操作,转而使用列表相关操作,可以操作本地缓存的数据结果,减少对数据库服务器的访问。虽然在客户端进行数据处理会造成性能下降,但是对于整体性能来说应该还是提升了的(大量的耗时应该会产生在对数据库访问的连接和查询上)。