django1.11入门
快速安装指南¶
在使用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. >>>
>>>
删除任何旧版本的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.py
在django-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
0是0.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
文件中包含以下代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), ]
下一步是将根URLconf指向polls.urls
模块。在 mysite/urls.py
,添加导入django.conf.urls.include
并include()
在urlpatterns
列表中插入,所以你有:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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), ]
该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()
函数传递了四个参数,两个必需:regex
和view
,以及两个可选: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()
参数:名称¶
命名您的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作为数据库,额外的设置,例如 USER
,PASSWORD
和HOST
必须加入。有关更多详细信息,请参阅参考文档DATABASES
。
对于SQLite以外的数据库
如果您使用的是除SQLite之外的数据库,请确保此时已创建数据库。在数据库的交互式提示中使用“ ” 执行此操作。CREATEDATABASE database_name;
还要确保提供的数据库用户mysite/settings.py
具有“create database”特权。这允许自动创建 测试数据库,这将在以后的教程中使用。
如果您使用的是SQLite,则无需事先创建任何内容 - 数据库文件将在需要时自动创建。
在编辑时mysite/settings.py
,请设置TIME_ZONE
为您的时区。
另外,请注意INSTALLED_APPS
文件顶部的设置。它包含在这个Django实例中激活的所有Django应用程序的名称。应用程序可以在多个项目中使用,您可以打包和分发它们以供项目中的其他人使用。
默认情况下,INSTALLED_APPS
包含以下应用程序,所有这些应用程序都随Django一起提供:
django.contrib.admin
- 管理站点。你很快就会用到它。django.contrib.auth
- 认证系统。django.contrib.contenttypes
- 内容类型的框架。django.contrib.sessions
- 会话框架。django.contrib.messages
- 消息传递框架。django.contrib.staticfiles
- 用于管理静态文件的框架。
默认情况下包含这些应用程序,以方便常见情况。
但是,其中一些应用程序至少使用了一个数据库表,因此我们需要先在数据库中创建表,然后才能使用它们。为此,请运行以下命令:
$ python manage.py migrate
该migrate
命令查看INSTALLED_APPS
设置并根据mysite/settings.py
文件中的数据库设置和应用程序附带的数据库迁移创建任何必要的数据库表(稍后我们将介绍这些表)。您将看到适用于每次迁移的消息。如果您有兴趣,请运行数据库的命令行客户端并键入\dt
(PostgreSQL),(MySQL), (SQLite)或(Oracle)以显示Django创建的表。SHOW TABLES;
.schema
SELECT TABLE_NAME FROMUSER_TABLES;
对于极简主义者
就像我们上面所说的那样,默认应用程序包含在常见情况中,但不是每个人都需要它们。如果您不需要其中任何一个或全部,请INSTALLED_APPS
在运行前随意注释或删除相应的行 migrate
。该 migrate
命令仅运行应用程序的迁移INSTALLED_APPS
。
创建模型¶
现在我们将定义您的模型 - 本质上是您的数据库布局,以及其他元数据。
哲学
模型是关于数据的单一,明确的真实来源。它包含您要存储的数据的基本字段和行为。Django遵循DRY原则。目标是在一个地方定义您的数据模型,并自动从中获取数据。
这包括迁移 - 与Ruby On Rails不同,例如,迁移完全来自您的模型文件,并且基本上只是Django可以通过更新数据库模式以匹配您当前模型的历史记录。
在我们简单的民意调查应用程序中,我们将创建两个模型:Question
和Choice
。A Question
有问题和出版日期。A Choice
有两个字段:选择的文本和投票记录。每个Choice
都与一个Question
。
这些概念由简单的Python类表示。编辑 polls/models.py
文件,使其如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
代码很简单。每个模型由一个子类表示django.db.models.Model
。每个模型都有许多类变量,每个变量代表模型中的数据库字段。
每个字段由Field
类的实例表示- 例如,CharField
用于字符字段和 DateTimeField
日期时间。这告诉Django每个字段包含哪种类型的数据。
每个Field
实例的名称(例如 question_text
或pub_date
)是字段名称,采用机器友好格式。您将在Python代码中使用此值,并且您的数据库将使用它作为列名。
您可以使用可选的第一个位置参数 Field
来指定一个人类可读的名称。这在Django的几个内省部分中使用,并且它兼作文档。如果未提供此字段,Django将使用机器可读的名称。在这个例子中,我们只定义了一个人类可读的名称Question.pub_date
。对于此模型中的所有其他字段,字段的机器可读名称就足以作为其可读的名称。
有些Field
类需要参数。 CharField
例如,要求你给它一个 max_length
。这不仅在数据库模式中使用,而且在验证中使用,我们很快就会看到。
A Field
也可以有各种可选参数; 在这种情况下,我们将default
值 设置votes
为0。
最后,请注意使用的定义关系 ForeignKey
。这告诉Django每个Choice
都与单个相关Question
。Django支持所有常见的数据库关系:多对一,多对多和一对一。
激活模型¶
这一小部分模型代码为Django提供了大量信息。有了它,Django能够:
- 为此应用程序创建数据库模式(语句)。
CREATE TABLE
- 创建用于访问
Question
和Choice
对象的Python数据库访问API 。
但首先我们需要告诉我们的项目polls
应用程序已安装。
哲学
Django应用程序是“可插拔的”:您可以在多个项目中使用应用程序,并且可以分发应用程序,因为它们不必绑定到给定的Django安装。
要在我们的项目中包含应用程序,我们需要在设置中添加对其配置类的引用INSTALLED_APPS
。该 PollsConfig
班是在polls/apps.py
文件中,所以它的虚线路径'polls.apps.PollsConfig'
。编辑mysite/settings.py
文件并将该虚线路径添加到INSTALLED_APPS
设置中。它看起来像这样:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
INSTALLED_APPS = [ 'polls.apps.PollsConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
现在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 KEY
DEFERRABLE
- 它是根据您正在使用的数据库量身定制的,因此可以自动为您处理特定于数据库的字段类型,如
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
)并针对您的数据库运行它们 - 实际上,您将对模型所做的更改与模型中的模式同步数据库。
迁移功能非常强大,您可以在开发项目时随时更改模型,而无需删除数据库或表并创建新数据库 - 它专门用于实时升级数据库,而不会丢失数据。我们将在本教程的后续部分中更深入地介绍它们,但是现在,请记住进行模型更改的三步指南:
- 更改模型(in
models.py
)。 - 运行以创建这些更改的迁移
python manage.py makemigrations
- 运行以将这些更改应用于数据库。
python manage.py migrate
之所以有单独的命令来制作和应用迁移是因为您将提交迁移到您的版本控制系统并随应用程序一起发送; 它们不仅使您的开发更容易,而且还可以被其他开发人员和生产中使用。
阅读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
,没问题。只需设置 DJANGO_SETTINGS_MODULE
环境变量to 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>
Question
polls/models.py
__str__()
Question
Choice
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
__str__()
向模型添加方法非常重要,不仅是为了您在处理交互式提示时的方便,还因为在Django自动生成的管理中使用了对象的表示。
请注意,这些是普通的Python方法。让我们添加一个自定义方法,仅用于演示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
请注意添加和分别引用Python的标准模块和Django的时区相关实用程序。如果您不熟悉Python中的时区处理,可以在时区支持文档中了解更多信息。import datetime
from django.utils import timezone
datetime
django.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()
介绍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/。您应该看到管理员的登录屏幕:
由于默认情况下打开翻译,因此登录屏幕可能会以您自己的语言显示,具体取决于您的浏览器设置以及Django是否有此语言的翻译。
进入管理站点¶
现在,尝试使用您在上一步中创建的超级用户帐户登录。你应该看到Django管理员索引页面:
![Django管理员索引页面](https://docs.djangoproject.com/en/1.11/_images/admin02.png)
您应该看到几种类型的可编辑内容:组和用户。它们django.contrib.auth
由Django 提供的身份验证框架提供。
在管理员中修改民意调查应用程序¶
但是我们的投票应用程序在哪里?它不会显示在管理员索引页面上。
只需做一件事:我们需要告诉管理员Question
对象有一个管理界面。为此,请打开该polls/admin.py
文件,然后将其编辑为如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.contrib import admin from .models import Question admin.site.register(Question)
探索免费的管理功能¶
现在我们已经注册了Question
,Django知道它应该显示在管理员索引页面上:
单击“问题”。现在,您将进入“更改列表”页面以查询问题。此页面显示数据库中的所有问题,您可以选择一个更改它。我们之前创建了“什么事?”这个问题:
点击“怎么了?”问题进行编辑:
这里要注意的事项:
- 表单是从
Question
模型自动生成的。 - 不同的模型字段类型(
DateTimeField
,CharField
)对应于相应的HTML输入窗口小部件。每种类型的字段都知道如何在Django管理员中显示自己。 - 每个都
DateTimeField
获得免费的JavaScript快捷方式。日期获得“今日”快捷方式和日历弹出窗口,时间获得“现在”快捷方式和方便的弹出窗口,列出常用的输入时间。
页面底部为您提供了几个选项:
- 保存 - 保存更改并返回此类对象的更改列表页面。
- 保存并继续编辑 - 保存更改并重新加载此对象的管理页面。
- 保存并添加另一个 - 保存更改并为此类对象加载新的空白表单。
- 删除 - 显示删除确认页面。
如果“发布日期”的值与您在教程1中创建问题的时间不匹配,则可能意味着您忘记为该TIME_ZONE
设置设置正确的值。更改它,重新加载页面并检查是否显示正确的值。
单击“今天”和“立即”快捷方式更改“发布日期”。然后单击“保存并继续编辑”。然后单击右上角的“历史记录”。您将看到一个页面,其中列出了通过Django管理员对此对象所做的所有更改,以及进行更改的人员的时间戳和用户名:
编写你的第一个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
。这些观点略有不同,因为他们提出了一个论点:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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.urls
通过添加以下url()
调用将这些新视图连接到模块中 :
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 / 34 /”。它将运行该detail()
方法并显示您在URL中提供的任何ID。尝试“/ polls / 34 / results /”和“/ polls / 34 / vote /” - 这些将显示占位符结果和投票页面。
当有人从你的网站请求一个页面 - 比如“/ polls / 34 /”时,Django将加载mysite.urls
Python模块,因为它被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个轮询问题,以逗号分隔:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
但是这里存在一个问题:页面的设计在视图中是硬编码的。如果要更改页面的外观,则必须编辑此Python代码。因此,让我们使用Django的模板系统,通过创建视图可以使用的模板将设计与Python分离。
首先,创建目录中调用templates
的polls
目录。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指向正确的,并且确保这一点的最简单方法是通过命名它们。也就是说,将这些模板放在为应用程序本身命名的另一个目录中。
将以下代码放在该模板中:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% 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 %}
现在让我们更新我们的index
视图polls/views.py
以使用模板:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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/index.html
并将其传递给上下文。上下文是将模板变量名称映射到Python对象的字典。
通过将浏览器指向“/ polls /”来加载页面,您应该看到一个项目符号列表,其中包含教程2中的“What is up”问题。该链接指向问题的详细信息页面。
快捷方式:¶render()
加载模板,填充上下文并返回HttpResponse
带有渲染模板结果的对象是一种非常常见的习惯用法 。Django提供了一个捷径。这是完整的index()
视图,重写:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
请注意,一旦我们在所有这些观点做到了这一点,我们不再需要进口 loader
和HttpResponse
(你想保留HttpResponse
,如果你仍然有存根方法detail
, results
和vote
)。
该render()
函数将请求对象作为其第一个参数,将模板名称作为其第二个参数,将字典作为其可选的第三个参数。它返回使用HttpResponse
给定上下文呈现的给定模板的对象。
引发404错误¶
现在,让我们解决问题详细信息视图 - 显示给定民意调查的问题文本的页面。这是观点:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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})
这里的新概念:Http404
如果不存在具有请求ID的问题,则视图会引发异常。
稍后我们将讨论您可以在该polls/detail.html
模板中添加的内容,但是如果您希望快速获得上述示例,则只需包含以下内容的文件:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{{ question }}
会让你现在开始。
快捷方式:¶get_object_or_404()
如果对象不存在,使用get()
和提升这是一个非常常见的习惯用法Http404
。Django提供了一个捷径。这是detail()
视图,重写:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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})
该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
模板的外观:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul>
模板系统使用点查找语法来访问变量属性。在示例中,首先Django对该对象进行字典查找。如果失败了,它会尝试进行属性查找 - 在这种情况下可以正常工作。如果属性查找失败,它将尝试列表索引查找。{{ question.question_text }}
question
方法调用在循环中发生: 被解释为Python代码 ,它返回一个可迭代的对象,适合在标记中使用。{% for%}
question.choice_set.all
question.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
以设置应用程序命名空间:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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/index.html
模板:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
指向命名空间详细信息视图:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
编写你的第一个Django应用程序,第4部分¶
本教程从教程3停止的地方开始。我们将继续使用Web-poll应用程序,并将专注于简单的表单处理和减少代码。
写一个简单的表格¶
让我们从上一个教程更新我们的民意调查详细信息模板(“polls / detail.html”),以便模板包含一个HTML <form>
元素:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<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>
快速简要说明:
- 上面的模板显示每个问题选择的单选按钮。的
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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
我们还创建了该vote()
函数的虚拟实现。让我们创建一个真实版本。将以下内容添加到polls/views.py
:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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,)))
此代码包含我们在本教程中尚未涉及的一些内容:
-
request.POST
是一个类似字典的对象,允许您按键名访问提交的数据。在这种情况下,request.POST['choice']
返回所选选项的ID,作为字符串。request.POST
值始终是字符串。请注意,Django也提供
request.GET
了以相同方式访问GET数据 - 但我们request.POST
在代码中明确使用,以确保数据仅通过POST调用进行更改。 -
request.POST['choice']
KeyError
如果choice
POST数据中没有提供,则会引发。上面的代码检查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})
这几乎
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/1/
您的浏览器并在问题中投票。您应该会看到每次投票时都会更新的结果页面。如果您在未选择的情况下提交表单,则应该看到错误消息。使用通用视图:更少的代码更好¶
在
detail()
(从教程3)和results()
意见是非常简单的-并且如上面提到的,冗余的。index()
显示民意调查列表的视图类似。这些视图代表了基本Web开发的常见情况:根据URL中传递的参数从数据库获取数据,加载模板并返回呈现的模板。因为这是如此常见,Django提供了一种称为“通用视图”系统的快捷方式。
通用视图将常见模式抽象到您甚至不需要编写Python代码来编写应用程序的程度。
让我们将我们的民意调查应用程序转换为使用通用视图系统,这样我们就可以删除一堆自己的代码。我们只需要采取一些步骤进行转换。我们会:
- 转换URLconf。
- 删除一些旧的,不需要的视图。
- 基于Django的通用视图引入新视图。
继续阅读以了解详情。
为什么代码洗牌?
通常,在编写Django应用程序时,您将评估通用视图是否适合您的问题,并且您将从一开始就使用它们,而不是在中途重构代码。但本教程有意集中于直到现在“以艰难的方式”编写视图,专注于核心概念。
在开始使用计算器之前,您应该先了解基本数学。
修改URL配置¶
首先,打开
polls/urls.py
URLconf并将其更改为: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'), ]
请注意,第二个和第三个模式的正则表达式中匹配模式的名称已从更改
<question_id>
为<pk>
。修改意见¶
接下来,我们将删除我们的老
index
,detail
和results
视图,并使用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.
我们在这里使用两个通用视图:
ListView
和DetailView
。这两个视图分别抽象出“显示对象列表”和“显示特定类型对象的详细页面”的概念。- 每个通用视图都需要知道它将采用什么模型。这是使用
model
属性提供的。 - 该
DetailView
通用视图预计从URL中捕获的主键值被调用"pk"
,所以我们已经改变question_id
,以pk
用于通用视图。
默认情况下,
DetailView
通用视图使用名为的模板。在我们的例子中,它将使用模板。该 属性用于告诉Django使用特定的模板名称而不是自动生成的默认模板名称。我们还为列表视图指定了- 这确保了结果视图和详细视图在渲染时具有不同的外观,即使它们都是 幕后的。<appname>/<model name>_detail.html
"polls/question_detail.html"
template_name
template_name
results
DetailView
同样,
ListView
通用视图使用一个名为的默认模板; 我们用来告诉 使用我们现有的 模板。<app name>/<modelname>_list.html
template_name
ListView
"polls/index.html"
在本教程的前几部分中,为模板提供了包含
question
和latest_question_list
上下文变量的上下文。对于DetailView
该question
自动提供的变量-因为我们使用的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
是最后一天(这是正确的)内发布,而且如果Question
的pub_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)
我们在这里所做的是创建一个
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 polls
在polls
应用程序中寻找测试- 它找到了
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()
应该返回。修改方法 ,以便只有在日期也是过去时它才会返回:False
pub_date
models.py
True
def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now
并再次运行测试:
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)
现在,我们有三项测试证实可以
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'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]
我们需要修改
get_queryset()
方法并对其进行更改,以便通过比较来检查日期timezone.now()
。首先我们需要添加一个导入:from django.utils import timezone
然后我们必须
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]
Question.objects.filter(pub_date__lte=timezone.now())
返回一个查询集,其中包含的Question
spub_date
小于或等于 - 即早于或等于 -timezone.now
。测试我们的新视图¶
现在,您可以通过启动runserver,在浏览器中加载站点,
Questions
在过去和将来创建日期以及检查是否仅列出已发布的日期来满足您自己的行为。您不希望每次进行任何可能影响此更改的更改时都这样做- 所以我们也要根据上面的shell
会话创建一个测试 。将以下内容添加到
polls/tests.py
:from django.urls import reverse
我们将创建一个快捷函数来创建问题以及一个新的测试类:
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.>'] )
让我们更仔细地看看其中的一些。
首先是一个问题快捷方式功能,
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())
当然,我们将增加一些测试,以检查一个
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)
更多测试的想法¶
我们应该为该视图添加一个类似的
get_queryset
方法ResultsView
并创建一个新的测试类。它与我们刚创造的非常相似; 事实上会有很多重复。我们还可以通过其他方式改进我们的应用程序,并在此过程中添加测试。例如,
Questions
可以在没有的网站上发布它是愚蠢的Choices
。因此,我们的观点可以检查这一点,并排除这种情况Questions
。我们的测试会创建一个Question
没有Choices
,然后测试它没有发布,以及创建一个类似Question
的Choices
,并测试它是否已发布。也许应该允许登录的管理员用户看到未发布的
Questions
,但不能看到 普通的访问者。再说一次:无论需要添加到软件中来实现这一点,都应该伴随着测试,无论是先编写测试还是让代码通过测试,或者首先在代码中计算出逻辑,然后再编写测试证明给我看。在某个时刻,你一定会看看你的测试,并想知道你的代码是否遭受了测试膨胀,这导致我们:
测试时,越多越好¶
我们的测试似乎越来越失控。按照这个速度,我们的测试中的代码很快会比我们的应用程序中的代码更多,并且与其他代码的优雅简洁相比,重复是不美观的。
没关系。让他们成长。在大多数情况下,您可以编写一次测试然后忘掉它。在您继续开发程序时,它将继续执行其有用的功能。
有时需要更新测试。假设我们修正了我们的观点,以便只
Questions
与Choices
发布。在这种情况下,我们现有的许多测试都会失败 - 告诉我们究竟需要修改哪些测试以使它们更新,所以在这种程度上测试有助于照顾自己。在最坏的情况下,当您继续开发时,您可能会发现您有一些现在多余的测试。即使这不是问题; 在测试中的冗余是一个很好的事情。
只要您的测试得到合理安排,它们就不会变得难以管理。良好的经验法则包括:
TestClass
每个模型或视图单独一个- 针对要测试的每组条件的单独测试方法
- 描述其功能的测试方法名称
进一步测试¶
本教程仅介绍一些测试基础知识。你可以做很多事情,并且可以使用一些非常有用的工具来实现一些非常聪明的事情。
例如,虽然我们的测试涵盖了模型的一些内部逻辑以及我们的视图发布信息的方式,但您可以使用“浏览器内”框架(如Selenium)来测试HTML在浏览器中实际呈现的方式。这些工具不仅可以检查Django代码的行为,还可以检查JavaScript的行为。很有可能看到测试启动浏览器,并开始与您的网站进行交互,就像一个人在驾驶它一样!Django包括
LiveServerTestCase
促进与Selenium等工具的集成。如果您有一个复杂的应用程序,您可能希望在每次提交时自动运行测试以实现持续集成,以便质量控制本身 - 至少部分 - 自动化。
发现应用程序未经测试的部分的一个好方法是检查代码覆盖率。这也有助于识别脆弱甚至死亡的代码。如果您无法测试一段代码,通常意味着代码应该被重构或删除。覆盖范围将有助于识别死代码。有关详细信息,请参阅 与coverage.py集成。
有关测试的完整详细信息,请参阅Django中的测试。
编写你的第一个Django应用程序,第6部分¶
本教程从教程5停止的地方开始。我们已经构建了一个经过测试的Web轮询应用程序,现在我们将添加样式表和图像。
除了服务器生成的HTML之外,Web应用程序通常还需要提供呈现完整网页所需的其他文件(如图像,JavaScript或CSS)。在Django中,我们将这些文件称为“静态文件”。
对于小型项目,这不是什么大问题,因为您可以将静态文件保存在Web服务器可以找到的位置。但是,在更大的项目中 - 特别是那些由多个应用程序组成的项目 - 处理每个应用程序提供的多组静态文件开始变得棘手。
这
django.contrib.staticfiles
就是:它从您的每个应用程序(以及您指定的任何其他位置)收集静态文件到一个可以在生产中轻松提供的位置。自定义应用程序的外观¶
首先,创建目录中调用
static
的polls
目录。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/templates/polls/index.html
:{% load static %} <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
该模板标签生成静态文件的绝对路径。
{% static %}
这就是开发所需要做的一切。重新加载
http://localhost:8000/polls/
,您应该看到问题链接是绿色(Django样式!),这意味着您的样式表已正确加载。添加背景图像¶
接下来,我们将为图像创建一个子目录。
images
在polls/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; }
重新加载
http://localhost:8000/polls/
,您应该看到屏幕右下方加载了背景。警告
当然,模板标签不能用于像样式表那样不是由Django生成的静态文件。您应始终使用相对路径将静态文件链接到彼此之间,因为您可以更改(由 模板标记用于生成其URL),而无需修改静态文件中的一堆路径。
{% static %}
STATIC_URL
static
这些是基础知识。有关框架中包含的设置和其他位的更多详细信息,请参阅 静态文件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)
您将遵循此模式 - 创建模型管理类,然后将其作为第二个参数传递给
admin.site.register()
- 任何时候您需要更改模型的管理选项。上述特定更改使“发布日期”出现在“问题”字段之前:
仅使用两个字段并不令人印象深刻,但对于具有数十个字段的管理表单,选择直观的顺序是一个重要的可用性细节。
说到有几十个字段的表单,您可能希望将表单拆分为字段集:
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)
每个元组的第一个元素
fieldsets
是fieldset的标题。这就是我们现在的形式:高级教程:如何编写可重用的应用程序¶
这个高级教程从教程7 停止的地方开始。我们将把Web-poll变成一个独立的Python包,您可以在新项目中重用它并与其他人共享。
如果您最近没有完成教程1-7,我们建议您查看这些内容,以便您的示例项目与下面描述的项目相匹配。
可重用性问题¶
设计,构建,测试和维护Web应用程序需要做很多工作。许多Python和Django项目都存在共同的问题。如果我们能省下一些重复的工作,那不是很好吗?
可重用性是Python的生活方式。Python包索引(PyPI)包含大量的包,您可以在自己的Python程序中使用它们。查看Django Packages,了解您可以在项目中包含的现有可重用应用程序。Django本身也只是一个Python包。这意味着您可以使用现有的Python包或Django应用程序并将它们组合到您自己的Web项目中。您只需编写使您的项目独特的部分。
假设您正在开始一个需要民意调查应用程序的新项目,就像我们一直在努力的那样。你如何使这个应用程序可重用?幸运的是,你已经很好了。在教程3中,我们看到了如何使用一个方法将民意调查与项目级URLconf分离
include
。在本教程中,我们将采取进一步措施,使应用程序易于在新项目中使用,并准备发布供其他人安装和使用。包?应用程序?
Python 包提供了一种对相关Python代码进行分组的方法,以便于重用。包中包含一个或多个Python代码文件(也称为“模块”)。
包可以用或导入。对于形成包的目录(如),它必须包含一个特殊文件,即使该文件为空。
import foo.bar
from fooimport bar
polls
__init__.py
Django 应用程序只是一个专门用于Django项目的Python包。应用程序可以使用通用的Django约定,诸如具有
models
,tests
,urls
,和views
子模块。稍后我们使用术语包装来描述使Python包易于安装的过程。我们知道,这可能有点令人困惑。
您的项目和可重用的应用程序¶
在前面的教程之后,我们的项目应如下所示:
mysite/ manage.py mysite/ __init__.py settings.py urls.py wsgi.py polls/ __init__.py admin.py migrations/ __init__.py 0001_initial.py models.py static/ polls/ images/ background.gif style.css templates/ polls/ detail.html index.html results.html tests.py urls.py views.py templates/ admin/ base_site.html
您创建
mysite/templates
的教程7,并且polls/templates
在教程3。现在也许更清楚的是为什么我们选择为项目和应用程序提供单独的模板目录:作为民意调查应用程序一部分的所有内容都在polls
。它使应用程序自包含,更容易进入新项目。该
polls
目录现在可以复制到新的Django项目中并立即重用。虽然它还没有准备好发表。为此,我们需要打包应用程序,以便其他人安装。安装一些先决条件¶
Python包装的当前状态与各种工具有点混乱。在本教程中,我们将使用setuptools来构建我们的包。这是推荐的打包工具(与
distribute
fork 合并)。我们还将使用pip来安装和卸载它。您应该立即安装这两个包。如果您需要帮助,可以参考如何使用pip安装Django。您可以setuptools
以相同的方式安装。打包您的应用¶
Python 打包是指以易于安装和使用的特定格式准备应用程序。Django本身的打包非常像这样。对于像民意调查这样的小应用程序,这个过程并不太难。
-
首先,
polls
在Django项目之外创建一个父目录。调用此目录django-polls
。为您的应用选择一个名称
为包选择名称时,请检查PyPI等资源,以避免与现有包发生命名冲突。
django-
在创建要分发的包时,在模块名称前添加通常很有用 。这有助于其他寻找Django应用的用户将您的应用识别为特定于Django。应用程序标签(即应用程序包的虚线路径的最后部分)必须是唯一的
INSTALLED_APPS
。避免使用相同的标签为任何Django的的的contrib包,例如auth
,admin
或messages
。 -
将
polls
目录移动到django-polls
目录中。 -
创建
django-polls/README.rst
包含以下内容的文件:===== Polls ===== 民意调查是一个简单的Django应用程序,用于进行基于Web的民意调查。 对于每一个 问题,访问者可以选择固定数量的答案。 详细文档位于“docs”目录中。 快速开始 ----------- 1.将“民意调查”添加到您的INSTALLED_APPS设置中,如下所示:: INSTALLED_APPS = [ ... 'polls', ] 2. 在您的项目urls.py中包含民意调查URLconf,如下所示:: url(r'^polls/', include('polls.urls')), 3.运行`python manage.py migrate`以创建民意调查模型。 4.启动开发服务器并访问http://127.0.0.1:8000/admin/ 创建投票(您需要启用管理员应用)。 5. 访问http://127.0.0.1:8000/polls/参加投票。
-
创建一个
django-polls/LICENSE
文件。选择许可证超出了本教程的范围,但足以说明没有许可证公开发布的代码是无用的。Django和许多兼容Django的应用程序是根据BSD许可证分发的; 但是,您可以自由选择自己的许可证。请注意,您的许可选择将影响谁可以使用您的代码。 -
接下来,我们将创建一个
setup.py
文件,提供有关如何构建和安装应用程序的详细信息。该文件的完整说明超出了本教程的范围,但setuptools文档有一个很好的解释。创建django-polls/setup.py
包含以下内容的文件:import os from setuptools import find_packages, setup with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: README = readme.read() # allow setup.py to be run from any path os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) setup( name='django-polls', version='0.1', packages=find_packages(), include_package_data=True, license='BSD License', # example license description='A simple Django app to conduct Web-based polls.', long_description=README, url='https://www.example.com/', author='Your Name', author_email='yourname@example.com', classifiers=[ 'Environment :: Web Environment', 'Framework :: Django', 'Framework :: Django :: X.Y', # replace "X.Y" as appropriate 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', # example license 'Operating System :: OS Independent', 'Programming Language :: Python', # Replace these appropriately if you are stuck on Python 2. 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], )
- 默认情况下,程序包中仅包含Python模块和程序包。要包含其他文件,我们需要创建一个
MANIFEST.in
文件。上一步中提到的setuptools文档更详细地讨论了该文件。要包含模板,README.rst
和我们的LICENSE
文件,请创建一个django-polls/MANIFEST.in
包含以下内容的文件:include LICENSE include README.rst recursive-include polls/static * recursive-include polls/templates *
-
它是可选的,但建议在您的应用程序中包含详细的文档。
django-polls/docs
为将来的文档创建一个空目录。添加一行到django-polls/MANIFEST.in
:recursive-include docs *
请注意,
docs
除非您向其中添加一些文件,否则该目录将不会包含在您的包中。许多Django应用程序还通过readthedocs.org等网站在线提供文档。 -
-
尝试使用(从内部运行 )构建包。这将创建一个名为的目录并构建您的新包。
python setup.py sdist
django-polls
dist
django-polls-0.1.tar.gz
有关打包的更多信息,请参阅Python的打包和分发项目教程。
使用自己的包¶
由于我们将
polls
目录移出项目,因此不再有效。我们现在将通过安装我们的新django-polls
软件包解决这个问题。作为用户库安装
以下步骤
django-polls
作为用户库安装。与在系统范围内安装软件包相比,每用户安装具有许多优势,例如可以在您没有管理员访问权限的系统上使用,以及防止软件包影响系统服务和机器的其他用户。请注意,每用户安装仍然会影响以该用户身份运行的系统工具的行为,因此
virtualenv
是一种更强大的解决方案(请参见下文)。-
要安装软件包,请使用pip(您已安装它,对吧?):
pip install --user django-polls/dist/django-polls-0.1.tar.gz
- 幸运的是,您的Django项目现在应该再次正常工作。再次运行服务器以确认这一点。
- 要卸载软件包,请使用pip:
pip uninstall django-polls
发布你的应用¶
现在我们已经打包并进行了测试
django-polls
,它已准备好与全世界分享!如果这不仅仅是一个例子,你现在可以:- 将包裹通过电子邮件发送给朋友
- 上传您网站上的包裹。
- 将包发布在公共存储库上,例如Python包索引(PyPI)。packaging.python.org提供了一个很好的教程 。
使用virtualenv安装Python包¶
之前,我们将民意调查应用程序安装为用户库。这有一些缺点:
- 修改用户库可能会影响系统上的其他Python软件。
- 您将无法运行此软件包的多个版本(或其他具有相同名称的版本)。
通常,只有在维护多个Django项目时才会出现这些情况。当他们这样做时,最好的解决方案是使用virtualenv。此工具允许您维护多个隔离的Python环境,每个环境都有自己的库和包命名空间的副本。
接下来要读什么¶
所以你已经阅读了所有的介绍材料,并决定你继续使用Django。我们只是简单介绍了这个介绍(事实上,如果你读过每一个单词,你已经阅读了大约5%的整体文档)。
下一个是什么?
好吧,我们一直都是通过实践学习的忠实粉丝。在这一点上,你应该知道自己开始一个项目并开始愚弄。当您需要学习新技巧时,请回到文档。
我们付出了很多努力使Django的文档变得有用,易于阅读并尽可能完整。本文档的其余部分将详细介绍文档的工作原理,以便您可以充分利用它。
(是的,这是关于文档的文档。请放心,我们没有计划编写有关如何阅读有关文档的文档的文档。)
如何组织文档¶
Django的主要文档被分解为“块”,旨在满足不同的需求:
-
在介绍性的材料是专为人们新的Django的-或者Web开发一般。它没有深入介绍任何内容,而是对Django的“感觉”发展进行了高级概述。
-
该主题将指导,另一方面,深潜入的Django的各个部分。有完整的Django 模型系统,模板引擎,表单框架等指南。
这可能是你想要花费大部分时间的地方; 如果你通过这些指南工作,你应该知道几乎所有关于Django的知识。
-
Web开发通常很广泛,而不是很深入 - 跨越许多领域的问题。我们编写了一套操作指南来回答常见的“我如何......?”的问题。在这里,您可以找到有关 使用Django生成PDF,编写自定义模板标签等信息。
答案非常常见的问题,也可以在发现的常见问题。
-
指南和操作方法不包括Django中可用的每个类,功能和方法 - 当您尝试学习时,这将是压倒性的。相反,有关各个类,函数,方法和模块的详细信息保留在参考中。您可以在此处查找特定功能的详细信息或您需要的任何内容。
-
最后,有一些“专业”文档通常与大多数开发人员无关。这包括那些想要向Django本身添加代码的人的发行说明和 内部文档,以及其他一些不适合其他地方的东西。
如何更新文档¶
正如Django代码库每天都在开发和改进一样,我们的文档也在不断改进。我们改进文档有以下几个原因:
- 进行内容修复,例如语法/拼写错误修正。
- 向需要扩展的现有部分添加信息和/或示例。
- 记录尚未记录的Django功能。(这些功能的列表正在缩小,但仍然存在。)
- 在添加新功能或添加Django API或行为时添加新功能的文档。
Django的文档与其代码保存在同一个源代码控制系统中。它位于我们的Git存储库的docs目录中。在线的每个文档都是存储库中的单独文本文件。
哪里可以得到它¶
您可以通过多种方式阅读Django文档。它们按优先顺序排列:
在网络上¶
最新版本的Django文档位于 https://docs.djangoproject.com/en/dev/。这些HTML页面是从源代码管理中的文本文件自动生成的。这意味着它们反映了Django中的“最新和最好的” - 它们包括最新的更正和补充,并且它们讨论了最新的Django功能,这些功能可能只适用于Django开发版本的用户。(参见下面的“版本之间的差异”。)
我们建议您通过在故障单系统中提交更改,更正和建议来帮助改进文档。Django开发人员主动监控票务系统并使用您的反馈来改进每个人的文档。
但请注意,票证应明确与文档相关,而不是询问广泛的技术支持问题。如果您需要有关特定Django设置的帮助,请尝试使用django-users邮件列表或#django IRC频道。
用明文¶
对于离线阅读,或者为了方便起见,您可以用纯文本阅读Django文档。
如果您正在使用Django的官方发行版,请注意代码的压缩包(tarball)包含一个
docs/
目录,其中包含该发行版的所有文档。如果您使用的是Django的开发版本(又名“trunk”),请注意该
docs/
目录包含所有文档。您可以更新您的Git结帐以获取最新的更改。利用文本文档的一种低技术方法是使用Unix
grep
实用程序在所有文档中搜索短语。例如,这将显示在任何Django文档中每次提到短语“max_length”:$ grep -r max_length /path/to/django/docs/
作为HTML,本地¶
您可以通过以下几个简单步骤获取HTML文档的本地副本:
版本之间的差异¶
如前所述,我们的Git存储库中的文本文档包含“最新和最好的”更改和添加。这些更改通常包括Django开发版本中添加的新功能的文档 - Django的Git(“trunk”)版本。出于这个原因,值得指出我们关于保持框架的各种版本的文档的政策。
我们遵循这一政策:
- djangoproject.com上的主要文档是Git中最新文档的HTML版本。这些文档始终对应于最新的官方Django版本,以及自最新版本以来我们在框架中添加/更改的任何功能。
- 当我们向Django的开发版本添加功能时,我们尝试在同一个Git提交事务中更新文档。
- 为了区分文档中的功能更改/添加,我们使用短语:“版本XY中的新功能”,XY是下一个版本(因此,正在开发的版本)。
- 文档修复和改进可能会被提交到最后一个版本分支,由提交者自行决定,但是,一旦不再支持 Django 版本,该版本的文档将不会获得任何进一步的更新。
- 在主文档Web页面包含指向文档,所有以前的版本。请确保您使用的是与您正在使用的Django版本相对应的文档版本!
为Django编写第一个补丁¶
介绍¶
有兴趣回馈一下社区吗?也许您在Django中发现了一个您希望修复的错误,或者您想添加一个小功能。
回馈Django本身是了解自己关注的最佳方式。这一开始看起来令人生畏,但它真的很简单。我们将引导您完成整个过程,以便您可以通过示例学习。
这个教程是谁的?¶
也可以看看
如果要查找有关如何提交修补程序的参考,请参阅 提交修补程序 文档。
对于本教程,我们希望您至少对Django的工作原理有基本的了解。这意味着您应该熟悉编写第一个Django应用程序的现有教程。另外,你应该对Python本身有一个很好的理解。但是,如果你不这样做,Dive Into Python是一本非常棒的(免费)在线书籍,适合初学Python程序员。
那些不熟悉版本控制系统和Trac的人会发现本教程及其链接包含足够的信息以便入门。但是,如果您计划定期为Django做贡献,您可能希望阅读更多有关这些不同工具的内容。
但是,在大多数情况下,本教程试图尽可能地解释,以便它可以用于最广泛的受众。
哪里可以获得帮助:
如果您在阅读本教程时遇到问题,请向django-developers发送消息,或者通过irc.freenode.net上的#django-dev与其他可能提供帮助的Django用户聊天。
本教程涵盖哪些内容?¶
我们将第一次带您到Django的补丁。在本教程结束时,您应该对所涉及的工具和过程有基本的了解。具体来说,我们将涵盖以下内容:
- 安装Git。
- 如何下载Django的开发副本。
- 运行Django的测试套件。
- 为您的补丁编写测试。
- 编写补丁的代码。
- 测试你的补丁。
- 提交拉取请求。
- 在哪里寻找更多信息。
完成本教程后,您可以查看Django关于贡献的其余 文档。它包含很多很棒的信息,对于那些想成为Django常规贡献者的人来说,必读。如果你有问题,可能会得到答案。
需要Python 3!
本教程假设您使用的是Python 3.在Python的下载页面或使用操作系统的软件包管理器获取最新版本 。
对于Windows用户
在Windows上安装Python时,请确保选中“将python.exe添加到路径”选项,以便它始终在命令行中可用。
安装Git¶
对于本教程,您需要安装Git才能下载Django的当前开发版本,并为您所做的更改生成补丁文件。
要检查是否安装了Git,请进入
git
命令行。如果您收到消息说无法找到此命令,则必须下载并安装它,请参阅Git的下载页面。对于Windows用户
在Windows上安装Git时,建议您选择“Git Bash”选项,以便Git在自己的shell中运行。本教程假设您已经安装了它。
如果您对Git不熟悉,可以通过在命令行输入来了解有关其命令(一旦安装)的更多信息。
git help
获取Django开发版本的副本¶
为Django做贡献的第一步是获取源代码的副本。首先,在GitHub上分叉Django。然后,从命令行,使用该
cd
命令导航到您希望Django的本地副本存在的目录。使用以下命令下载Django源代码存储库:
$ git clone git@github.com:YourGitHubName/django.git
现在您已经拥有了Django的本地副本,您可以像安装任何软件包一样安装它
pip
。最方便的方法是使用虚拟环境(或virtualenv),这是Python内置的一个功能,允许您为每个项目保留一个单独的已安装软件包目录,以便它们不会相互干扰。将所有virtualenvs保存在一个位置是个好主意,例如
.virtualenvs/
在您的主目录中。如果它还不存在,请创建它:$ mkdir ~/.virtualenvs
现在运行以下命令创建一个新的virtualenv:
$ python3 -m venv ~/.virtualenvs/djangodev
该路径是新环境将保存在您的计算机上的位置。
对于Windows用户
venv
如果您还在Windows上使用Git Bash shell,则使用内置模块将无法工作,因为仅为系统shell(.bat
)和PowerShell(.ps1
)创建了激活脚本。virtualenv
改为使用包:$ pip install virtualenv $ virtualenv ~/.virtualenvs/djangodev
对于Ubuntu用户
在某些版本的Ubuntu上,上述命令可能会失败。请改用
virtualenv
包装,首先确保您拥有pip3
:$ sudo apt-get install python3-pip $ # Prefix the next command with sudo if it gives a permission denied error $ pip3 install virtualenv $ virtualenv --python=`which python3` ~/.virtualenvs/djangodev
设置virtualenv的最后一步是激活它:
$ source ~/.virtualenvs/djangodev/bin/activate
如果该
source
命令不可用,您可以尝试使用点代替:$ . ~/.virtualenvs/djangodev/bin/activate
对于Windows用户
要在Windows上激活virtualenv,请运行:
$ source ~/virtualenvs/djangodev/Scripts/activate
每当打开新的终端窗口时,您都必须激活virtualenv。 virtualenvwrapper是一个有用的工具,使这更方便。
pip
从现在开始安装的任何内容都将安装在新的virtualenv中,与其他环境和系统范围的软件包隔离。此外,命令行上会显示当前激活的virtualenv的名称,以帮助您跟踪正在使用的名称。继续安装之前克隆的Django副本:$ pip install -e /path/to/your/local/clone/django/
已安装的Django版本现在指向您的本地副本。您将立即看到对其所做的任何更改,这在编写第一个补丁时非常有用。
回滚到以前版本的Django¶
在本教程中,我们将使用#24788作为案例研究,因此我们将在应用该票证补丁之前将gang中的Django版本历史回滚。这将允许我们完成从头开始编写该补丁所涉及的所有步骤,包括运行Django的测试套件。
请记住,虽然我们将在下面的教程中使用较旧的Django主干版本,但在处理自己的票证补丁时,应始终使用Django的当前开发版本!
注意
这张票的补丁是由PawełMarczewski编写的,它被应用于Django作为提交4df7e8483b2679fc1cba3410f08960bac6f51115。因此,我们将在此之前使用Django的修订版, 提交4ccfc4439a7add24f8db4ef3960d02ef8ae09887。
导航到Django的根目录下(这是包含一个
django
,docs
,tests
,AUTHORS
,等)。然后,您可以查看我们将在下面的教程中使用的旧版Django:$ git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887
第一次运行Django的测试套件¶
在为Django做贡献时,您的代码更改不会将错误引入Django的其他区域非常重要。在进行更改后检查Django是否仍然有效的一种方法是运行Django的测试套件。如果所有测试仍然通过,那么您可以合理地确定您的更改并未完全破坏Django。如果您以前从未运行过Django的测试套件,那么最好先预先运行一次,以便熟悉其输出应该是什么样子。
在运行测试套件之前,首先将其依赖项安装
cd
到Djangotests/
目录中,然后运行:$ pip install -r requirements/py3.txt
如果在安装过程中遇到错误,则系统可能缺少一个或多个Python包的依赖项。查阅失败的软件包文档或使用您遇到的错误消息在Web上搜索。
现在我们准备运行测试套件了。如果您使用的是GNU / Linux,macOS或其他一些Unix版本,请运行:
$ ./runtests.py
现在坐下来放松一下。Django的整个测试套件有超过9,600种不同的测试,因此运行时间可能需要5到15分钟,具体取决于计算机的速度。
当Django的测试套件运行时,您将看到一个字符流,表示每个测试运行时的状态。
E
表示在测试期间引发了错误,并F
指示测试的断言失败。这两个都被认为是测试失败。同时,x
并s
表示预期的失败和分别跳过的测试。圆点表示通过测试。跳过的测试通常是由于缺少运行测试所需的外部库; 请参阅运行所有测试以获取依赖项列表,并确保安装任何与您正在进行的更改相关的测试(本教程不需要任何测试)。某些测试特定于特定的数据库后端,如果不使用该后端进行测试,则会跳过这些测试。SQLite是默认设置的数据库后端。要使用其他后端运行测试,请参阅使用其他设置模块。
测试完成后,应该会收到一条消息,通知您测试套件是通过还是失败。由于您尚未对Django的代码进行任何更改,因此整个测试套件应该通过。如果您遇到故障或错误,请确保您已正确执行了上述所有步骤。有关更多信息,请参阅 运行单元测试。如果您使用的是Python 3.5+,则可能会忽略与弃用警告相关的一些故障。这些失败已经在Django中得到修复。
请注意,最新的Django主干可能并不总是稳定的。在针对主干进行开发时,您可以检查Django的持续集成构建,以确定故障是否特定于您的计算机,或者它们是否也出现在Django的官方版本中。如果单击以查看特定构建,则可以查看“配置矩阵”,其中显示了由Python版本和数据库后端细分的故障。
注意
对于本教程和我们正在处理的故障单,对SQLite进行测试就足够了,但是,使用不同的数据库运行测试是可能的(有时是必要的) 。
为补丁创建分支¶
在进行任何更改之前,请为故障单创建一个新分支:
$ git checkout -b ticket_24788
您可以为分支选择任何名称,“ticket_24788”就是一个例子。此分支中所做的所有更改都将特定于故障单,并且不会影响我们之前克隆的代码的主副本。
为你的票写一些测试¶
在大多数情况下,对于要接受Django的补丁,它必须包括测试。对于错误修复补丁,这意味着编写回归测试以确保以后永远不会将错误重新引入Django。回归测试应该以这样的方式编写,即在错误仍然存在时它将失败并在错误修复后通过。对于包含新功能的修补程序,您需要包含可确保新功能正常运行的测试。它们也应该在新功能不存在时失败,然后在实现后再通过。
执行此操作的一种好方法是在对代码进行任何更改之前先编写新测试。这种开发方式称为 测试驱动开发,可以应用于整个项目和单个补丁。在编写测试之后,然后运行它们以确保它们确实失败(因为您尚未修复该错误或添加了该功能)。如果您的新测试没有失败,您需要修复它们以便它们执行。毕竟,无论是否存在错误,都会通过回归测试,这对于防止错误重复发生并非常有帮助。
现在我们亲自动手的例子。
为票#24788写一些测试¶
故障#24788提出了一个小功能添加:能够
prefix
在Form类上指定类级别属性,以便:[…] forms which ship with apps could effectively namespace themselves such that N overlapping form fields could be POSTed at once and resolved to the correct form.
为了解决此故障单,我们将向该类添加一个
prefix
属性BaseForm
。在创建此类的实例时,将前缀传递给__init__()
方法仍将在创建的实例上设置该前缀。但是不传递前缀(或传递None
)将使用类级前缀。在我们进行这些更改之前,我们将编写几个测试来验证我们的修改是否正常运行并在将来继续正常运行。导航到Django的
tests/forms_tests/tests/
文件夹并打开test_forms.py
文件。在test_forms_with_null_boolean
函数前面的第1674行添加以下代码 :def test_class_prefix(self): # Prefix can be also specified at the class level. class Person(Form): first_name = CharField() prefix = 'foo' p = Person() self.assertEqual(p.prefix, 'foo') p = Person(prefix='bar') self.assertEqual(p.prefix, 'bar')
此新测试检查设置类级别前缀是否按预期工作,并且
prefix
在创建实例时传递参数仍然有效。运行新测试¶
请记住,我们尚未对其进行任何修改
BaseForm
,因此我们的测试将失败。让我们运行forms_tests
文件夹中的所有测试,以确保真正发生的事情。从命令行cd
进入Djangotests/
目录并运行:$ ./runtests.py forms_tests
如果测试运行正确,您应该看到与我们添加的测试方法相对应的一个失败。如果所有测试都通过了,那么您需要确保将上面显示的新测试添加到相应的文件夹和类中。
编写机票代码¶
接下来,我们将把票号#24788中描述的功能添加到Django。
编写故障单#24788的代码¶
导航到该
django/django/forms/
文件夹并打开该forms.py
文件。BaseForm
在第72行找到该类,并在prefix
属性后面添加classfield_order
属性:class BaseForm(object): # This is the main implementation of all the Form logic. Note that this # class is different than Form. See the comments by the Form class for # more information. Any improvements to the form API should be made to # *this* class, not to the Form class. field_order = None prefix = None
验证您的测试现在通过¶
一旦你完成了修改Django,我们需要确保我们之前写的测试通过,所以我们可以看到上面编写的代码是否正常工作。要在
forms_tests
文件夹中运行测试,请cd
进入Djangotests/
目录并运行:$ ./runtests.py forms_tests
哎呀,我们编写这些测试的好东西!您仍然应该看到一个失败,但有以下异常:
AssertionError: None != 'foo'
我们忘了在
__init__
方法中添加条件语句。继续改变现在的第87行 ,添加一个条件语句:self.prefix =prefix
django/forms/forms.py
if prefix is not None: self.prefix = prefix
重新运行测试,一切都应该通过。如果没有,请确保
BaseForm
如上所示正确修改了类并正确复制了新测试。第二次运行Django的测试套件¶
一旦你确认你的补丁和测试工作正常,最好运行整个Django测试套件,以验证你的更改没有在Django的其他区域引入任何错误。虽然成功通过整个测试套件并不能保证您的代码没有错误,但它确实有助于识别可能会被忽视的许多错误和回归。
要运行整个Django测试套件,
cd
进入Djangotests/
目录并运行:$ ./runtests.py
只要你没有看到任何失败,你就会很高兴。
编写文档¶
这是一个新功能,因此应该记录在案。在第1068行(文件末尾)添加以下部分
django/docs/ref/forms/api.txt
:The prefix can also be specified on the form class:: >>> class PersonForm(forms.Form): ... ... ... prefix = 'person' .. versionadded:: 1.9 The ability to specify ``prefix`` on the form class was added.
由于这个新功能将在即将发布的版本中,因此它也会添加到Django 1.9的发行说明中,位于文件中“Forms”部分的第164行
docs/releases/1.9.txt
:* A form prefix can be specified inside a form class, not only when instantiating a form. See :ref:`form-prefix` for details.
有关编写文档的更多信息,包括对
versionadded
所有内容的解释,请参阅 编写文档。该页面还包括如何在本地构建文档副本的说明,以便您可以预览将生成的HTML。预览您的更改¶
现在是时候完成补丁中的所有更改了。要显示当前Django副本(包括您的更改)与您最初在本教程中检出的修订版之间的差异:
$ git diff
使用箭头键上下移动。
diff --git a/django/forms/forms.py b/django/forms/forms.py index 509709f..d1370de 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -75,6 +75,7 @@ class BaseForm(object): # information. Any improvements to the form API should be made to *this* # class, not to the Form class. field_order = None + prefix = None def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=ErrorList, label_suffix=None, @@ -83,7 +84,8 @@ class BaseForm(object): self.data = data or {} self.files = files or {} self.auto_id = auto_id - self.prefix = prefix + if prefix is not None: + self.prefix = prefix self.initial = initial or {} self.error_class = error_class # Translators: This is the default suffix added to form field labels diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 3bc39cd..008170d 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -1065,3 +1065,13 @@ You can put several Django forms inside one ``<form>`` tag. To give each >>> print(father.as_ul()) <li><label for="id_father-first_name">First name:</label> <input type="text" name="father-first_name" id="id_father-first_name" /></li> <li><label for="id_father-last_name">Last name:</label> <input type="text" name="father-last_name" id="id_father-last_name" /></li> + +The prefix can also be specified on the form class:: + + >>> class PersonForm(forms.Form): + ... ... + ... prefix = 'person' + +.. versionadded:: 1.9 + + The ability to specify ``prefix`` on the form class was added. diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 5b58f79..f9bb9de 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -161,6 +161,9 @@ Forms :attr:`~django.forms.Form.field_order` attribute, the ``field_order`` constructor argument , or the :meth:`~django.forms.Form.order_fields` method. +* A form prefix can be specified inside a form class, not only when + instantiating a form. See :ref:`form-prefix` for details. + Generic Views ^^^^^^^^^^^^^ diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index 690f205..e07fae2 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -1671,6 +1671,18 @@ class FormsTestCase(SimpleTestCase): self.assertEqual(p.cleaned_data['last_name'], 'Lennon') self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9)) + def test_class_prefix(self): + # Prefix can be also specified at the class level. + class Person(Form): + first_name = CharField() + prefix = 'foo' + + p = Person() + self.assertEqual(p.prefix, 'foo') + + p = Person(prefix='bar') + self.assertEqual(p.prefix, 'bar') + def test_forms_with_null_boolean(self): # NullBooleanField is a bit of a special case because its presentation (widget) # is different than its data. This is handled transparently, though.
完成预览修补后,
q
按键返回命令行。如果补丁的内容看起来没问题,那么是时候提交更改了。提交补丁中的更改¶
提交更改:
$ git commit -a
这将打开一个文本编辑器来键入提交消息。遵循提交消息准则并编写如下消息:
Fixed #24788 -- Allowed Forms to specify a prefix at the class level.
推送提交并发出拉取请求¶
提交补丁后,将其发送到GitHub上的分支(如果不同,请将“ticket_24788”替换为分支的名称):
$ git push origin ticket_24788
您可以通过访问Django GitHub页面来创建拉取请求。你会在“你最近被推动的分支”下看到你的分支。点击旁边的“比较并提取请求”。
请不要为本教程执行此操作,但在显示修补程序预览的下一页上,您将单击“创建拉取请求”。
后续步骤¶
恭喜,您已经学会了如何向Django发出拉取请求!您可能需要的更高级技术的详细信息在 使用Git和GitHub。
现在,您可以通过帮助改进Django的代码库来充分利用这些技能。
有关新贡献者的更多信息¶
在你为Django编写补丁之前,还有一些关于贡献的更多信息,你应该看一下:
- 您应该确保阅读Django关于声明票证和提交补丁的文档 。它涵盖了Trac礼仪,如何为自己索取门票,预期补丁的编码风格以及许多其他重要细节。
- 第一次贡献者也应该首次阅读Django的文档。对于我们这些刚接触Django的人来说,它有很多很好的建议。
- 在那之后,如果您仍然渴望了解有关贡献的更多信息,您可以随时浏览Django关于贡献的其余 文档。它包含大量有用的信息,应该是您回答任何问题的第一手资料。
-
-