在使用Django以前,您须要安装它。咱们有 完整的安装指南,涵盖全部可能性; 本指南将指导您进行简单,最小化的安装,在您完成介绍时能够正常工做。css
做为一个Python Web框架,Django须要Python。请参阅 Django能够使用哪些Python版本?详情。Python包含一个名为SQLite的轻量级数据库,所以您不须要设置数据库。html
在https://www.python.org/downloads/或使用操做系统的软件包管理器获取最新版本的Python 。node
关于Jython的Djangopython
若是您使用Jython(Java平台的Python实现),则须要执行一些额外的步骤。有关详细信息,请参阅在Jython上运行Django。mysql
您能够经过python
从shell 输入来验证是否已安装Python ; 你应该看到相似的东西:linux
Python 3.4.x [GCC 4.x] on linux Type "help", "copyright", "credits" or "license" for more information. >>>
>>>
若是要从先前版本升级Django的安装,则须要在安装新版本以前卸载旧的Django版本。程序员
你有三个简单的选项来安装Django:github
请始终参考与您正在使用的Django版本对应的文档!web
若是您执行前两个步骤中的任何一个,请留意开发版本中标记为新文档的部分文档。该短语标记仅在Django的开发版本中可用的功能,而且它们可能不适用于正式版本。
要验证Python能够看到Django,请python
从shell中输入。而后在Python提示符下,尝试导入Django:
>>> import django >>> print(django.get_version()) 1.11
让咱们经过例子来学习。
在本教程中,咱们将引导您完成基本轮询应用程序的建立。
它由两部分组成:
咱们假设你已经安装了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
文件中包含如下代码:
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
列表中插入,因此你有:
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模式进行全局更改。
本教程从教程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
文件,使其以下所示:
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
设置中。它看起来像这样:
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;
请注意如下事项:
polls
)和模型的小写名字- question
和 choice
。(您能够覆盖此行为。)"_id"
到外键字段名称。(是的,你也能够覆盖它。)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
)并针对您的数据库运行它们 - 实际上,您将对模型所作的更改与模型中的模式同步数据库。
迁移功能很是强大,您能够在开发项目时随时更改模型,而无需删除数据库或表并建立新数据库 - 它专门用于实时升级数据库,而不会丢失数据。咱们将在本教程的后续部分中更深刻地介绍它们,可是如今,请记住进行模型更改的三步指南:
models.py
)。python manage.py makemigrations
python manage.py migrate
之因此有单独的命令来制做和应用迁移是由于您将提交迁移到您的版本控制系统并随应用程序一块儿发送; 它们不只使您的开发更容易,并且还能够被其余开发人员和生产中使用。
阅读django-admin文档,了解该manage.py
实用程序能够执行的操做的完整信息。
如今,让咱们进入交互式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
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方法。让咱们添加一个自定义方法,仅用于演示:
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解决了为站点管理员建立统一界面以编辑内容的问题。
管理员不打算由网站访问者使用。它适用于网站管理员。
首先,咱们须要建立一个能够登陆管理站点的用户。运行如下命令:
$ 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.contrib.auth
由Django 提供的身份验证框架提供。
可是咱们的投票应用程序在哪里?它不会显示在管理员索引页面上。
只需作一件事:咱们须要告诉管理员Question
对象有一个管理界面。为此,请打开该polls/admin.py
文件,而后将其编辑为以下所示:
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管理员对此对象所作的全部更改,以及进行更改的人员的时间戳和用户名:
本教程从教程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.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 / 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个轮询问题,以逗号分隔:
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指向正确的,而且确保这一点的最简单方法是经过命名它们。也就是说,将这些模板放在为应用程序自己命名的另外一个目录中。
将如下代码放在该模板中:
{% 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
以使用模板:
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()
视图,重写:
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
给定上下文呈现的给定模板的对象。
如今,让咱们解决问题详细信息视图 - 显示给定民意调查的问题文本的页面。这是观点:
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
模板中添加的内容,可是若是您但愿快速得到上述示例,则只需包含如下内容的文件:
{{ question }}
会让你如今开始。
get_object_or_404()
若是对象不存在,使用get()
和提高这是一个很是常见的习惯用法Http404
。Django提供了一个捷径。这是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})
该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>
模板系统使用点查找语法来访问变量属性。在示例中,首先Django对该对象进行字典查找。若是失败了,它会尝试进行属性查找 - 在这种状况下能够正常工做。若是属性查找失败,它将尝试列表索引查找。{{ question.question_text }}
question
方法调用在循环中发生: 被解释为Python代码 ,它返回一个可迭代的对象,适合在标记中使用。{% for%}
question.choice_set.all
question.choice_set.all()
Choice
{% for %}
请记住,当咱们在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'), ...
教程项目只有一个应用程序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/index.html
模板:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
指向命名空间详细信息视图:
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
本教程从教程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>
快速简要说明:
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
标记通过循环的次数{% csrf_token %}
如今,让咱们建立一个Django视图来处理提交的数据并对其进行处理。请记住,在教程3中,咱们为包含如下行的民意调查应用程序建立了一个URLconf:
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
咱们还建立了该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,)))
此代码包含咱们在本教程中还没有涉及的一些内容:
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代码来编写应用程序的程度。
让咱们将咱们的民意调查应用程序转换为使用通用视图系统,这样咱们就能够删除一堆本身的代码。咱们只须要采起一些步骤进行转换。咱们会:
继续阅读以了解详情。
为何代码洗牌?
一般,在编写Django应用程序时,您将评估通用视图是否适合您的问题,而且您将从一开始就使用它们,而不是在中途重构代码。但本教程有意集中于直到如今“以艰难的方式”编写视图,专一于核心概念。
在开始使用计算器以前,您应该先了解基本数学。
首先,打开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使用您想要的变量要容易得多。
运行服务器,并使用基于通用视图的新轮询应用程序。
有关通用视图的完整详细信息,请参阅通用视图文档。
本教程从教程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提供了一个测试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
s pub_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中的测试。
本教程从教程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引用。部署静态文件讨论了如何在真实服务器上使用静态文件。
本教程从教程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', ], )
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
pip uninstall django-polls
如今咱们已经打包并进行了测试django-polls
,它已准备好与全世界分享!若是这不只仅是一个例子,你如今能够:
以前,咱们将民意调查应用程序安装为用户库。这有一些缺点:
一般,只有在维护多个Django项目时才会出现这些状况。当他们这样作时,最好的解决方案是使用virtualenv。此工具容许您维护多个隔离的Python环境,每一个环境都有本身的库和包命名空间的副本。
因此你已经阅读了全部的介绍材料,并决定你继续使用Django。咱们只是简单介绍了这个介绍(事实上,若是你读过每个单词,你已经阅读了大约5%的总体文档)。
下一个是什么?
好吧,咱们一直都是经过实践学习的忠实粉丝。在这一点上,你应该知道本身开始一个项目并开始愚弄。当您须要学习新技巧时,请回到文档。
咱们付出了不少努力使Django的文档变得有用,易于阅读并尽量完整。本文档的其他部分将详细介绍文档的工做原理,以便您能够充分利用它。
(是的,这是关于文档的文档。请放心,咱们没有计划编写有关如何阅读有关文档的文档的文档。)
Django的主要文档被分解为“块”,旨在知足不一样的需求:
在介绍性的材料是专为人们新的Django的-或者Web开发通常。它没有深刻介绍任何内容,而是对Django的“感受”发展进行了高级概述。
该主题将指导,另外一方面,深潜入的Django的各个部分。有完整的Django 模型系统,模板引擎,表单框架等指南。
这多是你想要花费大部分时间的地方; 若是你经过这些指南工做,你应该知道几乎全部关于Django的知识。
Web开发一般很普遍,而不是很深刻 - 跨越许多领域的问题。咱们编写了一套操做指南来回答常见的“我如何......?”的问题。在这里,您能够找到有关 使用Django生成PDF,编写自定义模板标签等信息。
答案很是常见的问题,也能够在发现的常见问题。
指南和操做方法不包括Django中可用的每一个类,功能和方法 - 当您尝试学习时,这将是压倒性的。相反,有关各个类,函数,方法和模块的详细信息保留在参考中。您能够在此处查找特定功能的详细信息或您须要的任何内容。
最后,有一些“专业”文档一般与大多数开发人员无关。这包括那些想要向Django自己添加代码的人的发行说明和 内部文档,以及其余一些不适合其余地方的东西。
正如Django代码库天天都在开发和改进同样,咱们的文档也在不断改进。咱们改进文档有如下几个缘由:
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文档的本地副本:
如前所述,咱们的Git存储库中的文本文档包含“最新和最好的”更改和添加。这些更改一般包括Django开发版本中添加的新功能的文档 - Django的Git(“trunk”)版本。出于这个缘由,值得指出咱们关于保持框架的各类版本的文档的政策。
咱们遵循这一政策:
有兴趣回馈一下社区吗?也许您在Django中发现了一个您但愿修复的错误,或者您想添加一个小功能。
回馈Django自己是了解本身关注的最佳方式。这一开始看起来使人生畏,但它真的很简单。咱们将引导您完成整个过程,以便您能够经过示例学习。
也能够看看
若是要查找有关如何提交修补程序的参考,请参阅 提交修补程序 文档。
对于本教程,咱们但愿您至少对Django的工做原理有基本的了解。这意味着您应该熟悉编写第一个Django应用程序的现有教程。另外,你应该对Python自己有一个很好的理解。可是,若是你不这样作,Dive Into Python是一本很是棒的(免费)在线书籍,适合初学Python程序员。
那些不熟悉版本控制系统和Trac的人会发现本教程及其连接包含足够的信息以便入门。可是,若是您计划按期为Django作贡献,您可能但愿阅读更多有关这些不一样工具的内容。
可是,在大多数状况下,本教程试图尽量地解释,以便它能够用于最普遍的受众。
哪里能够得到帮助:
若是您在阅读本教程时遇到问题,请向django-developers发送消息,或者经过irc.freenode.net上的#django-dev与其余可能提供帮助的Django用户聊天。
咱们将第一次带您到Django的补丁。在本教程结束时,您应该对所涉及的工具和过程有基本的了解。具体来讲,咱们将涵盖如下内容:
完成本教程后,您能够查看Django关于贡献的其他 文档。它包含不少很棒的信息,对于那些想成为Django常规贡献者的人来讲,必读。若是你有问题,可能会获得答案。
须要Python 3!
本教程假设您使用的是Python 3.在Python的下载页面或使用操做系统的软件包管理器获取最新版本 。
对于Windows用户
在Windows上安装Python时,请确保选中“将python.exe添加到路径”选项,以便它始终在命令行中可用。
对于本教程,您须要安装Git才能下载Django的当前开发版本,并为您所作的更改生成补丁文件。
要检查是否安装了Git,请进入git
命令行。若是您收到消息说没法找到此命令,则必须下载并安装它,请参阅Git的下载页面。
对于Windows用户
在Windows上安装Git时,建议您选择“Git Bash”选项,以便Git在本身的shell中运行。本教程假设您已经安装了它。
若是您对Git不熟悉,能够经过在命令行输入来了解有关其命令(一旦安装)的更多信息。git help
为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版本如今指向您的本地副本。您将当即看到对其所作的任何更改,这在编写第一个补丁时很是有用。
在本教程中,咱们将使用#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的测试套件,那么最好先预先运行一次,以便熟悉其输出应该是什么样子。
在运行测试套件以前,首先将其依赖项安装cd
到Django tests/
目录中,而后运行:
$ 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提出了一个小功能添加:可以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
进入Django tests/
目录并运行:
$ ./runtests.py forms_tests
若是测试运行正确,您应该看到与咱们添加的测试方法相对应的一个失败。若是全部测试都经过了,那么您须要确保将上面显示的新测试添加到相应的文件夹和类中。
接下来,咱们将把票号#24788中描述的功能添加到Django。
导航到该django/django/forms/
文件夹并打开该forms.py
文件。BaseForm
在第72行找到该类,并在prefix
属性后面添加class field_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
进入Django tests/
目录并运行:
$ ./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测试套件,cd
进入Django tests/
目录并运行:
$ ./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编写补丁以前,还有一些关于贡献的更多信息,你应该看一下: