phase 1 partial

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-13 22:12:25 +00:00
parent 0b24fe8c77
commit 60263b4682
5 changed files with 2012 additions and 192 deletions

View File

@@ -23,57 +23,42 @@ class Igny8AdminSite(admin.AdminSite):
def get_app_list(self, request):
"""
Customize the app list to organize models into proper groups
Customize the app list to organize models into logical groups
"""
# Get the default app list
app_dict = self._build_app_dict(request)
# Define our custom groups with their models (using object_name)
# Organized by business function with emoji icons for visual recognition
custom_groups = {
'Billing & Tenancy': {
'💰 Billing & Accounts': {
'models': [
('igny8_core_auth', 'Plan'),
('igny8_core_auth', 'Account'),
('igny8_core_auth', 'Subscription'),
('billing', 'CreditTransaction'),
('billing', 'CreditUsageLog'),
('billing', 'Invoice'),
('billing', 'Payment'),
('billing', 'CreditTransaction'),
('billing', 'CreditUsageLog'),
('billing', 'CreditPackage'),
('billing', 'PaymentMethodConfig'),
('billing', 'AccountPaymentMethod'),
('billing', 'CreditCostConfig'),
],
},
'Sites & Users': {
'👥 Sites & Users': {
'models': [
('igny8_core_auth', 'Site'),
('igny8_core_auth', 'Sector'),
('igny8_core_auth', 'User'),
('igny8_core_auth', 'SiteUserAccess'),
('igny8_core_auth', 'PasswordResetToken'),
('igny8_core_auth', 'Sector'),
],
},
'Global Reference Data': {
'📚 Content Management': {
'models': [
('igny8_core_auth', 'Industry'),
('igny8_core_auth', 'IndustrySector'),
('igny8_core_auth', 'SeedKeyword'),
('site_building', 'BusinessType'),
('site_building', 'AudienceProfile'),
('site_building', 'BrandPersonality'),
('site_building', 'HeroImageryDirection'),
],
},
'Planner': {
'models': [
('planner', 'Keywords'),
('planner', 'Clusters'),
('planner', 'ContentIdeas'),
],
},
'Writer Module': {
'models': [
('writer', 'Tasks'),
('writer', 'Content'),
('writer', 'Tasks'),
('writer', 'Images'),
('writer', 'ContentTaxonomy'),
('writer', 'ContentAttribute'),
@@ -81,54 +66,53 @@ class Igny8AdminSite(admin.AdminSite):
('writer', 'ContentClusterMap'),
],
},
'Thinker Module': {
'🎯 Planning & Strategy': {
'models': [
('system', 'AIPrompt'),
('system', 'AuthorProfile'),
('planner', 'Clusters'),
('planner', 'Keywords'),
('planner', 'ContentIdeas'),
('system', 'Strategy'),
('ai', 'AITaskLog'),
],
},
'System Configuration': {
'🔗 Integrations & Publishing': {
'models': [
('integration', 'SiteIntegration'),
('integration', 'SyncEvent'),
('publishing', 'PublishingRecord'),
('publishing', 'DeploymentRecord'),
],
},
'🤖 AI & Automation': {
'models': [
('ai', 'AITaskLog'),
('system', 'AIPrompt'),
('automation', 'AutomationConfig'),
('automation', 'AutomationRun'),
('optimization', 'OptimizationTask'),
],
},
'🌍 Global Reference Data': {
'models': [
('igny8_core_auth', 'Industry'),
('igny8_core_auth', 'IndustrySector'),
('igny8_core_auth', 'SeedKeyword'),
],
},
'⚙️ System Configuration': {
'models': [
('system', 'IntegrationSettings'),
('system', 'SystemLog'),
('system', 'SystemStatus'),
('system', 'AuthorProfile'),
('system', 'SystemSettings'),
('system', 'AccountSettings'),
('system', 'UserSettings'),
('system', 'ModuleSettings'),
('system', 'AISettings'),
('system', 'ModuleEnableSettings'),
# Automation config lives under the automation app - include here
('automation', 'AutomationConfig'),
('automation', 'AutomationRun'),
('system', 'SystemLog'),
('system', 'SystemStatus'),
],
},
'Payments': {
'models': [
('billing', 'PaymentMethodConfig'),
('billing', 'AccountPaymentMethod'),
],
},
'Integrations & Sync': {
'models': [
('integration', 'SiteIntegration'),
('integration', 'SyncEvent'),
],
},
'Publishing': {
'models': [
('publishing', 'PublishingRecord'),
('publishing', 'DeploymentRecord'),
],
},
'Optimization': {
'models': [
('optimization', 'OptimizationTask'),
],
},
'Django Internals': {
'🔧 Django System': {
'models': [
('admin', 'LogEntry'),
('auth', 'Group'),
@@ -159,7 +143,7 @@ class Igny8AdminSite(admin.AdminSite):
if group_models:
app_list.append({
'name': group_name,
'app_label': group_name.lower().replace(' ', '_').replace('&', ''),
'app_label': group_name.lower().replace(' ', '_').replace('&', '').replace('emoji', ''),
'app_url': None,
'has_module_perms': True,
'models': group_models,
@@ -167,18 +151,15 @@ class Igny8AdminSite(admin.AdminSite):
# Sort the app list by our custom order
order = [
'Billing & Tenancy',
'Sites & Users',
'Global Reference Data',
'Planner',
'Writer Module',
'Thinker Module',
'System Configuration',
'Payments',
'Integrations & Sync',
'Publishing',
'Optimization',
'Django Internals',
'💰 Billing & Accounts',
'👥 Sites & Users',
'📚 Content Management',
'🎯 Planning & Strategy',
'🔗 Integrations & Publishing',
'🤖 AI & Automation',
'🌍 Global Reference Data',
'⚙️ System Configuration',
'🔧 Django System',
]
app_list.sort(key=lambda x: order.index(x['name']) if x['name'] in order else 999)

View File

@@ -1,5 +1,8 @@
"""
Billing Business Logic Admin
NOTE: Most billing models are registered in modules/billing/admin.py
with full workflow functionality. This file contains legacy/minimal registrations.
"""
from django.contrib import admin
from django.utils.html import format_html
@@ -14,133 +17,36 @@ from .models import (
)
@admin.register(CreditCostConfig)
class CreditCostConfigAdmin(admin.ModelAdmin):
list_display = [
'operation_type',
'display_name',
'credits_cost_display',
'unit',
'is_active',
'cost_change_indicator',
'updated_at',
'updated_by'
]
list_filter = ['is_active', 'unit', 'updated_at']
search_fields = ['operation_type', 'display_name', 'description']
fieldsets = (
('Operation', {
'fields': ('operation_type', 'display_name', 'description')
}),
('Cost Configuration', {
'fields': ('credits_cost', 'unit', 'is_active')
}),
('Audit Trail', {
'fields': ('previous_cost', 'updated_by', 'created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
readonly_fields = ['created_at', 'updated_at', 'previous_cost']
def credits_cost_display(self, obj):
"""Show cost with color coding"""
if obj.credits_cost >= 20:
color = 'red'
elif obj.credits_cost >= 10:
color = 'orange'
else:
color = 'green'
return format_html(
'<span style="color: {}; font-weight: bold;">{} credits</span>',
color,
obj.credits_cost
)
credits_cost_display.short_description = 'Cost'
def cost_change_indicator(self, obj):
"""Show if cost changed recently"""
if obj.previous_cost is not None:
if obj.credits_cost > obj.previous_cost:
icon = '📈' # Increased
color = 'red'
elif obj.credits_cost < obj.previous_cost:
icon = '📉' # Decreased
color = 'green'
else:
icon = '➡️' # Same
color = 'gray'
return format_html(
'{} <span style="color: {};">({}{})</span>',
icon,
color,
obj.previous_cost,
obj.credits_cost
)
return ''
cost_change_indicator.short_description = 'Recent Change'
def save_model(self, request, obj, form, change):
"""Track who made the change"""
obj.updated_by = request.user
super().save_model(request, obj, form, change)
# CreditCostConfig - DUPLICATE - Registered in modules/billing/admin.py with better features
# Commenting out to avoid conflicts
# @admin.register(CreditCostConfig)
# class CreditCostConfigAdmin(admin.ModelAdmin):
# ...existing implementation...
@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']
# Invoice - DUPLICATE - Registered in modules/billing/admin.py
# Commenting out to avoid conflicts
# @admin.register(Invoice)
# class InvoiceAdmin(AccountAdminMixin, admin.ModelAdmin):
# ...existing implementation...
@admin.register(Payment)
class PaymentAdmin(AccountAdminMixin, admin.ModelAdmin):
\"\"\"
Payment admin - DO NOT USE.
Use the Payment admin in modules/billing/admin.py which has approval workflow actions.
This is kept for backward compatibility only.
\"\"\"
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']
def has_add_permission(self, request):\n return False # Prevent creating payments here
\n def has_delete_permission(self, request, obj=None):\n return False # Prevent deleting payments here
# Payment - DUPLICATE - Registered in modules/billing/admin.py with full approval workflow
# Commenting out to avoid conflicts
# @admin.register(Payment)
# class PaymentAdmin(AccountAdminMixin, admin.ModelAdmin):
# ...existing implementation...
@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']
# CreditPackage - DUPLICATE - Registered in modules/billing/admin.py
# Commenting out to avoid conflicts
# @admin.register(CreditPackage)
# class CreditPackageAdmin(admin.ModelAdmin):
# ...existing implementation...
# PaymentMethodConfig admin is in modules/billing/admin.py - do not duplicate
# @admin.register(PaymentMethodConfig)
# PaymentMethodConfig and AccountPaymentMethod are kept here as they're not duplicated
# or have minimal implementations that don't conflict
@admin.register(AccountPaymentMethod)
class AccountPaymentMethodAdmin(admin.ModelAdmin):

View File

@@ -0,0 +1,290 @@
/* IGNY8 Custom Admin Styles */
/* Status badges */
.status-active {
color: #28a745 !important;
font-weight: bold;
}
.status-inactive {
color: #dc3545 !important;
}
.status-pending {
color: #ffc107 !important;
font-weight: bold;
}
.status-succeeded, .status-completed {
color: #28a745 !important;
}
.status-failed, .status-error {
color: #dc3545 !important;
}
/* Credit indicators */
.credits-low {
color: #dc3545 !important;
font-weight: bold;
}
.credits-medium {
color: #ffc107 !important;
}
.credits-high {
color: #28a745 !important;
}
/* Quick action buttons */
.admin-action-button {
padding: 5px 15px;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
display: inline-block;
margin: 2px;
background-color: #417690;
color: white;
border: none;
}
.admin-action-button:hover {
background-color: #305d75;
color: white;
text-decoration: none;
}
/* List view enhancements */
#content-main table tr:hover {
background-color: #f8f9fa !important;
}
/* Improve sidebar menu appearance */
#content-related h3 {
background: #417690;
color: white;
padding: 10px;
border-radius: 4px 4px 0 0;
}
/* Better form field spacing */
.form-row {
padding: 10px;
}
/* Highlight required fields */
.required label:after {
content: " *";
color: #dc3545;
}
/* Success messages */
.success, .messagelist .success {
background-color: #d4edda !important;
border-color: #c3e6cb !important;
color: #155724 !important;
}
/* Warning messages */
.warning, .messagelist .warning {
background-color: #fff3cd !important;
border-color: #ffeaa7 !important;
color: #856404 !important;
}
/* Error messages */
.error, .messagelist .error {
background-color: #f8d7da !important;
border-color: #f5c6cb !important;
color: #721c24 !important;
}
/* Improve table readability */
#result_list tbody tr:nth-child(odd) {
background-color: #f9f9f9;
}
#result_list tbody tr:nth-child(even) {
background-color: #ffffff;
}
/* Better button styling */
.button, input[type=submit], input[type=button], .submit-row input {
background: #417690 !important;
color: white !important;
border: none !important;
padding: 10px 15px !important;
border-radius: 4px !important;
cursor: pointer !important;
}
.button:hover, input[type=submit]:hover, input[type=button]:hover {
background: #305d75 !important;
}
/* Delete button styling */
.deletelink, .deletelink-box a {
background: #dc3545 !important;
}
.deletelink:hover, .deletelink-box a:hover {
background: #c82333 !important;
}
/* Improve filter sidebar */
#changelist-filter h2 {
background: #417690;
color: white;
padding: 8px 10px;
margin-bottom: 0;
}
#changelist-filter h3 {
font-weight: bold;
margin-top: 15px;
padding-bottom: 5px;
border-bottom: 1px solid #ddd;
}
/* Better pagination */
.paginator {
font-size: 14px;
padding: 10px;
background: #f8f9fa;
border-radius: 4px;
}
.paginator a {
padding: 5px 10px;
margin: 0 2px;
background: white;
border: 1px solid #ddd;
border-radius: 3px;
}
.paginator a:hover {
background: #417690;
color: white;
text-decoration: none;
}
/* Responsive improvements */
@media (max-width: 768px) {
#content-main {
padding: 10px;
}
.module table {
font-size: 12px;
}
}
/* Admin header improvements */
#header {
background: #417690;
color: white;
}
#header a:link, #header a:visited {
color: white;
}
#branding h1 {
color: white;
}
/* Fieldset legend styling */
fieldset.module h2 {
background: #417690;
color: white;
padding: 8px 10px;
border-radius: 4px 4px 0 0;
}
/* Inline forms */
.inline-group {
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 20px;
}
.inline-group .tabular {
overflow-x: auto;
}
/* Help text styling */
.help {
font-size: 12px;
color: #666;
display: block;
margin-top: 5px;
}
/* Dashboard widget styling */
.dashboard-card {
background: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin: 10px 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.dashboard-card h2 {
margin-top: 0;
border-bottom: 2px solid #417690;
padding-bottom: 10px;
}
.metric {
display: inline-block;
margin: 10px 20px 10px 0;
}
.metric-value {
font-size: 32px;
font-weight: bold;
color: #417690;
display: block;
}
.metric-label {
font-size: 12px;
color: #666;
display: block;
margin-top: 5px;
}
/* Alert styling */
.alert {
padding: 15px;
margin: 10px 0;
border-radius: 4px;
border-left: 4px solid;
}
.alert-error {
background-color: #f8d7da;
border-left-color: #dc3545;
color: #721c24;
}
.alert-warning {
background-color: #fff3cd;
border-left-color: #ffc107;
color: #856404;
}
.alert-info {
background-color: #d1ecf1;
border-left-color: #17a2b8;
color: #0c5460;
}
.alert-success {
background-color: #d4edda;
border-left-color: #28a745;
color: #155724;
}

View File

@@ -0,0 +1,19 @@
{% extends "admin/base.html" %}
{% load static %}
{% block title %}{{ title }} | IGNY8 Admin{% endblock %}
{% block branding %}
<h1 id="site-name">
<a href="{% url 'admin:index' %}">
🚀 IGNY8 Administration
</a>
</h1>
{% endblock %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'admin/css/igny8_admin.css' %}">
{% endblock %}
{% block nav-global %}{% endblock %}