Corp/backend/scheduling/realtime.py

105 lines
3.1 KiB
Python

import logging
from collections.abc import Iterable
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.db import models, transaction
from schedule.models import Calendar, CalendarRelation, Event, EventRelation
logger = logging.getLogger(__name__)
def get_calendar_recipient_ids(
calendar: Calendar | None,
*,
event: Event | None = None,
) -> set[int]:
if calendar is None or not calendar.pk:
return set()
User = get_user_model()
user_content_type = ContentType.objects.get_for_model(User)
recipient_ids = set(
CalendarRelation.objects.filter(
calendar=calendar,
content_type=user_content_type,
object_id__isnull=False,
).values_list("object_id", flat=True)
)
if event is not None and event.pk:
recipient_ids.update(
EventRelation.objects.filter(
event=event,
content_type=user_content_type,
object_id__isnull=False,
).values_list("object_id", flat=True)
)
# Администраторы могут работать в scope=all без CalendarRelation.
recipient_ids.update(
User.objects.filter(is_active=True).filter(
models.Q(is_staff=True) | models.Q(is_superuser=True)
).values_list("id", flat=True)
)
return set(
User.objects.filter(id__in=recipient_ids, is_active=True).values_list(
"id", flat=True
)
)
def send_calendar_update(
*,
action: str,
entity: str,
calendar_id: int,
recipient_ids: Iterable[int],
event_id: int | None = None,
changed_fields: Iterable[str] | None = None,
extra: dict | None = None,
) -> None:
recipients = {int(user_id) for user_id in recipient_ids if user_id}
if not recipients or not calendar_id:
return
payload = {
"action": action,
"entity": entity,
"calendar_id": calendar_id,
"event_id": event_id,
"changed_fields": list(changed_fields or []),
}
if extra:
payload.update(extra)
def _send() -> None:
channel_layer = get_channel_layer()
if channel_layer is None:
return
message = {
"type": "calendar_update",
"event": f"{entity}_{action}",
"data": payload,
}
for user_id in recipients:
try:
async_to_sync(channel_layer.group_send)(f"user_{user_id}", message)
except Exception as error:
logger.exception(
"Calendar WebSocket update failed",
extra={
"event": "calendar.websocket_update_failed",
"user_id": user_id,
"calendar_id": calendar_id,
"schedule_event_id": event_id,
"calendar_action": action,
"error_type": type(error).__name__,
},
)
transaction.on_commit(_send)