""" Base Admin Mixins for account and site/sector filtering """ from django.contrib import admin from django.core.exceptions import PermissionDenied class AccountAdminMixin: """Mixin for admin classes that need account filtering""" def get_queryset(self, request): """Filter queryset by account""" qs = super().get_queryset(request) # Check for account field has_account_field = hasattr(qs.model, 'account') if has_account_field: # Superuser and developers can see all if request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer()): return qs # Filter by user's account user_account = getattr(request.user, 'account', None) if user_account: return qs.filter(account=user_account) return qs def has_view_permission(self, request, obj=None): """Check if user can view this object""" if obj: obj_account = getattr(obj, 'account', None) if obj_account: if request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer()): return True user_account = getattr(request.user, 'account', None) if user_account: return obj_account == user_account return super().has_view_permission(request, obj) def has_change_permission(self, request, obj=None): """Check if user can change this object""" if obj: obj_account = getattr(obj, 'account', None) if obj_account: if request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer()): return True user_account = getattr(request.user, 'account', None) if user_account: return obj_account == user_account return super().has_change_permission(request, obj) def has_delete_permission(self, request, obj=None): """Check if user can delete this object""" if obj: obj_account = getattr(obj, 'account', None) if obj_account: if request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer()): return True user_account = getattr(request.user, 'account', None) if user_account: return obj_account == user_account return super().has_delete_permission(request, obj) class SiteSectorAdminMixin: """Mixin for admin classes that need site/sector filtering""" def get_queryset(self, request): """Filter queryset by site/sector access""" qs = super().get_queryset(request) if hasattr(qs.model, 'site') and hasattr(qs.model, 'sector'): # Superuser and developers can see all if request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer()): return qs # Filter by accessible sites if hasattr(request.user, 'get_accessible_sites'): accessible_sites = request.user.get_accessible_sites() return qs.filter(site__in=accessible_sites) return qs def has_view_permission(self, request, obj=None): """Check if user can view this object""" if obj and hasattr(obj, 'site'): if request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer()): return True if hasattr(request.user, 'get_accessible_sites'): accessible_sites = request.user.get_accessible_sites() return obj.site in accessible_sites return super().has_view_permission(request, obj) def has_change_permission(self, request, obj=None): """Check if user can change this object""" if obj and hasattr(obj, 'site'): if request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer()): return True if hasattr(request.user, 'get_accessible_sites'): accessible_sites = request.user.get_accessible_sites() return obj.site in accessible_sites return super().has_change_permission(request, obj) def has_delete_permission(self, request, obj=None): """Check if user can delete this object""" if obj and hasattr(obj, 'site'): if request.user.is_superuser or (hasattr(request.user, 'is_developer') and request.user.is_developer()): return True if hasattr(request.user, 'get_accessible_sites'): accessible_sites = request.user.get_accessible_sites() return obj.site in accessible_sites return super().has_delete_permission(request, obj) # ============================================================================ # Custom ModelAdmin for Sidebar Fix # ============================================================================ from unfold.admin import ModelAdmin as UnfoldModelAdmin class Igny8ModelAdmin(UnfoldModelAdmin): """ Custom ModelAdmin that ensures sidebar_navigation is set correctly on ALL pages Django's ModelAdmin views don't call AdminSite.each_context(), so we override them to inject our custom sidebar. """ def _inject_sidebar_context(self, request, extra_context=None): """Helper to inject custom sidebar into context""" if extra_context is None: extra_context = {} # Get our custom sidebar from the admin site from igny8_core.admin.site import admin_site # CRITICAL: Get the full Unfold context (includes all branding, form classes, etc.) # This is what makes the logo/title appear properly unfold_context = admin_site.each_context(request) # Get the current path to detect active group current_path = request.path sidebar_navigation = admin_site.get_sidebar_list(request) # Detect active group and expand it by setting collapsible=False for group in sidebar_navigation: group_is_active = False for item in group.get('items', []): # Unfold stores resolved link in 'link_callback', original lambda in 'link' item_link = item.get('link_callback') or item.get('link', '') # Convert to string (handles lazy proxy objects and ensures it's a string) try: item_link = str(item_link) if item_link else '' except: item_link = '' # Skip if it's a function representation (e.g., "") if item_link.startswith('<'): continue # Check if current path matches this item's link if item_link and current_path.startswith(item_link): item['active'] = True group_is_active = True # If any item in this group is active, expand the group if group_is_active: group['collapsible'] = False # Expanded state else: group['collapsible'] = True # Collapsed state # Merge Unfold context with our custom sidebar unfold_context['sidebar_navigation'] = sidebar_navigation unfold_context['available_apps'] = admin_site.get_app_list(request, app_label=None) unfold_context['app_list'] = unfold_context['available_apps'] # Merge with any existing extra_context unfold_context.update(extra_context) return unfold_context def changelist_view(self, request, extra_context=None): """Override to inject custom sidebar""" extra_context = self._inject_sidebar_context(request, extra_context) return super().changelist_view(request, extra_context) def change_view(self, request, object_id, form_url='', extra_context=None): """Override to inject custom sidebar""" extra_context = self._inject_sidebar_context(request, extra_context) return super().change_view(request, object_id, form_url, extra_context) def add_view(self, request, form_url='', extra_context=None): """Override to inject custom sidebar""" extra_context = self._inject_sidebar_context(request, extra_context) return super().add_view(request, form_url, extra_context)