HH/apps/surveys/views.py

271 lines
9.0 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', 'journey_stage_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__survey_template__hospital=user.hospital)
# Others see responses for their hospital
if user.hospital:
return queryset.filter(survey_instance__survey_template__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)