Разделение бизнес-логики и доступа к данным в django

Я пишу проект в Django, и я вижу, что 80% кода находится в файле models.py. Этот код запутан, и через некоторое время я перестаю понимать, что на самом деле происходит.

Вот что меня беспокоит:

  • Я считаю уродливым, что мой уровень модели (который должен был быть ответственный только за работу с данными из базы данных) также отправка электронной почты, переход по API на другие службы и т.д.
  • Кроме того, я считаю неприемлемым размещать бизнес-логику в представлении, потому что таким образом становится трудно контролировать. Например, в моем приложения существует как минимум три способа создания новых экземпляры User, но технически они должны создавать их равномерно.
  • Я не всегда замечаю, когда методы и свойства моих моделей становятся недетерминированными и когда они развиваются побочные эффекты.

Вот простой пример. Сначала модель User была такой:

class User(db.Models):

    def get_present_name(self):
        return self.name or 'Anonymous'

    def activate(self):
        self.status = 'activated'
        self.save()

Со временем это превратилось в это:

class User(db.Models):

    def get_present_name(self): 
        # property became non-deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

    def activate(self):
        # method now has a side effect (send message to user)
        self.status = 'activated'
        self.save()
        send_mail('Your account is activated!', '…', [self.email])

Я хочу отделить объекты в моем коде:

  • Объекты моей базы данных, уровень базы данных: что содержит мое приложение?
  • Объекты моего приложения, уровень бизнес-логики: что может сделать мое приложение?

Каковы наилучшие методы реализации такого подхода, который может быть применен в Django?

Ответ 1

Похоже, что вы спрашиваете о разнице между моделью данных и моделью предметной области - последняя - это место, где вы можете найти бизнес-логику и сущности, воспринимаемые вашим конечным пользователем, а первая - где вы на самом деле храните свои данные.

Кроме того, я интерпретировал третью часть вашего вопроса следующим образом: как заметить неспособность разделить эти модели.

Это две совершенно разные концепции, и всегда трудно разделить их. Тем не менее, есть несколько общих шаблонов и инструментов, которые можно использовать для этой цели.

О доменной модели

Первое, что вам нужно понять, это то, что ваша модель предметной области не связана с данными; речь идет о действиях и вопросах, таких как "активировать этого пользователя", "деактивировать этого пользователя", "какие пользователи активированы в настоящее время?" и "каково это имя пользователя?". В классических терминах: это про запросы и команды.

Мышление в командах

Давайте начнем с рассмотрения команд в вашем примере: "активировать этого пользователя" и "деактивировать этого пользователя". Хорошая вещь о командах в том, что они могут быть легко выражены с помощью небольшого сценария "когда - тогда":

данный неактивный пользователь
когда админ активирует этого пользователя
затем пользователь становится активным
и пользователю отправляется электронное письмо с подтверждением
и запись добавляется в системный журнал
 (и т.д.)

Такой сценарий полезен, чтобы увидеть, как одна команда может повлиять на различные части вашей инфраструктуры - в этом случае ваша база данных (своего рода "активный" флаг), ваш почтовый сервер, системный журнал и т.д.

Такой сценарий также действительно поможет вам в настройке среды разработки на основе тестирования.

И, наконец, мышление в командах действительно помогает вам создать приложение, ориентированное на задачи. Ваши пользователи это оценят :-)

Выражение команд

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: сигналы

Архитектура: доменный дизайн

Ответ 2

Я обычно реализую сервисный слой между представлениями и моделями. Это действует как API вашего проекта и дает вам хорошее представление о том, что происходит. Я унаследовал эту практику от моего коллеги, который часто использует эту технику многоуровневой работы с проектами Java (JSF), например:

models.py

class Book:
   author = models.ForeignKey(User)
   title = models.CharField(max_length=125)

   class Meta:
       app_label = "library"

services.py

from library.models import Book

def get_books(limit=None, **filters):
    """ simple service function for retrieving books can be widely extended """
    return Book.objects.filter(**filters)[:limit]  # list[:None] will return the entire list

views.py

from library.services import get_books

class BookListView(ListView):
    """ simple view, e.g. implement a _build and _apply filters function """
    queryset = get_books()

Имейте в виду, я обычно беру модели, представления и услуги на уровень модуля и отделить еще дальше в зависимости от размера проекта

Ответ 3

Прежде всего, Не повторяйте.

Тогда, пожалуйста, будьте осторожны, чтобы не перерабатывать, иногда это просто пустая трата времени и заставляет кого-то потерять сосредоточение на том, что важно. Периодически проверяйте zen of python.

Взгляните на активные проекты

  • больше людей = больше нужно правильно организовать
  • репозиторий django, они имеют простую структуру.
  • репозиторий pip у них есть структура каталогов straigtforward.
  • репозиторий ткани также является хорошим, на что можно смотреть.

    • вы можете разместить все свои модели под yourapp/models/logicalgroup.py
  • e.g User, Group и связанные модели могут находиться под yourapp/models/users.py
  • e.g Poll, Question, Answer... может находиться под yourapp/models/polls.py
  • загрузите то, что вам нужно в __all__ внутри yourapp/models/__init__.py

Подробнее о MVC

  • Модель
  • - ваши данные
    • это включает в себя ваши фактические данные
    • это также включает данные сессии/файла cookie/cache/fs/index
  • пользователь взаимодействует с контроллером для управления моделью
    • это может быть API или представление, которое сохраняет/обновляет ваши данные.
    • это можно настроить с помощью request.GET/request.POST... и т.д.
    • подкачка или фильтрация.
  • данные обновляют представление
    • шаблоны берут данные и форматируют их соответственно.
    • API-интерфейсы даже без шаблонов являются частью представления; например tastypie или piston
    • это также должно учитывать промежуточное программное обеспечение.

Воспользуйтесь middleware/templatetags

  • Если вам потребуется какая-то работа для каждого запроса, промежуточное ПО - это один из способов.
    • например. добавление временных меток
    • например. обновление метрик о просмотрах страниц
    • например. заполнение кеша
  • Если у вас есть фрагменты кода, которые всегда повторяются для форматирования объектов, templatetags хороши.
    • например. активные вкладки /url панировочные сундуки

Воспользуйтесь менеджерами моделей

  • Создание User может идти в UserManager(models.Manager).
  • gory детали для экземпляров должны идти на models.Model.
  • gory детали для queryset могут идти в models.Manager.
  • вам может понадобиться создать User по одному, так что вы можете подумать, что он должен жить на самой модели, но при создании объекта вы, вероятно, не имеете всех подробностей:

Пример:

class UserManager(models.Manager):
   def create_user(self, username, ...):
      # plain create
   def create_superuser(self, username, ...):
      # may set is_superuser field.
   def activate(self, username):
      # may use save() and send_mail()
   def activate_in_bulk(self, queryset):
      # may use queryset.update() instead of save()
      # may use send_mass_mail() instead of send_mail()

Использовать формы, где это возможно

Многие шаблоны кода могут быть устранены, если у вас есть формы, которые сопоставляются с моделью. ModelForm documentation довольно хорош. Разделение кода для форм из кода модели может быть хорошим, если у вас много настроек (или иногда избегайте циклических ошибок импорта для более сложных применений).

Используйте команды управления, когда это возможно

  • например. yourapp/management/commands/createsuperuser.py
  • например. yourapp/management/commands/activateinbulk.py

Если у вас есть бизнес-логика, вы можете выделить ее

  • django.contrib.auth использует бэкэнды, так же как db имеет бэкэнд... и т.д.
  • добавить setting для вашей бизнес-логики (например, AUTHENTICATION_BACKENDS)
  • вы можете использовать django.contrib.auth.backends.RemoteUserBackend
  • вы можете использовать yourapp.backends.remote_api.RemoteUserBackend
  • вы можете использовать yourapp.backends.memcached.RemoteUserBackend
  • делегировать сложную бизнес-логику на бэкэнд
  • обязательно установите правильное значение ожидания на входе/выходе.
  • изменение бизнес-логики так же просто, как изменение настройки:)

пример бэкэнд:

class User(db.Models):
    def get_present_name(self): 
        # property became not deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

может стать:

class User(db.Models):
   def get_present_name(self):
      for backend in get_backends():
         try:
            return backend.get_present_name(self)
         except: # make pylint happy.
            pass
      return None

больше о шаблонах проектирования

больше о границах интерфейса

  • Является ли код, который вы хотите использовать на самом деле частью моделей? → yourapp.models
  • Является ли код частью бизнес-логики? → yourapp.vendor
  • Является ли код частью общих инструментов /libs? → yourapp.libs
  • Является ли частью кода бизнес-логики libs? → yourapp.libs.vendor или yourapp.vendor.libs
  • Вот хороший: можете ли вы проверить свой код самостоятельно?
    • да, хорошо:)
    • Нет, у вас может возникнуть проблема с интерфейсом
    • когда есть четкое разделение, unittest должен быть ветерок с использованием насмешек
  • Является ли разделение логическим?
    • да, хорошо:)
    • нет, может возникнуть проблема с тестированием этих логических понятий отдельно.
  • Как вы думаете, вам нужно будет реорганизовать, когда вы получите 10-кратный код?
    • да, нехорошо, нет bueno, рефактору может быть много работы.
    • Нет, это просто потрясающе!

Короче говоря, вы могли бы

  • yourapp/core/backends.py
  • yourapp/core/models/__init__.py
  • yourapp/core/models/users.py
  • yourapp/core/models/questions.py
  • yourapp/core/backends.py
  • yourapp/core/forms.py
  • yourapp/core/handlers.py
  • yourapp/core/management/commands/__init__.py
  • yourapp/core/management/commands/closepolls.py
  • yourapp/core/management/commands/removeduplicates.py
  • yourapp/core/middleware.py
  • yourapp/core/signals.py
  • yourapp/core/templatetags/__init__.py
  • yourapp/core/templatetags/polls_extras.py
  • yourapp/core/views/__init__.py
  • yourapp/core/views/users.py
  • yourapp/core/views/questions.py
  • yourapp/core/signals.py
  • yourapp/lib/utils.py
  • yourapp/lib/textanalysis.py
  • yourapp/lib/ratings.py
  • yourapp/vendor/backends.py
  • yourapp/vendor/morebusinesslogic.py
  • yourapp/vendor/handlers.py
  • yourapp/vendor/middleware.py
  • yourapp/vendor/signals.py
  • yourapp/tests/test_polls.py
  • yourapp/tests/test_questions.py
  • yourapp/tests/test_duplicates.py
  • yourapp/tests/test_ratings.py

или что-нибудь еще, что поможет вам; поиск интерфейсов, которые вам нужны, а границы помогут вам.

Ответ 4

Django использует слегка модифицированный вид MVC. В Django нет понятия "контроллер". Самый близкий прокси - это "представление", которое, как правило, вызывает путаницу с конвертируемыми MVC, потому что в MVC вид больше похож на "шаблон" Django.

В Django "модель" - это не просто абстракция базы данных. В некоторых отношениях он разделяет обязанности с "представлением" Django в качестве контроллера MVC. Он содержит всю совокупность поведения, связанного с экземпляром. Если этому экземпляру необходимо взаимодействовать с внешним API как часть его поведения, то этот код все еще моделируется. На самом деле, модели не обязаны вообще взаимодействовать с базой данных, поэтому вы могли бы иметь модели, которые полностью существуют как интерактивный уровень для внешнего API. Это гораздо более свободная концепция "модели".

Ответ 5

В Django структура MVC, как сказал Крис Пратт, отличается от классической модели MVC, используемой в других рамках, я думаю, что основная причина для этого - избежать слишком строгой структуры приложения, например, происходит в других средах MVC, таких как CakePHP.

В Django MVC был реализован следующим образом:

Вид слоя разделен на два. Представления должны использоваться только для управления HTTP-запросами, они вызываются и отвечают на них. Представления взаимодействуют с остальной частью вашего приложения (формы, модели, пользовательские классы, в простых случаях непосредственно с моделями). Для создания интерфейса мы используем Шаблоны. Шаблоны похожи на Django, они сопоставляют контекст с ними, и этот контекст был передан приложению приложением (при запросе вида).

Уровень модели предоставляет инкапсуляцию, абстракцию, проверку достоверности и делает ваши данные объектно-ориентированными (они говорят, что когда-нибудь СУБД также будет). Это не означает, что вы должны создавать огромные файлы models.py(на самом деле очень хороший совет - разделить ваши модели в разных файлах, поместить их в папку под названием "модели", сделать файл "__init__.py" в этом где вы импортируете все свои модели и, наконец, используете атрибут "app_label" для моделей. Класс модели). Модель должна абстрагировать вас от работы с данными, это упростит ваше приложение. Вы также должны, при необходимости, создавать внешние классы, такие как "инструменты" для своих моделей. Вы также можете использовать наследие в моделях, устанавливая атрибут "abstract" вашей модели Meta-класса на "True".

Где остальные? Ну, небольшие веб-приложения, как правило, представляют собой своего рода интерфейс к данным, в некоторых небольших программных случаях достаточно использовать представления для запроса или вставки данных. Более распространенные случаи будут использовать Forms или ModelForms, которые на самом деле являются "контроллерами". Это не что иное, как практическое решение общей проблемы и очень быстрая. Это то, что использует веб-сайт.

Если формы для вас не нужны, тогда вы должны создать свои собственные классы, чтобы сделать магию, очень хорошим примером этого является приложение администратора: вы можете прочитать код ModelAmin, это фактически работает как контроллер. Существует не стандартная структура, я предлагаю вам изучить существующие приложения Django, это зависит от каждого случая. Это то, что намеревались разработчики Django, вы можете добавить класс парсера xml, класс соединителей API, добавить Celery для выполнения задач, скрутить для приложения на базе реакторов, использовать только ORM, сделать веб-сервис, изменить приложение администратора и многое другое... Это ваша ответственность, чтобы сделать код хорошего качества, уважать философию MVC или нет, сделать его модульным и создать свои собственные уровни абстракции. Он очень гибкий.

Мой совет: прочитайте как можно больше кода, есть много приложений django, но не воспринимайте их так серьезно. Каждый случай отличается, шаблоны и теория помогают, но не всегда, это неточный язык, django просто предоставляет вам хорошие инструменты, которые вы можете использовать, чтобы смягчить некоторые боли (например, интерфейс администратора, проверку веб-формы, i18n, реализацию шаблона наблюдателя, все ранее упомянутые и другие), но хорошие проекты исходят от опытных дизайнеров.

PS: используйте класс "Пользователь" из приложения auth (из стандартного django), вы можете сделать, например, профили пользователей или, по крайней мере, прочитать его код, это будет полезно для вашего случая.

Ответ 6

Я в основном согласен с выбранным ответом (fooobar.com/questions/25150/...), но хочу добавить опцию в раздел "Создание запросов".

Можно определить классы QuerySet для моделей для запросов фильтра и сына. После этого вы можете проксировать этот класс запроса для диспетчера модели, например, для классов сборки и QuerySet.

Хотя, если вам пришлось запрашивать несколько моделей данных для получения одной модели домена, мне кажется более разумным поставить это в отдельный модуль, как это было предложено ранее.

Ответ 7

Старый вопрос, но я все равно хочу предложить свое решение. Это основано на признании того, что объектам модели также требуются некоторые дополнительные функции, в то время как неудобно размещать их в пределах models.py. Тяжелая бизнес-логика может быть написана отдельно в зависимости от личного вкуса, но я, по крайней мере, как модель, делаю все, что связано с ней. Это решение также поддерживает тех, кто любит иметь всю логику, размещенную внутри самих моделей.

Как таковой, я разработал хак, который позволяет мне отделять логику от определений моделей и все еще получать все намеки от моей IDE.

Преимущества должны быть очевидны, но в этом перечислены некоторые из них, которые я наблюдал:

  • Определения БД остаются такими, что - никакой логический "мусор" не прикреплен
  • Логика, связанная с моделью, размещается аккуратно в одном месте.
  • Все службы (формы, REST, представления) имеют единую точку доступа к логике
  • Лучше всего: Мне не пришлось переписывать какой-либо код, как только я понял, что мои models.py стали слишком загроможденными и должны были отделить эту логику. Разделение является гладким и итеративным: я мог бы выполнять функцию за один раз или весь класс или все модели .py.

Я использовал это с Python 3.4 и выше и Django 1.8 и выше.

приложение/models.py

....
from app.logic.user import UserLogic

class User(models.Model, UserLogic):
    field1 = models.AnyField(....)
    ... field definitions ...

приложение/логика/user.py

if False:
    # This allows the IDE to know about the User model and its member fields
    from main.models import User

class UserLogic(object):
    def logic_function(self: 'User'):
        ... code with hinting working normally ...

Единственное, что я не могу понять, это сделать, чтобы моя IDE (PyCharm в этом случае) признала, что UserLogic на самом деле является моделью пользователя. Но поскольку это, очевидно, хак, я очень рад принять небольшую неприятность, всегда указывающую тип для параметра self.

Ответ 8

Я должен был бы согласиться с вами. В django есть много возможностей, но лучше всего начать с рассмотрения философии дизайна Django.

  1. Вызов API из свойства модели не был бы идеальным, кажется, что было бы более разумно сделать что-то подобное в представлении и, возможно, создать сервисный слой, чтобы все было сухо. Если вызов API является неблокирующим, а вызов дорогим, отправка запроса работнику службы (работнику, потребляющему очередь) может иметь смысл.

  2. Согласно философии дизайна Django, модели заключают в себе каждый аспект "объекта". Таким образом, вся бизнес-логика, связанная с этим объектом, должна там жить:

Include all relevant domain logic

Модели должны инкапсулировать каждый аспект "объекта" в соответствии с шаблоном проектирования Martin Fowlers Active Record.

  1. Побочные эффекты, которые вы описываете, очевидны, логику здесь лучше разбить на Querysets и менеджеров. Вот пример:

    models.py

    import datetime
    
    from djongo import models
    from django.db.models.query import QuerySet
    from django.contrib import admin
    from django.db import transaction
    
    
    class MyUser(models.Model):
    
        present_name = models.TextField(null=False, blank=True)
        status = models.TextField(null=False, blank=True)
        last_active = models.DateTimeField(auto_now=True, editable=False)
    
        # As mentioned you could put this in a template tag to pull it
        # from cache there. Depending on how it is used, it could be
        # retrieved from within the admin view or from a custom view
        # if that is the only place you will use it.
        #def get_present_name(self):
        #    # property became non-deterministic in terms of database
        #    # data is taken from another service by api
        #    return remote_api.request_user_name(self.uid) or 'Anonymous'
    
        # Moved to admin as an action
        # def activate(self):
        #     # method now has a side effect (send message to user)
        #     self.status = 'activated'
        #     self.save()
        #     # send email via email service
        #     #send_mail('Your account is activated!', '…', [self.email])
    
        class Meta:
            ordering = ['-id']  # Needed for DRF pagination
    
        def __unicode__(self):
            return '{}'.format(self.pk)
    
    
    class MyUserRegistrationQuerySet(QuerySet):
    
        def for_inactive_users(self):
            new_date = datetime.datetime.now() - datetime.timedelta(days=3*365)  # 3 Years ago
            return self.filter(last_active__lte=new_date.year)
    
        def by_user_id(self, user_ids):
            return self.filter(id__in=user_ids)
    
    
    class MyUserRegistrationManager(models.Manager):
    
        def get_query_set(self):
            return MyUserRegistrationQuerySet(self.model, using=self._db)
    
        def with_no_activity(self):
            return self.get_query_set().for_inactive_users()
    

    admin.py

    # Then in model admin
    
    class MyUserRegistrationAdmin(admin.ModelAdmin):
        actions = (
            'send_welcome_emails',
        )
    
        def send_activate_emails(self, request, queryset):
            rows_affected = 0
            for obj in queryset:
                with transaction.commit_on_success():
                    # send_email('welcome_email', request, obj) # send email via email service
                    obj.status = 'activated'
                    obj.save()
                    rows_affected += 1
    
            self.message_user(request, 'sent %d' % rows_affected)
    
    admin.site.register(MyUser, MyUserRegistrationAdmin)
    

Ответ 9

Django предназначен для простого использования для доставки веб-страниц. Если вы не можете с этим справиться, возможно, вы должны использовать другое решение.

Я пишу корневые или общие операции над моделью (чтобы иметь тот же интерфейс), а остальные - на контроллере модели. Если мне нужна операция из другой модели, я импортирую ее контроллер.

Этот подход достаточно для меня и сложности моих приложений.

Ответ Hedde - это пример, демонстрирующий гибкость самого django и python.

Очень интересный вопрос в любом случае!