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


485

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

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

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

Вот простой пример. Сначала 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])

Я хочу разделить сущности в моем коде:

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

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


14
Читайте о сигналах
Константа

1
хорошо, вы удалили тег, но вы могли бы использовать DCI, чтобы увидеть разделение того, что система делает (функциональность) и что такое система (модель данных / предметной области)
Rune FS

2
Вы предлагаете реализовать всю бизнес-логику в обратных вызовах? К сожалению, не все мои приложения могут быть связаны с событиями в базе данных.
defuz

Rune FS, я пытался использовать DCI, но мне показалось, что для моего проекта ему не нужно много: контекст, определение ролей как миксин к объектам и т. Д. Существует более простой способ разделения "делает" и " является"? Не могли бы вы привести минимальный пример?
defuz

Ответы:


635

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

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

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

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

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

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

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

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

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

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

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

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

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

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


11
Приятно видеть ответ, включающий DDD в вопрос, связанный с django. Тот факт, что Django использует ActiveRecord для постоянного хранения, не означает, что разделение интересов должно выходить за рамки. Отличный ответ.
Скотт Коутс

6
Если я хочу проверить, что освобожденный пользователь является владельцем объекта перед удалением этого объекта, я должен проверить это в представлении или в модуле формы / службы?
Иван

6
@Ivan: оба. Он должен быть в форме / сервисном модуле, потому что это является частью ваших бизнес-ограничений. Это также должно быть в представлении, потому что вы должны представлять только те действия, которые пользователи могут выполнять на самом деле.
Publysher

4
Пользовательский менеджер метода является хорошим способом для реализации запросов: User.objects.inactive_users(). Но пример модели прокси здесь IMO приводит к неверной семантике: u = InactiveUser.objects.all()[0]; u.active = True; u.save()и все же isinstance(u, InactiveUser) == True. Также я бы упомянул, что эффективный способ поддерживать модель запросов во многих случаях - это представление db.
Арье Лейб Таурог

1
@adnanmuttaleb Это правильно. Обратите внимание, что в самом ответе используется только термин «модель предметной области». Я включил ссылку на DDD не потому, что мой ответ - DDD, а потому, что эта книга отлично помогает вам думать о моделях доменов.
Publysher

148

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

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


8
Мне нравится общий подход, хотя из моего понимания ваш конкретный пример обычно реализуется как менеджер .
Arie

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

4
Мне тоже нравится этот подход, также сюда ява. Я новичок в Python, как бы вы протестировали views.py? Как бы вы посмеялись над уровнем сервиса (если, например, сервис делает некоторые удаленные вызовы API)?
Теймураз

71

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

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

Посмотрите на активные проекты

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

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

Подробнее о MVC

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

Воспользуйтесь преимуществами промежуточного программного обеспечения / тегов-шаблонов

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

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

  • создание Userможет идти в UserManager(models.Manager).
  • кровавые подробности для экземпляров должны идти на models.Model.
  • кровавые подробности 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
  • Является ли код частью общих инструментов / библиотек? ->yourapp.libs
  • Является ли код частью бизнес-логики libs? -> yourapp.libs.vendorилиyourapp.vendor.libs
  • Вот хороший: вы можете проверить свой код самостоятельно?
    • да хорошо :)
    • нет, у вас может быть проблема с интерфейсом
    • когда есть четкое разделение, unittest должен быть бриз с использованием насмешек
  • Логично ли разделение?
    • да хорошо :)
    • нет, у вас могут возникнуть проблемы при тестировании этих логических концепций по отдельности.
  • Как вы думаете, вам нужно будет провести рефакторинг, когда вы получите в 10 раз больше кода?
    • да, ничего хорошего, нет Буэно, рефакторинг может быть много работы
    • нет, это просто потрясающе!

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

  • 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

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


27

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

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


7

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

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

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

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

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

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

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

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


1

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

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

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

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


На самом деле я вижу это как простой в использовании подход. Но я бы переместил окончательную модель в другой файл, а не наследовал в models.py. Это было бы как service.py было столкновение userlogic + модель
Макс

1

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

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

  2. В соответствии с философией дизайна Django модели заключают в себе каждый аспект «объекта». Поэтому вся бизнес-логика, связанная с этим объектом, должна там жить:

Включить всю соответствующую доменную логику

Модели должны инкапсулировать каждый аспект «объекта» в соответствии с шаблоном дизайна 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)

0

Я в основном согласен с выбранным ответом ( https://stackoverflow.com/a/12857584/871392 ), но хочу добавить опцию в разделе «Создание запросов».

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

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


0

Наиболее полная статья о различных вариантах с за и против:

  1. Идея № 1: Жирные Модели
  2. Идея № 2: размещение бизнес-логики в представлениях / формах
  3. Идея № 3: Услуги
  4. Идея № 4: QuerySets / Менеджеры
  5. Вывод

Источник: https://sunscrapers.com/blog/where-to-put-business-logic-django/


Вы должны добавить некоторые объяснения.
m02ph3u5

-6

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

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

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

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

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


9
Как сказать, что это достаточно хорошо для тебя, чтобы понять мой вопрос?
Крис Весселинг

1
Django может предложить намного больше, кроме django.db.models, но большая часть экосистемы сильно зависит от вашей модели, использующей модели django.
andho

1
Шаблон проектирования, используемый для разработки программного обеспечения. И django разработан, чтобы легко использоваться для доставки программного обеспечения в среднем или большом масштабе, а не только на веб-страницах!
Мохаммед Торкашванд,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.