Совсем недавно я вдруг открыл для себя всю мощь моделей и форм 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-форму (инерфейс пользователя) для взаимодействия с библиотекой:
Какие тут есть варианты решения задачи?
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, которые не хранятся в базе данных. Они:
- классы
- почти не отличаются от обычных моделей django
- удобны для описания временных объектов (хранить их не получится)
- дают все преимущества объектно-ориентированного программирования
- удобны для автоматического создания форм на лету для временных объектов (не нужно создавать несколько классов в forms.py)
- позволяют абстрагироваться от ручного описания полей формы, т.е. задают инвариант (а вот уже генерирование формы позволяет выбрать варианты)
ссылка на оригинал статьи http://habrahabr.ru/post/207634/
Добавить комментарий