""" Public survey views - Token-based survey forms (no login required) """ from django.contrib import messages from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone from django.views.decorators.http import require_http_methods from apps.core.services import AuditService from .models import SurveyInstance, SurveyQuestion, SurveyResponse @require_http_methods(["GET", "POST"]) def survey_form(request, token): """ Public survey form - accessible via secure token link. Features: - No login required - Token-based access - Mobile-first responsive design - Bilingual support (AR/EN) - Progress indicator - Question type rendering - Form validation """ # Get survey instance by token try: survey = SurveyInstance.objects.select_related( 'survey_template', 'patient', 'journey_instance', 'journey_stage_instance' ).prefetch_related( 'survey_template__questions' ).get( access_token=token, status__in=['pending', 'sent'], token_expires_at__gt=timezone.now() ) except SurveyInstance.DoesNotExist: return render(request, 'surveys/invalid_token.html', { 'error': 'invalid_or_expired' }) # Mark as opened if first time if not survey.opened_at: survey.opened_at = timezone.now() survey.status = 'in_progress' survey.save(update_fields=['opened_at', 'status']) # Get questions questions = survey.survey_template.questions.filter( is_required=True ).order_by('order') | survey.survey_template.questions.filter( is_required=False ).order_by('order') if request.method == 'POST': # Process survey responses language = request.POST.get('language', 'en') errors = [] responses_data = [] # Validate and collect responses for question in questions: field_name = f'question_{question.id}' # Check if required if question.is_required and not request.POST.get(field_name): errors.append(f"Question {question.order + 1} is required") continue # Get response value based on question type if question.question_type in ['rating', 'likert']: numeric_value = request.POST.get(field_name) if numeric_value: responses_data.append({ 'question': question, 'numeric_value': float(numeric_value), 'text_value': '', 'choice_value': '' }) elif question.question_type == 'nps': numeric_value = request.POST.get(field_name) if numeric_value: responses_data.append({ 'question': question, 'numeric_value': float(numeric_value), 'text_value': '', 'choice_value': '' }) elif question.question_type == 'yes_no': choice_value = request.POST.get(field_name) if choice_value: # Convert yes/no to numeric for scoring numeric_value = 5.0 if choice_value == 'yes' else 1.0 responses_data.append({ 'question': question, 'numeric_value': numeric_value, 'text_value': '', 'choice_value': choice_value }) elif question.question_type == 'multiple_choice': choice_value = request.POST.get(field_name) if choice_value: responses_data.append({ 'question': question, 'numeric_value': None, 'text_value': '', 'choice_value': choice_value }) elif question.question_type in ['text', 'textarea']: text_value = request.POST.get(field_name, '') if text_value: responses_data.append({ 'question': question, 'numeric_value': None, 'text_value': text_value, 'choice_value': '' }) # If validation errors, show form again if errors: context = { 'survey': survey, 'questions': questions, 'errors': errors, 'language': language, } return render(request, 'surveys/public_form.html', context) # Save responses for response_data in responses_data: SurveyResponse.objects.update_or_create( survey_instance=survey, question=response_data['question'], defaults={ 'numeric_value': response_data['numeric_value'], 'text_value': response_data['text_value'], 'choice_value': response_data['choice_value'], } ) # Update survey status survey.status = 'completed' survey.completed_at = timezone.now() survey.save(update_fields=['status', 'completed_at']) # Calculate score score = survey.calculate_score() # Log completion AuditService.log( event_type='survey_completed', description=f"Survey completed: {survey.survey_template.name}", user=None, content_object=survey, metadata={ 'score': float(score) if score else None, 'is_negative': survey.is_negative, 'response_count': len(responses_data) } ) # Create PX action if negative if survey.is_negative: from apps.surveys.tasks import create_action_from_negative_survey create_action_from_negative_survey.delay(str(survey.id)) # Redirect to thank you page return redirect('surveys:thank_you', token=token) # GET request - show form # Determine language from query param or browser language = request.GET.get('lang', 'en') context = { 'survey': survey, 'questions': questions, 'language': language, 'total_questions': questions.count(), } return render(request, 'surveys/public_form.html', context) def thank_you(request, token): """Thank you page after survey completion""" try: survey = SurveyInstance.objects.select_related( 'survey_template', 'patient' ).get( access_token=token, status='completed' ) except SurveyInstance.DoesNotExist: return render(request, 'surveys/invalid_token.html', { 'error': 'not_found' }) language = request.GET.get('lang', 'en') context = { 'survey': survey, 'language': language, } return render(request, 'surveys/thank_you.html', context) def invalid_token(request): """Invalid or expired token page""" return render(request, 'surveys/invalid_token.html')