18.2 创建应用程序

Django项目 由一系列应用程序组成,它们协同工作让项目成为一个整体。本章只创建一个应用程序,它将完成项目的大部分工作。第19章将添加一个管理用户账户的应用程序。

当前,在前面打开的终端窗口中应该还运行着runserver 。请再打开一个终端窗口(或标签页),并切换到manage.py所在的目录。激活虚拟环境,再执行命令startapp

learninglog$ source llenv/bin/activate
(ll_env)learning_log$ python manage.py startapp learning_logs
❶ (ll_env)learning_log$ ls
db.sqlite3 learning_log learning_logs ll_env 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(见❶)。打开这个文件夹,看看Django都创建了什么(见❷),其中最重要的文件是models.py、admin.py和views.py。我们将使用models.py来定义要在应用程序中管理的数据,稍后再介绍admin.py和views.py。

18.2.1 定义模型

我们来想想涉及的数据。每位用户都需要在学习笔记中创建很多主题。用户输入的每个条目都与特定主题相关联,这些条目将以文本的方式显示。我们还需要存储每个条目的时间戳,以便告诉用户各个条目都是什么时候创建的。

打开文件models.py,看看它当前包含哪些内容:

models.py

from django.db import models

# 在这里创建模型。


这里导入了模块models ,并让我们创建自己的模型。模型告诉Django如何处理应用程序中存储的数据。在代码层面,模型就是一个类,就像前面讨论的每个类一样,包含属性和方法。下面是表示用户将存储的主题的模型:

from django.db import models

class Topic(models.Model):
"""用户学习的主题。"""
❶ text = models.CharField(maxlength=200)
❷ dateadded = models.DateTimeField(auto_now_add=True)

❸ def __str
(self):
"""返回模型的字符串表示。"""
return self.text


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

属性text 是一个CharField ——由字符组成的数据,即文本(见❶)。需要存储少量文本,如名称、标题或城市时,可使用CharField 。定义CharField 属性时,必须告诉Django该在数据库中预留多少空间。这里将max_length 设置成了200(即200字符),这对存储大多数主题名来说足够了。

属性date_added 是一个DateTimeField ——记录日期和时间的数据(见❷)。我们传递了实参auto_now_add=True ,每当用户创建新主题时,Django都会将这个属性自动设置为当前日期和时间。

注意  要获悉可在模型中使用的各种字段,请参阅Django Model Field Reference 。就当前而言,你无须全面了解其中的所有内容,但自己开发应用程序时,这些内容将提供极大的帮助。

需要告诉Django,默认使用哪个属性来显示有关主题的信息。Django调用方法str() 来显示模型的简单表示。这里编写了方法str() ,它返回存储在属性text 中的字符串(见❸)。

18.2.2 激活模型

要使用这些模型,必须让Django将前述应用程序包含到项目中。为此,打开settings.py(它位于目录learning_log/learning_log中),其中有个片段告诉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 = [
# 我的应用程序
'learning_logs',

# 默认添加的应用程序
'django.contrib.admin',
—snip—
]
—snip—


通过将应用程序编组,在项目不断增大,包含更多的应用程序时,有助于对应用程序进行跟踪。这里新建了一个名为“我的应用程序”的片段,当前它只包含应用程序learning_logs 。务必将自己创建的应用程序放在默认应用程序前面,这样能够覆盖默认应用程序的行为。

接下来,需要让Django修改数据库,使其能够存储与模型Topic 相关的信息。为此,在终端窗口中执行下面的命令:

(ll_env)learning_log$ python manage.py makemigrations learning_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_logs, sessions
Running migrations:
❶ Applying learning_logs.0001_initial… OK


这个命令的大部分输出与首次执行命令migrate 的输出相同。需要检查的是❶处的输出行。在这里,Django指出为learning_logs 应用迁移时一切正常。

每当需要修改“学习笔记”管理的数据时,都采取如下三个步骤:修改models.py,对learning_logs 调用makemigrations ,以及让Django迁移项目。

18.2.3 Django管理网站

Django提供的管理网站 (admin site)让你能够轻松地处理模型。网站管理员可以使用管理网站,但普通用户不能使用。本节将建立管理网站,并通过它使用模型Topic 来添加一些主题。

  • 创建超级用户

Django允许创建具备所有权限的用户,即超级用户 。权限 决定了用户可执行的操作。最严格的权限设置只允许用户阅读网站的公开信息。注册用户通常可阅读自己的私有数据,还可查看一些只有会员才能查看的信息。为有效地管理Web应用程序,网站所有者通常需要访问网站存储的所有信息。优秀的管理员会小心对待用户的敏感信息,因为用户极其信任自己访问的应用程序。

为在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自动在管理网站中添加了一些模型,如User 和Group ,但对于我们创建的模型,必须手工进行注册。

我们创建应用程序learning_logs 时,Django在models.py所在的目录中创建了一个名为admin.py的文件:

admin.py

from django.contrib import admin

# 在这里注册你的模型。


为向管理网站注册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所示的屏幕。这个页面让你能够添加和修改用户和用户组,还可管理与刚才定义的模型Topic 相关的数据。

18.2 创建应用程序 - 图1

图18-2 包含模型Topic的管理网站

注意  如果在浏览器中看到一条消息,指出访问的网页不可用,请确认在终端窗口中运行着Django服务器。如果没有,请激活虚拟环境,并执行命令python manage.py runserver 。在开发过程中,如果无法通过浏览器访问项目,首先应采取的故障排除措施是,关闭所有打开的终端,再打开终端并执行命令runserver
  • 添加主题

向管理网站注册Topic 后,我们来添加第一个主题。为此,单击Topics进入主题页面,它几乎是空的,因为还没有添加任何主题。单击Add,将出现一个用于添加新主题的表单。在第一个方框中输入Chess ,再单击Save回到主题管理页面,其中包含刚创建的主题。

下面再创建一个主题,以便有更多的数据可供使用。再次单击Add,并输入Rock Climbing,然后单击Save回到主题管理页面。现在,你可以看到其中包含了主题Chess和Rock Climbing 。

18.2.4 定义模型Entry

要记录学到的国际象棋和攀岩知识,用户必须能够在学习笔记中添加条目。为此,需要定义相关的模型。每个条目都与特定主题相关联,这种关系称为多对一关系 ,即多个条目可关联到同一个主题。

下面是模型Entry 的代码,请将这些代码放在文件models.py中:

models.py

from django.db import models

class Topic(models.Model):
—snip—

❶ class Entry(models.Model):
"""学到的有关某个主题的具体知识。"""
❷ topic = models.ForeignKey(Topic, ondelete=models.CASCADE)
❸ text = models.TextField()
dateadded = models.DateTimeField(auto_now_add=True)

❹ class Meta:
verbose_name_plural = 'entries'

def __str
(self):
""""""返回模型的字符串表示。"""
❺ return f"{self.text[:50]}…"


Topic 一样,Entry 也继承了Django基类Model (见❶)。第一个属性topic 是个ForeignKey 实例(见❷)。外键 (foreign key)是一个数据库术语,它指向数据库中的另一条记录,这里是将每个条目关联到特定主题。创建每个主题时,都分配了一个键(ID)。需要在两项数据之间建立联系时,Django使用与每项信息相关联的键。我们稍后将根据这些联系获取与特定主题相关联的所有条目。实参on_delete=models.CASCADE 让Django在删除主题的同时删除所有与之相关联的条目,这称为级联删除 (cascading delete)。

接下来是属性text ,它是一个TextField 实例(见❸)。这种字段的长度不受限制,因为我们不想限制条目的长度。属性date_added 让我们能够按创建顺序呈现条目,并在每个条目旁边放置时间戳。

在❹处,我们在Entry 类中嵌套了Meta 类。Meta 存储用于管理模型的额外信息。在这里,它让我们能够设置一个特殊属性,让Django在需要时使用Entries 来表示多个条目。如果没有这个类,Django将使用Entrys 来表示多个条目。

方法str() 告诉Django,呈现条目时应显示哪些信息。条目包含的文本可能很长,因此让Django只显示text 的前50字符(见❺)。我们还添加了一个省略号,指出显示的并非整个条目。

18.2.5 迁移模型Entry

添加新模型后,需要再次迁移数据库。你将慢慢地对这个过程了如指掌:修改models.py,执行命令python manage.py makemigrations app_name ,再执行命令python manage.py migrate

请使用如下命令迁移数据库并查看输出:

(ll_env)learning_log$ python manage.py makemigrations learning_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应用了该迁移且一切顺利。

18.2.6 向管理网站注册Entry

我们还需要注册模型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/,你将看到Learning_Logs下列出了Entries。单击Entries的Add链接,或者单击Entries再选择Add entry,将看到一个下拉列表,供你选择要为哪个主题创建条目,以及一个用于输入条目的文本框。从下拉列表中选择Chess,并添加一个条目。下面是我添加的第一个条目。

The opening is the first part of the game, roughly the first ten moves or so. In the opening, it's a good idea to do three things— bring out your bishops and knights, try to control the center of the board, and castle your king.(国际象棋的第一个阶段是开局,大致是前10步左右。在开局阶段,最好做三件事情:将象和马调出来,努力控制棋盘的中间区域,以及用车将王护住。)
Of course, these are just guidelines. It will be important to learn when to follow these guidelines and when to disregard these suggestions.(当然,这些只是指导原则。学习什么情况下遵守这些原则、什么情况下不用遵守很重要。)

当你单击Save时,将返回到主条目管理页面。在这里,你将发现使用text[:50] 作为条目的字符串表示的好处:在管理界面中只显示了条目的开头部分而不是其所有文本,这使得管理多个条目容易得多。

再来创建一个国际象棋条目,并创建一个攀岩条目,以提供一些初始数据。下面是第二个国际象棋条目。

In the opening phase of the game, it's important to bring out your bishops and knights. These pieces are powerful and maneuverable enough to play a significant role in the beginning moves of a game.(在国际象棋的开局阶段,将象和马调出来很重要。这些棋子威力大,机动性强,在开局阶段扮演着重要角色。)

下面是第一个攀岩条目。

One of the most important concepts in climbing is to keep your weight on your feet as much as possible. There's a myth that climbers can hang all day on their arms. In reality, good climbers have practiced specific ways of keeping their weight over their feet whenever possible.(最重要的攀岩概念之一是尽可能让双脚承受体重。有人误认为攀岩者能依靠手臂的力量坚持一整天。实际上,优秀的攀岩者都经过专门训练,能够尽可能让双脚承受体重。)

接着往下开发“学习笔记”时,这三个条目提供了可供使用的数据。

18.2.7 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 的所有实例,这将返回一个称为查询集 (queryset)的列表。

可以像遍历列表一样遍历查询集。下面演示了如何查看分配给每个主题对象的ID:

>>> topics = Topic.objects.all()
>>> for topic in topics:
… print(topic.id, topic)

1 Chess
2 Rock Climbing


将返回的查询集存储在topics 中,再打印每个主题的id 属性和字符串表示。从输出可知,主题Chess的ID为1,而Rock Climbing的ID为2。

知道主题对象的ID后,就可使用方法Topic.objects.get() 获取该对象并查看其属性。下面来看看主题Chess的属性textdate_added 的值:

>>> t = Topic.objects.get(id=1)
>>> t.text
'Chess'
>>> t.date_added
datetime.datetime(2019, 2, 19, 1, 55, 31, 98500, tzinfo=<UTC>)


我们还可以查看与主题相关联的条目。前面给模型Entry 定义了属性topic 。这是一个ForeignKey ,将条目与主题关联起来。利用这种关联,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…>]>


要通过外键关系获取数据,可使用相关模型的小写名称、下划线和单词set (见❶)。例如,假设有模型PizzaTopping ,而Topping 通过一个外键关联到Pizza 。如果有一个名为my_pizzaPizza 对象,就可使用代码my_pizza.topping_set.all() 来获取这张比萨的所有配料。

编写用户可请求的页面时,我们将使用这种语法。确认代码能获取所需的数据时,shell很有帮助。如果代码在shell中的行为符合预期,那么它们在项目文件中也能正确地工作。如果代码引发了错误或获取的数据不符合预期,那么在简单的shell环境中排除故障要比在生成页面的文件中排除故障容易得多。我们不会太多地使用shell,但应继续使用它来熟悉对存储在项目中的数据进行访问的Django语法。

注意  每次修改模型后,都需要重启shell,这样才能看到修改的效果。要退出shell会话,可按Ctr + D。如果你使用的是Windows系统,应按Ctr + Z,再按回车键。
动手试一试
练习18-2:简短的条目  当前,Django在管理网站或shell中显示Entry 实例时,模型Entry 的方法str() 都在其末尾加上省略号。请在方法str() 中添加一条if 语句,以便仅在条目长度超过50字符时才添加省略号。使用管理网站添加一个不超过50字符的条目,并核实显示它时没有省略号。
练习18-3:Django API  当你编写访问项目中数据的代码时,实际上编写的是查询。请浏览Django网站中有关如何查询数据的文档Making queries ,其中大部分内容是你不熟悉的,但等你自己开发项目时,这些内容会很有用。
练习18-4:比萨店  新建一个名为Pizzeria的项目,并在其中添加一个名为pizzas 的应用程序。定义一个名为Pizza 的模型,它包含字段name ,用于存储比萨名称,如HawaiianMeat Lovers 。定义一个名为Topping 的模型,它包含字段pizzaname ,其中字段pizza 是一个关联到Pizza 的外键,而字段name 用于存储配料,如pineappleCanadian baconsausage
向管理网站注册这两个模型,并使用管理网站输入一些比萨名和配料。使用shell来查看你输入的数据。

18.3 创建页面:学习笔记主页

使用Django创建页面的过程分三个阶段:定义URL,编写视图和编写模板。按什么顺序完成这三个阶段无关紧要,但在本项目中,总是先定义URL模式。URL模式描述了URL是如何设计的,让Django知道如何将浏览器请求与网站URL匹配,以确定返回哪个页面。

每个URL都被映射到特定的视图 ——视图函数获取并处理页面所需的数据。视图函数通常使用模板 来渲染页面,而模板定义页面的总体结构。为明白其中的工作原理,我们来创建学习笔记的主页。这包括定义该主页的URL,编写其视图函数并创建一个简单的模板。

我们只是要确保“学习笔记”按要求的那样工作,因此暂时让这个页面尽可能简单。Web应用程序能够正常运行后,设置样式可使其更有趣,但中看不中用的应用程序毫无意义。就目前而言,主页只显示标题和简单的描述。

18.3.1 映射URL

用户通过在浏览器中输入URL以及单击链接来请求页面,因此我们要确定项目需要哪些URL。主页的URL最重要,它是用户用来访问项目的基础URL。当前,基础URL(http://localhost: 8000/)返回默认的Django网站,让我们知道正确地建立了项目。下面修改这一点,将这个基础URL映射到“学习笔记”的主页。

打开项目主文件夹learning_log中的文件urls.py,你将看到如下代码:

urls.py

❶ from django.contrib import admin
from django.urls import path

❷ urlpatterns = [
❸ path('admin/', admin.site.urls),
]


前两行导入了一个模块和一个函数,以便对管理网站的URL进行管理(见❶)。这个文件的主体定义了变量urlpatterns (见❷)。在这个针对整个项目的urls.py文件中,变量urlpatterns 包含项目中应用程序的URL。❸处的代码包含模块admin.site.urls ,该模块定义了可在管理网站中请求的所有URL。

我们需要包含learning_logs 的URL,因此添加如下代码:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
❶ path('', include('learning_logs.urls')),
]


在❶处,添加一行代码来包含模块learning_logs.urls

默认的urls.py包含在文件夹learning_log中,现在需要在文件夹learning_logs中再创建一个urls.py文件。为此,新建一个文件,使用文件名urls.py将其存储到文件夹learning_logs中,再在这个文件中输入如下代码:

urls.py

❶ """定义learning_logs的URL模式。"""

❷ from django.urls import path

❸ from . import views

❹ app_name = 'learning_logs'
❺ urlpatterns = [
# 主页
❻ path('', views.index, name='index'),
]


为指出当前位于哪个urls.py文件中,在该文件开头添加一个文档字符串(见❶)。接下来,导入了函数path ,因为需要使用它将URL映射到视图(见❷)。我们还导入了模块views (见❸),其中的句点让Python从当前urls.py模块所在的文件夹导入views.py。变量app_name 让Django能够将这个urls.py文件同项目内其他应用程序中的同名文件区分开来(见❹)。在这个模块中,变量urlpatterns 是一个列表,包含可在应用程序learning_logs 中请求的页面。

实际的URL模式是对函数path() 的调用,这个函数接受三个实参(见❺)。第一个是一个字符串,帮助Django正确地路由(route)请求。收到请求的URL后,Django力图将请求路由给一个视图。为此,它搜索所有的URL模式,找到与当前请求匹配的那个。Django忽略项目的基础URL(http://localhost:8000/),因此空字符串('' )与基础URL匹配。其他URL都与这个模式不匹配。如果请求的URL与任何既有的URL模式都不匹配,Django将返回一个错误页面。

path() 的第二个实参(见❻)指定了要调用view.py中的哪个函数。请求的URL与前述正则表达式匹配时,Django将调用view.py中的函数index() (这个视图函数将在下一节编写)。第三个实参将这个URL模式的名称指定为index ,让我们能够在代码的其他地方引用它。每当需要提供到这个主页的链接时,都将使用这个名称,而不编写URL。

18.3.2 编写视图

视图函数接受请求中的信息,准备好生成页面所需的数据,再将这些数据发送给浏览器——这通常是使用定义页面外观的模板实现的。

learning_logs中的文件views.py是执行命令python manage.py startapp 时自动生成的,当前其内容如下:

views.py

from django.shortcuts import render

# 在这里创建视图。


当前,这个文件只导入了函数render() ,它根据视图提供的数据渲染响应。请在这个文件中添加为主页编写视图的代码,如下所示:

from django.shortcuts import render

def index(request):
"""学习笔记的主页。"""
return render(request, 'learning_logs/index.html')


URL请求与刚才定义的模式匹配时,Django将在文件views.py中查找函数index() ,再将对象request 传递给这个视图函数。这里不需要处理任何数据,因此这个函数只包含调用render() 的代码。这里向函数render() 提供了两个实参:对象request 以及一个可用于创建页面的模板。下面来编写这个模板。

18.3.3 编写模板

模板定义页面的外观,而每当页面被请求时,Django将填入相关的数据。模板让你能够访问视图提供的任何数据。我们的主页视图没有提供任何数据,因此相应的模板非常简单。

在文件夹learning_logs中新建一个文件夹,并将其命名为templates。在文件夹templates中,再新建一个文件夹,并将其命名为learning_logs。这好像有点多余(在文件夹learning_logs中创建文件夹templates,又在这个文件夹中创建文件夹learning_logs),但是建立了Django能够明确解读的结构,即便项目很大、包含很多应用程序亦如此。在最里面的文件夹learning_logs中,新建一个文件,并将其命名为index.html(这个文件的路径为learning_log/learning_logs/templates/ learning_logs/index.html),再在其中编写如下代码:

index.html

<p>Learning Log</p>

<p>Learning Log helps you keep track of your learning, for any topic you're
learning about.</p>


这个文件非常简单。这里向不熟悉HTML的读者解释一下:标签<p></p> 标识段落。标签<p> 指出段落的开头位置,而标签</p> 指出段落的结束位置。这里定义了两个段落:第一个充当标题,第二个阐述了用户可使用“学习笔记”来做什么。

现在,如果请求这个项目的基础URL http://localhost:8000/,将看到刚才创建的页面,而不是默认的Django页面。Django接受请求的URL,发现该URL与模式'' 匹配,因此调用函数views.index() 。这将使用index.html包含的模板来渲染页面,结果如图18-3所示。

18.2 创建应用程序 - 图2

图18-3 学习笔记的主页

创建页面的过程看起来可能很复杂,但将URL、视图和模板分离的效果很好。这让我们能够分别考虑项目的不同方面,在项目很大时,可让各个参与者专注于最擅长的方面。例如,数据库专家专注于模型,程序员专注于视图代码,而Web设计人员专注于模板。

注意  可能出现如下错误消息:
ModuleNotFoundError: No module named 'learning_logs.urls'


如果确实如此,请在执行命令python manage.py runserver 的终端窗口中按Ctrl + C停用服务器,再重新执行这个命令。这样做后,应该能够看到主页。每当遇到类似的错误时,都尝试停用并重启服务器,看看是否管用。
动手试一试
练习18-5:膳食规划程序  假设你要创建一个应用程序,帮助用户规划一周的膳食。为此,新建一个文件夹,并将其命名为meal_planner,再在这个文件夹中新建一个Django项目。然后,新建一个名为meal_plans 的应用程序,并为这个项目创建一个简单的主页。
练习18-6:比萨店主页  在为完成练习18-4而创建的项目Pizzeria中,添加一个主页。