Ответ
В Django для разделения бизнес-логики от представлений (views) и моделей (models) принято использовать паттерн Service Layer (слой сервисов).
Это помогает избежать анти-паттернов Fat Models (толстые модели) и Fat Views (толстые представления), делая код более тестируемым, переиспользуемым и поддерживаемым. Логику выносят в отдельные файлы, например, services.py внутри Django-приложения.
Пример
1. Файл с бизнес-логикой (services.py):
# myapp/services.py
from typing import List, Dict
from django.db import transaction
from .models import Order, OrderItem, Product
class OrderCreationError(Exception):
pass
@transaction.atomic
def create_order(user, items_data: List[Dict]) -> Order:
"""
Создает заказ и связанные с ним позиции в одной транзакции.
Инкапсулирует логику валидации, расчета и сохранения.
"""
order = Order.objects.create(user=user, status=Order.Status.PENDING)
for item_data in items_data:
product_id = item_data.get('product_id')
quantity = item_data.get('quantity')
if not Product.objects.filter(id=product_id, is_active=True).exists():
raise OrderCreationError(f"Товар с ID {product_id} не найден или неактивен.")
OrderItem.objects.create(order=order, product_id=product_id, quantity=quantity)
# Здесь может быть дополнительная логика: расчет итоговой суммы, отправка уведомлений и т.д.
order.calculate_total_price()
order.save()
return order
2. Представление, которое вызывает сервис (views.py):
# myapp/views.py
from django.http import JsonResponse
from . import services
def order_create_api(request):
# Данные обычно приходят из request.body в формате JSON
items_data = [{'product_id': 1, 'quantity': 2}]
user = request.user
try:
new_order = services.create_order(user=user, items_data=items_data)
return JsonResponse({"status": "ok", "order_id": new_order.id}, status=201)
except services.OrderCreationError as e:
return JsonResponse({"status": "error", "message": str(e)}, status=400)
Преимущества подхода:
- Тестируемость: Бизнес-логику в
services.pyможно тестировать изолированно, без HTTP-запросов. - Переиспользование: Сервисную функцию
create_orderможно вызвать из view, Celery-задачи или management-команды. - Принцип единственной ответственности (SRP): Views отвечают за HTTP, модели — за данные, а сервисы — за бизнес-процессы.
- Читаемость: Код представлений становится лаконичным и понятным.
Ответ 18+ 🔞
А, слушай, вот эта вся история с Django и бизнес-логикой — это просто пиздец какой-то цирк, если её не организовать. Смотри, чуваки часто делают так: вся их логика либо в views.py сидит, как толстый жлоб на диване, либо в models.py размазана, как говно по стенке. А потом орут: «Почему тесты писать — это ад? Почему ничего переиспользовать нельзя?».
Так вот, чтобы не быть таким мудаком, есть паттерн Service Layer. Слой сервисов, блядь. Выносишь всю свою бизнес-хуйню — создание заказов, расчёты, проверки — в отдельный файлик, например services.py. И живёшь потом, как сыр в масле катаешься.
Смотри, как это выглядит, на примере
1. Файл с бизнес-логикой (services.py):
Тут у нас вся магия, вся соль. Views и модели — это просто обёртки, а тут — настоящая работа.
# myapp/services.py
from typing import List, Dict
from django.db import transaction
from .models import Order, OrderItem, Product
class OrderCreationError(Exception):
"""Кастомная ошибка, чтобы не путать с другими еблями."""
pass
@transaction.atomic
def create_order(user, items_data: List[Dict]) -> Order:
"""
Создаёт заказ и все связанные с ним позиции в ОДНОЙ транзакции.
Инкапсулирует всю логику: валидацию, расчёты, сохранение.
Если что-то пошло не так — откатывает всё к хуям собачьим.
"""
order = Order.objects.create(user=user, status=Order.Status.PENDING)
for item_data in items_data:
product_id = item_data.get('product_id')
quantity = item_data.get('quantity')
# Проверяем, а существует ли вообще такой товар и не говно ли он?
if not Product.objects.filter(id=product_id, is_active=True).exists():
raise OrderCreationError(f"Товар с ID {product_id} не найден или неактивен, иди нахуй.")
# Создаём позицию в заказе
OrderItem.objects.create(order=order, product_id=product_id, quantity=quantity)
# Тут можешь добавить любую другую логику: расчёт суммы, списание бонусов, отправку письма админу...
order.calculate_total_price()
order.save()
return order
2. Представление, которое вызывает этот сервис (views.py):
А вот view становится лёгким и пушистым. Его задача — принять запрос, вызвать нужный сервис и отдать ответ. Всё, ебушки-воробушки!
# myapp/views.py
from django.http import JsonResponse
from . import services # Импортируем наш слой с логикой
def order_create_api(request):
# Данные обычно приходят из request.body (JSON)
# Для примера возьмём статические
items_data = [{'product_id': 1, 'quantity': 2}]
user = request.user
try:
# Вся сложность спрятана тут. View даже не парится, что там внутри.
new_order = services.create_order(user=user, items_data=items_data)
return JsonResponse({"status": "ok", "order_id": new_order.id}, status=201)
except services.OrderCreationError as e:
# Ловим нашу кастомную ошибку и отдаём клиенту внятное сообщение
return JsonResponse({"status": "error", "message": str(e)}, status=400)
Ну и зачем это всё, спросишь? А вот зачем, ёпта:
- Тестируемость — овердохуищная. Ты можешь тестировать функцию
create_orderв полной изоляции, без всяких там HTTP-запросов и фреймворков. Просто подал данные и смотришь, что вышло. - Переиспользование на уровне бога. Эту же сервисную функцию можно вызвать из обычной view, из Celery-задачи, из management-команды, да хоть из телеграм-бота! Всё в одном месте.
- Принцип единственной ответственности (SRP). View отвечает за HTTP-хуйню (принял запрос, отдал ответ). Модели — за работу с базой. А сервисы — за бизнес-процессы, за настоящую логику приложения. Всё по полочкам, ни одна сущность не пытается быть умнее другой.
- Читаемость. Открываешь
views.py— и сразу видишь, что происходит, а не ломаешь глаза, пытаясь разобраться в трёхэтажных условиях и циклах.
Короче, не будь мудаком — выноси логику в сервисы. А то потом сам же будешь плакать, когда придётся что-то менять или тестировать.