Схоже, ви запитуєте про різницю між моделлю даних та доменною моделлю - остання - це місце, де ви можете знайти ділову логіку та сутність, як сприймає ваш кінцевий користувач, перший - де ви фактично зберігаєте свої дані.
Крім того, я розтлумачив 3-ю частину вашого запитання як: як помітити невдачу утримувати ці моделі окремо.
Це дві дуже різні концепції, і завжди важко тримати їх окремо. Однак є деякі загальні закономірності та інструменти, які можна використовувати для цієї мети.
Про модель домену
Перше, що вам потрібно визнати, це те, що ваша модель домену насправді не стосується даних; йдеться про дії та питання, такі як "активувати цього користувача", "деактивувати цього користувача", "яких користувачів зараз активовано?" та "що це ім'я цього користувача?". У класичному плані: мова йде про запити та команди .
Мислення в командах
Для початку поглянемо на команди у вашому прикладі: "активувати цього користувача" та "відключити цього користувача". Приємна річ у командах полягає в тому, що вони можуть бути легко виражені малими сценаріями "задано коли-то":
якщо неактивний користувач,
коли адміністратор активує цього користувача,
він стає активним,
і електронною поштою підтвердження надсилається користувачеві,
а запис запису додається до системного журналу
(тощо. тощо)
Такі сценарії корисні для того, щоб побачити, як однією командою можуть впливати різні частини вашої інфраструктури - у цьому випадку ваша база даних (якийсь "активний" прапор), ваш поштовий сервер, ваш системний журнал тощо.
Такий сценарій також дуже допоможе вам створити середовище розробки тестових програм.
І нарешті, мислення в командах дійсно допомагає створити додаток, орієнтований на завдання. Ваші користувачі оцінять це :-)
Вираження команд
Джанго надає два простих способи вираження команд; обидва вони є дійсними варіантами, і це незвично поєднувати два підходи.
Сервісний рівень
Сервісний модуль вже описаний @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
Використання форм
Інший спосіб - використовувати Форму Джанго для кожної команди. Я віддаю перевагу такому підходу, оскільки він поєднує в собі декілька тісно пов'язаних аспектів:
- виконання команди (що вона робить?)
- перевірка параметрів команди (чи може це зробити?)
- презентація команди (як це зробити?)
form.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 (якщо ви цим користуєтесь) або ввести модуль queries.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)
Перший варіант - оновити ці моделі у своїх командах. Це дуже корисно, якщо ці моделі змінюються лише однією або двома командами.
form.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()
Кращим варіантом було б використання спеціальних сигналів. Ці сигнали, звичайно, випромінюються вашими командами. Сигнали мають перевагу в тому, що ви можете тримати декілька моделей запитів у синхронізації з початковою моделлю. Крім того, обробка сигналів може бути завантажена на фонові завдання, використовуючи селеру або подібні рамки.
signals.py
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
form.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: моделі проксі
Документація Джанго: сигнали
Архітектура: Дизайн, керований доменом