from django.db import transaction from django.db.models.signals import post_save from django.dispatch import receiver from asgiref.sync import async_to_sync from channels.layers import get_channel_layer from submissions.models import Submission, SubmissionFile, ImageCrop, Scope from submissions.schemas import SubmissionOut, SubmissionFileOut, ImageCropOut @receiver(post_save, sender=Submission) def broadcast_submission_update(sender, instance, created, **kwargs): if not instance.user: return user_group_name = f"submissions_user_{instance.user.id}" submission_data = SubmissionOut.from_orm(instance).dict() async_to_sync(get_channel_layer().group_send)( user_group_name, { "type": "submission.updated", "submission": submission_data, }, ) @receiver(post_save, sender=SubmissionFile) def broadcast_submission_file_update(sender, instance, created, **kwargs): submission = instance.submission if not submission.user: return user_group_name = f"submissions_user_{submission.user.id}" file_data = SubmissionFileOut.from_orm(instance).dict() async_to_sync(get_channel_layer().group_send)( user_group_name, { "type": "submission.file.updated", "file": file_data, }, ) @receiver(post_save, sender=ImageCrop) def broadcast_image_crop_update(sender, instance, created, **kwargs): submission = instance.submission_file.submission if not submission.user: return user_group_name = f"submissions_user_{submission.user.id}" crop_data = ImageCropOut.from_orm(instance).dict() async_to_sync(get_channel_layer().group_send)( user_group_name, { "type": "submission.crop.updated", "crop": crop_data, }, ) def broadcast_scope_updates(changes: dict[Scope, set[int]], /): """Tell users their scope changed (closed/opened, (un)assigned), once the current transaction commits, so the frontend can move them to the right match without a refresh. Args: changes: Changed scopes, each with every user id that was or now is assigned to it. """ def send(): channel_layer = get_channel_layer() for scope, user_ids in changes.items(): assigned_ids = set(scope.assigned_users.values_list("id", flat=True)) for user_id in user_ids: async_to_sync(channel_layer.group_send)( f"submissions_user_{user_id}", { "type": "scope.updated", "scope": { "id": str(scope.id), "is_active": scope.is_active, "assigned": user_id in assigned_ids, }, }, ) if changes: # robust: a websocket hiccup only gets logged, it mustn't fail the # (already committed) sync or admin save transaction.on_commit(send, robust=True)