""" Tenant-aware mixins for views and serializers """ from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied from django.http import Http404 from django.shortcuts import redirect from rest_framework import serializers class TenantAccessMixin: """ Mixin that validates hospital access for views. This mixin ensures: - Users can only access objects from their hospital - PX admins can access all hospitals - Hospital admins can only access their hospital - Department managers can only access their department """ def get_object(self, queryset=None): """Retrieve object with tenant validation.""" obj = super().get_object(queryset) # Check if user has access to this object's hospital if hasattr(obj, 'hospital'): if not self.can_access_hospital(obj.hospital): raise PermissionDenied("You don't have access to this hospital's data") return obj def get_queryset(self): """Filter queryset based on user's hospital and role.""" queryset = super().get_queryset() user = self.request.user # PX Admins can see all hospitals if user.is_px_admin(): return queryset # Users without a hospital cannot see any records if not user.hospital: return queryset.none() # Filter by user's hospital queryset = queryset.filter(hospital=user.hospital) # Department managers can only see their department's records if user.is_department_manager() and user.department: if hasattr(queryset.model, 'department'): queryset = queryset.filter(department=user.department) return queryset def can_access_hospital(self, hospital): """Check if user can access given hospital.""" user = self.request.user # PX Admins can access all hospitals if user.is_px_admin(): return True # Users can only access their own hospital if user.hospital == hospital: return True return False class TenantSerializerMixin: """ Mixin that validates hospital field in serializers. This mixin ensures: - Users can only create records for their hospital - PX admins can create records for any hospital - Hospital field is validated and set automatically """ def validate_hospital(self, value): """Ensure user can create records for this hospital.""" user = self.context['request'].user # PX admins can assign to any hospital if user.is_px_admin(): return value # Users must create records for their own hospital if user.hospital != value: raise serializers.ValidationError( "You can only create records for your hospital" ) return value def to_internal_value(self, data): """Set hospital from user's profile if not provided.""" # Convert data to mutable dict if needed mutable_data = data.copy() if hasattr(data, 'copy') else data user = self.context['request'].user # Auto-set hospital if not provided and user has one if 'hospital' not in mutable_data or not mutable_data['hospital']: if user.hospital: mutable_data['hospital'] = str(user.hospital.id) return super().to_internal_value(mutable_data) class TenantAdminMixin: """ Mixin for Django admin with tenant isolation. This mixin ensures: - Admin users only see their hospital's records - PX admins see all records - New records are automatically assigned to user's hospital """ def get_queryset(self, request): """Filter queryset based on user's hospital.""" qs = super().get_queryset(request) # PX Admins can see all hospitals if request.user.is_px_admin(): return qs # Users with a hospital can only see their hospital's records if request.user.hospital: qs = qs.filter(hospital=request.user.hospital) return qs def save_model(self, request, obj, form, change): """Auto-assign hospital on create.""" if not change and hasattr(obj, 'hospital') and not obj.hospital: obj.hospital = request.user.hospital super().save_model(request, obj, form, change) def formfield_for_foreignkey(self, db_field, request, **kwargs): """Limit foreign key choices to user's hospital.""" if db_field.name == 'hospital': # Only PX admins can select any hospital if not request.user.is_px_admin(): # Filter to user's hospital kwargs['queryset'] = db_field.related_model.objects.filter( id=request.user.hospital.id ) # Filter department choices to user's hospital if db_field.name == 'department': kwargs['queryset'] = db_field.related_model.objects.filter( hospital=request.user.hospital ) return super().formfield_for_foreignkey(db_field, request, **kwargs) class TenantRequiredMixin(LoginRequiredMixin): """ Mixin that ensures user has hospital context. This mixin ensures: - User is authenticated (from LoginRequiredMixin) - User has a hospital assigned (or is PX Admin) - Redirects PX Admins to hospital selector if no hospital selected - Redirects other users to error page if no hospital assigned """ def dispatch(self, request, *args, **kwargs): """Check hospital context before processing request.""" response = super().dispatch(request, *args, **kwargs) # PX Admins need to select a hospital if request.user.is_px_admin(): if not request.tenant_hospital: return redirect('core:select_hospital') # Other users must have a hospital assigned elif not request.user.hospital: return redirect('core:no_hospital_assigned') return response