From 31c06d032cb029b90bf001107046474fe9fa685e Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Sun, 7 Dec 2025 02:05:06 +0000 Subject: [PATCH] Add read-only admin functionality and enhance billing models in admin interface --- backend/igny8_core/admin/apps.py | 35 +++++++++++ backend/igny8_core/admin/site.py | 43 +++++++++++++ backend/igny8_core/business/billing/admin.py | 60 ++++++++++++++++++- .../igny8_core/business/integration/admin.py | 36 +++++++++++ .../igny8_core/business/optimization/admin.py | 12 ++++ .../igny8_core/business/publishing/admin.py | 36 +++++++++++ backend/igny8_core/modules/billing/admin.py | 58 +++++++++++++++++- .../modules/system/settings_admin.py | 17 ++++++ backend/igny8_core/modules/writer/admin.py | 17 +++++- 9 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 backend/igny8_core/business/integration/admin.py create mode 100644 backend/igny8_core/business/optimization/admin.py create mode 100644 backend/igny8_core/business/publishing/admin.py diff --git a/backend/igny8_core/admin/apps.py b/backend/igny8_core/admin/apps.py index 15d3f4cd..aa4b19d3 100644 --- a/backend/igny8_core/admin/apps.py +++ b/backend/igny8_core/admin/apps.py @@ -1,8 +1,43 @@ +from django.contrib import admin from django.contrib.admin.apps import AdminConfig +class ReadOnlyAdmin(admin.ModelAdmin): + """Generic read-only admin for system tables.""" + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + +def _safe_register(model, model_admin): + try: + admin.site.register(model, model_admin) + except admin.sites.AlreadyRegistered: + pass + + class Igny8AdminConfig(AdminConfig): default_site = 'igny8_core.admin.site.Igny8AdminSite' name = 'django.contrib.admin' + def ready(self): + super().ready() + # Register Django internals in admin (read-only where appropriate) + from django.contrib.admin.models import LogEntry + from django.contrib.auth.models import Group, Permission + from django.contrib.contenttypes.models import ContentType + from django.contrib.sessions.models import Session + + _safe_register(LogEntry, ReadOnlyAdmin) + _safe_register(Permission, admin.ModelAdmin) + _safe_register(Group, admin.ModelAdmin) + _safe_register(ContentType, ReadOnlyAdmin) + _safe_register(Session, ReadOnlyAdmin) + diff --git a/backend/igny8_core/admin/site.py b/backend/igny8_core/admin/site.py index 329c1a4d..63db99d2 100644 --- a/backend/igny8_core/admin/site.py +++ b/backend/igny8_core/admin/site.py @@ -37,6 +37,12 @@ class Igny8AdminSite(admin.AdminSite): ('igny8_core_auth', 'Subscription'), ('billing', 'CreditTransaction'), ('billing', 'CreditUsageLog'), + ('billing', 'Invoice'), + ('billing', 'Payment'), + ('billing', 'CreditPackage'), + ('billing', 'PaymentMethodConfig'), + ('billing', 'AccountPaymentMethod'), + ('billing', 'CreditCostConfig'), ], }, 'Sites & Users': { @@ -45,6 +51,7 @@ class Igny8AdminSite(admin.AdminSite): ('igny8_core_auth', 'User'), ('igny8_core_auth', 'SiteUserAccess'), ('igny8_core_auth', 'PasswordResetToken'), + ('igny8_core_auth', 'Sector'), ], }, 'Global Reference Data': { @@ -70,6 +77,10 @@ class Igny8AdminSite(admin.AdminSite): ('writer', 'Tasks'), ('writer', 'Content'), ('writer', 'Images'), + ('writer', 'ContentTaxonomy'), + ('writer', 'ContentAttribute'), + ('writer', 'ContentTaxonomyRelation'), + ('writer', 'ContentClusterMap'), ], }, 'Thinker Module': { @@ -77,6 +88,7 @@ class Igny8AdminSite(admin.AdminSite): ('system', 'AIPrompt'), ('system', 'AuthorProfile'), ('system', 'Strategy'), + ('ai', 'AITaskLog'), ], }, 'System Configuration': { @@ -89,11 +101,38 @@ class Igny8AdminSite(admin.AdminSite): ('system', 'UserSettings'), ('system', 'ModuleSettings'), ('system', 'AISettings'), + ('system', 'ModuleEnableSettings'), # Automation config lives under the automation app - include here ('automation', 'AutomationConfig'), ('automation', 'AutomationRun'), ], }, + 'Integrations & Sync': { + 'models': [ + ('integration', 'SiteIntegration'), + ('integration', 'SyncEvent'), + ], + }, + 'Publishing': { + 'models': [ + ('publishing', 'PublishingRecord'), + ('publishing', 'DeploymentRecord'), + ], + }, + 'Optimization': { + 'models': [ + ('optimization', 'OptimizationTask'), + ], + }, + 'Django Internals': { + 'models': [ + ('admin', 'LogEntry'), + ('auth', 'Group'), + ('auth', 'Permission'), + ('contenttypes', 'ContentType'), + ('sessions', 'Session'), + ], + }, } # Build the custom app list @@ -131,6 +170,10 @@ class Igny8AdminSite(admin.AdminSite): 'Writer Module', 'Thinker Module', 'System Configuration', + 'Integrations & Sync', + 'Publishing', + 'Optimization', + 'Django Internals', ] app_list.sort(key=lambda x: order.index(x['name']) if x['name'] in order else 999) diff --git a/backend/igny8_core/business/billing/admin.py b/backend/igny8_core/business/billing/admin.py index 85c16104..2ba80ebf 100644 --- a/backend/igny8_core/business/billing/admin.py +++ b/backend/igny8_core/business/billing/admin.py @@ -3,7 +3,15 @@ Billing Business Logic Admin """ from django.contrib import admin from django.utils.html import format_html -from .models import CreditCostConfig, AccountPaymentMethod +from igny8_core.admin.base import AccountAdminMixin +from .models import ( + CreditCostConfig, + AccountPaymentMethod, + Invoice, + Payment, + CreditPackage, + PaymentMethodConfig, +) @admin.register(CreditCostConfig) @@ -81,6 +89,56 @@ class CreditCostConfigAdmin(admin.ModelAdmin): super().save_model(request, obj, form, change) +@admin.register(Invoice) +class InvoiceAdmin(AccountAdminMixin, admin.ModelAdmin): + list_display = [ + 'invoice_number', + 'account', + 'status', + 'total', + 'currency', + 'invoice_date', + 'due_date', + 'subscription', + ] + list_filter = ['status', 'currency', 'invoice_date', 'account'] + search_fields = ['invoice_number', 'account__name', 'subscription__id'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(Payment) +class PaymentAdmin(AccountAdminMixin, admin.ModelAdmin): + list_display = [ + 'id', + 'invoice', + 'account', + 'payment_method', + 'status', + 'amount', + 'currency', + 'processed_at', + ] + list_filter = ['status', 'payment_method', 'currency', 'created_at'] + search_fields = ['invoice__invoice_number', 'account__name', 'stripe_payment_intent_id', 'paypal_order_id'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(CreditPackage) +class CreditPackageAdmin(admin.ModelAdmin): + list_display = ['name', 'slug', 'credits', 'price', 'discount_percentage', 'is_active', 'is_featured', 'sort_order'] + list_filter = ['is_active', 'is_featured'] + search_fields = ['name', 'slug'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(PaymentMethodConfig) +class PaymentMethodConfigAdmin(admin.ModelAdmin): + list_display = ['country_code', 'payment_method', 'is_enabled', 'display_name', 'sort_order'] + list_filter = ['payment_method', 'is_enabled', 'country_code'] + search_fields = ['country_code', 'display_name', 'payment_method'] + readonly_fields = ['created_at', 'updated_at'] + + @admin.register(AccountPaymentMethod) class AccountPaymentMethodAdmin(admin.ModelAdmin): list_display = [ diff --git a/backend/igny8_core/business/integration/admin.py b/backend/igny8_core/business/integration/admin.py new file mode 100644 index 00000000..5b2a9ac1 --- /dev/null +++ b/backend/igny8_core/business/integration/admin.py @@ -0,0 +1,36 @@ +from django.contrib import admin +from igny8_core.admin.base import AccountAdminMixin +from .models import SiteIntegration, SyncEvent + + +@admin.register(SiteIntegration) +class SiteIntegrationAdmin(AccountAdminMixin, admin.ModelAdmin): + list_display = [ + 'site', + 'platform', + 'platform_type', + 'is_active', + 'sync_enabled', + 'sync_status', + 'last_sync_at', + ] + list_filter = ['platform', 'platform_type', 'is_active', 'sync_enabled', 'sync_status'] + search_fields = ['site__name', 'site__domain', 'platform'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(SyncEvent) +class SyncEventAdmin(AccountAdminMixin, admin.ModelAdmin): + list_display = [ + 'integration', + 'site', + 'event_type', + 'action', + 'success', + 'external_id', + 'created_at', + ] + list_filter = ['event_type', 'action', 'success', 'created_at'] + search_fields = ['integration__site__name', 'site__name', 'description', 'external_id'] + readonly_fields = ['created_at'] + diff --git a/backend/igny8_core/business/optimization/admin.py b/backend/igny8_core/business/optimization/admin.py new file mode 100644 index 00000000..a9837f36 --- /dev/null +++ b/backend/igny8_core/business/optimization/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from igny8_core.admin.base import AccountAdminMixin +from .models import OptimizationTask + + +@admin.register(OptimizationTask) +class OptimizationTaskAdmin(AccountAdminMixin, admin.ModelAdmin): + list_display = ['content', 'account', 'status', 'credits_used', 'created_at'] + list_filter = ['status', 'created_at'] + search_fields = ['content__title', 'account__name'] + readonly_fields = ['created_at', 'updated_at'] + diff --git a/backend/igny8_core/business/publishing/admin.py b/backend/igny8_core/business/publishing/admin.py new file mode 100644 index 00000000..fd1ad64a --- /dev/null +++ b/backend/igny8_core/business/publishing/admin.py @@ -0,0 +1,36 @@ +from django.contrib import admin +from igny8_core.admin.base import SiteSectorAdminMixin +from .models import PublishingRecord, DeploymentRecord + + +@admin.register(PublishingRecord) +class PublishingRecordAdmin(SiteSectorAdminMixin, admin.ModelAdmin): + list_display = [ + 'content', + 'site', + 'sector', + 'destination', + 'status', + 'destination_url', + 'published_at', + ] + list_filter = ['destination', 'status', 'site'] + search_fields = ['content__title', 'destination', 'destination_url'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(DeploymentRecord) +class DeploymentRecordAdmin(SiteSectorAdminMixin, admin.ModelAdmin): + list_display = [ + 'site', + 'sector', + 'version', + 'deployed_version', + 'status', + 'deployment_url', + 'deployed_at', + ] + list_filter = ['status', 'site'] + search_fields = ['site__name', 'deployment_url'] + readonly_fields = ['created_at', 'updated_at'] + diff --git a/backend/igny8_core/modules/billing/admin.py b/backend/igny8_core/modules/billing/admin.py index 110edd8a..08dd41f7 100644 --- a/backend/igny8_core/modules/billing/admin.py +++ b/backend/igny8_core/modules/billing/admin.py @@ -4,7 +4,13 @@ Billing Module Admin from django.contrib import admin from django.utils.html import format_html from igny8_core.admin.base import AccountAdminMixin -from igny8_core.business.billing.models import CreditCostConfig +from igny8_core.business.billing.models import ( + CreditCostConfig, + Invoice, + Payment, + CreditPackage, + PaymentMethodConfig, +) from .models import CreditTransaction, CreditUsageLog, AccountPaymentMethod @@ -44,6 +50,56 @@ class CreditUsageLogAdmin(AccountAdminMixin, admin.ModelAdmin): get_account_display.short_description = 'Account' +@admin.register(Invoice) +class InvoiceAdmin(AccountAdminMixin, admin.ModelAdmin): + list_display = [ + 'invoice_number', + 'account', + 'status', + 'total', + 'currency', + 'invoice_date', + 'due_date', + 'subscription', + ] + list_filter = ['status', 'currency', 'invoice_date', 'account'] + search_fields = ['invoice_number', 'account__name', 'subscription__id'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(Payment) +class PaymentAdmin(AccountAdminMixin, admin.ModelAdmin): + list_display = [ + 'id', + 'invoice', + 'account', + 'payment_method', + 'status', + 'amount', + 'currency', + 'processed_at', + ] + list_filter = ['status', 'payment_method', 'currency', 'created_at'] + search_fields = ['invoice__invoice_number', 'account__name', 'stripe_payment_intent_id', 'paypal_order_id'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(CreditPackage) +class CreditPackageAdmin(admin.ModelAdmin): + list_display = ['name', 'slug', 'credits', 'price', 'discount_percentage', 'is_active', 'is_featured', 'sort_order'] + list_filter = ['is_active', 'is_featured'] + search_fields = ['name', 'slug'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(PaymentMethodConfig) +class PaymentMethodConfigAdmin(admin.ModelAdmin): + list_display = ['country_code', 'payment_method', 'is_enabled', 'display_name', 'sort_order'] + list_filter = ['payment_method', 'is_enabled', 'country_code'] + search_fields = ['country_code', 'display_name', 'payment_method'] + readonly_fields = ['created_at', 'updated_at'] + + @admin.register(AccountPaymentMethod) class AccountPaymentMethodAdmin(AccountAdminMixin, admin.ModelAdmin): list_display = [ diff --git a/backend/igny8_core/modules/system/settings_admin.py b/backend/igny8_core/modules/system/settings_admin.py index bf5ffeea..059e16d3 100644 --- a/backend/igny8_core/modules/system/settings_admin.py +++ b/backend/igny8_core/modules/system/settings_admin.py @@ -91,3 +91,20 @@ class AISettingsAdmin(AccountAdminMixin, admin.ModelAdmin): return '-' get_account_display.short_description = 'Account' + +@admin.register(ModuleEnableSettings) +class ModuleEnableSettingsAdmin(AccountAdminMixin, admin.ModelAdmin): + list_display = [ + 'account', + 'planner_enabled', + 'writer_enabled', + 'thinker_enabled', + 'automation_enabled', + 'site_builder_enabled', + 'linker_enabled', + 'optimizer_enabled', + 'publisher_enabled', + ] + list_filter = ['planner_enabled', 'writer_enabled', 'thinker_enabled', 'automation_enabled', 'site_builder_enabled', 'linker_enabled', 'optimizer_enabled', 'publisher_enabled'] + search_fields = ['account__name'] + diff --git a/backend/igny8_core/modules/writer/admin.py b/backend/igny8_core/modules/writer/admin.py index 8182ac60..ce17d241 100644 --- a/backend/igny8_core/modules/writer/admin.py +++ b/backend/igny8_core/modules/writer/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from igny8_core.admin.base import SiteSectorAdminMixin from .models import Tasks, Images, Content -from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute, ContentTaxonomyRelation +from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute, ContentTaxonomyRelation, ContentClusterMap class ContentTaxonomyInline(admin.TabularInline): @@ -223,3 +223,18 @@ class ContentAttributeAdmin(SiteSectorAdminMixin, admin.ModelAdmin): qs = super().get_queryset(request) return qs.select_related('content', 'cluster', 'site', 'sector') + +@admin.register(ContentTaxonomyRelation) +class ContentTaxonomyRelationAdmin(admin.ModelAdmin): + list_display = ['content', 'taxonomy', 'created_at'] + search_fields = ['content__title', 'taxonomy__name'] + readonly_fields = ['created_at', 'updated_at'] + + +@admin.register(ContentClusterMap) +class ContentClusterMapAdmin(SiteSectorAdminMixin, admin.ModelAdmin): + 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'] + readonly_fields = ['created_at', 'updated_at'] +