sideabar fixed in dhjanog

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-14 23:43:10 +00:00
parent 78f71558ed
commit aa48a55504
21 changed files with 703 additions and 71 deletions

View File

@@ -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__ = []

View File

@@ -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)

View File

@@ -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):

View File

@@ -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',

View File

@@ -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)

View File

@@ -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')

View File

@@ -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',

View File

@@ -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',

View File

@@ -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']

View File

@@ -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',

View File

@@ -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',

View File

@@ -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),

View File

@@ -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']

View File

@@ -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']

View File

@@ -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
},
}

View 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 dont have permission to view or edit anything." as error_message %}
{% include "unfold/helpers/messages/error.html" with error=error_message %}
</p>
{% endif %}

View File

@@ -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 %}