django1.11入门

快速安装指南

在使用Django以前,您须要安装它。咱们有 完整的安装指南,涵盖全部可能性; 本指南将指导您进行简单,最小化的安装,在您完成介绍时能够正常工做。css

安装Python¶

做为一个Python Web框架,Django须要Python。请参阅 Django能够使用哪些Python版本?详情。Python包含一个名为SQLite的轻量级数据库,所以您不须要设置数据库。html

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

关于Jython的Djangopython

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

您能够经过python从shell 输入来验证是否已安装Python 你应该看到相似的东西:linux

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

设置数据库

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

删除任何旧版本的Django¶

若是要从先前版本升级Django的安装,则须要在安装新版本以前卸载旧的Django版本程序员

安装Django¶

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

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

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

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

验证

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

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

 

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

让咱们经过例子来学习。

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

它由两部分组成:

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

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

$ python -m django --version

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

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

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

 

建立项目

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

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

$ django-admin startproject mysite

这将mysite在当前目录中建立一个目录。若是它不起做用,请参阅运行django-admin的问题

注意

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

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

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

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

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

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

这些文件是:

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

开发服务器

让咱们验证您的Django项目是否有效。mysite若是还没有更改到外部目录,请运行如下命令:

$ python manage.py runserver

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

Performing system checks...

System check identified no issues (0 silenced).

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

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

 

注意

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

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

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

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

改变端口

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

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

$ python manage.py runserver 8080

若是要更改服务器的IP,请将其与端口一块儿传递。例如,要监听全部可用的公共IP(若是您正在运行Vagrant或想要在网络上的其余计算机上展现您的工做,这颇有用),请使用:

$ python manage.py runserver 0:8000

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

自动重装 runserver

开发服务器根据须要自动为每一个请求从新加载Python代码。您无需从新启动服务器便可使代码更改生效。可是,某些操做(如添加文件)不会触发从新启动,所以在这些状况下您必须从新启动服务器。

建立民意调查应用

既然你的环境 - 一个“项目” - 已经创建起来,你就能够开始工做了。

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

项目与应用

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

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

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

$ python manage.py startapp polls

那将建立一个目录polls,其布局以下:

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

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

写下你的第一个视图

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

from django.http import HttpResponse


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

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

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

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

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

from django.conf.urls import url

from . import views

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

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

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

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

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

背后的想法include()是使即插即用的URL变得容易。因为民意调查位于他们本身的URLconf(polls/urls.py)中,所以能够将它们放在“/ polls /”下,或“/ fun_polls /”下,或“/ content / polls /”下,或任何其余路径根目录下,而且应用程序仍然能够工做。

何时用 include()

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

与你看到的不符?

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

您如今已将index视图链接到URLconf。让咱们验证它是否正常工做,运行如下命令:

$ python manage.py runserver

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

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

url()参数:正则表达式

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

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

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

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

url()参数:视图

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

url()参数:kwargs 

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

url()参数:名称

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

 

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

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

数据库设置

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

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

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

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

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

对于SQLite之外的数据库

若是您使用的是除SQLite以外的数据库,请确保此时已建立数据库。在数据库的交互式提示中使用“ ” 执行此操做。CREATEDATABASE database_name;

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

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

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

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

默认状况下,INSTALLED_APPS包含如下应用程序,全部这些应用程序都随Django一块儿提供:

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

可是,其中一些应用程序至少使用了一个数据库表,所以咱们须要先在数据库中建立表,而后才能使用它们。为此,请运行如下命令:

$ python manage.py migrate

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

对于极简主义者

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

建立模型

如今咱们将定义您的模型 - 本质上是您的数据库布局,以及其余元数据。

哲学

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

这包括迁移 - 与Ruby On Rails不一样,例如,迁移彻底来自您的模型文件,而且基本上只是Django能够经过更新数据库模式以匹配您当前模型的历史记录。

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

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

from django.db import models


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


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

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

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

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

您能够使用可选的第一个位置参数 Field来指定一我的类可读的名称。这在Django的几个内省部分中使用,而且它兼做文档。若是未提供此字段,Django将使用机器可读的名称。在这个例子中,咱们只定义了一我的类可读的名称Question.pub_date对于此模型中的全部其余字段,字段的机器可读名称就足以做为其可读的名称。

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

Field也能够有各类可选参数; 在这种状况下,咱们将default值 设置votes为0。

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

激活模型

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

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

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

哲学

Django应用程序是“可插拔的”:您能够在多个项目中使用应用程序,而且能够分发应用程序,由于它们没必要绑定到给定的Django安装。

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

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

如今Django知道要包含该polls应用程序。让咱们运行另外一个命令:

$ python manage.py makemigrations polls

您应该看到相似于如下内容的内容:

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

经过运行makemigrations,您告诉Django您已对模型进行了一些更改(在这种状况下,您已经建立了新模型),而且您但愿将更改存储为迁移

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

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

$ python manage.py sqlmigrate polls 0001

您应该看到相似于如下内容的东西(为了便于阅读,咱们从新格式化了它):

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

COMMIT;

请注意如下事项:

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

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

如今,migrate再次运行以在数据库中建立这些模型表:

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

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

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

之因此有单独的命令来制做和应用迁移是由于您将提交迁移到您的版本控制系统并随应用程序一块儿发送; 它们不只使您的开发更容易,并且还能够被其余开发人员和生产中使用。

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

使用API¶

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

$ python manage.py shell

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

绕过manage.py

若是你不想使用manage.py,没问题。只需设置 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>Questionpolls/models.py__str__()QuestionChoice

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

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

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

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

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

import datetime

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


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

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

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

>>> from polls.models import Question, Choice

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

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

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

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

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

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

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

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

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

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

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

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

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

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

介绍Django管理员

哲学

为您的员工或客户生成管理网站以添加,更改和删除内容是繁琐的工做,不须要太多的创造力。出于这个缘由,Django彻底自动化为模型建立管理界面。

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

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

建立管理员用户

首先,咱们须要建立一个能够登陆管理站点的用户。运行如下命令:

$ python manage.py createsuperuser

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

Username: admin

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

Email address: admin@example.com

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

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

启动开发服务器

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

若是服务器没有运行,请启动它:

$ python manage.py runserver

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

因为默认状况下打开翻译,所以登陆屏幕可能会以您本身的语言显示,具体取决于您的浏览器设置以及Django是否有此语言的翻译。

进入管理站点

如今,尝试使用您在上一步中建立的超级用户账户登陆。你应该看到Django管理员索引页面:

Django管理员索引页面

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

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

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

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

from django.contrib import admin

from .models import Question

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

探索免费的管理功能

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

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这些观点略有不一样,由于他们提出了一个论点:

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

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

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

polls.urls经过添加如下url()调用这些新视图链接到模块中 

from django.conf.urls import url

from . import views

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

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

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

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

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

由于URL模式是正则表达式,因此对它们能够作什么没有限制。.html除非您愿意,不然无需添加URL cruft ,在这种状况下,您能够执行如下操做:

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

可是,不要这样作。这太傻了。

写出实际作某事的视图

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

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

全部Django都想要的是HttpResponse或者例外。

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

from django.http import HttpResponse

from .models import Question


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

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

可是这里存在一个问题:页面的设计在视图中是硬编码的。若是要更改页面的外观,则必须编辑此Python代码。所以,让咱们使用Django的模板系统,经过建立视图能够使用的模板将设计与Python分离。

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

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

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

模板命名空间

如今咱们能够直接放入咱们的模板 polls/templates(而不是建立另外一个polls子目录),但实际上这是一个坏主意。Django将选择它找到的第一个名称匹配的模板,若是你在不一样的应用程序中有一个同名的模板,Django将没法区分它们。咱们须要可以将Django指向正确的,而且确保这一点的最简单方法是经过命名它们。也就是说,将这些模板放在为应用程序自己命名的另外一个目录中。

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

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

如今让咱们更新咱们的index视图polls/views.py以使用模板:

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

from .models import Question


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

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

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

快捷方式:render()

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

from django.shortcuts import render

from .models import Question


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

请注意,一旦咱们在全部这些观点作到了这一点,咱们再也不须要进口 loaderHttpResponse(你想保留HttpResponse,若是你仍然有存根方法detail, resultsvote)。

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

引起404错误

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

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

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

这里的新概念:Http404若是不存在具备请求ID的问题,则视图会引起异常。

稍后咱们将讨论您能够在该polls/detail.html模板中添加的内容,可是若是您但愿快速得到上述示例,则只需包含如下内容的文件:

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

会让你如今开始。

快捷方式:get_object_or_404()

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

from django.shortcuts import get_object_or_404, render

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

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

哲学

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

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

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

使用模板系统

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

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

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

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

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

删除模板中的硬编码URL¶

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

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

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

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

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

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

若是您想将民意调查详细信息视图的URL更改成其余内容,可能polls/specifics/12/会更改成在模板(或模板)中执行此操做,您能够将其更改成polls/urls.py

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

命名空间URL名称

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

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

from django.conf.urls import url

from . import views

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

如今更改您的polls/index.html模板:

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

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

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

 

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

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

写一个简单的表格

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

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

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

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

快速简要说明:

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

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

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

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

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

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

此代码包含咱们在本教程中还没有涉及的一些内容:

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

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

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

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

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

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

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

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

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

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

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

    如今,建立一个polls/results.html模板:

    <h1>{{ question.question_text }}</h1>
    
    <ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
    {% endfor %}
    </ul>
    
    <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
    polls/templates/polls/results.html

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

    注意

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

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

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

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

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

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

    让咱们将咱们的民意调查应用程序转换为使用通用视图系统,这样咱们就能够删除一堆本身的代码。咱们只须要采起一些步骤进行转换。咱们会:

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

    继续阅读以了解详情。

    为何代码洗牌?

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

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

    修改URL配置

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

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

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

    修改意见

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

    from django.shortcuts import get_object_or_404, render
    from django.http import HttpResponseRedirect
    from django.urls import reverse
    from django.views import generic
    
    from .models import Choice, Question
    
    
    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        context_object_name = 'latest_question_list'
    
        def get_queryset(self):
            """Return the last five published questions."""
            return Question.objects.order_by('-pub_date')[:5]
    
    
    class DetailView(generic.DetailView):
        model = Question
        template_name = 'polls/detail.html'
    
    
    class ResultsView(generic.DetailView):
        model = Question
        template_name = 'polls/results.html'
    
    
    def vote(request, question_id):
        ... # same as above, no changes needed.
    polls/views.py

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

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

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

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

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

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

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

     

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

    本教程从教程4中止的地方开始咱们已经构建了一个Web轮询应用程序,如今咱们将为它建立一些自动化测试。

    介绍自动化测试

    什么是自动化测试?

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

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

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

    为何须要建立测试

    那么为何要建立测试,为何如今呢?

    您可能以为本身已经足够了解Python / Django,而且还有另一些须要学习和作的事情可能看起来势不可挡,也许是没必要要的。毕竟,咱们的民意调查申请如今很是愉快; 经历建立自动化测试的麻烦不会让它更好地工做。若是建立民意调查应用程序是您将要作的最后一点Django编程,那么,您不须要知道如何建立自动化测试。可是,若是状况并不是如此,那么如今是学习的绝佳时机。

    测试会节省你的时间

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

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

    当自动化测试能够在几秒钟内为您完成此操做时尤为如此。若是出现问题,测试还将有助于识别致使意外行为的代码。

    有时,若是您知道本身的代码工做正常,那么将本身从富有成效的创造性编程工做中剔除,以面对编写测试的无趣和使人兴奋的业务,这彷佛是件苦差事。

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

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

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

    若是没有测试,应用程序的目的或预期行为可能会至关不透明。即便它是你本身的代码,你有时会发现本身在试图找出它究竟在作什么。

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

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

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

    其余开发人员但愿在认真对待以前在软件中看到测试是您开始编写测试的另外一个缘由。

    测试帮助团队一块儿工做

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

    基本测试策略

    有不少方法能够用来编写测试。

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

    更常见的状况是,测试的新手将建立一些代码,而后决定它应该进行一些测试。也许最先写一些测试会好一些,可是历来没有太晚开始。

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

    因此让咱们立刻作。

    写第一次测试

    咱们发现了一个错误

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

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

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

    因为将来的事情不是“最近的”,这显然是错误的。

    建立测试以揭示错误

    咱们在shell测试问题时所作的正是咱们在自动化测试中能够作的,因此让咱们把它变成一个自动测试。

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

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

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

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

    运行测试

    在终端中,咱们能够运行咱们的测试:

    $ python manage.py test polls

    你会看到相似的东西:

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

    发生了什么事:

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

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

    修复错误

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

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

    并再次运行测试:

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

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

    咱们的应用程序未来可能会出现许多其余问题,但咱们能够确定,咱们不会无心中从新引入此错误,由于只需运行测试就会当即向咱们发出警告。咱们能够认为应用程序的这一小部分永远安全地固定下来。

    更全面的测试

    当咱们在这里时,咱们能够进一步肯定was_published_recently() 方法; 事实上,若是修复咱们引入另外一个错误的一个错误,那将是很是尴尬的。

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

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

    如今,咱们有三项测试证明能够Question.was_published_recently() 为过去,近期和将来的问题返回合理的价值。

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

    测试视图

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

    对视图的测试

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

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

    在咱们尝试解决任何问题以前,让咱们来看看咱们能够使用的工具。

    Django测试客户端

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

    咱们将从新开始shell,咱们须要作一些没必要要的事情tests.py首先是在如下位置设置测试环境shell

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

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

    接下来咱们须要导入测试客户端类(稍后tests.py咱们将使用django.test.TestCase该类,它带有本身的客户端,所以不须要):

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

    准备好后,咱们能够要求客户为咱们作一些工做:

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

    改善咱们的观点

    民意调查清单显示还没有公布的民意调查(即pub_date将来的民意调查 )。咱们来解决这个问题。

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

    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        context_object_name = 'latest_question_list'
    
        def get_queryset(self):
            """Return the last five published questions."""
            return Question.objects.order_by('-pub_date')[:5]
    polls/views.py

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

    from django.utils import timezone
    polls/views.py

    而后咱们必须get_queryset像这样修改方法:

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

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

    测试咱们的新视图

    如今,您能够经过启动runserver,在浏览器中加载站点,Questions在过去和未来建立日期以及检查是否仅列出已发布的日期来知足您本身的行为您不但愿每次进行任何可能影响此更改的更改时都这样作- 因此咱们也要根据上面的shell会话建立一个测试 

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

    from django.urls import reverse
    polls/tests.py

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

    def create_question(question_text, days):
        """
        Create a question with the given `question_text` and published the
        given number of `days` offset to now (negative for questions published
        in the past, positive for questions that have yet to be published).
        """
        time = timezone.now() + datetime.timedelta(days=days)
        return Question.objects.create(question_text=question_text, pub_date=time)
    
    
    class QuestionIndexViewTests(TestCase):
        def test_no_questions(self):
            """
            If no questions exist, an appropriate message is displayed.
            """
            response = self.client.get(reverse('polls:index'))
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, "No polls are available.")
            self.assertQuerysetEqual(response.context['latest_question_list'], [])
    
        def test_past_question(self):
            """
            Questions with a pub_date in the past are displayed on the
            index page.
            """
            create_question(question_text="Past question.", days=-30)
            response = self.client.get(reverse('polls:index'))
            self.assertQuerysetEqual(
                response.context['latest_question_list'],
                ['<Question: Past question.>']
            )
    
        def test_future_question(self):
            """
            Questions with a pub_date in the future aren't displayed on
            the index page.
            """
            create_question(question_text="Future question.", days=30)
            response = self.client.get(reverse('polls:index'))
            self.assertContains(response, "No polls are available.")
            self.assertQuerysetEqual(response.context['latest_question_list'], [])
    
        def test_future_question_and_past_question(self):
            """
            Even if both past and future questions exist, only past questions
            are displayed.
            """
            create_question(question_text="Past question.", days=-30)
            create_question(question_text="Future question.", days=30)
            response = self.client.get(reverse('polls:index'))
            self.assertQuerysetEqual(
                response.context['latest_question_list'],
                ['<Question: Past question.>']
            )
    
        def test_two_past_questions(self):
            """
            The questions index page may display multiple questions.
            """
            create_question(question_text="Past question 1.", days=-30)
            create_question(question_text="Past question 2.", days=-5)
            response = self.client.get(reverse('polls:index'))
            self.assertQuerysetEqual(
                response.context['latest_question_list'],
                ['<Question: Past question 2.>', '<Question: Past question 1.>']
            )
    polls/tests.py

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

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

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

    test_past_question,咱们建立一个问题并验证它是否出如今列表中。

    test_future_question,咱们pub_date未来会建立一个问题为每一个测试方法重置数据库,所以第一个问题再也不存在,所以索引不该该有任何问题。

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

    测试DetailView

    咱们的工做作得很好; 可是,即便将来的问题没有出如今索引中,若是用户知道或猜到正确的URL,他们仍然能够联系到他们。因此咱们须要添加一个相似的约束DetailView

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

    固然,咱们将增长一些测试,以检查一个Question,其 pub_date在过去能够显示,而一个具备pub_date 在将来是否是:

    class QuestionDetailViewTests(TestCase):
        def test_future_question(self):
            """
            The detail view of a question with a pub_date in the future
            returns a 404 not found.
            """
            future_question = create_question(question_text='Future question.', days=5)
            url = reverse('polls:detail', args=(future_question.id,))
            response = self.client.get(url)
            self.assertEqual(response.status_code, 404)
    
        def test_past_question(self):
            """
            The detail view of a question with a pub_date in the past
            displays the question's text.
            """
            past_question = create_question(question_text='Past Question.', days=-5)
            url = reverse('polls:detail', args=(past_question.id,))
            response = self.client.get(url)
            self.assertContains(response, past_question.question_text)
    polls/tests.py

    更多测试的想法

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

    咱们还能够经过其余方式改进咱们的应用程序,并在此过程当中添加测试。例如,Questions能够在没有的网站上发布它是愚蠢的Choices所以,咱们的观点能够检查这一点,并排除这种状况 Questions咱们的测试会建立一个Question没有Choices,而后测试它没有发布,以及建立一个相似Question  Choices,并测试它是否已发布。

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

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

    测试时,越多越好

    咱们的测试彷佛愈来愈失控。按照这个速度,咱们的测试中的代码很快会比咱们的应用程序中的代码更多,而且与其余代码的优雅简洁相比,重复是不美观的。

    不要紧让他们成长。在大多数状况下,您能够编写一次测试而后忘掉它。在您继续开发程序时,它将继续执行其有用的功能。

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

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

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

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

    进一步测试

    本教程仅介绍一些测试基础知识。你能够作不少事情,而且能够使用一些很是有用的工具来实现一些很是聪明的事情。

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

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

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

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

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

     

     

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

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

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

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

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

    自定义应用程序的外观

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

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

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

    静态文件命名空间

    就像模板同样,咱们可能可以直接放入咱们的静态文件polls/static(而不是建立另外一个polls 子目录),但这其实是个坏主意。Django将选择其名称匹配的第一个静态文件,若是在不一样的应用程序中有一个具备相同名称的静态文件,Django将没法区分它们。咱们须要可以将Django指向正确的,而且确保这一点的最简单方法是经过命名它们。也就是说,将这些静态文件放在为应用程序自己命名的另外一个目录中。

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

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

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

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

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

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

    添加背景图像

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

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

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

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

    警告

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

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

     

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

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

    自定义管理表单

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

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

    from django.contrib import admin
    
    from .models import Question
    
    
    class QuestionAdmin(admin.ModelAdmin):
        fields = ['pub_date', 'question_text']
    
    admin.site.register(Question, QuestionAdmin)
    polls/admin.py

    您将遵循此模式 - 建立模型管理类,而后将其做为第二个参数传递给admin.site.register()- 任什么时候候您须要更改模型的管理选项。

    上述特定更改使“发布日期”出如今“问题”字段以前:

    字段已从新排序

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

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

    from django.contrib import admin
    
    from .models import Question
    
    
    class QuestionAdmin(admin.ModelAdmin):
        fieldsets = [
            (None,               {'fields': ['question_text']}),
            ('Date information', {'fields': ['pub_date']}),
        ]
    
    admin.site.register(Question, QuestionAdmin)
    polls/admin.py

    每一个元组的第一个元素 fieldsets是fieldset的标题。这就是咱们如今的形式:

    表单如今有字段集

     

    高级教程:如何编写可重用的应用程序

    这个高级教程从教程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.barfrom fooimport barpolls__init__.py

    Django 应用程序只是一个专门用于Django项目的Python包。应用程序能够使用通用的Django约定,诸如具备modelstestsurls,和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来构建咱们的包。这是推荐的打包工具(与distributefork 合并)。咱们还将使用pip来安装和卸载它。您应该当即安装这两个包。若是您须要帮助,能够参考如何使用pip安装Django您能够setuptools 以相同的方式安装

    打包您的应用

    Python 打包是指以易于安装和使用的特定格式准备应用程序。Django自己的打包很是像这样。对于像民意调查这样的小应用程序,这个过程并不太难。

    1. 首先,polls在Django项目以外建立一个父目录调用此目录django-polls

      为您的应用选择一个名称

      为包选择名称时,请检查PyPI等资源,以免与现有包发生命名冲突。django-在建立要分发的包时,在模块名称前添加一般颇有用 这有助于其余寻找Django应用的用户将您的应用识别为特定于Django。

      应用程序标签(即应用程序包的虚线路径的最后部分)必须是惟一的INSTALLED_APPS避免使用相同的标签为任何Django的的的contrib包,例如authadmin或 messages

    2. polls目录移动django-polls目录中。

    3. 建立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/README.rst
    4. 建立一个django-polls/LICENSE文件。选择许可证超出了本教程的范围,但足以说明没有许可证公开发布的代码是无用的Django和许多兼容Django的应用程序是根据BSD许可证分发的; 可是,您能够自由选择本身的许可证。请注意,您的许可选择将影响谁能够使用您的代码。

    5. 接下来,咱们将建立一个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',
          ],
      )
      django-polls/setup.py

       

    6. 默认状况下,程序包中仅包含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/MANIFEST.in

       

    7. 它是可选的,但建议在您的应用程序中包含详细的文档。django-polls/docs为未来的文档建立一个空目录添加一行到django-polls/MANIFEST.in

      recursive-include docs *

      请注意,docs除非您向其中添加一些文件,不然目录将不会包含在您的包中。许多Django应用程序还经过readthedocs.org等网站在线提供文档

      1. 尝试使用(从内部运行 构建包这将建立一个名为的目录并构建您的新包python setup.py sdistdjango-pollsdistdjango-polls-0.1.tar.gz

      有关打包的更多信息,请参阅Python的打包和分发项目教程

      使用本身的包

      因为咱们将polls目录移出项目,所以再也不有效。咱们如今将经过安装咱们的新django-polls软件包解决这个问题

      做为用户库安装

      如下步骤django-polls做为用户库安装与在系统范围内安装软件包相比,每用户安装具备许多优点,例如能够在您没有管理员访问权限的系统上使用,以及防止软件包影响系统服务和机器的其余用户。

      请注意,每用户安装仍然会影响以该用户身份运行的系统工具的行为,所以virtualenv是一种更强大的解决方案(请参见下文)。

      1. 要安装软件包,请使用pip(您已安装它,对吧?):

        pip install --user django-polls/dist/django-polls-0.1.tar.gz

         

      2. 幸运的是,您的Django项目如今应该再次正常工做。再次运行服务器以确认这一点。
      3. 要卸载软件包,请使用pip:
        pip uninstall django-polls

        发布你的应用

        如今咱们已经打包并进行了测试django-polls,它已准备好与全世界分享!若是这不只仅是一个例子,你如今能够:

        使用virtualenv安装Python包

        以前,咱们将民意调查应用程序安装为用户库。这有一些缺点:

        • 修改用户库可能会影响系统上的其余Python软件。
        • 您将没法运行此软件包的多个版本(或其余具备相同名称的版本)。

        一般,只有在维护多个Django项目时才会出现这些状况。当他们这样作时,最好的解决方案是使用virtualenv此工具容许您维护多个隔离的Python环境,每一个环境都有本身的库和包命名空间的副本。

         

        接下来要读什么

        因此你已经阅读了全部的介绍材料,并决定你继续使用Django。咱们只是简单介绍了这个介绍(事实上,若是你读过每个单词,你已经阅读了大约5%的总体文档)。

        下一个是什么?

        好吧,咱们一直都是经过实践学习的忠实粉丝。在这一点上,你应该知道本身开始一个项目并开始愚弄。当您须要学习新技巧时,请回到文档。

        咱们付出了不少努力使Django的文档变得有用,易于阅读并尽量完整。本文档的其他部分将详细介绍文档的工做原理,以便您能够充分利用它。

        (是的,这是关于文档的文档。请放心,咱们没有计划编写有关如何阅读有关文档的文档的文档。)

        查找文档

        Django有不少文档 - 大约450,000个单词和数量 - 因此找到你须要的东西有时候会很棘手。一些好的开始是搜索页面索引

        或者你能够浏览一下!

        如何组织文档

        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文档的本地副本:

        • Django的文档使用一个名为Sphinx的系统将纯文本转换为HTML。您须要经过从Sphinx网站下载和安装软件包来安装Sphinx,或者使用pip

          $ pip install Sphinx
        • 而后,只需使用包含Makefile将文档转换为HTML:

          $ cd path/to/django/docs
          $ make html

          你须要为此安装GNU Make

          若是您使用的是Windows,则能够使用附带的批处理文件:

          cd path\to\django\docs
          make.bat html
        • HTML文档将被放入docs/_build/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添加到路径”选项,以便它始终在命令行中可用。

        行为准则

        做为贡献者,您能够帮助咱们保持Django社区的开放性和包容性。请阅读并遵照咱们的行为准则

        安装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, docstestsAUTHORS,等)。而后,您能够查看咱们将在下面的教程中使用的旧版Django:

        $ git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887

        第一次运行Django的测试套件

        在为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指示测试的断言失败。这两个都被认为是测试失败。同时,xs 表示预期的失败和分别跳过的测试。圆点表示经过测试。

        跳过的测试一般是因为缺乏运行测试所需的外部库; 请参阅运行全部测试以获取依赖项列表,并确保安装任何与您正在进行的更改相关的测试(本教程不须要任何测试)。某些测试特定于特定的数据库后端,若是不使用该后端进行测试,则会跳过这些测试。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在建立实例时传递参数仍然有效。

        可是这个测试的东西看起来有点难......

        若是您之前从未处理过测试,那么乍一看它们看起来有点难以写。幸运的是,测试是计算机编程中很是重要的主题,因此有不少信息:

        • 编写和运行测试的文档中能够找到为Django编写测试的第一眼
        • Dive Into Python(一本面向初学Python开发人员的免费在线书籍)包括对单元测试的精彩介绍
        • 阅读完这些内容以后,若是你想要一些稍微有点内容的东西,那么总会有Python unittest文档。

        运行新测试

        请记住,咱们还没有对其进行任何修改BaseForm,所以咱们的测试将失败。让咱们运行forms_tests 文件夹中的全部测试,以确保真正发生的事情。从命令行cd 进入Django tests/目录并运行:

        $ ./runtests.py forms_tests
        

        若是测试运行正确,您应该看到与咱们添加的测试方法相对应的一个失败。若是全部测试都经过了,那么您须要确保将上面显示的新测试添加到相应的文件夹和类中。

        编写机票代码

        接下来,咱们将把票号#24788中描述的功能添加到Django。

        编写故障单#24788的代码

        导航到该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 =prefixdjango/forms/forms.py

        if prefix is not None: self.prefix = prefix 

        从新运行测试,一切都应该经过。若是没有,请确保BaseForm如上所示正确修改了类并正确复制了新测试。

        第二次运行Django的测试套件

        一旦你确认你的补丁和测试工做正常,最好运行整个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编写补丁以前,还有一些关于贡献的更多信息,你应该看一下:

        • 您应该确保阅读Django关于声明票证和提交补丁的文档 它涵盖了Trac礼仪,如何为本身索取门票,预期补丁的编码风格以及许多其余重要细节。
        • 第一次贡献者也应该首次阅读Django的文档对于咱们这些刚接触Django的人来讲,它有不少很好的建议。
        • 在那以后,若是您仍然渴望了解有关贡献的更多信息,您能够随时浏览Django关于贡献的其他 文档它包含大量有用的信息,应该是您回答任何问题的第一手资料。

        找到你的第一张真票

        一旦你浏览了一些这些信息,你就能够出去找一张你本身的票来写一个补丁了。使用“轻松选择”标准特别注意门票。这些门票一般更简单,对于初次参与者来讲很是棒。一旦熟悉了对Django的贡献,您就能够继续为更复杂的故障单编写补丁。

        若是您只想开始使用(而且没有人会责怪您!),请尝试查看须要补丁简单票证列表以及包含须要改进的补丁的 简单票证若是您熟悉编写测试,还能够查看须要测试简单票证列表 请记住遵循关于声明票证的说明,这些票据是在Django的文档连接中提到的,用于 声明票证和提交补丁

        建立拉取请求后的下一步是什么?

        票证有补丁后,须要经过第二组眼睛进行审核。提交拉取请求后,经过将故障单上的标记设置为“有补丁”,“不须要测试”等来更新故障单元数据,以便其余人能够找到它进行查看。贡献并不必定意味着从头开始编写补丁。查看现有补丁也是一项很是有用的贡献。有关详细信息,请参阅分类票证

         

         

相关文章
相关标签/搜索