Skip to main content

编写Django的第一个补丁

介绍

有兴趣回馈社会一点吗?也许你在Django中发现了一个你想要修复的错误,或者你想添加一个小的功能。

贡献回Django本身是看到你自己关注的问题的最佳方式。这可能看起来很吓人,但它真的很简单。我们将引导您完成整个过程,以便您通过示例学习。

这个教程是谁的?

参见

如果您正在寻找有关如何提交修补程序的参考,请参阅 提交修补程序 文档。

对于本教程,我们期望您至少对Django的工作原理有一个基本的了解。这意味着你应该很舒服地浏览现有的 writing your first Django app 教程。此外,你应该对Python本身有一个很好的理解。但如果你不,Dive Into Python 是一个梦幻般(和免费)的在线书籍开始Python程序员。

你们谁不熟悉版本控制系统和Trac会发现本教程及其链接仅包含足够的信息开始。但是,如果您计划定期为Django做出贡献,您可能需要阅读有关这些不同工具的更多信息。

在大多数情况下,本教程尝试尽可能解释,以便它可以用于最广泛的受众。

在哪里获得帮助:

如果您在阅读本教程时遇到问题,请向 django-developers 发送消息或通过 #django-dev on irc.freenode.net 删除以与可能能够帮助的其他Django用户聊天。

本教程包括什么?

我们将第一次带你到Django的补丁。在本教程结束时,您应该对这些工具和所涉及的过程有基本的了解。具体来说,我们将涵盖以下内容:

  • 安装Git。

  • 如何下载Django的开发副本。

  • 运行Django的测试套件。

  • 为你的补丁写一个测试。

  • 编写补丁的代码。

  • 测试你的补丁。

  • 为您的更改生成修补程序文件。

  • 在哪里寻找更多信息。

完成本教程后,您可以查看 Django’s documentation on contributing 的其余部分。它包含许多伟大的信息,是一个必须阅读的任何人谁想成为一个常规贡献者Django。如果你有问题,它可能得到的答案。

Python 3需要!

本教程假定您使用的是Python 3.在 Python下载页面 或使用操作系统的软件包管理器获取最新版本。

对于Windows用户

在Windows上安装Python时,请确保选中“将python.exe添加到路径”选项,以便在命令行中始终可用。

行为守则

作为贡献者,您可以帮助我们保持Django社区开放和包容。请阅读并遵守我们的 行为守则

安装Git

对于本教程,您需要安装Git以下载当前开发版本的Django并为您所做的更改生成修补程序文件。

要检查是否安装了Git,请在命令行中输入 git。如果您收到消息说无法找到此命令,您必须下载并安装它,请参阅 Git’s download page

对于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中的功能,它允许您为每个项目保留已安装软件包的单独目录,以使它们不会相互干扰。

将所有虚拟化保存在一个地方是个好主意,例如在您的主目录中的 .virtualenvs/ 中。创建它,如果它还不存在:

$ mkdir ~/.virtualenvs

现在创建一个新的virtualenv通过运行:

$ python3 -m venv ~/.virtualenvs/djangodev

路径是新环境将保存在您的计算机上的位置。

对于Windows用户

如果您还在Windows上使用Git Bash外壳,那么使用内置的 venv 模块将无法工作,因为仅为系统外壳程序(.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 作为案例研究,因此我们将在git中将Django的版本历史记录回滚到应用该故障单的补丁之前。这将允许我们从头开始编写该补丁,包括运行Django的测试套件的所有步骤。

请记住,虽然我们将使用一个旧版本的Django的树干为下面的教程的目的,你应该总是使用当前的开发版本的Django工作在自己的修补程序的票!

注解

这张票的补丁是由PawełMarczewski编写的,它被应用到Django作为 commit 4df7e8483b2679fc1cba3410f08960bac6f51115。因此,我们将使用之前的Django的修订版 commit 4ccfc4439a7add24f8db4ef3960d02ef8ae09887

导航到Django的根目录(这是包含 djangodocstestsAUTHORS 等)。然后,您可以查看我们将在以下教程中使用的Django的旧版本:

$ git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887

第一次运行Django的测试套件

当对Django做出贡献时,非常重要的一点是,您的代码更改不会将错误引入Django的其他区域。在进行更改后,检查Django是否仍然可以工作的一种方法是运行Django的测试套件。如果所有测试仍然通过,那么您可以合理地确保您的更改没有完全打破Django。如果你以前从来没有运行过Django的测试套件,最好先运行它一次,以便熟悉它的输出应该是什么样子。

在运行测试套件之前,首先通过 cd 将其依赖关系安装到Django tests/ 目录中,然后运行:

$ pip install -r requirements/py3.txt

如果在安装过程中遇到错误,您的系统可能会丢失一个或多个Python包的依赖项。请参阅故障包的文档或使用您遇到的错误消息搜索Web。

现在我们准备好运行测试套件了。如果您使用的是GNU/Linux,Mac OS X或其他Unix风格,请运行:

$ ./runtests.py

现在坐下来放松。 Django的整个测试套件有超过9600种不同的测试,所以它可以从5到15分钟运行,这取决于您的计算机的速度。

当Django的测试套件运行时,你会看到一个字符流,表示每个测试的运行状态。 E 表示在测试期间出现错误,F 表示测试的断言失败。这两个都被认为是测试失败。同时,xs 分别指示预期的故障和跳过的测试。点表示通过测试。

跳过的测试通常是由于缺少运行测试所需的外部库;请参阅 运行所有测试 的依赖关系列表,并确保安装任何与您正在进行的更改相关的测试(我们不需要任何本教程)。一些测试特定于特定的数据库后端,如果不使用该后端进行测试,将被跳过。 SQLite是数据库后端的默认设置。要使用不同的后端运行测试,请参阅 使用另一个 settings 模块

测试完成后,您应该收到一条消息,通知您测试套件是通过还是失败。由于您尚未对Django的代码进行任何更改,因此整个测试套件 应该 通过。如果您遇到故障或错误,请确保您已正确地遵循所有以前的步骤。有关详细信息,请参阅 运行单元测试。如果您使用的是Python 3.5+,则会出现一些与可以忽略的弃用警告相关的失败。这些故障已经在Django中得到修复。

注意,最新的Django中继可能不总是稳定。当对中继线进行开发时,您可以检查 Django’s continuous integration builds 以确定故障是否特定于您的机器,或者如果它们也存在于Django的官方版本中。如果单击查看特定构建,您可以查看“配置表”,其中显示按Python版本和数据库后端分解的故障。

注解

对于本教程和我们正在开发的故障单,针对SQLite测试就足够了,但是,使用不同的数据库运行测试 可能(有时是必要的)。

为你的票写一些测试

在大多数情况下,对于要接受到Django的补丁,它必须包括测试。对于错误修复补丁,这意味着编写一个回归测试,以确保该错误不会再被重新引入Django。回归测试应该以这样一种方式编写,即当错误仍然存在时它将失败,并且一旦错误被修复,它就会传递。对于包含新功能的修补程序,您需要包含确保新功能正常工作的测试。当新特性不存在时,它们也应该失败,然后在实现后传递。

一个好的方法是在对代码进行任何更改之前,首先编写新的测试。这种开发方式称为 test-driven development,可以应用于整个项目和单个补丁。在编写测试之后,然后运行它们以确保它们确实失败(因为您尚未修复该错误或添加了该功能)。如果你的新测试不失败,你需要修复它们,使他们做。毕竟,不管是否存在错误,通过的回归测试对于防止该错误在路上重现并不是非常有帮助。

现在我们的实例。

写一些测试票#24788

Ticket #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.

为了解决这张票,我们将添加一个 prefix 属性到 BaseForm 类。当创建这个类的实例时,传递一个前缀到 __init__() 方法将仍然在创建的实例上设置该前缀。但是不传递前缀(或传递 None)将使用类级别前缀。在我们进行这些更改之前,我们将编写一对夫妇测试,以验证我们的修改功能正确,并在未来继续正常运行。

导航到Django的 tests/forms_tests/tests/ 文件夹并打开 test_forms.py 文件。在第1674行的 test_forms_with_null_boolean 函数之前添加以下代码:

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 参数仍然有效。

但这个测试的东西看起来很难...

如果你从来没有必要处理测试,他们可能看起来有点难以写一瞥。幸运的是,测试是计算机编程中的一个 very 大主题,所以有很多信息:

  • 关于Django的写测试的好的第一个看法可以在 编写和运行测试 的文档中找到。

  • Dive Into Python(一本免费的Python开发人员在线书)包括一个伟大的 introduction to Unit Testing

  • 阅读完这些后,如果你想要一些更大的东西让你的牙齿陷入,总是有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(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__ 方法中添加条件语句。继续并更改现在在 django/forms/forms.py 第87行的 self.prefix = prefix,添加一个条件语句:

if prefix is not None:
    self.prefix = prefix

重新运行测试,一切都应该通过。如果没有,请确保您正确地修改了 BaseForm 类,如上所示,并正确复制新测试。

第二次运行Django的测试套件

一旦你验证了你的补丁和你的测试工作正常,一个好主意,运行整个Django测试套件,只是为了验证你的更改没有引入任何错误Django的其他领域。虽然成功通过整个测试套件并不能保证你的代码是无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.

由于这个新功能将在一个即将发布的版本中,它还添加到Django 1.9的发行说明,在线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。

为您的更改生成修补程序

现在是时候生成一个补丁文件,可以上传到Trac或应用到另一个Django的副本。要查看补丁的内容,请运行以下命令:

$ git diff

这将显示您当前的Django副本(与您的更改)和您最初在教程中检查的修订版本之间的差异。

一旦你看完补丁,点击 q 键退出到命令行。如果补丁的内容看起来不错,您可以运行以下命令将补丁文件保存到当前工作目录:

$ git diff > 24788.diff

您现在应该在根Django目录中有一个名为 24788.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.

那么我接下来该做什么?

恭喜,您已经生成了第一个Django补丁!现在你已经拥有了,你可以通过帮助改善Django的代码库,把这些技能好用。生成补丁并将它们附加到Trac票是有用的,但是,因为我们使用git - 建议采用更多的 git导向的工作流

由于我们从未在本地提交更改,请执行以下操作以将git分支恢复到良好的起点:

$ git reset --hard HEAD
$ git checkout master

更多信息为新贡献者

在你为Django编写补丁之前,还有一些关于贡献的信息,你应该看看:

  • 你应该确保阅读关于 索赔票和提交补丁 的Django的文档。它涵盖Trac礼仪,如何为自己申请门票,期望的编码风格的补丁,以及许多其他重要的细节。

  • 第一次贡献者也应该阅读Django的 documentation for first time contributors。它对我们谁是新的帮助Django的很多好的建议。

  • 之后,如果你仍然渴望有更多的贡献的信息,你可以随时浏览其余的 Django’s documentation on contributing。它包含大量有用的信息,应该是您回答任何问题的第一个来源。

找到你的第一张真正的票

一旦你看了一些这些信息,你就可以出去,找到一张自己的票,写一个补丁。特别注意有“简单挑选”标准的票。这些票通常更简单的性质,是伟大的第一次贡献者。一旦你熟悉对Django的贡献,你可以继续写更多困难和复杂的票的补丁。

如果你只是想开始(和没有人会怪你!),试试看看 easy tickets that need patcheseasy tickets that have patches which need improvement 的列表。如果你熟悉编写测试,你也可以看看 easy tickets that need tests 的列表。只记得遵循关于在Django的 索赔票和提交补丁 文档的链接中提到的声明权限的指南。

下一步是什么?

在票有补丁后,它需要由第二组眼睛审查。上传补丁或提交拉取请求后,请务必通过设置故障单上的标记说“has patch”,“不需要测试”等更新票证元数据,以便其他人可以查找它。贡献并不总是意味着从头开始写补丁。查看现有补丁也是一个非常有用的贡献。有关详细信息,请参阅 检票