From 7fdc61be46407d7ee9e1c65376ddd0b517e60a64 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 21 Dec 2023 07:45:05 -0500 Subject: [PATCH 1/9] remove nameservers model; update db with hosts and host_ips on _fetch_cache --- src/registrar/admin.py | 1 - .../migrations/0058_delete_nameserver.py | 15 ++++++ src/registrar/models/__init__.py | 3 -- src/registrar/models/domain.py | 50 ++++++++++++++++++- src/registrar/models/nameserver.py | 16 ------ 5 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 src/registrar/migrations/0058_delete_nameserver.py delete mode 100644 src/registrar/models/nameserver.py diff --git a/src/registrar/admin.py b/src/registrar/admin.py index def7c64b1..7777245fe 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1052,7 +1052,6 @@ admin.site.register(models.DomainInformation, DomainInformationAdmin) admin.site.register(models.Domain, DomainAdmin) admin.site.register(models.DraftDomain, DraftDomainAdmin) admin.site.register(models.Host, MyHostAdmin) -admin.site.register(models.Nameserver, MyHostAdmin) admin.site.register(models.Website, WebsiteAdmin) admin.site.register(models.PublicContact, AuditedAdmin) admin.site.register(models.DomainApplication, DomainApplicationAdmin) diff --git a/src/registrar/migrations/0058_delete_nameserver.py b/src/registrar/migrations/0058_delete_nameserver.py new file mode 100644 index 000000000..3ea3814a9 --- /dev/null +++ b/src/registrar/migrations/0058_delete_nameserver.py @@ -0,0 +1,15 @@ +# Generated by Django 4.2.7 on 2023-12-21 11:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0057_domainapplication_submission_date"), + ] + + operations = [ + migrations.DeleteModel( + name="Nameserver", + ), + ] diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index 1203c7878..6afad5a5c 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -7,7 +7,6 @@ from .draft_domain import DraftDomain from .host_ip import HostIP from .host import Host from .domain_invitation import DomainInvitation -from .nameserver import Nameserver from .user_domain_role import UserDomainRole from .public_contact import PublicContact from .user import User @@ -24,7 +23,6 @@ __all__ = [ "DomainInvitation", "HostIP", "Host", - "Nameserver", "UserDomainRole", "PublicContact", "User", @@ -41,7 +39,6 @@ auditlog.register(DomainInvitation) auditlog.register(DomainInformation) auditlog.register(HostIP) auditlog.register(Host) -auditlog.register(Nameserver) auditlog.register(UserDomainRole) auditlog.register(PublicContact) auditlog.register(User, m2m_fields=["user_permissions", "groups"]) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index ca2bc4951..7fa3311bd 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -10,6 +10,8 @@ from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignor from django.db import models from django.utils import timezone from typing import Any +from registrar.models.host import Host +from registrar.models.host_ip import HostIP from registrar.utility.errors import ( @@ -1605,6 +1607,7 @@ class Domain(TimeStampedModel, DomainHelper): cache = self._extract_data_from_response(data_response) cleaned = self._clean_cache(cache, data_response) self._update_hosts_and_contacts(cleaned, fetch_hosts, fetch_contacts) + self._update_hosts_and_ips_in_db(cleaned, fetch_hosts) self._update_dates(cleaned) self._cache = cleaned @@ -1651,7 +1654,7 @@ class Domain(TimeStampedModel, DomainHelper): return dnssec_data def _update_hosts_and_contacts(self, cleaned, fetch_hosts, fetch_contacts): - """Capture and store old hosts and contacts from cache if the don't exist""" + """Capture and cache old hosts and contacts from cache if they don't exist in cleaned""" old_cache_hosts = self._cache.get("hosts") old_cache_contacts = self._cache.get("contacts") @@ -1666,6 +1669,51 @@ class Domain(TimeStampedModel, DomainHelper): if old_cache_contacts is not None: cleaned["contacts"] = old_cache_contacts + def _update_hosts_and_ips_in_db(self, cleaned, fetch_hosts): + """Update hosts and host_ips in database if retrieved from registry. + + Parameters: + self: the domain to be updated with hosts and ips from cleaned + cleaned: dict containing hosts. Hosts are provided as a list of dicts, e.g. + [{"name": "ns1.example.com",}, {"name": "ns1.example.gov"}, "addrs": ["0.0.0.0"])] + fetch_hosts: boolean indicating whether or not fetch_hosts was called + """ + if fetch_hosts: + cleaned_hosts = cleaned["hosts"] + # Get all existing hosts from the database for this domain + existing_hosts_in_db = Host.objects.filter(domain=self) + # Identify hosts to delete + cleaned_host_names = set(cleaned_host["name"] for cleaned_host in cleaned_hosts) + hosts_to_delete_from_db = [ + existing_host for existing_host in existing_hosts_in_db if existing_host.name not in cleaned_host_names + ] + # Delete hosts and their associated HostIP instances + for host_to_delete in hosts_to_delete_from_db: + # Delete associated HostIP instances + HostIP.objects.filter(host=host_to_delete).delete() + # Delete the host itself + host_to_delete.delete() + # Update or create Hosts and HostIPs + for cleaned_host in cleaned_hosts: + # Check if the cleaned_host already exists + host_in_db, host_created = Host.objects.get_or_create(domain=self, name=cleaned_host["name"]) + # Get cleaned list of ips for update + cleaned_ips = cleaned_host["addrs"] + if not host_created: + # Get all existing ips from the database for this host + existing_ips_in_db = HostIP.objects.filter(host=host_in_db) + # Identify IPs to delete + ips_to_delete_from_db = [ + existing_ip for existing_ip in existing_ips_in_db if existing_ip.address not in cleaned_ips + ] + # Delete IPs + for ip_to_delete in ips_to_delete_from_db: + # Delete the ip + ip_to_delete.delete() + # Update or create HostIP instances + for ip_address in cleaned_ips: + HostIP.objects.get_or_create(address=ip_address, host=host_in_db) + def _update_dates(self, cleaned): """Update dates (expiration and creation) from cleaned""" requires_save = False diff --git a/src/registrar/models/nameserver.py b/src/registrar/models/nameserver.py deleted file mode 100644 index 13295f5b5..000000000 --- a/src/registrar/models/nameserver.py +++ /dev/null @@ -1,16 +0,0 @@ -from .host import Host - - -class Nameserver(Host): - """ - A nameserver is a host which has been delegated to respond to DNS queries. - - The registry is the source of truth for this data. - - This model exists ONLY to allow a new registrant to draft DNS entries - before their application is approved. - """ - - # there is nothing here because all of the fields are - # defined over there on the Host class - pass From f5f3b2335b2e70b9d99408d255e4b57fd4753dfd Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 21 Dec 2023 08:10:21 -0500 Subject: [PATCH 2/9] get nameservers from db in the event of registry error retrieving nameservers --- src/registrar/models/domain.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 7fa3311bd..fd62c29d8 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -299,11 +299,12 @@ class Domain(TimeStampedModel, DomainHelper): try: hosts = self._get_property("hosts") except Exception as err: - # Do not raise error when missing nameservers - # this is a standard occurence when a domain - # is first created - logger.info("Domain is missing nameservers %s" % err) - return [] + # If exception raised returning hosts from registry, get from db + hosts = [] + for host in self.host.all(): + host_name = host.name + ips = [ip.address for ip in host.ip.all()] + hosts.append({"name": host_name, "addrs": ips}) # TODO-687 fix this return value hostList = [] From bd3c150067cbec43cb5f7f6682ec15f66d9f6726 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 21 Dec 2023 13:47:25 -0500 Subject: [PATCH 3/9] wrote tests; fixed existing tests --- src/registrar/models/domain.py | 2 +- src/registrar/tests/test_models_domain.py | 61 ++++++++++++++++++++++- src/registrar/tests/test_views.py | 4 ++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index fd62c29d8..d7add0314 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -13,7 +13,6 @@ from typing import Any from registrar.models.host import Host from registrar.models.host_ip import HostIP - from registrar.utility.errors import ( ActionNotAllowed, NameserverError, @@ -297,6 +296,7 @@ class Domain(TimeStampedModel, DomainHelper): while non-subordinate hosts MUST NOT. """ try: + # attempt to retrieve hosts from registry and store in cache and db hosts = self._get_property("hosts") except Exception as err: # If exception raised returning hosts from registry, get from db diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 2f54d7794..b58af4ac4 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -7,7 +7,7 @@ from django.test import TestCase from django.db.utils import IntegrityError from unittest.mock import MagicMock, patch, call import datetime -from registrar.models import Domain +from registrar.models import Domain, Host, HostIP from unittest import skip from registrar.models.domain_application import DomainApplication @@ -38,6 +38,8 @@ logger = logging.getLogger(__name__) class TestDomainCache(MockEppLib): def tearDown(self): PublicContact.objects.all().delete() + HostIP.objects.all().delete() + Host.objects.all().delete() Domain.objects.all().delete() super().tearDown() @@ -1512,6 +1514,61 @@ class TestRegistrantNameservers(MockEppLib): with self.assertRaises(ActionNotAllowed): domain.nameservers = [self.nameserver1, self.nameserver2] + def test_nameserver_returns_on_registry_error(self): + """ + Scenario: Nameservers previously set through EPP and stored in registrar's database. + Registry is unavailable and throws exception when attempting to build cache from + registry. Nameservers retrieved from database. + """ + domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) + # set the host and host_ips directly in the database; this is normally handled through + # fetch_cache + host, _ = Host.objects.get_or_create(domain=domain, name="ns1.fake.gov") + host_ip, _ = HostIP.objects.get_or_create(host=host, address="1.1.1.1") + + # mock that registry throws an error on + + def side_effect(_request, cleaned): + raise RegistryError(code=ErrorCode.COMMAND_FAILED) + + patcher = patch("registrar.models.domain.registry.send") + mocked_send = patcher.start() + mocked_send.side_effect = side_effect + + nameservers = domain.nameservers + + self.assertEqual(len(nameservers), 1) + self.assertEqual(nameservers[0][0], "ns1.fake.gov") + self.assertEqual(nameservers[0][1], ["1.1.1.1"]) + + patcher.stop() + + def test_nameservers_stored_on_fetch_cache(self): + """ + Scenario: Nameservers are stored in db when they are retrieved from fetch_cache. + Verify the success of this by asserting get_or_create calls to db. + The mocked data for the EPP calls returns a host name + of 'fake.host.com' from InfoDomain and an array of 2 IPs: 1.2.3.4 and 2.3.4.5 + from InfoHost + """ + domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) + + # mock the get_or_create methods for Host and HostIP + with patch.object(Host.objects, 'get_or_create') as mock_host_get_or_create, \ + patch.object(HostIP.objects, 'get_or_create') as mock_host_ip_get_or_create: + # Set the return value for the mocks + mock_host_get_or_create.return_value = (Host(), True) + mock_host_ip_get_or_create.return_value = (HostIP(), True) + + # force fetch_cache to be called, which will return above documented mocked hosts + domain.nameservers + # assert that the mocks are called + mock_host_get_or_create.assert_called_once_with(domain=domain, name='fake.host.com') + # Retrieve the mocked_host from the return value of the mock + actual_mocked_host, _ = mock_host_get_or_create.return_value + mock_host_ip_get_or_create.assert_called_with(address='2.3.4.5', host=actual_mocked_host) + self.assertEqual(mock_host_ip_get_or_create.call_count, 2) + @skip("not implemented yet") def test_update_is_unsuccessful(self): """ @@ -1530,6 +1587,8 @@ class TestRegistrantNameservers(MockEppLib): domain.nameservers = [("ns1.failednameserver.gov", ["4.5.6"])] def tearDown(self): + HostIP.objects.all().delete() + Host.objects.all().delete() Domain.objects.all().delete() return super().tearDown() diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index e40e6196a..c16cdd7e3 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -29,6 +29,8 @@ from registrar.models import ( DomainInvitation, Contact, PublicContact, + Host, + HostIP, Website, UserDomainRole, User, @@ -1178,6 +1180,8 @@ class TestWithDomainPermissions(TestWithUser): DomainApplication.objects.all().delete() DomainInformation.objects.all().delete() PublicContact.objects.all().delete() + HostIP.objects.all().delete() + Host.objects.all().delete() Domain.objects.all().delete() UserDomainRole.objects.all().delete() except ValueError: # pass if already deleted From ed7868efba31bddd4548125af73f773498d3a82b Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 21 Dec 2023 13:55:23 -0500 Subject: [PATCH 4/9] fixed minor bug and formatting --- src/registrar/models/domain.py | 8 ++++---- src/registrar/tests/test_models_domain.py | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index d7add0314..7c0566774 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -298,12 +298,12 @@ class Domain(TimeStampedModel, DomainHelper): try: # attempt to retrieve hosts from registry and store in cache and db hosts = self._get_property("hosts") - except Exception as err: + except Exception: # If exception raised returning hosts from registry, get from db hosts = [] - for host in self.host.all(): - host_name = host.name - ips = [ip.address for ip in host.ip.all()] + for hostobj in self.host.all(): + host_name = hostobj.name + ips = [ip.address for ip in hostobj.ip.all()] hosts.append({"name": host_name, "addrs": ips}) # TODO-687 fix this return value diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index b58af4ac4..caea289a0 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -1517,7 +1517,7 @@ class TestRegistrantNameservers(MockEppLib): def test_nameserver_returns_on_registry_error(self): """ Scenario: Nameservers previously set through EPP and stored in registrar's database. - Registry is unavailable and throws exception when attempting to build cache from + Registry is unavailable and throws exception when attempting to build cache from registry. Nameservers retrieved from database. """ domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) @@ -1526,7 +1526,7 @@ class TestRegistrantNameservers(MockEppLib): host, _ = Host.objects.get_or_create(domain=domain, name="ns1.fake.gov") host_ip, _ = HostIP.objects.get_or_create(host=host, address="1.1.1.1") - # mock that registry throws an error on + # mock that registry throws an error on the InfoHost send def side_effect(_request, cleaned): raise RegistryError(code=ErrorCode.COMMAND_FAILED) @@ -1554,19 +1554,20 @@ class TestRegistrantNameservers(MockEppLib): domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) # mock the get_or_create methods for Host and HostIP - with patch.object(Host.objects, 'get_or_create') as mock_host_get_or_create, \ - patch.object(HostIP.objects, 'get_or_create') as mock_host_ip_get_or_create: + with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object( + HostIP.objects, "get_or_create" + ) as mock_host_ip_get_or_create: # Set the return value for the mocks mock_host_get_or_create.return_value = (Host(), True) mock_host_ip_get_or_create.return_value = (HostIP(), True) - + # force fetch_cache to be called, which will return above documented mocked hosts domain.nameservers # assert that the mocks are called - mock_host_get_or_create.assert_called_once_with(domain=domain, name='fake.host.com') + mock_host_get_or_create.assert_called_once_with(domain=domain, name="fake.host.com") # Retrieve the mocked_host from the return value of the mock actual_mocked_host, _ = mock_host_get_or_create.return_value - mock_host_ip_get_or_create.assert_called_with(address='2.3.4.5', host=actual_mocked_host) + mock_host_ip_get_or_create.assert_called_with(address="2.3.4.5", host=actual_mocked_host) self.assertEqual(mock_host_ip_get_or_create.call_count, 2) @skip("not implemented yet") From cea9394d273a1a6d55dc0718cde8d9e8fc606c23 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 28 Dec 2023 18:29:31 -0500 Subject: [PATCH 5/9] fixing an edge case in template that should never actually happen --- src/registrar/templates/includes/summary_item.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/registrar/templates/includes/summary_item.html b/src/registrar/templates/includes/summary_item.html index dea14553b..53364d1b2 100644 --- a/src/registrar/templates/includes/summary_item.html +++ b/src/registrar/templates/includes/summary_item.html @@ -47,6 +47,15 @@ {% if value|length == 1 %} {% if users %}

{{ value.0.user.email }}

+ {% elif domains %} + {{ value.0.0 }} + {% if value.0.1 %} + ({% spaceless %} + {% for addr in value.0.1 %} + {{addr}}{% if not forloop.last %}, {% endif %} + {% endfor %} + {% endspaceless %}) + {% endif %} {% else %}

{{ value | first }}

{% endif %} From 2e48a36b7d5c999cd26e6e20eb8a687a62f0f0f9 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 28 Dec 2023 18:40:17 -0500 Subject: [PATCH 6/9] fixing migrations --- .../{0058_delete_nameserver.py => 0059_delete_nameserver.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/registrar/migrations/{0058_delete_nameserver.py => 0059_delete_nameserver.py} (79%) diff --git a/src/registrar/migrations/0058_delete_nameserver.py b/src/registrar/migrations/0059_delete_nameserver.py similarity index 79% rename from src/registrar/migrations/0058_delete_nameserver.py rename to src/registrar/migrations/0059_delete_nameserver.py index 3ea3814a9..404f8abf0 100644 --- a/src/registrar/migrations/0058_delete_nameserver.py +++ b/src/registrar/migrations/0059_delete_nameserver.py @@ -5,7 +5,7 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ("registrar", "0057_domainapplication_submission_date"), + ("registrar", "0058_alter_domaininformation_options"), ] operations = [ From d0ca1ccff1e1c1b168d9dc29a4c6a1251f7724f6 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 29 Dec 2023 06:23:33 -0500 Subject: [PATCH 7/9] remove Hosts from django admin, update docstrings on Host and HostIP --- src/registrar/admin.py | 4 +++- src/registrar/models/host.py | 4 ++-- src/registrar/models/host_ip.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 0e7e4650d..6b674bd60 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1246,7 +1246,9 @@ admin.site.register(models.DomainInvitation, DomainInvitationAdmin) admin.site.register(models.DomainInformation, DomainInformationAdmin) admin.site.register(models.Domain, DomainAdmin) admin.site.register(models.DraftDomain, DraftDomainAdmin) -admin.site.register(models.Host, MyHostAdmin) +# Host and HostIP removed from django admin because changes in admin +# do not propogate to registry and logic not applied +# admin.site.register(models.Host, MyHostAdmin) admin.site.register(models.Website, WebsiteAdmin) admin.site.register(models.PublicContact, AuditedAdmin) admin.site.register(models.DomainApplication, DomainApplicationAdmin) diff --git a/src/registrar/models/host.py b/src/registrar/models/host.py index bab968afc..2d756111e 100644 --- a/src/registrar/models/host.py +++ b/src/registrar/models/host.py @@ -11,8 +11,8 @@ class Host(TimeStampedModel): The registry is the source of truth for this data. - This model exists ONLY to allow a new registrant to draft DNS entries - before their application is approved. + This model exists to make hosts/nameservers and ip addresses + available when registry is not available. """ name = models.CharField( diff --git a/src/registrar/models/host_ip.py b/src/registrar/models/host_ip.py index 4d646898b..777d14430 100644 --- a/src/registrar/models/host_ip.py +++ b/src/registrar/models/host_ip.py @@ -10,8 +10,8 @@ class HostIP(TimeStampedModel): The registry is the source of truth for this data. - This model exists ONLY to allow a new registrant to draft DNS entries - before their application is approved. + This model exists to make hosts/nameservers and ip addresses + available when registry is not available. """ address = models.CharField( From 66bc63aad4e4624c0af7441d8766f4c2f3b877db Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 29 Dec 2023 16:05:00 -0500 Subject: [PATCH 8/9] some minor modifications --- src/registrar/models/domain.py | 82 ++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 51c074a7d..a99a62fda 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1608,7 +1608,8 @@ class Domain(TimeStampedModel, DomainHelper): cache = self._extract_data_from_response(data_response) cleaned = self._clean_cache(cache, data_response) self._update_hosts_and_contacts(cleaned, fetch_hosts, fetch_contacts) - self._update_hosts_and_ips_in_db(cleaned, fetch_hosts) + if fetch_hosts: + self._update_hosts_and_ips_in_db(cleaned) self._update_dates(cleaned) self._cache = cleaned @@ -1655,7 +1656,11 @@ class Domain(TimeStampedModel, DomainHelper): return dnssec_data def _update_hosts_and_contacts(self, cleaned, fetch_hosts, fetch_contacts): - """Capture and cache old hosts and contacts from cache if they don't exist in cleaned""" + """ + Update hosts and contacts if fetch_hosts and/or fetch_contacts. + Additionally, capture and cache old hosts and contacts from cache if they + don't exist in cleaned + """ old_cache_hosts = self._cache.get("hosts") old_cache_contacts = self._cache.get("contacts") @@ -1670,50 +1675,49 @@ class Domain(TimeStampedModel, DomainHelper): if old_cache_contacts is not None: cleaned["contacts"] = old_cache_contacts - def _update_hosts_and_ips_in_db(self, cleaned, fetch_hosts): + def _update_hosts_and_ips_in_db(self, cleaned): """Update hosts and host_ips in database if retrieved from registry. + Only called when fetch_hosts is True. Parameters: self: the domain to be updated with hosts and ips from cleaned cleaned: dict containing hosts. Hosts are provided as a list of dicts, e.g. [{"name": "ns1.example.com",}, {"name": "ns1.example.gov"}, "addrs": ["0.0.0.0"])] - fetch_hosts: boolean indicating whether or not fetch_hosts was called """ - if fetch_hosts: - cleaned_hosts = cleaned["hosts"] - # Get all existing hosts from the database for this domain - existing_hosts_in_db = Host.objects.filter(domain=self) - # Identify hosts to delete - cleaned_host_names = set(cleaned_host["name"] for cleaned_host in cleaned_hosts) - hosts_to_delete_from_db = [ - existing_host for existing_host in existing_hosts_in_db if existing_host.name not in cleaned_host_names - ] - # Delete hosts and their associated HostIP instances - for host_to_delete in hosts_to_delete_from_db: - # Delete associated HostIP instances - HostIP.objects.filter(host=host_to_delete).delete() - # Delete the host itself - host_to_delete.delete() - # Update or create Hosts and HostIPs - for cleaned_host in cleaned_hosts: - # Check if the cleaned_host already exists - host_in_db, host_created = Host.objects.get_or_create(domain=self, name=cleaned_host["name"]) - # Get cleaned list of ips for update - cleaned_ips = cleaned_host["addrs"] - if not host_created: - # Get all existing ips from the database for this host - existing_ips_in_db = HostIP.objects.filter(host=host_in_db) - # Identify IPs to delete - ips_to_delete_from_db = [ - existing_ip for existing_ip in existing_ips_in_db if existing_ip.address not in cleaned_ips - ] - # Delete IPs - for ip_to_delete in ips_to_delete_from_db: - # Delete the ip - ip_to_delete.delete() - # Update or create HostIP instances - for ip_address in cleaned_ips: - HostIP.objects.get_or_create(address=ip_address, host=host_in_db) + cleaned_hosts = cleaned["hosts"] + # Get all existing hosts from the database for this domain + existing_hosts_in_db = Host.objects.filter(domain=self) + # Identify hosts to delete + cleaned_host_names = set(cleaned_host["name"] for cleaned_host in cleaned_hosts) + hosts_to_delete_from_db = [ + existing_host for existing_host in existing_hosts_in_db if existing_host.name not in cleaned_host_names + ] + # Delete hosts and their associated HostIP instances + for host_to_delete in hosts_to_delete_from_db: + # Delete associated HostIP instances + HostIP.objects.filter(host=host_to_delete).delete() + # Delete the host itself + host_to_delete.delete() + # Update or create Hosts and HostIPs + for cleaned_host in cleaned_hosts: + # Check if the cleaned_host already exists + host_in_db, host_created = Host.objects.get_or_create(domain=self, name=cleaned_host["name"]) + # Get cleaned list of ips for update + cleaned_ips = cleaned_host["addrs"] + if not host_created: + # Get all existing ips from the database for this host + existing_ips_in_db = HostIP.objects.filter(host=host_in_db) + # Identify IPs to delete + ips_to_delete_from_db = [ + existing_ip for existing_ip in existing_ips_in_db if existing_ip.address not in cleaned_ips + ] + # Delete IPs + for ip_to_delete in ips_to_delete_from_db: + # Delete the ip + ip_to_delete.delete() + # Update or create HostIP instances + for ip_address in cleaned_ips: + HostIP.objects.get_or_create(address=ip_address, host=host_in_db) def _update_dates(self, cleaned): """Update dates (expiration and creation) from cleaned""" From 920d6a3f233b9d16585ad7f7ab5b6354b284476b Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 29 Dec 2023 16:11:43 -0500 Subject: [PATCH 9/9] linting --- src/registrar/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index bf8d5aa9f..77cb8d70a 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1660,7 +1660,7 @@ class Domain(TimeStampedModel, DomainHelper): def _update_hosts_and_contacts(self, cleaned, fetch_hosts, fetch_contacts): """ Update hosts and contacts if fetch_hosts and/or fetch_contacts. - Additionally, capture and cache old hosts and contacts from cache if they + Additionally, capture and cache old hosts and contacts from cache if they don't exist in cleaned """ old_cache_hosts = self._cache.get("hosts")