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:
IGNY8 VPS (Salman)
2026-01-01 07:10:03 +00:00
parent f81fffc9a6
commit 0340016932
21 changed files with 1200 additions and 36 deletions

View File

@@ -5,7 +5,7 @@ Phase 6: Site Integration & Multi-Destination Publishing
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from igny8_core.modules.integration.views import IntegrationViewSet
from igny8_core.modules.integration.views import IntegrationViewSet, PublishingSettingsViewSet
from igny8_core.modules.integration.webhooks import (
wordpress_status_webhook,
wordpress_metadata_webhook,
@@ -14,9 +14,19 @@ from igny8_core.modules.integration.webhooks import (
router = DefaultRouter()
router.register(r'integrations', IntegrationViewSet, basename='integration')
# Create PublishingSettings ViewSet instance
publishing_settings_viewset = PublishingSettingsViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
})
urlpatterns = [
path('', include(router.urls)),
# Site-level publishing settings
path('sites/<int:site_id>/publishing-settings/', publishing_settings_viewset, name='publishing-settings'),
# Webhook endpoints
path('webhooks/wordpress/status/', wordpress_status_webhook, name='wordpress-status-webhook'),
path('webhooks/wordpress/metadata/', wordpress_metadata_webhook, name='wordpress-metadata-webhook'),

View File

@@ -838,5 +838,148 @@ class IntegrationViewSet(SiteSectorModelViewSet):
}, request=request)
# PublishingSettings ViewSet
from rest_framework import serializers, viewsets
from igny8_core.business.integration.models import PublishingSettings
class PublishingSettingsSerializer(serializers.ModelSerializer):
"""Serializer for PublishingSettings model"""
class Meta:
model = PublishingSettings
fields = [
'id',
'site',
'auto_approval_enabled',
'auto_publish_enabled',
'daily_publish_limit',
'weekly_publish_limit',
'monthly_publish_limit',
'publish_days',
'publish_time_slots',
'created_at',
'updated_at',
]
read_only_fields = ['id', 'site', 'created_at', 'updated_at']
@extend_schema_view(
retrieve=extend_schema(tags=['Integration']),
update=extend_schema(tags=['Integration']),
partial_update=extend_schema(tags=['Integration']),
)
class PublishingSettingsViewSet(viewsets.ViewSet):
"""
ViewSet for managing site-level publishing settings.
GET /api/v1/integration/sites/{site_id}/publishing-settings/
PUT /api/v1/integration/sites/{site_id}/publishing-settings/
PATCH /api/v1/integration/sites/{site_id}/publishing-settings/
"""
permission_classes = [IsAuthenticatedAndActive, IsEditorOrAbove]
throttle_scope = 'integration'
throttle_classes = [DebugScopedRateThrottle]
def _get_site(self, site_id, request):
"""Get site and verify user has access"""
from igny8_core.auth.models import Site
try:
site = Site.objects.get(id=int(site_id))
# Check if user has access to this site (same account)
if hasattr(request, 'account') and site.account != request.account:
return None
return site
except (Site.DoesNotExist, ValueError, TypeError):
return None
@extend_schema(tags=['Integration'])
def retrieve(self, request, site_id=None):
"""
Get publishing settings for a site.
Creates default settings if they don't exist.
"""
site = self._get_site(site_id, request)
if not site:
return error_response(
'Site not found or access denied',
None,
status.HTTP_404_NOT_FOUND,
request
)
# Get or create settings with defaults
settings, created = PublishingSettings.get_or_create_for_site(site)
serializer = PublishingSettingsSerializer(settings)
return success_response(
data=serializer.data,
message='Publishing settings retrieved' + (' (created with defaults)' if created else ''),
request=request
)
@extend_schema(tags=['Integration'])
def update(self, request, site_id=None):
"""
Update publishing settings for a site (full update).
"""
site = self._get_site(site_id, request)
if not site:
return error_response(
'Site not found or access denied',
None,
status.HTTP_404_NOT_FOUND,
request
)
# Get or create settings
settings, _ = PublishingSettings.get_or_create_for_site(site)
serializer = PublishingSettingsSerializer(settings, data=request.data)
if serializer.is_valid():
serializer.save()
return success_response(
data=serializer.data,
message='Publishing settings updated',
request=request
)
return error_response(
'Validation failed',
serializer.errors,
status.HTTP_400_BAD_REQUEST,
request
)
@extend_schema(tags=['Integration'])
def partial_update(self, request, site_id=None):
"""
Partially update publishing settings for a site.
"""
site = self._get_site(site_id, request)
if not site:
return error_response(
'Site not found or access denied',
None,
status.HTTP_404_NOT_FOUND,
request
)
# Get or create settings
settings, _ = PublishingSettings.get_or_create_for_site(site)
serializer = PublishingSettingsSerializer(settings, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return success_response(
data=serializer.data,
message='Publishing settings updated',
request=request
)
return error_response(
'Validation failed',
serializer.errors,
status.HTTP_400_BAD_REQUEST,
request
)

View File

@@ -0,0 +1,49 @@
# Generated migration for publishing scheduler fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('writer', '0014_add_approved_status'),
]
operations = [
migrations.AddField(
model_name='content',
name='site_status',
field=models.CharField(
choices=[
('not_published', 'Not Published'),
('scheduled', 'Scheduled'),
('publishing', 'Publishing'),
('published', 'Published'),
('failed', 'Failed'),
],
db_index=True,
default='not_published',
help_text='External site publishing status',
max_length=50,
),
),
migrations.AddField(
model_name='content',
name='scheduled_publish_at',
field=models.DateTimeField(
blank=True,
db_index=True,
help_text='Scheduled time for publishing to external site',
null=True,
),
),
migrations.AddField(
model_name='content',
name='site_status_updated_at',
field=models.DateTimeField(
blank=True,
help_text='Last time site_status was changed',
null=True,
),
),
]

View File

@@ -186,6 +186,9 @@ class ContentSerializer(serializers.ModelSerializer):
'external_url',
'source',
'status',
'site_status',
'scheduled_publish_at',
'site_status_updated_at',
'word_count',
'sector_name',
'site_id',
@@ -197,7 +200,7 @@ class ContentSerializer(serializers.ModelSerializer):
'created_at',
'updated_at',
]
read_only_fields = ['id', 'created_at', 'updated_at', 'account_id']
read_only_fields = ['id', 'created_at', 'updated_at', 'account_id', 'site_status', 'scheduled_publish_at', 'site_status_updated_at']
def validate(self, attrs):
"""Ensure required fields for Content creation"""