Repository: nautobot/nautobot

Issue:
Pre-Release Github Action Workflow Doesn't Publish Python Version Specific Builds
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Nautobot version (Docker tag too if applicable): 2.0.0-alpha.1
* Python version: 3.7-3.10
* Database platform, version: NA
* Middleware(s): NA

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
### Steps to Reproduce
1. Create Pre-Release Release named `2.0.0-alpha.1`
2. Wait for CI to Run

<!-- What did you expect to happen? -->
### Expected Behavior

Docker containers built following or normal release convention `2.0.0-alpha.1` as well as `2.0.0-alpha.1-py3.10`, `2.0.0-alpha.1-py3.9`, etc.



<!-- What happened instead? -->
### Observed Behavior

Whichever container is published last "wins" and becomes the `2.0.0-alpha.1` build: https://hub.docker.com/layers/networktocode/nautobot/2.0.0-alpha.1/images/sha256-6f41c067abac092cbed73b5b6176f771e8432e6cbd380b8d5f9013ba521d87a6?context=explore


Discussion:
Should investigate maybe a couple other CI cleanups such as this resolved issue: https://github.com/nautobot/nautobot/blob/1d5588f83e36498bcb7757aad18b9dbae0900e3d/.github/workflows/ci_integration.yml#L178-L181

Resolving patch:
diff --git a/nautobot/ipam/lookups.py b/nautobot/ipam/lookups.py
--- a/nautobot/ipam/lookups.py
+++ b/nautobot/ipam/lookups.py
@@ -8,7 +8,9 @@ def _mysql_varbin_to_broadcast():
     return "HEX(broadcast)"
 
 
-def _mysql_varbin_to_hex(lhs):
+def _mysql_varbin_to_hex(lhs, alias=None):
+    if alias:
+        return f"HEX({alias}.{lhs})"
     return f"HEX({lhs})"
 
 
@@ -20,7 +22,9 @@ def _postgresql_varbin_to_broadcast(length):
     return f"right(broadcast::text, -1)::varbit::bit({length})"
 
 
-def _postgresql_varbin_to_integer(lhs, length):
+def _postgresql_varbin_to_integer(lhs, length, alias=None):
+    if alias:
+        return f"right({alias}.{lhs}::text, -1)::varbit::bit({length})"
     return f"right({lhs}::text, -1)::varbit::bit({length})"
 
 
@@ -33,7 +37,7 @@ def py_to_hex(ip, length):
     return str(hex(int(ip)))[2:].zfill(int(length / 4))
 
 
-def get_ip_info(field_name, ip_str):
+def get_ip_info(field_name, ip_str, alias=None):
     """Function to set all details about an IP, that may be needed."""
     ip_details = IPDetails()
     ip = netaddr.IPNetwork(ip_str)
@@ -51,7 +55,7 @@ def get_ip_info(field_name, ip_str):
         ip_details.bcast_addr = f"'{py_to_hex(ip[-1], ip_details.length)}'"
         ip_details.q_net = _mysql_varbin_to_network()
         ip_details.q_bcast = _mysql_varbin_to_broadcast()
-        ip_details.q_ip = _mysql_varbin_to_hex(field_name)
+        ip_details.q_ip = _mysql_varbin_to_hex(field_name, alias=alias)
 
     elif _connection.vendor == "postgresql":
         ip_details.rhs = bin(int(ip_details.addr))[2:].zfill(ip_details.length)
@@ -60,7 +64,7 @@ def get_ip_info(field_name, ip_str):
         ip_details.bcast_addr = f"B'{bin(int(ip[-1]))[2:].zfill(ip_details.length)}'"
         ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length)
         ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length)
-        ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length)
+        ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length, alias=alias)
 
     return ip_details
 
@@ -133,7 +137,7 @@ def get_prep_lookup(self):
             raise NotSupportedError(f"Lookup for network field does not include the {self.lookup_name} lookup.")
         if field_name == "host" and self.lookup_name not in ["net_host", "net_host_contained", "net_in"]:
             raise NotSupportedError(f"Lookup for host field does not include the {self.lookup_name} lookup.")
-        self.ip = get_ip_info(field_name, self.rhs)
+        self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)
         return str(self.ip.ip)
 
     def process_rhs(self, qn, connection):
@@ -199,7 +203,7 @@ def get_prep_lookup(self):
         field_name = self.lhs.field.name
         if field_name != "host":
             raise NotSupportedError(f"Lookup only provided on the host fields, not {field_name}.")
-        self.ip = get_ip_info(field_name, self.rhs)
+        self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)
         return str(self.ip.ip)
 
     def process_rhs(self, qn, connection):
@@ -227,7 +231,7 @@ def get_prep_lookup(self):
             raise NotSupportedError(f"Lookup only provided on the host field, not {field_name}.")
         self.ips = []
         for _ip in self.rhs:
-            ip = get_ip_info(field_name, _ip)
+            ip = get_ip_info(field_name, _ip, alias=self.lhs.alias)
             self.ips.append(ip)
         # This is to satisfy an issue with django cacheops, specifically this line:
         # https://github.com/Suor/django-cacheops/blob/a5ed1ac28c7259f5ad005e596cc045d1d61e2c51/cacheops/query.py#L175


Test patch:
diff --git a/nautobot/ipam/tests/test_querysets.py b/nautobot/ipam/tests/test_querysets.py
--- a/nautobot/ipam/tests/test_querysets.py
+++ b/nautobot/ipam/tests/test_querysets.py
@@ -1,3 +1,4 @@
+import re
 from unittest import skipIf
 
 import netaddr
@@ -82,34 +83,50 @@ def setUpTestData(cls):
 
         cls.queryset.delete()
 
-        IPAddress.objects.create(address="10.0.0.1/24", vrf=None, tenant=None)
-        IPAddress.objects.create(address="10.0.0.2/24", vrf=None, tenant=None)
-        IPAddress.objects.create(address="10.0.0.3/24", vrf=None, tenant=None)
-        IPAddress.objects.create(address="10.0.0.4/24", vrf=None, tenant=None)
-        IPAddress.objects.create(address="10.0.0.1/25", vrf=None, tenant=None)
-        IPAddress.objects.create(address="2001:db8::1/64", vrf=None, tenant=None)
-        IPAddress.objects.create(address="2001:db8::2/64", vrf=None, tenant=None)
-        IPAddress.objects.create(address="2001:db8::3/64", vrf=None, tenant=None)
+        cls.ips = {
+            "10.0.0.1/24": IPAddress.objects.create(address="10.0.0.1/24", vrf=None, tenant=None),
+            "10.0.0.1/25": IPAddress.objects.create(address="10.0.0.1/25", vrf=None, tenant=None),
+            "10.0.0.2/24": IPAddress.objects.create(address="10.0.0.2/24", vrf=None, tenant=None),
+            "10.0.0.3/24": IPAddress.objects.create(address="10.0.0.3/24", vrf=None, tenant=None),
+            "10.0.0.4/24": IPAddress.objects.create(address="10.0.0.4/24", vrf=None, tenant=None),
+            "2001:db8::1/64": IPAddress.objects.create(address="2001:db8::1/64", vrf=None, tenant=None),
+            "2001:db8::2/64": IPAddress.objects.create(address="2001:db8::2/64", vrf=None, tenant=None),
+            "2001:db8::3/64": IPAddress.objects.create(address="2001:db8::3/64", vrf=None, tenant=None),
+        }
 
     def test_ip_family(self):
         self.assertEqual(self.queryset.ip_family(4).count(), 5)
         self.assertEqual(self.queryset.ip_family(6).count(), 3)
 
     def test_net_host_contained(self):
-        self.assertEqual(self.queryset.net_host_contained(netaddr.IPNetwork("10.0.0.0/24")).count(), 5)
-        self.assertEqual(self.queryset.net_host_contained(netaddr.IPNetwork("10.0.0.0/30")).count(), 4)
-        self.assertEqual(self.queryset.net_host_contained(netaddr.IPNetwork("10.0.0.0/31")).count(), 2)
-        self.assertEqual(self.queryset.net_host_contained(netaddr.IPNetwork("10.0.10.0/24")).count(), 0)
+        self.assertQuerysetEqualAndNotEmpty(
+            self.queryset.net_host_contained(netaddr.IPNetwork("10.0.0.0/24")),
+            [instance for ip, instance in self.ips.items() if "10.0" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            self.queryset.net_host_contained(netaddr.IPNetwork("10.0.0.0/30")),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.0\.0\.[0-3]/", ip)],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            self.queryset.net_host_contained(netaddr.IPNetwork("10.0.0.0/31")),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.0\.0\.[0-1]/", ip)],
+        )
+        self.assertQuerysetEqual(
+            self.queryset.net_host_contained(netaddr.IPNetwork("10.0.10.0/24")),
+            [],
+        )
 
     def test_net_in(self):
         args = ["10.0.0.1/24"]
-        self.assertEqual(self.queryset.net_in(args).count(), 1)
+        self.assertQuerysetEqualAndNotEmpty(self.queryset.net_in(args), [self.ips["10.0.0.1/24"]])
 
         args = ["10.0.0.1"]
-        self.assertEqual(self.queryset.net_in(args).count(), 2)
+        self.assertQuerysetEqualAndNotEmpty(
+            self.queryset.net_in(args), [self.ips["10.0.0.1/24"], self.ips["10.0.0.1/25"]]
+        )
 
         args = ["10.0.0.1/24", "10.0.0.1/25"]
-        self.assertEqual(self.queryset.net_in(args).count(), 2)
+        self.assertQuerysetEqualAndNotEmpty(self.queryset.net_in(args), [self.ips[arg] for arg in args])
 
     def test_get_by_address(self):
         address = self.queryset.net_in(["10.0.0.1/24"])[0]
@@ -194,107 +211,291 @@ def test_string_search(self):
             "fe80::": 0,
         }
         for term, cnt in search_terms.items():
+            # TODO refactor to be able to use assertQuerysetEqualAndNotEmpty()
             self.assertEqual(self.queryset.string_search(term).count(), cnt)
 
     def test_host_family(self):
-        self.assertEqual(IPAddress.objects.filter(host__family=4).count(), 5)
-        self.assertEqual(IPAddress.objects.filter(host__family=6).count(), 3)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__family=4),
+            [instance for ip, instance in self.ips.items() if "." in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__family=6),
+            [instance for ip, instance in self.ips.items() if ":" in ip],
+        )
 
     def test_host_net_host(self):
-        self.assertEqual(IPAddress.objects.filter(host__net_host="10.0.0.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__net_host="10.0.0.2").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__net_host="10.0.0.50").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__net_host="2001:db8::1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__net_host="2001:db8::5").count(), 0)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_host="10.0.0.1"),
+            [self.ips["10.0.0.1/24"], self.ips["10.0.0.1/25"]],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_host="10.0.0.2"),
+            [self.ips["10.0.0.2/24"]],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__net_host="10.0.0.50"), [])
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_host="2001:db8::1"),
+            [self.ips["2001:db8::1/64"]],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__net_host="2001:db8::5"), [])
+        # https://github.com/nautobot/nautobot/issues/3480
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__net_host="10.0.0.1"),
+            [self.ips["10.0.0.1/24"], self.ips["10.0.0.1/25"]],
+        )
 
     def test_host_net_host_contained(self):
-        self.assertEqual(IPAddress.objects.filter(host__net_host_contained="10.0.0.0/24").count(), 5)
-        self.assertEqual(IPAddress.objects.filter(host__net_host_contained="10.0.0.0/30").count(), 4)
-        self.assertEqual(IPAddress.objects.filter(host__net_host_contained="10.0.0.0/31").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__net_host_contained="10.0.0.2/31").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__net_host_contained="10.0.10.0/24").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__net_host_contained="2001:db8::/64").count(), 3)
-        self.assertEqual(IPAddress.objects.filter(host__net_host_contained="2222:db8::/64").count(), 0)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_host_contained="10.0.0.0/24"),
+            [instance for ip, instance in self.ips.items() if "10.0.0" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_host_contained="10.0.0.0/30"),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.0\.0\.[0-3]/", ip)],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_host_contained="10.0.0.0/31"),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.0\.0\.[0-1]/", ip)],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_host_contained="10.0.0.2/31"),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.0\.0\.[2-3]/", ip)],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__net_host_contained="10.0.10.0/24"), [])
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_host_contained="2001:db8::/64"),
+            [instance for ip, instance in self.ips.items() if "2001:db8:" in ip],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__net_host_contained="2222:db8::/64"), [])
+        # https://github.com/nautobot/nautobot/issues/3480
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__net_host_contained="10.0.0.0/24"),
+            [instance for ip, instance in self.ips.items() if "10.0.0" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__net_host_contained="2001:db8::/64"),
+            [instance for ip, instance in self.ips.items() if "2001:db8:" in ip],
+        )
 
     def test_host_net_in(self):
-        self.assertEqual(IPAddress.objects.filter(host__net_in=["10.0.0.0/31", "10.0.0.2/31"]).count(), 4)
-        self.assertEqual(IPAddress.objects.filter(host__net_in=["10.0.0.0/24"]).count(), 5)
-        self.assertEqual(IPAddress.objects.filter(host__net_in=["172.16.0.0/24"]).count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__net_in=["2001:db8::/64"]).count(), 3)
-        self.assertEqual(IPAddress.objects.filter(host__net_in=["10.0.0.0/24", "2001:db8::/64"]).count(), 8)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_in=["10.0.0.0/31", "10.0.0.2/31"]),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.0\.0\.[0-3]/", ip)],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_in=["10.0.0.0/24"]),
+            [instance for ip, instance in self.ips.items() if "10.0.0" in ip],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__net_in=["172.16.0.0/24"]), [])
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_in=["2001:db8::/64"]),
+            [instance for ip, instance in self.ips.items() if "2001:db8::" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_in=["10.0.0.0/24", "2001:db8::/64"]),
+            self.ips.values(),
+        )
 
-        IPAddress.objects.create(address="192.168.0.1/24", vrf=None, tenant=None)
-        self.assertEqual(IPAddress.objects.filter(host__net_in=["192.168.0.0/31"]).count(), 1)
+        extra_ip = IPAddress.objects.create(address="192.168.0.1/24", vrf=None, tenant=None)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__net_in=["192.168.0.0/31"]),
+            [extra_ip],
+        )
+
+        # https://github.com/nautobot/nautobot/issues/3480
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__net_in=["10.0.0.0/24"]),
+            [instance for ip, instance in self.ips.items() if "10.0.0" in ip],
+        )
 
     @skipIf(
         connection.vendor == "postgresql",
         "Not currently supported on postgresql",
     )
     def test_host_exact(self):
-        self.assertEqual(IPAddress.objects.filter(host__exact="10.0.0.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__exact="10.0.0.2").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__exact="10.0.0.10").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__iexact="10.0.0.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__iexact="10.0.0.2").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__iexact="10.0.0.10").count(), 0)
-
-        self.assertEqual(IPAddress.objects.filter(host__exact="2001:db8::1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__exact="2001:db8::5").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__iexact="2001:db8::1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__iexact="2001:db8::5").count(), 0)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__exact="10.0.0.1"),
+            [instance for ip, instance in self.ips.items() if "10.0.0.1/" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__exact="10.0.0.2"),
+            [self.ips["10.0.0.2/24"]],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__exact="10.0.0.10"), [])
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iexact="10.0.0.1"),
+            [instance for ip, instance in self.ips.items() if "10.0.0.1/" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iexact="10.0.0.2"),
+            [self.ips["10.0.0.2/24"]],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__iexact="10.0.0.10"), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__exact="2001:db8::1"),
+            [self.ips["2001:db8::1/64"]],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__exact="2001:db8::5"), [])
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iexact="2001:db8::1"),
+            [self.ips["2001:db8::1/64"]],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__iexact="2001:db8::5"), [])
+
+        # https://github.com/nautobot/nautobot/issues/3480
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__exact="10.0.0.1"),
+            [instance for ip, instance in self.ips.items() if "10.0.0.1/" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__iexact="2001:db8::1"),
+            [self.ips["2001:db8::1/64"]],
+        )
 
     @skipIf(
         connection.vendor == "postgresql",
         "Not currently supported on postgresql",
     )
     def test_host_endswith(self):
-        self.assertEqual(IPAddress.objects.filter(host__endswith="0.2").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__endswith="0.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__endswith="0.50").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__iendswith="0.2").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__iendswith="0.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__iendswith="0.50").count(), 0)
-
-        self.assertEqual(IPAddress.objects.filter(host__endswith="8::1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__endswith="8::5").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__iendswith="8::1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__iendswith="8::5").count(), 0)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__endswith="0.2"),
+            [instance for ip, instance in self.ips.items() if "0.2/" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__endswith="0.1"),
+            [instance for ip, instance in self.ips.items() if "0.1/" in ip],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__endswith="0.50"), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iendswith="0.2"),
+            [instance for ip, instance in self.ips.items() if "0.2/" in ip],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iendswith="0.1"),
+            [instance for ip, instance in self.ips.items() if "0.1/" in ip],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__iendswith="0.50"), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__endswith="8::1"),
+            [instance for ip, instance in self.ips.items() if "8::1/" in ip],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__endswith="8::5"), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iendswith="8::1"),
+            [instance for ip, instance in self.ips.items() if "8::1/" in ip],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__iendswith="8::5"), [])
+
+        # https://github.com/nautobot/nautobot/issues/3480
+        self.assertEqual(IPAddress.objects.select_related("nat_inside").filter(host__endswith="0.1").count(), 2)
+        self.assertEqual(IPAddress.objects.select_related("nat_inside").filter(host__iendswith="8::1").count(), 1)
 
     @skipIf(
         connection.vendor == "postgresql",
         "Not currently supported on postgresql",
     )
     def test_host_startswith(self):
-        self.assertEqual(IPAddress.objects.filter(host__startswith="10.0.0.").count(), 5)
-        self.assertEqual(IPAddress.objects.filter(host__startswith="10.0.0.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__startswith="10.50.0.").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__istartswith="10.0.0.").count(), 5)
-        self.assertEqual(IPAddress.objects.filter(host__istartswith="10.0.0.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__istartswith="10.50.0.").count(), 0)
-
-        self.assertEqual(IPAddress.objects.filter(host__startswith="2001:db8::").count(), 3)
-        self.assertEqual(IPAddress.objects.filter(host__startswith="2001:db8::1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__startswith="2001:db8::5").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__istartswith="2001:db8::").count(), 3)
-        self.assertEqual(IPAddress.objects.filter(host__istartswith="2001:db8::1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__istartswith="2001:db8::5").count(), 0)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__startswith="10.0.0."),
+            [instance for ip, instance in self.ips.items() if ip.startswith("10.0.0.")],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__startswith="10.0.0.1"),
+            [instance for ip, instance in self.ips.items() if ip.startswith("10.0.0.1")],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__startswith="10.50.0."), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__istartswith="10.0.0."),
+            [instance for ip, instance in self.ips.items() if ip.startswith("10.0.0.")],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__istartswith="10.0.0.1"),
+            [instance for ip, instance in self.ips.items() if ip.startswith("10.0.0.1")],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__istartswith="10.50.0."), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__startswith="2001:db8::"),
+            [instance for ip, instance in self.ips.items() if ip.startswith("2001:db8::")],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__startswith="2001:db8::1"),
+            [instance for ip, instance in self.ips.items() if ip.startswith("2001:db8::1")],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__startswith="2001:db8::5"), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__istartswith="2001:db8::"),
+            [instance for ip, instance in self.ips.items() if ip.startswith("2001:db8::")],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__istartswith="2001:db8::1"),
+            [instance for ip, instance in self.ips.items() if ip.startswith("2001:db8::1")],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__istartswith="2001:db8::5"), [])
+
+        # https://github.com/nautobot/nautobot/issues/3480
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__startswith="10.0"),
+            [instance for ip, instance in self.ips.items() if ip.startswith("10.0")],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__istartswith="2001:db8::"),
+            [instance for ip, instance in self.ips.items() if ip.startswith("2001:db8::")],
+        )
 
     @skipIf(
         connection.vendor == "postgresql",
         "Not currently supported on postgresql",
     )
     def test_host_regex(self):
-        self.assertEqual(IPAddress.objects.filter(host__regex=r"10\.(.*)\.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__regex=r"10\.(.*)\.4").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__regex=r"10\.(.*)\.50").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__iregex=r"10\.(.*)\.1").count(), 2)
-        self.assertEqual(IPAddress.objects.filter(host__iregex=r"10\.(.*)\.4").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__iregex=r"10\.(.*)\.50").count(), 0)
-
-        self.assertEqual(IPAddress.objects.filter(host__regex=r"2001(.*)1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__regex=r"2001(.*)5").count(), 0)
-        self.assertEqual(IPAddress.objects.filter(host__iregex=r"2001(.*)1").count(), 1)
-        self.assertEqual(IPAddress.objects.filter(host__iregex=r"2001(.*)5").count(), 0)
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__regex=r"10\.(.*)\.1"),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.(.*)\.1", ip)],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__regex=r"10\.(.*)\.4"),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.(.*)\.4", ip)],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__regex=r"10\.(.*)\.50"), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iregex=r"10\.(.*)\.1"),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.(.*)\.1", ip)],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iregex=r"10\.(.*)\.4"),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.(.*)\.4", ip)],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__iregex=r"10\.(.*)\.50"), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__regex=r"2001(.*)1"),
+            [instance for ip, instance in self.ips.items() if re.match(r"2001(.*)1", ip)],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__regex=r"2001(.*)5"), [])
+
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.filter(host__iregex=r"2001(.*)1"),
+            [instance for ip, instance in self.ips.items() if re.match(r"2001(.*)1", ip)],
+        )
+        self.assertQuerysetEqual(IPAddress.objects.filter(host__iregex=r"2001(.*)5"), [])
+
+        # https://github.com/nautobot/nautobot/issues/3480
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__regex=r"10\.(.*)\.1"),
+            [instance for ip, instance in self.ips.items() if re.match(r"10\.(.*)\.1", ip)],
+        )
+        self.assertQuerysetEqualAndNotEmpty(
+            IPAddress.objects.select_related("nat_inside").filter(host__iregex=r"2001(.*)1"),
+            [instance for ip, instance in self.ips.items() if re.match(r"2001(.*)1", ip)],
+        )
 
 
 class PrefixQuerysetTestCase(TestCase):