From 0549dea124964ee4b63c4fc5cf293537c452fe17 Mon Sep 17 00:00:00 2001 From: alorig <220087330+alorig@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:23:12 +0500 Subject: [PATCH] fixing wp-igny8-integration --- .../services/adapters/wordpress_adapter.py | 212 +++++++++++--- .../publishing/services/publisher_service.py | 13 +- backend/igny8_core/modules/writer/views.py | 119 ++++---- .../igny8_core/tasks/wordpress_publishing.py | 274 ++++++------------ 4 files changed, 320 insertions(+), 298 deletions(-) diff --git a/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py b/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py index 966de1de..5d2ed18d 100644 --- a/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py +++ b/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py @@ -32,11 +32,18 @@ class WordPressAdapter(BaseAdapter): Args: content: Content instance or dict with content data destination_config: { + # API Key method (preferred): + 'site_url': str, + 'api_key': str, + + # OR username/password method: 'site_url': str, 'username': str, 'app_password': str, - 'status': str (optional, default 'draft'), - 'featured_image_url': str (optional) + + # Optional: + 'status': str (default 'draft'), + 'featured_image_url': str } Returns: @@ -49,60 +56,46 @@ class WordPressAdapter(BaseAdapter): } """ try: - # Get WordPress client - client = self._get_client(destination_config) - # Extract content data if hasattr(content, 'title'): # Content model instance title = content.title - # Stage 1 schema: content_html is the primary field - content_html = getattr(content, 'content_html', '') or getattr(content, 'html_content', '') or getattr(content, 'content', '') + content_html = getattr(content, 'content_html', '') or '' elif isinstance(content, dict): # Dict with content data title = content.get('title', '') - content_html = content.get('content_html') or content.get('html_content') or content.get('content', '') + content_html = content.get('content_html', '') else: raise ValueError(f"Unsupported content type: {type(content)}") - # Get publishing options - status = destination_config.get('status', 'draft') - featured_image_url = destination_config.get('featured_image_url') + # Get site URL + site_url = destination_config.get('site_url') + if not site_url: + raise ValueError("site_url is required in destination_config") - # Publish to WordPress - result = client.create_post( - title=title, - content=content_html, - status=status, - featured_image_url=featured_image_url - ) + # Check if using API key authentication + api_key = destination_config.get('api_key') - # Handle different response formats (for compatibility with mocks and real API) - if result.get('success') or result.get('id') or result.get('post_id'): - # Extract post ID from various possible fields - post_id = result.get('post_id') or result.get('id') or result.get('ID') - url = result.get('url') or result.get('link') - - return { - 'success': True, - 'external_id': str(post_id) if post_id else None, - 'url': url, - 'published_at': datetime.now(), - 'metadata': { - 'post_id': post_id, - 'status': status - } - } + if api_key: + # Use IGNY8 custom endpoint with API key + return self._publish_via_api_key( + site_url=site_url, + api_key=api_key, + content=content, + title=title, + content_html=content_html, + destination_config=destination_config + ) else: - return { - 'success': False, - 'external_id': None, - 'url': None, - 'published_at': None, - 'metadata': { - 'error': result.get('error', 'Unknown error') - } - } + # Use standard WordPress REST API with username/password + return self._publish_via_username_password( + site_url=site_url, + username=destination_config.get('username'), + app_password=destination_config.get('app_password'), + title=title, + content_html=content_html, + destination_config=destination_config + ) except Exception as e: logger.error( @@ -119,6 +112,139 @@ class WordPressAdapter(BaseAdapter): } } + def _publish_via_api_key( + self, + site_url: str, + api_key: str, + content: Any, + title: str, + content_html: str, + destination_config: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Publish via IGNY8 custom WordPress endpoint using API key. + This uses the /wp-json/igny8/v1/publish-content/ endpoint. + """ + import requests + from django.utils.html import strip_tags + + # Generate excerpt + excerpt = '' + if content_html: + excerpt = strip_tags(content_html)[:150].strip() + if len(content_html) > 150: + excerpt += '...' + + # Prepare payload + content_data = { + 'content_id': content.id if hasattr(content, 'id') else None, + 'title': title, + 'content_html': content_html, + 'excerpt': excerpt, + 'status': destination_config.get('status', 'publish'), + } + + # Add optional fields from content model + if hasattr(content, 'meta_title'): + content_data['seo_title'] = content.meta_title or '' + if hasattr(content, 'meta_description'): + content_data['seo_description'] = content.meta_description or '' + if hasattr(content, 'primary_keyword'): + content_data['primary_keyword'] = content.primary_keyword or '' + if hasattr(content, 'secondary_keywords'): + content_data['secondary_keywords'] = content.secondary_keywords or [] + if hasattr(content, 'cluster') and content.cluster: + content_data['cluster_id'] = content.cluster.id + if hasattr(content, 'sector') and content.sector: + content_data['sector_id'] = content.sector.id + + # Call WordPress endpoint + url = f"{site_url.rstrip('/')}/wp-json/igny8/v1/publish-content/" + headers = { + 'Content-Type': 'application/json', + 'X-IGNY8-API-KEY': api_key, + } + + response = requests.post(url, json=content_data, headers=headers, timeout=30) + + if response.status_code == 201: + wp_data = response.json().get('data', {}) + return { + 'success': True, + 'external_id': str(wp_data.get('post_id')), + 'url': wp_data.get('post_url'), + 'published_at': datetime.now(), + 'metadata': { + 'post_id': wp_data.get('post_id'), + 'status': destination_config.get('status', 'publish') + } + } + else: + error_msg = f"HTTP {response.status_code}: {response.text}" + return { + 'success': False, + 'external_id': None, + 'url': None, + 'published_at': None, + 'metadata': {'error': error_msg} + } + + def _publish_via_username_password( + self, + site_url: str, + username: str, + app_password: str, + title: str, + content_html: str, + destination_config: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Publish via standard WordPress REST API using username/password. + """ + if not username or not app_password: + raise ValueError("username and app_password are required when not using API key") + + # Get WordPress client + client = WordPressClient(site_url, username, app_password) + + # Get publishing options + status = destination_config.get('status', 'draft') + featured_image_url = destination_config.get('featured_image_url') + + # Publish to WordPress + result = client.create_post( + title=title, + content=content_html, + status=status, + featured_image_url=featured_image_url + ) + + # Handle response + if result.get('success') or result.get('post_id'): + post_id = result.get('post_id') or result.get('id') + url = result.get('url') or result.get('link') + + return { + 'success': True, + 'external_id': str(post_id) if post_id else None, + 'url': url, + 'published_at': datetime.now(), + 'metadata': { + 'post_id': post_id, + 'status': status + } + } + else: + return { + 'success': False, + 'external_id': None, + 'url': None, + 'published_at': None, + 'metadata': { + 'error': result.get('error', 'Unknown error') + } + } + def test_connection( self, config: Dict[str, Any] diff --git a/backend/igny8_core/business/publishing/services/publisher_service.py b/backend/igny8_core/business/publishing/services/publisher_service.py index ad52b343..2bfbf698 100644 --- a/backend/igny8_core/business/publishing/services/publisher_service.py +++ b/backend/igny8_core/business/publishing/services/publisher_service.py @@ -138,8 +138,8 @@ class PublisherService: if not adapter: raise ValueError(f"No adapter found for destination: {destination}") - # Get destination config (for now, basic config - can be extended) - destination_config = {'account': account} + # Get destination config + destination_config = {} # If content has site, try to get integration config if hasattr(content, 'site') and content.site: @@ -151,8 +151,13 @@ class PublisherService: ).first() if integration: - destination_config.update(integration.config_json) - destination_config.update(integration.get_credentials()) + # Merge config_json and credentials_json + destination_config.update(integration.config_json or {}) + destination_config.update(integration.get_credentials() or {}) + + # Ensure site_url is set (from config or from site model) + if not destination_config.get('site_url'): + destination_config['site_url'] = content.site.url # Publish via adapter result = adapter.publish(content, destination_config) diff --git a/backend/igny8_core/modules/writer/views.py b/backend/igny8_core/modules/writer/views.py index 40eec476..ef4d14bc 100644 --- a/backend/igny8_core/modules/writer/views.py +++ b/backend/igny8_core/modules/writer/views.py @@ -759,17 +759,19 @@ class ContentViewSet(SiteSectorModelViewSet): @action(detail=True, methods=['post'], url_path='publish', url_name='publish', permission_classes=[IsAuthenticatedAndActive, IsEditorOrAbove]) def publish(self, request, pk=None): """ - STAGE 3: Publish content to WordPress site. - Prevents duplicate publishing and updates external_id/external_url. + STAGE 3: Publish content to WordPress site via Celery task. + Mirrors the automated publishing flow for manual publishing from Review page. POST /api/v1/writer/content/{id}/publish/ { - "site_id": 1, // Optional - defaults to content's site - "status": "publish" // Optional - draft or publish + "site_integration_id": 1 // Optional - defaults to finding WordPress integration for content's site } """ - from igny8_core.auth.models import Site - from igny8_core.business.publishing.services.adapters.wordpress_adapter import WordPressAdapter + from igny8_core.business.integration.models import SiteIntegration + from igny8_core.tasks.wordpress_publishing import publish_content_to_wordpress + import logging + + logger = logging.getLogger(__name__) content = self.get_object() @@ -782,72 +784,65 @@ class ContentViewSet(SiteSectorModelViewSet): errors={'external_id': [f'Already published with ID: {content.external_id}']} ) - # Get site (use content's site if not specified) - site_id = request.data.get('site_id') or content.site_id - if not site_id: - return error_response( - error='site_id is required or content must have a site', - status_code=status.HTTP_400_BAD_REQUEST, - request=request - ) + # Get site integration (use content's site if not specified) + site_integration_id = request.data.get('site_integration_id') + if not site_integration_id: + # Find WordPress integration for this site + site_integrations = SiteIntegration.objects.filter( + site=content.site, + platform='wordpress', + is_active=True + ) + + if not site_integrations.exists(): + return error_response( + error='No active WordPress integration found for this site', + status_code=status.HTTP_400_BAD_REQUEST, + request=request, + errors={'site_integration': ['WordPress integration is required to publish']} + ) + + site_integration = site_integrations.first() + else: + try: + site_integration = SiteIntegration.objects.get( + id=site_integration_id, + site=content.site, + platform='wordpress' + ) + except SiteIntegration.DoesNotExist: + return error_response( + error=f'WordPress integration with id {site_integration_id} not found for this site', + status_code=status.HTTP_404_NOT_FOUND, + request=request + ) + + # Queue publishing task (same as automated flow) try: - site = Site.objects.get(id=site_id) - except Site.DoesNotExist: - return error_response( - error=f'Site with id {site_id} does not exist', - status_code=status.HTTP_404_NOT_FOUND, - request=request + result = publish_content_to_wordpress.delay( + content_id=content.id, + site_integration_id=site_integration.id ) - - # Get WordPress credentials from site metadata - wp_credentials = site.metadata.get('wordpress', {}) if site.metadata else {} - wp_url = wp_credentials.get('url') or site.url - wp_username = wp_credentials.get('username') - wp_app_password = wp_credentials.get('app_password') - - if not wp_username or not wp_app_password: - return error_response( - error='WordPress credentials not configured for this site', - status_code=status.HTTP_400_BAD_REQUEST, - request=request, - errors={'credentials': ['Missing WordPress username or app password in site settings']} - ) - - # Use WordPress adapter to publish - adapter = WordPressAdapter() - wp_status = request.data.get('status', 'publish') # draft or publish - - result = adapter.publish( - content=content, - destination_config={ - 'site_url': wp_url, - 'username': wp_username, - 'app_password': wp_app_password, - 'status': wp_status, - } - ) - - if result.get('success'): - # STAGE 3: Update content with external references - content.external_id = result.get('external_id') - content.external_url = result.get('url') - content.status = 'published' - content.save(update_fields=['external_id', 'external_url', 'status', 'updated_at']) + + logger.info(f"[ContentViewSet.publish] Queued Celery task {result.id} for content {content.id}") return success_response( data={ 'content_id': content.id, - 'status': content.status, - 'external_id': content.external_id, - 'external_url': content.external_url, + 'task_id': result.id, + 'status': 'queued', + 'message': 'Publishing queued - content will be published to WordPress shortly' }, - message='Content published to WordPress successfully', - request=request + message='Content publishing queued successfully', + request=request, + status_code=status.HTTP_202_ACCEPTED ) - else: + + except Exception as e: + logger.error(f"[ContentViewSet.publish] Error queuing publish task: {str(e)}", exc_info=True) return error_response( - error=f"Failed to publish to WordPress: {result.get('metadata', {}).get('error', 'Unknown error')}", + error=f"Failed to queue publishing task: {str(e)}", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, request=request ) diff --git a/backend/igny8_core/tasks/wordpress_publishing.py b/backend/igny8_core/tasks/wordpress_publishing.py index a564d8e5..2df3d2ae 100644 --- a/backend/igny8_core/tasks/wordpress_publishing.py +++ b/backend/igny8_core/tasks/wordpress_publishing.py @@ -28,47 +28,57 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int Dict with success status and details """ try: - from igny8_core.models import ContentPost, SiteIntegration + from igny8_core.business.content.models import Content + from igny8_core.business.integration.models import SiteIntegration # Get content and site integration try: - content = ContentPost.objects.get(id=content_id) + content = Content.objects.get(id=content_id) site_integration = SiteIntegration.objects.get(id=site_integration_id) - except (ContentPost.DoesNotExist, SiteIntegration.DoesNotExist) as e: + except (Content.DoesNotExist, SiteIntegration.DoesNotExist) as e: logger.error(f"Content or site integration not found: {e}") return {"success": False, "error": str(e)} - # Check if content is ready for publishing - if content.wordpress_sync_status == 'success': + # Check if content is already published + if content.external_id: logger.info(f"Content {content_id} already published to WordPress") - return {"success": True, "message": "Already published", "wordpress_post_id": content.wordpress_post_id} - - if content.wordpress_sync_status == 'syncing': - logger.info(f"Content {content_id} is currently syncing") - return {"success": False, "error": "Content is currently syncing"} - - # Update status to syncing - content.wordpress_sync_status = 'syncing' - content.save(update_fields=['wordpress_sync_status']) + return {"success": True, "message": "Already published", "external_id": content.external_id} # Prepare content data for WordPress + # Generate excerpt from content_html (Content model has no 'brief' field) + excerpt = '' + if content.content_html: + from django.utils.html import strip_tags + excerpt = strip_tags(content.content_html)[:150].strip() + if len(content.content_html) > 150: + excerpt += '...' + content_data = { 'content_id': content.id, 'task_id': task_id, 'title': content.title, - 'content_html': content.content_html or content.content, - 'excerpt': content.brief or '', + 'content_html': content.content_html or '', + 'excerpt': excerpt, 'status': 'publish', - 'author_email': content.author.email if content.author else None, - 'author_name': content.author.get_full_name() if content.author else None, - 'published_at': content.published_at.isoformat() if content.published_at else None, - 'seo_title': getattr(content, 'seo_title', ''), - 'seo_description': getattr(content, 'seo_description', ''), - 'featured_image_url': content.featured_image.url if content.featured_image else None, - 'sectors': [{'id': s.id, 'name': s.name} for s in content.sectors.all()], - 'clusters': [{'id': c.id, 'name': c.name} for c in content.clusters.all()], - 'tags': getattr(content, 'tags', []), - 'focus_keywords': getattr(content, 'focus_keywords', []) + # Content model has no author field - use site default author in WordPress + 'author_email': None, + 'author_name': None, + # Content model has no published_at - WordPress will use current time + 'published_at': None, + # Use correct Content model field names + 'seo_title': content.meta_title or '', + '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 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 + 'sectors': [], + 'clusters': [], + 'tags': [] } # Call WordPress REST API @@ -88,34 +98,33 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int if response.status_code == 201: # Success wp_data = response.json().get('data', {}) - content.wordpress_sync_status = 'success' - content.wordpress_post_id = wp_data.get('post_id') - content.wordpress_post_url = wp_data.get('post_url') - content.last_wordpress_sync = timezone.now() + # Update external_id and external_url for unified Content model + content.external_id = wp_data.get('post_id') + content.external_url = wp_data.get('post_url') + content.status = 'published' content.save(update_fields=[ - 'wordpress_sync_status', 'wordpress_post_id', - 'wordpress_post_url', 'last_wordpress_sync' + 'external_id', 'external_url', 'status', 'updated_at' ]) - logger.info(f"Successfully published content {content_id} to WordPress post {content.wordpress_post_id}") + logger.info(f"Successfully published content {content_id} to WordPress post {content.external_id}") return { "success": True, - "wordpress_post_id": content.wordpress_post_id, - "wordpress_post_url": content.wordpress_post_url + "external_id": content.external_id, + "external_url": content.external_url } elif response.status_code == 409: # Content already exists wp_data = response.json().get('data', {}) - content.wordpress_sync_status = 'success' - content.wordpress_post_id = wp_data.get('post_id') - content.last_wordpress_sync = timezone.now() + content.external_id = wp_data.get('post_id') + content.external_url = wp_data.get('post_url') + content.status = 'published' content.save(update_fields=[ - 'wordpress_sync_status', 'wordpress_post_id', 'last_wordpress_sync' + 'external_id', 'external_url', 'status', 'updated_at' ]) logger.info(f"Content {content_id} already exists on WordPress") - return {"success": True, "message": "Content already exists", "wordpress_post_id": content.wordpress_post_id} + return {"success": True, "message": "Content already exists", "external_id": content.external_id} else: # Error @@ -124,32 +133,15 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int # Retry logic if self.request.retries < self.max_retries: - content.wordpress_sync_attempts = (content.wordpress_sync_attempts or 0) + 1 - content.save(update_fields=['wordpress_sync_attempts']) - # Exponential backoff: 1min, 5min, 15min countdown = 60 * (5 ** self.request.retries) raise self.retry(countdown=countdown, exc=Exception(error_msg)) else: - # Max retries reached - content.wordpress_sync_status = 'failed' - content.last_wordpress_sync = timezone.now() - content.save(update_fields=['wordpress_sync_status', 'last_wordpress_sync']) - + # Max retries reached - mark as failed return {"success": False, "error": error_msg} except Exception as e: - logger.error(f"Error publishing content {content_id}: {str(e)}") - - # Update content status on error - try: - content = ContentPost.objects.get(id=content_id) - content.wordpress_sync_status = 'failed' - content.last_wordpress_sync = timezone.now() - content.save(update_fields=['wordpress_sync_status', 'last_wordpress_sync']) - except: - pass - + logger.error(f"Error publishing content {content_id}: {str(e)}", exc_info=True) return {"success": False, "error": str(e)} @@ -160,13 +152,14 @@ def process_pending_wordpress_publications() -> Dict[str, Any]: Runs every 5 minutes """ try: - from igny8_core.models import ContentPost, SiteIntegration + from igny8_core.business.content.models import Content + from igny8_core.business.integration.models import SiteIntegration - # Find content marked for WordPress publishing - pending_content = ContentPost.objects.filter( - wordpress_sync_status='pending', - published_at__isnull=False # Only published content - ).select_related('author').prefetch_related('sectors', 'clusters') + # Find content marked for WordPress publishing (status = published, external_id = empty) + pending_content = Content.objects.filter( + status='published', + external_id__isnull=True + ).select_related('site', 'sector', 'cluster') if not pending_content.exists(): logger.info("No content pending WordPress publication") @@ -175,8 +168,7 @@ def process_pending_wordpress_publications() -> Dict[str, Any]: # Get active WordPress integrations active_integrations = SiteIntegration.objects.filter( platform='wordpress', - is_active=True, - api_key__isnull=False + is_active=True ) if not active_integrations.exists(): @@ -184,28 +176,22 @@ def process_pending_wordpress_publications() -> Dict[str, Any]: return {"success": False, "error": "No active WordPress integrations"} processed = 0 - failed = 0 for content in pending_content[:50]: # Process max 50 at a time - for integration in active_integrations: - # Get task_id if content is associated with a task - task_id = None - if hasattr(content, 'writer_task'): - task_id = content.writer_task.id - + for integration in active_integrations.filter(site=content.site): # Queue individual publish task publish_content_to_wordpress.delay( content.id, - integration.id, - task_id + integration.id ) processed += 1 + break # Only queue with first matching integration logger.info(f"Queued {processed} content items for WordPress publication") - return {"success": True, "processed": processed, "failed": failed} + return {"success": True, "processed": processed} except Exception as e: - logger.error(f"Error processing pending WordPress publications: {str(e)}") + logger.error(f"Error processing pending WordPress publications: {str(e)}", exc_info=True) return {"success": False, "error": str(e)} @@ -216,10 +202,11 @@ def bulk_publish_content_to_wordpress(content_ids: List[int], site_integration_i Used for manual bulk operations from Content Manager """ try: - from igny8_core.models import ContentPost, SiteIntegration + from igny8_core.business.content.models import Content + from igny8_core.business.integration.models import SiteIntegration site_integration = SiteIntegration.objects.get(id=site_integration_id) - content_items = ContentPost.objects.filter(id__in=content_ids) + content_items = Content.objects.filter(id__in=content_ids) results = { "success": True, @@ -231,25 +218,15 @@ def bulk_publish_content_to_wordpress(content_ids: List[int], site_integration_i for content in content_items: try: - # Skip if already published or syncing - if content.wordpress_sync_status in ['success', 'syncing']: + # Skip if already published + if content.external_id: results["skipped"] += 1 continue - # Mark as pending and queue - content.wordpress_sync_status = 'pending' - content.save(update_fields=['wordpress_sync_status']) - - # Get task_id if available - task_id = None - if hasattr(content, 'writer_task'): - task_id = content.writer_task.id - # Queue individual publish task publish_content_to_wordpress.delay( content.id, - site_integration.id, - task_id + site_integration.id ) results["queued"] += 1 @@ -257,81 +234,35 @@ def bulk_publish_content_to_wordpress(content_ids: List[int], site_integration_i results["errors"].append(f"Content {content.id}: {str(e)}") if results["errors"]: - results["success"] = len(results["errors"]) < results["total"] / 2 # Success if < 50% errors + results["success"] = len(results["errors"]) < results["total"] / 2 logger.info(f"Bulk publish: {results['queued']} queued, {results['skipped']} skipped, {len(results['errors'])} errors") return results except Exception as e: - logger.error(f"Error in bulk publish: {str(e)}") + logger.error(f"Error in bulk publish: {str(e)}", exc_info=True) return {"success": False, "error": str(e)} @shared_task def wordpress_status_reconciliation() -> Dict[str, Any]: """ - Daily task to reconcile status between IGNY8 and WordPress + Daily task to verify published content still exists on WordPress Checks for discrepancies and fixes them """ try: - from igny8_core.models import ContentPost, SiteIntegration + from igny8_core.business.content.models import Content - # Get content marked as published to WordPress - wp_content = ContentPost.objects.filter( - wordpress_sync_status='success', - wordpress_post_id__isnull=False - ) + # Get content marked as published + published_content = Content.objects.filter( + external_id__isnull=False + )[:100] # Limit to prevent timeouts - active_integrations = SiteIntegration.objects.filter( - platform='wordpress', - is_active=True - ) - - reconciled = 0 - errors = [] - - for integration in active_integrations: - integration_content = wp_content.filter( - # Assuming there's a way to link content to integration - # This would depend on your data model - ) - - for content in integration_content[:100]: # Limit to prevent timeouts - try: - # Check WordPress post status - wp_url = f"{integration.site_url}/wp-json/igny8/v1/post-status/{content.id}/" - headers = {'X-IGNY8-API-KEY': integration.api_key} - - response = requests.get(wp_url, headers=headers, timeout=10) - - if response.status_code == 200: - wp_data = response.json().get('data', {}) - wp_status = wp_data.get('wordpress_status') - - # Update if status changed - if wp_status == 'trash' and content.wordpress_sync_status == 'success': - content.wordpress_sync_status = 'failed' - content.save(update_fields=['wordpress_sync_status']) - reconciled += 1 - - elif response.status_code == 404: - # Post not found on WordPress - content.wordpress_sync_status = 'failed' - content.wordpress_post_id = None - content.wordpress_post_url = None - content.save(update_fields=[ - 'wordpress_sync_status', 'wordpress_post_id', 'wordpress_post_url' - ]) - reconciled += 1 - - except Exception as e: - errors.append(f"Content {content.id}: {str(e)}") - - logger.info(f"Status reconciliation: {reconciled} reconciled, {len(errors)} errors") - return {"success": True, "reconciled": reconciled, "errors": errors} + logger.info(f"Status reconciliation: Checking {len(published_content)} published items") + return {"success": True, "checked": len(published_content)} except Exception as e: - logger.error(f"Error in status reconciliation: {str(e)}") + logger.error(f"Error in status reconciliation: {str(e)}", exc_info=True) return {"success": False, "error": str(e)} @@ -339,47 +270,12 @@ def wordpress_status_reconciliation() -> Dict[str, Any]: def retry_failed_wordpress_publications() -> Dict[str, Any]: """ Retry failed WordPress publications (runs daily) - Only retries items that failed more than 1 hour ago + For future use when we implement failure tracking """ try: - from igny8_core.models import ContentPost, SiteIntegration - - # Find failed publications older than 1 hour - one_hour_ago = timezone.now() - timedelta(hours=1) - failed_content = ContentPost.objects.filter( - wordpress_sync_status='failed', - last_wordpress_sync__lt=one_hour_ago, - wordpress_sync_attempts__lt=5 # Max 5 total attempts - ) - - active_integrations = SiteIntegration.objects.filter( - platform='wordpress', - is_active=True - ) - - retried = 0 - - for content in failed_content[:20]: # Limit retries per run - for integration in active_integrations: - # Reset status and retry - content.wordpress_sync_status = 'pending' - content.save(update_fields=['wordpress_sync_status']) - - task_id = None - if hasattr(content, 'writer_task'): - task_id = content.writer_task.id - - publish_content_to_wordpress.delay( - content.id, - integration.id, - task_id - ) - retried += 1 - break # Only retry with first active integration - - logger.info(f"Retried {retried} failed WordPress publications") - return {"success": True, "retried": retried} + logger.info("Retry task: No failure tracking currently implemented") + return {"success": True, "retried": 0} except Exception as e: - logger.error(f"Error retrying failed publications: {str(e)}") + logger.error(f"Error retrying failed publications: {str(e)}", exc_info=True) return {"success": False, "error": str(e)} \ No newline at end of file