This commit is contained in:
alorig
2025-11-19 21:56:03 +05:00
parent 7321803006
commit 38f6026e73
13 changed files with 2370 additions and 142 deletions

View File

@@ -17,6 +17,8 @@ from igny8_core.business.site_building.models import (
HeroImageryDirection,
PageBlueprint,
SiteBlueprint,
SiteBlueprintCluster,
SiteBlueprintTaxonomy,
)
from igny8_core.business.site_building.services import (
PageGenerationService,
@@ -229,6 +231,380 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
payload = self.wizard_context_service.build_context(blueprint)
return success_response(payload, request=request)
@action(detail=True, methods=['post'], url_path='workflow/step')
def update_workflow_step(self, request, pk=None):
"""
Update workflow step status.
Request body:
{
"step": "business_details", # Step name
"status": "ready", # Status: ready, blocked, in_progress
"metadata": {} # Optional metadata
}
Returns:
{
"current_step": "business_details",
"step_status": {...},
"completed": false
}
"""
blueprint = self.get_object()
if not self.workflow_service.enabled:
return error_response(
'Workflow service not enabled',
status.HTTP_400_BAD_REQUEST,
request
)
step = request.data.get('step')
status_value = request.data.get('status')
metadata = request.data.get('metadata', {})
if not step or not status_value:
return error_response(
'step and status are required',
status.HTTP_400_BAD_REQUEST,
request
)
valid_statuses = ['ready', 'blocked', 'in_progress', 'complete']
if status_value not in valid_statuses:
return error_response(
f'Invalid status. Must be one of: {", ".join(valid_statuses)}',
status.HTTP_400_BAD_REQUEST,
request
)
updated_state = self.workflow_service.update_step(
blueprint,
step,
status_value,
metadata
)
if not updated_state:
return error_response(
'Failed to update workflow step',
status.HTTP_500_INTERNAL_SERVER_ERROR,
request
)
# Serialize state
serialized = self.workflow_service.serialize_state(updated_state)
return success_response(
data=serialized,
request=request
)
@action(detail=True, methods=['post'], url_path='clusters/attach')
def attach_clusters(self, request, pk=None):
"""
Attach planner clusters to site blueprint.
Request body:
{
"cluster_ids": [1, 2, 3], # List of cluster IDs to attach
"role": "hub" # Optional: default role (hub, supporting, attribute)
}
Returns:
{
"attached_count": 3,
"clusters": [...] # List of attached cluster data
}
"""
blueprint = self.get_object()
cluster_ids = request.data.get('cluster_ids', [])
role = request.data.get('role', 'hub')
if not cluster_ids:
return error_response(
'cluster_ids is required',
status.HTTP_400_BAD_REQUEST,
request
)
# Validate role
valid_roles = [choice[0] for choice in SiteBlueprintCluster.ROLE_CHOICES]
if role not in valid_roles:
return error_response(
f'Invalid role. Must be one of: {", ".join(valid_roles)}',
status.HTTP_400_BAD_REQUEST,
request
)
# Import Clusters model
from igny8_core.business.planning.models import Clusters
# Validate clusters exist and belong to same account/site/sector
clusters = Clusters.objects.filter(
id__in=cluster_ids,
account=blueprint.account,
site=blueprint.site,
sector=blueprint.sector
)
if clusters.count() != len(cluster_ids):
return error_response(
'Some clusters not found or do not belong to this blueprint\'s site/sector',
status.HTTP_400_BAD_REQUEST,
request
)
# Attach clusters (create SiteBlueprintCluster records)
attached = []
for cluster in clusters:
# Check if already attached with this role
existing = SiteBlueprintCluster.objects.filter(
site_blueprint=blueprint,
cluster=cluster,
role=role
).first()
if not existing:
link = SiteBlueprintCluster.objects.create(
site_blueprint=blueprint,
cluster=cluster,
role=role,
account=blueprint.account,
site=blueprint.site,
sector=blueprint.sector
)
attached.append({
'id': cluster.id,
'name': cluster.name,
'role': role,
'link_id': link.id
})
else:
# Already attached, include in response
attached.append({
'id': cluster.id,
'name': cluster.name,
'role': role,
'link_id': existing.id
})
# Refresh workflow state if enabled
if self.workflow_service.enabled:
self.workflow_service.refresh_state(blueprint)
return success_response(
data={
'attached_count': len(attached),
'clusters': attached
},
request=request
)
@action(detail=True, methods=['post'], url_path='clusters/detach')
def detach_clusters(self, request, pk=None):
"""
Detach planner clusters from site blueprint.
Request body:
{
"cluster_ids": [1, 2, 3], # List of cluster IDs to detach (optional: detach all if omitted)
"role": "hub" # Optional: only detach clusters with this role
}
Returns:
{
"detached_count": 3
}
"""
blueprint = self.get_object()
cluster_ids = request.data.get('cluster_ids', [])
role = request.data.get('role')
# Build query
query = SiteBlueprintCluster.objects.filter(site_blueprint=blueprint)
if cluster_ids:
query = query.filter(cluster_id__in=cluster_ids)
if role:
valid_roles = [choice[0] for choice in SiteBlueprintCluster.ROLE_CHOICES]
if role not in valid_roles:
return error_response(
f'Invalid role. Must be one of: {", ".join(valid_roles)}',
status.HTTP_400_BAD_REQUEST,
request
)
query = query.filter(role=role)
detached_count = query.count()
query.delete()
# Refresh workflow state if enabled
if self.workflow_service.enabled:
self.workflow_service.refresh_state(blueprint)
return success_response(
data={'detached_count': detached_count},
request=request
)
@action(detail=True, methods=['get'], url_path='taxonomies')
def list_taxonomies(self, request, pk=None):
"""
List taxonomies for a blueprint.
Returns:
{
"count": 5,
"taxonomies": [...]
}
"""
blueprint = self.get_object()
taxonomies = blueprint.taxonomies.all().select_related().prefetch_related('clusters')
# Serialize taxonomies
data = []
for taxonomy in taxonomies:
data.append({
'id': taxonomy.id,
'name': taxonomy.name,
'slug': taxonomy.slug,
'taxonomy_type': taxonomy.taxonomy_type,
'description': taxonomy.description,
'cluster_ids': list(taxonomy.clusters.values_list('id', flat=True)),
'external_reference': taxonomy.external_reference,
'created_at': taxonomy.created_at.isoformat(),
'updated_at': taxonomy.updated_at.isoformat(),
})
return success_response(
data={'count': len(data), 'taxonomies': data},
request=request
)
@action(detail=True, methods=['post'], url_path='taxonomies')
def create_taxonomy(self, request, pk=None):
"""
Create a taxonomy for a blueprint.
Request body:
{
"name": "Product Categories",
"slug": "product-categories",
"taxonomy_type": "product_category",
"description": "Product category taxonomy",
"cluster_ids": [1, 2, 3], # Optional
"external_reference": "wp_term_123" # Optional
}
"""
blueprint = self.get_object()
name = request.data.get('name')
slug = request.data.get('slug')
taxonomy_type = request.data.get('taxonomy_type', 'blog_category')
description = request.data.get('description', '')
cluster_ids = request.data.get('cluster_ids', [])
external_reference = request.data.get('external_reference')
if not name or not slug:
return error_response(
'name and slug are required',
status.HTTP_400_BAD_REQUEST,
request
)
# Validate taxonomy type
valid_types = [choice[0] for choice in SiteBlueprintTaxonomy.TAXONOMY_TYPE_CHOICES]
if taxonomy_type not in valid_types:
return error_response(
f'Invalid taxonomy_type. Must be one of: {", ".join(valid_types)}',
status.HTTP_400_BAD_REQUEST,
request
)
# Create taxonomy
taxonomy = self.taxonomy_service.create_taxonomy(
blueprint,
name=name,
slug=slug,
taxonomy_type=taxonomy_type,
description=description,
clusters=cluster_ids if cluster_ids else None,
external_reference=external_reference,
)
# Refresh workflow state
if self.workflow_service.enabled:
self.workflow_service.refresh_state(blueprint)
return success_response(
data={
'id': taxonomy.id,
'name': taxonomy.name,
'slug': taxonomy.slug,
'taxonomy_type': taxonomy.taxonomy_type,
},
request=request,
status_code=status.HTTP_201_CREATED
)
@action(detail=True, methods=['post'], url_path='taxonomies/import')
def import_taxonomies(self, request, pk=None):
"""
Import taxonomies from external source (WordPress/WooCommerce).
Request body:
{
"records": [
{
"name": "Category Name",
"slug": "category-slug",
"taxonomy_type": "blog_category",
"description": "Category description",
"external_reference": "wp_term_123"
},
...
],
"default_type": "blog_category" # Optional
}
"""
blueprint = self.get_object()
records = request.data.get('records', [])
default_type = request.data.get('default_type', 'blog_category')
if not records:
return error_response(
'records array is required',
status.HTTP_400_BAD_REQUEST,
request
)
# Import taxonomies
imported = self.taxonomy_service.import_from_external(
blueprint,
records,
default_type=default_type
)
# Refresh workflow state
if self.workflow_service.enabled:
self.workflow_service.refresh_state(blueprint)
return success_response(
data={
'imported_count': len(imported),
'taxonomies': [
{
'id': t.id,
'name': t.name,
'slug': t.slug,
'taxonomy_type': t.taxonomy_type,
}
for t in imported
]
},
request=request
)
@action(detail=False, methods=['POST'], url_path='bulk_delete', url_name='bulk_delete')
def bulk_delete(self, request):
"""