Section 3-8 - #MIgration Runs -
Multiple Migfeat: Update publishing terminology and add publishing settings - Changed references from "WordPress" to "Site" across multiple components for consistency. - Introduced a new "Publishing" tab in Site Settings to manage automatic content approval and publishing behavior. - Added publishing settings model to the backend with fields for auto-approval, auto-publish, and publishing limits. - Implemented Celery tasks for scheduling and processing automated content publishing. - Enhanced Writer Dashboard to include metrics for content published to the site and scheduled for publishing.
This commit is contained in:
@@ -1476,11 +1476,36 @@ class AutomationService:
|
||||
|
||||
This stage automatically approves content in 'review' status and
|
||||
marks it as 'approved' (ready for publishing to WordPress).
|
||||
|
||||
Respects PublishingSettings:
|
||||
- If auto_approval_enabled is False, skip approval and keep content in 'review'
|
||||
"""
|
||||
stage_number = 7
|
||||
stage_name = "Review → Approved"
|
||||
start_time = time.time()
|
||||
|
||||
# Check publishing settings for auto-approval
|
||||
from igny8_core.business.integration.models import PublishingSettings
|
||||
publishing_settings, _ = PublishingSettings.get_or_create_for_site(self.site)
|
||||
|
||||
if not publishing_settings.auto_approval_enabled:
|
||||
self.logger.log_stage_progress(
|
||||
self.run.run_id, self.account.id, self.site.id,
|
||||
stage_number, "Auto-approval is disabled for this site - skipping Stage 7"
|
||||
)
|
||||
self.run.stage_7_result = {
|
||||
'ready_for_review': 0,
|
||||
'approved_count': 0,
|
||||
'content_ids': [],
|
||||
'skipped': True,
|
||||
'reason': 'auto_approval_disabled'
|
||||
}
|
||||
self.run.status = 'completed'
|
||||
self.run.completed_at = datetime.now()
|
||||
self.run.save()
|
||||
cache.delete(f'automation_lock_{self.site.id}')
|
||||
return
|
||||
|
||||
# Query content ready for review
|
||||
ready_for_review = Content.objects.filter(
|
||||
site=self.site,
|
||||
@@ -1602,13 +1627,55 @@ class AutomationService:
|
||||
stage_number, approved_count, time_elapsed, 0
|
||||
)
|
||||
|
||||
# Check if auto-publish is enabled and queue approved content for publishing
|
||||
published_count = 0
|
||||
if publishing_settings.auto_publish_enabled and approved_count > 0:
|
||||
self.logger.log_stage_progress(
|
||||
self.run.run_id, self.account.id, self.site.id,
|
||||
stage_number, f"Auto-publish enabled - queuing {len(content_ids)} content items for publishing"
|
||||
)
|
||||
|
||||
# Get WordPress integration for this site
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
wp_integration = SiteIntegration.objects.filter(
|
||||
site=self.site,
|
||||
platform='wordpress',
|
||||
is_active=True
|
||||
).first()
|
||||
|
||||
if wp_integration:
|
||||
from igny8_core.tasks.wordpress_publishing import publish_content_to_wordpress
|
||||
|
||||
for content_id in content_ids:
|
||||
try:
|
||||
# Queue publish task
|
||||
publish_content_to_wordpress.delay(
|
||||
content_id=content_id,
|
||||
site_integration_id=wp_integration.id
|
||||
)
|
||||
published_count += 1
|
||||
except Exception as e:
|
||||
logger.error(f"[AutomationService] Failed to queue publish for content {content_id}: {str(e)}")
|
||||
|
||||
self.logger.log_stage_progress(
|
||||
self.run.run_id, self.account.id, self.site.id,
|
||||
stage_number, f"Queued {published_count} content items for WordPress publishing"
|
||||
)
|
||||
else:
|
||||
self.logger.log_stage_progress(
|
||||
self.run.run_id, self.account.id, self.site.id,
|
||||
stage_number, "No active WordPress integration found - skipping auto-publish"
|
||||
)
|
||||
|
||||
self.run.stage_7_result = {
|
||||
'ready_for_review': total_count,
|
||||
'review_total': total_count,
|
||||
'approved_count': approved_count,
|
||||
'content_ids': content_ids,
|
||||
'time_elapsed': time_elapsed,
|
||||
'in_progress': False
|
||||
'in_progress': False,
|
||||
'auto_published_count': published_count if publishing_settings.auto_publish_enabled else 0,
|
||||
'auto_publish_enabled': publishing_settings.auto_publish_enabled,
|
||||
}
|
||||
self.run.status = 'completed'
|
||||
self.run.completed_at = datetime.now()
|
||||
@@ -1617,7 +1684,8 @@ class AutomationService:
|
||||
# Release lock
|
||||
cache.delete(f'automation_lock_{self.site.id}')
|
||||
|
||||
logger.info(f"[AutomationService] Stage 7 complete: {approved_count} content pieces approved (ready for publishing)")
|
||||
logger.info(f"[AutomationService] Stage 7 complete: {approved_count} content pieces approved" +
|
||||
(f", {published_count} queued for publishing" if published_count > 0 else " (ready for publishing)"))
|
||||
|
||||
def pause_automation(self):
|
||||
"""Pause current automation run"""
|
||||
|
||||
@@ -282,6 +282,33 @@ class Content(SoftDeletableModel, SiteSectorBaseModel):
|
||||
help_text="Content status"
|
||||
)
|
||||
|
||||
# Publishing scheduler fields
|
||||
SITE_STATUS_CHOICES = [
|
||||
('not_published', 'Not Published'),
|
||||
('scheduled', 'Scheduled'),
|
||||
('publishing', 'Publishing'),
|
||||
('published', 'Published'),
|
||||
('failed', 'Failed'),
|
||||
]
|
||||
site_status = models.CharField(
|
||||
max_length=50,
|
||||
choices=SITE_STATUS_CHOICES,
|
||||
default='not_published',
|
||||
db_index=True,
|
||||
help_text="External site publishing status"
|
||||
)
|
||||
scheduled_publish_at = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text="Scheduled time for publishing to external site"
|
||||
)
|
||||
site_status_updated_at = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Last time site_status was changed"
|
||||
)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.2.9 on 2026-01-01 06:37
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0018_add_country_remove_intent_seedkeyword'),
|
||||
('integration', '0002_add_sync_event_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PublishingSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('auto_approval_enabled', models.BooleanField(default=True, help_text="Automatically approve content after review (moves to 'approved' status)")),
|
||||
('auto_publish_enabled', models.BooleanField(default=True, help_text='Automatically publish approved content to the external site')),
|
||||
('daily_publish_limit', models.PositiveIntegerField(default=3, help_text='Maximum number of articles to publish per day', validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('weekly_publish_limit', models.PositiveIntegerField(default=15, help_text='Maximum number of articles to publish per week', validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('monthly_publish_limit', models.PositiveIntegerField(default=50, help_text='Maximum number of articles to publish per month', validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('publish_days', models.JSONField(default=list, help_text='Days of the week to publish (mon, tue, wed, thu, fri, sat, sun)')),
|
||||
('publish_time_slots', models.JSONField(default=list, help_text="Times of day to publish (HH:MM format, e.g., ['09:00', '14:00', '18:00'])")),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('site', models.OneToOneField(help_text='Site these publishing settings belong to', on_delete=django.db.models.deletion.CASCADE, related_name='publishing_settings', to='igny8_core_auth.site')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Publishing Settings',
|
||||
'verbose_name_plural': 'Publishing Settings',
|
||||
'db_table': 'igny8_publishing_settings',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -244,3 +244,100 @@ class SyncEvent(AccountBaseModel):
|
||||
def __str__(self):
|
||||
return f"{self.get_event_type_display()} - {self.description[:50]}"
|
||||
|
||||
|
||||
class PublishingSettings(AccountBaseModel):
|
||||
"""
|
||||
Site-level publishing configuration settings.
|
||||
Controls automatic approval, publishing limits, and scheduling.
|
||||
"""
|
||||
|
||||
DEFAULT_PUBLISH_DAYS = ['mon', 'tue', 'wed', 'thu', 'fri']
|
||||
DEFAULT_TIME_SLOTS = ['09:00', '14:00', '18:00']
|
||||
|
||||
site = models.OneToOneField(
|
||||
'igny8_core_auth.Site',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='publishing_settings',
|
||||
help_text="Site these publishing settings belong to"
|
||||
)
|
||||
|
||||
# Auto-approval settings
|
||||
auto_approval_enabled = models.BooleanField(
|
||||
default=True,
|
||||
help_text="Automatically approve content after review (moves to 'approved' status)"
|
||||
)
|
||||
|
||||
# Auto-publish settings
|
||||
auto_publish_enabled = models.BooleanField(
|
||||
default=True,
|
||||
help_text="Automatically publish approved content to the external site"
|
||||
)
|
||||
|
||||
# Publishing limits
|
||||
daily_publish_limit = models.PositiveIntegerField(
|
||||
default=3,
|
||||
validators=[MinValueValidator(1)],
|
||||
help_text="Maximum number of articles to publish per day"
|
||||
)
|
||||
|
||||
weekly_publish_limit = models.PositiveIntegerField(
|
||||
default=15,
|
||||
validators=[MinValueValidator(1)],
|
||||
help_text="Maximum number of articles to publish per week"
|
||||
)
|
||||
|
||||
monthly_publish_limit = models.PositiveIntegerField(
|
||||
default=50,
|
||||
validators=[MinValueValidator(1)],
|
||||
help_text="Maximum number of articles to publish per month"
|
||||
)
|
||||
|
||||
# Publishing schedule
|
||||
publish_days = models.JSONField(
|
||||
default=list,
|
||||
help_text="Days of the week to publish (mon, tue, wed, thu, fri, sat, sun)"
|
||||
)
|
||||
|
||||
publish_time_slots = models.JSONField(
|
||||
default=list,
|
||||
help_text="Times of day to publish (HH:MM format, e.g., ['09:00', '14:00', '18:00'])"
|
||||
)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'integration'
|
||||
db_table = 'igny8_publishing_settings'
|
||||
verbose_name = 'Publishing Settings'
|
||||
verbose_name_plural = 'Publishing Settings'
|
||||
|
||||
def __str__(self):
|
||||
return f"Publishing Settings for {self.site.name}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Set defaults for JSON fields if empty"""
|
||||
if not self.publish_days:
|
||||
self.publish_days = self.DEFAULT_PUBLISH_DAYS
|
||||
if not self.publish_time_slots:
|
||||
self.publish_time_slots = self.DEFAULT_TIME_SLOTS
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_or_create_for_site(cls, site):
|
||||
"""Get or create publishing settings for a site with defaults"""
|
||||
settings, created = cls.objects.get_or_create(
|
||||
site=site,
|
||||
defaults={
|
||||
'account': site.account,
|
||||
'auto_approval_enabled': True,
|
||||
'auto_publish_enabled': True,
|
||||
'daily_publish_limit': 3,
|
||||
'weekly_publish_limit': 15,
|
||||
'monthly_publish_limit': 50,
|
||||
'publish_days': cls.DEFAULT_PUBLISH_DAYS,
|
||||
'publish_time_slots': cls.DEFAULT_TIME_SLOTS,
|
||||
}
|
||||
)
|
||||
return settings, created
|
||||
|
||||
|
||||
Reference in New Issue
Block a user