old automation cleanup adn status feilds of planner udpate
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
"""
|
||||
Automation Module - API Layer
|
||||
Business logic is in business/automation/
|
||||
"""
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
"""
|
||||
Automation App Configuration
|
||||
"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AutomationConfig(AppConfig):
|
||||
"""Configuration for automation module"""
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'igny8_core.modules.automation'
|
||||
label = 'automation'
|
||||
verbose_name = 'Automation'
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AutomationRule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Rule name', max_length=255)),
|
||||
('description', models.TextField(blank=True, help_text='Rule description', null=True)),
|
||||
('trigger', models.CharField(choices=[('schedule', 'Schedule'), ('event', 'Event'), ('manual', 'Manual')], default='manual', max_length=50)),
|
||||
('schedule', models.CharField(blank=True, help_text="Cron-like schedule string (e.g., '0 0 * * *' for daily at midnight)", max_length=100, null=True)),
|
||||
('conditions', models.JSONField(default=list, help_text='List of conditions that must be met for rule to execute')),
|
||||
('actions', models.JSONField(default=list, help_text='List of actions to execute when rule triggers')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Whether rule is active')),
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive'), ('paused', 'Paused')], default='active', max_length=50)),
|
||||
('last_executed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('execution_count', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional metadata')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Automation Rule',
|
||||
'verbose_name_plural': 'Automation Rules',
|
||||
'db_table': 'igny8_automation_rules',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ScheduledTask',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('scheduled_at', models.DateTimeField(help_text='When the task is scheduled to run')),
|
||||
('executed_at', models.DateTimeField(blank=True, help_text='When the task was actually executed', null=True)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('running', 'Running'), ('completed', 'Completed'), ('failed', 'Failed'), ('cancelled', 'Cancelled')], default='pending', max_length=50)),
|
||||
('result', models.JSONField(default=dict, help_text='Execution result data')),
|
||||
('error_message', models.TextField(blank=True, help_text='Error message if execution failed', null=True)),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional metadata')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Scheduled Task',
|
||||
'verbose_name_plural': 'Scheduled Tasks',
|
||||
'db_table': 'igny8_scheduled_tasks',
|
||||
'ordering': ['-scheduled_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,74 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('automation', '0001_initial'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='automationrule',
|
||||
name='account',
|
||||
field=models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automationrule',
|
||||
name='sector',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automationrule',
|
||||
name='site',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scheduledtask',
|
||||
name='account',
|
||||
field=models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scheduledtask',
|
||||
name='automation_rule',
|
||||
field=models.ForeignKey(help_text='The automation rule this task belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_tasks', to='automation.automationrule'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['trigger', 'is_active'], name='igny8_autom_trigger_32979f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['status'], name='igny8_autom_status_827c0d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_autom_site_id_d0a51d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['trigger', 'is_active', 'status'], name='igny8_autom_trigger_f3f3e2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['automation_rule', 'status'], name='igny8_sched_automat_da6c85_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['scheduled_at', 'status'], name='igny8_sched_schedul_1e3342_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_sched_tenant__7244a8_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['status', 'scheduled_at'], name='igny8_sched_status_21f32f_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
# Backward compatibility alias - models moved to business/automation/
|
||||
from igny8_core.business.automation.models import AutomationRule, ScheduledTask
|
||||
|
||||
__all__ = ['AutomationRule', 'ScheduledTask']
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
"""
|
||||
Serializers for Automation Models
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from igny8_core.business.automation.models import AutomationRule, ScheduledTask
|
||||
|
||||
|
||||
class AutomationRuleSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for AutomationRule model"""
|
||||
|
||||
class Meta:
|
||||
model = AutomationRule
|
||||
fields = [
|
||||
'id', 'name', 'description', 'trigger', 'schedule',
|
||||
'conditions', 'actions', 'is_active', 'status',
|
||||
'last_executed_at', 'execution_count',
|
||||
'metadata', 'created_at', 'updated_at',
|
||||
'account', 'site', 'sector'
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'last_executed_at', 'execution_count']
|
||||
|
||||
|
||||
class ScheduledTaskSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for ScheduledTask model"""
|
||||
automation_rule_name = serializers.CharField(source='automation_rule.name', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ScheduledTask
|
||||
fields = [
|
||||
'id', 'automation_rule', 'automation_rule_name',
|
||||
'scheduled_at', 'executed_at', 'status',
|
||||
'result', 'error_message', 'metadata',
|
||||
'created_at', 'updated_at', 'account'
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'executed_at']
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
"""
|
||||
URL patterns for automation module.
|
||||
"""
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import AutomationRuleViewSet, ScheduledTaskViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'rules', AutomationRuleViewSet, basename='automation-rule')
|
||||
router.register(r'scheduled-tasks', ScheduledTaskViewSet, basename='scheduled-task')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
"""
|
||||
ViewSets for Automation Models
|
||||
Unified API Standard v1.0 compliant
|
||||
"""
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import filters
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
from igny8_core.api.base import SiteSectorModelViewSet, AccountModelViewSet
|
||||
from igny8_core.api.pagination import CustomPageNumberPagination
|
||||
from igny8_core.api.response import success_response, error_response
|
||||
from igny8_core.api.throttles import DebugScopedRateThrottle
|
||||
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsViewerOrAbove
|
||||
from igny8_core.business.automation.models import AutomationRule, ScheduledTask
|
||||
from igny8_core.business.automation.services.automation_service import AutomationService
|
||||
from .serializers import AutomationRuleSerializer, ScheduledTaskSerializer
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(tags=['Automation']),
|
||||
create=extend_schema(tags=['Automation']),
|
||||
retrieve=extend_schema(tags=['Automation']),
|
||||
update=extend_schema(tags=['Automation']),
|
||||
partial_update=extend_schema(tags=['Automation']),
|
||||
destroy=extend_schema(tags=['Automation']),
|
||||
)
|
||||
class AutomationRuleViewSet(SiteSectorModelViewSet):
|
||||
"""
|
||||
ViewSet for managing automation rules
|
||||
Unified API Standard v1.0 compliant
|
||||
"""
|
||||
queryset = AutomationRule.objects.all()
|
||||
serializer_class = AutomationRuleSerializer
|
||||
permission_classes = [IsAuthenticatedAndActive, IsViewerOrAbove]
|
||||
pagination_class = CustomPageNumberPagination
|
||||
throttle_scope = 'automation'
|
||||
throttle_classes = [DebugScopedRateThrottle]
|
||||
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
search_fields = ['name', 'description']
|
||||
ordering_fields = ['name', 'created_at', 'last_executed_at', 'execution_count']
|
||||
ordering = ['-created_at']
|
||||
filterset_fields = ['trigger', 'is_active', 'status']
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='execute', url_name='execute')
|
||||
def execute(self, request, pk=None):
|
||||
"""Manually execute an automation rule"""
|
||||
rule = self.get_object()
|
||||
service = AutomationService()
|
||||
|
||||
try:
|
||||
result = service.execute_rule(rule, context=request.data.get('context', {}))
|
||||
return success_response(
|
||||
data=result,
|
||||
message='Rule executed successfully',
|
||||
request=request
|
||||
)
|
||||
except Exception as e:
|
||||
return error_response(
|
||||
error=str(e),
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(tags=['Automation']),
|
||||
create=extend_schema(tags=['Automation']),
|
||||
retrieve=extend_schema(tags=['Automation']),
|
||||
update=extend_schema(tags=['Automation']),
|
||||
partial_update=extend_schema(tags=['Automation']),
|
||||
destroy=extend_schema(tags=['Automation']),
|
||||
)
|
||||
class ScheduledTaskViewSet(AccountModelViewSet):
|
||||
"""
|
||||
ViewSet for managing scheduled tasks
|
||||
Unified API Standard v1.0 compliant
|
||||
"""
|
||||
queryset = ScheduledTask.objects.select_related('automation_rule')
|
||||
serializer_class = ScheduledTaskSerializer
|
||||
permission_classes = [IsAuthenticatedAndActive, IsViewerOrAbove]
|
||||
pagination_class = CustomPageNumberPagination
|
||||
throttle_scope = 'automation'
|
||||
throttle_classes = [DebugScopedRateThrottle]
|
||||
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
ordering_fields = ['scheduled_at', 'executed_at', 'status', 'created_at']
|
||||
ordering = ['-scheduled_at']
|
||||
filterset_fields = ['automation_rule', 'status']
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# Generated migration for unified status refactor
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_status_data(apps, schema_editor):
|
||||
"""Transform existing status data to new values"""
|
||||
Keywords = apps.get_model('planner', 'Keywords')
|
||||
Clusters = apps.get_model('planner', 'Clusters')
|
||||
ContentIdeas = apps.get_model('planner', 'ContentIdeas')
|
||||
|
||||
# Keywords: pending→new, active→mapped, archived→mapped+disabled=true
|
||||
Keywords.objects.filter(status='pending').update(status='new')
|
||||
Keywords.objects.filter(status='active').update(status='mapped')
|
||||
# Handle archived: set to mapped and mark as disabled
|
||||
archived_keywords = Keywords.objects.filter(status='archived')
|
||||
for kw in archived_keywords:
|
||||
kw.status = 'mapped'
|
||||
kw.disabled = True
|
||||
kw.save()
|
||||
|
||||
# Clusters: active (with ideas)→mapped, active (no ideas)→new
|
||||
# Check if cluster has any related ideas using the reverse relationship
|
||||
for cluster in Clusters.objects.all():
|
||||
if cluster.ideas.exists():
|
||||
cluster.status = 'mapped'
|
||||
else:
|
||||
cluster.status = 'new'
|
||||
cluster.save()
|
||||
|
||||
# ContentIdeas: scheduled→queued, published→completed, new stays new
|
||||
ContentIdeas.objects.filter(status='scheduled').update(status='queued')
|
||||
ContentIdeas.objects.filter(status='published').update(status='completed')
|
||||
|
||||
|
||||
def reverse_status_data(apps, schema_editor):
|
||||
"""Reverse migration: restore old status values"""
|
||||
Keywords = apps.get_model('planner', 'Keywords')
|
||||
Clusters = apps.get_model('planner', 'Clusters')
|
||||
ContentIdeas = apps.get_model('planner', 'ContentIdeas')
|
||||
|
||||
# Keywords: new→pending, mapped→active (or archived if disabled)
|
||||
Keywords.objects.filter(status='new').update(status='pending')
|
||||
Keywords.objects.filter(status='mapped', disabled=False).update(status='active')
|
||||
Keywords.objects.filter(status='mapped', disabled=True).update(status='archived', disabled=False)
|
||||
|
||||
# Clusters: all back to 'active'
|
||||
Clusters.objects.all().update(status='active')
|
||||
|
||||
# ContentIdeas: queued→scheduled, completed→published
|
||||
ContentIdeas.objects.filter(status='queued').update(status='scheduled')
|
||||
ContentIdeas.objects.filter(status='completed').update(status='published')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('planner', '0005_field_rename_implementation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Step 1: Add disabled field to all models (with default=False)
|
||||
migrations.AddField(
|
||||
model_name='clusters',
|
||||
name='disabled',
|
||||
field=models.BooleanField(default=False, help_text='Exclude from processes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='disabled',
|
||||
field=models.BooleanField(default=False, help_text='Exclude from processes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contentideas',
|
||||
name='disabled',
|
||||
field=models.BooleanField(default=False, help_text='Exclude from processes'),
|
||||
),
|
||||
|
||||
# Step 2: Alter Keywords status field choices
|
||||
migrations.AlterField(
|
||||
model_name='keywords',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[('new', 'New'), ('mapped', 'Mapped')],
|
||||
default='new',
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
|
||||
# Step 3: Alter Clusters status field (add choices, change default)
|
||||
migrations.AlterField(
|
||||
model_name='clusters',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[('new', 'New'), ('mapped', 'Mapped')],
|
||||
default='new',
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
|
||||
# Step 4: Alter ContentIdeas status field choices
|
||||
migrations.AlterField(
|
||||
model_name='contentideas',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[('new', 'New'), ('queued', 'Queued'), ('completed', 'Completed')],
|
||||
default='new',
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
|
||||
# Step 5: Data migration - transform existing records
|
||||
migrations.RunPython(migrate_status_data, reverse_status_data),
|
||||
]
|
||||
@@ -560,7 +560,7 @@ class KeywordViewSet(SiteSectorModelViewSet):
|
||||
volume=int(row.get('volume', 0) or 0),
|
||||
difficulty=int(row.get('difficulty', 0) or 0),
|
||||
intent=row.get('intent', 'informational') or 'informational',
|
||||
status=row.get('status', 'pending') or 'pending',
|
||||
status=row.get('status', 'new') or 'new',
|
||||
site=site,
|
||||
sector=sector,
|
||||
account=account
|
||||
@@ -1080,8 +1080,8 @@ class ContentIdeasViewSet(SiteSectorModelViewSet):
|
||||
|
||||
created_tasks.append(task.id)
|
||||
|
||||
# Update idea status
|
||||
idea.status = 'scheduled'
|
||||
# Update idea status to queued
|
||||
idea.status = 'queued'
|
||||
idea.save()
|
||||
except Exception as e:
|
||||
errors.append({
|
||||
|
||||
Reference in New Issue
Block a user