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

comments