Django standalone models

от автора

imageДобрый день, хабравчане.

Совсем недавно я вдруг открыл для себя всю мощь моделей и форм Django.

Архитектура Django — это Model-View-Template (MVT). Модель отображает данные в базе, вид выполняет код приложения, шаблон занимается выводом. Часть этой тройки — Template, шаблоны — можно использовать отдельно от Django вообще — загружать шаблон и рендерить его с учётом контекста. Об этом говорится в документации.

Однако в документации не говорится, что ещё одна часть — Model — также вполне самостоятельна, и даже более того — её использование вкупе с формами в правильных случаях сильно упрощает жизнь разработчику и делает код простым и понятным.


Итак, поговорим подробнее о моделях.

Модели

Модель — это класс с заданными полями:

class SomeModel(models.Model):     name = models.CharField('name of SomeModel', blank=True, max_length=255) 

Django уже определил для нас всевозможные поля, навроде CharField, PositiveIntegerField, EmailField и так далее, то есть думать не надо — всё уже есть, а чего нет — можно дописать самому. У полей есть атрибуты — максимальная длина поля, возможность пустого значения.

Модели соответствует таблица в базе данных, которая создаётся после manage.py syncdb… или нет? Всем ли моделям соответствует таблица в базе данных? Знающие люди сразу ответят: нет, ведь есть абстрактные базовые классы.

Абстрактные базовые классы

Заглянем в документацию:

Абстрактные базовые классы полезны, когда вы хотите использовать одну и ту же информацию в нескольких моделях. Вы пишите базовый класс и ставите ему abstract=True в его Meta-класс. Эта модель не будет создавать таблицу в базе данных. Вместо этого эта модель будет использована как базовый класс для других моделей, её поля будут добавлены к полям родительского класса.

К сожалению, это единственное применение абстрактных моделей, поэтому отбросим этот вариант как скучный:

Абстрактная модель не может использоваться как нормальная модель Django. Она не генерирует таблицу в базе данных, у неё нет менеджера, и на её основе нельзя создавать инстансы и сохранять их. (вольный перевод)

И всё же, всем ли моделям соответствует таблица в базе данных?

Standalone-модели

В своё время, в связи с ростом количества классов в models.py, я хотел сделать разбиение этого файла на несколько логически обособленных кусочков. Задача решаема, но в ходе разбиения я заметил одну интересную вещь: если определить модель где-нибудь вне models.py (вне видимости стандартного парсера моделей django), то при manage.py syncdb таблица не создаётся, однако модель… работает! То есть это абсолютно валидная конструкция, которую можно создавать в памяти, заполнять данными, вызывать встроенные функции, ну и вообще вытворять всё, что мы привыкли проделывать с моделями django — за исключением сохранения, удаления и прочих операций, связанных с базой данных (соответствующая таблица попросту не существует).

Внешне — никаких отличий от обычных моделей, за исключением расположения:

# project/app/StandaloneModels/models.py # Если бы модель была в project/app/models.py - то таблица создавалась бы class SomeModel(models.Model):     name = models.CharField('name of SomeModel', blank=True, max_length=255)  # Задавать null бессмысленно 

Я их называю standalone models — что-то вроде «одинокие модели» — потому что они находятся не с основными моделями и ведут себя иначе.

Ну и зачем?

Логичный вопрос. Что нам дают такие модели? А вот что:

  • Они как простые классы в python, но с функционалом от django — валидация email, проверка длины полей, проверка на «пустоту» значения, преобразование в dict, что угодно.
  • Интеграция с мощным инструментом — формами от django.

Примеры

Приведу пример из моей недавней практики.

Есть внешний модуль для заполнения бланков «Почты России» и вывода pdf. Модуль — это класс вроде

class RusPost():     def __init__(         self,         client={             'name': '',             'address': '',             'index': '',         }     ):         self.client = client      def render(self):         # ... some magic code         return render_pdf(self.client) 

При инициализации инстанса класса мы передаём в словаре данные клиента (client), для генерации pdf вызываем render().

Появилась задача создать html-форму (инерфейс пользователя) для взаимодействия с библиотекой:
image
Какие тут есть варианты решения задачи?

1. Хардкор-стайл для отлучённых от Django

Вручную писать html форму, которая передаёт данные в view, который вызывает библиотеку:

<!-- template.html --> <form action="" method="POST">     <input type="text" name="name">     ...     <input type="submit" value="Сгенерировать форму"> </form> 

def toPdf(request):     ruspost = RusPost(         client={             'name': request.POST.get('name', ''),             'address': request.POST.get('address', ''),             'index': request.POST.get('index', ''),         }     )     ruspost.render()     ... 

Минусы — ручной мартышкин труд.

2. Form-стайл

Создать форму

# forms.py class RuspostClientForm(forms.Form):     name = forms.CharField()     address = forms.CharField()     index = forms.CharField() 

<!-- template.html --> <form action="" method="POST">     {{ form }}     <input type="submit" value="Сгенерировать форму"> </form> 

def toPdf(request):     form = RuspostForm(request.POST or None)     if form.is_valid():         ruspost = RusPost(             client=form.cleaned_data,         )         ruspost.render()     ... 

Чем хороши формы — это

  • автогенерация формы (использование {{ form }})
  • валидация формы (form.is_valid())
  • получение готового словаря с данными формы (form.cleaned_data)
3. Standalone models стайл

Во втором примере мы явно описывали, какие поля должна иметь форма. Давайте вместо этого перейдём на новый уровень абстракции — опишем нашего клиента через standalone-модель:

class RuspostClient(models.Model):     name = models.CharField('имя', max_length=64)     address = models.CharField('адрес', max_length=255)     index = models.CharField('индекс', max_length=6) 

Мы задали понятие «Клиент» (RuspostClient) через класс так, как мы его понимаем — имя, адрес, индекс, тип полей и длина. Нас не сильно-то волнует, какие типы полей должны быть в форме, потому что формы для клиента мы теперь автоматически создаём одной строчкой:

from django.forms.models import modelform_factory form_client = modelform_factory(RuspostClient)(request.POST or None) 

Но мы также можем и более точно описать, что мы хотим от формы:

from django.forms.models import modelform_factory form_client = modelform_factory(     RuspostClient,     widgets={         'name': TextInput(attrs={'style': 'width: 300px;'}),  # Для этих полей задать ширину 300 px         'address': Textarea(attrs={'style': 'width: 300px;'}),     },     exclude=('index', ) )(     request.POST or {  # Заполни поля формы либо данными из POST (если они есть)...         'name': 'Аноним',   # ... либо значениями по умолчанию, которые здесь указаны         'address': 'Ул. Пушкина, дом Калатушкина',     } ) 

Полученную форму мы можем проверить и сохранить полученную модель:

if form_client.is_valid():     client = form_client.save(commit=False)  # Без commit=False будет ошибка, т.к. в БД мы сохранить не можем 

Фактически, мы получили ту же самую форму, что и в пункте 2. Так ради чего была вообще введена модель?

  • На выходе мы получили не словарь, а инстанс модели django. Отличия инстанса класса от словаря очевидны, но, как минимум, инстанс может иметь методы. Мы переходим от функционального программирования к ООП — согласитесь, что куда приятней (и правильней) вызывать client.send_email_notification(), чем send_email_notification(client).
  • Мы генерируем формы «на лету». Если в одном случае мне нужно создать форму с одними полями (fieldset1), а в другом случае — с другими (fieldset2), то я просто напишу:
    form_client1 = modelform_factory(     RuspostClient,     fields=fieldset1, ) form_client2 = modelform_factory(     RuspostClient,     fields=fieldset2, ) 

Кстати, полученный инстанс мы таки можем преобразовать в словарь и вернуться к своему корыту из пункта 2: ruspost_client_dict = model_to_dict(ruspost_client)

Выводы

Мы с вами рассмотрели модели в Django, которые не хранятся в базе данных. Они:

  1. классы
  2. почти не отличаются от обычных моделей django
  3. удобны для описания временных объектов (хранить их не получится)
  4. дают все преимущества объектно-ориентированного программирования
  5. удобны для автоматического создания форм на лету для временных объектов (не нужно создавать несколько классов в forms.py)
  6. позволяют абстрагироваться от ручного описания полей формы, т.е. задают инвариант (а вот уже генерирование формы позволяет выбрать варианты)

ссылка на оригинал статьи http://habrahabr.ru/post/207634/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *