首页 > 技术文章 > Django 2.0.1 官方文档翻译:编写你的第一个djang补丁(page 15)

resn 2018-12-07 15:42 原文

编写你的第一个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 表示测试的断言失败。这两种情况都被认为是测试失败。同时,xs 分别表示预期的失败和跳过的测试,圆点则表示测试通过。

跳过的测试通常是因为缺少运行测试的外部库;请检查运行所有测试需要使用的依赖的列表,确保安装了所有与你正在修改的相关的测试测所有依赖(本教程不需要额外安装任何依赖)。一些特定的测试用于测试数据库后端,如果不做后端测试,那么就会被跳过。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”等,来更新故障单的元数据。以便其他人来查看。贡献不代表要从头开始编写补丁。查看现有的补丁也是一项很有用的贡献,详细信息可以查看工单分类

推荐阅读