Django admin cleanup
This commit is contained in:
@@ -1,28 +1,30 @@
|
||||
"""
|
||||
Custom AdminSite for IGNY8 to organize models into proper groups using Unfold
|
||||
NO EMOJIS - Unfold handles all icons via Material Design
|
||||
Custom AdminSite for IGNY8 using Unfold theme.
|
||||
|
||||
SIMPLIFIED VERSION - Navigation is now handled via UNFOLD settings in settings.py
|
||||
This file only handles:
|
||||
1. Custom URLs for dashboard, reports, and monitoring pages
|
||||
2. Index redirect to dashboard
|
||||
|
||||
All sidebar navigation is configured in settings.py under UNFOLD["SIDEBAR"]["navigation"]
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.apps import AdminConfig
|
||||
from django.apps import apps
|
||||
from django.urls import path, reverse_lazy
|
||||
from django.urls import path
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.admin import sites
|
||||
from unfold.admin import ModelAdmin as UnfoldModelAdmin
|
||||
from unfold.sites import UnfoldAdminSite
|
||||
|
||||
|
||||
class Igny8AdminSite(UnfoldAdminSite):
|
||||
"""
|
||||
Custom AdminSite based on Unfold that organizes models into the planned groups
|
||||
Custom AdminSite based on Unfold.
|
||||
Navigation is handled via UNFOLD settings - this just adds custom URLs.
|
||||
"""
|
||||
site_header = 'IGNY8 Administration'
|
||||
site_title = 'IGNY8 Admin'
|
||||
index_title = 'IGNY8 Administration'
|
||||
|
||||
|
||||
def get_urls(self):
|
||||
"""Get admin URLs with dashboard, reports, and monitoring pages available"""
|
||||
from django.urls import path
|
||||
"""Add custom URLs for dashboard, reports, and monitoring pages"""
|
||||
from .dashboard import admin_dashboard
|
||||
from .reports import (
|
||||
revenue_report, usage_report, content_report, data_quality_report,
|
||||
@@ -31,12 +33,12 @@ class Igny8AdminSite(UnfoldAdminSite):
|
||||
from .monitoring import (
|
||||
system_health_dashboard, api_monitor_dashboard, debug_console
|
||||
)
|
||||
|
||||
|
||||
urls = super().get_urls()
|
||||
custom_urls = [
|
||||
# Dashboard
|
||||
path('dashboard/', self.admin_view(admin_dashboard), name='dashboard'),
|
||||
|
||||
|
||||
# Reports
|
||||
path('reports/revenue/', self.admin_view(revenue_report), name='report_revenue'),
|
||||
path('reports/usage/', self.admin_view(usage_report), name='report_usage'),
|
||||
@@ -44,311 +46,17 @@ class Igny8AdminSite(UnfoldAdminSite):
|
||||
path('reports/data-quality/', self.admin_view(data_quality_report), name='report_data_quality'),
|
||||
path('reports/token-usage/', self.admin_view(token_usage_report), name='report_token_usage'),
|
||||
path('reports/ai-cost-analysis/', self.admin_view(ai_cost_analysis), name='report_ai_cost_analysis'),
|
||||
|
||||
# Monitoring (NEW)
|
||||
|
||||
# Monitoring
|
||||
path('monitoring/system-health/', self.admin_view(system_health_dashboard), name='monitoring_system_health'),
|
||||
path('monitoring/api-monitor/', self.admin_view(api_monitor_dashboard), name='monitoring_api_monitor'),
|
||||
path('monitoring/debug-console/', self.admin_view(debug_console), name='monitoring_debug_console'),
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
def index(self, request, extra_context=None):
|
||||
"""Redirect to custom dashboard"""
|
||||
from django.shortcuts import redirect
|
||||
return redirect('admin:dashboard')
|
||||
|
||||
def get_sidebar_list(self, request):
|
||||
"""
|
||||
Override Unfold's get_sidebar_list to return our custom app groups
|
||||
Convert Django app_list format to Unfold sidebar navigation format
|
||||
"""
|
||||
# Get our custom Django app list
|
||||
django_apps = self.get_app_list(request, app_label=None)
|
||||
|
||||
# Convert to Unfold navigation format: {title, items: [{title, link, icon}]}
|
||||
sidebar_groups = []
|
||||
|
||||
for app in django_apps:
|
||||
group = {
|
||||
'title': app['name'],
|
||||
'collapsible': True,
|
||||
'items': []
|
||||
}
|
||||
|
||||
# Convert each model to navigation item
|
||||
for model in app.get('models', []):
|
||||
if model.get('perms', {}).get('view', False) or model.get('perms', {}).get('change', False):
|
||||
item = {
|
||||
'title': model['name'],
|
||||
'link': model['admin_url'],
|
||||
'icon': None, # Unfold will use default
|
||||
'has_permission': True, # CRITICAL: Template checks this
|
||||
}
|
||||
group['items'].append(item)
|
||||
|
||||
# Only add groups that have items
|
||||
if group['items']:
|
||||
sidebar_groups.append(group)
|
||||
|
||||
return sidebar_groups
|
||||
|
||||
def each_context(self, request):
|
||||
"""
|
||||
Override context to ensure our custom app_list is always used
|
||||
This is called by all admin templates for sidebar rendering
|
||||
|
||||
CRITICAL FIX: Force custom sidebar on ALL pages including model detail/list views
|
||||
"""
|
||||
# CRITICAL: Must call parent to get sidebar_navigation set
|
||||
context = super().each_context(request)
|
||||
|
||||
# DEBUGGING: Print to console what parent returned
|
||||
print(f"\n=== DEBUG each_context for {request.path} ===")
|
||||
print(f"sidebar_navigation length from parent: {len(context.get('sidebar_navigation', []))}")
|
||||
if context.get('sidebar_navigation'):
|
||||
print(f"First sidebar group: {context['sidebar_navigation'][0].get('title', 'NO TITLE')}")
|
||||
|
||||
# Force our custom app list to be used everywhere - IGNORE app_label parameter
|
||||
custom_apps = self.get_app_list(request, app_label=None)
|
||||
context['available_apps'] = custom_apps
|
||||
context['app_list'] = custom_apps # Also set app_list for compatibility
|
||||
|
||||
# CRITICAL FIX: Ensure sidebar_navigation is using our custom sidebar
|
||||
# Parent's each_context already called get_sidebar_list(), which returns our custom sidebar
|
||||
# So sidebar_navigation should already be correct, but let's verify
|
||||
if not context.get('sidebar_navigation') or len(context.get('sidebar_navigation', [])) == 0:
|
||||
# If sidebar_navigation is empty, force it
|
||||
print("WARNING: sidebar_navigation was empty, forcing it!")
|
||||
context['sidebar_navigation'] = self.get_sidebar_list(request)
|
||||
|
||||
print(f"Final sidebar_navigation length: {len(context['sidebar_navigation'])}")
|
||||
print("=== END DEBUG ===\n")
|
||||
|
||||
return context
|
||||
|
||||
def get_app_list(self, request, app_label=None):
|
||||
"""
|
||||
Customize the app list to organize models into logical groups
|
||||
NO EMOJIS - Unfold handles all icons via Material Design
|
||||
|
||||
Args:
|
||||
request: The HTTP request
|
||||
app_label: IGNORED - Always return full custom sidebar for consistency
|
||||
"""
|
||||
# CRITICAL: Always build full app_dict (ignore app_label) for consistent sidebar
|
||||
app_dict = self._build_app_dict(request, None)
|
||||
|
||||
# Define our custom groups with their models (using object_name)
|
||||
# Organized by business function - Material icons configured in Unfold
|
||||
custom_groups = {
|
||||
'Accounts & Tenancy': {
|
||||
'models': [
|
||||
('igny8_core_auth', 'Account'),
|
||||
('igny8_core_auth', 'User'),
|
||||
('igny8_core_auth', 'Site'),
|
||||
('igny8_core_auth', 'Sector'),
|
||||
('igny8_core_auth', 'SiteUserAccess'),
|
||||
],
|
||||
},
|
||||
'Global Resources': {
|
||||
'models': [
|
||||
('igny8_core_auth', 'Industry'),
|
||||
('igny8_core_auth', 'IndustrySector'),
|
||||
('igny8_core_auth', 'SeedKeyword'),
|
||||
],
|
||||
},
|
||||
'Global Settings': {
|
||||
'models': [
|
||||
('system', 'GlobalIntegrationSettings'),
|
||||
('system', 'GlobalModuleSettings'),
|
||||
('billing', 'AIModelConfig'),
|
||||
('system', 'GlobalAIPrompt'),
|
||||
('system', 'GlobalAuthorProfile'),
|
||||
('system', 'GlobalStrategy'),
|
||||
],
|
||||
},
|
||||
'Plans and Billing': {
|
||||
'models': [
|
||||
('igny8_core_auth', 'Plan'),
|
||||
('igny8_core_auth', 'Subscription'),
|
||||
('billing', 'BillingConfiguration'),
|
||||
('billing', 'Invoice'),
|
||||
('billing', 'Payment'),
|
||||
('billing', 'CreditPackage'),
|
||||
('billing', 'PaymentMethodConfig'),
|
||||
('billing', 'AccountPaymentMethod'),
|
||||
],
|
||||
},
|
||||
'Credits': {
|
||||
'models': [
|
||||
('billing', 'CreditTransaction'),
|
||||
('billing', 'CreditUsageLog'),
|
||||
('billing', 'CreditCostConfig'),
|
||||
('billing', 'PlanLimitUsage'),
|
||||
],
|
||||
},
|
||||
'Content Planning': {
|
||||
'models': [
|
||||
('planner', 'Keywords'),
|
||||
('planner', 'Clusters'),
|
||||
('planner', 'ContentIdeas'),
|
||||
],
|
||||
},
|
||||
'Content Generation': {
|
||||
'models': [
|
||||
('writer', 'Tasks'),
|
||||
('writer', 'Content'),
|
||||
('writer', 'Images'),
|
||||
('writer', 'ImagePrompts'),
|
||||
],
|
||||
},
|
||||
'Taxonomy & Organization': {
|
||||
'models': [
|
||||
('writer', 'ContentTaxonomy'),
|
||||
('writer', 'ContentTaxonomyRelation'),
|
||||
('writer', 'ContentClusterMap'),
|
||||
('writer', 'ContentAttribute'),
|
||||
],
|
||||
},
|
||||
'Publishing & Integration': {
|
||||
'models': [
|
||||
('integration', 'SiteIntegration'),
|
||||
('integration', 'SyncEvent'),
|
||||
('publishing', 'PublishingRecord'),
|
||||
('system', 'PublishingChannel'),
|
||||
('publishing', 'DeploymentRecord'),
|
||||
],
|
||||
},
|
||||
'AI & Automation': {
|
||||
'models': [
|
||||
('system', 'IntegrationSettings'),
|
||||
('system', 'AIPrompt'),
|
||||
('system', 'Strategy'),
|
||||
('system', 'AuthorProfile'),
|
||||
('system', 'APIKey'),
|
||||
('system', 'WebhookConfig'),
|
||||
('automation', 'AutomationConfig'),
|
||||
('automation', 'AutomationRun'),
|
||||
],
|
||||
},
|
||||
'System Settings': {
|
||||
'models': [
|
||||
('contenttypes', 'ContentType'),
|
||||
('system', 'ContentTemplate'),
|
||||
('system', 'TaxonomyConfig'),
|
||||
('system', 'SystemSetting'),
|
||||
('system', 'ContentTypeConfig'),
|
||||
('system', 'NotificationConfig'),
|
||||
],
|
||||
},
|
||||
'Django Admin': {
|
||||
'models': [
|
||||
('auth', 'Group'),
|
||||
('auth', 'Permission'),
|
||||
('igny8_core_auth', 'PasswordResetToken'),
|
||||
('sessions', 'Session'),
|
||||
],
|
||||
},
|
||||
'Tasks & Logging': {
|
||||
'models': [
|
||||
('ai', 'AITaskLog'),
|
||||
('system', 'AuditLog'),
|
||||
('admin', 'LogEntry'),
|
||||
('django_celery_results', 'TaskResult'),
|
||||
('django_celery_results', 'GroupResult'),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# ALWAYS build and return our custom organized app list
|
||||
# regardless of app_label parameter (for consistent sidebar on all pages)
|
||||
organized_apps = []
|
||||
|
||||
# Add Dashboard link as first item
|
||||
organized_apps.append({
|
||||
'name': '📊 Dashboard',
|
||||
'app_label': '_dashboard',
|
||||
'app_url': '/admin/dashboard/',
|
||||
'has_module_perms': True,
|
||||
'models': [],
|
||||
})
|
||||
|
||||
# Add Reports section with links to all reports
|
||||
organized_apps.append({
|
||||
'name': 'Reports & Analytics',
|
||||
'app_label': '_reports',
|
||||
'app_url': '#',
|
||||
'has_module_perms': True,
|
||||
'models': [
|
||||
{
|
||||
'name': 'Revenue Report',
|
||||
'object_name': 'RevenueReport',
|
||||
'admin_url': '/admin/reports/revenue/',
|
||||
'view_only': True,
|
||||
'perms': {'view': True},
|
||||
},
|
||||
{
|
||||
'name': 'Usage Report',
|
||||
'object_name': 'UsageReport',
|
||||
'admin_url': '/admin/reports/usage/',
|
||||
'view_only': True,
|
||||
'perms': {'view': True},
|
||||
},
|
||||
{
|
||||
'name': 'Content Report',
|
||||
'object_name': 'ContentReport',
|
||||
'admin_url': '/admin/reports/content/',
|
||||
'view_only': True,
|
||||
'perms': {'view': True},
|
||||
},
|
||||
{
|
||||
'name': 'Data Quality Report',
|
||||
'object_name': 'DataQualityReport',
|
||||
'admin_url': '/admin/reports/data-quality/',
|
||||
'view_only': True,
|
||||
'perms': {'view': True},
|
||||
},
|
||||
{
|
||||
'name': 'Token Usage Report',
|
||||
'object_name': 'TokenUsageReport',
|
||||
'admin_url': '/admin/reports/token-usage/',
|
||||
'view_only': True,
|
||||
'perms': {'view': True},
|
||||
},
|
||||
{
|
||||
'name': 'AI Cost Analysis',
|
||||
'object_name': 'AICostAnalysis',
|
||||
'admin_url': '/admin/reports/ai-cost-analysis/',
|
||||
'view_only': True,
|
||||
'perms': {'view': True},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
for group_name, group_config in custom_groups.items():
|
||||
group_models = []
|
||||
|
||||
for app_label, model_name in group_config['models']:
|
||||
# Find the model in app_dict
|
||||
for app in app_dict.values():
|
||||
if app['app_label'] == app_label:
|
||||
for model in app.get('models', []):
|
||||
if model['object_name'] == model_name:
|
||||
group_models.append(model)
|
||||
break
|
||||
|
||||
if group_models:
|
||||
# Get the first model's app_label to use as the real app_label
|
||||
first_model_app_label = group_config['models'][0][0]
|
||||
organized_apps.append({
|
||||
'name': group_name,
|
||||
'app_label': first_model_app_label, # Use real app_label, not fake one
|
||||
'app_url': f'/admin/{first_model_app_label}/', # Real URL, not '#'
|
||||
'has_module_perms': True,
|
||||
'models': group_models,
|
||||
})
|
||||
|
||||
return organized_apps
|
||||
def index(self, request, extra_context=None):
|
||||
"""Redirect admin index to custom dashboard"""
|
||||
return redirect('admin:dashboard')
|
||||
|
||||
|
||||
# Instantiate custom admin site
|
||||
|
||||
Reference in New Issue
Block a user