Refactor workflow state management in site building; enhance error handling and field validation in models and serializers. Remove obsolete workflow components from frontend and adjust API response structure for clarity.

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-20 23:08:07 +00:00
parent 1b4cd59e5b
commit c31567ec9f
13 changed files with 437 additions and 704 deletions

Binary file not shown.

View File

@@ -330,9 +330,13 @@ class WorkflowState(SiteSectorBaseModel):
def save(self, *args, **kwargs):
if self.site_blueprint:
self.account = self.site_blueprint.account
self.site = self.site_blueprint.site
self.sector = self.site_blueprint.sector
# Only set fields if blueprint has them (avoid errors if blueprint is missing fields)
if self.site_blueprint.account_id:
self.account_id = self.site_blueprint.account_id
if self.site_blueprint.site_id:
self.site_id = self.site_blueprint.site_id
if self.site_blueprint.sector_id:
self.sector_id = self.site_blueprint.sector_id
super().save(*args, **kwargs)
def __str__(self):

View File

@@ -100,7 +100,29 @@ class WorkflowStateService:
state.step_status = step_status
state.blocking_reason = blocking_reason
state.completed = all(value.get('status') == 'ready' for value in step_status.values())
state.save(update_fields=['step_status', 'blocking_reason', 'completed', 'updated_at'])
# Ensure account/site/sector are set from blueprint before saving
update_fields = ['step_status', 'blocking_reason', 'completed', 'updated_at']
if state.site_blueprint:
if state.site_blueprint.account_id:
state.account_id = state.site_blueprint.account_id
update_fields.append('account')
if state.site_blueprint.site_id:
state.site_id = state.site_blueprint.site_id
update_fields.append('site')
if state.site_blueprint.sector_id:
state.sector_id = state.site_blueprint.sector_id
update_fields.append('sector')
try:
state.save(update_fields=update_fields)
except Exception as e:
logger.error(
f"Failed to save workflow state for blueprint {site_blueprint.id}: {str(e)}. "
f"Blueprint fields: account_id={site_blueprint.account_id}, site_id={site_blueprint.site_id}, sector_id={site_blueprint.sector_id}",
exc_info=True
)
raise
return state
def update_step(
@@ -149,11 +171,28 @@ class WorkflowStateService:
)
else:
state.completed = False
# Ensure account/site/sector are set from blueprint before saving
update_fields = ['current_step', 'step_status', 'blocking_reason', 'completed', 'updated_at']
if state.site_blueprint:
if state.site_blueprint.account_id:
state.account_id = state.site_blueprint.account_id
update_fields.append('account')
if state.site_blueprint.site_id:
state.site_id = state.site_blueprint.site_id
update_fields.append('site')
if state.site_blueprint.sector_id:
state.sector_id = state.site_blueprint.sector_id
update_fields.append('sector')
try:
state.save(update_fields=['current_step', 'step_status', 'blocking_reason', 'completed', 'updated_at'])
state.save(update_fields=update_fields)
except Exception as e:
logger.error(f"Failed to save workflow state for blueprint {site_blueprint.id}: {str(e)}")
logger.error(
f"Failed to save workflow state for blueprint {site_blueprint.id}: {str(e)}. "
f"Blueprint fields: account_id={site_blueprint.account_id}, site_id={site_blueprint.site_id}, sector_id={site_blueprint.sector_id}",
exc_info=True
)
raise
self._emit_event(site_blueprint, 'wizard_step_updated', {

View File

@@ -45,8 +45,9 @@ class PageBlueprintSerializer(serializers.ModelSerializer):
class SiteBlueprintSerializer(serializers.ModelSerializer):
pages = PageBlueprintSerializer(many=True, read_only=True)
site_id = serializers.IntegerField(write_only=True, required=False)
sector_id = serializers.IntegerField(write_only=True, required=False)
site_id = serializers.IntegerField(required=False, read_only=True)
sector_id = serializers.IntegerField(required=False, read_only=True)
account_id = serializers.IntegerField(read_only=True)
workflow_state = serializers.SerializerMethodField()
gating_messages = serializers.SerializerMethodField()
@@ -62,6 +63,7 @@ class SiteBlueprintSerializer(serializers.ModelSerializer):
'hosting_type',
'version',
'deployed_version',
'account_id',
'site_id',
'sector_id',
'created_at',

View File

@@ -27,7 +27,6 @@ from igny8_core.business.site_building.services import (
PageGenerationService,
SiteBuilderFileService,
StructureGenerationService,
WorkflowStateService,
TaxonomyService,
WizardContextService,
)
@@ -51,7 +50,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.workflow_service = WorkflowStateService()
self.taxonomy_service = TaxonomyService()
self.wizard_context_service = WizardContextService()
@@ -115,18 +113,10 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
raise ValidationError({'sector_id': 'Sector does not belong to the selected site.'})
blueprint = serializer.save(account=site.account, site=site, sector=sector)
if self.workflow_service.enabled:
self.workflow_service.initialize(blueprint)
@action(detail=True, methods=['post'])
def generate_structure(self, request, pk=None):
blueprint = self.get_object()
if self.workflow_service.enabled:
try:
self.workflow_service.validate_step(blueprint, 'clusters')
self.workflow_service.validate_step(blueprint, 'taxonomies')
except ValidationError as exc:
return error_response(str(exc), status.HTTP_400_BAD_REQUEST, request)
business_brief = request.data.get('business_brief') or \
blueprint.config_json.get('business_brief', '')
objectives = request.data.get('objectives') or \
@@ -143,8 +133,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
metadata=request.data.get('metadata', {}),
)
response = Response(result, status=status.HTTP_202_ACCEPTED if 'task_id' in result else status.HTTP_200_OK)
if self.workflow_service.enabled:
self.workflow_service.refresh_state(blueprint)
return response
@action(detail=True, methods=['post'])
@@ -162,11 +150,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
page_ids = request.data.get('page_ids')
force = request.data.get('force', False)
if self.workflow_service.enabled:
try:
self.workflow_service.validate_step(blueprint, 'sitemap')
except ValidationError as exc:
return error_response(str(exc), status.HTTP_400_BAD_REQUEST, request)
service = PageGenerationService()
try:
result = service.bulk_generate_pages(
@@ -176,8 +159,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
)
response_status = status.HTTP_202_ACCEPTED if result.get('success') else status.HTTP_400_BAD_REQUEST
response = success_response(result, request=request, status_code=response_status)
if self.workflow_service.enabled:
self.workflow_service.refresh_state(blueprint)
return response
except Exception as e:
return error_response(str(e), status.HTTP_400_BAD_REQUEST, request)
@@ -200,12 +181,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
blueprint = self.get_object()
page_ids = request.data.get('page_ids')
if self.workflow_service.enabled:
try:
self.workflow_service.validate_step(blueprint, 'coverage')
except ValidationError as exc:
return error_response(str(exc), status.HTTP_400_BAD_REQUEST, request)
service = PageGenerationService()
try:
tasks = service.create_tasks_for_pages(blueprint, page_ids=page_ids)
@@ -215,8 +190,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
serializer = TasksSerializer(tasks, many=True)
response = success_response({'tasks': serializer.data, 'count': len(tasks)}, request=request)
if self.workflow_service.enabled:
self.workflow_service.refresh_state(blueprint)
return response
except Exception as e:
return error_response(str(e), status.HTTP_400_BAD_REQUEST, request)
@@ -306,103 +279,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
request=request
)
@action(detail=True, methods=['get'], url_path='workflow/context')
def workflow_context(self, request, pk=None):
"""Return aggregated wizard context (steps, clusters, taxonomies, coverage)."""
blueprint = self.get_object()
if not self.workflow_service.enabled:
# Return empty context structure matching frontend expectations
return success_response(
data={
'workflow': None,
'cluster_summary': {'attached_count': 0, 'coverage_counts': {}, 'clusters': []},
'taxonomy_summary': {'total_taxonomies': 0, 'counts_by_type': {}, 'taxonomies': []},
'sitemap_summary': {'pages_total': 0, 'pages_by_status': {}, 'pages_by_type': {}},
'coverage': {'pages_total': 0, 'pages_by_status': {}, 'pages_by_type': {}},
'next_actions': None,
},
request=request,
)
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
)
try:
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
)
except Exception as e:
logger.exception(f"Error updating workflow step for blueprint {blueprint.id}: {str(e)}")
return error_response(
f'Internal server error: {str(e)}',
status.HTTP_500_INTERNAL_SERVER_ERROR,
request
)
@action(detail=True, methods=['post'], url_path='clusters/attach')
def attach_clusters(self, request, pk=None):
"""
@@ -492,10 +368,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
'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),
@@ -543,10 +415,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
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
@@ -636,10 +504,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
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,
@@ -689,10 +553,6 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
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),