Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Device Modules: REST API and Filters #5667

Merged

Conversation

gsnider2195
Copy link
Contributor

Closes #2101

What's Changed

Screenshots

TODO

  • Explanation of Change(s)
  • Added change log fragment(s) (for more information see the documentation)
  • Attached Screenshots, Payload Example
  • Unit, Integration Tests
  • Documentation Updates (when adding/changing features)
  • Example App Updates (when adding/changing features)
  • Outline Remaining Work, Constraints from Design

nautobot/dcim/api/urls.py Outdated Show resolved Hide resolved
nautobot/dcim/filters/__init__.py Outdated Show resolved Hide resolved
nautobot/dcim/filters/__init__.py Outdated Show resolved Hide resolved
nautobot/dcim/tests/test_api.py Outdated Show resolved Hide resolved
@gsnider2195 gsnider2195 mentioned this pull request May 6, 2024
9 tasks
…ponent templates, fix a bunch of failing tests
Comment on lines +143 to +147
qs_result = self.queryset.exclude(**{f"{field_name}__isnull": True})
self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result)
with self.subTest(f"{self.filterset.__name__} RelatedMembershipBooleanFilter {filter_name} (False)"):
filterset_result = self.filterset({filter_name: False}, self.queryset).qs
qs_result = self.queryset.filter(**{f"{field_name}__isnull": True}).distinct()
qs_result = self.queryset.exclude(**{f"{field_name}__isnull": False})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated query to match the query generated by the filter

nautobot/core/filters.py Show resolved Hide resolved
nautobot/dcim/filters/__init__.py Show resolved Hide resolved
nautobot/dcim/tests/test_filters.py Outdated Show resolved Hide resolved
@gsnider2195
Copy link
Contributor Author

Should the change above be using rear_port_templates instead? Since a device can have rear-ports without any front-ports, but not the reverse?

I understood "has pass-through ports" to mean it has both front ports and rear ports. So if you only have rear ports, you don't have pass-through ports, right? But if you have front ports, you have to have at least one of each. The docstrings for FrontPort and RearPort are "A pass-through port on the front/rear of a Device."

@glennmatthews
Copy link
Contributor

Should the change above be using rear_port_templates instead? Since a device can have rear-ports without any front-ports, but not the reverse?

I understood "has pass-through ports" to mean it has both front ports and rear ports. So if you only have rear ports, you don't have pass-through ports, right? But if you have front ports, you have to have at least one of each. The docstrings for FrontPort and RearPort are "A pass-through port on the front/rear of a Device."

Right you are. Sorry for the noise.

nautobot/dcim/api/serializers.py Outdated Show resolved Hide resolved
nautobot/dcim/api/urls.py Outdated Show resolved Hide resolved
Comment on lines +868 to +875
queryset = Module.objects.select_related(
"parent_module_bay__parent_device__location",
"parent_module_bay__parent_device__tenant",
"module_type__manufacturer",
"tenant",
"role",
"location",
).prefetch_related("module_bays", "tags")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh we need so much to make this logic automatic.

Comment on lines 1068 to 1071
BaseFilterSet,
DeviceComponentModelFilterSetMixin,
ModularDeviceComponentModelFilterSetMixin,
CableTerminationModelFilterSetMixin,
PathEndpointModelFilterSetMixin,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we fix up the MRO on all of these as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like any filterset mixins that are using get_filters() to inject filters into the filterset (StatusModelFilterSetMixin and RoleModelFilterSetMixin) require this broken MRO so that BaseFilterSet can add the extra filter lookups to these filters. We'll need to fix those mixins before we can make this change, unfortunately.

Correct MRO:
>>> [k for k in DeviceFilterSet().filters.keys() if "status" in k or "role" in k]
['role', 'status']

Old Incorrect MRO:
>>> [k for k in DeviceFilterSet().filters.keys() if "status" in k or "role" in k]
['role', 'status', 'role__n', 'status__n']

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This patch appears to fix it:

diff --git a/nautobot/core/utils/filtering.py b/nautobot/core/utils/filtering.py
index 568c34a02..9a5673738 100644
--- a/nautobot/core/utils/filtering.py
+++ b/nautobot/core/utils/filtering.py
@@ -119,6 +119,8 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
             "to_field_name": field.extra.get("to_field_name", "id"),
             "query_params": field.extra.get("query_params", {}),
         }
+        form_attr["query_params"].setdefault("content_types", [model._meta.label_lower])
+
         # ConfigContext requires content_type set to Device and VirtualMachine
         if model == ConfigContext:
             form_attr["query_params"] = {"content_types": [Device._meta.label_lower, VirtualMachine._meta.label_lower]}
diff --git a/nautobot/extras/filters/mixins.py b/nautobot/extras/filters/mixins.py
index 56d56ba0e..6bc1fd6f0 100644
--- a/nautobot/extras/filters/mixins.py
+++ b/nautobot/extras/filters/mixins.py
@@ -303,17 +303,7 @@ class RoleModelFilterSetMixin(django_filters.FilterSet):
     Mixin to add a `role` filter field to a FilterSet.
     """
 
-    @classmethod
-    def get_filters(cls):
-        filters = super().get_filters()
-
-        if cls._meta.model is not None:
-            filters["role"] = RoleFilter(
-                field_name="role",
-                query_params={"content_types": [cls._meta.model._meta.label_lower]},
-            )
-
-        return filters
+    role = RoleFilter(field_name="role")
 
 
 class StatusFilter(NaturalKeyOrPKMultipleChoiceFilter):
@@ -332,14 +322,4 @@ class StatusModelFilterSetMixin(django_filters.FilterSet):
     Mixin to add a `status` filter field to a FilterSet.
     """
 
-    @classmethod
-    def get_filters(cls):
-        filters = super().get_filters()
-
-        if cls._meta.model is not None:
-            filters["status"] = StatusFilter(
-                field_name="status",
-                query_params={"content_types": [cls._meta.model._meta.label_lower]},
-            )
-
-        return filters
+    status = StatusFilter(field_name="status")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's reverting a change I made recently... IIRC the downside is that now the filters won't have the correct query_params to restrict the queryset in the UI?

As an alternative, do we just need to split the get_filters logic into a couple of separate methods so that mixin code gets called correctly before the base filterset adds the extra lookups to the filters?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you added logic back into get_filterset_parameter_form_field to set the content-types, but it's a step backwards from our goal of moving the brains into FilterSet rather than having a separate API to massage the filterset fields into a more correct state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to move the dynamic generation of filterset form fields into the filter field instead of a separate utility function. I opened #5700 to track this and I'll be reverting these changes.

nautobot/dcim/filters/__init__.py Outdated Show resolved Hide resolved
nautobot/dcim/filters/__init__.py Outdated Show resolved Hide resolved
nautobot/dcim/models/device_component_templates.py Outdated Show resolved Hide resolved
nautobot/dcim/models/device_components.py Outdated Show resolved Hide resolved
nautobot/dcim/tests/test_filters.py Outdated Show resolved Hide resolved
nautobot/dcim/tests/test_filters.py Show resolved Hide resolved
@gsnider2195 gsnider2195 changed the title WIP Device Modules: REST API and Filters Device Modules: REST API and Filters May 9, 2024
@gsnider2195 gsnider2195 marked this pull request as ready for review May 9, 2024 19:08
nautobot/dcim/api/serializers.py Show resolved Hide resolved

class Meta:
model = Module
fields = "__all__"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something I noticed yesterday is that when we're using __all__ on our new FilterSets, this includes a _custom_field_data filter, which the GraphQL schema generation complains about because GraphQL filters can't start with an underscore. Not blocking for this PR in any way, but we should fix that, possibly by enhancing NautobotFilterSet to auto-exclude _custom_field_data as a filter?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#5703.

Thanks!

nautobot/dcim/filters/__init__.py Show resolved Hide resolved
@gsnider2195 gsnider2195 merged commit 5f28434 into u/gas-2101-device-modules May 10, 2024
15 of 17 checks passed
@gsnider2195 gsnider2195 deleted the u/gas-2101-device-modules-rest-api-filters branch May 10, 2024 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants