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:
-
PX Admin (Level 4)
- Can view and manage all hospitals
- Must select a hospital before viewing data
- Can switch between hospitals
-
Hospital Admin (Level 3)
- Assigned to a specific hospital
- Can only view/manage their hospital's data
-
Department Manager (Level 2)
- Assigned to a specific department within a hospital
- Can only view/manage their department's data
-
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_hospitalattribute - 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 UItemplates/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
- Data Isolation: PX Admins can safely work with one hospital at a time
- Consistent User Experience: Clear indication of which hospital is being viewed
- Security: Automatic filtering prevents cross-hospital data leakage
- Flexibility: PX Admins can easily switch between hospitals
- Backward Compatible: Other user roles work as before
Future Enhancements
- Remember Last Hospital: Store PX Admin's last hospital in user profile
- Hospital Quick Switcher: Add dropdown in header for quick switching
- Multi-Hospital View: Option to see aggregated data across hospitals
- Audit Trail: Track hospital selection changes in audit logs
- 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
apps/core/middleware.py- Enhanced TenantMiddlewareapps/core/views.py- Added select_hospital and no_hospital_assigned viewsapps/core/urls.py- Added hospital selector URLsapps/core/context_processors.py- Updated sidebar counts for tenant awarenessapps/accounts/views.py- Enhanced JWT token view with redirect URLsapps/dashboard/views.py- Added hospital context and filteringtemplates/core/select_hospital.html- New hospital selector templatetemplates/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:
- Use
TenantHospitalRequiredMixinif hospital context is required - Access hospital via
self.request.tenant_hospital - Filter querysets based on user role and hospital context
- 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.