stage2-2
This commit is contained in:
@@ -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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user