开始应用程序

Django 项目由一组单独的应用程序组成,这些应用程序协同工作以使项目作为一个整体运行。现在,我们将创建一个应用程序来完成项目的大部分工作。我们将在第 19 章添加另一个应用程序来管理用户帐户。

您应该让开发服务器在之前打开的终端窗口中运行。打开一个新的终端窗口(或选项卡)并导航到包含 manage.py 的目录。 激活虚拟环境,然后运行 startapp 命令:

learning_log$ source ll_env/bin/activate
(ll_env)learning_log$ python manage.py startapp learning_logs
❶ (ll_env)learning_log$ ls
db.sqlite3 learning_logs ll_env ll_project manage.py
❷ (ll_env)learning_log$ ls learning_logs/
__init__.py admin.py apps.py migrations models.py tests.py views.py

startapp appname 命令会告诉 Django 创建构建应用程序所需的基础结构。现在查看项目目录,你会看到一个名为 learning_logs ❶的新文件夹。使用 ls 命令查看 Django 创建了哪些文件❷。最重要的文件是 models.pyadmin.pyviews.py。我们将使用 models.py 来定义应用程序中要管理的数据。稍后我们将查看 admin.pyviews.py

定义模型

让我们先思考一下我们的数据。每个用户都需要在学习日志中创建若干主题。他们的每个条目都将与主题绑定,这些条目将以文本形式显示。我们还需要存储每个条目的时间戳,这样就可以向用户显示他们创建每个条目的时间。

打开文件 models.py,查看其现有内容:

models.py
from django.db import models

# Create your models here.

我们将导入一个名为模型的模块,并受邀创建我们自己的模型。模型告诉 Django 如何处理应用程序中存储的数据。模型是一个类;它有属性和方法,就像我们讨论过的每个类一样。下面是用户将存储的主题模型:

from django.db import models
class Topic(models.Model):
    """A topic the user is learning about."""
    text = models.CharField(max_length=200) # 1
    date_added = models.DateTimeField(auto_now_add=True) # 2

    def __str__(self): # 3
        """Return a string representation of the model."""
        return self.text

我们创建了一个名为 Topic 的类,它继承自 Django 中定义模型基本功能的父类 Model。我们为 Topic 类添加了两个属性:textdate_added

text 属性是一个 CharField,是由字符或文本❶ 组成的数据块。如果要存储少量文本,如姓名、标题或城市,就需要使用 CharField。定义 CharField 属性时,我们必须告诉 Django 它应在数据库中保留多少空间。这里我们给它的 max_length 是 200 个字符,这足够容纳大多数主题名称。

date_added 属性是一个 DateTimeField,是记录日期和时间❷ 的数据。我们通过参数 aut_now_add=True,让 Django 在用户创建新主题时自动将该属性设置为当前日期和时间。

告诉 Django 如何表示模型实例是个好主意。如果一个模型有一个 __str__() 方法,只要 Django 需要生成引用该模型实例的输出,它就会调用该方法。这里我们编写了一个 __str__() 方法,用于返回分配给文本属性 ❸ 的值。

要查看模型中可以使用的各种字段,请参见 https://docs.djangoproject.com/en/4.1/ref/models/fields 中的 "模型字段参考" 页面。您现在不需要所有信息,但在开发自己的 Django 项目时,这些信息将非常有用。

激活模型

要使用我们的模型,我们必须告诉 Django 在整个项目中包含我们的应用程序。打开 settings.py(在 ll_project 目录中);你会看到一个告诉 Django 项目中安装了哪些应用程序的部分:

settings.py
--snip--
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
--snip--

通过修改 INSTALLED_APPS 将我们的应用程序添加到此列表中,如下所示:

--snip--
INSTALLED_APPS = [
    # My apps.
    'learning_logs',
    # Default django apps.
    'django.contrib.admin',
    --snip--
]
--snip--

在项目中将应用程序分组,有助于在项目发展到包含更多应用程序时对它们进行跟踪。在这里,我们启动一个名为 "我的应用程序" 的部分,其中暂时只包括 "learning_logs"。将自己的应用程序放在默认应用程序之前很重要,以防需要用自己的自定义行为覆盖默认应用程序的任何行为。

接下来,我们需要告诉 Django 修改数据库,以便它能存储与模型 Topic 相关的信息。在终端运行以下命令:

(ll_env)learning_log$ python manage.py makemigrations learnin
g_logs
Migrations for 'learning_logs':
learning_logs/migrations/0001_initial.py
- Create model Topic
(ll_env)learning_log$

makemigrations 命令会告诉 Django 如何修改数据库,以便存储与我们定义的新模型相关的数据。这里的输出显示 Django 创建了一个名为 0001_initial.py 的迁移文件。该迁移将在数据库中为模型 Topic 创建一个表。

现在我们应用该迁移,让 Django 为我们修改数据库:

(ll_env)learning_log$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, learning_l
ogs, sessions
Running migrations:
Applying learning_logs.0001_initial... OK

该命令的大部分输出与我们第一次发布 migrate 命令时的输出相同。我们需要检查输出中的最后一行,Django 在此确认 learning_logs 的迁移工作正常。

每当我们要修改学习日志管理的数据时,我们都会遵循以下三个步骤:修改 models.py、调用 learn_logs 上的 makemigrations 以及告诉 Django 迁移项目。

Django 管理站点

Django 可通过其管理站点轻松处理您的模型。Django 的管理站点仅供站点管理员使用,普通用户无法使用。在本节中,我们将设置管理站点,并使用它通过主题模型添加一些主题。

设置超级用户

Django 允许您创建一个超级用户,一个拥有网站所有权限的用户。用户的权限控制着他们可以采取的行动。最严格的权限设置允许用户只能读取网站上的公共信息。注册用户通常有权阅读自己的私人数据和一些仅对会员开放的特定信息。要有效管理一个项目,网站所有者通常需要访问网站上存储的所有信息。一个好的管理员会小心处理用户的敏感信息,因为用户对他们访问的应用程序非常信任。

要在 Django 中创建超级用户,请输入以下命令并按提示操作:

(ll_env)learning_log$ python manage.py createsuperuser
❶ Username (leave blank to use 'eric'): ll_admin
❷ Email address:
❸ Password:
Password (again):
Superuser created successfully.
(ll_env)learning_log$

发出 createsuperuser 命令时,Django 会提示输入超级用户的用户名❶。这里我使用的是 ll_admin,但你可以输入任何你想要的用户名。你可以输入电子邮件地址,也可以留空❷。你需要输入两次密码❸。

一些敏感信息可以对网站管理员隐藏起来。例如,Django 不会存储您输入的密码;相反,它会存储一个从密码导出的字符串,称为哈希值。每次输入密码时,Django 都会对输入的密码进行散列,并与存储的散列进行比较。如果两个哈希值匹配,您就通过了验证。通过要求哈希值匹配,Django 可以确保攻击者在访问网站数据库时,只能读取存储的哈希值,而无法读取密码。如果网站设置得当,几乎不可能从哈希值中获取原始密码。

使用管理站点注册模型

Django 在管理站点中自动包含了一些模型,如 UserGroup,但我们创建的模型需要手动添加。

当我们启动 learning_logs 应用程序时,Django 在与 models.py 相同的目录下创建了一个 admin.py 文件。打开 admin.py 文件:

admin.py
from django.contrib import admin

# Register your models here.

要在管理站点注册 Topic,请输入以下内容:

from django.contrib import admin

from .models import Topic

admin.site.register(Topic)

这段代码首先导入了我们要注册的模型 Topic。models 前面的点告诉 Django 在 admin.py 的同一目录下查找 models.py。代码 admin.site.register() 告诉 Django 通过管理员站点管理我们的模型。

现在使用超级用户账户访问管理员站点。访问 http://localhost:8000/admin/ ,输入刚刚创建的超级用户的用户名和密码。您将看到与图 18-2 所示类似的页面。该页面允许您添加新用户和组,并更改现有用户和组。您还可以处理与我们刚刚定义的主题模型相关的数据。

image 2023 12 05 10 17 37 880
Figure 1. Figure 18-2: The admin site with Topic included

如果在浏览器中看到网页不可用的信息,请确保您的终端窗口中仍在运行 Django 服务器。如果没有,请激活虚拟环境并重新执行 python manage.py runserver 命令。如果在开发过程中遇到无法查看项目的问题,关闭所有打开的终端并重新执行 runserver 命令是解决故障的第一步。

添加主题

现在,主题已经在管理站点注册,让我们添加第一个主题。单击 "主题" 进入 "主题" 页面,该页面大部分是空的,因为我们还没有要管理的主题。单击 "添加主题",出现添加新主题的表单。在第一个框中输入国际象棋,然后单击保存。你将返回主题管理页面,并看到刚刚创建的主题。

让我们创建第二个主题,这样我们就有更多的数据可以使用。再次点击添加主题,输入攀岩。单击 "保存",你将再次回到主主题页面。现在你会看到国际象棋和攀岩在列。

定义实体模型

为了让用户记录他们学习国际象棋和攀岩的情况,我们需要为用户在学习日志中的条目类型定义一个模型。每个条目都需要与特定主题相关联。这种关系被称为多对一关系,即一个主题可以关联多个条目。

下面是 Entry 模型的代码。将其放入你的 models.py 文件中:

models.py
from django.db import models

class Topic(models.Model):
    --snip--

class Entry(models.Model): # 1
    """Something specific learned about a topic."""
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE) # 2
    text = models.TextField() # 3
    date_added = models.DateTimeField(auto_now_add=True)

    class Meta: # 4
        verbose_name_plural = 'entries'

    def __str__(self):
        """Return a simple string representing the entry."""
        return f"{self.text[:50]}..." # 5

Entry 类继承自 Django 的基础 Model 类,就像 Topic 类一样❶。第一个属性 topic 是一个外键实例❷。外键是一个数据库术语;它是对数据库中另一条记录的引用。这是连接每个条目和特定主题的代码。每个主题在创建时都会分配一个键或 ID。当 Django 需要在两段数据之间建立连接时,它会使用与每段信息相关联的键。我们很快就会使用这些连接来检索与某个主题相关的所有条目。on_delete=models.CASCADE 参数告诉 Django,当一个主题被删除时,与该主题相关的所有条目也会被删除。这就是所谓的级联删除。

接下来是一个名为 text 的属性,它是 TextField ❸ 的一个实例。这种字段不需要大小限制,因为我们不想限制单个条目的大小。date_added 属性允许我们按条目创建的顺序显示条目,并在每个条目旁放置一个时间戳。

元类嵌套在条目类❹ 中。在这里,它允许我们设置一个特殊属性,告诉 Django 在需要引用多个条目时使用 Entries。如果没有这个属性,Django 会将多个条目称为 Entrys。

__str__() 方法告诉 Django 在引用单个条目时要显示哪些信息。由于条目可以是很长的文本,所以 __str__() 只返回文本的前 50 个字符❺。我们还添加了一个省略号,以说明我们并不总是显示整个条目。

迁移实体模型

因为我们添加了一个新模型,所以需要再次迁移数据库。这个过程很熟悉:修改 models.py,运行 python manage.py makemigrations app_name 命令,然后运行 python manage.py migrate 命令。

迁移数据库并输入以下命令检查输出结果:

(ll_env)learning_log$ python manage.py makemigrations learnin
g_logs
Migrations for 'learning_logs':
❶ learning_logs/migrations/0002_entry.py
- Create model Entry
(ll_env)learning_log$ python manage.py migrate
Operations to perform:
--snip--
❷ Applying learning_logs.0002_entry... OK

生成的名为 0002_entry.py 的新迁移告诉 Django 如何修改数据库以存储与模型 Entry ❶相关的信息。当我们发出 migrate 命令时,我们会看到 Django 应用了该迁移,并且一切运行正常 ❷。

使用管理站点注册条目

我们还需要注册 Entry 模型。admin.py 现在应该是这样的:

admin.py
from django.contrib import admin

from .models import Topic, Entry

admin.site.register(Topic)
admin.site.register(Entry)

返回 http://localhost/admin/ ,应该可以看到学习日志下列出的条目。单击 "添加条目" 链接,或单击 "条目",然后选择 "添加条目"。你会看到一个下拉列表,用于选择要创建条目的主题,以及一个用于添加条目的文本框。从下拉列表中选择国际象棋,然后添加条目。这是我创建的第一个条目:

开局是对局的第一部分,大约是前十步左右。在开局中,最好做三件事--带出你的象和马,试图控制棋盘中央,并为你的王筑好城堡。当然,这些只是指导原则。重要的是要学会何时遵循这些准则,何时忽略这些建议。

点击 "保存" 后,你将回到条目的主管理页面。在这里,你会发现使用 text[:50] 作为每个条目的字符串表示的好处;如果你只看到条目的第一部分,而不是每个条目的全部文本,那么在管理界面中处理多个条目就会容易得多。

为国际象棋创建第二个条目,为攀岩创建一个条目,这样我们就有了一些初始数据。下面是国际象棋的第二个条目:

在对局的开局阶段,重要的是要把你的象和马拿出来。这些棋子强大而灵活,足以在对局的开局阶段发挥重要作用。

这是攀岩运动的第一个条目:

攀岩运动中最重要的理念之一就是尽可能将重心放在脚上。有一种说法认为,攀岩者可以整天靠双臂支撑。实际上,优秀的攀岩者都练习过尽可能将重心放在双脚上的特定方法。

在我们继续开发 "学习日志" 的过程中,这三个条目将为我们提供一些参考。

Django Shell

现在我们已经输入了一些数据,可以通过交互式终端会话以编程方式检查这些数据。这种交互式环境称为 Django shell,是测试和排除项目故障的绝佳环境。下面是一个交互式 shell 会话的示例:

(ll_env)learning_log$ python manage.py shell
❶ >>> from learning_logs.models import Topic
>>> Topic.objects.all()
<QuerySet [<Topic: Chess>, <Topic: Rock Climbing>]>

在活动的虚拟环境中运行命令 python manage.py shell,就会启动一个 Python 解释器,用于探索项目数据库中存储的数据。在这里,我们从 learning_logs.models 模块❶ 中导入模型 Topic。然后,我们使用 Topic.objects.all() 方法获取 Topic 模型的所有实例;返回的列表称为查询集。

我们可以对查询集进行循环,就像对列表进行循环一样。下面是查看分配给每个 Topic 对象的 ID 的方法:

>>> topics = Topic.objects.all()
>>> for topic in topics:
... print(topic.id, topic)
...
1 Chess
2 Rock Climbing

我们将查询集分配给主题,然后打印每个主题的 id 属性和每个主题的字符串表示。我们可以看到,国际象棋的 ID 是 1,攀岩的 ID 是 2。

如果知道某个对象的 ID,就可以使用 Topic.objects.get() 方法检索该对象,并检查该对象的任何属性。让我们来看看 "国际象棋 "的文本和添加日期值:

>>> t = Topic.objects.get(id=1)
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2022, 5, 20, 3, 33, 36, 928759,
tzinfo=datetime.timezone.utc)

我们还可以查看与某个主题相关的条目。之前,我们为条目模型定义了主题属性。这是一个外键,是每个条目和主题之间的连接。Django 可以使用这个连接来获取与某个主题相关的每个条目,就像这样:

❶ >>> t.entry_set.all()
<QuerySet [<Entry: The opening is the first part of the game,
roughly...>, <Entry:
In the opening phase of the game, it's important t...>]>

要通过外键关系获取数据,需要使用相关模型的小写名称,然后是下划线和单词集 ❶。例如,假设有两个模型 PizzaToppingTopping 通过外键与 Pizza 关联。如果你的对象名为 my_pizza,代表一个披萨,那么你可以使用代码 my_pizza.topping_set.all() 获得披萨的所有配料。

当我们开始编码用户可以请求的页面时,我们将使用这种语法。shell 对于确保代码检索到所需数据非常有用。如果你的代码能在 shell 中正常运行,那么它也能在项目中的文件中正常运行。如果您的代码生成错误或无法检索到所需的数据,在简单的 shell 环境中排除故障要比在生成网页的文件中容易得多。我们不会经常使用 shell,但您应该继续使用它来练习使用 Django 的语法来访问存储在项目中的数据。

每次修改模型时,都需要重启 shell 以查看更改的效果。要退出 shell 会话,请按 CTRL-D;在 Windows 下,请按 CTRL-Z 然后按 ENTER 键。

亲身体验

18-2. 短条目:目前,当 Django 在管理站点或 shell 中显示 Entry 时,Entry 模型中的 __str__() 方法会在每个 Entry 实例中添加省略号。在 __str__() 方法中添加 if 语句,只有当条目长度超过 50 个字符时才添加省略号。使用管理站点添加长度少于 50 个字符的条目,并检查查看时是否有省略号。

18-3. Django API:当您编写代码访问项目中的数据时,您就是在编写查询。请浏览 https://docs.djangoproject.com/en/4.1/topics/db/queri es 上的数据查询文档。您看到的大部分内容对您来说都很陌生,但在您开始开发自己的项目时,这些内容将非常有用。

18-4. 比萨店:启动一个名为 pizzeria_project 的新项目,其中包含一个名为披萨的应用程序。定义一个名为 Pizza 的模型,其中包含一个名为 name 的字段,该字段将保存名称值,如夏威夷和肉类爱好者。定义一个名为 Topping 的模型,其中包含名为 pizza 和 name 的字段。pizza 字段应是 Pizza 的外键,name 应能保存菠萝、加拿大培根和香肠等值。

在管理站点注册这两个模型,并使用站点输入一些披萨名称和配料。使用 shell 浏览输入的数据。