from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.db.models import Count, Q from django.utils import timezone from apps.standards.models import ( StandardSource, StandardCategory, Standard, StandardCompliance, StandardAttachment ) from apps.organizations.models import Department from apps.standards.forms import ( StandardSourceForm, StandardCategoryForm, StandardForm, StandardComplianceForm, StandardAttachmentForm ) # ==================== API ViewSets ==================== class StandardSourceViewSet(viewsets.ModelViewSet): queryset = StandardSource.objects.all() filterset_fields = ['is_active'] search_fields = ['name', 'name_ar', 'code'] ordering = ['name'] def get_serializer_class(self): from apps.standards.serializers import StandardSourceSerializer return StandardSourceSerializer class StandardCategoryViewSet(viewsets.ModelViewSet): queryset = StandardCategory.objects.all() filterset_fields = ['is_active'] search_fields = ['name', 'name_ar'] ordering = ['order', 'name'] def get_serializer_class(self): from apps.standards.serializers import StandardCategorySerializer return StandardCategorySerializer class StandardViewSet(viewsets.ModelViewSet): queryset = Standard.objects.all() filterset_fields = ['source', 'category', 'department', 'is_active'] search_fields = ['code', 'title', 'title_ar', 'description'] ordering = ['source', 'category', 'code'] def get_serializer_class(self): from apps.standards.serializers import StandardSerializer return StandardSerializer class StandardComplianceViewSet(viewsets.ModelViewSet): queryset = StandardCompliance.objects.all() filterset_fields = ['department', 'standard', 'status'] search_fields = ['department__name', 'standard__code', 'notes'] ordering = ['-created_at'] def get_serializer_class(self): from apps.standards.serializers import StandardComplianceSerializer return StandardComplianceSerializer class StandardAttachmentViewSet(viewsets.ModelViewSet): queryset = StandardAttachment.objects.all() filterset_fields = ['compliance'] search_fields = ['filename', 'description'] ordering = ['-uploaded_at'] def get_serializer_class(self): from apps.standards.serializers import StandardAttachmentSerializer return StandardAttachmentSerializer # ==================== UI Views ==================== @login_required def standards_dashboard(request): """Standards dashboard with statistics""" # Get current hospital from tenant_hospital (set by middleware) hospital = getattr(request, 'tenant_hospital', None) if not hospital: return render(request, 'core/no_hospital_assigned.html') departments = hospital.departments.filter(status='active') # Get compliance statistics compliance_records = StandardCompliance.objects.filter( department__hospital=hospital ) stats = { 'total_standards': Standard.objects.filter(is_active=True).count(), 'total_departments': departments.count(), 'met': compliance_records.filter(status='met').count(), 'partially_met': compliance_records.filter(status='partially_met').count(), 'not_met': compliance_records.filter(status='not_met').count(), 'not_assessed': compliance_records.filter(status='not_assessed').count(), } # Recent compliance updates recent_updates = compliance_records.order_by('-updated_at')[:10] context = { 'hospital': hospital, 'departments': departments, 'stats': stats, 'recent_updates': recent_updates, } return render(request, 'standards/dashboard.html', context) @login_required def department_standards_view(request, pk): """View all standards for a department""" department = get_object_or_404(Department, pk=pk) # Check if user is PX admin hospital = getattr(request, 'tenant_hospital', None) is_px_admin = request.user.is_superuser or ( hasattr(request.user, 'hospital_user') and request.user.hospital_user.hospital == hospital and request.user.hospital_user.role == 'px_admin' ) if hospital else False # Get all active standards (both department-specific and general) department_standards = Standard.objects.filter(is_active=True).filter( Q(department=department) | Q(department__isnull=True) ).order_by('source', 'category', 'code') # Get compliance status for each standard standards_data = [] for standard in department_standards: compliance = StandardCompliance.objects.filter( department=department, standard=standard ).first() standards_data.append({ 'standard': standard, 'compliance': compliance, 'attachment_count': compliance.attachments.count() if compliance else 0, }) context = { 'department': department, 'standards_data': standards_data, 'is_px_admin': is_px_admin, } return render(request, 'standards/department_standards.html', context) @login_required def standard_detail(request, pk): """View standard details and compliance history""" standard = get_object_or_404(Standard, pk=pk) # Get compliance records for all departments compliance_records = StandardCompliance.objects.filter( standard=standard ).select_related('department', 'assessor').order_by('-created_at') context = { 'standard': standard, 'compliance_records': compliance_records, } return render(request, 'standards/standard_detail.html', context) @login_required def standard_compliance_update(request, compliance_id): """Update compliance status""" compliance = get_object_or_404(StandardCompliance, pk=compliance_id) if request.method == 'POST': form = StandardComplianceForm(request.POST, instance=compliance) if form.is_valid(): form.save() return redirect('standards:department_standards', pk=compliance.department.pk) else: form = StandardComplianceForm(instance=compliance) context = { 'compliance': compliance, 'form': form, } return render(request, 'standards/compliance_form.html', context) @login_required def standard_attachment_upload(request, compliance_id): """Upload attachment for compliance""" compliance = get_object_or_404(StandardCompliance, pk=compliance_id) if request.method == 'POST': form = StandardAttachmentForm(request.POST, request.FILES) if form.is_valid(): attachment = form.save(commit=False) attachment.compliance = compliance attachment.uploaded_by = request.user attachment.filename = request.FILES['file'].name attachment.save() return redirect('standards:standard_compliance_update', compliance_id=compliance.pk) else: form = StandardAttachmentForm() context = { 'compliance': compliance, 'form': form, } return render(request, 'standards/attachment_upload.html', context) @login_required def standards_search(request): """Search standards""" query = request.GET.get('q', '') source_filter = request.GET.get('source', '') category_filter = request.GET.get('category', '') status_filter = request.GET.get('status', '') # Get current hospital from tenant_hospital (set by middleware) hospital = getattr(request, 'tenant_hospital', None) if not hospital: return render(request, 'core/no_hospital_assigned.html') # Build queryset standards = Standard.objects.filter(is_active=True) if query: standards = standards.filter( Q(code__icontains=query) | Q(title__icontains=query) | Q(title_ar__icontains=query) | Q(description__icontains=query) ) if source_filter: standards = standards.filter(source_id=source_filter) if category_filter: standards = standards.filter(category_id=category_filter) standards = standards.select_related('source', 'category').order_by('source', 'category', 'code') # Get filters sources = StandardSource.objects.filter(is_active=True) categories = StandardCategory.objects.filter(is_active=True) context = { 'hospital': hospital, 'standards': standards, 'query': query, 'source_filter': source_filter, 'category_filter': category_filter, 'sources': sources, 'categories': categories, } return render(request, 'standards/search.html', context) @login_required def standard_create(request, department_id=None): """Create a new standard (PX Admin only)""" # Check if user is PX admin hospital = getattr(request, 'tenant_hospital', None) if not hospital: return render(request, 'core/no_hospital_assigned.html') is_px_admin = request.user.is_superuser or ( hasattr(request.user, 'hospital_user') and request.user.hospital_user.hospital == hospital and request.user.hospital_user.role == 'px_admin' ) if not is_px_admin: from django.contrib import messages messages.error(request, 'You do not have permission to create standards.') if department_id: return redirect('standards:department_standards', pk=department_id) return redirect('standards:dashboard') if request.method == 'POST': form = StandardForm(request.POST) if form.is_valid(): standard = form.save(commit=False) standard.save() from django.contrib import messages messages.success(request, 'Standard created successfully.') if department_id: return redirect('standards:department_standards', pk=department_id) return redirect('standards:dashboard') else: form = StandardForm() # If department_id is provided, pre-select that department if department_id: from apps.organizations.models import Department department = Department.objects.filter(pk=department_id).first() if department: form.fields['department'].initial = department # Get all departments for the hospital departments = hospital.departments.filter(status='active') context = { 'form': form, 'department_id': department_id, 'departments': departments, 'hospital': hospital, } return render(request, 'standards/standard_form.html', context) @login_required def create_compliance_ajax(request): """Create compliance record via AJAX""" if request.method != 'POST': return JsonResponse({'success': False, 'error': 'Invalid request method'}) department_id = request.POST.get('department_id') standard_id = request.POST.get('standard_id') if not department_id or not standard_id: return JsonResponse({'success': False, 'error': 'Missing required fields'}) try: department = Department.objects.get(pk=department_id) standard = Standard.objects.get(pk=standard_id) # Check if compliance already exists compliance, created = StandardCompliance.objects.get_or_create( department=department, standard=standard, defaults={ 'assessor': request.user, 'last_assessed_date': timezone.now().date(), } ) return JsonResponse({ 'success': True, 'compliance_id': compliance.id, 'status': compliance.status, 'created': created, }) except Exception as e: return JsonResponse({'success': False, 'error': str(e)}) @login_required def update_compliance_ajax(request): """Update compliance record via AJAX""" if request.method != 'POST': return JsonResponse({'success': False, 'error': 'Invalid request method'}) compliance_id = request.POST.get('compliance_id') status = request.POST.get('status') notes = request.POST.get('notes', '') evidence_summary = request.POST.get('evidence_summary', '') if not compliance_id or not status: return JsonResponse({'success': False, 'error': 'Missing required fields'}) try: compliance = StandardCompliance.objects.get(pk=compliance_id) compliance.status = status compliance.notes = notes compliance.evidence_summary = evidence_summary compliance.assessor = request.user compliance.last_assessed_date = timezone.now().date() compliance.save() return JsonResponse({ 'success': True, 'status': compliance.status, 'status_display': compliance.get_status_display(), }) except Exception as e: return JsonResponse({'success': False, 'error': str(e)}) @login_required def get_compliance_status(request, department_id, standard_id): """API endpoint to get compliance status""" compliance = StandardCompliance.objects.filter( department_id=department_id, standard_id=standard_id ).first() if compliance: data = { 'status': compliance.status, 'last_assessed_date': compliance.last_assessed_date, 'assessor': compliance.assessor.get_full_name() if compliance.assessor else None, 'notes': compliance.notes, 'attachment_count': compliance.attachments.count(), } else: data = { 'status': 'not_assessed', 'last_assessed_date': None, 'assessor': None, 'notes': '', 'attachment_count': 0, } return JsonResponse(data)