diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index de2ae3f74..bb9a18c75 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -4,6 +4,7 @@ ### Enhancements +* [#2101](https://github.com/netbox-community/netbox/issues/2101) - Add missing `q` filters for necessary models * [#7803](https://github.com/netbox-community/netbox/issues/7803) - Improve live reloading of custom scripts * [#7810](https://github.com/netbox-community/netbox/issues/7810) - Add IEEE 802.15.1 interface type diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index df7f415e2..ba7ede783 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1394,6 +1394,10 @@ class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathE # class ConnectionFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) site_id = MultiValueNumberFilter( method='filter_connections', field_name='device__site_id' @@ -1416,6 +1420,15 @@ class ConnectionFilterSet(BaseFilterSet): return queryset return queryset.filter(**{f'{name}__in': value}) + def search(self, queryset, name, value): + if not value.strip(): + return queryset + qs_filter = ( + Q(device__name__icontains=value) | + Q(cable__label__icontains=value) + ) + return queryset.filter(qs_filter) + class ConsoleConnectionFilterSet(ConnectionFilterSet): diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 4ef53c469..93299a17e 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -1068,6 +1068,11 @@ class InventoryItemFilterForm(DeviceComponentFilterForm): # class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form): + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -1095,6 +1100,11 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form): class PowerConnectionFilterForm(BootstrapMixin, forms.Form): + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -1122,6 +1132,11 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form): class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form): + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py index af8d904f4..1ed25cdac 100644 --- a/netbox/extras/filtersets.py +++ b/netbox/extras/filtersets.py @@ -35,6 +35,10 @@ EXACT_FILTER_TYPES = ( class WebhookFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) content_types = ContentTypeFilter() http_method = django_filters.MultipleChoiceFilter( choices=WebhookHttpMethodChoices @@ -47,30 +51,81 @@ class WebhookFilterSet(BaseFilterSet): 'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path', ] + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(payload_url__icontains=value) + ) + class CustomFieldFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) content_types = ContentTypeFilter() class Meta: model = CustomField fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight'] + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(label__icontains=value) | + Q(description__icontains=value) + ) + class CustomLinkFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) class Meta: model = CustomLink fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window'] + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(link_text__icontains=value) | + Q(link_url__icontains=value) | + Q(group_name__icontains=value) + ) + class ExportTemplateFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) class Meta: model = ExportTemplate fields = ['id', 'content_type', 'name'] + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(description__icontains=value) + ) + class ImageAttachmentFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) created = django_filters.DateTimeFilter() content_type = ContentTypeFilter() @@ -78,6 +133,11 @@ class ImageAttachmentFilterSet(BaseFilterSet): model = ImageAttachment fields = ['id', 'content_type_id', 'object_id', 'name'] + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter(name__icontains=value) + class JournalEntryFilterSet(ChangeLoggedModelFilterSet): q = django_filters.CharFilter( diff --git a/netbox/users/filtersets.py b/netbox/users/filtersets.py index 80643c1e5..ad296ea25 100644 --- a/netbox/users/filtersets.py +++ b/netbox/users/filtersets.py @@ -99,8 +99,20 @@ class TokenFilterSet(BaseFilterSet): model = Token fields = ['id', 'key', 'write_enabled'] + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(user__username__icontains=value) | + Q(description__icontains=value) + ) + class ObjectPermissionFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) user_id = django_filters.ModelMultipleChoiceFilter( field_name='users', queryset=User.objects.all(), @@ -127,3 +139,11 @@ class ObjectPermissionFilterSet(BaseFilterSet): class Meta: model = ObjectPermission fields = ['id', 'name', 'enabled', 'object_types'] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(description__icontains=value) + )