HH/apps/core/views.py
2026-04-19 10:53:12 +03:00

427 lines
14 KiB
Python

"""
Core views - Health check and utility views
"""
from django.contrib.auth.decorators import login_required
from django.db import connection
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_GET, require_POST
@never_cache
@require_GET
def health_check(request):
"""
Health check endpoint for monitoring and load balancers.
Returns JSON with status of various services.
"""
health_status = {"status": "ok", "services": {}}
# Check database connection
try:
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
health_status["services"]["database"] = "ok"
except Exception as e:
health_status["status"] = "error"
health_status["services"]["database"] = f"error: {str(e)}"
# Check Redis/Celery (optional - don't fail if not available)
try:
from django_celery_beat.models import PeriodicTask
PeriodicTask.objects.count()
health_status["services"]["celery_beat"] = "ok"
except Exception:
health_status["services"]["celery_beat"] = "not_configured"
# Return appropriate status code
status_code = 200 if health_status["status"] == "ok" else 503
return JsonResponse(health_status, status=status_code)
@login_required
def select_hospital(request):
"""
Hospital selection page for PX Admins.
Allows PX Admins to switch between hospitals.
Stores selected hospital in session.
"""
# Only PX Admins should access this page
if not request.user.is_px_admin():
return redirect("dashboard:dashboard")
from apps.organizations.models import Hospital
hospitals = Hospital.objects.all().order_by("name")
# Handle hospital selection
if request.method == "POST":
hospital_id = request.POST.get("hospital_id")
if hospital_id:
try:
hospital = Hospital.objects.get(id=hospital_id)
request.session["selected_hospital_id"] = str(hospital.id)
# Redirect to referring page or dashboard
next_url = request.POST.get("next", request.GET.get("next", "/"))
return redirect(next_url)
except Hospital.DoesNotExist:
pass
context = {
"hospitals": hospitals,
"selected_hospital_id": request.session.get("selected_hospital_id"),
"next": request.GET.get("next", "/"),
}
return render(request, "core/select_hospital.html", context)
@login_required
@require_POST
def switch_hospital(request):
"""
AJAX endpoint to switch hospitals for PX Admins.
Stores selected hospital in session and returns JSON response.
"""
# Only PX Admins can switch hospitals
if not request.user.is_px_admin():
return JsonResponse(
{"success": False, "error": "Permission denied. Only PX Admins can switch hospitals."}, status=403
)
from apps.organizations.models import Hospital
hospital_id = request.POST.get("hospital_id")
if not hospital_id:
return JsonResponse({"success": False, "error": "Hospital ID is required"}, status=400)
try:
hospital = Hospital.objects.get(id=hospital_id)
request.session["selected_hospital_id"] = str(hospital.id)
return JsonResponse(
{
"success": True,
"hospital": {
"id": str(hospital.id),
"name": hospital.name,
"display_name": hospital.get_display_name(),
"display_name_ar": hospital.get_display_name_ar(),
"code": hospital.code,
},
}
)
except Hospital.DoesNotExist:
return JsonResponse({"success": False, "error": "Hospital not found"}, status=404)
@login_required
def no_hospital_assigned(request):
"""
Error page for users without a hospital assigned.
Users without a hospital assignment cannot access the system.
"""
return render(request, "core/no_hospital_assigned.html", status=403)
# ============================================================================
# PUBLIC SUBMISSION VIEWS
# ============================================================================
def public_submit_landing(request):
"""
Landing page for public submissions.
Allows users to choose between Complaint, Observation, or Inquiry.
No authentication required.
"""
from apps.organizations.models import Hospital
if request.method == "POST":
# Return 405 Method Not Allowed with proper JSON response
from django.http import JsonResponse
return JsonResponse(
{"success": False, "error": "Method not allowed. Please use GET to access the landing page."}, status=405
)
hospitals = Hospital.objects.all().order_by("name")
context = {
"hospitals": hospitals,
}
return render(request, "core/public_submit.html", context)
@require_POST
def public_inquiry_submit(request):
"""
Handle public inquiry submissions.
Creates an inquiry from public submission.
Returns JSON response with reference number.
"""
from apps.complaints.models import Inquiry
from apps.organizations.models import Hospital
import uuid
# Get form data
name = request.POST.get("name", "").strip()
email = request.POST.get("email", "").strip()
phone = request.POST.get("phone", "").strip()
hospital_id = request.POST.get("hospital")
category = request.POST.get("category", "").strip()
subject = request.POST.get("subject", "").strip()
message = request.POST.get("message", "").strip()
# Validation
errors = []
if not name:
errors.append("Name is required")
if not email:
errors.append("Email is required")
if not phone:
errors.append("Phone number is required")
if not hospital_id:
errors.append("Hospital selection is required")
if not subject:
errors.append("Subject is required")
if not message:
errors.append("Message is required")
if errors:
return JsonResponse({"success": False, "errors": errors}, status=400)
try:
# Validate hospital
hospital = Hospital.objects.get(id=hospital_id)
# Create inquiry (using correct field names from model)
inquiry = Inquiry.objects.create(
hospital=hospital,
contact_name=name,
contact_email=email,
contact_phone=phone,
subject=subject,
message=message,
category=category,
status="open",
)
# Generate a simple reference for display
reference_number = f"INQ-{str(inquiry.id)[:8].upper()}"
# Send notification email (optional)
try:
from django.core.mail import send_mail
from django.conf import settings
from django.template.loader import render_to_string
subject = f"New Public Inquiry - {reference_number}"
html_message = render_to_string(
"emails/public_inquiry_notification.html",
{
"name": name,
"subject": subject,
"message": message,
"reference_number": reference_number,
},
)
plain_message = f"Inquiry from {name}\n\nSubject: {subject}\n\nMessage:\n{message}"
send_mail(
subject=subject,
message=plain_message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[settings.DEFAULT_FROM_EMAIL],
fail_silently=True,
html_message=html_message,
)
except Exception:
pass # Don't fail if email doesn't send
return JsonResponse({"success": True, "reference_number": reference_number, "inquiry_id": str(inquiry.id)})
except Hospital.DoesNotExist:
return JsonResponse({"success": False, "errors": ["Invalid hospital selected"]}, status=400)
except Exception as e:
return JsonResponse({"success": False, "errors": [str(e)]}, status=500)
@require_GET
def api_hospitals(request):
"""
API endpoint to get hospitals list.
Used by public submission forms to populate hospital dropdown.
"""
from apps.organizations.models import Hospital
hospitals = Hospital.objects.all().order_by("name").values("id", "name")
return JsonResponse({"success": True, "hospitals": list(hospitals)})
@require_GET
def set_language(request):
"""
Set's language preference for the session and cookie.
Stores the selected language in session and sets a persistent cookie
for better reliability across page reloads and incognito sessions.
"""
from django.conf import settings
from django.utils import translation
from urllib.parse import urlparse
language = request.GET.get("language", "en")
# Validate language code
if language not in dict(settings.LANGUAGES):
language = "en"
# Activate and store the language in session
translation.activate(language)
request.session["django_language"] = language
# Explicitly save the session to ensure it persists
if hasattr(request, "session") and request.session.modified:
request.session.save()
# Get the referring URL or use a default
next_url = request.META.get("HTTP_REFERER", "/")
parsed_url = urlparse(next_url)
# Keep the path but remove query parameters if needed
redirect_url = parsed_url.path if parsed_url.path else "/"
# If there's no referer, redirect to home or public submit landing
if next_url == "/" or not next_url:
redirect_url = "/"
# Create response with redirect
response = redirect(redirect_url)
# Also set a persistent cookie as a fallback mechanism
# This ensures language persists even if session storage fails
response.set_cookie(
"django_language",
language,
max_age=365 * 24 * 60 * 60, # 1 year
httponly=False, # Allow JavaScript to read it if needed
secure=False, # Allow over HTTP for local development
samesite="Lax", # Standard SameSite policy
)
return response
@require_GET
def api_observation_categories(request):
"""
API endpoint to get observation categories list.
Used by public observation form to populate category dropdown.
"""
from apps.observations.models import ObservationCategory
categories = (
ObservationCategory.objects.filter(is_active=True)
.order_by("sort_order", "name_en")
.values("id", "name_en", "name_ar")
)
return JsonResponse({"success": True, "categories": list(categories)})
@require_POST
def public_observation_submit(request):
"""
Handle public observation submissions.
Creates an observation from public submission.
Returns JSON response with tracking code.
"""
from apps.observations.models import Observation, ObservationAttachment, ObservationCategory
from django.shortcuts import get_object_or_404
from apps.observations.services import ObservationService
import mimetypes
# Get form data
category_id = request.POST.get("category")
severity = request.POST.get("severity", "medium")
title = request.POST.get("title", "").strip()
description = request.POST.get("description", "").strip()
location_text = request.POST.get("location_text", "").strip()
incident_datetime = request.POST.get("incident_datetime", "")
reporter_staff_id = request.POST.get("reporter_staff_id", "").strip()
reporter_name = request.POST.get("reporter_name", "").strip()
reporter_phone = request.POST.get("reporter_phone", "").strip()
reporter_email = request.POST.get("reporter_email", "").strip()
# Validation
errors = []
if not description:
errors.append("Description is required")
if severity not in ["low", "medium", "high", "critical"]:
errors.append("Invalid severity selected")
if errors:
return JsonResponse({"success": False, "errors": errors}, status=400)
try:
category = None
if category_id:
category = get_object_or_404(ObservationCategory, id=category_id)
# Get client info
def get_client_ip(req):
x_forwarded_for = req.META.get("HTTP_X_FORWARDED_FOR")
if x_forwarded_for:
ip = x_forwarded_for.split(",")[0].strip()
else:
ip = req.META.get("REMOTE_ADDR")
return ip
client_ip = get_client_ip(request)
user_agent = request.META.get("HTTP_USER_AGENT", "")
# Handle file uploads
attachments = request.FILES.getlist("attachments")
# Create observation using service
observation = ObservationService.create_observation(
description=description,
severity=severity,
category=category,
title=title,
location_text=location_text,
incident_datetime=incident_datetime if incident_datetime else None,
reporter_staff_id=reporter_staff_id,
reporter_name=reporter_name,
reporter_phone=reporter_phone,
reporter_email=reporter_email,
client_ip=client_ip,
user_agent=user_agent,
attachments=attachments,
)
return JsonResponse(
{"success": True, "tracking_code": observation.tracking_code, "observation_id": str(observation.id)}
)
except Exception as e:
return JsonResponse({"success": False, "errors": [str(e)]}, status=500)