- Add Django Channels 4 + channels-redis to requirements and INSTALLED_APPS
- Upgrade ASGI config to ProtocolTypeRouter with JWT-authenticated WebSocket routing
- Add JWTAuthMiddleware for WebSocket token auth via query param
- Add CallSession, CallParticipant, CallEvent models with migration 0004
- MeetingCallConsumer: join/leave, P2P SDP/ICE relay via per-user groups,
instructor mute/unmute/kick with DB audit and WS broadcast
- REST endpoints: GET/POST/DELETE /meetings/{id}/call/ (session lifecycle),
POST /moderate/ (mute/unmute/kick), POST /screen-share/, GET /events/ (audit log)
- IsMeetingModerator permission (accepts training:signoff or meeting:moderate)
- Services: get_or_create_call_session, end_call_session, apply_moderation_action,
toggle_screen_share with full CallEvent audit trail
- Integration tests covering REST lifecycle, moderation, and WebSocket signaling
Co-Authored-By: Paperclip <noreply@paperclip.ing>
101 lines
3.6 KiB
Python
101 lines
3.6 KiB
Python
from rest_framework import serializers
|
|
|
|
from .models import (
|
|
AttendanceStatus,
|
|
CallEvent,
|
|
CallParticipant,
|
|
CallSession,
|
|
EmployeeAttendance,
|
|
Meeting,
|
|
MeetingParticipant,
|
|
SignoffDecision,
|
|
TrainerSignoff,
|
|
TrainingRecord,
|
|
)
|
|
|
|
|
|
class TrainingRecordSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TrainingRecord
|
|
fields = ["id", "enrollment", "completion_mode", "status", "online_passed_at", "completed_at", "created_at", "updated_at"]
|
|
read_only_fields = fields
|
|
|
|
|
|
class TrainerSignoffSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TrainerSignoff
|
|
fields = ["id", "training_record", "trainer", "decision", "notes", "signed_off_at"]
|
|
read_only_fields = ["id", "trainer", "signed_off_at"]
|
|
|
|
|
|
class SignoffRequestSerializer(serializers.Serializer):
|
|
decision = serializers.ChoiceField(choices=SignoffDecision.choices)
|
|
notes = serializers.CharField(allow_blank=True, default="")
|
|
|
|
|
|
class MeetingSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Meeting
|
|
fields = ["id", "title", "starts_at", "created_at", "updated_at"]
|
|
read_only_fields = ["id", "created_at", "updated_at"]
|
|
|
|
|
|
class MeetingParticipantSerializer(serializers.ModelSerializer):
|
|
attendance_session_id = serializers.UUIDField(source="attendance_session.id", read_only=True)
|
|
|
|
class Meta:
|
|
model = MeetingParticipant
|
|
fields = ["id", "meeting", "user", "attendance_status", "attendance_recorded_at", "attendance_recorded_by", "attendance_session_id", "created_at"]
|
|
read_only_fields = ["id", "attendance_status", "attendance_recorded_at", "attendance_recorded_by", "attendance_session_id", "created_at"]
|
|
|
|
|
|
class EmployeeAttendanceSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = EmployeeAttendance
|
|
fields = ["id", "user", "attendance_date", "status", "meeting", "source", "updated_by", "updated_at", "created_at"]
|
|
read_only_fields = ["id", "updated_at", "created_at"]
|
|
|
|
|
|
class MeetingAttendanceRequestSerializer(serializers.Serializer):
|
|
user_id = serializers.UUIDField()
|
|
status = serializers.ChoiceField(choices=AttendanceStatus.choices)
|
|
|
|
|
|
class CallParticipantSerializer(serializers.ModelSerializer):
|
|
user_id = serializers.UUIDField(source="user.id", read_only=True)
|
|
user_name = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = CallParticipant
|
|
fields = ["id", "user_id", "user_name", "status", "is_muted", "screen_share_active", "joined_at", "left_at"]
|
|
read_only_fields = fields
|
|
|
|
def get_user_name(self, obj):
|
|
return obj.user.get_full_name() or str(obj.user)
|
|
|
|
|
|
class CallSessionSerializer(serializers.ModelSerializer):
|
|
active_participants = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = CallSession
|
|
fields = ["id", "meeting", "status", "started_by", "started_at", "ended_at", "active_participants"]
|
|
read_only_fields = fields
|
|
|
|
def get_active_participants(self, obj):
|
|
from .models import CallParticipantStatus
|
|
qs = obj.call_participants.filter(status=CallParticipantStatus.JOINED).select_related("user")
|
|
return CallParticipantSerializer(qs, many=True).data
|
|
|
|
|
|
class CallEventSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = CallEvent
|
|
fields = ["id", "event_type", "actor", "target_user", "metadata", "occurred_at"]
|
|
read_only_fields = fields
|
|
|
|
|
|
class CallModerationRequestSerializer(serializers.Serializer):
|
|
action = serializers.ChoiceField(choices=["mute", "unmute", "kick"])
|
|
user_id = serializers.UUIDField()
|