Написание первого патча для 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. Она содержит много ценной информации и является обязательной к прочтению для всех, кто хочет стать регулярным участником проекта 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 — получить копию исходного кода. Сначала сделайте форк Django на GitHub. Затем в командной строке используйте команду cd, чтобы перейти в каталог, где вы хотите разместить локальную копию Django.
Загрузите репозиторий исходного кода Django с помощью следующей команды:
$ git clone git@github.com:YourGitHubName/django.git
Теперь, когда у вас есть локальная копия Django, вы можете установить её так же, как и любой другой пакет, используя pip. Самый удобный способ сделать это — использовать виртуальную среду (или virtualenv), которая является функцией, встроенной в Python, позволяющей создавать отдельный каталог установленных пакетов для каждого проекта, чтобы они не мешали друг другу.
Хорошо держать все виртуальные среды в одном месте, например, в .virtualenvs/ в вашем домашнем каталоге. Создайте его, если он еще не существует:
$ mkdir ~/.virtualenvs
Теперь создайте новую виртуальную среду, выполнив:
$ 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
Последний шаг по настройке вашей виртуальной среды — активировать её:
$ source ~/.virtualenvs/djangodev/bin/activate
Если команда source недоступна, вы можете попробовать использовать точку вместо неё:
$ . ~/.virtualenvs/djangodev/bin/activate
Для пользователей Windows
Чтобы активировать вашу виртуальную среду на Windows, выполните:
$ source ~/virtualenvs/djangodev/Scripts/activate
Вам необходимо активировать виртуальную среду каждый раз, когда вы открываете новое окно терминала. virtualenvwrapper — полезный инструмент для упрощения этой задачи.
Всё, что вы установите через pip с этого момента, будет установлено в вашей новой виртуальной среде, изолированно от других сред и системных пакетов. Также имя текущей активной виртуальной среды отображается в командной строке, чтобы вы могли отслеживать, какую среду вы используете. Приступайте к установке ранее клонированной копии 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
Если при установке возникнет ошибка, на вашем компьютере может отсутствовать зависимость для одного или нескольких пакетов Python. Обратитесь к документации отсутствующего пакета или выполните поиск в Интернете с сообщением об ошибке.
Теперь мы готовы запустить набор тестов. Если вы используете GNU/Linux, macOS или другую разновидность Unix, выполните:
$ ./runtests.py
Теперь сядьте и расслабьтесь. Полный набор тестов Django содержит более 9600 различных тестов, поэтому его выполнение может занять от 5 до 15 минут, в зависимости от скорости вашего компьютера.
Во время выполнения набора тестов Django вы увидите поток символов, представляющих статус каждого теста по мере его выполнения. E указывает, что во время теста была вызвана ошибка, а F указывает, что утверждения теста не выполнились. Оба эти случая считаются ошибками теста. В то же время, x и s указывают на ожидаемые ошибки и пропущенные тесты соответственно. Точки указывают на успешное выполнение тестов.
Пропущенные тесты обычно связаны с отсутствием внешних библиотек, необходимых для выполнения теста; см. Запуск всех тестов для списка зависимостей и убедитесь, что установлены все зависимости, связанные с внесенными вами изменениями (нам не понадобятся никакие для этого учебника). Некоторые тесты специфичны для определенного бэкэнда базы данных и будут пропущены, если не используется этот бэкэнд. SQLite является бэкэндом базы данных по умолчанию. Чтобы запустить тесты с помощью другого бэкэнда, см. Использование другого модуля настроек.
После завершения тестов вам должно быть сообщено, прошёл ли набор тестов или нет. Поскольку вы еще не внесли никаких изменений в код Django, весь набор тестов должен пройти успешно. Если у вас есть ошибки или сбои, убедитесь, что вы правильно выполнили все предыдущие шаги. См. Запуск модульных тестов для получения дополнительной информации. Если вы используете Python 3.5+ , могут быть несколько ошибок, связанных с предупреждениями о устаревании, которые можно проигнорировать. Эти ошибки с тех пор были исправлены в Django.
Обратите внимание, что последний ствол Django не всегда стабилен. При разработке с использованием последней версии вы можете проверить непрерывную интеграцию Django, чтобы определить, являются ли ошибки специфичными для вашего компьютера или также присутствуют в официальных сборках Django. Если вы нажмете, чтобы просмотреть определённую сборку, вы сможете просмотреть «Матрицу конфигурации», которая показывает ошибки, разбитые по версии Python и бэкэнду базы данных.
Примечание
Для этого учебника и тикета, над которым мы работаем, тестирование против SQLite достаточно, однако, возможно (и иногда необходимо) запустить тесты с использованием другой базы данных.
Создание ветки для вашего исправления
Перед внесением любых изменений создайте новую ветку для тикета:
$ git checkout -b ticket_24788
Вы можете выбрать любое имя для ветки, «ticket_24788» — пример. Все изменения, внесенные в эту ветку, будут специфичными для тикета и не повлияют на основную копию кода, которую мы клонировали ранее.
Написание тестов для вашего тикета
В большинстве случаев для того, чтобы исправление было принято в Django, оно должно включать тесты. Для исправлений ошибок это означает написание регрессионного теста, чтобы убедиться, что ошибка никогда не будет повторно введена в Django позднее. Регрессионный тест должен быть написан таким образом, чтобы он не проходил, пока ошибка существует, и проходил, как только ошибка будет исправлена. Для исправлений, содержащих новые функции, вам нужно будет включить тесты, которые гарантируют, что новые функции работают правильно. Они также должны не проходить, когда новая функция отсутствует, и проходить, как только она будет реализована.
Хороший способ сделать это — написать новые тесты сначала, прежде чем вносить какие-либо изменения в код. Этот стиль разработки называется разработка через тестирование и может применяться как к целым проектам, так и к отдельным исправлениям. После написания тестов вы запускаете их, чтобы убедиться, что они действительно не проходят (поскольку вы ещё не исправили ошибку или не добавили функцию). Если ваши новые тесты не не проходят, вам нужно будет их исправить, чтобы они проходили. В конце концов, регрессионный тест, который проходит независимо от того, существует ли ошибка, не очень полезен для предотвращения ее повторного появления.
Теперь для нашего практического примера.
Написание тестов для тикета #24788
Тикет #24788 предлагает небольшое добавление функции: возможность указать атрибут уровня класса 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), будет использоваться префикс на уровне класса. Однако перед внесением этих изменений мы напишем несколько тестов, чтобы проверить, что наше изменение работает правильно и будет продолжать работать правильно в будущем.
Перейдите в папку 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.
Предварительный просмотр ваших изменений
Теперь пришло время просмотреть все внесённые изменения в нашем патче. Чтобы отобразить различия между вашей текущей копией 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(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.
После просмотра патча нажмите клавишу 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 https://github.com/django/django/. Ваша ветка будет отображаться в разделе «Ваши недавно отправленные ветки». Нажмите «Сравнить и создать запрос на объединение» рядом с ней.
Пожалуйста, не делайте этого для этого руководства, но на следующей странице, которая отображает предварительный просмотр патча, вы нажмёте «Создать запрос на объединение».
Следующие шаги
Поздравляем, вы узнали, как создать запрос на объединение для Django! Подробности более сложных техник, которые могут потребоваться, содержатся в работе с Git и GitHub.
Теперь вы можете использовать эти навыки, чтобы помочь улучшить базу кода Django.
Дополнительная информация для новых участников
Прежде чем углубляться в написание патчей для 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.11/intro/contributing/