Corp/backend/communications/STANDALONE_SETUP.md
2026-03-11 21:56:51 +03:00

521 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Django Communications - Standalone Setup Guide
## О приложении
`django-communications` - универсальная система чатов для Django с поддержкой:
- 🔌 **WebSocket** (Django Channels) - real-time messaging
- 💬 **Типы чатов**: private, group, channel, announcement, global, comments
- 🔗 **GenericForeignKey** - привязка к любой модели
- 📎 **Вложения**, реакции, голосования, пересылка сообщений
- 🔔 **Уведомления** (опционально через django-notifications-hq)
- 🔐 **Permissions** (django-rules)
- 📱 **REST API** (Django REST Framework)
---
## Быстрая установка
### 1. Установка пакета
```bash
# Из локальной копии
pip install -e /path/to/communications/
# ИЛИ из PyPI (когда будет опубликован)
pip install django-communications
# С опциональными зависимостями
pip install django-communications[all]
```
### 2. Добавить в INSTALLED_APPS
```python
# settings.py
INSTALLED_APPS = [
# Django apps
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third-party apps (обязательные)
'rest_framework',
'channels',
'rules',
# Communications
'communications',
# Опциональные (рекомендуется)
'notifications', # для уведомлений
]
```
### 3. Настройка
```python
# settings.py
# ===== Django REST Framework =====
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# ===== Django Channels (WebSocket) =====
ASGI_APPLICATION = 'your_project.asgi.application'
CHANNEL_LAYERS = {
'default': {
# Для разработки - In-Memory
'BACKEND': 'channels.layers.InMemoryChannelLayer',
# Для продакшена - Redis (раскомментируйте)
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
# 'CONFIG': {
# "hosts": [('127.0.0.1', 6379)],
# },
},
}
# ===== django-rules (Permissions) =====
AUTHENTICATION_BACKENDS = (
'rules.permissions.ObjectPermissionBackend',
'django.contrib.auth.backends.ModelBackend',
)
# ===== Communications Settings =====
# URL для профиля автора (ОБЯЗАТЕЛЬНО настроить под ваш проект)
COMMUNICATIONS_AUTHOR_URL_PATTERN = '/users/{id}/' # По умолчанию: '/api/v1/employees/{id}/'
# Функция сжатия изображений (опционально)
COMMUNICATIONS_IMAGE_COMPRESSOR = None # Отключает сжатие
# COMMUNICATIONS_IMAGE_COMPRESSOR = 'myapp.utils.compress_avatar' # Ваша функция
# Resolver для определения участников чата (опционально)
# COMMUNICATIONS_PARTICIPANT_RESOLVER = 'myapp.utils.get_chat_participants'
# Автоматические уведомления (по умолчанию True)
COMMUNICATIONS_AUTO_NOTIFY = True
```
### 4. URL routing
```python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from communications.api.viewsets import ChatViewSet, MessageViewSet, PollViewSet
router = DefaultRouter()
router.register(r'chats', ChatViewSet, basename='chats')
router.register(r'messages', MessageViewSet, basename='messages')
router.register(r'polls', PollViewSet, basename='polls')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/communications/', include(router.urls)),
]
```
### 5. ASGI routing (WebSocket)
```python
# asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings')
django_asgi_app = get_asgi_application()
from your_project.routing import websocket_urlpatterns
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AuthMiddlewareStack(
URLRouter(websocket_urlpatterns)
),
})
```
```python
# routing.py (создайте файл)
from django.urls import path
from your_app.consumers import YourConsumer # Ваш consumer с ChatConsumerMixin
websocket_urlpatterns = [
path('ws/', YourConsumer.as_asgi()),
]
```
### 6. WebSocket Consumer
```python
# consumers.py
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from communications.consumers import ChatConsumerMixin
class YourConsumer(ChatConsumerMixin, AsyncJsonWebsocketConsumer):
"""
WebSocket consumer с поддержкой чатов.
ChatConsumerMixin предоставляет все методы для работы с чатами.
"""
async def connect(self):
self.user = self.scope["user"]
if not self.user.is_authenticated:
await self.close()
return
self.active_chat_id = None
await self.accept()
# Подключение к персональному каналу
await self.channel_layer.group_add(
f"user_{self.user.id}",
self.channel_name
)
async def disconnect(self, close_code):
if hasattr(self, 'user') and self.user.is_authenticated:
await self.channel_layer.group_discard(
f"user_{self.user.id}",
self.channel_name
)
async def receive_json(self, content):
"""
Обработка сообщений от клиента.
ChatConsumerMixin автоматически обрабатывает chat_* actions.
"""
action = content.get('action')
# Chat actions (обрабатываются автоматически ChatConsumerMixin)
if action in ['open_chat', 'close_chat', 'send_message', 'edit_message',
'delete_message', 'add_reaction', 'remove_reaction',
'typing', 'stop_typing', 'mark_read', 'vote_poll']:
# Вызов соответствующего метода из mixin
handler = getattr(self, f'_handle_{action}', None)
if handler:
await handler(content)
return
# Ваша дополнительная логика
await self.send_json({
'type': 'error',
'error': f'Unknown action: {action}'
})
```
### 7. Миграции
```bash
python manage.py migrate
```
### 8. Создать главный глобальный чат (опционально)
```python
# В Django shell или через signal
from communications.models import Chat
Chat.objects.create(
type='global',
name='Общий чат',
flags={'is_primary': True},
is_main=True # backward compatibility
)
```
---
## Настройка (детально)
### COMMUNICATIONS_AUTHOR_URL_PATTERN
Шаблон URL для профиля автора сообщения. Используется в `serialize_message()`.
```python
# Настройка по умолчанию (проектно-специфичная)
COMMUNICATIONS_AUTHOR_URL_PATTERN = '/api/v1/employees/{id}/'
# Для standalone проекта
COMMUNICATIONS_AUTHOR_URL_PATTERN = '/users/{id}/'
# Отключить URL (пустая строка)
COMMUNICATIONS_AUTHOR_URL_PATTERN = None
```
**Placeholder `{id}`** заменяется на `author.id`.
---
### COMMUNICATIONS_IMAGE_COMPRESSOR
Функция для сжатия аватаров чатов. Вызывается в pre_save signal.
```python
# Отключить сжатие
COMMUNICATIONS_IMAGE_COMPRESSOR = None
# Использовать свою функцию
COMMUNICATIONS_IMAGE_COMPRESSOR = 'myapp.utils.compress_avatar'
# По умолчанию (backward compatibility)
COMMUNICATIONS_IMAGE_COMPRESSOR = 'common.image_utils.compress_avatar'
```
**Сигнатура функции:**
```python
def compress_avatar(image_bytes: bytes) -> bytes:
"""
Принимает байты изображения, возвращает сжатые байты.
"""
from PIL import Image
import io
img = Image.open(io.BytesIO(image_bytes))
# ... ваша логика сжатия ...
return compressed_bytes
```
---
### COMMUNICATIONS_PARTICIPANT_RESOLVER
Callback для определения участников чата. Используется в `Chat.get_participants()`.
```python
# Не использовать callback (использовать fallback логику)
# COMMUNICATIONS_PARTICIPANT_RESOLVER = None # (по умолчанию)
# Использовать проектно-специфичную логику
COMMUNICATIONS_PARTICIPANT_RESOLVER = 'employees.utils.get_chat_participants'
```
**Сигнатура функции:**
```python
from django.db.models import QuerySet
from communications.models import Chat
def get_chat_participants(chat: Chat) -> QuerySet | None:
"""
Возвращает QuerySet участников или None (использовать fallback).
Примеры специфичной логики:
- Для department чата - все сотрудники департамента
- Для project чата - все участники проекта
- И т.д.
"""
if chat.type == 'group' and chat.context_object:
# Пример: context связан с Project
if hasattr(chat.context_object, 'members'):
return chat.context_object.members.all()
return None # Использовать fallback
```
---
### COMMUNICATIONS_AUTO_NOTIFY
Включает/отключает автоматические уведомления при новых сообщениях.
```python
# Включить (по умолчанию)
COMMUNICATIONS_AUTO_NOTIFY = True
# Отключить (управлять уведомлениями вручную)
COMMUNICATIONS_AUTO_NOTIFY = False
```
**Требует:** `django-notifications-hq` (опционально)
---
## API Endpoints
После настройки доступны следующие endpoints:
### Chats
- `GET /api/communications/chats/` - список чатов
- `GET /api/communications/chats/{id}/` - детали чата
- `POST /api/communications/chats/` - создать чат
- `POST /api/communications/chats/{id}/pin/` - закрепить
- `POST /api/communications/chats/{id}/notifications/` - настроить уведомления
- `GET /api/communications/chats/{id}/messages/` - сообщения (с автоотметкой)
### Messages
- `GET /api/communications/messages/` - список сообщений
- `POST /api/communications/messages/` - отправить сообщение
- `PATCH /api/communications/messages/{id}/` - редактировать
- `DELETE /api/communications/messages/{id}/` - удалить
- `POST /api/communications/messages/{id}/react/` - добавить реакцию
- `POST /api/communications/messages/{id}/forward/` - переслать
- `GET /api/communications/messages/search/` - поиск
### Polls
- `GET /api/communications/polls/` - список голосований
- `POST /api/communications/polls/` - создать голосование
- `POST /api/communications/polls/{id}/vote/` - проголосовать
- `POST /api/communications/polls/{id}/close_poll/` - закрыть
**Документация:** См. [api/README.md](api/README.md)
---
## WebSocket Events
### От клиента (action)
```json
{"action": "open_chat", "chat_id": 1}
{"action": "send_message", "chat_id": 1, "content": "Hello"}
{"action": "typing", "chat_id": 1}
{"action": "add_reaction", "message_id": 123, "emoji": "👍"}
```
### От сервера (type)
```json
{"type": "chat_message", "message": {...}}
{"type": "chat_user_typing", "user_id": 5, "chat_id": 1}
{"type": "chat_reaction_added", "message_id": 123, "emoji": "👍"}
```
**Детали:** См. [consumers.py](consumers.py) docstring
---
## Celery Tasks (опционально)
Если используете Celery, добавьте периодическую задачу:
```python
# celery.py
from celery import Celery
from celery.schedules import crontab
app = Celery('your_project')
app.conf.beat_schedule = {
'cleanup-orphaned-attachments': {
'task': 'communications.tasks.cleanup_orphaned_attachments',
'schedule': crontab(hour='*/1'), # Каждый час
},
}
```
---
## Примеры использования
### Создание чата
```python
from communications.models import Chat
from django.contrib.auth import get_user_model
User = get_user_model()
# Личный чат
chat = Chat.objects.create(type='private')
chat.participants.add(user1, user2)
# Групповой чат
chat = Chat.objects.create(
type='group',
name='Команда разработки',
created_by=request.user
)
chat.participants.add(user1, user2, user3)
# Чат привязанный к объекту (GenericFK)
from myapp.models import Project
project = Project.objects.get(id=1)
chat = Chat.objects.create(
type='comments',
context_object=project, # GenericForeignKey
name=f'Комментарии: {project.name}'
)
```
### Отправка сообщения
```python
from communications.models import Message
message = Message.objects.create(
chat=chat,
author=request.user,
content='Привет всем!'
)
```
### Проверка прав доступа
```python
# В view
from rules.contrib.views import PermissionRequiredMixin
class ChatDetailView(PermissionRequiredMixin, DetailView):
model = Chat
permission_required = 'communications.can_view_chat'
```
---
## Миграция с других систем
Если у вас уже есть проектно-специфичные чаты, используйте:
1. GenericForeignKey для привязки к существующим моделям
2. `COMMUNICATIONS_PARTICIPANT_RESOLVER` для логики участников
3. Миграции данных через management команды
---
## Troubleshooting
### Ошибка импорта `common.image_utils`
**Решение:** Установите `COMMUNICATIONS_IMAGE_COMPRESSOR = None` в settings.py
### Ошибка импорта `employees.models`
**Решение:** Команда `verify_chat_migration` автоматически пропустит проверку
### WebSocket не работает
**Проверьте:**
1. ASGI_APPLICATION настроен
2. CHANNEL_LAYERS настроен
3. Redis запущен (для продакшена)
4. routing.py содержит websocket_urlpatterns
### Уведомления не приходят
**Проверьте:**
1. `django-notifications-hq` установлен
2. `COMMUNICATIONS_AUTO_NOTIFY = True`
3. `notifications` в INSTALLED_APPS
---
## Лицензия
MIT License
---
## Поддержка
- **Issues:** https://github.com/yourusername/django-communications/issues
- **Documentation:** https://github.com/yourusername/django-communications