forked from enviPath/enviPy
The scenarios lists both in /scenarios and /package/<id>/scenario no longer show related scenarios (children).
All related scenarios are shown on the scenario page under Related Scenarios if there are any.
<img width="500" alt="{C2D38DED-A402-4A27-A241-BC2302C62A50}.png" src="attachments/1371c177-220c-42d5-94ff-56f9fbab761f">
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#323
Co-authored-by: Liam Brydon <lbry121@aucklanduni.ac.nz>
Co-committed-by: Liam Brydon <lbry121@aucklanduni.ac.nz>
131 lines
5.0 KiB
Python
131 lines
5.0 KiB
Python
from django.conf import settings as s
|
|
from django.db import IntegrityError, OperationalError, DatabaseError
|
|
from ninja import Router, Body
|
|
from ninja.errors import HttpError
|
|
from ninja_extra.pagination import paginate
|
|
from uuid import UUID
|
|
from pydantic import ValidationError
|
|
import logging
|
|
import json
|
|
|
|
from epdb.models import Scenario
|
|
from epdb.logic import PackageManager
|
|
from epdb.views import _anonymous_or_real
|
|
from ..pagination import EnhancedPageNumberPagination
|
|
from ..schemas import (
|
|
ScenarioOutSchema,
|
|
ScenarioCreateSchema,
|
|
ScenarioReviewStatusAndRelatedFilter,
|
|
)
|
|
from ..dal import get_user_entities_for_read, get_package_entities_for_read
|
|
from envipy_additional_information import registry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = Router()
|
|
|
|
|
|
@router.get("/scenarios/", response=EnhancedPageNumberPagination.Output[ScenarioOutSchema])
|
|
@paginate(
|
|
EnhancedPageNumberPagination,
|
|
page_size=s.API_PAGINATION_DEFAULT_PAGE_SIZE,
|
|
filter_schema=ScenarioReviewStatusAndRelatedFilter,
|
|
)
|
|
def list_all_scenarios(request):
|
|
user = request.user
|
|
items = get_user_entities_for_read(Scenario, user)
|
|
return items.order_by("name").all()
|
|
|
|
|
|
@router.get(
|
|
"/package/{uuid:package_uuid}/scenario/",
|
|
response=EnhancedPageNumberPagination.Output[ScenarioOutSchema],
|
|
)
|
|
@paginate(
|
|
EnhancedPageNumberPagination,
|
|
page_size=s.API_PAGINATION_DEFAULT_PAGE_SIZE,
|
|
filter_schema=ScenarioReviewStatusAndRelatedFilter,
|
|
)
|
|
def list_package_scenarios(request, package_uuid: UUID):
|
|
user = request.user
|
|
items = get_package_entities_for_read(Scenario, package_uuid, user)
|
|
return items.order_by("name").all()
|
|
|
|
|
|
@router.post("/package/{uuid:package_uuid}/scenario/", response=ScenarioOutSchema)
|
|
def create_scenario(request, package_uuid: UUID, payload: ScenarioCreateSchema = Body(...)):
|
|
"""Create a new scenario with optional additional information."""
|
|
user = _anonymous_or_real(request)
|
|
|
|
try:
|
|
current_package = PackageManager.get_package_by_id(user, package_uuid)
|
|
except ValueError as e:
|
|
error_msg = str(e)
|
|
if "does not exist" in error_msg:
|
|
raise HttpError(404, f"Package not found: {package_uuid}")
|
|
elif "Insufficient permissions" in error_msg:
|
|
raise HttpError(403, "You do not have permission to access this package")
|
|
else:
|
|
logger.error(f"Unexpected ValueError from get_package_by_id: {error_msg}")
|
|
raise HttpError(400, "Invalid package request")
|
|
|
|
# Build additional information models from payload
|
|
additional_information_models = []
|
|
validation_errors = []
|
|
|
|
for ai_item in payload.additional_information:
|
|
# Get model class from registry
|
|
model_cls = registry.get_model(ai_item.type.lower())
|
|
if not model_cls:
|
|
validation_errors.append(f"Unknown additional information type: {ai_item.type}")
|
|
continue
|
|
|
|
try:
|
|
# Validate and create model instance
|
|
instance = model_cls(**ai_item.data)
|
|
additional_information_models.append(instance)
|
|
except ValidationError as e:
|
|
# Collect validation errors to return to user
|
|
error_messages = [err.get("msg", "Validation error") for err in e.errors()]
|
|
validation_errors.append(f"{ai_item.type}: {', '.join(error_messages)}")
|
|
except (TypeError, AttributeError, KeyError) as e:
|
|
logger.warning(f"Failed to instantiate {ai_item.type} model: {str(e)}")
|
|
validation_errors.append(f"{ai_item.type}: Invalid data structure - {str(e)}")
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error instantiating {ai_item.type}: {str(e)}")
|
|
validation_errors.append(f"{ai_item.type}: Failed to process - please check your data")
|
|
|
|
# If there are validation errors, return them
|
|
if validation_errors:
|
|
raise HttpError(
|
|
400,
|
|
json.dumps(
|
|
{
|
|
"error": "Validation errors in additional information",
|
|
"details": validation_errors,
|
|
}
|
|
),
|
|
)
|
|
|
|
# Create scenario using the existing Scenario.create method
|
|
try:
|
|
new_scenario = Scenario.create(
|
|
package=current_package,
|
|
name=payload.name,
|
|
description=payload.description,
|
|
scenario_date=payload.scenario_date,
|
|
scenario_type=payload.scenario_type,
|
|
additional_information=additional_information_models,
|
|
)
|
|
except IntegrityError as e:
|
|
logger.error(f"Database integrity error creating scenario: {str(e)}")
|
|
raise HttpError(400, "Scenario creation failed - data constraint violation")
|
|
except OperationalError as e:
|
|
logger.error(f"Database operational error creating scenario: {str(e)}")
|
|
raise HttpError(503, "Database temporarily unavailable - please try again")
|
|
except (DatabaseError, AttributeError) as e:
|
|
logger.error(f"Error creating scenario: {str(e)}")
|
|
raise HttpError(500, "Failed to create scenario due to database error")
|
|
|
|
return new_scenario
|