Repository: nautobot/nautobot

Issue:
Error when searching for IP without prefix
<!--
    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
* Python version: 3.8.5
* Nautobot version: 1.0.0b3

<!--
    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.
-->

When I use search field in the top right most corner (in navbar nex to user) and I search only by IP without prefix, I get the following error:
![image](https://user-images.githubusercontent.com/26114847/114363064-5470ee00-9b78-11eb-8235-1654a36985da.png)

Full trace:
```
Traceback (most recent call last):
  File "/opt/nautobot/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/opt/nautobot/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/nautobot/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/opt/nautobot/lib/python3.8/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/opt/nautobot/lib/python3.8/site-packages/nautobot/core/views/__init__.py", line 159, in get
    filtered_queryset = filterset({"q": form.cleaned_data["q"]}, queryset=queryset).qs
  File "/opt/nautobot/lib/python3.8/site-packages/django_filters/filterset.py", line 243, in qs
    qs = self.filter_queryset(qs)
  File "/opt/nautobot/lib/python3.8/site-packages/django_filters/filterset.py", line 230, in filter_queryset
    queryset = self.filters[name].filter(queryset, value)
  File "/opt/nautobot/lib/python3.8/site-packages/django_filters/filters.py", line 779, in __call__
    return self.method(qs, self.f.field_name, value)
  File "/opt/nautobot/lib/python3.8/site-packages/nautobot/ipam/filters.py", line 183, in search
    broadcast__gte=bytes(query.broadcast),

Exception Type: TypeError at /search/
Exception Value: cannot convert 'NoneType' object to bytes
```

Searching with prefix works:
![image](https://user-images.githubusercontent.com/26114847/114363397-a0bc2e00-9b78-11eb-8d2d-bb5fc2e13ce9.png)


### Steps to Reproduce
1. Search IP without prefix in the navbar box
![image](https://user-images.githubusercontent.com/26114847/114363465-b7628500-9b78-11eb-9cb7-83e1da8c4409.png)

<!-- What did you expect to happen? -->
### Expected Behavior
IP searched, results showed

<!-- What happened instead? -->
### Observed Behavior
TypeError


Discussion:

Resolving patch:
diff --git a/nautobot/ipam/filters.py b/nautobot/ipam/filters.py
--- a/nautobot/ipam/filters.py
+++ b/nautobot/ipam/filters.py
@@ -180,7 +180,7 @@ def search(self, queryset, name, value):
             qs_filter |= Q(
                 prefix_length__lte=query.prefixlen,
                 network__lte=bytes(query.network),
-                broadcast__gte=bytes(query.broadcast),
+                broadcast__gte=bytes(query.broadcast if query.broadcast else query.network),
             )
         except (AddrFormatError, ValueError):
             pass
@@ -333,7 +333,7 @@ def search(self, queryset, name, value):
             qs_filter |= Q(
                 prefix_length__lte=query.prefixlen,
                 network__lte=bytes(query.network),
-                broadcast__gte=bytes(query.broadcast),
+                broadcast__gte=bytes(query.broadcast if query.broadcast else query.network),
             )
         except (AddrFormatError, ValueError):
             pass
diff --git a/nautobot/ipam/forms.py b/nautobot/ipam/forms.py
--- a/nautobot/ipam/forms.py
+++ b/nautobot/ipam/forms.py
@@ -295,6 +295,11 @@ class Meta:
 
 class AggregateFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = Aggregate
+    field_order = [
+        "q",
+        "rir",
+    ]
+
     q = forms.CharField(required=False, label="Search")
     family = forms.ChoiceField(
         required=False,


Test patch:
diff --git a/nautobot/ipam/tests/test_filters.py b/nautobot/ipam/tests/test_filters.py
--- a/nautobot/ipam/tests/test_filters.py
+++ b/nautobot/ipam/tests/test_filters.py
@@ -250,6 +250,7 @@ def setUpTestData(cls):
             RIR.objects.create(name="RIR 2", slug="rir-2"),
             RIR.objects.create(name="RIR 3", slug="rir-3"),
         )
+        cls.rirs = rirs
 
         tenant_groups = (
             TenantGroup.objects.create(name="Tenant group 1", slug="tenant-group-1"),
@@ -268,37 +269,49 @@ def setUpTestData(cls):
             rir=rirs[0],
             tenant=tenants[0],
             date_added="2020-01-01",
-        ),
+        )
         Aggregate.objects.create(
             prefix="10.2.0.0/16",
             rir=rirs[0],
             tenant=tenants[1],
             date_added="2020-01-02",
-        ),
+        )
         Aggregate.objects.create(
             prefix="10.3.0.0/16",
             rir=rirs[1],
             tenant=tenants[2],
             date_added="2020-01-03",
-        ),
+        )
         Aggregate.objects.create(
             prefix="2001:db8:1::/48",
             rir=rirs[1],
             tenant=tenants[0],
             date_added="2020-01-04",
-        ),
+        )
         Aggregate.objects.create(
             prefix="2001:db8:3::/48",
             rir=rirs[2],
             tenant=tenants[2],
             date_added="2020-01-06",
-        ),
+        )
         Aggregate.objects.create(
             prefix="2001:db8:2::/48",
             rir=rirs[2],
             tenant=tenants[1],
             date_added="2020-01-05",
-        ),
+        )
+
+    def test_search(self):
+        Aggregate.objects.create(prefix="10.150.255.0/31", rir=self.rirs[0])
+        Aggregate.objects.create(prefix="10.150.255.2/31", rir=self.rirs[0])
+        test_values = [
+            "10.150.255.0/31",
+            "10.150.255.0",
+            "10.150.255.2",
+        ]
+        for value in test_values:
+            params = {"q": value}
+            self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
     def test_id(self):
         params = {"id": self.queryset.values_list("pk", flat=True)[:2]}
@@ -442,7 +455,7 @@ def setUpTestData(cls):
             vlan=vlans[0],
             role=roles[0],
             status=status_map["active"],
-        ),
+        )
         Prefix.objects.create(
             prefix="10.0.2.0/24",
             tenant=tenants[1],
@@ -451,7 +464,7 @@ def setUpTestData(cls):
             vlan=vlans[1],
             role=roles[1],
             status=status_map["deprecated"],
-        ),
+        )
         Prefix.objects.create(
             prefix="10.0.3.0/24",
             tenant=tenants[2],
@@ -460,7 +473,7 @@ def setUpTestData(cls):
             vlan=vlans[2],
             role=roles[2],
             status=status_map["reserved"],
-        ),
+        )
         Prefix.objects.create(
             prefix="2001:db8::/64",
             tenant=None,
@@ -470,7 +483,7 @@ def setUpTestData(cls):
             role=None,
             is_pool=True,
             status=status_map["active"],
-        ),
+        )
         Prefix.objects.create(
             prefix="2001:db8:0:1::/64",
             tenant=tenants[0],
@@ -479,7 +492,7 @@ def setUpTestData(cls):
             vlan=vlans[0],
             role=roles[0],
             status=status_map["active"],
-        ),
+        )
         Prefix.objects.create(
             prefix="2001:db8:0:2::/64",
             tenant=tenants[1],
@@ -488,7 +501,7 @@ def setUpTestData(cls):
             vlan=vlans[1],
             role=roles[1],
             status=status_map["deprecated"],
-        ),
+        )
         Prefix.objects.create(
             prefix="2001:db8:0:3::/64",
             tenant=tenants[2],
@@ -497,9 +510,21 @@ def setUpTestData(cls):
             vlan=vlans[2],
             role=roles[2],
             status=status_map["reserved"],
-        ),
-        Prefix.objects.create(prefix="10.0.0.0/16", status=status_map["active"]),
-        Prefix.objects.create(prefix="2001:db8::/32", status=status_map["active"]),
+        )
+        Prefix.objects.create(prefix="10.0.0.0/16", status=status_map["active"])
+        Prefix.objects.create(prefix="2001:db8::/32", status=status_map["active"])
+
+    def test_search(self):
+        Prefix.objects.create(prefix="10.150.255.0/31")
+        Prefix.objects.create(prefix="10.150.255.2/31")
+        test_values = [
+            "10.150.255.0/31",
+            "10.150.255.0",
+            "10.150.255.2",
+        ]
+        for value in test_values:
+            params = {"q": value}
+            self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
     def test_id(self):
         params = {"id": self.queryset.values_list("pk", flat=True)[:2]}