feat(communications): Добавить тип 'comments', пометить 'department' как legacy
BREAKING CHANGE: Тип 'department' помечен как deprecated.
Изменения:
- Добавлен новый тип чата 'comments' для систем комментариев
- Тип 'department' перемещен в конец списка и помечен как [Legacy]
- Обновлен help_text: рекомендуется использовать 'group' + context_object
- Обновлен __str__() для обработки типа 'comments'
- Создана миграция 0036_add_comments_type_deprecate_department
Универсальность:
- 'comments' + context_object работает с любыми моделями
- Треды, реакции, вложения доступны из коробки
- Real-time обновления через WebSocket
- Автоматические уведомления при упоминаниях и ответах
Рекомендации:
- Для проектно-специфичных чатов используй type='group' + context_object
- Department: type='group' + context_object=department + flags={'is_department': True}
- Comments: type='comments' + context_object=any_model
Документация:
- Создан docs/guides/COMMENTS_USAGE.md с примерами
- Описаны все возможности системы комментариев
- Примеры API, WebSocket, миграции с legacy типов
This commit is contained in:
parent
c670246dd5
commit
6ddd3f69ba
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.4 on 2026-03-11 14:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('communications', '0035_remove_department_field'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='chat',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('private', 'Личный'), ('group', 'Групповой'), ('channel', 'Канал'), ('announcement', 'Объявления'), ('global', 'Глобальный'), ('comments', 'Комментарии'), ('department', '[Legacy] Отдел')], db_index=True, help_text="Chat type: private, group, channel, announcement, global, comments. Use 'group' + context_object for domain-specific chats.", max_length=32, verbose_name='Тип чата'),
|
||||
),
|
||||
]
|
||||
@ -80,10 +80,12 @@ class Chat(models.Model):
|
||||
CHAT_TYPE_CHOICES = [
|
||||
("private", "Личный"),
|
||||
("group", "Групповой"),
|
||||
("department", "Отдел"),
|
||||
("channel", "Канал"),
|
||||
("announcement", "Объявления"),
|
||||
("global", "Глобальный"),
|
||||
("comments", "Комментарии"),
|
||||
# DEPRECATED: проектно-специфичные типы (для обратной совместимости)
|
||||
("department", "[Legacy] Отдел"),
|
||||
]
|
||||
|
||||
type = models.CharField(
|
||||
@ -91,7 +93,7 @@ class Chat(models.Model):
|
||||
choices=CHAT_TYPE_CHOICES,
|
||||
verbose_name="Тип чата",
|
||||
db_index=True,
|
||||
help_text="Chat type: private, group, channel, announcement, department, global"
|
||||
help_text="Chat type: private, group, channel, announcement, global, comments. Use 'group' + context_object for domain-specific chats."
|
||||
)
|
||||
|
||||
# Новые базовые поля
|
||||
@ -348,14 +350,18 @@ class Chat(models.Model):
|
||||
return f"Личный чат: {names}"
|
||||
if self.type == "group":
|
||||
return self.name or "Групповой чат"
|
||||
if self.type == "department":
|
||||
# Used context_object (GenericFK)
|
||||
dept = self.context_object if self.context_object else None
|
||||
return f"Чат отдела: {dept or '—'}"
|
||||
if self.type == "comments":
|
||||
# Comments linked to any object via GenericFK
|
||||
context = self.context_object if self.context_object else None
|
||||
return f"Комментарии: {context or self.name or '—'}"
|
||||
if self.type == "channel":
|
||||
return self.name or "Канал"
|
||||
if self.type == "announcement":
|
||||
return self.name or "Объявления"
|
||||
if self.type == "department":
|
||||
# LEGACY: Use type='group' + context_object instead
|
||||
dept = self.context_object if self.context_object else None
|
||||
return f"[Legacy] Чат отдела: {dept or '—'}"
|
||||
return "Глобальный чат"
|
||||
|
||||
|
||||
|
||||
335
backend/docs/guides/COMMENTS_USAGE.md
Normal file
335
backend/docs/guides/COMMENTS_USAGE.md
Normal file
@ -0,0 +1,335 @@
|
||||
# Использование Communications для комментариев
|
||||
|
||||
## Новый тип чата: `comments`
|
||||
|
||||
С версии migrations/0036 добавлен специальный тип чата **"comments"** для систем комментариев.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
### 1. Создание чата комментариев
|
||||
|
||||
```python
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from communications.models import Chat, Message
|
||||
|
||||
# Привязать комментарии к любому объекту
|
||||
document = Document.objects.get(id=123)
|
||||
|
||||
comments_chat = Chat.objects.create(
|
||||
type='comments',
|
||||
name=f'Комментарии: {document.title}',
|
||||
context_object=document, # GenericFK - работает с ЛЮБОЙ моделью
|
||||
created_by=request.user,
|
||||
flags={'allow_replies': True, 'allow_reactions': True}
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Добавление комментария
|
||||
|
||||
```python
|
||||
# Простой комментарий
|
||||
comment = Message.objects.create(
|
||||
chat=comments_chat,
|
||||
author=request.user,
|
||||
content="Отличный документ!"
|
||||
)
|
||||
|
||||
# Комментарий с вложениями
|
||||
comment = Message.objects.create(
|
||||
chat=comments_chat,
|
||||
author=request.user,
|
||||
content="См. прикрепленный файл",
|
||||
has_attachments=True
|
||||
)
|
||||
MessageAttachment.objects.create(
|
||||
message=comment,
|
||||
file=uploaded_file,
|
||||
file_type='file',
|
||||
file_name='report.pdf',
|
||||
file_size=12345
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Вложенные комментарии (треды)
|
||||
|
||||
```python
|
||||
# Ответ на комментарий
|
||||
reply = Message.objects.create(
|
||||
chat=comments_chat,
|
||||
author=another_user,
|
||||
content="Согласен!",
|
||||
reply_to=comment, # Прямой ответ
|
||||
thread_root=comment # Корень треда
|
||||
)
|
||||
|
||||
# Получить все ответы на комментарий
|
||||
replies = comment.direct_replies.filter(is_deleted=False)
|
||||
|
||||
# Получить весь тред
|
||||
thread = Message.objects.filter(
|
||||
thread_root=comment,
|
||||
is_deleted=False
|
||||
).order_by('created_at')
|
||||
```
|
||||
|
||||
### 4. Редактирование и удаление
|
||||
|
||||
```python
|
||||
# Редактировать комментарий
|
||||
from django.utils import timezone
|
||||
|
||||
comment.content = "Отличный документ! (обновлено)"
|
||||
comment.is_edited = True
|
||||
comment.edited_at = timezone.now()
|
||||
comment.save()
|
||||
|
||||
# Мягкое удаление
|
||||
comment.is_deleted = True
|
||||
comment.deleted_at = timezone.now()
|
||||
comment.deleted_by = request.user
|
||||
comment.save()
|
||||
```
|
||||
|
||||
### 5. Реакции (эмодзи)
|
||||
|
||||
```python
|
||||
from communications.models import MessageReaction
|
||||
|
||||
# Добавить реакцию
|
||||
MessageReaction.objects.create(
|
||||
message=comment,
|
||||
user=request.user,
|
||||
emoji='👍'
|
||||
)
|
||||
|
||||
# Получить сводку по реакциям
|
||||
reactions_summary = comment.get_reactions_summary()
|
||||
# {
|
||||
# '👍': {'count': 3, 'users': [1, 2, 3], 'user_names': ['User1', ...]},
|
||||
# '❤️': {'count': 1, 'users': [4], 'user_names': ['User4']}
|
||||
# }
|
||||
```
|
||||
|
||||
### 6. Уведомления (автоматические)
|
||||
|
||||
Модуль автоматически отправляет уведомления при:
|
||||
- **Упоминаниях**: `@username` в тексте комментария
|
||||
- **Ответах**: reply_to на чужой комментарий
|
||||
- **Новых комментариях**: всем участникам чата (кроме автора)
|
||||
|
||||
```python
|
||||
# Упомянуть пользователя
|
||||
comment = Message.objects.create(
|
||||
chat=comments_chat,
|
||||
author=request.user,
|
||||
content="@john.doe посмотри этот документ"
|
||||
)
|
||||
# → john.doe получит уведомление автоматически
|
||||
```
|
||||
|
||||
### 7. Real-time обновления через WebSocket
|
||||
|
||||
```javascript
|
||||
// Frontend: подключение к WebSocket
|
||||
const ws = new WebSocket('ws://localhost:8000/ws/realtime/');
|
||||
|
||||
// Подписаться на комментарии
|
||||
ws.send(JSON.stringify({
|
||||
type: 'chat.subscribe',
|
||||
chat_id: commentsChat.id
|
||||
}));
|
||||
|
||||
// Получать обновления в реальном времени
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'chat.message') {
|
||||
// Новый комментарий → обновить UI
|
||||
addCommentToUI(data.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## API эндпоинты
|
||||
|
||||
### Получить список комментариев
|
||||
|
||||
```http
|
||||
GET /api/v1/communications/chats/{chat_id}/messages/?page=1&page_size=20
|
||||
|
||||
Response:
|
||||
{
|
||||
"count": 42,
|
||||
"next": "...",
|
||||
"results": [
|
||||
{
|
||||
"id": 123,
|
||||
"author": {...},
|
||||
"content": "Отличный документ!",
|
||||
"created_at": "2026-03-11T15:30:00Z",
|
||||
"is_edited": false,
|
||||
"reply_to": null,
|
||||
"thread_root": null,
|
||||
"reactions_summary": {
|
||||
"👍": {"count": 3, "users": [1,2,3]}
|
||||
},
|
||||
"attachments": [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Создать комментарий
|
||||
|
||||
```http
|
||||
POST /api/v1/communications/chats/{chat_id}/messages/
|
||||
|
||||
{
|
||||
"content": "Отличный документ!",
|
||||
"reply_to": 123 // опционально
|
||||
}
|
||||
```
|
||||
|
||||
### Редактировать комментарий
|
||||
|
||||
```http
|
||||
PATCH /api/v1/communications/messages/{message_id}/
|
||||
|
||||
{
|
||||
"content": "Отличный документ! (обновлено)"
|
||||
}
|
||||
```
|
||||
|
||||
### Удалить комментарий
|
||||
|
||||
```http
|
||||
DELETE /api/v1/communications/messages/{message_id}/
|
||||
```
|
||||
|
||||
### Добавить реакцию
|
||||
|
||||
```http
|
||||
POST /api/v1/communications/messages/{message_id}/react/
|
||||
|
||||
{
|
||||
"emoji": "👍"
|
||||
}
|
||||
```
|
||||
|
||||
## Преимущества использования Communications для комментариев
|
||||
|
||||
✅ **Треды** - вложенные комментарии "из коробки"
|
||||
✅ **Реакции** - эмодзи-реакции на комментарии
|
||||
✅ **Вложения** - картинки, файлы, видео
|
||||
✅ **Редактирование** - с историей изменений
|
||||
✅ **Упоминания** - @username с автоматическими уведомлениями
|
||||
✅ **Real-time** - WebSocket обновления
|
||||
✅ **Универсальность** - GenericFK работает с любыми моделями
|
||||
✅ **Оптимизация** - денормализованные счетчики, prefetch
|
||||
|
||||
## Миграция с "department" на универсальные типы
|
||||
|
||||
### ❌ Старый подход (проектно-специфичный)
|
||||
|
||||
```python
|
||||
# Чат отдела (жестко привязан к EUSRR)
|
||||
chat = Chat.objects.create(
|
||||
type='department', # DEPRECATED!
|
||||
department=department_instance
|
||||
)
|
||||
```
|
||||
|
||||
### ✅ Новый подход (универсальный)
|
||||
|
||||
```python
|
||||
# Используй type='group' + context_object
|
||||
chat = Chat.objects.create(
|
||||
type='group', # Универсальный тип
|
||||
name=f'Чат отдела: {department.name}',
|
||||
context_object=department, # GenericFK
|
||||
flags={'is_department_chat': True}
|
||||
)
|
||||
|
||||
# Или для комментариев к объектам отдела
|
||||
chat = Chat.objects.create(
|
||||
type='comments',
|
||||
name=f'Обсуждение: {department.name}',
|
||||
context_object=department
|
||||
)
|
||||
```
|
||||
|
||||
## Настройка участников через callback
|
||||
|
||||
Для проектно-специфичной логики участников используй callback:
|
||||
|
||||
```python
|
||||
# settings.py
|
||||
COMMUNICATIONS_PARTICIPANT_RESOLVER = 'myproject.utils.get_chat_participants'
|
||||
|
||||
# myproject/utils.py
|
||||
def get_chat_participants(chat):
|
||||
"""
|
||||
Кастомная логика определения участников.
|
||||
|
||||
Возвращает QuerySet пользователей или None для fallback.
|
||||
"""
|
||||
if chat.type == 'comments':
|
||||
# Комментарии видят только те, кто имеет доступ к объекту
|
||||
if hasattr(chat.context_object, 'get_allowed_users'):
|
||||
return chat.context_object.get_allowed_users()
|
||||
|
||||
# Для department используй GenericFK
|
||||
if chat.flags.get('is_department_chat'):
|
||||
from employees.models import EmployeeDepartment
|
||||
dept = chat.context_object
|
||||
user_ids = EmployeeDepartment.objects.filter(
|
||||
department=dept,
|
||||
is_active=True
|
||||
).values_list('employee_id', flat=True)
|
||||
return User.objects.filter(id__in=user_ids)
|
||||
|
||||
return None # Fallback к стандартной логике
|
||||
```
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Комментарии к документу
|
||||
|
||||
```python
|
||||
document = Document.objects.get(id=123)
|
||||
chat = Chat.objects.create(
|
||||
type='comments',
|
||||
name=f'Комментарии: {document.title}',
|
||||
context_object=document,
|
||||
flags={'allow_anonymous': False}
|
||||
)
|
||||
```
|
||||
|
||||
### Комментарии к задаче
|
||||
|
||||
```python
|
||||
task = Task.objects.get(id=456)
|
||||
chat = Chat.objects.create(
|
||||
type='comments',
|
||||
name=f'Обсуждение: {task.title}',
|
||||
context_object=task,
|
||||
flags={'notify_assignee': True}
|
||||
)
|
||||
```
|
||||
|
||||
### Комментарии к посту в соцсети
|
||||
|
||||
```python
|
||||
post = Post.objects.get(id=789)
|
||||
chat = Chat.objects.create(
|
||||
type='comments',
|
||||
name='Comments',
|
||||
context_object=post,
|
||||
flags={'allow_public_comments': True, 'moderation_enabled': True}
|
||||
)
|
||||
```
|
||||
|
||||
## См. также
|
||||
|
||||
- [LDAP_CONFIGURATION.md](LDAP_CONFIGURATION.md) - интеграция с LDAP
|
||||
- [JS_MODULES_GUIDE.md](JS_MODULES_GUIDE.md) - frontend интеграция
|
||||
- [REFACTORING_SUMMARY.md](REFACTORING_SUMMARY.md) - история рефакторинга
|
||||
Loading…
x
Reference in New Issue
Block a user