Spec-Zone.ru › Django 2.0

Написание вашего первого патча для 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!

Текущая версия Django не поддерживает Python 2.7. Скачайте 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 как commit 4df7e8483b2679fc1cba3410f08960bac6f51115. Следовательно, мы будем использовать ревизию Django непосредственно перед этим, commit 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 trunk может не всегда быть стабильной. При разработке на trunk вы можете проверить постоянные интеграционные сборки Django, чтобы определить, являются ли ошибки специфичными для вашей машины или они также присутствуют в официальных сборках Django. Если вы нажмёте, чтобы просмотреть определённую сборку, вы сможете просмотреть «Матрицу конфигурации», которая показывает ошибки, разбитые по версиям Python и бэкендам баз данных.

Примечание

Для этого руководства и тикета, над которым мы работаем, тестирование с использованием SQLite достаточно, однако, возможно (и иногда необходимо) запустить тесты с использованием другой базы данных.

Создание ветки для вашего исправления

Перед внесением любых изменений создайте новую ветку для тикета:

$ git checkout -b ticket_24788

Вы можете выбрать любое имя для ветки, «ticket_24788» — пример. Все изменения, внесённые в эту ветку, будут специфичны для тикета и не повлияют на основную копию кода, которую мы клонировали ранее.

Написание тестов для вашего тикета

В большинстве случаев для того, чтобы исправление было принято в 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:
    # 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:
     # 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. Вы увидите свою ветку в разделе «Ваши недавно отправленные ветки». Нажмите «Сравнить и создать запрос на вытягивание» рядом с ней.

Пожалуйста, не делайте этого для этого учебника, но на следующей странице, которая отображает предварительный просмотр патча, вы нажмете «Создать запрос на вытягивание».

Следующие шаги

Поздравляем, вы научились создавать запрос на вытягивание для 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/2.0/intro/contributing/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API