feat(core): add core module with mixins, services, and background jobs

- Add Model Mixins: TimestampMixin, SoftDeleteMixin, AuditMixin, etc.
- Add Base Services: BaseService, BulkOperationsMixin, QueryOptimizerMixin
- Add Base ViewSets with bulk operations
- Add BackgroundJob model for Celery task tracking
- Add BaseAppCommand for management commands
- Add permissions, pagination, filters, cache, logging
- Migrate tests to factory_boy + faker
- Add CHANGELOG.md
- 297 tests passing
This commit is contained in:
2026-01-21 11:47:26 +01:00
parent 06b30fca02
commit f121445313
72 changed files with 9258 additions and 594 deletions

View File

@@ -0,0 +1,128 @@
"""
Базовые классы разрешений для API.
Предоставляет переиспользуемые permission classes для контроля доступа.
"""
from rest_framework import permissions
from rest_framework.request import Request
from rest_framework.views import APIView
class IsOwner(permissions.BasePermission):
"""
Разрешает доступ только владельцу объекта.
Объект должен иметь атрибут `user` или `owner`,
который сравнивается с текущим пользователем.
Пример использования:
class MyView(APIView):
permission_classes = [IsAuthenticated, IsOwner]
"""
owner_field = "user" # Можно переопределить в подклассах
def has_object_permission(self, request: Request, view: APIView, obj) -> bool:
"""Проверяет, является ли пользователь владельцем объекта."""
owner = getattr(obj, self.owner_field, None)
if owner is None:
owner = getattr(obj, "owner", None)
return owner == request.user
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Разрешает изменение только владельцу, остальным - только чтение.
SAFE_METHODS (GET, HEAD, OPTIONS) доступны всем аутентифицированным.
Изменяющие методы (POST, PUT, PATCH, DELETE) - только владельцу.
"""
owner_field = "user"
def has_object_permission(self, request: Request, view: APIView, obj) -> bool:
"""Проверяет права на объект."""
# Чтение разрешено всем
if request.method in permissions.SAFE_METHODS:
return True
# Изменение - только владельцу
owner = getattr(obj, self.owner_field, None)
if owner is None:
owner = getattr(obj, "owner", None)
return owner == request.user
class IsAdminOrReadOnly(permissions.BasePermission):
"""
Разрешает изменение только админам, остальным - только чтение.
SAFE_METHODS доступны всем (включая анонимных).
Изменяющие методы - только staff/superuser.
"""
def has_permission(self, request: Request, view: APIView) -> bool:
"""Проверяет права на уровне view."""
if request.method in permissions.SAFE_METHODS:
return True
return request.user and request.user.is_staff
class IsAdmin(permissions.BasePermission):
"""
Разрешает доступ только администраторам (is_staff=True).
"""
def has_permission(self, request: Request, view: APIView) -> bool:
"""Проверяет, является ли пользователь администратором."""
return request.user and request.user.is_staff
class IsSuperuser(permissions.BasePermission):
"""
Разрешает доступ только суперпользователям (is_superuser=True).
"""
def has_permission(self, request: Request, view: APIView) -> bool:
"""Проверяет, является ли пользователь суперпользователем."""
return request.user and request.user.is_superuser
class IsVerified(permissions.BasePermission):
"""
Разрешает доступ только пользователям с подтверждённым email.
Требует наличие поля `is_verified` у модели пользователя.
"""
message = "Email не подтверждён"
def has_permission(self, request: Request, view: APIView) -> bool:
"""Проверяет, подтверждён ли email пользователя."""
return (
request.user
and request.user.is_authenticated
and getattr(request.user, "is_verified", False)
)
class IsOwnerOrAdmin(permissions.BasePermission):
"""
Разрешает доступ владельцу объекта или администратору.
Полезно для случаев, когда админ должен иметь доступ
к любым объектам, а пользователь - только к своим.
"""
owner_field = "user"
def has_object_permission(self, request: Request, view: APIView, obj) -> bool:
"""Проверяет права на объект."""
if request.user and request.user.is_staff:
return True
owner = getattr(obj, self.owner_field, None)
if owner is None:
owner = getattr(obj, "owner", None)
return owner == request.user