bulk actions & some next audits docs
This commit is contained in:
@@ -8,12 +8,31 @@ from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from .models import AutomationConfig, AutomationRun
|
||||
|
||||
|
||||
from import_export.admin import ExportMixin
|
||||
from import_export import resources
|
||||
|
||||
|
||||
class AutomationConfigResource(resources.ModelResource):
|
||||
"""Resource class for exporting Automation Configs"""
|
||||
class Meta:
|
||||
model = AutomationConfig
|
||||
fields = ('id', 'site__domain', 'is_enabled', 'frequency', 'scheduled_time',
|
||||
'within_stage_delay', 'between_stage_delay', 'last_run_at', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(AutomationConfig)
|
||||
class AutomationConfigAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
class AutomationConfigAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = AutomationConfigResource
|
||||
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',)
|
||||
actions = ['bulk_enable', 'bulk_disable']
|
||||
actions = [
|
||||
'bulk_enable',
|
||||
'bulk_disable',
|
||||
'bulk_update_frequency',
|
||||
'bulk_update_delays',
|
||||
]
|
||||
|
||||
def bulk_enable(self, request, queryset):
|
||||
"""Enable selected automation configs"""
|
||||
@@ -26,10 +45,122 @@ class AutomationConfigAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
updated = queryset.update(is_enabled=False)
|
||||
self.message_user(request, f'{updated} automation config(s) disabled.', messages.SUCCESS)
|
||||
bulk_disable.short_description = 'Disable selected automations'
|
||||
|
||||
def bulk_update_frequency(self, request, queryset):
|
||||
"""Update frequency for selected automation configs"""
|
||||
from django import forms
|
||||
|
||||
if 'apply' in request.POST:
|
||||
frequency = request.POST.get('frequency')
|
||||
if frequency:
|
||||
updated = queryset.update(frequency=frequency)
|
||||
self.message_user(request, f'{updated} automation config(s) updated to frequency: {frequency}', messages.SUCCESS)
|
||||
return
|
||||
|
||||
FREQUENCY_CHOICES = [
|
||||
('hourly', 'Hourly'),
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
]
|
||||
|
||||
class FrequencyForm(forms.Form):
|
||||
frequency = forms.ChoiceField(
|
||||
choices=FREQUENCY_CHOICES,
|
||||
label="Select Frequency",
|
||||
help_text=f"Update frequency for {queryset.count()} automation config(s)"
|
||||
)
|
||||
|
||||
from django.shortcuts import render
|
||||
return render(request, 'admin/bulk_action_form.html', {
|
||||
'title': 'Update Automation Frequency',
|
||||
'queryset': queryset,
|
||||
'form': FrequencyForm(),
|
||||
'action': 'bulk_update_frequency',
|
||||
})
|
||||
bulk_update_frequency.short_description = 'Update frequency'
|
||||
|
||||
def bulk_update_delays(self, request, queryset):
|
||||
"""Update delay settings for selected automation configs"""
|
||||
from django import forms
|
||||
|
||||
if 'apply' in request.POST:
|
||||
within_delay = int(request.POST.get('within_stage_delay', 0))
|
||||
between_delay = int(request.POST.get('between_stage_delay', 0))
|
||||
|
||||
updated = queryset.update(
|
||||
within_stage_delay=within_delay,
|
||||
between_stage_delay=between_delay
|
||||
)
|
||||
self.message_user(request, f'{updated} automation config(s) delay settings updated.', messages.SUCCESS)
|
||||
return
|
||||
|
||||
class DelayForm(forms.Form):
|
||||
within_stage_delay = forms.IntegerField(
|
||||
min_value=0,
|
||||
initial=10,
|
||||
label="Within Stage Delay (minutes)",
|
||||
help_text="Delay between operations within the same stage"
|
||||
)
|
||||
between_stage_delay = forms.IntegerField(
|
||||
min_value=0,
|
||||
initial=60,
|
||||
label="Between Stage Delay (minutes)",
|
||||
help_text="Delay between different stages"
|
||||
)
|
||||
|
||||
from django.shortcuts import render
|
||||
return render(request, 'admin/bulk_action_form.html', {
|
||||
'title': 'Update Automation Delays',
|
||||
'queryset': queryset,
|
||||
'form': DelayForm(),
|
||||
'action': 'bulk_update_delays',
|
||||
})
|
||||
bulk_update_delays.short_description = 'Update delay settings'
|
||||
|
||||
|
||||
class AutomationRunResource(resources.ModelResource):
|
||||
"""Resource class for exporting Automation Runs"""
|
||||
class Meta:
|
||||
model = AutomationRun
|
||||
fields = ('id', 'run_id', 'site__domain', 'status', 'current_stage',
|
||||
'started_at', 'completed_at', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(AutomationRun)
|
||||
class AutomationRunAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
class AutomationRunAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = AutomationRunResource
|
||||
list_display = ('run_id', 'site', 'status', 'current_stage', 'started_at', 'completed_at')
|
||||
list_filter = ('status', 'current_stage')
|
||||
search_fields = ('run_id', 'site__domain')
|
||||
actions = [
|
||||
'bulk_retry_failed',
|
||||
'bulk_cancel_running',
|
||||
'bulk_delete_old_runs',
|
||||
]
|
||||
|
||||
def bulk_retry_failed(self, request, queryset):
|
||||
"""Retry failed automation runs"""
|
||||
failed_runs = queryset.filter(status='failed')
|
||||
count = failed_runs.update(status='pending', current_stage='keyword_research')
|
||||
self.message_user(request, f'{count} failed run(s) marked for retry.', messages.SUCCESS)
|
||||
bulk_retry_failed.short_description = 'Retry failed runs'
|
||||
|
||||
def bulk_cancel_running(self, request, queryset):
|
||||
"""Cancel running automation runs"""
|
||||
running = queryset.filter(status__in=['pending', 'running'])
|
||||
count = running.update(status='failed')
|
||||
self.message_user(request, f'{count} running automation(s) cancelled.', messages.SUCCESS)
|
||||
bulk_cancel_running.short_description = 'Cancel running automations'
|
||||
|
||||
def bulk_delete_old_runs(self, request, queryset):
|
||||
"""Delete automation runs older than 30 days"""
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
cutoff_date = timezone.now() - timedelta(days=30)
|
||||
old_runs = queryset.filter(created_at__lt=cutoff_date)
|
||||
count = old_runs.count()
|
||||
old_runs.delete()
|
||||
self.message_user(request, f'{count} old automation run(s) deleted (older than 30 days).', messages.SUCCESS)
|
||||
bulk_delete_old_runs.short_description = 'Delete old runs (>30 days)'
|
||||
@@ -5,6 +5,7 @@ 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.contrib import messages
|
||||
from django.utils.html import format_html
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
@@ -49,8 +50,22 @@ from .models import (
|
||||
# PaymentMethodConfig and AccountPaymentMethod are kept here as they're not duplicated
|
||||
# or have minimal implementations that don't conflict
|
||||
|
||||
from import_export.admin import ExportMixin
|
||||
from import_export import resources
|
||||
|
||||
|
||||
class AccountPaymentMethodResource(resources.ModelResource):
|
||||
"""Resource class for exporting Account Payment Methods"""
|
||||
class Meta:
|
||||
model = AccountPaymentMethod
|
||||
fields = ('id', 'display_name', 'type', 'account__name', 'is_default',
|
||||
'is_enabled', 'is_verified', 'country_code', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(AccountPaymentMethod)
|
||||
class AccountPaymentMethodAdmin(Igny8ModelAdmin):
|
||||
class AccountPaymentMethodAdmin(ExportMixin, Igny8ModelAdmin):
|
||||
resource_class = AccountPaymentMethodResource
|
||||
list_display = [
|
||||
'display_name',
|
||||
'type',
|
||||
@@ -64,6 +79,12 @@ class AccountPaymentMethodAdmin(Igny8ModelAdmin):
|
||||
list_filter = ['type', 'is_default', 'is_enabled', 'is_verified', 'country_code']
|
||||
search_fields = ['display_name', 'account__name', 'account__id']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
'bulk_enable',
|
||||
'bulk_disable',
|
||||
'bulk_set_default',
|
||||
'bulk_delete_methods',
|
||||
]
|
||||
fieldsets = (
|
||||
('Payment Method', {
|
||||
'fields': ('account', 'type', 'display_name', 'is_default', 'is_enabled', 'is_verified', 'country_code')
|
||||
@@ -75,4 +96,48 @@ class AccountPaymentMethodAdmin(Igny8ModelAdmin):
|
||||
'fields': ('created_at', 'updated_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
)
|
||||
def bulk_enable(self, request, queryset):
|
||||
updated = queryset.update(is_enabled=True)
|
||||
self.message_user(request, f'{updated} payment method(s) enabled.', messages.SUCCESS)
|
||||
bulk_enable.short_description = 'Enable selected payment methods'
|
||||
|
||||
def bulk_disable(self, request, queryset):
|
||||
updated = queryset.update(is_enabled=False)
|
||||
self.message_user(request, f'{updated} payment method(s) disabled.', messages.SUCCESS)
|
||||
bulk_disable.short_description = 'Disable selected payment methods'
|
||||
|
||||
def bulk_set_default(self, request, queryset):
|
||||
from django import forms
|
||||
|
||||
if 'apply' in request.POST:
|
||||
method_id = request.POST.get('payment_method')
|
||||
if method_id:
|
||||
method = AccountPaymentMethod.objects.get(pk=method_id)
|
||||
# Unset all others for this account
|
||||
AccountPaymentMethod.objects.filter(account=method.account).update(is_default=False)
|
||||
method.is_default = True
|
||||
method.save()
|
||||
self.message_user(request, f'{method.display_name} set as default for {method.account.name}.', messages.SUCCESS)
|
||||
return
|
||||
|
||||
class PaymentMethodForm(forms.Form):
|
||||
payment_method = forms.ModelChoiceField(
|
||||
queryset=queryset,
|
||||
label="Select Payment Method to Set as Default"
|
||||
)
|
||||
|
||||
from django.shortcuts import render
|
||||
return render(request, 'admin/bulk_action_form.html', {
|
||||
'title': 'Set Default Payment Method',
|
||||
'queryset': queryset,
|
||||
'form': PaymentMethodForm(),
|
||||
'action': 'bulk_set_default',
|
||||
})
|
||||
bulk_set_default.short_description = 'Set as default'
|
||||
|
||||
def bulk_delete_methods(self, request, queryset):
|
||||
count = queryset.count()
|
||||
queryset.delete()
|
||||
self.message_user(request, f'{count} payment method(s) deleted.', messages.SUCCESS)
|
||||
bulk_delete_methods.short_description = 'Delete selected payment methods'
|
||||
@@ -16,8 +16,18 @@ class SyncEventResource(resources.ModelResource):
|
||||
export_order = fields
|
||||
|
||||
|
||||
class SiteIntegrationResource(resources.ModelResource):
|
||||
"""Resource class for exporting Site Integrations"""
|
||||
class Meta:
|
||||
model = SiteIntegration
|
||||
fields = ('id', 'site__name', 'platform', 'platform_type', 'is_active',
|
||||
'sync_enabled', 'sync_status', 'last_sync_at', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(SiteIntegration)
|
||||
class SiteIntegrationAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
class SiteIntegrationAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = SiteIntegrationResource
|
||||
list_display = [
|
||||
'site',
|
||||
'platform',
|
||||
@@ -30,7 +40,13 @@ class SiteIntegrationAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_filter = ['platform', 'platform_type', 'is_active', 'sync_enabled', 'sync_status']
|
||||
search_fields = ['site__name', 'site__domain', 'platform']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = ['bulk_enable_sync', 'bulk_disable_sync', 'bulk_trigger_sync']
|
||||
actions = [
|
||||
'bulk_enable_sync',
|
||||
'bulk_disable_sync',
|
||||
'bulk_trigger_sync',
|
||||
'bulk_test_connection',
|
||||
'bulk_delete_integrations',
|
||||
]
|
||||
|
||||
def bulk_enable_sync(self, request, queryset):
|
||||
"""Enable sync for selected integrations"""
|
||||
@@ -52,6 +68,29 @@ class SiteIntegrationAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
count += 1
|
||||
self.message_user(request, f'{count} integration(s) queued for sync.', messages.INFO)
|
||||
bulk_trigger_sync.short_description = 'Trigger sync now'
|
||||
|
||||
def bulk_test_connection(self, request, queryset):
|
||||
"""Test connection for selected integrations"""
|
||||
tested = 0
|
||||
successful = 0
|
||||
for integration in queryset.filter(is_active=True):
|
||||
# TODO: Implement actual connection test logic
|
||||
tested += 1
|
||||
successful += 1 # Placeholder
|
||||
|
||||
self.message_user(
|
||||
request,
|
||||
f'Tested {tested} integration(s). {successful} successful. (Connection test logic to be implemented)',
|
||||
messages.INFO
|
||||
)
|
||||
bulk_test_connection.short_description = 'Test connections'
|
||||
|
||||
def bulk_delete_integrations(self, request, queryset):
|
||||
"""Delete selected integrations"""
|
||||
count = queryset.count()
|
||||
queryset.delete()
|
||||
self.message_user(request, f'{count} integration(s) deleted.', messages.SUCCESS)
|
||||
bulk_delete_integrations.short_description = 'Delete selected integrations'
|
||||
|
||||
|
||||
@admin.register(SyncEvent)
|
||||
@@ -69,7 +108,10 @@ class SyncEventAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_filter = ['event_type', 'action', 'success', 'created_at']
|
||||
search_fields = ['integration__site__name', 'site__name', 'description', 'external_id']
|
||||
readonly_fields = ['created_at']
|
||||
actions = ['bulk_mark_reviewed']
|
||||
actions = [
|
||||
'bulk_mark_reviewed',
|
||||
'bulk_delete_old_events',
|
||||
]
|
||||
|
||||
def bulk_mark_reviewed(self, request, queryset):
|
||||
"""Mark selected sync events as reviewed"""
|
||||
@@ -77,4 +119,16 @@ class SyncEventAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
count = queryset.count()
|
||||
self.message_user(request, f'{count} sync event(s) marked as reviewed.', messages.SUCCESS)
|
||||
bulk_mark_reviewed.short_description = 'Mark as reviewed'
|
||||
|
||||
def bulk_delete_old_events(self, request, queryset):
|
||||
"""Delete sync events older than 30 days"""
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
cutoff_date = timezone.now() - timedelta(days=30)
|
||||
old_events = queryset.filter(created_at__lt=cutoff_date)
|
||||
count = old_events.count()
|
||||
old_events.delete()
|
||||
self.message_user(request, f'{count} old sync event(s) deleted (older than 30 days).', messages.SUCCESS)
|
||||
bulk_delete_old_events.short_description = 'Delete old events (>30 days)'
|
||||
|
||||
|
||||
@@ -1,13 +1,45 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin, Igny8ModelAdmin
|
||||
from .models import OptimizationTask
|
||||
from import_export.admin import ExportMixin
|
||||
from import_export import resources
|
||||
|
||||
|
||||
class OptimizationTaskResource(resources.ModelResource):
|
||||
"""Resource class for exporting Optimization Tasks"""
|
||||
class Meta:
|
||||
model = OptimizationTask
|
||||
fields = ('id', 'content__title', 'account__name', 'status',
|
||||
'credits_used', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(OptimizationTask)
|
||||
class OptimizationTaskAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
class OptimizationTaskAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = OptimizationTaskResource
|
||||
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']
|
||||
|
||||
actions = [
|
||||
'bulk_mark_completed',
|
||||
'bulk_mark_failed',
|
||||
'bulk_retry',
|
||||
]
|
||||
|
||||
def bulk_mark_completed(self, request, queryset):
|
||||
updated = queryset.update(status='completed')
|
||||
self.message_user(request, f'{updated} optimization task(s) marked as completed.', messages.SUCCESS)
|
||||
bulk_mark_completed.short_description = 'Mark as completed'
|
||||
|
||||
def bulk_mark_failed(self, request, queryset):
|
||||
updated = queryset.update(status='failed')
|
||||
self.message_user(request, f'{updated} optimization task(s) marked as failed.', messages.SUCCESS)
|
||||
bulk_mark_failed.short_description = 'Mark as failed'
|
||||
|
||||
def bulk_retry(self, request, queryset):
|
||||
updated = queryset.filter(status='failed').update(status='pending')
|
||||
self.message_user(request, f'{updated} failed optimization task(s) queued for retry.', messages.SUCCESS)
|
||||
bulk_retry.short_description = 'Retry failed tasks'
|
||||
|
||||
@@ -31,7 +31,11 @@ class PublishingRecordAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_filter = ['destination', 'status', 'site']
|
||||
search_fields = ['content__title', 'destination', 'destination_url']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = ['bulk_retry_failed']
|
||||
actions = [
|
||||
'bulk_retry_failed',
|
||||
'bulk_cancel_pending',
|
||||
'bulk_mark_published',
|
||||
]
|
||||
|
||||
def bulk_retry_failed(self, request, queryset):
|
||||
"""Retry failed publishing records"""
|
||||
@@ -39,10 +43,34 @@ class PublishingRecordAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
count = failed_records.update(status='pending')
|
||||
self.message_user(request, f'{count} failed record(s) marked for retry.', messages.SUCCESS)
|
||||
bulk_retry_failed.short_description = 'Retry failed publishes'
|
||||
|
||||
def bulk_cancel_pending(self, request, queryset):
|
||||
"""Cancel pending publishing records"""
|
||||
pending = queryset.filter(status__in=['pending', 'publishing'])
|
||||
count = pending.update(status='failed', error_message='Cancelled by admin')
|
||||
self.message_user(request, f'{count} publishing record(s) cancelled.', messages.SUCCESS)
|
||||
bulk_cancel_pending.short_description = 'Cancel pending publishes'
|
||||
|
||||
def bulk_mark_published(self, request, queryset):
|
||||
"""Mark selected records as published"""
|
||||
from django.utils import timezone
|
||||
count = queryset.update(status='published', published_at=timezone.now())
|
||||
self.message_user(request, f'{count} record(s) marked as published.', messages.SUCCESS)
|
||||
bulk_mark_published.short_description = 'Mark as published'
|
||||
|
||||
|
||||
class DeploymentRecordResource(resources.ModelResource):
|
||||
"""Resource class for exporting Deployment Records"""
|
||||
class Meta:
|
||||
model = DeploymentRecord
|
||||
fields = ('id', 'site__name', 'sector__name', 'version', 'deployed_version',
|
||||
'status', 'deployment_url', 'deployed_at', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(DeploymentRecord)
|
||||
class DeploymentRecordAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
class DeploymentRecordAdmin(ExportMixin, SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = DeploymentRecordResource
|
||||
list_display = [
|
||||
'site',
|
||||
'sector',
|
||||
@@ -55,4 +83,35 @@ class DeploymentRecordAdmin(SiteSectorAdminMixin, Igny8ModelAdmin):
|
||||
list_filter = ['status', 'site']
|
||||
search_fields = ['site__name', 'deployment_url']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
'bulk_retry_failed',
|
||||
'bulk_rollback',
|
||||
'bulk_cancel_pending',
|
||||
]
|
||||
actions = [
|
||||
'bulk_retry_failed',
|
||||
'bulk_rollback',
|
||||
'bulk_cancel_pending',
|
||||
]
|
||||
|
||||
def bulk_retry_failed(self, request, queryset):
|
||||
"""Retry failed deployments"""
|
||||
failed = queryset.filter(status='failed')
|
||||
count = failed.update(status='pending', error_message='')
|
||||
self.message_user(request, f'{count} failed deployment(s) marked for retry.', messages.SUCCESS)
|
||||
bulk_retry_failed.short_description = 'Retry failed deployments'
|
||||
|
||||
def bulk_rollback(self, request, queryset):
|
||||
"""Rollback selected deployments"""
|
||||
deployed = queryset.filter(status='deployed')
|
||||
count = deployed.update(status='rolled_back')
|
||||
self.message_user(request, f'{count} deployment(s) marked for rollback.', messages.SUCCESS)
|
||||
bulk_rollback.short_description = 'Rollback deployments'
|
||||
|
||||
def bulk_cancel_pending(self, request, queryset):
|
||||
"""Cancel pending deployments"""
|
||||
pending = queryset.filter(status__in=['pending', 'deploying'])
|
||||
count = pending.update(status='failed', error_message='Cancelled by admin')
|
||||
self.message_user(request, f'{count} deployment(s) cancelled.', messages.SUCCESS)
|
||||
bulk_cancel_pending.short_description = 'Cancel pending deployments'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user