编写你的第一个djang补丁(page 15)
介绍
有兴趣为社区做一些贡献?可能你发现了django中的一个你想修复的bug,或者你你想添加一个小小的功能。
回馈django就是解决你遇到的问题的最好的方法。一开始这可能会让你望而生畏,但它真的很简单。我们会带你熟悉整个过程,所以你可以通过例子来学习。
本节教程面向的谁呢?
另请参见
如果你在寻找如何提交更新的参考资料,请查看文档Submitting patches(少一个链接)
在本节教程,我们假设你对django如何工作至少有了一个基本的了解。也就是说,你应该已经熟悉了已有的编写你的第一个django app的教程。另外,你应该对python本身有很好的理解。但如果你不熟悉,Dive Into Python是一本非常棒(且免费)的为python程序员新手准备的在线电子书。
对于不熟悉版本控系统和Trac的人来说,会发现本教程和他的链接包含足够的让你开始学习的信息。不管怎样,如果你计划定期为django贡献,你可能会想阅读更多关于这些不同工具的内容。
不过,大部分情况下,本教程试图尽可能多的去解释,以它可以用于最广泛的读者,
从哪里获取帮助:
如果你在使用本教程时遇到麻烦,请提交信息给django开发者或者登陆到 #django-dev on irc.freenode.net向有可能帮助你的django使用者寻求帮助。
本教程包含哪些内容?
第一次,我们会带领你给django编写一个补丁,教程的最后,你应该对涉及的工具和流程有一个基本的理解。具体来说,我们会涵盖一下内容:
- 安装Git
- 如何下载django的开发副本
- 运行django的测试套件
- 为你的补丁写一个测试
- 为你的补丁编写代码
- 测试你的补丁
- 提交一个pull请求
- 去哪查找更多的信息
一旦你完成本教程,你就可以学习剩下的 [Django’s documentation on contributing](file:///Users/resn/Desktop/django-docs-2.0-en/internals/contributing/index.html)了。它包含了大量信息,所有想变成django贡献者的人都必须阅读它。如果你遇到问题,它可能会给你答案。
需要使用python3
当前版本的django不再支持python2.7。去python下载页或者你的操作系统的包管理器获取python3。
windows用户
在windows中安装python时,去报你选中了“Add python.exe to Path”选项,这样在命令行就总是有效的了
代码规范
作为一个贡献者,你可以帮助我们保持django社区的开放性和包容性,请阅读我们的代码规范
安装Git
在本教程中,你需要安装Git以下载django当前的开发版本,并为你做的更改生成补丁文件。
检查你是否已经安装it,在命令行中输入git
。如果你得到的信息是找不到这个命令。你必须去下载并安装它,请参阅 Git下载页
windows用户
在windows上安装Git时,建议选择“Git Bash”选项,这样Git就会运行在它自己的shell中。本教程假定你已经安装它。
如果你不熟悉Git,你可以通过在命令行输入git help
来找出更多关于它的命令。
获取django开发版本的一个副本
为django做出共享的第一步是获取源代码副本。首先fork Django on GitHub。然后,在命令行,使用cd
命令进入你想要存放本地django副本的目录。
使用下面的命令下载django源代码库:
$ git clone git@github.com:YourGitHubName/django.git
现在你就有了一份django的本地副本,你可以安装它,就像你使用pip安装其他的包一样。最方便的方式是使用虚拟环境(或者virtualenv),虚拟环境是python的内置特性,它允许你为每一个项目保留一个单独的安装包目录,这样项目之间就不会互相干扰。
有一个好主意是把你的所有virtualenv保存在一个位置。例如,在你家目录下的.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
设置vitualenv的最后一步是激活它:
$ 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来作为学习用例,因此我们需要把git中django的版本回滚到问题补丁没有提交之前的版本。这会让我们参与进从头开始编写补丁的所有步骤中,包括正在运行的django测试组件。
请记住,虽然外面将在下面的教程中使用旧的django主干版本,但在处理自己的补丁时,你应该始终使用django的当前开发版本
注意
这里的补丁由Paweł Marczewski编写,并已经应用于django,提交标记4df7e8483b2679fc1cba3410f08960bac6f51115,因此,我们将使用在此之前的django版本,4ccfc4439a7add24f8db4ef3960d02ef8ae09887.
导航到djanmgo的根目录(它是包含django, docs, tests, AUTHORS等的目录)。你可以检出我们将在下面的教程中使用的django的旧版本:
git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887
第一次运行django的测试组件
在为django贡献代码时,非常重要的一点就是你的代码不要为django的其他部分引入bug。一种办法就是在你做了更改之后,运行django的测试组件,检查django是否可以正常工作。如果所有的测试都通过了,那么你有理由确信你的更改完全没有破坏django。如果你以前从未运行过django的测试组件,事先运行一次,以熟悉它的输出是什么样子的,会是一个很好的注意。
运行测试组件之前,先进入django的tests目录,并运行下面的命令安装依赖:
$ pip install -r requirements/py3.txt
如果在安装期间你突然遇到一个错误,这可能是你的系统没有安装一个或多个python包的依赖。请查阅失败包的文档或者在网上搜索你遇到的错误信息。
现在,我们准备肉运行测试组件,如果你使用的是GNU/Linux, macOS或者其他的UYnix版本,运行下面的命令:
$ ./runtests.py
现在坐下来放松一下,django的全部测试组件有超过9600中测试,因此,他可能需要运行5到15分钟,当然,这取决于你的计算机的速度。
当django的测试组件运行的时候,你可以看一个显示每个测试用例状态的字符串流。E 表示测试期间出现了一个错误,F 表示测试的断言失败。这两种情况都被认为是测试失败。同时,x 和 s 分别表示预期的失败和跳过的测试,圆点则表示测试通过。
跳过的测试通常是因为缺少运行测试的外部库;请检查运行所有测试需要使用的依赖的列表,确保安装了所有与你正在修改的相关的测试测所有依赖(本教程不需要额外安装任何依赖)。一些特定的测试用于测试数据库后端,如果不做后端测试,那么就会被跳过。SQLite是默认的数据库后端。运行其他后端的测试,请参阅https://docs.djangoproject.com/en/2.0/internals/contributing/writing-code/unit-tests/
一旦测试完成,你应该会收到一条消息,通知你测试套件通过还是未通过。由于你尚未对django的代码做任何更改,测试应该是通过的。如果遇到失败或者错误,请确保你按照前面步骤执行了所有操作。更多的信息请查看https://docs.djangoproject.com/en/2.0/internals/contributing/writing-code/unit-tests/。如果你使用的是Python 3.5+,你会遇到一些可以忽略的与弃用相关的失败内容,这些失败内容已经在django中进行了修复。
注意,最新的django主干版本可能不是稳定版。当针对主干进行开发时,你可以先检查一下django的持续集成,以确定故障是否仅在你的机器上发生,或者是否他们也存在于django的官方构建中。如果点击了查看特定构建,则可以看到“Configuration Matrix”,其中显示了细分后的关于Python版本和数据库后端的故障信息。
注意
对于本教程和正在处理的故障问题,测试SQLite是足够了,但是还有可能使用不同的数据库后端进行测试(有时测试必须的)。
为你的更新创建一个分支
在做出更改前,先为你的故障创建一个分支:
$ git checkout -b ticket_24788
你可以随意为你的分支命名,“ticket_24788”仅仅是一个示例。在这个分支中,所有的更改都只针对前面遇到的故障,且不会影响到之前我们克隆的代码的主副本。
为故障编写测试
在大多数情况下,对于要接受的django补丁,必须包含测试用例。对于bug修复补丁来说,编写回归测试以确保这个bug以后不会再重新被引入django中。回归测试要写成的样子是 bug存在时会失败,bug修复后会通过。对于包含新功能的补丁,你需要包含保证新功能正常工作的测试。他们也应该在新功能不存在时失败,新功能实现后通过。
实现这种操作的一种好方法是,在更改代码前先编写新的测试。这种开发方式成为 测试驱动开发,可以应用于整个项目或者是单个的补丁。在编写测试后,运行这些测试以确保他们执行失败(因为这里还没有修复bug或者添加新的功能)。如果新的测试执行时没有失败,那就需要对他们进行修复以便他们执行正确,毕竟,无论是否存在错误,都会通过回归测试来测试,这对防止错误的出现非常有帮助。
下面的例子我们亲自动手实现。
为工单 #24788 编写测试
工单#24788提出了添加一个小功能:能够在Form类上指定类级别属性prefix。
[...]与app一起提供的表单可以有效的命名自己,这样N个重叠的表单字段可以立即发布并解析为正确形式。
为了解决这个工单,我们需要为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参数人就可用。
这个测试看起来可能有些难
如果你没有编写过测试,乍一看,感觉会有点难写。幸运的是,在计算机编程中,测试是一个非常大的概念,因此有很多相关的内容:
- 为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文件。找到72上的 BaseForm类,在field_order属性之后添加prefix类属性。
class BaseForm:
# 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 方法中添加条件判断语句。修改django/forms/forms.py中的87行为self.prefix = prefix
,添加条件语句:
if prefix is not None:
self.prefix = prefix
重新运行测试,所有的测试应该都会通过。如果没有,确保你像前面展示的一样正确的修改了BaseForm类,并正确复制了新测试。
第二次运行Django的测试套件
当你验证了你的补丁和测试可以正常工作后,最好运行django的整个测试组件来检查你做的更改是否把其他bug引入了django的其他区域。虽然整个测试组件通过测试不代表你的代码没有问题,但它有助于检查出许多被忽视的bug。
cd进入Django的tests/目录,运行django的整个测试组件:
$ ./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.
由于这个新功能会出现在即将发布的新版本中,因为它会被添加到 Django1.9的发布说明中,位置在docs/releases/1.9.txt文件的164行,关于“Forms”的部分。
- 可以在form类中指定表单前缀,而不仅仅是在实例化form时,更多信息请参阅:ref:
form-prefix
有关编写文档的更多信息,包括有关版本versionadded的说明,请参阅https://docs.djangoproject.com/en/2.0/internals/contributing/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:
# 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:
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上的fork(如果不一样,可以使用你的分支名代替“ticket_24788):
$ git push origin ticket_24788
你可以通过访问Django GitHub页面来创建一个拉取请求。你会在“Your recently pushed branches”下看到你的分支,点击它旁边的“Compare & pull request” 。
在本教程范围内,你不要执行该操作,但在下个页面会显示补丁的预览,你可以点击 “Create pull request”。
下一步
祝贺你,你已经学会了向Django发送pull请求。更多高级技巧的信息可以查看使用Git和GitHub工作。
现在你可以通过帮助django改进代码库来充分使用这些技巧。
有关贡献者的贡多信息
在你开始为django编写补丁前,还有一些关于贡献者的内容需要你了解一下:
- 你应确保你已经阅读了django文档中认领工单和提交补丁的部分,。它涵盖了Trac规则(Trac是一个软件),如何认领工单、补丁的代码风格,和许多其他重要细节。
- 第一次贡献代码的时候也应该阅读django中关于第一次贡献代码的文档。对于刚接触django的新人来说,它有许多很好的建议。
- 之后,如果你希望了解更多关于贡献者的信息,你也可以查看django文档中关于贡献的其余部分。这里包含大量有用的信息,应该是解答你的问题的第一手资料。
找出你的第一个工单
当你查看了上面提到的内容后,就可以去找一个工单来编写补丁了。特别要注意哪些“easy pickings”工单。这些工单非常简单,非常适合初次参与的贡献者。当你熟悉了如何向Django提交贡献,你就可以改而解决更复杂困难的工单。
如果你只是想了解一下,请查看需要打补丁的简单工单和需要该进的补丁的工单。如果你熟悉编写测试,你可以查看需要测试的简单工单。请记得遵循关于认领工单的知道原则,这些工单是在django文档
请记得认领工单和提交补丁中,关于遵循Django文档链接中提到的认领工单的指导原则。
创建pull请求后做什么?
工单有补丁后,需要通过通过第二组人来进行审查。提交pull请求后,通过将工单上的标志设置为 “has patch”、“doesn’t need tests”等,来更新故障单的元数据。以便其他人来查看。贡献不代表要从头开始编写补丁。查看现有的补丁也是一项很有用的贡献,详细信息可以查看工单分类。