Написание вашего первого патча для 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. Если у вас есть вопросы, там, вероятно, есть ответы.
Установка Git
Для этого учебника вам потребуется установленный Git, чтобы загрузить текущую рабочую версию Django и сгенерировать файлы патчей для внесенных изменений.
Чтобы проверить, установлен ли Git, введите git в командной строке. Если вы получаете сообщения о том, что эта команда не найдена, вам нужно загрузить и установить её, см. страницу загрузки Git.
Если вы не очень знакомы с Git, вы всегда можете узнать больше о его командах (после установки) набрав git help в командной строке.
Получение копии рабочей версии Django
Первый шаг к участию в разработке Django — получить копию исходного кода. В командной строке используйте команду cd для перехода в каталог, где вы хотите разместить локальную копию Django.
Загрузите репозиторий исходного кода Django, используя следующую команду:
git clone https://github.com/django/django.git
Примечание
Для пользователей, которые хотят использовать virtualenv, вы можете использовать:
pip install -e /path/to/your/local/clone/django/
(где django — каталог вашего клонирования, содержащий setup.py) для связи вашего клонированного контрольного пункта с виртуальной средой. Это отличный вариант для изоляции вашей рабочей копии Django от остальной системы и предотвращает потенциальные конфликты пакетов.
Возврат к предыдущей версии Django
Для этого учебника мы будем использовать билет #17549 в качестве учебного случая, поэтому мы отмотаем историю версий Django в git до момента, когда патч этого билета ещё не был применён. Это позволит нам пройти все шаги, связанные с написанием этого патча с нуля, включая запуск набора тестов Django.
Помните, что, хотя мы будем использовать более старую версию основного кода Django для целей учебника ниже, всегда используйте текущую рабочую версию Django при работе над своим патчем для билета!
Примечание
Патч для этого билета был написан Ульрихом Петри и был применён к Django как коммит ac2052ebc84c45709ab5f0f25e685bf656ce79bc. Поэтому мы будем использовать версию Django непосредственно перед этим, коммит 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac.
Перейдите в корневой каталог Django (это тот, который содержит django, docs, tests, AUTHORS, и т. д.). Затем вы можете переключиться на более старую версию Django, которую мы будем использовать в учебнике ниже:
git checkout 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac
Запуск набора тестов Django в первый раз
При внесении изменений в Django очень важно, чтобы ваши изменения не вносили ошибки в другие части Django. Один из способов проверить, работает ли Django после внесения изменений, — это запустить набор тестов Django. Если все тесты пройдут, можно быть относительно уверенным, что ваши изменения не полностью сломали Django. Если вы никогда раньше не запускали набор тестов Django, рекомендуется запустить его один раз предварительно, просто чтобы ознакомиться с тем, как должен выглядеть его вывод.
Мы можем запустить набор тестов, просто cd-нув в каталог Django tests/ и, если вы используете GNU/Linux, Mac OS X или какую-либо другую разновидность Unix, выполните:
PYTHONPATH=.. python runtests.py --settings=test_sqlite
Если вы работаете в Windows, вышеуказанное должно работать при условии использования «Git Bash», предоставляемого стандартной установкой Git. GitHub предлагает хороший учебник.
Примечание
Если вы используете virtualenv, вы можете опустить PYTHONPATH=.. при запуске тестов. Это указывает Python на то, чтобы искать Django в родительском каталоге tests. virtualenv автоматически помещает вашу копию Django в PYTHONPATH.
Теперь расслабьтесь и отдохните. Весь набор тестов Django насчитывает более 4800 различных тестов, поэтому его выполнение может занимать от 5 до 15 минут в зависимости от скорости вашего компьютера.
Во время выполнения набора тестов Django вы увидите поток символов, представляющий состояние каждого теста по мере его выполнения. E указывает, что во время тестирования возникла ошибка, а F указывает, что утверждения теста не сработали. Оба эти случая считаются ошибками теста. В то же время x и s указывают на ожидаемые ошибки и пропущенные тесты соответственно. Точки обозначают проходящие тесты.
Пропущенные тесты, как правило, обусловлены отсутствием внешних библиотек, необходимых для выполнения теста; см. Запуск всех тестов для получения списка зависимостей и убедитесь в установке всех, относящихся к изменениям, которые вы делаете (нам не понадобятся никакие для этого учебника).
По завершении тестов вы должны увидеть сообщение, информирующее вас о том, прошёл набор тестов или нет. Поскольку вы ещё не внесли никаких изменений в код Django, весь набор тестов **должен** пройти. Если у вас есть ошибки, убедитесь, что вы правильно выполнили все предыдущие шаги. См. Запуск модульных тестов для получения дополнительной информации.
Обратите внимание, что последняя версия Django может не всегда быть стабильной. При разработке с использованием основной ветки вы можете проверить непрерывную интеграцию Django, чтобы определить, являются ли ошибки специфичными для вашего компьютера или также присутствуют в официальных сборках Django. Нажав для просмотра конкретного сборки, вы можете посмотреть «Матрицу конфигурации», которая показывает ошибки, разбитые по версиям Python и базам данных.
Примечание
Для этого учебника и билета, над которым мы работаем, тестирование против SQLite достаточно, однако, возможно (и иногда необходимо) запустить тесты с использованием другой базы данных.
Написание тестов для вашего билета
В большинстве случаев для принятия патча в Django он должен включать тесты. Для патчей исправления ошибок это означает написание регрессионного теста, чтобы убедиться, что ошибка никогда не будет повторно введена в Django позже. Регрессионный тест должен быть написан таким образом, чтобы он завершался неудачей, пока ошибка существует, и завершался успехом, когда ошибка была исправлена. Для патчей, содержащих новые функции, вам нужно будет включить тесты, гарантирующие правильную работу новых функций. Они также должны завершаться неудачей, когда новая функция отсутствует, а затем завершаться успехом после её реализации.
Хороший способ сделать это — сначала написать новые тесты, прежде чем вносить какие-либо изменения в код. Этот стиль разработки называется разработкой с тестированием и может быть применён как к целым проектам, так и к отдельным патчам. После написания тестов вы запускаете их, чтобы убедиться, что они действительно завершаются неудачей (поскольку вы ещё не исправили эту ошибку или не добавили эту функцию). Если ваши новые тесты не завершаются неудачей, вам нужно их исправить, чтобы они завершались неудачей. В конце концов, регрессионный тест, который проходит независимо от того, существует ли ошибка, не очень полезен для предотвращения возникновения этой ошибки в будущем.
END_OF_DOCUMENT_MARKERТеперь рассмотрим практический пример.
Написание тестов для задачи №17549
Задача #17549 описывает добавление следующего небольшого функционального улучшения:
Полезно для поля URLField предоставить способ открытия URL; в противном случае можно использовать поле CharField.Для решения этой задачи мы добавим метод render в класс AdminURLFieldWidget, чтобы отобразить ссылку по клику над виджетом ввода. Однако перед внесением этих изменений мы напишем несколько тестов, чтобы убедиться, что наши изменения работают корректно и будут работать правильно в будущем.
Перейдите в папку tests/regressiontests/admin_widgets/ Django и откройте файл tests.py. Добавьте следующий код в строку 269, сразу перед классом AdminFileWidgetTest:
class AdminURLWidgetTest(DjangoTestCase):
def test_render(self):
w = widgets.AdminURLFieldWidget()
self.assertHTMLEqual(
conditional_escape(w.render('test', '')),
'<input class="vURLField" name="test" type="text" />'
)
self.assertHTMLEqual(
conditional_escape(w.render('test', 'http://example.com')),
'<p class="url">Currently:<a href="http://example.com">http://example.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com" /></p>'
)
def test_render_idn(self):
w = widgets.AdminURLFieldWidget()
self.assertHTMLEqual(
conditional_escape(w.render('test', 'http://example-äüö.com')),
'<p class="url">Currently:<a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com" /></p>'
)
def test_render_quoting(self):
w = widgets.AdminURLFieldWidget()
self.assertHTMLEqual(
conditional_escape(w.render('test', 'http://example.com/<sometag>some text</sometag>')),
'<p class="url">Currently:<a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com/<sometag>some text</sometag>" /></p>'
)
self.assertHTMLEqual(
conditional_escape(w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')),
'<p class="url">Currently:<a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com/<sometag>some text</sometag>" /></p>'
)
Новые тесты проверяют, что метод render, который мы добавим, работает правильно в нескольких различных ситуациях.
Но это дело с тестами выглядит немного сложно...
Если вы никогда раньше не сталкивались с тестами, на первый взгляд их написание может показаться сложным. К счастью, тестирование — очень важная тема в компьютерном программировании, поэтому в интернете много информации по этой теме:
- Хорошее начальное знакомство с написанием тестов для Django можно найти в документации по написанию и запуску тестов.
- В книге «Dive Into Python» (бесплатная онлайн-книга для начинающих разработчиков Python) есть отличное введение в модульное тестирование.
- После прочтения этих материалов, если вам понадобится более глубокое погружение, всегда можно обратиться к документации Python unittest.
Запуск нового теста
Помните, что мы еще не внесли никаких изменений в AdminURLFieldWidget , поэтому наши тесты будут провалены. Давайте запустим все тесты в папке model_forms_regress, чтобы убедиться, что это действительно происходит. Из командной строки перейдите в каталог Django tests/ и запустите:
PYTHONPATH=.. python runtests.py --settings=test_sqlite admin_widgets
Если тесты прошли успешно, вы должны увидеть три ошибки, соответствующие каждому из добавленных методов тестирования. Если все тесты прошли, убедитесь, что вы добавили новый тест, показанный выше, в соответствующую папку и класс.
Написание кода для вашей задачи
Далее мы добавим функциональность, описанную в задаче #17549, в Django.
Написание кода для задачи №17549
Перейдите в папку django/django/contrib/admin/ и откройте файл widgets.py. Найдите класс AdminURLFieldWidget в строке 302 и добавьте следующий метод render после существующего метода __init__:
def render(self, name, value, attrs=None):
html = super(AdminURLFieldWidget, self).render(name, value, attrs)
if value:
value = force_text(self._format_value(value))
final_attrs = {'href': mark_safe(smart_urlquote(value))}
html = format_html(
'<p class="url">{} <a {}>{}</a><br />{} {}</p>',
_('Currently:'), flatatt(final_attrs), value,
_('Change:'), html
)
return html
Проверка успешного выполнения теста
После внесения изменений в Django нам нужно убедиться, что написанные ранее тесты проходят, чтобы проверить, правильно ли работает добавленный код. Для запуска тестов в папке admin_widgets перейдите в каталог Django tests/ и запустите:
PYTHONPATH=.. python runtests.py --settings=test_sqlite admin_widgets
Упс, хорошо, что мы написали эти тесты! Вы по-прежнему увидите 3 ошибки с исключением:
NameError: global name 'smart_urlquote' is not defined
Мы забыли добавить импорт для этого метода. Добавьте импорт smart_urlquote в конец строки 13 файла django/contrib/admin/widgets.py, чтобы он выглядел следующим образом:
from django.utils.html import escape, format_html, format_html_join, smart_urlquote
Перезапустите тесты, и все должно пройти успешно. Если нет, убедитесь, что вы правильно изменили класс AdminURLFieldWidget как показано выше и правильно скопировали новые тесты.
Запуск всей тестовой базы Django во второй раз
После того, как вы проверили, что ваш патч и тест работают правильно, рекомендуется запустить весь набор тестов Django, чтобы убедиться, что ваше изменение не ввело никаких ошибок в другие части Django. Хотя успешное прохождение всего набора тестов не гарантирует отсутствие ошибок, это помогает обнаружить многие ошибки и регрессии, которые в противном случае могли остаться незамеченными.
Чтобы запустить весь набор тестов Django, перейдите в каталог Django tests/ и запустите:
PYTHONPATH=.. python runtests.py --settings=test_sqlite
До тех пор, пока вы не увидите никаких ошибок, все в порядке. Обратите внимание, что это исправление также внесло небольшое изменение в CSS для форматирования нового виджета. Вы можете внести изменения, если хотите, но мы пропустим это сейчас ради краткости.
Написание документации
Это новая функция, поэтому она должна быть задокументирована. Добавьте следующее в строку 925 файла django/docs/ref/models/fields.txt под существующей документацией для URLField:
.. versionadded:: 1.5
The current value of the field will be displayed as a clickable link above the
input widget.
Дополнительную информацию о написании документации, в том числе объяснение того, что представляет собой versionadded , см. в разделе Написание документации. Эта страница также содержит объяснение того, как создать копию документации локально, чтобы вы могли предварительно просмотреть сгенерированный HTML.
Генерация патча для ваших изменений
Сейчас пришло время сгенерировать файл патча, который можно загрузить в Trac или применить к другой копии Django. Чтобы посмотреть содержимое патча, выполните следующую команду:
git diff
Это покажет различия между вашей текущей копией Django (с вашими изменениями) и ревизией, которую вы изначально проверили ранее в руководстве.
После просмотра патча нажмите клавишу q для возврата в командную строку. Если содержимое патча выглядит нормально, вы можете выполнить следующую команду, чтобы сохранить файл патча в текущем рабочем каталоге:
git diff > 17549.diff
Теперь в корневом каталоге Django должен быть файл 17549.diff. Этот файл патча содержит все ваши изменения и должен выглядеть так:
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 1e0bc2d..9e43a10 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -10,7 +10,7 @@ from django.contrib.admin.templatetags.admin_static import static
from django.core.urlresolvers import reverse
from django.forms.widgets import RadioFieldRenderer
from django.forms.util import flatatt
-from django.utils.html import escape, format_html, format_html_join
+from django.utils.html import escape, format_html, format_html_join, smart_urlquote
from django.utils.text import Truncator
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
@@ -306,6 +306,18 @@ class AdminURLFieldWidget(forms.TextInput):
final_attrs.update(attrs)
super(AdminURLFieldWidget, self).__init__(attrs=final_attrs)
+ def render(self, name, value, attrs=None):
+ html = super(AdminURLFieldWidget, self).render(name, value, attrs)
+ if value:
+ value = force_text(self._format_value(value))
+ final_attrs = {'href': mark_safe(smart_urlquote(value))}
+ html = format_html(
+ '<p class="url">{} <a {}>{}</a><br />{} {}</p>',
+ _('Currently:'), flatatt(final_attrs), value,
+ _('Change:'), html
+ )
+ return html
+
class AdminIntegerFieldWidget(forms.TextInput):
class_name = 'vIntegerField'
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index 809d56e..d44f85f 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -922,6 +922,10 @@ Like all :class:`CharField` subclasses, :class:`URLField` takes the optional
:attr:`~CharField.max_length`argument. If you don't specify
:attr:`~CharField.max_length`, a default of 200 is used.
+.. versionadded:: 1.5
+
+The current value of the field will be displayed as a clickable link above the
+input widget.
Relationship fields
===================
diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
index 4b11543..94acc6d 100644
--- a/tests/regressiontests/admin_widgets/tests.py
+++ b/tests/regressiontests/admin_widgets/tests.py
@@ -265,6 +265,35 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase):
'<p class="datetime">Datum: <input value="01.12.2007" type="text" class="vDateField" name="test_0" size="10" /><br />Zeit: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>',
)
+class AdminURLWidgetTest(DjangoTestCase):
+ def test_render(self):
+ w = widgets.AdminURLFieldWidget()
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', '')),
+ '<input class="vURLField" name="test" type="text" />'
+ )
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', 'http://example.com')),
+ '<p class="url">Currently:<a href="http://example.com">http://example.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com" /></p>'
+ )
+
+ def test_render_idn(self):
+ w = widgets.AdminURLFieldWidget()
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', 'http://example-äüö.com')),
+ '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com" /></p>'
+ )
+
+ def test_render_quoting(self):
+ w = widgets.AdminURLFieldWidget()
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', 'http://example.com/<sometag>some text</sometag>')),
+ '<p class="url">Currently:<a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com/<sometag>some text</sometag>" /></p>'
+ )
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')),
+ '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com/<sometag>some text</sometag>" /></p>'
+ )
class AdminFileWidgetTest(DjangoTestCase):
def test_render(self):
Что делать дальше?
Поздравляем, вы сгенерировали свой первый патч Django! Теперь, когда у вас есть этот опыт, вы можете использовать эти навыки, чтобы улучшить базу кода Django. Создание патчей и их прикрепление к задачам Trac полезно, однако, так как мы используем git — рекомендуется использовать более ориентированный на git рабочий процесс.
Поскольку мы никогда не коммитили наши изменения локально, выполните следующие действия, чтобы вернуть свою ветку git в хорошее исходное состояние:
git reset --hard HEAD git checkout master
Дополнительная информация для новых участников
Перед тем как углубиться в написание патчей для Django, вам следует ознакомиться со следующей информацией о сотрудничестве:
- Вы должны ознакомиться с документацией Django по получению задач и отправке патчей. Она охватывает правила этикета Trac, как заявлять задачи, ожидаемый стиль кодирования патчей и многие другие важные детали.
- Первые участники проекта также должны прочитать документацию для новых участников. Она содержит много полезных советов для тех, кто только начинает помогать с Django.
- После этого, если вы хотите получить больше информации о сотрудничестве, вы всегда можете просмотреть остальную часть документации Django по сотрудничеству. Она содержит массу полезной информации и должна быть вашим первоисточником для ответа на любые вопросы.
Поиск вашей первой реальной задачи
После изучения этой информации вы будете готовы найти свою собственную задачу, для которой можно написать патч. Обращайте особое внимание на задачи с критерием «легкие задачи». Такие задачи обычно более простые по природе и идеально подходят для новых участников. После того, как вы освоитесь с участием в разработке Django, вы можете перейти к написанию патчей для более сложных задач.
Если вы просто хотите начать (и никто вас за это не осудит!), попробуйте взглянуть на список легких задач, требующих патчей, и легких задач, у которых есть патчи, требующие улучшения. Если вы знакомы с написанием тестов, вы также можете посмотреть список легких задач, нуждающихся в тестах. Просто помните о рекомендациях по заявлянию задач, упомянутых в ссылке на документацию Django о заявлении задач и отправке патчей.
Что дальше?
После того, как для задачи есть патч, его необходимо проверить второй человек. После загрузки патча или отправки запроса на объединение убедитесь, что вы обновили метаданные задачи, установив флаги в задаче, чтобы указать, что «есть патч», «не нужны тесты» и т. д., чтобы другие могли найти ее для проверки. Сотрудничество не всегда подразумевает написание патча с нуля. Проверка существующих патчей также является очень полезным вкладом. Подробности см. в разделе Проверка задач.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/intro/contributing/