当前位置:   article > 正文

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

python框架django项目

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 项目名称
  • 1

执行完成后,会在当前目录下创建一个项目名称命名的文件夹。这样项目就创建成功了。

pycharm 创建项目

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

默认项目文件

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

learnDjango
│  manage.py
│
└─ learnDjango
        asgi.py
        settings.py
        urls.py
        wsgi.py
        __init__.py
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 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名称
  • 1

app 的文件结构

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

app01
│  admin.py
│  apps.py
│  models.py
│  tests.py
│  views.py
│  __init__.py
│
└─migrations
        __init__.py
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 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 类的 ready() 方法

from django.apps import AppConfig

class TestappConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'testapp'

	def ready(self):
		super().ready()		# 运行基类的 ready() 方法
		# 这里可以添加一些需要在 app 加载完成后执行的代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

快速上手

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

  • 确保 app 类已经注册

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

  • 编写 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")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 有登录需要的创建超级管理员,可以查看之后的权限与后台管理

启动 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'])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注册路由和路径传参

路由及相应的视图处理函数在工程目录(与项目同名的文件夹)下的 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)
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

也可以使用正则表达式

# 此种方法为老版本的 django,4.0以后已经废除!!!!!
from django.conf.urls import url

urlpatterns = [
	url(r'^hi/(?P<name>\w+)$', views.hello),
]
# 此种方式会使用路径传参,将匹配正则的参数以关键参数 name 传入视图处理函数
# 如果不指定关键参数传值,则会按照括号的顺序传值
# 需注意的是传参必须使用括号,视图处理函数必须接收参数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

或者

# 新版本 django 支持的方式,使用 re_path 方法
from django.urls import path, re_path

urlpatterns = [
	path('', views.login),
	re_path(r'hi/(\w+)/(\d{4})', views.hello),
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

反向解析路由

注册路由时,使用 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')
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以通过定义的名称或命名空间来反向解析路由路径。需要注意的是,使用子路由时,需要定义 app_name (一般在app 的 urls.py 中),否则会报错。反向解析路由字符串为 namespace:nameapp_name:name

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

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

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

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

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

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

from django.urls import reverse

url = reverse('user:list',args=('id',5))
  • 1
  • 2
  • 3

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

from django.urls import reverse

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

templates 模板

在视图函数中使用 render() 方法可以返回 html 页面

def index(request):
	return render(request, 'index.html')
  • 1
  • 2

默认情况下 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' %}">
  • 1
  • 2
  • 3

如果需要更改 static 目录,则在 settings.py 文件里的 STATIC_URL(静态文件的 url 地址) 和 STATICFILES_DIRS(静态文件的物理地址) 里更改。

# setting.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, app_name, 'static')
    for app_name in INSTALLED_APPS if os.path.isdir(os.path.join(BASE_DIR, app_name, 'static'))
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

静态文件 favicon.ico 的调用

浏览器会经常访问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
			  ]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这种在开发模式中可以使用,生产模式中因为通常静态资源是由 nginx 之类的负责,所以 django 没有相应配置。但是可以通过修改页面模板的方式指定所要访问的静态资源。

 {% load static %}
 <head>
	<link rel="shortcut icon" href="{% static 'img/favicon.ico' %}" type="image/x-icon">
</head>
  • 1
  • 2
  • 3
  • 4

生产环境下使用静态文件

在开发环境中(DEBUG=True),django.contrib.staticfiles 会自动帮助寻找静态路径。在生产环境中(DEBUG=False),django.contrib.staticfiles就失效了,需要我们自己设置静态路径。

  1. 打开settings.py,把STATIC_URL 和 STATICFILES_DIRS 注释掉,新增STATIC_ROOT
# 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')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 在 url.py 中新增静态文件路由
    开发环境使用:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns

if settings.DEBUG:
    urlpatterns += staticfiles_urlpatterns()
	# 也有使用下面这种方式
	# urlpatterns += static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

生产环境使用:

from django.views.static import serve

urlpatterns.append(re_path(r"^static/(?P<path>.*)$", serve, {'document_root': settings.STATIC_ROOT}))
  • 1
  • 2
  • 3
  1. 因为生产环境中不会轮流从app中查找静态文件,所以要进行静态文件迁移,将所有注册APP绑定的静态文件迁移到 STATIC_ROOT 的目录中
python manage.py collectstatic
  • 1

自定义静态资源 MEDIA

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

# setting.py
MEDIA_URL = '/upload/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'upload'),
  • 1
  • 2
  • 3

开发环境下的设置

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

urlpatterns = [
	...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

生产环境下的路径配置

urlpatterns.append(re_path(r"^media/(?P<path>.*)$", serve, {'document_root': settings.MEDIA_ROOT}))
  • 1

在前端中,可以使用 {{ MEDIA_URL }} 来获取 MEDIA 的url路径。例如

<img id="Img" class="Img" src="{{ MEDIA_URL }}img/img.jpg"/>
  • 1

但是需要在 settings.py 中的模板项里添加定义:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                .
                .
                .
                'django.template.context_processors.media',		# 添加这一行
            ],
        },
    },
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在视图函数中,可以使用 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

视图(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")
  • 1
  • 2
  • 3
  • request.content_type : 获取请求正文类型
  • request.encoding : 请求编码
  • request.is_ajax : 请求是否是 ajax 请求
  • request.GET : 获取GET请求信息
  • request.POST : 获取POST请求信息
  • request.FILES : 获取文件信息
  • request.COOKIES : 获取COOKIE信息
  • request.get_host() : 获取主机和端口信息
  • request.path : 获取请求路径信息(没有主机和端口号的路径)
  • request.body : 获取请求体(字节类型)
  • request.META : 客户端的元信息(环境信息),经常使用的是 REMOTE_ADDR 客户端地址

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

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

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

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

可以使用 request.POST.get() 方法获取正文参数

username = request.POST.get("user")
password = request.POST.get("pwd")
  • 1
  • 2

获取名称相同的参数

get 请求和 post 请求可以附带很多参数,且参数名可以重复。对于重复的参数名,可以使用 request.GET.getlist()request.POST.getlist() 方法获取所有的参数值列表。

userlist = request.GET.getlist('user')
  • 1

获取路由路径中的参数

在路由路径中,除了使用正则可以获取路径中的参数外,可以使用 <> 直接指定参数类型和名称,来传递参数到视图处理函数中。注意视图处理函数中需使用形参获取参数。

# urls.py
urlpatterns = [
	path('depart/<int:nid>/edit/', views.depart_edit)
]
  • 1
  • 2
  • 3
  • 4
# views.py
def depart_edit(request, nid):
	pass
  • 1
  • 2
  • 3

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>')
  • 1
  • 2

返回页面(渲染模板)

使用 render() 方法通常返回一个 html 页面,也可以使用此方法向页面中传递数据(渲染模板)。

def index(request: HttpRequest):
	user={'id': 1, 'name': '张三'}
	return render(request, 'index.html', user=user)
  • 1
  • 2
  • 3

需注意 render() 方法必须返回 request 对象。

重定向

redirect() 方法会重定向到另一个 url ,使用方法为:return redirect("https://www.baidu.com")

返回 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

自定义响应头

在 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'})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

为了方便使用,可以将加载和渲染进行结合,同时使用

result = loader.render_to_string('a.html', context={})
  • 1

此时,可以将渲染好的页面进行缓存,在需要时添加到响应中返回(使用 HttpResponse(result))。

在使用中,可以进一步简化,使用 render() 方法,直接渲染模板并返回响应。

def index(request):
	text = '初学 Django'
	return render(request, 'index.html', {n1: text})
  • 1
  • 2
  • 3

语法

flask 的模板语法都是包含在 {} 中的。

“.” 的作用

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

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

注释

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

{{ var }} 变量模板标签

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

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

传递单个数据给页面

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

def index(request):
	text = '初学 Django'
	return render(request, 'index.html', {'n1': text})
  • 1
  • 2
  • 3

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

接收传递的数据

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

<h1> {{ n1 }} </h1>
  • 1

通过标签创建的数据(变量)

可以通过 with 标签在模板中创建变量,此变量可以使用视图传递的数据,也可以使用常量或表达式

{% with aaa = n1 %}
<h1> {{ aaa }} </h1>
{% endwith %}
{% with bbb = n1 + '111' %}
<h1> {{ bbb }} </h1>
{% endwith %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

过滤器

过滤器,就是将传入的数据以某些方法进行过滤或修改。使用管道符分隔数据和方法,冒号添加方法的参数(部分参数也可以写到括号内,类似于调用 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])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

列表数据的使用

列表数据的传递和单个数据是一样的。注意下标使用 . 而不是方括号

def index(request):
	text=["Python", "C", "Java", "Basic"]
	return render(request, 'index.html', n1=text)
  • 1
  • 2
  • 3
<h1>计算机编程语言有 {{ n1.0 }},{{ n1.1 }},{{ n1.2 }},{{ n1.3 }} 等</h1>
  • 1

字典数据的使用

字典数据的传递是一样的。接收时,使用 字典名.key 来获取字典数据。

def index(request):
	text ={"y1": "Python", "y2": "C", "y3": "Java", "y4": "Basic"}
	return render(request, 'index.html', n1=text)
  • 1
  • 2
  • 3
<h1>第一种语言是 {{ n1.y1 }},第二种语言是 {{ n1.y2 }},第三种语言是 {{ n1.y3 }},第四种语言是 {{ n1.y4 }}</h1>
  • 1

{% 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
子页面

在子页面的第一行,写入要继承的母版页,然后将需要填充到母版页的代码同样使用 block 标记即可。

{% extends 'layout.html' %}

{% block content %}
{#内容#}
<div class="container">
    <div class="panel panel-default">
		...
    </div>
</div>
{% endblock %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

需注意的是,使用相同的 block 标记(即 block 后的名称相同,通常用在子页面被其它子页面继承的情况),后者会覆盖前者。如果不想覆盖, 需要后者在 block 块内部添加 {{ block.super }} 继承父模板的内容。

include 包含、引入

在当前页面中,导入目标页面内容,则使用 {% include '页面文件’ %}。这种方法将导入页面的 head 标签和 body 标签去掉(但是保留其内容),然后再进行整体导入,例如广告栏等。

此方法可能会因 id 冲突、脚本冲突等原因引起页面混乱,所以不推荐使用。一般使用在引入 css 文件等地方。

在 jinjia 中有的概念,即模板中的函数。django 不支持使用宏,但是可以另类的使用 {% include %} 标签达到宏的效果。

<!-- partial.html -->
{%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%}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
<!-- 主模板文件 -->
{% 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"%}
  • 1
  • 2
  • 3
  • 4

在这种调用方式中,也可以进行传参。

变量定义

如果需要在模板中临时定义变量,可以使用 {% with %} 标签。例如

<!-- 使用赋值定义变量 -->
{% with age=user.age %}
	{{ age|add:"2" }}
{% endwith %}
<!-- 使用常量定义变量 -->
{% with age=20 %}
	{{ age|add:"2" }}
{% endwith %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

循环及流程控制

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

循环语句

循环的语法如下:

<!-- 列表循环 -->
{% for item in n1 %}
	<span>{{ item }}</span>
{% empty %}
	<span> 没有数据 </span>
{% endfor %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以添加 reversed 来反向循环一个列表

<!-- 反向循环 -->
{% for item in n1 reversed %}
	<span>{{ item }}</span>
{% endfor %}
  • 1
  • 2
  • 3
  • 4

字典也可以使用循环

{% for k, v in n1.items %}
	<li>{{ k }} = {{ v }} </li>
{% endfor %}
  • 1
  • 2
  • 3

还可以使用 forloop 对象来获取一些循环的数据信息,例如

{% for item in n1 %}
	<span>{{ forloop.counter }} - {{ item }}</span>
{% endfor %}
  • 1
  • 2
  • 3

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 %}
  • 1
  • 2
  • 3

则第一次循环到 cycle ,则输出 row1,第二次就是 row2 ,依此类推。也可以将循环选择器设置为变量

{% cycle 'row1' 'row2' as cbc %}
{% cycle 'row3' 'row3' as aaa%}
  • 1
  • 2

使用的时候直接使用变量就可以了,就像 {{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
-->
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果不想在定义时输出,可以添加 silent 使其沉默

{% for foo in list %}
我是定义1:{% cycle 'row1' 'row2' as cbc silent%}
我是定义2:{% cycle 'row3' 'row4' as aaa silent%}
    变量1:{{ cbc }}
    变量2:{{ aaa }}
{% endfor %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用变量时,可以不使用 for 标签进行循环,而是直接调用变量,也可以达到循环输出的效果

我是定义1:{% cycle 'row1' 'row2' as cbc %}
    变量1:{{ cbc }}
    循环:{% cycle cbc %}
    循环:{% cycle cbc %}
    循环:{% cycle cbc %}
  • 1
  • 2
  • 3
  • 4
  • 5
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 %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
判断语句

判断语句语法如下:

{% if text == "Django老手" %}
	<h1> 高手高手高高手 </h1>
{% elif text == "初学 Django" %}
	<h1> 菜鸟一个 </h1>
{% else %}
	<h1> 无法识别 </h1>
{% endif %}
{% if x > 0 %}
...
{% endif %}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

也可以在判断中使用逻辑判断

{% if condition1 and condition2 %}
	<h1>同时满足两个判断条件</h1>
{% endif %}
{% if condition1 or condition2 %}
    <h1>满足两个条件其中之一</h1>
{% endif %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
firstof 标签

firstof 标签可以输出第一个不是 false 的变量,如果所有传递的变量都是 false,则不输出任何内容,也可以添加一个后备值。

{% firstof var1 var2 var3 'fallback value' %}
# 相当于
{% if var1 %}
    {{ var1 }}
{% elif var2 %}
    {{ var2 }}
{% elif var3 %}
    {{ var3 }}
{% else %}
	fallback value
{% endif %}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

其他标签

安全代码

如果将一段 HTML 代码以变量形式渲染到页面上,为了安全起见,渲染结果是作为字符串进行渲染,而不是作为 HTML 代码渲染。如果需要关闭这个功能,则可以使用 autoescape 标签

{% autoescape off %}
{{ info }}
{% endautoescape %}
  • 1
  • 2
  • 3

此时通过变量 info 传递的文本就可以作为 html 代码使用了。而使用过滤器 escape 则可以在关闭区域将某个变量设置为 escape ,将符号等渲染为 html 字符。

转义标签

默认在DTL模板中是会去解析那些特殊字符的。比如{%%}以及{{等。如果你在某个代码片段中不想使用DTL的解析引擎。那么你可以把这个代码片段放在verbatim标签中。例如使用 vue 等框架的时候。

{% verbatim %}
{{if dying}}Still alive.{{/if}}
{% endverbatim %}
  • 1
  • 2
  • 3

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) => {}
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 因为上一种方法是使用的 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})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在测试中,cookie 中保存的 csrf_token 和通过视图处理函数方法返回的 token 值不一样,但是无论是在数据体中还是请求头中使用哪个值都是可以的。另外验证的时候需要 cookie 中保存的有 csrftoken 值,如果 post 请求 cookie 中没有 csrftoken 值,无论请求体中验证还是请求头中验证都无法通过。

模板片段缓存

如果模板中有部分内容需要根据某些数据动态生成,但是生成后基本不会改变,并且会频繁的调用。如果每次调用就重新获取数据进行动态渲染会浪费很多计算资源,这时可以使用模板片段缓存,将渲染的片段部分进行缓存,需要的时候直接调用。

使用 {% load cache %}{% endcache %} 来表明此模板片段使用缓存。缓存片段中需要显示确定缓存时间(单位为秒)和片段名称

{% load cache %}
{% cache 500 sidebar %}
<!-- sidebar 片段缓存的内容 -->
{% endcache %}
  • 1
  • 2
  • 3
  • 4

根据需要,可以将这个片段内的动态缓存多个版本,例如上个例子可以根据站点每个用户生成不同版本的缓存。需要给 {% cache %} 再传递一个参数来标识区分这个缓存片段

{% load cache %}
{% cache 500 sidebar request.user.username %}
<!-- sidebar 片段根据每个 username 将进行不同的缓存 -->
{% endcache %}
  • 1
  • 2
  • 3
  • 4

缓存超时时间可以使用模板变量

具体的缓存机制后面缓存部分再分析。模板片段缓存默认尝试使用名称为 template_fragments 的缓存,如果没有则使用默认缓存。也可以使用关键字 using 指定高速缓存,该参数必须是标记的组后一个参数。如果该参数指定的缓存名称未配置则会报错。

{% cache 500 sidebar ...一些参数 using="localcache" %}
  • 1

如果需要手动删除模板片段缓存,则可以获得该缓存名称,再进行删除

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)
  • 1
  • 2
  • 3
  • 4
  • 5

数据库操作

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			# 访问数据库的端口
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

表操作

因为每个表在数据模型中是一个类,所以可以将共性的字段提取出来形成一个抽象的基类,由实体数据模型类继承。在模型类中的元信息中,声明了 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 = (('定义权限','权限说明'))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

数据库中,字段名称就是定义的变量名,另外会自动添加一个名称为 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 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)
  • 1
  • 2
  • 3

删除表或字段

只需要在 models.py 文件中,将需要删除的表相对的类删除(或注释),即可删除表。同理删除字段就是在类定义中将相应字段定义的成员删除。

更改表或字段

更改表或字段和删除表或字段类似,更改 models.py 文件中相应的类或类的成员即可。需要注意的是更改时数据库对数据的约束条件。例如没有设置可为空或具有默认值的字段,添加时要输入默认值等。

执行数据库结构变更

在终端执行命令

python manage.py makemigrations
  • 1

就会读取并处理 models.py,如果第一次则会在 migrations 文件夹下建立索引为 1 的初始化文件 initial.py 。之后每次执行此命令会根据当时的 models.py 和 migrations 下的历史文件,对数据库表进行比较,创建结构变更记录文件(生成迁移文件)。

然后在终端执行

python manage.py migrate
  • 1

就会根据 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])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

使用 ModelForm 组件可以将抛出的错误信息直接呈现给用户。

数据操作

Django 的数据模型操作涉及一个对象 objects,它是查询结果集对象,所有的 CURD 操作都会围绕这个对象来进行。

获取字段信息

使用模型类或数据实例的 _meta 属性能获取很多有用的信息,例如字段信息

for field in Model._meta.get_fields():		# 通过模型类,也可以换成数据实例对象
	print(field.name)		# 字段名称,即定义的变量名
	print(field.verbose_name)		# 定义模型类时定义字段的 verbose_name 值
  • 1
  • 2
  • 3

新增数据

在实际使用中,会在视图函数中进行数据操作,所以在 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

或者以创建数据对象的方式添加数据

u = models.Department()
u.title = '公共关系部'
u.save()
  • 1
  • 2
  • 3

当创建多个数据对象时可以批量保存

u1 = models.Department()
u1.title = '公共关系部'
u2 = models.Department()
u2.title = '研发部'
u3 = models.Department()
u3.title = '技术部'
models.Department.objects.bulk_create([u1, u2, u3])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

获取(查询)数据

可以使用 all 方法获取所有数据,得到的是可迭代的 QuarySet 类型的对象,可以当成数据列表。列表中的每个元素都是一个数据对象,可以使用 对象.字段 的方式获取数据。

data_list = models.UserInfo.objects.all()
for data in data_list:
	print(data.id,data.name,data.password,data.age)
  • 1
  • 2
  • 3

需要注意的是,查询的数据哪怕只有一条,获取的也是 QuarySet 对象。不过可以使用 first 方法从 QuarySet 中获取第一条数据,或使用 last 方法获取最后一条数据。此时获取的是数据模型对象。

过滤器 filter

可以使用 filter 方法进行条件筛选(过滤),参数即为过滤条件

data = models.UserInfo.objects.filter(id=1).first()
  • 1

过滤条件的语法为 属性名__运算符=临界值

对于查询条件,除了等于外运算符还有

示例说明
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()
  • 1
排除过滤器 exclude

另外可以使用 exclude 方法来排除筛选条件(即对 filter 取反)

data = models.UserInfo.objects.filter(age=19).exclude(id=2).first()
  • 1

过滤器 filter 和 exclude 可以链式调用,且得到的结果均是类似于 all 方法获取的 QuerySet 对象。

查询结果返回其他类型

使用 filter 方法和 all 方法返回的是一个查询的结果对象,是无法被 json 序列化的。有时候我们需要返回 json 结果,则可以使用 values 方法,参数可以指定返回的列。如果 values 方法没有参数,则返回全部列。

row_dict = models.UserInfo.objects.filter(id=uid).values('id', 'name', 'password', 'age').first()
  • 1

需注意的是,values 方法获取的是个 QuerySet 类型的字典列表,每个成员是字典类型,但是整体还是 QuerySet,是不可序列化的,当要序列化时,还需进一步处理。可以使用 first 方法获取第一个值,或使用 list 方法将全部数据转为列表。另外可以使用 values_list 方法获取元组列表,元组顺序为参数指定的列顺序。

row_dict = models.UserInfo.objects.filter(id=uid).values_list('id', 'name', 'password', 'age').first()
  • 1

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
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))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

也可以动态构造Q对象

filterBy = [{'username': 'lilei'}, {'age': 19}, {'sex': 'male'}]
q = Q()
for item in filterBy:
	q.add(Q(**item), Q.OR)
User.objects.filter(q)
  • 1
  • 2
  • 3
  • 4
  • 5

如果动态构造的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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

原生查询

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

QuerySet.raw()

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

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

此方法返回的查询结果是一个 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', '果'])
  • 1
数据库连接对象

可以使用 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()		# 提交更新
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

删除数据

找到了需要处理的数据后,可以使用 delete 方法进行删除

models.Department.objects.all().delete()
models.UserInfo.objects.filter(id=3).delete()
  • 1
  • 2

更改数据

类似于删除数据,可以使用 update 方法对数据进行更新

models.Department.objects.all().update(password=999)		# 将所有数据的 password 字段值改为 999
models.UserInfo.objests.filter(id=3).update(name='张三',password='5445',age=20)
  • 1
  • 2

或者更改查询结果数据对象的相应成员,然后使用 save 方法进行保存

data = models.Department.objects.get(pk=1)
data.password = 999
data.save()
  • 1
  • 2
  • 3

查询分页

django 可以控制返回查询记录的起始索引和结束索引,有点类似于切片。即,使用 [ 起始记录索引号 : 结束记录索引号 ] 的方式,并且也遵循左闭右开的原则。

models.UserInfo.objects.all()[0:10]		# 返回查询结果索引为 0 - 9 的记录
models.UserInfo.objects.filter(age__gte=22)[10:20]		# 返回查询结果索引为 10 - 19 的记录
  • 1
  • 2

这样就可以进行分页了。起始记录索引号的值为 (页码 - 1) * 每页大小 , 结束记录索引号的值为 页码 * 每页大小

排序

可以使用 order_by() 方法进行排序,需要注意的是,参数是字段名,而不是数据库中的列名。如果需要使用逆序,则可以在字段前添加 ‘-’ 或者 ‘?’ 符号。可以添加多个字段进行排序,作为多个参数添加即可。

num = models.UserInfo.objects.filter(age__gt=19).order_by('-id')
  • 1

一些聚合操作

可以使用 distinct 方法来去重查询

ages = models.UserInfo.objects.values('age').distinct()
  • 1

可以使用 count 方法来返回查询记录总数

num = models.UserInfo.objects.filter(age__gt=19).count()
  • 1

可以使用 exists 方法来判断查询结果集中是否有数据

num = models.UserInfo.objects.filter(age__gt=19).exists()
  • 1

可以使用 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'))
  • 1
  • 2
  • 3
  • 4

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

使用一对一关系,需要从表主动声明关系。从表获取主表使用的是一个字段(使用 数据对象.关联字段.字段名),主表获取从表是隐性的(使用 数据对象.模型名.字段名注意小写,或定义 related_name 使用 数据对象.反向引用名 访问)。

关系参数

关联关系必须指定 on_delete 参数,删除的约束行为,值在 django.db.models 中引用。含义为:

  • CASCADE : 级联删除,即一并删除关联的数据对象
  • PROTECT : 通过引发 ProtectedError 防止删除被关联引用的对象
  • RESTRICT : 和 PROTECT 一样防止删除被关联的引用对象,不同处在于如果被引用的对象也引用了一个在同一操作中被删除的不同对象,但通过 CASCADE 关系,则允许删除被引用对象
  • SET_NULL : 设置外键为空,只有当 null 为 True 时可以使用
  • SET_DEFAULT : 设置外键为默认值,必须设置默认值,即使用 default 参数
  • SET() : 使用一个函数或方法返回外键设置的数据对象
  • DO_NOTHING : 不采取任何行动。如果数据库后端强制执行引用完整性,则会引发错误

可选参数 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用一对多关系,从表声明外键关联主表。从表获取主表是通过字段属性(即 数据对象.外键字段.字段名),主表获取从表使用 数据对象.模型名_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='收藏的用户列表')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用 ManyToManyField 来创建多对多关系,会自动创建关系表,并自动进行维护。而使用外键则需要手动维护关系表。使用时需在从表定义多对多关系。

主表获取从表的数据,可以使用 数据对象.模型名_set数据对象.反向引用名 来获取;从表获取主表的数据,可以使用 数据对象.关系字段 来获取。获取到的数据均是一个数据集合

和一对一、一对多关系关联数据的不同是,关联数据其实是向多端数据集合添加数据实体:

u1 = User.objects.get(pk=1)
# 因为 Goods 模型中进行了多对多关联,且声明了反向引用名称,所以 User 模型中多了一个 goods 字段
# 向 u1 关联的集合中添加数据实体,则实现了2个数据实体的关联
u1.goods.add(Goods.objects.get(pk=2))
  • 1
  • 2
  • 3
  • 4

相应的,取消多对多关系,则是将数据实体从数据集合中移除

u1 = User.objects.get(pk=1)
u1.goods.remove(Goods.objects.get(pk=2))
  • 1
  • 2
关系参数

多段多的关系参数中,部分与一对一相同,包括 : related_name related_query_name ,另外还有一些独有的参数:

django 在使用 ManyToManyField 定义了多对多字段时,会自动创建并维护一个关系表,名称可以由字段参数 db_table 指定,默认使用定义关系的模型表和字段本身名称。

如果在自身定义多对多关系时,例如

class Person(models.Model):
	friends = models.ManyToManyField('self')
  • 1
  • 2

当 django 处理这个模型时,会识别出它本身有一个 ManyToManyField,因此,它没有给 Person 类添加 person_set 属性。相反, ManyToManyField 被认为是对称的,也就是说,如果我是你的朋友,那么你就是我的朋友。如果你不想让 self 的多对多关系对称,可以将 symmetrical 设置为 False。这样会强制 Django 添加反向关系的描述符,允许 ManyToManyField 关系是非对称的。

定义多对多关系时,如果想手动指定中间模型,而不使用默认生成的关系表,可以使用 through 参数来指定。through_fields 用来指定自定义中间模型的关系字段,具体的操作后面详细说明。

自定义关系模型

使用自定义关系模型,可以在 ManyToManyField 字段中使用 through 参数来指定中间表的模型。最常用的地方是想把额外的数据与多对多关系起来。例如给多对多关系加上一个时间、标识、说明等等。

如果没有显式的指定 through 模型,会隐式的创建 through 模型,此模型有三个字段:

  • id : 关系表的主键
  • <containing_model>_id 或 from_<model>_id : 声明关系的模型的id(非自关联),或源实例的id(自关联)
  • <other_model>_id 或 t_<model>_id : 关系指向的模型的id(非自关联),或目标模型实例的id(自关联)

如果需要查询关联记录,可以这样使用:

# Model 即定义的数据模型
# m2mfield 是多对多字段
Model.m2mfield.through.objects.all()
  • 1
  • 2
  • 3

需要注意的是,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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

可以像操作其他模型一样,通过中间模型来访问和操作关联关系表:

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当自定义的关系模型中,有两个外键字段同时指向一个模型,例如:

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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='八')
  • 1
  • 2
  • 3
  • 4

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

queryset 结果集可以使用 values() 方法获取所有列的数据,但是数据对象不行,这时可以使用 model._meta.fields 来获取全部的列对象,列对象的属性 name 就是列名了。使用遍历的方式可以获取全部列名,根据列名可以使用 getattr()数据对象的相应属性得到,也就变相遍历了数据对象

关于 ORM 的迁移

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

结合模板

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

ORM 的原子性操作

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

from django.db import transaction

try:
	with transaction.atomic():
		orm 操作
		orm 操作
except:
	pass
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

ORM 的缓存机制

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/443344
推荐阅读
相关标签
  

闽ICP备14008679号