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:
Binary file not shown.
@@ -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):
|
||||
|
||||
@@ -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', {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user