Repository: netbox-community/netbox
Pull request: #8580 add interface filters for connected
<!--
    Thank you for your interest in contributing to NetBox! Please note that
    our contribution policy requires that a feature request or bug report be
    approved and assigned prior to filing a pull request. This helps avoid
    wasting time and effort on something that we might not be able to accept.

    IF YOUR PULL REQUEST DOES NOT REFERENCE AN ISSUE WHICH HAS BEEN ASSIGNED
    TO YOU, IT WE BE CLOSED AUTOMATICALLY.

    Specify your assigned issue number on the line below.
-->
### Fixes: #8580

<!--
    Please include a summary of the proposed changes below.
-->
Adds filters to interface for:

**cabled** - Boolean filter returning only models which have a cable assigned
**connected** - Boolean filter for path endpoints which returns only models with a complete end-to-end path assigned
**is_occupied** - Boolean filter for objects which are cabled or which have mark_connected set to True

Added them as a new group to the filters page "Connection" at the end (see screenshot):

![Monosnap Interfaces | NetBox 2022-09-07 15-44-18](https://user-images.githubusercontent.com/99642/188996547-fdff0e70-cf87-4a4d-8487-c0f2522e5f41.png)

@jeremystretch I didn't see any place in the docs that need to be updated for a new filter.  Do the API specs / GraphQL stuff need to be separately updated if adding a new filter?

Problem statement:
Ability to filter interfaces by 'mark_connected' or those with a connected cable
### NetBox version

3.1.7

### Feature type

New functionality

### Proposed functionality

Add the ability to filter interfaces under /dcim/interfaces/ by those that are marked / not marked as 'mark_connected' 
Add the ability to filter interfaces under /dcim/interfaces/ by those that have / have not got a cable connected

### Use case

A large switch with multiple interfaces can be trick to sort/filter in the current GUI. E.g. I have a 9410 switch with 358 interfaces, the /dcim/devices/<device>/interfaces/ view allows me to fairly quickly see which are connected / not connected albeit with no way to filter.

However when looking at /dcim/interfaces/ I can add the Mark Connected and Cable columns but i'm unable to filter by interfaces that are marked connected or have a cable attached

### Database changes

_No response_

### External dependencies

_No response_

Hints from issue discussion:
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
> Add the ability to filter interfaces under /dcim/interfaces/ by those that have / have not got a cable connected

This is already provided by the `cabled` boolean filter on relevant models.
We currently have two relevent filters in place:

* `cabled` - Boolean filter returning only models which have a cable assigned
* `connected` - Boolean filter for path endpoints which returns only models with a complete end-to-end path assigned

Given that the current `connected` filter serves a different purpose than whats being proposed here, we have a few choices:

1. Extend the `cabled` filter to include objects with `mark_connected` set to True
2. Introduce a new filter for objects which are cabled _or_ which have `mark_connected` set to True
3. Introduce a new filter which matches only on the `mark_connected` field (this could not be practically combined with the `cabled` filter as the two are mutually exclusive)

IMO the second option is preferable because it preserves the existing behavior of the `cabled` function and avoids potential confusion. However, I'm not sure what the new filter should be called.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
> 1. Extend the `cabled` filter to include objects with `mark_connected` set to True

This makes the most sense IMO, but this is still open to debate.
I know it is a bit of a change, but, would it make sense to have the following filters?

- is_cabled
- has_endpoint
- is_connected

`is_connected` would include both cabled and marked conections.

The `has_endpoint` would replace the current `connected` filter currently.

It would be a breaking change and perhaps we can deprecate it over the next couple of cycles but argueably has_endpoint makes more sense then "connected" in my own eye.  I also prefer the is_/has_ semantic and we have used it elsewhere where we have a Boolean (`is_private` in RIR model, `is_full_depth`, `is_pool`, `has_primary_ip`, etc).
I personally like this option, but I am not the one who would have to 
implement it. |is_connected| always drives me nuts when I check the box 
"mark connected" and this still comes back false.


On 8/3/22 4:56 PM, Daniel Sheppard wrote:
>
> I know it is a bit of a change, but, would it make sense to have the 
> following filters?
>
>   * is_cabled
>   * has_endpoint
>   * is_connected
>
> |is_connected| would include both cabled and marked conections.
>
> The |has_endpoint| would replace the current |connected| filter currently.
>
> It would be a breaking change and perhaps we can deprecate it over the 
> next couple of cycles but argueably has_endpoint makes more sense then 
> "connected" in my own eye. I also prefer the is_/has_ semantic and we 
> have used it elsewhere where we have a Boolean (|is_private| in RIR 
> model, |is_full_depth|, |is_pool|, |has_primary_ip|, etc).
>
> —
> Reply to this email directly, view it on GitHub 
> <https://nam10.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnetbox-community%2Fnetbox%2Fissues%2F8580%23issuecomment-1204474444&data=05%7C01%7Cjos100%40psu.edu%7C9e9fa23dde4b4fdecc3d08da7592ac20%7C7cf48d453ddb4389a9c1c115526eb52e%7C0%7C0%7C637951570066785310%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=384CFedu%2BVSqTnuA4H8hFJyNR%2BToZhwFJyeJNHH4JTU%3D&reserved=0>, 
> or unsubscribe 
> <https://nam10.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FANYW3MO2CCJGEZKA72EKA7DVXLMIXANCNFSM5NYA3HRQ&data=05%7C01%7Cjos100%40psu.edu%7C9e9fa23dde4b4fdecc3d08da7592ac20%7C7cf48d453ddb4389a9c1c115526eb52e%7C0%7C0%7C637951570066785310%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=em83gfezn5g4nD3VgIJz%2BGhHKmGSGtefP8q2sxkt0XY%3D&reserved=0>.
> You are receiving this because you are subscribed to this 
> thread.Message ID: 
> ***@***.***>
>
@jeremystretch per discussion, proposal is (does this sound correct):

Keep the current two filters as is:

**cabled** - Boolean filter returning only models which have a cable assigned
**connected** - Boolean filter for path endpoints which returns only models with a complete end-to-end path assigned

Add a new filter:

**is_occupied** - Boolean filter for objects which are cabled or which have mark_connected set to True
@arthanson that sounds good to me!

Commit message: #8580 add interface filters for connected

Patch to review:
diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py
index 5d92af87839..1a9887c31d0 100644
--- a/netbox/dcim/filtersets.py
+++ b/netbox/dcim/filtersets.py
@@ -1304,6 +1304,9 @@ class InterfaceFilterSet(
         to_field_name='rd',
         label='VRF (RD)',
     )
+    is_occupied = django_filters.BooleanFilter(
+        method='filter_is_occupied'
+    )
 
     class Meta:
         model = Interface
@@ -1359,6 +1362,12 @@ def filter_kind(self, queryset, name, value):
             'wireless': queryset.filter(type__in=WIRELESS_IFACE_TYPES),
         }.get(value, queryset.none())
 
+    def filter_is_occupied(self, queryset, name, value):
+        if value:
+            return queryset.filter(Q(cable__isnull=False) | Q(mark_connected=True))
+        else:
+            return queryset.filter(cable__isnull=True, mark_connected=False)
+
 
 class FrontPortFilterSet(
     ModularDeviceComponentFilterSet,
diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py
index 173ea5d1ea9..85fe909c5d0 100644
--- a/netbox/dcim/forms/filtersets.py
+++ b/netbox/dcim/forms/filtersets.py
@@ -1009,6 +1009,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
         ('PoE', ('poe_mode', 'poe_type')),
         ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
         ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
+        ('Connection', ('cabled', 'connected', 'is_occupied'))
     )
     kind = MultipleChoiceField(
         choices=InterfaceKindChoices,
@@ -1087,6 +1088,24 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
         label='VRF'
     )
     tag = TagFilterField(model)
+    cabled = forms.NullBooleanField(
+        required=False,
+        widget=StaticSelect(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        )
+    )
+    connected = forms.NullBooleanField(
+        required=False,
+        widget=StaticSelect(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        )
+    )
+    is_occupied = forms.NullBooleanField(
+        required=False,
+        widget=StaticSelect(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        )
+    )
 
 
 class FrontPortFilterForm(DeviceComponentFilterForm):


Code review comments:
diff_hunk: @@ -1087,6 +1088,24 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
         label='VRF'
     )
     tag = TagFilterField(model)
+    cabled = forms.NullBooleanField(
+        required=False,
+        widget=StaticSelect(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        )
+    )
+    connected = forms.NullBooleanField(
+        required=False,
+        widget=StaticSelect(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        )
+    )
+    is_occupied = forms.NullBooleanField(
+        required=False,
+        widget=StaticSelect(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        )
+    )
path: netbox/dcim/forms/filtersets.py
text: @user1:
These should be added to the filter form for all cabled objects to be consistent.

diff_hunk: @@ -1304,6 +1304,9 @@ class InterfaceFilterSet(
         to_field_name='rd',
         label='VRF (RD)',
     )
+    is_occupied = django_filters.BooleanFilter(
path: netbox/dcim/filtersets.py
text: @user1:
This should go under CabledObjectFilterSet so that the filter is available for all object types to which a cable can be attached (those which inherit from CabledObjectModel).