""" Base ViewSet with account filtering support """ from rest_framework import viewsets from rest_framework.response import Response from django.core.exceptions import PermissionDenied class AccountModelViewSet(viewsets.ModelViewSet): """ Base ViewSet that automatically filters by account. All module ViewSets should inherit from this. """ def get_queryset(self): queryset = super().get_queryset() # Filter by account if model has account field if hasattr(queryset.model, 'account'): user = getattr(self.request, 'user', None) # ADMIN/DEV/SYSTEM ACCOUNT OVERRIDE: Skip account filtering for: # - Admins and developers (by role) # - Users in system accounts (aws-admin, default-account) if user and hasattr(user, 'is_authenticated') and user.is_authenticated: try: # Check if user has admin/developer privileges is_admin_or_dev = (hasattr(user, 'is_admin_or_developer') and user.is_admin_or_developer()) if user else False is_system_user = (hasattr(user, 'is_system_account_user') and user.is_system_account_user()) if user else False if is_admin_or_dev or is_system_user: # Skip account filtering - allow all accounts pass else: # Get account from request (set by middleware) account = getattr(self.request, 'account', None) if account: queryset = queryset.filter(account=account) elif hasattr(self.request, 'user') and self.request.user and hasattr(self.request.user, 'is_authenticated') and self.request.user.is_authenticated: # Fallback to user's account try: user_account = getattr(self.request.user, 'account', None) if user_account: queryset = queryset.filter(account=user_account) except (AttributeError, Exception): # If account access fails (e.g., column mismatch), skip account filtering pass except (AttributeError, TypeError) as e: # If there's an error accessing user attributes, return empty queryset return queryset.none() else: # Require authentication - return empty queryset for unauthenticated users return queryset.none() return queryset def perform_create(self, serializer): # Set account from request (set by middleware) account = getattr(self.request, 'account', None) if not account and hasattr(self.request, 'user') and self.request.user and self.request.user.is_authenticated: try: account = getattr(self.request.user, 'account', None) except (AttributeError, Exception): # If account access fails (e.g., column mismatch), set to None account = None # If model has account field, set it if account and hasattr(serializer.Meta.model, 'account'): serializer.save(account=account) else: serializer.save() def get_serializer_context(self): context = super().get_serializer_context() # Add account to context for serializers account = getattr(self.request, 'account', None) if account: context['account'] = account return context class SiteSectorModelViewSet(AccountModelViewSet): """ Base ViewSet for models that belong to Site and Sector (Keywords, Clusters, etc.). Automatically filters by: 1. Account (via parent class) 2. User's accessible sites (based on role and SiteUserAccess) 3. Optional site/sector query parameters """ def get_queryset(self): queryset = super().get_queryset() # Check if model has site and sector fields (SiteSectorBaseModel) if hasattr(queryset.model, 'site') and hasattr(queryset.model, 'sector'): user = getattr(self.request, 'user', None) # Check if user is authenticated and is a proper User instance (not AnonymousUser) if user and hasattr(user, 'is_authenticated') and user.is_authenticated and hasattr(user, 'get_accessible_sites'): try: # ADMIN/DEV/SYSTEM ACCOUNT OVERRIDE: Developers, admins, and system account users # can see all data regardless of site/sector if (hasattr(user, 'is_admin_or_developer') and user.is_admin_or_developer()) or \ (hasattr(user, 'is_system_account_user') and user.is_system_account_user()): # Skip site/sector filtering for admins, developers, and system account users # But still respect optional query params if provided pass else: # Get user's accessible sites accessible_sites = user.get_accessible_sites() # If no accessible sites, return empty queryset (unless admin/developer/system account) if not accessible_sites.exists(): queryset = queryset.none() else: # Filter by accessible sites queryset = queryset.filter(site__in=accessible_sites) except (AttributeError, TypeError) as e: # If there's an error accessing user attributes, return empty queryset queryset = queryset.none() else: # Require authentication - return empty queryset for unauthenticated users queryset = queryset.none() # Optional: Filter by specific site (from query params) # Safely access query_params (DRF wraps request with Request class) try: query_params = getattr(self.request, 'query_params', None) if query_params is None: # Fallback for non-DRF requests query_params = getattr(self.request, 'GET', {}) site_id = query_params.get('site_id') else: site_id = query_params.get('site_id') except AttributeError: site_id = None if site_id: try: # Convert site_id to int if it's a string site_id_int = int(site_id) if site_id else None if site_id_int: # ADMIN/DEV/SYSTEM ACCOUNT OVERRIDE: Admins, developers, and system account users # can filter by any site, others must verify access if user and hasattr(user, 'is_authenticated') and user.is_authenticated and hasattr(user, 'get_accessible_sites'): try: if (hasattr(user, 'is_admin_or_developer') and user.is_admin_or_developer()) or \ (hasattr(user, 'is_system_account_user') and user.is_system_account_user()): # Admin/Developer/System Account User can filter by any site queryset = queryset.filter(site_id=site_id_int) else: accessible_sites = user.get_accessible_sites() if accessible_sites.filter(id=site_id_int).exists(): queryset = queryset.filter(site_id=site_id_int) else: queryset = queryset.none() # Site not accessible except (AttributeError, TypeError) as e: # If there's an error accessing user attributes, return empty queryset queryset = queryset.none() else: # Require authentication for site filtering queryset = queryset.none() except (ValueError, TypeError): # Invalid site_id, return empty queryset queryset = queryset.none() # Optional: Filter by specific sector (from query params) # Safely access query_params (DRF wraps request with Request class) try: query_params = getattr(self.request, 'query_params', None) if query_params is None: # Fallback for non-DRF requests query_params = getattr(self.request, 'GET', {}) sector_id = query_params.get('sector_id') else: sector_id = query_params.get('sector_id') except AttributeError: sector_id = None if sector_id: try: # Convert sector_id to int if it's a string sector_id_int = int(sector_id) if sector_id else None if sector_id_int: queryset = queryset.filter(sector_id=sector_id_int) # If site_id also provided, ensure sector belongs to that site if site_id: try: site_id_int = int(site_id) if site_id else None if site_id_int: queryset = queryset.filter(site_id=site_id_int) except (ValueError, TypeError): pass except (ValueError, TypeError): # Invalid sector_id, return empty queryset queryset = queryset.none() # Always exclude records where site is null (orphaned records) # This prevents showing keywords/clusters/ideas/tasks that aren't associated with any site # Only skip this if explicitly requested (e.g., for admin cleanup operations) queryset = queryset.exclude(site__isnull=True) return queryset def perform_create(self, serializer): # First call parent to set account super().perform_create(serializer) # If model has site and sector fields, validate access if hasattr(serializer.Meta.model, 'site') and hasattr(serializer.Meta.model, 'sector'): user = getattr(self.request, 'user', None) site = serializer.validated_data.get('site') sector = serializer.validated_data.get('sector') if user and hasattr(user, 'is_authenticated') and user.is_authenticated and site: try: # ADMIN/DEV/SYSTEM ACCOUNT OVERRIDE: Admins, developers, and system account users # can create in any site, others must verify access if not ((hasattr(user, 'is_admin_or_developer') and user.is_admin_or_developer()) or (hasattr(user, 'is_system_account_user') and user.is_system_account_user())): if hasattr(user, 'get_accessible_sites'): accessible_sites = user.get_accessible_sites() if not accessible_sites.filter(id=site.id).exists(): raise PermissionDenied("You do not have access to this site") # Verify sector belongs to site if sector and hasattr(sector, 'site') and sector.site != site: raise PermissionDenied("Sector must belong to the selected site") except (AttributeError, TypeError) as e: # If there's an error accessing user attributes, raise permission denied raise PermissionDenied("Unable to verify access permissions") def get_serializer_context(self): context = super().get_serializer_context() user = getattr(self.request, 'user', None) # Add accessible sites to context for serializer (e.g., for dropdown choices) if user and hasattr(user, 'is_authenticated') and user.is_authenticated and hasattr(user, 'get_accessible_sites'): try: context['accessible_sites'] = user.get_accessible_sites() # Get accessible sectors from accessible sites from igny8_core.auth.models import Sector context['accessible_sectors'] = Sector.objects.filter( site__in=context['accessible_sites'], is_active=True ) except (AttributeError, TypeError): # If there's an error, set empty querysets from igny8_core.auth.models import Site, Sector context['accessible_sites'] = Site.objects.none() context['accessible_sectors'] = Sector.objects.none() else: # Set empty querysets for unauthenticated users from igny8_core.auth.models import Site, Sector context['accessible_sites'] = Site.objects.none() context['accessible_sectors'] = Sector.objects.none() return context class StandardResponseMixin: """ Mixin for standard API response format. """ def get_response(self, data, message=None, status_code=200): return Response({ 'success': True, 'message': message, 'data': data }, status=status_code) def get_error_response(self, message, errors=None, status_code=400): return Response({ 'success': False, 'message': message, 'errors': errors }, status=status_code)