Django之五模型
Posted woyouyihujiu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django之五模型相关的知识,希望对你有一定的参考价值。
前言
在第三节,我们讲述了用 Django 建造网站的基本途径: 建立视图和 URLConf 。 正如我们所阐述的,视图负责处理一些主观逻辑,然后返回响应结果。 作为例子之一,我们的主观逻辑是要计算当前的日期和时间。
在当代 Web 应用中,主观逻辑经常牵涉到与数据库的交互。 数据库驱动网站 在后台连接数据库服务器,从中取出一些数据,然后在 Web 页面用漂亮的格式展示这些数据。 这个网站也可能会向访问者提供修改数据库数据的方法。
许多复杂的网站都提供了以上两个功能的某种结合。 例如 Amazon.com 就是一个数据库驱动站点的良好范例。 本质上,每个产品页面都是数据库中数据以 html格式进行的展现,而当你发表客户评论时,该评论被插入评论数据库中。
由于先天具备 Python 简单而强大的数据库查询执行方法,Django 非常适合开发数据库驱动网站。 本节深入介绍了该功能: Django 数据库层。
(注意: 尽管对 Django 数据库层的使用中并不特别强调这点,但是我们还是强烈建议您掌握一些数据库和 SQL 原理。 对这些概念的介绍超越了本书的范围,但就算你是数据库方面的菜鸟,我们也建议你继续阅读。 你也许能够跟上进度,并在上下文学习过程中掌握一些概念。)
在视图中进行数据库查询的笨方法
正如第三节详细介绍的那个在视图中输出 HTML 的笨方法(通过在视图里对文本直接硬编码HTML),在视图中也有笨方法可以从数据库中获取数据。 很简单: 用现有的任何 Python 类库执行一条 SQL 查询并对结果进行一些处理。
在本例的视图中,我们使用了 Pymysql 类库(可以使用pip install PyMySQL安装)来连接 MySQL 数据库,取回一些记录,将它们提供给模板以显示一个网页:
import pymysql
from django.shortcuts import render
def book_list(request):
conn = pymysql.connect(
host="localhost",
user="root",
password="mysql",
db="mydb",
port=3306,
charset=‘utf8‘)
# 使用cursor()方法获取操作游标
cursor = conn.cursor()
# 获取数据
sql = "select * from books"
cursor.execute(sql) #执行sql语句
names = cursor.fetchall() #获取查询的所有记录
conn.close()
return render(request, ‘book_list.html‘, {‘names‘, names})
这个方法可用,但很快一些问题将出现在你面前:
我们将数据库连接参数硬行编码于代码之中。 理想情况下,这些参数应当保存在 Django 配置中。
我们不得不重复同样的代码: 创建数据库连接、创建数据库游标、执行某个语句、然后关闭数据库。 理想情况下,我们所需要应该只是指定所需的结果。
它把我们栓死在 MySQL 之上。 如果过段时间,我们要从 MySQL 换到 PostgreSQL,就不得不使用不同的数据库适配器(例如 psycopg 而不是 MySQLdb ),改变连接参数,根据 SQL 语句的类型可能还要修改SQL 。 理想情况下,应对所使用的数据库服务器进行抽象,这样一来只在一处修改即可变换数据库服务器。 (如果你正在建立一个开源的Django应用程序来尽可能让更多人使用的话,这个特性是非常适当的。)
正如你所期待的,Django数据库层正是致力于解决这些问题。 以下提前揭示了如何使用 Django 数据库 API 重写之前那个视图。
from django.shortcuts import render
from mysite.books.models import Book
def book_list(request):
books = Book.objects.order_by(‘name‘)
return render(request,‘book_list.html‘, {‘books‘: books})
我们将在本节稍后的地方解释这段代码。 目前而言,仅需对它有个大致的认识。
MVT开发模式
在钻研更多代码之前,让我们先花点时间考虑下 Django 数据驱动 Web 应用的总体设计。
我们在前面章节提到过,Django 的设计鼓励松耦合及对应用程序中不同部分的严格分割。 遵循这个理念的话,要想修改应用的某部分而不影响其它部分就比较容易了。 在视图函数中,我们已经讨论了通过模板系统把业务逻辑和表现逻辑分隔开的重要性。 在数据库层中,我们对数据访问逻辑也应用了同样的理念。
把数据存取逻辑、业务逻辑和表现逻辑组合在一起的概念有时被称为软件架构的 Model-View-Controller (MVC)模式。 在这个模式中, Model 代表数据存取层,View 代表的是系统中选择显示什么和怎么显示的部分,Controller 指的是系统中根据用户输入并视需要访问模型,以决定使用哪个视图的那部分。
为什么用缩写?
像 MVC 这样的明确定义模式的主要用于改善开发人员之间的沟通。 比起告诉同事,“让我们采用抽象的数据存取方式,然后单独划分一层来显示数据,并且在中间加上一个控制它的层”,一个通用的说法会让你收益,你只需要说:“我们在这里使用MVC模式吧。”。
Django 紧紧地遵循这种 MVC 模式,可以称得上是一种 MVC 框架。 以下是 Django 中 M、V 和 C 各自的含义:
M ,数据存取部分,由django数据库层处理,本章要讲述的内容。
V ,选择显示哪些数据要显示以及怎样显示的部分,由视图和模板处理。
C ,根据用户输入委派视图的部分,由 Django 框架根据 URLconf 设置,对给定 URL 调用适当的 Python 函数。
由于 C 由框架自行处理,而 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),Django 也被称为 MTV 框架 。在 MTV 开发模式中:
M 代表模型(Model),即数据存取层。 该层处理与数据相关的所有事务: 如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。
T 代表模板(Template),即表现层。 该层处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。
V 代表视图(View),即业务逻辑层。 该层包含存取模型及调取恰当模板的相关逻辑。 你可以把它看作模型与模板之间的桥梁。
如果你熟悉其它的 MVC Web开发框架,比方说 Ruby on Rails,你可能会认为 Django 视图是控制器,而 Django 模板是视图。 很不幸,这是对 MVC 不同诠释所引起的错误认识。 在 Django 对 MVC 的诠释中,视图用来描述要展现给用户的数据;不是数据 如何展现 ,而且展现 哪些 数据。 相比之下,Ruby on Rails 及一些同类框架提倡控制器负责决定向用户展现哪些数据,而视图则仅决定 如何 展现数据,而不是展现 哪些 数据。
两种诠释中没有哪个更加正确一些。 重要的是要理解底层概念。
数据库配置
记住这些理念之后,让我们来开始 Django 数据库层的探索。 首先,我们需要做些初始配置;我们需要告诉Django使用什么数据库以及如何连接数据库。
我们假定你已经完成了数据库服务器的安装和激活,并且已经在其中创建了数据库(例如,用 CREATE DATABASE 语句)。 如果你使用SQLite,不需要这步安装,因为SQLite使用文件系统上的独立文件来存储数据。
像前面章节提到的 TEMPLATE DIRS 一样,数据库配置也是在Django的配置文件里,缺省 是 settings.py 。 打开这个文件并查找数据库配置:
DATABASES = {
‘default‘: {
‘ENGINE‘: ‘django.db.backends.sqlite3‘,
‘NAME‘: os.path.join(BASE_DIR, ‘db.sqlite3‘),
}
}
如果你想使用其他数据库,你需要安装合适的 database bindings ,然后改变设置文件中 DATABASES ‘default‘ 项目中的一些键值:
ENGINE -- 可选值有 ‘django.db.backends.sqlite3‘,‘django.db.backends.postgresql‘,‘django.db.backends.mysql‘,或 ‘django.db.backends.oracle‘。其它 可用后端。
NAME - 数据库的名称。如果使用的是 SQLite,数据库将是你电脑上的一个文件,在这种情况下, NAME 应该是此文件的绝对路径,包括文件名。默认值 os.path.join(BASE_DIR, ‘db.sqlite3‘) 将会把数据库文件储存在项目的根目录。
如果你不使用 SQLite,则必须添加一些额外设置,比如 USER 、 PASSWORD 、 HOST 等等。
配置示例:
‘ENGINE‘: ‘django.db.backends.mysql‘,
NAME 将数据库名称告知 Django 。 例如:
‘NAME‘: ‘mysite‘
如果使用 SQLite,请对数据库文件指定完整的文件系统路径。 例如:
‘NAME‘: os.path.join(BASE_DIR, ‘db.sqlite3‘),
# 下面的配置不适用于sqlite3:
USER 告诉 Django 用哪个用户连接数据库。 例如: 如果用SQLite,空白即可。
PASSWORD 告诉Django连接用户的密码。 SQLite 用空密码即可。
HOST 告诉 Django 连接哪一台主机的数据库服务器。 如果数据库与 Django 安装于同一台计算机(即本机),可将此项保留空白。 如果你使用SQLite,此项留空。
PORT 告诉 Django 连接主机的哪一个端口。 如果你使用SQLite,此项留空。
接下来让我们看一下完整的配置:
DATABASES = {
‘default‘: {
#‘ENGINE‘: ‘django.db.backends.‘, # Add ‘postgresql_psycopg2‘, ‘mysql‘, ‘sqlite3‘ or ‘oracle‘.
‘ENGINE‘: ‘django.db.backends.mysql‘, #添加数据库引擎;选项[‘postgresql_psycopg2‘, ‘mysql‘, ‘sqlite3‘ or ‘oracle‘].
‘NAME‘: ‘mysite‘, # 数据库文件的路径.
# The following settings are not used with sqlite3:
# 下面的配置不适用于sqlite3:
‘USER‘: ‘‘, # 数据库登陆用户名
‘PASSWORD‘: ‘‘, # 数据库登陆密码
‘HOST‘: ‘‘, # Empty for localhost through domain sockets or ‘127.0.0.1‘ for localhost through TCP. # 主机名
‘PORT‘: ‘‘, # Set to empty string for default. # 端口号
}
}
一旦在输入了那些设置并保存之后应当测试一下你的配置。 我们可以在mysite
项目目录下执行上章所提到的python manage.py shell
来进行测试。 (我们上一节提到过在,manager.py shell
命令是以正确Django配置启用Python交互解释器的一种方法。 这个方法在这里是很有必要的,因为Django需要知道加载哪个配置文件来获取数据库连接信息。)
在python3的环境中,配置好以上参数之后,直接运行 python manage.py shell 将会报错:
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module. Did you install mysqlclie
这是因为在 python3 中,改变了连接库,改为了 pymysql 库,( python2 中为 mysqldb 库)使用pip install pymysql 进行安装,直接导入即可使用。
但是在 Django 中, 连接数据库时使用的是 MySQLdb 库,这在与 python3 的合作中就会报以上错误了。
解决方法:在 __init__.py
文件中添加以下代码即可。
import pymysql
pymysql.install_as_MySQLdb()
之后输入下面这些命令来测试你的数据库配置:
>>> from django.db import connection
>>> cursor = connection.cursor()
如果没有显示什么错误信息,那么你的数据库配置是正确的。 否则,你就得 查看错误信息来纠正错误。
第一个应用程序
你现在已经确认数据库连接正常工作了,让我们来创建一个 Django app-一个包含模型,视图和Django代码,并且形式为独立Python包的完整Django应用。
在这里要先解释一些术语,初学者可能会混淆它们。 在第二节我们已经创建了 project , 那么 project 和 app 之间到底有什么不同呢?它们的区别就是一个是配置另一个是 代码:
一个project包含很多个Django app以及对它们的配置。
技术上,project的作用是提供配置文件,比方说哪里定义数据库连接信息, 安装的app列表, TEMPLATE ,等等。
一个app是一套Django功能的集合,通常包括模型和视图,按Python的包结构的方式存在。
例如,Django本身内建有一些app,例如注释系统和自动管理界面。 app的一个关键点是它们是很容易移植到其他project和被多个project复用。
对于如何架构Django代码并没有快速成套的规则。 如果你只是建造一个简单的Web站点,那么可能你只需要一个app就可以了; 但如果是一个包含许多不相关的模块的复杂的网站,例如电子商务和社区之类的站点,那么你可能需要把这些模块划分成不同的app,以便以后复用。
不错,你可以不用创建app,这一点应经被我们之前编写的视图函数的例子证明了 。 在那些例子中,我们只是简单的创建了一个称为views.py的文件,编写了一些函数并在URLconf中设置了各个函数的映射。 这些情况都不需要使用apps。
但是,系统对app有一个约定: 如果你使用了Django的数据库层(模型),你 必须创建一个Django app。 模型必须存放在apps中。 因此,为了开始建造 我们的模型,我们必须创建一个新的app。
在mysite
项目文件下输入下面的命令来创建books
app:
python manage.py startapp books
这个命令并没有输出什么,它只在 mysite 的目录里创建了一个 books 目录。 让我们来看看这个目录的内容:
books
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
这个目录包含了这个app的模型和视图。
使用你最喜欢的文本编辑器查看一下 models.py 和 views.py 文件的内容。 它们都是空的,除了 models.py 里有一个 import。这就是你Django app的基础。
在Python代码里定义模型
我们早些时候谈到。MTV里的M代表模型。 Django模型是用Python代码形式表述的数据在数据库中的定义。 对数据层来说它等同于 CREATE TABLE 语句,只不过执行的是Python代码而不是 SQL,而且还包含了比数据库字段定义更多的含义。 Django用模型在后台执行SQL代码并把结果用Python的数据结构来描述。 Django也使用模型来呈现SQL无法处理的高级概念。
如果你对数据库很熟悉,你可能马上就会想到,用Python 和 SQL来定义数据模型是不是有点多余? Django这样做是有下面几个原因的:
自省(运行时自动识别数据库)会导致过载和有数据完整性问题。 为了提供方便的数据访问API, Django需要以 某种方式 知道数据库层内部信息,有两种实现方式。 第一种方式是用Python明确地定义数据模型,第二种方式是通过自省来自动侦测识别数据模型。
第二种方式看起来更清晰,因为数据表信息只存放在一个地方-数据库里,但是会带来一些问题。 首先,运行时扫描数据库会带来严重的系统过载。 如果每个请求都要扫描数据库的表结构,或者即便是 服务启动时做一次都是会带来不能接受的系统过载。 (有人认为这个程度的系统过载是可以接受的,而Django开发者的目标是尽可能地降低框架的系统过载)。第二,某些数据库,尤其是老版本的MySQL,并未完整存储那些精确的自省元数据。
编写Python代码是非常有趣的,保持用Python的方式思考会避免你的大脑在不同领域来回切换。 尽可能的保持在单一的编程环境/思想状态下可以帮助你提高生产率。 不得不去重复写SQL,再写Python代码,再写SQL,…,会让你头都要裂了。
把数据模型用代码的方式表述来让你可以容易对它们进行版本控制。 这样,你可以很容易了解数据层 的变动情况。
SQL只能描述特定类型的数据字段。 例如,大多数数据库都没有专用的字段类型来描述Email地址、URL。 而用Django的模型可以做到这一点。 好处就是高级的数据类型带来更高的效率和更好的代码复用。
SQL还有在不同数据库平台的兼容性问题。 发布Web应用的时候,使用Python模块描述数据库结构信息可以避免为MySQL, PostgreSQL, and SQLite编写不同的CREATE TABLE。
当然,这个方法也有一个缺点,就是Python代码和数据库表的同步问题。 如果你修改了一个Django模型, 你要自己来修改数据库来保证和模型同步。 我们将在稍后讲解解决这个问题的几种策略。
最后,我们要提醒你Django提供了实用工具来从现有的数据库表中自动扫描生成模型。 这对已有的数据库来说是非常快捷有用的。 我们将在后续课程中对此进行讨论。
第一个模型
在本节和后续章节里,我们把注意力放在一个基本的 书籍/作者/出版商 数据库结构上。 我们这样做是因为 这是一个众所周知的例子,很多SQL有关的书籍也常用这个举例。
我们来假定下面的这些概念、字段和关系:
一个作者有姓,有名及email地址。
出版商有名称,地址,所在城市、省,国家,网站。
书籍有书名和出版日期。 它有一个或多个作者(和作者是多对多的关联关系[many-to-many]), 只有一个出版商(和出版商是一对多的关联关系[one-to-many],也被称作外键[foreign key])
第一步是用Python代码来描述它们。 打开由startapp
命令创建的models.py 并输入下面的内容:
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE)
publication_date = models.DateField()
让我们来快速讲解一下这些代码的含义。 首先要注意的事是每个数据模型都是 django.db.models.Model 的子类。它的父类 Model 包含了所有必要的和数据库交互的方法,并提供了一个简洁漂亮的定义数据库字段的语法。 信不信由你,这些就是我们需要编写的通过Django存取基本数据的所有代码。
每个模型相当于单个数据库表,每个属性也是这个表中的一个字段。 属性名就是字段名,它的类型(例如 CharField )相当于数据库的字段类型 (例如 varchar )。例如, Publisher 模块等同于下面这张表(用PostgreSQL的 CREATE TABLE 语法描述):
CREATE TABLE "books_publisher" (
"id" serial NOT NULL PRIMARY KEY,
"name" varchar(30) NOT NULL,
"address" varchar(50) NOT NULL,
"city" varchar(60) NOT NULL,
"state_province" varchar(30) NOT NULL,
"country" varchar(50) NOT NULL,
"website" varchar(200) NOT NULL
);
事实上,正如过一会儿我们所要展示的,Django 可以自动生成这些 CREATE TABLE 语句。
“每个数据库表对应一个类”这条规则的例外情况是多对多关系。 在我们的范例模型中, Book 有一个 多对多字段 叫做 authors 。 该字段表明一本书籍有一个或多个作者,但 Book 数据库表却并没有 authors 字段。 相反,Django创建了一个额外的表(多对多连接表)来处理书籍和作者之间的映射关系。
最后需要注意的是,我们并没有显式地为这些模型定义任何主键。 除非你单独指明,否则Django会自动为每个模型生成一个自增长的整数主键字段id。每个Django模型都要求有单独的主键。
注:
在django2.0后,定义外键和一对一关系的时候需要加on_delete选项,此参数为了避免两个表里的数据不一致问题,不然会报错:
TypeError: __init__() missing 1 required positional argument: ‘on_delete‘
举例说明:
user=models.OneToOneField(User)
owner=models.ForeignKey(UserProfile)
需要改成:
user=models.OneToOneField(User,on_delete=models.CASCADE) --在老版本这个参数(models.CASCADE)是默认值
owner=models.ForeignKey(UserProfile,on_delete=models.CASCADE) --在老版本这个参数(models.CASCADE)是默认值
参数说明:
on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五个可选择的值
CASCADE:此值设置,是级联删除。
PROTECT:此值设置,是会报完整性错误。
SET_NULL:此值设置,会把外键设置为null,前提是允许为null。
SET_DEFAULT:此值设置,会把设置为外键的默认值。
SET():此值设置,会调用外面的值,可以是一个函数。
一般情况下使用CASCADE就可以了。
模型安装
完成这些代码之后,现在让我们来在数据库中创建这些表。 要完成该项工作,第一步是在 Django 项目中 激活 这些模型。 将 books app 添加到配置文件的已安装应用列表中即可完成此步骤。
再次编辑 settings.py 文件, 找到 INSTALLED_APPS 设置。 INSTALLED_APPS 告诉 Django 项目哪些 app 处于激活状态。 缺省情况下如下所示:
INSTALLED_APPS = [
‘django.contrib.admin‘,
‘django.contrib.auth‘,
‘django.contrib.contenttypes‘,
‘django.contrib.sessions‘,
‘django.contrib.messages‘,
‘django.contrib.staticfiles‘,
]
django.contrib.admin -- 管理员站点, 你很快就会使用它。
django.contrib.auth -- 认证授权系统。
django.contrib.contenttypes -- 内容类型框架。
django.contrib.sessions -- 会话框架。
django.contrib.messages -- 消息框架。
django.contrib.staticfiles -- 管理静态文件的框架。
这些应用被默认启用是为了给常规项目提供方便。
添加‘books’
到INSTALLED_APPS
的末尾,此时设置的内容看起来应该是这样的:
INSTALLED_APPS = [
‘django.contrib.admin‘,
‘django.contrib.auth‘,
‘django.contrib.contenttypes‘,
‘django.contrib.sessions‘,
‘django.contrib.messages‘,
‘django.contrib.staticfiles‘,
‘books‘,
]
MIDDLEWARE = [
‘django.middleware.security.SecurityMiddleware‘,
‘django.contrib.sessions.middleware.SessionMiddleware‘,
‘django.middleware.common.CommonMiddleware‘,
‘django.middleware.csrf.CsrfViewMiddleware‘,
‘django.contrib.auth.middleware.AuthenticationMiddleware‘,
‘django.contrib.messages.middleware.MessageMiddleware‘,
‘django.middleware.clickjacking.XFrameOptionsMiddleware‘,
]
(就像我们在上一章设置TEMPLATE_DIRS所提到的逗号,同样在INSTALLED_APPS的末尾也需添加一个逗号,因为这是个单元素的元组。 另外,建议在 每一个 tuple元素后面加一个逗号,不管它是不是 只有一个元素。 这是为了避免忘了加逗号,而且也没什么坏处。)
‘books‘指示我们正在编写的books app。 INSTALLED_APPS 中的每个app都使用 Python的路径描述,包的路径,用小数点“.”间隔。
现在我们可以创建数据库表了。 首先,用下面的命令验证模型的有效性:
python manage.py check
check 命令检查你的模型的语法和逻辑是否正确。 如果一切正常,你会看到
System check identified no issues (0 silenced).
。如果出错,请检查你输入的模型代码。 错误输出会给出非常有用的错误信息来帮助你修正你的模型。
一旦你觉得你的模型可能有问题,运行 python manage.py check 。 它可以帮助你捕获一些常见的模型定义错误。
模型确认没问题了,运行下面的几条命令来生成 CREATE TABLE 语句(如果你使用的是Unix,那么可以启用语法高亮):
在你的项目目录下执行下面的命令
python manage.py makemigrations books
执行这个命令后,将看到类似以下的内容:
Migrations for ‘books‘:
books/migrations/0001_initial.py
- Create model Author
- Create model Book
- Create model Publisher
- Add field publisher to book
通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移。
迁移是 Django 对于模型定义(也就是你的数据库结构)的变化的储存形式 - 没那么玄乎,它们其实也只是一些你磁盘上的文件。如果你想的话,你可以阅读一下你模型的迁移数据,它被储存在 polls/migrations/0001_initial.py 里。别担心,你不需要每次都阅读迁移文件,但是它们被设计成人类可读的形式,这是为了便于你手动修改它们。
Django 有一个自动执行数据库迁移并同步管理你的数据库结构的命令 - 这个命令是 migrate,我们马上就会接触它 - 但是首先,让我们看看迁移命令会执行哪些 SQL 语句。sqlmigrate 命令接收一个迁移的名称,然后返回对应的 SQL:
python manage.py sqlmigrate books 0001
你将会看到类似下面这样的输出
BEGIN;
--
-- Create model Author
--
CREATE TABLE `books_author` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `first_name` varchar(30) NOT NULL, `last_name` varchar(40) NOT NULL, `email` varchar(254) NOT NULL);
--
-- Create model Book
--
CREATE TABLE `books_book` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `title` varchar(100) NOT NULL, `publication_date` date NOT NULL);
CREATE TABLE `books_book_authors` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `book_id` integer NOT NULL, `author_id` integer NOT NULL);
--
-- Create model Publisher
--
CREATE TABLE `books_publisher` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL, `address` varchar(50) NOT NULL, `city` varchar(60) NOT NULL, `state_province` varchar(30) NOT NULL, `country` varchar(50) NOT NULL, `website` varchar(200) NOT NULL);
--
-- Add field publisher to book
--
ALTER TABLE `books_book` ADD COLUMN `publisher_id` integer NOT NULL;
ALTER TABLE `books_book_authors` ADD CONSTRAINT `books_book_authors_book_id_ed3433e7_fk_books_book_id` FOREIGN KEY (`book_id`) REFERENCES `books_book` (`id`);
ALTER TABLE `books_book_authors` ADD CONSTRAINT `books_book_authors_author_id_984f1ab8_fk_books_author_id` FOREIGN KEY (`author_id`) REFERENCES `books_author` (`id`);
ALTER TABLE `books_book_authors` ADD CONSTRAINT books_book_authors_book_id_author_id_8714badb_uniq UNIQUE (`book_id`, `author_id`);
ALTER TABLE `books_book` ADD CONSTRAINT `books_book_publisher_id_189e6c56_fk_books_publisher_id` FOREIGN KEY (`publisher_id`) REFERENCES `books_publisher` (`id`);
COMMIT;
请注意以下几点:
输出的内容和你使用的数据库有关,上面的输出示例使用的是 MySQL。
数据库的表名是由应用名(books)和模型名的小写形式( publisher,author,book)连接而来。(如果需要,你可以自定义此行为。)
主键(IDs)会被自动创建。(当然,你也可以自定义。)
默认的,Django 会在外键字段名后追加字符串 "_id" 。(同样,这也可以自定义。)
生成的 SQL 语句是为你所用的数据库定制的,所以那些和数据库有关的字段类型,比如 auto_increment (MySQL)、 serial (PostgreSQL)和 integer primary key autoincrement (SQLite),Django 会帮你自动处理。那些和引号相关的事情 - 例如,是使用单引号还是双引号 - 也一样会被自动处理。
这个 sqlmigrate 命令并没有真正在你的数据库中的执行迁移 - 它只是把命令输出到屏幕上,让你看看 Django 认为需要执行哪些 SQL 语句。这在你想看看 Django 到底准备做什么,或者当你是数据库管理员,需要写脚本来批量处理数据库时会很有用。
现在,再次运行 migrate 命令,在数据库里创建新定义的模型的数据表:
python manage.py migrate
你将看到类似以下输出:
Operations to perform:
Apply all migrations: admin, auth, books, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying books.0001_initial... OK
Applying sessions.0001_initial... OK
这个 migrate 命令选中所有还没有执行过的迁移(Django 通过在数据库中创建一个特殊的表 django_migrations 来跟踪执行过哪些迁移)并应用在数据库上 - 也就是将你对模型的更改同步到数据库结构上。
迁移是非常强大的功能,它能让你在开发过程中持续的改变数据库结构而不需要重新删除和创建表 - 它专注于使数据库平滑升级而不会丢失数据。我们会在后面的教程中更加深入的学习这部分内容,现在,你只需要记住,改变模型需要这三步:
编辑 models.py 文件,改变模型
运行 python manage.py makemigrations 为模型的改变生成迁移文件。
运行 python manage.py migrate 来应用数据库迁移。
数据库迁移被分解成生成和应用两个命令是为了让你能够在代码控制系统上提交迁移数据并使其能在多个应用里使用;这不仅仅会让开发更加简单,也给别的开发者和生产环境中的使用带来方便。
基本数据访问
一旦你创建了模型,Django自动为这些模型提供了高级的Python API。 运行 python manage.py shell 并输入下面的内容试试看:
>>> from books.models import Publisher
>>> p1 = Publisher(name=‘Apress‘, address=‘2855 Telegraph Avenue‘,city=‘Berkeley‘, state_province=‘CA‘, country=‘U.S.A.‘,website=‘http://www.apress.com/‘)
>>> p1.save()
>>> p2 = Publisher(name="O‘Reilly", address=‘10 Fawcett St.‘,city=‘Cambridge‘, state_province=‘MA‘, country=‘U.S.A.‘,website=‘http://www.oreilly.com/‘)
>>> p2.save()
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
<QuerySet [<Publisher: Publisher object (1)>, <Publisher: Publisher object (2)>]>
这短短几行代码干了不少的事。 这里简单的说一下:
首先,导入Publisher模型类, 通过这个类我们可以与包含 出版社 的数据表进行交互。
接着,创建一个
Publisher
类的实例并设置了字段name, address
等的值。调用该对象的 save() 方法,将对象保存到数据库中。 Django 会在后台执行一条 INSERT 语句。
最后,使用
Publisher.objects
属性从数据库取出出版商的信息,这个属性可以认为是包含出版商的记录集。 这个属性有许多方法, 这里先介绍调用Publisher.objects.all()
方法获取数据库中Publisher
类的所有对象。这个操作的幕后,Django执行了一条SQLSELECT
语句。
这里有一个值得注意的地方,在这个例子可能并未清晰地展示。 当你使用Django modle API创建对象时Django并未将对象保存至数据库内,除非你调用save()
方法:
p1 = Publisher(...)
# At this point, p1 is not saved to the database yet!
p1.save()
# Now it is.
如果需要一步完成对象的创建与存储至数据库,就使用objects.create()
方法。 下面的例子与之前的例子等价:
>>> p1 = Publisher.objects.create(name=‘Apress‘,address=‘2855 Telegraph Avenue‘,city=‘Berkeley‘, state_province=‘CA‘, country=‘U.S.A.‘,website=‘http://www.apress.com/‘)
>>> p2 = Publisher.objects.create(name="O‘Reilly",address=‘10 Fawcett St.‘, city=‘Cambridge‘,state_province=‘MA‘, country=‘U.S.A.‘,website=‘http://www.oreilly.com/‘)
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
当然,你肯定想执行更多的Django数据库API试试看,不过,还是让我们先解决一点烦人的小问题。
添加模块的字符串表示
当我们打印整个publisher列表时,我们没有得到想要的有用信息,无法把````对象区分开来:
[<Publisher: Publisher object>, <Publisher: Publisher object>]
我们可以简单解决这个问题,只需要为Publisher 对象添加一个方法 str() 。 str() 方法告诉Python如何将对象以字符串的方式显示出来。 为以上三个模型添加__str__()方法后,就可以看到效果了:
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __str__(self):
return self.name
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
def __str__(self):
return ‘%s, %s‘ %(self.first_name, self.last_name)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE)
publication_date = models.DateField()
def __str__(self):
return self.title
就象你看到的一样, str() 方法可以进行任何处理来返回对一个对象的字符串表示。 Publisher和Book对象的__str__()方法简单地返回各自的名称和标题,Author对象的__str__()方法则稍微复杂一些,它将first_name和last_name字段值以空格连接后再返回。
为了让我们的修改生效,先退出Python Shell,然后再次运行 python manage.py shell 进入。(这是保证代码修改生效的最简单方法。)现在Publisher
对象列表容易理解多了。
>>> from books.models import Publisher
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
<QuerySet [<Publisher: Apress>, <Publisher: O‘Reilly>, <Publisher: Apress>]>
插入和更新数据
你已经知道怎么做了: 先使用一些关键参数创建对象实例,如下:
>>> p = Publisher(name=‘Apress‘,address=‘2855 Telegraph Ave.‘,city=‘Berkeley‘,state_province=‘CA‘,country=‘U.S.A.‘, website=‘http://www.apress.com/‘)
这个对象实例并 没有 对数据库做修改。 在调用save()
方法之前,记录并没有保存至数据库,像这样:
>>> p.save()
在SQL里,这大致可以转换成这样:
INSERT INTO books_publisher
(name, address, city, state_province, country, website)
VALUES
(‘Apress‘, ‘2855 Telegraph Ave.‘, ‘Berkeley‘, ‘CA‘,
‘U.S.A.‘, ‘http://www.apress.com/‘);
因为 Publisher 模型有一个自动增加的主键 id ,所以第一次调用 save() 还多做了一件事: 计算这个主键的值并把它赋值给这个对象实例:
>>> p.id
3 # this will differ based on your own data
接下来再调用 save() 将不会创建新的记录,而只是修改记录内容(也就是 执行 UPDATE SQL语句,而不是 INSERT 语句):
>>> p.name = ‘Apress Publishing‘
>>> p.save()
前面执行的 save() 相当于下面的SQL语句:
UPDATE books_publisher SET
name = ‘Apress Publishing‘,
address = ‘2855 Telegraph Ave.‘,
city = ‘Berkeley‘,
state_province = ‘CA‘,
country = ‘U.S.A.‘,
website = ‘http://www.apress.com‘
WHERE id = 3;
注意,并不是只更新修改过的那个字段,所有的字段都会被更新。 这个操作有可能引起竞态条件(从多进程间通信的角度来讲,是指两个或多个进程对共享的数据进行读或写的操作时,最终的结果取决于这些进程的执行顺序),这取决于你的应用程序。 请参阅后面的“更新多个对象”小节以了解如何实现这种轻量的修改(只修改对象的部分字段)。
UPDATE books_publisher SET
name = ‘Apress Publishing‘
WHERE id=3;
选择对象
当然,创建新的数据库,并更新之中的数据是必要的,但是,对于 Web 应用程序来说,更多的时候是在检索查询数据库。 我们已经知道如何从一个给定的模型中取出所有记录:
>>> Publisher.objects.all()
[<Publisher: Apress>, <Publisher: O‘Reilly>]
这相当于这个SQL语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher;
注意
注意到Django在选择所有数据时并没有使用 SELECT* ,而是显式列出了所有字段。 设计的时候就是这样: SELECT* 会更慢,而且最重要的是列出所有字段遵循了Python 界的一个信条: 明言胜于暗示。
有关Python之禅(戒律),在Python提示行输入 import this 试试看。
让我们来仔细看看 Publisher.objects.all() 这行的每个部分:
首先,我们有一个已定义的模型 Publisher 。没什么好奇怪的: 你想要查找数据, 你就用模型来获得数据。
然后,是objects属性。 它被称为管理器,我们将在第10节中详细讨论它。 目前,我们只需了解管理器管理着所有针对数据包含、还有最重要的数据查询的表格级操作。
所有的模型都自动拥有一个 objects 管理器;你可以在想要查找数据时使用它。
最后,还有 all() 方法。这个方法返回返回数据库中所有的记录。 尽管这个对象 看起来 象一个列表(list),它实际是一个 QuerySet 对象, 这个对象是数据库中一些记录的集合。
任何对数据库的查询操作都遵循这样的模式:即 调用绑定在该模型上的管理器(objects)的相应方法(如all)。
数据过滤
我们很少会一次性从数据库中取出所有的数据;通常都只针对一部分数据进行操作。 在Django API中,我们可以使用filter()
方法对数据进行过滤:
>>> Publisher.objects.filter(name=‘Apress‘)
[<Publisher: Apress>]
filter() 根据关键字参数来转换成 WHERE SQL语句。 前面这个例子 相当于这样:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = ‘Apress‘;
你可以传递多个参数到 filter() 来缩小选取范围:
>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")
[<Publisher: Apress>]
多个参数会被转换成 AND SQL从句, 因此上面的代码可以转化成这样:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = ‘U.S.A.‘
AND state_province = ‘CA‘;
注意,SQL缺省的 = 操作符是精确匹配的, 其他类型的查找也可以使用:2
>>> Publisher.objects.filter(name__contains="press")
[<Publisher: Apress>]
在 name 和 contains 之间有双下划线。和Python一样,Django也使用双下划线来表明会进行一些魔术般的操作。这里,contains部分会被Django翻译成LIKE语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name LIKE BINARY ‘%press%‘;
其他的一些查找类型有:icontains(大小写无关的LIKE),startswith和endswith, 还有range(SQLBETWEEN查询)。
获取单个对象
上面的例子中filter()
函数返回一个记录集,这个记录集是一个列表。 相对列表来说,有些时候我们更需要获取单个的对象, get()
方法就是在此时使用的:
>>> Publisher.objects.get(name="Apress")
<Publisher: Apress>
这样,就返回了单个对象,而不是列表(更准确的说,QuerySet)。 所以,如果结果是多个对象,会导致抛出异常:
>>> Publisher.objects.get(country="U.S.A")
Traceback (most recent call last):
...
books.models.MultipleObjectsReturned: get() returned more than one Publisher -- it returned 2!
如果查询没有返回结果也会抛出异常:
>>> Publisher.objects.get(name="Apress")
Traceback (most recent call last):
...
books.models.DoesNotExist: Publisher matching query does not exist.
这个 DoesNotExist 异常 是 Publisher 这个 model 类的一个属性,即 Publisher.DoesNotExist。在你的应用中,你可以捕获并处理这个异常,像这样:
try:
p = Publisher.objects.get(name=‘Apress‘)
except Publisher.DoesNotExist:
print("Apress isn‘t in the database yet.")
else:
print("Apress is in the database.")
数据排序
在运行前面的例子中,你可能已经注意到返回的结果是无序的。 我们还没有告诉数据库 怎样对结果进行排序,所以我们返回的结果是无序的。
在你的 Django 应用中,你或许希望根据某字段的值对检索结果排序,比如说,按字母顺序。 那么,使用 order_by() 这个方法就可以搞定了。
>>> Publisher.objects.order_by("name")
[<Publisher: Apress>, <Publisher: O‘Reilly>]
跟以前的 all() 例子差不多,SQL语句里多了指定排序的部分:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name;
我们可以对任意字段进行排序:
>>> Publisher.objects.order_by("address")
[<Publisher: O‘Reilly>, <Publisher: Apress>]
>>> Publisher.objects.order_by("state_province")
[<Publisher: Apress>, <Publisher: O‘Reilly>]
如果需要以多个字段为标准进行排序(第二个字段会在第一个字段的值相同的情况下被使用到),使用多个参数就可以了,如下:
>>> Publisher.objects.order_by("state_province", "address")
[<Publisher: Apress>, <Publisher: O‘Reilly>]
我们还可以指定逆向排序,在前面加一个减号 - 前缀:
>>> Publisher.objects.order_by("-name")
[<Publisher: O‘Reilly>, <Publisher: Apress>]
尽管很灵活,但是每次都要用 order_by() 显得有点啰嗦。 大多数时间你通常只会对某些 字段进行排序。 在这种情况下,Django让你可以指定模型的缺省排序方式:
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __unicode__(self):
return self.name
class Meta:
ordering = [‘name‘]
现在,让我们来接触一个新的概念。 class Meta,内嵌于 Publisher 这个类的定义中(如果 class Publisher 是顶格的,那么 class Meta 在它之下要缩进4个空格--按 Python 的传统 )。你可以在任意一个 模型 类中使用 Meta 类,来设置一些与特定模型相关的选项。 现在,我们关注 ordering 这个选项就够了。 如果你设置了这个选项,那么除非你检索时特意额外地使用了 order_by(),否则,当你使用 Django 的数据库 API 去检索时,Publisher对象的相关返回值默认地都会按 name 字段排序。
连锁查询
我们已经知道如何对数据进行过滤和排序。 当然,通常我们需要同时进行过滤和排序查询的操作。 因此,你可以简单地写成这种“链式”的形式:
>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")
[<Publisher: O‘Reilly>, <Publisher: Apress>]
你应该没猜错,转换成SQL查询就是 WHERE 和 ORDER BY 的组合:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = ‘U.S.A‘
ORDER BY name DESC;
限制返回的数据
另一个常用的需求就是取出固定数目的记录。 想象一下你有成千上万的出版商在你的数据库里, 但是你只想显示第一个。 你可以使用标准的Python列表裁剪语句:
>>> Publisher.objects.order_by(‘name‘)[0]
<Publisher: Apress>
这相当于:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
LIMIT 1;
类似的,你可以用Python的range-slicing语法来取出数据的特定子集:
>>> Publisher.objects.order_by(‘name‘)[0:2]
这个例子返回两个对象,等同于以下的SQL语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
OFFSET 0 LIMIT 2;
注意,不支持Python的负索引(negative slicing):
>>> Publisher.objects.order_by(‘name‘)[-1]
Traceback (most recent call last):
...
AssertionError: Negative indexing is not supported.
虽然不支持负索引,但是我们可以使用其他的方法。 比如,稍微修改 order_by() 语句来实现:
>>> Publisher.objects.order_by(‘-name‘)[0]
更新多个对象
在“插入和更新数据”小节中,我们有提到模型的save()方法,这个方法会更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列。
例如说我们现在想要将Apress Publisher的名称由原来的”Apress”更改为”Apress Publishing”。若使用save()方法,如:
>>> p = Publisher.objects.get(name=‘Apress‘)
>>> p.name = ‘Apress Publishing‘
>>> p.save()
这等同于如下SQL语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = ‘Apress‘;
UPDATE books_publisher SET
name = ‘Apress Publishing‘,
address = ‘2855 Telegraph Ave.‘,
city = ‘Berkeley‘,
state_province = ‘CA‘,
country = ‘U.S.A.‘,
website = ‘http://www.apress.com‘
WHERE id = 3;
(注意在这里我们假设Apress的ID为3)
在这个例子里我们可以看到Django的save()方法更新了不仅仅是name列的值,还有更新了所有的列。 若name以外的列有可能会被其他的进程所改动的情况下,只更改name列显然是更加明智的。 更改某一指定的列,我们可以调用结果集(QuerySet)对象的update()方法: 示例如下:
>>> Publisher.objects.filter(id=3).update(name=‘Apress Publishing‘)
与之等同的SQL语句变得更高效,并且不会引起竞态条件。
UPDATE books_publisher
SET name = ‘Apress Publishing‘
WHERE id = 52;
update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录。 以下示例演示如何将所有Publisher的country字段值由’U.S.A’更改为’USA’:
>>> Publisher.objects.all().update(country=‘USA‘)
2
update()方法会返回一个整型数值,表示受影响的记录条数。 在上面的例子中,这个值是2。
删除对象
删除数据库中的对象只需调用该对象的delete()方法即可:
>>> p = Publisher.objects.get(name="O‘Reilly")
>>> p.delete()
>>> Publisher.objects.all()
[<Publisher: Apress Publishing>]a
同样我们可以在结果集上调用delete()方法同时删除多条记录。这一点与我们上一小节提到的update()方法相似:2
>>> Publisher.objects.filter(country=‘USA‘).delete()
>>> Publisher.objects.all().delete()
>>> Publisher.objects.all()
[]
删除数据时要谨慎! 为了预防误删除掉某一个表内的所有数据,Django要求在删除表内所有数据时显示使用all()。 比如,下面的操作将会出错:
>>> Publisher.objects.delete()
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: ‘Manager‘ object has no attribute ‘delete‘
而一旦使用all()方法,所有数据将会被删除:
>>> Publisher.objects.all().delete()
如果只需要删除部分的数据,就不需要调用all()方法。再看一下之前的例子:
>>> Publisher.objects.filter(country=‘USA‘).delete()
以上是关于Django之五模型的主要内容,如果未能解决你的问题,请参考以下文章
Django 学习笔记之五 Django中数据库中ManyToManyField及ForeignKey
Django运维后台的搭建之五:引入databases和django-crispy-forms