feat: enhance project and scenario management with role-based access control
- Implemented role-based access control for project and scenario routes. - Added authorization checks to ensure users have appropriate roles for viewing and managing projects and scenarios. - Introduced utility functions for ensuring project and scenario access based on user roles. - Refactored project and scenario routes to utilize new authorization helpers. - Created initial data seeding script to set up default roles and an admin user. - Added tests for authorization helpers and initial data seeding functionality. - Updated exception handling to include authorization errors.
This commit is contained in:
@@ -6,7 +6,8 @@ from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from dependencies import get_unit_of_work
|
||||
from dependencies import get_unit_of_work, require_authenticated_user
|
||||
from models import User
|
||||
from models import ScenarioStatus
|
||||
from services.unit_of_work import UnitOfWork
|
||||
|
||||
@@ -27,6 +28,8 @@ def _format_timestamp_with_time(moment: datetime | None) -> str | None:
|
||||
|
||||
|
||||
def _load_metrics(uow: UnitOfWork) -> dict[str, object]:
|
||||
if not uow.projects or not uow.scenarios or not uow.financial_inputs:
|
||||
raise RuntimeError("UnitOfWork repositories not initialised")
|
||||
total_projects = uow.projects.count()
|
||||
active_scenarios = uow.scenarios.count_by_status(ScenarioStatus.ACTIVE)
|
||||
pending_simulations = uow.scenarios.count_by_status(ScenarioStatus.DRAFT)
|
||||
@@ -40,11 +43,15 @@ def _load_metrics(uow: UnitOfWork) -> dict[str, object]:
|
||||
|
||||
|
||||
def _load_recent_projects(uow: UnitOfWork) -> list:
|
||||
if not uow.projects:
|
||||
raise RuntimeError("Project repository not initialised")
|
||||
return list(uow.projects.recent(limit=5))
|
||||
|
||||
|
||||
def _load_simulation_updates(uow: UnitOfWork) -> list[dict[str, object]]:
|
||||
updates: list[dict[str, object]] = []
|
||||
if not uow.scenarios:
|
||||
raise RuntimeError("Scenario repository not initialised")
|
||||
scenarios = uow.scenarios.recent(limit=5, with_project=True)
|
||||
for scenario in scenarios:
|
||||
project_name = scenario.project.name if scenario.project else f"Project #{scenario.project_id}"
|
||||
@@ -65,6 +72,9 @@ def _load_scenario_alerts(
|
||||
) -> list[dict[str, object]]:
|
||||
alerts: list[dict[str, object]] = []
|
||||
|
||||
if not uow.scenarios:
|
||||
raise RuntimeError("Scenario repository not initialised")
|
||||
|
||||
drafts = uow.scenarios.list_by_status(
|
||||
ScenarioStatus.DRAFT, limit=3, with_project=True
|
||||
)
|
||||
@@ -102,6 +112,7 @@ def _load_scenario_alerts(
|
||||
@router.get("/", response_class=HTMLResponse, include_in_schema=False, name="dashboard.home")
|
||||
def dashboard_home(
|
||||
request: Request,
|
||||
_: User = Depends(require_authenticated_user),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> HTMLResponse:
|
||||
context = {
|
||||
|
||||
@@ -6,8 +6,13 @@ from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from dependencies import get_unit_of_work
|
||||
from models import MiningOperationType, Project, ScenarioStatus
|
||||
from dependencies import (
|
||||
get_unit_of_work,
|
||||
require_any_role,
|
||||
require_project_resource,
|
||||
require_roles,
|
||||
)
|
||||
from models import MiningOperationType, Project, ScenarioStatus, User
|
||||
from schemas.project import ProjectCreate, ProjectRead, ProjectUpdate
|
||||
from services.exceptions import EntityConflictError, EntityNotFoundError
|
||||
from services.unit_of_work import UnitOfWork
|
||||
@@ -15,11 +20,20 @@ from services.unit_of_work import UnitOfWork
|
||||
router = APIRouter(prefix="/projects", tags=["Projects"])
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
READ_ROLES = ("viewer", "analyst", "project_manager", "admin")
|
||||
MANAGE_ROLES = ("project_manager", "admin")
|
||||
|
||||
|
||||
def _to_read_model(project: Project) -> ProjectRead:
|
||||
return ProjectRead.model_validate(project)
|
||||
|
||||
|
||||
def _require_project_repo(uow: UnitOfWork):
|
||||
if not uow.projects:
|
||||
raise RuntimeError("Project repository not initialised")
|
||||
return uow.projects
|
||||
|
||||
|
||||
def _operation_type_choices() -> list[tuple[str, str]]:
|
||||
return [
|
||||
(op.value, op.name.replace("_", " ").title()) for op in MiningOperationType
|
||||
@@ -27,18 +41,23 @@ def _operation_type_choices() -> list[tuple[str, str]]:
|
||||
|
||||
|
||||
@router.get("", response_model=List[ProjectRead])
|
||||
def list_projects(uow: UnitOfWork = Depends(get_unit_of_work)) -> List[ProjectRead]:
|
||||
projects = uow.projects.list()
|
||||
def list_projects(
|
||||
_: User = Depends(require_any_role(*READ_ROLES)),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> List[ProjectRead]:
|
||||
projects = _require_project_repo(uow).list()
|
||||
return [_to_read_model(project) for project in projects]
|
||||
|
||||
|
||||
@router.post("", response_model=ProjectRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_project(
|
||||
payload: ProjectCreate, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
payload: ProjectCreate,
|
||||
_: User = Depends(require_roles(*MANAGE_ROLES)),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> ProjectRead:
|
||||
project = Project(**payload.model_dump())
|
||||
try:
|
||||
created = uow.projects.create(project)
|
||||
created = _require_project_repo(uow).create(project)
|
||||
except EntityConflictError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail=str(exc)
|
||||
@@ -53,9 +72,11 @@ def create_project(
|
||||
name="projects.project_list_page",
|
||||
)
|
||||
def project_list_page(
|
||||
request: Request, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
request: Request,
|
||||
_: User = Depends(require_any_role(*READ_ROLES)),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> HTMLResponse:
|
||||
projects = uow.projects.list(with_children=True)
|
||||
projects = _require_project_repo(uow).list(with_children=True)
|
||||
for project in projects:
|
||||
setattr(project, "scenario_count", len(project.scenarios))
|
||||
return templates.TemplateResponse(
|
||||
@@ -73,7 +94,9 @@ def project_list_page(
|
||||
include_in_schema=False,
|
||||
name="projects.create_project_form",
|
||||
)
|
||||
def create_project_form(request: Request) -> HTMLResponse:
|
||||
def create_project_form(
|
||||
request: Request, _: User = Depends(require_roles(*MANAGE_ROLES))
|
||||
) -> HTMLResponse:
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"projects/form.html",
|
||||
@@ -93,6 +116,7 @@ def create_project_form(request: Request) -> HTMLResponse:
|
||||
)
|
||||
def create_project_submit(
|
||||
request: Request,
|
||||
_: User = Depends(require_roles(*MANAGE_ROLES)),
|
||||
name: str = Form(...),
|
||||
location: str | None = Form(None),
|
||||
operation_type: str = Form(...),
|
||||
@@ -128,7 +152,7 @@ def create_project_submit(
|
||||
description=_normalise(description),
|
||||
)
|
||||
try:
|
||||
uow.projects.create(project)
|
||||
_require_project_repo(uow).create(project)
|
||||
except EntityConflictError as exc:
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
@@ -150,29 +174,18 @@ def create_project_submit(
|
||||
|
||||
|
||||
@router.get("/{project_id}", response_model=ProjectRead)
|
||||
def get_project(project_id: int, uow: UnitOfWork = Depends(get_unit_of_work)) -> ProjectRead:
|
||||
try:
|
||||
project = uow.projects.get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
def get_project(project: Project = Depends(require_project_resource())) -> ProjectRead:
|
||||
return _to_read_model(project)
|
||||
|
||||
|
||||
@router.put("/{project_id}", response_model=ProjectRead)
|
||||
def update_project(
|
||||
project_id: int,
|
||||
payload: ProjectUpdate,
|
||||
project: Project = Depends(
|
||||
require_project_resource(require_manage=True)
|
||||
),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> ProjectRead:
|
||||
try:
|
||||
project = uow.projects.get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(project, field, value)
|
||||
@@ -182,13 +195,11 @@ def update_project(
|
||||
|
||||
|
||||
@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_project(project_id: int, uow: UnitOfWork = Depends(get_unit_of_work)) -> None:
|
||||
try:
|
||||
uow.projects.delete(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
def delete_project(
|
||||
project: Project = Depends(require_project_resource(require_manage=True)),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> None:
|
||||
_require_project_repo(uow).delete(project.id)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -198,14 +209,11 @@ def delete_project(project_id: int, uow: UnitOfWork = Depends(get_unit_of_work))
|
||||
name="projects.view_project",
|
||||
)
|
||||
def view_project(
|
||||
project_id: int, request: Request, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
request: Request,
|
||||
project: Project = Depends(require_project_resource()),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> HTMLResponse:
|
||||
try:
|
||||
project = uow.projects.get(project_id, with_children=True)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
project = _require_project_repo(uow).get(project.id, with_children=True)
|
||||
|
||||
scenarios = sorted(project.scenarios, key=lambda s: s.created_at)
|
||||
scenario_stats = {
|
||||
@@ -236,15 +244,11 @@ def view_project(
|
||||
name="projects.edit_project_form",
|
||||
)
|
||||
def edit_project_form(
|
||||
project_id: int, request: Request, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
request: Request,
|
||||
project: Project = Depends(
|
||||
require_project_resource(require_manage=True)
|
||||
),
|
||||
) -> HTMLResponse:
|
||||
try:
|
||||
project = uow.projects.get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"projects/form.html",
|
||||
@@ -252,10 +256,10 @@ def edit_project_form(
|
||||
"project": project,
|
||||
"operation_types": _operation_type_choices(),
|
||||
"form_action": request.url_for(
|
||||
"projects.edit_project_submit", project_id=project_id
|
||||
"projects.edit_project_submit", project_id=project.id
|
||||
),
|
||||
"cancel_url": request.url_for(
|
||||
"projects.view_project", project_id=project_id
|
||||
"projects.view_project", project_id=project.id
|
||||
),
|
||||
},
|
||||
)
|
||||
@@ -267,21 +271,16 @@ def edit_project_form(
|
||||
name="projects.edit_project_submit",
|
||||
)
|
||||
def edit_project_submit(
|
||||
project_id: int,
|
||||
request: Request,
|
||||
project: Project = Depends(
|
||||
require_project_resource(require_manage=True)
|
||||
),
|
||||
name: str = Form(...),
|
||||
location: str | None = Form(None),
|
||||
operation_type: str | None = Form(None),
|
||||
description: str | None = Form(None),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
):
|
||||
try:
|
||||
project = uow.projects.get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
def _normalise(value: str | None) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
@@ -301,10 +300,10 @@ def edit_project_submit(
|
||||
"project": project,
|
||||
"operation_types": _operation_type_choices(),
|
||||
"form_action": request.url_for(
|
||||
"projects.edit_project_submit", project_id=project_id
|
||||
"projects.edit_project_submit", project_id=project.id
|
||||
),
|
||||
"cancel_url": request.url_for(
|
||||
"projects.view_project", project_id=project_id
|
||||
"projects.view_project", project_id=project.id
|
||||
),
|
||||
"error": "Invalid operation type.",
|
||||
},
|
||||
@@ -315,6 +314,6 @@ def edit_project_submit(
|
||||
uow.flush()
|
||||
|
||||
return RedirectResponse(
|
||||
request.url_for("projects.view_project", project_id=project_id),
|
||||
request.url_for("projects.view_project", project_id=project.id),
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
|
||||
@@ -7,8 +7,13 @@ from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from dependencies import get_unit_of_work
|
||||
from models import ResourceType, Scenario, ScenarioStatus
|
||||
from dependencies import (
|
||||
get_unit_of_work,
|
||||
require_any_role,
|
||||
require_roles,
|
||||
require_scenario_resource,
|
||||
)
|
||||
from models import ResourceType, Scenario, ScenarioStatus, User
|
||||
from schemas.scenario import (
|
||||
ScenarioComparisonRequest,
|
||||
ScenarioComparisonResponse,
|
||||
@@ -26,6 +31,9 @@ from services.unit_of_work import UnitOfWork
|
||||
router = APIRouter(tags=["Scenarios"])
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
READ_ROLES = ("viewer", "analyst", "project_manager", "admin")
|
||||
MANAGE_ROLES = ("project_manager", "admin")
|
||||
|
||||
|
||||
def _to_read_model(scenario: Scenario) -> ScenarioRead:
|
||||
return ScenarioRead.model_validate(scenario)
|
||||
@@ -44,20 +52,36 @@ def _scenario_status_choices() -> list[tuple[str, str]]:
|
||||
]
|
||||
|
||||
|
||||
def _require_project_repo(uow: UnitOfWork):
|
||||
if not uow.projects:
|
||||
raise RuntimeError("Project repository not initialised")
|
||||
return uow.projects
|
||||
|
||||
|
||||
def _require_scenario_repo(uow: UnitOfWork):
|
||||
if not uow.scenarios:
|
||||
raise RuntimeError("Scenario repository not initialised")
|
||||
return uow.scenarios
|
||||
|
||||
|
||||
@router.get(
|
||||
"/projects/{project_id}/scenarios",
|
||||
response_model=List[ScenarioRead],
|
||||
)
|
||||
def list_scenarios_for_project(
|
||||
project_id: int, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
project_id: int,
|
||||
_: User = Depends(require_any_role(*READ_ROLES)),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> List[ScenarioRead]:
|
||||
project_repo = _require_project_repo(uow)
|
||||
scenario_repo = _require_scenario_repo(uow)
|
||||
try:
|
||||
uow.projects.get(project_id)
|
||||
project_repo.get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
|
||||
scenarios = uow.scenarios.list_for_project(project_id)
|
||||
scenarios = scenario_repo.list_for_project(project_id)
|
||||
return [_to_read_model(scenario) for scenario in scenarios]
|
||||
|
||||
|
||||
@@ -69,10 +93,11 @@ def list_scenarios_for_project(
|
||||
def compare_scenarios(
|
||||
project_id: int,
|
||||
payload: ScenarioComparisonRequest,
|
||||
_: User = Depends(require_any_role(*READ_ROLES)),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> ScenarioComparisonResponse:
|
||||
try:
|
||||
uow.projects.get(project_id)
|
||||
_require_project_repo(uow).get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
@@ -116,10 +141,13 @@ def compare_scenarios(
|
||||
def create_scenario_for_project(
|
||||
project_id: int,
|
||||
payload: ScenarioCreate,
|
||||
_: User = Depends(require_roles(*MANAGE_ROLES)),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> ScenarioRead:
|
||||
project_repo = _require_project_repo(uow)
|
||||
scenario_repo = _require_scenario_repo(uow)
|
||||
try:
|
||||
uow.projects.get(project_id)
|
||||
project_repo.get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
@@ -127,7 +155,7 @@ def create_scenario_for_project(
|
||||
scenario = Scenario(project_id=project_id, **payload.model_dump())
|
||||
|
||||
try:
|
||||
created = uow.scenarios.create(scenario)
|
||||
created = scenario_repo.create(scenario)
|
||||
except EntityConflictError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
|
||||
@@ -136,28 +164,19 @@ def create_scenario_for_project(
|
||||
|
||||
@router.get("/scenarios/{scenario_id}", response_model=ScenarioRead)
|
||||
def get_scenario(
|
||||
scenario_id: int, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
scenario: Scenario = Depends(require_scenario_resource()),
|
||||
) -> ScenarioRead:
|
||||
try:
|
||||
scenario = uow.scenarios.get(scenario_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
return _to_read_model(scenario)
|
||||
|
||||
|
||||
@router.put("/scenarios/{scenario_id}", response_model=ScenarioRead)
|
||||
def update_scenario(
|
||||
scenario_id: int,
|
||||
payload: ScenarioUpdate,
|
||||
scenario: Scenario = Depends(
|
||||
require_scenario_resource(require_manage=True)
|
||||
),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> ScenarioRead:
|
||||
try:
|
||||
scenario = uow.scenarios.get(scenario_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(scenario, field, value)
|
||||
@@ -168,13 +187,12 @@ def update_scenario(
|
||||
|
||||
@router.delete("/scenarios/{scenario_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_scenario(
|
||||
scenario_id: int, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
scenario: Scenario = Depends(
|
||||
require_scenario_resource(require_manage=True)
|
||||
),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> None:
|
||||
try:
|
||||
uow.scenarios.delete(scenario_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
_require_scenario_repo(uow).delete(scenario.id)
|
||||
|
||||
|
||||
def _normalise(value: str | None) -> str | None:
|
||||
@@ -208,10 +226,13 @@ def _parse_discount_rate(value: str | None) -> float | None:
|
||||
name="scenarios.create_scenario_form",
|
||||
)
|
||||
def create_scenario_form(
|
||||
project_id: int, request: Request, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
project_id: int,
|
||||
request: Request,
|
||||
_: User = Depends(require_roles(*MANAGE_ROLES)),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> HTMLResponse:
|
||||
try:
|
||||
project = uow.projects.get(project_id)
|
||||
project = _require_project_repo(uow).get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
@@ -243,6 +264,7 @@ def create_scenario_form(
|
||||
def create_scenario_submit(
|
||||
project_id: int,
|
||||
request: Request,
|
||||
_: User = Depends(require_roles(*MANAGE_ROLES)),
|
||||
name: str = Form(...),
|
||||
description: str | None = Form(None),
|
||||
status_value: str = Form(ScenarioStatus.DRAFT.value),
|
||||
@@ -253,8 +275,10 @@ def create_scenario_submit(
|
||||
primary_resource: str | None = Form(None),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
):
|
||||
project_repo = _require_project_repo(uow)
|
||||
scenario_repo = _require_scenario_repo(uow)
|
||||
try:
|
||||
project = uow.projects.get(project_id)
|
||||
project = project_repo.get(project_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
@@ -288,7 +312,7 @@ def create_scenario_submit(
|
||||
)
|
||||
|
||||
try:
|
||||
uow.scenarios.create(scenario)
|
||||
scenario_repo.create(scenario)
|
||||
except EntityConflictError as exc:
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
@@ -322,16 +346,13 @@ def create_scenario_submit(
|
||||
name="scenarios.view_scenario",
|
||||
)
|
||||
def view_scenario(
|
||||
scenario_id: int, request: Request, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
request: Request,
|
||||
scenario: Scenario = Depends(
|
||||
require_scenario_resource(with_children=True)
|
||||
),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> HTMLResponse:
|
||||
try:
|
||||
scenario = uow.scenarios.get(scenario_id, with_children=True)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
project = uow.projects.get(scenario.project_id)
|
||||
project = _require_project_repo(uow).get(scenario.project_id)
|
||||
financial_inputs = sorted(
|
||||
scenario.financial_inputs, key=lambda item: item.created_at
|
||||
)
|
||||
@@ -366,16 +387,13 @@ def view_scenario(
|
||||
name="scenarios.edit_scenario_form",
|
||||
)
|
||||
def edit_scenario_form(
|
||||
scenario_id: int, request: Request, uow: UnitOfWork = Depends(get_unit_of_work)
|
||||
request: Request,
|
||||
scenario: Scenario = Depends(
|
||||
require_scenario_resource(require_manage=True)
|
||||
),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
) -> HTMLResponse:
|
||||
try:
|
||||
scenario = uow.scenarios.get(scenario_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
project = uow.projects.get(scenario.project_id)
|
||||
project = _require_project_repo(uow).get(scenario.project_id)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
@@ -386,10 +404,10 @@ def edit_scenario_form(
|
||||
"scenario_statuses": _scenario_status_choices(),
|
||||
"resource_types": _resource_type_choices(),
|
||||
"form_action": request.url_for(
|
||||
"scenarios.edit_scenario_submit", scenario_id=scenario_id
|
||||
"scenarios.edit_scenario_submit", scenario_id=scenario.id
|
||||
),
|
||||
"cancel_url": request.url_for(
|
||||
"scenarios.view_scenario", scenario_id=scenario_id
|
||||
"scenarios.view_scenario", scenario_id=scenario.id
|
||||
),
|
||||
},
|
||||
)
|
||||
@@ -401,8 +419,10 @@ def edit_scenario_form(
|
||||
name="scenarios.edit_scenario_submit",
|
||||
)
|
||||
def edit_scenario_submit(
|
||||
scenario_id: int,
|
||||
request: Request,
|
||||
scenario: Scenario = Depends(
|
||||
require_scenario_resource(require_manage=True)
|
||||
),
|
||||
name: str = Form(...),
|
||||
description: str | None = Form(None),
|
||||
status_value: str = Form(ScenarioStatus.DRAFT.value),
|
||||
@@ -413,14 +433,7 @@ def edit_scenario_submit(
|
||||
primary_resource: str | None = Form(None),
|
||||
uow: UnitOfWork = Depends(get_unit_of_work),
|
||||
):
|
||||
try:
|
||||
scenario = uow.scenarios.get(scenario_id)
|
||||
except EntityNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
project = uow.projects.get(scenario.project_id)
|
||||
project = _require_project_repo(uow).get(scenario.project_id)
|
||||
|
||||
scenario.name = name.strip()
|
||||
scenario.description = _normalise(description)
|
||||
@@ -447,6 +460,6 @@ def edit_scenario_submit(
|
||||
uow.flush()
|
||||
|
||||
return RedirectResponse(
|
||||
request.url_for("scenarios.view_scenario", scenario_id=scenario_id),
|
||||
request.url_for("scenarios.view_scenario", scenario_id=scenario.id),
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user