在第2章中,我们介绍了使用Django构建动态网站的基本原理:设置视图和URLconf。 正如我所解释的,一个视图负责做一些任意的逻辑,然后返回一个响应。 在其中一个例子中,我们的任意逻辑就是计算当前的日期和时间。
在现代Web应用程序中,任意逻辑通常涉及与数据库交互。 在幕后,数据库驱动的网站连接到数据库服务器,从中检索一些数据,并在网页上显示这些数据。 该网站还可能为网站访问者提供自行填充数据库的方法。
许多复杂的网站提供了两者的一些组合。 例如Amazon.com就是数据库驱动的网站的一个很好的例子。 每个产品页面基本上都是以亚马逊的产品数据库格式化为HTML的查询,当您发布客户评论时,它会被插入到评论数据库中。
Django非常适合制作数据库驱动的网站,因为它配备了使用Python执行数据库查询的简单而强大的工具。 本章解释了这个功能:Django的数据库层。
尽管为了使用Django的数据库层而不需要知道基本的关系数据库理论和SQL,但强烈推荐。 这些概念的介绍超出了本书的范围,但即使你是数据库新手,也要继续阅读。 你可能能够跟随和掌握基于上下文的概念。
在视图中执行数据库查询的“笨拙”方式
正如第2章详细描述在视图中产生输出的“笨拙”方式(通过在视图内直接对文本进行硬编码),在视图中从数据库检索数据有一种“笨拙”的方式。 很简单:只要使用任何现有的Python库来执行SQL查询并对结果进行一些操作即可。 在这个示例视图中,我们使用MySQLdb库连接到MySQL数据库,检索一些记录,并将它们提供给一个模板以显示为网页:
from django.shortcuts import render
import MySQLdb
def book_list(request):
db = MySQLdb.connect(user='me', db='mydb', passwd='secret', host='localhost')
cursor = db.cursor()
cursor.execute('SELECT name FROM books ORDER BY name')
names = [row[0] for row in cursor.fetchall()]
db.close()
return render(request, 'book_list.html', {'names': names})
这种方法是有效的,但是一些问题应该立即跳出来:
-
我们正在对数据库连接参数进行硬编码。 理想情况下,这些参数将存储在Django配置中。
-
我们不得不写一些样板代码:创建一个连接,创建一个游标,执行一个语句,关闭连接。 理想情况下,我们所要做的就是指定我们想要的结果。
-
它把我们和MySQL联系起来。 如果我们从MySQL切换到PostgreSQL,我们很可能不得不重写大量的代码。 理想情况下,我们正在使用的数据库服务器将被抽象化,以便数据库服务器更改可以在一个地方进行。 (如果您正在构建一个开源的Django应用程序,那么您希望尽可能多的人使用该功能,此功能尤其重要。)
正如您所料,Django的数据库层解决了这些问题。
配置数据库
考虑到所有这些理念,让我们开始探索Django的数据库层。 首先,我们来探讨创建应用程序时添加到settings.py的初始配置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
默认设置非常简单。 下面是每个设置的简要说明:
- ENGINE告诉Django要使用哪个数据库引擎。 正如我们在本书的例子中使用SQLite,我们将把它保留为默认的django.db.backends.sqlite3。
- NAME会告诉Django您的数据库的名称。 例如:'NAME':'mydb',
由于我们使用SQLite,startproject为我们创建了一个完整的文件系统路径到数据库文件。
这就是默认设置 - 你不需要改变任何东西来运行本书中的代码,我只是简单地介绍一下Django中配置数据库的简单方法。 有关如何设置Django支持的各种数据库的详细说明,请参见第21章。
你的第一个应用
现在您已经验证了连接正在工作,现在是时候创建一个Django应用程序 - 一个包含模型和视图的Django代码包,它们在一个Python包中并且代表一个完整的Django应用程序。 这里有必要解释一下这个术语,因为这往往会让初学者感到困惑。 我们已经在第一章创建了一个项目,那么项目和应用程序有什么区别呢? 区别在于配置与代码的区别:
- 项目是一组Django应用程序的实例,以及这些应用程序的配置。 从技术上讲,一个项目的唯一要求是它提供了一个设置文件,它定义了数据库连接信息,安装的应用程序列表,DIRS等等。
- 一个应用程序是一个可移植的Django功能集,通常包括模型和视图,它们一起生活在一个Python包中。 例如,Django带有一些应用程序,比如自动管理界面。 关于这些应用程序的一个关键要点是,它们是可移植的,可以跨多个项目重用。
关于如何将Django代码安装到这个方案中,有很少的硬性规定。 如果你正在建立一个简单的网站,你可能只使用一个应用程序。 如果你正在建立一个复杂的网站,其中有几个不相关的部分,比如电子商务系统和留言板,你可能想把它们分成不同的应用程序,以便将来可以单独使用它们。
实际上,根本不需要创建应用程序,正如本书迄今为止创建的示例视图函数所证明的那样。 在这些情况下,我们只是简单地创建一个名为views.py的文件,填充视图函数,并在这些函数中指向我们的URLconf。 没有需要的应用程序。
然而,关于应用约定有一个要求:如果你使用Django的数据库层(模型),你必须创建一个Django应用程序。 模型必须存在于应用程序中 因此,为了开始编写我们的模型,我们需要创建一个新的应用程序。
在mysite项目目录(这是manage.py文件所在的目录,而不是mysite app目录)中,输入以下命令来创建书籍应用程序:
python manage.py startapp books
该命令不会产生任何输出,但它确实在mysite目录中创建了一个books目录。 我们来看看该目录的内容(显示项目文件夹树,以便您可以看到应用程序的位置):
\mysite_project
\books
\migrations
__init__.py
admin.py
apps.py
models.py
tests.py
views.py
\mysite
\templates
manage.py
这些文件将包含这个应用程序的模型和视图。 在你最喜欢的文本编辑器中查看models.py和views.py。 这两个文件都是空的,除了在models.py中的注释和导入。 这是你Django应用程序的空白页面。
在Python中定义Django模型
正如我们前面在第一章中讨论过的,“MTV”中的“M”代表“Model”。Django模型是数据库中数据的描述,用Python代码表示。 这是你的数据布局 - 相当于你的SQL CREATE TABLE语句 - 除了它是用Python而不是SQL,它不仅包括数据库列定义。
Django使用模型在幕后执行SQL代码,并返回表示数据库表中的行的方便的Python数据结构。 Django也使用模型来表示SQL不一定能够处理的更高层次的概念。
如果您熟悉数据库,您的直接想法可能是“在Python中而不是在SQL中定义数据模型是多余的吗?”Django这样的工作方式有以下几个原因:
-
自我检查需要开销,是不完善的。为了提供方便的数据访问API,Django需要以某种方式知道数据库的布局,并且有两种方法来完成这个任务。第一种方法是在Python中明确地描述数据,第二种方法是在运行时检查数据库以确定数据模型。由于自我检查增加了不可接受的开销水平,并且可能不准确,Django的开发人员决定第一个选项是最好的。
-
编写Python很有趣,而将所有内容都保存在Python中会限制你的大脑进行上下文切换的次数。如果您尽可能长时间保持在单一的编程环境/心态中,它将有助于提高工作效率。必须先编写SQL,然后再编写Python,然后再编写SQL,这是破坏性的。
-
将数据模型存储为代码而不是存储在数据库中,可以更容易地将模型保持在版本控制之下。这样,您可以轻松地跟踪数据布局的变化。
-
SQL仅允许关于数据布局的特定级别的元数据。例如,大多数数据库系统不提供用于表示电子邮件地址或URL的专用数据类型。 Django模型呢。更高级的数据类型的优点是更高的生产力和更多的可重用的代码。
-
SQL在数据库平台上不一致。例如,如果您正在发布一个Web应用程序,那么分发一个描述您的数据布局的Python模块比用于MySQL,PostgreSQL和SQLite的单独的CREATE TABLE语句组更为实用。
但是,这种方法的缺点是Python代码可能与数据库中的实际内容不同步。 如果对Django模型进行更改,则需要在数据库中进行相同的更改,以使数据库与模型保持一致。 在本章后面讨论迁移时,我会告诉你如何处理这个问题。
最后,你应该注意到,Django包含一个实用工具,它可以通过自我检查现有的数据库来生成模型。 这对于快速启动和运行遗留数据非常有用。 我将在第21章中介绍。
你的第一个模型
作为本章和下一章的一个例子,我将重点介绍基本的book/author/publisher的数据布局。 我用这个作为例子,因为书,作者和出版商之间的概念关系是众所周知的,而且这是介绍性SQL教科书中常用的数据布局。 您还正在阅读由作者撰写并由出版商出版的书籍!
我会假设下面的概念,领域和关系:
-
作者有一个名字,一个姓氏和一个电子邮件地址。
-
发布者有姓名,街道地址,城市,州/省,国家和网站。
-
一本书有一个标题和一个出版日期。 它还有一个或多个作者(与作者的多对多关系)和一个发布者(一对多的关系 - 也就是外键)。
在Django中使用这个数据库布局的第一步是将其表示为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)
publication_date = models.DateField()
让我们快速检查这个代码来涵盖基础知识。 首先要注意的是,每个模型都是由django.db.models.Model的一个子类Python类表示的。 父类Model包含使这些对象能够与数据库进行交互所需的所有机制,并使我们的模型完全负责定义它们的字段,并且语法简洁。
不管你信不信,这就是我们需要编写的所有代码都可以使用Django进行基本的数据访问。 每个模型通常对应于单个数据库表,并且模型上的每个属性通常对应于该数据库表中的列。 属性名称对应于列的名称,并且字段的类型(例如,CharField)对应于数据库列类型(例如,varchar)。 例如,发布者模型等同于下面的表(假定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的ManyToManyField。 这表明一本书有一个或多个作者,但Book数据库表没有得到一个作者列。 相反,Django创建了一个额外的表格 - 一个多对多的连接表格 - 处理书籍到作者的映射。
有关字段类型和模型语法选项的完整列表,请参阅附录B.最后,请注意,我们没有在这些模型中明确定义主键。 除非你另有指示,否则Django会自动给每个模型一个自动递增的整数主键字段id。 每个Django模型都需要有一个单列主键。
安装模型
我们编写了代码; 现在让我们在我们的数据库中创建表格。 为了做到这一点,第一步是在我们的Django项目中激活这些模型。 我们通过在设置文件中将书籍应用程序添加到已安装的应用程序列表来实现这一点。 再次编辑settings.py文件,并查找INSTALLED_APPS设置。 INSTALLED_APPS告诉Django为一个给定的项目激活了哪些应用程序。 默认情况下,它看起来像这样:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
要注册我们的“书籍”应用程序,添加“books.apps.BooksConfig”INSTALLED_APPS,所以设置结果如下所示:
INSTALLED_APPS = [
'books.apps.BooksConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
INSTALLED_APPS中的每个应用程序都由其完整的Python路径来表示,也就是用点分隔的包的路径,这些路径通向应用程序包。 在这种情况下,虚线路径指向Django在apps.py文件中为您创建的BooksConfig类。 那么将在本书后面更详细地看看应用程序配置。
现在Django应用程序已经在设置文件中被激活了,我们可以在我们的数据库中创建数据库表。 首先,让我们通过运行这个命令来验证模型:
python manage.py check
check命令运行Django系统检查框架 - 一组用于验证Django项目的静态检查。 如果一切正常,你会看到消息系统检查确定没有问题(0沉默)。 如果不这样做,请确保正确输入了型号代码。 错误输出应该会提供有关代码错误的有用信息。 任何时候你认为你的模型有问题,运行python manage.py check。 它倾向于捕捉所有常见的模型问题。
如果你的模型是有效的,运行下面的命令告诉Django你已经对你的模型进行了一些改变(在这种情况下,你创建了一个新模型):
python manage.py makemigrations books
您应该看到类似于以下内容的内容:
Migrations for 'books':
0001_initial.py:
- Create model Author
- Create model Book
- Create model Publisher
- Add field publisher to book
迁移是Django如何将更改存储到模型(以及数据库模式) - 它们只是磁盘上的文件。 在这种情况下,您将在books应用程序的\ migrations文件夹中找到文件名0001_initial.py。 migrate命令会自动获取最新的迁移文件并更新您的数据库模式,但首先,我们来看看迁移过程中将运行的SQL。 sqlmigrate命令采用迁移名称并返回它们的SQL:
python manage.py sqlmigrate books 0001
您应该看到类似于以下的内容(为便于阅读,请重新格式化):
BEGIN;
CREATE TABLE "books_author" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(40) NOT NULL,
"email" varchar(254) NOT NULL
);
CREATE TABLE "books_book" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" varchar(100) NOT NULL,
"publication_date" date NOT NULL
);
CREATE TABLE "books_book_authors" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"book_id" integer NOT NULL REFERENCES "books_book" ("id"),
"author_id" integer NOT NULL REFERENCES "books_author" ("id"),
UNIQUE ("book_id", "author_id")
);
CREATE TABLE "books_publisher" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"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_book__new" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" varchar(100) NOT NULL,
"publication_date" date NOT NULL,
"publisher_id" integer NOT NULL REFERENCES
"books_publisher" ("id")
);
INSERT INTO "books_book__new" ("id", "publisher_id", "title",
"publication_date") SELECT "id", NULL, "title", "publication_date" FROM
"books_book";
DROP TABLE "books_book";
ALTER TABLE "books_book__new" RENAME TO "books_book";
CREATE INDEX "books_book_2604cbea" ON "books_book" ("publisher_id");
COMMIT;
请注意以下几点:
-
表名称是通过将应用程序的名称(书籍)和模型的小写名称(发布者,书籍和作者)组合而自动生成的。 您可以覆盖此行为,详见附录B.
-
正如我们前面提到的,Django自动为每个表添加一个主键 - id字段。 你可以重写这个。 按照惯例,Django将“_id”附加到外键字段名称。 正如你可能已经猜到的,你也可以重写这个行为。
-
外键关系通过REFERENCES语句显式化。
这些CREATE TABLE语句是针对您正在使用的数据库而定制的,因此数据库特定的字段类型(例如auto_increment(MySQL),serial(PostgreSQL)或整型主键(SQLite))会自动处理。 列名引用也是如此(例如,使用双引号或单引号)。 此示例输出是PostgreSQL语法。
sqlmigrate命令实际上并不创建表或以其他方式触摸数据库 - 它只是将输出打印到屏幕上,以便您可以看到SQL Django在执行时会执行什么操作。 如果你想,你可以将这个SQL复制并粘贴到你的数据库客户端,但是Django提供了一个更简单的方式将SQL提交给数据库:migrate命令:
python manage.py migrate
运行该命令,你会看到如下所示:
Operations to perform:
Apply all migrations: admin, auth, books, contenttypes, sessions
Running migrations:
Applying books.0001_initial... OK
如果你得到一堆额外的迁移,这很可能是因为你忘了在第1章中运行初始迁移。第一次运行迁移时,Django也会创建Django需要的所有系统表。
迁移是Django将您对模型进行的更改(添加字段,删除模型等)传播到数据库模式的方式。 他们的设计主要是自动的,但有一些注意事项。 有关迁移的更多信息,请参阅第21章。
网友评论