From 87fdbce0e9bc3381688dbd5e44bfca2053eea724 Mon Sep 17 00:00:00 2001 From: alorig <220087330+alorig@users.noreply.github.com> Date: Sun, 30 Nov 2025 18:35:46 +0500 Subject: [PATCH] igny8-wp int --- backend/igny8_core/modules/writer/views.py | 106 +++++++++++++++++- .../igny8_core/tasks/wordpress_publishing.py | 58 +++++++++- 2 files changed, 156 insertions(+), 8 deletions(-) diff --git a/backend/igny8_core/modules/writer/views.py b/backend/igny8_core/modules/writer/views.py index ef4d14bc..ae4da5a3 100644 --- a/backend/igny8_core/modules/writer/views.py +++ b/backend/igny8_core/modules/writer/views.py @@ -818,6 +818,11 @@ class ContentViewSet(SiteSectorModelViewSet): request=request ) + # OPTIMISTIC UPDATE: Set status to published immediately for better UX + # The Celery task will update external_id and external_url when WordPress responds + content.status = 'published' + content.save(update_fields=['status', 'updated_at']) + # Queue publishing task (same as automated flow) try: result = publish_content_to_wordpress.delay( @@ -825,28 +830,123 @@ class ContentViewSet(SiteSectorModelViewSet): site_integration_id=site_integration.id ) - logger.info(f"[ContentViewSet.publish] Queued Celery task {result.id} for content {content.id}") + logger.info(f"[ContentViewSet.publish] Queued Celery task {result.id} for content {content.id}, status set to 'published'") return success_response( data={ 'content_id': content.id, 'task_id': result.id, - 'status': 'queued', + 'status': 'published', 'message': 'Publishing queued - content will be published to WordPress shortly' }, - message='Content publishing queued successfully', + message='Content status updated to published and queued for WordPress', request=request, status_code=status.HTTP_202_ACCEPTED ) except Exception as e: logger.error(f"[ContentViewSet.publish] Error queuing publish task: {str(e)}", exc_info=True) + # Revert status on error + content.status = 'review' + content.save(update_fields=['status', 'updated_at']) return error_response( error=f"Failed to queue publishing task: {str(e)}", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, request=request ) + @action(detail=True, methods=['get'], url_path='wordpress_status', url_name='wordpress_status') + def wordpress_status(self, request, pk=None): + """ + Get WordPress post status for published content. + Calls WordPress REST API to get current status. + + GET /api/v1/writer/content/{id}/wordpress_status/ + Returns: { + 'wordpress_status': 'publish'|'draft'|'pending'|null, + 'external_id': 123, + 'external_url': 'https://...', + 'last_checked': '2025-11-30T...' + } + """ + import requests + from django.utils import timezone + from igny8_core.business.integration.models import SiteIntegration + import logging + + logger = logging.getLogger(__name__) + content = self.get_object() + + if not content.external_id: + return success_response( + data={ + 'wordpress_status': None, + 'external_id': None, + 'external_url': None, + 'message': 'Content not published to WordPress yet' + }, + request=request + ) + + # Get WordPress integration for this content's site + try: + site_integration = SiteIntegration.objects.filter( + site=content.site, + platform='wordpress', + is_active=True + ).first() + + if not site_integration: + return error_response( + error='No active WordPress integration found', + status_code=status.HTTP_404_NOT_FOUND, + request=request + ) + + # Call WordPress REST API to get post status + wordpress_url = f"{site_integration.site_url}/wp-json/igny8/v1/post-status/{content.external_id}/" + headers = { + 'X-IGNY8-API-KEY': site_integration.api_key, + } + + response = requests.get(wordpress_url, headers=headers, timeout=10) + + if response.status_code == 200: + wp_data = response.json().get('data', {}) + return success_response( + data={ + 'wordpress_status': wp_data.get('post_status'), + 'external_id': content.external_id, + 'external_url': content.external_url, + 'post_title': wp_data.get('post_title'), + 'post_modified': wp_data.get('post_modified'), + 'last_checked': timezone.now().isoformat() + }, + request=request + ) + else: + logger.error(f"WordPress API error: {response.status_code} - {response.text}") + return error_response( + error=f'Failed to get WordPress status: {response.status_code}', + status_code=status.HTTP_502_BAD_GATEWAY, + request=request + ) + + except requests.RequestException as e: + logger.error(f"Request to WordPress failed: {str(e)}") + return error_response( + error=f'Connection to WordPress failed: {str(e)}', + status_code=status.HTTP_502_BAD_GATEWAY, + request=request + ) + except Exception as e: + logger.error(f"Error getting WordPress status: {str(e)}", exc_info=True) + return error_response( + error=str(e), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + request=request + ) + @action(detail=True, methods=['post'], url_path='unpublish', url_name='unpublish', permission_classes=[IsAuthenticatedAndActive, IsEditorOrAbove]) def unpublish(self, request, pk=None): """ diff --git a/backend/igny8_core/tasks/wordpress_publishing.py b/backend/igny8_core/tasks/wordpress_publishing.py index 1074a0bb..5a155c12 100644 --- a/backend/igny8_core/tasks/wordpress_publishing.py +++ b/backend/igny8_core/tasks/wordpress_publishing.py @@ -58,6 +58,51 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int if len(content.content_html) > 150: excerpt += '...' + # Get taxonomy terms from ContentTaxonomyMap + from igny8_core.business.content.models import ContentTaxonomyMap + taxonomy_maps = ContentTaxonomyMap.objects.filter(content=content).select_related('taxonomy') + + # Build categories and tags arrays from taxonomy mappings + categories = [] + tags = [] + for mapping in taxonomy_maps: + tax = mapping.taxonomy + if tax: + # Add taxonomy term name to categories (will be mapped in WordPress) + categories.append(tax.name) + + # Get images from Images model + from igny8_core.modules.writer.models import Images + featured_image_url = None + gallery_images = [] + + images = Images.objects.filter(content=content).order_by('position') + for image in images: + if image.image_type == 'featured' and image.image_url: + featured_image_url = image.image_url + elif image.image_type == 'in_article' and image.image_url: + gallery_images.append({ + 'url': image.image_url, + 'alt': image.alt_text or '', + 'position': image.position + }) + + # Add primary and secondary keywords as tags + if content.primary_keyword: + tags.append(content.primary_keyword) + + if content.secondary_keywords: + if isinstance(content.secondary_keywords, list): + tags.extend(content.secondary_keywords) + elif isinstance(content.secondary_keywords, str): + import json + try: + keywords = json.loads(content.secondary_keywords) + if isinstance(keywords, list): + tags.extend(keywords) + except (json.JSONDecodeError, TypeError): + pass + content_data = { 'content_id': content.id, 'task_id': task_id, @@ -75,15 +120,18 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int 'seo_description': content.meta_description or '', 'primary_keyword': content.primary_keyword or '', 'secondary_keywords': content.secondary_keywords or [], - # Content model has no featured_image field - 'featured_image_url': None, + # Send featured image URL from Images model + 'featured_image_url': featured_image_url, + 'gallery_images': gallery_images, # Send cluster and sector IDs (Content has ForeignKey to cluster, not many-to-many) 'cluster_id': content.cluster.id if content.cluster else None, 'sector_id': content.sector.id if content.sector else None, - # Content model has no direct sectors/clusters array or tags + # Send categories and tags from taxonomy mappings and keywords + 'categories': categories, + 'tags': tags, + # Keep for backward compatibility 'sectors': [], - 'clusters': [], - 'tags': [] + 'clusters': [] } # Call WordPress REST API