Initial commit: igny8 project

This commit is contained in:
igny8
2025-11-09 10:27:02 +00:00
commit 60b8188111
27265 changed files with 4360521 additions and 0 deletions

View File

@@ -0,0 +1,274 @@
"""
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)