""" Feedback views - Server-rendered templates for feedback management """ from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.db.models import Q, Count, Avg 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.accounts.models import User from apps.core.services import AuditService from apps.organizations.models import Department, Hospital, Patient, Staff from .models import ( Feedback, FeedbackAttachment, FeedbackResponse, FeedbackStatus, FeedbackType, FeedbackCategory, ) from .forms import ( FeedbackForm, FeedbackResponseForm, FeedbackStatusChangeForm, ) @login_required def feedback_list(request): """ Feedback list view with advanced filters and pagination. Features: - Server-side pagination - Advanced filters (status, type, sentiment, category, hospital, etc.) - Search by title, message, patient name - Statistics dashboard - Export capability """ # Base queryset with optimizations queryset = Feedback.objects.select_related( 'patient', 'hospital', 'department', 'staff', 'assigned_to', 'reviewed_by', 'acknowledged_by', 'closed_by' ).filter(is_deleted=False) # Apply RBAC filters user = request.user if user.is_px_admin(): pass # See all elif user.is_hospital_admin() and user.hospital: queryset = queryset.filter(hospital=user.hospital) elif user.is_department_manager() and user.department: queryset = queryset.filter(department=user.department) elif user.hospital: queryset = queryset.filter(hospital=user.hospital) else: queryset = queryset.none() # Apply filters from request feedback_type_filter = request.GET.get('feedback_type') if feedback_type_filter: queryset = queryset.filter(feedback_type=feedback_type_filter) status_filter = request.GET.get('status') if status_filter: queryset = queryset.filter(status=status_filter) category_filter = request.GET.get('category') if category_filter: queryset = queryset.filter(category=category_filter) sentiment_filter = request.GET.get('sentiment') if sentiment_filter: queryset = queryset.filter(sentiment=sentiment_filter) priority_filter = request.GET.get('priority') if priority_filter: queryset = queryset.filter(priority=priority_filter) hospital_filter = request.GET.get('hospital') if hospital_filter: queryset = queryset.filter(hospital_id=hospital_filter) department_filter = request.GET.get('department') if department_filter: queryset = queryset.filter(department_id=department_filter) staff_filter = request.GET.get('staff') if staff_filter: queryset = queryset.filter(staff_id=staff_filter) assigned_to_filter = request.GET.get('assigned_to') if assigned_to_filter: queryset = queryset.filter(assigned_to_id=assigned_to_filter) rating_min = request.GET.get('rating_min') if rating_min: queryset = queryset.filter(rating__gte=rating_min) rating_max = request.GET.get('rating_max') if rating_max: queryset = queryset.filter(rating__lte=rating_max) is_featured = request.GET.get('is_featured') if is_featured == 'true': queryset = queryset.filter(is_featured=True) requires_follow_up = request.GET.get('requires_follow_up') if requires_follow_up == 'true': queryset = queryset.filter(requires_follow_up=True) # Search search_query = request.GET.get('search') if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(message__icontains=search_query) | Q(patient__mrn__icontains=search_query) | Q(patient__first_name__icontains=search_query) | Q(patient__last_name__icontains=search_query) | Q(contact_name__icontains=search_query) ) # Date range filters date_from = request.GET.get('date_from') if date_from: queryset = queryset.filter(created_at__gte=date_from) date_to = request.GET.get('date_to') if date_to: queryset = queryset.filter(created_at__lte=date_to) # Ordering order_by = request.GET.get('order_by', '-created_at') queryset = queryset.order_by(order_by) # Pagination page_size = int(request.GET.get('page_size', 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get('page', 1) page_obj = paginator.get_page(page_number) # Get filter options hospitals = Hospital.objects.filter(status='active') if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) departments = Department.objects.filter(status='active') if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) # Get assignable users assignable_users = User.objects.filter(is_active=True) if user.hospital: assignable_users = assignable_users.filter(hospital=user.hospital) # Statistics stats = { 'total': queryset.count(), 'submitted': queryset.filter(status=FeedbackStatus.SUBMITTED).count(), 'reviewed': queryset.filter(status=FeedbackStatus.REVIEWED).count(), 'acknowledged': queryset.filter(status=FeedbackStatus.ACKNOWLEDGED).count(), 'compliments': queryset.filter(feedback_type=FeedbackType.COMPLIMENT).count(), 'suggestions': queryset.filter(feedback_type=FeedbackType.SUGGESTION).count(), 'avg_rating': queryset.aggregate(Avg('rating'))['rating__avg'] or 0, 'positive': queryset.filter(sentiment='positive').count(), 'negative': queryset.filter(sentiment='negative').count(), } context = { 'page_obj': page_obj, 'feedbacks': page_obj.object_list, 'stats': stats, 'hospitals': hospitals, 'departments': departments, 'assignable_users': assignable_users, 'status_choices': FeedbackStatus.choices, 'type_choices': FeedbackType.choices, 'category_choices': FeedbackCategory.choices, 'filters': request.GET, } return render(request, 'feedback/feedback_list.html', context) @login_required def feedback_detail(request, pk): """ Feedback detail view with timeline, attachments, and actions. Features: - Full feedback details - Timeline of all responses - Attachments management - Workflow actions (assign, status change, add response) """ feedback = get_object_or_404( Feedback.objects.select_related( 'patient', 'hospital', 'department', 'staff', 'assigned_to', 'reviewed_by', 'acknowledged_by', 'closed_by' ).prefetch_related( 'attachments', 'responses__created_by' ), pk=pk, is_deleted=False ) # Check access user = request.user if not user.is_px_admin(): if user.is_hospital_admin() and feedback.hospital != user.hospital: messages.error(request, "You don't have permission to view this feedback.") return redirect('feedback:feedback_list') elif user.is_department_manager() and feedback.department != user.department: messages.error(request, "You don't have permission to view this feedback.") return redirect('feedback:feedback_list') elif user.hospital and feedback.hospital != user.hospital: messages.error(request, "You don't have permission to view this feedback.") return redirect('feedback:feedback_list') # Get timeline (responses) timeline = feedback.responses.all().order_by('-created_at') # Get attachments attachments = feedback.attachments.all().order_by('-created_at') # Get assignable users assignable_users = User.objects.filter(is_active=True) if feedback.hospital: assignable_users = assignable_users.filter(hospital=feedback.hospital) context = { 'feedback': feedback, 'timeline': timeline, 'attachments': attachments, 'assignable_users': assignable_users, 'status_choices': FeedbackStatus.choices, 'can_edit': user.is_px_admin() or user.is_hospital_admin(), } return render(request, 'feedback/feedback_detail.html', context) @login_required @require_http_methods(["GET", "POST"]) def feedback_create(request): """Create new feedback""" if request.method == 'POST': form = FeedbackForm(request.POST, user=request.user) if form.is_valid(): try: feedback = form.save(commit=False) # Set default sentiment if not set if not feedback.sentiment: feedback.sentiment = 'neutral' feedback.save() # Create initial response FeedbackResponse.objects.create( feedback=feedback, response_type='note', message=f"Feedback submitted by {request.user.get_full_name()}", created_by=request.user, is_internal=True ) # Log audit AuditService.log_event( event_type='feedback_created', description=f"Feedback created: {feedback.title}", user=request.user, content_object=feedback, metadata={ 'feedback_type': feedback.feedback_type, 'category': feedback.category, 'rating': feedback.rating } ) messages.success(request, f"Feedback #{feedback.id} created successfully.") return redirect('feedback:feedback_detail', pk=feedback.id) except Exception as e: messages.error(request, f"Error creating feedback: {str(e)}") else: messages.error(request, "Please correct the errors below.") else: form = FeedbackForm(user=request.user) # Get patients for selection patients = Patient.objects.filter(status='active') if request.user.hospital: patients = patients.filter(primary_hospital=request.user.hospital) context = { 'form': form, 'patients': patients, 'is_create': True, } return render(request, 'feedback/feedback_form.html', context) @login_required @require_http_methods(["GET", "POST"]) def feedback_update(request, pk): """Update existing feedback""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to edit feedback.") return redirect('feedback:feedback_detail', pk=pk) if request.method == 'POST': form = FeedbackForm(request.POST, instance=feedback, user=request.user) if form.is_valid(): try: feedback = form.save() # Create update response FeedbackResponse.objects.create( feedback=feedback, response_type='note', message=f"Feedback updated by {request.user.get_full_name()}", created_by=request.user, is_internal=True ) # Log audit AuditService.log_event( event_type='feedback_updated', description=f"Feedback updated: {feedback.title}", user=request.user, content_object=feedback ) messages.success(request, "Feedback updated successfully.") return redirect('feedback:feedback_detail', pk=feedback.id) except Exception as e: messages.error(request, f"Error updating feedback: {str(e)}") else: messages.error(request, "Please correct the errors below.") else: form = FeedbackForm(instance=feedback, user=request.user) # Get patients for selection patients = Patient.objects.filter(status='active') if request.user.hospital: patients = patients.filter(primary_hospital=request.user.hospital) context = { 'form': form, 'feedback': feedback, 'patients': patients, 'is_create': False, } return render(request, 'feedback/feedback_form.html', context) @login_required @require_http_methods(["GET", "POST"]) def feedback_delete(request, pk): """Soft delete feedback""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to delete feedback.") return redirect('feedback:feedback_detail', pk=pk) if request.method == 'POST': try: feedback.soft_delete(user=request.user) # Log audit AuditService.log_event( event_type='feedback_deleted', description=f"Feedback deleted: {feedback.title}", user=request.user, content_object=feedback ) messages.success(request, "Feedback deleted successfully.") return redirect('feedback:feedback_list') except Exception as e: messages.error(request, f"Error deleting feedback: {str(e)}") return redirect('feedback:feedback_detail', pk=pk) context = { 'feedback': feedback, } return render(request, 'feedback/feedback_delete_confirm.html', context) @login_required @require_http_methods(["POST"]) def feedback_assign(request, pk): """Assign feedback to user""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to assign feedback.") return redirect('feedback:feedback_detail', pk=pk) user_id = request.POST.get('user_id') note = request.POST.get('note', '') if not user_id: messages.error(request, "Please select a user to assign.") return redirect('feedback:feedback_detail', pk=pk) try: assignee = User.objects.get(id=user_id) feedback.assigned_to = assignee feedback.assigned_at = timezone.now() feedback.save(update_fields=['assigned_to', 'assigned_at']) # Create response message = f"Assigned to {assignee.get_full_name()}" if note: message += f"\nNote: {note}" FeedbackResponse.objects.create( feedback=feedback, response_type='assignment', message=message, created_by=request.user, is_internal=True ) # Log audit AuditService.log_event( event_type='assignment', description=f"Feedback assigned to {assignee.get_full_name()}", user=request.user, content_object=feedback ) messages.success(request, f"Feedback assigned to {assignee.get_full_name()}.") except User.DoesNotExist: messages.error(request, "User not found.") return redirect('feedback:feedback_detail', pk=pk) @login_required @require_http_methods(["POST"]) def feedback_change_status(request, pk): """Change feedback status""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to change feedback status.") return redirect('feedback:feedback_detail', pk=pk) new_status = request.POST.get('status') note = request.POST.get('note', '') if not new_status: messages.error(request, "Please select a status.") return redirect('feedback:feedback_detail', pk=pk) old_status = feedback.status feedback.status = new_status # Handle status-specific logic if new_status == FeedbackStatus.REVIEWED: feedback.reviewed_at = timezone.now() feedback.reviewed_by = request.user elif new_status == FeedbackStatus.ACKNOWLEDGED: feedback.acknowledged_at = timezone.now() feedback.acknowledged_by = request.user elif new_status == FeedbackStatus.CLOSED: feedback.closed_at = timezone.now() feedback.closed_by = request.user feedback.save() # Create response message = note or f"Status changed from {old_status} to {new_status}" FeedbackResponse.objects.create( feedback=feedback, response_type='status_change', message=message, created_by=request.user, old_status=old_status, new_status=new_status, is_internal=True ) # Log audit AuditService.log_event( event_type='status_change', description=f"Feedback status changed from {old_status} to {new_status}", user=request.user, content_object=feedback, metadata={'old_status': old_status, 'new_status': new_status} ) messages.success(request, f"Feedback status changed to {new_status}.") return redirect('feedback:feedback_detail', pk=pk) @login_required @require_http_methods(["POST"]) def feedback_add_response(request, pk): """Add response to feedback""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) response_type = request.POST.get('response_type', 'response') message = request.POST.get('message') is_internal = request.POST.get('is_internal') == 'on' if not message: messages.error(request, "Please enter a response message.") return redirect('feedback:feedback_detail', pk=pk) # Create response FeedbackResponse.objects.create( feedback=feedback, response_type=response_type, message=message, created_by=request.user, is_internal=is_internal ) messages.success(request, "Response added successfully.") return redirect('feedback:feedback_detail', pk=pk) @login_required @require_http_methods(["POST"]) def feedback_toggle_featured(request, pk): """Toggle featured status""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to feature feedback.") return redirect('feedback:feedback_detail', pk=pk) feedback.is_featured = not feedback.is_featured feedback.save(update_fields=['is_featured']) status = "featured" if feedback.is_featured else "unfeatured" messages.success(request, f"Feedback {status} successfully.") return redirect('feedback:feedback_detail', pk=pk) @login_required @require_http_methods(["POST"]) def feedback_toggle_follow_up(request, pk): """Toggle follow-up required status""" feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) # Check permission user = request.user if not (user.is_px_admin() or user.is_hospital_admin()): messages.error(request, "You don't have permission to modify feedback.") return redirect('feedback:feedback_detail', pk=pk) feedback.requires_follow_up = not feedback.requires_follow_up feedback.save(update_fields=['requires_follow_up']) status = "marked for follow-up" if feedback.requires_follow_up else "unmarked for follow-up" messages.success(request, f"Feedback {status} successfully.") return redirect('feedback:feedback_detail', pk=pk)