HH/apps/surveys/public_views.py
2025-12-24 12:42:31 +03:00

221 lines
7.4 KiB
Python

"""
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')