273 lines
8.8 KiB
Python
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,
|
|
)
|