在使用Django之前,您需要安装它。我们有 完整的安装指南,涵盖所有可能性; 本指南将指导您进行简单,最小化的安装,在您完成介绍时可以正常工作。

安装Python¶

作为一个Python Web框架,Django需要Python。请参阅 Django可以使用哪些Python版本?详情。Python包含一个名为SQLite的轻量级数据库,因此您不需要设置数据库。

https://www.python.org/downloads/或使用操作系统的软件包管理器获取最新版本的Python 

关于Jython的Django

如果您使用Jython(Java平台的Python实现),则需要执行一些额外的步骤。有关详细信息,请参阅在Jython上运行Django

您可以通过python从shell 输入来验证是否已安装Python 你应该看到类似的东西:

Python 3.4.x
[GCC 4.x] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>>

设置数据库

只有在您希望使用PostgreSQL,MySQL或Oracle等“大型”数据库引擎时,才需要执行此步骤。要安装此类数据库,请查阅 数据库安装信息

删除任何旧版本的Django¶

如果要从先前版本升级Django的安装,则需要在安装新版本之前卸载旧的Django版本

安装Django¶

你有三个简单的选项来安装Django:

  • 安装正式版这是大多数用户的最佳方法。
  • 安装操作系统分发提供的Django版本
  • 安装最新的开发版本此选项适用于需要最新和最强大功能并且不怕运行全新代码的发烧友。您可能会在开发版本中遇到新的错误,但报告它们有助于Django的开发。此外,与最新的稳定版本相比,第三方软件包的发行版不太可能与开发版本兼容。

请始终参考与您正在使用的Django版本对应的文档!

如果您执行前两个步骤中的任何一个,请留意开发版本中标记为文档的部分文档该短语标记仅在Django的开发版本中可用的功能,并且它们可能不适用于正式版本。

验证

要验证Python可以看到Django,请python从shell中输入。然后在Python提示符下,尝试导入Django:

>>> import django
>>> print(django.get_version())
1.11

 

编写你的第一个Django应用程序,第1部分

让我们通过例子来学习。

在本教程中,我们将引导您完成基本轮询应用程序的创建。

它由两部分组成:

  • 一个公共站点,允许人们查看民意调查并在其中投票。
  • 一个管理站点,允许您添加,更改和删除民意调查。

我们假设你已经安装Django您可以通过在shell提示符中运行以下命令(由$前缀表示)来告知Django已安装以及哪个版本:

$ python -m django --version

如果安装了Django,您应该会看到安装的版本。如果不是,您将收到错误消息“没有名为django的模块”。

本教程是为Django 1.11和Python 3.4或更高版本编写的。如果Django版本不匹配,您可以使用本页右下角的版本切换器参考您的Django版本的教程,或者将Django更新到最新版本。如果您仍在使用Python 2.7,则需要稍微调整代码示例,如注释中所述。

有关如何删除旧版本Django并安装较新版本的建议,请参阅如何安装Django

 

创建项目

如果这是你第一次使用Django,你将不得不处理一些初始设置。也就是说,您需要自动生成一些建立Django 项目的代码- Django实例的设置集合,包括数据库配置,Django特定选项和特定于应用程序的设置。

从命令行cd进入要存储代码的目录,然后运行以下命令:

$ django-admin startproject mysite

这将mysite在当前目录中创建一个目录。如果它不起作用,请参阅运行django-admin的问题

注意

您需要避免在内置Python或Django组件之后命名项目。特别是,这意味着你应该避免使用像 django(这将与Django本身冲突)或test(与内置Python包冲突)这样的名称。

这段代码应该在哪里生活?

如果您的背景是普通的PHP(不使用现代框架),那么您可能习惯将代码放在Web服务器的文档根目录下(在某个地方/var/www)。使用Django,你不会这样做。将任何此Python代码放在​​Web服务器的文档根目录中并不是一个好主意,因为它可能会使人们可能通过Web查看您的代码。这对安全性不利。

将代码放在文档根目录之外某个目录中,例如 /home/mycode

让我们来看看startproject创造了什么

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

这些文件是:

  • 外部mysite/根目录只是项目的容器。它的名字对Django来说无关紧要; 你可以将它重命名为你喜欢的任何东西。
  • manage.py:一个命令行实用程序,允许您以各种方式与此Django项目进行交互。您可以manage.pydjango-admin和manage.py中阅读有关的所有详细信息 
  • 内部mysite/目录是项目的实际Python包。它的名称是您需要用来导入其中任何内容的Python包名称(例如mysite.urls)。
  • mysite/__init__.py:一个空文件,告诉Python该目录应该被视为Python包。如果您是Python初学者,请阅读官方Python文档中有关包的更多信息
  • mysite/settings.py:此Django项目的设置/配置。 Django设置将告诉您有关设置如何工作的所有信息。
  • mysite/urls.py:这个Django项目的URL声明; 您的Django支持的站点的“目录”。您可以在URL调度程序中阅读有关URL的更多信息
  • mysite/wsgi.py:与WSGI兼容的Web服务器的入口点,用于为您的项目提供服务。有关更多详细信息,请参阅如何使用WSGI进行部署

开发服务器

让我们验证您的Django项目是否有效。mysite如果尚未更改到外部目录,请运行以下命令:

$ python manage.py runserver

您将在命令行中看到以下输出:

Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

December 05, 2018 - 15:50:53
Django version 1.11, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

 

注意

暂时忽略有关未应用数据库迁移的警告; 我们很快就会处理数据库。

您已经启动了Django开发服务器,这是一个纯粹用Python编写的轻量级Web服务器。我们已经将它包含在Django中,因此您可以快速开发,而无需处理配置生产服务器(如Apache),直到您准备好进行生产。

现在是时候注意了:不要在类似生产环境的任何地方使用这个服务器。它仅用于开发时使用。(我们的业务是制作Web框架,而不是Web服务器。)

现在服务器正在运行,请使用Web浏览器访问http://127.0.0.1:8000/您将看到一个“欢迎来到Django”页面,采用令人愉悦的浅蓝色粉彩。有效!

改变端口

默认情况下,该runserver命令在端口8000的内部IP上启动开发服务器。

如果要更改服务器的端口,请将其作为命令行参数传递。例如,此命令在端口8080上启动服务器:

$ python manage.py runserver 8080

如果要更改服务器的IP,请将其与端口一起传递。例如,要监听所有可用的公共IP(如果您正在运行Vagrant或想要在网络上的其他计算机上展示您的工作,这很有用),请使用:

$ python manage.py runserver 0:8000

00.0.0.0的快捷方式可以在runserver参考中找到开发服务器的完整文档

自动重装 runserver

开发服务器根据需要自动为每个请求重新加载Python代码。您无需重新启动服务器即可使代码更改生效。但是,某些操作(如添加文件)不会触发重新启动,因此在这些情况下您必须重新启动服务器。

创建民意调查应用

既然你的环境 - 一个“项目” - 已经建立起来,你就可以开始工作了。

您在Django中编写的每个应用程序都包含一个遵循特定约定的Python包。Django附带了一个实用程序,可以自动生成应用程序的基本目录结构,因此您可以专注于编写代码而不是创建目录。

项目与应用

项目和应用程序之间有什么区别?应用程序是执行某些操作的Web应用程序 - 例如,Weblog系统,公共记录数据库或简单的轮询应用程序。项目是特定网站的配置和应用程序的集合。项目可以包含多个应用程序。一个应用程序可以在多个项目中。

您的应用程序可以存在于Python路径的任何位置在本教程中,我们将在您的manage.py 文件旁边创建我们的民意调查应用程序,以便可以将其导入为自己的顶级模块,而不是子模块mysite

要创建应用程序,请确保您与该目录位于同一目录中manage.py 并键入以下命令:

$ python manage.py startapp polls

那将创建一个目录polls,其布局如下:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

此目录结构将容纳轮询应用程序。

写下你的第一个视图

我们来写第一个视图。打开文件polls/views.py 并在其中放入以下Python代码:

from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

这是Django中最简单的视图。要调用视图,我们需要将其映射到URL - 为此我们需要一个URLconf。

要在polls目录中创建URLconf,请创建一个名为的文件urls.py您的app目录现在应该如下所示:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    urls.py
    views.py

在该polls/urls.py文件中包含以下代码:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
]
polls/urls.py

下一步是将根URLconf指向polls.urls模块。在 mysite/urls.py,添加导入django.conf.urls.includeinclude()urlpatterns列表中插入,所以你有:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', admin.site.urls),
]
mysite/urls.py

include()函数允许引用其他URLconf。请注意,该include()函数的正则表达式 没有$(字符串结尾匹配字符),而是一个尾部斜杠。每当Django遇到时 include(),它都会删除与该点匹配的URL的任何部分,并将剩余的字符串发送到包含的URLconf以进行进一步处理。

背后的想法include()是使即插即用的URL变得容易。由于民意调查位于他们自己的URLconf(polls/urls.py)中,因此可以将它们放在“/ polls /”下,或“/ fun_polls /”下,或“/ content / polls /”下,或任何其他路径根目录下,并且应用程序仍然可以工作。

什么时候用 include()

include()当您包含其他URL模式时,应始终使用。 admin.site.urls是唯一的例外。

与你看到的不符?

如果您看到的include(admin.site.urls)不是公正的 admin.site.urls,那么您可能正在使用与本教程版本不匹配的Django版本。您将要切换到较旧的教程或较新的Django版本。

您现在已将index视图连接到URLconf。让我们验证它是否正常工作,运行以下命令:

$ python manage.py runserver

在浏览器中转到http:// localhost:8000 / polls /,您应该看到文本“ Hello,world。. You’re at the polls index.”, 您在index视图中定义的 

url()函数传递了四个参数,两个必需:regexview,以及两个可选:kwargs,和name在这一点上,值得回顾一下这些论点的用途。

url()参数:正则表达式

术语“正则表达式”是一种常用的缩写形式,意思是“正则表达式”,它是用于匹配字符串中的模式的语法,或者在这种情况下是url模式。Django从第一个正则表达式开始,沿着列表向下,将请求的URL与每个正则表达式进行比较,直到找到匹配的正则表达式。

请注意,这些正则表达式不会搜索GET和POST参数或域名。例如,在请求中 https://www.example.com/myapp/,URLconf将查找myapp/在请求中https://www.example.com/myapp/?page=3,URLconf也会查找myapp/

如果您需要有关正则表达式的帮助,请参阅Wikipedia的条目re模块的文档另外,杰弗里·弗里德(Jeffrey Friedl)的奥莱利(O'Reilly)着作“掌握正则表达式”(Mastering Regular Expressions 但是,在实践中,您不需要成为正则表达式的专家,因为您实际上只需要知道如何捕获简单模式。事实上,复杂的正则表达式可能具有较差的查找性能,因此您可能不应该依赖于正则表达式的全部功能。

最后,一个性能说明:这些正则表达式是在第一次加载URLconf模块时编译的。它们超快(只要查找不是太复杂,如上所述)。

url()参数:视图

当Django找到正则表达式匹配时,Django调用指定的视图函数,将HttpRequest对象作为第一个参数,将正则表达式中的任何“捕获”值作为其他参数。如果正则表达式使用简单捕获,则值将作为位置参数传递; 如果它使用命名捕获,则值将作为关键字参数传递。我们稍后会给出一个例子。

url()参数:kwargs 

任意关键字参数可以在字典中传递到目标视图。我们不打算在教程中使用Django的这个功能。

url()参数:名称

命名您的URL可让您从Django的其他地方明确地引用它,尤其是在模板中。此强大功能允许您在仅触摸单个文件的同时对项目的URL模式进行全局更改。

 

编写你的第一个Django应用程序,第2部分

本教程从教程1停止的地方开始我们将设置数据库,创建您的第一个模型,并快速介绍Django自动生成的管理站点。

数据库设置

现在,打开mysite/settings.py这是一个普通的Python模块,其中模块级变量代表Django设置。

默认情况下,配置使用SQLite。如果您是数据库新手,或者您只是想尝试Django,这是最简单的选择。SQLite包含在Python中,因此您无需安装任何其他东西来支持您的数据库。但是,在启动第一个真正的项目时,您可能希望使用像PostgreSQL这样的更具伸缩性的数据库,以避免数据库切换问题。

如果要使用其他数据库,请安装相应的数据库绑定并更改项目中的以下键 以匹配数据库连接设置:DATABASES 'default'

  • ENGINE-要么 'django.db.backends.sqlite3', 'django.db.backends.postgresql', 'django.db.backends.mysql',或'django.db.backends.oracle'其他后端也可用
  • NAME - 数据库的名称。如果您使用的是SQLite,则数据库将是您计算机上的文件; 在这种情况下,NAME 应该是该文件的完整绝对路径,包括文件名。默认值,, 将文件存储在项目目录中。os.path.join(BASE_DIR, 'db.sqlite3')

如果你不使用SQLite作为数据库,额外的设置,例如 USERPASSWORDHOST必须加入。有关更多详细信息,请参阅参考文档DATABASES

对于SQLite以外的数据库

如果您使用的是除SQLite之外的数据库,请确保此时已创建数据库。在数据库的交互式提示中使用“ ” 执行此操作。CREATEDATABASE database_name;

还要确保提供的数据库用户mysite/settings.py 具有“create database”特权。这允许自动创建 测试数据库,这将在以后的教程中使用。

如果您使用的是SQLite,则无需事先创建任何内容 - 数据库文件将在需要时自动创建。

在编辑时mysite/settings.py,请设置TIME_ZONE为您的时区。

另外,请注意INSTALLED_APPS文件顶部设置。它包含在这个Django实例中激活的所有Django应用程序的名称。应用程序可以在多个项目中使用,您可以打包和分发它们以供项目中的其他人使用。

默认情况下,INSTALLED_APPS包含以下应用程序,所有这些应用程序都随Django一起提供:

默认情况下包含这些应用程序,以方便常见情况。

但是,其中一些应用程序至少使用了一个数据库表,因此我们需要先在数据库中创建表,然后才能使用它们。为此,请运行以下命令:

$ python manage.py migrate

migrate命令查看INSTALLED_APPS设置并根据mysite/settings.py文件中的数据库设置和应用程序附带的数据库迁移创建任何必要的数据库表(稍后我们将介绍这些表)。您将看到适用于每次迁移的消息。如果您有兴趣,请运行数据库的命令行客户端并键入\dt(PostgreSQL),(MySQL), (SQLite)或(Oracle)以显示Django创建的表。SHOW TABLES;.schemaSELECT TABLE_NAME FROMUSER_TABLES;

对于极简主义者

就像我们上面所说的那样,默认应用程序包含在常见情况中,但不是每个人都需要它们。如果您不需要其中任何一个或全部,请INSTALLED_APPS在运行前随意注释或删除相应的行 migrate该 migrate命令仅运行应用程序的迁移INSTALLED_APPS

创建模型

现在我们将定义您的模型 - 本质上是您的数据库布局,以及其他元数据。

哲学

模型是关于数据的单一,明确的真实来源。它包含您要存储的数据的基本字段和行为。Django遵循DRY原则目标是在一个地方定义您的数据模型,并自动从中获取数据。

这包括迁移 - 与Ruby On Rails不同,例如,迁移完全来自您的模型文件,并且基本上只是Django可以通过更新数据库模式以匹配您当前模型的历史记录。

在我们简单的民意调查应用程序中,我们将创建两个模型:QuestionChoiceQuestion有问题和出版日期。Choice有两个字段:选择的文本和投票记录。每个Choice都与一个Question

这些概念由简单的Python类表示。编辑 polls/models.py文件,使其如下所示:

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
polls/models.py

代码很简单。每个模型由一个子类表示django.db.models.Model每个模型都有许多类变量,每个变量代表模型中的数据库字段。

每个字段由Field 类的实例表示- 例如,CharField用于字符字段和 DateTimeField日期时间。这告诉Django每个字段包含哪种类型的数据。

每个Field实例的名称(例如 question_textpub_date)是字段名称,采用机器友好格式。您将在Python代码中使用此值,并且您的数据库将使用它作为列名。

您可以使用可选的第一个位置参数 Field来指定一个人类可读的名称。这在Django的几个内省部分中使用,并且它兼作文档。如果未提供此字段,Django将使用机器可读的名称。在这个例子中,我们只定义了一个人类可读的名称Question.pub_date对于此模型中的所有其他字段,字段的机器可读名称就足以作为其可读的名称。

有些Field类需要参数。 CharField例如,要求你给它一个 max_length这不仅在数据库模式中使用,而且在验证中使用,我们很快就会看到。

Field也可以有各种可选参数; 在这种情况下,我们将default值 设置votes为0。

最后,请注意使用的定义关系 ForeignKey这告诉Django每个Choice都与单个相关QuestionDjango支持所有常见的数据库关系:多对一,多对多和一对一。

激活模型

这一小部分模型代码为Django提供了大量信息。有了它,Django能够:

  • 为此应用程序创建数据库模式(语句)。CREATE TABLE
  • 创建用于访问QuestionChoice对象的Python数据库访问API 

但首先我们需要告诉我们的项目polls应用程序已安装。

哲学

Django应用程序是“可插拔的”:您可以在多个项目中使用应用程序,并且可以分发应用程序,因为它们不必绑定到给定的Django安装。

要在我们的项目中包含应用程序,我们需要在设置中添加对其配置类的引用INSTALLED_APPS该 PollsConfig班是在polls/apps.py文件中,所以它的虚线路径'polls.apps.PollsConfig'编辑mysite/settings.py文件并将该虚线路径添加到INSTALLED_APPS设置中。它看起来像这样:

INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
mysite/settings.py

现在Django知道要包含该polls应用程序。让我们运行另一个命令:

$ python manage.py makemigrations polls

您应该看到类似于以下内容的内容:

Migrations for 'polls':
  polls/migrations/0001_initial.py:
    - Create model Choice
    - Create model Question
    - Add field question to choice

通过运行makemigrations,您告诉Django您已对模型进行了一些更改(在这种情况下,您已经创建了新模型),并且您希望将更改存储为迁移

迁移是Django如何存储对模型(以及数据库模式)的更改 - 它们只是磁盘上的文件。如果您愿意,可以阅读新模型的迁移; 这是文件polls/migrations/0001_initial.py不要担心,每次Django制作时都不会读它们,但是如果你想手动调整Django如何改变它们,它们的设计是人为可编辑的。

有一个命令可以为您运行迁移并自动管理您的数据库模式 - 这是被调用的migrate,我们马上就会看到它 - 但首先,让我们看看迁移将运行的SQL。该 sqlmigrate命令获取迁移名称并返回其SQL:

$ python manage.py sqlmigrate polls 0001

您应该看到类似于以下内容的东西(为了便于阅读,我们重新格式化了它):

BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);
--
-- Create model Question
--
CREATE TABLE "polls_question" (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;

COMMIT;

请注意以下事项:

  • 确切的输出将根据您使用的数据库而有所不同。上面的示例是为PostgreSQL生成的。
  • 表名是通过组合应用程序的名称(自动生成polls)和模型的小写名字- question和 choice(您可以覆盖此行为。)
  • 主键(ID)会自动添加。(你也可以覆盖它。)
  • 按照惯例,Django附加"_id"到外键字段名称。(是的,你也可以覆盖它。)
  • 外键关系通过 约束显式化不要担心零件; 这只是告诉PostgreSQL在事务结束之前不强制执行外键。FOREIGN KEYDEFERRABLE
  • 它是根据您正在使用的数据库量身定制的,因此可以自动为您处理特定于数据库的字段类型,如auto_increment(MySQL),serial(PostgreSQL)或(SQLite)。引用字段名称也是如此 - 例如,使用双引号或单引号。integer primary key autoincrement
  • sqlmigrate命令实际上并不在您的数据库上运行迁移 - 它只是将其打印到屏幕上,以便您可以看到SQL Django认为需要什么。它对于检查Django将要执行的操作或者是否有需要SQL脚本进行更改的数据库管理员非常有用。

如果你有兴趣,你也可以跑 这将检查项目中的任何问题,而无需进行迁移或触摸数据库。python manage.py check

现在,migrate再次运行以在数据库中创建这些模型表:

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Rendering model states... DONE
  Applying polls.0001_initial... OK

migrate命令将执行所有尚未应用的迁移(Django跟踪使用数据库中的特殊表来应用哪些迁移django_migrations)并针对您的数据库运行它们 - 实际上,您将对模型所做的更改与模型中的模式同步数据库。

迁移功能非常强大,您可以在开发项目时随时更改模型,而无需删除数据库或表并创建新数据库 - 它专门用于实时升级数据库,而不会丢失数据。我们将在本教程的后续部分中更深入地介绍它们,但是现在,请记住进行模型更改的三步指南:

之所以有单独的命令来制作和应用迁移是因为您将提交迁移到您的版本控制系统并随应用程序一起发送; 它们不仅使您的开发更容易,而且还可以被其他开发人员和生产中使用。

阅读django-admin文档,了解该manage.py实用程序可以执行的操作的完整信息

使用API¶

现在,让我们进入交互式Python shell并使用Django为您提供的免费API。要调用Python shell,请使用以下命令:

$ python manage.py shell

我们使用它而不是简单地输入“python”,因为manage.py 设置了DJANGO_SETTINGS_MODULE环境变量,这为Django提供了mysite/settings.py文件的Python导入路径

绕过manage.py

如果你不想使用manage.py,没问题。只需设置 mysite.settings,启动一个普通的Python shell,并设置Django:

>>> import django
>>> django.setup()

如果这引发了AttributeError,你可能正在使用与本教程版本不匹配的Django版本。您将要切换到较旧的教程或较新的Django版本。

您必须python从同一目录运行manage.py,或确保该目录位于Python路径上,这样才 有效。import mysite

有关所有这些的更多信息,请参阅django-admin文档

进入shell后,浏览数据库API

>>> from polls.models import Question, Choice   # Import the model classes we just wrote.

# 系统中还没有任何问题。
>>> Question.objects.all()
<QuerySet []>

# 创建一个新问题。
# 默认设置文件中启用了对时区的支持,因此
# Django期望pub_date的日期时间为tzinfo。 使用timezone.now()
#而不是datetime.datetime.now(),它会做正确的事情。
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

#将对象保存到数据库中。 您必须显式调用save()。
>>> q.save()

# 现在它有一个ID。 请注意,这可能会说“1L”而不是“1”,具体取决于
# 您正在使用哪个数据库。 那不是什么大事; 它只是意味着你的
# 据库后端更喜欢将整数作为Python长整数返回
# objects.
>>> q.id
1

# 通过Python属性访问模型字段值。
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

# 通过更改属性更改值,然后调用 save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all()显示数据库中的所有问题。
>>> Question.objects.all()
<QuerySet [<Question: Question object>]>

等一下。完全是对这个对象的无益表现。让我们来解决这个问题通过编辑模型(在 文件),并加入 到两个方法和 <Question: Questionobject>Questionpolls/models.py__str__()QuestionChoice

from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible  # only if you need to support Python 2
class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text

@python_2_unicode_compatible  # only if you need to support Python 2
class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text
polls/models.py

__str__()向模型添加方法非常重要,不仅是为了您在处理交互式提示时的方便,还因为在Django自动生成的管理中使用了对象的表示。

请注意,这些是普通的Python方法。让我们添加一个自定义方法,仅用于演示:

import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
polls/models.py

请注意添加分别引用Python的标准模块和Django的时区相关实用程序如果您不熟悉Python中的时区处理,可以在时区支持文档中了解更多信息import datetimefrom django.utils import timezonedatetimedjango.utils.timezone

保存这些更改并通过再次运行启动新的Python交互式shell python manage.py shell

>>> from polls.models import Question, Choice

# 确保我们的__str __()添加有效。
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

#Django提供了一个完全由...驱动的丰富的数据库查找API
#关键字参数。
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>

# 得到今年发布的问题。
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# 请求不存在的ID,这将引发异常。
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# 通过主键查找是最常见的情况,因此Django提供了一个
# 主键精确查找的快捷方式。
# 以下是相同的 Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# 确保我们的自定义方法有效。
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

#给出问题几个选择。 create调用构造一个new
#Choice对象,执行INSERT语句,将选择添加到集合中
可用选项数并返回新的Choice对象。 Django创造了
#a设置保存ForeignKey关系的“另一面”
#(例如问题的选择)可以通过API访问。
>>> q = Question.objects.get(pk=1)

#显示相关对象集中的任何选项 - 目前为止都没有。
>>> q.choice_set.all()
<QuerySet []>

# 创建三个选择。
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# Choice对象具有对其相关Question对象的API访问权限。
>>> c.question
<Question: What's up?>

# 反之亦然:问题对象可以访问Choice对象。
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

#API会根据您的需要自动跟踪关系。
#使用双下划线分隔关系。
#这可以根据你的需要进行多级操作; 没有限制。
#查找pub_date在今年的任何问题的所有选择
#(重用我们上面创建的'current_year'变量)。
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# 我们删除其中一个选项。 使用delete()。
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

有关模型关系的更多信息,请参阅访问相关对象有关如何使用双下划线通过API执行字段查找的更多信息,请参阅字段查找有关数据库API的完整详细信息,请参阅我们的数据库API参考

介绍Django管理员

哲学

为您的员工或客户生成管理网站以添加,更改和删除内容是繁琐的工作,不需要太多的创造力。出于这个原因,Django完全自动化为模型创建管理界面。

Django是在新闻编辑室环境中编写的,“内容发布者”和“公共”网站之间有明显的分离。站点管理员使用该系统添加新闻报道,事件,体育比分等,并且该内容显示在公共站点上。Django解决了为站点管理员创建统一界面以编辑内容的问题。

管理员不打算由网站访问者使用。它适用于网站管理员。

创建管理员用户

首先,我们需要创建一个可以登录管理站点的用户。运行以下命令:

$ python manage.py createsuperuser

输入所需的用户名,然后按Enter键。

Username: admin

然后,系统将提示您输入所需的电子邮件地址:

Email address: admin@example.com

最后一步是输入密码。系统会要求您输入两次密码,第二次输入密码作为第一次确认。

Password: **********
Password (again): *********
Superuser created successfully.

启动开发服务器

Django管理站点默认激活。让我们启动开发服务器并进行探索。

如果服务器没有运行,请启动它:

$ python manage.py runserver

现在,打开Web浏览器并转到本地域的“/ admin /” - 例如 http://127.0.0.1:8000/admin/您应该看到管理员的登录屏幕:

django1.11入门

由于默认情况下打开翻译,因此登录屏幕可能会以您自己的语言显示,具体取决于您的浏览器设置以及Django是否有此语言的翻译。

进入管理站点

现在,尝试使用您在上一步中创建的超级用户帐户登录。你应该看到Django管理员索引页面:

django1.11入门

您应该看到几种类型的可编辑内容:组和用户。它们django.contrib.auth由Django 提供的身份验证框架提供。

在管理员中修改民意调查应用程序

但是我们的投票应用程序在哪里?它不会显示在管理员索引页面上。

只需做一件事:我们需要告诉管理员Question 对象有一个管理界面。为此,请打开该polls/admin.py 文件,然后将其编辑为如下所示:

from django.contrib import admin

from .models import Question

admin.site.register(Question)
polls/admin.py

探索免费的管理功能

现在我们已经注册了Question,Django知道它应该显示在管理员索引页面上:

django1.11入门

单击“问题”。现在,您将进入“更改列表”页面以查询问题。此页面显示数据库中的所有问题,您可以选择一个更改它。我们之前创建了“什么事?”这个问题:

django1.11入门

点击“怎么了?”问题进行编辑:

django1.11入门

这里要注意的事项:

  • 表单是从Question模型自动生成的
  • 不同的模型字段类型(DateTimeField, CharField)对应于相应的HTML输入窗口小部件。每种类型的字段都知道如何在Django管理员中显示自己。
  • 每个都DateTimeField获得免费的JavaScript快捷方式。日期获得“今日”快捷方式和日历弹出窗口,时间获得“现在”快捷方式和方便的弹出窗口,列出常用的输入时间。

页面底部为您提供了几个选项:

  • 保存 - 保存更改并返回此类对象的更改列表页面。
  • 保存并继续编辑 - 保存更改并重新加载此对象的管理页面。
  • 保存并添加另一个 - 保存更改并为此类对象加载新的空白表单。
  • 删除 - 显示删除确认页面。

如果“发布日期”的值与您在教程1中创建问题的时间不匹配,则可能意味着您忘记为该TIME_ZONE设置设置正确的值更改它,重新加载页面并检查是否显示正确的值。

单击“今天”和“立即”快捷方式更改“发布日期”。然后单击“保存并继续编辑”。然后单击右上角的“历史记录”。您将看到一个页面,其中列出了通过Django管理员对此对象所做的所有更改,以及进行更改的人员的时间戳和用户名:

django1.11入门

 

编写你的第一个Django应用程序,第3部分

本教程从教程2停止的地方开始我们将继续使用Web-poll应用程序,并将专注于创建公共界面 - “视图”。

概述

视图是Django应用程序中Web页面的“类型”,通常用于特定功能并具有特定模板。例如,在博客应用程序中,您可能具有以下视图:

  • 博客主页 - 显示最新的几个条目。
  • 条目“详细信息”页面 - 单个条目的永久链接页面。
  • 基于年份的存档页面 - 显示给定年份中包含条目的所有月份。
  • 基于月份的存档页面 - 显示给定月份中包含条目的所有日期。
  • 基于日期的存档页面 - 显示给定日期内的所有条目。
  • 评论操作 - 处理向给定条目发布评论。

在我们的民意调查申请中,我们将有以下四种观点:

  • 问题“索引”页面 - 显示最新的几个问题。
  • 问题“详细信息”页面 - 显示问题文本,没有结果,但有一个表单可以投票。
  • 问题“结果”页面 - 显示特定问题的结果。
  • 投票行动 - 处理特定问题中特定选择的投票。

在Django中,网页和其他内容由视图提供。每个视图都由一个简单的Python函数(或基于类的视图的方法)表示。Django将通过检查所请求的URL(确切地说,是域名后面的URL部分)来选择视图。

现在,在网络上你可能遇到过诸如“ME2 / Sites / dirmod.asp?sid =&type = gen&mod = Core + Pages&gid = A6CD4967199A42D9B65B1B”这样的美女。您会很高兴地知道Django允许我们提供更优雅的 URL模式

URL模式只是URL的一般形式 - 例如: /newsarchive/<year>/<month>/

为了从URL到视图,Django使用所谓的“URLconfs”。URLconf将URL模式(描述为正则表达式)映射到视图。

本教程提供了使用URLconf的基本说明,您可以参考以django.urls获取更多信息。

写更多视图

现在让我们再添加一些视图polls/views.py这些观点略有不同,因为他们提出了一个论点:

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)
polls/views.py

polls.urls通过添加以下url()调用这些新视图连接到模块中 

from django.conf.urls import url

from . import views

urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
polls/urls.py

在浏览器中查看“/ polls / 34 /”。它将运行该detail() 方法并显示您在URL中提供的任何ID。尝试“/ polls / 34 / results /”和“/ polls / 34 / vote /” - 这些将显示占位符结果和投票页面。

当有人从你的网站请求一个页面 - 比如“/ polls / 34 /”时,Django将加载mysite.urlsPython模块,因为它被ROOT_URLCONF设置指向 它找到名为的变量urlpatterns 并按顺序遍历正则表达式。找到匹配后 '^polls/',它会剥离匹配的文本("polls/")并将剩余的文本 - "34/"- 发送到'polls.urls'URLconf以进行进一步处理。它匹配r'^(?P<question_id>[0-9]+)/$',导致调用detail()视图,如下所示:

detail(request=<HttpRequest object>, question_id='34')

question_id='34'部分来自(?P<question_id>[0-9]+)在模式周围使用括号“捕获”该模式匹配的文本,并将其作为参数发送给视图函数; ?P<question_id>定义将用于标识匹配模式的名称; 并且[0-9]+是匹配数字序列(即数字)的正则表达式。

因为URL模式是正则表达式,所以对它们可以做什么没有限制。.html除非您愿意,否则无需添加URL cruft ,在这种情况下,您可以执行以下操作:

url(r'^polls/latest\.html$', views.index),

但是,不要这样做。这太傻了。

写出实际做某事的视图

每个视图负责执行以下两项操作之一:返回HttpResponse包含所请求页面内容的 对象,或者引发异常,例如Http404剩下的由你决定。

您的视图可以读取数据库中的记录。它可以使用模板系统,如Django的 - 或第三方Python模板系统 - 或不。它可以生成PDF文件,输出XML,动态创建ZIP文件,任何你想要的东西,使用你想要的任何Python库。

所有Django都想要的是HttpResponse或者例外。

因为它很方便,所以让我们使用Django自己的数据库API,我们在教程2中介绍了它这是一个新index() 视图,它根据发布日期显示系统中最新的5个轮询问题,以逗号分隔:

from django.http import HttpResponse

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged
polls/views.py

但是这里存在一个问题:页面的设计在视图中是硬编码的。如果要更改页面的外观,则必须编辑此Python代码。因此,让我们使用Django的模板系统,通过创建视图可以使用的模板将设计与Python分离。

首先,创建目录中调用templatespolls目录。Django会在那里寻找模板。

您的项目TEMPLATES设置描述了Django如何加载和呈现模板。默认设置文件配置DjangoTemplates 后端,其APP_DIRS选项设置为 True按照惯例DjangoTemplates,在每个中查找“templates”子目录INSTALLED_APPS

templates刚刚创建目录中,创建另一个名为的目录polls,并在其中创建一个名为的文件 index.html换句话说,您的模板应该是polls/templates/polls/index.html由于app_directories 模板加载器的工作方式如上所述,您可以简单地在Django中引用此模板polls/index.html

模板命名空间

现在我们可以直接放入我们的模板 polls/templates(而不是创建另一个polls子目录),但实际上这是一个坏主意。Django将选择它找到的第一个名称匹配的模板,如果你在不同的应用程序中有一个同名的模板,Django将无法区分它们。我们需要能够将Django指向正确的,并且确保这一点的最简单方法是通过命名它们。也就是说,将这些模板放在为应用程序本身命名的另一个目录中。

将以下代码放在该模板中:

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}
polls/templates/polls/index.html

现在让我们更新我们的index视图polls/views.py以使用模板:

from django.http import HttpResponse
from django.template import loader

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))
polls/views.py

该代码加载调用的模板 polls/index.html并将其传递给上下文。上下文是将模板变量名称映射到Python对象的字典。

通过将浏览器指向“/ polls /”来加载页面,您应该看到一个项目符号列表,其中包含教程2中的“What is up”问题该链接指向问题的详细信息页面。

快捷方式:render()

加载模板,填充上下文并返回HttpResponse带有渲染模板结果对象是一种非常常见的习惯用法 Django提供了一个捷径。这是完整的index()视图,重写:

from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)
polls/views.py

请注意,一旦我们在所有这些观点做到了这一点,我们不再需要进口 loaderHttpResponse(你想保留HttpResponse,如果你仍然有存根方法detail, resultsvote)。

render()函数将请求对象作为其第一个参数,将模板名称作为其第二个参数,将字典作为其可选的第三个参数。它返回使用HttpResponse 给定上下文呈现的给定模板对象。

引发404错误

现在,让我们解决问题详细信息视图 - 显示给定民意调查的问题文本的页面。这是观点:

from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})
polls/views.py

这里的新概念:Http404如果不存在具有请求ID的问题,则视图会引发异常。

稍后我们将讨论您可以在该polls/detail.html模板中添加的内容,但是如果您希望快速获得上述示例,则只需包含以下内容的文件:

{{ question }}
polls/templates/polls/detail.html

会让你现在开始。

快捷方式:get_object_or_404()

如果对象不存在,使用get() 和提升这是一个非常常见的习惯用法Http404Django提供了一个捷径。这是detail()视图,重写:

from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})
polls/views.py

get_object_or_404()函数将Django模型作为其第一个参数和任意数量的关键字参数,并将其传递给get()模型管理器函数。Http404如果对象不存在则引发

哲学

为什么我们使用辅助函数get_object_or_404() 而不是自动捕获ObjectDoesNotExist更高级别的 异常,或者使用模型API Http404而不是 ObjectDoesNotExist

因为这会将模型层耦合到视图层。Django最重要的设计目标之一是保持松耦合。django.shortcuts模块中引入了一些受控耦合

还有一个get_list_or_404()函数,它的工作方式与get_object_or_404()- 除了使用 filter()而不是 get()Http404如果列表为空,它会引发 

使用模板系统

返回detail()我们的民意调查应用程序视图。给定上下文变量question,这是polls/detail.html模板的外观:

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
polls/templates/polls/detail.html

模板系统使用点查找语法来访问变量属性。在示例中,首先Django对该对象进行字典查找如果失败了,它会尝试进行属性查找 - 在这种情况下可以正常工作。如果属性查找失败,它将尝试列表索引查找。{{ question.question_text }}question

方法调用在循环中发生: 被解释为Python代码 ,它返回一个可迭代的对象,适合在标记中使用{% for%}question.choice_set.allquestion.choice_set.all()Choice{% for %}

有关模板的更多信息,请参阅模板指南

删除模板中的硬编码URL¶

请记住,当我们在polls/index.html 模板中写入问题的链接时,链接部分硬编码如下:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

这种硬编码,紧密耦合方法的问题在于,在具有大量模板的项目上更改URL变得具有挑战性。但是,由于您url()polls.urls模块中的函数中定义了name参数,因此可以使用模板标记来消除对URL配置中定义的特定URL路径的依赖{% url %}

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

这种方式的工作方式是查找polls.urls模块中指定的URL定义 您可以在下面确切地看到“详细信息”的URL名称的位置:

...
# the 'name' value as called by the {% url %} template tag
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

如果您想将民意调查详细信息视图的URL更改为其他内容,可能polls/specifics/12/会更改为在模板(或模板)中执行此操作,您可以将其更改为polls/urls.py

...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

命名空间URL名称

教程项目只有一个应用程序polls在真正的Django项目中,可能有五个,十个,二十个应用程序或更多。Django如何区分它们之间的URL名称?例如,polls应用程序有一个detail 视图,因此同一项目中的应用程序可能适用于博客。如何使Django知道在使用模板标签时为url创建哪个应用视图 {% url %}

答案是为URLconf添加名称空间。polls/urls.py 文件中,继续并添加一个app_name以设置应用程序命名空间:

from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
polls/urls.py

现在更改您的polls/index.html模板:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
polls/templates/polls/index.html

指向命名空间详细信息视图:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
polls/templates/polls/index.html

 

编写你的第一个Django应用程序,第4部分

本教程从教程3停止的地方开始我们将继续使用Web-poll应用程序,并将专注于简单的表单处理和减少代码。

写一个简单的表格

让我们从上一个教程更新我们的民意调查详细信息模板(“polls / detail.html”),以便模板包含一个HTML <form>元素:

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>
polls/templates/polls/detail.html

快速简要说明:

  • 上面的模板显示每个问题选择的单选按钮。的 value每个单选按钮的是相关联的问题的选择的ID。的 name每个单选按钮的是"choice"这意味着,当某人选择其中一个单选按钮并提交表单时,它将发送POST数据choice=#,其中#是所选选项的ID。这是HTML表单的基本概念。
  • 我们将表单设置action,然后设置使用(而不是 )非常重要,因为提交此表单的行为将改变数据服务器端。每当您创建一个改变数据服务器端的表单时,请使用这个提示不是Django特有的; 这只是一个很好的Web开发实践。{% url 'polls:vote' question.id%}method="post"method="post"method="get"method="post"
  • forloop.counter表示for标记经过循环的次数
  • 由于我们正在创建一个POST表单(可以具有修改数据的效果),因此我们需要担心跨站点请求伪造。值得庆幸的是,您不必太担心,因为Django带有一个非常易于使用的系统来防范它。简而言之,所有针对内部URL的POST表单都应使用 模板标记。{% csrf_token %}

现在,让我们创建一个Django视图来处理提交的数据并对其进行处理。请记住,在教程3中,我们为包含以下行的民意调查应用程序创建了一个URLconf:

url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
polls/urls.py

我们还创建了该vote()函数的虚拟实现让我们创建一个真实版本。将以下内容添加到polls/views.py

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse

from .models import Choice, Question
# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
polls/views.py

此代码包含我们在本教程中尚未涉及的一些内容:

  • request.POST是一个类似字典的对象,允许您按键名访问提交的数据。在这种情况下, request.POST['choice']返回所选选项的ID,作为字符串。request.POST值始终是字符串。

    请注意,Django也提供request.GET了以相同方式访问GET数据 - 但我们request.POST在代码中明确使用,以确保数据仅通过POST调用进行更改。

  • request.POST['choice']KeyError如果 choicePOST数据中没有提供,则会引发上面的代码检查 KeyError并重新显示问题表单,如果choice没有给出错误消息

  • 增加选择计数后,代码返回 HttpResponseRedirect而不是正常 HttpResponse。 HttpResponseRedirect采用一个参数:用户将被重定向到的URL(在这种情况下,请参阅以下关于我们如何构造URL的点)。

    正如上面的Python注释指出的那样,HttpResponseRedirect在成功处理POST数据之后应该总是返回一个 这个提示不是Django特有的; 这只是一个很好的Web开发实践。

  • 我们在这个例子中使用构造reverse()函数中的 HttpResponseRedirect函数。此功能有助于避免在视图功能中对URL进行硬编码。它给出了我们想要将控制权传递给的视图的名称以及指向该视图的URL模式的可变部分。在这种情况下,使用我们在教程3中设置的URLconf ,此reverse()调用将返回一个类似的字符串

    '/polls/3/results/'
    • 其中的3是值question.id然后,此重定向的URL将调用'results'视图以显示最终页面。

    教程3中所述request是一个 HttpRequest对象。有关HttpRequest对象的更多信息 ,请参阅请求和响应文档

    在某人投票问题后,该vote()视图会重定向到问题的结果页面。让我们写下这个观点:

    from django.shortcuts import get_object_or_404, render
    
    
    def results(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        return render(request, 'polls/results.html', {'question': question})
    polls/views.py

这几乎detail()教程3中视图完全相同唯一的区别是模板名称。我们稍后会修复此冗余。

现在,创建一个polls/results.html模板:

<h1>{{ question.question_text }}</h1>

<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
polls/templates/polls/results.html

现在,转到/polls/1/您的浏览器并在问题中投票。您应该会看到每次投票时都会更新的结果页面。如果您在未选择的情况下提交表单,则应该看到错误消息。

注意

我们vote()视图的代码确实存在一个小问题。它首先selected_choice从数据库中获取对象,然后计算新值votes,然后将其保存回数据库。如果您网站的两个用户尝试在同一时间投票,则可能会出错:将检索相同的值,例如42 votes然后,对于两个用户,计算并保存新值43,但是44将是期望值。

这被称为竞争条件如果您有兴趣,可以阅读 使用F()避免竞争条件,以了解如何解决此问题。

使用通用视图:更少的代码更好

detail()(从教程3)和results() 意见是非常简单的-并且如上面提到的,冗余的。index() 显示民意调查列表视图类似。

这些视图代表了基本Web开发的常见情况:根据URL中传递的参数从数据库获取数据,加载模板并返回呈现的模板。因为这是如此常见,Django提供了一种称为“通用视图”系统的快捷方式。

通用视图将常见模式抽象到您甚至不需要编写Python代码来编写应用程序的程度。

让我们将我们的民意调查应用程序转换为使用通用视图系统,这样我们就可以删除一堆自己的代码。我们只需要采取一些步骤进行转换。我们会:

  1. 转换URLconf。
  2. 删除一些旧的,不需要的视图。
  3. 基于Django的通用视图引入新视图。

继续阅读以了解详情。

为什么代码洗牌?

通常,在编写Django应用程序时,您将评估通用视图是否适合您的问题,并且您将从一开始就使用它们,而不是在中途重构代码。但本教程有意集中于直到现在“以艰难的方式”编写视图,专注于核心概念。

在开始使用计算器之前,您应该先了解基本数学。

修改URL配置

首先,打开polls/urls.pyURLconf并将其更改为:

from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
polls/urls.py

请注意,第二个和第三个模式的正则表达式中匹配模式的名称已从更改<question_id><pk>

修改意见

接下来,我们将删除我们的老indexdetailresults 视图,并使用Django的通用视图代替。为此,请打开 polls/views.py文件并进行更改,如下所示:

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'


class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'


def vote(request, question_id):
    ... # same as above, no changes needed.
polls/views.py

我们在这里使用两个通用视图: ListView和 DetailView这两个视图分别抽象出“显示对象列表”和“显示特定类型对象的详细页面”的概念。

  • 每个通用视图都需要知道它将采用什么模型。这是使用model属性提供的
  • DetailView通用视图预计从URL中捕获的主键值被调用 "pk",所以我们已经改变question_id,以pk用于通用视图。

默认情况下,DetailView通用视图使用名为的模板在我们的例子中,它将使用模板该 属性用于告诉Django使用特定的模板名称而不是自动生成的默认模板名称。我们还列表视图指定了- 这确保了结果视图和详细视图在渲染时具有不同的外观,即使它们都是 幕后的。<appname>/<model name>_detail.html"polls/question_detail.html"template_nametemplate_nameresultsDetailView

同样,ListView通用视图使用一个名为的默认模板我们用来告诉 使用我们现有的 模板。<app name>/<modelname>_list.htmltemplate_nameListView"polls/index.html"

在本教程的前几部分中,为模板提供了包含questionlatest_question_list 上下文变量的上下文。对于DetailViewquestion自动提供的变量-因为我们使用的Django模型(Question),Django的是能够确定一个适当的名称为上下文变量。但是,对于ListView,自动生成的上下文变量是 question_list要覆盖它,我们提供context_object_name 属性,指定我们要使用属性latest_question_list作为替代方法,您可以更改模板以匹配新的默认上下文变量 - 但只是告诉Django使用您想要的变量要容易得多。

运行服务器,并使用基于通用视图的新轮询应用程序。

有关通用视图的完整详细信息,请参阅通用视图文档

 

编写你的第一个Django应用程序,第5部分

本教程从教程4停止的地方开始我们已经构建了一个Web轮询应用程序,现在我们将为它创建一些自动化测试。

介绍自动化测试

什么是自动化测试?

测试是检查代码操作的简单例程。

测试在不同级别进行。某些测试可能适用于一个微小的细节(特定模型方法是否按预期返回值?)而其他测试则检查软件的整体操作(网站上的一系列用户输入是否会产生所需的结果?)。这与您在教程2中之前所做的测试类型没有什么不同,使用它 shell来检查方法的行为,或者运行应用程序并输入数据来检查它的行为方式。

自动化测试的不同之处在于测试工作是由系统完成的。您只需创建一组测试,然后在对应用程序进行更改时,可以检查代码是否仍按预期工作,而无需执行耗时的手动测试。

为什么需要创建测试

那么为什么要创建测试,为什么现在呢?

您可能觉得自己已经足够了解Python / Django,并且还有另外一些需要学习和做的事情可能看起来势不可挡,也许是不必要的。毕竟,我们的民意调查申请现在非常愉快; 经历创建自动化测试的麻烦不会让它更好地工作。如果创建民意调查应用程序是您将要做的最后一点Django编程,那么,您不需要知道如何创建自动化测试。但是,如果情况并非如此,那么现在是学习的绝佳时机。

测试会节省你的时间

在某一点上,“检查它似乎有用”将是一个令人满意的测试。在更复杂的应用程序中,组件之间可能会有许多复杂的交互。

任何这些组件的更改都可能会对应用程序的行为产生意外后果。检查它是否“似乎工作”可能意味着通过代码的功能运行20种不同的测试数据变体,以确保您没有破坏某些东西 - 不能充分利用您的时间。

当自动化测试可以在几秒钟内为您完成此操作时尤其如此。如果出现问题,测试还将有助于识别导致意外行为的代码。

有时,如果您知道自己的代码工作正常,那么将自己从富有成效的创造性编程工作中剔除,以面对编写测试的无趣和令人兴奋的业务,这似乎是件苦差事。

但是,编写测试的任务比花费数小时手动测试应用程序或尝试确定新引入的问题的原因要多得多。

测试不只是识别问题,而是阻止它们

将测试仅仅视为发展的消极方面是错误的。

如果没有测试,应用程序的目的或预期行为可能会相当不透明。即使它是你自己的代码,你有时会发现自己在试图找出它究竟在做什么。

测试改变了; 他们从内部点亮你的代码,当出现问题时,他们将光线集中在出错的部分 - 即使你甚至没有意识到它出了问题

测试使您的代码更具吸引力

您可能已经创建了一个出色的软件,但您会发现很多其他开发人员只会拒绝查看它,因为它缺少测试; 没有测试,他们就不会相信它。Django最初的开发人员之一Jacob Kaplan-Moss说:“没有测试的代码被设计破坏了。”

其他开发人员希望在认真对待之前在软件中看到测试是您开始编写测试的另一个原因。

测试帮助团队一起工作

以前的观点是从维护应用程序的单个开发人员的角度编写的。复杂的应用程序将由团队维护。测试保证同事不会无意中破坏您的代码(并且您不会在不知情的情况下破坏他们的代码)。如果你想以Django程序员谋生,你必须善于编写测试!

基本测试策略

有很多方法可以用来编写测试。

一些程序员遵循一门名为“ 测试驱动开发 ” 的学科他们实际上在编写代码之前编写测试。这可能看似违反直觉,但实际上它与大多数人经常会做的类似:他们描述一个问题,然后创建一些代码来解决它。测试驱动的开发只是在Python测试用例中形式化了问题。

更常见的情况是,测试的新手将创建一些代码,然后决定它应该进行一些测试。也许最早写一些测试会好一些,但是从来没有太晚开始。

有时候很难弄清楚从哪里开始编写测试。如果您已经编写了几千行Python,那么选择要测试的东西可能并不容易。在这种情况下,无论是在添加新功能还是修复错误时,下次进行更改时编写第一个测试都很有成效。

所以让我们马上做。

写第一次测试

我们发现了一个错误

幸运的是,在一个小错误polls的应用为我们立即进行修复:该Question.was_published_recently()方法返回True,如果Question是最后一天(这是正确的)内发布,而且如果Questionpub_date领域是未来(这当然不是) 。

要检查错误是否确实存在,请使用Admin创建一个日期位于将来的问题,并使用以下方法检查方法shell

>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # create a Question instance with pub_date 30 days in the future
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # was it published recently?
>>> future_question.was_published_recently()
True

由于未来的事情不是“最近的”,这显然是错误的。

创建测试以揭示错误

我们在shell测试问题时所做的正是我们在自动化测试中可以做的,所以让我们把它变成一个自动测试。

应用程序测试的常规位置在应用程序的 tests.py文件中; 测试系统将自动在名称以...开头的任何文件中查找测试test

将以下内容放在应用程序tests.py文件中polls

import datetime

from django.utils import timezone
from django.test import TestCase

from .models import Question


class QuestionModelTests(TestCase):

    def test_was_published_recently_with_future_question(self):
        """
        was_published_recently() returns False for questions whose pub_date
        is in the future.
        """
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)
polls/tests.py

我们在这里所做的是创建一个django.test.TestCase子类,其中Question包含一个pub_date在将来创建一个实例的方法然后我们检查输出was_published_recently()- 哪个 应该是假的。

运行测试

在终端中,我们可以运行我们的测试:

$ python manage.py test polls

你会看到类似的东西:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

发生了什么事:

  • python manage.py test pollspolls应用程序中寻找测试
  • 它找到了django.test.TestCase该类的子
  • 它创建了一个特殊的数据库用于测试
  • 它寻找测试方法 - 名称以其开头的方法 test
  • test_was_published_recently_with_future_question其中创建了一个Question 实例,其pub_date字段在将来30天
  • ...并且使用该assertIs()方法,它发现它的 was_published_recently()返回True,尽管我们希望它返回 False

测试通知我们哪个测试失败,甚至是发生故障的线路。

修复错误

我们已经知道问题是什么:如果它是将来Question.was_published_recently()应该返回修改方法 ,以便只有在日期也是过去时它才会返回Falsepub_datemodels.pyTrue

def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now
polls/models.py

并再次运行测试:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

在识别出错误之后,我们编写了一个公开它的测试并更正了代码中的错误,以便我们的测试通过。

我们的应用程序将来可能会出现许多其他问题,但我们可以肯定,我们不会无意中重新引入此错误,因为只需运行测试就会立即向我们发出警告。我们可以认为应用程序的这一小部分永远安全地固定下来。

更全面的测试

当我们在这里时,我们可以进一步确定was_published_recently() 方法; 事实上,如果修复我们引入另一个错误的一个错误,那将是非常尴尬的。

在同一个类中再添加两个测试方法,以更全面地测试该方法的行为:

def test_was_published_recently_with_old_question(self):
    """
    was_published_recently() returns False for questions whose pub_date
    is older than 1 day.
    """
    time = timezone.now() - datetime.timedelta(days=1, seconds=1)
    old_question = Question(pub_date=time)
    self.assertIs(old_question.was_published_recently(), False)

def test_was_published_recently_with_recent_question(self):
    """
    was_published_recently() returns True for questions whose pub_date
    is within the last day.
    """
    time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
    recent_question = Question(pub_date=time)
    self.assertIs(recent_question.was_published_recently(), True)
polls/tests.py

现在,我们有三项测试证实可以Question.was_published_recently() 为过去,近期和未来的问题返回合理的价值。

同样,它polls是一个简单的应用程序,但无论它在将来如何复杂,以及它与之交互的其他代码,我们现在都能保证我们编写的测试方法将以预期的方式运行。

测试视图

民意调查申请是相当无差别的:它将发布任何问题,包括其pub_date领域在未来的问题。我们应该改善这一点。pub_date在将来设置a 应该意味着该问题在那个时刻发布,但在此之前是不可见的。

对视图的测试

当我们修复上面的错误时,我们首先编写测试,然后编写代码来修复它。事实上,这是测试驱动开发的一个简单示例,但我们的工作顺序并不重要。

在我们的第一次测试中,我们密切关注代码的内部行为。对于此测试,我们希望检查用户通过Web浏览器体验的行为。

在我们尝试解决任何问题之前,让我们来看看我们可以使用的工具。

Django测试客户端

Django提供了一个测试Client来模拟用户在视图级别与代码交互。我们可以在它中使用它tests.py 甚至在它中使用它shell

我们将重新开始shell,我们需要做一些不必要的事情tests.py首先是在以下位置设置测试环境shell

>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

setup_test_environment()安装模板渲染器,这将允许我们检查响应上的一些其他属性 response.context,否则将无法使用。请注意,此方法不会设置测试数据库,因此将针对现有数据库运行以下内容,并且输出可能会略有不同,具体取决于您已创建的问题。如果你的TIME_ZONE输入settings.py不正确,你可能会得到意想不到的结果 如果您不记得先提前设置,请在继续之前进行检查。

接下来我们需要导入测试客户端类(稍后tests.py我们将使用django.test.TestCase该类,它带有自己的客户端,因此不需要):

>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()

准备好后,我们可以要求客户为我们做一些工作:

>>> # get a response from '/'
>>> response = client.get('/')
Not Found: /
>>> # we should expect a 404 from that address; if you instead see an
>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably
>>> # omitted the setup_test_environment() call described earlier.
>>> response.status_code
404
>>> # on the other hand we should expect to find something at '/polls/'
>>> # we'll use 'reverse()' rather than a hardcoded URL
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n    <ul>\n    \n        <li><a href="/polls/1/">What&#39;s up?</a></li>\n    \n    </ul>\n\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>

改善我们的观点

民意调查清单显示尚未公布的民意调查(即pub_date未来的民意调查 )。我们来解决这个问题。

教程4中,我们介绍了一个基于类的视图,基于ListView

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]
polls/views.py

我们需要修改get_queryset()方法并对其进行更改,以便通过比较来检查日期timezone.now()首先我们需要添加一个导入:

from django.utils import timezone
polls/views.py

然后我们必须get_queryset像这样修改方法:

def get_queryset(self):
    """
    Return the last five published questions (not including those set to be
    published in the future).
    """
    return Question.objects.filter(
        pub_date__lte=timezone.now()
    ).order_by('-pub_date')[:5]
polls/views.py

Question.objects.filter(pub_date__lte=timezone.now())返回一个查询集,其中包含的Questionpub_date小于或等于 - 即早于或等于 - timezone.now

测试我们的新视图

现在,您可以通过启动runserver,在浏览器中加载站点,Questions在过去和将来创建日期以及检查是否仅列出已发布的日期来满足您自己的行为您不希望每次进行任何可能影响此更改的更改时都这样做- 所以我们也要根据上面的shell会话创建一个测试 

将以下内容添加到polls/tests.py

from django.urls import reverse
polls/tests.py

我们将创建一个快捷函数来创建问题以及一个新的测试类:

def create_question(question_text, days):
    """
    Create a question with the given `question_text` and published the
    given number of `days` offset to now (negative for questions published
    in the past, positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)


class QuestionIndexViewTests(TestCase):
    def test_no_questions(self):
        """
        If no questions exist, an appropriate message is displayed.
        """
        response = self.client.get(reverse('polls:index'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_past_question(self):
        """
        Questions with a pub_date in the past are displayed on the
        index page.
        """
        create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question.>']
        )

    def test_future_question(self):
        """
        Questions with a pub_date in the future aren't displayed on
        the index page.
        """
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_future_question_and_past_question(self):
        """
        Even if both past and future questions exist, only past questions
        are displayed.
        """
        create_question(question_text="Past question.", days=-30)
        create_question(question_text="Future question.", days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question.>']
        )

    def test_two_past_questions(self):
        """
        The questions index page may display multiple questions.
        """
        create_question(question_text="Past question 1.", days=-30)
        create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            ['<Question: Past question 2.>', '<Question: Past question 1.>']
        )
polls/tests.py

让我们更仔细地看看其中的一些。

首先是一个问题快捷方式功能,create_question在创建问题的过程中重复一些。

test_no_questions不会产生任何问题,但会检查消息:“没有可用的民意调查。”并验证是否latest_question_list为空。请注意,django.test.TestCase该类提供了一些额外的断言方法。在这些例子中,我们使用 assertContains()和 assertQuerysetEqual()

test_past_question,我们创建一个问题并验证它是否出现在列表中。

test_future_question,我们pub_date将来会创建一个问题为每个测试方法重置数据库,因此第一个问题不再存在,因此索引不应该有任何问题。

等等。实际上,我们使用测试来讲述网站上管理员输入和用户体验的故事,并检查每个州的状态以及系统状态的每次新变化,都会发布预期结果。

测试DetailView

我们的工作做得很好; 但是,即使未来的问题没有出现在索引中,如果用户知道或猜到正确的URL,他们仍然可以联系到他们。所以我们需要添加一个类似的约束DetailView

class DetailView(generic.DetailView):
    ...
    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Question.objects.filter(pub_date__lte=timezone.now())
polls/views.py

当然,我们将增加一些测试,以检查一个Question,其 pub_date在过去可以显示,而一个具有pub_date 在未来是不是:

class QuestionDetailViewTests(TestCase):
    def test_future_question(self):
        """
        The detail view of a question with a pub_date in the future
        returns a 404 not found.
        """
        future_question = create_question(question_text='Future question.', days=5)
        url = reverse('polls:detail', args=(future_question.id,))
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_past_question(self):
        """
        The detail view of a question with a pub_date in the past
        displays the question's text.
        """
        past_question = create_question(question_text='Past Question.', days=-5)
        url = reverse('polls:detail', args=(past_question.id,))
        response = self.client.get(url)
        self.assertContains(response, past_question.question_text)
polls/tests.py

更多测试的想法

我们应该为该视图添加一个类似的get_queryset方法ResultsView并创建一个新的测试类。它与我们刚创造的非常相似; 事实上会有很多重复。

我们还可以通过其他方式改进我们的应用程序,并在此过程中添加测试。例如,Questions可以在没有的网站上发布它是愚蠢的Choices因此,我们的观点可以检查这一点,并排除这种情况 Questions我们的测试会创建一个Question没有Choices,然后测试它没有发布,以及创建一个类似Question  Choices,并测试它是否已发布。

也许应该允许登录的管理员用户看到未发布的Questions,但不能看到 普通的访问者。再说一次:无论需要添加到软件中来实现这一点,都应该伴随着测试,无论是先编写测试还是让代​​码通过测试,或者首先在代码中计算出逻辑,然后再编写测试证明给我看。

在某个时刻,你一定会看看你的测试,并想知道你的代码是否遭受了测试膨胀,这导致我们:

测试时,越多越好

我们的测试似乎越来越失控。按照这个速度,我们的测试中的代码很快会比我们的应用程序中的代码更多,并且与其他代码的优雅简洁相比,重复是不美观的。

没关系让他们成长。在大多数情况下,您可以编写一次测试然后忘掉它。在您继续开发程序时,它将继续执行其有用的功能。

有时需要更新测试。假设我们修正了我们的观点,以便只QuestionsChoices发布。在这种情况下,我们现有的许多测试都会失败 - 告诉我们究竟需要修改哪些测试以使它们更新,所以在这种程度上测试有助于照顾自己。

在最坏的情况下,当您继续开发时,您可能会发现您有一些现在多余的测试。即使这不是问题; 在测试中的冗余是一个很好的事情。

只要您的测试得到合理安排,它们就不会变得难以管理。良好的经验法则包括:

  • TestClass每个模型或视图单独一个
  • 针对要测试的每组条件的单独测试方法
  • 描述其功能的测试方法名称

进一步测试

本教程仅介绍一些测试基础知识。你可以做很多事情,并且可以使用一些非常有用的工具来实现一些非常聪明的事情。

例如,虽然我们的测试涵盖了模型的一些内部逻辑以及我们的视图发布信息的方式,但您可以使用“浏览器内”框架(如Selenium)来测试HTML在浏览器中实际呈现的方式。这些工具不仅可以检查Django代码的行为,还可以检查JavaScript的行为。很有可能看到测试启动浏览器,并开始与您的网站进行交互,就像一个人在驾驶它一样!Django包括LiveServerTestCase 促进与Selenium等工具的集成。

如果您有一个复杂的应用程序,您可能希望在每次提交时自动运行测试以实现持续集成,以便质量控制本身 - 至少部分 - 自动化。

发现应用程序未经测试的部分的一个好方法是检查代码覆盖率。这也有助于识别脆弱甚至死亡的代码。如果您无法测试一段代码,通常意味着代码应该被重构或删除。覆盖范围将有助于识别死代码。有关详细信息,请参阅 与coverage.py集成

Django中的测试具有关于测试的全面信息。

有关测试的完整详细信息,请参阅Django中的测试

 

 

编写你的第一个Django应用程序,第6部分

本教程从教程5停止的地方开始我们已经构建了一个经过测试的Web轮询应用程序,现在我们将添加样式表和图像。

除了服务器生成的HTML之外,Web应用程序通常还需要提供呈现完整网页所需的其他文件(如图像,JavaScript或CSS)。在Django中,我们将这些文件称为“静态文件”。

对于小型项目,这不是什么大问题,因为您可以将静态文件保存在Web服务器可以找到的位置。但是,在更大的项目中 - 特别是那些由多个应用程序组成的项目 - 处理每个应用程序提供的多组静态文件开始变得棘手。

django.contrib.staticfiles就是:它从您的每个应用程序(以及您指定的任何其他位置)收集静态文件到一个可以在生产中轻松提供的位置。

自定义应用程序的外观

首先,创建目录中调用staticpolls目录。Django会在那里查找静态文件,类似于Django在其中找到模板的方式polls/templates/

Django的STATICFILES_FINDERS设置包含一个知道如何从各种来源发现静态文件的查找器列表。其中一个默认值是AppDirectoriesFinder查找每个中的“静态”子目录 INSTALLED_APPS,就像polls我们刚刚创建的那个管理站点对其静态文件使用相同的目录结构。

static刚刚创建目录中,创建另一个名为的目录,polls并在其中创建一个名为的文件style.css换句话说,你的样式表应该是polls/static/polls/style.css由于AppDirectoriesFinder静态文件查找器的工作原理,您可以简单地在Django中引用此静态文件polls/style.css,类似于引用模板路径的方式。

静态文件命名空间

就像模板一样,我们可能能够直接放入我们的静态文件polls/static(而不是创建另一个polls 子目录),但这实际上是个坏主意。Django将选择其名称匹配的第一个静态文件,如果在不同的应用程序中有一个具有相同名称的静态文件,Django将无法区分它们。我们需要能够将Django指向正确的,并且确保这一点的最简单方法是通过命名它们。也就是说,将这些静态文件放在为应用程序本身命名的另一个目录中。

将以下代码放在该stylesheet(polls/static/polls/style.css)中:

li a {
    color: green;
}
polls/static/polls/style.css

接下来,在顶部添加以下内容polls/templates/polls/index.html

{% load static %}

<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
polls/templates/polls/index.html

模板标签生成静态文件的绝对路径。{% static %}

这就是开发所需要做的一切。重新加载 http://localhost:8000/polls/,您应该看到问题链接是绿色(Django样式!),这意味着您的样式表已正确加载。

添加背景图像

接下来,我们将为图像创建一个子目录。imagespolls/static/polls/目录中创建一个子目录在此目录中,放置一个名为的图像background.gif换句话说,把你的形象放进去 polls/static/polls/images/background.gif

然后,添加到stylesheet(polls/static/polls/style.css):

body {
    background: white url("images/background.gif") no-repeat right bottom;
}
polls/static/polls/style.css

重新加载http://localhost:8000/polls/,您应该看到屏幕右下方加载了背景。

警告

当然,模板标签不能用于像样式表那样不是由Django生成的静态文件。您应始终使用相对路径将静态文件链接到彼此之间,因为您可以更改(由 模板标记用于生成其URL),而无需修改静态文件中的一堆路径。{% static %}STATIC_URLstatic

这些是基础知识有关框架中包含的设置和其他位的更多详细信息,请参阅 静态文件howto和 staticfiles引用部署静态文件讨论了如何在真实服务器上使用静态文件。

 

编写你的第一个Django应用程序,第7部分

本教程从教程6停止的地方开始我们将继续使用Web-poll应用程序,并将专注于自定义Django自动生成的管理站点,这是我们在教程2中首次探讨的

自定义管理表单

通过注册Question模型admin.site.register(Question),Django能够构造默认的表单表示。通常,您需要自定义管理表单的外观和工作方式。你可以通过在注册对象时告诉Django你想要的选项来做到这一点。

让我们通过重新排序编辑表单上的字段来了解它是如何工作的。admin.site.register(Question)线替换为

from django.contrib import admin

from .models import Question


class QuestionAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question_text']

admin.site.register(Question, QuestionAdmin)
polls/admin.py

您将遵循此模式 - 创建模型管理类,然后将其作为第二个参数传递给admin.site.register()- 任何时候您需要更改模型的管理选项。

上述特定更改使“发布日期”出现在“问题”字段之前:

django1.11入门

仅使用两个字段并不令人印象深刻,但对于具有数十个字段的管理表单,选择直观的顺序是一个重要的可用性细节。

说到有几十个字段的表单,您可能希望将表单拆分为字段集:

from django.contrib import admin

from .models import Question


class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date']}),
    ]

admin.site.register(Question, QuestionAdmin)
polls/admin.py

每个元组的第一个元素 fieldsets是fieldset的标题。这就是我们现在的形式:

django1.11入门

添加相关对象

好的,我们有问题管理页面,但是Question有多个 Choices,管理页面不显示选项。

然而。

有两种方法可以解决这个问题。第一个是Choice 像我们一样向管理员注册Question这很简单:

from django.contrib import admin

from .models import Choice, Question
# ...
admin.site.register(Choice)
polls/admin.py

现在,“选择”是Django管理员中的一个可用选项。“添加选项”表单如下所示:

django1.11入门

在该表单中,“问题”字段是包含数据库中每个问题的选择框。Django知道ForeignKey应该在管理员中将a表示为一个<select>框。在我们的例子中,此时只存在一个问题。

另请注意“问题”旁边的“添加另一个”链接。与ForeignKey另一个关系的每个对象 都可以免费获得。当您单击“添加另一个”时,您将看到一个带有“添加问题”表单的弹出窗口。如果您在该窗口中添加一个问题并单击“保存”,Django会将问题保存到数据库,并在您正在查看的“添加选项”表单中将其作为所选选项动态添加。

但是,实际上,这是Choice向系统添加对象的低效方法如果你可以在创建Question对象时直接添加一堆Choices,那会更好 让我们实现这一目标。

删除模型register()调用Choice然后,编辑Question 注册码阅读:

from django.contrib import admin

from .models import Choice, Question


class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3


class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)
polls/admin.py

这告诉Django:“ ChoiceQuestion管理页面上编辑对象默认情况下,为3个选项提供足够的字段。“

加载“添加问题”页面以查看其外观:

django1.11入门

它的工作方式如下:相关选项有三个插槽 - 由指定extra- 并且每次返回已更改对象的“更改”页面时,您将获得另外三个额外插槽。

在三个当前位置的末尾,您将找到“添加另一个选择”链接。如果单击它,将添加一个新插槽。如果要删除添加的插槽,可以单击添加插槽右上角的X. 请注意,您无法删除原来的三个插槽。此图显示了添加的插槽:

django1.11入门

但是有一个小问题。显示用于输入相关Choice对象的所有字段需要大量屏幕空间出于这个原因,Django提供了一种显示内联相关对象的表格方式; 你只需要将ChoiceInline声明改为:

class ChoiceInline(admin.TabularInline):
    #...
polls/admin.py

使用它TabularInline(而不是StackedInline),相关对象以更紧凑,基于表格的格式显示:

django1.11入门

请注意,还有一个额外的“删除?”列,允许删除使用“添加另一个选择”按钮添加的行和已保存的行。

自定义管理员更改列表

现在问题管理页面看起来很好,让我们对“更改列表”页面进行一些调整 - 显示系统中所有问题的页面。

这就是它在这一点上的样子:

django1.11入门

默认情况下,Django显示str()每个对象。但有时如果我们可以显示单个字段会更有帮助。为此,请使用 list_displayadmin选项,该选项是要在对象的更改列表页面上显示的字段名称元组:

class QuestionAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question_text', 'pub_date')
polls/admin.py

为了更好的衡量,我们还要包含教程2中was_published_recently() 方法

class QuestionAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question_text', 'pub_date', 'was_published_recently')
polls/admin.py

现在,问题更改列表页面如下所示:

django1.11入门

您可以单击列标题以按这些值排序 - 除了was_published_recently标题之外,因为不支持按任意方法的输出进行排序。另请注意,was_published_recently默认情况下,列标题 是方法的名称(下划线替换为空格),并且每行包含输出的字符串表示形式。

您可以通过为该方法(in polls/models.py)提供一些属性来改进它,如下所示:

class Question(models.Model):
    # ...
    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'
polls/models.py

相关文章:

  • 2021-07-06
  • 2022-12-23
  • 2021-06-22
  • 2022-12-23
  • 2022-12-23
  • 2021-10-19
  • 2021-10-17
猜你喜欢
  • 2021-07-29
  • 2022-12-23
  • 2022-12-23
  • 2021-11-19
  • 2021-09-20
  • 2022-12-23
  • 2021-05-03
相关资源
相似解决方案