编写你的第一个 Django 补丁

介绍

想为 Django 社区做一点贡献?也许是你发现了一个想修复的 bug,或者想添加一个新的功能。

回报 Django 这件事本身就是使你的顾虑得到解决的最好方式。一开始这可能会使你怯步,但这真的很简单。整个过程中我们会一步一步为你解说,所以你可以通过例子学习。

这个教程适合谁?

参见

如果你正在寻找一个关于如何提交补丁的说明文档,请查看 Submitting patches

使用教程前,我们希望你至少对于 Django 的运行方式有一定的认识。 这意味着你可以很容易地通读 编写第一个 Django 应用。 除此之外,你应该对于 Python 有很好的理解。 如果不太熟悉 Python,我们为您推荐 `Dive Into Python`__,对于初学Python的程序员来说这是一本很棒(而且免费)的在线电子书。

那些不熟悉版本控制系统及缺陷跟踪的朋友可以查看这个教程,这个链接包含了足够的信息。如果你打算定期地为 Django 做贡献,你可能期望阅读更多关于这些不同工具的资料。

当然对于此教程中的大部分内容,Django 会尽可能做出解释以帮助广大的读者。

从哪里获得帮助:

如果你在使用本教程时遇到困难, 你可以发信息给 django-developers 中的人或登陆 `#django-dev on irc.freenode.net`__ 向其他 Django 使用者寻求帮助。

这个指南涵盖哪些内容?

我们将指导你贡献你的第一个 Django 补丁,在本教程完毕时,你将对相关工具及流程有一个基本的认识。特别的,我们将覆盖以下内容:

  • 安装 Git。
  • 下载一份Django开发版的拷贝。
  • 运行 Django 的测试套件。
  • 为你的补丁写一个测试。
  • 为你的补丁编写代码。
  • 测试你的补丁。
  • 提交一个 pull request(PR)。
  • 在哪里查找更多的信息。

一旦你完成了这份教程,你可以浏览 Django 贡献文档 的剩余部分。它包含了大量信息。任何想成为 Django 的正式贡献者的人都必须阅读它。如果你有问题,它也许会给你答案。

必须Python 3!

目前的 Django 版本不再支持 Python 2.7。你可以在 Python 下载页 或通过操作系统的包管理器下载 Python 3。

对于 Windows 用户

在Windows上安装 Python 时,确保勾选在环境变量path中“添加 python.exe 的路径”,这样才可以让它在命令行下总是可用。

代码规范

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

安装Git

在本教程中,你需要安装好 Git,用 Git 下载 Django 的最新开发版本并且为你的修改生成补丁文件。

要检查你是否已经安装 Git,命令行输入 git。如果提示这个命令无法找到,你必须下载并安装它,参考 `Git's download page`__

如果你还不熟悉 Git, 你可以在命令行下输入 git help 了解更多关于 Git 命令的使用方法 (确保已安装)

获得一个 Django 开发版本的副本

为 Django 做贡献的第一步就是获取源代码副本。首先, fork Github 上的 Django 项目 <https://github.com/django/django/fork>。接下来,在命令行中,使用 cd 命令切换至某个你想存放 Django 源码的目录。

使用下面的命令来下载 Django 的源码库:

$ git clone [email protected]:YourGitHubName/django.git
...\> git clone [email protected]:YourGitHubName/django.git

Low bandwidth connection?

You can add the --depth 1 argument to git clone to skip downloading all of Django's commit history, which reduces data transfer from ~250 MB to ~70 MB.

你现在已经将Django拷贝到本地,可以像安装其他软件包一样使用 ``pip``进行安装。 最便捷的方式是通过 virtual environment, 这是 Python 的一个内置特性,它可以让你在一个目录中保持独立的软件包环境而不影响其他的项目。

将你的虚拟环境都放在一个位置是明智的做法,例如将它们放置在你主目录下的 ``.virtualenvs/``中。

通过运行以下命令创建一个虚拟环境:

$ python3 -m venv ~/.virtualenvs/djangodev
...\> py -m venv %HOMEPATH%\.virtualenvs\djangodev

该路径就是保存这个新的虚拟运行环境的地方。

设置虚拟环境的最后一步是激活它;

$ source ~/.virtualenvs/djangodev/bin/activate

如果 source 命令不可用,你可以试试 . :

$ . ~/.virtualenvs/djangodev/bin/activate

对于 Windows 用户

在Windows下采用如下命令进行激活虚拟环境:

...\> %HOMEPATH%\.virtualenvs\djangodev\Scripts\activate.bat

每当你开启一个新的终端时,你都需要激活虚拟环境。 virtualenvwrapper__ 可以使其更便捷。

The name of the currently activated virtual environment is displayed on the command line to help you keep track of which one you are using. Anything you install through pip while this name is displayed will be installed in that virtual environment, isolated from other environments and system-wide packages.

Go ahead and install the previously cloned copy of Django:

$ pip install -e /path/to/your/local/clone/django/
...\> pip install -e \path\to\your\local\clone\django\

现在安装的 Django 版本就是你本地副本的版本。你将立刻见到任何你对它的修改,这对你编写第一个补丁很有帮助。

首先运行 Django 的测试套件

When contributing to Django it's very important that your code changes don't introduce bugs into other areas of Django. One way to check that Django still works after you make your changes is by running Django's test suite. If all the tests still pass, then you can be reasonably sure that your changes work and haven't broken other parts Django. If you've never run Django's test suite before, it's a good idea to run it once beforehand to get familiar with its output.

Before running the test suite, install its dependencies by cd-ing into the Django tests/ directory and then running:

$ pip install -r requirements/py3.txt
...\> pip install -r requirements\py3.txt

如果安装过程中发生了错误,可能是你的系统缺少一个或多个 Python 依赖包。请参考安装失败的包的文档或者在网上搜索提示的错误信息。

现在你可以运行测试套件。如果你用的是 GNU/Linux, macOS 或者其它类 Unix 系统,运行:

$ ./runtests.py
...\> runtests.py 

现在坐下来放松一下。Django 完整的测试套件有成千上万种不同的测试,所以它至少需要运行几分钟,这个时间取决于你的电脑的速度。

While Django's test suite is running, you'll see a stream of characters representing the status of each test as it completes. E indicates that an error was raised during a test, and F indicates that a test's assertions failed. Both of these are considered to be test failures. Meanwhile, x and s indicated expected failures and skipped tests, respectively. Dots indicate passing tests.

缺失外部依赖库通常会导致测试被跳过;查看 Running all the tests 获取依赖库列表,如果你修改了测试代码,请同时安装相关依赖库(本教程无需额外依赖库)。某些测试使用了特定的数据库后端,如果当前测试设置并未使用此数据库后端,那么这些相关的测试也会被跳过。SQLite 是默认的数据库后端。如果想使用其他后端进行测试,查看 Using another settings module

代码测试集当测试执行完毕后,得到反馈信息显示测试已通过,或者测试失败。 因为还没有对 Django 的代码做任何修改, 所有的测试集 应该 测试通过. 如果测试失败或出现错误,回头确认以上执行操作是否正确. 查看 Running the unit tests 获取更多信息。

注意最新版本 Django 分支不总是稳定的。当在分支上开发时,你可以查看代码持续集成构建页面的信息 来判断测试错误只在你指定的电脑上发生,还是官方版本中也存在该错误。如果点击某个构建信息,可以通过 "Configuration Matrix" 查看错误发生时 Python 以及后端数据库的信息。

注解

在本教程以及处理工单所用分支中,测试使用数据库 SQLite 即可,然而在某些情况下需要(有时需要) ,参考 :ref:`run the tests using a different database `

Working on a feature

For this tutorial, we'll work on a "fake ticket" as a case study. Here are the imaginary details:

Ticket #99999 -- Allow making toast

Django should provide a function django.shortcuts.make_toast() that returns 'toast'.

We'll now implement this feature and associated tests.

为你的补丁创建一个分支

在做出任何修改之前,为你的工单创建一个分支:

$ git checkout -b ticket_99999
...\> git checkout -b ticket_99999

You can choose any name that you want for the branch, "ticket_99999" is an example. All changes made in this branch will be specific to the ticket and won't affect the main copy of the code that we cloned earlier.

为你的工单写一些测试用例

大多数情况下,Django 的补丁必需包含测试。Bug 修复补丁的测试是一个回归测试,确保该 Bug 不会再次在 Django 中出现。该测试应该在 Bug 存在时测试失败,在 Bug 已经修复后通过测试。新功能补丁的测试必须验证新功能是否正常运行。新功能的测试将在功能正常时通过测试,功能未执行时测试失败。

最好的方式是在修改代码之前写测试单元代码。这种开发风格叫做 `test-driven development`__ 被应用在项目开发和单一补丁开发过程中。单元测试编写完毕后,执行单元测试,此时测试失败(因为目前还没有修复 bug 或添加新功能),如果测试成功通过,你需要重新修改单元测试保证测试失败。因为单元测试并不能阻止 bug 发生。

现在看我们的操作示例。

Writing a test for ticket #99999

In order to resolve this ticket, we'll add a make_toast() function to the top-level django module. First we are going to write a test that tries to use the function and check that its output looks correct.

Navigate to Django's tests/shortcuts/ folder and create a new file test_make_toast.py. Add the following code:

from django.shortcuts import make_toast
from django.test import SimpleTestCase


class MakeToastTests(SimpleTestCase):
    def test_make_toast(self):
        self.assertEqual(make_toast(), 'toast')

This test checks that the make_toast() returns 'toast'.

但这种测试看起来有点困难……

如果你之前从未处理过测试,那他们看起来会有点难以编写。幸运的是,测试是一个计算机编程中 非常 大的一个主题,所以这里有大量的相关资料:

运行你的新测试

Since we haven't made any modifications to django.shortcuts yet, our test should fail. Let's run all the tests in the shortcuts folder to make sure that's really what happens. cd to the Django tests/ directory and run:

$ ./runtests.py shortcuts
...\> runtests.py shortcuts

If the tests ran correctly, you should see one failure corresponding to the test method we added, with this error:

ImportError: cannot import name 'make_toast' from 'django.shortcuts'

If all of the tests passed, then you'll want to make sure that you added the new test shown above to the appropriate folder and file name.

为你的工单编写代码

Next we'll be adding the make_toast() function.

Navigate to the django/ folder and open the shortcuts.py file. At the bottom, add:

def make_toast():
    return 'toast'

Now we need to make sure that the test we wrote earlier passes, so we can see whether the code we added is working correctly. Again, navigate to the Django tests/ directory and run:

$ ./runtests.py shortcuts
...\> runtests.py shortcuts

Everything should pass. If it doesn't, make sure you correctly added the function to the correct file.

第二次运行 Django 测试套件

如果已经确认补丁以及测试结果都正常,就运行 Django 的测试套件,验证你的修改是否导致 Django 的其它部分引入了新的 bug。 虽然测试用例帮助识别容易被人忽略的错误,但测试通过并不能保证完全没有 bug 存在。

运行 Django 完整的测试用例,cd 进入 Django下的 tests/ 目录并运行:

$ ./runtests.py
...\> runtests.py 

书写文档

This is a new feature, so it should be documented. Open the file docs/topics/http/shortcuts.txt and add the following at the end of the file:

``make_toast()``
================

.. versionadded:: 2.2

Returns ``'toast'``.

Since this new feature will be in an upcoming release it is also added to the release notes for the next version of Django. Open the release notes for the latest version in docs/releases/, which at time of writing is 2.2.txt. Add a note under the "Minor Features" header:

:mod:`django.shortcuts`
~~~~~~~~~~~~~~~~~~~~~~~

* The new :func:`django.shortcuts.make_toast` function returns ``'toast'``.

更多关于编写文档和 versionadded 的解释和信息,请参考 编写文档。这个页面还介绍了怎么在本地重新生成一份文档,方便你在本地预览文档。

预览你的修改

Now it's time to go through all the changes made in our patch. To stage all the changes ready for commit, run:

$ git add --all
...\> git add --all

Then display the differences between your current copy of Django (with your changes) and the revision that you initially checked out earlier in the tutorial with:

$ git diff --cached
...\> git diff --cached

使用方向键上下移动

diff --git a/django/shortcuts.py b/django/shortcuts.py
index 7ab1df0e9d..8dde9e28d9 100644
--- a/django/shortcuts.py
+++ b/django/shortcuts.py
@@ -156,3 +156,7 @@ def resolve_url(to, *args, **kwargs):

     # Finally, fall back and assume it's a URL
     return to
+
+
+def make_toast():
+    return 'toast'
diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt
index 7d85d30c4a..81518187b3 100644
--- a/docs/releases/2.2.txt
+++ b/docs/releases/2.2.txt
@@ -40,6 +40,11 @@ database constraints. Constraints are added to models using the
 Minor features
 --------------

+:mod:`django.shortcuts`
+~~~~~~~~~~~~~~~~~~~~~~~
+
+* The new :func:`django.shortcuts.make_toast` function returns ``'toast'``.
+
 :mod:`django.contrib.admin`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~

diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt
index 7b3a3a2c00..711bf6bb6d 100644
--- a/docs/topics/http/shortcuts.txt
+++ b/docs/topics/http/shortcuts.txt
@@ -271,3 +271,12 @@ This example is equivalent to::
         my_objects = list(MyModel.objects.filter(published=True))
         if not my_objects:
             raise Http404("No MyModel matches the given query.")
+
+``make_toast()``
+================
+
+.. function:: make_toast()
+
+.. versionadded:: 2.2
+
+Returns ``'toast'``.
diff --git a/tests/shortcuts/test_make_toast.py b/tests/shortcuts/test_make_toast.py
new file mode 100644
index 0000000000..6f4c627b6e
--- /dev/null
+++ b/tests/shortcuts/test_make_toast.py
@@ -0,0 +1,7 @@
+from django.shortcuts import make_toast
+from django.test import SimpleTestCase
+
+
+class MakeToastTests(SimpleTestCase):
+    def test_make_toast(self):
+        self.assertEqual(make_toast(), 'toast')

当你检查完补丁后,敲击 q 键返回到命令行。如果补丁内容看起来没问题,可以提交这些修改了。

提交补丁中的修改

为了提交这些修改:

$ git commit
...\> git commit

这会打开文本编辑器以便输入提交信息。参考 commit message guidelines 输入类似这样的信息:

Fixed #99999 -- Added a shortcut function to make toast.

推送这次提交并生成一个 pull 请求

After committing the patch, send it to your fork on GitHub (substitute "ticket_99999" with the name of your branch if it's different):

$ git push origin ticket_99999
...\> git push origin ticket_99999

你可以访问 Django GitHub page 创建一个 pull 请求。 你会在“你最近推送的分支”下看到你的分支。 单击旁边的 "Compare & pull request"。

本教程中请不要这么做。不过,在接下来显示补丁预览的页面,你可以单击 "Create pull request"。

下一步

恭喜,你已经学会了如何为 Django 创建 pull request!如需获知更多高级技巧,参考 Working with Git and GitHub

现在你可以活用这些技能帮助改善 Django 的代码库。

针对新贡献者的更多注意事项

在你开始为 Django 编写补丁时,这里有些信息,你应该看一看:

  • 确保你阅读了 Django 的参考文档 创建工单和提交补丁。它涵盖了Trac 规则,如何创建自己的工单,补丁期望的代码风格和其他一些重要信息。
  • 初次提交补丁应额外阅读 首次贡献者文档。这里有很多对新手贡献者的建议。
  • 接下来,如果你渴望更多关于为 Django 做贡献的信息,可以阅读余下的文档 为 Django 文档上作出贡献。它包含了大量的有用信息,这里可以解决你可能遇到的所有问题。

寻找你的第一个真正意义上的工单

一旦你看过了之前那些信息,你便已经具备了走出困境,编写修复自己找到的工单的补丁的能力。对于那些有着“容易获得”标准的工单要尤其注意。这些工单实际上常常很简单而且对于第一次撰写补丁的人很有帮助。一旦你熟悉了给 Django 写补丁,你就可以进一步为更难且更复杂的工单写补丁。

如果你只是想要简单的了解(没人会因此责备你!),那么你可以试着看看 `easy tickets that need patches`__`easy tickets that have patches which need improvement`__。如果你比较擅长写测试,那么你也可以看看这个 `easy tickets that need tests`__。一定要记得遵循在 Django 的文档声明标签和递交补丁中提到的关于声明标签的指导规则 声明标签和提交补丁.

创建完 pull request,下一步做什么呢?

工单有了补丁后,需要他人来复审。提交 pull 请求后,为工单打上如“有补丁”,“无需测试”之类的标签,如此他人便可查找到该工单以便复审。从头开始编写补丁固然是贡献的一种方式,但复审已有补丁同样能帮助 Django。 查看 Triaging tickets 了解更多。