239 lines
7.5 KiB
Markdown
239 lines
7.5 KiB
Markdown
# 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:**
|
|
```python
|
|
# 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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```python
|
|
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.
|