...

пятница, 7 февраля 2014 г.

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. позволяют абстрагироваться от ручного описания полей формы, т.е. задают инвариант (а вот уже генерирование формы позволяет выбрать варианты)


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.


Комментариев нет:

Отправить комментарий