From 4c726ff91c9b5b13d960a45ac788a8229c24c730 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:43:04 -0800 Subject: [PATCH 1/4] Add Aquatic Plant Observation Context, correct Entries table name --- .../invasives/api/migrations/0002_initial.py | 33 +++++++++++++++++-- .../models/activity/observations/__init__.py | 1 + .../aquatic_plant_observation_context.py | 12 +++++++ .../aquatic_plant_observation_entry.py | 2 +- 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 api-rework/invasives/api/models/activity/observations/aquatic_plant_observation_context.py diff --git a/api-rework/invasives/api/migrations/0002_initial.py b/api-rework/invasives/api/migrations/0002_initial.py index 5356e4ca8..66e2e141f 100644 --- a/api-rework/invasives/api/migrations/0002_initial.py +++ b/api-rework/invasives/api/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 6.0 on 2026-02-03 19:30 +# Generated by Django 6.0 on 2026-02-05 19:42 import api.models.enums.water_level_management import django.core.validators @@ -953,6 +953,35 @@ class Migration(migrations.Migration): "db_table": '"activity"."mechanical_authorization_pa"', }, ), + migrations.CreateModel( + name="AquaticPlantObservationContext", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "suitable_for_biocontrol", + models.CharField( + choices=[("Y", "Yes"), ("N", "No"), ("U", "Unknown")] + ), + ), + ( + "activity", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, to="api.activity" + ), + ), + ], + options={ + "db_table": '"activity"."observation_context_pa"', + }, + ), migrations.CreateModel( name="AquaticVoucherSpecimen", fields=[ @@ -1253,7 +1282,7 @@ class Migration(migrations.Migration): ), ], options={ - "db_table": '"activity"."observation_context_pa"', + "db_table": '"activity"."observation_entries_pa"', }, ), migrations.CreateModel( diff --git a/api-rework/invasives/api/models/activity/observations/__init__.py b/api-rework/invasives/api/models/activity/observations/__init__.py index 27dc8b8bb..e159c595f 100644 --- a/api-rework/invasives/api/models/activity/observations/__init__.py +++ b/api-rework/invasives/api/models/activity/observations/__init__.py @@ -1,4 +1,5 @@ from .aquatic_plant_observation_entry import * +from .aquatic_plant_observation_context import AquaticPlantObservationContext from .pre_treatment_observation import * from .shoreline_types import * from .terrestrial_plant_observation_detail import * diff --git a/api-rework/invasives/api/models/activity/observations/aquatic_plant_observation_context.py b/api-rework/invasives/api/models/activity/observations/aquatic_plant_observation_context.py new file mode 100644 index 000000000..bddc2b5ed --- /dev/null +++ b/api-rework/invasives/api/models/activity/observations/aquatic_plant_observation_context.py @@ -0,0 +1,12 @@ +from django.db import models +from api.models.activity.abstract_sub_tables import BaseOneToOneActivityTable +from api.models.enums import YesNoUnknown + + +class AquaticPlantObservationContext(BaseOneToOneActivityTable): + suitable_for_biocontrol = models.CharField( + choices=YesNoUnknown, + ) + + class Meta: + db_table = '"activity"."observation_context_pa"' diff --git a/api-rework/invasives/api/models/activity/observations/aquatic_plant_observation_entry.py b/api-rework/invasives/api/models/activity/observations/aquatic_plant_observation_entry.py index ad9f5e5c3..f91dc1620 100644 --- a/api-rework/invasives/api/models/activity/observations/aquatic_plant_observation_entry.py +++ b/api-rework/invasives/api/models/activity/observations/aquatic_plant_observation_entry.py @@ -25,7 +25,7 @@ class AquaticPlantObservationEntry(BaseOneToManyActivityTable): sample_point_id = models.CharField(max_length=128, blank=True, null=True) class Meta: - db_table = '"activity"."observation_context_pa"' + db_table = '"activity"."observation_entries_pa"' constraints = [ models.UniqueConstraint( fields=["activity", "invasive_plant"], From 8c846cc6737dd99084a1676b4a16b5a4e598f1c6 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:46:50 -0800 Subject: [PATCH 2/4] Add Serializer --- .../type/subtype/aquatic_observation.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/api-rework/invasives/api/serializers/type/subtype/aquatic_observation.py b/api-rework/invasives/api/serializers/type/subtype/aquatic_observation.py index 2d733ebb6..70f44147f 100644 --- a/api-rework/invasives/api/serializers/type/subtype/aquatic_observation.py +++ b/api-rework/invasives/api/serializers/type/subtype/aquatic_observation.py @@ -1,6 +1,7 @@ from rest_framework import serializers from api.serializers.common import ShorelineTypesSerializer from api.models.activity import ( + AquaticPlantObservationContext, AquaticPlantObservationEntry, AquaticVoucherSpecimen, WaterbodySubstrateType, @@ -133,6 +134,12 @@ class Meta: ) +class AquaticPlantObservationContextSerializer(serializers.ModelSerializer): + class Meta: + model = AquaticPlantObservationContext + fields = ["suitable_for_biocontrol"] + + class AquaticPlantObservationEntrySerializer(serializers.ModelSerializer): voucher_specimen = serializers.SerializerMethodField() @@ -178,10 +185,11 @@ class AquaticObservationSerializer(serializers.Serializer): substrate_type = WaterbodySubstrateTypeSerializer( source="waterbodysubstratetype_set", many=True ) - # @todo add to subtype obs model - # suitable_for_biocontrol = serializers.CharField( - # source="suitableforbiocontrol.suitable_for_biocontrol" - # ) + + aquatic_observation_context = AquaticPlantObservationContextSerializer( + source="aquaticplantobservationcontext" + ) + waterbody_context = WaterbodyDataSerializer(source="waterbodycontext") water_use = WaterbodyUseSerializer(source="waterbodyuse_set", many=True) waterlevel_management = WaterbodyLevelManagementSerializer( @@ -204,10 +212,10 @@ class AquaticObservationSerializer(serializers.Serializer): ) def to_representation(self, instance): - """Flatten Waterbody Details into top_level""" + """Flatten Waterbody Details into top level""" ret = super().to_representation(instance) - info_data = ret.pop("waterbody_context", None) - - if info_data and isinstance(info_data, dict): - ret.update(info_data) + for key in ["waterbody_context", "aquatic_observation_context"]: + info_data = ret.pop(key, None) + if info_data and isinstance(info_data, dict): + ret.update(info_data) return ret From 86a64bf9b5f1b1c417b806d7cea2987f19d81432 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:52:44 -0800 Subject: [PATCH 3/4] Update Form Field --- .../client/src/activities/subtypes/AquaticObservation.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api-rework/client/src/activities/subtypes/AquaticObservation.tsx b/api-rework/client/src/activities/subtypes/AquaticObservation.tsx index b90317fe0..614c1d9bb 100644 --- a/api-rework/client/src/activities/subtypes/AquaticObservation.tsx +++ b/api-rework/client/src/activities/subtypes/AquaticObservation.tsx @@ -45,7 +45,9 @@ const AquaticObservation = ({ subtypeData }: SubtypeData) => { ))} - +
+ +
{subtypeData?.entries.map((od) => (
From 61d725d086c05725e9318a4e267e3a6044ca8006 Mon Sep 17 00:00:00 2001 From: LocalNewsTV <62873746+LocalNewsTV@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:56:34 -0800 Subject: [PATCH 4/4] update test --- .../observations/test_aquatic_observation.json | 16 ++++++++++++++++ .../tests/subtypes/test_aquatic_observation.py | 8 +++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/api-rework/invasives/api/fixtures/test/subtypes/observations/test_aquatic_observation.json b/api-rework/invasives/api/fixtures/test/subtypes/observations/test_aquatic_observation.json index 3baa11c73..08e9f4f0e 100644 --- a/api-rework/invasives/api/fixtures/test/subtypes/observations/test_aquatic_observation.json +++ b/api-rework/invasives/api/fixtures/test/subtypes/observations/test_aquatic_observation.json @@ -37,6 +37,22 @@ "batch_row_id": 78 } }, + { + "model": "api.AquaticPlantObservationContext", + "pk": 1, + "fields": { + "activity_id": "6BBA2749-EE3D-41B6-A9F1-4A0CB37029F7", + "suitable_for_biocontrol": "No" + } + }, + { + "model": "api.AquaticPlantObservationContext", + "pk": 2, + "fields": { + "activity_id": "CD542709-F767-402F-818E-117B3FBC797D", + "suitable_for_biocontrol": "Yes" + } + }, { "model": "api.PretreatmentObservation", "pk": 1, diff --git a/api-rework/invasives/api/tests/subtypes/test_aquatic_observation.py b/api-rework/invasives/api/tests/subtypes/test_aquatic_observation.py index bd6e67431..90b2c7cec 100644 --- a/api-rework/invasives/api/tests/subtypes/test_aquatic_observation.py +++ b/api-rework/invasives/api/tests/subtypes/test_aquatic_observation.py @@ -24,10 +24,9 @@ def test_subtype_details_full(self): sd = response_object["subtype_data"] - # @todo add subtype observation info class - # self.assertEqual(sd["suitable_for_biocontrol"], "No") - self.assertEqual(sd["pretreatment_observation"], "Yes") + self.assertEqual(sd["suitable_for_biocontrol"], "No") + self.assertEqual(sd["pretreatment_observation"], "Yes") self.assertGreaterEqual(len(sd["entries"]), 1) self.assertIn("WET", sd["inflow_permanent"]) self.assertIn("DISP", sd["inflow_seasonal"]) @@ -69,8 +68,7 @@ def test_subtype_details_partial(self): sd = response_object["subtype_data"] - # @todo add subtype observation info class - # self.assertEqual(sd["suitable_for_biocontrol"], "Yes") + self.assertEqual(sd["suitable_for_biocontrol"], "Yes") self.assertEqual(sd["pretreatment_observation"], "No") self.assertEqual(len(sd["entries"]), 2)