HH/apps/surveys/views.py
2026-03-28 14:03:56 +03:00

273 lines
8.8 KiB
Python

"""
Surveys views and viewsets
"""
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from apps.accounts.permissions import IsPXAdminOrHospitalAdmin
from apps.core.services import AuditService
from .models import (
SurveyInstance,
SurveyQuestion,
SurveyResponse,
SurveyTemplate,
)
from .serializers import (
PublicSurveySerializer,
SurveyInstanceSerializer,
SurveyQuestionSerializer,
SurveyResponseSerializer,
SurveySubmissionSerializer,
SurveyTemplateSerializer,
)
class SurveyTemplateViewSet(viewsets.ModelViewSet):
"""
ViewSet for Survey Templates.
Permissions:
- PX Admins and Hospital Admins can manage templates
- Others can view templates
"""
queryset = SurveyTemplate.objects.all()
serializer_class = SurveyTemplateSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ["survey_type", "hospital", "is_active", "hospital__organization"]
search_fields = ["name", "name_ar", "description"]
ordering_fields = ["name", "created_at"]
ordering = ["hospital", "name"]
def get_queryset(self):
"""Filter templates based on user role"""
queryset = super().get_queryset().select_related("hospital").prefetch_related("questions")
user = self.request.user
# PX Admins see all templates
if user.is_px_admin():
return queryset
# Hospital Admins see templates for their hospital
if user.is_hospital_admin() and user.hospital:
return queryset.filter(hospital=user.hospital)
# Others see templates for their hospital
if user.hospital:
return queryset.filter(hospital=user.hospital)
return queryset.none()
class SurveyQuestionViewSet(viewsets.ModelViewSet):
"""
ViewSet for Survey Questions.
Permissions:
- PX Admins and Hospital Admins can manage questions
"""
queryset = SurveyQuestion.objects.all()
serializer_class = SurveyQuestionSerializer
permission_classes = [IsAuthenticated, IsPXAdminOrHospitalAdmin]
filterset_fields = ["survey_template", "question_type", "is_required"]
search_fields = ["text", "text_ar"]
ordering_fields = ["order", "created_at"]
ordering = ["survey_template", "order"]
def get_queryset(self):
queryset = super().get_queryset().select_related("survey_template")
user = self.request.user
# PX Admins see all questions
if user.is_px_admin():
return queryset
# Hospital Admins see questions for their hospital
if user.is_hospital_admin() and user.hospital:
return queryset.filter(survey_template__hospital=user.hospital)
return queryset.none()
class SurveyInstanceViewSet(viewsets.ModelViewSet):
"""
ViewSet for Survey Instances.
Permissions:
- All authenticated users can view survey instances
- PX Admins and Hospital Admins can create/manage instances
"""
queryset = SurveyInstance.objects.all()
serializer_class = SurveyInstanceSerializer
permission_classes = [IsAuthenticated]
filterset_fields = [
"survey_template",
"patient",
"status",
"delivery_channel",
"is_negative",
"journey_instance",
"survey_template__hospital__organization",
]
search_fields = ["patient__mrn", "patient__first_name", "patient__last_name", "encounter_id"]
ordering_fields = ["sent_at", "completed_at", "created_at"]
ordering = ["-created_at"]
def get_queryset(self):
"""Filter survey instances based on user role"""
queryset = (
super()
.get_queryset()
.select_related("survey_template", "patient", "journey_instance")
.prefetch_related("responses")
)
user = self.request.user
# PX Admins see all survey instances
if user.is_px_admin():
return queryset
# Hospital Admins see instances for their hospital
if user.is_hospital_admin() and user.hospital:
return queryset.filter(survey_template__hospital=user.hospital)
# Others see instances for their hospital
if user.hospital:
return queryset.filter(survey_template__hospital=user.hospital)
return queryset.none()
@action(detail=True, methods=["post"])
def resend(self, request, pk=None):
"""Resend survey invitation"""
survey_instance = self.get_object()
if survey_instance.status == "completed":
return Response({"error": "Cannot resend completed survey"}, status=status.HTTP_400_BAD_REQUEST)
# Queue survey send task
from apps.surveys.tasks import send_survey_reminder
send_survey_reminder.delay(str(survey_instance.id))
return Response({"message": "Survey invitation queued for resend"})
class SurveyResponseViewSet(viewsets.ReadOnlyModelViewSet):
"""
ViewSet for Survey Responses (read-only).
Responses are created via the public survey submission endpoint.
"""
queryset = SurveyResponse.objects.all()
serializer_class = SurveyResponseSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ["survey_instance", "question"]
ordering = ["survey_instance", "question__order"]
def get_queryset(self):
queryset = super().get_queryset().select_related("survey_instance", "question")
user = self.request.user
# PX Admins see all responses
if user.is_px_admin():
return queryset
# Hospital Admins see responses for their hospital
if user.is_hospital_admin() and user.hospital:
return queryset.filter(survey_instance__hospital=user.hospital)
# Others see responses for their hospital
if user.hospital:
return queryset.filter(survey_instance__hospital=user.hospital)
return queryset.none()
class PublicSurveyViewSet(viewsets.GenericViewSet):
"""
Public survey viewset for patient-facing survey access.
No authentication required - uses secure token.
"""
permission_classes = [AllowAny]
def retrieve(self, request, token=None):
"""
Get survey by access token.
GET /api/surveys/public/{token}/
"""
survey_instance = get_object_or_404(
SurveyInstance.objects.select_related("survey_template").prefetch_related("survey_template__questions"),
access_token=token,
)
# Check if token expired
if survey_instance.token_expires_at and survey_instance.token_expires_at < timezone.now():
return Response({"error": "Survey link has expired"}, status=status.HTTP_410_GONE)
# Check if already completed
if survey_instance.status == "completed":
return Response(
{"error": "Survey already completed", "completed_at": survey_instance.completed_at},
status=status.HTTP_410_GONE,
)
# Mark as opened if first time
if not survey_instance.opened_at:
survey_instance.opened_at = timezone.now()
survey_instance.save(update_fields=["opened_at"])
serializer = PublicSurveySerializer(survey_instance)
return Response(serializer.data)
@action(detail=False, methods=["post"], url_path="(?P<token>[^/.]+)/submit")
def submit(self, request, token=None):
"""
Submit survey responses.
POST /api/surveys/public/{token}/submit/
Body:
{
"responses": [
{"question_id": "uuid", "numeric_value": 5},
{"question_id": "uuid", "text_value": "Great service!"}
]
}
"""
survey_instance = get_object_or_404(SurveyInstance, access_token=token)
# Check if token expired
if survey_instance.token_expires_at and survey_instance.token_expires_at < timezone.now():
return Response({"error": "Survey link has expired"}, status=status.HTTP_410_GONE)
# Check if already completed
if survey_instance.status == "completed":
return Response({"error": "Survey already completed"}, status=status.HTTP_400_BAD_REQUEST)
# Validate and create responses
serializer = SurveySubmissionSerializer(data=request.data, context={"survey_instance": survey_instance})
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
{
"message": "Survey submitted successfully",
"score": float(survey_instance.total_score) if survey_instance.total_score else None,
},
status=status.HTTP_201_CREATED,
)