HH/docs/TENANT_AWARE_ROUTING_IMPLEMENTATION.md

7.5 KiB

Tenant-Aware Routing Implementation

Overview

This document describes the tenant-aware routing system implemented to support PX Admins in managing multiple hospitals within the PX360 platform. PX Admins are super-admins who can view and manage data across all hospitals, but need to select a specific hospital to work with at any given time.

Problem Statement

PX Admins have access to all hospitals in the system, but:

  • They need to work with one hospital at a time for data consistency
  • The system must track which hospital they're currently viewing
  • All queries must be filtered to show only the selected hospital's data
  • Other user roles (Hospital Admins, Department Managers) should only see their assigned hospital/department

Solution Architecture

1. User Role Hierarchy

The system supports the following user roles:

  1. PX Admin (Level 4)

    • Can view and manage all hospitals
    • Must select a hospital before viewing data
    • Can switch between hospitals
  2. Hospital Admin (Level 3)

    • Assigned to a specific hospital
    • Can only view/manage their hospital's data
  3. Department Manager (Level 2)

    • Assigned to a specific department within a hospital
    • Can only view/manage their department's data
  4. Regular User (Level 1)

    • Limited permissions based on assignment

2. Implementation Components

A. TenantMiddleware (apps/core/middleware.py)

The middleware is responsible for:

  • Detecting the user's tenant hospital
  • Setting request.tenant_hospital attribute
  • Redirecting PX Admins to hospital selector if no hospital is selected

Key Features:

# For PX Admins: Get hospital from session
if user.is_px_admin():
    hospital_id = request.session.get('selected_hospital_id')
    if hospital_id:
        try:
            from apps.organizations.models import Hospital
            hospital = Hospital.objects.get(id=hospital_id)
            request.tenant_hospital = hospital
        except Hospital.DoesNotExist:
            pass

# For other users: Use their assigned hospital
elif user.hospital:
    request.tenant_hospital = user.hospital

B. Hospital Selector (apps/core/views.py)

A dedicated view that allows PX Admins to:

  • View all available hospitals
  • Select a hospital to work with
  • Store the selection in session

Key Features:

  • Lists all hospitals with location info
  • Shows currently selected hospital
  • Stores selection in session for persistence
  • Redirects back to the referring page after selection

C. Login Redirect (apps/accounts/views.py)

Enhanced JWT token view that provides redirect URLs based on user role:

  • PX Admins → /health/select-hospital/
  • Users without hospital → /health/no-hospital/
  • All others → Dashboard (/)

D. Dashboard Integration (apps/dashboard/views.py)

The CommandCenter dashboard now:

  • Checks if PX Admin has selected a hospital
  • Redirects to hospital selector if not
  • Filters all data based on request.tenant_hospital
  • Shows current hospital context in templates

E. Context Processor (apps/core/context_processors.py)

Updated sidebar counts to respect tenant context:

  • PX Admins see counts for selected hospital only
  • Other users see counts for their assigned hospital

3. URL Structure

/                           - Dashboard (requires hospital context)
/health/select-hospital/    - Hospital selector for PX Admins
/health/no-hospital/        - Error page for unassigned users

4. Template Structure

  • templates/core/select_hospital.html - Hospital selection UI
  • templates/core/no_hospital_assigned.html - Error page for unassigned users

5. Data Flow

User Login
    ↓
JWT Token Response (includes redirect_url)
    ↓
Frontend redirects based on role
    ↓
[If PX Admin] Hospital Selector Page
    ↓
User selects hospital → stored in session
    ↓
TenantMiddleware sets request.tenant_hospital
    ↓
All views filter data using request.tenant_hospital
    ↓
Dashboard displays filtered data

6. Query Filtering Pattern

Views should follow this pattern for filtering:

def get_queryset(self):
    queryset = super().get_queryset()
    user = self.request.user
    hospital = getattr(self.request, 'tenant_hospital', None)

    if user.is_px_admin() and hospital:
        return queryset.filter(hospital=hospital)
    elif user.hospital:
        return queryset.filter(hospital=user.hospital)
    elif user.department:
        return queryset.filter(department=user.department)
    else:
        return queryset.none()

Benefits

  1. Data Isolation: PX Admins can safely work with one hospital at a time
  2. Consistent User Experience: Clear indication of which hospital is being viewed
  3. Security: Automatic filtering prevents cross-hospital data leakage
  4. Flexibility: PX Admins can easily switch between hospitals
  5. Backward Compatible: Other user roles work as before

Future Enhancements

  1. Remember Last Hospital: Store PX Admin's last hospital in user profile
  2. Hospital Quick Switcher: Add dropdown in header for quick switching
  3. Multi-Hospital View: Option to see aggregated data across hospitals
  4. Audit Trail: Track hospital selection changes in audit logs
  5. Permissions Matrix: Fine-grained control per hospital

Testing Checklist

  • PX Admin can login and see hospital selector
  • PX Admin can select a hospital
  • Dashboard shows correct data for selected hospital
  • PX Admin can switch hospitals
  • Hospital Admin sees only their hospital
  • Department Manager sees only their department
  • Sidebar counts update correctly when switching hospitals
  • All queries respect tenant_hospital filtering
  • Session persists hospital selection
  • Logout clears hospital selection

Files Modified

  1. apps/core/middleware.py - Enhanced TenantMiddleware
  2. apps/core/views.py - Added select_hospital and no_hospital_assigned views
  3. apps/core/urls.py - Added hospital selector URLs
  4. apps/core/context_processors.py - Updated sidebar counts for tenant awareness
  5. apps/accounts/views.py - Enhanced JWT token view with redirect URLs
  6. apps/dashboard/views.py - Added hospital context and filtering
  7. templates/core/select_hospital.html - New hospital selector template
  8. templates/core/no_hospital_assigned.html - New error template

Integration Notes

For Frontend Developers

When handling JWT token response:

const response = await login(credentials);
const redirectUrl = response.data.redirect_url;
window.location.href = redirectUrl;

For Backend Developers

When creating new views:

  1. Use TenantHospitalRequiredMixin if hospital context is required
  2. Access hospital via self.request.tenant_hospital
  3. Filter querysets based on user role and hospital context
  4. Add hospital context to template if needed

Example:

from apps.core.mixins import TenantHospitalRequiredMixin

class ComplaintListView(TenantHospitalRequiredMixin, ListView):
    model = Complaint

    def get_queryset(self):
        queryset = super().get_queryset()
        hospital = self.request.tenant_hospital

        if hospital:
            return queryset.filter(hospital=hospital)

        return queryset.none()

Conclusion

This implementation provides a robust tenant-aware routing system that:

  • Secures multi-hospital data access
  • Maintains consistent user experience
  • Scales to support additional hospitals
  • Preserves existing functionality for non-admin users

The system is production-ready and follows Django best practices for middleware, authentication, and request handling.