[Feature] Dynamic additional information rendering in frontend (#282)

This implements a version of #274, relying on Pydantics built in JSON schema and JSON rendering.
Requires additional UI tagging in the ai model repo but will remove HTML tags.

Example scenario with filled information: 5882df9c-dae1-4d80-a40e-db4724271456/scenario/3a4d395a-6a6d-4154-8ce3-ced667fceec0

Reviewed-on: enviPath/enviPy#282
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
2026-01-31 00:44:03 +13:00
committed by jebus
parent 9f63a9d4de
commit d80dfb5ee3
42 changed files with 3732 additions and 609 deletions

View File

@ -11,13 +11,11 @@ from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAll
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from envipy_additional_information import NAME_MAPPING
from oauth2_provider.decorators import protected_resource
from sentry_sdk import capture_exception
from utilities.chem import FormatConverter, IndigoUtils
from utilities.decorators import package_permission_required
from utilities.misc import HTMLGenerator
from .logic import (
EPDBURLParser,
@ -2455,72 +2453,7 @@ def package_scenarios(request, package_uuid):
}
)
from envipy_additional_information import (
SEDIMENT_ADDITIONAL_INFORMATION,
SLUDGE_ADDITIONAL_INFORMATION,
SOIL_ADDITIONAL_INFORMATION,
)
context["scenario_types"] = {
"Soil Data": {
"name": "soil",
"widgets": [
HTMLGenerator.generate_html(ai, prefix=f"soil_{0}")
for ai in [x for sv in SOIL_ADDITIONAL_INFORMATION.values() for x in sv]
],
},
"Sludge Data": {
"name": "sludge",
"widgets": [
HTMLGenerator.generate_html(ai, prefix=f"sludge_{0}")
for ai in [x for sv in SLUDGE_ADDITIONAL_INFORMATION.values() for x in sv]
],
},
"Water-Sediment System Data": {
"name": "sediment",
"widgets": [
HTMLGenerator.generate_html(ai, prefix=f"sediment_{0}")
for ai in [x for sv in SEDIMENT_ADDITIONAL_INFORMATION.values() for x in sv]
],
},
}
context["sludge_additional_information"] = SLUDGE_ADDITIONAL_INFORMATION
context["soil_additional_information"] = SOIL_ADDITIONAL_INFORMATION
context["sediment_additional_information"] = SEDIMENT_ADDITIONAL_INFORMATION
return render(request, "collections/scenarios_paginated.html", context)
elif request.method == "POST":
log_post_params(request)
scenario_name = request.POST.get("scenario-name")
scenario_description = request.POST.get("scenario-description")
scenario_date_year = request.POST.get("scenario-date-year")
scenario_date_month = request.POST.get("scenario-date-month")
scenario_date_day = request.POST.get("scenario-date-day")
scenario_date = scenario_date_year
if scenario_date_month is not None and scenario_date_month.strip() != "":
scenario_date += f"-{int(scenario_date_month):02d}"
if scenario_date_day is not None and scenario_date_day.strip() != "":
scenario_date += f"-{int(scenario_date_day):02d}"
scenario_type = request.POST.get("scenario-type")
additional_information = HTMLGenerator.build_models(request.POST.dict())
additional_information = [x for sv in additional_information.values() for x in sv]
new_scen = Scenario.create(
current_package,
name=scenario_name,
description=scenario_description,
scenario_date=scenario_date,
scenario_type=scenario_type,
additional_information=additional_information,
)
return redirect(new_scen.url)
else:
return HttpResponseNotAllowed(
[
@ -2547,21 +2480,9 @@ def package_scenario(request, package_uuid, scenario_uuid):
context["scenario"] = current_scenario
available_add_infs = []
for add_inf in NAME_MAPPING.values():
available_add_infs.append(
{
"display_name": add_inf.property_name(None),
"name": add_inf.__name__,
"widget": HTMLGenerator.generate_html(add_inf, prefix=f"{0}"),
}
)
context["available_additional_information"] = available_add_infs
context["update_widgets"] = [
HTMLGenerator.generate_html(ai, prefix=f"{i}")
for i, ai in enumerate(current_scenario.get_additional_information())
]
# Note: Modals now fetch schemas and data from API endpoints
# Keeping these for backwards compatibility if needed elsewhere
# They are no longer used by the main scenario template
return render(request, "objects/scenario.html", context)
@ -2581,28 +2502,15 @@ def package_scenario(request, package_uuid, scenario_uuid):
current_scenario.save()
return redirect(current_scenario.url)
elif hidden == "set-additional-information":
ais = HTMLGenerator.build_models(request.POST.dict())
if s.DEBUG:
logger.info(ais)
current_scenario.set_additional_information(ais)
return redirect(current_scenario.url)
# Legacy POST handler - no longer used, modals use API endpoints
return HttpResponseBadRequest(
"This endpoint is deprecated. Please use the API endpoints."
)
elif hidden == "add-additional-information":
ais = HTMLGenerator.build_models(request.POST.dict())
if len(ais.keys()) != 1:
raise ValueError(
"Only one additional information field can be added at a time."
)
ai = list(ais.values())[0][0]
if s.DEBUG:
logger.info(ais)
current_scenario.add_additional_information(ai)
return redirect(current_scenario.url)
# Legacy POST handler - no longer used, modals use API endpoints
return HttpResponseBadRequest(
"This endpoint is deprecated. Please use the API endpoints."
)
else:
return HttpResponseBadRequest()