353 lines
13 KiB
Python
353 lines
13 KiB
Python
"""
|
|
System Module Admin
|
|
"""
|
|
from django.contrib import admin
|
|
from unfold.admin import ModelAdmin
|
|
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
|
from .models import AIPrompt, IntegrationSettings, AuthorProfile, Strategy
|
|
from .global_settings_models import GlobalModuleSettings
|
|
|
|
from django.contrib import messages
|
|
from import_export.admin import ExportMixin, ImportExportMixin
|
|
from import_export import resources
|
|
|
|
|
|
class AIPromptResource(resources.ModelResource):
|
|
"""Resource class for importing/exporting AI Prompts"""
|
|
class Meta:
|
|
model = AIPrompt
|
|
fields = ('id', 'account__name', 'prompt_type', 'prompt_value', 'is_active', 'created_at')
|
|
export_order = fields
|
|
import_id_fields = ('id',)
|
|
skip_unchanged = True
|
|
|
|
|
|
# Import settings admin
|
|
from .settings_admin import (
|
|
SystemSettingsAdmin, AccountSettingsAdmin, UserSettingsAdmin,
|
|
ModuleSettingsAdmin, AISettingsAdmin
|
|
)
|
|
|
|
try:
|
|
from .models import SystemLog, SystemStatus
|
|
|
|
@admin.register(SystemLog)
|
|
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']
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
date_hierarchy = 'created_at'
|
|
|
|
|
|
@admin.register(SystemStatus)
|
|
class SystemStatusAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
|
list_display = ['component', 'account', 'status', 'message', 'last_check']
|
|
list_filter = ['status', 'component', 'account']
|
|
search_fields = ['component', 'message']
|
|
readonly_fields = ['last_check']
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
@admin.register(AIPrompt)
|
|
class AIPromptAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
|
resource_class = AIPromptResource
|
|
list_display = ['id', 'prompt_type', 'account', 'is_active', 'updated_at']
|
|
list_filter = ['prompt_type', 'is_active', 'account']
|
|
search_fields = ['prompt_type']
|
|
readonly_fields = ['created_at', 'updated_at', 'default_prompt']
|
|
actions = [
|
|
'bulk_activate',
|
|
'bulk_deactivate',
|
|
'bulk_reset_to_default',
|
|
]
|
|
|
|
fieldsets = (
|
|
('Basic Info', {
|
|
'fields': ('account', 'prompt_type', 'is_active')
|
|
}),
|
|
('Prompt Content', {
|
|
'fields': ('prompt_value', 'default_prompt')
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ('created_at', 'updated_at')
|
|
}),
|
|
)
|
|
|
|
def get_account_display(self, obj):
|
|
"""Safely get account name"""
|
|
try:
|
|
account = getattr(obj, 'account', None)
|
|
return account.name if account else '-'
|
|
except:
|
|
return '-'
|
|
get_account_display.short_description = 'Account'
|
|
|
|
def bulk_activate(self, request, queryset):
|
|
updated = queryset.update(is_active=True)
|
|
self.message_user(request, f'{updated} AI prompt(s) activated.', messages.SUCCESS)
|
|
bulk_activate.short_description = 'Activate selected prompts'
|
|
|
|
def bulk_deactivate(self, request, queryset):
|
|
updated = queryset.update(is_active=False)
|
|
self.message_user(request, f'{updated} AI prompt(s) deactivated.', messages.SUCCESS)
|
|
bulk_deactivate.short_description = 'Deactivate selected prompts'
|
|
|
|
def bulk_reset_to_default(self, request, queryset):
|
|
count = 0
|
|
for prompt in queryset:
|
|
if prompt.default_prompt:
|
|
prompt.prompt_value = prompt.default_prompt
|
|
prompt.save()
|
|
count += 1
|
|
self.message_user(request, f'{count} AI prompt(s) reset to default values.', messages.SUCCESS)
|
|
bulk_reset_to_default.short_description = 'Reset to default values'
|
|
|
|
|
|
class IntegrationSettingsResource(resources.ModelResource):
|
|
"""Resource class for exporting Integration Settings (config masked)"""
|
|
class Meta:
|
|
model = IntegrationSettings
|
|
fields = ('id', 'account__name', 'integration_type', 'is_active', 'created_at')
|
|
export_order = fields
|
|
|
|
|
|
@admin.register(IntegrationSettings)
|
|
class IntegrationSettingsAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
|
resource_class = IntegrationSettingsResource
|
|
list_display = ['id', 'integration_type', 'account', 'is_active', 'updated_at']
|
|
list_filter = ['integration_type', 'is_active', 'account']
|
|
search_fields = ['integration_type']
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
actions = [
|
|
'bulk_activate',
|
|
'bulk_deactivate',
|
|
'bulk_test_connection',
|
|
]
|
|
|
|
fieldsets = (
|
|
('Basic Info', {
|
|
'fields': ('account', 'integration_type', 'is_active')
|
|
}),
|
|
('Configuration', {
|
|
'fields': ('config',),
|
|
'description': 'JSON configuration containing API keys and settings. Example: {"apiKey": "sk-...", "model": "gpt-4.1", "enabled": true}'
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ('created_at', 'updated_at')
|
|
}),
|
|
)
|
|
|
|
def get_readonly_fields(self, request, obj=None):
|
|
"""Make config readonly when viewing to prevent accidental exposure"""
|
|
if obj: # Editing existing object
|
|
return self.readonly_fields + ['config']
|
|
return self.readonly_fields
|
|
|
|
def get_account_display(self, obj):
|
|
"""Safely get account name"""
|
|
try:
|
|
account = getattr(obj, 'account', None)
|
|
return account.name if account else '-'
|
|
except:
|
|
return '-'
|
|
get_account_display.short_description = 'Account'
|
|
|
|
def bulk_activate(self, request, queryset):
|
|
updated = queryset.update(is_active=True)
|
|
self.message_user(request, f'{updated} integration setting(s) activated.', messages.SUCCESS)
|
|
bulk_activate.short_description = 'Activate selected integrations'
|
|
|
|
def bulk_deactivate(self, request, queryset):
|
|
updated = queryset.update(is_active=False)
|
|
self.message_user(request, f'{updated} integration setting(s) deactivated.', messages.SUCCESS)
|
|
bulk_deactivate.short_description = 'Deactivate selected integrations'
|
|
|
|
def bulk_test_connection(self, request, queryset):
|
|
"""Test connection for selected integration settings"""
|
|
count = queryset.filter(is_active=True).count()
|
|
self.message_user(
|
|
request,
|
|
f'{count} integration(s) queued for connection test. (Test logic to be implemented)',
|
|
messages.INFO
|
|
)
|
|
bulk_test_connection.short_description = 'Test connections'
|
|
|
|
|
|
class AuthorProfileResource(resources.ModelResource):
|
|
"""Resource class for importing/exporting Author Profiles"""
|
|
class Meta:
|
|
model = AuthorProfile
|
|
fields = ('id', 'name', 'account__name', 'tone', 'language', 'is_active', 'created_at')
|
|
export_order = fields
|
|
import_id_fields = ('id',)
|
|
skip_unchanged = True
|
|
|
|
|
|
@admin.register(AuthorProfile)
|
|
class AuthorProfileAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
|
resource_class = AuthorProfileResource
|
|
list_display = ['name', 'account', 'tone', 'language', 'is_active', 'created_at']
|
|
list_filter = ['is_active', 'tone', 'language', 'account']
|
|
search_fields = ['name', 'description', 'tone']
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
actions = [
|
|
'bulk_activate',
|
|
'bulk_deactivate',
|
|
'bulk_clone',
|
|
]
|
|
|
|
fieldsets = (
|
|
('Basic Info', {
|
|
'fields': ('account', 'name', 'description', 'is_active')
|
|
}),
|
|
('Writing Style', {
|
|
'fields': ('tone', 'language', 'structure_template')
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ('created_at', 'updated_at')
|
|
}),
|
|
)
|
|
|
|
def get_account_display(self, obj):
|
|
"""Safely get account name"""
|
|
try:
|
|
account = getattr(obj, 'account', None)
|
|
return account.name if account else '-'
|
|
except:
|
|
return '-'
|
|
get_account_display.short_description = 'Account'
|
|
|
|
def bulk_activate(self, request, queryset):
|
|
updated = queryset.update(is_active=True)
|
|
self.message_user(request, f'{updated} author profile(s) activated.', messages.SUCCESS)
|
|
bulk_activate.short_description = 'Activate selected profiles'
|
|
|
|
def bulk_deactivate(self, request, queryset):
|
|
updated = queryset.update(is_active=False)
|
|
self.message_user(request, f'{updated} author profile(s) deactivated.', messages.SUCCESS)
|
|
bulk_deactivate.short_description = 'Deactivate selected profiles'
|
|
|
|
def bulk_clone(self, request, queryset):
|
|
count = 0
|
|
for profile in queryset:
|
|
profile_copy = AuthorProfile.objects.get(pk=profile.pk)
|
|
profile_copy.pk = None
|
|
profile_copy.name = f"{profile.name} (Copy)"
|
|
profile_copy.is_active = False
|
|
profile_copy.save()
|
|
count += 1
|
|
self.message_user(request, f'{count} author profile(s) cloned.', messages.SUCCESS)
|
|
bulk_clone.short_description = 'Clone selected profiles'
|
|
|
|
|
|
class StrategyResource(resources.ModelResource):
|
|
"""Resource class for importing/exporting Strategies"""
|
|
class Meta:
|
|
model = Strategy
|
|
fields = ('id', 'name', 'account__name', 'sector__name', 'is_active', 'created_at')
|
|
export_order = fields
|
|
import_id_fields = ('id',)
|
|
skip_unchanged = True
|
|
|
|
|
|
@admin.register(Strategy)
|
|
class StrategyAdmin(ImportExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
|
resource_class = StrategyResource
|
|
list_display = ['name', 'account', 'sector', 'is_active', 'created_at']
|
|
list_filter = ['is_active', 'account']
|
|
search_fields = ['name', 'description']
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
actions = [
|
|
'bulk_activate',
|
|
'bulk_deactivate',
|
|
'bulk_clone',
|
|
]
|
|
|
|
fieldsets = (
|
|
('Basic Info', {
|
|
'fields': ('account', 'name', 'description', 'sector', 'is_active')
|
|
}),
|
|
('Strategy Configuration', {
|
|
'fields': ('prompt_types', 'section_logic')
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ('created_at', 'updated_at')
|
|
}),
|
|
)
|
|
|
|
def get_account_display(self, obj):
|
|
"""Safely get account name"""
|
|
try:
|
|
account = getattr(obj, 'account', None)
|
|
return account.name if account else '-'
|
|
except:
|
|
return '-'
|
|
get_account_display.short_description = 'Account'
|
|
|
|
def get_sector_display(self, obj):
|
|
"""Safely get sector name"""
|
|
try:
|
|
return obj.sector.name if obj.sector else 'Global'
|
|
except:
|
|
return 'Global'
|
|
get_sector_display.short_description = 'Sector'
|
|
def bulk_activate(self, request, queryset):
|
|
updated = queryset.update(is_active=True)
|
|
self.message_user(request, f'{updated} strategy/strategies activated.', messages.SUCCESS)
|
|
bulk_activate.short_description = 'Activate selected strategies'
|
|
|
|
def bulk_deactivate(self, request, queryset):
|
|
updated = queryset.update(is_active=False)
|
|
self.message_user(request, f'{updated} strategy/strategies deactivated.', messages.SUCCESS)
|
|
bulk_deactivate.short_description = 'Deactivate selected strategies'
|
|
|
|
def bulk_clone(self, request, queryset):
|
|
count = 0
|
|
for strategy in queryset:
|
|
strategy_copy = Strategy.objects.get(pk=strategy.pk)
|
|
strategy_copy.pk = None
|
|
strategy_copy.name = f"{strategy.name} (Copy)"
|
|
strategy_copy.is_active = False
|
|
strategy_copy.save()
|
|
count += 1
|
|
self.message_user(request, f'{count} strategy/strategies cloned.', messages.SUCCESS)
|
|
bulk_clone.short_description = 'Clone selected strategies'
|
|
|
|
|
|
@admin.register(GlobalModuleSettings)
|
|
class GlobalModuleSettingsAdmin(ModelAdmin):
|
|
"""Admin for Global Module Settings (Singleton)"""
|
|
|
|
list_display = [
|
|
'id',
|
|
'planner_enabled',
|
|
'writer_enabled',
|
|
'thinker_enabled',
|
|
'automation_enabled',
|
|
'site_builder_enabled',
|
|
'linker_enabled',
|
|
]
|
|
|
|
fieldsets = (
|
|
('Module Toggles', {
|
|
'fields': (
|
|
'planner_enabled',
|
|
'writer_enabled',
|
|
'thinker_enabled',
|
|
'automation_enabled',
|
|
'site_builder_enabled',
|
|
'linker_enabled',
|
|
),
|
|
'description': 'Platform-wide module enable/disable controls. Changes affect all accounts immediately.'
|
|
}),
|
|
)
|
|
|
|
def has_add_permission(self, request):
|
|
"""Only allow one instance (singleton)"""
|
|
return not self.model.objects.exists()
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
"""Prevent deletion of singleton"""
|
|
return False |