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

@@ -1,9 +1,6 @@
---
trigger: always_on
---
---
trigger: always_on
---
## 0) Язык и стиль общения (СТРОГО)
- ИИ-агент **ВСЕГДА отвечает на русском языке**
@@ -264,5 +261,162 @@ systemctl restart apache2
- объясняет, почему оно не подходит
- запрашивает разрешение в чате
## 15) Структура проекта
-
## 15) Структура проекта и миксины (ОБЯЗАТЕЛЬНО)
### 15.0 Правило Core-First (КРИТИЧНО)
**ПЕРЕД созданием любого нового компонента** агент ОБЯЗАН проверить модуль `apps.core`:
```
src/apps/core/
├── mixins.py # Model mixins (TimestampMixin, SoftDeleteMixin, etc.)
├── services.py # BaseService, BackgroundJobService
├── views.py # Health checks, BackgroundJob API
├── viewsets.py # BaseViewSet, ReadOnlyViewSet
├── exceptions.py # APIError, NotFoundError, ValidationError
├── permissions.py # IsOwner, IsAdminOrReadOnly, etc.
├── pagination.py # CursorPagination
├── filters.py # BaseFilterSet
├── cache.py # cache_result, invalidate_cache
├── tasks.py # BaseTask для Celery
├── logging.py # StructuredLogger
├── middleware.py # RequestIDMiddleware
├── signals.py # SignalDispatcher
├── responses.py # APIResponse wrapper
├── openapi.py # api_docs decorator
└── management/commands/base.py # BaseAppCommand
```
**Порядок действий:**
1. Проверить `apps.core` на наличие нужного базового класса/миксина
2. Наследоваться от существующего, а не создавать с нуля
3. Если нужного нет — обсудить добавление в core
**ЗАПРЕЩЕНО:** создавать дублирующую функциональность в app-модулях
---
### 15.1 Model Mixins
При создании моделей **ОБЯЗАТЕЛЬНО** использовать миксины из `apps.core.mixins`:
| Миксин | Когда использовать | Поля |
|--------|-------------------|------|
| `TimestampMixin` | **ВСЕГДА** для любой модели | `created_at`, `updated_at` |
| `UUIDPrimaryKeyMixin` | Когда нужен UUID вместо int ID | `id` (UUID) |
| `SoftDeleteMixin` | Когда нельзя физически удалять | `is_deleted`, `deleted_at` |
| `AuditMixin` | Когда нужно знать кто создал/изменил | `created_by`, `updated_by` |
| `OrderableMixin` | Для сортируемых списков | `order` |
| `StatusMixin` | Для моделей со статусами | `status` |
| `SlugMixin` | Для URL-friendly идентификаторов | `slug` |
**Пример правильного использования:**
```python
from apps.core.mixins import TimestampMixin, SoftDeleteMixin, AuditMixin
class Document(TimestampMixin, SoftDeleteMixin, AuditMixin, models.Model):
"""Документ с историей и мягким удалением."""
title = models.CharField(max_length=200)
class Meta:
ordering = ['-created_at']
```
**Порядок наследования миксинов:**
1. `UUIDPrimaryKeyMixin` (если нужен)
2. `TimestampMixin`
3. `SoftDeleteMixin` (если нужен)
4. `AuditMixin` (если нужен)
5. `OrderableMixin` / `StatusMixin` / `SlugMixin`
6. `models.Model` (последним)
---
### 15.2 Management Commands
Все management commands наследуются от `BaseAppCommand`:
```python
from apps.core.management.commands.base import BaseAppCommand
class Command(BaseAppCommand):
help = 'Описание команды'
use_transaction = True # Обернуть в транзакцию
def add_arguments(self, parser):
super().add_arguments(parser) # Добавляет --dry-run, --silent
parser.add_argument('--my-arg', type=str)
def execute_command(self, *args, **options):
items = MyModel.objects.all()
for item in self.progress_iter(items, desc="Обработка"):
if not self.dry_run:
self.process(item)
return "Обработано успешно"
```
**Возможности BaseAppCommand:**
- `--dry-run` — тестовый запуск без изменений
- `--silent` — минимальный вывод
- `self.progress_iter()` — прогресс-бар
- `self.timed_operation()` — измерение времени
- `self.confirm()` — подтверждение
- `self.log_info/success/warning/error()` — логирование
---
### 15.3 Background Jobs (Celery)
Для отслеживания статуса фоновых задач использовать `BackgroundJob`:
```python
# В сервисе при запуске задачи
from apps.core.services import BackgroundJobService
job = BackgroundJobService.create_job(
task_id=task.id,
task_name="apps.myapp.tasks.process_data",
user_id=request.user.id,
)
# В Celery таске
from apps.core.models import BackgroundJob
@shared_task(bind=True)
def my_task(self, data):
job = BackgroundJob.objects.get(task_id=self.request.id)
job.mark_started()
for i, item in enumerate(items):
process(item)
job.update_progress(i * 100 // len(items), "Обработка...")
job.complete(result={"processed": len(items)})
```
**API эндпоинты:**
- `GET /api/v1/jobs/` — список задач пользователя
- `GET /api/v1/jobs/{task_id}/` — статус конкретной задачи
---
### 15.4 Factories (тестирование)
Все фабрики используют `factory_boy` + `faker`:
```python
import factory
from faker import Faker
fake = Faker("ru_RU")
class MyModelFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyModel
name = factory.LazyAttribute(lambda _: fake.word())
email = factory.LazyAttribute(lambda _: fake.unique.email())
```
**Правила:**
- Никакого хардкода в тестах (`"test@example.com"``fake.email()`)
- Использовать `fake.unique.*` для уникальных полей
- Локаль: `Faker("ru_RU")` для русских данных