Совсем недавно я вдруг открыл для себя всю мощь моделей и форм 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)
- позволяют абстрагироваться от ручного описания полей формы, т.е. задают инвариант (а вот уже генерирование формы позволяет выбрать варианты)
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий