We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nautobot/ipam/lookups.py`
Content:
```
1 import netaddr
2 from django.db import NotSupportedError
3 from django.db import connection as _connection
4 from django.db.models import Lookup, lookups
5 
6 
7 def _mysql_varbin_to_broadcast():
8     return "HEX(broadcast)"
9 
10 
11 def _mysql_varbin_to_hex(lhs):
12     return f"HEX({lhs})"
13 
14 
15 def _mysql_varbin_to_network():
16     return "HEX(network)"
17 
18 
19 def _postgresql_varbin_to_broadcast(length):
20     return f"right(broadcast::text, -1)::varbit::bit({length})"
21 
22 
23 def _postgresql_varbin_to_integer(lhs, length):
24     return f"right({lhs}::text, -1)::varbit::bit({length})"
25 
26 
27 def _postgresql_varbin_to_network(lhs, length):
28     # convert to bitstring, 0 out everything larger than prefix_length
29     return f"lpad(right({lhs}::text, -1)::varbit::text, prefix_length, '0')::bit({length})"
30 
31 
32 def py_to_hex(ip, length):
33     return str(hex(int(ip)))[2:].zfill(int(length / 4))
34 
35 
36 def get_ip_info(field_name, ip_str):
37     """Function to set all details about an IP, that may be needed."""
38     ip_details = IPDetails()
39     ip = netaddr.IPNetwork(ip_str)
40     if field_name == "network":
41         ip_details.addr = ip.network
42     elif field_name == "host":
43         ip_details.addr = ip.ip
44     ip_details.ip = ip
45     ip_details.prefix = ip.prefixlen
46     ip_details.length = ip_details.to_len[ip.version]
47 
48     if _connection.vendor == "mysql":
49         ip_details.rhs = py_to_hex(ip.ip, ip_details.length)
50         ip_details.net_addr = f"'{py_to_hex(ip.network, ip_details.length)}'"
51         ip_details.bcast_addr = f"'{py_to_hex(ip[-1], ip_details.length)}'"
52         ip_details.q_net = _mysql_varbin_to_network()
53         ip_details.q_bcast = _mysql_varbin_to_broadcast()
54         ip_details.q_ip = _mysql_varbin_to_hex(field_name)
55 
56     elif _connection.vendor == "postgresql":
57         ip_details.rhs = bin(int(ip_details.addr))[2:].zfill(ip_details.length)
58         ip_details.addr_str = f"B'{bin(int(ip_details.addr))[2:].zfill(ip_details.length)}'"
59         ip_details.net_addr = f"B'{bin(int(ip.network))[2:].zfill(ip_details.length)}'"
60         ip_details.bcast_addr = f"B'{bin(int(ip[-1]))[2:].zfill(ip_details.length)}'"
61         ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length)
62         ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length)
63         ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length)
64 
65     return ip_details
66 
67 
68 class IPDetails:
69     """Class for setting up all details about an IP they may be needed"""
70 
71     net = None
72     addr = None
73     ip = None
74     prefix = None
75     length = None
76     addr_str = None
77     rhs = None
78     net_addr = None
79     bcast_addr = None
80     q_net = None
81     q_bcast = None
82     q_ip = None
83     to_len = {4: 32, 6: 128}
84 
85 
86 class StringMatchMixin:
87     def process_lhs(self, qn, connection, lhs=None):
88         lhs = lhs or self.lhs
89         lhs_string, lhs_params = qn.compile(lhs)
90         if connection.vendor == "postgresql":
91             raise NotSupportedError("Lookup not supported on postgresql.")
92         return f"INET6_NTOA({lhs_string})", lhs_params
93 
94 
95 class Exact(StringMatchMixin, lookups.Exact):
96     pass
97 
98 
99 class IExact(StringMatchMixin, lookups.IExact):
100     pass
101 
102 
103 class EndsWith(StringMatchMixin, lookups.EndsWith):
104     pass
105 
106 
107 class IEndsWith(StringMatchMixin, lookups.IEndsWith):
108     pass
109 
110 
111 class StartsWith(StringMatchMixin, lookups.StartsWith):
112     pass
113 
114 
115 class IStartsWith(StringMatchMixin, lookups.IStartsWith):
116     pass
117 
118 
119 class Regex(StringMatchMixin, lookups.Regex):
120     pass
121 
122 
123 class IRegex(StringMatchMixin, lookups.IRegex):
124     pass
125 
126 
127 class NetworkFieldMixin:
128     def get_prep_lookup(self):
129         field_name = self.lhs.field.name
130         if field_name not in ["host", "network"]:
131             raise NotSupportedError(f"Lookup only provided on the host and network fields, not {field_name}.")
132         if field_name == "network" and self.lookup_name in ["net_host", "net_host_contained", "net_in"]:
133             raise NotSupportedError(f"Lookup for network field does not include the {self.lookup_name} lookup.")
134         if field_name == "host" and self.lookup_name not in ["net_host", "net_host_contained", "net_in"]:
135             raise NotSupportedError(f"Lookup for host field does not include the {self.lookup_name} lookup.")
136         self.ip = get_ip_info(field_name, self.rhs)
137         return str(self.ip.ip)
138 
139     def process_rhs(self, qn, connection):
140         sql, params = super().process_rhs(qn, connection)
141         params[0] = self.ip.rhs
142         return sql, params
143 
144 
145 class NetEquals(NetworkFieldMixin, Lookup):
146     lookup_name = "net_equals"
147 
148     def as_sql(self, qn, connection):
149         _, lhs_params = self.process_lhs(qn, connection)
150         rhs, rhs_params = self.process_rhs(qn, connection)
151         query = f"prefix_length = {self.ip.prefix} AND {rhs} = {self.ip.q_ip}"
152         return query, lhs_params + rhs_params
153 
154 
155 class NetContainsOrEquals(NetworkFieldMixin, Lookup):
156     lookup_name = "net_contains_or_equals"
157 
158     def as_sql(self, qn, connection):
159         _, lhs_params = self.process_lhs(qn, connection)
160         rhs, rhs_params = self.process_rhs(qn, connection)
161         query = f"prefix_length <= {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
162         return query, lhs_params + rhs_params
163 
164 
165 class NetContains(NetworkFieldMixin, Lookup):
166     lookup_name = "net_contains"
167 
168     def as_sql(self, qn, connection):
169         _, lhs_params = self.process_lhs(qn, connection)
170         rhs, rhs_params = self.process_rhs(qn, connection)
171         query = f"prefix_length < {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
172         return query, lhs_params + rhs_params
173 
174 
175 class NetContainedOrEqual(NetworkFieldMixin, Lookup):
176     lookup_name = "net_contained_or_equal"
177 
178     def as_sql(self, qn, connection):
179         _, lhs_params = self.process_lhs(qn, connection)
180         rhs, rhs_params = self.process_rhs(qn, connection)
181         query = f"prefix_length >= {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
182         return query, lhs_params + rhs_params
183 
184 
185 class NetContained(NetworkFieldMixin, Lookup):
186     lookup_name = "net_contained"
187 
188     def as_sql(self, qn, connection):
189         _, lhs_params = self.process_lhs(qn, connection)
190         rhs, rhs_params = self.process_rhs(qn, connection)
191         query = f"prefix_length > {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
192         return query, lhs_params + rhs_params
193 
194 
195 class NetHost(Lookup):
196     lookup_name = "net_host"
197 
198     def get_prep_lookup(self):
199         field_name = self.lhs.field.name
200         if field_name != "host":
201             raise NotSupportedError(f"Lookup only provided on the host fields, not {field_name}.")
202         self.ip = get_ip_info(field_name, self.rhs)
203         return str(self.ip.ip)
204 
205     def process_rhs(self, qn, connection):
206         sql, params = super().process_rhs(qn, connection)
207         params[0] = self.ip.rhs
208         return sql, params
209 
210     def process_lhs(self, qn, connection, lhs=None):
211         lhs = lhs or self.lhs
212         _, lhs_params = qn.compile(lhs)
213         return self.ip.q_ip, lhs_params
214 
215     def as_sql(self, qn, connection):
216         lhs, lhs_params = self.process_lhs(qn, connection)
217         rhs, rhs_params = self.process_rhs(qn, connection)
218         return f"{lhs} = {rhs}", lhs_params + rhs_params
219 
220 
221 class NetIn(Lookup):
222     lookup_name = "net_in"
223 
224     def get_prep_lookup(self):
225         field_name = self.lhs.field.name
226         if field_name != "host":
227             raise NotSupportedError(f"Lookup only provided on the host field, not {field_name}.")
228         self.ips = []
229         for _ip in self.rhs:
230             ip = get_ip_info(field_name, _ip)
231             self.ips.append(ip)
232         # This is to satisfy an issue with django cacheops, specifically this line:
233         # https://github.com/Suor/django-cacheops/blob/a5ed1ac28c7259f5ad005e596cc045d1d61e2c51/cacheops/query.py#L175
234         # Without 1, and one 1 value as %s, will result in stacktrace. A non-impacting condition is added to the query
235         if _connection.vendor == "mysql":
236             self.query_starter = "'1' NOT IN %s AND "
237         elif _connection.vendor == "postgresql":
238             self.query_starter = "'1' != ANY(%s) AND "
239         return self.rhs
240 
241     def as_sql(self, qn, connection):
242         _, lhs_params = self.process_lhs(qn, connection)
243         _, rhs_params = self.process_rhs(qn, connection)
244         query = self.query_starter
245         query += "OR ".join(f"{ip.q_ip} BETWEEN {ip.net_addr} AND {ip.bcast_addr} " for ip in self.ips)
246         return query, lhs_params + rhs_params
247 
248 
249 class NetHostContained(NetworkFieldMixin, Lookup):
250     lookup_name = "net_host_contained"
251 
252     def as_sql(self, qn, connection):
253         _, lhs_params = self.process_lhs(qn, connection)
254         rhs, rhs_params = self.process_rhs(qn, connection)
255         query = f"{self.ip.q_ip} BETWEEN {rhs} AND {self.ip.bcast_addr}"
256         return query, lhs_params + rhs_params
257 
258 
259 class NetFamily(Lookup):
260     lookup_name = "family"
261 
262     def get_prep_lookup(self):
263         if self.rhs not in [4, 6]:
264             raise NotSupportedError("Family must be either integer of value 4 or 6")
265         if self.rhs == 6:
266             self.rhs = 16
267         return self.rhs
268 
269     def process_lhs(self, qn, connection, lhs=None):
270         lhs = lhs or self.lhs
271         lhs_string, lhs_params = qn.compile(lhs)
272         return f"LENGTH({lhs_string})", lhs_params
273 
274     def as_sql(self, qn, connection):
275         lhs, lhs_params = self.process_lhs(qn, connection)
276         rhs, rhs_params = self.process_rhs(qn, connection)
277         return f"{lhs} = {rhs}", lhs_params + rhs_params
278
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
     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 @@
     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 @@
     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 @@
         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 @@
         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 @@
             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 @@
         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 @@
             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

{"golden_diff": "diff --git a/nautobot/ipam/lookups.py b/nautobot/ipam/lookups.py\n--- a/nautobot/ipam/lookups.py\n+++ b/nautobot/ipam/lookups.py\n@@ -8,7 +8,9 @@\n     return \"HEX(broadcast)\"\n \n \n-def _mysql_varbin_to_hex(lhs):\n+def _mysql_varbin_to_hex(lhs, alias=None):\n+    if alias:\n+        return f\"HEX({alias}.{lhs})\"\n     return f\"HEX({lhs})\"\n \n \n@@ -20,7 +22,9 @@\n     return f\"right(broadcast::text, -1)::varbit::bit({length})\"\n \n \n-def _postgresql_varbin_to_integer(lhs, length):\n+def _postgresql_varbin_to_integer(lhs, length, alias=None):\n+    if alias:\n+        return f\"right({alias}.{lhs}::text, -1)::varbit::bit({length})\"\n     return f\"right({lhs}::text, -1)::varbit::bit({length})\"\n \n \n@@ -33,7 +37,7 @@\n     return str(hex(int(ip)))[2:].zfill(int(length / 4))\n \n \n-def get_ip_info(field_name, ip_str):\n+def get_ip_info(field_name, ip_str, alias=None):\n     \"\"\"Function to set all details about an IP, that may be needed.\"\"\"\n     ip_details = IPDetails()\n     ip = netaddr.IPNetwork(ip_str)\n@@ -51,7 +55,7 @@\n         ip_details.bcast_addr = f\"'{py_to_hex(ip[-1], ip_details.length)}'\"\n         ip_details.q_net = _mysql_varbin_to_network()\n         ip_details.q_bcast = _mysql_varbin_to_broadcast()\n-        ip_details.q_ip = _mysql_varbin_to_hex(field_name)\n+        ip_details.q_ip = _mysql_varbin_to_hex(field_name, alias=alias)\n \n     elif _connection.vendor == \"postgresql\":\n         ip_details.rhs = bin(int(ip_details.addr))[2:].zfill(ip_details.length)\n@@ -60,7 +64,7 @@\n         ip_details.bcast_addr = f\"B'{bin(int(ip[-1]))[2:].zfill(ip_details.length)}'\"\n         ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length)\n         ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length)\n-        ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length)\n+        ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length, alias=alias)\n \n     return ip_details\n \n@@ -133,7 +137,7 @@\n             raise NotSupportedError(f\"Lookup for network field does not include the {self.lookup_name} lookup.\")\n         if field_name == \"host\" and self.lookup_name not in [\"net_host\", \"net_host_contained\", \"net_in\"]:\n             raise NotSupportedError(f\"Lookup for host field does not include the {self.lookup_name} lookup.\")\n-        self.ip = get_ip_info(field_name, self.rhs)\n+        self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)\n         return str(self.ip.ip)\n \n     def process_rhs(self, qn, connection):\n@@ -199,7 +203,7 @@\n         field_name = self.lhs.field.name\n         if field_name != \"host\":\n             raise NotSupportedError(f\"Lookup only provided on the host fields, not {field_name}.\")\n-        self.ip = get_ip_info(field_name, self.rhs)\n+        self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)\n         return str(self.ip.ip)\n \n     def process_rhs(self, qn, connection):\n@@ -227,7 +231,7 @@\n             raise NotSupportedError(f\"Lookup only provided on the host field, not {field_name}.\")\n         self.ips = []\n         for _ip in self.rhs:\n-            ip = get_ip_info(field_name, _ip)\n+            ip = get_ip_info(field_name, _ip, alias=self.lhs.alias)\n             self.ips.append(ip)\n         # This is to satisfy an issue with django cacheops, specifically this line:\n         # https://github.com/Suor/django-cacheops/blob/a5ed1ac28c7259f5ad005e596cc045d1d61e2c51/cacheops/query.py#L175\n", "issue": "Pre-Release Github Action Workflow Doesn't Publish Python Version Specific Builds\n<!--\r\n    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.\r\n\r\n    This form is only for reporting reproducible bugs. If you need assistance\r\n    with Nautobot installation, or if you have a general question, please start a\r\n    discussion instead: https://github.com/nautobot/nautobot/discussions\r\n\r\n    Please describe the environment in which you are running Nautobot. Be sure\r\n    that you are running an unmodified instance of the latest stable release\r\n    before submitting a bug report, and that any plugins have been disabled.\r\n-->\r\n### Environment\r\n* Nautobot version (Docker tag too if applicable): 2.0.0-alpha.1\r\n* Python version: 3.7-3.10\r\n* Database platform, version: NA\r\n* Middleware(s): NA\r\n\r\n<!--\r\n    Describe in detail the exact steps that someone else can take to reproduce\r\n    this bug using the current stable release of Nautobot. Begin with the\r\n    creation of any necessary database objects and call out every operation\r\n    being performed explicitly. If reporting a bug in the REST API, be sure to\r\n    reconstruct the raw HTTP request(s) being made: Don't rely on a client\r\n    library such as pynautobot.\r\n-->\r\n### Steps to Reproduce\r\n1. Create Pre-Release Release named `2.0.0-alpha.1`\r\n2. Wait for CI to Run\r\n\r\n<!-- What did you expect to happen? -->\r\n### Expected Behavior\r\n\r\nDocker 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.\r\n\r\n\r\n\r\n<!-- What happened instead? -->\r\n### Observed Behavior\r\n\r\nWhichever 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\n", "before_files": [{"content": "import netaddr\nfrom django.db import NotSupportedError\nfrom django.db import connection as _connection\nfrom django.db.models import Lookup, lookups\n\n\ndef _mysql_varbin_to_broadcast():\n    return \"HEX(broadcast)\"\n\n\ndef _mysql_varbin_to_hex(lhs):\n    return f\"HEX({lhs})\"\n\n\ndef _mysql_varbin_to_network():\n    return \"HEX(network)\"\n\n\ndef _postgresql_varbin_to_broadcast(length):\n    return f\"right(broadcast::text, -1)::varbit::bit({length})\"\n\n\ndef _postgresql_varbin_to_integer(lhs, length):\n    return f\"right({lhs}::text, -1)::varbit::bit({length})\"\n\n\ndef _postgresql_varbin_to_network(lhs, length):\n    # convert to bitstring, 0 out everything larger than prefix_length\n    return f\"lpad(right({lhs}::text, -1)::varbit::text, prefix_length, '0')::bit({length})\"\n\n\ndef py_to_hex(ip, length):\n    return str(hex(int(ip)))[2:].zfill(int(length / 4))\n\n\ndef get_ip_info(field_name, ip_str):\n    \"\"\"Function to set all details about an IP, that may be needed.\"\"\"\n    ip_details = IPDetails()\n    ip = netaddr.IPNetwork(ip_str)\n    if field_name == \"network\":\n        ip_details.addr = ip.network\n    elif field_name == \"host\":\n        ip_details.addr = ip.ip\n    ip_details.ip = ip\n    ip_details.prefix = ip.prefixlen\n    ip_details.length = ip_details.to_len[ip.version]\n\n    if _connection.vendor == \"mysql\":\n        ip_details.rhs = py_to_hex(ip.ip, ip_details.length)\n        ip_details.net_addr = f\"'{py_to_hex(ip.network, ip_details.length)}'\"\n        ip_details.bcast_addr = f\"'{py_to_hex(ip[-1], ip_details.length)}'\"\n        ip_details.q_net = _mysql_varbin_to_network()\n        ip_details.q_bcast = _mysql_varbin_to_broadcast()\n        ip_details.q_ip = _mysql_varbin_to_hex(field_name)\n\n    elif _connection.vendor == \"postgresql\":\n        ip_details.rhs = bin(int(ip_details.addr))[2:].zfill(ip_details.length)\n        ip_details.addr_str = f\"B'{bin(int(ip_details.addr))[2:].zfill(ip_details.length)}'\"\n        ip_details.net_addr = f\"B'{bin(int(ip.network))[2:].zfill(ip_details.length)}'\"\n        ip_details.bcast_addr = f\"B'{bin(int(ip[-1]))[2:].zfill(ip_details.length)}'\"\n        ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length)\n        ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length)\n        ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length)\n\n    return ip_details\n\n\nclass IPDetails:\n    \"\"\"Class for setting up all details about an IP they may be needed\"\"\"\n\n    net = None\n    addr = None\n    ip = None\n    prefix = None\n    length = None\n    addr_str = None\n    rhs = None\n    net_addr = None\n    bcast_addr = None\n    q_net = None\n    q_bcast = None\n    q_ip = None\n    to_len = {4: 32, 6: 128}\n\n\nclass StringMatchMixin:\n    def process_lhs(self, qn, connection, lhs=None):\n        lhs = lhs or self.lhs\n        lhs_string, lhs_params = qn.compile(lhs)\n        if connection.vendor == \"postgresql\":\n            raise NotSupportedError(\"Lookup not supported on postgresql.\")\n        return f\"INET6_NTOA({lhs_string})\", lhs_params\n\n\nclass Exact(StringMatchMixin, lookups.Exact):\n    pass\n\n\nclass IExact(StringMatchMixin, lookups.IExact):\n    pass\n\n\nclass EndsWith(StringMatchMixin, lookups.EndsWith):\n    pass\n\n\nclass IEndsWith(StringMatchMixin, lookups.IEndsWith):\n    pass\n\n\nclass StartsWith(StringMatchMixin, lookups.StartsWith):\n    pass\n\n\nclass IStartsWith(StringMatchMixin, lookups.IStartsWith):\n    pass\n\n\nclass Regex(StringMatchMixin, lookups.Regex):\n    pass\n\n\nclass IRegex(StringMatchMixin, lookups.IRegex):\n    pass\n\n\nclass NetworkFieldMixin:\n    def get_prep_lookup(self):\n        field_name = self.lhs.field.name\n        if field_name not in [\"host\", \"network\"]:\n            raise NotSupportedError(f\"Lookup only provided on the host and network fields, not {field_name}.\")\n        if field_name == \"network\" and self.lookup_name in [\"net_host\", \"net_host_contained\", \"net_in\"]:\n            raise NotSupportedError(f\"Lookup for network field does not include the {self.lookup_name} lookup.\")\n        if field_name == \"host\" and self.lookup_name not in [\"net_host\", \"net_host_contained\", \"net_in\"]:\n            raise NotSupportedError(f\"Lookup for host field does not include the {self.lookup_name} lookup.\")\n        self.ip = get_ip_info(field_name, self.rhs)\n        return str(self.ip.ip)\n\n    def process_rhs(self, qn, connection):\n        sql, params = super().process_rhs(qn, connection)\n        params[0] = self.ip.rhs\n        return sql, params\n\n\nclass NetEquals(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_equals\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length = {self.ip.prefix} AND {rhs} = {self.ip.q_ip}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetContainsOrEquals(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_contains_or_equals\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length <= {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetContains(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_contains\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length < {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetContainedOrEqual(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_contained_or_equal\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length >= {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetContained(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_contained\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length > {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetHost(Lookup):\n    lookup_name = \"net_host\"\n\n    def get_prep_lookup(self):\n        field_name = self.lhs.field.name\n        if field_name != \"host\":\n            raise NotSupportedError(f\"Lookup only provided on the host fields, not {field_name}.\")\n        self.ip = get_ip_info(field_name, self.rhs)\n        return str(self.ip.ip)\n\n    def process_rhs(self, qn, connection):\n        sql, params = super().process_rhs(qn, connection)\n        params[0] = self.ip.rhs\n        return sql, params\n\n    def process_lhs(self, qn, connection, lhs=None):\n        lhs = lhs or self.lhs\n        _, lhs_params = qn.compile(lhs)\n        return self.ip.q_ip, lhs_params\n\n    def as_sql(self, qn, connection):\n        lhs, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        return f\"{lhs} = {rhs}\", lhs_params + rhs_params\n\n\nclass NetIn(Lookup):\n    lookup_name = \"net_in\"\n\n    def get_prep_lookup(self):\n        field_name = self.lhs.field.name\n        if field_name != \"host\":\n            raise NotSupportedError(f\"Lookup only provided on the host field, not {field_name}.\")\n        self.ips = []\n        for _ip in self.rhs:\n            ip = get_ip_info(field_name, _ip)\n            self.ips.append(ip)\n        # This is to satisfy an issue with django cacheops, specifically this line:\n        # https://github.com/Suor/django-cacheops/blob/a5ed1ac28c7259f5ad005e596cc045d1d61e2c51/cacheops/query.py#L175\n        # Without 1, and one 1 value as %s, will result in stacktrace. A non-impacting condition is added to the query\n        if _connection.vendor == \"mysql\":\n            self.query_starter = \"'1' NOT IN %s AND \"\n        elif _connection.vendor == \"postgresql\":\n            self.query_starter = \"'1' != ANY(%s) AND \"\n        return self.rhs\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        _, rhs_params = self.process_rhs(qn, connection)\n        query = self.query_starter\n        query += \"OR \".join(f\"{ip.q_ip} BETWEEN {ip.net_addr} AND {ip.bcast_addr} \" for ip in self.ips)\n        return query, lhs_params + rhs_params\n\n\nclass NetHostContained(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_host_contained\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"{self.ip.q_ip} BETWEEN {rhs} AND {self.ip.bcast_addr}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetFamily(Lookup):\n    lookup_name = \"family\"\n\n    def get_prep_lookup(self):\n        if self.rhs not in [4, 6]:\n            raise NotSupportedError(\"Family must be either integer of value 4 or 6\")\n        if self.rhs == 6:\n            self.rhs = 16\n        return self.rhs\n\n    def process_lhs(self, qn, connection, lhs=None):\n        lhs = lhs or self.lhs\n        lhs_string, lhs_params = qn.compile(lhs)\n        return f\"LENGTH({lhs_string})\", lhs_params\n\n    def as_sql(self, qn, connection):\n        lhs, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        return f\"{lhs} = {rhs}\", lhs_params + rhs_params\n", "path": "nautobot/ipam/lookups.py"}], "after_files": [{"content": "import netaddr\nfrom django.db import NotSupportedError\nfrom django.db import connection as _connection\nfrom django.db.models import Lookup, lookups\n\n\ndef _mysql_varbin_to_broadcast():\n    return \"HEX(broadcast)\"\n\n\ndef _mysql_varbin_to_hex(lhs, alias=None):\n    if alias:\n        return f\"HEX({alias}.{lhs})\"\n    return f\"HEX({lhs})\"\n\n\ndef _mysql_varbin_to_network():\n    return \"HEX(network)\"\n\n\ndef _postgresql_varbin_to_broadcast(length):\n    return f\"right(broadcast::text, -1)::varbit::bit({length})\"\n\n\ndef _postgresql_varbin_to_integer(lhs, length, alias=None):\n    if alias:\n        return f\"right({alias}.{lhs}::text, -1)::varbit::bit({length})\"\n    return f\"right({lhs}::text, -1)::varbit::bit({length})\"\n\n\ndef _postgresql_varbin_to_network(lhs, length):\n    # convert to bitstring, 0 out everything larger than prefix_length\n    return f\"lpad(right({lhs}::text, -1)::varbit::text, prefix_length, '0')::bit({length})\"\n\n\ndef py_to_hex(ip, length):\n    return str(hex(int(ip)))[2:].zfill(int(length / 4))\n\n\ndef get_ip_info(field_name, ip_str, alias=None):\n    \"\"\"Function to set all details about an IP, that may be needed.\"\"\"\n    ip_details = IPDetails()\n    ip = netaddr.IPNetwork(ip_str)\n    if field_name == \"network\":\n        ip_details.addr = ip.network\n    elif field_name == \"host\":\n        ip_details.addr = ip.ip\n    ip_details.ip = ip\n    ip_details.prefix = ip.prefixlen\n    ip_details.length = ip_details.to_len[ip.version]\n\n    if _connection.vendor == \"mysql\":\n        ip_details.rhs = py_to_hex(ip.ip, ip_details.length)\n        ip_details.net_addr = f\"'{py_to_hex(ip.network, ip_details.length)}'\"\n        ip_details.bcast_addr = f\"'{py_to_hex(ip[-1], ip_details.length)}'\"\n        ip_details.q_net = _mysql_varbin_to_network()\n        ip_details.q_bcast = _mysql_varbin_to_broadcast()\n        ip_details.q_ip = _mysql_varbin_to_hex(field_name, alias=alias)\n\n    elif _connection.vendor == \"postgresql\":\n        ip_details.rhs = bin(int(ip_details.addr))[2:].zfill(ip_details.length)\n        ip_details.addr_str = f\"B'{bin(int(ip_details.addr))[2:].zfill(ip_details.length)}'\"\n        ip_details.net_addr = f\"B'{bin(int(ip.network))[2:].zfill(ip_details.length)}'\"\n        ip_details.bcast_addr = f\"B'{bin(int(ip[-1]))[2:].zfill(ip_details.length)}'\"\n        ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length)\n        ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length)\n        ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length, alias=alias)\n\n    return ip_details\n\n\nclass IPDetails:\n    \"\"\"Class for setting up all details about an IP they may be needed\"\"\"\n\n    net = None\n    addr = None\n    ip = None\n    prefix = None\n    length = None\n    addr_str = None\n    rhs = None\n    net_addr = None\n    bcast_addr = None\n    q_net = None\n    q_bcast = None\n    q_ip = None\n    to_len = {4: 32, 6: 128}\n\n\nclass StringMatchMixin:\n    def process_lhs(self, qn, connection, lhs=None):\n        lhs = lhs or self.lhs\n        lhs_string, lhs_params = qn.compile(lhs)\n        if connection.vendor == \"postgresql\":\n            raise NotSupportedError(\"Lookup not supported on postgresql.\")\n        return f\"INET6_NTOA({lhs_string})\", lhs_params\n\n\nclass Exact(StringMatchMixin, lookups.Exact):\n    pass\n\n\nclass IExact(StringMatchMixin, lookups.IExact):\n    pass\n\n\nclass EndsWith(StringMatchMixin, lookups.EndsWith):\n    pass\n\n\nclass IEndsWith(StringMatchMixin, lookups.IEndsWith):\n    pass\n\n\nclass StartsWith(StringMatchMixin, lookups.StartsWith):\n    pass\n\n\nclass IStartsWith(StringMatchMixin, lookups.IStartsWith):\n    pass\n\n\nclass Regex(StringMatchMixin, lookups.Regex):\n    pass\n\n\nclass IRegex(StringMatchMixin, lookups.IRegex):\n    pass\n\n\nclass NetworkFieldMixin:\n    def get_prep_lookup(self):\n        field_name = self.lhs.field.name\n        if field_name not in [\"host\", \"network\"]:\n            raise NotSupportedError(f\"Lookup only provided on the host and network fields, not {field_name}.\")\n        if field_name == \"network\" and self.lookup_name in [\"net_host\", \"net_host_contained\", \"net_in\"]:\n            raise NotSupportedError(f\"Lookup for network field does not include the {self.lookup_name} lookup.\")\n        if field_name == \"host\" and self.lookup_name not in [\"net_host\", \"net_host_contained\", \"net_in\"]:\n            raise NotSupportedError(f\"Lookup for host field does not include the {self.lookup_name} lookup.\")\n        self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)\n        return str(self.ip.ip)\n\n    def process_rhs(self, qn, connection):\n        sql, params = super().process_rhs(qn, connection)\n        params[0] = self.ip.rhs\n        return sql, params\n\n\nclass NetEquals(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_equals\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length = {self.ip.prefix} AND {rhs} = {self.ip.q_ip}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetContainsOrEquals(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_contains_or_equals\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length <= {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetContains(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_contains\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length < {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetContainedOrEqual(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_contained_or_equal\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length >= {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetContained(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_contained\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"prefix_length > {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetHost(Lookup):\n    lookup_name = \"net_host\"\n\n    def get_prep_lookup(self):\n        field_name = self.lhs.field.name\n        if field_name != \"host\":\n            raise NotSupportedError(f\"Lookup only provided on the host fields, not {field_name}.\")\n        self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)\n        return str(self.ip.ip)\n\n    def process_rhs(self, qn, connection):\n        sql, params = super().process_rhs(qn, connection)\n        params[0] = self.ip.rhs\n        return sql, params\n\n    def process_lhs(self, qn, connection, lhs=None):\n        lhs = lhs or self.lhs\n        _, lhs_params = qn.compile(lhs)\n        return self.ip.q_ip, lhs_params\n\n    def as_sql(self, qn, connection):\n        lhs, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        return f\"{lhs} = {rhs}\", lhs_params + rhs_params\n\n\nclass NetIn(Lookup):\n    lookup_name = \"net_in\"\n\n    def get_prep_lookup(self):\n        field_name = self.lhs.field.name\n        if field_name != \"host\":\n            raise NotSupportedError(f\"Lookup only provided on the host field, not {field_name}.\")\n        self.ips = []\n        for _ip in self.rhs:\n            ip = get_ip_info(field_name, _ip, alias=self.lhs.alias)\n            self.ips.append(ip)\n        # This is to satisfy an issue with django cacheops, specifically this line:\n        # https://github.com/Suor/django-cacheops/blob/a5ed1ac28c7259f5ad005e596cc045d1d61e2c51/cacheops/query.py#L175\n        # Without 1, and one 1 value as %s, will result in stacktrace. A non-impacting condition is added to the query\n        if _connection.vendor == \"mysql\":\n            self.query_starter = \"'1' NOT IN %s AND \"\n        elif _connection.vendor == \"postgresql\":\n            self.query_starter = \"'1' != ANY(%s) AND \"\n        return self.rhs\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        _, rhs_params = self.process_rhs(qn, connection)\n        query = self.query_starter\n        query += \"OR \".join(f\"{ip.q_ip} BETWEEN {ip.net_addr} AND {ip.bcast_addr} \" for ip in self.ips)\n        return query, lhs_params + rhs_params\n\n\nclass NetHostContained(NetworkFieldMixin, Lookup):\n    lookup_name = \"net_host_contained\"\n\n    def as_sql(self, qn, connection):\n        _, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        query = f\"{self.ip.q_ip} BETWEEN {rhs} AND {self.ip.bcast_addr}\"\n        return query, lhs_params + rhs_params\n\n\nclass NetFamily(Lookup):\n    lookup_name = \"family\"\n\n    def get_prep_lookup(self):\n        if self.rhs not in [4, 6]:\n            raise NotSupportedError(\"Family must be either integer of value 4 or 6\")\n        if self.rhs == 6:\n            self.rhs = 16\n        return self.rhs\n\n    def process_lhs(self, qn, connection, lhs=None):\n        lhs = lhs or self.lhs\n        lhs_string, lhs_params = qn.compile(lhs)\n        return f\"LENGTH({lhs_string})\", lhs_params\n\n    def as_sql(self, qn, connection):\n        lhs, lhs_params = self.process_lhs(qn, connection)\n        rhs, rhs_params = self.process_rhs(qn, connection)\n        return f\"{lhs} = {rhs}\", lhs_params + rhs_params\n", "path": "nautobot/ipam/lookups.py"}]}