Написание вашего первого патча для Django
Введение
Хотите немного внести вклад в сообщество? Возможно, вы обнаружили ошибку в Django, которую хотели бы исправить, или, быть может, хотите добавить небольшую функцию.
Внесение вклада в сам Django — лучший способ увидеть, как ваши проблемы решаются. Это может показаться сложным на первый взгляд, но на самом деле это довольно просто. Мы проведём вас через весь процесс, чтобы вы могли учиться на примерах.
Для кого предназначен этот учебник?
См. также
Если вы ищете справочник по отправке патчей, см. документацию по отправке патчей.
Для этого учебника предполагается, что вы имеете хотя бы базовое понимание работы Django. Это означает, что вы должны быть знакомы с существующими учебниками по созданию вашего первого приложения Django. Кроме того, вы должны хорошо понимать сам Python. Но если нет, то Dive Into Python — замечательная (и бесплатная) онлайн-книга для начинающих программистов Python.
Те из вас, кто не знаком с системами управления версиями и Trac, увидят, что этот учебник и ссылки в нём содержат достаточно информации для начала работы. Тем не менее, вам, вероятно, захочется прочитать больше об этих инструментах, если вы планируете регулярно вносить вклад в Django.
В основном, этот учебник пытается объяснить как можно больше, чтобы он был полезен для максимально широкой аудитории.
Где получить помощь:
Если у вас возникнут трудности с этим учебником, пожалуйста, отправьте сообщение на django-developers или зайдите в #django-dev на irc.freenode.net, чтобы пообщаться с другими пользователями Django, которые могут помочь.
Что охватывает этот учебник?
Мы покажем вам, как впервые внести патч в Django. По окончании этого учебника вы должны будете иметь базовое понимание как инструментов, так и процессов, которые в этом участвуют. В частности, мы рассмотрим следующее:
- Установка Git.
- Как загрузить копию разработки Django.
- Запуск набора тестов Django.
- Написание теста для вашего патча.
- Написание кода для вашего патча.
- Тестирование вашего патча.
- Генерация файла патча для ваших изменений.
- Где найти больше информации.
После завершения учебника вы можете изучить остальную часть документации Django по внесению вклада. Она содержит много полезной информации и является обязательной к прочтению для всех, кто хотел бы стать постоянным участником проекта Django. Если у вас есть вопросы, там, вероятно, есть ответы.
Требуется Python 3!
Этот учебник предполагает использование Python 3. Скачайте последнюю версию на странице загрузки Python или с помощью менеджера пакетов вашей операционной системы.
Для пользователей Windows
При установке Python на Windows убедитесь, что вы выбрали опцию «Добавить python.exe в переменную PATH», чтобы он всегда был доступен в командной строке.
Кодекс поведения
Как участник, вы можете помочь нам сохранить сообщество Django открытым и инклюзивным. Пожалуйста, прочитайте и следуйте нашему Кодексу поведения.
Установка Git
Для этого учебника вам потребуется установленный Git для загрузки текущей версии разработки Django и для генерации файлов патчей для внесённых изменений.
Чтобы проверить, установлен ли Git, введите git в командной строке. Если вы получите сообщения о том, что эта команда не найдена, вам нужно загрузить и установить её, см. страницу загрузки Git.
Для пользователей Windows
При установке Git на Windows рекомендуется выбрать опцию «Git Bash», чтобы Git выполнялся в своём собственном оболочке. Этот учебник предполагает, что вы установили его именно так.
Если вы не очень знакомы с Git, вы всегда можете узнать больше о его командах (после установки) набрав git help в командной строке.
Получение копии версии разработки Django
Первый шаг для внесения вклада в Django — получение копии исходного кода. Из командной строки используйте команду cd для перехода в каталог, где вы хотите разместить локальную копию Django.
Загрузите репозиторий исходного кода Django с помощью следующей команды:
$ git clone https://github.com/django/django.git
Теперь, когда у вас есть локальная копия Django, вы можете установить её так же, как любой пакет, используя pip. Наиболее удобным способом является использование виртуальной среды (или virtualenv), которая является встроенной функцией Python, позволяющей вам хранить отдельный каталог установленных пакетов для каждого из ваших проектов, чтобы они не мешали друг другу.
В идеале, храните все ваши virtualenv в одном месте, например, в .virtualenvs/ в вашем домашнем каталоге. Создайте его, если он ещё не существует:
$ mkdir ~/.virtualenvs
Теперь создайте новую virtualenv, выполнив:
$ python3 -m venv ~/.virtualenvs/djangodev
Путь — это место, где новая среда будет сохранена на вашем компьютере.
Для пользователей Windows
Использование встроенного модуля venv не будет работать, если вы также используете оболочку Git Bash на Windows, поскольку скрипты активации создаются только для системной оболочки (.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
Для активации вашей virtualenv на Windows выполните:
$ source ~/virtualenvs/djangodev/Scripts/activate
Вам необходимо активировать virtualenv каждый раз при открытии нового окна терминала. virtualenvwrapper — полезный инструмент для упрощения этого процесса.
Всё, что вы установите через pip отныне, будет установлено в вашей новой virtualenv, изолированной от других сред и системных пакетов. Также, имя текущей активной virtualenv отображается в командной строке, чтобы вы могли отслеживать, какую из них используете. Продолжайте и установите ранее клонированную копию Django:
$ pip install -e /path/to/your/local/clone/django/
Установленная версия Django теперь указывает на вашу локальную копию. Вы сразу увидите любые внесённые вами изменения, что очень полезно при написании вашего первого патча.
Откат к предыдущей версии Django
В этом учебнике мы будем использовать билет #24788 в качестве примера, поэтому мы отмотаем историю версий Django до момента, когда патч для этого билета ещё не был применён. Это позволит нам пройти через все шаги по написанию этого патча с нуля, включая запуск набора тестов Django.
Помните, что, хотя для целей учебника мы будем использовать более старую версию ствола Django, вы всегда должны использовать текущую версию разработки Django при работе над собственным патчем для билета!
Примечание
Патч для этого билета был написан Павлом Марчевским и применён к Django как коммит 4df7e8483b2679fc1cba3410f08960bac6f51115. Следовательно, мы будем использовать версию Django непосредственно до этого — коммит 4ccfc4439a7add24f8db4ef3960d02ef8ae09887.
Перейдите в корневой каталог Django (это тот, который содержит django, docs, tests, AUTHORS, и т. д.). Затем вы можете проверить более старую версию Django, которую мы будем использовать в учебнике ниже:
$ git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887
Запуск набора тестов Django в первый раз
При внесении вклада в Django очень важно, чтобы ваши изменения не вносили ошибки в другие области Django. Один из способов проверить, что Django по-прежнему работает после внесения изменений, — это запустить набор тестов Django. Если все тесты по-прежнему проходят, то можно с уверенностью сказать, что ваши изменения не полностью сломали Django. Если вы никогда раньше не запускали набор тестов Django, то имеет смысл запустить его один раз предварительно, чтобы познакомиться с тем, как должен выглядеть его вывод.
Перед запуском набора тестов установите его зависимости, сначала перейдя в каталог Django tests/ и затем выполнив:
$ pip install -r requirements/py3.txt
Теперь мы готовы запустить набор тестов. Если вы используете GNU/Linux, Mac OS X или какой-нибудь другой вариант Unix, выполните:
$ ./runtests.py
Теперь расслабьтесь и отдохните. Весь набор тестов Django состоит из более чем 9600 разных тестов, поэтому его выполнение может занять от 5 до 15 минут, в зависимости от скорости вашего компьютера.
Во время выполнения набора тестов Django вы увидите поток символов, представляющих состояние каждого теста по мере его выполнения. E указывает, что во время теста была вызвана ошибка, а F указывает, что утверждения теста не сработали. Оба этих случая считаются сбоями тестов. Между тем, x и s указывают на ожидаемые сбои и пропущенные тесты соответственно. Точки обозначают проходящие тесты.
Пропущенные тесты обычно связаны с отсутствием внешних библиотек, необходимых для выполнения теста; см. Выполнение всех тестов для списка зависимостей и убедитесь, что установлены все необходимые для тестов, связанные с изменениями, которые вы вносите (нам не понадобятся никакие для этого учебника).
После завершения тестов вам должно быть отображено сообщение, информирующее вас о том, пройден ли набор тестов или нет. Поскольку вы еще не внесли никаких изменений в код Django, весь набор тестов должен пройти. Если у вас есть сбои или ошибки, убедитесь, что вы правильно выполнили все предыдущие шаги. См. Запуск юнит-тестов для получения дополнительной информации. Если вы используете Python 3.5+ или выше, будет несколько сбоев, связанных с предупреждениями о устаревании, которые можно проигнорировать. Эти сбои с тех пор были исправлены в Django.
Обратите внимание, что последний ствол Django не всегда стабилен. При разработке на основе ствола вы можете проверить непрерывную интеграцию Django, чтобы определить, являются ли сбои специфичными для вашей машины или присутствуют также в официальных сборках Django. Если вы нажмете, чтобы просмотреть конкретную сборку, вы можете просмотреть «Матрицу конфигурации», которая показывает сбои, разбитые по версиям Python и бэкенду базы данных.
Примечание
Для этого учебника и проблемы, над которой мы работаем, тестирование против SQLite достаточно, однако, возможно (и иногда необходимо) запустить тесты с использованием другой базы данных.
Написание некоторых тестов для вашего тикета
В большинстве случаев для того, чтобы исправление было принято в Django, оно должно включать тесты. Для исправлений ошибок это означает написание регрессионного теста, чтобы убедиться, что ошибка никогда не будет повторно введена в Django позже. Регрессионный тест должен быть написан таким образом, чтобы он завершался ошибкой, пока ошибка существует, и проходил, когда ошибка была исправлена. Для исправлений, содержащих новые функции, вам необходимо включить тесты, которые гарантируют, что новые функции работают правильно. Они также должны завершаться ошибкой, когда новая функция отсутствует, а затем пройти, как только она будет реализована.
Хороший способ сделать это — написать новые тесты сначала, прежде чем вносить какие-либо изменения в код. Этот стиль разработки называется разработкой на основе тестов и может применяться как к целым проектам, так и к отдельным исправлениям. После написания тестов вы запускаете их, чтобы убедиться, что они действительно завершаются ошибкой (поскольку вы еще не исправили эту ошибку или не добавили эту функцию). Если ваши новые тесты не завершаются ошибкой, вам нужно исправить их, чтобы они завершались. В конце концов, регрессионный тест, который проходит независимо от того, существует ли ошибка, не очень полезен для предотвращения повторного появления этой ошибки в будущем.
Теперь для нашего практического примера.
Написание некоторых тестов для тикета #24788
Тикет #24788 предлагает небольшое добавление функции: возможность указать атрибут уровня класса prefix для классов Form, так что:
[…] 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) будет использоваться префикс на уровне класса. Однако прежде чем мы внесем эти изменения, мы напишем несколько тестов, чтобы проверить, что наши изменения работают правильно и будут продолжать работать правильно в будущем.
Перейдите в папку tests/forms_tests/tests/ Django и откройте файл 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 при создании экземпляра также работает.
Но это тестирование выглядит немного сложно...
Если вы никогда раньше не работали с тестами, они могут показаться немного сложными для написания с первого взгляда. К счастью, тестирование — очень важная тема в компьютерном программировании, поэтому в интернете много информации:
- Хорошее первое знакомство с написанием тестов для Django можно найти в документации по Написанию и запуску тестов.
- Dive Into Python (бесплатная онлайн-книга для начинающих разработчиков Python) включает прекрасное введение в тестирование на основе единиц.
- После прочтения этих материалов, если вам нужно что-то более существенное, всегда есть документация по Python
unittest.
Запуск вашего нового теста
Помните, что мы еще не внесли никаких изменений в BaseForm, поэтому наши тесты завершатся ошибкой. Давайте запустим все тесты в папке forms_tests чтобы убедиться, что это действительно происходит. Из командной строки перейдите в каталог Django tests/ и запустите:
$ ./runtests.py forms_tests
Если тесты были выполнены правильно, вы должны увидеть один сбой, соответствующий методу теста, который мы добавили. Если все тесты прошли, то вы должны убедиться, что вы добавили новый тест, показанный выше, в соответствующую папку и класс.
Написание кода для вашего тикета
Далее мы добавим функциональность, описанную в тикете #24788 в Django.
Написание кода для тикета #24788
Перейдите в папку django/django/forms/ и откройте файл forms.py. Найдите класс BaseForm в строке 72 и добавьте атрибут класса prefix сразу после атрибута field_order.
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, перейдите в каталог Django tests/ и запустите:
$ ./runtests.py forms_tests
Упс, хорошо, что мы написали эти тесты! Вы все равно должны увидеть один сбой с следующим исключением:
AssertionError: None != 'foo'
Мы забыли добавить условное выражение в метод __init__. Исправьте self.prefix = prefix, который сейчас находится на строке 87 в django/forms/forms.py, добавив условное выражение:
if prefix is not None:
self.prefix = prefix
Перезапустите тесты, и все должно пройти. Если это не так, убедитесь, что вы правильно изменили класс BaseForm как показано выше и скопировали новый тест правильно.
Запуск набора тестов Django во второй раз
После того, как вы убедитесь, что ваше исправление и ваш тест работают правильно, неплохо запустить весь набор тестов Django, чтобы убедиться, что ваши изменения не ввели никаких ошибок в другие области Django. Хотя успешное прохождение всего набора тестов не гарантирует отсутствие ошибок в вашем коде, это помогает выявить многие ошибки и регрессии, которые в противном случае могли бы остаться незамеченными.
Чтобы запустить весь набор тестов Django, перейдите в каталог Django tests/ и выполните:
$ ./runtests.py
Пока вы не увидите никаких сбоев, все в порядке.
Написание документации
Это новая функция, поэтому ее следует задокументировать. Добавьте следующий раздел в строке 1068 (в конце файла) django/docs/ref/forms/api.txt.
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 для первых авторов. В ней содержится много хороших советов для тех из нас, кто только начинает помогать Django.
- После этого, если вы все еще хотите узнать больше о внесении вклада, вы всегда можете просмотреть остальную часть документации Django по внесению вклада. Она содержит массу полезной информации и должна быть вашим первоначальным источником для ответа на любые вопросы, которые у вас могут возникнуть.
Поиск вашего первого реального билета
После того, как вы изучили некоторую информацию, вы будете готовы выйти и найти собственный билет для написания исправления. Обращайте особое внимание на билеты с критерием «легкие». Эти билеты, как правило, намного проще по своей природе и отлично подходят для авторов, которые только начинают свой вклад. После того, как вы освоитесь с внесением вклада в Django, вы сможете перейти к написанию исправлений для более сложных и запутанных билетов.
Если вы просто хотите начать уже (и никто не станет вас винить!), попробуйте взглянуть на список легких билетов, требующих исправлений и легких билетов, для которых требуются улучшенные исправления. Если вы знакомы с написанием тестов, вы также можете взглянуть на список легких билетов, требующих тестов. Помните о том, что необходимо соблюдать рекомендации по захвату билетов, о которых упоминалось в ссылке на документацию Django по заявке на билеты и отправке исправлений.
Что дальше?
После того, как к билету приложено исправление, его необходимо проверить второй парной глазом. После загрузки исправления или отправки запроса на включение обязательно обновите метаданные билета, установив флажки на билете, указывающие «есть исправление», «не требуются тесты» и т. д., чтобы другие могли его найти для проверки. Вклад не обязательно всегда означает написание исправления с нуля. Проверка существующих исправлений также является очень полезным вкладом. См. Проверка билетов для получения подробностей.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/intro/contributing/