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 创建项目

在 pycharm 中新建项目时,选择 Django 项目,即可创建 Django 项目了。需要注意的是,需使用企业版的 pycharm。

默认项目文件

创建完成的项目目录结构为(learnDjango是项目名称,也是主工程目录):

learnDjango
│  manage.py
│
└─ learnDjango
        asgi.py
        settings.py
        urls.py
        wsgi.py
        __init__.py
  • manage.py 负责项目的管理,包括启动项目、创建app、数据管理等
  • asgi.py 异步接收网络请求
  • wsgi.py 同步接收网络请求
  • urls.py URL和处理函数的对应关系(总路由)
  • settings.py 项目的配置文件

配置项目

可以在主工程目录下的 settings.py 中进行项目配置

  • BASE_DIR : 基础目录,除非特殊情况不推荐更改
  • SECRET_KEY : 安全密钥,默认不用更改
  • DEBUG : 是否启动 debug 模式,开发环境可以选是,生产环境选否
  • ALLOWED_HOSTS : 允许访问的主机地址,默认为空,即全部
  • INSTALLED_APPS : 安装(注册)的应用(app)
  • MIDDLEWARE : 使用的中间件
  • ROOT_URLCOMF : 根路由配置文件,默认工程目录下的 urls.py
  • TEMPLATES : 模板配置信息,使用默认不用更改
  • WSGI_APPLICATION : WSGI 通信应用,除非特殊情况不推荐更改
  • DATABASES : 数据库信息,可以根据需要使用的数据库进行更改
  • AUTH_PASSWORD_VALIDATORS : 密码校验控件,不用更改
  • LANGUAGE_CODE : 语言,英文使用 en-us ,中文使用 zh-hans
  • TIME_ZONE : 当前时区,不用更改(也可以改为当地时区,例如 Asia/Shanghai)
  • USE_I18N : 是否支持国际化,不用更改
  • USE_TZ : 是否使用 UTC 时区,不用更改
  • STATIC_URL : 静态资源URL路径,不用更改
  • STATICFILES_DIRS : 这是一个列表,当静态资源目录比较多时,可以在这里进行添加
  • MEDIA_URL : 媒体文件URL路径(自定义的配置项)
  • MEDIA_ROOT : 媒体文件保存的物理路径目录(自定义的配置项),例如 os.path.join(BASE_DIR, 'media')
  • DEFAULT_AUTO_FIELD : 默认主键字段类型,不用更改

APP

一个项目下可以有 N 个独立的 app 来处理不同的功能,每个 app 可以拥有独立的、不同的表结构、函数、HTML模板、CSS等。

创建 app

终端里,创建好的项目文件夹下,带参数运行 manage.py 即可创建一个新的 app:

python manage.py startapp app名称

app 的文件结构

app 的目录结构为(app01是app的名称)

app01
│  admin.py
│  apps.py
│  models.py
│  tests.py
│  views.py
│  __init__.py
│
└─migrations
        __init__.py
  • migrations 记录 Django 的 model 对数据库进行的变更
  • tests.py 进行单元测试
  • admin.py django 默认提供的 admin 后台管理
  • app.py app的启动设置
  • views.py URL执行的函数(视图函数)
  • models.py 操作数据库

app 的配置

当创建好 app 后,在 app 的目录下有 apps.py ,这里注册了 app 的一些配置信息。例如

  • default_auto_field : 数据模型的自动字段类
  • name : app 的名称

快速上手

当创建了项目和app之后,到应用启动还需要注意以下几点:

  • 确保 app 类已经注册

在 app 目录下的 apps.py 中能看到类名称,通常是 app项目名+Config。将此类名称添加到 settings.py 里的 INSTALLED_APPS 列表中即可。需注意的是添加目录名、文件名和启动类名,例如 ‘app01.apps.App01Config’

  • 编写 URL 和视图函数的对应关系(路由及处理函数)

在 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")

启动 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 启动

使用 pycharm 建立的 django 项目可以通过默认启动项直接启动,默认快捷键是 shift + F10

通过 python 脚本启动

如果需要由其他的 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
from django.conf.urls import url

urlpatterns = [
	url(r'^hi/(?P<name>\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

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:nameapp_name:name

在模板中使用反向解析路由

在模板中,可以使用 {% url %} 结构来获取反向解析路由,语法为 {% url 反向解析路由字符串 参数1 参数2 %},参数可以是多个,使用空格分隔,默认位置传参,也可以使用关键参数传参。例如

<a href="{% url 'user:list' id=1 5 %}">链接</a>

需注意的是,反向解析路由字符串是由 urls.py 中的 app_name、namespace、和 name 定义的字符串组成的。传递的参数是 url 中传参的形式,不是 GET 参数形式。

在视图函数中使用反向解析路由

在视图函数中,使用 reverse() 函数来反向解析路由路径。然后可以使用 redirect 或 RedirectHttpResponse 来重定向。

from django.urls import reverse

url = reverse('user:list',args=(1,5))

也可以使用字典传递关键字参数

from django.urls import reverse

url = reverse('user:list', kwargs=dict(id=1, page=5))

templates 模板

在视图函数中使用 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 和 STATICFILES_DIRS 里更改。

如果使用了自定义的静态资源,例如 MEDIA ,则除了需要在 settings.py 中进行配置(例如 MEDIA_URL 和 MEDIA_ROOT)外,还需要在 urls.py 中进行注册。

# urls.py
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
	...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

在生产环境部署 django 时,执行命令

python manage.py collectstatic

会将 STATICFILES_DIRS 里定义的静态文件统一复制到 STATIC_ROOT定义的文件夹中,使用的也是 STATIC_ROOT 作为静态文件目录。

视图(view)处理

在 settings.py 中注册路由时,会绑定相应的视图(view)处理函数,即使用指定的函数处理路由请求。视图处理函数默认有一个参数 request 即请求对象,返回 HttpResponse 对象即响应对象。

在面向对象的设计思想中,能够通过继承、重写等相关特性实现业务功能。所以除了使用函数处理视图(FBV Function-based View)外,还能够使用类处理视图(Class-based View)。

Django 请求和响应

Django 接收到一个请求时,一般会经过以下基本流程

  1. 获取请求 url 到 urls 分发器
  2. urls 分发器根据路由规则(或正则规则)分发到视图处理函数(Views)
  3. 视图处理函数调用数据模型(Models),交互数据,并进行必要的处理
  4. 视图处理函数将处理好的数据渲染到模板(Templates)中
  5. 将模板通过响应呈现给用户

需注意的是,这个是基本流程,在实际使用中可能会不太一样。

请求

视图函数中有一个默认形参 request,它是一个对象,封装了用户发送过来的所有请求数据。

获取请求信息

request 对象的常用属性和方法有:

  • request.method : 请求类型
def index(request):
	if request.method == "GET":
		return render(request, "login.html")
  • request.content_type : 获取请求正文类型
  • request.encoding : 请求编码
  • request.is_ajax : 请求是否是 ajax 请求
  • request.GET : 获取GET请求信息
  • request.POST : 获取POST请求信息
  • request.FILES : 获取文件信息
  • request.COOKIES : 获取COOKIE信息
  • request.path : 获取请求路径信息
  • request.body : 获取请求体(字节类型)
  • request.META : 客户端的元信息(环境信息),经常使用的是 REMOTE_ADDR 客户端地址

get 请求的参数(查询参数)

可以使用 request.GET.get() 方法获取查询参数

username = request.GET.get("user")
password = request.GET.get("pwd")

post 请求的参数(正文参数)

可以使用 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/<int:nid>/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() 等函数快速创建。

返回响应包或HTML内容

使用 HttpResponse 对象可以直接返回响应包,例如 HttpResponse(‘String’) 的响应包返回内容就是字符串。

def index(request):
	return HttpResponse('<h1 style="color:green;">hi, Django</h1>')

返回页面(渲染模板)

使用 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")

返回 json 数据

可以直接返回一个 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 的模板语法都是包含在 {} 中的。

“.” 的作用

在模板语言中,不支持方括号式获取索引下标、元素等,而是使用 “.” 。这个点使用在很多地方获取数据

  • 对象.属性
  • 对象.方法
  • 列表.索引下标
  • 字典名.key

注释

注释内容不会被渲染。使用{# 注释内容 #} 进行单行注释,使用 {% comment %}{% endcomment %} 进行多行注释。

{{ var }} 变量模板标签

使用双花括号可以接收数据或使用过滤器

接收从视图函数传递的数据

传递单个数据给页面

使用 render() 方法,将数据发送给页面相应的变量:

def index(request):
	text = '初学 Django'
	return render(request, 'index.html', n1=text)

render() 方法的第三个参数就是传递数据,它是个上下文参数,也可以使用字典对象来传递多个数据。

接收传递的数据

在页面中使用 {{ 变量名 }} 来接送 render_template() 方法传递的数据。

<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 : 渲染之前将值中的标签去掉(如果不去掉则直接以文本显示)
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>

{% tag %} 布局模板标签

{% %} 这个方法主要用在结构标签宏定义循环及流程控制中。

结构标签

结构标签主要包含 blockextendsinclude

block 和 extends

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 包含、引入

在当前页面中,导入目标页面内容,则使用 {% include '页面文件’ %}。这种方法将导入页面的 head 标签和 body 标签去掉(但是保留其内容),然后再进行整体导入。此方法可能会因 id 冲突、脚本冲突等原因引起页面混乱,所以不推荐使用。一般使用在引入 css 文件等地方。

宏定义

宏定义使用 macro,可以在模板中定义、调用函数,来生成需要的 HTML 内容。

定义宏

定义宏时需添加名称,然后在内部添加内容。

{% macro macro_name() %}
	<ul>
		<li>列表1</li>
		<li>列表2</li>
		<li>列表3</li>
	</ul>
{% endmacro %}

调用宏

调用宏时,直接在需要添加宏内容的地方使用 {{ macro_name() }} 即可。调用外部文件中定义的宏需要导入,使用 {% from 宏定义文件 import 宏名称 %},注意宏名称不带括号。

循环及流程控制

使用循环语句进行循环控制,使用判断语句进行流程控制

循环语句

循环的语法如下:

<!-- 列表循环 -->
{% 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

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 %}
<!-- 结果为
 我是定义1: row1
 我是定义2: row3
    变量1:	row1
    变量2: row3
-->

如果不想在定义时输出,可以添加 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 %}
resetcycle

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 %}
firstof 标签

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 %}

csrf_token

django 为了防止 XSS 攻击,每当收到 post 请求时会验证其 csrf_token 是否和 cookie 中的数据一致,使用django.middleware.csrf.CsrfViewMiddleware 中间件来验证 csrf_token ,如果没有或不一致就会报错。这就需要模板在所有 form 表单中加上 {% csrf_token %} 标签,会在渲染时生成一个 input 标签<input type='hidden' name='csrfmiddlewaretoken' value=服务器随机生成的token>。这样在每次客户在请求 csrf_token 时,会在服务器端生成一个 token,渲染至模板并存放在 cookie 里面,提交时也会将此 token 随表单一起提交以便验证。

在 ajax 等不方便加 {% csrf_token %} 标签的地方,由2种处理方式:关闭 csrf_token 验证和手动获取验证 token。

虽然可以通过在 settings.py 中取消使用中间件的方式关闭 csrf_token 验证,但是不建议这么做。通常会使用在视图处理函数中使用装饰器 @csrf_exempt 声明此函数不进行 csrf_token 校验。

如果需要手动使用 csrf_token,则有三种方法:

  • 获取渲染模板时设置的 csrf_token。因为使用 django 渲染模板时,如果需要会将 csrf_token 渲染至模板,所以可以在需要的位置接收其值,并在提交的表单或ajax数据体中添加 csrfmiddlewaretoken 键。需要获取 csrf_token 值,可以使用 {{ csrf_token }} 直接渲染数据,也可以使用 {% csrf_token %} 标签,并获取 name=csrfmiddlewaretoken 的 input 标签元素的 value 值即可。
  • 在请求头中添加之前获取的 csrf_token 数据。因为获取的 csrf_token 比对数据会放在 cookie 中,所以可以将 cookie 中的 csrftoken 的值,放在请求头的 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) => {}
})
  • 因为上一种方法是使用的 cookie 中已经存在的 csrf_token,如果 cookie 中没有(没有访问过需要 csrf_token 的页面,就不会生成 csrf_token),则需要专门请求 django 发送 csrf_token 数据。例如在 django 中创建视图处理函数:
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 值,无论请求体中验证还是请求头中验证都无法通过。

数据库操作

Django 开发操作数据库更简单,其内部提供了 ORM 框架,可以轻松的访问数据库。Django 官方支持 PostgreSQL、MariaDB、MySQL、Oracle、SQLite,还支持 MS SQL Server 等提供的后端。

安装数据库的第三方库

django 数据库是基于其他的第三方库,所以需要先安装。 这里使用 MySQL 数据库。因为 django 对 pymysql 的支持不是很好,据说有错误,所以使用 mysqlclient 库。pip install mysqlclient

使用 pymysql

如果需要使用 pymysql 的话,可以执行 pymysql.install_as_MySQLdb(),然后就可以使用 pymysql 了。这种默认执行一般可以放在 init.py 文件中。

ORM 及其设计思想

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 型的自增字段作为主键。

常用的字段类型

常用的类型有:

  • CharField : varchar 类型,必须设置最大长度
  • TextField : text类型
  • UUIDField : 字符串类型,Django Admin 以及 ModelForm 中提供对 UUID 格式的支持
# 使用 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)
  • IntegerField : int 类型
  • FloatField : float 类型
  • DecimalField : decimal 类型(精准小数)
  • BooleanField : 数据库中没有布尔类型,所以存放的是0或1
  • AutoField : int 自增列,必须设置主键,且自动创建
  • FileField : 文件字段,字符串,将文件主体上传到指定目录,路径保存进数据库
  • ImageField : 图片,类似于 FileField,多了 width_field 和 height_field 两个辅助字段
# 使用 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 : 日期类型
# 使用 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)
  • DateTimeField :日期时间类型
  • TimeField : 时间类型
  • ForeignKey :外键类型

常用的约束参数

常用的参数(约束)有:

  • max_length :最大长度(字符类型使用)
  • primary_key : 是否主键
  • auto_created : 是否自动创建
  • verbose_name : 数据模型的备注名称
  • default : 是否默认值
  • unique : 是否唯一
  • null :数据库是否可以为空
  • blank :数据模型中是否可以为空,一般同 null 一起使用。
  • db_index : 是否设置索引
  • db_column : 指定数据库字段的名称
  • max_digits :数值最大总位数(用于 decimal)
  • decimal_places :小数部分位数(用于 decimal)
  • to :关联表(外键)
  • to_field :关联列(外键)
  • on_delete :主键删除时处理方式(外键),可以使用值有 models.CASCADE (级联删除) 和 models.SET_NULL (置空)
  • choices :django 的选择值约束
  • editable : 是否可以被编辑
  • unique_for_date : 日期必须唯一
  • unique_for_month : 月份必须唯一
  • auto_now_add : 自动生成新插入的日期时间
  • auto_now : 添加字段信息更新的时间
	# 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。

验证器 validators

使用验证器,可以对数据模型字段的内容进行验证,如果验证不通过可以抛出 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(字段=值,字段=值...)
models.Department.objects.create(title='销售部')
models.UserInfo.objects.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

可以使用 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

当使用多个参数作为过滤条件时,则认为取其交集,即类似逻辑运算“且”。

排除过滤器 exclude

另外可以使用 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 和 Q 对象的使用

F 对象

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 对象

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)

原生查询

针对一些复杂查询,可能通过数据模型 QuerySet 查询不方便,这时就需要使用原生 SQL 查询。

QuerySet.raw()

直接将原生 sql 语句字符串作为参数传给 raw 方法

Store.objects.raw('select * from t_store')

此方法返回的查询结果是一个 RawQuerySet 对象,此对象包含了一些属性,例如 query 属性是查询的原生 sql 字符串,columns 是查询结果列,model 是查询的数据模型对象,model_fields 是模型的字段对象。获取的结果可以进行遍历直接输出(输出的是数据模型对象,所以需要重写该对象的 str 方法)。需要注意的是,使用 raw 方法查询,查询的字段必须是数据模型类中声明的字段,且必须包含主键列,否则会报错。

QuerySet.extra()

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 提供了各种关系下使用的字段

一对一关系

建立第二个数据模型(从表),并创建关联字段,类型为 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

使用一对一关系,需要从表主动声明关系。从表获取主表使用的是一个字段(使用 数据对象.关联字段.字段名),主表获取从表是隐性的(使用 数据对象.模型名.字段名)。

一对多关系

一对多的关系通常在从表中使用外键来关联主表

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))

自关联的关系

自关联根据需要可以使用一对一、一对多、多对多的类型,用法是一样的。只是在声明关系时,参数无法使用实体数据类(因为实体数据类是自己本身,还没有完成定义),可以使用实体数据类的名称字符串作为参数传入。

关系查询

在查询条件中,多端外键字段的值可以是一端相应字段的值,也可以是一端的数据对象。

获取数据方面,从多端可以使用外键字段获取一端数据结果集,从一端可以使用反向引用字段或多端数据模型(小写)_set来获取多端的数据结果集。

关于 ORM 的迁移

django 使用了 migrations 来管理和处理相应的数据模型的迁移,每次迁移会产生记录(在 django_migrations 表中),下次迁移则会根据历史记录和变化产生迁移。如果没有使用 migrations 来进行迁移,而是直接对数据表或迁移记录等进行操作,则会影响到迁移的历史检索个比对。所以复制数据库时,也需要注意迁移记录。

结合模板

获取模型数据后,可以直接传递到模板使用。如果是多个数据,则可以作为数据列表使用。单个数据可以当成字典使用。

ORM 的原子性操作

原子性操作即不可分割的操作,使得若干操作要么全部成功,要么全部失败。django orm 中使用原子性操作需要使用到事务

from django.db import transaction

try:
	with transaction.atomic():
		orm 操作
		orm 操作
except:
	pass

ORM 的缓存机制

django orm 在每次获取结果集会进行一次查询,如果有类似于遍历比对的操作,会对数据库产生大量的查询操作。

例如通过结果集1的某个字段值获取结果集2的数据,一般会使用查询结果集2筛选条件为结果集2某字段等于结果集1(外键)的方式。如果结果集1数量很多,则遍历结果集1会产生大量的数据库查询访问。

django orm 会在获取结果集时,会进行查询,并缓存至本地。如果不使用筛选的方式,则可以减少数据库查询访问。例如结果集1和结果集2是一对一关系,遍历结果集1产生条件字段列表(此操作只产生一条查询,即获取结果集1),遍历结果集2产生筛选数据字段列表(此操作只产生一条查询,即获取结果集2)。假设两个结果集顺序也是对应的(可以提前进行排序),则使用查询条件在结果集1中使用 index() 方法获取索引,使用索引可以在结果集2数据字段列表中获取需要的数据(也可以使用索引对结果集2进行切片,不会产生查询)。

简而言之,就是尽量少用筛选等结果集操作,转而使用列表相关操作,可以操作本地缓存的数据结果,减少对数据库服务器的访问。虽然在客户端进行数据处理会造成性能下降,但是对于整体性能来说应该还是提升了的(大量的耗时应该会产生在对数据库访问的连接和查询上)。

更多推荐

自学Python第二十二天- Django框架(一)创建项目、APP、快速上手、请求和响应流程、模板、数据库操作