不耐烦程序员的-Django5-指南-全-

不耐烦程序员的 Django5 指南(全)

原文:zh.annas-archive.org/md5/120ade4a86489235e5e3f7a026be0fb8

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章:前言

Django 是一个高级 Python Web 框架,鼓励快速开发和清晰、实用的设计。Django 用于构建现代 Python Web 应用,它是免费且开源的。

学习 Django 可能是一项棘手且耗时的工作。有成百上千的教程、大量的文档和许多难以消化的解释。然而,这本书能让你在短短几天内学会并使用 Django。

在本书中,你将踏上愉快、动手实践且实用的旅程,学习 Django 全栈开发。你将在几分钟内开始构建你的第一个 Django 应用。你将获得简短的解释和实用的方法,涵盖一些最重要的 Django 特性,例如 Django 的结构、URL、视图、模板、模型、CSS 包含、图像存储、表单、会话、认证和授权,以及 Django 管理面板。你还将学习如何设计 Django 的模型-视图-模板 (MVT) 架构以及如何实现它们。此外,你将使用 Django 开发一个 Movies Store 应用并将其部署到 互联网上。

在本书结束时,你将能够构建和部署自己的 Django Web 应用。

本书面向对象

本书适用于任何水平的 Python 开发者,他们希望使用 Django 构建全栈 Python Web 应用。本书适用于完全的 Django 初学者。

本书涵盖内容

第一章安装 Python 和 Django,以及介绍 Movies Store 应用,涵盖了 Python 和 Django 的安装,并介绍了 Movies Store 应用,展示了功能、类图和 MVT 架构。

第二章理解项目结构和创建我们的第一个应用,探讨了 Django 的项目结构和应用创建,并演示了如何使用 Django 的 URL、视图和模板来 创建页面。

第三章设计基础模板,探讨了如何使用 Django 基础模板来减少重复代码并改善 Movies Store 应用的外观和感觉。

第四章, 使用模拟数据创建电影应用,使用 模拟数据 构建一个显示电影列表的电影应用。

第五章, 与模型一起工作,讨论 Django 模型的基础知识以及如何与数据库一起工作。

第六章, 从数据库收集和显示数据,讨论了如何从 数据库 收集和显示数据。

第七章, 理解数据库,展示了如何检查数据库信息以及如何在数据库引擎之间切换。

第八章, 实现用户注册和登录,讨论 Django 认证系统,并通过一些功能增强电影商店应用程序,允许用户注册和 登录。

第九章, 允许用户创建、读取、更新和删除电影评论,通过在评论上执行标准的 CRUD(创建、读取、更新、删除) 操作来增强电影商店应用程序。

第十章, 实现购物车系统,涵盖了 Django 会话的使用,以及如何使用 Web 会话来实现购物 车系统。

第十一章, 实现订单和项目模型,探讨发票的工作原理,并创建订单和项目模型来管理购买信息。

第十二章, 实现购买和订单页面,创建购买和订单页面,并以对电影商店架构的回顾结束。

第十三章, 将应用程序部署到云,展示了如何将 Django 应用程序部署到 云上。

为了充分利用这本书

您需要安装 Python 3.10+,pip,以及一个好的代码编辑器,如 Visual Studio Code。 最后一章需要使用 Git 将应用程序代码部署到云中。 所有软件要求均适用于 Windows,macOS, 和 Linux。

本书涵盖的软件/硬件 操作系统要求
Python 3.10+ Windows, macOS, 或 Linux
Pip Windows, macOS, 或 Linux
Visual Studio Code Windows, macOS, 或 Linux
Git Windows, macOS, 或 Linux

如果您正在使用本书的数字版,我们建议您亲自输入代码或访问 本书的 GitHub 仓库中的代码(下一节将提供链接)。 这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。 代码。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件 https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition。如果代码有更新,它将在 GitHub 仓库中更新。

我们还在我们的丰富图书和视频目录中提供了其他代码包,可在 https://github.com/PacktPublishing/找到。查看它们!

《代码实战》

本书的《代码实战》视频可在 https://packt.link/L3S8S查看。

使用的约定

本书中使用了多种文本约定。

<st c="4849">代码文本</st>:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。 以下是一个示例:“db.sqlite3 文件是 Django 用于开发目的的默认 SQLite 数据库文件。”

代码块设置为以下格式:

 from django.contrib import admin
from django.urls import path
urlpatterns = [
    path('admin/', admin.site.urls),
]

当我们希望您注意代码块中的特定部分时,相关的行或项目将被设置为粗体:

 from django.shortcuts import render <st c="5439">def index(request):</st>
 <st c="5458">return render(request, 'home/index.html')</st>

任何命令行输入或输出都按照以下方式编写:

 python3 --version

粗体:表示新术语、重要单词或屏幕上看到的单词。 例如,菜单或对话框中的单词以 粗体显示。以下是一个示例:“对于 Windows,您必须选择 将 python.exe 添加到 PATH 选项。”

提示或重要注意事项

看起来像这样。

联系我们

我们的读者反馈 总是受欢迎的。

一般反馈:如果您对此书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com ,并在邮件主题中提及书名。

勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。 如果您在此书中发现错误,如果您能向我们报告,我们将不胜感激。 请访问 www.packtpub.com/support/errata 并填写 表格。

盗版:如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。 请通过以下方式联系我们 copyright@packt.com 并提供 材料的链接。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请 访问 authors.packtpub.com

分享您的想法

一旦您阅读了 《Django 5 for the Impatient》,我们很乐意听听您的想法! 请点击此处直接进入此书的亚马逊评论页面并分享 您的反馈

您的评论对我们和科技社区都很重要,并将帮助我们确保我们提供的是优秀的 内容质量。

下载此书的免费 PDF 副本

感谢购买 此书!

您喜欢在路上阅读,但无法携带您的印刷 书籍到处走吗?

您的电子书购买是否与您选择的设备不兼容?

不用担心,现在每购买一本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。 无需付费。

在任何地方、任何设备上阅读。 从您最喜欢的技术书籍中直接搜索、复制和粘贴代码到您的应用程序中。

优惠不仅限于此,您还可以每天在您的 收件箱中获取独家折扣、时事通讯和丰富的免费内容

按照以下简单步骤获取 福利:

  1. 扫描二维码或访问以下 链接

https://packt.link/free-ebook/9781835461556

  1. 提交您的购买 证明

  2. 这就完了! 我们将直接将您的免费 PDF 和其他福利发送到您的 邮箱

第二章:1

安装 Python 和 Django,并介绍电影商店应用程序

欢迎来到 《Django 5 快速入门》!本书专注于关键任务和概念,以帮助您快速学习和构建 Django 应用程序。 本书面向那些不需要 Django 的所有细节,只需了解真正需要知道的部分。 本书结束时,您将能够自信地创建自己的 Django 项目。

那么,Django 是什么呢? Django 是一个免费的、开源的 Web 框架,用于构建现代 Python Web 应用程序。 Django 通过抽象化构建网站时涉及的大量重复性挑战,如连接数据库、处理安全、启用用户认证、创建 URL 路由、通过模板和表单在页面上显示内容、支持多个数据库后端以及设置管理界面,帮助您快速构建 Web 应用程序。

这种减少重复性任务的做法使得开发者能够专注于构建 Web 应用程序的功能,而不是为标准 Web 应用程序功能重新发明轮子。

Django 是最受欢迎的框架之一,被像 Instagram Pinterest Mozilla 国家地理这样的知名公司所使用。 它也足够简单,可以用于初创公司和构建个人项目。

还有其他流行的框架,例如 Python 中的 Flask 和 JavaScript 中的 Express(有关 Express 的更多信息,请参阅 Greg Lim 的《Node.js、Express 与 MongoDB 开发入门》:www.amazon.com/dp/B07TWDNMHJ/)。 然而,这些框架只为简单的 Web 页面提供所需的最小功能,开发者必须进行更多基础工作,例如自行安装和配置第三方包以实现基本的网站功能。

在本章中,我们将熟悉我们将要构建的应用程序,使用 Django 5,并通过安装和设置所需的一切来为开发项目做好准备。 本章结束时,您将成功创建自己的 开发环境。

在本章中,我们将介绍以下主题:

  • 介绍和 安装 Python

  • 介绍和 安装 Django

  • 创建和运行一个 Django 项目

  • 理解电影 存储应用程序

  • 介绍 Django MVT 架构

技术要求

在本章中,我们将使用 Python 3.10+

本章的代码位于 以下位置 https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter01/moviesstore

本章的 CiA 视频可以在 以下位置找到 https://packt.link/ygUpr

介绍和安装 Python

Python 是一种高级编程语言(https://www.python.org/),由 Guido van Rossum 在 20 世纪 80 年代末期创建。 Python 的名字来源于创建者对英国喜剧团体 Monty Python 的喜爱,而不是像人们普遍认为的那样是“蛇”。 The name Python comes from the creator’s affection for the British comedy group Monty Python and not the “snake,” as is commonly believed.

Python 拥有开源许可,这意味着开发者可以免费修改、使用和重新分发其代码,无需支付 原始作者的费用。

Python 被描述为一种友好且易于学习的编程语言。 Python 可以用于开发各种应用程序,包括网站开发、数据分析、人工智能、科学计算 以及自动化。

目前,让我们 检查是否已安装 Python,以及安装的版本 是什么。

如果您使用的是 Mac,请打开您的终端。 如果您使用的是 Windows,请打开命令提示符。 为了方便起见,我们将把终端和命令提示符统称为 *终端 全书。

为了使用 Django 5,我们需要 检查是否至少安装了 Python 3.10。 要这样做,请转到您的终端并运行以下命令: 以下命令:

  • 对于 macOS, 运行以下命令:

    <st c="3745">python3 --version</st>
    
  • 对于 Windows, 运行以下命令:

    <st c="3786">python --version</st>
    

这显示了您已安装的 Python 版本。 请确保版本至少为 3.10。如果不是,请访问 https://www.python.org/downloads/ 并安装适用于您的操作系统的版本。 对于 Windows,您必须选择 将 python.exe 添加到 PATH 选项(以确保可以从命令提示符或 终端中的任何目录访问 Python 解释器),如图 图 1**.1:

图 1.1 – 在 Windows 上安装 Python

图 1.1 – 在 Windows 上安装 Python

安装后,再次运行该命令以检查已安装的 Python 版本。

输出应反映 Python 的最新版本,例如 Python 3.12.2(撰写本文时),如图 图 1**.2:

图 1.2 – 检查 Windows 上的 Python 版本

图 1.2 – 检查 Windows 上的 Python 版本

既然我们已经安装了 Python ,接下来我们将介绍并 安装 Django。

介绍和安装 Django

Django 是一个高级 Python Web 框架,它鼓励快速开发和简洁、实用的设计(https://www.djangoproject.com/)。 Django 使构建更好的 Web 应用更加快速,并且代码更少。

安装 Django 有几种方法;在这本书中,我们将使用 <st c="5527">pip</st> 来安装 Django。 <st c="5682">pip</st> 如果您从 https://www.python.org/.

首先,通过转到终端并运行以下命令来检查您是否已安装 <st c="5798">pip</st>

  • 对于 macOS, 运行以下命令:

    <st c="5893">pip3</st>
    
  • 对于 Windows, 运行以下命令:

    <st c="5921">pip</st>
    

如果您已安装 <st c="5938">pip</st> ,输出应显示一个 <st c="5989">pip</st> 命令列表,如图 图 1**.3:

图 1.3 – 检查 Windows 上是否已安装 pip

图 1.3 – 检查 Windows 上是否已安装 pip

接下来,要安装 Django,运行以下命令:Next, to install Django, run the following commands:

  • 对于 macOS,For macOS, run this:

    <st c="6295">pip3 install django==5.0</st>
    
  • 对于 Windows,For Windows, run this:

    <st c="6343">pip install django==5.0</st>
    

上述命令 The preceding command will retrieve the Django 5.0 code version and install it on your machine. Note that there may be newer versions available when you’re reading this book. However, we recommend continuing to use Django 5.0 to ensure that the code in this book will function correctly. After installation, close and reopen your Terminal.

要检查您是否已安装 Django,运行以下命令。To check whether you have installed Django, run the following commands.

  • 对于 macOS,For macOS, run this:

    <st c="6800">python3 -m django</st>
    
  • 对于 Windows,For Windows, run this:

    <st c="6841">python -m django</st>
    

现在,输出将显示你可以使用的所有 Django 命令,如图 图 1**.4*

图 1.4 – macOS 上的 Django 模块命令

图 1.4 – macOS 上的 Django 模块命令 Figure 1.4 – The Django module commands on macOS

在本书的过程中,你将逐步被介绍一些先前的命令。Over the course of the book, you will progressively be introduced to some of the preceding commands.

注意 Note

通常也使用 虚拟环境 (例如, venv 模块)来管理您的 Python 和 Django 项目及其依赖关系。 目前,我们将不使用 venv 来快速开始 Django。 我们将在本书末尾学习如何使用和配置 venv。

我们已经有了创建 Django 项目所需的所有工具。We have all the tools we need to create a Django project. Now, let’s move on to doing that.

创建和运行 Django 项目 Creating and running a Django project

现在我们已经安装了 Django,我们准备好创建我们的 Django 项目。Now that we have Django installed, we are ready to create our Django project.

有几种方法可以创建 Django 项目。There are several ways to create Django projects. In this book, we will use <st c="8217">django-admin</st>. django-admin 是 Django 的 命令行实用程序,用于管理任务。 提供了各种命令,帮助您创建、管理、交互 Django 项目、应用程序和其他 相关组件。

在终端中,导航到您想要创建项目的文件夹,并运行以下命令:In the Terminal, navigate to the folder where you want to create your project and run the following command:

 django-admin startproject moviesstore

这将创建一个 <st c="8605">moviesstore</st> 文件夹在你的当前目录中。 这个文件夹包含我们的 Django 应用程序代码。 我们将在稍后讨论其内容。 现在,让我们在我们的 Django 本地 Web 服务器上运行我们的第一个网站。

在终端中,运行 <st c="8831">cd</st> 命令以进入 创建的文件夹:

 cd moviesstore

然后,运行以下命令:

  • 对于 macOS, 运行以下命令:

    <st c="8943">python3 manage.py runserver</st>
    
  • 对于 Windows, 运行以下命令:

    <st c="8994">python manage.py runserver</st>
    

当你运行上述命令时,你将在你的机器上启动本地 Web 服务器(用于本地开发目的)。 将会有一个 URL 链接—— <st c="9172">http://127.0.0.1:8000/</st> (相当于 <st c="9210">http://localhost:8000</st>)。 在浏览器中打开此链接,你将看到默认的着陆页面,如图 图 1**.5所示:

图 1.5 – Django 项目的着陆页面

图 1.5 – Django 项目的着陆页面

这意味着 你的本地 Web 服务器正在运行并服务着陆页面。 有时,你需要停止服务器以运行其他 Python 命令。 要停止本地服务器,请在 Ctrl + C 终端中按下。

我们已经成功执行了我们的 第一个 Django 项目。 现在,是时候介绍本书中将开发的程序了。

理解电影商店应用程序

在编程文献中,使用运行示例是一种常见的做法。 运行示例 作为说明方法论、过程、工具或技术原理的手段。 在这本书中,我们将定义一个 电影商店 运行示例。 我们将全书回顾这个运行示例,以实际方式解释许多 Django 概念和元素。

电影商店将是一个基于 Web 的平台,用户可以访问有关电影的信息,并可以下订单来 购买它们。

现在,让我们概述这个特定应用程序的应用范围:

  • 主页将展示一个欢迎信息

  • 关于页面将提供关于电影商店的详细信息。

  • 电影页面将展示可用的电影信息,并包括一个按名称搜索电影的过滤器。此外,用户可以点击特定的电影来查看其详细信息并发表评论

  • 购物车页面将展示添加到购物车中的电影以及需要支付的总价。用户还可以从购物车中删除电影并继续购买。

  • 注册页面将提供一个允许用户注册账户的表单。

  • 登录页面将提供一个允许用户登录应用程序的表单。

  • 订单页面将显示已登录用户下过的订单

  • 管理员面板将包括管理商店信息的部分,包括创建、更新、删除和列出信息

电影商店将使用 Django(Python)、SQLite 数据库和 Bootstrap(一个 CSS 框架)开发。关于这些组件的更多细节将在接下来的章节中介绍。

图 1.6中,你可以找到一个概述应用程序范围和设计的类图。用户类展示了其关联的数据(例如 id、用户名、电子邮件和密码),并且能够下订单。每个订单由一个或多个商品组成,这些商品与单个电影相关联。每个电影将拥有其各自的数据(包括 id、名称、价格、描述和图片)。最后,用户有创建评论电影的能力。

图 1.6 – 电影商店类图

图 1.6 – 电影商店类图

本书不会深入探讨类图的复杂性;因此,我们不会详细说明图中的额外细节(您可以参考以下链接获取有关类图的更多信息: https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/)。 随着您阅读本书的深入,您会注意到代码与这张图之间的关联。 作为一份蓝图,这张图指导着我们应用程序的构建。

注意

在开始编码之前创建类图有助于理解应用程序的范围并识别关键数据点。 此外,它有助于理解应用程序中各个元素之间的相互关系。 此图可以与团队成员或同事分享以获取反馈,并根据需要做出调整。 由于其作为图的特点,修改可以迅速实施。 请查看书中以下陈述 *《构建微服务》 * 纽曼 S. (2015):我倾向于在变更成本和错误成本尽可能低的地方进行大量思考: 白板。

基于之前的范围,我们将构建一个 *电影商店 * 应用程序,允许用户查看和搜索电影,如图 图 1**.7

图 1.7 – 带搜索功能的电影页面

图 1.7 – 带搜索功能的电影页面

用户将能够注册,如图 图 1**.8

图 1.8 – 注册页面

图 1.8 – 注册页面

用户将能够 登录,将电影添加到购物车,并进行购买,如图 图 1**.9

图 1.9 – 购物车页面

图 1.9 – 购物车页面

用户 还将能够列出、创建、编辑和删除电影评论,如图 图 1.10 所示:

图 1.10 – 一个带有其评论的具体电影页面

图 1.10 – 一个带有其评论的具体电影页面

本书中还将开发并解释许多其他功能。 现在,让我们看看我们将用于构建 *电影 * *商店 * 应用程序 的架构。

介绍 Django MVT 架构

设计和管理网络应用程序有各种方法和途径。 其中一种方法是将所有代码合并到一个文件中,以构建整个网络应用程序。 然而,在包含数千行代码的此类文件中查找错误可能极具挑战性。 另一种策略是将代码分散到不同的文件和目录中。 此外,一些方法将应用程序分割成多个较小的应用程序,这些应用程序分散在多个服务器上,尽管管理这些服务器的分布也带来了一组自己的挑战。 此外,一些方法将应用程序分割成多个较小的应用程序,这些应用程序分散在多个服务器上,尽管管理这些服务器的分布也带来了一组自己的挑战。 管理这些服务器的分布也带来了一组自己的挑战。

有效地组织代码会带来挑战。 这就是为什么开发人员和计算机科学家创建了软件架构模式。 软件架构模式 提供了结构化框架或布局来解决常见的软件设计问题。 通过利用这些模式,初创公司和经验不足的开发者可以避免在每个新项目中重新发明解决方案。 存在各种架构模式,包括模型-视图-控制器 (MVC)模型-视图-模板** (MVT)**,层、面向服务、和微服务。 每种模式都有自己的 优缺点。 许多 框架,如 Django,在构建其应用程序时遵循特定的模式。

在 Django 的情况下,Django 是基于 MVT 模式的。 这种模式与 MVC 类似,但在每个组件的责任上有所不同:

  • 模型:模型表示 数据结构。 在 Django 中,模型是定义数据结构和它与数据库交互方式的 Python 类。 模型处理 查询数据库、执行 CRUD 创建、读取、更新、删除)操作以及执行数据验证等任务。 电影商店* 应用程序的情况下,电影**评论、*订单 以及我们类图中的其他类将被编码为 Django 模型

  • 视图:Django 中的视图负责处理用户请求并返回适当的 响应。 视图通常接收来自客户端的 HTTP 请求,使用模型从数据库中获取数据,并渲染模板以生成 HTML 响应。 在 Django 中,视图是接受 HTTP 请求并返回 HTTP 响应的 Python 函数或类。 电影商店* 应用程序的情况下,我们将创建视图和函数来处理电影、账户和购物车等。

  • 模板:模板用于动态生成 HTML。 它们包含应用程序的用户界面,并定义了视图中的数据应该如何显示给用户。 电影商店* 应用程序的情况下,我们将创建一个允许用户登录的模板,一个列出电影的模板,以及一个显示购物车的模板等。

MVT 模式提供了诸如代码分离增强、促进多个团队成员之间的协作、简化错误识别、提高代码重用性和改进可维护性等好处。 *图 1.11 展示了我们将在这本书中开发的 *电影商店**的软件架构。 虽然现在可能看起来令人不知所措,但到本书结束时,你将理解这个架构的复杂性。 我们将在最后几章中更深入地探讨架构。

图 1.11 – 电影商店软件架构图

图 1.11 – 电影商店软件架构图

让我们简要分析 这个 架构:

  • 位于左侧的是客户端,它们是我们应用程序的用户,使用移动或桌面设备上的浏览器。 这些客户端通过 超文本传输协议 (HTTP)与应用程序建立连接,为用户提供与我们的 Web 应用程序交互的手段。

  • 在右侧,我们有服务器,它托管我们的 应用程序代码。

  • 所有客户端交互首先传递给一个名为 <st c="19468">urls.py</st>的项目级别 URL 文件。 该文件位于名为 <st c="19532">moviesstore/</st>的主项目文件夹中。 URLs 将在 第二章中探讨。 此项目文件夹还包含一个 <st c="19618">templates/</st> 文件夹,我们将在此设计一个可重用的基础模板。 基础模板将在 第三章中探讨。

  • 项目级别的 URL 文件将交互传递给应用级别的 URL 文件。 对于这个项目,我们将设计和实现四个 Django 应用—— 主页, 电影, 购物车,以及 账户。Django 应用将在 第二章中探讨。

  • 每个应用级别的 URL 文件将交互传递给一个 <st c="20003">views.py</st> 文件。 视图将在 第二章中探讨。

  • 视图如果需要,会与模型通信,并将信息传递给模板,最终将 HTML、CSS 和 JS 代码作为 HTML、CSS 和 JS 代码发送到客户端。 模板将在 第二章中探讨,而模型将在 第五章中探讨。

图 1**.11中, 模型视图,和 模板 层以灰色突出显示,代表了 Django 中常用的架构模式,本书将使用这一模式。 我们 有四个模型对应于我们在类图中定义的类(如前所述在 图 1**.6中所示)。 用户模型没有出现在这个图中,因为我们将会重用内置的 Django 用户模型。

因此,如前所述,使用 Django 实现 Web 应用有不同的方法。 甚至有不同方式来实现 Django MVT 架构。 在接下来的章节中,我们将看到采用 MVT 架构的优势,如图 图 1**.11所示。

总结

在本章中,我们学习了如何安装和使用 Python, <st c="21054">pip</st>,以及 Django。 我们还学习了如何创建一个新的 Django 项目并运行 Django 本地 Web 服务器。 然后,我们解释了 电影商店 项目的范围。 我们还通过一个类图展示了应用程序数据和其关系。 此外,我们还展示了一个架构图,显示了 电影商店的主要组件和元素。这些图表将作为蓝图,在接下来的章节中用于规范 电影商店 项目。

在下一章中,我们将深入了解 Django 为我们创建的项目文件夹,以便更好地理解它,并创建我们的第一个 Django 应用。

第三章:理解项目结构和创建我们的第一个应用

Django 项目包含一个预定义的结构,其中包含一些关键文件夹和文件。在本章中,我们将讨论 Django 项目结构以及如何使用这些文件夹和文件来配置我们的 Web 应用。此外,Django 项目由一个或多个应用组成。我们将学习如何创建一个由“主页”和“关于”部分组成的“主页”应用,以及如何在我们的 Django 项目中注册它。

在本章中,我们将涵盖以下主题:

  • 理解项目结构

  • 创建我们的第一个应用

  • 创建一个主页

  • 创建一个关于页面

完成所有这些主题后,您将了解如何创建 Django 应用和 Web 页面。

技术要求

在本章中,我们将使用 Python 3.10+。此外,我们将在本书中使用《Visual Studio (VS) Code》编辑器,您可以从code.visualstudio.com/下载。

本章的代码位于github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter02/moviesstore

本章的 CiA 视频可以在packt.link/rzU25找到。

理解项目结构

让我们看看为我们创建的第一章项目文件,在《创建和运行 Django 项目》部分。图 2.1.1 中展示了这些元素:

图 2.1.1 – MOVIESSTORE 目录结构

图 2.1 – MOVIESSTORE 目录结构

让我们了解这些元素中的每一个。

MOVIESSTORE 文件夹

正如您 图 2**.1中看到的,有一个文件夹与我们在 VS Code 中最初打开的文件夹同名 – <st c="1684">moviesstore</st>。 The <st c="1701">moviesstore</st> 文件夹包含一组用于配置 Django 项目的文件。 图 2**.2 显示了 <st c="1813">moviesstore</st> 文件夹的内容:

图 2.2 – moviesstore 文件夹内容结构

图 2.2 – moviesstore 文件夹内容结构

让我们简要地看看 moviesstore 文件夹下的所有 元素:

  • <st c="2074">__pycache__</st>: 这个文件夹 在生成我们的项目时存储编译后的字节码。 您可以基本上忽略这个文件夹。 它的目的是通过缓存编译代码来使您的项目启动稍微快一点,这些代码可以 被轻松执行。

  • <st c="2318">__init__.py</st>: 这个文件指示 Python 将这个目录视为一个 Python 包。 我们可以忽略 这个文件。

  • <st c="2446">asgi.py</st>: Django 作为一个 Web 框架,需要 Web 服务器来运行。 由于大多数 Web 服务器本身不支持 Python,我们需要一个接口来实现这种通信。 Django 目前支持两个接口 – <st c="2766">asgi.py</st> 文件包含一个入口点,用于 ASGI 兼容的 Web 服务器异步地为您 项目提供服务。

  • <st c="2872">settings.py</st>: The <st c="2891">settings.py</st> 文件是一个重要的文件,它控制着我们的项目设置。 它包含几个属性;让我们分析一些 它们:

    • <st c="3026">BASE_DIR</st>: 确定项目 在您的机器上的位置。

    • <st c="3095">SECRET_KEY</st>: 这是一个特定 Django 项目的密钥。 它用于提供加密签名,应设置为唯一、不可预测的值。 在生产环境中,它应该被一个安全 生成的密钥所替换。

    • <st c="3340">DEBUG</st>: 我们网站可以运行在调试模式或非调试模式。 在调试模式下,我们可以获得有关错误的详细信息,这在开发我们的应用程序时非常有用。 例如,如果我们尝试在浏览器中运行 <st c="3528">http://localhost:8000/123</st> ,我们将看到一个 页面未找到 (404) 错误(见 图 2.3):

图 2.3 – 访问无效的应用程序路由

图 2.3 – 访问无效的应用程序路由

  • <st c="4142">INSTALLED_APPS</st>: 这个 设置指定了 所有启用在此项目中的 Django 应用程序的列表。 列表中的每个字符串都代表一个 Django 应用的 Python 路径。 默认情况下,Django 包含了几个内置应用程序,例如 admin、auth、contenttypes 和 sessions。 在本章的后面,我们将看到如何创建我们自己的应用程序以及如何将它们包含在这个配置中。

  • <st c="4553">MIDDLEWARE</st>: Django 中的中间件拦截并管理请求和响应处理流程。 列出的中间件由 Django 提供,并处理请求/响应处理的各个方面,包括安全、会话管理、身份验证等。

  • <st c="4822">ROOT_URLCONF</st>: 指定 Django 项目的根 URL 配置的 Python 路径。

  • <st c="4917">TEMPLATES</st>: 定义 Django 模板系统的配置。 它包括有关系统应查找模板源文件和其他特定 模板设置的信息。

  • <st c="5174">settings.py</st>》中还有一些其他属性,例如 <st c="5195">DATABASES</st>》、《st c="5206">LANGUAGE_CODE<st c="5219">》和TIME_ZONE`》,但我们专注于前面列表中更重要的属性。 我们将在稍后重新访问此文件,并查看它在开发我们的网站时有多相关。

  • <st c="5391">urls.py</st>: 此文件包含此 Django 项目的 URL 声明。 它可以链接特定的 URL 路径到函数、类或其他 URL 文件以生成响应,或者根据浏览器或 URL 请求渲染页面。 我们将在稍后添加路径到该文件,并更好地理解它是如何工作的。

  • <st c="5699">wsgi.py</st>:此文件包含一个 WSGI 兼容的 Web 服务器用于服务项目的入口点。 默认情况下,当我们使用 python manage.py runserver 命令运行服务器时,它使用 WSGI 配置。

manage.py

The <st c="5925">manage.py</st> file seen in *图 2.1 *图 2.2是本书中将广泛使用的关键元素。 此文件提供了一种命令行工具,允许您与 Django 项目交互并执行一些管理操作。 例如,我们之前在 第一章 创建和运行 Django 项目 部分中运行了以下命令:

 python manage.py runserver

该命令的目的是启动本地 Web 服务器。 我们将稍后展示更多管理 功能,例如创建新应用的一个命令 – <st c="6474">python</st> <st c="6481">manage.py startapp</st>

db.sqlite3

The <st c="6516">db.sqlite3</st> file is the default SQLite database file that Django uses for development purposes. <st c="6840">db.sqlite3</st> 文件)。 目前我们不会使用此文件;然而,我们将在 第五章中讨论它。

我们已经了解了 Django 项目结构和其主要元素。 现在,让我们创建我们的第一个 Django 应用。

创建我们的第一个应用

一个 Django 应用 是一个包含特定功能或服务于 Django 项目中特定目的的自包含 代码包。

一个 Django 项目可以包含一个或多个应用,这些应用协同工作以支持一个 Web 应用程序。 Django 使用项目和应用的概念来保持代码整洁 和可读性。

例如,在一个电影评论网站如 烂番茄上,如图 *图 2.4所示,我们可以有一个用于列出电影的 app,一个用于列出新闻的 app,一个用于支付的 app,一个用于用户认证的 app,等等:

图 2.4 – Rotten Tomatoes 网站

图 2.4 – Rotten Tomatoes 网站

Django 中的应用就像网站的一部分。 你可以用一个单一的应用创建整个网站,但将其拆分成不同的应用,每个应用代表一个 明确的功能。

我们的 电影商店 网站将从一个应用开始。 随着我们的进展,我们将添加更多。 要添加应用,在终端中,通过按 Cmd+ C停止服务器。导航到顶级 <st c="8157">moviesstore</st> 文件夹(包含 <st c="8203">manage.py</st> 文件的文件夹)并在终端中运行以下命令:

对于 macOS,运行以下命令:

 python3 manage.py startapp home

对于 Windows,运行以下命令:

 python manage.py startapp home

将在项目中添加一个新的文件夹, <st c="8413">home</st>(见 图 2**.5)。 随着我们在本书中的进展,我们将解释 文件夹内的文件。

图 2.5 – 包含主页应用的 MOVIESSTORE 项目结构

图 2.5 – 包含主页应用的 MOVIESSTORE 项目结构

尽管我们的新 <st c="8771">主页</st> 应用存在于我们的 Django 项目中,但 Django 不会识别它,直到我们明确添加它。 为此,我们需要在 <st c="8900">settings.py</st>中指定它。所以,前往 <st c="8923">/moviesstore/settings.py</st>,在 <st c="8955">INSTALLED_APPS</st>下,你会看到已经存在六个内置应用

添加应用名称,如下所示高亮显示(每次创建新应用时都应这样做):

 …
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles', <st c="9306">'home',</st> ]
…

我们已经成功 创建了我们的第一个应用,并将其包含在我们的 Django 设置项目中。 现在,我们将在该应用内部创建和提供两个页面。

创建主页

在 Django 中创建一个简单的 页面或部分通常涉及 三个步骤:

  1. 配置 一个 URL。

  2. 定义一个视图函数 或类。

  3. 创建 一个模板。

让我们看看如何应用这些步骤来创建一个简单的“主页”,它将向 最终用户 显示“欢迎”信息。

配置 URL

Django URL (统一资源定位符)是用于将传入的 HTTP 请求映射到处理这些请求的适当视图函数或类的模式。 它们定义了 Django 项目的路由机制,指定了不同 URL 应调用哪些视图。 它们定义了 Django 项目的路由机制,指定了不同 URL 应调用哪些视图。

位于 <st c="10111">/moviesstore/urls.py</st> 的主 URL 配置文件目前包含以下代码: 以下代码:

 …
from django.contrib import admin
from django.urls import path
urlpatterns = [
    path('admin/', admin.site.urls),
]

当用户在浏览器中输入 URL(与我们的 Django 应用相关)时,请求首先通过 <st c="10396">/moviesstore/urls.py</st> 文件,它将尝试匹配 <st c="10450">path</st> 对象在 <st c="10465">urlpatterns</st> 中 – 例如,如果用户在浏览器中输入 <st c="10509">http://localhost:8000/admin</st> ,URL 将匹配 <st c="10578">admin/</st> 路径。 服务器将随后响应 Django 管理页面(如图图 2.6**所示),我们将在稍后探讨:

图 2.6 – /admin 路由 – 管理页面

图 2.6 – /admin 路由 – 管理页面

相反,如果用户输入 <st c="10875">localhost:8000/hello</st>,Django 将返回一个 <st c="10918">404 not found</st> 页面,因为 URL 配置文件中没有匹配的路径

创建主页的路径

创建页面自定义路径有两种方法: 以下方法:

  • 在项目级别的 URL 文件中创建路径( <st c="11148">/moviesstore/urls.py</st>

  • 在应用级别的 <st c="11191">urls.py</st> 文件中创建路径,该文件定义在应用级别( <st c="11235">/home/urls.py</st>

本书中我们将使用第二种选项,因为它允许我们保持 URL 的分离和有序。

<st c="11359">/home/</st>中,创建一个名为 <st c="11392">urls.py</st>的新文件。此文件将包含与主页应用 URL 相关的路径。 目前,请用以下内容填充它: 以下内容:

 from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='home.index'),
]

让我们解释一下 `之前的代码:

  • 我们导入 <st c="11667">path</st> 函数,该函数用于在 Django 中定义 URL 模式

  • 我们导入 <st c="11744">views</st> 文件。 在下一节中,我们将在 <st c="11798">views</st> 文件中实现一个 <st c="11824">index</st> 函数。 该函数将渲染一个包含“欢迎”消息的模板。

  • 我们定义了 <st c="11923">urlpatterns</st> 的 home 应用。 在这种情况下,在 <st c="11978">urlpatterns</st> 列表中,我们添加了一个带有 三个参数 的新路径对象:

    • 第一个参数, <st c="12063">''</st>, 代表 URL 模式本身。 在这种情况下,它是一个空字符串,表示根 URL。 这意味着当访问应用程序的根 URL(<st c="12229">localhost:8000/</st>)时,它将匹配 此路径。

    • 第二个参数, <st c="12295">views.index</st>, 指的是将处理 HTTP 请求的视图函数。 在这里, <st c="12377">views.index</st> 表示 <st c="12408">index</st> 函数位于 <st c="12430">views</st> 文件中,负责处理 请求。

    • 第三个参数, <st c="12504">name='home.index'</st>, 是 URL 模式的名称。 此名称用于唯一标识 URL 模式,并可以在 Django 项目的其他部分中引用,例如模板或其他 URL 模式。

现在,让我们继续定义 <st c="12744">views.index</st> 函数代码。

定义视图函数

Django 视图 是 Python 函数或类,它们接收网络请求并返回网络响应。 它们包含处理 HTTP 请求并生成适当 HTTP 响应的逻辑,通常是用于在用户 的网页浏览器中渲染的 HTML 内容。

我们的 home 应用已经包含一个 <st c="13094">views.py</st> 文件;让我们利用它并做一些简单的修改。 <st c="13171">/home/views.py</st>中,添加以下 粗体中:

 from django.shortcuts import render <st c="13251">def index(request):</st>
 <st c="13270">return render(request, 'home/index.html')</st>

让我们解释一下 之前的代码:

  • 默认情况下, <st c="13362">views</st> 文件导入 <st c="13385">render</st> 函数,该函数用于渲染模板并返回包含 <st c="13473">渲染内容</st> 的 HTTP 响应。

  • 我们定义一个<index>函数。该函数接受一个参数,<request>,它代表服务器接收到的 HTTP 请求。

  • 最后,<index>函数返回一个渲染的模板。<render>函数将<request>作为第一个参数,第二个参数(<'home/index.html'>)表示要渲染的模板文件的路径。在下一节中,我们将创建该模板。

我们现在已经将''路径与适当的<views.index>函数连接起来,但我们缺少<views.index>函数和<home/index.html>模板之间的连接。所以,让我们实现模板。

创建一个模板

Django 模板是包含 HTML 和Django 模板语言DTL)语法的文本文件,它描述了网页的结构。Django 模板允许你通过在 HTML 标记中插入变量、循环、条件和其他逻辑来动态生成 HTML 内容。

我们的“主页”应用不包括存储模板的位置,所以让我们创建它。在<home>中,创建一个<templates>文件夹。然后,在<home/templates>中,创建一个<home>文件夹。

现在,在<home/templates/home>中,创建一个新文件,<index.html>。这将是我们“主页”的完整 HTML 页面。目前,用以下内容填充它:

 <!DOCTYPE html>
<html>
<head>
  <title>Home page</title>
</head>
<body>
  <h1>Welcome to the Home Page</h1>
</body>
</html>

该文件包含一个简单的 HTML 代码,带有“欢迎”消息。

注意

我们建议将您的应用程序模板存储在以下目录结构下<st c="15004">app_name/templates/app_name/my_template.html</st>。有时,不同的应用程序可能包含具有相同名称的模板,这可能导致模板解析中的潜在名称冲突。 通过使用之前的策略,您可以在不同的 Django 应用程序中定义具有相同名称的模板,而不会出现任何潜在的 名称冲突。

我们已经完成了 URL、视图函数和模板之间的连接。 然而,Django 不知道如何使用我们的 <st c="15448">/home/urls.py</st> 文件。 所以,让我们将此文件连接到我们的主要 URL 配置文件,然后我们将完成 拼图。

将项目级 URL 文件与应用程序级 URL 文件连接

<st c="15642">/moviesstore/urls.py</st>中,添加以下 粗体

 …
from django.contrib import admin
from django.urls import path<st c="15754">, include</st> urlpatterns = [
    path('admin/', admin.site.urls), <st c="15813">path('', include('home.urls')),</st> ]

让我们解释一下 之前的代码:

  • 我们修改代码以导入 <st c="15917">include</st> 函数,该函数用于包含来自其他 URL 配置文件的 URL。

  • 我们将一个新的路径对象添加到 <st c="16033">urlpatterns</st> 列表中。 空字符串 <st c="16069">''</st>表示包含来自 <st c="16126">home.urls</st> 文件》的 URL 的基本 URL。

现在,保存这些文件,运行服务器,然后返回到 <st c="16196">http://localhost:8000</st>;你应该 看到显示的主页(图 2**.7):

图 2.7 – 主页

图 2.7 – 主页

注意

当我们对文件进行更改并保存时,Django 会观察文件更改并使用它们重新加载服务器。 因此,每次代码更改时,我们不必手动重新启动服务器。

现在我们的“主页”已经启动并运行,让我们重复这个过程来创建“关于”页面。

创建关于页面

现在我们学习了如何 创建一个简单的页面,让我们重复这个过程 来创建关于页面。 我们将遵循以下 三个步骤:

  1. 配置 关于 URL。

  2. 定义 关于函数。

  3. 创建关于模板。

让我们开始。

配置关于页面的 URL

<st c="16938">/home/urls.py</st>中,添加以下路径 (以下内容加粗) bold

 from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='home.index'),
 <st c="17093">path('about', views.about, name='home.about'),</st> ]

因此,如果 URL 与 <st c="17166">/about</st> 路径匹配,它将执行 <st c="17199">about</st> 函数,该函数定义在 <st c="17229">views</st> 文件中。

定义关于函数

<st c="17268">/home/views.py</st>中,添加以下内容 (以下内容加粗) bold

 from django.shortcuts import render
def index(request):
    return render(request, 'home/index.html') <st c="17410">def about(request):</st>
<st c="17476">about</st> function is similar to the <st c="17509">index</st> function. This function renders the <st c="17551">'home/about.html'</st> template, which will be implemented next.
			<st c="17610">Creating about template</st>
			<st c="17634">Now, in</st> `<st c="17643">/home/templates/home/</st>`<st c="17664">, create a</st> <st c="17675">new file,</st> `<st c="17685">about.html</st>`<st c="17695">. This file contains the HTML for the about page.</st> <st c="17745">For now, fill it in with</st> <st c="17770">the following:</st>

<head> <title>关于页面</title> </head> <body>

欢迎使用关于页面

</body>

			<st c="17906">Save the files, and when you navigate to</st> `<st c="17948">localhost:8000/about</st>`<st c="17968">, it will show the about page (</st>*<st c="17999">Figure 2</st>**<st c="18008">.8</st>*<st c="18010">):</st>
			![Figure 2.8 – The about page](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_02_08.jpg)

			<st c="18090">Figure 2.8 – The about page</st>
			<st c="18117">Note</st>
			<st c="18122">When we executed the command to create the home app, some folders and files were automatically created for us.</st> <st c="18234">For the home app, we won’t use many of them.</st> <st c="18279">So, you can optionally delete the following folders and files to keep your application clean and simple –</st> `<st c="18385">migrations/</st>`<st c="18396">,</st> `<st c="18398">admin.py</st>`<st c="18406">,</st> `<st c="18408">models.py</st>`<st c="18417">,</st> <st c="18419">and</st> `<st c="18423">tests.py</st>`<st c="18431">.</st>
			<st c="18432">We quickly created our second page, “about.” Now, we hope you have a better understanding of how URLs, views, and</st> <st c="18547">templates connect.</st>
			<st c="18565">Summary</st>
			<st c="18573">In this chapter, we discussed the Django project structure.</st> <st c="18634">We analyzed some of the most important project folders, files, and their functionalities.</st> <st c="18724">We saw how a web project can be composed of several applications, and we learned how to create a Django app.</st> <st c="18833">We also learned how URLs, views, and templates connect to create web pages.</st> <st c="18909">We created a couple of pages and loaded them into our local web server.</st> <st c="18981">In the next chapter, we will see how to improve the look and feel of our Django applications by using base templates and a</st> <st c="19104">CSS framework.</st>

第四章:3

设计基本模板

Django 项目可以包含数十或数百个模板文件。 有时,这些文件可能包含重复的 HTML 和 CSS 代码,这会影响项目的可维护性。 在本章中,我们介绍了 基本模板 的概念以及如何使用它们来减少重复的模板代码。 我们还将通过设计一个包含页眉和页脚以及指向不同页面的链接的基本模板来改进我们应用程序的外观和感觉。

在本章中,我们将涵盖以下主题: 以下主题:

  • 使用 Bootstrap 创建 基本模板

  • 更新 主页 以使用 基本模板

  • 更新 关于 页面以使用 基本模板

  • 添加一个 页眉部分

  • 添加一个 页脚部分

最终,你将了解基本模板的重要性以及如何使用它们来减少重复代码并改善你 Web 应用程序的外观和感觉。

技术要求

在本章中,我们将使用 Python 3.10+。此外,我们将在本书中使用 VS Code 编辑器,您可以从 以下位置下载 https://code.visualstudio.com/

本章的代码位于 https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter03/moviesstore

本章的 CiA 视频可以在 以下位置找到 https://packt.link/psU29

使用 Bootstrap 创建基本模板

我们目前 有两个模板(<st c="1390">index.html</st> <st c="1406">about.html</st>)重复了网站的结构和一些 HTML 标签。 目前,这似乎不是一个严重的问题。 然而,一旦应用程序开始增长,我们将有大量的重复 HTML 代码 散布在数十个模板文件中。 为了避免这个问题,我们将创建一个包含网站主要结构的单个文件的基本模板,其他模板将扩展这个 基本模板。

介绍 Bootstrap

Bootstrap 是开发响应式和移动优先网站最流行的 CSS 框架(见图 3.1)。Bootstrap 提供了一套 HTML、CSS 和 JavaScript 组件和实用工具,开发者可以使用它们快速构建现代用户界面。 对于 Django 项目,如果开发者愿意,可以从头开始设计用户界面。 然而,由于本书不是关于用户界面的,我们将利用 CSS 框架(如 Bootstrap)并使用其中的一些元素和示例来创建看起来专业的产品。 您可以在https://getbootstrap.com/了解更多关于 Bootstrap 的信息。 .

图 3.1 – Bootstrap 网站

图 3.1 – Bootstrap 网站

介绍 Django 模板语言(DTL)

我们将构建基础模板,作为 Bootstrap、HTML、CSS、JavaScript 和DTL的组合。

DTL 是 Django Web 框架中用于构建动态网页的模板语言(https://docs.djangoproject.com/en/5.0/topics/templates/)。它旨在将表示层与应用程序的业务逻辑分离,促进编写干净和可维护的代码。

Django 模板语言的关键特性包括以下内容:

  • <st c="3417">{{</st> <st c="3420">变量</st>.

  • <st c="3538">{% %}</st>. 模板标签允许循环、条件和其他控制流语句。 例如, <st c="3638">{% if condition %} ...</st> {% endif %}`.

  • <st c="3721">{# #}</st> 在最终的输出 HTML 中不会被渲染。

  • 模板继承:Django 模板支持继承,允许创建定义页面整体结构和布局的基础模板,子模板继承并覆盖特定的块或部分。

创建基础模板

基本模板将作为“全局”模板(将在所有页面和应用程序中使用)。 因此,我们将将其添加到我们的主要 项目文件夹中。 <st c="4190">moviesstore/</st> 文件夹中(包含 <st c="4243">settings.py</st> 文件的目录),创建一个名为 <st c="4277">templates</st>的文件夹。在该文件夹中,创建一个名为 <st c="4333">base.html</st>的文件。目前,用以下内容填充它:

 <!DOCTYPE html>
<html>
  <head>
    <title>{{ template_data.title }}</title>
    <link href=
      "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/
      dist/css/bootstrap.min.css" rel="stylesheet"
      crossorigin="anonymous">
    <link rel=
      "stylesheet"  href="https://cdnjs.cloudflare.com/
      ajax/libs/font-awesome/6.1.1/css/all.min.css">
    <link href=
      "https://fonts.googleapis.com/
      css2?family=Poppins:wght@300;400;500;600;700&display=
      swap" rel="stylesheet">
    <script src=
      "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/
      js/bootstrap.bundle.min.js"crossorigin="anonymous">
    </script>
    <meta name="viewport" content="width=device-width,
      initial-scale=1" />
  </head>
  <body>
    <!-- Header -->
    <!-- Header -->
    <div>
      {% block content %}
      {% endblock content %}
    </div>
    <!-- Footer -->
    <!-- Footer -->
  </body>
</html>

前面的文件包含我们网站的基 HTML 结构。 让我们回顾一下前面代码的一些重要方面:

  • <st c="5286">head</st> 标签包含 <st c="5308">title</st> 标签,该标签使用 DTL 双大括号来显示变量的信息(<st c="5396">{{ template_data.title }}</st>)。 稍后,我们将看到如何 从视图传递该变量到这个模板。 它还包含一些链接和脚本,用于包含 Bootstrap 以及我们网站的字体。 我们从以下 网站 https://getbootstrap.com/docs/5.3/getting-started/introduction/#cdn-links中获取了一些链接。

  • <st c="5714">body</st> 标签包含一个 HTML 注释,指示头部(我们将在该位置包含头部)的位置和 <st c="5842">div</st>,其中包含一些 DTL 模板标签。 <st c="5893">{% block %}</st> <st c="5909">{% endblock %}</st> 是用于模板继承的模板标签。 这是一个定义名为 <st c="6023">content</st>的块的模板标签。块是模板中的占位符,可以被子模板覆盖。 此块内的内容将被扩展此模板的子模板中定义的内容所替换(我们将在后面看到它的实际应用)。 它还包含一个 HTML 注释,指示脚部的位置(我们将在该位置包含脚部)。

注册基本模板

最后,我们需要 在应用设置中 注册 <st c="6453">moviesstore/templates</st> 文件夹。 我们需要导入 <st c="6533">os</st> 模块,并将新的模板路径包含在我们的 <st c="6584">/moviesstore/settings.py</st> 文件中。 <st c="6618">/moviesstore/settings.py</st>中,添加以下 粗体内容:

 … <st c="6672">import os</st> from pathlib import Path
…
ROOT_URLCONF = 'moviesstore.urls'
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django. DjangoTemplates',
        'DIRS': [<st c="6831">os.path.join(BASE_DIR,</st>
 <st c="6854">'moviesstore/templates')</st>],
        'APP_DIRS': True,
        …

现在我们已经定义了基本模板的结构,让我们更新 主页 关于 页面以扩展 此模板。

更新主页以使用基本模板

新的主页将 扩展基本模板;它将包含带有图像的背景,并且将包含自定义 CSS。 让我们创建新的 主页。

创建新的索引模板

<st c="7259">/home/templates/home/index.html</st>中,将整个模板代码 替换为 以下内容:

 {% extends 'base.html' %}
{% block content %}
<header class="masthead bg-index text-white text-center
           py-4">
  <div class="container d-flex align-items-center flex-
  column pt-2">
    <h2>Movies Store</h2>
    <p>Your Ticket to Unlimited Entertainment!</p>
  </div>
</header>
<div class="p-3">
  <div class="container">
    <div class="row mt-3">
      <div class="col mx-auto text-center mb-3">
        <h4>Welcome to the best movie store!!</h4>
      </div>
    </div>
  </div>
</div>
{% endblock content %}

让我们解释一下 前面的代码:

  • 新的 <st c="7851">index.html</st> 文件现在扩展了 <st c="7883">base.html</st> 模板。

  • 内部的代码 <st c="7927">{% block content %}</st> <st c="7946">{% endblock content %}</st> 将被注入到 <st c="7994">div</st> <st c="8005">base.html</st> 模板文件中。 此代码定义了一些消息并使用了一些将在下面定义的自定义 CSS 类。

创建自定义 CSS 文件

<st c="8163">moviesstore/</st> 文件夹(包含 <st c="8216">settings.py</st> 文件的目录),创建一个名为 <st c="8258">static</st>的文件夹。在该文件夹中,创建一个名为 <st c="8291">css</st>的子文件夹。然后,在 <st c="8322">moviesstore/static/css/</st> 创建一个名为 <st c="8367">style.css</st>的文件。目前,用以下内容填充它:

 .bg-index{
  background: url("/static/img/background.jpg") no-repeat
    fixed;
  background-size: 100% auto;
}

前面的代码定义了一个名为 <st c="8566">bg-index</st>的 CSS 类,它将被用于在主页上显示图像作为背景。

存储图像

让我们也将 <st c="8689">background.jpg</st> 图像包含到我们的项目中。 <st c="8729">moviesstore/static</st>中,创建一个名为 img<st c="8775">的文件夹。然后,在</st> moviesstore/static/img/<st c="8809">中,从以下链接下载并存储</st> background.jpg` 图像: https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/blob/main/Chapter03/moviesstore/moviesstore/static/img/background.jpg (如图 图 3**.2所示)。

Figure 3.2 – Including a background image under the project structure

图 3.2 – 在项目结构下包含背景图像

服务静态文件

我们已经定义了一些静态文件,一个 CSS 文件和一个 JPG 文件。 为了能够在我们的应用程序中使用或显示它们,我们需要注册包含它们的文件夹。 <st c="9538">/</st>``<st c="9539">moviesstore/settings.py</st> 文件中添加以下代码:

 …
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' <st c="9623">STATICFILES_DIRS = [</st>
 <st c="9643">BASE_DIR / 'moviesstore/static/',</st>
<st c="9677">]</st>

更新基础模板以使用定制 CSS 和加载静态文件

我们还需要更新基础 模板以链接我们之前创建的定制 CSS,并且我们需要使用一个定制的 DTL 标签来加载静态文件。 <st c="9902">/moviesstore/templates/base.html</st>中,添加以下 粗体内容:

 <!DOCTYPE html>
<html> <st c="9986">{% load static %}</st> <head>
    <title>{{ template_data.title }}</title>
    … <st c="10054"><link rel="stylesheet" type="text/css"</st>
 <st c="10092">href="{% static 'css/style.css' %}"></st> <meta name="viewport"
          content="width=device-width, initial-scale=1" />
  </head>
  …

在前面的代码中,使用了 <st c="10236">load static</st> 模板标签来在 <st c="10301">base.html</st> 模板中加载静态文件。 一旦我们使用了这个标签,我们就可以使用 <st c="10364">static</st> 模板标签来引用要加载的特定静态文件。 它将根据 <st c="10481">STATICFILES_DIRS</st> 文件夹的位置来搜索静态文件。

现在,保存这些文件,运行服务器,然后回到 <st c="10569">http://localhost:8000</st>;你应该会看到新的主页被显示出来(图 3**.3)。 检查标签标题是否没有出现,因为我们需要从视图函数发送 <st c="10716">template_data.title</st> 变量到模板中(这将在下一部分进行)。

Figure 3.3 – The new home page with the missing tab title

图 3.3 – 缺少标签页标题的新主页

注意

如果你在加载背景图片时遇到问题,我们建议你停止服务器并重新运行,或者清除浏览器缓存。 此外,尝试直接从浏览器访问图像文件, 检查图像是否已正确加载 http://localhost:8000/static/img/background.jpg)。

更新视图索引函数

最后,让我们从视图 函数传递标题到 模板。 <st c="11394">/home/views.py</st>中,添加以下内容,并用粗体标出:

 from django.shortcuts import render
def index(request): <st c="11493">template_data = {}</st><st c="11511">template_data['title'] = 'Movies Store'</st> return render(request, 'home/index.html'<st c="11592">, {</st>
 <st c="11595">'template_data': template_data})</st> def about(request):
    return render(request, 'home/about.html')

让我们解释一下 之前的代码:

  • 我们创建了一个名为 <st c="11761">template_data</st>的 Python 字典。 当我们需要从视图函数 传递信息到模板时,我们总是使用这个字典。

  • 我们在 <st c="11911">template_data</st> 字典中添加了一个名为 <st c="11898">title</st> 的键。 <st c="11937">title</st> 将用于定义浏览器标签页标题。 请记住 <st c="12003">template_data.title</st> <st c="12038">base.html</st> 模板中 被使用。

  • 我们修改了 <st c="12072">render</st> 函数以传递第三个参数。 这次我们传递了 <st c="12136">template_data</st> 变量,它将在 <st c="12195">home/index.html</st> 模板 或它扩展的模板中 可用。

图 3**.4 显示了带有适当浏览器 标签页标题的更新后的 主页

图 3.4 – 带有适当浏览器标签页标题的新主页

图 3.4 – 带有适当浏览器标签页标题的新主页

更新关于页面以使用基本模板

新的 关于 页面也将扩展基本模板,并且它 将包含关于页面的示例文本和图像。

创建新的关于模板

<st c="12742">/home/templates/home/about.html</st>中,将整个模板代码 替换为以下内容: 以下内容:

 {% extends 'base.html' %}
{% block content %}
{% load static %}
<div class="p-3">
  <div class="container">
    <div class="row mt-3">
      <div class="col-md-6 mx-auto mb-3">
        <h2>About</h2>
        <hr />
        <p>
           At Movies Store, we offer a vast digital library
           that spans across genres, ensuring there's
           something for every movie lover. Browse our
           extensive collection of films, including the
           latest releases, timeless classics, and hidden
           gems. With just a few clicks, you can rent or
           purchase your favorite titles and instantly
           stream them in high-definition quality. </p>
        <p>
          Discover the convenience of our digital platform,
          where you have the flexibility to watch movies
          on your preferred device, whether it's a smart
          TV, tablet, or smartphone. With our intuitive
          search and recommendation features, finding your
          next movie night pick has never been easier. </p>
      </div>
      <div class="col-md-6 mx-auto mb-3 text-center">
        <img src="img/about.jpg' %}"
             class="max-width-100"
             alt="about" />
      </div>
    </div>
  </div>
</div>
{% endblock content %}

让我们解释一下 之前的代码:

  • 新的 <st c="13899">about.html</st> 文件现在扩展了 <st c="13931">base.html</st> 模板。

  • 我们使用 <st c="13958">{% block content %}</st> <st c="13977">{% endblock content %}</st> 来在 <st c="14039">div</st> <st c="14050">base.html</st> 模板文件中注入适当的 HTML 代码。 此代码定义了一个关于页面的段落,并显示一个将要 存储的图片。

  • 我们同样使用 <st c="14184">{% load static %}</st> 标签,因为这个模板通过使用 <st c="14260">static</st> 模板标签来加载自定义图片。

存储 about.jpg 图片

让我们也把 <st c="14332">about.jpg</st> 图片添加到我们的 项目中。 <st c="14367">moviesstore/static/img/</st>目录下,从以下 链接下载并存储<st c="14424">about.jpg</st> 图片: https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/blob/main/Chapter03/moviesstore/moviesstore/static/img/about.jpg

更新关于函数的视图

最后,让我们将从视图 关于函数传递的标题到模板中。 <st c="14704">/home/views.py</st>中,添加以下 加粗内容:

 from django.shortcuts import render
…
def about(request): <st c="14805">template_data = {}</st><st c="14823">template_data['title'] = 'About'</st> return render(request,
                  'home/about.html'<st c="14897">,</st>
<st c="14948">index</st> function, we define the <st c="14978">template_data</st> dictionary and create the proper <st c="15025">title</st> key with its value. Then, we pass the <st c="15069">template_data</st> variable to the templates.
			<st c="15109">Now, save those files, run the server, and go to</st> [<st c="15159">http://localhost:8000/about</st>](http://localhost:8000/about)<st c="15186">; you should see the new</st> **<st c="15212">About</st>** <st c="15217">page displayed (</st>*<st c="15234">Figure 3</st>**<st c="15243">.5</st>*<st c="15245">).</st>
			![Figure 3.5 – New About page](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_03_05.jpg)

			<st c="15937">Figure 3.5 – New About page</st>
			<st c="15964">Now that we have updated the home and</st> **<st c="16003">About</st>** <st c="16008">pages, let’s improve the base template by adding a header</st> <st c="16067">section that includes the website</st> <st c="16101">menu options.</st>
			<st c="16114">Adding a header section</st>
			<st c="16138">To complete the</st> `<st c="16155">base.html</st>` <st c="16164">template, we need to include a header section and a footer section.</st> <st c="16233">Let’s start</st> <st c="16245">with</st> <st c="16250">the header.</st>
			<st c="16261">Updating the base template</st>
			<st c="16288">In</st> `<st c="16292">/moviesstore/templates/base.html</st>`<st c="16324">, add the</st> <st c="16333">following</st> <st c="16344">in bold:</st>

<body>
<!-- Header --> <st c="16377"><nav class="p-3 navbar navbar-dark bg-dark</st>

navbar-expand-lg">

<a class="navbar-brand"

href="{% url 'home.index' %}">

<img src="img/logo.png' %}" alt="logo"

height="40" />

<button class="navbar-toggler" type="button"

data-bs-toggle="collapse"

data-bs-target="#navbarNavAltMarkup"

aria-controls="navbarNavAltMarkup"

aria-expanded="false"

aria-label="Toggle navigation">

<div class="collapse navbar-collapse"

id="navbarNavAltMarkup"

…</st>**

 **<st c="17047">We included a responsive</st> `<st c="17073">navbar</st>` <st c="17079">between the</st> `<st c="17092">Header</st>` <st c="17098">HTML comments.</st> <st c="17114">This responsive navbar includes a</st> `<st c="17148">logo.png</st>` <st c="17156">file that links to the</st> `<st c="17180">home.index</st>` <st c="17190">URL, and includes an</st> `<st c="17212">About</st>` <st c="17217">text that links to the</st> `<st c="17241">home.about</st>` <st c="17251">URL.</st> <st c="17257">Check that we used the</st> `<st c="17280">url</st>` <st c="17283">template tag, as this tag</st> <st c="17309">links to the specified URL</st> <st c="17337">pattern name.</st>

<st c="17350">Note</st>

<st c="17355">The construction of the previous header section is inspired by the Bootstrap</st> `<st c="17433">navbar</st>` <st c="17439">component.</st> <st c="17451">You can take a look at this component and its available options at this</st> <st c="17523">link:</st> [<st c="17529">https://getbootstrap.com/docs/5.3/components/navbar/</st>](https://getbootstrap.com/docs/5.3/components/navbar/)<st c="17581">.</st>

## <st c="17582">Storing the logo image</st>

<st c="17605">Let’s include the</st> `<st c="17624">logo.png</st>` <st c="17632">image in</st> <st c="17641">our project.</st> <st c="17655">In</st> `<st c="17658">moviesstore/static/img/</st>`<st c="17681">, download and store the</st> `<st c="17706">logo.png</st>` <st c="17714">image from this</st> <st c="17731">link:</st> [<st c="17737">https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/blob/main/Chapter03/moviesstore/moviesstore/static/img/logo.png</st>](https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/blob/main/Chapter03/moviesstore/moviesstore/static/img/logo.png)<st c="17877">.</st>

## <st c="17878">Updating the style.css</st>

<st c="17901">Finally, let’s include a couple of CSS</st> <st c="17940">classes in our custom CSS file.</st> <st c="17973">In</st> `<st c="17976">/moviesstore/static/css/style.css</st>`<st c="18009">, add the following in bold at the end of</st> <st c="18051">the file:</st>

.navbar a.nav-link {

color: #FFFEF6 !important;

}

.bg-dark {

background-color: #2E2E2E !important;

}


<st c="18161">Now, save those files, run the server, and go to</st> [<st c="18210">http://localhost:8000/</st>](http://localhost:8000/)<st c="18232">; you should see the home page</st> <st c="18264">with the new header section (</st>*<st c="18293">Figure 3</st>**<st c="18302">.6</st>*<st c="18304">).</st>

			![Figure 3.6 – The home page with the header section](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_03_06.jpg)

<st c="18415">Figure 3.6 – The home page with the header section</st>

<st c="18465">This new header section is responsive.</st> <st c="18505">If you reduce the browser window width, you will see a responsive navbar, thanks to the use of different Bootstrap classes (see</st> *<st c="18633">Figure 3</st>**<st c="18641">.7</st>*<st c="18643">).</st>

			![Figure 3.7 – Home page with a responsive navbar](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_03_07.jpg)

<st c="18737">Figure 3.7 – Home page with a responsive navbar</st>

<st c="18784">The base template now includes a proper header section.</st> <st c="18841">Let’s finalize this template by adding a</st> <st c="18882">footer section.</st>

# <st c="18897">Adding a footer section</st>

<st c="18921">Let’s complete the website</st> <st c="18949">structure with the inclusion of</st> <st c="18981">a footer.</st>

## <st c="18990">Updating the base template</st>

<st c="19017">In</st> `<st c="19021">/moviesstore/templates/base.html</st>`<st c="19053">, add the</st> <st c="19062">following</st> <st c="19073">in bold:</st>

<!-- Footer --> <st c="19099"><section class="p-3 ms-footer d-none d-md-block"></st>

<div class="col-md-6 col-lg-6 col-xl-6

mx-auto mb-4">

电影商店


欢迎来到电影商店,您首选的在线

其他任何地方的电影冒险的目的地!

从家中深入电影的世界! 探索电影世界的奥秘!

在家中享受我们的用户-

一个友好且沉浸式的在线电影商店。

<div class="col-md-3 col-lg-3 col-xl-3

mx-auto mb-4">

链接


<a class="nav-link"

href="{% url 'home.about' %}">

关于

<div class="col-md-3 col-lg-3 col-xl-3 mx-auto

mb-4">

联系


150-2345 东京都,日本

**

info@moviesstore.com

+81 03-3333-3333

**

…</st>******

 ****<st c="20379">We included a footer section with</st> <st c="20413">information on the website, some links, and the book’s author names and links to their</st> <st c="20501">X accounts.</st>

## <st c="20512">Updating the style.css</st>

<st c="20535">Finally, let’s include some custom</st> <st c="20571">CSS classes.</st> <st c="20584">In</st> `<st c="20587">/moviesstore/static/css/style.css</st>`<st c="20620">, add the following in bold at the end of</st> <st c="20662">the file:</st>

.ms-footer {

background-color: #202020;

}

.ms-footer p {

颜色: #7F7F7F;

字体大小: 13 像素;

}

.ms-footer a:hover {

颜色: #6ab43e;

文本装饰: 无;

}

.ms-footer-bottom span{

字体大小: 13 像素;

行高: 38 像素;

}

.ms-footer-bottom a {

颜色: #6ab43e;

文本装饰: 无;

}

.ms-footer-bottom a:hover {

颜色: #fff;

}


 **<st c="20989">Now, save those files, run the server, and go to</st> [<st c="21038">http://localhost:8000/</st>](http://localhost:8000/)<st c="21060">; you should see the home page</st> <st c="21092">with the new footer section (</st>*<st c="21121">Figure 3</st>**<st c="21130">.8</st>*<st c="21132">).</st>

			![Figure 3.8 – The home page with the footer section](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_03_08.jpg)

<st c="21315">Figure 3.8 – The home page with the footer section</st>

<st c="21365">You can also click the</st> **<st c="21389">About</st>** <st c="21394">link, and you will see the</st> **<st c="21422">About</st>** <st c="21427">page with the same</st> <st c="21447">website structure.</st>

# <st c="21465">Summary</st>

<st c="21473">In this chapter, we learned how to create base templates that reduce duplicated code.</st> <st c="21560">We improved our application interface with the inclusion of a header and footer, and we learned how to manage static files.</st> <st c="21684">We redesigned the home and</st> **<st c="21711">About</st>** <st c="21716">pages to extend the base template and created proper links to those pages.</st> <st c="21792">In the next chapter, we’ll learn how to start</st> <st c="21838">managing movies.</st>******** 

第五章:使用虚拟数据创建电影应用

目前,我们的项目包含一个包含几个显示静态信息的部分的应用程序。Web 应用程序更复杂。在本章中,我们将学习如何开发更复杂的应用程序,例如电影应用。电影应用将用于列出电影,并允许用户点击它们在单独的页面上显示其数据。目前,我们将使用虚拟数据来模拟电影数据。

在本章中,我们将介绍以下主题:

  • 创建电影应用

  • 使用虚拟数据列出电影

  • 列出单个电影

  • 在基础模板中添加链接

最后,我们将了解如何创建更复杂的多吉安应用以及如何在那些应用中管理信息。

技术要求

在本章中,我们将使用 Python 3.10+。此外,我们将在本书中使用VS Code编辑器,您可以从code.visualstudio.com/下载。

本章的代码位于github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter04/moviesstore

本章的 CiA 视频可以在packt.link/WmJR1找到

创建电影应用

目前,我们有一个包含在HomeAbout页面之间导航逻辑的首页应用。现在,我们将开始设计和实现电影逻辑。我们更喜欢将此逻辑与首页应用分离。因此,让我们创建一个新的 Django 应用。我们将遵循以下步骤:(i)创建电影应用,(ii)将电影应用添加到设置中,(iii)将电影 URL 文件包含在项目级别的 URL 文件中。

创建电影应用

导航到顶级<st c="1664">moviesstore</st>文件夹(包含<st c="1710">manage.py</st>文件的文件夹)并在终端中运行以下命令:

对于 macOS,运行以下命令:

 python3 manage.py startapp movies

对于 Windows,运行以下命令:

 python manage.py startapp movies

图 4**.1 显示了新的项目结构。 请确认它与您当前的 文件夹结构相匹配。

图 4.1 – 包含电影应用的 MOVIESSTORE 项目结构

图 4.1 – 包含电影应用的 MOVIESSTORE 项目结构

将电影应用添加到设置中

请记住,对于 每个新创建的应用,我们必须在 <st c="2326">settings.py</st> 文件中注册它。 <st c="2347">/moviesstore/settings.py</st>中,在 <st c="2379">INSTALLED_APPS</st>下,添加以下内容(加粗):

 …
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'home', <st c="2612">'movies',</st> ]
…

在项目级别的 URL 文件中包含电影 URL 文件

<st c="2689">/moviesstore/urls.py</st>中,添加以下内容(加粗):

 …
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('home.urls')), <st c="2891">path('movies/', include('movies.urls')),</st> ]

类似于包含 <st c="2965">home.urls</st> 文件,我们包含 <st c="2996">movies.urls</st> 文件,该文件将包含与电影应用相关的 URL。 <st c="3102">movies.urls</st> 文件中定义的所有 URL 都将包含一个 <st c="3134">movies/</st> 前缀(如前所述路径中定义的)。 我们将在稍后创建 <st c="3203">movies.urls</st> 文件。

现在我们已经创建并包含了电影应用,我们准备编写此应用的功能。 让我们从列出电影开始。

使用虚拟数据列出电影

列出电影涉及一系列步骤,类似于 我们在实现 <st c="3592">views</st> <st c="3597">index</st> 函数时遵循的步骤,以及 创建一个 <st c="3642">movies</st> 索引模板。

配置电影 URL

<st c="3688">/movies/</st>中,创建一个名为 <st c="3707">urls.py</st>的新文件。此文件将包含关于电影应用 URL 的路径。 目前,请用以下内容填充它: 以下内容:

 from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='movies.index'),
]

我们定义了一个 <st c="3966">''</st> 路径,但请记住,项目级别的 URL 文件为该文件定义了一个 <st c="4031">/movies</st> 前缀。 因此,如果一个 URL 与 <st c="4086">/movies</st> 路径匹配,它将执行在 <st c="4120">views</st> 文件中定义的 <st c="4150">index</st> 函数。 我们将接下来实现 <st c="4184">index</st> 函数。

定义视图索引函数

<st c="4242">/movies/views.py</st> 文件中,添加以下内容(加粗):

 from django.shortcuts import render <st c="4323">movies = [</st>
 <st c="4333">{</st>
 <st c="4335">'id': 1, 'name': 'Inception', 'price': 12,</st>
 <st c="4378">'description': 'A mind-bending heist thriller.'</st>
 <st c="4426">},</st>
 <st c="4429">{</st>
 <st c="4431">'id': 2, 'name': 'Avatar', 'price': 13,</st>
 <st c="4471">'description': 'A journey to a distant world and</st>
 <st c="4520">the battle for resources.'</st>
 <st c="4547">},</st>
 <st c="4550">{</st>
 <st c="4552">'id': 3, 'name': 'The Dark Knight', 'price': 14,</st>
 <st c="4601">'description': 'Gothams vigilante faces the Joker.'</st>
 <st c="4653">},</st>
 <st c="4656">{</st>
 <st c="4658">'id': 4, 'name': 'Titanic', 'price': 11,</st>
 <st c="4699">'description': 'A love story set against the</st>
 <st c="4744">backdrop of the sinking Titanic.',</st>
 <st c="4779">},</st>
<st c="4782">]</st>
<st c="4784">def index(request):</st>
 <st c="4803">template_data = {}</st>
 <st c="4822">template_data['title'] = 'Movies'</st>
 <st c="4856">template_data['movies'] = movies</st>
 <st c="4889">return render(request, 'movies/index.html',</st>
 <st c="4933">{'template_data': template_data})</st>

让我们解释一下之前的代码:

  • 我们定义了一个名为 <st c="5030">movies</st> 的变量。这个变量是一个字典列表,其中每个字典代表一部特定电影的信息。 例如,在索引 <st c="5172">0</st> 处,我们有 id=1 的电影(即 <st c="5208">Inception</st> 电影)。 我们有四部虚拟电影。 我们将在后续章节中从 SQLite 数据库中检索电影数据。

  • 我们还有一个 <st c="5342">index</st> 函数。 此函数将渲染 <st c="5388">movies/index.html</st> 模板,但首先,它将页面标题和电影完整列表传递给该模板。

创建电影索引模板

<st c="5532">/movies/</st> 目录下,创建一个 <st c="5555">templates</st> 文件夹。 然后,在 <st c="5582">/movies/templates/</st> 目录下,创建一个 <st c="5611">movies</st> 文件夹。

现在,在 <st c="5634">/movies/templates/movies/</st> 目录下,创建一个新文件,<st c="5680">index.html</st>。目前,请用以下内容填充它:

 {% extends 'base.html' %}
{% block content %}
{% load static %}
<div class="p-3">
  <div class="container">
    <div class="row mt-3">
      <div class="col mx-auto mb-3">
        <h2>List of Movies</h2>
        <hr />
      </div>
    </div>
    <div class="row">
      {% for movie in template_data.movies %}
      <div class="col-md-4 col-lg-3 mb-2">
        <div class="p-2 card align-items-center pt-4">
          <img src="img/about.jpg' %}"
            class="card-img-top rounded">
          <div class="card-body text-center">
            {{ movie.name }}
          </div>
        </div>
      </div>
      {% endfor %}
    </div>
  </div>
</div>
{% endblock content %}

让我们解释一下之前的代码:

  • 我们扩展了 <st c="6327">base.html</st> 模板。

  • 我们定义了一个带有文本 <st c="6389">List</st> <st c="6394">of Movies</st> 的标题元素。

  • 我们使用 DTL <st c="6420">for</st> 模板 标签遍历每部电影,并显示电影名称。 目前,我们为所有电影显示默认图片;我们将在后续章节中上传并显示每部电影的正确图片。

注意

我们以 Bootstrap 卡片组件为基础来设计电影显示的方式。 您可以在以下链接中找到更多信息: https://getbootstrap.com/docs/5.3/components/card/

现在,保存这些文件,运行 服务器,并访问 http://localhost:8000/movies;你应该看到新的 电影列表 页面(图 4**.2)。

图 4.2 – 电影列表页面

图 4.2 – 电影列表页面

我们现在能够看到所有电影的详细信息。 现在,让我们实现一个列出 单个电影的功能。

列出单个电影

要列出单个 电影,我们将遵循以下步骤:(i)配置单个电影 URL,(ii)定义 <st c="7314">views</st> <st c="7319">show</st> 函数,(iii)创建电影 <st c="7359">show</st> 模板,以及(iv)在电影页面上添加单个电影链接。

配置单个电影 URL

<st c="7471">/movies/urls.py</st>中,添加以下加粗路径:

 from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='movies.index'), <st c="7624">path('<int:id>/', views.show, name='movies.show'),</st> ]

此路径与之前定义的路径略有不同。 <st c="7747"><int:id></st> 部分表示此路径期望从 URL 传递一个整数值,并且该整数值将与名为 <st c="7905">id</st>的变量相关联,该变量将用于标识要显示的电影数据。 例如,如果我们访问 <st c="7992">movies/1</st>,应用程序将显示 <st c="8058">id=1</st>的电影数据。 最后,该路径将执行在 <st c="8129">views</st> 文件中定义的 <st c="8100">show</st> 函数。 您可以在以下位置了解更多关于 Django URL 的信息: https://docs.djangoproject.com/en/5.0/topics/http/urls/

定义视图 show 函数

<st c="8277">/movies/views.py</st>中,在文件末尾添加以下加粗内容:

 … <st c="8346">def show(request, id):</st>
 <st c="8368">movie = movies[id - 1]</st>
 <st c="8391">template_data = {}</st>
 <st c="8410">template_data['title'] = movie['name']</st>
 <st c="8449">template_data['movie'] = movie</st>
 <st c="8480">return render(request, 'movies/show.html',</st>
 <st c="8523">{'template_data': template_data})</st>

让我们解释一下 之前的代码:

  • 我们定义了 <st c="8605">show</st> 函数。 此函数接受两个参数: <st c="8656">request</st> <st c="8668">id</st> (<st c="8672">id</st> 是从 URL 中收集的。

  • 然后,我们使用该 ID 提取电影数据。 我们减去一个单位,因为我们用 <st c="8801">id=1</st> 存储电影,在电影列表索引 <st c="8831">0</st>中,电影 <st c="8849">id=2</st> 在电影列表索引 <st c="8879">1</st>中,以此类推。

  • 最后,我们将电影名称和单个电影传递给 movies/show.html 模板。

创建电影展示模板

<st c="9019">/movies/templates/movies/</st>中,创建一个新文件, <st c="9065">show.html</st>。目前,用以下内容填充它: 以下内容:

 {% extends 'base.html' %}
{% block content %}
{% load static %}
<div class="p-3">
  <div class="container">
    <div class="row mt-3">
      <div class="col-md-6 mx-auto mb-3">
        <h2>{{ template_data.movie.name }}</h2>
        <hr />
        <p><b>Description:</b> {{
          template_data.movie.description }}</p>
        <p><b>Price:</b> ${{
          template_data.movie.price }}</p>
      </div>
      <div class="col-md-6 mx-auto mb-3 text-center">
        <img src="img/about.jpg' %}"
          class="rounded" />
      </div>
    </div>
  </div>
</div>
{% endblock content %}

前面的代码显示了 单个 电影信息。

在电影页面上添加单个电影链接

<st c="9725">/movies/templates/movies/index.html</st>中,添加以下内容,并加粗:

 …
      {% for movie in template_data.movies %}
      <div class="col-md-4 col-lg-3 mb-2">
        <div class="p-2 card align-items-center pt-4">
          <img src="img/about.jpg' %}"
            class="card-img-top rounded">
          <div class="card-body text-center"> <st c="10020"><a href="{% url 'movies.show' id=movie.id %}"</st>
 <st c="10065">class="btn bg-dark text-white"></st> {{ movie.name }} <st c="10115"></a></st> </div>
        </div>
      </div>
      {% endfor %}
      …

我们为每个电影名称添加了一个链接到每个单个电影页面。 我们使用了 <st c="10236">url</st> 模板标签来链接到指定的 URL 模式名称(<st c="10296">movie.show</st>)。 但我们还指定了一个要传递给 URL 的参数(<st c="10370">id=movie.id</st>)。 在这种情况下,它将 <st c="10417">id</st> 参数设置为 <st c="10437">id</st> 属性 <st c="10457">movie</st> 对象。 这对于需要动态部分的 URL 很有用,例如特定电影的详细信息。

现在,保存这些文件,运行服务器,并转到 <st c="10610">http://localhost:8000/movies</st>。你会看到每个电影名称都变成了可以点击的按钮。 点击电影名称,你将被重定向到单个电影页面(图 4**.3)。

图 4.3 – 单个电影页面

图 4.3 – 单个电影页面

我们可以列出所有电影并导航到单个电影;然而,我们还没有在电影部分添加链接。 让我们在下一段中实现这个链接。 下一节。

在基本模板中添加链接

最后,让我们在基本模板中添加电影链接 <st c="11237">/moviesstore/templates/base.html</st>中,在标题部分,添加以下内容,并加粗:

 …
        <div class="collapse navbar-collapse"
                   id="navbarNavAltMarkup">
          <div class="navbar-nav ms-auto navbar-ml">
            <a class="nav-link" href=
              "{% url 'home.about' %}">About</a> <st c="11489"><a class="nav-link" href=</st>
 <st c="11514">"{% url 'movies.index' %}">Movies</a></st> </div>
        </div>
        …

现在,保存这些文件,运行服务器,并访问 <st c="11617">http://localhost:8000/movies</st>。你将在页眉中看到新的 电影 菜单选项(图 4**.4)。

图 4.4 – 电影页面更新

图 4.4 – 电影页面更新

摘要

在本章中,我们回顾了如何创建 Django 应用程序。 我们创建了一个允许列出电影和单个电影的 电影应用程序。 我们学习了如何通过 URL 传递信息,如何创建虚拟数据,如何使用 <st c="12098">for</st> 模板标签,以及如何链接不同的页面。 我们希望这为我们的项目下一部分的学习打下坚实的基础,在那里我们将探讨更高级的主题,例如模型,以使我们的 网站数据库驱动。

第六章:5

与模型一起工作

在大多数 Web 应用程序中,将数据存储在数据库中是一种常见的做法。 在 Django 项目中,这涉及到与 Django 模型一起工作。 在本章中,我们将创建一个数据库模型(例如,电影)并且 Django 会为我们把这个模型转换成数据库表。 我们还将探索一个强大的内置管理界面,它提供了一种可视化的方式来管理 Django 项目的所有方面,例如用户和管理模型数据的变化。

在本章中,我们将涵盖以下主题: 以下主题:

  • 创建我们的 第一个模型

  • 安装 Pillow

  • 管理迁移

  • 访问 Django 管理界面

  • 配置 图像上传

  • 服务 存储的图像

  • 将电影模型 添加到管理界面

技术要求

在本章中,我们将使用 Python 3.10+。 此外,我们还将使用 VS Code 编辑器,您可以从 以下链接 https://code.visualstudio.com/下载。

本章的代码位于 以下链接 https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter05/moviesstore

本章的 CiA 视频可以在 以下链接 https://packt.link/HEeUM

创建我们的第一个模型

一个 Django 模型 是一个 Python 类,它代表了一个数据库表。 模型用于定义将存储在数据库中的数据的结构和行为。 每个模型类通常对应一个数据库表,并且类的每个实例代表该表中的一行。 有关 Django 模型的更多信息 可以在以下位置找到: https://docs.djangoproject.com/en/5.0/topics/db/models/.

我们可以创建如电影、评论和订单等模型,Django 会为我们将这些模型转换为数据库表

以下是 Django 模型基础:

  • 每个模型都是一个类,它 扩展了 <st c="1745">django.db.models.Model</st>

  • 每个模型属性代表一个 数据库列

  • 有了这些,Django 为我们提供了一组有用的方法来 创建、更新、读取和删除 (CRUD**)数据库中的模型信息

创建一个 Movie 模型

我们的第一个模型将是 Movie。 我们可以在每个项目应用中创建模型。 Movie 似乎与 <st c="2106">movies</st> 应用更相关,所以我们将 <st c="2140">Movie</st> 模型创建在那里。 <st c="2162">/movies</st>,我们有</st> models.py<st c="2192">文件,我们在其中为</st>movies` 应用创建模型。 打开该文件,并放置以下代码行: :

 from django.db import models
class Movie(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255)
    price = models.IntegerField()
    description = models.TextField()
    image = models.ImageField(upload_to='movie_images/')
    def __str__(self):
        return str(self.id) + ' - ' + self.name

让我们解释一下 之前的代码:

  • 首先,我们导入 <st c="2665">models</st> 模块,它提供了定义数据库模型所需的各类类和实用工具。

  • 接下来,我们定义一个名为 <st c="2792">Movie</st>的 Python 类,它继承自 <st c="2819">models.Model</st>。这意味着 <st c="2849">Movie</st> 是一个 Django 模型类。

  • <st c="2891">Movie</st> 类内部,我们定义了 几个字段:

    • <st c="2929">id</st>: 这是一个 <st c="2946">AutoField</st> 值,它会自动为数据库中添加的每个新记录增加其值。 <st c="3056">primary_key=True</st> `参数指定该字段是表的唯一主键,唯一标识 每条记录。

    • <st c="3176">name</st>: 这是一个 <st c="3194">CharField</st> 值,表示一个最大长度为 255 个字符的字符串字段。 它存储电影的名称。

    • <st c="3314">price</st>: 这是一个 <st c="3334">IntegerField</st> 值,用于存储整数值。 它代表电影的票价。

    • <st c="3418">description</st>: 这是一个 <st c="3443">TextField</st> 值,表示一个没有指定最大长度的文本字段。 它存储电影的文本描述。

    • <st c="3567">图像</st>:这是一个 <st c="3587">ImageField</st> 值,用于存储图像文件。 <st c="3633">upload_to</st> 参数指定了上传的图像将被存储的目录。 在这种情况下,上传的图像将被存储在 Django 项目媒体目录下的<st c="3767">movie_images/</st> 目录中。 媒体目录用于存储用户上传的文件,如图像、文档或其他媒体文件。 此目录在您的 Django 项目设置中指定(我们将在本章后面配置它)。

  • <st c="4055">__str__</st>:这是 Python 类中的一个特殊方法,它返回对象的字符串表示形式。 它将电影的<st c="4188">id</st> 值(转换为字符串)与一个连字符和电影名称连接起来。 此方法将在我们稍后在 Django 管理面板中显示电影时非常有用。

注意

Django 提供了许多其他模型 字段来支持常见类型,如日期、整数和电子邮件。 要了解各种类型及其使用方法,请参阅 Django 文档中的 <st c="4536">模型</st> 字段参考(https://docs.djangoproject.com/en/5.0/ref/models/fields/)。

安装 Pillow

因为我们使用 图像,所以我们需要 安装 Pillow(https://pypi.org/project/pillow/),这将为我们的 Python 解释器添加图像处理功能。

在终端中,停止服务器并执行 以下操作:

  • 对于 macOS,运行以下命令:

    <st c="4918">pip3 install pillow</st>
    
  • 对于 Windows,运行以下命令:

    <st c="4978">pip install pillow</st>
    

现在 Pillow 已经安装,让我们学习如何管理 Django 迁移。

管理迁移

Django 迁移 是 Django 的一个功能,允许 您管理数据库模式的变化——也就是说,随着您的 Django 项目的发展,对数据库表结构和其中数据的变化——随着时间的推移进行管理。

在 Django 中定义模型时,你实际上是在定义数据库表的结构。 然而,随着项目的增长和变化,你可能需要修改这些模型,例如添加新字段、删除字段或修改现有字段。 Django 迁移提供了一种以受控和一致的方式将更改传播到数据库模式的方法(作为一个版本 控制系统)。

要使用迁移,我们必须应用 默认迁移,创建自定义迁移,并应用 自定义迁移。

应用默认迁移

当前,当你在终端中运行服务器时,请注意一条消息

 You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them.

根据消息说明,停止服务器并执行以下操作(请确保位于包含 <st c="6266">moviesstore</st> 文件夹的 <st c="6303">manage.py</st> 文件):

  • 对于 macOS,运行以下命令:

    <st c="6357">python3 manage.py migrate</st>
    
  • 对于 Windows,运行以下命令:

    <st c="6423">python manage.py migrate</st>
    

<st c="6453">migrate</st> 命令根据 Django 的默认设置创建初始数据库。 请注意,项目根目录中有一个 <st c="6554">db.sqlite3</st> 文件。 此文件代表我们的 SQLite 数据库。 它是在我们第一次运行 <st c="6675">migrate</st> <st c="6686">runserver</st>时创建的。

在前一个例子中, <st c="6723">migrate</st> 命令应用了 18 个默认迁移(如图 图 5**.1所示)。 这些迁移是由一些默认 Django 应用程序定义的 – <st c="6856">admin</st> <st c="6863">auth</st> <st c="6869">contenttypes</st> <st c="6887">sessions</st>。这些应用程序在 <st c="6926">INSTALLED_APPS</st> 变量中加载,位于 <st c="6957">moviesstore/settings.py</st> 文件中。

因此, <st c="6995">migrate</st> 命令运行所有已安装应用程序的迁移。 请注意 <st c="7068">INSTALLED_APPS</st> 还加载了 <st c="7098">movies</st> 应用程序。 然而,没有为 <st c="7154">movies</st> 应用程序应用迁移。 这是因为我们没有为 <st c="7226">movies</st> 应用程序生成迁移:

图 5.1 – 应用默认 Django 迁移

图 5.1 – 应用默认 Django 迁移

创建自定义迁移

目前,我们在 <st c="7949">movies</st> 应用中定义了一个 <st c="7952">电影</st> 模型。 基于该模型,我们可以创建自己的迁移。 要为 <st c="8075">movies</st> 应用创建迁移,我们需要在 <st c="8106">makemigrations</st> 命令中 <st c="8132">终端</st> 中运行:

  • 对于 macOS,运行以下命令:

    <st c="8183">python3 manage.py makemigrations</st>
    
  • 对于 Windows,运行以下命令:

    <st c="8256">python manage.py makemigrations</st>
    

上一个命令基于我们在 Django 应用中定义的模型创建迁移文件(见 图 5**.2):

图 5.2 – 执行 makemigrations 命令

图 5.2 – 执行 makemigrations 命令

迁移存储在相应的应用级 <st c="8569">迁移</st> 文件夹中。 目前,我们只在 <st c="8622">电影</st> 模型中定义了 <st c="8645">movies</st> 应用。 因此,此命令在 <st c="8711">电影</st> 模型中生成迁移文件,位于 <st c="8734">movies/migrations/</st> 文件夹中(见 图 5**.3):

图 5.3 – 为电影应用生成的迁移

图 5.3 – 为电影应用生成的迁移

如果我们更改 <st c="8953">电影</st> 模型或创建新的模型,我们需要再次执行 <st c="9010">makemigrations</st> 命令。 此命令将创建新的迁移文件,这些文件将作为我们 数据库模式 的版本控制。

请注意,迁移文件已创建,但数据库尚未 更新。

应用自定义迁移

运行 <st c="9271">makemigrations</st>后,通常需要运行 migrate 命令将迁移应用到数据库并做出相应的更改。 现在,在 终端 中执行以下操作:

  • 对于 macOS,运行以下命令:

    <st c="9480">python3 manage.py migrate</st>
    
  • 对于 Windows,运行以下命令:

    <st c="9546">python manage.py migrate</st>
    

如图 图 5**.4所示,我们应用了 <st c="9611">movies</st> 应用的迁移:

图 5.4 – 应用电影应用迁移

图 5.4 – 应用电影应用迁移

总之,每次你修改模型文件时,你必须做以下操作: 以下操作:

  • 对于 macOS,运行以下命令: 以下命令:

    <st c="9949">python3 manage.py makemigrations</st>
    <st c="9982">python3 manage.py migrate</st>
    
  • 对于 Windows,运行以下命令: 以下命令:

    <st c="10048">python manage.py makemigrations</st>
    <st c="10080">python manage.py migrate</st>
    

但我们如何访问我们的数据库 并查看里面的内容? 为此,我们使用 Django 中的一个强大工具,即管理界面。 我们将在下一节中讨论这一点。

访问 Django 管理界面

要访问 我们的数据库,我们必须进入 Django 管理界面。 请记住,在 <st c="10409">admin</st> 路径 <st c="10423">/moviesstore/urls.py</st>

 …
urlpatterns = [ <st c="10461">path('admin/', admin.site.urls),</st> path('', include('home.urls')),
    path('movies/', include('movies.urls')),
]

如果你访问 <st c="10582">localhost:8000/admin</st>,你将被带到管理站点,如图 图 5**.5所示:

图 5.5 – 管理页面

图 5.5 – 管理页面

Django 有一个强大的内置管理界面,它以可视化的方式管理 Django 项目的所有方面 – 例如,用户、电影,等等。 更多。

我们用什么用户名和密码登录到管理界面? 为此,我们必须在终端中创建一个超级用户。 对于此,我们必须在终端中创建一个超级用户。 以下操作:

创建超级用户

让我们创建一个超级用户 以访问管理面板。 在终端中,停止服务器并执行以下操作: 以下操作:

  • 对于 macOS,运行以下命令: 以下命令:

    <st c="11241">python3 manage.py createsuperuser</st>
    
  • 对于 Windows,运行以下命令: 以下命令:

    <st c="11315">python manage.py createsuperuser</st>
    

然后,你将被要求指定用户名、电子邮件和密码。 请注意,任何人都可以访问你网站上的管理路径,所以请确保你的密码是安全的。 创建超级用户后,你应该从终端收到如下消息: 以下消息: 终端

 Superuser created successfully.

恢复超级用户密码

如果你稍后想更改密码,你可以运行以下命令: 以下命令: 以下命令:

  • 以下是 macOS 的命令 以下命令:

    <st c="11784">python3 manage.py changepassword <username></st>
    
  • 这是 Windows 的命令 以下命令:

    <st c="11860">python manage.py changepassword <username></st>
    

访问管理面板

现在,再次启动服务器并使用您刚刚创建的用户名登录到管理员 ,如图 图 5**.6所示:

图 5.6 – 网站管理页面

图 5.6 – 网站管理页面

用户下,您将看到您刚刚创建的用户,如图 图 5**.7所示:

图 5.7 – 用户管理页面

图 5.7 – 用户管理页面

您可以在此处添加 额外的用户账户 以供您的团队使用。

目前,我们的 <st c="12787">电影</st> 模型在管理界面中未显示。 我们需要明确告诉 Django 在管理界面中显示什么。 在我们将 <st c="12900">电影</st> 模型添加到管理界面之前,让我们配置我们的项目,以便可以上传图像。

配置图片上传

我们必须配置 我们在添加图像时要存储的位置。 首先,在 <st c="13090">/moviesstore/settings.py</st>中,在文件末尾添加以下粗体内容:

 … <st c="13167">MEDIA_ROOT = os.path.join(BASE_DIR, 'media')</st>
<st c="13211">MEDIA_URL = '/media/'</st>

让我们解释一下 之前的代码:

  • <st c="13266">MEDIA_ROOT</st>: 这个变量指定了上传媒体文件将存储的文件系统路径。 在此处, <st c="13390">BASE_DIR</st> 是一个变量,代表 Django 项目的基目录,而 <st c="13475">'media'</st> 是位于 <st c="13510">BASE_DIR</st> 中的子目录,其中将存储媒体文件。 因此, <st c="13557">MEDIA_ROOT</st> 将被设置为类似 <st c="13595">/your_project_folder/media</st>的路径。

  • <st c="13622">MEDIA_URL</st>: 这个变量指定了将用于从网络服务器提供媒体文件的 URL 前缀。 在此代码中,它被设置为 <st c="13760">'/media/'</st>,这意味着上传到 Django 应用程序的媒体文件将通过以 <st c="13873">/media/</st>开头的 URL 进行访问。 例如,如果你上传一个名为 <st c="13924">example.jpg</st>的图片,它可能可以通过以下 URL 访问 http://localhost:8000/media/example.jpg

这样,服务器已经配置好了图像上传。 因此,让我们学习如何提供 这些图像。

服务存储的图像

接下来,为了使服务器能够服务存储的图像,我们必须修改 <st c="14221">/moviesstore/urls.py</st> 文件,并添加以下 粗体内容:

 … <st c="14279">from django.conf.urls.static import static</st>
<st c="14321">from django.conf import settings</st> urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('home.urls')),
    path('movies/', include('movies.urls')),
] <st c="14479">urlpatterns += static(settings.MEDIA_URL,</st>
<st c="14611">MEDIA_ROOT</st> directory when the <st c="14641">MEDIA_URL</st> URL prefix is accessed.
			<st c="14674">Note</st>
			<st c="14679">It’s important to stop the server and run the server again to apply the</st> <st c="14752">previous changes.</st>
			<st c="14769">Now that the image configuration is done, let’s add movies to the</st> <st c="14836">admin panel.</st>
			<st c="14848">Adding a movie model to the admin panel</st>
			<st c="14888">We are now ready to create movies from the admin panel and store the images in our Django project.</st> <st c="14988">We will add the</st> `<st c="15004">Movie</st>` <st c="15009">model to the admin panel, and we will</st> <st c="15048">create movies.</st>
			<st c="15062">Adding the Movie model to the admin panel</st>
			<st c="15104">To add the</st> `<st c="15116">Movie</st>` <st c="15121">model to the admin panel, go back to</st> `<st c="15159">/movies/admin.py</st>` <st c="15175">and</st> <st c="15180">register our model</st> <st c="15198">by adding the following</st> <st c="15223">in</st> **<st c="15226">bold</st>**<st c="15230">:</st>

from django.contrib import admin from .models import Movie

/admin. 现在,电影 模型将显示(如图 *图 5**.8):

        ![图 5.8 – 可用电影的管理页面](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_05_08.jpg)

        <st c="15629">图 5.8 – 可用电影的管理页面</st>

        <st c="15674">通过点击</st> **<st c="15713">+添加</st>**<st c="15717">来尝试添加一个</st> `<st c="15688">电影</st>` <st c="15693">对象。您将被带到</st> **<st c="15746">添加电影</st>** <st c="15755">表单,如图</st> *<st c="15774">图 5</st>**<st c="15782">.9</st>*<st c="15784">所示:</st>

        ![图 5.9 – 添加电影表单](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_05_09.jpg)

        <st c="16083">图 5.9 – 添加电影表单</st>

        <st c="16114">尝试添加一个电影并点击</st> **<st c="16142">保存</st>**<st c="16146">。您的电影对象将被保存到数据库中,并在管理页面中显示,如图</st> *<st c="16241">图 5</st>**<st c="16249">.10</st>*<st c="16252">所示:</st>

        ![图 5.10 – 电影管理页面](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_05_10.jpg)

        <st c="16399">图 5.10 – 电影管理页面</st>

        <st c="16430">请注意,管理面板将电影信息显示为电影 ID 与短横线结合以及电影名称的组合。</st> <st c="16558">这是因为我们定义了</st> `<st c="16588">Movie</st>` <st c="16593">模型</st> 的 `<st c="16602">__str__</st>` <st c="16609">方法以这种方式工作。</st>

        <st c="16635">您还可以在</st> `<st c="16672">/moviesstore/media/movie_images/<image file>.jpg</st>`<st c="16720">中看到电影</st> <st c="16722">图像。</st> *<st c="16722">图 5</st>**<st c="16730">.11</st> <st c="16733">显示了存储在</st> <st c="16756">inception.jpg</st> <st c="16769">之前的文件夹中的图像:</st>

        ![图 5.11 – 存储的电影图像位置](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_05_11.jpg)

        <st c="16917">图 5.11 – 存储的电影图像位置</st>

        <st c="16962">每次您上传电影图像时,它将被存储在之前的文件夹中。</st> <st c="17040">通过这样,我们已经配置了我们的项目,使其能够存储和</st> <st c="17067">服务图像。</st>

        <st c="17118">总结</st>

        <st c="17126">在 Django 中处理数据库时,模型是必不可少的。</st> <st c="17186">在本章中,我们学习了 Django 模型的基础知识,并创建了一个</st> `<st c="17268">电影</st>` <st c="17273">模型。</st> <st c="17281">我们还学习了如何使用 Django 管理界面以及如何创建电影。</st> <st c="17361">在下一章中,我们将学习如何从我们的数据库中提取并显示存储在我们的网站上的电影。</st>

第七章:6

从数据库收集和展示数据

在前几章中,我们使用 Python 列表中的虚拟数据来收集电影信息。 虽然这种方法作为展示电影信息的一个良好初始尝试是有效的,但它并不具有良好的可扩展性。 如果我们想添加一部新电影或编辑现有的电影,我们需要修改我们的 Python 代码。 本章重点介绍重构电影和单个电影页面以直接从数据库检索和展示信息的过程。 使用这种方法,如果我们需要添加新电影或修改现有电影,我们可以直接访问管理面板,而无需修改 Python 代码。 此外,我们还将实现一个新的电影 搜索功能。

在本章中,我们将涵盖以下主题: 以下内容:

  • 删除电影的 虚拟数据

  • 更新电影 列表页面

  • 更新单个 电影页面的列表

  • 实现搜索 电影功能

在本章结束时,你将了解如何从 数据库中收集和展示信息。

技术要求

在本章中,我们将使用 Python 3.10+。 此外,我们将在本书中使用 VS Code 编辑器,你可以从 以下链接 code.visualstudio.com/](https://code.visualstudio.com/)下载。

本章的代码位于 以下链接 github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter06/moviesstore

本章的 CiA 视频可以在以下链接找到 packt.link/mZUvA

删除电影的虚拟数据

提取数据库 数据的第一个步骤是删除电影的虚拟数据。 <st c="1567">/movies/views.py</st>中,删除 <st c="1596">movies</st> 变量,如下所示 粗体中:

 from django.shortcuts import render <st c="1685">movies = [</st>
 <st c="1695">{</st>
 <st c="1697">'id': 1, 'name': 'Inception', 'price': 12,</st>
 <st c="1740">'description': 'A mind-bending heist thriller.'</st>
 <st c="1788">},</st>
 <st c="1791">{</st>
 <st c="1793">'id': 2, 'name': 'Avatar', 'price': 13,</st>
 <st c="1833">'description': 'A journey to a distant world and the battle for resources.'</st>
**<st c="1909">},</st>**
 **<st c="1912">{</st>**
 **<st c="1914">'id': 3, 'name': 'The Dark Knight', 'price': 14,</st>**
 **<st c="1963">'description': 'Gothams vigilante faces the Joker.'</st>**
 **<st c="2015">},</st>**
 **<st c="2018">{</st>**
 **<st c="2020">'id': 4, 'name': 'Titanic', 'price': 11,</st>**
 **<st c="2061">'description': 'A love story set against the backdrop of the sinking Titanic.',</st>**
 **<st c="2141">},</st>**
**<st c="2144">]</st>** <st c="2146">…</st>

**我们不再需要这个变量,因为我们将从数据库中提取电影信息。 此外,请记住访问管理面板并创建几个 电影对象。

现在我们已经移除了虚拟数据,让我们更新我们 列出电影的方式。

更新电影列表页面

现在,让我们更新 代码以从数据库中提取电影信息。 我们 首先需要更新 <st c="2538">index</st> 函数;其次,更新 <st c="2573">movies.index</st> 模板;最后,添加一个自定义 CSS 类。

更新索引函数

<st c="2658">/movies/views.py</st>中,添加以下 加粗内容:

 from django.shortcuts import render <st c="2739">from .models import Movie</st> def index(request):
    template_data = {}
    template_data['title'] = 'Movies'
    template_data['movies'] = <st c="2864">Movie.objects.all()</st> return render(request, 'movies/index.html',
                  {'template_data': template_data})
…

让我们解释一下 之前的代码:

  • 我们从 <st c="3011">Movie</st> 模型中导入 <st c="3032">models</st> 文件。 我们将使用此模型来访问 数据库信息。

  • 我们通过使用 <st c="3153">Movie.objects.all()</st> 方法从数据库中收集所有电影。 <st c="3181">Movie.objects</st> 是 Django 中的一个管理器,作为查询与模型关联的数据库表的默认接口。 它提供了执行数据库操作(如创建、更新、删除和检索对象)的各种方法。 <st c="3435">all()</st> 方法从模型表示的数据库表获取所有对象。 记住,我们之前通过使用 <st c="3591">movies</st> 变量收集了电影信息;现在,我们使用 <st c="3624">Movie</st> Django 模型。

注意

Django 提供了几种操作和访问数据库信息的方法。 您可以在以下位置找到更多这些方法: https://docs.djangoproject.com/en/5.0/topics/db/queries/

更新 movies.index 模板

<st c="3863">/movies/templates/movies/index.html</st>中,添加以下 加粗内容:

 …
      {% for movie in template_data.movies %}
      <div class="col-md-4 col-lg-3 mb-2">
        <div class="p-2 card align-items-center pt-4">
          <img src="img/st>**<st c="4062">{{ movie.image.url }}</st>**<st c="4084">"
            class="card-img-top rounded</st> **<st c="4115">img-card-200</st>**<st c="4127">">
          <div class="card-body text-center">
            <a href="{% url 'movies.show' id=movie.id %}"
              class="btn bg-dark text-white">
              {{ movie.name }}
            </a>
          </div>
        </div>
      </div>
      {% endfor %}
      …</st>

我们已经移除了默认图片,现在我们将展示每部电影的特定图片。 因此,我们将包含 一个自定义 CSS 类来以相同的比例显示图片。 我们将添加这个 CSS 类。

添加自定义 CSS 类

<st c="4550">/moviesstore/static/css/style.css</st>的末尾添加以下 加粗内容:

 … <st c="4636">.img-card-200 {</st>
 <st c="4651">width: fit-content;</st>
 <st c="4671">max-height: 200px;</st>
<st c="4690">}</st>

现在,保存这些文件,运行服务器,并访问 http://localhost:8000/movies;你应该看到电影页面,该页面从数据库中提取信息(图 6**.1)。

图 6.1 – 电影页面

图 6.1 – 电影页面

电影页面现在列出了数据库中的电影;让我们通过修改单个 电影页面来完成这个过程。

更新单个电影页面的列表

现在,让我们更新代码以 从数据库中提取单个电影信息。 我们首先需要更新 show 函数;其次,更新 <st c="5333">movies.show</st> 模板;最后,添加一个自定义 CSS 类。

更新显示函数

<st c="5416">/movies/views.py</st> 中,添加以下内容并以粗体显示:

 …
def show(request, id):
    movie = <st c="5493">Movie.objects.get(id=id)</st> template_data = {}
    template_data['title'] = <st c="5562">movie.name</st> template_data['movie'] = movie
    return render(request, 'movies/show.html',
                  {'template_data': template_data})

让我们解释一下 之前的代码:

  • 我们使用 <st c="5725">Movie.objects.get(id=id)</st> 方法根据其 <st c="5799">id</st>检索一个特定的电影。记住 <st c="5817">id</st> 是通过 URL 传递的,并在 <st c="5876">show</st> 函数中作为参数接收的。

  • 我们现在将 <st c="5905">movie.name</st> 作为对象属性访问。 之前,我们通过键(<st c="5983">movie['name']</st>)访问名称,因为虚拟数据变量 存储字典。

更新 movies.show 模板

<st c="6085">/movies/templates/movies/show.html</st> 中,添加以下内容并以粗体显示:

 …
      <div class="col-md-6 mx-auto mb-3">
        <h2>{{ template_data.movie.name }}</h2>
        <hr />
        <p><b>Description:</b> {{
          template_data.movie.description }}</p>
        <p><b>Price:</b> ${{
          template_data.movie.price }}</p>
      </div>
      <div class="col-md-6 mx-auto mb-3 text-center">
        <img src="img/st>**<st c="6420">{{ template_data.movie.image.url }}</st>**<st c="6456">"
          class="rounded</st> **<st c="6474">img-card-400</st>**<st c="6486">" />
      </div>
      …</st>

与之前的 代码类似,我们现在显示特定的电影图片,并使用自定义 CSS 类以相同的比例显示电影图片。

添加自定义 CSS 类

<st c="6674">/moviesstore/static/css/style.css</st> 中,在文件末尾添加以下内容并以粗体显示:

 … <st c="6760">.img-card-400 {</st>
 <st c="6775">width: fit-content;</st>
 <st c="6795">max-height: 400px;</st>
<st c="6814">}</st>

现在保存这些文件,运行服务器,并访问特定电影在 http://localhost:8000/movies/1;你应该看到单个电影页面,该页面从数据库中提取电影信息(图 6.2**.2)。

图 6.2 – 单个电影页面

图 6.2 – 单个电影页面

我们现在正在列出电影和 数据库中的单个电影。 最后,让我们添加一个新功能,以便能够 搜索电影。

实现搜索电影功能

让我们通过实现一个 搜索电影功能来结束这一章。 首先,我们需要更新 <st c="7527">movies.index</st> 模板,其次,更新 <st c="7573">index</st> 函数。

更新 movies.index 模板

<st c="7627">/movies/templates/movies/index.html</st>中,添加以下 以下内容,并加粗:

 …
      <div class="col mx-auto mb-3">
        <h2>List of Movies</h2>
        <hr /> <st c="7754"><p class="card-text"></st>
 <st c="7775"><form method="GET"></st>
**<st c="7795"><div class="row"></st>**
 **<st c="7813"><div class="col-auto"></st>**
 **<st c="7836"><div class="input-group col-auto"></st>**
 **<st c="7871"><div class="input-group-text"></st>**
 **<st c="7902">Search</div></st>**
 **<st c="7915"><input type="text" class="form-control"</st>**
 **<st c="7955">name="search"></st>**
 **<st c="7970"></div></st>**
 **<st c="7977"></div></st>**
 **<st c="7984"><div class="col-auto"></st>**
****<st c="8007"><button class="btn bg-dark text-white"</st>**
 **<st c="8046">type="submit">Search</button></st>**
 **<st c="8076"></div></st>**
 **<st c="8083"></div></st>**
 **<st c="8090"></form></st>**
 **<st c="8098"></p></st>** <st c="8103"></div>
    </div>
    …</st>**

****我们创建了一个 HTML 表单,允许用户执行搜索操作。 此表单将重定向到当前 URL 路由,并通过 URL 发送 <st c="8257">search</st> 信息。 例如,如果我们搜索 <st c="8318">Avatar</st>,它将重定向我们到 <st c="8347">http://localhost:8000/movies/?search=Avatar</st>

更新 index 函数

<st c="8419">/movies/views.py</st>中,添加以下 以下内容,并加粗:

 …
def index(request): <st c="8485">search_term = request.GET.get('search')</st>
 <st c="8524">if search_term:</st>
 <st c="8540">movies =</st>
 <st c="8549">Movie.objects.filter(name__icontains=search_term)</st>
 <st c="8599">else:</st>
 <st c="8605">movies = Movie.objects.all()</st> template_data = {}
    template_data['title'] = 'Movies'
    template_data['movies'] = <st c="8714">movies</st> return render(request, 'movies/index.html',
                  {'template_data': template_data})

<st c="8803">index</st> 函数已更改。 现在,如果当前请求中没有发送 <st c="8871">search</st> 参数,它将检索所有电影,或者根据 <st c="8973">search</st> 参数检索特定电影。 让我们解释一下 `之前的代码。

  • 我们通过使用 <st c="9053">search</st> 参数的 <st c="9083">request.GET.get('search')</st> 方法来检索该值,并将其分配给 <st c="9145">search_term</st> 变量。 在这里,我们捕获了 <st c="9188">search</st> 输入值,该值是通过上一节中定义的表单提交的。

  • 如果 <st c="9270">search_term</st> 不为空,我们将过滤包含 <st c="9337">search_term</st>名称的电影。我们使用 <st c="9354">__icontains</st> 查找进行不区分大小写的 包含搜索。

  • 如果 <st c="9427">search_term</st> 为空,我们将从数据库中检索所有电影,而不应用 `任何过滤器。

  • 最后,我们将提取的 <st c="9551">movies</st> 传递给 <st c="9565">template_data</st> 字典。

现在,保存这些文件,运行服务器,转到 <st c="9636">http://localhost:8000/movies</st>,输入搜索词,提交表单;你应该能看到与搜索词匹配的电影(图 6**.3)。

图 6.3 – 带自定义搜索的电影页面

图 6.3 – 带自定义搜索的电影页面

我们已经重构了我们的代码,使其与数据库而不是虚拟数据一起工作。 电影商店 代码现在可以通过修改 Python 代码来包括新的电影或编辑现有的电影。 此外,搜索功能的添加帮助我们理解如何在 Django 中过滤不同的数据,并增强了项目的功能。

摘要

在本章中,我们学习了如何从数据库中提取信息。 我们学习了不同的 Django 模型方法,例如 <st c="10409">all</st>, <st c="10414">get</st>, 和 <st c="10423">filter</st>,以及它们如何用于检索不同类型的信息。 我们重构了电影和单个电影页面,以从数据库中收集信息,并学习了如何实现搜索功能。

在下一章中,我们将更深入地了解数据库是如何工作的。******

第八章:7

理解数据库

前几章向我们展示了如何使用 Django 模型将数据持久化并从数据库中检索数据。 在本章中,我们将探讨 Django 中数据库的工作方式。 我们将利用数据库查看器来检查 Django 如何管理各种信息和存储它。 此外,我们还将学习如何自定义 Django 管理面板并在不同 数据库引擎之间切换。

在本章中,我们将介绍以下主题: 以下主题:

  • 理解数据库查看器

  • 自定义 Django 管理面板

  • 切换到 MySQL 数据库

在本章结束时,您将了解数据库的工作方式,如何可视化数据库信息,以及如何切换到不同的 数据库引擎。

技术要求

在本章中,我们将使用 Python 3.10+。 此外,我们还将使用本书中使用的 VS Code 编辑器,您可以从 以下链接 code.visualstudio.com/下载。

本章的代码位于 以下链接 github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter07/moviesstore

本章的 CiA 视频可在 以下链接 packt.link/wD2bK 找到。

理解数据库查看器

让我们花些时间来了解数据库的工作方式。 对象存储在 <st c="1242">db.sqlite3</st> 文件中。 如果您点击它,它不太容易阅读。 然而,您可以使用 SQLite 查看器查看此类 SQLite 文件;只需在谷歌上搜索 <st c="1378">SQLite 查看器</st> 即可找到它们的列表。 一个 例子 https://inloop.github.io/sqlite-viewer/

将您的 <st c="1487">db.sqlite3</st> 文件拖放到前面的链接(在 SQLite 查看器上),您将看到数据库中的不同表(如图 图 7**.1)所示:

图 7.1 – 在 SQLite 查看器中打开 db.sqlite3

图 7.1 – 在 SQLite 查看器中打开 db.sqlite3

您可以看到我们创建的模型表 – 那就是说, <st c="2123">电影</st>。请注意,表的实际名称是由应用程序的名称和模型的名称组合而成的。 例如,如果您的应用程序名称为 <st c="2278">movies</st> 并且您的模型名称为 <st c="2309">Movie</st>,相应的表名将是 <st c="2354">movies_movie</st>。这种命名约定有助于 Django 区分不同应用程序和模型中的表。

还有其他表,例如 <st c="2528">django_session</st>,因为安装了用于会话 和身份验证等功能的不同应用程序。

选择一个表(例如, <st c="2674">movies_movie</st>),您应该能够看到其行(图 7**.2)。

图 7.2 – 在 SQLite 查看器中选择表

图 7.2 – 在 SQLite 查看器中选择表

希望如此, 这能让您欣赏到 Django 数据库幕后发生的事情。 目前,我们正在使用 SQLite 数据库。 然而,如果我们想切换到其他数据库引擎怎么办呢? Django 官方支持以下数据库 – PostgreSQL、MariaDB、MySQL、Oracle、 和 SQLite。

注意

除了官方支持的数据库之外,还有第三方提供的后端,允许您使用其他数据库与 Django 一起使用,例如 CockroachDB、Firebird、Google Cloud Spanner、Microsoft SQL Server、Snowflake、TiDB 和 YugabyteDB。 您可以在 这里 找到更多信息: https://docs.djangoproject.com/en/5.0/ref/databases/#third-party-notes

要切换到另一个数据库引擎,请转到 <st c="3999">/moviereviews/settings.py</st> 并修改 以下 粗体行:

 …
DATABASES = {
    'default': { <st c="4093">'ENGINE': 'django.db.backends.sqlite3',</st>
 <st c="4132">'NAME': BASE_DIR / 'db.sqlite3',</st> }
}
…

您仍然可以像平常一样创建 您的模型,并且更改将由 Django 在幕后处理。

在书中,我们使用 SQLite 因为它是最简单的。 Django 默认使用 SQLite,这对于小型项目来说是一个很好的选择。 它运行在一个单独的文件上,不需要复杂的安装。 相比之下,其他选项配置起来有些复杂。 在本章末尾,我们将看到如何配置一个更 健壮的数据库。

自定义 Django 管理面板

Django 管理面板是 Django 的一个强大内置功能,它自动生成一个用户友好的界面来管理我们的应用程序的数据模型。 这是 Django 的一个很好的功能,许多其他框架 都不提供。

图 7**.3 显示了当前的电影 管理页面。

图 7.3 – 电影管理页面

图 7.3 – 电影管理页面

管理面板可能看起来非常僵化,但幸运的是,Django 允许我们根据我们的需求进行自定义。 让我们将两个自定义应用到电影管理页面 – 首先,按名称排序电影,以及 其次,允许按名称搜索

按名称排序电影

<st c="5623">/movies/admin.py</st>中,添加以下 以下内容(加粗):

 from django.contrib import admin
from .models import Movie <st c="5727">class MovieAdmin(admin.ModelAdmin):</st>
 <st c="5762">ordering = ['name']</st> admin.site.register(Movie<st c="5808">, MovieAdmin</st>)

让我们解释一下 之前的代码:

  • 我们创建了一个 <st c="5868">MovieAdmin</st> 类,它继承自 <st c="5904">admin.ModelAdmin</st>。这定义了一个自定义管理类,允许您自定义电影 <st c="6029">模型</st> 的管理界面。

  • 我们设置了一个 <st c="6052">排序</st> 属性。 此属性设置管理界面中电影对象的默认排序。 在我们的例子中,它指定电影应按其 <st c="6227">名称</st> 字段排序。

  • 最后,我们将 <st c="6266">Movie</st> 模型与自定义管理类 <st c="6307">MovieAdmin</st>注册。 这告诉 Django 使用 <st c="6348">MovieAdmin</st> 类来自定义电影 <st c="6406">模型</st> 的管理界面。

现在,保存你的文件,返回到 <st c="6451">/admin</st>,并导航到电影页面。 您将看到按名称排序的电影对象(如图 图 7**.4):

图 7.4 – 电影管理页面

图 7.4 – 电影管理页面

允许按名称搜索

<st c="6903">/movies/admin.py</st>中,添加以下 以下内容,并以粗体显示:

 from django.contrib import admin
from .models import Movie
class MovieAdmin(admin.ModelAdmin):
    ordering = ['name'] <st c="7063">search_fields = ['name']</st> admin.site.register(Movie, MovieAdmin)

我们添加了一个 <st c="7138">search_fields</st> 属性,指定只有 <st c="7191">name</st> 字段中的 <st c="7209">Movie</st> 模型可以在管理界面中搜索。 这意味着用户可以在管理界面提供的搜索框中输入关键词,Django 将根据输入的关键词是否与电影名称的任何部分匹配来过滤电影对象列表。

现在,保存你的文件,回到 <st c="7505">/admin</st>,并导航到电影页面。 你将看到可用的新搜索框(如图 图 7**.5):

图 7.5 – 带有搜索框的电影管理页面

图 7.5 – 带有搜索框的电影管理页面

注意

正如你所看到的,使用很少的代码行就可以轻松应用一些自定义。 如果你想探索一些额外的自定义,请查看这个 链接: https://docs.djangoproject.com/en/5.0/ref/contrib/admin/

让我们通过 了解如何切换到 不同的数据库来结束这一章。

切换到 MySQL 数据库

正如我们之前提到的,我们在这本书中一直使用 SQLite,因为它是最简单的。 然而,我们将解释如何切换到 一个更健壮的数据库引擎 ,称为 MySQL。

注意

本书的代码基于 SQLite,因此本节中的更改是可选的,并且不会反映在 GitHub 书籍仓库或即将到来的章节中。

MySQL 是由 Oracle 开发的流行的开源 SQL 数据库管理系统。 有几种不同的方式可以安装 MySQL。 在本节中,我们将安装 MySQL 和一个名为 phpMyAdmin 的 MySQL 管理工具。这两个工具都可以在名为 XAMPP 的开发环境中找到,所以让我们 安装它。

XAMPP 是一个流行的 PHP 开发环境。 它是一个包含 MySQL、PHP 和 Perl 的免费 Apache 发行版。 如前所述,XAMPP 还包括 phpMyAdmin。如果您还没有安装 XAMPP,请访问 https://www.apachefriends.org/download.html,下载它,并 安装它。

要切换到 MySQL 数据库,我们需要遵循 以下步骤:

  1. 配置 MySQL 数据库。

  2. 配置我们的项目以使用 MySQL 数据库。

  3. 运行 迁移。

配置 MySQL 数据库

执行 XAMPP,然后启动 Apache 模块(1),启动 MySQL 模块(2),然后点击 MySQL 管理员 按钮(在 MySQL 模块中)(3),这将带我们到 phpMyAdmin 应用程序(如图 7.6 所示)。

图 7.6 – 在 XAMPP 中启动 MySQL 模块

图 7.6 – 在 XAMPP 中启动 MySQL 模块

phpMyAdmin 应用程序中,输入您的用户名和密码。 默认值是 <st c="10018">root</st> (用户名) 和空密码 (图 7**.7):

图 7.7 – XAMPP phpMyAdmin 应用程序

图 7.7 – XAMPP phpMyAdmin 应用程序

一旦您登录到 phpMyAdmin*,点击数据库标签页(<st c="10393">moviesstore</st> (2),然后点击 创建 按钮(3)(如图 7.8 所示)。

图 7.8 – 数据库创建

图 7.8 – 数据库创建

配置我们的项目以使用 MySQL 数据库

首先,我们需要安装一个名为 PyMySQL 的包。 PyMySQL 是从 Python 连接到 MySQL 数据库的接口。 在终端中运行以下命令:

  • 对于 macOS,运行以下命令:

    <st c="10898">pip3 install pymysql</st>
    
  • 对于 Windows 系统, 请运行以下命令:

    <st c="10942">pip install pymysql</st>
    

然后,我们需要将以下加粗行添加到 <st c="11016">moviesstore/__init__.py</st> 文件中:

<st c="11045">import pymysql</st>
<st c="11095">__init__.py</st> file will be executed when we run the Django project, and the previous two lines import the PyMySQL package into the project.
			<st c="11232">Finally, we need to modify the database settings to switch to MySQL.</st> <st c="11302">In</st> `<st c="11305">/moviesstore/settings.py</st>`<st c="11329">, modify the</st> `<st c="11342">DATABASES</st>` <st c="11351">variable to the following</st> <st c="11378">in bold:</st>

DATABASES = {

'default': { <st c="11415">'ENGINE': 'django.db.backends.mysql',</st>

'NAME': 'moviesstore',

'USER': 'root',

'PASSWORD': '',

'HOST': 'localhost',

'PORT': '3306', }

}


			<st c="11550">Running the migrations</st>
			<st c="11573">Since we have switched</st> <st c="11596">the database, the new database is empty.</st> <st c="11638">So, we need to run</st> <st c="11657">the migrations:</st>

				*   <st c="11672">For macOS,</st> <st c="11684">run this:</st>

    ```

    <st c="11693">python3 manage.py migrate</st>

    ```py

    				*   <st c="11719">For Windows,</st> <st c="11733">run this:</st>

    ```

    <st c="11742">python manage.py migrate</st>

    ```py

			<st c="11767">Then, we should</st> <st c="11784">see the tables in our</st> *<st c="11806">phpMyAdmin</st>* <st c="11816">application (as shown in</st> *<st c="11842">Figure 7</st>**<st c="11850">.9</st>*<st c="11852">).</st>
			![Figure 7.9 – The MySQL database](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_07_09.jpg)

			<st c="12825">Figure 7.9 – The MySQL database</st>
			<st c="12856">Finally, we</st> <st c="12869">repeat the process of creating a superuser and accessing the admin panel to create</st> <st c="12952">some movies.</st>
			<st c="12964">Summary</st>
			<st c="12972">We hope that you now better understand how SQLite databases work, how Django supports database management, and how you can customize the Django admin panel.</st> <st c="13130">In the next chapter, we will learn how to allow a user to sign up and</st> <st c="13200">log in.</st>

第九章:8

实现用户注册和登录

我们应用的下个部分将涉及用户认证,在这里我们允许用户注册和登录。 实现用户认证是众所周知的困难。 幸运的是,我们可以使用 Django 强大的内置认证系统来处理可能出现的许多安全陷阱,如果我们从头开始创建自己的用户认证 的话。

在本章中,我们将涵盖以下主题:

  • 创建一个 账户应用

  • 创建一个基本的 注册页面

  • 改进注册页面以处理 <st c="528">POST</st> 操作

  • 自定义 <st c="553">UserCreationForm</st>

  • 创建一个 登录页面

  • 实现登出功能

在本章结束时,您将了解如何实现认证系统并处理常见的 认证操作。

技术要求

在本章中,我们将使用 Python 3.10+。 此外,我们将在本书中使用 VS Code 编辑器,您可以从 这里 https://code.visualstudio.com/下载。

本章的代码位于 此处 https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter08/moviesstore

本章的 CiA 视频可以在 这里 https://packt.link/XmYIk

创建一个账户应用

完整的用户认证系统包括一系列功能,如注册、登录、登出和一些验证。 这些功能似乎都不属于我们的 主页 应用或 电影 应用,因此让我们在新的应用中分离它们。 这个新应用将被 命名为 <st c="1466">accounts</st>

我们将遵循以下步骤来创建和配置新应用:

  1. 创建一个 账户应用。

  2. 将账户应用添加到 设置文件中。

  3. 在项目级别的 URL 文件中包含一个账户 URL 文件。

在接下来的几个部分中,我们将详细讨论这些步骤。

创建一个账户应用

导航到最顶层的 <st c="1785">moviesstore</st> 文件夹(包含 <st c="1831">manage.py</st> 文件的文件夹)并在终端中运行以下命令:

  • 对于 macOS,运行以下命令:

    <st c="1923">python3 manage.py startapp accounts</st>
    
  • 对于 Windows,运行以下命令:

    <st c="1999">python manage.py startapp accounts</st>
    

图 8**.1 显示了新的项目结构。 请确认它与您的当前 文件夹结构相匹配。

图 8.1 – 包含 accounts 应用的 MOVIESSTORE 项目结构

图 8.1 – 包含 accounts 应用的 MOVIESSTORE 项目结构

现在,让我们将 accounts 应用添加到 设置文件中。

将 accounts 应用添加到设置文件中

记住 我们必须在 <st c="2528">settings.py</st> 文件中注册每个新创建的应用。

<st c="2549">/moviesstore/settings.py</st>中,在 <st c="2581">INSTALLED_APPS</st>下,添加以下加粗的

 …
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'home',
    'movies', <st c="2830">'accounts',</st> ]
…

现在,让我们将 accounts URL 文件包含到 我们的项目中。

将 accounts URL 文件包含到项目级别的 URL 文件中

<st c="2968">/moviesstore/urls.py</st>中,添加以下加粗的 行:

 …
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('home.urls')),
    path('movies/', include('movies.urls')), <st c="3153">path('accounts/', include('accounts.urls')),</st> ]
…

<st c="3230">accounts.urls</st> 文件中定义的所有 <st c="3265">accounts/</st> 前缀(如前一个路径中定义的) 都将包含。 稍后我们将创建 <st c="3336">accounts.urls</st> 文件。

现在我们已经创建了 accounts 应用,让我们创建第一个功能,即注册页面。

创建基本的注册页面

注册页面具有复杂的功能。 我们需要考虑许多可能的场景。 现在,让我们实现一个基本的注册页面。 我们将在接下来的章节中重构和改进这个功能。

为了实现一个基本的注册页面,我们将遵循以下 步骤:

  1. 配置一个 注册 URL。

  2. 定义一个 <st c="3807">注册</st> 函数。

  3. 创建一个 accounts 注册模板。

  4. 将注册链接添加到 基本模板中。

接下来,让我们详细看看这些步骤。

配置注册 URL

<st c="3970">/accounts/</st>中,创建一个名为 <st c="4007">urls.py</st>的新文件。这个文件将包含与账户应用 URL 相关的路径。 目前,用以下代码填充它:

 from django.urls import path
from . import views
urlpatterns = [
    path('signup', views.signup, name='accounts.signup'),
]

我们定义了一个 <st c="4269">/signup</st> 路径,但请记住,项目级别的 URL 文件为这个 <st c="4364">urls.py</st> 文件定义了一个 <st c="4338">/accounts</st> 前缀。 所以,如果一个 URL 与 <st c="4403">/accounts/signup</st> 路径匹配,它将执行在 <st c="4477">views</st> 文件中定义的 <st c="4446">signup</st> 函数。 接下来,我们将实现 <st c="4517">signup</st> 函数。

定义注册函数

<st c="4566">/accounts/views.py</st>中,添加以下加粗的行:

 from django.shortcuts import render <st c="4664">from django.contrib.auth.forms import UserCreationForm</st>
<st c="4718">def signup(request):</st>
 <st c="4739">template_data = {}</st>
 <st c="4758">template_data['title'] = 'Sign Up'</st>
 <st c="4793">if request.method == 'GET':</st>
 <st c="4821">template_data['form'] = UserCreationForm()</st>
 <st c="4864">return render(request, 'accounts/signup.html',</st>
 <st c="4911">{'template_data': template_data})</st>

让我们解释 代码:

  • 我们导入了 <st c="4982">UserCreationForm</st>,这是 Django 提供的一个内置表单类。 它旨在简化用户注册表单的创建,特别是用于创建新用户账户。 在 Django 中,我们可以创建自己的 HTML 表单,使用这些 Django 表单的一些,或者甚至自定义 Django 表单。 在这本书中,我们将学习和使用这三种方法。

  • 我们创建了我们的 <st c="5354">template_data</st> 变量,并给它 分配了一个标题。

  • 然后,我们检查当前的 HTTP 请求方法是否是 <st c="5462">GET</st>。如果它是一个 <st c="5478">GET</st> 请求,这意味着用户通过 <st c="5555">localhost:8000/accounts/signup</st> URL 导航到注册表单,在这种情况下,我们只需将 <st c="5635">UserCreationForm</st> 的一个实例发送到模板。 最后,我们渲染了 <st c="5694">accounts/signup.html</st> 模板。

现在,让我们继续创建 注册模板。

创建账户注册模板

<st c="5815">/accounts/</st>中,创建 一个 <st c="5836">templates</st> 文件夹。 然后,在 <st c="5863">/accounts/templates/</st>中,创建一个 <st c="5895">accounts</st> 文件夹。

<st c="5911">现在,在</st> <st c="5920">/accounts/templates/accounts/</st> <st c="5949">,创建一个新文件,</st> <st c="5963">文件名为</st> <st c="5970">signup.html</st> <st c="5981">。目前,请用以下内容填充它:</st>

 {% extends 'base.html' %}
{% block content %}
<div class="p-3 mt-4">
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-md-8">
        <div class="card shadow p-3 mb-4 rounded">
          <div class="card-body">
            <h2>Sign Up</h2>
            <hr />
            <form method="POST">
              {% csrf_token %}
              {{ template_data.form.as_p }}
              <button type="submit"
                class="btn bg-dark text-white">Sign Up
              </button>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
{% endblock content %}

<st c="6482">让我们</st> <st c="6488">解释</st> <st c="6497">这段代码:</st>

  • <st c="6507">我们扩展了</st> <st c="6522">base.html</st> <st c="6531">模板。</st>

  • <st c="6541">我们定义了一个文本为</st> <st c="6584">注册</st> <st c="6591">的标题元素。</st>

  • <st c="6592">我们定义</st> <st c="6603">表单</st> <st c="6607">的方法为</st> <st c="6627">POST</st> <st c="6631">。这意味着当表单提交时,数据将通过 HTTP</st> <st c="6740">POST</st> <st c="6745">方法发送到当前服务器 URL。</st>

  • 在表单内部,我们使用 DTL <st c="6785">csrf_token</st> <st c="6795">模板标签。</st> <st c="6810">它生成一个</st> **<st c="6825">跨站请求伪造</st>** (CSRF) 令牌,这 有助于防止 CSRF 攻击。 它确保表单提交来自渲染表单的同一站点。 你应该为你的所有 Django 表单 使用此标签。`

  • <st c="7045">在表单内部,我们渲染</st> <st c="7073">template_data.form</st> <st c="7091">,它代表从视图函数传递过来的</st> <st c="7114">UserCreationForm</st> <st c="7130">实例。</st> <st c="7171">.as_p</st> <st c="7176">将表单字段渲染为 HTML 段落(</st> <st c="7221"><p></st> <st c="7225">),每个表单字段都包裹在其自己的段落中。</st> <st c="7280">默认情况下,</st> <st c="7292">UserCreationForm</st> <st c="7308">包含三个表单字段——</st> <st c="7338">用户名</st> <st c="7346">、</st> <st c="7348">密码</st> <st c="7356">和</st> <st c="7362">密码确认</st> <st c="7383">。</st>

  • 在表单内部,我们包含一个<st c="7415">submit</st> <st c="7421">按钮。</st> <st c="7430">此按钮将使用 HTTP</st> <st c="7488">POST</st> <st c="7492">方法将用户重定向到当前 URL。</st> <st c="7501">目前,我们的注册视图函数仅指定了</st> <st c="7568">GET</st> <st c="7571">方法的逻辑。</st> <st c="7580">稍后,我们将实现</st> <st c="7623">POST</st> <st c="7627">方法的逻辑。</st>

<st c="7635">注意</st>

<st c="7640">除了</st> <st c="7656">form.as_p</st> <st c="7665">之外,还有其他选项可以用来使用不同的 HTML 标签渲染表单元素。</st> <st c="7742">你可以在以下位置找到更多信息:</st> <st c="7778">https://docs.djangoproject.com/en/5.0/ref/forms/api/#output-styles</st> [<st c="7778">https://docs.djangoproject.com/en/5.0/ref/forms/api/#output-styles</st>](https://docs.djangoproject.com/en/5.0/ref/forms/api/#output-styles)<st c="7844">。</st>

现在,让我们通过添加注册链接到基础模板来完成。

添加注册链接到基础模板

<st c="7961">/moviesstore/templates/base.html</st> 中,在标题部分,添加以下粗体行:

 …
        <div class="collapse navbar-collapse"
                    id="navbarNavAltMarkup">
          <div class="navbar-nav ms-auto navbar-ml">
            <a class="nav-link"
              href="{% url 'home.about' %}">About</a>
            <a class="nav-link" href=
              "{% url 'movies.index' %}">Movies</a> <st c="8291"><div class="vr bg-white mx-2 d-none</st>
 <st c="8326">d-lg-block"></div></st>
 <st c="8345"><a class="nav-link"</st>
 <st c="8365">href="{% url 'accounts.signup' %}"></st>
 <st c="8401">Sign Up</st>
 <st c="8409"></a></st> </div>
        </div>
        …

现在,保存这些文件,运行服务器,并访问 http://localhost:8000/accounts/signup;你应该能看到新的 注册 页面(图 8.2)。

图 8.2 – 注册页面

图 8.2 – 注册页面

注意,如果你尝试完成并提交表单,它将显示错误。这是因为我们还没有完成 <st c="9195">注册</st> 函数。

改进注册页面以处理 POST 操作

当用户提交注册表单时,我们必须处理请求并在管理员中创建一个用户。为了实现这一点,我们将修改 <st c="9399">注册</st> 函数。

<st c="9415">/accounts/views.py</st> 中,添加以下粗体行:

 from django.shortcuts import render
from django.contrib.auth.forms import UserCreationForm <st c="9572">from django.shortcuts import redirect</st> def signup(request):
    template_data = {}
    template_data['title'] = 'Sign Up'
    if request.method == 'GET':
        template_data['form'] = UserCreationForm()
        return render(request, 'accounts/signup.html',
            {'template_data': template_data}) **<st c="9837">elif request.method == 'POST':</st>**
 **<st c="9867">form = UserCreationForm(request.POST)</st>**
 **<st c="9905">if form.is_valid():</st>**
 **<st c="9925">form.save()</st>**
 **<st c="9937">return redirect('home.index')</st>**
 **<st c="9967">else:</st>**
 **<st c="9973">template_data['form'] = form</st>**
 **<st c="10002">return render(request, 'accounts/signup.html',</st>**
 **<st c="10049">{'template_data': template_data})</st>**

**让我们解释 这段代码:

  • 我们导入 <st c="10123">redirect</st> 函数,该函数用于在应用程序内部重定向用户到不同的 URL。

  • 我们添加一个 <st c="10230">elif</st> 部分。该部分检查 HTTP 请求方法是否为 <st c="10299">POST</st>,表示表单已被提交。

  • <st c="10361">elif</st> 部分内部,我们创建一个 <st c="10404">UserCreationForm</st> 类的实例,将请求的 <st c="10464">POST</st> 参数(request.POST`)传递给表单字段以填充数据。这使用提交的数据初始化表单。这将使用提交的数据初始化表单。

  • <st c="10581">if form.is_valid()</st> 检查提交的表单数据是否有效,根据 <st c="10698">UserCreationForm</st> 类中定义的验证规则。这些验证包括两个密码字段匹配,密码不是常见的,用户名是唯一的,以及其他一些验证。

    • 如果表单数据有效, <st c="10881">form.save()</st> 将用户数据保存到数据库。 这意味着使用提供的用户名和密码创建一个新的用户账户。 此外,我们根据 URL <st c="11044">模式名称</st> 将用户重定向到 <st c="11044">主页</st>

    • 如果表单数据无效,则执行 <st c="11120">else</st> 部分,并将表单(包括错误)传递到模板中,再次渲染 <st c="11221">accounts/signup.html</st> 模板。

现在,运行服务器,并前往 http://localhost:8000/accounts/signup。首先,尝试使用两个不匹配的密码注册用户(图 8**.3)。

图 8.3 – 出错的注册页面

图 8.3 – 出错的注册页面

然后,尝试使用适当的信息注册 一个用户,你应该会被重定向到 主页 然后,前往 http://127.0.0.1:8000/admin/,导航到 用户 部分,你应该能在数据库中看到新注册的用户(图 8**.4)。

图 8.4 – 用户管理页面

图 8.4 – 用户管理页面

我们现在 可以注册用户。 现在,让我们 自定义 <st c="12437">UserCreationForm</st>

自定义 UserCreationForm

<st c="12483">UserCreationForm</st> 当前 显示了相当多的附加帮助文本(默认包含),这使我们的表单显得杂乱。 为了解决这个问题,我们可以自定义 <st c="12637">UserCreationForm</st> (这是一个很大的主题)。 在这里,我们将对 注册页面 进行一些简单的修改,以改善其外观和感觉。

为了 实施这些修改,我们将遵循 以下步骤:

  1. 创建 <st c="12853">CustomUserCreationForm</st>

  2. 更新 <st c="12888">注册</st> 函数以 使用 <st c="12911">CustomUserCreationForm</st>

  3. 自定义错误 显示方式。

我们将在接下来的 几个小节中详细说明这些步骤。

创建 CustomUserCreationForm

<st c="13087">/accounts/</st> 中,创建一个名为 new file called <st c="13124">forms.py</st> 的新文件。这个文件将包含 accounts 应用的自定义表单。 目前,用以下代码填充它:

 from django.contrib.auth.forms import UserCreationForm
class CustomUserCreationForm(UserCreationForm):
    def __init__(self, *args, **kwargs):
        super(CustomUserCreationForm, self).__init__
            (*args, **kwargs)
        for fieldname in ['username', 'password1',
        'password2']:
            self.fields[fieldname].help_text = None
            self.fields[fieldname].widget.attrs.update(
                {'class': 'form-control'}
            )

让我们解释 这段代码:

  • 我们从 Django 的 <st c="13650">UserCreationForm</st> 类中导入。

  • 我们创建一个名为 <st c="13737">CustomUserCreationForm</st> 的新类,它继承自 <st c="13781">UserCreationForm</st>,使其成为 Django 内置用户 创建表单的子类。

  • 我们定义类构造函数(即 <st c="13898">__init__</st> 方法)。 构造函数通过 <st c="14007">super</st> 方法调用父类的构造函数(<st c="13975">UserCreationForm</st>)。

  • 然后,我们 遍历由 <st c="14069">UserCreationForm</st> 提供的字段。这些是 <st c="14097">'username'</st>, <st c="14109">'password1'</st> <st c="14126">'password2'</st>。对于循环中指定的每个字段,我们将 <st c="14188">help_text</st> 属性设置为 <st c="14211">None</st>,这将移除与这些字段关联的任何帮助文本。 最后,对于循环中指定的每个字段,我们添加 CSS <st c="14337">form-control</st> 类到字段的控件中。 这是一个 Bootstrap 类,它改善了字段的视觉效果。

接下来,让我们在我们的 <st c="14498">signup</st> 函数中使用 <st c="14468">CustomUserCreationForm</st>

将注册功能更新为使用 CustomUserCreationForm

让我们使用这个新表单来改善注册页面的视觉效果。

<st c="14645">/accounts/views.py</st> 中,添加以下加粗的行:

 from django.shortcuts import render <st c="14747">from .forms import CustomUserCreationForm</st> from django.shortcuts import redirect
def signup(request):
    template_data = {}
    template_data['title'] = 'Sign Up'
    if request.method == 'GET':
        template_data['form'] = <st c="14954">CustomUserCreationForm</st>()
        return render(request, 'accounts/signup.html',
            {'template_data': template_data})
    elif request.method == 'POST':
        form = <st c="15099">CustomUserCreationForm</st>(request.POST)
        if form.is_valid():
            form.save()
            return redirect('home.index')
        else:
            template_data['form'] = form
            return render(request, 'accounts/signup.html',
                {'template_data': template_data})

在更新的代码中,我们移除了对 <st c="15361">UserCreationForm</st> 的导入,并添加了对 <st c="15402">CustomUserCreationForm</st>的导入。 然后,我们将对 <st c="15457">UserCreationForm()</st> 的调用替换为对 <st c="15494">CustomUserCreationForm()</st>的调用。

现在,保存这些文件,运行服务器,前往 http://localhost:8000/accounts/signup,并尝试使用两个不匹配的密码注册一个用户(图 8**.5);你会看到外观和感觉 已经得到了改善。

图 8.5 – 带有错误的改进注册页面

图 8.5 – 带有错误的改进注册页面

我们可以改进 错误显示的方式。 所以,让我们在下一节中自定义它。

自定义错误显示方式

让我们自定义 Django 表单显示错误的方式。 <st c="16036">/accounts/forms.py</st>中,添加以下加粗的行: (加粗):

 from django.contrib.auth.forms import UserCreationForm <st c="16153">from django.forms.utils import ErrorList</st>
<st c="16193">from django.utils.safestring import mark_safe</st>
<st c="16239">class CustomErrorList(ErrorList):</st>
 <st c="16273">def __str__(self):</st>
 <st c="16292">if not self:</st>
 <st c="16305">return ''</st>
 <st c="16315">return mark_safe(''.join([</st>
 <st c="16342">f'<div class="alert alert-danger" role="alert"></st>
 <st c="16390">{e}</div>' for e in self]))</st> class CustomUserCreationForm(UserCreationForm):
    …

让我们解释 一下代码:

  • 我们导入 <st c="16507">ErrorList</st> 类,这是一个默认类,用于存储和显示与 表单字段相关的验证错误消息。

  • 我们导入 <st c="16644">mark_safe</st> 函数,该函数用于将字符串标记为适合 HTML 渲染,表示它不包含任何有害内容,应该原样渲染,无需转义。 (无需转义直接渲染)。

  • 我们定义了一个名为 <st c="16853">CustomErrorList</st>的新类,它扩展了 Django 的 <st c="16893">ErrorList</st> 类。 这将是我们定义自定义错误外观和感觉的类。

  • 我们重写了 <st c="16991">__str__()</st> 方法,这是基类 <st c="17020">ErrorList</st> 的方法。 如果错误列表为空(即没有错误),它返回一个空字符串,表示不应生成任何 HTML。 否则,它定义了一个自定义 HTML 代码,该代码使用 <st c="17217"><div></st> 元素和 Bootstrap CSS 类来改进错误显示的方式。 它还使用了 <st c="17320">mark_safe</st> 函数来渲染代码,保持原样。

现在我们已经定义了这个 <st c="17395">CustomErrorList</st> 类,我们只需要指定给我们的表单我们将使用它。

<st c="17479">/accounts/views.py</st>中,添加以下 粗体内容:

 from django.shortcuts import render
from .forms import CustomUserCreationForm<st c="17603">, CustomErrorList</st> from django.shortcuts import redirect
def signup(request):
    template_data = {}
    template_data['title'] = 'Sign Up'
    if request.method == 'GET':
        template_data['form'] = CustomUserCreationForm()
        return render(request, 'accounts/signup.html',
            {'template_data': template_data})
    elif request.method == 'POST':
        form = CustomUserCreationForm(request.POST<st c="17965">,</st>
 <st c="17966">error_class=CustomErrorList</st>)
        ...

我们导入了 我们的 <st c="18017">CustomErrorList</st> 类,并将此类作为参数传递给 <st c="18083">CustomUserCreationForm</st>。这次,如果在提交注册表单时发现错误,表单将使用我们的 <st c="18193">CustomErrorList</st> 类,并使用我们的自定义 HTML 和 CSS 代码显示错误。

现在,保存这些文件,运行服务器,转到 http://localhost:8000/accounts/signup,并尝试使用两个不匹配的密码注册用户(图 8**.6);你会看到外观和感觉 已经改进。

图 8.6 – 改进错误样式的注册页面

图 8.6 – 改进错误样式的注册页面

我们已经改进了我们的错误的外观和感觉。 现在,让我们实现一个 登录页面。

创建登录页面

让我们 实现登录页面。 这次,我们不会使用 Django 表单;我们将创建自己的 HTML 表单(为了学习一种新方法)。 让我们按照以下步骤进行:

  1. 配置登录 URL。

  2. 定义 <st c="18945">登录</st> 函数。

  3. 创建一个账户 登录模板。

  4. 在基础模板中添加一个链接。

  5. 将已注册用户重定向到 登录页面。

我们将在下一章的几个小节中深入探讨创建登录页面的这些步骤。

配置登录 URL

<st c="19187">/accounts/urls.py</st>中,添加 以下路径 的粗体内容:

 from django.urls import path
from . import views
urlpatterns = [
    path('signup', views.signup, name='accounts.signup'), <st c="19357">path('login/', views.login, name='accounts.login'),</st> ]

因此,如果 URL 与 <st c="19435">/accounts/login</st> 路径匹配,它将执行在 <st c="19477">views</st> 文件中定义的 <st c="19482">登录</st> 函数。

现在我们有了新的路径,让我们定义 <st c="19567">登录</st> 函数。

定义登录函数

<st c="19610">/accounts/views.py</st>中,添加以下加粗的行:

 from django.shortcuts import render <st c="19708">from django.contrib.auth import login as auth_login, authenticate</st> from .forms import CustomUserCreationForm, CustomErrorList
from django.shortcuts import redirect <st c="19871">def login(request):</st>
 <st c="19890">template_data = {}</st>
 <st c="19909">template_data['title'] = 'Login'</st>
 <st c="19942">if request.method == 'GET':</st>
 <st c="19970">return render(request, 'accounts/login.html',</st>
 <st c="20016">{'template_data': template_data})</st>
 <st c="20050">elif request.method == 'POST':</st>
 <st c="20081">user = authenticate(</st>
 <st c="20102">request,</st>
 <st c="20111">username = request.POST['username'],</st>
 <st c="20148">password = request.POST['password']</st>
 <st c="20184">)</st>
 <st c="20186">if user is None:</st>
 <st c="20203">template_data['error'] =</st>
 <st c="20228">'The username or password is incorrect.'</st>
 <st c="20269">return render(request, 'accounts/login.html',</st>
 <st c="20315">{'template_data': template_data})</st>
 <st c="20349">else:</st>
 <st c="20355">auth_login(request, user)</st>
 <st c="20381">return redirect('home.index')</st> def signup(request):
    …

让我们 解释 代码:

  • 我们导入 <st c="20469">login</st> <st c="20479">authenticate</st>。这些用于用户认证。 我们导入 <st c="20543">login</st> 并使用别名(<st c="20564">auth_login</st>)以避免与 <st c="20606">login</st> 函数名混淆。

  • 我们创建 <st c="20641">login</st> 函数。 此函数定义 <st c="20679">template_data</st> 并检查 <st c="20697">request.method</st>

  • 对于 <st c="20724">GET</st> 请求,函数渲染 <st c="20763">accounts/login.html</st> 模板。

  • 对于 <st c="20797">POST</st> 请求,函数尝试使用提供的 <st c="20878">username</st> <st c="20891">password</st>进行用户认证。如果认证失败,它将再次渲染带有错误信息的登录模板。 如果认证成功,它将登录用户并将 他们重定向到 主页

现在,让我们创建需要 <st c="21119">login</st> 函数的模板。

创建账户登录模板

<st c="21171">/accounts/templates/accounts/</st>中,创建一个新文件, <st c="21221">login.html</st>。此文件 包含登录页面的 HTML。 目前,用以下内容填充 它:

 {% extends 'base.html' %}
{% block content %}
<div class="p-3 mt-4">
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-md-8">
        <div class="card shadow p-3 mb-4 rounded">
          <div class="card-body">
            <h2>Login</h2>
            <hr />
            {% if template_data.error %}
            <div class="alert alert-danger" role="alert">
              {{ template_data.error }}
            </div>
            {% endif %}
            <form method="POST">
              {% csrf_token %}
              <p>
                <label for="username">Username</label>
                <input id="username" type="text"
                       name="username" required
                       autocomplete="username"
                       class="form-control">
              </p>
              <p>
                <label for="password">Password</label>
                <input id="password" type="password"
                       name="password" required
                       autocomplete="current-password"
                       class="form-control">
              </p>
              <div class="text-center">
                <button type="submit"
                  class="btn bg-dark text-white">Login
                </button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
{% endblock content %}

让我们 解释 代码:

  • 我们扩展 <st c="22254">base.html</st> 模板并定义一个包含文本 <st c="22311">Login</st>的标题元素。

  • 我们检查是否有错误,如果有, 则显示它。

  • 我们创建一个具有 <st c="22412">POST</st> 方法<st c="22432">csrf_token</st> 令牌 的 HTML 表单。 此表单包含两个输入,一个用于用户名,另一个用于密码。 它还包含一个 提交按钮。

让我们继续,将登录链接添加到 基本模板。

将链接添加到基本模板

让我们 在基本模板中添加登录链接。 <st c="22715">/moviesstore/templates/base.html</st>中,在标题部分,添加以下加粗的 行:

 …
          <div class="navbar-nav ms-auto navbar-ml">
            <a class="nav-link"
              href="{% url 'home.about' %}">About</a>
            <a class="nav-link"
              href="{% url 'movies.index' %}">Movies</a>
            <div class=
              "vr bg-white mx-2 d-none d-lg-block"></div> <st c="23035"><a class="nav-link"</st>
 <st c="23054">href="{% url 'accounts.login' %}">Login</a></st> <a class="nav-link"
              href="{% url 'accounts.signup' %}">Sign Up
            </a>
          </div>
          …

现在,保存 这些文件,运行服务器,并访问 http://localhost:8000/accounts/login;您将看到新的 登录 页面(图 8**.7)。

图 8.7 – 登录页面

图 8.7 – 登录页面

现在我们有了登录页面,让我们在用户创建 账户时将其重定向到该页面。

将注册用户重定向到登录页面

让我们通过将刚刚注册的用户重定向到 <st c="23625">/accounts/views.py</st>,添加以下加粗的 内容来结束本节:

 …
def signup(request):
    template_data = {}
    template_data['title'] = 'Sign Up'
    if request.method == 'GET':
        template_data['form'] = CustomUserCreationForm()
        return render(request, 'accounts/signup.html',
            {'template_data': template_data})
    elif request.method == 'POST':
        form = CustomUserCreationForm(request.POST,
            error_class=CustomErrorList)
        if form.is_valid():
            form.save()
            return redirect(<st c="24058">'accounts.login</st>')
            …

我们刚刚修改了重定向到 登录 页面的操作。 尝试创建一个新用户,他们应该被重定向到 登录 页面。

让我们通过实现 登出功能来结束这一章。

实现登出功能

我们将 按照以下 步骤进行:

  1. 配置一个 登出 URL。

  2. 定义 <st c="24380">登出</st> 函数。

  3. 基本模板中 添加一个链接。

我们将在接下来的章节中执行这些步骤。

配置登出 URL

<st c="24512">/accounts/urls.py</st>中,添加 以下加粗的 路径:

 from django.urls import path
from . import views
urlpatterns = [
    path('signup', views.signup, name='accounts.signup'),
    path('login/', views.login, name='accounts.login'), <st c="24732">path('logout/', views.logout, name='accounts.logout'),</st> ]

现在,如果 URL 与 <st c="24814">/accounts/logout</st> 路径匹配,它将执行在 <st c="24857">views</st> 文件中定义的 <st c="24888">登出</st> 函数。

定义登出函数

<st c="24932">/accounts/views.py</st>中,添加 以下加粗的 行:

 from django.shortcuts import render
from django.contrib.auth import login as auth_login, authenticate<st c="25095">, logout as auth_logout</st> from .forms import CustomUserCreationForm, CustomErrorList
from django.shortcuts import redirect <st c="25216">from django.contrib.auth.decorators import login_required</st>
<st c="25273">@login_required</st>
<st c="25289">def logout(request):</st>
 <st c="25310">auth_logout(request)</st>
 <st c="25331">return redirect('home.index')</st> def login(request):
    …

让我们解释 以下代码:

  • 我们导入 <st c="25422">登出</st> 函数作为 <st c="25441">auth_logout</st>。这用于登出用户。

  • 我们导入了 <st c="25496">login_required</st>,这是一个装饰器,用于确保只有经过身份验证的用户才能访问特定的视图函数。 Django 装饰器 是一个函数,它 包装另一个函数或方法以修改其行为。 装饰器通常用于诸如认证、权限 和日志记录等。

  • 我们创建了一个 <st c="25808">注销</st> 函数,该函数使用了 <st c="25840">login_required</st> 装饰器。 这意味着只有经过身份验证的用户才能访问 此函数。

  • <st c="25937">注销</st> 函数调用 <st c="25959">auth_logout</st>,该函数用于注销当前用户。 然后,该函数将用户重定向到 主页

接下来,让我们将注销链接添加到 基本模板。

将链接添加到基本模板

<st c="26165">/moviesstore/templates/base.html</st>中,在标题部分,添加以下 行,这些行是 粗体:

 …
            <a class="nav-link"
              href="{% url 'home.about' %}">About</a>
            <a class="nav-link"
              href="{% url 'movies.index' %}">Movies</a>
            <div class=
              "vr bg-white mx-2 d-none d-lg-block"></div> <st c="26444">{% if user.is_authenticated %}</st>
 <st c="26474"><a class="nav-link"</st>
 <st c="26494">href="{% url 'accounts.logout' %}">Logout ({{</st>
 <st c="26540">user.username }})</a></st>
 <st c="26562">{% else %}</st> <a class="nav-link"
              href="{% url 'accounts.login' %}">Login</a>
            <a class="nav-link"
              href="{% url 'accounts.signup' %}">Sign Up
            </a> <st c="26706">{% endif %}</st> …

我们使用了一个 Django 模板标签,该标签检查用户是否经过身份验证(已登录)。 这种验证来自 Django 的认证系统。 如果用户经过身份验证,我们显示 注销 选项(包括用户名)。 否则,我们显示 登录 注册 *选项。

现在,保存这些文件,运行服务器,并访问 http://localhost:8000/;您将看到导航栏选项如何根据用户是否登录而改变(图 8**.8)。

图 8.8 – 更新后的主页

图 8.8 – 更新后的主页

我们已经完成了 用户注册、登录和 注销系统。

总结

在本章中,我们实现了一个完整的认证系统。 现在,用户可以注册、登录和注销。 我们还学习了如何利用一些 Django 表单,如何创建我们自己的 HTML 表单,以及如何处理验证 和错误。

在下一章中,我们将实现一个电影 评论系统。**

第十章:9

让用户创建、阅读、更新和删除电影评论

实现了认证系统后,现在是时候让登录用户对电影的评论执行标准的 CRUD 操作了。 本章将教您如何执行完整的 CRUD 操作以及如何 管理授权。

在本章中,我们将涵盖以下主题:

  • 创建评论模型

  • 创建评论

  • 阅读评论

  • 更新 评论

  • 删除 评论

到本章结束时,您将学会如何为您的模型创建 CRUD 操作并处理授权。 您还将回顾如何使用表单以及如何管理不同的 HTTP 方法。

技术要求

在本章中,我们将使用 Python 3.10 或更高版本。 此外,本书中将使用 VS Code 编辑器,您可以从 以下位置下载 https://code.visualstudio.com/

本章的代码位于 以下位置 https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter09/moviesstore

本章的 CiA 视频可以在 以下位置找到 https://packt.link/dsqdR

创建评论模型

为了存储电影的评论 信息,我们需要创建一个评论 Django 模型并遵循 以下步骤:

  1. 创建 评论模型。

  2. 应用迁移。

  3. 将评论模型添加到 管理面板。

创建评论模型

评论信息与电影紧密相关。 因此,我们将此模型包含在 <st c="1421">movies</st> 应用中。 <st c="1440">/movies/models.py</st> 文件中,添加以下加粗的部分:

 from django.db import models <st c="1547">from django.contrib.auth.models import User</st> class Movie(models.Model):
    … <st c="1620">class Review(models.Model):</st>
 <st c="1647">id = models.AutoField(primary_key=True)</st>
 <st c="1687">comment = models.CharField(max_length=255)</st>
 <st c="1730">date = models.DateTimeField(auto_now_add=True)</st>
 <st c="1777">movie = models.ForeignKey(Movie,</st>
 <st c="1810">on_delete=models.CASCADE)</st>
 <st c="1836">user = models.ForeignKey(User,</st>
**<st c="1867">on_delete=models.CASCADE)</st>**
 **<st c="1893">def __str__(self):</st>**
 **<st c="1912">return str(self.id) + ' - ' + self.movie.name</st>**

**让我们解释一下 前面的代码:

  • 我们从 Django 的 <st c="2007">User</st> 模型中导入 <st c="2032">django.contrib.auth.models</st> 模块。

  • 我们定义了一个名为 <st c="2098">Review</st>的 Python 类,它继承自 <st c="2126">models.Model</st>。这意味着 <st c="2156">Review</st> 是一个 Django 模型类。

  • <st c="2199">Review</st> 类中,我们定义了 几个字段:

    • <st c="2238">id</st> 是一个 <st c="2248">AutoField</st>,它自动为数据库中添加的每个新记录增加其值。 <st c="2347">primary_key=True</st> 参数指定该字段是表的 主键,唯一标识 每条记录。

    • <st c="2467">评论</st> 是一个 <st c="2481">CharField</st>,它代表一个最大长度为 255 个字符的字符串字段。 它存储电影评论文本。

    • <st c="2597">日期</st> 是一个 <st c="2608">DateTimeField</st> ,用于日期和时间数据。 <st c="2665">auto_now_add=True</st> 确保在创建评论时,日期和时间会自动设置为当前日期和时间。

    • <st c="2792">电影</st> 是与 <st c="2836">Movie</st> 模型的外键关系。 评论与电影相关联。 <st c="2890">on_delete</st> 参数指定如何处理与评论相关联的电影的删除。 在这种情况下, <st c="3006">on_delete=models.CASCADE</st> 意味着如果相关电影被删除,相关的评论也将被删除。

    • <st c="3118">用户</st> 是另一个外键关系,但指向 <st c="3171">User</st> 模型。 评论与用户(撰写评论的人)相关联。 类似于 <st c="3268">电影</st> 属性, <st c="3285">on_delete=models.CASCADE</st> 指定如果相关用户被删除,相关的评论也将被删除。

  • <st c="3400">__str__</st> 是一个返回评论字符串表示的方法。 在这种情况下,它返回一个由评论 ID 和与评论相关的电影名称组成的字符串。 评论。

应用迁移

现在我们已经创建了 <st c="3641">Review</st> 模型,让我们通过运行以下 命令将更改应用到我们的数据库中,具体命令根据您的 操作系统 而定:

  • For macOS, run this:
 python3 manage.py makemigrations
python3 manage.py migrate
  • For Windows, run this:
 python manage.py makemigrations
python manage.py migrate

Now, you should see something like in Figure 9**.1:

Figure 9.1 – Applying the review migration

Figure 9.1 – Applying the review migration

Add the review model to the admin panel

To add the <st c="4146">Review</st> model to <st c="4162">admin</st>, go back to <st c="4180">/movies/admin.py</st> and register it by adding the following parts that are highlighted in bold:

 from django.contrib import admin
from .models import Movie<st c="4331">, Review</st> class MovieAdmin(admin.ModelAdmin):
    ordering = ['name']
    search_fields = ['name']
admin.site.register(Movie, MovieAdmin) <st c="4561">/admin</st>. The review model will now show up (as shown in *<st c="4616">Figure 9</st>**<st c="4624">.2</st>*):
			![Figure 9.2 – The admin page with reviews available](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_09_2.jpg)

			<st c="4812">Figure 9.2 – The admin page with reviews available</st>
			<st c="4862">Now that we have created and applied our</st> `<st c="4904">Review</st>` <st c="4910">model, let’s create the functionality to</st> <st c="4952">create reviews.</st>
			<st c="4967">Creating reviews</st>
			<st c="4984">To allow users to</st> <st c="5002">create reviews, we need to follow the</st> <st c="5041">next steps:</st>

				1.  <st c="5052">Update the</st> `<st c="5064">movies.show</st>` <st c="5075">template.</st>
				2.  <st c="5085">Define the</st> `<st c="5097">create_review</st>` <st c="5110">function.</st>
				3.  <st c="5120">Configure</st> <st c="5130">the</st> `<st c="5135">create</st>` `<st c="5142">review</st>` <st c="5148">URL.</st>

			<st c="5153">Updating the movies.show template</st>
			<st c="5187">We will include a form to</st> <st c="5213">allow authenticated users to create reviews.</st> <st c="5259">This form will be included in the</st> `<st c="5293">movies.show</st>` <st c="5304">template.</st> <st c="5315">In the</st> `<st c="5322">/movies/templates/movies/show.html</st>` <st c="5356">file, add the following, as presented</st> <st c="5395">in bold:</st>

    <p><b>Price:</b> ${{ template_data.movie.price }}</p> <st c="5459">{% if user.is_authenticated %}</st>

Create a review



<form method="POST" action=

"{% url 'movies.create_review'

id=template_data.movie.id %}">

{% csrf_token %}

<textarea name="comment" required

class="form-control"

id="comment">

**

<button type="submit"

class="btn bg-dark text-white">

Add Review

{% endif %}

<div class="col-md-6 mx-auto mb-3 text-center">

    <img src="img/{{ template_data.movie.image.url }}"

    class="rounded img-card-400" />

</div>

…

{% endblock content %}**


 **<st c="6264">Let’s explain the</st> <st c="6282">preceding code:</st>

*   <st c="6298">We use the</st> `<st c="6310">{% if user.is_authenticated %}</st>` <st c="6340">DTL conditional statement that checks whether the user is authenticated (logged in).</st> <st c="6426">If the user is authenticated, the block of HTML code within the</st> `<st c="6490">if</st>` <st c="6492">statement will be rendered</st> <st c="6520">and displayed.</st>

*   <st c="6534">We create an HTML form with the</st> `<st c="6567">POST</st>` <st c="6571">method and the</st> `<st c="6587">csrf_token</st>` <st c="6597">token.</st> <st c="6605">This form contains a single input named</st> `<st c="6645">comment</st>`<st c="6652">. This input stores the review text.</st> <st c="6689">The form also contains a</st> <st c="6714">submit button.</st>

*   <st c="6728">The form is linked</st> <st c="6747">to the</st> `<st c="6755">movies.create_review</st>` <st c="6775">URL, and it also passes the movie ID to that URL.</st> <st c="6826">The movie ID will be used to link the current comment with the movie that</st> <st c="6900">it represents.</st>

## <st c="6914">Defining the create_review function</st>

<st c="6950">In</st> `<st c="6954">/movies/views.py</st>`<st c="6970">, add the following, as</st> <st c="6994">presented</st> <st c="7004">in bold:</st>

from django.shortcuts import render, redirect from .models import Movie, Review

from django.contrib.auth.decorators import login_required def index(request):

def show(request):

… <st c="7194">@login_required</st>

def create_review(request, id):

if request.method == 'POST' and request.POST['comment']

!= '':

movie = Movie.objects.get(id=id)

review = Review()

review.comment = request.POST['comment']

review.movie = movie

review.user = request.user

review.save()

return redirect('movies.show', id=id)

else:

return redirect('movies.show', id=id)


 **<st c="7539">Let’s explain the</st> <st c="7558">preceding code:</st>

*   <st c="7573">We import the</st> `<st c="7588">redirect</st>` <st c="7596">function, which is</st> <st c="7616">used to redirect the user to a</st> <st c="7647">different URL.</st>

*   <st c="7661">We import the</st> `<st c="7676">Review</st>` <st c="7682">model, which will be used to create</st> <st c="7719">new reviews.</st>

*   <st c="7731">We import</st> `<st c="7742">login_required</st>`<st c="7756">, which is used to verify that only logged users can access the</st> `<st c="7820">create_review</st>` <st c="7833">function.</st> <st c="7844">If a guest user attempts to access this function via the corresponding URL, they will be redirected to the</st> <st c="7951">login page.</st>

*   <st c="7962">We create the</st> `<st c="7977">create_review</st>` <st c="7990">function that handles creating</st> <st c="8022">a review.</st>

*   <st c="8031">The</st> `<st c="8036">create_review</st>` <st c="8049">takes two arguments: the</st> `<st c="8075">request</st>` <st c="8082">that contains information about the HTTP request, and the</st> `<st c="8141">id</st>`<st c="8143">, which represents the ID of the movie for which a review is</st> <st c="8204">being created.</st>

*   <st c="8218">Then, we check whether the request method is</st> `<st c="8264">POST</st>` <st c="8268">and the</st> `<st c="8277">comment</st>` <st c="8284">field in the request’s</st> `<st c="8308">POST</st>` <st c="8312">data is not empty.</st> <st c="8332">If that is</st> `<st c="8343">TRUE</st>`<st c="8347">, the</st> <st c="8353">following happens:</st>

*   <st c="8371">We retrieve the movie using</st> `<st c="8400">Movie.objects.get(id=id)</st>` <st c="8424">based on the</st> <st c="8438">provided</st> `<st c="8447">id</st>`<st c="8449">.</st>
    *   <st c="8450">We create a new</st> `<st c="8467">Review</st>` <st c="8473">object.</st>
    *   <st c="8481">We set the review properties</st> <st c="8511">as follows:</st>
        *   <st c="8522">We set the</st> `<st c="8534">comment</st>` <st c="8541">based on the comments collected in</st> <st c="8577">the form</st>
        *   <st c="8585">We set the</st> `<st c="8597">movie</st>`<st c="8602">, based on the retrieved movie from</st> <st c="8638">the database</st>
        *   <st c="8650">We set the</st> `<st c="8662">user</st>`<st c="8666">, based on the</st> <st c="8681">authenticated user who submitted</st> <st c="8714">the form.</st>
    *   <st c="8723">Finally, we save the review to the database and redirect the user to the movie</st> <st c="8803">show page.</st>

*   <st c="8813">In the</st> `<st c="8821">else</st>` <st c="8825">case, we redirect the user to the movie show page using the</st> `<st c="8886">redirect('movies.show',</st>` `<st c="8910">id=id)</st>` <st c="8916">code.</st>

## <st c="8922">Configuring the create review URL</st>

<st c="8956">In</st> `<st c="8960">/movies/urls.py</st>`<st c="8975">, add the next path as highlighted</st> <st c="9010">in</st> <st c="9012">bold:</st>

from django.urls import path

from . import views

urlpatterns = [

path('', views.index, name='movies.index'),

path('<int:id>/', views.show, name='movies.show'), <st c="9179">path('<int:id>/review/create/', views.create_review,</st>

name='movies.create_review'), ]


<st c="9263">Let’s analyze the new path.</st> <st c="9291">The</st> `<st c="9295"><int:id></st>` <st c="9303">part indicates that this path expects an integer value to be passed from the URL and that the integer value will be associated with a variable named</st> `<st c="9453">id</st>`<st c="9455">. The</st> `<st c="9461">id</st>` <st c="9463">variable will be used to identify to which movie the review that we want to create is linked.</st> <st c="9558">For example, if the form is submitted to</st> `<st c="9599">movies/1/review/create</st>`<st c="9621">, it indicates that the new review will be associated with the movie</st> <st c="9690">with</st> `<st c="9695">id</st>`<st c="9697">=</st>`<st c="9699">1</st>`<st c="9700">.</st>

<st c="9701">Now save those files, run the</st> <st c="9732">server, and go to</st> `<st c="9750">http://localhost:8000/movies</st>`<st c="9778">. Click on a specific movie and you will see the form to create reviews (</st>*<st c="9851">Figure 9</st>**<st c="9860">.3</st>*<st c="9862">).</st>

			![Figure 9.3 – A movie page with the review form](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_09_3.jpg)

<st c="10073">Figure 9.3 – A movie page with the review form</st>

<st c="10119">Then, enter a comment and click</st> **<st c="10152">Add Review</st>**<st c="10162">. A new review should be created, and you should be redirected to the movie show page.</st> <st c="10249">Go to the admin panel, click</st> **<st c="10278">Reviews</st>**<st c="10285">, and you will see the new review there (</st>*<st c="10326">Figure 9</st>**<st c="10335">.4</st>*<st c="10337">).</st>

			![Figure 9.4 – The reviews admin page](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_09_4.jpg)

<st c="10478">Figure 9.4 – The reviews admin page</st>

<st c="10513">Let’s now include a</st> <st c="10533">functionality to read and list reviews from our</st> <st c="10582">web application.</st>

# <st c="10598">Reading reviews</st>

<st c="10614">To be able to read and list reviews, we</st> <st c="10655">need to follow the steps</st> <st c="10680">that follow:</st>

1.  <st c="10692">Update the</st> `<st c="10704">movies.show</st>` <st c="10715">template.</st>

2.  <st c="10725">Update the</st> `<st c="10737">show</st>` <st c="10741">function.</st>

## <st c="10751">Updating the movies.show template</st>

<st c="10785">We will list the</st> <st c="10802">reviews in the</st> `<st c="10818">movies.show</st>` <st c="10829">template.</st> <st c="10840">In the</st> `<st c="10847">/movies/templates/movies/show.html</st>` <st c="10881">file, add the following, as highlighted</st> <st c="10922">in bold:</st>

    <p><b>价格:</b> ${{ template_data.movie.price }}

    </p> <st c="10987"><h2>评论</h2></st>


    {% for review in template_data.reviews %}

  • 评论者:{{ review.user.username }}

    {{ review.date }}

    {{ review.comment }}

  • {% endfor %}

{% if user.is_authenticated %}


<st c="11353">We have added a new section inside the template.</st> <st c="11403">This section iterates through the</st> `<st c="11437">reviews</st>` <st c="11444">and displays</st> <st c="11457">the review</st> `<st c="11469">date</st>` <st c="11473">and</st> `<st c="11478">comment</st>`<st c="11485">, as well as the username of the user who created</st> <st c="11535">the review.</st>

## <st c="11546">Updating the show function</st>

<st c="11573">In</st> `<st c="11577">/movies/views.py</st>`<st c="11593">, add the following, as</st> <st c="11616">highlighted</st> <st c="11629">in bold:</st>

def show(request, id):

movie =  Movie.objects.get(id=id) <st c="11695">reviews = Review.objects.filter(movie=movie)</st> template_data = {}

template_data['title'] = movie.name

template_data['movie'] = movie <st c="11826">template_data['reviews'] = reviews</st> return render(request, 'movies/show.html',

    {'template_data': template_data})


<st c="11939">Let’s explain the</st> <st c="11958">preceding code.</st>

*   <st c="11973">We retrieve all review objects that are associated with the movie that we are showing.</st> <st c="12061">To do this, we use the</st> `<st c="12084">filter</st>` <st c="12090">method to limit the query to reviews related to the</st> <st c="12143">specific movie.</st>

*   <st c="12158">We add those reviews to the</st> `<st c="12187">template_data</st>` <st c="12200">dictionary, which is passed to the</st> `<st c="12236">movies/show.html</st>` <st c="12252">template.</st>

<st c="12262">Now, save those files, run the server, and go to</st> `<st c="12312">http://localhost:8000/movies</st>`<st c="12340">. Click on a specific movie that contains reviews and you will see the movie information, including its corresponding reviews (</st>*<st c="12467">Figure 9</st>**<st c="12476">.5</st>*<st c="12478">).</st>

			![Figure 9.5 – A movie page with reviews](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_09_5.jpg)

<st c="12700">Figure 9.5 – A movie page with reviews</st>

<st c="12738">Now, let’s move on</st> <st c="12757">to</st> <st c="12761">updating reviews.</st>

# <st c="12778">Updating a review</st>

<st c="12796">To be able to update reviews, we need to</st> <st c="12838">follow</st> <st c="12845">these steps:</st>

1.  <st c="12857">Update the</st> `<st c="12869">movies.show</st>` <st c="12880">template.</st>

2.  <st c="12890">Create the</st> `<st c="12902">movies</st>` `<st c="12909">edit_review</st>` <st c="12920">template.</st>

3.  <st c="12930">Define the</st> `<st c="12942">edit_review</st>` <st c="12953">function.</st>

4.  <st c="12963">Configure the</st> `<st c="12978">edit</st>` `<st c="12983">review</st>` <st c="12989">URL.</st>

## <st c="12994">Updating movies.show template</st>

<st c="13024">In</st> `<st c="13028">/movies/templates/movies/show.html</st>` <st c="13062">file, add the following</st> <st c="13087">bold text:</st>

    {% for review in template_data.reviews %}

    <li class="list-group-item pb-3 pt-3">

        <h5 class="card-title">

        评论者:{{ review.user.username }}

        </h5>

        <h6 class="card-subtitle mb-2 text-muted">

        {{ review.date }}

        </h6>

        <p class="card-text">{{ review.comment }}</p> <st c="13360">{% if user.is_authenticated and user ==</st>

review.user %}

<a class="btn btn-primary"

href="{% url 'movies.edit_review'

id=template_data.movie.id

review_id=review.id %}">编辑

{% endif %}

    {% endfor %}

    …

<st c="13568">We added a code snippet for</st> <st c="13597">each review that is displayed.</st> <st c="13628">That code checks whether a user is authenticated and whether the user is the one who wrote a specific review.</st> <st c="13738">If both of these conditions are true, it will render the</st> `<st c="13827">movies.edit_review</st>` <st c="13845">URL.</st>

## <st c="13850">Creating the movies edit_review template</st>

<st c="13891">Now, in</st> `<st c="13900">/movies/templates/movies/</st>`<st c="13925">, create a new file,</st> `<st c="13946">edit_review.html</st>`<st c="13962">. For now, fill it in with</st> <st c="13989">the</st> <st c="13993">following:</st>

{% extends 'base.html' %}

{% block content %}

<div class="row mt-3">

<div class="col mx-auto mb-3">

    <h2>编辑审查</h2>

    <hr />

    <form method="POST">

    {% csrf_token %}

    <p>

        <label for="comment">评论:</label>

        <textarea name="comment" required

        class="form-control" id="comment">{{

        template_data.review.comment }}</textarea>

    </p>

    <div class="text-start">

        <button type="submit"

        class="btn bg-dark text-white">编辑评论

        </button>

    </div>

    </form>

</div>

</div>

{% endblock content %}


<st c="14538">We have created a form to edit the review.</st> <st c="14582">This form is very similar to the review creation form.</st> <st c="14637">The differences</st> <st c="14653">are</st> <st c="14657">as follows:</st>

*   <st c="14668">We removed the form action, which means that the form will be submitted to the</st> <st c="14748">current URL</st>

*   <st c="14759">We displayed the current review comment value inside the</st> <st c="14817">text area</st>

*   <st c="14826">We modified the</st> <st c="14843">button text</st>

## <st c="14854">Defining the edit_review function</st>

<st c="14888">In</st> `<st c="14892">/movies/views.py</st>`<st c="14908">, add the</st> <st c="14917">following, as highlighted</st> <st c="14944">in bold:</st>

from django.shortcuts import render, redirect, get_object_or_404@login_required

def edit_review(request, id, review_id):

review = get_object_or_404(Review, id=review_id)

if request.user != review.user:

return redirect('movies.show', id=id)

如果请求方法为'GET':

template_data = {}

template_data['title'] = '编辑评论'

template_data['review'] = review

返回渲染请求,模板为'movies/edit_review.html',参数为{'template_data': template_data})

{'template_data': template_data})

如果请求方法为'POST'并且

请求的 POST 数据中的'comment'字段不为空

获取 Review 对象,id 为 review_id)

评论的评论内容设置为请求的 POST 数据中的'comment'字段

保存评论)

返回重定向到'movies.show',参数 id 为 id)

否则:

返回重定向到'movies.show',参数 id 为 id)


 **<st c="15641">Let’s explain the</st> <st c="15660">preceding code:</st>

*   <st c="15675">We import the</st> `<st c="15690">get_object_or_404</st>` <st c="15707">function, which retrieves an object from the database or</st> <st c="15764">raises an HTTP 404 (Not Found) error (if the object is</st> <st c="15820">not found).</st>

*   <st c="15831">We use the</st> `<st c="15843">@login_required</st>` <st c="15858">decorator to ensure that the</st> `<st c="15888">edit_review</st>` <st c="15899">function can only be accessed by authenticated users.</st> <st c="15954">If an unauthenticated user tries to access this function, they will be redirected to the</st> <st c="16043">login page.</st>

*   <st c="16054">We define the</st> `<st c="16069">edit_review</st>` <st c="16080">function, which takes three parameters: the request, the movie ID, and the</st> <st c="16156">review ID.</st>

*   <st c="16166">We retrieve the</st> `<st c="16183">Review</st>` <st c="16189">object with the given</st> `<st c="16212">review_id</st>`<st c="16221">. If the review does not exist, a 404 error will</st> <st c="16270">be raised.</st>

*   <st c="16280">We check whether the current user (</st>`<st c="16316">request.user</st>`<st c="16329">) is the owner of the review to be edited (</st>`<st c="16373">review.user</st>`<st c="16385">).</st> <st c="16389">If the user does not own the review, the function redirects them to the</st> `<st c="16461">movie.show</st>` <st c="16471">page.</st>

*   <st c="16477">Then, we check whether the request method is</st> `<st c="16523">GET</st>`<st c="16526">. In that case, the function prepares data for the template and renders the</st> `<st c="16602">edit_review.html</st>` <st c="16618">template.</st>

*   <st c="16628">If the request method is</st> `<st c="16654">POST</st>` <st c="16658">and the</st> `<st c="16667">comment</st>` <st c="16674">field in the request’s</st> `<st c="16698">POST</st>` <st c="16702">data is not empty, the</st> <st c="16725">function proceeds to update the review and redirects the user to the movie</st> <st c="16801">show page.</st>

*   <st c="16811">In any other case, the function redirects the user to the movie</st> <st c="16876">show page.</st>

<st c="16886">Note</st>

<st c="16891">You can improve the look and feel of these functionalities by including your own error messages.</st> <st c="16989">You can use the</st> `<st c="17005">login</st>` <st c="17010">template and the</st> `<st c="17028">login</st>` <st c="17033">function, which uses and passes a</st> `<st c="17068">template_data.error</st>`<st c="17087">, as</st> <st c="17092">a base.</st>

## <st c="17099">Configuring the edit_review URL</st>

<st c="17131">In</st> `<st c="17135">/movies/urls.py</st>`<st c="17150">, add the next path, as shown</st> <st c="17180">in</st> <st c="17182">bold:</st>

从 django.urls 导入 path

从 . 导入 views

urlpatterns = [

路径('', views.index, name='movies.index'),

路径('<int:id>/', views.show, name='movies.show'),

路径('<int:id>/review/create/', views.create_review,

    name='movies.create_review'), <st c="17432">路径('<int:id>/review/<int:review_id>/edit/',</st><st c="17477">views.edit_review, name='movies.edit_review'),</st> ]

<st c="17526">This path captures two integer values (the movie ID and review ID) from the URL and passes them to the</st> `<st c="17629">edit_review</st>` <st c="17640">function</st> <st c="17649">as arguments.</st>

<st c="17663">Now, save those files, run the server, and go to</st> `<st c="17713">http://localhost:8000/movies</st>`<st c="17741">. Click on a specific movie that contains a review you created, then click the</st> **<st c="17820">Edit</st>** <st c="17824">button (</st>*<st c="17833">Figure 9</st>**<st c="17842">.6</st>*<st c="17844">).</st>

			![Figure 9.6 – A movie page with reviews and an edit button](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_09_6.jpg)

<st c="18050">Figure 9.6 – A movie page with reviews and an edit button</st>

<st c="18107">An edit form will be shown.</st> <st c="18136">Modify the review and click the</st> **<st c="18168">Edit Review</st>** <st c="18179">button (</st>*<st c="18188">Figure 9</st>**<st c="18197">.7</st>*<st c="18199">).</st>

			![Figure 9.7 – The Edit Review page](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_09_7.jpg)

<st c="18270">Figure 9.7 – The Edit Review page</st>

<st c="18303">You will be redirected to the movie show page.</st> <st c="18351">The new review comment</st> <st c="18374">should appear.</st>

<st c="18388">We just learned how to update</st> <st c="18418">reviews and models in general, so let’s move to the final functionality and learn how to</st> <st c="18508">delete information.</st>

# <st c="18527">Deleting a review</st>

<st c="18545">To be able to delete reviews, we</st> <st c="18579">need to follow the</st> <st c="18598">ensuing steps:</st>

1.  <st c="18612">Update the</st> `<st c="18624">movies.show</st>` <st c="18635">template.</st>

2.  <st c="18645">Define the</st> `<st c="18657">delete_review</st>` <st c="18670">function.</st>

3.  <st c="18680">Configure the</st> `<st c="18695">delete</st>` `<st c="18702">review</st>` <st c="18708">URL.</st>

## <st c="18713">Updating the movies.show template</st>

<st c="18747">In the</st> `<st c="18755">/movies/templates/movies/show.html</st>` <st c="18789">file, add the</st> <st c="18803">following</st> <st c="18814">bolded code:</st>

        <h5 class="card-title">

        评论者:{{ review.user.username }}

        </h5>

        <h6 class="card-subtitle mb-2 text-muted">

        {{ review.date }}

        </h6>

        <p class="card-text">{{ review.comment }}</p>

        {% if user.is_authenticated and user ==

        review.user %}

        <a class="btn btn-primary"

        href="{% url 'movies.edit_review' %}

        id=template_data.movie.id

        review_id=review.id %}">编辑

        </a> <st c="19184"><a class="btn btn-danger"</st>

href="{% url 'movies.delete_review' %}

id=template_data.movie.id

review_id=review.id %}">删除

{% endif %}


<st c="19321">We have added a new delete</st> <st c="19348">button.</st> <st c="19357">This button links to the</st> `<st c="19382">movies.delete_review</st>` <st c="19402">URL, and much like the</st> **<st c="19426">Edit</st>** <st c="19430">button, it passes the movie ID and the</st> <st c="19470">review ID.</st>

## <st c="19480">Defining the delete_review function</st>

<st c="19516">In</st> `<st c="19520">/movies/views.py</st>`<st c="19536">, add the following</st> <st c="19555">bold code at the end of</st> <st c="19580">the file:</st>

@login_required

def delete_review(request, id, review_id):

获取 Review 对象,id 为 review_id,如果不存在则返回 404

user=request.user)

**删除评论)

返回重定向到'movies.show',参数 id 为 id)


 **<st c="19771">Let’s explain the</st> <st c="19790">preceding code:</st>

*   <st c="19805">We use the</st> `<st c="19817">@login_required</st>` <st c="19832">decorator to ensure that the</st> `<st c="19862">delete_review</st>` <st c="19875">function can be only accessed by authenticated users.</st> <st c="19930">If an unauthenticated user tries to access this function, they will be redirected to the</st> <st c="20019">login page.</st>

*   <st c="20030">We retrieve the</st> `<st c="20047">Review</st>` <st c="20053">object with the given</st> `<st c="20076">review_id</st>` <st c="20085">that belongs to the current user (</st>`<st c="20120">request.user</st>`<st c="20133">).</st> <st c="20137">If the review does not exist, or if the user does not own the review, an HTTP 404 error will</st> <st c="20230">be raised.</st>

*   <st c="20240">We delete the review from the database using the Django model’s</st> `<st c="20305">delete()</st>` <st c="20313">method.</st>

*   <st c="20321">We redirect to the previous movie</st> <st c="20356">show page.</st>

## <st c="20366">Configuring the delete_review URL</st>

<st c="20400">In</st> `<st c="20404">/movies/urls.py</st>`<st c="20419">, add the following path, as highlighted</st> <st c="20459">in bold:</st>

urlpatterns = [

…

路径('<int:id>/review/<int:review_id>/edit/',

    views.edit_review, name='movies.edit_review'), <st c="20581">路径('<int:id>/review/<int:review_id>/delete/',</st>

删除评论视图,名称为'movies.delete_review'), ]


<st c="20681">This path captures two integer values (the movie ID and the review ID) from the URL and passes them as arguments to the</st> `<st c="20801">delete_review</st>` <st c="20814">function.</st>

<st c="20824">Now, save those files, run</st> <st c="20851">the server, and go to</st> `<st c="20874">http://localhost:8000/movies</st>`<st c="20902">. Click on a specific movie that contains a review that you created, then click the</st> **<st c="20986">Delete</st>** <st c="20992">button (</st>*<st c="21001">Figure 9</st>**<st c="21010">.8</st>*<st c="21012">).</st>

			![Figure 9.8 – A movie page with reviews and a Delete button](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_09_8.jpg)

<st c="21226">Figure 9.8 – A movie page with reviews and a Delete button</st>

<st c="21284">The review should be deleted, and</st> <st c="21318">you should be redirected to the movie</st> <st c="21357">show page.</st>

# <st c="21367">Summary</st>

<st c="21375">In this chapter, we implemented a complete CRUD for movie reviews.</st> <st c="21443">With the tools we’ve developed, we can now create various CRUD systems by applying the knowledge gained in this chapter to other projects and models.</st> <st c="21593">As for the</st> *<st c="21604">Movies Store</st>* <st c="21616">project, users can now create, read, update, and delete reviews.</st> <st c="21682">Additionally, we have acquired the skills to manage application authorization, restricting access to certain routes and functions for</st> <st c="21816">non-logged-in users.</st>

<st c="21836">In the next chapter, we will learn how to create a</st> <st c="21888">shopping cart.</st>******** 
```**


# 第十一章:<st c="0">10</st>

# <st c="3">实现购物车系统</st>

<st c="39">在本章中,我们将学习如何为网站创建购物车。</st> <st c="121">为了实现这一功能,我们需要了解</st> <st c="169">如何</st> **<st c="174">使用网络会话</st>** <st c="186">以及如何使用</st> **<st c="207">Django 会话</st>**<st c="222">。Django 会话</st> <st c="239">将用于在用户浏览网站时存储特定于用户的信息。</st>

<st c="322">在本章中,我们将涵盖以下主题:</st> <st c="364">包括:</st>

+   <st c="381">介绍</st> <st c="394">网络会话</st>

+   <st c="406">创建一个</st> <st c="418">购物车应用程序</st>

+   <st c="426">将电影添加到购物车</st> <st c="444">。</st>

+   <st c="452">列出添加到购物车的电影</st> <st c="477">。</st>

+   <st c="485">从购物车中删除电影</st> <st c="507">。</st>

<st c="515">到本章结束时,你将具备使用网络会话、实现购物车系统以及跟踪和维护同一用户之间请求的用户信息所需的知识。</st> <st c="697">。</st>

# <st c="707">技术要求</st>

<st c="730">在本章中,我们将使用</st> **<st c="765">Python 3.10+</st>**<st c="777">。此外,我们将在本书中使用</st> **<st c="814">Visual Studio Code</st>** <st c="821">编辑器,您可以从</st> <st c="866">以下位置</st> [<st c="871">https://code.visualstudio.com/</st>](https://code.visualstudio.com/)<st c="901">下载。</st>

<st c="902">本章的代码位于</st> <st c="940">以下位置</st> [<st c="943">https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter10/moviesstore</st>](https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter10/moviesstore)<st c="1051">。</st>

<st c="1052">本章的 CiA 视频可以在</st> <st c="1097">以下位置找到</st> [<st c="1100">https://packt.link/mEcH8</st>](https://packt.link/mEcH8)

# <st c="1124">介绍网络会话</st>

<st c="1149">你理解登录系统是如何工作的吗?</st> <st c="1200">应用程序是如何识别我的连接状态的?</st> <st c="1257">它是如何区分为已登录用户显示注销按钮和为未连接的朋友显示登录按钮的?</st> <st c="1391">应用程序会保留我的</st> <st c="1431">连接状态多长时间?</st>

<st c="1449">在本章中,我们将解答这些问题,并探讨网络会话在 Web 应用程序开发中的重要性。</st> <st c="1582">我们将按照以下顺序探讨这些元素:</st>

1.  <st c="1636">HTTP</st> <st c="1642">协议限制</st>

1.  <st c="1662">网络会话</st>

1.  <st c="1675">Django</st> <st c="1683">登录场景</st>

1.  <st c="1697">Django 会话</st>

## <st c="1713">HTTP 协议限制</st>

<st c="1739">目前,我们与电影商店网站的交互依赖于 HTTP 协议。</st> <st c="1826">例如,要访问电影信息,我们使用</st> `<st c="1880">http://localhost:8000/movies</st>`<st c="1908">,而登录时使用</st> `<st c="1937">http://localhost:8000/login</st>`<st c="1964">。我们发出的每个请求都使用 HTTP 协议作为其</st> <st c="2021">通信媒介。</st>

<st c="2042">尽管如此,HTTP 协议有其局限性。</st> <st c="2096">它以</st> **<st c="2113">无状态</st>** <st c="2122">方式运行,这意味着服务器在连续请求之间不保留任何信息(状态)。</st> <st c="2224">每次新的请求都会建立一个新的连接,消除对先前交互的任何了解。</st> <st c="2334">本质上,它缺乏对</st> <st c="2366">过去行为的记忆。</st>

<st c="2379">然而,当登录到应用程序后,后续请求会显示一个注销按钮,这表明应用程序能够识别用户并维护状态数据。</st> <st c="2548">此功能是通过 Django 会话实现的,它增强了 HTTP 协议的功能。</st>

## <st c="2656">Web 会话</st>

<st c="2669">一个</st> **<st c="2672">Web 会话</st>** <st c="2683">由</st> <st c="2693">在指定时间内访问者在网站上执行的一系列连续操作组成。</st> <st c="2790">每个框架都提供自己的方法来实现会话,以监控访问者的活动。</st> *<st c="2886">图 10</st>**<st c="2895">.1</st>* <st c="2897">说明了 Django 会话的工作原理。</st>

![图 10.1 – Django 会话操作的示例](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_10_1.jpg)

<st c="3299">图 10.1 – Django 会话操作的示例</st>

<st c="3353">让我们分析</st> <st c="3372">之前的场景:</st>

1.  <st c="3390">用户导航到登录页面</st> <st c="3428">在</st> `<st c="3431">http://localhost:8000/login</st>`<st c="3458">。</st>

1.  <st c="3459">然后 Django 向用户发送一个包含</st> <st c="3523">登录表单</st>的 HTTP 响应。</st>

1.  <st c="3534">用户填写登录表单并点击</st> **<st c="3585">登录</st>** <st c="3590">按钮。</st> <st c="3599">Django 验证用户数据,如果准确无误,则为用户创建会话数据,并分配一个 ID 或</st> <st c="3713">一个</st> **<st c="3716">会话密钥</st>**<st c="3727">。默认情况下,会话数据存储在数据库中(我们将在下一节中看到其操作)。</st>

1.  <st c="3831">Django 向用户的浏览器发送一个 cookie。</st> <st c="3877">这个 cookie 包含一个会话 ID,用于在后续请求中检索用户的会话数据。</st> <st c="3986">登录成功后,Django 将用户重定向到</st> <st c="4043">主页。</st>

1.  <st c="4053">每个后续请求都会渲染带有</st> **<st c="4119">注销</st>** <st c="4125">按钮的导航菜单,因为会话 ID 包含在用户的请求中。</st> <st c="4188">这个过程会一直持续到用户点击</st> **<st c="4240">注销</st>** <st c="4246">按钮(这将删除会话数据和 cookie),或者直到会话过期,默认为</st> <st c="4351">两周。</st>

<st c="4361">现在我们已经学习了 Django 会话的工作原理,让我们用我们的电影</st> <st c="4448">商店项目来复制它。</st>

## <st c="4462">Django 登录场景</st>

<st c="4484">让我们按照以下步骤</st> <st c="4513">来查看 Django 会话的实际操作:</st>

1.  <st c="4546">在隐身模式下运行应用程序,转到</st> `<st c="4592">http://localhost:8000/login</st>`<st c="4619">,并使用已注册用户的凭据进行</st> <st c="4678">登录。</st>

1.  <st c="4685">转到</st> [<st c="4692">https://inloop.github.io/sqlite-viewer/</st>](https://inloop.github.io/sqlite-viewer/)<st c="4731">,将您的</st> `<st c="4752">db.sqlite3</st>` <st c="4762">文件拖放到页面上,然后选择</st> `<st c="4803">django_session</st>` <st c="4817">表。</st> <st c="4825">您将看到与刚刚登录的用户对应的新的</st> `<st c="4844">session_key</st>`<st c="4855">,</st> `<st c="4857">session_data</st>`<st c="4869">,和</st> `<st c="4875">expire_date</st>` <st c="4886">(</st>*<st c="4933">图 10</st>**<st c="4943">.2</st>*<st c="4945">)。</st>

![图 10.2 – django_session 表](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_10_2.jpg)

<st c="5833">图 10.2 – django_session 表</st>

1.  <st c="5871">之前的</st> <st c="5885">Django 会话数据包含一个 cookie。</st> <st c="5926">打开您的浏览器,确认您位于</st> **<st c="5980">电影商店</st>** <st c="5992">主页上,并打开开发者控制台。</st> <st c="6036">对于 Google Chrome,您可以通过</st> *<st c="6095">Shift</st>* <st c="6100">+</st> *<st c="6103">Ctrl</st>* <st c="6107">+</st> *<st c="6110">J</st>* <st c="6111">(在 Windows/Linux 上),或者</st> *<st c="6135">option</st>* <st c="6141">+</st> *<st c="6144">⌘</st>* <st c="6145">+</st> *<st c="6148">J</st>* <st c="6149">(在 macOS 上)来打开开发者控制台。</st>

1.  <st c="6161">然后,导航到</st> `<st c="6221">http://127.0.0.1:8000</st>` <st c="6242">选项,您将看到存储的 cookie 数据,其中包括一个</st> `<st c="6309">sessionid</st>` <st c="6318">与数据库中存储的</st> `<st c="6341">session_key</st>` <st c="6352">相匹配(</st>*<st c="6377">图 10</st>**<st c="6387">.3</st>*<st c="6389">)。</st>

![图 10.3 – 电影商店 cookie 数据](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_10_3.jpg)

<st c="6786">图 10.3 – 电影商店 cookie 数据</st>

<st c="6828">这就是 Django 跟踪我们的网站交互的方式;如果你点击</st> **<st c="6897">注销</st>** <st c="6903">按钮,所有这些数据</st> <st c="6926">都将消失。</st>

<st c="6941">注意</st>

<st c="6946">会话数据不仅在登录场景中创建,也不只是为登录用户创建。</st> <st c="7042">如果你的应用程序在任何时候利用 Django 的会话功能,它也会生成相应的会话和 cookie 数据。</st> <st c="7183">我们将在实现购物车系统时看到这一点。</st> <st c="7260">有关</st> <st c="7296">Django 会话</st>的更多信息,请参阅:</st> [<st c="7318">https://docs.djangoproject.com/en/5.0/topics/http/sessions/</st>](https://docs.djangoproject.com/en/5.0/topics/http/sessions/)<st c="7377">。</st>

## <st c="7378">Django 会话</st>

**<st c="7394">Django 会话</st>** <st c="7410">是在 Web 应用程序中跨 HTTP 请求持久化数据的一种机制。</st> <st c="7490">它们允许 Django 在多个请求中为特定用户存储和检索任意数据。</st> <st c="7591">以下是关于</st> <st c="7622">Django 会话</st>的一些关键点:

+   **<st c="7638">客户端 cookies</st>**<st c="7658">:默认情况下,Django 会话是通过客户端 cookies 实现的。</st> <st c="7732">Django</st> <st c="7739">通常会在用户的浏览器中设置一个唯一的会话 ID,作为</st> <st c="7798">cookie。</st>

+   **<st c="7807">服务器端存储</st>**<st c="7827">:虽然会话 ID 存储在客户端的浏览器中,但实际的会话数据存储在服务器端。</st> <st c="7933">默认情况下,会话数据存储在</st> <st c="7975">数据库中。</st>

+   **<st c="7988">配置选项</st>**<st c="8010">:Django 为会话提供了各种配置选项,包括</st> <st c="8081">会话引擎(例如,数据库支持、缓存、基于文件),会话过期时间和会话数据的加密。</st>

+   **<st c="8197">与认证集成</st>**<st c="8229">:Django 会话通常与 Django 的认证</st> <st c="8288">系统一起工作。</st> <st c="8296">例如,当用户登录时,他们的认证状态通常会存储在会话中,使得 Django 能够在多个请求之间保持用户登录状态,直到他们明确退出或</st> <st c="8499">会话过期。</st>

+   `<st c="8602">request.session</st>` <st c="8617">属性。</st> <st c="8629">这允许它们在请求处理过程中根据需要读取、修改和删除会话数据。</st>

<st c="8723">现在我们已经学习了 Web 会话和 Django 会话的基础知识,让我们开始创建</st> <st c="8826">购物车应用</st>。

# <st c="8835">创建购物车应用</st>

<st c="8855">所有购物车</st> <st c="8878">功能将由它们自己的应用管理。</st> <st c="8936">因此,让我们创建一个购物车应用。</st> <st c="8965">导航到最顶部的</st> `<st c="8985">moviesstore</st>` <st c="8996">文件夹(包含</st> `<st c="9032">manage.py</st>` <st c="9041">文件的文件夹)并在终端中运行以下命令:</st>

+   <st c="9086">对于 macOS,运行以下命令:</st> <st c="9106">以下命令:</st>

    ```py
    <st c="9124">python3 manage.py startapp cart</st>
    ```

+   <st c="9156">对于 Windows,运行以下命令:</st>

    ```py
    <st c="9196">python manage.py startapp cart</st>
    ```

*<st c="9227">图 10</st>**<st c="9237">.4</st>* <st c="9239">显示了新的项目结构。</st> <st c="9273">请确认它与您当前的文件夹结构相匹配。</st>

![图 10.4 – 包含购物车应用的 MOVIESSTORE 项目结构](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_10_4.jpg)

<st c="9492">图 10.4 – 包含购物车应用的 MOVIESSTORE 项目结构</st>

## <st c="9563">在设置中添加购物车应用</st>

<st c="9591">记住</st> <st c="9600">对于每个新创建的应用,我们必须在</st> `<st c="9661">settings.py</st>` <st c="9672">文件中注册它。</st> <st c="9679">在</st> `<st c="9682">/moviesstore/settings.py</st>`<st c="9706">中,在</st> `<st c="9714">INSTALLED_APPS</st>`<st c="9728">下,添加以下加粗行:</st> <st c="9754">以下内容:</st>

```py
 …
INSTALLED_APPS = [
    …
    'movies',
    'accounts', <st c="9807">'cart',</st> ]
…

包括购物车 URL 文件到项目级别的 URL 文件中

<st c="9880">/moviesstore/urls.py</st>中,添加以下加粗行: 以下内容:

 …
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('home.urls')),
    path('movies/', include('movies.urls')),
    path('accounts/', include('accounts.urls')), <st c="10103">path('cart/', include('cart.urls')),</st> ]
…

<st c="10181">cart.urls</st> 文件中定义的所有 URL 都将包含一个 <st c="10211">cart/</st> 前缀(如前一个路径中定义的)。 我们将在稍后创建 <st c="10278">cart.urls</st> 文件。

现在我们已经创建了购物车应用,让我们允许将电影添加到 购物车。

将电影添加到购物车

为了允许将电影添加到购物车,我们将遵循以下步骤: 以下步骤:

  1. 配置 <st c="10503">add_to_cart</st> URL。

  2. 定义 <st c="10531">add_to_cart</st> 函数。

  3. 更新 <st c="10559">movies.show</st> 模板。

配置 add_to_cart URL

<st c="10621">/cart/</st>中,创建一个名为 <st c="10641">urls.py</st>的新文件。此文件将包含关于购物车应用 URL 的路径。 目前,请用以下内容填充它: 以下内容:

 from django.urls import path
from . import views
urlpatterns = [
    path('<int:id>/add/', views.add, name='cart.add'),
]

我们添加了一个名为 <st c="10916">cart/<int:id>/add</st> 的新路径(记住项目级别的 URL 文件为该文件定义了一个 <st c="10987">/cart</st> 前缀)。 其中 <st c="11020"><int:id></st> 部分表示该路径期望从 URL 传递一个整数值,并且该整数值将与名为 <st c="11178">id</st>的变量相关联。 id 变量将用于识别我们想要添加到购物车中的电影。 例如, <st c="11280">cart/1/add</st> 路径表示我们想要将 <st c="11341">id=1</st> 的电影添加到 `购物车中。

定义 add_to_cart 函数

<st c="11392">/cart/views.py</st>中,添加以下 (加粗):

 from django.shortcuts import render <st c="11477">from django.shortcuts import get_object_or_404, redirect</st>
<st c="11533">from movies.models import Movie</st>
<st c="11565">def add(request, id):</st>
 <st c="11587">get_object_or_404(Movie, id=id)</st>
 <st c="11619">cart = request.session.get('cart', {})</st>
 <st c="11658">cart[id] = request.POST['quantity']</st>
 <st c="11694">request.session['cart'] = cart</st>
 <st c="11725">return redirect('home.index')</st>

让我们解释一下 之前的代码:

  • 我们导入 <st c="11803">redirect</st> <st c="11816">get_object_or_404</st> 函数。 我们还从“movies" 应用程序中导入 <st c="11869">Movie</st> 模型。

  • 我们定义了一个 <st c="11914">add</st> 函数,它接受两个参数:请求和 电影 ID。

  • 我们从数据库中获取 <st c="11999">Movie</st> 对象,使用给定的 <st c="12027">id</st> (通过使用 <st c="12062">get_object_or_404</st> 函数)。 如果没有找到此类对象,将引发一个 404(未找到)错误。

  • 我们检查会话存储中是否存在一个名为 <st c="12200">'cart'</st>的键。如果该键不存在,则将一个 <st c="12237">{}</st> 空字典分配给 <st c="12276">cart</st> 变量。

  • 我们修改了 <st c="12305">cart</st> 变量。 我们根据电影 ID 在 <st c="12344">cart</st> 字典中添加一个新的键,并根据用户想要添加到购物车中的电影数量设置相应的值(我们将在稍后通过 HTML 表单收集 <st c="12490">quantity</st> )。 例如,如果用户想要添加 <st c="12566">2</st> <st c="12580">id=1</st>的电影,将添加一个新键/值,例如 <st c="12615">cart["1"] = "2"</st> `字典中。

  • 更新后的 <st c="12676">购物车</st> 字典随后使用 <st c="12732">request.session['cart'] =</st> <st c="12758">cart</st>.

  • <st c="12770">更新购物车后</st>,我们将用户重定向到主页(</st>home.index`)。

现在,让我们更新 <st c="12865">movies.show</st> 模板,以包含一个用于将电影添加到 <st c="12921">购物车</st> 的表单。

更新 movies.show 模板

<st c="12972">/movies/templates/movies/show.html</st> 文件中,添加以下以粗体显示的 <st c="13017">行:</st>

 …
        <p><b>Description:</b> {{
          template_data.movie.description }}</p>
        <p>
          <b>Price:</b> ${{ template_data.movie.price }}
        </p> <st c="13168"><p class="card-text"></st>
 <st c="13189"><form method="post"</st>
 <st c="13209">action="{% url 'cart.add'</st>
 <st c="13235">id=template_data.movie.id %}"></st>
 <st c="13266"><div class="row"></st>
 <st c="13284">{% csrf_token %}</st>
 <st c="13301"><div class="col-auto"></st>
 <st c="13324"><div class="input-group col-auto"></st>
 <st c="13359"><div class="input-group-text">Quantity</st>
 <st c="13398"></div></st>
**<st c="13405"><input type="number" min="1" max="10"</st>**
 **<st c="13443">class="form-control quantity-input"</st>**
 **<st c="13479">name="quantity" value="1"></st>**
 **<st c="13506"></div></st>**
 **<st c="13513"></div></st>**
 **<st c="13520"><div class="col-auto"></st>**
 **<st c="13543"><button class="btn bg-dark text-white"</st>**
 **<st c="13582">type="submit">Add to cart</button></st>**
 **<st c="13617"></div></st>**
 **<st c="13624"></div></st>**
****<st c="13631"></form></st>**
 **<st c="13639"></p></st>** <st c="13644">…</st>**

****我们 <st c="13648">添加了一个新的表单,允许用户将电影添加到购物车。</st> 此表单还包括一个输入字段,用于指定用户希望添加到购物车的电影数量。 <st c="13819">该表单链接到</st> 'cart.add' 路径,并将电影 <st c="13882">id</st> <st c="13884">作为表单操作的</st> 一部分。`

现在,保存这些文件,运行服务器,访问 <st c="13958">http://localhost:8000/movies</st>,点击一个特定的电影,你将看到可用的 添加到购物车 功能(图 10**.5)。

图 10.5 – 带有“添加到购物车”功能的电影页面

图 10.5 – 带有“添加到购物车”功能的电影页面

我们成功添加了一个按钮来添加电影到购物车。 <st c="14383">现在,让我们继续列出添加到</st> 购物车` 的电影。

列出添加到购物车的电影

为了 让我们能够列出添加到购物车的电影,我们将遵循以下 <st c="14541">步骤:</st>

  1. 配置购物车 <st c="14572">索引 URL。</st>

  2. 定义一个 <st c="14592">utils</st> 文件。

  3. 定义一个 <st c="14611">过滤器。</st>

  4. 定义一个 <st c="14631">索引</st> 函数。

  5. 创建 <st c="14660">cart.index</st> 模板。

  6. 更新 <st c="14694">add_to_cart</st> `函数。

  7. <st c="14737">基本模板</st> 中添加链接。

配置购物车索引 URL

<st c="14782">/cart/urls.py</st>中,通过添加以下以粗体显示的 <st c="14805">行来添加下一个路径:</st>

 from django.urls import path
from . import views
urlpatterns = [ <st c="14909">path('', views.index, name='cart.index'),</st> path('<int:id>/add/', views.add, name='cart.add'),
]

我们定义了一个 <st c="15017">''</st> 路径,但请记住,项目级别的 URL 文件为该文件定义了一个 <st c="15080">/cart</st> 前缀。 因此,如果一个 URL 与 <st c="15133">/cart</st> 路径匹配,它将执行 <st c="15165">index</st> 函数,该函数定义在 <st c="15190">views</st> 文件中。 我们将在稍后实现 <st c="15229">index</st> 函数。

定义一个 utils 文件

通常,购物车页面会显示 需要支付的总金额。 这是通过将电影的价格根据其对应数量相加来计算的。 我们将定义一个 <st c="15444">calculate_cart_total</st> 函数来完成这个过程。 然而,不建议将此类函数放置在 <st c="15575">views</st> 文件或 <st c="15593">models</st> 文件中。 因此,我们将创建一个名为 utilities (<st c="15645">utils</st>) 的文件,并将此函数放置在其中以便 易于重用。

<st c="15720">/cart/</st>目录下,创建一个名为 <st c="15753">utils.py</st>的新文件。目前,可以将其填充为以下内容:

 def calculate_cart_total(cart, movies_in_cart):
    total = 0
    for movie in movies_in_cart:
        quantity = cart[str(movie.id)]
        total += movie.price * int(quantity)
    return total

让我们解释一下 之前的代码:

  • 我们定义了一个 <st c="16018">calculate_cart_total</st> 函数,它接受两个参数: <st c="16077">cart</st> <st c="16086">movies_in_cart</st><st c="16106">cart</st> 参数是一个字典,表示用户的购物车。 请记住,键是表示电影 ID 的字符串,值是表示购物车中每部电影数量的字符串。 <st c="16324">movies_in_cart</st> 参数是一个表示购物车中电影的 <st c="16362">Movie</st> 对象的列表。

  • 我们通过 <st c="16429">total</st> 变量 初始化 <st c="16447">0</st>

  • 我们遍历购物车中的电影列表。 对于每部电影,我们提取添加到购物车中的对应数量,并将其乘以电影的价格。 然后,我们将该电影的总成本添加到 <st c="16660">total</st> 变量中。

  • 最后,我们返回 total 变量。

在总结中,<st c="16714">calculate_cart_total</st> <st c="16747">通过遍历购物车中的每部电影,将电影的价格乘以其数量,并</st> <st c="16900">计算总成本来计算用户购物车中电影的总额。</st> <st c="16928">这个函数将在后面的</st> <st c="16968">views</st> <st c="16973">文件中使用。</st>

定义一个过滤器

<st c="17153">|</st> <st c="17155">)字符是自定义模板数据展示的强大工具。

我们想要列出添加到购物车中的电影,并显示每部电影的数量。这需要访问购物车会话数据,并使用每部电影的 ID 作为键来获取 数量 值。 虽然这可能听起来并不复杂,但 Django 模板被设计得简单,主要关注渲染视图提供的数据。 有时,Django 模板不允许访问依赖于其他变量或复杂数据结构的数据。 在这种情况下,您需要创建一个自定义过滤器或标签。 我们将创建一个自定义过滤器来访问购物车中电影的 数量 数据。`

<st c="17849">/cart/</st> <st c="17853">中创建一个<st c="17870">templatetags</st> <st c="17882">文件夹。</st> <st c="17891">然后,在</st> <st c="17900">/cart/templatetags/</st> <st c="17919">创建一个名为</st> <st c="17945">cart_filters.py</st> <st c="17960">的新文件。</st> <st c="17987">目前,请用以下内容填充它:</st> <st c="17987">

 from django import template
register = template.Library()
@register.filter(name='get_quantity')
def get_cart_quantity(cart, movie_id):
    return cart[str(movie_id)]

让我们解释一下之前的代码:

  • 我们导入<st c="18196">template</st> <st c="18211">模块,它提供了用于处理</st> <st c="18219">Django 模板</st> <st c="18270">的实用工具。</st>

  • 我们使用<st c="18287">register = template.Library()</st> <st c="18328">代码来创建一个template.Library `的实例,它用于注册自定义模板标签和过滤器。

  • 我们使用<st c="18436">@register.filter(name='get_quantity')</st> <st c="18485">装饰器来注册</st> get_cart_quantity 函数作为一个名为 <st c="18573">get_quantity</st> <st c="18585">的自定义模板过滤器</st><st c="18591">name='get_quantity'</st> <st c="18610">参数指定了过滤器在模板中使用时的名称。</st> <st c="18672">

  • 我们定义了 <st c="18700">get_cart_quantity</st> 函数,它接受两个参数: <st c="18759">cart</st> 会话字典,以及所需数量的电影的 ID。

  • 我们通过使用 <st c="18856">quantity</st> 值,通过 <st c="18884">cart</st> 字典和 <st c="18904">movie_id</st> 作为键来访问。 我们将 <st c="18936">movie_id</st> 转换为字符串以确保与 <st c="18990">购物车键</st> 的兼容性。

  • 最后,我们 返回相应的 <st c="19038">quantity</st> 值。

注意

一旦自定义过滤器被定义并注册,你就可以在 Django 模板中使用它,如下所示: <st c="19159">{{</st> <st c="19162">request.session.cart|get_quantity:movie.id }}</st>

在先前的例子中, <st c="19238">get_quantity</st> 过滤器被应用于 <st c="19272">request.session.cart</st> ,使用 <st c="19302">movie.id</st> 参数来获取购物车中特定电影的数量。

你可以在 https://docs.djangoproject.com/en/5.0/howto/custom-template-tags/中找到有关自定义过滤器和标签的更多信息。

我们已经设计了我们需要的所有元素来实现购物车 索引功能。

定义一个索引函数

<st c="19621">/cart/views.py</st>中,添加以下 粗体内容:

 from django.shortcuts import render
from django.shortcuts import get_object_or_404, redirect
from movies.models import Movie <st c="19789">from .utils import calculate_cart_total</st>
<st c="19828">def index(request):</st>
 <st c="19848">cart_total = 0</st>
 <st c="19863">movies_in_cart = []</st>
 <st c="19883">cart = request.session.get('cart', {})</st>
 <st c="19922">movie_ids = list(cart.keys())</st>
 <st c="19952">if (movie_ids != []):</st>
 <st c="19974">movies_in_cart =</st>
 <st c="19991">Movie.objects.filter(id__in=movie_ids)</st>
 <st c="20030">cart_total = calculate_cart_total(cart,</st>
 <st c="20070">movies_in_cart)</st>
 <st c="20086">template_data = {}</st>
 <st c="20105">template_data['title'] = 'Cart'</st>
 <st c="20137">template_data['movies_in_cart'] = movies_in_cart</st>
 <st c="20186">template_data['cart_total'] = cart_total</st>
 <st c="20227">return render(request, 'cart/index.html',</st>
 <st c="20269">{'template_data': template_data})</st> def add(request, id):
    …

让我们解释一下 之前的代码:

  • 我们从 <st c="20375">utils</st> 文件中导入 <st c="20414">calculate_cart_total</st> 函数。

  • 我们定义了 <st c="20440">index</st> 函数。

  • 我们将 <st c="20474">cart_total</st> 初始化为 <st c="20488">0</st>,并将 <st c="20495">movies_in_cart</st> 初始化为一个空列表。

  • 我们使用 <st c="20584">request.session.get('cart', {})</st>从会话中检索购物车信息。

  • 根据 <st c="20683">购物车键</st> ,我们提取添加到购物车中的电影 ID。

  • 如果购物车中有任何电影 ID,该函数将使用 <st c="20800">Movie.objects.filter(id__in=movie_ids)</st> <st c="20838">查询数据库以获取具有这些 ID 的电影。此外,我们使用 <st c="20918">calculate_cart_total</st> <st c="20938">函数</st> 计算购物车中电影的总额,该函数更新了 cart_total 变量。`

  • 最后,我们准备 <st c="21012">template_data</st> <st c="21025">字典并渲染 <st c="21052">cart/index.html</st> <st c="21067">模板。</st>

总结来说,<st c="21094">index</st> <st c="21099">函数</st> `旨在渲染购物车页面,显示购物车中的电影以及这些电影的总额。

创建 cart.index 模板

<st c="21249">/cart/</st> <st c="21255">,创建一个 <st c="21264">templates</st> <st c="21275">文件夹。</st> 然后,在 <st c="21293">/cart/templates/</st> <st c="21309">,创建一个 <st c="21320">cart</st> <st c="21324">文件夹。</st>

现在,在 <st c="21341">/cart/templates/cart/</st>,创建一个 <st c="21373">新文件</st><st c="21383">index.html</st>。目前,用以下内容填充它: <st c="21420">以下内容:</st>

 {% extends 'base.html' %}
{% block content %}
{% load static %}
{% load cart_filters %}
<div class="p-3">
  <div class="container">
    <div class="row mt-3">
      <div class="col mx-auto mb-3">
        <h2>Shopping Cart</h2>
        <hr />
      </div>
    </div>
    <div class="row m-1">
      <table class="table table-bordered table-striped
        text-center">
        <thead>
          <tr>
            <th scope="col">ID</th>
            <th scope="col">Name</th>
            <th scope="col">Price</th>
            <th scope="col">Quantity</th>
          </tr>
        </thead>
        <tbody>
          {% for movie in template_data.movies_in_cart %}
          <tr>
            <td>{{ movie.id }}</td>
            <td>{{ movie.name }}</td>
            <td>${{ movie.price }}</td>
            <td>{{
              request.session.cart|get_quantity:movie.id }}
            </td>
          </tr>
          {% endfor %}
        </tbody>
      </table>
    </div>
    <div class="row">
      <div class="text-end">
        <a class="btn btn-outline-secondary mb-2"><b>Total
          to pay:</b> ${{ template_data.cart_total }}</a>
      </div>
    </div>
  </div>
</div>
{% endblock content %}

让我们解释一下之前的代码:

  • 我们使用 <st c="22360">{% load cart_filters %}</st> <st c="22383">标签</st>,它加载在 <st c="22440">cart_filters</st> <st c="22452">文件</st> 中定义的自定义模板过滤器。在我们的例子中,它包括名为 <st c="22501">get_quantity</st> <st c="22513">的过滤器。</st>

  • 我们创建一个 <st c="22528">HTML 表格。</st>

  • 我们遍历购物车中的电影。对于每部电影,我们显示其 <st c="22614">id</st> name<st c="22622">、</st> <st c="22624">price</st>quantity 在购物车中的数量。为了显示购物车中的数量,我们使用get_quantity 过滤器。`

  • 最后,我们 <st c="22744">显示</st> <st c="22756">购物车总额</st> `的值。

更新 add_to_cart 函数

<st c="22811">/cart/views.py</st> `,添加以下加粗的行:

 …
def add_to_cart(request, id):
    get_object_or_404(Movie, id=id)
    cart = request.session.get('cart', {})
    cart[id] = request.POST['quantity']
    request.session['cart'] = cart
    return redirect('<st c="23046">cart</st>.index')

如果用户将电影添加到购物车,他们现在将被重定向到 <st c="23131">购物车页面。</st>

在基础模板中添加链接

最后,让我们 在基本模板中添加购物车链接。 <st c="23235">/moviesstore/templates/base.html</st>中,在标题部分,添加以下 行(粗体):

 …
            <a class="nav-link"
              href="{% url 'home.about' %}">About</a>
            <a class="nav-link"
              href="{% url 'movies.index' %}">Movies</a> <st c="23449"><a class="nav-link"</st>
 <st c="23468">href="{% url 'cart.index' %}">Cart</a></st> …

现在,保存这些 文件,运行服务器,转到 <st c="23553">http://localhost:8000/movies</st>,点击几部电影,并将它们添加到购物车。 然后,转到 购物车 部分,您将看到一个 购物车 页面及其对应的信息(图 10**.6)。

图 10.6 – 购物车页面

图 10.6 – 购物车页面

现在我们已经学会了如何实现购物车系统,让我们通过包括从购物车中删除电影的功能来完成本章。 购物车中删除电影。

从购物车中删除电影

从购物车中删除电影,我们将遵循以下 步骤:

  1. 配置一个 <st c="24213">清晰的</st> URL。

  2. 定义一个 <st c="24235">清晰的</st> 函数。

  3. 更新 <st c="24264">cart.index</st> 模板。

配置清晰的 URL

<st c="24310">/cart/urls.py</st>中,通过添加以下 行来添加下一个路径 (粗体):

 from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='cart.index'),
    path('<int:id>/add/', views.add, name='cart.add'), <st c="24540">path('clear/', views.clear, name='cart.clear'),</st> ]

我们定义了一个 <st c="24601">cart/clear/</st> 路径,该路径将执行在 <st c="24640">views</st> 文件中定义的 <st c="24645">clear</st> 函数。 我们将在稍后实现 <st c="24704">clear</st> 函数。

定义清晰的函数

<st c="24753">/cart/views.py</st>中,在文件末尾添加以下 行(粗体):

 … <st c="24826">def clear(request):</st>
 <st c="24845">request.session['cart'] = {}</st>
 <st c="24874">return redirect('cart.index')</st>

我们只需将用户的购物车会话更新为空字典。 这将删除购物车中添加的所有先前电影,并将用户重定向到 购物车页面。

更新 cart.index 模板

<st c="25103">/cart/templates/cart/index.html</st> 文件中,添加以下 行(粗体):

 …
    <div class="row">
      <div class="text-end">
        <a class="btn btn-outline-secondary mb-2"><b>Total
          to pay:</b> ${{ template_data.cart_total }}</a> <st c="25315">{% if template_data.movies_in_cart|length > 0 %}</st>
 <st c="25363"><a href="{% url 'cart.clear' %}"></st>
 <st c="25397"><button class="btn btn-danger mb-2"></st>
 <st c="25434">Remove all movies from Cart</st>
 <st c="25462"></button></st>
 <st c="25472"></a></st>
 <st c="25477">{% endif %}</st> </div>
    </div>
    …

我们添加了一个 <st c="25517">if</st> 部分来检查购物车中是否有任何电影。 如果有,我们显示一个按钮,允许用户清除 购物车。

现在,保存这些文件,运行服务器,访问 <st c="25698">http://localhost:8000/movies</st>,点击几部电影,并将它们添加到购物车。 然后,转到 购物车 部分并点击 从购物车中移除所有电影 (图 10**.7). 您将看到一个 空购物车。

图 10.7 – 购物车页面

图 10.7 – 购物车页面

总结

在本章中,我们学习了 Web 会话和 Django 会话的工作原理。 我们创建了一个购物车应用程序,允许我们添加电影到购物车,列出已添加到购物车的电影,以及从购物车中移除电影。 我们还了解到, <st c="26252">utils</st> 文件用于存储可以在我们的应用程序中重复使用的函数。 此外,我们还了解到过滤器允许我们修改或格式化在模板中显示的数据,并且我们学习了如何利用一些 Django 会话功能。 在下一章中,我们将创建订单和项目模型,以便用户可以 购买电影。****

第十二章:11

实现订单和项目模型

在前一章中,我们实现了购物车系统,并允许用户将电影添加到购物车中。 为了使用户能够购买电影,我们需要在数据库中存储额外的信息,特别是存储订单和项目信息。 在本章中,我们将实现订单和项目模型,并建立它们之间的连接。

在本章中,我们将涵盖以下主题:

  • 分析 商店发票

  • 创建 订单模型

  • 创建 项目模型

  • 回顾电影商店 类图

到本章结束时,我们将拥有存储购买信息的完整结构。 此外,我们将回顾类图,并检查 Django 模型与类图中的类之间的关系。

技术要求

在本章中,我们将使用 Python 3.10+。此外,我们将使用本书中的 VS Code 编辑器,您可以从 以下链接 https://code.visualstudio.com/下载。

本章的代码位于 以下链接 https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter11/moviesstore

本章的 CiA 视频可在以下链接找到 以下链接 https://packt.link/eQzNG

分析商店发票

如果你在现代商店购买东西,你几乎肯定会收到一张发票。 不同的商店以不同的信息管理发票,但在大多数情况下,你都会找到相同的基本信息。 图 11**.1 显示了一张简单的发票。 我们将以此作为蓝图来设计和实现我们将用于存储购买信息的 Django 模型。

图 11.1 – 简单发票的示例

图 11.1 – 简单发票的示例

让我们分析图 11*.1**中显示的发票,以了解我们需要存储哪些购买信息(基于订单和项目)。

我们必须为订单存储以下信息:

  • ID:用于唯一标识每个订单。 在先前的图中,它表示为 #1

  • 日期:标识订单完成的日期。 在先前的图中,它表示为 2024-04-22

  • 总计:标识订单的总金额。 在先前的图中,它表示为 $50

  • 用户:标识进行购买的用户。 在先前的图中,它表示为 1 - daniel

一个订单由项目组成,表示为图 11.1**.1.我们必须为每个项目存储以下信息:

  • ID:用于唯一标识每个项目。 在先前的图中,第一件商品的 ID 表示为 1

  • 数量:指定用户想要购买的电影数量。 在先前的示例中,第一件商品的数量表示为 2

  • 价格:指定用户购买商品时的电影价格。 在先前的示例中,第一件商品的价格表示为 12

  • 电影:指定与项目链接的电影。 在先前的示例中,第一件商品的链接电影表示为 1 - Inception

  • 订单:指定与项目链接的订单。 在先前的示例中,第一件商品的链接订单表示为 #1

现在我们已经掌握了这些简单发票的功能,让我们继续创建 适当的模型。

创建订单模型

为了存储购买信息,我们 需要首先创建一个 Order Django 模型。 以下是为存储购买信息所需的三个步骤:

  1. 创建订单模型。

  2. 应用迁移。

  3. 将订单模型添加到 <st c="3634">管理面板。</st>

让我们详细地 <st c="3669">了解它们。</st>

创建订单模型

我们将开始创建 <st c="3737">Order</st> 模型。 我们将在这个 <st c="3782">cart app</st> 中创建这个模型。

<st c="3795">/cart/models.py</st> 文件中,添加以下 <st c="3835">在</st> **<st c="3838">粗体</st>**<st c="3842">:</st>

 from django.db import models <st c="3874">from django.contrib.auth.models import User</st>
<st c="3917">class Order(models.Model):</st>
 <st c="3944">id = models.AutoField(primary_key=True)</st>
 <st c="3984">total = models.IntegerField()</st>
 <st c="4014">date = models.DateTimeField(auto_now_add=True)</st>
 <st c="4061">user = models.ForeignKey(User,</st>
 <st c="4092">on_delete=models.CASCADE)</st>
 <st c="4118">def __str__(self):</st>
 <st c="4137">return str(self.id) + ' - ' + self.user.username</st>

让我们解释一下 <st c="4205">之前的代码:</st>

  • 我们从 Django 的 <st c="4234">User</st> 模型中导入 <st c="4259">django.contrib.auth.models</st> 模块。

  • 我们定义了一个名为 <st c="4325">Order</st>的 Python 类,它继承自 <st c="4352">models.Model</st>。这意味着 <st c="4382">Order</st> 是一个 Django <st c="4400">模型类。</st>

  • <st c="4424">Order</st> 类中,我们定义了 <st c="4447">几个字段:</st>

    • <st c="4462">id</st>:这是一个 <st c="4479">AutoField</st>,它会自动为数据库中添加的每个新记录增加其值。 <st c="4578">primary_key=True</st> 参数指定这个字段是表的键,唯一标识</st> 每个记录。`

    • <st c="4698">total</st>:这是一个 <st c="4717">IntegerField</st>,它表示订单的总金额。 它存储整数值。

    • <st c="4804">date</st>:这是一个 <st c="4822">DateTimeField</st> ,它表示订单创建的日期和时间。 <st c="4900">auto_now_add=True</st> 确保在创建订单时自动将日期和时间设置为当前日期和时间。

    • <st c="5026">user</st>:这是一个与 <st c="5076">User</st> 模型的 <st c="5080">外键关系,它建立了订单和用户之间的多对一关系。</st> <st c="5159">这意味着每个订单都与单个用户相关联,每个用户可以有多个订单。</st> on_delete=models.CASCADE<st c="5282">指定如果相关用户被删除,相关的订单也将</st>被删除。`

  • <st c="5373">__str__</st> 是一个返回订单字符串表示的方法。 在这种情况下,它返回一个由订单 ID 和下订单用户的用户名组成的字符串。

应用迁移

现在我们已经创建了 订单 模型,根据您的 操作系统,运行以下命令之一来更新数据库:

  • 对于 macOS 系统, 运行以下命令:

    <st c="5741">python3 manage.py makemigrations</st>
    <st c="5774">python3 manage.py migrate</st>
    
  • 对于 Windows 系统, 运行以下命令:

    <st c="5823">python manage.py makemigrations</st>
    <st c="5855">python manage.py migrate</st>
    

现在,您应该会看到以下内容: (如图 11.2 所示)

图 11.2 – 应用订单迁移

图 11.2 – 应用订单迁移

将订单模型添加到管理面板

要将 <st c="6111">订单</st> 模型添加到管理界面,请前往 <st c="6139">/cart/admin.py</st> 并按以下步骤注册它,添加以下 内容 (以粗体显示):

 from django.contrib import admin <st c="6237">from .models import Order</st>
<st c="6363">/admin</st>. The order model will now appear (as shown in *<st c="6416">Figure 11</st>**<st c="6425">.3</st>*):
			![Figure 11.3 – Admin page with orders available](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_11_3.jpg)

			<st c="6824">Figure 11.3 – Admin page with orders available</st>
			<st c="6870">Now that we have created and applied our</st> `<st c="6912">Order</st>` <st c="6917">model, let’s create the</st> `<st c="6942">Item</st>` <st c="6946">model to complete the information</st> <st c="6981">required to</st> <st c="6993">store purchases.</st>
			<st c="7009">Creating the Item model</st>
			<st c="7033">Let’s continue by</st> <st c="7051">creating an</st> `<st c="7064">Item</st>` <st c="7068">model and follow</st> <st c="7086">these steps:</st>

				1.  <st c="7098">Create the</st> <st c="7110">Item model.</st>
				2.  <st c="7121">Apply migrations.</st>
				3.  <st c="7139">Add the item model to the</st> <st c="7166">admin panel.</st>

			<st c="7178">Creating the Item model</st>
			<st c="7202">In</st> `<st c="7206">/cart/models.py</st>` <st c="7221">file, add the following</st> <st c="7246">in</st> *<st c="7249">bold</st>*<st c="7253">:</st>

从 django.db 导入 models

从 django.contrib.auth.models 导入 User 从 movies.models 导入 Movie class Order(models.Model):

… <st c="7390">class Item(models.Model):</st>

id = models.AutoField(primary_key=True)

价格 = models.IntegerField()

数量 = models.IntegerField()

订单 = models.ForeignKey(Order,

on_delete=models.CASCADE)

电影 = models.ForeignKey(Movie,

on_delete=models.CASCADE)

def str(self):

return str(self.id) + ' - ' + self.movie.name


 <st c="7701">Let’s explain the</st> <st c="7720">previous code:</st>

*   <st c="7734">We import the</st> `<st c="7749">Movie</st>` <st c="7754">model from the</st> `<st c="7770">movies</st>` <st c="7776">app.</st>

*   <st c="7781">We define a Python class named</st> `<st c="7813">Item</st>`<st c="7817">, which inherits from</st> `<st c="7839">models.Model</st>`<st c="7851">. This means that</st> `<st c="7869">Item</st>` <st c="7873">is a Django</st> <st c="7886">model class.</st>

*   <st c="7898">Inside the</st> `<st c="7910">Item</st>` <st c="7914">class, we</st> <st c="7925">define</st> <st c="7932">several fields:</st>
    *   `<st c="7947">id</st>`<st c="7950">: This is an</st> `<st c="7964">AutoField</st>`<st c="7973">, which automatically increments its value for each new record added to the database.</st> <st c="8059">The</st> `<st c="8063">primary_key=True</st>` <st c="8079">parameter specifies that this field is the primary key for the table, uniquely identifying</st> <st c="8171">each record.</st>
    *   `<st c="8183">price</st>`<st c="8189">: This is an</st> `<st c="8203">IntegerField</st>`<st c="8215">, which represents the price at which the item</st> <st c="8262">was purchased.</st>
    *   `<st c="8276">quantity</st>`<st c="8285">: This is an</st> `<st c="8299">IntegerField</st>`<st c="8311">, which represents the desired quantity of the item</st> <st c="8363">to purchase.</st>
    *   `<st c="8375">order</st>`<st c="8381">: This is a foreign key relationship with the</st> `<st c="8428">Order</st>` <st c="8433">model, which defines a foreign key relating each item to a</st> <st c="8493">specific order.</st>
    *   `<st c="8508">movie</st>`<st c="8514">: This is a foreign key relationship with the</st> `<st c="8561">Movie</st>` <st c="8566">model, which defines a foreign key relating each item to a</st> <st c="8626">specific movie.</st>

*   `<st c="8641">__str__</st>` <st c="8649">is a method that returns a string representation of the item.</st> <st c="8712">In this case, it returns a string composed of the item ID and the name of the</st> <st c="8790">associated movie.</st>

## <st c="8807">Applying migrations</st>

<st c="8827">Now that we have created the</st> `<st c="8857">Item</st>` <st c="8861">model, let’s update our database by running the following commands</st> <st c="8928">based on your</st> <st c="8943">operating system.</st>

*   <st c="8960">For macOS,</st> <st c="8972">run this:</st>

    ```

    <st c="8981">python3 manage.py makemigrations</st>

    <st c="9014">python3 manage.py migrate</st>

    ```py

*   <st c="9040">For Windows,</st> <st c="9054">run this:</st>

    ```

    <st c="9063">python manage.py makemigrations</st>

    <st c="9095">python manage.py migrate</st>

    ```py

<st c="9120">Now, you should see something</st> <st c="9151">like this:</st>

			![Figure 11.4 – Applying the item migration](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_11_4.jpg)

<st c="9283">Figure 11.4 – Applying the item migration</st>

## <st c="9324">Adding the item model to the admin panel</st>

<st c="9365">To add the</st> `<st c="9377">Item</st>` <st c="9381">model to admin, go to</st> `<st c="9404">/cart/admin.py</st>` <st c="9418">and register it by adding the</st> <st c="9448">following</st> <st c="9459">in</st> *<st c="9462">bold</st>*<st c="9466">:</st>

从 django.contrib 导入 admin

从 .models 导入 Order, Item admin.site.register(Order) /admin. 现在项目模型将显示出来(如图 图 11**.5 所示):

        ![图 11.5 – 可用项目的管理页面](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_11_5.jpg)

        <st c="10038">图 11.5 – 可用项目的管理页面</st>

        现在我们已经完成了进行购买所需的数据结构。</st> <st c="10115">在继续购买流程之前,让我们回顾一下我们的模型与项目</st> <st c="10153">类图之间的关系。</st>

        <st c="10267">回顾电影商店类图</st>

        <st c="10308">我们在</st> *<st c="10367">第一章</st>* <st c="10376">中设计的电影商店类图是设计电影商店代码的蓝图。</st> <st c="10417">我们已经实现了完成项目代码所需的所有模型。</st> <st c="10443">因此,让我们快速回顾一下模型与类之间的这种关系。</st> <st c="10525">和类。</st>

        *<st c="10594">图 11</st>**<st c="10604">.6</st>* `<st c="10606">显示了类图,突出了我们实现相应</st>` `<st c="10698">Django 模型</st>` 的位置:</st>

        ![图 11.6 – 电影商店类图,突出模型位置](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_11_6.jpg)

        `<st c="11118">图 11.6 – 电影商店类图,突出模型位置</st>`

        `<st c="11188">让我们分析一下</st>` `<st c="11207">前面的图:</st>`

            +   `<st c="11223">《</st>` `<st c="11228">电影</st>` `<st c="11233">和</st>` `<st c="11238">评论</st>` `<st c="11244">模型在</st>` `<st c="11280">movies</st>` `<st c="11286">应用</st>` 中实现。</st>

            +   `<st c="11291">《</st>` `<st c="11296">订单</st>` `<st c="11301">和</st>` `<st c="11306">项目</st>` `<st c="11310">模型在</st>` `<st c="11346">购物车</st>` `<st c="11350">应用</st>` 中实现。</st>

            +   `<st c="11355">《</st>` `<st c="11360">用户</st>` `<st c="11364">模型尚未实现。</st>` `<st c="11378">相反,我们利用了位于</st>` `<st c="11472">admin.contrib.auth</st>` `<st c="11490">应用</st>` 中的 Django 内置模型。</st>

        `<st c="11495">最后,让我们回顾一下特定类与模型之间的关系(</st>`*<st c="11559">图 11</st>**<st c="11569">.7</st>*<st c="11571">):</st>`

            +   `<st c="11582">评论</st>` `<st c="11588">类名变成了一个</st>` `<st c="11609">评论</st>` `<st c="11615">Python 类。</st>` `<st c="11630">我们继承自</st>` `<st c="11648">models.Model</st>` `<st c="11660">来定义它为一个 Django</st>` `<st c="11686">模型类。</st>`

            +   `<st c="11706">id</st>` `<st c="11708">,</st>` `<st c="11710">comment</st>` `<st c="11717">, 和</st>` `<st c="11723">date</st>` `<st c="11727">类属性变成了 Python 类属性。</st>` `<st c="11777">我们利用了</st>` `<st c="11793">models</st>` `<st c="11799">模块来利用类似于在类图中定义的字段类型。</st>`

            +   `<st c="11919">评论</st>` `<st c="11925">和</st>` `<st c="11930">电影</st>` `<st c="11935">类变成了 Python 类属性。</st>` `<st c="11977">我们利用了</st>` `<st c="11993">models.ForeignKey</st>` `<st c="12010">方法来定义两个模型之间的外键关系</st>` `<st c="12067">。</st>`

            +   `<st c="12111">评论</st>` `<st c="12117">和</st>` `<st c="12122">用户</st>` `<st c="12126">类变成了 Python 类属性。</st>` `<st c="12168">我们利用了</st>` `<st c="12184">models.ForeignKey</st>` `<st c="12201">方法来定义两个模型之间的外键关系</st>` `<st c="12246">。</st>`

        ![图 11.7 – 类与模型之间的关系](https://github.com/OpenDocCN/freelearn-python-zh/raw/master/docs/dj5-impt/img/B22457_11_7.jpg)

        `<st c="12531">图 11.7 – 类与模型之间的关系</st>`

        `<st c="12585">我们已经完成了类图和 Django 模型之间的所有连接。</st>` `<st c="12673">现在,我们准备启用用户进行购买。</st>`

        `<st c="12725">总结</st>`

        <st c="12733">在本章中,我们学习了简单发票的工作原理。</st> <st c="12788">我们创建了一些模型(</st>`<st c="12819">订单</st>` <st c="12825">和</st> `<st c="12830">项目</st>`<st c="12834">)。</st> <st c="12838">这些模型将使我们能够存储有关用户购买信息。</st> <st c="12914">我们回顾了创建 Django 模型和应用迁移的过程。</st> <st c="12994">最后,我们回顾了类图如何作为创建项目模型的蓝图。</st> <st c="13089">在下一章中,我们将实现购买功能,并允许用户查看</st> <st c="13175">他们的订单。</st>

第十三章:12

实现购买和订单页面

在前一章中,我们实现了存储购买信息的模型。 在本章中,我们将实现购买功能,并通过订单页面完成电影商店项目的最终构建。 用户将能够查看他们已下的订单。 稍后,我们将回顾电影商店 MVT 架构,以检查 Python 代码与 架构图之间的连贯性。

在本章中,我们将涵盖以下主题: 以下内容:

  • 创建 购买页面

  • 创建 订单页面

  • 回顾电影商店 MVT 架构

到本章结束时,我们将拥有我们电影商店项目的完整代码。 我们还将能够将架构图与实际 实现的代码联系起来。

技术要求

在本章中,我们将使用 Python 3.10+。 此外,我们将在本书中使用 VS Code 编辑器,您可以从 以下位置 code.visualstudio.com/下载。

本章的代码位于 以下位置 https://github.com/PacktPublishing/Django-5-for-the-Impatient-Second-Edition/tree/main/Chapter12/moviesstore

本章的 CiA 视频可以在 以下位置找到 https://packt.link/4NyAv

创建购买页面

让我们改进我们的购物车页面 并添加一些功能,以便用户可以进行购买。 为了实现这一点,我们需要遵循 以下步骤:

  1. 配置购买 URL。

  2. 定义 <st c="1422">购买</st> 函数。

  3. 更新 <st c="1454">购物车索引</st> 模板。

  4. 创建 <st c="1488">购物车购买</st> 模板。

配置购买 URL

<st c="1544">/cart/urls.py</st>中,添加以下 路径,如 粗体 所示:

 from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='cart.index'),
    path('<int:id>/add/', views.add, name='cart.add'),
    path('clear/', views.clear, name='cart.clear'), <st c="1802">path('purchase/', views.purchase,</st>
 <st c="1835">name='cart.purchase'),</st> ]

我们定义了一个<st c="1873">cart/purchase/</st> 路径,该路径将执行在<st c="1915">purchase</st> 文件中定义的<st c="1948">purchase</st> 函数。 我们将在稍后实现<st c="1982">purchase</st> 函数。**

定义购买函数

<st c="2041">/cart/views.py</st>中,添加以下代码行(加粗)

 …
from movies.models import Movie
from .utils import calculate_cart_total <st c="2172">from .models import Order, Item</st>
<st c="2203">from django.contrib.auth.decorators import login_required</st> … <st c="2263">@login_required</st>
<st c="2278">def purchase(request):</st>
 <st c="2301">cart = request.session.get('cart', {})</st>
 <st c="2340">movie_ids = list(cart.keys())</st>
 <st c="2370">if (movie_ids == []):</st>
 <st c="2392">return redirect('cart.index')</st>
 <st c="2422">movies_in_cart = Movie.objects.filter(id__in=movie_ids)</st>
 <st c="2478">cart_total = calculate_cart_total(cart, movies_in_cart)</st>
 <st c="2534">order = Order()</st>
 <st c="2550">order.user = request.user</st>
 <st c="2576">order.total = cart_total</st>
 <st c="2601">order.save()</st>
 <st c="2614">for movie in movies_in_cart:</st>
 <st c="2643">item = Item()</st>
 <st c="2657">item.movie = movie</st>
 <st c="2676">item.price = movie.price</st>
 <st c="2701">item.order = order</st>
 <st c="2720">item.quantity = cart[str(movie.id)]</st>
**<st c="2756">item.save()</st>**
 **<st c="2768">request.session['cart'] = {}</st>**
 **<st c="2797">template_data = {}</st>**
 **<st c="2816">template_data['title'] = 'Purchase confirmation'</st>**
 **<st c="2865">template_data['order_id'] = order.id</st>**
 **<st c="2902">return render(request, 'cart/purchase.html',</st>**
 **<st c="2947">{'template_data': template_data})</st>**

**此前的函数是我们在这本书中实现的最大函数。 让我们通过分解它来解释这个函数: 让我们将这个函数分解成几个部分:

  • <st c="3116">from .models import</st> <st c="3137">Order, Item</st>

    <st c="3148">from django.contrib.auth.decorators</st> <st c="3185">import login_required</st>

    让我们分析这段 代码:

    • 我们从当前<st c="3255">Order</st> <st c="3265">Item</st> 模型从当前<st c="3294">app 目录</st> 导入。

    • 我们导入<st c="3323">login_required</st> 装饰器。

  • <st c="3348">@</st>``<st c="3350">login_required</st>

    <st c="3364">def purchase(request):</st>

    <st c="3387">cart =</st> <st c="3395">request.session.get('cart', {})</st>

    <st c="3426">movie_ids =</st> <st c="3439">list(cart.keys())</st>

    <st c="3456">if (movie_ids == []):</st>

    **<st c="3478">return redirect('cart.index')</st>**

    让我们分析这段 代码:

    • 我们使用<st c="3554">login_required</st> 装饰器来确保用户必须登录才能访问<st c="3635">purchase</st> 函数。

    • 我们定义了<st c="3668">purchase</st> 函数,该函数将处理购买过程。

    • 我们从用户的会话中检索购物车数据。 <st c="3782">cart</st> 变量将包含一个字典,其中以电影 ID 为键,数量为值。

    • 我们从<st c="3911">cart</st> 字典中检索存储的电影 ID 并将它们转换为名为<st c="3956">movie_ids</st> 的列表。

    • 我们检查<st c="3983">movie_ids</st> 列表是否为空(这表示购物车为空)。 在这种情况下,用户将被重定向到<st c="4088">cart.index</st> 页面(在这里,<st c="4115">purchase</st> 函数完成其执行)。

*** <st c="4158">movies_in_cart =</st> <st c="4176">Movie.objects.filter(id__in=movie_ids)</st>

`<st c="4214">cart_total =</st>` `<st c="4228">calculate_cart_total(cart, movies_in_cart)</st>`

`<st c="4270">order =</st>` `<st c="4279">Order()</st>`

`<st c="4286">order.user =</st>` `<st c="4300">request.user</st>`

`<st c="4312">order.total =</st>` `<st c="4327">cart_total</st>`

`**<st c="4337">order.save()</st>**`

**`<st c="4350">for movie</st>` `<st c="4361">in movies_in_cart:</st>`**

**`<st c="4379">item =</st>` `<st c="4387">Item()</st>`**

**`<st c="4393">item.movie =</st>` `<st c="4407">movie</st>`**

**`<st c="4412">item.price =</st>` `<st c="4426">movie.price</st>`**

**`<st c="4437">item.order =</st>` `<st c="4451">order</st>`**

**`<st c="4456">item.quantity =</st>` `<st c="4473">cart[str(movie.id)]</st>`**

**`**<st c="4492">item.save()</st>**`**

****<st c="4504">让我们分析这段</st> <st c="4530">代码:</st>****

+   ****<st c="4538">如果购物车不为空,我们</st> <st c="4567">继续购买流程。</st>****

+   ****<st c="4598">根据购物车中存储的 ID 从数据库中检索电影对象</st> <st c="4679">使用</st> `<st c="4685">Movie.objects.filter(id__in=movie_ids</st>`<st c="4722">.</st>****

+   ****<st c="4723">我们使用</st> `<st c="4788">calculate_cart_total()</st>` <st c="4810">函数计算购物车中电影的总额。</st>****

+   ****<st c="4820">我们创建一个新的</st> `<st c="4837">Order</st>` <st c="4842">对象。</st> <st c="4851">我们设置其属性,例如</st> `<st c="4881">user</st>` <st c="4885">(登录用户) 和</st> `<st c="4911">total</st>` <st c="4916">(购物车总额),并将其保存到</st> <st c="4950">数据库中。</st>****

+   ****<st c="4963">我们遍历购物车中的电影。</st> <st c="5004">对于购物车中的每部电影,我们创建一个</st> `<st c="5017">Item</st>` <st c="5021">对象。</st> <st c="5057">对于每个</st> `<st c="5066">Item</st>`<st c="5070">,我们设置其</st> `<st c="5083">价格</st>` <st c="5088">和</st> `<st c="5093">数量</st>`<st c="5101">,链接相应的</st> `<st c="5126">电影</st>` <st c="5131">和</st> `<st c="5136">订单</st>`<st c="5141">,并将其保存到</st> <st c="5158">数据库中。</st>*********   `<st c="5171">request.session['cart'] = {}</st>`

`<st c="5200">template_data = {}</st>`

`<st c="5219">template_data['title'] = '</st>``<st c="5246">购买确认'</st>`

`<st c="5269">template_data['order_id'] =</st>` `<st c="5298">order.id</st>`

`<st c="5306">return render(request, 'cart/purchase.html', {'</st>``<st c="5354">template_data': template_data})</st>`

<st c="5386">让我们分析这段</st> <st c="5412">代码:</st>

+   <st c="5420">购买完成后,我们通过将</st> `<st c="5505">request.session['cart']</st>` <st c="5528">设置为空字典来清除用户会话中的购物车。</st>

+   <st c="5552">我们准备要发送到购买确认模板的数据。</st> <st c="5579">这些数据包括页面标题和创建的订单 ID。</st>

+   <st c="5696">最后,我们渲染</st> `<st c="5720">cart/purchase.html</st>` <st c="5738">模板。</st>******

******现在我们已经完成了购买功能,让我们添加一个链接到 此功能的按钮。

更新购物车.index 模板

<st c="5886">/cart/templates/cart/index.html</st> 文件中,添加 以下行 粗体

 …
        <a class="btn btn-outline-secondary mb-2">
          <b>Total to pay:</b> ${{ template_data.cart_total
        }}</a>
        {% if template_data.movies_in_cart|length > 0 %} <st c="6108"><a href="{% url 'cart.purchase' %}"</st>
 <st c="6143">class="btn bg-dark text-white mb-2">Purchase</st><st c="6188"></a></st> <a href="{% url 'cart.clear' %}">
          <button class="btn btn-danger mb-2">
            Remove all movies from Cart
          </button>
        </a>
        {% endif %}
        …

我们添加了一个按钮,将购物车页面与购买功能链接起来。 此按钮仅在 购物车中添加了电影 时显示

创建购物车购买模板

现在,在 <st c="6521">/cart/templates/cart/</st>中,创建一个新文件, <st c="6563">purchase.html</st>。目前,用以下内容填充它:

 {% extends 'base.html' %}
{% block content %}
<div class="p-3">
  <div class="container">
    <div class="row mt-3">
      <div class="col mx-auto mb-3">
        <h2>Purchase Completed</h2>
        <hr />
        <p>Congratulations, purchase completed. Order
          number is: <b>#{{ template_data.order_id }}</b>
        </p>
      </div>
    </div>
  </div>
</div>
{% endblock content %}

我们创建了一个简单的模板,它扩展了 <st c="6993">base.html</st> 模板,并向用户显示祝贺信息,包括当前购买的订单编号。

现在,保存这些文件,运行服务器,转到 <st c="7158">http://localhost:8000/movies</st>,点击几部电影,并将它们添加到购物车。 然后,转到 购物车 部分并点击 购买 (您需要 登录才能执行购买操作)。 然后,您将看到一个购买确认消息(图 12**.1):

图 12.1 – 购买页面

图 12.1 – 购买页面

如果您导航到管理面板,您将看到一个新订单已注册(与进行购买的用户相关联)以及一些项目(与之前的订单相关联),如图 图 12**.2所示:

图 12.2 – 管理面板中的订单和项目

图 12.2 – 管理面板中的订单和项目

此时,我们能够创建订单并将相应的信息注册到数据库中。 现在,让我们实现一个查看订单的页面。

创建订单页面

让我们通过允许用户查看他们的订单来完善我们的电影商店。 为了实现这一点,我们需要遵循 <st c="8213">以下步骤:</st>

  1. 配置 <st c="8277">订单 URL。</st>

  2. 定义 <st c="8302">订单</st> 函数。

  3. 创建 <st c="8332">accounts.orders</st> 模板。

  4. 在基础模板中添加链接。

配置订单 URL

订单属于一个 <st c="8442">特定用户</st>因此,我们将在 <st c="8523">accounts</st> 应用中添加订单功能。 <st c="8540">/accounts/urls.py</st>中,添加以下粗体路径:

 from django.urls import path
from . import views
urlpatterns = [
    path('signup', views.signup, name='accounts.signup'),
    path('login/', views.login, name='accounts.login'),
    path('logout/', views.logout, name='accounts.logout'), <st c="8813">path('orders/', views.orders, name='accounts.orders'),</st> ]

我们定义了一个 <st c="8883">accounts/orders/</st> 路径,该路径将执行在 <st c="8960">views</st> 文件中定义的 <st c="8929">orders</st> 函数。 我们 将在稍后实现 <st c="8994">orders</st> 函数。

定义订单函数

<st c="9049">/accounts/views.py</st>中,添加以下 <st c="9076">行</st> ,并在粗体中:

 …
from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required <st c="9200">from django.contrib.auth.models import User</st> … <st c="9245">@login_required</st>
<st c="9260">def orders(request):</st>
**<st c="9281">template_data = {}</st>**
 **<st c="9300">template_data['title'] = 'Orders'</st>**
 **<st c="9334">template_data['orders'] = request.user.order_set.all()</st>**
 **<st c="9389">return render(request, 'accounts/orders.html',</st>**
 **<st c="9436">{'template_data': template_data})</st>**

**让我们解释一下 <st c="9489">之前的代码:</st>

  • 我们从 Django 的 <st c="9518">用户</st> 模型 中导入。 <st c="9543">认证系统</st>

  • 我们使用 <st c="9577">login_required</st> 装饰器来确保用户必须登录才能访问 <st c="9658">orders</st> 函数。

  • 我们定义了 <st c="9689">orders</st> 函数,它 <st c="9711">接受</st> <st c="9720">request</st> 对象作为 <st c="9738">参数</st>

  • 我们定义了 <st c="9765">template_data</st> 变量并将其 分配 <st c="9804">标题</st>

  • 我们检索属于当前登录用户的全部订单(<st c="9877">request.user</st>)。 order_set 属性用于通过其关系访问与用户相关联的相关订单(你可以在这里了解更多关于此类关系的信息 https://docs.djangoproject.com/en/5.0/topics/db/examples/many_to_one/)。 记住,User 外键 关系存在于User 模型和Order 模型之间。

  • 最后,我们将订单传递给模板并渲染它。

创建accounts.orders模板

现在,在/accounts/templates/accounts/</st>中,创建一个新文件, <st c="10371">新文件</st> <st c="10381">orders.html</st>。目前,用以下内容填充它:

 {% extends 'base.html' %}
{% block content %}
<div class="p-3">
  <div class="container">
    <div class="row mt-3">
      <div class="col mx-auto mb-3">
        <h2>My Orders</h2>
        <hr />
        {% for order in template_data.orders %}
        <div class="card mb-4">
          <div class="card-header">
            Order #{{ order.id }}
          </div>
          <div class="card-body">
            <b>Date:</b> {{ order.date }}<br />
            <b>Total:</b> ${{ order.total }}<br />
            <table class="table table-bordered
              table-striped text-center mt-3">
              <thead>
                <tr>
                  <th scope="col">Item ID</th>
                  <th scope="col">Movie</th>
                  <th scope="col">Price</th>
                  <th scope="col">Quantity</th>
                </tr>
              </thead>
              <tbody>
                {% for item in order.item_set.all %}
                <tr>
                  <td>{{ item.movie.id }}</td>
                  <td>
                    <a class="link-dark"
                      href="{% url 'movies.show'
                      id=item.movie.id %}">
                      {{ item.movie.name }}
                    </a>
                  </td>
                  <td>${{ item.movie.price }}</td>
                  <td>{{ item.quantity }}</td>
                </tr>
                {% endfor %}
              </tbody>
            </table>
          </div>
        </div>
        {% endfor %}
      </div>
    </div>
  </div>
</div>
{% endblock content %}

让我们解释一下之前的代码:

  • 我们扩展了<st c="11437">base.html</st> 模板。

  • 我们遍历存储在<st c="11501">template_data.orders</st>中的每个订单对象。对于每个订单,我们显示其<st c="11554">日期</st> <st c="11563">总计</st>

  • 然后,我们遍历当前订单中的每个项目。The <st c="11639">order.item_set.all</st> 检索与当前订单相关联的所有相关项目。 对于这些项目中的每一个,我们显示其 <st c="11761">价格</st> <st c="11771">数量</st>,以及相应的电影 <st c="11808">id</st> 和名称。

在基础模板中添加链接

让我们在基础模板 中添加订单链接。 <st c="11908">/moviesstore/templates/base.html</st>中,在标题部分,添加以下带有粗体的行:

 …
            {% if user.is_authenticated %} <st c="12031"><a class="nav-link"</st>
 <st c="12050">href="{% url 'accounts.orders' %}">Orders</st><st c="12092"></a></st> <a class="nav-link"
              href="{% url 'accounts.logout' %}">Logout
              ({{ user.username }})
            </a>
            {% else %}
            <a class="nav-link"
              href="{% url 'accounts.login' %}">Login
            </a>
            <a class="nav-link"
              href="{% url 'accounts.signup' %}">Sign Up
            </a>
            {% endif %}
            …

现在,保存这些文件,运行服务器,并转到http://localhost:8000/accounts/orders。如果你进行了购买,你会看到相应的订单(图 12**.3):

图 12.3 – 订单页面

图 12.3 – 订单页面

我们已经完成了 电影商店项目的代码。 我们已经实现了在第一章第一章中计划的所有功能。 现在,让我们将实现的代码与架构图进行比较。 architecture diagram.

回顾电影商店 MVT 架构

我们设计的电影商店架构图 第一章 为设计电影商店的应用、层和代码提供了一个蓝图。 我们已经实现了该图中描述的所有应用程序和元素。 因此,让我们快速回顾一下我们迄今为止所取得的成果。 So, let’s quickly recap what we have accomplished so far.

图 12**.4 显示了完整的项目树目录结构,并将其与项目架构的简化版本进行了比较。 我们已经成功实现了四个应用程序(<st c="13509">accounts</st> <st c="13520">cart</st> <st c="13526">home</st>,和 <st c="13536">movies</st>),它们包含了项目的大部分功能。

图 12.4 – 项目树目录与简化架构对比

图 12.4 – 项目树目录与简化架构对比

图 12**.5 显示了完整的 架构。 我们希望您能更好地理解每个架构元素以及它们之间的关系。

图 12.5 – 电影商店架构

图 12.5 – 电影商店架构

让我们做一个最后的 快速分析:

  • 我们实现了一个名为 <st c="14519">moviesstore</st>的项目级文件夹。这个文件夹包含了项目级的 URL 文件,它与应用程序级的 URL 文件相连接。

  • 我们实现了四个 Django 应用程序。 对于这些应用程序中的每一个,我们都展示了三个主要层(模型、视图和模板)之间的通信。

  • 我们学习了如何将代码分布在多个应用程序中以提高可维护性和 分离职责。

  • 我们通过为我们的电影商店项目实现一系列功能来实践那些文件和层的实现。 Store project.

这是一段怎样的旅程啊! 我们使用了大量的 Django 模块、库、函数、概念和元素来 实现 这个项目。

摘要

在本章中,我们完成了电影商店项目。 我们实现了购买功能,该功能利用了 <st c="15276">订单</st> <st c="15286">商品</st> 模型。 我们创建了一个订单页面,允许用户查看他们的订单。 我们回顾了电影商店架构图,并与实际项目代码进行了比较和讨论。 自从我们开始以来,我们已经学到了很多。 现在,是时候进入最后一章了。 让我们学习如何将我们的电影商店项目部署到 云端。**********

第十四章:13

将应用程序部署到云端

我们的项目目前运行在本地的计算机上。 为了使此项目可供他人访问,我们需要将其部署到互联网上的服务器上。 一种常见的方法是将我们的 Django 项目部署到 PythonAnywhere,因为它对小型网站免费。 让我们看看如何将我们的应用程序部署到 云端。

在本章中,我们将涵盖以下主题:

  • 管理 GitHub 和 Git

  • 将代码克隆到 PythonAnywhere

  • 配置 虚拟环境

  • 设置您的 Web 应用

  • 配置 静态文件

在本章结束时,您将具备将小型 Python 应用程序部署到云的知识和能力。

技术要求

在本章中,我们将使用 Python 3.10+。 我们将使用 Git 将代码上传到云端,您可以从 https://git-scm.com/downloads下载。 最后,我们将使用本书中的VS Code 编辑器,您可以从 以下位置下载 https://code.visualstudio.com/

本章的 CiA 视频可以在 以下位置找到 https://packt.link/QXahe

管理 GitHub 和 Git

要将我们的代码上传到 PythonAnywhere 等网站,首先,我们需要将代码放在 GitHub 或 GitLab 等代码共享平台上。 在本章中,我们将使用 GitHub。 如果您已经熟悉将代码上传到 GitHub,请跳过以下 部分,并将 Movies Store 代码上传到新的 GitHub 仓库。 否则,您可以 跟随操作。

要将我们的代码上传到 GitHub,我们将遵循以下 步骤:

  1. 理解 Git 和 GitHub。

  2. 创建一个 GitHub 仓库。

  3. 将我们的代码 上传到 GitHub。

理解 Git 和 GitHub

Git 是一个 分布式版本控制系统,旨在以速度和效率处理从小型到非常大的项目。 它允许多个开发者通过跟踪文件(https://git-scm.com/)的更改来协作。

GitHub 是一个基于 Git 版本控制系统的 Web 平台。 它为使用 Git 进行版本控制的软件开发项目提供托管服务(https://github.com/)。

我们将通过利用 Git 来增强我们的电影商店项目,使其成为一个版本控制系统。 然后,我们将使用 GitHub 在云端托管电影商店项目的代码。 使用 GitHub。

创建 GitHub 仓库

GitHub 仓库是一个中央 位置,用于存储和管理与项目相关的文件和文件夹。 它作为项目的版本控制中心,允许多个协作者参与到 开发过程中。

让我们按照以下步骤创建一个 GitHub 仓库:

  1. 前往 https://github.com/ ,如果你还没有账户,请注册一个。 然后,通过点击右上角的 + 创建一个新的仓库,并选择 新建仓库 (图 13**.1):

图 13.1 – GitHub – 创建新仓库选项

图 13.1 – GitHub – 创建新仓库选项

  1. 给你的仓库起一个名字,例如 <st c="2905">moviesstore</st>。选择 公共 单选按钮,然后点击 创建仓库 (图 13**.2):

图 13.2 – GitHub – 创建新仓库

图 13.2 – GitHub – 创建新仓库

我们已经成功创建了一个 GitHub 仓库。 我们将用它来存储电影商店项目的代码。 保持你的 GitHub 仓库开启;我们将在下一节中使用那个页面。

将我们的代码上传到 GitHub

我们将开始将我们的 代码移动到 GitHub。 在你的本地机器的终端中,确保你已经安装了 Git,通过运行以下命令来检查: 以下命令:

 git

如果你在终端中运行 <st c="3942">git</st> 命令并看到 Git 使用和命令列表,这表明你已经安装了 Git(图 13**.3):

图 13.3 – 在终端中执行 git 命令

图 13.3 – 在终端中执行 git 命令

如果你看不到它们,你需要安装 Git。 访问 Git 网站(https://git-scm.com/downloads)并按照说明安装 Git。 安装 Git 后,你可能需要关闭并重新打开终端,并在其中输入“git”以确保它 已安装。

现在我们已经安装了 Git,让我们继续下一步,将我们的 Movies Store 项目代码上传到我们的 GitHub 仓库:

  1. 在顶部 <st c="4894">moviesstore</st> 文件夹(包含 <st c="4940">manage.py</st> 文件的文件夹)中打开你的终端。 然后,运行以下命令: 以下命令:

    <st c="4989">git init</st>
    

    之前的命令将你的文件夹标记为 Git 项目,允许你开始跟踪更改。 在项目目录中添加了一个名为 .git 的隐藏文件夹。 此文件夹存储了 Git 需要跟踪更改和管理项目的所有元数据、配置文件和元素。

  2. 接下来,运行以下命令: 以下命令:

    <st c="5317">git add .</st>
    

    之前的命令将我们项目中的所有内容(文件夹、子文件夹和文件)添加到暂存区,为下一次提交做准备。

  3. 然后,继续提交之前的更改: 以下更改:

    <st c="5531">git commit -m "first version"</st>
    

    之前的命令用于记录我们对暂存区所做的更改和包含内容。 当你运行 git commit 时,你实际上是在创建项目当前状态的快照。 你可以通过你提供的描述性消息来识别不同的提交。

  4. 接下来,运行以下命令: 以下命令:

    <st c="6076">git remote add origin <your-origin-path></st> command (*<st c="6126">Figure 13</st>**<st c="6136">.4</st>*) and run it in the Terminal (remember to replace <st c="6189"><your-origin-path></st> with yours):
    

图 13.4 – 定位你的 GitHub 仓库路径

图 13.4 – 定位你的 GitHub 仓库路径

<st c="6525">git remote add origin <your-origin-path></st>

之前的命令实际上是在告诉 Git 创建一个名为 origin 的新远程仓库,并将其与提供的 URL 或路径关联。 这将允许 你稍后推送你的本地更改到远程 仓库。

  1. 要将代码从你的本地计算机移动到 GitHub,运行以下命令: 以下命令:

    <st c="6871">git push -u origin main</st>
    

    如果上传成功,你应该会看到类似这样的消息(图 13.5):

图 13.5 – 成功将 git 推送到 GitHub 仓库

图 13.5 – 成功将 git 推送到 GitHub 仓库

注意

如果您是第一次将代码上传到 GitHub,您可能会看到一个提示,要求您登录 GitHub。 请完成 此过程。

现在,当您重新加载 GitHub 仓库页面时,应该会看到正确上传的 Movies Store 项目结构和文件(如图 图 13.6**.6):

图 13.6 – 包含 Movies Store 项目代码的 GitHub 仓库

图 13.6 – 包含 Movies Store 项目代码的 GitHub 仓库

注意

请注意,Git 和 GitHub 还有很多内容。 我们刚刚介绍了上传我们的代码到 GitHub 的必要步骤。

有了这个,我们现在已经将 我们的代码放置在 GitHub 上。 接下来,我们将在 PythonAnywhere 上克隆它。

在 PythonAnywhere 上克隆您的代码

PythonAnywhere (https://www.pythonanywhere.com/)是一个基于云的平台,为 Python 应用程序提供 Web 托管 环境。 它允许 用户直接在他们的网络浏览器中编写、编辑和运行 Python 代码,而无需在本地安装任何 软件。

在 PythonAnywhere 上部署现有 Django 项目的步骤可以在 https://help.pythonanywhere.com/pages/DeployExistingDjangoProject找到,但我们将在这里为您指导。

现在,我们的代码已经上传到 GitHub,接下来我们将进行下一步,创建 PythonAnywhere 账户并将代码从 GitHub 迁移到 PythonAnywhere:

  1. 前往 https://www.pythonanywhere.com/registration/register/beginner/ 并注册一个免费的初学者账户,如果您还没有的话。

  2. 然后,点击 仪表板 | 新建控制台 | $ Bash (图 13.7**.7):

图 13.7 – 创建新控制台

图 13.7 – 创建新控制台

  1. 上一步将打开一个 Bash 控制台。 回到您的 GitHub 仓库,点击 代码 并复制克隆的 URL(图 13**.8):

图 13.8 – 复制仓库 URL

图 13.8 – 复制仓库 URL

  1. 要克隆之前的 仓库,请返回 PythonAnywhere Bash 控制台并运行以下命令(将 <st c="11178"><repo-url></st> 部分替换为您的,例如, <st c="11219">git</st> <st c="11223">clone</st> https://github.com/danielgara/moviesstore.git):

    <st c="11461">ls</st> command in Bash, and you will see a folder with the repository name containing the repository code (refer to Figure 13.9).
    

图 13.9 – 使用 ls 命令检查仓库是否成功克隆

图 13.9 – 使用 ls 命令检查仓库是否成功克隆

我们已经成功将我们的仓库代码克隆到 PythonAnywhere 中。 现在,让我们配置一个虚拟 环境,以便能够运行我们的项目。

配置虚拟环境

在 Python 中,一个 虚拟环境 是一个包含特定 Python 解释器版本、一系列库和包的自包含目录。 它允许您为每个 Python 项目创建一个隔离的环境,确保依赖项保持独立,不会相互干扰。 It allows you to create an isolated environment for each of your Python projects, ensuring that dependencies are kept separate and do not interfere with each other.

接下来,我们将在 PythonAnywhere Bash 控制台中创建一个虚拟环境,以隔离我们的项目代码和依赖项。 让我们按照以下步骤进行: 以下步骤:

  1. 在 PythonAnywhere Bash 控制台中创建虚拟环境,我们需要执行类似以下命令: <st c="12928">mkvirtualenv -p python3.10 <environment-name></st>。目前,我们将用 <st c="13000"><environment-name></st> 替换为 <st c="13024">moviesstoreenv</st> 并运行以下命令:

    <st c="13061">mkvirtualenv -p python3.10 moviesstoreenv</st>
    

    我们将在 Bash 中看到虚拟 env 的名称,例如,(moviesstoreenv)。 这意味着我们处于虚拟环境中(图 13.10):

图 13.10 – Bash 位于虚拟环境内

图 13.10 – Bash 位于虚拟环境内

  1. 回到我们的虚拟 env 中,我们需要安装 <st c="14270">django</st> <st c="14281">pillow</st> (就像我们在开发中做的那样)。 因此,运行以下命令: 以下命令:

    <st c="14338">pip install django==5.0 pillow</st>
    

    之前的执行可能需要几分钟到十分钟。 PythonAnywhere 拥有非常快的互联网,但文件系统访问可能较慢,Django 在安装过程中会创建大量的 小文件幸运的是,您只需做一次。 一旦完成,您应该会看到一个类似于图 13.11中所示的消息:

图 13.11 – Django 和 Pillow 已安装

图 13.11 – Django 和 Pillow 已安装

我们已经配置好了我们的虚拟环境。 目前,您可以保持 Bash 控制台开启或关闭它。 现在,让我们创建一个利用这个 虚拟环境的 Web 应用。

设置您的 Web 应用

在这个时候,我们需要准备 三份信息:

  • 您的 Django 项目顶级文件夹的路径(包含 <st c="15879">manage.py</st> 文件的文件夹)。 对于这个项目,它通常是 <st c="15946">/home</st> <st c="15956">/<pythonanywhere-user></st> 以及 <st c="15983">/<github-repo-name></st>的组合。在我们的例子中,它 <st c="16024">/home/danielgara/moviesstore</st>

  • 您主项目文件夹的名称(即包含您的 <st c="16141">settings.py</st> 文件的文件夹名称)。 在我们的例子中,它 <st c="16179">moviesstore</st>

  • 您的虚拟环境名称。 在我们的例子中,它 <st c="16239">moviesstoreenv</st>

现在,按照以下步骤设置您的 Web 应用:

  1. 在您的浏览器中,打开一个新标签页并转到 PythonAnywhere 仪表板。 然后,点击 Web 标签页并点击 添加新的 Web 应用 (图 13**.12):

图 13.12 – PythonAnywhere Web 标签

图 13.12 – PythonAnywhere Web 标签

  1. PythonAnywhere 将要求您提供 您的 Web 应用的域名。只需点击 下一步 (*图 13.13*):

图 13.13 – PythonAnywhere 域名

图 13.13 – PythonAnywhere 域名

  1. 选择 Python Web 框架 部分,选择 手动配置 (图 13**.14):

图 13.14 – 选择手动配置

图 13.14 – 选择手动配置

注意

确保你选择手动配置,而不是 Django 选项;这仅适用于新 项目。

  1. 选择正确的 Python 版本(与您创建虚拟环境时使用的版本相同)。 在我们的例子中,它是 <st c="17732">Python 3.10</st> (图 13**.15)。 最后,当被要求选择 手动配置时,点击 下一步 (图 13**.16)。

图 13.15 – 选择正确的 Python 版本

图 13.15 – 选择正确的 Python 版本

图 13.16 – 完成 Web 应用

图 13.16 – 完成 Web 应用

  1. 一旦创建 Web 应用,你需要在 <st c="18621">moviesstoreenv</st>中输入你的虚拟环境名称,它将自动完成其完整路径 <st c="18689">/home/username/.virtualenvs/moviesstoreenv</st>

图 13.17 – 输入虚拟环境名称

图 13.17 – 输入虚拟环境名称

  1. 接下来,在 代码部分中输入你的用户文件夹路径(<st c="19097">/home/<your-username>/</st>),用于 源代码 工作目录 (图 13**.18):

图 13.18 – 输入你的代码路径

图 13.18 – 输入你的代码路径

  1. 点击 <st c="19395">wsgi.py</st> 文件,位于 代码部分内,而不是你本地 Django 项目文件夹中的那个(图 13**.19):

图 13.19 – 访问 wsgi.py 文件

图 13.19 – 访问 wsgi.py 文件

这将带您到一个编辑器,您可以在其中 进行更改。

  1. 删除除 Django 部分之外的所有内容,并取消注释该部分。 您的 WSGI 文件将类似于以下内容: 以下:

     # +++++++++++ DJANGO +++++++++++
    # To use your own django app use code like this:
    import os
    import sys
    path = '<st c="20013">/home/danielgara/moviesstore</st>'
    if path not in sys.path:
        sys.path.append(path)
    os.environ['DJANGO_SETTINGS_MODULE'] =
        '<st c="20132">moviesstore.settings</st>'
    from django.core.wsgi import get_wsgi_application
    application = get_wsgi_application()
    path = '<st c="20423">DJANGO_SETTINGS_MODULE</st> (where the <st c="20457">settings.py</st> file is located):
    
    os.environ['DJANGO_SETTINGS_MODULE'] =
        '<st c="20619">settings.py</st>. Go to the PythonAnywhere <st c="20733">settings.py</st> file (*<st c="20751">Figure 13</st>**<st c="20761">.20</st>*):
    

图 13.20 – 访问 settings.py 文件

图 13.20 – 访问 settings.py 文件

  1. 点击 <st c="21465">settings.py</st> 文件。 <st c="21486">settings.py</st>中,修改 <st c="21510">ALLOWED_HOSTS</st> 变量:

     …
    # SECURITY WARNING: don't run with debug turned on in
      production! DEBUG = True
    ALLOWED_HOSTS = [<st c="21631">'*'</st>]
    …
    

    保存 文件。

  2. 然后,转到 Web 选项卡,并 点击 Reload 按钮以刷新您的域名(图 13**.21):

图 13.21 – 重新加载 web 应用

图 13.21 – 重新加载 web 应用

以下 <st c="21926">ALLOWED_HOSTS</st> 设置表示我们的 Django 站点可以服务哪些主机/域名。 这是一项安全措施,以防止 HTTP Host 头攻击。 我们使用了星号(<st c="22096">*</st>)通配符来表示所有域名都是可接受的。 在您的生产项目中,您可以明确列出允许的域名。

  1. 转到您的项目 URL(例如,在之前的屏幕截图中的蓝色链接,例如,<st c="22321">danielgara.pythonanywhere.com</st>),主页现在应该出现(图 13**.22):

注意

主页看起来会很奇怪,因为我们需要配置我们的应用程序以服务静态文件(如图片和样式)。 我们稍后会修复它。

图 13.22 – PythonAnywhere web 应用链接

图 13.22 – PythonAnywhere web 应用链接

我们几乎完成了! 让我们在下一节中修复静态图片。

配置静态文件

让我们解决我们的 静态和媒体图片 不显示的问题:

  1. 在 PythonAnywhere 中,返回到 <st c="22921">settings.py</st> 文件。 我们需要在粗体中添加以下内容:

     …
    STATIC_URL = 'static/' <st c="23002">STATIC_ROOT = os.path.join(BASE_DIR, 'static')</st> # Default primary key field type
    # https://docs.djangoproject.com/en/4.0/ref/settings/
    #default-auto-field
    DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
    MEDIA_ROOT = os.path.join(BASE_DIR,'media')
    MEDIA_URL = '/media/'
    …
    

    以下 <st c="23281">STATIC_ROOT</st> 变量定义了一个中央位置,我们将所有 静态文件收集到该位置。

  2. 在 PythonAnywhere 中,转到 控制台 选项卡,并点击您的 Bash 控制台。然后,通过执行以下命令连接到您的虚拟环境:

    <st c="23905">settings.py</st> and copies them into <st c="23938">STATIC_ROOT</st>:
    

图 13.23 – 执行 python manage.py collectstatic 命令

图 13.23 – 执行 python manage.py collectstatic 命令

您需要每次想要发布您静态文件的新版本时都重新运行此 命令。

  1. 接下来,设置静态文件映射,以便我们的 web 服务器为您提供服务。 <st c="24597">/static/</st>. 在 <st c="24650">static/</st>, 例如, <st c="24672">/home/danielgara/moviesstore/static/</st> (图 13**.24):

图 13.24 – 定义静态文件

图 13.24 – 定义静态文件

  1. 然后,在 Web 选项卡中,点击 重新加载,打开您的网站,现在您的静态图像应该会出现(图 13**.25):

图 13.25 – 电影商店 – 首页

图 13.25 – 电影商店 – 首页

我们做到了! 我们的电影商店 项目已部署到云端。 现在您可以在网站的不同部分之间导航或与同事 和朋友们分享您的网站链接。

总结

我们已经介绍了大量内容,为您提供创建全栈 Django 应用所需的所有技能。 我们涵盖了 Django 的主要功能:模板、视图、URL、用户认证、授权、模型、会话、表单和部署。 您现在有了构建自己的 Django 网站所需的知识。 在我们的 Reviews 应用程序中,CRUD 功能在许多网络应用程序中都很常见 – 例如,您已经有了创建博客、待办事项列表或购物车 网络应用程序的所有工具。

希望您喜欢这本书,并希望从我们这里学到更多

posted @ 2025-09-23 21:55  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报