我们假定你现在已经安装上数据库管理系统,并且确认数据库连接正常工作了,让我们来创建一个 Django app-一个包含模型,视图和 Django 代码,并且形式为独立 Python 包的完整 Django 应用。
在这里要先解释一些术语,初学者可能会混淆它们。在第二章我们已经创建了 project , 那么 project 和 app 之间到底有什么不同呢?它们的区别就是一个是配置另一个是 代码:
一个 project 包含很多个 Django app 以及对它们的配置。技术上,project 的作用是提供配置文件,比方说哪里定义数据库连接信息、安装的 app 列表、TEMPLATE_DIRS
等等。一个 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/ migrations __init__.py __init__.py admin.py apps.py models.py tests.py views.py
这个目录包含了这个 app 的模型和视图。
我们查看一下 models.py
和 views.py
文件的内容。它们都是空的,除了 models.py
里有一个 import。这就是你
Django app 的基础。
我们早些时候谈到。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
模块等同于下面这张表(用 Mysql 的 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
会自动为每个模型生成一个自增长的整数主键字段,每个 Django
模型都要求有单独的主键 id
。
完成这些代码之后,现在让我们来在数据库中创建这些表。要完成该项工作,第一步是在 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', ]
然后,添加 ‘books’
到 INSTALLED_APPS
的末尾,此时设置的内容看起来应该是这样的:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'books', ]
你可能发现,我在 INSTALLED_APPS 最后一个元素 books 后面也添加一个逗号,本人喜欢这样做,这是为了避免添加新元素时忘了加逗号,而且也没什么坏处。
'books'
指示我们正在编写的books
app。INSTALLED_APPS
中的每个 app 都使用 Python 的路径描述,包的路径,用小数点“.”间隔。
再次编辑 settings.py 文件,找到 DATABASES 设置,DATABASES 的缺省设置如下(Django 默认连接的数据库管理系统为 sqlite3):
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
我们从控制台进入到 mysql 数据库管理系统下,使用 CREATE DATABASE dbbook;
创建一个名称为
dbbook 的数据库。
然后把 settings.py 的 DATABASES 设置改成如下:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 使用 mysql 数据库管理系统 'NAME': 'dbbook', # 刚刚创建的数据库名称 'USER': 'root', # 连接数据库的用户名 'PASSWORD': '123456', # root 用户对应的密码 'HOST': '', # 主机地址,不填写的话,默认为 127.0.0.1 'PORT': '', # 连接 mysql 的端口,不填写的话,默认为 3306 } }
我们建好了模型类并且按以上操作配置好了 settings.py 文件之后,还需要安装一个和 mysql 通信的客户端模块,这个模块叫
pymysql,我们可以使用命令:pip install pymysql
进行安装,安装成功后,还需要在项目根目录的
__init__.py
文件中加上如下代码:
try: import pymysql pymysql.install_as_MySQLdb() except: pass
注意:由于 pymysql 模块对 django 2.0 版本之后的支持有个 bug,你在执行下面我们马上就要用到的 check,makemigrations, sqlmigrate, migrate
等一系列命令操作中,则会报如下错误:
Traceback (most recent call last): File "manage.py", line 21, in <module> main() File "manage.py", line 17, in main execute_from_command_line(sys.argv) ...... query = query.decode(errors='replace') AttributeError: 'str' object has no attribute 'decode'
解决方案如下。
找的你安装的 Django 目录下的 operations.py 文件(比如我的该文件路径在:C:\Python3\Scripts\djangoenv\Lib\site-packages\django\db\backends\mysql\ 目录下),将编码 decode
改成解码
encode
:
# 将代码 if query is not None: query = query.decode(errors='replace') return query # 改为 if query is not None: query = query.encode(errors='replace') return query
现在我们可以创建数据库表了。首先,用下面的命令验证模型的有效性:
python manage.py check
check
命令检查你的模型的语法和逻辑是否正确。如果一切正常,你会看到 0 errors found
消息。如果出错,请检查你输入的模型代码。
错误输出会给出非常有用的错误信息来帮助你修正你的模型。
一旦你觉得你的模型可能有问题,运行 python manage.py check
。它可以帮助你捕获一些常见的模型定义错误。
模型确认没问题了,运行下面的命令来生成一个模型的 python 类转为 sql
语句的中间语句,这个语句很重要,该语句是把模型类转为 sql
语句或者把模型类迁移(生成)成数据库管理系统内的数据库表的前提,CREATE TABLE
语句:
python manage.py makemigrations books
执行完这个语句在,它会在 books 目录下生成一个 migrations 文件夹,里面有一个 0001_initial 文件,该文件的内容就是 python 类转为 sql 语句的中间语句。
确定模型没问题且成功生成了中间语句后,运行下面的命令来生成 CREATE TABLE 语句:
python manage.py sqlmigrate books 0001_initial
在这个命令行中, books
是 app 的名称。和你运行 manage.py startapp
中的一样。执行之后,输出如下:
BEGIN; 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 ) ; CREATE TABLE "books_author" ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(40) NOT NULL, "email" varchar(75) NOT NULL ) ; CREATE TABLE "books_book" ( "id" serial NOT NULL PRIMARY KEY, "title" varchar(100) NOT NULL, "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id") DEFERRABLE INITIALLY DEFERRED, "publication_date" date NOT NULL ) ; CREATE TABLE "books_book_authors" ( "id" serial NOT NULL PRIMARY KEY, "book_id" integer NOT NULL REFERENCES "books_book" ("id") DEFERRABLE INITIALLY DEFERRED, "author_id" integer NOT NULL REFERENCES "books_author" ("id") DEFERRABLE INITIALLY DEFERRED, UNIQUE ("book_id", "author_id") ) ; CREATE INDEX "books_book_publisher_id" ON "books_book" ("publisher_id"); COMMIT;
注意:
自动生成的表名是 app 名称(books
)和模型的小写名称(publisher
,book
,author
)的组合。
我们前面已经提到,Django为每个表格自动添加加了一个 id
主键,你可以重新设置它。
按约定,Django 添加 "_id"
后缀到外键字段名。你猜对了,这个同样是可以自定义的。
外键是用 REFERENCES
语句明确定义的。
这些 CREATE TABLE
语句会根据你的数据库而作调整,这样象数据库特定的一些字段例如:(MySQL),auto_increment
(mysql),serial
(SQLite),都会自动生成。integer
primary key
同样的,字段名称也是自动处理(例如单引号还好是双引号)。例子中的输出是基于 mysql 语法的。
sqlmigrate
命令并没有在数据库中真正创建数据表,只是把 SQL
语句段打印出来,这样你可以看到 Django 究竟会做些什么。如果你想这么做的话,你可以把那些
SQL 语句复制到你的数据库客户端执行。不过,Django 提供了一种更为简易的提交 SQL
语句至数据库的方法:migrate
命令(注意:在执行这个命令前,你要确保你已经执行过
python manage.py makemigrations
命令)。
python manage.py migrate
执行这个命令后,将看到类似以下的内容:
Creating table books_publisher Creating table books_author Creating table books_book Installing index for books.Book model
migrate
命令是同步你的模型到数据库的一个简单方法。它会根据
INSTALLEDAPPS
里设置的app来检查数据库,如果表不存在,它就会创建它。需要注意的是,migrate
并不能_将模型的修改或删除同步到数据库;如果你修改或删除了一个模型,并想把它提交到数据库,migrate
并不会做出任何处理。
如果你再次运行 python manage.py migrate
,什么也没发生,因为你没有添加新的模型或者添加新的
app。因此,运行 python manage.py migrate
总是安全的,因为它不会重复执行 SQL 语句。
如果你有兴趣,花点时间用你的 mysql 客户端登录进数据库服务器看看刚才 Django
创建的数据表。也可以执行 python manage.py dbshell
,这个命令将依据
DATABASE_SERVER
的里设置自动检测使用哪种命令行客户端。常言说,后来者居上。