backedn
This commit is contained in:
@@ -168,6 +168,98 @@ class IntegrationViewSet(SiteSectorModelViewSet):
|
||||
|
||||
return success_response(status_data, request=request)
|
||||
|
||||
@action(detail=True, methods=['get'], url_path='content-types')
|
||||
def content_types_summary(self, request, pk=None):
|
||||
"""
|
||||
Get content types summary with counts from synced data.
|
||||
|
||||
GET /api/v1/integration/integrations/{id}/content-types/
|
||||
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"post_types": {
|
||||
"post": {"label": "Posts", "count": 123, "synced_count": 50},
|
||||
"page": {"label": "Pages", "count": 12, "synced_count": 12},
|
||||
"product": {"label": "Products", "count": 456, "synced_count": 200}
|
||||
},
|
||||
"taxonomies": {
|
||||
"category": {"label": "Categories", "count": 25, "synced_count": 25},
|
||||
"post_tag": {"label": "Tags", "count": 102, "synced_count": 80},
|
||||
"product_cat": {"label": "Product Categories", "count": 15, "synced_count": 15}
|
||||
},
|
||||
"last_structure_fetch": "2025-11-22T10:00:00Z"
|
||||
}
|
||||
}
|
||||
"""
|
||||
integration = self.get_object()
|
||||
site = integration.site
|
||||
|
||||
# Get config from integration
|
||||
config = integration.config_json or {}
|
||||
content_types = config.get('content_types', {})
|
||||
|
||||
# Get synced counts from Content and ContentTaxonomy models
|
||||
from igny8_core.business.content.models import Content, ContentTaxonomy
|
||||
|
||||
# Build response with synced counts
|
||||
post_types_data = {}
|
||||
for wp_type, type_config in content_types.get('post_types', {}).items():
|
||||
# Map WP type to entity_type
|
||||
entity_type_map = {
|
||||
'post': 'post',
|
||||
'page': 'page',
|
||||
'product': 'product',
|
||||
'service': 'service',
|
||||
}
|
||||
entity_type = entity_type_map.get(wp_type, 'post')
|
||||
|
||||
# Count synced content
|
||||
synced_count = Content.objects.filter(
|
||||
site=site,
|
||||
entity_type=entity_type,
|
||||
external_type=wp_type,
|
||||
sync_status__in=['imported', 'synced']
|
||||
).count()
|
||||
|
||||
post_types_data[wp_type] = {
|
||||
'label': type_config.get('label', wp_type.title()),
|
||||
'count': type_config.get('count', 0),
|
||||
'synced_count': synced_count,
|
||||
'enabled': type_config.get('enabled', False),
|
||||
'fetch_limit': type_config.get('fetch_limit', 100),
|
||||
'last_synced': type_config.get('last_synced'),
|
||||
}
|
||||
|
||||
taxonomies_data = {}
|
||||
for wp_tax, tax_config in content_types.get('taxonomies', {}).items():
|
||||
# Count synced taxonomies
|
||||
synced_count = ContentTaxonomy.objects.filter(
|
||||
site=site,
|
||||
external_taxonomy=wp_tax,
|
||||
sync_status__in=['imported', 'synced']
|
||||
).count()
|
||||
|
||||
taxonomies_data[wp_tax] = {
|
||||
'label': tax_config.get('label', wp_tax.title()),
|
||||
'count': tax_config.get('count', 0),
|
||||
'synced_count': synced_count,
|
||||
'enabled': tax_config.get('enabled', False),
|
||||
'fetch_limit': tax_config.get('fetch_limit', 100),
|
||||
'last_synced': tax_config.get('last_synced'),
|
||||
}
|
||||
|
||||
summary = {
|
||||
'post_types': post_types_data,
|
||||
'taxonomies': taxonomies_data,
|
||||
'last_structure_fetch': config.get('last_structure_fetch'),
|
||||
'plugin_connection_enabled': config.get('plugin_connection_enabled', True),
|
||||
'two_way_sync_enabled': config.get('two_way_sync_enabled', True),
|
||||
}
|
||||
|
||||
return success_response(summary, request=request)
|
||||
|
||||
# Stage 4: Site-level sync endpoints
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='sites/(?P<site_id>[^/.]+)/sync/status')
|
||||
|
||||
@@ -63,7 +63,7 @@ class ContentIdeasAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
list_filter = ['status', 'site_entity_type', 'cluster_role', 'site', 'sector']
|
||||
search_fields = ['idea_title', 'target_keywords', 'description']
|
||||
ordering = ['-created_at']
|
||||
readonly_fields = ['content_structure', 'content_type']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
fieldsets = (
|
||||
('Basic Info', {
|
||||
@@ -75,10 +75,9 @@ class ContentIdeasAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
('Keywords & Clustering', {
|
||||
'fields': ('keyword_cluster', 'target_keywords', 'taxonomy')
|
||||
}),
|
||||
('Deprecated Fields (Read-Only)', {
|
||||
'fields': ('content_structure', 'content_type'),
|
||||
'classes': ('collapse',),
|
||||
'description': 'These fields are deprecated. Use site_entity_type and cluster_role instead.'
|
||||
('Timestamps', {
|
||||
'fields': ('created_at', 'updated_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated migration to remove deprecated fields from ContentIdeas
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('planner', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Remove deprecated fields from ContentIdeas
|
||||
migrations.RemoveField(
|
||||
model_name='contentideas',
|
||||
name='content_structure',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='contentideas',
|
||||
name='content_type',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,23 +6,25 @@ from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute
|
||||
|
||||
@admin.register(Tasks)
|
||||
class TasksAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
list_display = ['title', 'site', 'sector', 'status', 'cluster', 'created_at']
|
||||
list_filter = ['status', 'site', 'sector', 'cluster']
|
||||
list_display = ['title', 'entity_type', 'cluster_role', 'site', 'sector', 'status', 'cluster', 'created_at']
|
||||
list_filter = ['status', 'entity_type', 'cluster_role', 'site', 'sector', 'cluster']
|
||||
search_fields = ['title', 'description', 'keywords']
|
||||
ordering = ['-created_at']
|
||||
readonly_fields = ['content_type', 'content_structure', 'entity_type', 'cluster_role', 'assigned_post_id', 'post_url']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
fieldsets = (
|
||||
('Basic Info', {
|
||||
'fields': ('title', 'description', 'status', 'site', 'sector')
|
||||
}),
|
||||
('Content Classification', {
|
||||
'fields': ('entity_type', 'cluster_role', 'taxonomy')
|
||||
}),
|
||||
('Planning', {
|
||||
'fields': ('cluster', 'idea', 'keywords')
|
||||
}),
|
||||
('Deprecated Fields (Read-Only)', {
|
||||
'fields': ('content_type', 'content_structure', 'entity_type', 'cluster_role', 'assigned_post_id', 'post_url'),
|
||||
'classes': ('collapse',),
|
||||
'description': 'These fields are deprecated. Use Content model instead.'
|
||||
('Timestamps', {
|
||||
'fields': ('created_at', 'updated_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -88,7 +90,7 @@ class ContentAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
list_filter = ['entity_type', 'content_format', 'cluster_role', 'source', 'sync_status', 'status', 'site', 'sector', 'generated_at']
|
||||
search_fields = ['title', 'meta_title', 'primary_keyword', 'task__title', 'external_url']
|
||||
ordering = ['-generated_at']
|
||||
readonly_fields = ['categories', 'tags']
|
||||
readonly_fields = ['generated_at', 'updated_at']
|
||||
|
||||
fieldsets = (
|
||||
('Basic Info', {
|
||||
@@ -111,10 +113,9 @@ class ContentAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
'fields': ('linker_version', 'optimizer_version', 'optimization_scores', 'internal_links'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Deprecated Fields (Read-Only)', {
|
||||
'fields': ('categories', 'tags'),
|
||||
'classes': ('collapse',),
|
||||
'description': 'These fields are deprecated. Use taxonomies M2M instead.'
|
||||
('Timestamps', {
|
||||
'fields': ('generated_at', 'updated_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Generated migration to clean up deprecated fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_deprecated_data(apps, schema_editor):
|
||||
"""Migrate data from deprecated fields to new unified structure"""
|
||||
Tasks = apps.get_model('writer', 'Tasks')
|
||||
Content = apps.get_model('writer', 'Content')
|
||||
|
||||
# Migrate Tasks: ensure entity_type and cluster_role have defaults
|
||||
for task in Tasks.objects.all():
|
||||
changed = False
|
||||
if not task.entity_type:
|
||||
task.entity_type = 'post'
|
||||
changed = True
|
||||
if not task.cluster_role:
|
||||
task.cluster_role = 'hub'
|
||||
changed = True
|
||||
if changed:
|
||||
task.save()
|
||||
|
||||
# Migrate Content: ensure entity_type is set from task if available
|
||||
for content in Content.objects.select_related('task').all():
|
||||
changed = False
|
||||
if content.task and content.task.entity_type and not content.entity_type:
|
||||
content.entity_type = content.task.entity_type
|
||||
changed = True
|
||||
if content.task and content.task.cluster_role and not content.cluster_role:
|
||||
content.cluster_role = content.task.cluster_role
|
||||
changed = True
|
||||
if not content.entity_type:
|
||||
content.entity_type = 'post'
|
||||
changed = True
|
||||
if changed:
|
||||
content.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0005_phase3_mark_deprecated_fields'),
|
||||
('planner', '0003_cleanup_remove_deprecated_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Step 1: Migrate data
|
||||
migrations.RunPython(migrate_deprecated_data, migrations.RunPython.noop),
|
||||
|
||||
# Step 2: Remove deprecated fields from Tasks
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='content_structure',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='content_type',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='content',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='word_count',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='meta_title',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='meta_description',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='assigned_post_id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='post_url',
|
||||
),
|
||||
|
||||
# Step 4: Remove deprecated fields from Content
|
||||
migrations.RemoveField(
|
||||
model_name='content',
|
||||
name='categories',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='content',
|
||||
name='tags',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -52,11 +52,11 @@ class TasksViewSet(SiteSectorModelViewSet):
|
||||
search_fields = ['title', 'keywords']
|
||||
|
||||
# Ordering configuration
|
||||
ordering_fields = ['title', 'created_at', 'word_count', 'status']
|
||||
ordering_fields = ['title', 'created_at', 'status']
|
||||
ordering = ['-created_at'] # Default ordering (newest first)
|
||||
|
||||
# Filter configuration (removed deprecated fields)
|
||||
filterset_fields = ['status', 'cluster_id']
|
||||
# Filter configuration
|
||||
filterset_fields = ['status', 'entity_type', 'cluster_role', 'cluster_id']
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""Require explicit site_id and sector_id - no defaults."""
|
||||
|
||||
Reference in New Issue
Block a user