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

15 KiB
Raw Blame History

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. Установка пакета

# Из локальной копии
pip install -e /path/to/communications/

# ИЛИ из PyPI (когда будет опубликован)
pip install django-communications

# С опциональными зависимостями
pip install django-communications[all]

2. Добавить в INSTALLED_APPS

# 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. Настройка

# 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

# 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)

# 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)
    ),
})
# 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

# 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. Миграции

python manage.py migrate

8. Создать главный глобальный чат (опционально)

# В 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().

# Настройка по умолчанию (проектно-специфичная)
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.

# Отключить сжатие
COMMUNICATIONS_IMAGE_COMPRESSOR = None

# Использовать свою функцию
COMMUNICATIONS_IMAGE_COMPRESSOR = 'myapp.utils.compress_avatar'

# По умолчанию (backward compatibility)
COMMUNICATIONS_IMAGE_COMPRESSOR = 'common.image_utils.compress_avatar'

Сигнатура функции:

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().

# Не использовать callback (использовать fallback логику)
# COMMUNICATIONS_PARTICIPANT_RESOLVER = None  # (по умолчанию)

# Использовать проектно-специфичную логику
COMMUNICATIONS_PARTICIPANT_RESOLVER = 'employees.utils.get_chat_participants'

Сигнатура функции:

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

Включает/отключает автоматические уведомления при новых сообщениях.

# Включить (по умолчанию)
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


WebSocket Events

От клиента (action)

{"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)

{"type": "chat_message", "message": {...}}
{"type": "chat_user_typing", "user_id": 5, "chat_id": 1}
{"type": "chat_reaction_added", "message_id": 123, "emoji": "👍"}

Детали: См. consumers.py docstring


Celery Tasks (опционально)

Если используете Celery, добавьте периодическую задачу:

# 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'),  # Каждый час
    },
}

Примеры использования

Создание чата

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}'
)

Отправка сообщения

from communications.models import Message

message = Message.objects.create(
    chat=chat,
    author=request.user,
    content='Привет всем!'
)

Проверка прав доступа

# В 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


Поддержка