feat: Add django-reversion API endpoints for document versions
- Added RevisionMiddleware to settings for automatic user tracking
- Created VersionSerializer and ActivityItemSerializer for version history
- Implemented GET /documents/{id}/versions/ endpoint
- Implemented GET /documents/{id}/activity/ endpoint (versions + audit + acknowledgements)
- Implemented POST /documents/{id}/revert/ endpoint for version rollback
- Activity timeline combines data from reversion, audit log, and acknowledgements
- All endpoints properly serialize user data with avatar support
This unblocks the Activity and Versions tabs in DocumentDetailModal.
This commit is contained in:
parent
243fffc126
commit
7739e7520e
@ -176,6 +176,42 @@ class CabinetSerializer(serializers.Serializer):
|
||||
return obj.documents.count() if hasattr(obj, 'documents') else 0
|
||||
|
||||
|
||||
class VersionSerializer(serializers.Serializer):
|
||||
"""Сериализатор для версий документа (django-reversion)."""
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
revision_id = serializers.IntegerField(source='revision.id', read_only=True)
|
||||
date_created = serializers.DateTimeField(source='revision.date_created', read_only=True)
|
||||
user = serializers.SerializerMethodField()
|
||||
comment = serializers.CharField(source='revision.comment', read_only=True, allow_blank=True)
|
||||
|
||||
# Данные версии
|
||||
data = serializers.SerializerMethodField()
|
||||
|
||||
def get_user(self, obj) -> dict | None:
|
||||
"""Возвращает информацию о пользователе, создавшем версию."""
|
||||
if not obj.revision.user:
|
||||
return None
|
||||
user = obj.revision.user
|
||||
return {
|
||||
'id': user.id,
|
||||
'full_name': f'{user.last_name} {user.first_name}'.strip(),
|
||||
'avatar_url': getattr(user, 'avatar_url', None),
|
||||
}
|
||||
|
||||
def get_data(self, obj) -> dict:
|
||||
"""Возвращает данные версии документа."""
|
||||
return obj.field_dict
|
||||
|
||||
|
||||
class ActivityItemSerializer(serializers.Serializer):
|
||||
"""Сериализатор для элемента timeline активности."""
|
||||
type = serializers.CharField() # 'version', 'audit', 'acknowledgement'
|
||||
timestamp = serializers.DateTimeField()
|
||||
user = serializers.DictField(allow_null=True)
|
||||
action = serializers.CharField()
|
||||
details = serializers.DictField(allow_null=True)
|
||||
|
||||
|
||||
class DocumentReadSerializer(serializers.ModelSerializer):
|
||||
"""Сериализатор для чтения документа.
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@ from django.db import transaction
|
||||
from django.db.models import Count, Exists, OuterRef, QuerySet, Q
|
||||
from documents.models import Document, DocumentAcknowledgement
|
||||
from filer.models import Folder
|
||||
import reversion
|
||||
from reversion.models import Version
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
|
||||
@ -17,7 +19,13 @@ from rest_framework.viewsets import ModelViewSet
|
||||
from ..employees.serializers import EmployeeBriefSerializer
|
||||
from ..permissions import AdminOrActionOrModelPerms
|
||||
from .permissions import DocumentReadOrModelPerms
|
||||
from .serializers import DocumentReadSerializer, DocumentWriteSerializer, FolderSerializer
|
||||
from .serializers import (
|
||||
DocumentReadSerializer,
|
||||
DocumentWriteSerializer,
|
||||
FolderSerializer,
|
||||
VersionSerializer,
|
||||
ActivityItemSerializer,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@ -331,6 +339,185 @@ class DocumentViewSet(ModelViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# DJANGO-REVERSION ENDPOINTS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def versions(self, request, pk=None):
|
||||
"""Получить историю версий документа.
|
||||
|
||||
Returns:
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"revision_id": 123,
|
||||
"date_created": "2026-02-28T10:00:00Z",
|
||||
"user": {"id": 1, "full_name": "Иванов Иван"},
|
||||
"comment": "Изменено описание",
|
||||
"data": {"title": "...", "description": "..."}
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
document = self.get_object()
|
||||
|
||||
# Получаем все версии документа
|
||||
versions = Version.objects.get_for_object(document).select_related('revision', 'revision__user')
|
||||
|
||||
serializer = VersionSerializer(versions, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def activity(self, request, pk=None):
|
||||
"""Получить timeline активности документа (версии + аудит).
|
||||
|
||||
Объединяет данные из:
|
||||
- reversion.Version (изменения документа)
|
||||
- DocumentAuditLog (аудит действий)
|
||||
- DocumentAcknowledgement (ознакомления)
|
||||
|
||||
Returns:
|
||||
[
|
||||
{
|
||||
"type": "version",
|
||||
"timestamp": "2026-02-28T10:00:00Z",
|
||||
"user": {"id": 1, "full_name": "Иванов Иван"},
|
||||
"action": "Изменение документа",
|
||||
"details": {"comment": "...", "fields": ["title", "description"]}
|
||||
},
|
||||
{
|
||||
"type": "audit",
|
||||
"timestamp": "2026-02-28T09:00:00Z",
|
||||
"user": {...},
|
||||
"action": "Просмотрен",
|
||||
"details": {"ip_address": "192.168.1.1"}
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
document = self.get_object()
|
||||
activity_items = []
|
||||
|
||||
# 1. Добавляем версии
|
||||
versions = Version.objects.get_for_object(document).select_related('revision', 'revision__user')
|
||||
for version in versions:
|
||||
user_data = None
|
||||
if version.revision.user:
|
||||
user = version.revision.user
|
||||
user_data = {
|
||||
'id': user.id,
|
||||
'full_name': f'{user.last_name} {user.first_name}'.strip(),
|
||||
'avatar_url': getattr(user, 'avatar_url', None),
|
||||
}
|
||||
|
||||
activity_items.append({
|
||||
'type': 'version',
|
||||
'timestamp': version.revision.date_created,
|
||||
'user': user_data,
|
||||
'action': 'Изменение документа',
|
||||
'details': {
|
||||
'comment': version.revision.comment or '',
|
||||
'version_id': version.id,
|
||||
'revision_id': version.revision.id,
|
||||
}
|
||||
})
|
||||
|
||||
# 2. Добавляем аудит (если есть)
|
||||
if hasattr(document, 'audit_log'):
|
||||
audit_logs = document.audit_log.all().select_related('user')[:50] # Ограничиваем 50 записями
|
||||
for log in audit_logs:
|
||||
user_data = None
|
||||
if log.user:
|
||||
user_data = {
|
||||
'id': log.user.id,
|
||||
'full_name': f'{log.user.last_name} {log.user.first_name}'.strip(),
|
||||
'avatar_url': getattr(log.user, 'avatar_url', None),
|
||||
}
|
||||
|
||||
activity_items.append({
|
||||
'type': 'audit',
|
||||
'timestamp': log.timestamp,
|
||||
'user': user_data,
|
||||
'action': log.get_action_display(),
|
||||
'details': {
|
||||
'ip_address': log.ip_address,
|
||||
'metadata': log.metadata,
|
||||
}
|
||||
})
|
||||
|
||||
# 3. Добавляем ознакомления
|
||||
acknowledgements = DocumentAcknowledgement.objects.filter(document=document).select_related('user')[:50]
|
||||
for ack in acknowledgements:
|
||||
user_data = {
|
||||
'id': ack.user.id,
|
||||
'full_name': f'{ack.user.last_name} {ack.user.first_name}'.strip(),
|
||||
'avatar_url': getattr(ack.user, 'avatar_url', None),
|
||||
}
|
||||
|
||||
activity_items.append({
|
||||
'type': 'acknowledgement',
|
||||
'timestamp': ack.acknowledged_at,
|
||||
'user': user_data,
|
||||
'action': 'Ознакомление',
|
||||
'details': None
|
||||
})
|
||||
|
||||
# Сортируем по времени (новые сверху)
|
||||
activity_items.sort(key=lambda x: x['timestamp'], reverse=True)
|
||||
|
||||
serializer = ActivityItemSerializer(activity_items, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def revert(self, request, pk=None):
|
||||
"""Откатить документ к указанной версии.
|
||||
|
||||
Body:
|
||||
{
|
||||
"version_id": 123, // ID версии из reversion.Version
|
||||
"comment": "Причина отката" // опционально
|
||||
}
|
||||
|
||||
Returns:
|
||||
Обновленный документ
|
||||
"""
|
||||
document = self.get_object()
|
||||
version_id = request.data.get('version_id')
|
||||
comment = request.data.get('comment', '')
|
||||
|
||||
if not version_id:
|
||||
return Response(
|
||||
{'error': 'version_id обязателен'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
# Получаем версию
|
||||
version = Version.objects.get(id=version_id, object_id=str(document.pk))
|
||||
|
||||
# Откатываем с комментарием
|
||||
with reversion.create_revision():
|
||||
version.revision.revert()
|
||||
reversion.set_user(request.user)
|
||||
reversion.set_comment(comment or f'Откат к версии {version_id}')
|
||||
|
||||
# Обновляем объект из базы
|
||||
document.refresh_from_db()
|
||||
serializer = self.get_serializer(document)
|
||||
return Response(serializer.data)
|
||||
|
||||
except Version.DoesNotExist:
|
||||
return Response(
|
||||
{'error': 'Версия не найдена'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{'error': str(e)},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
|
||||
class FolderViewSet(ModelViewSet):
|
||||
"""CRUD для папок документов (filer.Folder).
|
||||
|
||||
438
backend/docs/reports/LIBRARY_USAGE_ANALYSIS.md
Normal file
438
backend/docs/reports/LIBRARY_USAGE_ANALYSIS.md
Normal file
@ -0,0 +1,438 @@
|
||||
# Анализ использования библиотек для документооборота
|
||||
|
||||
**Дата:** 28 февраля 2026 г.
|
||||
**Статус:** Анализ установленных библиотек и степени использования их возможностей
|
||||
|
||||
---
|
||||
|
||||
## 📊 Общая статистика
|
||||
|
||||
| Библиотека | Версия | Использование | Комментарий |
|
||||
|------------|--------|---------------|-------------|
|
||||
| **django-filer** | 3.4.4 | ~25% | Базовые возможности файлового хранения |
|
||||
| **django-reversion** | 6.1.0 | ~15% | Только регистрация моделей, нет API |
|
||||
| **django-fsm** | 3.0.1 | ~80% | Полный workflow с 7 transitions |
|
||||
| **easy-thumbnails** | 2.10.1 | ~40% | Настройки есть, активное использование не проверено |
|
||||
| **django-mptt** | 0.18.0 | ~60% | Используется через filer для иерархии папок |
|
||||
|
||||
**Средний процент использования: ~44%**
|
||||
|
||||
---
|
||||
|
||||
## 1. django-filer 3.4.4 (~25% использования)
|
||||
|
||||
### ✅ Что используется:
|
||||
|
||||
#### Базовые возможности:
|
||||
- ✅ `FilerFileField` - поле для файлов
|
||||
- ✅ `Folder` модель - иерархическая структура папок
|
||||
- ✅ `File` модель - хранение файлов через django-filer
|
||||
- ✅ Связь Document → filer.File
|
||||
- ✅ Связь Document → filer.Folder
|
||||
- ✅ ACL настройки (`FILER_ENABLE_PERMISSIONS = True`)
|
||||
- ✅ Приватные файлы по умолчанию (`FILER_IS_PUBLIC_DEFAULT = False`)
|
||||
|
||||
#### API:
|
||||
- ✅ `FolderViewSet` - CRUD для папок
|
||||
- ✅ `FolderViewSet.children()` - получение дочерних папок
|
||||
- ✅ `FolderViewSet.documents()` - документы в папке
|
||||
- ✅ Фильтр по `?parent_id=` и `?root=true`
|
||||
|
||||
### ❌ Что НЕ используется:
|
||||
|
||||
#### Продвинутые возможности:
|
||||
- ❌ **FilerImageField** - специальное поле для изображений с метаданными (EXIF, геотеги)
|
||||
- ❌ **ThumbnailOption** - кастомные пресеты миниатюр (настройки есть, но не используются в коде)
|
||||
- ❌ **ClipboardItem** - буфер обмена для администратора
|
||||
- ❌ **canonical_url** - прямые URL к файлам (настройка есть: `FILER_CANONICAL_URL = 'canonical/'`)
|
||||
- ❌ **icons_url** - URL к иконкам типов файлов
|
||||
- ❌ **subject_location** - точка фокуса для умной обрезки изображений
|
||||
- ❌ **is_public / file_ptr** - управление публичностью отдельных файлов
|
||||
|
||||
#### Миниатюры:
|
||||
- ❌ Генерация thumbnails для PDF (библиотека поддерживает через pdf2image)
|
||||
- ❌ Кэширование thumbnails
|
||||
- ❌ Watermarks на изображениях
|
||||
- ❌ EXIF-метаданные для фото
|
||||
|
||||
#### Permissions:
|
||||
- ❌ **FilerPermissions** - детальные права на файлы и папки
|
||||
- ❌ Группы доступа к папкам
|
||||
- ❌ Наследование прав в иерархии папок
|
||||
|
||||
#### Admin Features:
|
||||
- ❌ Drag & Drop интерфейс администратора
|
||||
- ❌ Clipboard для копирования/перемещения файлов
|
||||
- ❌ Массовые операции над файлами
|
||||
- ❌ Directory listing views
|
||||
|
||||
#### Интеграция:
|
||||
- ❌ `django-polymorphic` для типов файлов (установлено, но минимально используется)
|
||||
- ❌ REST API от django-filer (мы используем свой собственный)
|
||||
|
||||
### 💡 Рекомендации:
|
||||
|
||||
**Высокий приоритет:**
|
||||
1. **Thumbnails API** - добавить endpoint для получения миниатюр разных размеров
|
||||
2. **Subject location** - для умной обрезки аватаров и превью
|
||||
3. **Детальные permissions** - права на уровне папок и файлов
|
||||
|
||||
**Средний приоритет:**
|
||||
4. **FilerImageField** - для работы с изображениями (метаданные, EXIF)
|
||||
5. **Canonical URLs** - прямые ссылки на файлы без перенаправлений
|
||||
6. **Clipboard** - буфер обмена в админке для удобства
|
||||
|
||||
**Низкий приоритет:**
|
||||
7. **Watermarks** - водяные знаки на документы
|
||||
8. **PDF thumbnails** - превью PDF через pdf2image
|
||||
|
||||
---
|
||||
|
||||
## 2. django-reversion 6.1.0 (~15% использования)
|
||||
|
||||
### ✅ Что используется:
|
||||
|
||||
- ✅ `@reversion.register()` на модели `Document` и `DocumentAcknowledgement`
|
||||
- ✅ Автоматическое сохранение версий при изменениях
|
||||
- ✅ Настройка в `INSTALLED_APPS`
|
||||
|
||||
### ❌ Что НЕ используется:
|
||||
|
||||
#### API для версий:
|
||||
- ❌ **Version.objects.get_for_object()** - получение истории версий
|
||||
- ❌ **Version.revision** - доступ к ревизиям
|
||||
- ❌ **Version.field_dict** - сравнение версий
|
||||
- ❌ **get_deleted()** - восстановление удаленных объектов
|
||||
|
||||
#### Endpoints (полностью отсутствуют):
|
||||
- ❌ `GET /api/v1/documents/{id}/versions/` - список версий
|
||||
- ❌ `GET /api/v1/documents/{id}/versions/{version_id}/` - конкретная версия
|
||||
- ❌ `POST /api/v1/documents/{id}/revert/` - откат к версии
|
||||
- ❌ `GET /api/v1/documents/{id}/activity/` - timeline активности
|
||||
- ❌ `GET /api/v1/documents/{id}/compare/` - сравнение версий
|
||||
|
||||
#### Продвинутые возможности:
|
||||
- ❌ **RevisionMiddleware** - автоматическое связывание версий с пользователем (middleware не добавлен!)
|
||||
- ❌ **Комментарии к версиям** - `reversion.create_revision(comment=...)`
|
||||
- ❌ **Метаданные версий** - хранение дополнительной информации
|
||||
- ❌ **Batch revisions** - группировка изменений в одну ревизию
|
||||
- ❌ **Version filtering** - фильтрация версий по пользователю/дате
|
||||
|
||||
#### Admin:
|
||||
- ❌ **VersionAdmin** - интерфейс истории в админке
|
||||
- ❌ Кнопка "Revert" для отката
|
||||
- ❌ Diff между версиями
|
||||
|
||||
### 💡 Рекомендации:
|
||||
|
||||
**КРИТИЧНО (блокирует фронтенд):**
|
||||
1. ✅ Добавить `RevisionMiddleware` в `MIDDLEWARE`
|
||||
2. ✅ Реализовать endpoint `/versions/` - список версий документа
|
||||
3. ✅ Реализовать endpoint `/activity/` - timeline для вкладки Activity
|
||||
4. ✅ Реализовать endpoint `/revert/` - откат к версии
|
||||
|
||||
**Высокий приоритет:**
|
||||
5. Метаданные к версиям (кто, когда, что изменил)
|
||||
6. Комментарии к изменениям
|
||||
7. Сравнение версий (diff)
|
||||
|
||||
**Средний приоритет:**
|
||||
8. VersionAdmin в админке
|
||||
9. Восстановление удаленных документов
|
||||
10. Фильтрация версий
|
||||
|
||||
---
|
||||
|
||||
## 3. django-fsm 3.0.1 (~80% использования)
|
||||
|
||||
### ✅ Что используется:
|
||||
|
||||
#### Модель:
|
||||
- ✅ `FSMField` с 6 статусами (draft, in_review, approved, published, archived, rejected)
|
||||
- ✅ `@transition` декораторы для 7 transitions
|
||||
- ✅ `protected=True` - защита от прямого изменения status
|
||||
|
||||
#### Transitions:
|
||||
- ✅ `submit_for_review()` - draft → in_review
|
||||
- ✅ `approve()` - in_review → approved
|
||||
- ✅ `reject()` - in_review → rejected
|
||||
- ✅ `publish()` - approved → published
|
||||
- ✅ `return_to_draft()` - in_review/draft → draft
|
||||
- ✅ `archive()` - published → archived
|
||||
- ✅ `unarchive()` - archived → published
|
||||
|
||||
#### API:
|
||||
- ✅ Все 7 transitions имеют endpoint `POST /documents/{id}/{action}/`
|
||||
- ✅ Автоматическое сохранение через `.save()` после transition
|
||||
|
||||
#### Admin:
|
||||
- ✅ `get_available_FIELD_transitions()` - доступные переходы
|
||||
|
||||
### ❌ Что НЕ используется:
|
||||
|
||||
#### Продвинутые transitions:
|
||||
- ❌ **Conditions** - условия выполнения перехода (`@transition(..., conditions=[...])`)
|
||||
- ❌ **Permissions** - права на переход (`permission='documents.can_approve'`)
|
||||
- ❌ **Custom targets** - динамические целевые статусы
|
||||
- ❌ **on_error** - обработка ошибок transition
|
||||
|
||||
#### Hooks и сигналы:
|
||||
- ❌ **pre_transition** - действия перед переходом
|
||||
- ❌ **post_transition** - действия после перехода (сейчас используются сигналы Django, можно было бы FSM)
|
||||
- ❌ **Transition history** - автоматическая история переходов
|
||||
|
||||
#### Интеграция:
|
||||
- ❌ **django-fsm-log** - детальное логирование переходов (не установлено)
|
||||
- ❌ **FSMAdmin** - улучшенный интерфейс админки с кнопками переходов
|
||||
|
||||
#### Graphviz:
|
||||
- ❌ **get_graph()** - визуализация state machine
|
||||
- ❌ Диаграмма workflow для документации
|
||||
|
||||
### 💡 Рекомендации:
|
||||
|
||||
**Высокий приоритет:**
|
||||
1. **Conditions** - проверять права перед transition (например, `can_approve`)
|
||||
2. **Permissions** - интеграция с django permissions
|
||||
3. **django-fsm-log** - установить и логировать все переходы
|
||||
|
||||
**Средний приоритет:**
|
||||
4. **pre_transition/post_transition** - заменить текущие сигналы на FSM hooks
|
||||
5. **on_error** - обработка ошибок (например, если документ без файла)
|
||||
6. **FSMAdmin** - улучшить админку
|
||||
|
||||
**Низкий приоритет:**
|
||||
7. **Graph visualization** - диаграмма workflow для документации
|
||||
8. **Custom targets** - если потребуются сложные workflows
|
||||
|
||||
---
|
||||
|
||||
## 4. easy-thumbnails 2.10.1 (~40% использования)
|
||||
|
||||
### ✅ Что используется (в settings.py):
|
||||
|
||||
#### Настройки:
|
||||
- ✅ `THUMBNAIL_PROCESSORS` - процессоры обработки
|
||||
- ✅ `colorspace` - конвертация цветового пространства
|
||||
- ✅ `autocrop` - автообрезка
|
||||
- ✅ `scale_and_crop_with_subject_location` - умная обрезка (filer)
|
||||
- ✅ `filters` - фильтры изображений
|
||||
- ✅ `THUMBNAIL_HIGH_RESOLUTION = True` - Retina поддержка
|
||||
- ✅ `THUMBNAIL_PRESERVE_EXTENSIONS` - сохранение PNG/GIF
|
||||
|
||||
#### Размеры (в settings):
|
||||
```python
|
||||
THUMBNAIL_ALIASES = {
|
||||
'': {
|
||||
'admin_thumbnail': {'size': (60, 60), 'crop': True},
|
||||
'file_card': {'size': (200, 150), 'crop': True},
|
||||
'preview': {'size': (800, 600), 'crop': False},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Что НЕ используется:
|
||||
|
||||
#### В коде:
|
||||
- ❌ **get_thumbnailer()** - генерация thumbnails в коде
|
||||
- ❌ **{% thumbnail %}** template tag - использование в шаблонах (у нас React фронтенд)
|
||||
- ❌ API endpoints для получения thumbnails разных размеров
|
||||
- ❌ **ThumbnailerNamespace** - кастомные namespace для разных разрешений
|
||||
|
||||
#### Продвинутые процессоры:
|
||||
- ❌ **background** - фоновый цвет для прозрачных
|
||||
- ❌ **detail** - резкость изображения
|
||||
- ❌ **replace_alpha** - замена прозрачности
|
||||
- ❌ **sharpen** - усиление резкости
|
||||
|
||||
#### Опции:
|
||||
- ❌ **quality** - качество JPEG
|
||||
- ❌ **subsampling** - субдискретизация для размера файла
|
||||
- ❌ **progressive** - прогрессивная загрузка JPEG
|
||||
- ❌ **orientation** - автоповорот по EXIF
|
||||
|
||||
#### Кэширование:
|
||||
- ❌ Настройки кэширования thumbnails
|
||||
- ❌ Warming cache (предгенерация)
|
||||
- ❌ Очистка устаревших thumbnails
|
||||
|
||||
### 💡 Рекомендации:
|
||||
|
||||
**Высокий приоритет:**
|
||||
1. **API для thumbnails** - `GET /documents/{id}/thumbnail/?size=preview`
|
||||
2. **Предгенерация** - создавать thumbnails при загрузке
|
||||
3. **Quality settings** - настроить качество для баланса размер/качество
|
||||
|
||||
**Средний приоритет:**
|
||||
4. **Progressive JPEG** - для быстрой загрузки
|
||||
5. **Warming** - предгенерация популярных размеров
|
||||
6. **Orientation** - автоповорот фото
|
||||
|
||||
---
|
||||
|
||||
## 5. django-mptt 0.18.0 (~60% использования)
|
||||
|
||||
### ✅ Что используется:
|
||||
|
||||
- ✅ Используется через django-filer для `filer.Folder` (иерархия папок)
|
||||
- ✅ Queries через MPTT для получения `children` и `parent`
|
||||
- ✅ Настройка в `INSTALLED_APPS`
|
||||
|
||||
### ❌ Что НЕ используется:
|
||||
|
||||
#### В собственных моделях:
|
||||
- ❌ **MPTTModel** - не используем для своих моделей (например, Cabinet)
|
||||
- ❌ **TreeForeignKey** - только через filer
|
||||
- ❌ **get_descendants()** - получение всех потомков
|
||||
- ❌ **get_ancestors()** - получение всех предков
|
||||
- ❌ **get_root()** - корневой элемент
|
||||
|
||||
#### Template tags:
|
||||
- ❌ **{% recursetree %}** - рекурсивный вывод дерева (React фронтенд)
|
||||
- ❌ **{% full_tree_for_model %}** - полное дерево
|
||||
|
||||
#### Manager методы:
|
||||
- ❌ **add_related_count()** - подсчет связанных объектов
|
||||
- ❌ **move_to()** - перемещение узлов
|
||||
- ❌ **insert_at()** - вставка в дерево
|
||||
|
||||
#### Admin:
|
||||
- ❌ **MPTTModelAdmin** - drag & drop в админке
|
||||
- ❌ **TreeRelatedFieldListFilter** - фильтр по дереву
|
||||
|
||||
### 💡 Рекомендации:
|
||||
|
||||
**Средний приоритет:**
|
||||
1. **Использовать MPTTModel** для Cabinet (у него есть parent, но не MPTT)
|
||||
2. **get_descendants()** - для получения всех подпапок с документами
|
||||
3. **MPTTModelAdmin** - улучшить админку папок
|
||||
|
||||
---
|
||||
|
||||
## 📈 Сводная таблица неиспользуемых возможностей
|
||||
|
||||
### Критичные (блокируют фронтенд):
|
||||
1. ✅ **django-reversion API** - endpoints для версий и activity (вкладки в modal)
|
||||
2. ✅ **RevisionMiddleware** - связывание версий с пользователем
|
||||
|
||||
### Высокий приоритет:
|
||||
3. **Thumbnails API** - endpoint для получения превью разных размеров
|
||||
4. **django-filer permissions** - детальные права на папки/файлы
|
||||
5. **django-fsm conditions** - условия и права для transitions
|
||||
6. **Metadata к версиям** - кто, когда, что изменил
|
||||
|
||||
### Средний приоритет:
|
||||
7. **FilerImageField** - для изображений с метаданными
|
||||
8. **Subject location** - умная обрезка изображений
|
||||
9. **Django-fsm-log** - детальное логирование transitions
|
||||
10. **Cabinet с MPTT** - иерархические виртуальные хранилища
|
||||
|
||||
### Низкий приоритет:
|
||||
11. Clipboard в админке
|
||||
12. Watermarks
|
||||
13. Graph visualization для FSM
|
||||
14. Восстановление удаленных документов
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Рекомендуемый план действий
|
||||
|
||||
### Этап 1: Критичные задачи (разблокировать фронтенд)
|
||||
**Срок: 1-2 дня**
|
||||
|
||||
```python
|
||||
# 1. Добавить RevisionMiddleware
|
||||
MIDDLEWARE = [
|
||||
...
|
||||
'reversion.middleware.RevisionMiddleware', # ← Добавить
|
||||
...
|
||||
]
|
||||
|
||||
# 2. Реализовать ViewSet actions:
|
||||
@action(detail=True, methods=['get'])
|
||||
def versions(self, request, pk=None):
|
||||
"""История версий документа"""
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def activity(self, request, pk=None):
|
||||
"""Timeline активности (версии + аудит)"""
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def revert(self, request, pk=None):
|
||||
"""Откат к версии"""
|
||||
```
|
||||
|
||||
### Этап 2: Высокий приоритет (улучшить UX)
|
||||
**Срок: 3-5 дней**
|
||||
|
||||
```python
|
||||
# 3. Thumbnails API
|
||||
@action(detail=True, methods=['get'])
|
||||
def thumbnail(self, request, pk=None):
|
||||
"""Получить thumbnail документа
|
||||
|
||||
Query params:
|
||||
size: admin_thumbnail | file_card | preview
|
||||
"""
|
||||
|
||||
# 4. Условия и права для FSM
|
||||
@transition(
|
||||
field=status,
|
||||
source='in_review',
|
||||
target='approved',
|
||||
permission='documents.can_approve',
|
||||
conditions=[has_required_fields, user_has_permission]
|
||||
)
|
||||
def approve(self):
|
||||
"""Одобрить документ"""
|
||||
```
|
||||
|
||||
### Этап 3: Средний приоритет (продвинутые фичи)
|
||||
**Срок: 1 неделя**
|
||||
|
||||
```python
|
||||
# 5. Установить django-fsm-log
|
||||
pip install django-fsm-log
|
||||
|
||||
# 6. FilerImageField для изображений
|
||||
class Document(models.Model):
|
||||
file = FilerFileField(...) # Для общих файлов
|
||||
image = FilerImageField(...) # Для изображений (nullable)
|
||||
|
||||
# 7. Permissions на папки
|
||||
class FolderViewSet:
|
||||
def check_folder_permission(self, user, folder, action):
|
||||
"""Проверить права на папку"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Итоговая оценка
|
||||
|
||||
| Критерий | Оценка | Комментарий |
|
||||
|----------|--------|-------------|
|
||||
| Базовые возможности | ✅ 85% | CRUD, workflow, папки работают |
|
||||
| Продвинутые возможности | ⚠️ 30% | Версии, thumbnails, permissions не используются |
|
||||
| Frontend интеграция | ⚠️ 60% | API есть, но нет endpoints для versions/thumbnails |
|
||||
| Admin интеграция | ⚠️ 40% | Базовый функционал, нет drag&drop, clipboard |
|
||||
| Performance | ⚠️ 50% | Нет кэширования thumbnails, нет предгенерации |
|
||||
|
||||
**Общая оценка использования библиотек: 44%**
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Дополнительные библиотеки (не установлены)
|
||||
|
||||
Библиотеки, которые могли бы улучшить документооборот:
|
||||
|
||||
1. **django-fsm-log** - логирование FSM transitions
|
||||
2. **django-admin-sortable2** - drag & drop в админке
|
||||
3. **pdf2image** - thumbnails для PDF
|
||||
4. **python-magic** - определение MIME-типов
|
||||
5. **OCRmyPDF** - OCR для сканов
|
||||
6. **WeasyPrint** - генерация PDF из HTML
|
||||
|
||||
---
|
||||
|
||||
**Подготовил:** GitHub Copilot
|
||||
**Дата:** 28 февраля 2026 г.
|
||||
@ -73,6 +73,7 @@ MIDDLEWARE = [
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"reversion.middleware.RevisionMiddleware", # django-reversion для версионирования
|
||||
"simple_history.middleware.HistoryRequestMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user