一、建立项目
1. 制定规范
在着手开发像 Web 应用这样的大项目时,首先需要制定规范(spec),对项目的目标进行描述。确定要达成的目标后,就能着手找出为达成这些目标而需要完成的任务了。
我们要编写一个名为"学习笔记"的 Web 应用程序,让用户能够记录感兴趣的主题,并在学习每个主题的过程中添加日志条目。"学习笔记"的主页对这个网站进行描述,并邀请用户注册或登录。用户登录后,可以创建新主题、添加新条目以及阅读既有的条目。
2. 建立虚拟环境
使用 Django 之前,需要建立虚拟的工作环境。虚拟环境是系统的一个位置,你可在其中安装包,并将这些包与其他 Python 包隔离开来。
为项目新建一个目录,将其命名为 learning_log,再在终端中切换到这个目录,并执行如下命令创建一个虚拟环境:

3. 激活虚拟环境
现在需要使用下面的命令激活虚拟环境:
python
ll_env\Scripts\activate
要停止使用虚拟环境,可执行命令 deactivate:
python
deactivate
4. 安装 Django
激活虚拟环境后,执行如下命令来更新 pip 并安装 Django:
python
pip install django
5. 在 Django 中创建项目
在虚拟环境依然处于活动状态的情况下(ll_env 包含在括号内),执行如下命令新建一个项目:
python
django-admin startproject ll_project
6. 创建数据库
Django 将大部分与项目相关的信息存储在数据库中,因此需要创建一个供Django 使用的数据库。
python
python manage.py migrate
7. 查看项目
下面来核实 Django 正确地创建了项目。为此,可使用命令 runserver查看项目的状态:
python
python manage.py runserver
现在打开一款 Web 浏览器,输入 URL http://localhost:8000/(如果不管用,请输入http://127.0.0.1:8000/)。将看到如下页面:

二、创建应用程序
Django 项目(project)由一系列应用程序组成,它们协同工作让项目成为一个整体。本章只创建一个应用程序,它将完成项目里的大部分工作。
再打开一个终端窗口(或标签页),并切换到 manage.py 所在的目录。激活虚拟环境,然后执行命令 startapp:
python
python manage.py startapp learning_logs
使用命令 ls 查看 Django 都创建了什么
python
ls learning_logs/
1. 创建应用程序
我们来想想涉及的数据。每个用户都需要在学习笔记中创建很多主题。用户输入的每个条目都与特定的主题相关联,这些条目将以文本的方式显示。还需要存储每个条目的时间戳,以便告诉用户各个条目都是什么时候创建的。
打开文件 models.py,看看它当前包含哪些内容:

然后我们创建自己的模型:
python
from django.db import models
# Create your models here.
class Topic(models.Model):
"""用户学习的主题"""
# 属性 text 是一个 CharField------由字符组成的数据,即文本
text = models.CharField(max_length=200)
# 属性 date_added 是一个 DateTimeField------记录日期和时间的数据
date_added = models.DateTimeField(auto_now_add=True)
# __str__() 方法,它返回属性 text 的值
def __str__(self):
return self.text
2. 激活模型
要使用这些模型,必须让 Django 将前述应用程序包含到项目中。为此,打开 settings.py(它位于目录 ll_project 中),其中有个片段告诉Django,哪些应用程序被安装到了项目中:

请将 INSTALLED_APPS 修改成下面这样,将前面的应用程序添加到这个列表中:
python
INSTALLED_APPS = [
# 添加我的应用
'learning_logs',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
接下来,需要让 Django 修改数据库,使其能够存储与模型 Topic 相关的信息。为此,在终端窗口中执行如下命令:
python
python manage.py makemigrations learning_logs
结果如下:

下面应用这种迁移,让 Django 替我们修改数据库:
python
python manage.py migrate
3. Django 管理网站
Django 提供的管理网站(admin site)让你能够轻松地处理模型。Django管理网站仅供网站的管理员使用,普通用户不能使用。本节将建立管理网站,并通过它使用模型 Topic 来添加一些主题。
创建超级用户
Django 允许创建具备所有权限的用户,即超级用户(superuser)。权限决定了用户可执行的操作。
要在 Django 中创建超级用户,请执行下面的命令并按提示做:
python
python manage.py createsuperuser
然后输入用户名和密码
向管理网站注册模型
Django 自动在管理网站中添加了一些模型,如 User 和 Group,如果要添加我们创建的模型,则必须手动注册。
在我们创建应用程序 learning_logs 时,Django 在 models.py 所在的目录中创建了一个名为 admin.py 的文件:
为了向管理网站注册 Topic,请输入下面的代码:
python
from django.contrib import admin
# Register your models here.
from .models import Topic
admin.site.register(Topic)
现在浏览器输入http://127.0.0.1:8000/admin/,并输入账号密码,可得以下页面:

添加主题
向管理网站注册 Topic 后,我们来添加第一个主题。为此,单击Topics 进入主题网页,它几乎是空的,因为还没有添加任何主题。单击Add Topic,会出现一个用于添加新主题的表单。在第一个方框中输入Chess 并单击 Save,我们将回到主题管理页面,其中包含刚创建的主题。
下面再创建一个主题,以便有更多的数据可用。再次单击 AddTopic,并输入 Rock Climbing。单击 Save 后将回到主题管理页面,其中会包含主题 Chess 和 Rock Climbing。
4. 定义模型 Entry
要记录学到的国际象棋和攀岩知识,用户必须能够在学习笔记中添加条目。因此,需要定义相关的模型。每个条目都与特定的主题相关联,这种关系称为多对一关系,即多个条目可关联到同一个主题。
下面是模型 Entry 的代码,请将这些代码放在文件 models.py 中:
python
class Entry(models.Model):
"""学到的有关某个主题的具体知识"""
# 属性 topic 是个 ForeignKey 实例,外键(foreign key)是一个数据库术语,
# 它指向数据库中的另一条记录,这里则是将每个条目关联到特定的主题。在创建每个主题时,都为其分配一个键(ID)。
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
# 它是一个 TextField 实例
# 这种字段的长度不受限制,因为我们不想限制条目的长度。属性 date_added 让我们能
# 够按创建顺序呈现条目,并在每个条目旁边放置时间戳。
text = models.CharField()
date_added = models.DateTimeField(auto_now_add=True)
# Meta 存储用于管理模型的额外信息。
class Meta:
verbose_name_plural = 'Entries'
def __str__(self):
# 返回 text 的前 50 个字符
return f"{self.text[:50]}..."
5. 迁移模型 Entry
添加新模型后,需要再次迁移数据库。你将慢慢地对这个过程了如指掌:修改 models.py,执行命令 python manage.py makemigrations app_name,再执行命令 python manage.py migrate。

6. 向管理网站注册 Entry
我们还需要注册模型 Entry,为此要将 admin.py 修改成类似于下面这样:
python
from django.contrib import admin
# Register your models here.
from .models import Topic, Entry
admin.site.register(Topic)
admin.site.register(Entry)
三、创建网页:学习笔记主页
1. 映射 URL
用户通过在浏览器中输入 URL 和单击链接来请求网页,因此需要确定项目需要哪些 URL。主页的 URL 最重要,它是用户用来访问项目的基础 URL。当前,基础 URL(http://localhost:8000/)返回默认的 Django 网站,让我们知道正确地建立了项目。下面进行修改,将这个基础 URL 映射到"习笔记"的主页
打开项目主文件夹 ll_project 中的文件 urls.py,将看到如下代码:

开头两行导入模块 admin 和一个函数,以便创建 URL 路径。这个文件的主体定义了变量 urlpatterns。在这个为整个项目定义URL 的 urls.py 文件中,变量 urlpatterns 包含项目中应用程序的 URL。这里使用了模块 admin.site.urls,它定义了可在管理网站中请求的所有 URL。
因为需要包含 learning_logs 的 URL,所以添加如下代码:
python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('learning_log.urls')),
]
默认的 urls.py 在文件夹 ll_project 中,现在需要在文件夹learning_logs 中再创建一个 urls.py 文件。为此,新建一个 Python 文件,将其命名为 urls.py 并存储到文件夹 learning_logs 中,再在这个文件中输入如下代码:
python
"""定义 learning_logs 的 URL 模式"""
# 导入函数 path,因为需要使用它将 URL 映射到视图
from django.urls import path
# 让 Python 从当前 urls.py 模块所在的文件夹中导入 views
from . import views
# 变量 app_name 让 Django 能够将这个 urls.py 文件与项目内其他应用程序中的同名文件区分开来
app_name = 'learning_logs'
# 实际的 URL 模式是对 path() 函数的调用,这个函数接受三个实参
urlpatterns = [
path('', views.index, name='index'),
]
2. 编写视图
视图函数接受请求中的信息,准备好生成网页所需的数据,再将这些数据发送给浏览器。这通常是使用定义网页外观的模板实现的。
learning_logs 中的文件 views.py 是执行命令 python manage.pystartapp 时自动生成的,其当前内容如下:
python
from django.shortcuts import render
# Create your views here.
def index(request):
return render(request, 'learning_logs/index.html')
3. 编写模板
模板定义网页的外观,而每当网页被请求时,Django 都将填入相关的数据。模板让你能够访问视图提供的任何数据。我们的主页视图没有提供任何数据,因此相应的模板非常简单。
在文件夹 learning_logs 中新建一个文件夹,并将其命名为 templates。在文件夹 templates 中,再新建一个文件夹并将其命名为 learning_logs。这好像有点多余(在文件夹 learning_logs 中创建文件夹 templates,又在这个文件夹中创建文件夹 learning_logs),但是建立了 Django 能够明确解读的结构,即使项目很大、包含很多应用程序时也是如此。在最里面的文件夹learning_logs 中,新建一个文件并将其命名为 index.html(这个文件的路径为 learning_logs/templates/learning_logs/index.html),再在其中编写如下代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<p>Learning Log</p>
<p>Learning Log helps you keep track of your learning, for any topic you're interested in.</p>
</head>
<body>
</body>
</html>
页面结果如下:

四、创建其他网页
1. 模板继承
在创建网站时,一些通用元素会出现在所有网页中。在这种情况下,可编写一个包含通用元素的父模板,并让每个网页都继承父模板,而不是在每个网页中重复定义这些通用元素。这种方法不仅能够让你专注于开发每个网页的独特方面,还使得修改项目的整体外观容易得多。
父模板
下面创建一个名为 base.html 的模板,并将其存储在 index.html 所在的目录中(路径为
learning_logs/templates/learning_logs/base.html)。这个模板包含所有页面都有的元素,而其他模板都继承它。当前,所有页面都包含的元素只有顶端的标题。因为将在每个页面中包含这个模板,所以将这个标题设置为主页的链接:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>Learning Log</p>
<p>Learning Log helps you keep track of your learning, for any topic you're interested in.</p>
</body>
</html>
这个文件的第一部分创建一个包含项目名的段落,该段落也是主页的链接。为了创建该链接,使用一个模板标签。模板标签是用花括号和百分号({% %})表示的,实质是一小段代码,生成要在网页中显示的信息。这里的模板标签 {% url 'learning_logs:index' %} 生成一个URL,该 URL 与 learning_logs/urls.py 中定义的名为 index 的 URL模式匹配。
我们在最后一行插入了一对块标签。这个块名为 content,是一个占位符,其中包含的信息由子模板指定。
子模板
现在需要重写 index.html,使其继承 base.html。为此,向index.html 添加如下代码:
python
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% extends 'learning_logs/base.html' %}
{% block content %}
<p>Learning Log helps you keep track of your learning, for any topic you're interested in.</p>
{% endblock content %}
</body>
</html>
如果将这些代码与原来的 index.html 进行比较,将发现标题Learning Log 没有了,取而代之的是指定要继承哪个模板的代码。
子模板的第一行必须包含标签 {% extends %},让 Django 知道它继承了哪个父模板。文件 base.html 位于文件夹 learning_logs 中,因此父模板路径中包含 learning_logs。这行代码导入模板 base.html 的所有内容,让 index.html 能够指定要在 content 块预留的空间中添加
的内容。
我们插入一个名为 content 的 {% block %} 标签,以定义content 块。不是从父模板继承的内容都在 content 块中,在这里是一个描述项目"学习笔记"的段落。我们使用标签 {% endblock
content %} 指出内容定义的结束位置。在标签 {% endblock%} 中,并非必须指定块名,但如果模板包含多个块,指定块名有助于确定结束的是哪个块。
2. 显示所有主题的页面
有了高效的网页创建方法后,就可专注于另外两个网页了:显示全部主题的网页以及显示特定主题中条目的网页。所有主题页面显示用户创建的所有主题,它是第一个需要使用数据的网页。
URL 模式
首先,定义显示所有主题的页面的 URL。通常,使用一个简单的 URL片段来指出网页显示的信息;这里将使用单词 topics,因此 URLhttp://localhost:8000/topics/ 将返回显示所有主题的页面。下面演示了该如何修改 learning_logs/urls.py:
python
urlpatterns = [
path('', views.index, name='index'),
# 显示所有主题的页面
path('topics/', views.topics, name='topics'),
]
视图
topics() 函数需要从数据库中获取一些数据,并将其交给给模板。需要在 views.py 中添加的代码如下:
python
from django.shortcuts import render
from .models import Topic
# Create your views here.
def index(request):
return render(request, 'learning_logs/index.html')
# 函数 topics() 包含一个形参:Django 从服务器那里收到的 request 对象
def topics(request):
# 我们查询数据库:请求提供 Topic 对象,并根据属性 date_added 进行排序
topics = Topic.objects.order_by('date_added')
# 接下来,定义一个将发送给模板的上下文
context = {'topics': topics}
# 在创建使用数据的网页时,调用了 render(),并向它传递对象 request、要使用的模板和字典 context
return render(request, 'learning_logs/topics.html', context)
模板
显示所有主题的页面的模板接受字典 context,以便能够使用topics() 提供的数据。新建一个文件,将其命名为 topics.html,并存储到 index.html 所在的目录中。下面演示了如何在这个模板中显示主题:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% extends 'learning_logs/base.html' %}
{% block content %}
<p>Topics</p>
<ul>
{% for topic in topics %}
<li>{{ topic.text }}</li>
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>
{% endblock content %}
</body>
</html>
现在需要修改父模板,使其包含显示所有主题的页面的链接。为此,在 base.html 中添加如下代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Learning Log</title>
</head>
<body>
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a>
<a href="{% url 'learning_logs:topics' %}">Topics</a>
</p>
{% block content %}{% endblock content %}
</body>
</html>
运行结果如下:
3. 显示特定主题的页面
接下来,需要创建一个专注于特定主题的页面,用于显示该主题的名称及其所有条目。我们将定义一个新的 URL 模式,编写一个视图并创建一个模板。还将修改显示所有主题的网页,让每个项目列表项都变为链接:通过单击可显示相应主题的所有条目。
URL 模式
显示特定主题的页面的 URL 模式与前面的所有 URL 模式都稍有不同,因为它使用主题的 id 属性来指出请求的是哪个主题。如果用户要查看主题 Chess(其 id 为 1)的详细页面,URL 将为
http://localhost:8000/topics/1/。下面是与这个 URL 匹配的模式,它应放在 learning_logs/urls.py 中:
python
# 特定主题的详细页面
path('topics/<int:topic_id>/', views.topic, name='topic'),
视图
topic() 函数需要从数据库中获取指定的主题以及与之相关联的所有条目(就像前面在 Django shell 中所做的一样):
python
def topic(request, topic_id):
# 我们查询数据库:请求提供 Topic 对象,并根据属性 date_added 进行排序
topics = Topic.objects.get(id=topic_id)
entries = topics.entry_set.order_by('-date_added')
context = {'topic': topics, 'entries': entries}
return render(request, 'learning_logs/topic.html', context)
模板
这个模板需要显示主题的名称和条目的内容。如果当前主题不包含任何条目,还需要向用户指出这一点:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% extends 'learning_logs/base.html' %}
{% block content %}
<p>Topic: {{ topic.text }}</p>
<p>Entries:</p>
<ul>
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
<p>{{ entry.text|linebreaks }}</p>
</li>
{% empty %}
<li>There are no entries for this topic yet.</li>
{% endfor %}
</ul>
{% endblock content %}
</body>
</html>
将显示所有主题的页面中的每个主题都设置为链接
在浏览器中查看显示特定主题的页面之前,需要修改模板topics.html,让每个主题都链接到相应的网页,如下所示:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% extends 'learning_logs/base.html' %}
{% block content %}
<p>Topics</p>
<ul>
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">
{{ topic.text }}</a>
</li>
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>
{% endblock content %}
</body>
</html>

