Files
2026-01-12 14:23:05 +05:00

197 lines
8.5 KiB
Python

"""
Base Admin Mixins for account and site/sector filtering.
"""
from django.contrib import admin, messages
from django.core.exceptions import PermissionDenied
from django.db import models, transaction
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 + Delete Fix
# ============================================================================
from unfold.admin import ModelAdmin as UnfoldModelAdmin
class Igny8ModelAdmin(UnfoldModelAdmin):
"""
Custom ModelAdmin that:
1. Ensures sidebar_navigation is set correctly on ALL pages
2. Uses standard Django filters
"""
# Standard Django filters
pass
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., "<function ...>")
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)