让我们通过示例来学习。

通过这个教程,我们将带着你创建一个基本的投票应用程序。

它将由两部分组成:

  • 一个让人们查看和投票的公共站点。
  • 一个让你能添加、修改和删除投票的管理站点。

我们假定你已经阅读了 安装 Django。你能知道 Django 已被安装,且安装的是哪个版本,通过在命令提示行输入命令(由 $ 前缀)。

1. 提前准备

我应该使用哪个版本的 Python 来配合 Django?

Django 版本 Python 版本
2.2 3.5,3.6,3.7,3.8(2.2.8 添加),3.9(2.2.17 添加)
3.0 3.6,3.7,3.8,3.9 (3.0.11 添加)
3.1 3.6,3.7,3.8,3.9(3.1.3 添加)
3.2 3.6, 3.7, 3.8, 3.9, 3.10 (在 3.2.9 中就已经加入了)

对于每个版本的 Python,官方仅支持最新的三级版本(A.B.C)。你可以在 Python下载页面 找到每个系列的微版本。

一般来说,我们会支持一个 Python 版本直到其第一个发布的 Django LTS 安全支持停止。而 Django LTS 版本安全支持停止是跟随这其对应版本的 Python 安全支持结束的。例如,Python 3.3 安全支持在 2017 年 9 月结束,然后 Django 1.8 LTS 安全支持在 2018 年 4 月结束。因此 Django 1.8 是支持 Python 3.3 的最后一版。

安装命令:

  1. python -m venv djangoenv
  2. pip install Django==3.2.9
  1. # py -m django --version
  2. 3.2.9

如果这行命令输出了一个版本号,证明你已经安装了此版本的 Django;如果你得到的是一个“No module named django”的错误提示,则表明你还未安装。

这个教程是为了 Django 3.2 写的,它支持 Python 3.6 和后续版本。如果 Django 的版本不匹配,你可以自行换版本。

2. 创建项目

如果这是你第一次使用 Django 的话,你需要一些初始化设置。也就是说,你需要用一些自动生成的代码配置一个 Django project —— 即一个 Django 项目实例需要的设置项集合,包括数据库配置、Django 配置和应用程序配置。

打开命令行,cd 到一个你想放置你代码的目录,然后运行以下命令:

  1. django-admin startproject mysite

那我使用如下这个命令:

  1. # django-admin startproject Bornforthi

这行代码将会在当前目录下创建一个 Bornforthi 目录。如果命令失败了,查看 运行 django-admin 时遇到的问题,可能能给你提供帮助。

你得避免使用 Python 或 Django 的内部保留字来命名你的项目。具体地说,你得避免使用像 django (会和 Django 自己产生冲突)或 test (会和 Python 的内置组件产生冲突)这样的名字。

我的代码该放在哪?

如果你曾经是原生 PHP 程序员(没有使用过现代框架),你可能会习惯于把代码放在 Web 服务器的文档根目录(诸如 /var/www)。当使用 Django 时不需要这样做。把所有 Python 代码放在 Web 服务器的根目录不是个好主意,因为这样会有风险。比如会提高人们在网站上看到你的代码的可能性。这不利于网站的安全。 把你的代码放在文档根目录 以外 的某些地方吧,比如 /home/mycode。

让我们看看 startproject 创建了些什么:

  1. Bornforthi/
  2. manage.py
  3. Bornforthi/
  4. __init__.py
  5. settings.py
  6. urls.py
  7. asgi.py
  8. wsgi.py

这些目录和文件的用处是:

  • 最外层的 Bornforthi/ 根目录只是你项目的容器, 根目录名称对 Django 没有影响,你可以将它重命名为任何你喜欢的名称。
  • manage.py: 一个让你用各种方式管理 Django 项目的命令行工具。你可以阅读 django-admin 和 manage.py 获取所有 manage.py 的细节。
  • 里面一层的 Bornforthi/ 目录包含你的项目,它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。 (比如 Bornforthi.urls).
  • Bornforthi/init.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。如果你是 Python 初学者,阅读官方文档中的 更多关于包的知识
  • Bornforthi/settings.py:Django 项目的配置文件。如果你想知道这个文件是如何工作的,请查看 Django 配置 了解细节。
  • Bornforthi/urls.py:Django 项目的 URL 声明,就像你网站的“目录”。阅读 URL调度器 文档来获取更多关于 URL 的内容。
  • Bornforthi/asgi.py:作为你的项目的运行在 ASGI 兼容的 Web 服务器上的入口。阅读 如何使用 ASGI 来部署 了解更多细节。
  • Bornforthi/wsgi.py:作为你的项目的运行在 WSGI 兼容的 Web 服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。

3. 用于开发的简易服务器

让我们来确认一下你的 Django 项目是否真的创建成功了。如果你的当前目录不是外层的 Bornforthi 目录的话,请切换到此目录,然后运行下面的命令:

  1. python manage.py runserver

你应该会看到如下输出:

  1. # python manage.py runserver
  2. Watching for file changes with StatReloader
  3. Performing system checks...
  4. System check identified no issues (0 silenced).
  5. You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
  6. Run 'python manage.py migrate' to apply them.
  7. December 06, 2021 - 13:55:35
  8. Django version 3.2.9, using settings 'Bornforthi.settings'
  9. Starting development server at http://127.0.0.1:8000/
  10. Quit the server with CTRL-BREAK.

忽略有关未应用最新数据库迁移的警告,稍后我们处理数据库。

你刚刚启动的是 Django 自带的用于开发的简易服务器,它是一个用纯 Python 写的轻量级的 Web 服务器。

我们将这个服务器内置在 Django 中是为了让你能快速的开发出想要的东西,因为你不需要进行配置生产级别的服务器(比如 Apache)方面的工作,除非你已经准备好投入生产环境了。

现在是个提醒你的好时机:千万不要 将这个服务器用于和生产环境相关的任何地方。这个服务器只是为了开发而设计的。(我们在 Web 框架方面是专家,在 Web 服务器方面并不是。)

现在,服务器正在运行,浏览器访问 http://127.0.0.1:8000/。你将会看到一个“祝贺”页面,随着一只火箭发射,服务器已经运行了。
image.png
更换端口
默认情况下,runserver 命令会将服务器设置为监听本机内部 IP 的 8000 端口。
如果你想更换服务器的监听端口,请使用命令行参数。举个例子,下面的命令会使服务器监听 8080 端口:

  1. python manage.py runserver 8080

如果你想要修改服务器监听的 IP,在端口之前输入新的。比如,为了监听所有服务器的公开IP(这你运行 Vagrant 或想要向网络上的其它电脑展示你的成果时很有用),使用:

  1. python manage.py runserver 0:8000

0 是 0.0.0.0 的简写。完整的关于开发服务器的文档可以在 :djamdin:runserver 参考文档中找到。

会自动重新加载的服务器 runserver
用于开发的服务器在需要的情况下会对每一次的访问请求重新载入一遍 Python 代码。所以你不需要为了让修改的代码生效而频繁的重新启动服务器。然而,一些动作,比如添加新文件,将不会触发自动重新加载,这时你得自己手动重启服务器。

4. 创建投票应用

现在你的开发环境——这个“项目” ——已经配置好了,你可以开始干活了。
在 Django 中,每一个应用都是一个 Python 包,并且遵循着相同的约定。Django 自带一个工具,可以帮你生成应用的基础目录结构,这样你就能专心写代码,而不是创建目录了。

项目 VS 应用
项目和应用有什么区别?应用是一个专门做某件事的网络应用程序——比如博客系统,或者公共记录的数据库,或者小型的投票程序。项目则是一个网站使用的配置和应用的集合。项目可以包含很多个应用。应用可以被很多个项目使用。

你的应用可以存放在任何 Python 路径 中定义的路径。在这个教程中,我们将在你的 manage.py 同级目录下创建投票应用。这样它就可以作为顶级模块导入,而不是 Bornforthi 的子模块。

请确定你现在处于 manage.py 所在的目录下,然后运行这行命令来创建一个应用:

  1. python manage.py startapp polls

这将会创建一个 polls 目录,它的目录结构大致如下:

  1. polls/
  2. __init__.py
  3. admin.py
  4. apps.py
  5. migrations/
  6. __init__.py
  7. models.py
  8. tests.py
  9. views.py

这个目录结构包括了投票应用的全部内容。

5. 编写第一个视图

让我们开始编写第一个视图吧。打开 polls/views.py,把下面这些 Python 代码输入进去:

  1. from django.shortcuts import render
  2. from django.http import HttpResponse
  3. # Create your views here.
  4. def index(request):
  5. return HttpResponse("Hello, world. You're at the polls index.")

这是 Django 中最简单的视图。如果想看见效果,我们需要将一个 URL 映射到它——这就是我们需要 URLconf 的原因了。

为了创建 URLconf,请在 polls 目录里新建一个 urls.py 文件。你的应用目录现在看起来应该是这样:

  1. polls/
  2. __init__.py
  3. admin.py
  4. apps.py
  5. migrations/
  6. __init__.py
  7. models.py
  8. tests.py
  9. urls.py
  10. views.py

polls/urls.py 中,输入如下代码:

  1. from django.urls import path
  2. from . import views
  3. urlpatterns = [
  4. path('', views.index, name='index'),
  5. ]

下一步是要在根 URLconf 文件中指定我们创建的 polls.urls 模块。在 Bornforthi/urls.py 文件的 urlpatterns 列表里插入一个 include(), 如下:

  1. from django.contrib import admin
  2. from django.urls import path, include
  3. urlpatterns = [
  4. path('admin/', admin.site.urls),
  5. path("polls/", include("polls.urls")),
  6. ]

函数 include() 允许引用其它 URLconfs。每当 Django 遇到 include() 时,它会截断与此项匹配的 URL 的部分,并将剩余的字符串发送到 URLconf 以供进一步处理。

我们设计 include() 的理念是使其可以即插即用。因为投票应用有它自己的 URLconf( polls/urls.py ),他们能够被放在 “/polls/“ , “/fun_polls/“ ,”/content/polls/“,或者其他任何路径下,这个应用都能够正常工作。

何时使用 include()
当包括其它 URL 模式时你应该总是使用 include()admin.site.urls 是唯一例外。

你现在把 index 视图添加进了 URLconf。通过以下命令验证是否正常工作:

  1. python manage.py runserver

用你的浏览器访问 http://localhost:8000/polls/,你应该能够看见 “Hello, world. You’re at the polls index.“ ,这是你在 index 视图中定义的。

没有找到页面?
如果你在这里得到了一个错误页面,检查一下你是不是正访问着http://localhost:8000/polls/ 而不应该是 http://localhost:8000/。

函数 path() 具有四个参数,两个必须参数:routeview,两个可选参数:kwargsname。现在,是时候来研究这些参数的含义了。

6. path()

6.1 参数: route

route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。

这些准则不会匹配 GET 和 POST 参数或域名。例如,URLconf 在处理请求 https://www.example.com/myapp/ 时,它会尝试匹配 myapp/ 。处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/

6.2 参数: view

当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。稍后,我们会给出一个例子。

6.3 参数: kwargs

任意个关键字参数可以作为一个字典传递给目标视图函数。本教程中不会使用这一特性。

6.4 参数: name

为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。
当你了解了基本的请求和响应流程后,请阅读 教程的第 2 部分 开始使用数据库。

接下来,我们将建立数据库,创建您的第一个模型,并主要关注 Django 提供的自动生成的管理页面。

7. 数据库配置

现在,打开 Bornforthi/settings.py 。这是个包含了 Django 项目设置的 Python 模块。

通常,这个配置文件使用 SQLite 作为默认数据库。如果你不熟悉数据库,或者只是想尝试下 Django,这是最简单的选择。Python 内置 SQLite,所以你无需安装额外东西来使用它。当你开始一个真正的项目时,你可能更倾向使用一个更具扩展性的数据库,例如 PostgreSQL,避免中途切换数据库这个令人头疼的问题。

如果你想使用其他数据库,你需要安装合适的 database bindings ,然后改变设置文件中 DATABASES ‘default’ 项目中的一些键值:

  • ENGINE — 可选值有 ‘django.db.backends.sqlite3’‘django.db.backends.postgresql’‘django.db.backends.mysql’,或 ‘django.db.backends.oracle’。其它 可用后端
  • NAME — 数据库的名称。如果你使用 SQLite,数据库将是你电脑上的一个文件,在这种情况下,NAME 应该是此文件完整的绝对路径,包括文件名。默认值 BASE_DIR / ‘db.sqlite3’ 将把数据库文件储存在项目的根目录。

如果你不使用 SQLite,则必须添加一些额外设置,比如 USERPASSWORDHOST 等等。想了解更多数据库设置方面的内容,请看文档:DATABASES

SQLite 以外的其它数据库
如果你使用了 SQLite 以外的数据库,请确认在使用前已经创建了数据库。你可以通过在你的数据库交互式命令行中使用 “CREATE DATABASE database_name;“ 命令来完成这件事。
另外,还要确保该数据库用户中提供 Bornforthi/settings.py 具有 “create database” 权限。这使得自动创建的 test database 能被以后的教程使用。
如果你使用 SQLite,那么你不需要在使用前做任何事——数据库会在需要的时候自动创建。

这里我使用的是 MySQL 我就稍微切换记录一下 MySQL 的操作:

  1. # mysql -u root -p
  2. Enter password: ******
  3. ......
  4. mysql> show databases;
  5. ERROR 2013 (HY000): Lost connection to MySQL server during query
  6. No connection. Trying to reconnect...
  7. Connection id: 18
  8. Current database: *** NONE ***
  9. +--------------------+
  10. | Database |
  11. +--------------------+
  12. | information_schema |
  13. | mysql |
  14. | performance_schema |
  15. | scratch |
  16. | sys |
  17. +--------------------+
  18. 5 rows in set (0.12 sec)
  19. mysql> create database bornforthi;
  20. Query OK, 1 row affected (0.02 sec)
  21. mysql> show databases;
  22. +--------------------+
  23. | Database |
  24. +--------------------+
  25. | bornforthi |
  26. | information_schema |
  27. | mysql |
  28. | performance_schema |
  29. | scratch |
  30. | sys |
  31. +--------------------+
  32. 6 rows in set (0.00 sec)

我的数据库配置信息:

  1. DATABASES = {
  2. 'default': {
  3. 'ENGINE': 'django.db.backends.mysql',
  4. 'NAME': 'bornforthi',
  5. 'USER': 'root',
  6. 'PASSWORD': '123456',
  7. 'HOST': '127.0.0.1',
  8. 'PORT': '3306'
  9. }
  10. }

然后要在虚拟环境 pip 安装驱动:

  1. pip install mysqlclient

编辑 Bornforthi/settings.py 文件前,先设置 TIME_ZONE 为你自己时区。

此外,关注一下文件头部的 INSTALLED_APPS 设置项。这里包括了会在你项目中启用的所有 Django 应用。应用能在多个项目中使用,你也可以打包并且发布应用,让别人使用它们。
通常, INSTALLED_APPS 默认包括了以下 Django 的自带应用:

这些应用被默认启用是为了给常规项目提供方便。

默认开启的某些应用需要至少一个数据表,所以,在使用他们之前需要在数据库中创建一些表。请执行以下命令:

  1. python manage.py migrate

这个 migrate 命令检查 INSTALLED_APPS 设置,为其中的每个应用创建需要的数据表,至于具体会创建什么,这取决于你的 Bornforthi/settings.py 设置文件和每个应用的数据库迁移文件(我们稍后会介绍这个)。

这个命令所执行的每个迁移操作都会在终端中显示出来。如果你感兴趣的话,运行你数据库的命令行工具,并输入 \dt (PostgreSQL)、SHOW TABLES; (MariaDB,MySQL)、.schema (SQLite)或者 SELECT TABLE_NAME FROM USER_TABLES; (Oracle)来看看 Django 到底创建了哪些表。

写给极简主义者
就像之前说的,为了方便大多数项目,我们默认激活了一些应用,但并不是每个人都需要它们。如果你不需要某个或某些应用,你可以在运行 migrate 前毫无顾虑地从 INSTALLED_APPS 里注释或者删除掉它们。 migrate 命令只会为在 INSTALLED_APPS 里声明了的应用进行数据库迁移。

8. 创建模型

在 Django 里写一个数据库驱动的 Web 应用的第一步是定义模型 - 也就是数据库结构设计和附加的其它元数据。

设计哲学
一个模型就是单个定义你的数据的信息源。模型中包含了不可缺少的数据区域和你存储数据的行为。Django 遵循 DRY 原则。目的就是定义你的数据模型要在一位置上,而且自动从该位置推导一些事情。
来介绍一下迁移 - 举个例子,不像 Ruby On Rails,Django 的迁移代码是由你的模型文件自动生成的,它本质上是个历史记录,Django 可以用它来进行数据库的滚动更新,通过这种方式使其能够和当前的模型匹配。

在这个投票应用中,需要创建两个模型:问题 Question 和选项 Choice

Question 模型包括问题描述和发布时间。Choice 模型有两个字段,选项描述和当前得票数。每个选项属于一个问题。

这些概念可以通过一个 Python 类来描述。按照下面的例子来编辑 polls/models.py 文件:

  1. from django.db import models
  2. # Create your models here.
  3. class Question(models.Model):
  4. question_text = models.CharField(max_length=200)
  5. pub_date = models.DateTimeField('date published')
  6. class Choice(models.Model):
  7. question = models.ForeignKey(Question, on_delete=models.CASCADE) # 要关联这个选择项属于哪个题目
  8. choice_text = models.CharField(max_length=200)
  9. votes = models.IntegerField(default=0)

models.CASCADE 这是删除所引用对象时要采用的行为。它不是特定于 Django; 这是一个 SQL 标准。尽管 Django 在 SQL 之上有自己的实现。

当此类事件发生时,有七种可能采取的行动:

  • CASCADE:当引用的对象被删除时,也删除引用它的对象(例如,当您删除博客文章时,您可能还想删除评论)。SQL 等效项:CASCADE。
  • PROTECT:禁止删除引用的对象。要删除它,您必须手动删除所有引用它的对象。SQL 等效项:RESTRICT。
  • RESTRICT:(在 Django 3.1 中引入)类似的行为,因为 PROTECT 它 RESTRICT 更准确地匹配 SQL 。(参见django 文档示例
  • SET_NULL:将引用设置为 NULL(要求字段可为空)。例如,当您删除一个用户时,您可能希望保留他在博客文章上发表的评论,但假设它是由匿名(或已删除)用户发表的。SQL 等效项:SET NULL。
  • SET_DEFAULT:设置默认值。SQL 等效项:SET DEFAULT.
  • SET(…):设置一个给定的值。这不是 SQL 标准的一部分,完全由 Django 处理。
  • DO_NOTHING:可能是一个非常糟糕的主意,因为这会在您的数据库中造成完整性问题(引用一个实际上不存在的对象)。SQL 等效项:NO ACTION

在大多数情况下,CASCADE 是预期的行为,但对于每个外键,您应该始终问自己在这种情况下的预期行为是什么。PROTECT 并且 SET_NULL 经常有用。CASCADE 通过简单地删除单个用户,设置不应该的位置可能会以级联方式删除所有数据库。

每个模型被表示为 django.db.models.Model 类的子类。每个模型有许多类变量,它们都表示模型里的一个数据库字段。

每个字段都是 Field 类的实例 - 比如,字符字段被表示为 CharField ,日期时间字段被表示为 DateTimeField 。这将告诉 Django 每个字段要处理的数据类型。

每个 Field 类实例变量的名字(例如 question_textpub_date )也是字段名,所以最好使用对机器友好的格式。你将会在 Python 代码里使用它们,而数据库会将它们作为列名。

你可以使用可选的选项来为 Field 定义一个人类可读的名字。这个功能在很多 Django 内部组成部分中都被使用了,而且作为文档的一部分。如果某个字段没有提供此名称,Django 将会使用对机器友好的名称,也就是变量名。

在上面的例子中,我们只为 Question.pub_date 定义了对人类友好的名字。对于模型内的其它字段,它们的机器友好名也会被作为人类友好名使用。

定义某些 Field 类实例需要参数。例如 CharField 需要一个 max_length 参数。这个参数的用处不止于用来定义数据库结构,也用于验证数据,我们稍后将会看到这方面的内容。

Field 也能够接收多个可选参数;在上面的例子中:我们将 votesdefault 也就是默认值,设为 0。
注意在最后,我们使用 ForeignKey 定义了一个关系。这将告诉 Django,每个 Choice 对象都关联到一个 Question 对象。Django 支持所有常用的数据库关系:多对一、多对多和一对一。

9. 激活模型

上面的一小段用于创建模型的代码给了 Django 很多信息,通过这些信息,Django 可以:

  • 为这个应用创建数据库 schema(生成 CREATE TABLE 语句)。
  • 创建可以与 QuestionChoice 对象进行交互的 Python 数据库 API。

但是首先得把 polls 应用安装到我们的项目里。

设计哲学 Django 应用是“可插拔”的。你可以在多个项目中使用同一个应用。除此之外,你还可以发布自己的应用,因为它们并不会被绑定到当前安装的 Django 上。

为了在我们的工程中包含这个应用,我们需要在配置类 INSTALLED_APPS 中添加设置。因为 PollsConfig 类写在文件 polls/apps.py 中,所以它的点式路径是 ‘polls.apps.PollsConfig’。在文件 Bornforthi/settings.pyINSTALLED_APPS 子项添加点式路径后,它看起来像这样:

  1. INSTALLED_APPS = [
  2. 'polls.apps.PollsConfig',
  3. 'django.contrib.admin',
  4. 'django.contrib.auth',
  5. 'django.contrib.contenttypes',
  6. 'django.contrib.sessions',
  7. 'django.contrib.messages',
  8. 'django.contrib.staticfiles',
  9. ]

现在你的 Django 项目会包含 polls 应用。接着运行下面的命令:

  1. python manage.py makemigrations polls
  2. # python manage.py makemigrations 理论上这样写也是可以的
  3. # python manage.py makemigrations polls
  4. Migrations for 'polls':
  5. polls\migrations\0001_initial.py
  6. - Create model Question
  7. - Create model Choice

通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移

迁移是 Django 对于模型定义(也就是你的数据库结构)的变化的储存形式 - 它们其实也只是一些你磁盘上的文件。如果你想的话,你可以阅读一下你模型的迁移数据,它被储存在 polls/migrations/0001_initial.py 里。别担心,你不需要每次都阅读迁移文件,但是它们被设计成人类可读的形式,这是为了便于你手动调整 Django 的修改方式。

Django 有一个自动执行数据库迁移并同步管理你的数据库结构的命令 - 这个命令是 migrate,我们马上就会接触它 - 但是首先,让我们看看迁移命令会执行哪些 SQL 语句。sqlmigrate 命令接收一个迁移的名称,然后返回对应的 SQL:

  1. python manage.py sqlmigrate polls 0001

你将会看到类似下面这样的输出:

  1. # python manage.py sqlmigrate polls 0001
  2. --
  3. -- Create model Question
  4. --
  5. CREATE TABLE `polls_question` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `question_text` varchar(200) NOT NULL, `pub_date` datetime(6) NOT NULL);
  6. --
  7. -- Create model Choice
  8. --
  9. CREATE TABLE `polls_choice` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `choice_text` varchar(200) NOT NULL, `votes` integer NOT NULL, `question_id` bigint NOT NULL); ALTER TABLE `polls_choice` ADD CONSTRAINT `polls_choice_question_id_c5b4b260_fk_polls_question_id` FOREIGN KEY (`question_id`) REFERENCES `polls_question` (`id`);

请注意以下几点:

  • 输出的内容和你使用的数据库有关,上面的输出示例使用的是 MySql。
  • 数据库的表名是由应用名(polls)和模型名的小写形式( questionchoice)连接而来。(如果需要,你可以自定义此行为。)
  • 主键(IDs)会被自动创建。(当然,你也可以自定义。)
  • 默认的,Django 会在外键字段名后追加字符串 “_id” 。(同样,这也可以自定义。)
  • 外键关系由 FOREIGN KEY 生成。
  • 生成的 SQL 语句是为你所用的数据库定制的,所以那些和数据库有关的字段类型,比如 auto_increment (MySQL)、 serial (PostgreSQL)和 integer primary key autoincrement (SQLite),Django 会帮你自动处理。那些和引号相关的事情 - 例如,是使用单引号还是双引号 - 也一样会被自动处理。
  • 这个 sqlmigrate 命令并没有真正在你的数据库中的执行迁移 - 相反,它只是把命令输出到屏幕上,让你看看 Django 认为需要执行哪些 SQL 语句。这在你想看看 Django 到底准备做什么,或者当你是数据库管理员,需要写脚本来批量处理数据库时会很有用。

如果你感兴趣,你也可以试试运行 python manage.py check ;这个命令帮助你检查项目中的问题,并且在检查过程中不会对数据库进行任何操作。
现在,再次运行 migrate 命令,在数据库里创建新定义的模型的数据表:

  1. python manage.py migrate
  2. # python manage.py migrate
  3. Operations to perform:
  4. Apply all migrations: admin, auth, contenttypes, polls, sessions
  5. Running migrations:
  6. Applying polls.0001_initial... OK

这个 migrate 命令选中所有还没有执行过的迁移(Django 通过在数据库中创建一个特殊的表 django_migrations 来跟踪执行过哪些迁移)并应用在数据库上 - 也就是将你对模型的更改同步到数据库结构上。

迁移是非常强大的功能,它能让你在开发过程中持续的改变数据库结构而不需要重新删除和创建表 - 它专注于使数据库平滑升级而不会丢失数据。我们会在后面的教程中更加深入的学习这部分内容,现在,你只需要记住,改变模型需要这三步:

数据库迁移被分解成生成和应用两个命令是为了让你能够在代码控制系统上提交迁移数据并使其能在多个应用里使用;这不仅仅会让开发更加简单,也给别的开发者和生产环境中的使用带来方便。通过阅读文档 Django 后台文档 ,你可以获得关于 manage.py 工具的更多信息。

10. 初试 API

现在让我们进入交互式 Python 命令行,尝试一下 Django 为你创建的各种 API。通过以下命令打开 Python 命令行:

  1. # python manage.py shell
  2. Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)] on win32
  3. Type "help", "copyright", "credits" or "license" for more information.
  4. (InteractiveConsole)
  5. >>>

我们使用这个命令而不是简单的使用“python”是因为 manage.py 会设置 DJANGO_SETTINGS_MODULE 环境变量,这个变量会让 Django 根据 Bornforthi/settings.py 文件来设置 Python 包的导入路径。

当你成功进入命令行后,来试试 数据库 API 吧:

  1. >>> from polls.models import Choice, Question # Import the model classes we just wrote.
  2. # No questions are in the system yet.
  3. >>> Question.objects.all()
  4. <QuerySet []>
  5. # Create a new Question.
  6. # Support for time zones is enabled in the default settings file, so
  7. # Django expects a datetime with tzinfo for pub_date. Use timezone.now()
  8. # instead of datetime.datetime.now() and it will do the right thing.
  9. >>> from django.utils import timezone
  10. >>> q = Question(question_text="What's new?", pub_date=timezone.now())
  11. # Save the object into the database. You have to call save() explicitly.
  12. >>> q.save()
  13. # Now it has an ID.
  14. >>> q.id
  15. 1
  16. # Access model field values via Python attributes.
  17. >>> q.question_text
  18. "What's new?"
  19. >>> q.pub_date
  20. datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
  21. # Change values by changing the attributes, then calling save().
  22. >>> q.question_text = "What's up?"
  23. >>> q.save()
  24. # objects.all() displays all the questions in the database.
  25. >>> Question.objects.all()
  26. <QuerySet [<Question: Question object (1)>]>

等等。 对于我们了解这个对象的细节没什么帮助。让我们通过编辑 Question 模型的代码(位于 polls/models.py 中)来修复这个问题。给 QuestionChoice 增加 str() 方法。

  1. from django.db import models
  2. class Question(models.Model):
  3. # ...
  4. def __str__(self):
  5. return self.question_text
  6. class Choice(models.Model):
  7. # ...
  8. def __str__(self):
  9. return self.choice_text

给模型增加 str() 方法是很重要的,这不仅仅能给你在命令行里使用带来方便,Django 自动生成的 admin 里也使用这个方法来表示对象。
让我们再为此模型添加一个自定义方法:

  1. import datetime
  2. from django.db import models
  3. from django.utils import timezone
  4. class Question(models.Model):
  5. # ...
  6. def was_published_recently(self):
  7. return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

新加入的 import datetimefrom django.utils import timezone 分别导入了 Python 的标准 datetime 模块和 Django 中和时区相关的 django.utils.timezone 工具模块。如果你不太熟悉 Python 中的时区处理,看看 时区支持文档 吧。(时区文档要看,后面看)

保存文件然后通过 python manage.py shell 命令再次打开 Python 交互式命令行:

  1. >>> from polls.models import Choice, Question
  2. # Make sure our __str__() addition worked.
  3. >>> Question.objects.all()
  4. <QuerySet [<Question: What's up?>]>
  5. # Django provides a rich database lookup API that's entirely driven by
  6. # keyword arguments.
  7. >>> Question.objects.filter(id=1)
  8. <QuerySet [<Question: What's up?>]>
  9. >>> Question.objects.filter(question_text__startswith='What')
  10. <QuerySet [<Question: What's up?>]>
  11. # Get the question that was published this year.
  12. >>> from django.utils import timezone
  13. >>> current_year = timezone.now().year
  14. >>> Question.objects.get(pub_date__year=current_year)
  15. <Question: What's up?>
  16. # Request an ID that doesn't exist, this will raise an exception.
  17. >>> Question.objects.get(id=2)
  18. Traceback (most recent call last):
  19. ...
  20. DoesNotExist: Question matching query does not exist.
  21. # Lookup by a primary key is the most common case, so Django provides a
  22. # shortcut for primary-key exact lookups.
  23. # The following is identical to Question.objects.get(id=1).
  24. >>> Question.objects.get(pk=1)
  25. <Question: What's up?>
  26. # Make sure our custom method worked.
  27. >>> q = Question.objects.get(pk=1)
  28. >>> q.was_published_recently()
  29. True
  30. # Give the Question a couple of Choices. The create call constructs a new
  31. # Choice object, does the INSERT statement, adds the choice to the set
  32. # of available choices and returns the new Choice object. Django creates
  33. # a set to hold the "other side" of a ForeignKey relation
  34. # (e.g. a question's choice) which can be accessed via the API.
  35. >>> q = Question.objects.get(pk=1)
  36. # Display any choices from the related object set -- none so far.
  37. >>> q.choice_set.all()
  38. <QuerySet []>
  39. # Create three choices.
  40. >>> q.choice_set.create(choice_text='Not much', votes=0)
  41. <Choice: Not much>
  42. >>> q.choice_set.create(choice_text='The sky', votes=0)
  43. <Choice: The sky>
  44. >>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
  45. # Choice objects have API access to their related Question objects.
  46. >>> c.question
  47. <Question: What's up?>
  48. # And vice versa: Question objects get access to Choice objects.
  49. >>> q.choice_set.all()
  50. <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
  51. >>> q.choice_set.count()
  52. 3
  53. # The API automatically follows relationships as far as you need.
  54. # Use double underscores to separate relationships.
  55. # This works as many levels deep as you want; there's no limit.
  56. # Find all Choices for any question whose pub_date is in this year
  57. # (reusing the 'current_year' variable we created above).
  58. >>> Choice.objects.filter(question__pub_date__year=current_year)
  59. <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
  60. # Let's delete one of the choices. Use delete() for that.
  61. >>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
  62. >>> c.delete()

阅读 访问关系对象 文档可以获取关于数据库关系的更多内容。想知道关于双下划线的更多用法,参见 查找字段 文档。数据库 API 的所有细节可以在 数据库 API 参考 文档中找到。

11. 介绍 Django 管理页面

设计哲学
为你的员工或客户生成一个用户添加,修改和删除内容的后台是一项缺乏创造性和乏味的工作。因此,Django 全自动地根据模型创建后台界面。
Django 产生于一个公众页面和内容发布者页面完全分离的新闻类站点的开发过程中。站点管理人员使用管理系统来添加新闻、事件和体育时讯等,这些添加的内容被显示在公众页面上。Django 通过为站点管理人员创建统一的内容编辑界面解决了这个问题。
管理界面不是为了网站的访问者,而是为管理者准备的。