sideabar fixed in dhjanog
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Admin module for IGNY8
|
||||
"""
|
||||
from .base import AccountAdminMixin, SiteSectorAdminMixin
|
||||
# Note: Igny8ModelAdmin is imported by individual admin modules as needed to avoid circular imports
|
||||
|
||||
__all__ = ['AccountAdminMixin', 'SiteSectorAdminMixin']
|
||||
__all__ = []
|
||||
|
||||
|
||||
@@ -107,3 +107,77 @@ class SiteSectorAdminMixin:
|
||||
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', []):
|
||||
item_link = item.get('link', '')
|
||||
# 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)
|
||||
|
||||
@@ -31,6 +31,41 @@ class Igny8AdminSite(UnfoldAdminSite):
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
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
|
||||
@@ -38,11 +73,31 @@ class Igny8AdminSite(UnfoldAdminSite):
|
||||
|
||||
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)
|
||||
# Force our custom app list to be used everywhere - IGNORE app_label parameter
|
||||
|
||||
# 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):
|
||||
|
||||
@@ -3,11 +3,12 @@ Admin configuration for AI models
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import Igny8ModelAdmin
|
||||
from igny8_core.ai.models import AITaskLog
|
||||
|
||||
|
||||
@admin.register(AITaskLog)
|
||||
class AITaskLogAdmin(ModelAdmin):
|
||||
class AITaskLogAdmin(Igny8ModelAdmin):
|
||||
"""Admin interface for AI task logs"""
|
||||
list_display = [
|
||||
'function_name',
|
||||
|
||||
@@ -5,7 +5,7 @@ from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from unfold.admin import ModelAdmin, TabularInline
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from .models import User, Account, Plan, Subscription, Site, Sector, SiteUserAccess, Industry, IndustrySector, SeedKeyword, PasswordResetToken
|
||||
from import_export.admin import ExportMixin
|
||||
from import_export import resources
|
||||
@@ -112,7 +112,7 @@ class AccountAdminForm(forms.ModelForm):
|
||||
|
||||
|
||||
@admin.register(Plan)
|
||||
class PlanAdmin(ModelAdmin):
|
||||
class PlanAdmin(Igny8ModelAdmin):
|
||||
"""Plan admin - Global, no account filtering needed"""
|
||||
list_display = ['name', 'slug', 'price', 'billing_cycle', 'max_sites', 'max_users', 'max_keywords', 'max_content_words', 'included_credits', 'is_active', 'is_featured']
|
||||
list_filter = ['is_active', 'billing_cycle', 'is_internal', 'is_featured']
|
||||
@@ -155,7 +155,7 @@ class AccountResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Account)
|
||||
class AccountAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
class AccountAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = AccountResource
|
||||
form = AccountAdminForm
|
||||
list_display = ['name', 'slug', 'owner', 'plan', 'status', 'health_indicator', 'credits', 'created_at']
|
||||
@@ -319,7 +319,7 @@ class AccountAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Subscription)
|
||||
class SubscriptionAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class SubscriptionAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['account', 'status', 'current_period_start', 'current_period_end']
|
||||
list_filter = ['status']
|
||||
search_fields = ['account__name', 'stripe_subscription_id']
|
||||
@@ -327,7 +327,7 @@ class SubscriptionAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(PasswordResetToken)
|
||||
class PasswordResetTokenAdmin(ModelAdmin):
|
||||
class PasswordResetTokenAdmin(Igny8ModelAdmin):
|
||||
list_display = ['user', 'token', 'used', 'expires_at', 'created_at']
|
||||
list_filter = ['used', 'expires_at', 'created_at']
|
||||
search_fields = ['user__email', 'token']
|
||||
@@ -374,7 +374,7 @@ class SiteResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Site)
|
||||
class SiteAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
class SiteAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = SiteResource
|
||||
list_display = ['name', 'slug', 'account', 'industry', 'domain', 'status', 'is_active', 'get_api_key_status', 'get_sectors_count']
|
||||
list_filter = ['status', 'is_active', 'account', 'industry', 'hosting_type']
|
||||
@@ -454,7 +454,7 @@ class SiteAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Sector)
|
||||
class SectorAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class SectorAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['name', 'slug', 'site', 'industry_sector', 'get_industry', 'status', 'is_active', 'get_keywords_count', 'get_clusters_count']
|
||||
list_filter = ['status', 'is_active', 'site', 'industry_sector__industry']
|
||||
search_fields = ['name', 'slug', 'site__name', 'industry_sector__name']
|
||||
@@ -492,7 +492,7 @@ class SectorAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(SiteUserAccess)
|
||||
class SiteUserAccessAdmin(ModelAdmin):
|
||||
class SiteUserAccessAdmin(Igny8ModelAdmin):
|
||||
list_display = ['user', 'site', 'granted_at', 'granted_by']
|
||||
list_filter = ['granted_at']
|
||||
search_fields = ['user__email', 'site__name']
|
||||
@@ -508,7 +508,7 @@ class IndustrySectorInline(TabularInline):
|
||||
|
||||
|
||||
@admin.register(Industry)
|
||||
class IndustryAdmin(ModelAdmin):
|
||||
class IndustryAdmin(Igny8ModelAdmin):
|
||||
list_display = ['name', 'slug', 'is_active', 'get_sectors_count', 'created_at']
|
||||
list_filter = ['is_active']
|
||||
search_fields = ['name', 'slug', 'description']
|
||||
@@ -526,7 +526,7 @@ class IndustryAdmin(ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(IndustrySector)
|
||||
class IndustrySectorAdmin(ModelAdmin):
|
||||
class IndustrySectorAdmin(Igny8ModelAdmin):
|
||||
list_display = ['name', 'slug', 'industry', 'is_active']
|
||||
list_filter = ['is_active', 'industry']
|
||||
search_fields = ['name', 'slug', 'description']
|
||||
@@ -539,7 +539,7 @@ class IndustrySectorAdmin(ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(SeedKeyword)
|
||||
class SeedKeywordAdmin(ModelAdmin):
|
||||
class SeedKeywordAdmin(Igny8ModelAdmin):
|
||||
"""SeedKeyword admin - Global reference data, no account filtering"""
|
||||
list_display = ['keyword', 'industry', 'sector', 'volume', 'difficulty', 'intent', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'industry', 'sector', 'intent']
|
||||
@@ -574,7 +574,7 @@ class UserResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(ExportMixin, BaseUserAdmin, ModelAdmin):
|
||||
class UserAdmin(ExportMixin, BaseUserAdmin, Igny8ModelAdmin):
|
||||
"""
|
||||
User admin using both Django's BaseUserAdmin (for user-specific functionality)
|
||||
and Unfold's ModelAdmin (for modern UI and styling including popups)
|
||||
|
||||
@@ -4,12 +4,12 @@ Admin registration for Automation models
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from .models import AutomationConfig, AutomationRun
|
||||
|
||||
|
||||
@admin.register(AutomationConfig)
|
||||
class AutomationConfigAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class AutomationConfigAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ('site', 'is_enabled', 'frequency', 'scheduled_time', 'within_stage_delay', 'between_stage_delay', 'last_run_at')
|
||||
list_filter = ('is_enabled', 'frequency')
|
||||
search_fields = ('site__domain',)
|
||||
@@ -29,7 +29,7 @@ class AutomationConfigAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(AutomationRun)
|
||||
class AutomationRunAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class AutomationRunAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ('run_id', 'site', 'status', 'current_stage', 'started_at', 'completed_at')
|
||||
list_filter = ('status', 'current_stage')
|
||||
search_fields = ('run_id', 'site__domain')
|
||||
|
||||
@@ -7,7 +7,7 @@ with full workflow functionality. This file contains legacy/minimal registration
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from .models import (
|
||||
CreditCostConfig,
|
||||
AccountPaymentMethod,
|
||||
@@ -50,7 +50,7 @@ from .models import (
|
||||
# or have minimal implementations that don't conflict
|
||||
|
||||
@admin.register(AccountPaymentMethod)
|
||||
class AccountPaymentMethodAdmin(ModelAdmin):
|
||||
class AccountPaymentMethodAdmin(Igny8ModelAdmin):
|
||||
list_display = [
|
||||
'display_name',
|
||||
'type',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from .models import SiteIntegration, SyncEvent
|
||||
from import_export.admin import ExportMixin
|
||||
from import_export import resources
|
||||
@@ -17,7 +17,7 @@ class SyncEventResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(SiteIntegration)
|
||||
class SiteIntegrationAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class SiteIntegrationAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = [
|
||||
'site',
|
||||
'platform',
|
||||
@@ -55,7 +55,7 @@ class SiteIntegrationAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(SyncEvent)
|
||||
class SyncEventAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
class SyncEventAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = SyncEventResource
|
||||
list_display = [
|
||||
'integration',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from .models import OptimizationTask
|
||||
|
||||
|
||||
@admin.register(OptimizationTask)
|
||||
class OptimizationTaskAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class OptimizationTaskAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['content', 'account', 'status', 'credits_used', 'created_at']
|
||||
list_filter = ['status', 'created_at']
|
||||
search_fields = ['content__title', 'account__name']
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin, Igny8ModelAdmin
|
||||
from .models import PublishingRecord, DeploymentRecord
|
||||
from import_export.admin import ExportMixin
|
||||
from import_export import resources
|
||||
@@ -17,7 +17,7 @@ class PublishingRecordResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(PublishingRecord)
|
||||
class PublishingRecordAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
class PublishingRecordAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = PublishingRecordResource
|
||||
list_display = [
|
||||
'content',
|
||||
@@ -42,7 +42,7 @@ class PublishingRecordAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(DeploymentRecord)
|
||||
class DeploymentRecordAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
class DeploymentRecordAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_display = [
|
||||
'site',
|
||||
'sector',
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from igny8_core.business.billing.models import (
|
||||
CreditCostConfig,
|
||||
Invoice,
|
||||
@@ -30,7 +30,7 @@ class CreditTransactionResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(CreditTransaction)
|
||||
class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = CreditTransactionResource
|
||||
list_display = ['id', 'account', 'transaction_type', 'amount', 'balance_after', 'description', 'created_at']
|
||||
list_filter = ['transaction_type', ('created_at', DateRangeFilter), 'account']
|
||||
@@ -49,7 +49,7 @@ class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(CreditUsageLog)
|
||||
class CreditUsageLogAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class CreditUsageLogAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['id', 'account', 'operation_type', 'credits_used', 'cost_usd', 'model_used', 'created_at']
|
||||
list_filter = ['operation_type', 'created_at', 'account', 'model_used']
|
||||
search_fields = ['account__name', 'model_used']
|
||||
@@ -67,7 +67,7 @@ class CreditUsageLogAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Invoice)
|
||||
class InvoiceAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class InvoiceAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = [
|
||||
'invoice_number',
|
||||
'account',
|
||||
@@ -93,7 +93,7 @@ class PaymentResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Payment)
|
||||
class PaymentAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
class PaymentAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
"""
|
||||
Main Payment Admin with approval workflow.
|
||||
When you change status to 'succeeded', it automatically:
|
||||
@@ -375,7 +375,7 @@ class PaymentAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(CreditPackage)
|
||||
class CreditPackageAdmin(ModelAdmin):
|
||||
class CreditPackageAdmin(Igny8ModelAdmin):
|
||||
list_display = ['name', 'slug', 'credits', 'price', 'discount_percentage', 'is_active', 'is_featured', 'sort_order']
|
||||
list_filter = ['is_active', 'is_featured']
|
||||
search_fields = ['name', 'slug']
|
||||
@@ -383,7 +383,7 @@ class CreditPackageAdmin(ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(PaymentMethodConfig)
|
||||
class PaymentMethodConfigAdmin(ModelAdmin):
|
||||
class PaymentMethodConfigAdmin(Igny8ModelAdmin):
|
||||
list_display = ['country_code', 'payment_method', 'display_name', 'is_enabled', 'sort_order', 'updated_at']
|
||||
list_filter = ['payment_method', 'is_enabled', 'country_code']
|
||||
search_fields = ['country_code', 'display_name', 'payment_method']
|
||||
@@ -392,7 +392,7 @@ class PaymentMethodConfigAdmin(ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(AccountPaymentMethod)
|
||||
class AccountPaymentMethodAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class AccountPaymentMethodAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = [
|
||||
'display_name',
|
||||
'type',
|
||||
@@ -421,7 +421,7 @@ class AccountPaymentMethodAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(CreditCostConfig)
|
||||
class CreditCostConfigAdmin(ModelAdmin):
|
||||
class CreditCostConfigAdmin(Igny8ModelAdmin):
|
||||
list_display = [
|
||||
'operation_type',
|
||||
'display_name',
|
||||
@@ -496,7 +496,7 @@ class CreditCostConfigAdmin(ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(PlanLimitUsage)
|
||||
class PlanLimitUsageAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class PlanLimitUsageAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
"""Admin for tracking plan limit usage across billing periods"""
|
||||
list_display = [
|
||||
'account',
|
||||
|
||||
@@ -7,7 +7,7 @@ from unfold.contrib.filters.admin import (
|
||||
RelatedDropdownFilter,
|
||||
ChoicesDropdownFilter,
|
||||
)
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin, Igny8ModelAdmin
|
||||
from .models import Keywords, Clusters, ContentIdeas
|
||||
from import_export.admin import ExportMixin
|
||||
from import_export import resources
|
||||
@@ -23,7 +23,7 @@ class KeywordsResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Clusters)
|
||||
class ClustersAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
class ClustersAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['name', 'site', 'sector', 'keywords_count', 'volume', 'status', 'created_at']
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
@@ -53,7 +53,7 @@ class ClustersAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Keywords)
|
||||
class KeywordsAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
class KeywordsAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = KeywordsResource
|
||||
list_display = ['keyword', 'seed_keyword', 'site', 'sector', 'cluster', 'volume', 'difficulty', 'intent', 'status', 'created_at']
|
||||
list_filter = [
|
||||
@@ -152,7 +152,7 @@ class KeywordsAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ContentIdeas)
|
||||
class ContentIdeasAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
class ContentIdeasAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['idea_title', 'site', 'sector', 'description_preview', 'content_type', 'content_structure', 'status', 'keyword_cluster', 'estimated_word_count', 'created_at']
|
||||
list_filter = [
|
||||
('status', ChoicesDropdownFilter),
|
||||
|
||||
@@ -3,7 +3,7 @@ System Module Admin
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from .models import AIPrompt, IntegrationSettings, AuthorProfile, Strategy
|
||||
|
||||
# Import settings admin
|
||||
@@ -16,7 +16,7 @@ try:
|
||||
from .models import SystemLog, SystemStatus
|
||||
|
||||
@admin.register(SystemLog)
|
||||
class SystemLogAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class SystemLogAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['id', 'account', 'module', 'level', 'action', 'message', 'created_at']
|
||||
list_filter = ['module', 'level', 'created_at', 'account']
|
||||
search_fields = ['message', 'action']
|
||||
@@ -25,7 +25,7 @@ try:
|
||||
|
||||
|
||||
@admin.register(SystemStatus)
|
||||
class SystemStatusAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class SystemStatusAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['component', 'account', 'status', 'message', 'last_check']
|
||||
list_filter = ['status', 'component', 'account']
|
||||
search_fields = ['component', 'message']
|
||||
@@ -35,7 +35,7 @@ except ImportError:
|
||||
|
||||
|
||||
@admin.register(AIPrompt)
|
||||
class AIPromptAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class AIPromptAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['id', 'prompt_type', 'account', 'is_active', 'updated_at']
|
||||
list_filter = ['prompt_type', 'is_active', 'account']
|
||||
search_fields = ['prompt_type']
|
||||
@@ -64,7 +64,7 @@ class AIPromptAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(IntegrationSettings)
|
||||
class IntegrationSettingsAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class IntegrationSettingsAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['id', 'integration_type', 'account', 'is_active', 'updated_at']
|
||||
list_filter = ['integration_type', 'is_active', 'account']
|
||||
search_fields = ['integration_type']
|
||||
@@ -100,7 +100,7 @@ class IntegrationSettingsAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(AuthorProfile)
|
||||
class AuthorProfileAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class AuthorProfileAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['name', 'account', 'tone', 'language', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'tone', 'language', 'account']
|
||||
search_fields = ['name', 'description', 'tone']
|
||||
@@ -129,7 +129,7 @@ class AuthorProfileAdmin(AccountAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Strategy)
|
||||
class StrategyAdmin(AccountAdminMixin, ModelAdmin):
|
||||
class StrategyAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['name', 'account', 'sector', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'account']
|
||||
search_fields = ['name', 'description']
|
||||
|
||||
@@ -7,7 +7,7 @@ from unfold.contrib.filters.admin import (
|
||||
RelatedDropdownFilter,
|
||||
ChoicesDropdownFilter,
|
||||
)
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin, Igny8ModelAdmin
|
||||
from .models import Tasks, Images, Content
|
||||
from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute, ContentTaxonomyRelation, ContentClusterMap
|
||||
from import_export.admin import ExportMixin
|
||||
@@ -33,7 +33,7 @@ class TaskResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Tasks)
|
||||
class TasksAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
class TasksAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = TaskResource
|
||||
list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'status', 'cluster', 'created_at']
|
||||
list_filter = [
|
||||
@@ -156,7 +156,7 @@ class TasksAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Images)
|
||||
class ImagesAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
class ImagesAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['get_content_title', 'site', 'sector', 'image_type', 'status', 'position', 'created_at']
|
||||
list_filter = ['image_type', 'status', 'site', 'sector']
|
||||
search_fields = ['content__title']
|
||||
@@ -198,7 +198,7 @@ class ContentResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Content)
|
||||
class ContentAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
class ContentAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = ContentResource
|
||||
list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'source', 'status', 'word_count', 'get_taxonomy_count', 'created_at']
|
||||
list_filter = [
|
||||
@@ -351,7 +351,7 @@ class ContentAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ContentTaxonomy)
|
||||
class ContentTaxonomyAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
class ContentTaxonomyAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['name', 'taxonomy_type', 'slug', 'count', 'external_id', 'external_taxonomy', 'site', 'sector']
|
||||
list_filter = ['taxonomy_type', 'site', 'sector']
|
||||
search_fields = ['name', 'slug', 'external_taxonomy']
|
||||
@@ -382,7 +382,7 @@ class ContentTaxonomyAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ContentAttribute)
|
||||
class ContentAttributeAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
class ContentAttributeAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['name', 'value', 'attribute_type', 'content', 'cluster', 'external_id', 'source', 'site', 'sector']
|
||||
list_filter = ['attribute_type', 'source', 'site', 'sector']
|
||||
search_fields = ['name', 'value', 'external_attribute_name', 'content__title']
|
||||
@@ -407,14 +407,14 @@ class ContentAttributeAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ContentTaxonomyRelation)
|
||||
class ContentTaxonomyRelationAdmin(ModelAdmin):
|
||||
class ContentTaxonomyRelationAdmin(Igny8ModelAdmin):
|
||||
list_display = ['content', 'taxonomy', 'created_at']
|
||||
search_fields = ['content__title', 'taxonomy__name']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
|
||||
@admin.register(ContentClusterMap)
|
||||
class ContentClusterMapAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
class ContentClusterMapAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_display = ['content', 'task', 'cluster', 'role', 'source', 'site', 'sector', 'created_at']
|
||||
list_filter = ['role', 'source', 'site', 'sector']
|
||||
search_fields = ['content__title', 'task__title', 'cluster__name']
|
||||
|
||||
@@ -652,7 +652,7 @@ UNFOLD = {
|
||||
},
|
||||
"SIDEBAR": {
|
||||
"show_search": True,
|
||||
"show_all_applications": True, # Let Unfold show our custom app_list
|
||||
"show_all_applications": False, # MUST be False - we provide custom sidebar_navigation
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
95
backend/igny8_core/templates/unfold/helpers/app_list.html
Normal file
95
backend/igny8_core/templates/unfold/helpers/app_list.html
Normal file
@@ -0,0 +1,95 @@
|
||||
{% include "unfold/helpers/app_list_debug.html" %}
|
||||
{% load i18n unfold %}
|
||||
|
||||
{% if sidebar_navigation %}
|
||||
<div class="h-0 grow overflow-auto" data-simplebar>
|
||||
{% for group in sidebar_navigation %}
|
||||
{% if group.items %}
|
||||
{% has_nav_item_active group.items as has_active %}
|
||||
|
||||
<div class="hidden mb-2 has-[ol]:has-[li]:block" {% if group.collapsible %}x-data="{navigationOpen: {% if has_active %}true{% else %}false{% endif %}}"{% endif %}>
|
||||
{% if group.separator %}
|
||||
<hr class="border-t border-base-200 mx-6 my-2 dark:border-base-800" />
|
||||
{% endif %}
|
||||
|
||||
{% if group.title %}
|
||||
<h2 class="font-semibold flex flex-row group items-center mb-1 mx-3 py-1.5 px-3 select-none text-font-important-light text-sm dark:text-font-important-dark {% if group.collapsible %}cursor-pointer hover:text-primary-600 dark:hover:text-primary-500{% endif %}" {% if group.collapsible %}x-on:click="navigationOpen = !navigationOpen"{% endif %}>
|
||||
{{ group.title }}
|
||||
|
||||
{% include "unfold/helpers/app_list_badge.html" with item=group %}
|
||||
|
||||
{% if group.collapsible %}
|
||||
<span class="material-symbols-outlined ml-auto transition-all group-hover:text-primary-600 dark:group-hover:text-primary-500" x-bind:class="{'rotate-90': navigationOpen}">
|
||||
chevron_right
|
||||
</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% endif %}
|
||||
|
||||
<ol class="flex flex-col gap-1 px-6" {% if group.collapsible %}x-show="navigationOpen"{% endif %}>
|
||||
{% for item in group.items %}
|
||||
{% if item.has_permission %}
|
||||
<li>
|
||||
<a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}" class="flex h-[38px] items-center -mx-3 px-3 rounded-default hover:text-primary-600 dark:hover:text-primary-500 {% if item.active %}bg-base-100 font-semibold text-primary-600 dark:bg-white/[.06] dark:text-primary-500{% endif %}">
|
||||
{% if item.icon %}
|
||||
<span class="material-symbols-outlined md-18 mr-3 w-[18px]">
|
||||
{{ item.icon }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<span>{{ item.title|safe }}</span>
|
||||
|
||||
{% include "unfold/helpers/app_list_badge.html" with item=item %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if sidebar_show_all_applications and app_list|length > 0 %}
|
||||
<div class="mt-auto" x-data="{ openAllApplications: false }">
|
||||
<a class="cursor-pointer flex items-center h-[64px] px-6 py-3 text-sm dark:text-font-default-dark hover:text-primary-600 dark:hover:text-primary-500" x-on:click="openAllApplications = !openAllApplications">
|
||||
<span class="material-symbols-outlined md-18 mr-3">
|
||||
apps
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{% trans "All applications" %}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div class="absolute bottom-0 left-0 right-0 top-0 z-50 md:left-72" x-cloak x-show="openAllApplications">
|
||||
<div class="absolute bg-base-900/80 backdrop-blur-xs bottom-0 left-0 right-0 top-0 z-10 w-screen"></div>
|
||||
|
||||
<div class="bg-white flex flex-col h-full overflow-x-hidden overflow-y-auto py-5 px-8 relative text-sm w-80 z-20 dark:bg-base-900 dark:border-r dark:border-base-800" x-on:click.outside="openAllApplications = false" x-on:keydown.escape.window="openAllApplications = false" data-simplebar>
|
||||
{% for app in app_list %}
|
||||
<div class="mb-6 last:mb-0">
|
||||
<h2 class="mb-4 font-semibold text-font-important-light truncate dark:text-font-important-dark">
|
||||
{{ app.name }}
|
||||
</h2>
|
||||
|
||||
<ul>
|
||||
{% for model in app.models %}
|
||||
<li class="block mb-4 last:mb-0">
|
||||
<a href="{{ model.admin_url }}" class="block truncate hover:text-primary-600 dark:hover:text-primary-500">
|
||||
{{ model.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>
|
||||
{% trans "You don’t have permission to view or edit anything." as error_message %}
|
||||
{% include "unfold/helpers/messages/error.html" with error=error_message %}
|
||||
</p>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- DEBUG: sidebar_navigation length = {{ sidebar_navigation|length }} -->
|
||||
<!-- DEBUG: First group = {{ sidebar_navigation.0.title|default:"NONE" }} -->
|
||||
{% if sidebar_navigation %}
|
||||
<!-- DEBUG: Has sidebar_navigation -->
|
||||
{% else %}
|
||||
<!-- DEBUG: NO sidebar_navigation -->
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user