diff --git a/src/meshapi/models/install.py b/src/meshapi/models/install.py index e50d6b62..b6f265e2 100644 --- a/src/meshapi/models/install.py +++ b/src/meshapi/models/install.py @@ -142,4 +142,11 @@ def save(self, *args: Any, **kwargs: Any) -> None: if self.install_number != original.install_number: raise ValidationError("Install number is immutable") + if self._state.adding: + if not self.node: + if self.building.primary_node: + self.node = self.building.primary_node + elif self.building.nodes: + self.node = self.building.nodes.first() + super().save(*args, **kwargs) diff --git a/src/meshapi/tests/test_install.py b/src/meshapi/tests/test_install.py index b79494f5..9178fefe 100644 --- a/src/meshapi/tests/test_install.py +++ b/src/meshapi/tests/test_install.py @@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError from django.test import TestCase -from meshapi.models import Building, Install, Member +from meshapi.models import Building, Install, Member, Node from meshapi.tests.sample_data import sample_building, sample_install, sample_member @@ -43,6 +43,78 @@ def test_construct_install_no_id_yes_install_number(self): self.assertIsNotNone(install2.id) self.assertEqual(install2.install_number, 89) + def test_construct_install_building_has_node(self): + building_node = Node(status=Node.NodeStatus.ACTIVE, latitude=0, longitude=0) + building_node.save() + + self.building_1.primary_node = building_node + self.building_1.save() + + install = Install(**self.sample_install_copy, install_number=45) + install.save() + + install.refresh_from_db() + self.assertEqual(install.node, building_node) + + def test_construct_install_building_has_node_without_primary(self): + building_node = Node(status=Node.NodeStatus.ACTIVE, latitude=0, longitude=0) + building_node.save() + + self.building_1.nodes.add(building_node) + self.building_1.save() + + install = Install(**self.sample_install_copy, install_number=45) + install.save() + + install.refresh_from_db() + self.assertEqual(install.node, building_node) + + def test_remove_node_when_building_has_one(self): + building_node = Node(status=Node.NodeStatus.ACTIVE, latitude=0, longitude=0) + building_node.save() + + self.building_1.primary_node = building_node + self.building_1.save() + + install = Install(**self.sample_install_copy, install_number=45) + install.node = building_node + install.save() + + install.refresh_from_db() + self.assertEqual(install.node, building_node) + + install.node = None + install.save() + + install.refresh_from_db() + self.assertEqual(install.node, None) + + def test_set_install_to_different_to_building(self): + building1_node = Node(status=Node.NodeStatus.ACTIVE, latitude=0, longitude=0) + building2_node = Node(status=Node.NodeStatus.ACTIVE, latitude=0, longitude=0) + building1_node.save() + building2_node.save() + + self.building_1.primary_node = building1_node + self.building_1.save() + + building2 = Building(**sample_building) + building2.primary_node = building2_node + building2.save() + + install = Install(**self.sample_install_copy, install_number=45) + install.node = building1_node + install.save() + + install.refresh_from_db() + self.assertEqual(install.node, building1_node) + + install.building = building2 + install.save() + + install.refresh_from_db() + self.assertEqual(install.node, building1_node) + def test_construct_install_yes_id_no_install_number(self): install = Install( id=uuid.UUID("23ef170c-f37d-44e3-aaac-93dae636c86e"), diff --git a/src/meshapi/tests/test_nn.py b/src/meshapi/tests/test_nn.py index f02aa894..9216659b 100644 --- a/src/meshapi/tests/test_nn.py +++ b/src/meshapi/tests/test_nn.py @@ -28,8 +28,8 @@ def setUp(self): self.admin_c.login(username="admin", password="admin_password") # Create sample data - member_obj = Member(**sample_member) - member_obj.save() + self.member_obj = Member(**sample_member) + self.member_obj.save() self.building = Building(**sample_building) self.building.latitude = 4 @@ -42,7 +42,7 @@ def setUp(self): inst["abandon_date"] = None inst["building"] = self.building - inst["member"] = member_obj + inst["member"] = self.member_obj self.install = Install(**inst) self.install.install_number = 10001 @@ -109,6 +109,98 @@ def test_nn_valid_install_number(self): f"nn incorrect for test_nn_valid_install_number. Should be {expected_nn}, but got {resp_nn}", ) + def test_nn_assign_set_all_installs_on_building_without_nns(self): + pre_existing_node = Node(status=Node.NodeStatus.ACTIVE, latitude=0, longitude=0, network_number=123) + pre_existing_node.save() + + inst = sample_install.copy() + inst["status"] = Install.InstallStatus.REQUEST_RECEIVED + if inst["abandon_date"] == "": + inst["abandon_date"] = None + + inst["building"] = self.building + inst["member"] = self.member_obj + + install2 = Install(**inst) + install2.status = Install.InstallStatus.ACTIVE + install2.install_number = 10002 + install2.node = pre_existing_node + install2.save() + + install3 = Install(**inst) + install3.status = Install.InstallStatus.BLOCKED + install3.install_number = 10003 + install3.save() + + install4 = Install(**inst) + install4.status = Install.InstallStatus.CLOSED + install4.install_number = 10004 + install4.save() + + response = self.admin_c.post( + "/api/v1/nn-assign/", + {"install_number": self.install_number, "password": os.environ.get("NN_ASSIGN_PSK")}, + content_type="application/json", + ) + + code = 201 + self.assertEqual( + code, + response.status_code, + f"status code incorrect for test_nn_valid_install_number. Should be {code}, but got {response.status_code}", + ) + + resp_nn = json.loads(response.content.decode("utf-8"))["network_number"] + expected_nn = 101 + self.assertEqual( + expected_nn, + resp_nn, + f"nn incorrect for test_nn_valid_install_number. Should be {expected_nn}, but got {resp_nn}", + ) + + node_object = Node.objects.get(network_number=expected_nn) + self.assertEqual(node_object.status, Node.NodeStatus.PLANNED) + self.assertEqual(node_object.latitude, self.building.latitude) + self.assertEqual(node_object.longitude, self.building.longitude) + self.assertEqual(node_object.install_date, datetime.date.today()) + + self.install.refresh_from_db() + self.assertEqual(self.install.node, node_object) + self.assertEqual(self.install.status, Install.InstallStatus.PENDING) + + install2.refresh_from_db() + self.assertEqual(install2.node, pre_existing_node) + self.assertEqual(install2.status, Install.InstallStatus.ACTIVE) + + install3.refresh_from_db() + self.assertEqual(install3.node, node_object) + self.assertEqual(install3.status, Install.InstallStatus.BLOCKED) + + install4.refresh_from_db() + self.assertEqual(install4.node, node_object) + self.assertEqual(install4.status, Install.InstallStatus.CLOSED) + + # Now test to make sure that we get 200 for dupes + response = self.admin_c.post( + "/api/v1/nn-assign/", + {"install_number": self.install_number, "password": os.environ.get("NN_ASSIGN_PSK")}, + content_type="application/json", + ) + + code = 200 + self.assertEqual( + code, + response.status_code, + f"status code incorrect for test_nn_valid_install_number DUPLICATE. Should be {code}, but got {response.status_code}", + ) + + resp_nn = json.loads(response.content.decode("utf-8"))["network_number"] + self.assertEqual( + expected_nn, + resp_nn, + f"nn incorrect for test_nn_valid_install_number. Should be {expected_nn}, but got {resp_nn}", + ) + def test_doesnt_change_the_status_of_active_nodes(self): self.install.status = Install.InstallStatus.ACTIVE self.install.save() @@ -168,6 +260,62 @@ def test_building_already_has_nn(self): self.assertEqual(self.install.status, Install.InstallStatus.PENDING) self.assertEqual(node.status, Node.NodeStatus.ACTIVE) + def test_building_already_has_nn_but_another_install_is_missing_the_node(self): + node = Node( + network_number=9999, + status=Node.NodeStatus.ACTIVE, + type=Node.NodeType.STANDARD, + latitude=0, + longitude=0, + ) + node.save() + + self.building.primary_node = node + self.building.save() + + inst = sample_install.copy() + inst["status"] = Install.InstallStatus.REQUEST_RECEIVED + if inst["abandon_date"] == "": + inst["abandon_date"] = None + + inst["building"] = self.building + inst["member"] = self.member_obj + + install2 = Install(**inst) + install2.status = Install.InstallStatus.PENDING + install2.install_number = 10002 + install2.save() + + response = self.admin_c.post( + "/api/v1/nn-assign/", + {"install_number": self.install_number, "password": os.environ.get("NN_ASSIGN_PSK")}, + content_type="application/json", + ) + + code = 201 + self.assertEqual( + code, + response.status_code, + f"status code incorrect for test_nn_valid_install_number. Should be {code}, but got {response.status_code}", + ) + + resp_nn = json.loads(response.content.decode("utf-8"))["network_number"] + expected_nn = 9999 + self.assertEqual( + expected_nn, + resp_nn, + f"nn incorrect for test_nn_valid_install_number. Should be {expected_nn}, but got {resp_nn}", + ) + + self.install.refresh_from_db() + self.assertEqual(self.install.node, node) + self.assertEqual(self.install.status, Install.InstallStatus.PENDING) + self.assertEqual(node.status, Node.NodeStatus.ACTIVE) + + install2.refresh_from_db() + self.assertEqual(install2.node, node) + self.assertEqual(install2.status, Install.InstallStatus.PENDING) + def test_node_already_exists_no_nn(self): node = Node( status=Node.NodeStatus.PLANNED, diff --git a/src/meshapi/views/forms.py b/src/meshapi/views/forms.py index 0f3f5b4f..65af4721 100644 --- a/src/meshapi/views/forms.py +++ b/src/meshapi/views/forms.py @@ -580,6 +580,12 @@ def network_number_assignment(request: Request) -> Response: nn_install.status = Install.InstallStatus.PENDING dirty = True + other_building_installs = [install for install in nn_building.installs.all() if install != nn_install] + for install in other_building_installs: + if install.node is None: + dirty = True + install.node = nn_install.node + # If nothing was changed by this request, return a 200 instead of a 201 if not dirty: message = f"This Install Number ({r.install_number}) already has a " @@ -601,6 +607,8 @@ def network_number_assignment(request: Request) -> Response: nn_install.node.save() nn_building.save() nn_install.save() + for install in other_building_installs: + install.save() except IntegrityError: logging.exception("NN Request failed. Could not save node number.") return Response(