Django TODO: конструирование системы
Fri 29 June 2012
При работе над проектом конструирование включает другие процессы, в том числе проектирование. Формальная архитектура дает ответы только на вопросы системного уровня, при этом значительная часть проектирования может быть намеренно оставлена на этап конструирования. Проектирование -- это "постепенный" процесс. Проекты приложений не возникают в умах разработчиков сразу в готовом виде. Они развиваются и улучшаются в ходе обзоров, неформальных обсуждений, написания кода и выполнения его ревизий.
Практически во всех случаях проект несколько меняется во время первоначальной разработки системы и еще больше -- при ее модернизации.
Управление сложностью -- самый важный технический аспект разработки ПО. Управлять сложностью гораздо легче, если при проектировании стремиться к простоте. Есть два общих способа достижения простоты: минимизация объема существенной сложности, с которой приходится иметь дело в любой конкретный момент времени, и подавление необязательного роста несущественной сложности.
Одной из самых полезных концепций проектирования является сокрытие информации. Оно полезно на всех уровнях проектирования: от применения именованных констант вместо литералов до создания типов данных и проектирования классов, методов и подсистем [1].
Для снижения сложности Стив Макконелл определил ряд рекомендаций [1]:
- тщательно определяйте интерфейсы классов, чтобы можно было игнорировать внутреннее устройство классов;
- поддерживайте абстракцию, формируемую интерфейсом класса, чтобы не запоминать ненужных деталей;
- избегайте глобальных данных, потому что их использование значительно увеличивает процент кода, который нужно удерживать в уме в любой момент времени;
- избегайте глубоких иерархий наследования, потому что они предъявляют высокие требования к интеллекту;
- избегайте глубокой вложенности циклов и условных операторов, поскольку их можно заменить на более простые управляющие структуры, позволяющие бережнее расходовать умственные ресурсы;
- тщательно определите подход к обработке ошибок, вместо того, чтобы использовать произвольную комбинацию произвольных методик;
- не позволяйте классам достигать размеров целых программ;
- поддерживайте методы короткими;
- используйте ясные, очевидные имена переменных;
- минимизируйте число параметров, передаваемых в метод, или, что еще важнее, передавайте только те параметры, которые нужны для поддержания абстракции, формируемой интерфейсом метода;
- используйте соглашения, чтобы не запоминать произвольные, несущественные различия между разными фрагментами кода;
- создавая для сложного теста булеву функцию и абстрагирую суть теста упрощается код.
При конструировании системы Django TODO я старался придерживаться вышеперечисленных рекомендаций.
Модель данных
Уровень доступа к данным вынесен в отдельную часть приложения, называемый моделью. Модели размещаются в файле models.py.
Модель в Django представляет собой описание данных в базе, представленное на языке Python. В Django модель используется, чтобы выполнить SQL-запрос и вернуть удобные структуры данных Python. Кроме того, модели позволяют представить высокоуровневые концепции, для которых в SQL может не быть аналогов.
Описание моделей на Python лучше по ряду причин:
- сокращается количество мысленных "переключений контекста". Когда приходится писать код на SQL, потом на Python, а потом снова на SQL, продуктивность падает;
- размещение моделей данных в коде упрощает их хранение в системе управления версиями;
- удобные средства миграции моделей (например, South).
Каждая модель соответствует одной таблице БД, а каждый атрибут модели соответствует одному столбцу таблицы. Из этого правила есть исключение, касающееся отношения многие-ко-многим [2].
Соглашения по оформлению кода
При конструировании моделей приняты следующие соглашения по оформлению кода. Последовательность размещения атрибутов класса-модели [3]:
- названия полей;
- атрибуты менеджера модели;
- класс Meta;
- метод __unicode__;
- метод __str__;
- метод save;
- метод get_absolute_url;
- остальные методы.
Вышеперечисленные группы атрибутов должны быть отделены друг от друга пустой строкой.
Если в модели есть поле выбора (choices), оно должно быть оформлено в виде кортежа из кортежей, заглавными буквами. Должны быть расположены в начале модели [3] и названы без использования "магических чисел" [1]. Например:
class Task(models.Model):
UNCERTAIN_STATUS = 0
DONE_STATUS = 1
STOP_STATUS = 2
WAIT_STATUS = 3
WORK_STATUS = 4
STATUS_CHOICES = (
(DONE_STATUS, 'done'),
(STOP_STATUS, 'stop')
)
status = models.IntegerField(choices=STATUS_CHOICES,
default=UNCERTAIN_STATUS)
Методы модели
Разработчик может определить в модели свои собственные методы и тем самым наделять свои объекты дополнительной функциональностью на уровне строк. Методы модели подходят для инкапсуляции всей бизнес-логики в одном месте. Например, фрагмент модели Цепочка:
class Chain(models.Model):
"""Цепочка задач."""
# Пропущены атрибуты модели для экономии места.
# Default manager.
objects = PassThroughManager.for_queryset_class(ChainQuerySet)()
def actual_status(self):
"""Определяет фактический статус цепочки."""
if self.start_date > datetime.date.today():
return self.WAIT_STATUS
if self.task_set.filter(status=Task.STOP_STATUS).exists():
return self.STOP_STATUS
last_task = self.last_task()
if last_task.actual_status() == Task.DONE_STATUS:
return self.DONE_STATUS
else:
return self.WORK_STATUS
Согласно спроектированной архитектуре системы были разработаны следующие методы модели данных Задача:
- be_in_time определяет, успевает ли задача к дедлайну;
- actual_status определяет фактический статус задачи, основываясь на таких данных, как «статический» статус задачи (DONE, STOP), статус предыдущей задачи, порядковый номер задачи и текущая дата;
- start_date определяет дату начала работы над задачей, если это возможно;
- days_to_start определяет количество дней, оставшихся до начала работы над задачей;
- remaining_days определяет количество полных дней, оставшихся до дедлайна. Например, задача ограничена сроком [26; 29) и сейчас 27 число. До дедлайна остался один полный день (28 число), так как текущий день не учитывается;
- days_quantity_after_deadline определяет количество дней, на которые просрочена задача;
- expended_days определяет количество дней, затраченных на задачу;
- duration определяет количество дней, выделенных на выполнение задачи.
Для модели Цепочка были реализованы методы:
- actual_status определяет фактический статус цепочки, основываясь на таких данных, как дата начала работы над цепочкой, статус текущей задачи цепочки;
- deadline определяет дедлайн цепочки. Дедлайн цепочки равен дедлайну последней задачи в цепочке;
- finish_date определяет дату завершения цепочки. Дата завершения цепочки равна дате завершения последней задачи в цепочке;
- be_in_time определяет, успевает ли цепочка задач к дедлайну;
- days_to_start определяет количество дней, оставшихся до начала работы цепочки;
- remaining_days определяет количество дней, оставшихся до дедлайна цепочки. Совпадает с количеством дней, оставшихся до дедлайна последней задачи в цепочке;
- days_quantity_after_deadline определяет количество дней, на которые просрочена цепочка;
- expended_days определяет количество дней, затраченных на цепочку;
- last_task возвращает последнюю задачу из цепочки.
Менеджер модели
Менеджер модели -- это объект, с помощью которого Django выполняет запросы к БД. Каждая модель Django имеет по меньшей мере один менеджер, и разработчик может создавать свои менеджеры для организации специализированных видов доступа.
Потребность создания собственного менеджера может быть вызвана двумя причинами: необходимостью добавить менеджеру дополнительные методы или необходимостью модифицировать исходный объект QuerySet, возвращаемый менеджером [2].
Встроенный в Django менеджер моделей не позволяет строить цепочки методов, таких как actual_tasks = Task.objects.by_worker(user).actual(). Для обхода этого ограничения используется сторонняя библиотека django-model-utils, которая позволяет писать QuerySet вместо менеджера [4]. Например:
# -*- coding: utf-8 -*-
from django.db.models.query import QuerySet
class ChainQuerySet(QuerySet):
def by_owner(self, owner):
"""Возвращает цепочки владельца."""
return self.filter(owner=owner)
def actual(self):
"""Возвращает актуальтуные цепочки задач."""
return self.filter(archive=False).order_by('start_date')
Миграция схемы модели
Во время разработки Django приложений наступает момент, когда необходимо изменить схему модели данных, например, добавить новое поле. Если разработка ведется в группе, то проблема усугубляется тем, что необходимо синхронизировать модель. Эту проблему призвана решить библиотека South. Ее основными задачами является обеспечение простого, стабильного и независимого от БД слоя миграции, чтобы избавить разработчика от проблем изменения схемы.
Рассмотрим типовые примеры начала работы с South [5]. Случай, когда в базе данных нет таблиц и нет файлов миграций -- состояние проекта сразу после выполнения команда manage.py startapp myapp. Далее, вместо команды manage.py syncdb нужно создать начальную миграцию командой manage.py schemamigration myapp --initial и применить миграцию командой manage.py migrate myapp.
Следующий вариант, когда таблицы уже созданы, но нет файлов миграций. В данной ситуации необходимо выполнить команду manage.py convert_to_south myapp.
Возможна ситуация, когда таблицы уже созданы, но миграции еще не применены. Тогда необходимо выполнить manage.py migrate myapp 0001 --fake.
Шаблон
Шаблон -- это текстовый документ или строка Python, который размечен с применением языка шаблонов Django. Шаблон может содержать шаблонные теги и шаблонные переменные.
При выборе места хранения шаблонов в многоразовых Django приложениях рекомендуется [6] использовать следующий путь: корень-репозитория/название_приложения/templates/название_приложения/ название_шаблона. Например, django-todo/todo/templates/todo/base.html.
Название шаблонов следует выбирать придерживаясь следующей конвенции [model]_[function].html, например, task_list.html. Отнюдь не каждое название шаблона, полученное в соответствии с конвенцией, получается подходящим. В таких случаях следует выбирать название по своему усмотрению.
Шаблонный тег -- это некоторое обозначение в шаблоне, с которым ассоциирована программная логика. Например, шаблонный тег может порождать содержимое, выступать в роли управляющей конструкции, получать содержимое из базы данных или разрешать доступ к другим шаблонным тегам.
Шаблоны пользовательских тегов и частичные шаблоны рекомендуется хранить в директории includes. Например, отображение информации о цепочке задач вынесено в шаблон includes/chain.html.
Пользовательские теги шаблона рекомендуется хранить в каталоге: корень-репозитория/название_приложения/templatetags/ [название_приложения]_tags.py. Например, django-todo/todo/templatetags/ todo_tags.py.
Представления и конфигурирование URL
Представление -- функция на языке Python, которая принимает экземпляр класса HttpRequest в качестве первого параметра и возвращает экземпляр класса HttpResponse. Ниже приведен код функции вместе с командами импорта из файла views.py:
# -*- coding: utf-8 -*-
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from todo.models import Chain, Task
@login_required
def actual_tasks(request):
"""Отображает список актуальных задач для исполнителя."""
user = request.user
actual_tasks = Task.objects.by_worker(user).actual()
return render(request, 'todo/task_list.html', {
'place': 'tasks',
'actual_tasks': actual_tasks,
})
Чтобы связать функцию представления с URL, в Django используется механизм конфигурации URL. Django ожидает найти переменную urlpatterns в конфигурации URL. Она определяет соответствие между URL-адресами и обрабатывающим их кодом.
Вот как подключается представление actual_tasks и task_archive в файле urls.py:
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('todo.views',
url(r'^$', 'actual_tasks', name='todo_actual_tasks'),
url(r'^task/archive/$', 'task_archive', name='todo_task_archive'),
)
Любой запрос к URL /task/archive/ должен обрабатываться функцией task_archive, а запрос / будет обрабатываться actual_tasks.
Названия шаблонам URL рекомендуется давать в форме APP_MODEL_VIEW, например, blog_post_detail или blog_post_list.
[1] | (1, 2, 3) Макконелл С. Совершенный код. Мастер-класс / Пер. с англ. – М. : Издательство "Русская редакция", 2012. – 896 стр. : ил. |
[2] | (1, 2) Головатый А., Каплан-Мосс Дж. Django. Подробное руководство, 2-е издание. – Пер. с англ. – СПб.: Символ-Плюс, 2010. – 560 с., ил. |
[3] | (1, 2) Django community. Django Coding Style. |
[4] | Коробов М. Рецепты от ПанГурмана. |
[5] | Godwin A. South documentation. |
[6] | Lincoln Loop company. Django Best Practices. |
Category: Python Tagged: python django django-todo construction