feat(websocket): frontend websocket

This commit is contained in:
2026-05-25 13:01:22 +02:00
parent b64161b371
commit d7bdb85fb0
9 changed files with 343 additions and 76 deletions
+15
View File
@@ -1,4 +1,17 @@
from django.apps import AppConfig
from django.core.serializers.json import DjangoJSONEncoder
from channels_redis.serializers import JSONSerializer, registry
import json
class CJSONSerializer(JSONSerializer):
"""Override `JSONSerializer` to use `DjangoJSONEncoder`."""
def as_bytes(self, message, *args, **kwargs): # noqa: ANN001 ANN002 ANN003 ANN201
message = json.dumps(message, *args, **kwargs, cls=DjangoJSONEncoder)
return message.encode("utf-8")
class SubmissionsConfig(AppConfig):
@@ -6,4 +19,6 @@ class SubmissionsConfig(AppConfig):
name = "submissions"
def ready(self):
registry.register_serializer("cjson", CJSONSerializer)
import submissions.signals # noqa
@@ -1,11 +1,7 @@
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from django.contrib.auth.models import AnonymousUser
from submissions.models import Submission, SubmissionFile, ImageCrop
from submissions.schemas import SubmissionOut, SubmissionFileOut, ImageCropOut
class SubmissionsConsumer(AsyncWebsocketConsumer):
async def connect(self):
+3 -10
View File
@@ -1,7 +1,6 @@
from datetime import datetime
from uuid import UUID
from ninja import Schema
from pydantic import BaseModel, ConfigDict
class ImageCropIn(Schema):
@@ -12,9 +11,7 @@ class ImageCropIn(Schema):
label: str | None = None
class ImageCropOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
class ImageCropOut(Schema):
id: UUID
x1: int
y1: int
@@ -26,9 +23,7 @@ class ImageCropOut(BaseModel):
created_at: datetime
class SubmissionFileOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
class SubmissionFileOut(Schema):
id: UUID
original_filename: str
content_type: str
@@ -39,9 +34,7 @@ class SubmissionFileOut(BaseModel):
crops: list[ImageCropOut] = []
class SubmissionOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
class SubmissionOut(Schema):
id: UUID
game_id: int | None = None
notes: str | None = None
+6 -10
View File
@@ -1,4 +1,3 @@
import json
from django.db.models.signals import post_save
from django.dispatch import receiver
from asgiref.sync import async_to_sync
@@ -17,15 +16,14 @@ def broadcast_submission_update(sender, instance, created, **kwargs):
return
user_group_name = f"submissions_user_{instance.user.id}"
schema = SubmissionOut.model_validate(instance)
submission_data = schema.model_dump(mode="json")
submission_data = SubmissionOut.from_orm(instance).dict()
async_to_sync(channel_layer.group_send)(
user_group_name,
{
"type": "submission.updated",
"submission": submission_data,
}
},
)
@@ -36,15 +34,14 @@ def broadcast_submission_file_update(sender, instance, created, **kwargs):
return
user_group_name = f"submissions_user_{submission.user.id}"
schema = SubmissionFileOut.model_validate(instance)
file_data = schema.model_dump(mode="json")
file_data = SubmissionFileOut.from_orm(instance).dict()
async_to_sync(channel_layer.group_send)(
user_group_name,
{
"type": "submission.file.updated",
"file": file_data,
}
},
)
@@ -55,13 +52,12 @@ def broadcast_image_crop_update(sender, instance, created, **kwargs):
return
user_group_name = f"submissions_user_{submission.user.id}"
schema = ImageCropOut.model_validate(instance)
crop_data = schema.model_dump(mode="json")
crop_data = ImageCropOut.from_orm(instance).dict()
async_to_sync(channel_layer.group_send)(
user_group_name,
{
"type": "submission.crop.updated",
"crop": crop_data,
}
},
)
+16 -13
View File
@@ -5,25 +5,28 @@ from PIL import Image
import pytesseract
def _process_ocr_file(file) -> bool:
if file.ocr_status != OCRStatus.TO_PROCESS:
def _process_ocr_file(obj, file, crop: ImageCrop | None = None) -> bool:
if obj.ocr_status != OCRStatus.TO_PROCESS:
return False
file.ocr_status = OCRStatus.PROCESSING
file.save(update_fields=["ocr_status"])
obj.ocr_status = OCRStatus.PROCESSING
obj.save(update_fields=["ocr_status"])
try:
file.ocr_raw_data = pytesseract.image_to_string(Image.open(file.file))
image = Image.open(file)
except Exception:
file.ocr_status = OCRStatus.ERROR
file.save(update_fields=["ocr_status"])
if crop:
image = image.crop((crop.x1, crop.y1, crop.x2, crop.y2))
return False
obj.ocr_raw_data = pytesseract.image_to_string(image)
obj.ocr_status = OCRStatus.PROCESSED
except Exception as e:
obj.ocr_status = OCRStatus.ERROR
raise e
finally:
file.ocr_status = OCRStatus.PROCESSED
file.save(update_fields=["ocr_status", "ocr_raw_data"])
obj.save(update_fields=["ocr_status", "ocr_raw_data"])
return True
@@ -42,7 +45,7 @@ def process_submission_file(file_id: str):
if not file:
return
_process_ocr_file(file)
_process_ocr_file(file, file.file)
process_crops.enqueue(file_id)
@@ -65,4 +68,4 @@ def process_crop(crop_id: str):
if not crop:
return
_process_ocr_file(crop)
_process_ocr_file(crop, crop.submission_file.file, crop=crop)