有兴趣为社区做出点贡献吗? 也许你会在Django中发现你想要修复的漏洞,或者你希望为它添加一个小功能。
为Django作贡献这件事本身就是使你的顾虑得到解决的最好方式。 一开始这可能会使你怯步,但事实上是很简单的。 整个过程中我们会一步一步为你解说,所以你可以通过例子学习。
请参见
如果您正在寻找有关如何提交修补程序的参考,请参阅Submitting patches文档。
使用教程前,我们希望你至少对于Django的运行方式有基础的了解。 这意味着你可以自如地在编写你的第一个Django应用程序时使用教程。 除此之外,你应该对于Python本身有很好的了解。 如果您并不太了解, 我们为您推荐Dive Into Python,对于初次使用Python的程序员来说这是一本很棒(而且免费)的在线电子书。
对于版本控制系统及Trac不熟悉的人来说,这份教程及其中的链接所包含的信息足以满足你们开始学习的需求。 然而,如果你希望定期为Django贡献,你可能会希望阅读更多关于这些不同工具的信息。
当然对于其中的大部分内容,Django会尽可能做出解释以帮助广大的读者。
获得帮助:
如果你在使用本教程时遇到困难,你可以发送信息给django-developers 或者登陆 #django-dev on irc.freenode.net 向其他Django使用者需求帮助。
一开始我们会帮助你为Django编写补丁, 在教程结束时,你将具备对于工具和所包含过程的基本了解。 准确来说,我们的教程将包含以下几点:
一旦你完成了这份教程,你可以浏览剩下的Django’s documentation on contributing. 它包含了大量信息。任何想成为Django的正式贡献者必须去阅读它。 如果你有问题,它也许会给你答案
Python 3需要!
本教程假定您使用的是Python 3。 在Python的下载页面或您的操作系统的软件包管理器获取最新版本。
对于Windows用户
在Windows上安装Python时,请确保选中“将python.exe添加到路径”选项,以便在命令行中始终可用。
使用教程前,你需要安装好Git,下载Django的最新开发版本并且为你作出的改变生成补丁文件
为了确认你是否已经安装了Git, 输入 git
进入命令行。 如果信息提示命令无法找到, 你就需要下载并安装Git, 详情阅读 Git’s download page.
对于Windows用户
在Windows上安装Git时,建议您选择“Git Bash”选项,以便Git在自己的shell中运行。 本教程假定您已安装它。
如果你还不熟悉 Git, 你可以在命令行下输入 git help
了解更多关于它的命令(确认已安装)。
为Django贡献的第一步就是获取源代码复本。
首先,在gitHub上的fork 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用户
如果您还在Windows上使用Git Bash shell,则使用内置的venv
模块将不起作用,因为激活脚本仅为系统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作为案例研究,因此我们将在git之前将Django的版本历史倒转到该票证的修补程序被应用之前。 这样的话我们就可以参与到从草稿到补丁的所有过程,包括运行Django的测试套件。
请记住,为了下面的教程,我们将使用Django的主干旧版本,当您使用自己的补丁程序时,应始终使用Django的当前开发版本!
注
这张票的补丁是由PawełMarczewski编写的,它被应用于Django作为commit 4df7e8483b2679fc1cba3410f08960bac6f51115。 因此,我们将在之前使用Django的修订版,commit 4ccfc4439a7add24f8db4ef3960d02ef8ae09887。
首先打开Django源码的根目录(这个目录包含了 tests
, django
, AUTHORS
, docs
, 等) 然后你你可以根据下面的教程check out老版本的Django:
$ git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887
当你贡献代码给Django的时候,一个非常重要的问题就是你修改的代码不要给其他部分引入新的bug。 有个办法可以在你更改代码之后检查Django是否能正常工作,就是运行Django的测试套件。 如果所有的测试用例都通过,你就有理由相信你的改动完全没有破坏Django。 如果你从来没有运行过Django的测试套件,那么比较好的做法是事先运行一遍,熟悉下正常情况下应该输出什么结果。
运行测试套件之前,先将cd
-ing安装到Django tests/
目录中,然后运行:
$ pip install -r requirements/py3.txt
如果在安装期间遇到错误,则您的系统可能缺少一个或多个Python包的依赖关系。 请参阅失败的软件包文档或使用您遇到的错误消息搜索Web。
现在我们准备运行测试套件。 如果您使用GNU / Linux,macOS或其他一些风格的Unix,请运行:
$ ./runtests.py
现在坐下来放松一下。 Django的整个测试套件有超过9600种不同的测试,因此可能需要5到15分钟的时间才能运行,具体取决于您的计算机的速度。
Django的测试套件运行时,您将看到一个字符流代表每个测试的运行的状态。 E
表示测试中出现异常 和 F
表示断言失败。
这两种情况都被认为测试失败。 同时,x
和 s
分别表示与期望结果不同和跳过测试。 点表示测试通过。
跳过的测试通常是由于缺少运行测试所需的外部库;请参阅Running all the tests以获得依赖关系列表,并确保安装任何与您正在进行的更改相关的测试(本教程不需要任何内容)。 一些测试特定于特定的数据库后端,如果没有使用该后端测试,则会跳过此测试。 SQLite是默认设置的数据库后端。 要使用其他后端运行测试,请参阅Using another settings module。
当测试执行完毕后,得到反馈信息显示测试已通过,或者测试失败。 因为还没有对 Django 的源码做任何修改,所有的测试用例应该测试通过。 如果测试失败或出现错误,回头确认以上执行操作是否正确。 查看 Running the unit tests 获取更多信息。 如果您使用的是Python 3.5+,则会出现与您可以忽略的弃用警告相关的几个故障。 这些失败已经在Django中被修复了。
注意最新版本 Django 分支不总稳定。 当在分支上开发时,你可以查看代码持续集成构建页面的信息 Django’s continuous integration builds 来判断测试错误只在你指定的电脑上发生,还是官方版本中也存在该错误。 如果点击某个构建信息,可以通过配置列表信息查看错误发生时 Python 以及后端数据库的信息。
注
在本教程以及所用分支中,测试使用数据库 SQLite 即可, 然而在某些情况下需要 run the tests using a different database。
在进行任何更改之前,请为该机票创建一个新的分支:
$ git checkout -b ticket_24788
您可以为分支选择任何名称,“ticket_24788”就是一个例子。 该分支中所做的所有更改将特定于故障单,不会影响我们之前克隆的代码的主要副本。
大多数情况下,Django 的补丁必需包含测试。 Bug 修复补丁的测试是一个回归测试,确保该 Bug 不会再次在 Django 中出现。 该测试应该在 Bug 存在时测试失败,在 Bug 已经修复后通过测试。 新功能补丁的测试必须验证新功能是否正常运行。 新功能的测试将在功能正常时通过测试,功能未执行时测试失败。
最好的方式是在修改代码之前写测试单元代码。 这种开发风格叫做 测试驱动开发 被应用在项目开发和单一补丁开发过程中。 测试单元编写完毕后,执行测试单元,此时测试失败(因为目前还没有修复 BuG 或 添加新功能), 如果测试成功通过,你需要重新修改测试单元保证测试失败。 然而测试单元并没有阻止 BUG 发生的作用。
现在我们的操作示例。
票#24788提出了一个小功能添加:能够在Form类上指定类级别属性prefix
,以便:
[…] 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.
为了解决这张票,我们将在BaseForm
类中添加一个prefix
属性。 当创建此类的实例时,将前缀传递给__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
参数仍然有效。
但这个测试的东西看起来有点难吗
如果你没有写过测试,第一眼看上去测试代码会有点难。 幸运的是测试在编程里是一个 非常 重要的部分, 因此下面有更多的相关信息:
unittest
文档。记住,我们还没有对BaseForm
进行任何修改,所以我们的测试将会失败。 我们来运行forms_tests
文件夹中的所有测试,以确保真的发生了什么。 在命令行中 cd
Django 的 tests/
目录并执行:
$ ./runtests.py forms_tests
如果测试运行正常,则应该看到与我们添加的测试方法相对应的一个故障。 如果所有测试方法都正常通过,请检查上面的测试方法是否添加到了正确的文件位置。
接下来,我们将把Ticket #24788中描述的功能添加到Django。
导航到django/django/forms/
文件夹,然后打开forms.py
文件。
在第72行找到BaseForm
类,并在field_order
属性之后添加prefix
类属性:
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__
方法中添加条件语句。 Go ahead
and change self.prefix = prefix
that is now on line 87 of
django/forms/forms.py
, adding a conditional statement:
if prefix is not None:
self.prefix = prefix
重新运行测试方法正常会通过测试。 如果没有,请确保您正确地修改了BaseForm
类,并正确复制了新的测试。
如果已经确认补丁以及测试结果都正常,现在是时候运行 Django 完整的测试用例,验证你的修改是否对 Django 的其他部分造成新的 Bug。 虽然测试用例帮助识别容易被人忽略的错误,但测试通过并不能保证完全没有 Bug 存在。
运行 Django 完整的测试用例, cd
Django 下 tests/
目录并执行:
$ ./runtests.py
只要没有看到测试异常,你可以继续下一步骤。
这个新功能信息应该被记录到文档。 在django/docs/ref/forms/api.txt
的行1068(文件末尾)添加以下部分:
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.
由于这个新功能将在即将发布的版本中,它也被添加到文件docs/releases/1.9.txt
中的“Forms”部分的第164行Django 1.9的发行说明中:
* A form prefix can be specified inside a form class, not only when
instantiating a form. See :ref:`form-prefix` for details.
关于 versionadded
的解释以及文档编写的更多信息,请参考 Writing documentation。 这个页面还介绍了怎么在本地重新生成一份文档,你可以查看新生成的 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
这将打开一个文本编辑器来键入提交消息。 按照commit message guidelines,并写下如下消息:
Fixed #24788 -- Allowed Forms to specify a prefix at the class level.
提交修补程序后,将其发送到GitHub上的分支(如果不同,则将其替换为“ticket_24788”)。
$ git push origin ticket_24788
您可以通过访问Django GitHub页面来创建提取请求。 您将在“您最近推荐的分行”下看到您的分支。 点击旁边的“比较和拉动请求”。
请不要为此教程,但在下一页显示修补程序的预览,您将单击“创建拉请求”。
恭喜,您已经学会了如何向Django提出请求! 您可能需要的更多高级技术的细节在Working with Git and GitHub。
现在,您可以通过帮助改进Django的代码库来使这些技能很好用。
在你开始为 Django 编写补丁时,这里有些信息,你应该看一看:
一旦你看过了之前那些信息,你便已经具备了走出困境,为自己编写补丁寻找门票的能力。 对于那些有着“容易获得”标准的门票要尤其注意。 这些门票实际上常常很简单而且对于第一次撰写补丁的人很有帮助。 一旦你熟悉了给Django写补丁,你就可以进一步为更难且更复杂的门票写补丁。
如果你只想开始上手(没有人会怪你!),请尝试查看需要补丁的简单票据列表和需要改进的补丁的简单票据。 如果你比较擅长写测试,那么你也可以看看这个 需要测试的简单标签列表. 一定要记得遵循在Django的文档claiming tickets and submitting patches中提到的关于声明标签的指导规则.
一旦一个标签有了补丁,那么它就需要其他人来重审。 提交拉动请求后,通过设置机票上的标志来更新机票元数据,以说“有补丁”,“不需要测试”等,以便其他人可以找到它进行审查。 从零开始写补丁并不是做贡献的唯一方式。 重审一些已经存在的补丁也是一种非常有用的做贡献方式。 点击Triaging tickets 查看更多详细信息.
2017年9月6日