Похоже , что вы спрашиваете о разнице между моделью данных и моделью домена - последний, где вы можете найти бизнес - логику и объекты , как воспринимаются вашим конечным пользователем, бывшее когда вы на самом деле хранения данных.
Кроме того, я интерпретировал третью часть вашего вопроса следующим образом: как заметить неспособность разделить эти модели.
Это две совершенно разные концепции, и всегда трудно их разделить. Тем не менее, есть несколько общих шаблонов и инструментов, которые можно использовать для этой цели.
О доменной модели
Первое, что вам нужно понять, это то, что ваша модель предметной области не связана с данными; речь идет о действиях и вопросах, таких как «активировать этого пользователя», «деактивировать этого пользователя», «какие пользователи активированы в настоящее время?» и «как зовут этого пользователя?». В классических терминах: речь идет о запросах и командах .
Мышление в командах
Давайте начнем с рассмотрения команд в вашем примере: «активировать этого пользователя» и «деактивировать этого пользователя». Хорошая вещь о командах состоит в том, что они могут быть легко выражены маленьким сценарием «дано, когда тогда»:
дается неактивный пользователь,
когда администратор активирует этого пользователя,
затем пользователь становится активным,
и пользователю отправляется электронное письмо с подтверждением,
и запись добавляется в системный журнал
(и т. д. и т. д.)
Такой сценарий полезен, чтобы увидеть, как одна команда может повлиять на различные части вашей инфраструктуры - в этом случае ваша база данных (своего рода «активный» флаг), ваш почтовый сервер, системный журнал и т. Д.
Такой сценарий также действительно поможет вам в настройке среды разработки через тестирование.
И, наконец, мышление в командах действительно помогает вам создать приложение, ориентированное на задачи. Ваши пользователи оценят это :-)
Выражения команд
Django предоставляет два простых способа выражения команд; оба они являются допустимыми вариантами, и нет ничего необычного в том, чтобы смешать два подхода.
Сервисный уровень
Сервисный модуль уже описан @Hedde . Здесь вы определяете отдельный модуль, и каждая команда представляется как функция.
services.py
def activate_user(user_id):
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
Использование форм
Другой способ - использовать форму Django для каждой команды. Я предпочитаю этот подход, потому что он сочетает в себе несколько тесно связанных аспектов:
- выполнение команды (что она делает?)
- проверка параметров команды (можно ли это сделать?)
- презентация команды (как я могу это сделать?)
forms.py
class ActivateUserForm(forms.Form):
user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")
# the username select widget is not a standard Django widget, I just made it up
def clean_user_id(self):
user_id = self.cleaned_data['user_id']
if User.objects.get(pk=user_id).active:
raise ValidationError("This user cannot be activated")
# you can also check authorizations etc.
return user_id
def execute(self):
"""
This is not a standard method in the forms API; it is intended to replace the
'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern.
"""
user_id = self.cleaned_data['user_id']
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
Мышление в запросах
Ваш пример не содержал никаких запросов, поэтому я позволил себе составить несколько полезных запросов. Я предпочитаю использовать термин «вопрос», но запросы - это классическая терминология. Интересные запросы: «Как зовут этого пользователя?», «Может ли этот пользователь войти в систему?», «Показать список деактивированных пользователей» и «Каково географическое распределение деактивированных пользователей?»
Прежде чем отвечать на эти запросы, вы всегда должны задать себе два вопроса: это презентационный запрос только для моих шаблонов и / или запрос бизнес-логики, связанный с выполнением моих команд, и / или отчетный запрос.
Презентационные запросы просто сделаны для улучшения пользовательского интерфейса. Ответы на запросы бизнес-логики напрямую влияют на выполнение ваших команд. Отчеты о запросах предназначены только для аналитических целей и имеют более короткие временные ограничения. Эти категории не являются взаимоисключающими.
Другой вопрос: «Есть ли у меня полный контроль над ответами?» Например, при запросе имени пользователя (в этом контексте) мы не имеем никакого контроля над результатом, потому что мы полагаемся на внешний API.
Создание запросов
Самый простой запрос в Django - это использование объекта Manager:
User.objects.filter(active=True)
Конечно, это работает, только если данные фактически представлены в вашей модели данных. Это не всегда так. В этих случаях вы можете рассмотреть варианты ниже.
Пользовательские теги и фильтры
Первый вариант полезен для запросов, которые являются просто презентационными: пользовательские теги и шаблоны фильтров.
template.html
<h1>Welcome, {{ user|friendly_name }}</h1>
template_tags.py
@register.filter
def friendly_name(user):
return remote_api.get_cached_name(user.id)
Методы запросов
Если ваш запрос не просто презентационный, вы можете добавить запросы в ваш services.py (если вы его используете) или ввести модуль query.py :
queries.py
def inactive_users():
return User.objects.filter(active=False)
def users_called_publysher():
for user in User.objects.all():
if remote_api.get_cached_name(user.id) == "publysher":
yield user
Модели прокси
Прокси-модели очень полезны в контексте бизнес-логики и отчетности. Вы в основном определяете расширенное подмножество вашей модели. Вы можете переопределить базовый QuerySet менеджера, переопределив Manager.get_queryset()
метод.
models.py
class InactiveUserManager(models.Manager):
def get_queryset(self):
query_set = super(InactiveUserManager, self).get_queryset()
return query_set.filter(active=False)
class InactiveUser(User):
"""
>>> for user in InactiveUser.objects.all():
… assert user.active is False
"""
objects = InactiveUserManager()
class Meta:
proxy = True
Модели запросов
Для запросов, которые по своей сути являются сложными, но выполняются довольно часто, существует возможность моделей запросов. Модель запроса - это форма денормализации, в которой релевантные данные для одного запроса хранятся в отдельной модели. Хитрость, конечно, заключается в том, чтобы поддерживать денормализованную модель в синхронизации с основной моделью. Модели запросов можно использовать только в том случае, если изменения полностью находятся под вашим контролем.
models.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
Первый вариант - обновить эти модели в ваших командах. Это очень полезно, если эти модели меняются только одной или двумя командами.
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
Лучшим вариантом будет использование пользовательских сигналов. Эти сигналы, конечно, испускаются вашими командами. Преимущество сигналов заключается в том, что вы можете синхронизировать несколько моделей запросов с исходной моделью. Кроме того, обработка сигналов может быть загружена в фоновые задачи, используя Celery или аналогичные фреймворки.
signals.py
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
models.py
class InactiveUserDistribution(models.Model):
# see above
@receiver(user_activated)
def on_user_activated(sender, **kwargs):
user = kwargs['user']
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
Держать его в чистоте
При использовании этого подхода становится невероятно легко определить, остается ли ваш код чистым. Просто следуйте этим рекомендациям:
- Содержит ли моя модель методы, которые не просто управляют состоянием базы данных? Вы должны извлечь команду.
- Содержит ли моя модель свойства, которые не отображаются на поля базы данных? Вы должны извлечь запрос.
- Моя модель ссылается на инфраструктуру, которая не является моей базой данных (например, почта)? Вы должны извлечь команду.
То же самое касается представлений (потому что представления часто страдают от одной и той же проблемы).
- Мой взгляд активно управляет моделями баз данных? Вы должны извлечь команду.
Некоторые ссылки
Документация Django: модели прокси
Документация Django: сигналы
Архитектура: доменный дизайн