Django への初めてのパッチを書く

はじめに

小さなものでも、コミュニティへ恩返しすることに興味がありますか? たとえば、Django に直してほしいバグを見つけたり、ちょっとした機能を追加してほしいと思っているかもしれません。

その願いを叶える一番の方法は Django 自体にコントリビュートすることです。最初はすごく大変なことだと想像するかもしれませんが、実際はとても簡単です。これからコントリビュートのプロセス全体を順番に詳しく解説していくので、例を通して理解できると思います。

このチュートリアルの対象者は誰ですか?

参考

パッチの送り方について知りたい方は、 Submitting patches をご覧ください。

このチュートリアルは、少なくとも Django が動作に関して基本的な理解があることを想定しています。つまりチュートリアル 初めての Django アプリ を十分に理解していることを想定しています。また、Python 自体もよく理解している必要があります。もしそうでない場合、 Dive Into Python というガイドがあります。これは Python プログラマーになるための素晴らしい (かつ無料の) オンラインドキュメントです。

バージョン管理システムや Trac をよく知らない方でも、このチュートリアルとリンク先から、コントリビュートに必要な情報は十分得られます。しかし、Django に定期的に貢献したい場合は、このツールの詳細を知っておいた方がいいでしょう。

このチュートリアルでは、できるだけ多くの方が使用できるように、可能な限り詳しく説明したいと思います。

困ったときは:

もしこのチュートリアルを通じて理解できないことがあれば、 django-developers にメッセージを送るか、#django-dev on irc.freenode.net で他の Django ユーザーとチャットすることで助けてもらえるでしょう。

このチュートリアルはどの範囲をカバーしていますか?

初めて Django にパッチを送る手順までを詳しく解説しています。このチュートリアルを終えると、関連するツールとプロセス両方について基本的な理解が得られます。具体的には次が対象とする範囲です。

  • Git のインストール
  • 開発版の Django の複製をダウンロード
  • Django のテストスイートの実行
  • パッチへのテストを書く
  • パッチのコードを書く
  • パッチをテスト
  • プルリクエストを送る
  • より多くの情報を得る方法

チュートリアルを終えたら、次は Django への貢献 を参照してください。このドキュメントには、多くの重要な情報が含まれており、Django に定期的に貢献したい方は是非一読してください。あなたの疑問への答えが見つかるはずです。

3系の Python が必要です!

Django の現在のバージョンは、Python 2.7 をサポートしません。Python のダウンロードページ <https://www.python.org/downloads/> や OS のパッケージ管理システムを用いて Python 3 をインストールしてください。

Windows を使用している方へ

Windows に Python をインストールする際、 "Add python.exe to Path" のオプションにチェックを入れると、コマンドラインからいつでも利用できるようになります。

ソースコードの管理

あなたはコントリビューターとして私たちDjangoコミュニティをオープンかつ包摂的なものでありつづけることを援助できます。私たちの ソースコードの管理 <https://www.djangoproject.com/conduct/> を参照し、フォローして下さい。

Git のインストール

このチュートリアルでは、最新の Django 開発版のダウンロードとその変更のパッチファイルを生成するために Git をインストールする必要があります。

Git がインストールされているかどうかを確認するために、コマンドラインで git を入力します。もし入っていない場合は、ダウンロード及びインストールするために、 Git's download page を参照してください。

Windows を使用している方へ

Windows に Git をインストールする際、 "Git Bash" オプションを選択し、シェルから Git が操作できるようにしておくことをおすすめします。このチュートリアルは Git Bash がインストールされていることを想定しています。

もし Git について詳しく知らない場合は、(インストール後に) コマンドラインから git help と入力するとコマンドの使い方を確認できます。

Django 開発版の複製を取得

Django へ貢献するためのはじめの一歩は、ソースコードのコピーです。まず、 GitHub で Django をフォーク して、コマンドラインからリポジトリのクローンを作り、 cd コマンドで Django のローカルコピーのディレクトリに移動しましょう。

以下のコマンドで Django のソースコードリポジトリをダウンロードします:

$ git clone git@github.com:YourGitHubName/django.git

今やあなたはDjangoのローカルコピーを手に入れました、"pip"を使ってどんなパッケージでもインストールできるのと同じように、Djangoもインストールできます。Pythonに含まれる機能である virtual environment (もしくはvirtualenv)は、プロジェクトごとにインストールされたパッケージが互いに害を及ぼすことないよう、別々のディレクトリで維持することを可能にする、もっとも便利な方法です。

ホームディレクトリの .virtualenvs/ 以下に、すべてのvirtualenvを 1 箇所においておくといいです。もしまだそのフォルダが存在しないなら作成しましょう。

$ mkdir ~/.virtualenvs

それではアプリケーションを実行するために新しいvirtualenvを作りましょう:

$ python3 -m venv ~/.virtualenvs/djangodev

新しい環境があるパスはコンピュータに保存されました。

Windows を使用している方へ

ビルトインの venv は、 Windows 用にはシステムシェル(.bat) やPowerShell(.ps1)のためだけに作られたアクティベーションスクリプトであるため、WindowsのGit Bash shellを使用していても動作しません。その代わりに``virtualenv``パッケージを使用します:

$ pip install virtualenv
$ virtualenv ~/.virtualenvs/djangodev

Ubuntuを使用している方へ

いくつかのバージョンのUbuntuでは上記のコマンドは失敗するでしょう。まず pip3 があることを確認し、 virtualenv パッケージを使用します。

$ 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 を用います。そのために、まず Django のバージョンをパッチが適用される前の git のバージョンに戻します。これによりスクラッチからパッチの作成、 Django のテストスイートの実行を含む全ての手順を解説します。

チュートリアルでは trunk ではない古いリビジョンの Django を使用しますが、自分のパッチを作成する場合は Django の最新の開発版を使用する必要があります!

注釈

このチケットのパッチは Paweł Marczewski 氏により書かれ、これは commit 4df7e8483b2679fc1cba3410f08960bac6f51115 で Django に適用されました。よって commit 4ccfc4439a7add24f8db4ef3960d02ef8ae09887 が適用される前の Django のリビジョンを使用します。

Django のルートディレクトリ (このディレクトリには django, docs, tests, AUTHORS, 等が含まれています) へ移動します。次にチュートリアルで使用する Django の旧リビジョンをチェックアウトします。

$ git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887

最初に Django のテストスイートを実行する

Django へ貢献する際、変更したコードが Django の他の領域にバグを混入しないことが非常に重要です。変更後に Django の正常動作を確認するには Django のテストスイートを実行します。すべてのテストに合格した場合は、その変更により Django が壊れていないことが確認できます。Django のテストスイートを実行したことが一度もない場合、その出力結果を把握しておくために一度テストスイートを実行しておくことを推奨します。

Django のテストスイートを走らせる前に、まずは Djangoの tests/ ディレクトリに cd で移動して、依存関係をインストールしてください:

$ pip install -r requirements/py3.txt

もしこのインストールの間にエラーが発生した場合は、システムが Python のパッケージのうちの 1 つ、もしくはそれ以上の依存関係を見失っている可能性があります。失敗したパッケージの資料を調べるか、発生したエラーメッセージを Web で検索してください。

テストスイートを実効する準備が出来ました。もし GNU/Linux, macOS 等 Unix 系OSを使用している場合、下記のコマンドを実行します:

$ ./runtests.py

それでは一息つきましょう。Django 全体のテストスイートは 9600 以上のテストが含まれており、コンピュータの速度により 5 〜 15分程度かかります。

Django のテストスイートを実行中に、各テストの状態を文字のストリームで確認することができます。 E はテストにエラーが発生したことを表し、 F はテストのアサーションが失敗したことを表しています。これらは共にテスト失敗となります。xs はそれぞれ期待する失敗とスキップを表しています。ドットはテストの成功を表しています。

スキップされたテストは、テストを実行するために必要な外部ライブラリがインストールされていないことが原因です; 依存については Running all the tests を参照し、関連するテストを実行してください (このチュートリアルでは必要ありません)。いくつかのテストは、特定のデータベースバックエンドに固有であり、そのバックエンドでテストされない場合はスキップされます。 SQLite は、デフォルト設定のバックエンドです。別のバックエンドを使用してテストを実行するには、 Using another settings module を参照してください。

テストが終了するとテストが成功したか、失敗したかを知らせるメッセージが表示されます。まだ Django のコードに変更を加えていなければ、テストは全て パスするはずです 。もし失敗するかエラーが起こる場合は、これまでの全ステップを適切に実行してください。 Running the unit tests で、よりテストについて知れます。もし Python 3.5 以上を使っているのであれば、おそらく無視できる非推奨の警告に関係するいくつかの失敗でしょう。それらの失敗は、後の Django で修正されています。

開発中の、最新の Django ではステーブルとは限りません。トランクバージョンで開発を行う場合、 Django の継続インテグレーションビルド をチェックしてください。これで、テストの失敗があなたのマシンだけのものか、 Django 公式のビルドによるものかが分かります。各ビルドについてのリンクをクリックすれば、 "Configuration Matrix" という、 各 Python のバージョン、 DB バックエンドに対応したテストの失敗を閲覧できます。

注釈

このチュートリアルや、各チケットで作業する際は、 SQLite のテストで十分です。しかし可能 (か必要な場合は) 他のデータベースでテストを実行する を参照してください。

パッチ用のブランチを作る

変更を加える前に、チケット用に新しいブランチを作ります。

$ git checkout -b ticket_24788

ブランチ名は好きな名前で構いません。"ticket_24788" は一例です。新しいブランチ内で行ったすべての変更は、先ほどクローンしたコードのマスターコピーには影響しません。

チケットにテストを書く

大抵の場合、 Django にアクセプトされるパッチはテストを含んでいます。リグレッションテストを書くことで、 Django にバグを再混入していないと後に保証できます。レグレッションテストは、バグが存在しているときに落ちるように書き、バグが修正された後にパスするように書かれるべきです。新機能を含むパッチでは、その新機能が正しく動作すると保証するためにテストが必要です。その際も同じように、新機能が無い際には落ち、実装されてから通るテストを書くべきです。

これをするには、コードに変更を加える前に先にテストを書くのが良いでしょう。この開発手法は テスト駆動開発 と呼ばれ、プロジェクト全体にも単一のパッチにも適応可能です。テストを書いた後には、テストを走らせて確かに落ちることを確認します (バグ修正や昨日の追加はまだしてないので落ちます)。 新しいテストが落ちない場合は落ちるよう修正しましょう。ともかく、バグが存在していようとも通るテストは将来バグが再発するのを防ぐのに、何の役にもたちません。

ハンズオンでの例題

チケット #24788 にテストを書いてみましょう

チケット #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__() メソッドに prefix を渡すと、作成されたインスタンスに prefix がセットされます。 しかし prefix を渡さない(または None を与える)場合、クラスレベルのprefixが使用されます。変更を加える前に、変更が正しく動作し、これからも動作し続けることを確認するためにテストをいくつか追加します。

Django の tests/forms_tests/tests/ フォルダに移動して tests_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 が期待通りに設定されていることと、インスタンスの生成時にまだ prefix パラメータが渡されていることを確認します。

でもテストをするのはすこし難しそうです……

テストを書いたとこがない場合は、最初は難しく見えるかもしれません。ですが実は、テスティングはプログラミングにおいて とても 重要なことです。ここではテストについて詳細に紹介します。

  • Django のための良いテストの書き方は テストを書いて実行する のドキュメントに記載されています。
  • Dive Into Python (Python初心者のための、オンラインの無料の本) では素晴らしい 初めてのユニットテスト という章があります。
  • Dive Into Python を読んだあと、もう少し情報が欲しい場合は、Python の unittest のドキュメントを参照してください。

新しいテストを走らせる

BaseForm `` にはまだ変更を加えていませんので、テストは落ちます。 ``forms_tests `` フォルダの全テストを走らせて、実際に何が起こるか観てみましょう。コマンドラインで ``tests/ ディレクトリに cd して、以下を実行してください:

$ ./runtests.py forms_tests

テストが正しく実行されれば、追加したテストメソッドに対応する1つのテストが失敗するでしょう。テストがすべて通れば、上記した新しいテストを正しいフォルダ、クラスに追加したことを確認してください。

チケットにコードを書く

次は :ticket:`24788 ` で説明されている機能を Django に追加します。

チケット #24788 のコードを書いてみましょう

django/django/contrib/forms フォルダーに行って、 forms.py ファイルを開いてください。 72行目にある BaseForm クラスを見つけて、以下の prefix クラス属性をすでにある field_order 属性の下に追加してください:

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 への変更が完了したあと、コードが正しく動作すると確認するため、先ほど書いたテストを実行してください。Djangoの form_tests フォルダーに cd で移動して、以下を実行してください:

$ ./runtests.py forms_tests

あぁ、テストを書いていて良かったですね。以下のように1つテストが落ちています:

AssertionError: None != 'foo'

__init__``メソッドの中に条件文を追加するのを忘れていました。``django/forms/forms.py の87行目の``self.prefix = prefix``を参照して変更します。条件文を追加しましょう:

if prefix is not None:
    self.prefix = prefix

再実行すればテストがすべてパスするはずです。もしそうでない場合、 BaseForm クラスと新しいテストが正しくコピーされているされていると確認してください。

Django の テストスイートをもう一度走らせる

パッチとテストが正しく動作していることを確認できたら、 Django のテストをすべて走らせて、変更が Django の別の場所にバグを仕込んでいないか確認しましょう。すべてのテストが通るという事は、追加したコードがバグフリーだと保証します。大量のバグや手戻りを発見できます。そうでないとバグを見逃してしまうでしょう。

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のリリースノート、ファイル docs/releases/1.9.txt 164行目以降の"Forms"のセクションに追加されています。

* A form prefix can be specified inside a form class, not only when
  instantiating a form. See :ref:`form-prefix` for details.

ドキュメントの書き方についてもっと知りたい場合は Writing documentation を参照してください。ここでは、 ``versionadded` の書き方についてや、 ドキュメントのコピーをローカルでビルドしてみて、 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 上のあなたのフォークに送りましょう (ブランチ名を変えた場合には "ticket_24788" の部分を置き換えてください)。

$ git push origin ticket_24788

プルリクエストは Django の GitHub ページ から作成できます。"Your recently pushed branches" の下にあなたのブランチが表示されているはずです。その下の "Compare & pull request" ボタンをクリックします。

このチュートリアルでは行ってはいけませんが、次のページにパッチのプレビューが表示されるので、"Create pull request" をクリックすれば、Django プロジェクトに実際にプルリクエストを送ることができます。

次のステップ

おめでとうございます! これで Django へのプルリクエストの作成方法を学ぶことができました。応用テクニックについて詳しくは Working with Git and GitHub を読んでください。

これで、Django のコードベースを改良する手助けができるようになりました。

新しい貢献者のための情報

Django へのパッチを書き始める前に、貢献するために見ておいたほうがいい情報があります:

実際にチケットを探してみましょう

ドキュメントをたくさん読んだ後は、実際のパッチを書けるのでチケットを探してみましょう。 "easy picking" タグが付いたチケットを見つけてください。このチケットは大抵はより簡単なものなので、初めての貢献者には適切でしょう。 Django への貢献に慣れてきたあとは、難しく、複雑なチケットを書き始めれるでしょう。

今すぐ始めたいなら (責める人はいません!)、 パッチが必要な簡単なチケットパッチに改善が必要な簡単なチケット を見つけましょう。テストを書きなれているなら テストが必要な簡単なチケット でもよいでしょう。ただ、チケットのクレームに関するガイドラインに従ってください。 Django ドキュメントの チケットをクレームしてパッチを送る で記述されています。

プルリクエストを作ったあとは?

チケットに対してパッチを作ったら、他の人によるレビューが必要です。プルリクエストを送った後は、チケットのメタデータを "has patch"、"doesn't need tests" などのフラグを付けてアップデートして、他の人にレビューが必要だと分かるようにしてください。プロジェクトへの貢献は、必ずしもパッチを書くことだけではありません。すでに書かれたパッチをレビューのレビューもとても助かります。詳しくは Triaging tickets を見てください。

Back to Top