From 07f39b31da1994e4bad6dc2ec967c5a192842c0c Mon Sep 17 00:00:00 2001 From: Matt Palmer Date: Mon, 19 Apr 2021 12:00:27 +1000 Subject: [PATCH 01/67] Expose Django SESSION_COOKIE_NAME setting There are situations in which it is convenient to be able to modify the name of the cookie that the application uses for storing the session token (conflicts with other cookies on the same domain, for example). --- netbox/netbox/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 5408b47d8..55e713f38 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -120,6 +120,7 @@ REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 're RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300) SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/') SESSION_FILE_PATH = getattr(configuration, 'SESSION_FILE_PATH', None) +SESSION_COOKIE_NAME = getattr(configuration, 'SESSION_COOKIE_NAME', 'sessionid') SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d') SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i') SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s') From 75fdff4d410e7e26276b79d12df358b38d4c1bad Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 08:10:11 -0400 Subject: [PATCH 02/67] Changelog & docs for #6197 --- docs/configuration/optional-settings.md | 8 ++++++++ docs/release-notes/version-2.11.md | 8 ++++++++ netbox/netbox/configuration.example.py | 3 +++ 3 files changed, 19 insertions(+) diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index 4ed3d946e..927bf9f37 100644 --- a/docs/configuration/optional-settings.md +++ b/docs/configuration/optional-settings.md @@ -515,6 +515,14 @@ The file path to the location where custom scripts will be kept. By default, thi --- +## SESSION_COOKIE_NAME + +Default: `sessionid` + +The name used for the session cookie. See the [Django documentation](https://docs.djangoproject.com/en/stable/ref/settings/#session-cookie-name) for more detail. + +--- + ## SESSION_FILE_PATH Default: None diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index a43e354de..94c7a984f 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -1,5 +1,13 @@ # NetBox v2.11 +## v2.11.3 (FUTURE) + +### Enhancements + +* [#6197](https://github.com/netbox-community/netbox/issues/6197) - Introduced `SESSION_COOKIE_NAME` config parameter + +--- + ## v2.11.2 (2021-04-27) ### Enhancements diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index c40e280dd..461d7f4cd 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -246,6 +246,9 @@ RQ_DEFAULT_TIMEOUT = 300 # this setting is derived from the installed location. # SCRIPTS_ROOT = '/opt/netbox/netbox/scripts' +# The name to use for the session cookie. +SESSION_COOKIE_NAME = 'sessionid' + # By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use # local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only # database access.) Note that the user as which NetBox runs must have read and write permissions to this path. From bb988701fe8262305eb008b5f954a7021d8e4db3 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 08:43:46 -0400 Subject: [PATCH 03/67] Fixes #6308: Fix linking of available VLANs in VLAN group view --- docs/release-notes/version-2.11.md | 4 ++++ netbox/ipam/views.py | 8 ++++++++ netbox/templates/ipam/vlangroup.html | 26 +++++++++++--------------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index 94c7a984f..1ec3e0fdf 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -6,6 +6,10 @@ * [#6197](https://github.com/netbox-community/netbox/issues/6197) - Introduced `SESSION_COOKIE_NAME` config parameter +### Bug Fixes + +* [#6308](https://github.com/netbox-community/netbox/issues/6308) - Fix linking of available VLANs in VLAN group view + --- ## v2.11.2 (2021-04-27) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 0339aff07..f7da1f583 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -684,9 +684,17 @@ class VLANGroupView(generic.ObjectView): vlans_table.columns.hide('group') paginate_table(vlans_table, request) + # Compile permissions list for rendering the object table + permissions = { + 'add': request.user.has_perm('ipam.add_vlan'), + 'change': request.user.has_perm('ipam.change_vlan'), + 'delete': request.user.has_perm('ipam.delete_vlan'), + } + return { 'vlans_count': vlans_count, 'vlans_table': vlans_table, + 'permissions': permissions, } diff --git a/netbox/templates/ipam/vlangroup.html b/netbox/templates/ipam/vlangroup.html index 3118c1796..67330ef80 100644 --- a/netbox/templates/ipam/vlangroup.html +++ b/netbox/templates/ipam/vlangroup.html @@ -10,6 +10,15 @@
  • {{ object }}
  • {% endblock %} +{% block buttons %} + {% if perms.ipam.add_vlan %} + + Add VLAN + + {% endif %} + {{ block.super }} +{% endblock %} + {% block content %}
    @@ -52,21 +61,8 @@
    -
    -
    - VLANs -
    - {% include 'inc/table.html' with table=vlans_table %} - {% if perms.ipam.add_vlan %} - - {% endif %} -
    - {% include 'inc/paginator.html' with paginator=vlans_table.paginator page=vlans_table.page %} -
    + {% include 'utilities/obj_table.html' with table=vlans_table table_template='panel_table.html' heading='VLANs' bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %} +
    {% endblock %} From cc57d1edf77a0c10cabc3ae9b891f8923ad396a5 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 08:50:19 -0400 Subject: [PATCH 04/67] Fixes #6309: Restrict parent VM interface assignment to the parent VM --- docs/release-notes/version-2.11.md | 1 + netbox/virtualization/forms.py | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index 1ec3e0fdf..f83875a4d 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -9,6 +9,7 @@ ### Bug Fixes * [#6308](https://github.com/netbox-community/netbox/issues/6308) - Fix linking of available VLANs in VLAN group view +* [#6309](https://github.com/netbox-community/netbox/issues/6309) - Restrict parent VM interface assignment to the parent VM --- diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 265674803..84a7728c2 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -646,7 +646,7 @@ class VMInterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm) vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine') # Restrict parent interface assignment by VM - self.fields['parent'].widget.add_query_param('virtualmachine_id', vm_id) + self.fields['parent'].widget.add_query_param('virtual_machine_id', vm_id) # Limit VLAN choices by virtual machine self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id) @@ -669,7 +669,7 @@ class VMInterfaceCreateForm(BootstrapMixin, InterfaceCommonForm): required=False, display_field='display_name', query_params={ - 'virtualmachine_id': 'virtual_machine', + 'virtual_machine_id': '$virtual_machine', } ) mtu = forms.IntegerField( @@ -712,9 +712,6 @@ class VMInterfaceCreateForm(BootstrapMixin, InterfaceCommonForm): super().__init__(*args, **kwargs) vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine') - # Restrict parent interface assignment by VM - self.fields['parent'].widget.add_query_param('virtualmachine_id', vm_id) - # Limit VLAN choices by virtual machine self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id) self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id) @@ -798,7 +795,7 @@ class VMInterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): vm_id = self.initial.get('virtual_machine') # Restrict parent interface assignment by VM - self.fields['parent'].widget.add_query_param('virtualmachine_id', vm_id) + self.fields['parent'].widget.add_query_param('virtual_machine_id', vm_id) # Limit VLAN choices by virtual machine self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id) From 763b02975cf70e1ad1729e9667d963ea64c3d92f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 13:45:44 -0400 Subject: [PATCH 05/67] Reference the demo instance in the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e35b72c2e..3566ff7eb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ NetBox runs as a web application atop the [Django](https://www.djangoproject.com Python framework with a [PostgreSQL](https://www.postgresql.org/) database. For a complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/netbox-community/netbox). -The complete documentation for NetBox can be found at [Read the Docs](https://netbox.readthedocs.io/en/stable/). +The complete documentation for NetBox can be found at [Read the Docs](https://netbox.readthedocs.io/en/stable/). A public demo instance is available at https://demo.netbox.dev. ### Discussion From 0de50e0afefcb96337882116007a70d7d0e3eed4 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 15:13:44 -0400 Subject: [PATCH 06/67] Split Filter and FilterSet classes --- netbox/circuits/filters.py | 5 +- netbox/dcim/filters.py | 4 +- netbox/extras/filters.py | 3 +- netbox/ipam/filters.py | 5 +- netbox/secrets/filters.py | 3 +- netbox/tenancy/filters.py | 3 +- netbox/users/filters.py | 2 +- netbox/utilities/filters.py | 190 +------------------------ netbox/utilities/filtersets.py | 190 +++++++++++++++++++++++++ netbox/utilities/tests/test_filters.py | 5 +- netbox/virtualization/filters.py | 6 +- 11 files changed, 211 insertions(+), 205 deletions(-) create mode 100644 netbox/utilities/filtersets.py diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 034a99ac9..7e0b1cade 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -5,9 +5,8 @@ from dcim.filters import CableTerminationFilterSet from dcim.models import Region, Site, SiteGroup from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from tenancy.filters import TenancyFilterSet -from utilities.filters import ( - BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter -) +from utilities.filters import TagFilter, TreeNodeMultipleChoiceFilter +from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet from .choices import * from .models import * diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 29c4281ba..777ca5884 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -6,9 +6,9 @@ from tenancy.filters import TenancyFilterSet from tenancy.models import Tenant from utilities.choices import ColorChoices from utilities.filters import ( - BaseFilterSet, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, - NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter, + MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TagFilter, TreeNodeMultipleChoiceFilter, ) +from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet from virtualization.models import Cluster from .choices import * from .constants import * diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index aacdbda6b..9105da81f 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -6,7 +6,8 @@ from django.forms import DateField, IntegerField, NullBooleanField from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup from tenancy.models import Tenant, TenantGroup -from utilities.filters import BaseFilterSet, ContentTypeFilter +from utilities.filtersets import BaseFilterSet +from utilities.filters import ContentTypeFilter from virtualization.models import Cluster, ClusterGroup from .choices import * from .models import * diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 8f4030411..af047424f 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -9,9 +9,10 @@ from dcim.models import Device, Interface, Region, Site, SiteGroup from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from tenancy.filters import TenancyFilterSet from utilities.filters import ( - BaseFilterSet, ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, - NumericArrayFilter, TagFilter, TreeNodeMultipleChoiceFilter, + ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TagFilter, + TreeNodeMultipleChoiceFilter, ) +from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet from virtualization.models import VirtualMachine, VMInterface from .choices import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index fb36c827a..ec41ac364 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -3,7 +3,8 @@ from django.db.models import Q from dcim.models import Device from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet -from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter +from utilities.filters import TagFilter +from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet from virtualization.models import VirtualMachine from .models import Secret, SecretRole diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index 0581866a4..8d43a3794 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -2,7 +2,8 @@ import django_filters from django.db.models import Q from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet -from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter +from utilities.filters import TagFilter, TreeNodeMultipleChoiceFilter +from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet from .models import Tenant, TenantGroup diff --git a/netbox/users/filters.py b/netbox/users/filters.py index 359cf9cc7..42f97bedc 100644 --- a/netbox/users/filters.py +++ b/netbox/users/filters.py @@ -3,7 +3,7 @@ from django.contrib.auth.models import Group, User from django.db.models import Q from users.models import ObjectPermission -from utilities.filters import BaseFilterSet +from utilities.filtersets import BaseFilterSet __all__ = ( 'GroupFilterSet', diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 6305c0bba..97608ed22 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -1,17 +1,10 @@ import django_filters -from django_filters.constants import EMPTY_VALUES -from copy import deepcopy -from dcim.forms import MACAddressField from django import forms from django.conf import settings -from django.db import models -from django_filters.utils import get_model_field, resolve_field +from django_filters.constants import EMPTY_VALUES +from dcim.forms import MACAddressField from extras.models import Tag -from utilities.constants import ( - FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP, - FILTER_NUMERIC_BASED_LOOKUP_MAP -) def multivalue_field_factory(field_class): @@ -134,182 +127,3 @@ class ContentTypeFilter(django_filters.CharFilter): f'{self.field_name}__model': model } ) - - -# -# FilterSets -# - -class BaseFilterSet(django_filters.FilterSet): - """ - A base filterset which provides common functionaly to all NetBox filtersets - """ - FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS) - FILTER_DEFAULTS.update({ - models.AutoField: { - 'filter_class': MultiValueNumberFilter - }, - models.CharField: { - 'filter_class': MultiValueCharFilter - }, - models.DateField: { - 'filter_class': MultiValueDateFilter - }, - models.DateTimeField: { - 'filter_class': MultiValueDateTimeFilter - }, - models.DecimalField: { - 'filter_class': MultiValueNumberFilter - }, - models.EmailField: { - 'filter_class': MultiValueCharFilter - }, - models.FloatField: { - 'filter_class': MultiValueNumberFilter - }, - models.IntegerField: { - 'filter_class': MultiValueNumberFilter - }, - models.PositiveIntegerField: { - 'filter_class': MultiValueNumberFilter - }, - models.PositiveSmallIntegerField: { - 'filter_class': MultiValueNumberFilter - }, - models.SlugField: { - 'filter_class': MultiValueCharFilter - }, - models.SmallIntegerField: { - 'filter_class': MultiValueNumberFilter - }, - models.TimeField: { - 'filter_class': MultiValueTimeFilter - }, - models.URLField: { - 'filter_class': MultiValueCharFilter - }, - MACAddressField: { - 'filter_class': MultiValueMACAddressFilter - }, - }) - - @staticmethod - def _get_filter_lookup_dict(existing_filter): - # Choose the lookup expression map based on the filter type - if isinstance(existing_filter, ( - MultiValueDateFilter, - MultiValueDateTimeFilter, - MultiValueNumberFilter, - MultiValueTimeFilter - )): - lookup_map = FILTER_NUMERIC_BASED_LOOKUP_MAP - - elif isinstance(existing_filter, ( - TreeNodeMultipleChoiceFilter, - )): - # TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression - lookup_map = FILTER_TREENODE_NEGATION_LOOKUP_MAP - - elif isinstance(existing_filter, ( - django_filters.ModelChoiceFilter, - django_filters.ModelMultipleChoiceFilter, - TagFilter - )) or existing_filter.extra.get('choices'): - # These filter types support only negation - lookup_map = FILTER_NEGATION_LOOKUP_MAP - - elif isinstance(existing_filter, ( - django_filters.filters.CharFilter, - django_filters.MultipleChoiceFilter, - MultiValueCharFilter, - MultiValueMACAddressFilter - )): - lookup_map = FILTER_CHAR_BASED_LOOKUP_MAP - - else: - lookup_map = None - - return lookup_map - - @classmethod - def get_filters(cls): - """ - Override filter generation to support dynamic lookup expressions for certain filter types. - - For specific filter types, new filters are created based on defined lookup expressions in - the form `__` - """ - filters = super().get_filters() - - new_filters = {} - for existing_filter_name, existing_filter in filters.items(): - # Loop over existing filters to extract metadata by which to create new filters - - # If the filter makes use of a custom filter method or lookup expression skip it - # as we cannot sanely handle these cases in a generic mannor - if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']: - continue - - # Choose the lookup expression map based on the filter type - lookup_map = cls._get_filter_lookup_dict(existing_filter) - if lookup_map is None: - # Do not augment this filter type with more lookup expressions - continue - - # Get properties of the existing filter for later use - field_name = existing_filter.field_name - field = get_model_field(cls._meta.model, field_name) - - # Create new filters for each lookup expression in the map - for lookup_name, lookup_expr in lookup_map.items(): - new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name) - - try: - if existing_filter_name in cls.declared_filters: - # The filter field has been explicity defined on the filterset class so we must manually - # create the new filter with the same type because there is no guarantee the defined type - # is the same as the default type for the field - resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid - new_filter = type(existing_filter)( - field_name=field_name, - lookup_expr=lookup_expr, - label=existing_filter.label, - exclude=existing_filter.exclude, - distinct=existing_filter.distinct, - **existing_filter.extra - ) - else: - # The filter field is listed in Meta.fields so we can safely rely on default behaviour - # Will raise FieldLookupError if the lookup is invalid - new_filter = cls.filter_for_field(field, field_name, lookup_expr) - except django_filters.exceptions.FieldLookupError: - # The filter could not be created because the lookup expression is not supported on the field - continue - - if lookup_name.startswith('n'): - # This is a negation filter which requires a queryset.exclude() clause - # Of course setting the negation of the existing filter's exclude attribute handles both cases - new_filter.exclude = not existing_filter.exclude - - new_filters[new_filter_name] = new_filter - - filters.update(new_filters) - return filters - - -class NameSlugSearchFilterSet(django_filters.FilterSet): - """ - A base class for adding the search method to models which only expose the `name` and `slug` fields - """ - q = django_filters.CharFilter( - method='search', - label='Search', - ) - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - models.Q(name__icontains=value) | - models.Q(slug__icontains=value) - ) diff --git a/netbox/utilities/filtersets.py b/netbox/utilities/filtersets.py new file mode 100644 index 000000000..0fb188d11 --- /dev/null +++ b/netbox/utilities/filtersets.py @@ -0,0 +1,190 @@ +import django_filters +from copy import deepcopy +from dcim.forms import MACAddressField +from django.db import models +from django_filters.utils import get_model_field, resolve_field + +from utilities.constants import ( + FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP, + FILTER_NUMERIC_BASED_LOOKUP_MAP +) +from utilities import filters + + +# +# FilterSets +# + +class BaseFilterSet(django_filters.FilterSet): + """ + A base filterset which provides common functionaly to all NetBox filtersets + """ + FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS) + FILTER_DEFAULTS.update({ + models.AutoField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.CharField: { + 'filter_class': filters.MultiValueCharFilter + }, + models.DateField: { + 'filter_class': filters.MultiValueDateFilter + }, + models.DateTimeField: { + 'filter_class': filters.MultiValueDateTimeFilter + }, + models.DecimalField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.EmailField: { + 'filter_class': filters.MultiValueCharFilter + }, + models.FloatField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.IntegerField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.PositiveIntegerField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.PositiveSmallIntegerField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.SlugField: { + 'filter_class': filters.MultiValueCharFilter + }, + models.SmallIntegerField: { + 'filter_class': filters.MultiValueNumberFilter + }, + models.TimeField: { + 'filter_class': filters.MultiValueTimeFilter + }, + models.URLField: { + 'filter_class': filters.MultiValueCharFilter + }, + MACAddressField: { + 'filter_class': filters.MultiValueMACAddressFilter + }, + }) + + @staticmethod + def _get_filter_lookup_dict(existing_filter): + # Choose the lookup expression map based on the filter type + if isinstance(existing_filter, ( + filters.MultiValueDateFilter, + filters.MultiValueDateTimeFilter, + filters.MultiValueNumberFilter, + filters.MultiValueTimeFilter + )): + lookup_map = FILTER_NUMERIC_BASED_LOOKUP_MAP + + elif isinstance(existing_filter, ( + filters.TreeNodeMultipleChoiceFilter, + )): + # TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression + lookup_map = FILTER_TREENODE_NEGATION_LOOKUP_MAP + + elif isinstance(existing_filter, ( + django_filters.ModelChoiceFilter, + django_filters.ModelMultipleChoiceFilter, + filters.TagFilter + )) or existing_filter.extra.get('choices'): + # These filter types support only negation + lookup_map = FILTER_NEGATION_LOOKUP_MAP + + elif isinstance(existing_filter, ( + django_filters.filters.CharFilter, + django_filters.MultipleChoiceFilter, + filters.MultiValueCharFilter, + filters.MultiValueMACAddressFilter + )): + lookup_map = FILTER_CHAR_BASED_LOOKUP_MAP + + else: + lookup_map = None + + return lookup_map + + @classmethod + def get_filters(cls): + """ + Override filter generation to support dynamic lookup expressions for certain filter types. + + For specific filter types, new filters are created based on defined lookup expressions in + the form `__` + """ + filters = super().get_filters() + + new_filters = {} + for existing_filter_name, existing_filter in filters.items(): + # Loop over existing filters to extract metadata by which to create new filters + + # If the filter makes use of a custom filter method or lookup expression skip it + # as we cannot sanely handle these cases in a generic mannor + if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']: + continue + + # Choose the lookup expression map based on the filter type + lookup_map = cls._get_filter_lookup_dict(existing_filter) + if lookup_map is None: + # Do not augment this filter type with more lookup expressions + continue + + # Get properties of the existing filter for later use + field_name = existing_filter.field_name + field = get_model_field(cls._meta.model, field_name) + + # Create new filters for each lookup expression in the map + for lookup_name, lookup_expr in lookup_map.items(): + new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name) + + try: + if existing_filter_name in cls.declared_filters: + # The filter field has been explicity defined on the filterset class so we must manually + # create the new filter with the same type because there is no guarantee the defined type + # is the same as the default type for the field + resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid + new_filter = type(existing_filter)( + field_name=field_name, + lookup_expr=lookup_expr, + label=existing_filter.label, + exclude=existing_filter.exclude, + distinct=existing_filter.distinct, + **existing_filter.extra + ) + else: + # The filter field is listed in Meta.fields so we can safely rely on default behaviour + # Will raise FieldLookupError if the lookup is invalid + new_filter = cls.filter_for_field(field, field_name, lookup_expr) + except django_filters.exceptions.FieldLookupError: + # The filter could not be created because the lookup expression is not supported on the field + continue + + if lookup_name.startswith('n'): + # This is a negation filter which requires a queryset.exclude() clause + # Of course setting the negation of the existing filter's exclude attribute handles both cases + new_filter.exclude = not existing_filter.exclude + + new_filters[new_filter_name] = new_filter + + filters.update(new_filters) + return filters + + +class NameSlugSearchFilterSet(django_filters.FilterSet): + """ + A base class for adding the search method to models which only expose the `name` and `slug` fields + """ + q = django_filters.CharFilter( + method='search', + label='Search', + ) + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + models.Q(name__icontains=value) | + models.Q(slug__icontains=value) + ) diff --git a/netbox/utilities/tests/test_filters.py b/netbox/utilities/tests/test_filters.py index 56eaabd4c..6d2826b70 100644 --- a/netbox/utilities/tests/test_filters.py +++ b/netbox/utilities/tests/test_filters.py @@ -13,9 +13,10 @@ from dcim.models import ( ) from extras.models import TaggedItem from utilities.filters import ( - BaseFilterSet, MACAddressFilter, MultiValueCharFilter, MultiValueDateFilter, MultiValueDateTimeFilter, - MultiValueNumberFilter, MultiValueTimeFilter, TagFilter, TreeNodeMultipleChoiceFilter, + MACAddressFilter, MultiValueCharFilter, MultiValueDateFilter, MultiValueDateTimeFilter, MultiValueNumberFilter, + MultiValueTimeFilter, TagFilter, TreeNodeMultipleChoiceFilter, ) +from utilities.filtersets import BaseFilterSet class TreeNodeMultipleChoiceFilterTest(TestCase): diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 6d706b6cf..9ace5c49e 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -4,10 +4,8 @@ from django.db.models import Q from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet from tenancy.filters import TenancyFilterSet -from utilities.filters import ( - BaseFilterSet, MultiValueMACAddressFilter, NameSlugSearchFilterSet, TagFilter, - TreeNodeMultipleChoiceFilter, -) +from utilities.filters import MultiValueMACAddressFilter, TagFilter, TreeNodeMultipleChoiceFilter +from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet from .choices import * from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface From c4e88fd11a7fe73a50e234e0a83a719cbd1cca8b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 15:59:11 -0400 Subject: [PATCH 07/67] Consolidate FilterSet classes --- netbox/circuits/filters.py | 13 +- netbox/dcim/filters.py | 101 ++++---- netbox/extras/api/views.py | 24 +- netbox/extras/filters.py | 366 ---------------------------- netbox/extras/filtersets.py | 340 ++++++++++++++++++++++++++ netbox/extras/tests/test_filters.py | 2 +- netbox/extras/views.py | 16 +- netbox/ipam/filters.py | 23 +- netbox/secrets/filters.py | 7 +- netbox/tenancy/filters.py | 7 +- netbox/utilities/filtersets.py | 52 +++- netbox/virtualization/filters.py | 20 +- 12 files changed, 484 insertions(+), 487 deletions(-) create mode 100644 netbox/extras/filtersets.py diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 7e0b1cade..6ff6bb104 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -3,10 +3,9 @@ from django.db.models import Q from dcim.filters import CableTerminationFilterSet from dcim.models import Region, Site, SiteGroup -from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from tenancy.filters import TenancyFilterSet from utilities.filters import TagFilter, TreeNodeMultipleChoiceFilter -from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet +from utilities.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet from .choices import * from .models import * @@ -19,7 +18,7 @@ __all__ = ( ) -class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class ProviderFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -79,7 +78,7 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdated ) -class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class ProviderNetworkFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -109,14 +108,14 @@ class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, Created ).distinct() -class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class CircuitTypeFilterSet(OrganizationalModelFilterSet): class Meta: model = CircuitType fields = ['id', 'name', 'slug'] -class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet): +class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -206,7 +205,7 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSe ).distinct() -class CircuitTerminationFilterSet(BaseFilterSet, CreatedUpdatedFilterSet, CableTerminationFilterSet): +class CircuitTerminationFilterSet(ChangeLoggedModelFilterSet, CableTerminationFilterSet): q = django_filters.CharFilter( method='search', label='Search', diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 777ca5884..a45900466 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -1,14 +1,16 @@ import django_filters from django.contrib.auth.models import User -from extras.filters import CustomFieldModelFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet +from extras.filtersets import LocalConfigContextFilterSet from tenancy.filters import TenancyFilterSet from tenancy.models import Tenant from utilities.choices import ColorChoices from utilities.filters import ( MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TagFilter, TreeNodeMultipleChoiceFilter, ) -from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet +from utilities.filtersets import ( + BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet, +) from virtualization.models import Cluster from .choices import * from .constants import * @@ -57,7 +59,7 @@ __all__ = ( ) -class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class RegionFilterSet(OrganizationalModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=Region.objects.all(), label='Parent region (ID)', @@ -74,7 +76,7 @@ class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilt fields = ['id', 'name', 'slug', 'description'] -class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class SiteGroupFilterSet(OrganizationalModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=SiteGroup.objects.all(), label='Parent site group (ID)', @@ -91,7 +93,7 @@ class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedF fields = ['id', 'name', 'slug', 'description'] -class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -154,7 +156,7 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, return queryset.filter(qs_filter) -class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class LocationFilterSet(OrganizationalModelFilterSet): region_id = TreeNodeMultipleChoiceFilter( queryset=Region.objects.all(), field_name='site__region', @@ -218,14 +220,14 @@ class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFi ) -class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class RackRoleFilterSet(OrganizationalModelFilterSet): class Meta: model = RackRole fields = ['id', 'name', 'slug', 'color'] -class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -323,7 +325,7 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, ) -class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -383,14 +385,14 @@ class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModel ) -class ManufacturerFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class ManufacturerFilterSet(OrganizationalModelFilterSet): class Meta: model = Manufacturer fields = ['id', 'name', 'slug', 'description'] -class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class DeviceTypeFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -476,7 +478,7 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdat return queryset.exclude(devicebaytemplates__isnull=value) -class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class DeviceTypeComponentFilterSet(django_filters.FilterSet): devicetype_id = django_filters.ModelMultipleChoiceFilter( queryset=DeviceType.objects.all(), field_name='device_type_id', @@ -484,28 +486,28 @@ class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet, CreatedUpdatedFilter ) -class ConsolePortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): class Meta: model = ConsolePortTemplate fields = ['id', 'name', 'type'] -class ConsoleServerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): class Meta: model = ConsoleServerPortTemplate fields = ['id', 'name', 'type'] -class PowerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): class Meta: model = PowerPortTemplate fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw'] -class PowerOutletTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): feed_leg = django_filters.MultipleChoiceFilter( choices=PowerOutletFeedLegChoices, null_value=None @@ -516,7 +518,7 @@ class PowerOutletTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): fields = ['id', 'name', 'type', 'feed_leg'] -class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): type = django_filters.MultipleChoiceFilter( choices=InterfaceTypeChoices, null_value=None @@ -527,7 +529,7 @@ class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): fields = ['id', 'name', 'type', 'mgmt_only'] -class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -538,7 +540,7 @@ class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): fields = ['id', 'name', 'type'] -class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -549,21 +551,21 @@ class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): fields = ['id', 'name', 'type', 'positions'] -class DeviceBayTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet): +class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet): class Meta: model = DeviceBayTemplate fields = ['id', 'name'] -class DeviceRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class DeviceRoleFilterSet(OrganizationalModelFilterSet): class Meta: model = DeviceRole fields = ['id', 'name', 'slug', 'color', 'vm_role'] -class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class PlatformFilterSet(OrganizationalModelFilterSet): manufacturer_id = django_filters.ModelMultipleChoiceFilter( field_name='manufacturer', queryset=Manufacturer.objects.all(), @@ -581,13 +583,7 @@ class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFi fields = ['id', 'name', 'slug', 'napalm_driver', 'description'] -class DeviceFilterSet( - BaseFilterSet, - TenancyFilterSet, - LocalConfigContextFilterSet, - CustomFieldModelFilterSet, - CreatedUpdatedFilterSet -): +class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -792,7 +788,7 @@ class DeviceFilterSet( return queryset.exclude(devicebays__isnull=value) -class DeviceComponentFilterSet(CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class DeviceComponentFilterSet(django_filters.FilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -876,7 +872,7 @@ class PathEndpointFilterSet(django_filters.FilterSet): return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False)) -class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): +class ConsolePortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): type = django_filters.MultipleChoiceFilter( choices=ConsolePortTypeChoices, null_value=None @@ -887,12 +883,7 @@ class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTermina fields = ['id', 'name', 'label', 'description'] -class ConsoleServerPortFilterSet( - BaseFilterSet, - DeviceComponentFilterSet, - CableTerminationFilterSet, - PathEndpointFilterSet -): +class ConsoleServerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): type = django_filters.MultipleChoiceFilter( choices=ConsolePortTypeChoices, null_value=None @@ -903,7 +894,7 @@ class ConsoleServerPortFilterSet( fields = ['id', 'name', 'label', 'description'] -class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): +class PowerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): type = django_filters.MultipleChoiceFilter( choices=PowerPortTypeChoices, null_value=None @@ -914,7 +905,7 @@ class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description'] -class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): +class PowerOutletFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): type = django_filters.MultipleChoiceFilter( choices=PowerOutletTypeChoices, null_value=None @@ -929,7 +920,7 @@ class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTermina fields = ['id', 'name', 'label', 'feed_leg', 'description'] -class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): +class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1027,7 +1018,7 @@ class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati }.get(value, queryset.none()) -class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): +class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -1038,7 +1029,7 @@ class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati fields = ['id', 'name', 'label', 'type', 'description'] -class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): +class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet): type = django_filters.MultipleChoiceFilter( choices=PortTypeChoices, null_value=None @@ -1049,14 +1040,14 @@ class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminatio fields = ['id', 'name', 'label', 'type', 'positions', 'description'] -class DeviceBayFilterSet(BaseFilterSet, DeviceComponentFilterSet): +class DeviceBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet): class Meta: model = DeviceBay fields = ['id', 'name', 'label', 'description'] -class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet): +class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1129,7 +1120,7 @@ class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet): return queryset.filter(qs_filter) -class VirtualChassisFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class VirtualChassisFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1209,7 +1200,7 @@ class VirtualChassisFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedU return queryset.filter(qs_filter).distinct() -class CableFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class CableFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1273,7 +1264,7 @@ class CableFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFil return queryset -class ConnectionFilterSet: +class ConnectionFilterSet(BaseFilterSet): def filter_site(self, queryset, name, value): if not value.strip(): @@ -1286,7 +1277,7 @@ class ConnectionFilterSet: return queryset.filter(**{f'{name}__in': value}) -class ConsoleConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): +class ConsoleConnectionFilterSet(ConnectionFilterSet): site = django_filters.CharFilter( method='filter_site', label='Site (slug)', @@ -1304,7 +1295,7 @@ class ConsoleConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): fields = ['name'] -class PowerConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): +class PowerConnectionFilterSet(ConnectionFilterSet): site = django_filters.CharFilter( method='filter_site', label='Site (slug)', @@ -1322,7 +1313,7 @@ class PowerConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): fields = ['name'] -class InterfaceConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): +class InterfaceConnectionFilterSet(ConnectionFilterSet): site = django_filters.CharFilter( method='filter_site', label='Site (slug)', @@ -1340,7 +1331,7 @@ class InterfaceConnectionFilterSet(ConnectionFilterSet, BaseFilterSet): fields = [] -class PowerPanelFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class PowerPanelFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -1402,13 +1393,7 @@ class PowerPanelFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdat return queryset.filter(qs_filter) -class PowerFeedFilterSet( - BaseFilterSet, - CableTerminationFilterSet, - PathEndpointFilterSet, - CustomFieldModelFilterSet, - CreatedUpdatedFilterSet -): +class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathEndpointFilterSet): q = django_filters.CharFilter( method='search', label='Search', diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index cee5146a6..7e6c97782 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -9,7 +9,7 @@ from rest_framework.routers import APIRootView from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet from rq import Worker -from extras import filters +from extras import filtersets from extras.choices import JobResultStatusChoices from extras.models import * from extras.models import CustomField @@ -61,7 +61,7 @@ class WebhookViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = Webhook.objects.all() serializer_class = serializers.WebhookSerializer - filterset_class = filters.WebhookFilterSet + filterset_class = filtersets.WebhookFilterSet # @@ -72,7 +72,7 @@ class CustomFieldViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = CustomField.objects.all() serializer_class = serializers.CustomFieldSerializer - filterset_class = filters.CustomFieldFilterSet + filterset_class = filtersets.CustomFieldFilterSet class CustomFieldModelViewSet(ModelViewSet): @@ -101,7 +101,7 @@ class CustomLinkViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = CustomLink.objects.all() serializer_class = serializers.CustomLinkSerializer - filterset_class = filters.CustomLinkFilterSet + filterset_class = filtersets.CustomLinkFilterSet # @@ -112,7 +112,7 @@ class ExportTemplateViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = ExportTemplate.objects.all() serializer_class = serializers.ExportTemplateSerializer - filterset_class = filters.ExportTemplateFilterSet + filterset_class = filtersets.ExportTemplateFilterSet # @@ -124,7 +124,7 @@ class TagViewSet(ModelViewSet): tagged_items=count_related(TaggedItem, 'tag') ) serializer_class = serializers.TagSerializer - filterset_class = filters.TagFilterSet + filterset_class = filtersets.TagFilterSet # @@ -135,7 +135,7 @@ class ImageAttachmentViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = ImageAttachment.objects.all() serializer_class = serializers.ImageAttachmentSerializer - filterset_class = filters.ImageAttachmentFilterSet + filterset_class = filtersets.ImageAttachmentFilterSet # @@ -146,7 +146,7 @@ class JournalEntryViewSet(ModelViewSet): metadata_class = ContentTypeMetadata queryset = JournalEntry.objects.all() serializer_class = serializers.JournalEntrySerializer - filterset_class = filters.JournalEntryFilterSet + filterset_class = filtersets.JournalEntryFilterSet # @@ -158,7 +158,7 @@ class ConfigContextViewSet(ModelViewSet): 'regions', 'site_groups', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants', ) serializer_class = serializers.ConfigContextSerializer - filterset_class = filters.ConfigContextFilterSet + filterset_class = filtersets.ConfigContextFilterSet # @@ -358,7 +358,7 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet): metadata_class = ContentTypeMetadata queryset = ObjectChange.objects.prefetch_related('user') serializer_class = serializers.ObjectChangeSerializer - filterset_class = filters.ObjectChangeFilterSet + filterset_class = filtersets.ObjectChangeFilterSet # @@ -371,7 +371,7 @@ class JobResultViewSet(ReadOnlyModelViewSet): """ queryset = JobResult.objects.prefetch_related('user') serializer_class = serializers.JobResultSerializer - filterset_class = filters.JobResultFilterSet + filterset_class = filtersets.JobResultFilterSet # @@ -384,4 +384,4 @@ class ContentTypeViewSet(ReadOnlyModelViewSet): """ queryset = ContentType.objects.order_by('app_label', 'model') serializer_class = serializers.ContentTypeSerializer - filterset_class = filters.ContentTypeFilterSet + filterset_class = filtersets.ContentTypeFilterSet diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index 9105da81f..dff12aa91 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -1,32 +1,10 @@ import django_filters -from django.contrib.auth.models import User -from django.contrib.contenttypes.models import ContentType -from django.db.models import Q from django.forms import DateField, IntegerField, NullBooleanField -from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup -from tenancy.models import Tenant, TenantGroup -from utilities.filtersets import BaseFilterSet -from utilities.filters import ContentTypeFilter -from virtualization.models import Cluster, ClusterGroup from .choices import * -from .models import * - __all__ = ( - 'ConfigContextFilterSet', - 'ContentTypeFilterSet', - 'CreatedUpdatedFilterSet', 'CustomFieldFilter', - 'CustomLinkFilterSet', - 'CustomFieldModelFilterSet', - 'ExportTemplateFilterSet', - 'ImageAttachmentFilterSet', - 'JournalEntryFilterSet', - 'LocalConfigContextFilterSet', - 'ObjectChangeFilterSet', - 'TagFilterSet', - 'WebhookFilterSet', ) EXACT_FILTER_TYPES = ( @@ -37,41 +15,6 @@ EXACT_FILTER_TYPES = ( ) -class CreatedUpdatedFilterSet(django_filters.FilterSet): - created = django_filters.DateFilter() - created__gte = django_filters.DateFilter( - field_name='created', - lookup_expr='gte' - ) - created__lte = django_filters.DateFilter( - field_name='created', - lookup_expr='lte' - ) - last_updated = django_filters.DateTimeFilter() - last_updated__gte = django_filters.DateTimeFilter( - field_name='last_updated', - lookup_expr='gte' - ) - last_updated__lte = django_filters.DateTimeFilter( - field_name='last_updated', - lookup_expr='lte' - ) - - -class WebhookFilterSet(BaseFilterSet): - content_types = ContentTypeFilter() - http_method = django_filters.MultipleChoiceFilter( - choices=WebhookHttpMethodChoices - ) - - class Meta: - model = Webhook - fields = [ - 'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled', - 'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path', - ] - - class CustomFieldFilter(django_filters.Filter): """ Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name. @@ -93,312 +36,3 @@ class CustomFieldFilter(django_filters.Filter): if custom_field.type not in EXACT_FILTER_TYPES: if custom_field.filter_logic == CustomFieldFilterLogicChoices.FILTER_LOOSE: self.lookup_expr = 'icontains' - - -class CustomFieldModelFilterSet(django_filters.FilterSet): - """ - Dynamically add a Filter for each CustomField applicable to the parent model. - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - custom_fields = CustomField.objects.filter( - content_types=ContentType.objects.get_for_model(self._meta.model) - ).exclude( - filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED - ) - for cf in custom_fields: - self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf) - - -class CustomFieldFilterSet(django_filters.FilterSet): - content_types = ContentTypeFilter() - - class Meta: - model = CustomField - fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight'] - - -class CustomLinkFilterSet(BaseFilterSet): - - class Meta: - model = CustomLink - fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window'] - - -class ExportTemplateFilterSet(BaseFilterSet): - - class Meta: - model = ExportTemplate - fields = ['id', 'content_type', 'name'] - - -class ImageAttachmentFilterSet(BaseFilterSet): - content_type = ContentTypeFilter() - - class Meta: - model = ImageAttachment - fields = ['id', 'content_type_id', 'object_id', 'name'] - - -class JournalEntryFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - created = django_filters.DateTimeFromToRangeFilter() - assigned_object_type = ContentTypeFilter() - created_by_id = django_filters.ModelMultipleChoiceFilter( - queryset=User.objects.all(), - label='User (ID)', - ) - created_by = django_filters.ModelMultipleChoiceFilter( - field_name='created_by__username', - queryset=User.objects.all(), - to_field_name='username', - label='User (name)', - ) - kind = django_filters.MultipleChoiceFilter( - choices=JournalEntryKindChoices - ) - - class Meta: - model = JournalEntry - fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind'] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter(comments__icontains=value) - - -class TagFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - - class Meta: - model = Tag - fields = ['id', 'name', 'slug', 'color'] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(slug__icontains=value) - ) - - -class ConfigContextFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - region_id = django_filters.ModelMultipleChoiceFilter( - field_name='regions', - queryset=Region.objects.all(), - label='Region', - ) - region = django_filters.ModelMultipleChoiceFilter( - field_name='regions__slug', - queryset=Region.objects.all(), - to_field_name='slug', - label='Region (slug)', - ) - site_group = django_filters.ModelMultipleChoiceFilter( - field_name='site_groups__slug', - queryset=SiteGroup.objects.all(), - to_field_name='slug', - label='Site group (slug)', - ) - site_group_id = django_filters.ModelMultipleChoiceFilter( - field_name='site_groups', - queryset=SiteGroup.objects.all(), - label='Site group', - ) - site_id = django_filters.ModelMultipleChoiceFilter( - field_name='sites', - queryset=Site.objects.all(), - label='Site', - ) - site = django_filters.ModelMultipleChoiceFilter( - field_name='sites__slug', - queryset=Site.objects.all(), - to_field_name='slug', - label='Site (slug)', - ) - device_type_id = django_filters.ModelMultipleChoiceFilter( - field_name='device_types', - queryset=DeviceType.objects.all(), - label='Device type', - ) - role_id = django_filters.ModelMultipleChoiceFilter( - field_name='roles', - queryset=DeviceRole.objects.all(), - label='Role', - ) - role = django_filters.ModelMultipleChoiceFilter( - field_name='roles__slug', - queryset=DeviceRole.objects.all(), - to_field_name='slug', - label='Role (slug)', - ) - platform_id = django_filters.ModelMultipleChoiceFilter( - field_name='platforms', - queryset=Platform.objects.all(), - label='Platform', - ) - platform = django_filters.ModelMultipleChoiceFilter( - field_name='platforms__slug', - queryset=Platform.objects.all(), - to_field_name='slug', - label='Platform (slug)', - ) - cluster_group_id = django_filters.ModelMultipleChoiceFilter( - field_name='cluster_groups', - queryset=ClusterGroup.objects.all(), - label='Cluster group', - ) - cluster_group = django_filters.ModelMultipleChoiceFilter( - field_name='cluster_groups__slug', - queryset=ClusterGroup.objects.all(), - to_field_name='slug', - label='Cluster group (slug)', - ) - cluster_id = django_filters.ModelMultipleChoiceFilter( - field_name='clusters', - queryset=Cluster.objects.all(), - label='Cluster', - ) - tenant_group_id = django_filters.ModelMultipleChoiceFilter( - field_name='tenant_groups', - queryset=TenantGroup.objects.all(), - label='Tenant group', - ) - tenant_group = django_filters.ModelMultipleChoiceFilter( - field_name='tenant_groups__slug', - queryset=TenantGroup.objects.all(), - to_field_name='slug', - label='Tenant group (slug)', - ) - tenant_id = django_filters.ModelMultipleChoiceFilter( - field_name='tenants', - queryset=Tenant.objects.all(), - label='Tenant', - ) - tenant = django_filters.ModelMultipleChoiceFilter( - field_name='tenants__slug', - queryset=Tenant.objects.all(), - to_field_name='slug', - label='Tenant (slug)', - ) - tag = django_filters.ModelMultipleChoiceFilter( - field_name='tags__slug', - queryset=Tag.objects.all(), - to_field_name='slug', - label='Tag (slug)', - ) - - class Meta: - model = ConfigContext - fields = ['id', 'name', 'is_active'] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(name__icontains=value) | - Q(description__icontains=value) | - Q(data__icontains=value) - ) - - -# -# Filter for Local Config Context Data -# - -class LocalConfigContextFilterSet(django_filters.FilterSet): - local_context_data = django_filters.BooleanFilter( - method='_local_context_data', - label='Has local config context data', - ) - - def _local_context_data(self, queryset, name, value): - return queryset.exclude(local_context_data__isnull=value) - - -class ObjectChangeFilterSet(BaseFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - time = django_filters.DateTimeFromToRangeFilter() - changed_object_type = ContentTypeFilter() - user_id = django_filters.ModelMultipleChoiceFilter( - queryset=User.objects.all(), - label='User (ID)', - ) - user = django_filters.ModelMultipleChoiceFilter( - field_name='user__username', - queryset=User.objects.all(), - to_field_name='username', - label='User name', - ) - - class Meta: - model = ObjectChange - fields = [ - 'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id', - 'object_repr', - ] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(user_name__icontains=value) | - Q(object_repr__icontains=value) - ) - - -# -# Job Results -# - -class JobResultFilterSet(BaseFilterSet): - q = django_filters.CharFilter( - method='search', - label='Search', - ) - created = django_filters.DateTimeFilter() - completed = django_filters.DateTimeFilter() - status = django_filters.MultipleChoiceFilter( - choices=JobResultStatusChoices, - null_value=None - ) - - class Meta: - model = JobResult - fields = [ - 'id', 'created', 'completed', 'status', 'user', 'obj_type', 'name' - ] - - def search(self, queryset, name, value): - if not value.strip(): - return queryset - return queryset.filter( - Q(user__username__icontains=value) - ) - - -# -# ContentTypes -# - -class ContentTypeFilterSet(django_filters.FilterSet): - - class Meta: - model = ContentType - fields = ['id', 'app_label', 'model'] diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py new file mode 100644 index 000000000..1451a34c0 --- /dev/null +++ b/netbox/extras/filtersets.py @@ -0,0 +1,340 @@ +import django_filters +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.db.models import Q + +from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup +from tenancy.models import Tenant, TenantGroup +from utilities.filters import ContentTypeFilter +from utilities.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet +from virtualization.models import Cluster, ClusterGroup +from .choices import * +from .models import * + + +__all__ = ( + 'ConfigContextFilterSet', + 'ContentTypeFilterSet', + 'CustomLinkFilterSet', + 'ExportTemplateFilterSet', + 'ImageAttachmentFilterSet', + 'JournalEntryFilterSet', + 'LocalConfigContextFilterSet', + 'ObjectChangeFilterSet', + 'TagFilterSet', + 'WebhookFilterSet', +) + +EXACT_FILTER_TYPES = ( + CustomFieldTypeChoices.TYPE_BOOLEAN, + CustomFieldTypeChoices.TYPE_DATE, + CustomFieldTypeChoices.TYPE_INTEGER, + CustomFieldTypeChoices.TYPE_SELECT, +) + + +class WebhookFilterSet(BaseFilterSet): + content_types = ContentTypeFilter() + http_method = django_filters.MultipleChoiceFilter( + choices=WebhookHttpMethodChoices + ) + + class Meta: + model = Webhook + fields = [ + 'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled', + 'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path', + ] + + +class CustomFieldFilterSet(django_filters.FilterSet): + content_types = ContentTypeFilter() + + class Meta: + model = CustomField + fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight'] + + +class CustomLinkFilterSet(BaseFilterSet): + + class Meta: + model = CustomLink + fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window'] + + +class ExportTemplateFilterSet(BaseFilterSet): + + class Meta: + model = ExportTemplate + fields = ['id', 'content_type', 'name'] + + +class ImageAttachmentFilterSet(BaseFilterSet): + content_type = ContentTypeFilter() + + class Meta: + model = ImageAttachment + fields = ['id', 'content_type_id', 'object_id', 'name'] + + +class JournalEntryFilterSet(ChangeLoggedModelFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + created = django_filters.DateTimeFromToRangeFilter() + assigned_object_type = ContentTypeFilter() + created_by_id = django_filters.ModelMultipleChoiceFilter( + queryset=User.objects.all(), + label='User (ID)', + ) + created_by = django_filters.ModelMultipleChoiceFilter( + field_name='created_by__username', + queryset=User.objects.all(), + to_field_name='username', + label='User (name)', + ) + kind = django_filters.MultipleChoiceFilter( + choices=JournalEntryKindChoices + ) + + class Meta: + model = JournalEntry + fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind'] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter(comments__icontains=value) + + +class TagFilterSet(ChangeLoggedModelFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + + class Meta: + model = Tag + fields = ['id', 'name', 'slug', 'color'] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(slug__icontains=value) + ) + + +class ConfigContextFilterSet(ChangeLoggedModelFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + region_id = django_filters.ModelMultipleChoiceFilter( + field_name='regions', + queryset=Region.objects.all(), + label='Region', + ) + region = django_filters.ModelMultipleChoiceFilter( + field_name='regions__slug', + queryset=Region.objects.all(), + to_field_name='slug', + label='Region (slug)', + ) + site_group = django_filters.ModelMultipleChoiceFilter( + field_name='site_groups__slug', + queryset=SiteGroup.objects.all(), + to_field_name='slug', + label='Site group (slug)', + ) + site_group_id = django_filters.ModelMultipleChoiceFilter( + field_name='site_groups', + queryset=SiteGroup.objects.all(), + label='Site group', + ) + site_id = django_filters.ModelMultipleChoiceFilter( + field_name='sites', + queryset=Site.objects.all(), + label='Site', + ) + site = django_filters.ModelMultipleChoiceFilter( + field_name='sites__slug', + queryset=Site.objects.all(), + to_field_name='slug', + label='Site (slug)', + ) + device_type_id = django_filters.ModelMultipleChoiceFilter( + field_name='device_types', + queryset=DeviceType.objects.all(), + label='Device type', + ) + role_id = django_filters.ModelMultipleChoiceFilter( + field_name='roles', + queryset=DeviceRole.objects.all(), + label='Role', + ) + role = django_filters.ModelMultipleChoiceFilter( + field_name='roles__slug', + queryset=DeviceRole.objects.all(), + to_field_name='slug', + label='Role (slug)', + ) + platform_id = django_filters.ModelMultipleChoiceFilter( + field_name='platforms', + queryset=Platform.objects.all(), + label='Platform', + ) + platform = django_filters.ModelMultipleChoiceFilter( + field_name='platforms__slug', + queryset=Platform.objects.all(), + to_field_name='slug', + label='Platform (slug)', + ) + cluster_group_id = django_filters.ModelMultipleChoiceFilter( + field_name='cluster_groups', + queryset=ClusterGroup.objects.all(), + label='Cluster group', + ) + cluster_group = django_filters.ModelMultipleChoiceFilter( + field_name='cluster_groups__slug', + queryset=ClusterGroup.objects.all(), + to_field_name='slug', + label='Cluster group (slug)', + ) + cluster_id = django_filters.ModelMultipleChoiceFilter( + field_name='clusters', + queryset=Cluster.objects.all(), + label='Cluster', + ) + tenant_group_id = django_filters.ModelMultipleChoiceFilter( + field_name='tenant_groups', + queryset=TenantGroup.objects.all(), + label='Tenant group', + ) + tenant_group = django_filters.ModelMultipleChoiceFilter( + field_name='tenant_groups__slug', + queryset=TenantGroup.objects.all(), + to_field_name='slug', + label='Tenant group (slug)', + ) + tenant_id = django_filters.ModelMultipleChoiceFilter( + field_name='tenants', + queryset=Tenant.objects.all(), + label='Tenant', + ) + tenant = django_filters.ModelMultipleChoiceFilter( + field_name='tenants__slug', + queryset=Tenant.objects.all(), + to_field_name='slug', + label='Tenant (slug)', + ) + tag = django_filters.ModelMultipleChoiceFilter( + field_name='tags__slug', + queryset=Tag.objects.all(), + to_field_name='slug', + label='Tag (slug)', + ) + + class Meta: + model = ConfigContext + fields = ['id', 'name', 'is_active'] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(description__icontains=value) | + Q(data__icontains=value) + ) + + +# +# Filter for Local Config Context Data +# + +class LocalConfigContextFilterSet(django_filters.FilterSet): + local_context_data = django_filters.BooleanFilter( + method='_local_context_data', + label='Has local config context data', + ) + + def _local_context_data(self, queryset, name, value): + return queryset.exclude(local_context_data__isnull=value) + + +class ObjectChangeFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + time = django_filters.DateTimeFromToRangeFilter() + changed_object_type = ContentTypeFilter() + user_id = django_filters.ModelMultipleChoiceFilter( + queryset=User.objects.all(), + label='User (ID)', + ) + user = django_filters.ModelMultipleChoiceFilter( + field_name='user__username', + queryset=User.objects.all(), + to_field_name='username', + label='User name', + ) + + class Meta: + model = ObjectChange + fields = [ + 'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id', + 'object_repr', + ] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(user_name__icontains=value) | + Q(object_repr__icontains=value) + ) + + +# +# Job Results +# + +class JobResultFilterSet(BaseFilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) + created = django_filters.DateTimeFilter() + completed = django_filters.DateTimeFilter() + status = django_filters.MultipleChoiceFilter( + choices=JobResultStatusChoices, + null_value=None + ) + + class Meta: + model = JobResult + fields = [ + 'id', 'created', 'completed', 'status', 'user', 'obj_type', 'name' + ] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(user__username__icontains=value) + ) + + +# +# ContentTypes +# + +class ContentTypeFilterSet(django_filters.FilterSet): + + class Meta: + model = ContentType + fields = ['id', 'app_label', 'model'] diff --git a/netbox/extras/tests/test_filters.py b/netbox/extras/tests/test_filters.py index bb78c4daf..bd4f1c246 100644 --- a/netbox/extras/tests/test_filters.py +++ b/netbox/extras/tests/test_filters.py @@ -6,7 +6,7 @@ from django.test import TestCase from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices -from extras.filters import * +from extras.filtersets import * from extras.models import * from ipam.models import IPAddress from tenancy.models import Tenant, TenantGroup diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 4cda84d99..3f86c98d2 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -13,7 +13,7 @@ from utilities.forms import ConfirmationForm from utilities.tables import paginate_table from utilities.utils import copy_safe_request, count_related, shallow_compare_dict from utilities.views import ContentTypePermissionRequiredMixin -from . import filters, forms, tables +from . import filtersets, forms, tables from .choices import JobResultStatusChoices from .models import ConfigContext, ImageAttachment, JournalEntry, ObjectChange, JobResult, Tag, TaggedItem from .reports import get_report, get_reports, run_report @@ -28,7 +28,7 @@ class TagListView(generic.ObjectListView): queryset = Tag.objects.annotate( items=count_related(TaggedItem, 'tag') ) - filterset = filters.TagFilterSet + filterset = filtersets.TagFilterSet filterset_form = forms.TagFilterForm table = tables.TagTable @@ -94,7 +94,7 @@ class TagBulkDeleteView(generic.BulkDeleteView): class ConfigContextListView(generic.ObjectListView): queryset = ConfigContext.objects.all() - filterset = filters.ConfigContextFilterSet + filterset = filtersets.ConfigContextFilterSet filterset_form = forms.ConfigContextFilterForm table = tables.ConfigContextTable action_buttons = ('add',) @@ -127,7 +127,7 @@ class ConfigContextEditView(generic.ObjectEditView): class ConfigContextBulkEditView(generic.BulkEditView): queryset = ConfigContext.objects.all() - filterset = filters.ConfigContextFilterSet + filterset = filtersets.ConfigContextFilterSet table = tables.ConfigContextTable form = forms.ConfigContextBulkEditForm @@ -173,7 +173,7 @@ class ObjectConfigContextView(generic.ObjectView): class ObjectChangeListView(generic.ObjectListView): queryset = ObjectChange.objects.all() - filterset = filters.ObjectChangeFilterSet + filterset = filtersets.ObjectChangeFilterSet filterset_form = forms.ObjectChangeFilterForm table = tables.ObjectChangeTable template_name = 'extras/objectchange_list.html' @@ -300,7 +300,7 @@ class ImageAttachmentDeleteView(generic.ObjectDeleteView): class JournalEntryListView(generic.ObjectListView): queryset = JournalEntry.objects.all() - filterset = filters.JournalEntryFilterSet + filterset = filtersets.JournalEntryFilterSet filterset_form = forms.JournalEntryFilterForm table = tables.JournalEntryTable action_buttons = ('export',) @@ -338,14 +338,14 @@ class JournalEntryDeleteView(generic.ObjectDeleteView): class JournalEntryBulkEditView(generic.BulkEditView): queryset = JournalEntry.objects.prefetch_related('created_by') - filterset = filters.JournalEntryFilterSet + filterset = filtersets.JournalEntryFilterSet table = tables.JournalEntryTable form = forms.JournalEntryBulkEditForm class JournalEntryBulkDeleteView(generic.BulkDeleteView): queryset = JournalEntry.objects.prefetch_related('created_by') - filterset = filters.JournalEntryFilterSet + filterset = filtersets.JournalEntryFilterSet table = tables.JournalEntryTable diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index af047424f..4ecea50af 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -6,13 +6,12 @@ from django.db.models import Q from netaddr.core import AddrFormatError from dcim.models import Device, Interface, Region, Site, SiteGroup -from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from tenancy.filters import TenancyFilterSet from utilities.filters import ( ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TagFilter, TreeNodeMultipleChoiceFilter, ) -from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet +from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from virtualization.models import VirtualMachine, VMInterface from .choices import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF @@ -32,7 +31,7 @@ __all__ = ( ) -class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class VRFFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -75,7 +74,7 @@ class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, C fields = ['id', 'name', 'rd', 'enforce_unique'] -class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class RouteTargetFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -117,14 +116,14 @@ class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilt fields = ['id', 'name'] -class RIRFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class RIRFilterSet(OrganizationalModelFilterSet): class Meta: model = RIR fields = ['id', 'name', 'slug', 'is_private', 'description'] -class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class AggregateFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -174,7 +173,7 @@ class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter return queryset.none() -class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class RoleFilterSet(OrganizationalModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -185,7 +184,7 @@ class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilter fields = ['id', 'name', 'slug'] -class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class PrefixFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -370,7 +369,7 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet ) -class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -536,7 +535,7 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter return queryset.exclude(assigned_object_id__isnull=value) -class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class VLANGroupFilterSet(OrganizationalModelFilterSet): scope_type = ContentTypeFilter() region = django_filters.NumberFilter( method='filter_scope' @@ -571,7 +570,7 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedF ) -class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -667,7 +666,7 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, return queryset.get_for_virtualmachine(value) -class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet): +class ServiceFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index ec41ac364..6571c60f9 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -2,9 +2,8 @@ import django_filters from django.db.models import Q from dcim.models import Device -from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from utilities.filters import TagFilter -from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet +from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from virtualization.models import VirtualMachine from .models import Secret, SecretRole @@ -15,14 +14,14 @@ __all__ = ( ) -class SecretRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class SecretRoleFilterSet(OrganizationalModelFilterSet): class Meta: model = SecretRole fields = ['id', 'name', 'slug'] -class SecretFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class SecretFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index 8d43a3794..9cbaed170 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -1,9 +1,8 @@ import django_filters from django.db.models import Q -from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from utilities.filters import TagFilter, TreeNodeMultipleChoiceFilter -from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet +from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from .models import Tenant, TenantGroup @@ -14,7 +13,7 @@ __all__ = ( ) -class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class TenantGroupFilterSet(OrganizationalModelFilterSet): parent_id = django_filters.ModelMultipleChoiceFilter( queryset=TenantGroup.objects.all(), label='Tenant group (ID)', @@ -31,7 +30,7 @@ class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdate fields = ['id', 'name', 'slug', 'description'] -class TenantFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class TenantFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', diff --git a/netbox/utilities/filtersets.py b/netbox/utilities/filtersets.py index 0fb188d11..c3a2e8443 100644 --- a/netbox/utilities/filtersets.py +++ b/netbox/utilities/filtersets.py @@ -1,9 +1,13 @@ import django_filters from copy import deepcopy -from dcim.forms import MACAddressField +from django.contrib.contenttypes.models import ContentType from django.db import models from django_filters.utils import get_model_field, resolve_field +from dcim.forms import MACAddressField +from extras.choices import CustomFieldFilterLogicChoices +from extras.filters import CustomFieldFilter +from extras.models import CustomField from utilities.constants import ( FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP, FILTER_NUMERIC_BASED_LOOKUP_MAP @@ -11,6 +15,14 @@ from utilities.constants import ( from utilities import filters +__all__ = ( + 'BaseFilterSet', + 'ChangeLoggedModelFilterSet', + 'OrganizationalModelFilterSet', + 'PrimaryModelFilterSet', +) + + # # FilterSets # @@ -172,7 +184,43 @@ class BaseFilterSet(django_filters.FilterSet): return filters -class NameSlugSearchFilterSet(django_filters.FilterSet): +class ChangeLoggedModelFilterSet(BaseFilterSet): + created = django_filters.DateFilter() + created__gte = django_filters.DateFilter( + field_name='created', + lookup_expr='gte' + ) + created__lte = django_filters.DateFilter( + field_name='created', + lookup_expr='lte' + ) + last_updated = django_filters.DateTimeFilter() + last_updated__gte = django_filters.DateTimeFilter( + field_name='last_updated', + lookup_expr='gte' + ) + last_updated__lte = django_filters.DateTimeFilter( + field_name='last_updated', + lookup_expr='lte' + ) + + +class PrimaryModelFilterSet(ChangeLoggedModelFilterSet): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Dynamically add a Filter for each CustomField applicable to the parent model + custom_fields = CustomField.objects.filter( + content_types=ContentType.objects.get_for_model(self._meta.model) + ).exclude( + filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED + ) + for cf in custom_fields: + self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf) + + +class OrganizationalModelFilterSet(PrimaryModelFilterSet): """ A base class for adding the search method to models which only expose the `name` and `slug` fields """ diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 9ace5c49e..187061f5e 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -2,10 +2,10 @@ import django_filters from django.db.models import Q from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup -from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilterSet +from extras.filtersets import LocalConfigContextFilterSet from tenancy.filters import TenancyFilterSet from utilities.filters import MultiValueMACAddressFilter, TagFilter, TreeNodeMultipleChoiceFilter -from utilities.filtersets import BaseFilterSet, NameSlugSearchFilterSet +from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from .choices import * from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface @@ -18,21 +18,21 @@ __all__ = ( ) -class ClusterTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class ClusterTypeFilterSet(OrganizationalModelFilterSet): class Meta: model = ClusterType fields = ['id', 'name', 'slug', 'description'] -class ClusterGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet): +class ClusterGroupFilterSet(OrganizationalModelFilterSet): class Meta: model = ClusterGroup fields = ['id', 'name', 'slug', 'description'] -class ClusterFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -108,13 +108,7 @@ class ClusterFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSe ) -class VirtualMachineFilterSet( - BaseFilterSet, - LocalConfigContextFilterSet, - TenancyFilterSet, - CustomFieldModelFilterSet, - CreatedUpdatedFilterSet -): +class VirtualMachineFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet): q = django_filters.CharFilter( method='search', label='Search', @@ -235,7 +229,7 @@ class VirtualMachineFilterSet( return queryset.exclude(params) -class VMInterfaceFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): +class VMInterfaceFilterSet(PrimaryModelFilterSet): q = django_filters.CharFilter( method='search', label='Search', From d35ac1347cebd46740188f1b294ac50499f3c1ea Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 16:12:31 -0400 Subject: [PATCH 08/67] Move TagFilter to extras --- netbox/circuits/filters.py | 3 ++- netbox/dcim/filters.py | 3 ++- netbox/extras/filters.py | 16 ++++++++++++++++ netbox/ipam/filters.py | 4 ++-- netbox/secrets/filters.py | 2 +- netbox/tenancy/filters.py | 3 ++- netbox/utilities/filters.py | 16 ---------------- netbox/utilities/filtersets.py | 4 ++-- netbox/utilities/tests/test_filters.py | 3 ++- netbox/virtualization/filters.py | 3 ++- 10 files changed, 31 insertions(+), 26 deletions(-) diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py index 6ff6bb104..bb778dec7 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filters.py @@ -3,8 +3,9 @@ from django.db.models import Q from dcim.filters import CableTerminationFilterSet from dcim.models import Region, Site, SiteGroup +from extras.filters import TagFilter from tenancy.filters import TenancyFilterSet -from utilities.filters import TagFilter, TreeNodeMultipleChoiceFilter +from utilities.filters import TreeNodeMultipleChoiceFilter from utilities.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet from .choices import * from .models import * diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index a45900466..3c81da460 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -1,12 +1,13 @@ import django_filters from django.contrib.auth.models import User +from extras.filters import TagFilter from extras.filtersets import LocalConfigContextFilterSet from tenancy.filters import TenancyFilterSet from tenancy.models import Tenant from utilities.choices import ColorChoices from utilities.filters import ( - MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TagFilter, TreeNodeMultipleChoiceFilter, + MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter, ) from utilities.filtersets import ( BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet, diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index dff12aa91..3757d73f3 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -1,6 +1,7 @@ import django_filters from django.forms import DateField, IntegerField, NullBooleanField +from .models import Tag from .choices import * __all__ = ( @@ -36,3 +37,18 @@ class CustomFieldFilter(django_filters.Filter): if custom_field.type not in EXACT_FILTER_TYPES: if custom_field.filter_logic == CustomFieldFilterLogicChoices.FILTER_LOOSE: self.lookup_expr = 'icontains' + + +class TagFilter(django_filters.ModelMultipleChoiceFilter): + """ + Match on one or more assigned tags. If multiple tags are specified (e.g. ?tag=foo&tag=bar), the queryset is filtered + to objects matching all tags. + """ + def __init__(self, *args, **kwargs): + + kwargs.setdefault('field_name', 'tags__slug') + kwargs.setdefault('to_field_name', 'slug') + kwargs.setdefault('conjoined', True) + kwargs.setdefault('queryset', Tag.objects.all()) + + super().__init__(*args, **kwargs) diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 4ecea50af..db1b76f31 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -6,10 +6,10 @@ from django.db.models import Q from netaddr.core import AddrFormatError from dcim.models import Device, Interface, Region, Site, SiteGroup +from extras.filters import TagFilter from tenancy.filters import TenancyFilterSet from utilities.filters import ( - ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TagFilter, - TreeNodeMultipleChoiceFilter, + ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter, ) from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from virtualization.models import VirtualMachine, VMInterface diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py index 6571c60f9..1149fbd9b 100644 --- a/netbox/secrets/filters.py +++ b/netbox/secrets/filters.py @@ -2,7 +2,7 @@ import django_filters from django.db.models import Q from dcim.models import Device -from utilities.filters import TagFilter +from extras.filters import TagFilter from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from virtualization.models import VirtualMachine from .models import Secret, SecretRole diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filters.py index 9cbaed170..6a428f4b6 100644 --- a/netbox/tenancy/filters.py +++ b/netbox/tenancy/filters.py @@ -1,7 +1,8 @@ import django_filters from django.db.models import Q -from utilities.filters import TagFilter, TreeNodeMultipleChoiceFilter +from extras.filters import TagFilter +from utilities.filters import TreeNodeMultipleChoiceFilter from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from .models import Tenant, TenantGroup diff --git a/netbox/utilities/filters.py b/netbox/utilities/filters.py index 97608ed22..ed71afc1b 100644 --- a/netbox/utilities/filters.py +++ b/netbox/utilities/filters.py @@ -4,7 +4,6 @@ from django.conf import settings from django_filters.constants import EMPTY_VALUES from dcim.forms import MACAddressField -from extras.models import Tag def multivalue_field_factory(field_class): @@ -84,21 +83,6 @@ class NullableCharFieldFilter(django_filters.CharFilter): return qs.distinct() if self.distinct else qs -class TagFilter(django_filters.ModelMultipleChoiceFilter): - """ - Match on one or more assigned tags. If multiple tags are specified (e.g. ?tag=foo&tag=bar), the queryset is filtered - to objects matching all tags. - """ - def __init__(self, *args, **kwargs): - - kwargs.setdefault('field_name', 'tags__slug') - kwargs.setdefault('to_field_name', 'slug') - kwargs.setdefault('conjoined', True) - kwargs.setdefault('queryset', Tag.objects.all()) - - super().__init__(*args, **kwargs) - - class NumericArrayFilter(django_filters.NumberFilter): """ Filter based on the presence of an integer within an ArrayField. diff --git a/netbox/utilities/filtersets.py b/netbox/utilities/filtersets.py index c3a2e8443..f738441dd 100644 --- a/netbox/utilities/filtersets.py +++ b/netbox/utilities/filtersets.py @@ -6,7 +6,7 @@ from django_filters.utils import get_model_field, resolve_field from dcim.forms import MACAddressField from extras.choices import CustomFieldFilterLogicChoices -from extras.filters import CustomFieldFilter +from extras.filters import CustomFieldFilter, TagFilter from extras.models import CustomField from utilities.constants import ( FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP, @@ -100,7 +100,7 @@ class BaseFilterSet(django_filters.FilterSet): elif isinstance(existing_filter, ( django_filters.ModelChoiceFilter, django_filters.ModelMultipleChoiceFilter, - filters.TagFilter + TagFilter )) or existing_filter.extra.get('choices'): # These filter types support only negation lookup_map = FILTER_NEGATION_LOOKUP_MAP diff --git a/netbox/utilities/tests/test_filters.py b/netbox/utilities/tests/test_filters.py index 6d2826b70..21f020fb4 100644 --- a/netbox/utilities/tests/test_filters.py +++ b/netbox/utilities/tests/test_filters.py @@ -11,10 +11,11 @@ from dcim.filters import DeviceFilterSet, SiteFilterSet from dcim.models import ( Device, DeviceRole, DeviceType, Interface, Manufacturer, Platform, Rack, Region, Site ) +from extras.filters import TagFilter from extras.models import TaggedItem from utilities.filters import ( MACAddressFilter, MultiValueCharFilter, MultiValueDateFilter, MultiValueDateTimeFilter, MultiValueNumberFilter, - MultiValueTimeFilter, TagFilter, TreeNodeMultipleChoiceFilter, + MultiValueTimeFilter, TreeNodeMultipleChoiceFilter, ) from utilities.filtersets import BaseFilterSet diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filters.py index 187061f5e..6c2e0a48a 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filters.py @@ -2,9 +2,10 @@ import django_filters from django.db.models import Q from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup +from extras.filters import TagFilter from extras.filtersets import LocalConfigContextFilterSet from tenancy.filters import TenancyFilterSet -from utilities.filters import MultiValueMACAddressFilter, TagFilter, TreeNodeMultipleChoiceFilter +from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from .choices import * from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface From 1024782b9e0abb48f6da65f8248741227d53dbed Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 16:38:56 -0400 Subject: [PATCH 09/67] Rename FilterSet modules --- netbox/circuits/api/views.py | 12 +- netbox/circuits/{filters.py => filtersets.py} | 4 +- netbox/circuits/tests/test_filters.py | 2 +- netbox/circuits/views.py | 22 +-- netbox/dcim/api/views.py | 74 ++++---- netbox/dcim/{filters.py => filtersets.py} | 2 +- netbox/dcim/tests/test_filters.py | 2 +- netbox/dcim/views.py | 160 +++++++++--------- netbox/extras/filters.py | 1 + netbox/extras/tests/test_customfields.py | 2 +- netbox/ipam/api/views.py | 22 +-- netbox/ipam/{filters.py => filtersets.py} | 2 +- netbox/ipam/tests/test_filters.py | 2 +- netbox/ipam/views.py | 60 +++---- netbox/netbox/constants.py | 12 +- netbox/secrets/api/views.py | 6 +- netbox/secrets/{filters.py => filtersets.py} | 0 netbox/secrets/tests/test_filters.py | 2 +- netbox/secrets/views.py | 12 +- netbox/tenancy/api/views.py | 6 +- netbox/tenancy/{filters.py => filtersets.py} | 0 netbox/tenancy/tests/test_filters.py | 2 +- netbox/tenancy/views.py | 10 +- netbox/users/api/views.py | 8 +- netbox/users/{filters.py => filtersets.py} | 0 netbox/users/tests/test_filters.py | 2 +- netbox/utilities/tests/test_filters.py | 2 +- netbox/virtualization/api/views.py | 12 +- .../{filters.py => filtersets.py} | 2 +- netbox/virtualization/tests/test_filters.py | 2 +- netbox/virtualization/views.py | 22 +-- 31 files changed, 234 insertions(+), 233 deletions(-) rename netbox/circuits/{filters.py => filtersets.py} (98%) rename netbox/dcim/{filters.py => filtersets.py} (99%) rename netbox/ipam/{filters.py => filtersets.py} (99%) rename netbox/secrets/{filters.py => filtersets.py} (100%) rename netbox/tenancy/{filters.py => filtersets.py} (100%) rename netbox/users/{filters.py => filtersets.py} (100%) rename netbox/virtualization/{filters.py => filtersets.py} (99%) diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 0ea8d1973..3bceb2de0 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -1,6 +1,6 @@ from rest_framework.routers import APIRootView -from circuits import filters +from circuits import filtersets from circuits.models import * from dcim.api.views import PassThroughPortMixin from extras.api.views import CustomFieldModelViewSet @@ -26,7 +26,7 @@ class ProviderViewSet(CustomFieldModelViewSet): circuit_count=count_related(Circuit, 'provider') ) serializer_class = serializers.ProviderSerializer - filterset_class = filters.ProviderFilterSet + filterset_class = filtersets.ProviderFilterSet # @@ -38,7 +38,7 @@ class CircuitTypeViewSet(CustomFieldModelViewSet): circuit_count=count_related(Circuit, 'type') ) serializer_class = serializers.CircuitTypeSerializer - filterset_class = filters.CircuitTypeFilterSet + filterset_class = filtersets.CircuitTypeFilterSet # @@ -50,7 +50,7 @@ class CircuitViewSet(CustomFieldModelViewSet): 'type', 'tenant', 'provider', 'termination_a', 'termination_z' ).prefetch_related('tags') serializer_class = serializers.CircuitSerializer - filterset_class = filters.CircuitFilterSet + filterset_class = filtersets.CircuitFilterSet # @@ -62,7 +62,7 @@ class CircuitTerminationViewSet(PassThroughPortMixin, ModelViewSet): 'circuit', 'site', 'provider_network', 'cable' ) serializer_class = serializers.CircuitTerminationSerializer - filterset_class = filters.CircuitTerminationFilterSet + filterset_class = filtersets.CircuitTerminationFilterSet brief_prefetch_fields = ['circuit'] @@ -73,4 +73,4 @@ class CircuitTerminationViewSet(PassThroughPortMixin, ModelViewSet): class ProviderNetworkViewSet(CustomFieldModelViewSet): queryset = ProviderNetwork.objects.prefetch_related('tags') serializer_class = serializers.ProviderNetworkSerializer - filterset_class = filters.ProviderNetworkFilterSet + filterset_class = filtersets.ProviderNetworkFilterSet diff --git a/netbox/circuits/filters.py b/netbox/circuits/filtersets.py similarity index 98% rename from netbox/circuits/filters.py rename to netbox/circuits/filtersets.py index bb778dec7..cd0b720e6 100644 --- a/netbox/circuits/filters.py +++ b/netbox/circuits/filtersets.py @@ -1,10 +1,10 @@ import django_filters from django.db.models import Q -from dcim.filters import CableTerminationFilterSet +from dcim.filtersets import CableTerminationFilterSet from dcim.models import Region, Site, SiteGroup from extras.filters import TagFilter -from tenancy.filters import TenancyFilterSet +from tenancy.filtersets import TenancyFilterSet from utilities.filters import TreeNodeMultipleChoiceFilter from utilities.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet from .choices import * diff --git a/netbox/circuits/tests/test_filters.py b/netbox/circuits/tests/test_filters.py index 448e42368..40c68c73c 100644 --- a/netbox/circuits/tests/test_filters.py +++ b/netbox/circuits/tests/test_filters.py @@ -1,7 +1,7 @@ from django.test import TestCase from circuits.choices import * -from circuits.filters import * +from circuits.filtersets import * from circuits.models import * from dcim.models import Cable, Region, Site, SiteGroup from tenancy.models import Tenant, TenantGroup diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 92e53c30f..6fd0c2208 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -7,7 +7,7 @@ from netbox.views import generic from utilities.forms import ConfirmationForm from utilities.tables import paginate_table from utilities.utils import count_related -from . import filters, forms, tables +from . import filtersets, forms, tables from .choices import CircuitTerminationSideChoices from .models import * @@ -20,7 +20,7 @@ class ProviderListView(generic.ObjectListView): queryset = Provider.objects.annotate( count_circuits=count_related(Circuit, 'provider') ) - filterset = filters.ProviderFilterSet + filterset = filtersets.ProviderFilterSet filterset_form = forms.ProviderFilterForm table = tables.ProviderTable @@ -63,7 +63,7 @@ class ProviderBulkEditView(generic.BulkEditView): queryset = Provider.objects.annotate( count_circuits=count_related(Circuit, 'provider') ) - filterset = filters.ProviderFilterSet + filterset = filtersets.ProviderFilterSet table = tables.ProviderTable form = forms.ProviderBulkEditForm @@ -72,7 +72,7 @@ class ProviderBulkDeleteView(generic.BulkDeleteView): queryset = Provider.objects.annotate( count_circuits=count_related(Circuit, 'provider') ) - filterset = filters.ProviderFilterSet + filterset = filtersets.ProviderFilterSet table = tables.ProviderTable @@ -82,7 +82,7 @@ class ProviderBulkDeleteView(generic.BulkDeleteView): class ProviderNetworkListView(generic.ObjectListView): queryset = ProviderNetwork.objects.all() - filterset = filters.ProviderNetworkFilterSet + filterset = filtersets.ProviderNetworkFilterSet filterset_form = forms.ProviderNetworkFilterForm table = tables.ProviderNetworkTable @@ -125,14 +125,14 @@ class ProviderNetworkBulkImportView(generic.BulkImportView): class ProviderNetworkBulkEditView(generic.BulkEditView): queryset = ProviderNetwork.objects.all() - filterset = filters.ProviderNetworkFilterSet + filterset = filtersets.ProviderNetworkFilterSet table = tables.ProviderNetworkTable form = forms.ProviderNetworkBulkEditForm class ProviderNetworkBulkDeleteView(generic.BulkDeleteView): queryset = ProviderNetwork.objects.all() - filterset = filters.ProviderNetworkFilterSet + filterset = filtersets.ProviderNetworkFilterSet table = tables.ProviderNetworkTable @@ -183,7 +183,7 @@ class CircuitTypeBulkEditView(generic.BulkEditView): queryset = CircuitType.objects.annotate( circuit_count=count_related(Circuit, 'type') ) - filterset = filters.CircuitTypeFilterSet + filterset = filtersets.CircuitTypeFilterSet table = tables.CircuitTypeTable form = forms.CircuitTypeBulkEditForm @@ -203,7 +203,7 @@ class CircuitListView(generic.ObjectListView): queryset = Circuit.objects.prefetch_related( 'provider', 'type', 'tenant', 'termination_a', 'termination_z' ) - filterset = filters.CircuitFilterSet + filterset = filtersets.CircuitFilterSet filterset_form = forms.CircuitFilterForm table = tables.CircuitTable @@ -252,7 +252,7 @@ class CircuitBulkEditView(generic.BulkEditView): queryset = Circuit.objects.prefetch_related( 'provider', 'type', 'tenant', 'terminations' ) - filterset = filters.CircuitFilterSet + filterset = filtersets.CircuitFilterSet table = tables.CircuitTable form = forms.CircuitBulkEditForm @@ -261,7 +261,7 @@ class CircuitBulkDeleteView(generic.BulkDeleteView): queryset = Circuit.objects.prefetch_related( 'provider', 'type', 'tenant', 'terminations' ) - filterset = filters.CircuitFilterSet + filterset = filtersets.CircuitFilterSet table = tables.CircuitTable diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index cb46c1eca..9d402227f 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -16,7 +16,7 @@ from rest_framework.routers import APIRootView from rest_framework.viewsets import GenericViewSet, ViewSet from circuits.models import Circuit -from dcim import filters +from dcim import filtersets from dcim.models import * from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet from ipam.models import Prefix, VLAN @@ -103,7 +103,7 @@ class RegionViewSet(CustomFieldModelViewSet): cumulative=True ) serializer_class = serializers.RegionSerializer - filterset_class = filters.RegionFilterSet + filterset_class = filtersets.RegionFilterSet # @@ -119,7 +119,7 @@ class SiteGroupViewSet(CustomFieldModelViewSet): cumulative=True ) serializer_class = serializers.SiteGroupSerializer - filterset_class = filters.SiteGroupFilterSet + filterset_class = filtersets.SiteGroupFilterSet # @@ -138,7 +138,7 @@ class SiteViewSet(CustomFieldModelViewSet): virtualmachine_count=count_related(VirtualMachine, 'cluster__site') ) serializer_class = serializers.SiteSerializer - filterset_class = filters.SiteFilterSet + filterset_class = filtersets.SiteFilterSet # @@ -160,7 +160,7 @@ class LocationViewSet(CustomFieldModelViewSet): cumulative=True ).prefetch_related('site') serializer_class = serializers.LocationSerializer - filterset_class = filters.LocationFilterSet + filterset_class = filtersets.LocationFilterSet # @@ -172,7 +172,7 @@ class RackRoleViewSet(CustomFieldModelViewSet): rack_count=count_related(Rack, 'role') ) serializer_class = serializers.RackRoleSerializer - filterset_class = filters.RackRoleFilterSet + filterset_class = filtersets.RackRoleFilterSet # @@ -187,7 +187,7 @@ class RackViewSet(CustomFieldModelViewSet): powerfeed_count=count_related(PowerFeed, 'rack') ) serializer_class = serializers.RackSerializer - filterset_class = filters.RackFilterSet + filterset_class = filtersets.RackFilterSet @swagger_auto_schema( responses={200: serializers.RackUnitSerializer(many=True)}, @@ -244,7 +244,7 @@ class RackViewSet(CustomFieldModelViewSet): class RackReservationViewSet(ModelViewSet): queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant') serializer_class = serializers.RackReservationSerializer - filterset_class = filters.RackReservationFilterSet + filterset_class = filtersets.RackReservationFilterSet # Assign user from request def perform_create(self, serializer): @@ -262,7 +262,7 @@ class ManufacturerViewSet(CustomFieldModelViewSet): platform_count=count_related(Platform, 'manufacturer') ) serializer_class = serializers.ManufacturerSerializer - filterset_class = filters.ManufacturerFilterSet + filterset_class = filtersets.ManufacturerFilterSet # @@ -274,7 +274,7 @@ class DeviceTypeViewSet(CustomFieldModelViewSet): device_count=count_related(Device, 'device_type') ) serializer_class = serializers.DeviceTypeSerializer - filterset_class = filters.DeviceTypeFilterSet + filterset_class = filtersets.DeviceTypeFilterSet brief_prefetch_fields = ['manufacturer'] @@ -285,49 +285,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet): class ConsolePortTemplateViewSet(ModelViewSet): queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.ConsolePortTemplateSerializer - filterset_class = filters.ConsolePortTemplateFilterSet + filterset_class = filtersets.ConsolePortTemplateFilterSet class ConsoleServerPortTemplateViewSet(ModelViewSet): queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.ConsoleServerPortTemplateSerializer - filterset_class = filters.ConsoleServerPortTemplateFilterSet + filterset_class = filtersets.ConsoleServerPortTemplateFilterSet class PowerPortTemplateViewSet(ModelViewSet): queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.PowerPortTemplateSerializer - filterset_class = filters.PowerPortTemplateFilterSet + filterset_class = filtersets.PowerPortTemplateFilterSet class PowerOutletTemplateViewSet(ModelViewSet): queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.PowerOutletTemplateSerializer - filterset_class = filters.PowerOutletTemplateFilterSet + filterset_class = filtersets.PowerOutletTemplateFilterSet class InterfaceTemplateViewSet(ModelViewSet): queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.InterfaceTemplateSerializer - filterset_class = filters.InterfaceTemplateFilterSet + filterset_class = filtersets.InterfaceTemplateFilterSet class FrontPortTemplateViewSet(ModelViewSet): queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.FrontPortTemplateSerializer - filterset_class = filters.FrontPortTemplateFilterSet + filterset_class = filtersets.FrontPortTemplateFilterSet class RearPortTemplateViewSet(ModelViewSet): queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.RearPortTemplateSerializer - filterset_class = filters.RearPortTemplateFilterSet + filterset_class = filtersets.RearPortTemplateFilterSet class DeviceBayTemplateViewSet(ModelViewSet): queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.DeviceBayTemplateSerializer - filterset_class = filters.DeviceBayTemplateFilterSet + filterset_class = filtersets.DeviceBayTemplateFilterSet # @@ -340,7 +340,7 @@ class DeviceRoleViewSet(CustomFieldModelViewSet): virtualmachine_count=count_related(VirtualMachine, 'role') ) serializer_class = serializers.DeviceRoleSerializer - filterset_class = filters.DeviceRoleFilterSet + filterset_class = filtersets.DeviceRoleFilterSet # @@ -353,7 +353,7 @@ class PlatformViewSet(CustomFieldModelViewSet): virtualmachine_count=count_related(VirtualMachine, 'platform') ) serializer_class = serializers.PlatformSerializer - filterset_class = filters.PlatformFilterSet + filterset_class = filtersets.PlatformFilterSet # @@ -365,7 +365,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet): 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay', 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags', ) - filterset_class = filters.DeviceFilterSet + filterset_class = filtersets.DeviceFilterSet def get_serializer_class(self): """ @@ -510,7 +510,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet): class ConsolePortViewSet(PathEndpointMixin, ModelViewSet): queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags') serializer_class = serializers.ConsolePortSerializer - filterset_class = filters.ConsolePortFilterSet + filterset_class = filtersets.ConsolePortFilterSet brief_prefetch_fields = ['device'] @@ -519,21 +519,21 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet): 'device', '_path__destination', 'cable', '_cable_peer', 'tags' ) serializer_class = serializers.ConsoleServerPortSerializer - filterset_class = filters.ConsoleServerPortFilterSet + filterset_class = filtersets.ConsoleServerPortFilterSet brief_prefetch_fields = ['device'] class PowerPortViewSet(PathEndpointMixin, ModelViewSet): queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags') serializer_class = serializers.PowerPortSerializer - filterset_class = filters.PowerPortFilterSet + filterset_class = filtersets.PowerPortFilterSet brief_prefetch_fields = ['device'] class PowerOutletViewSet(PathEndpointMixin, ModelViewSet): queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags') serializer_class = serializers.PowerOutletSerializer - filterset_class = filters.PowerOutletFilterSet + filterset_class = filtersets.PowerOutletFilterSet brief_prefetch_fields = ['device'] @@ -542,35 +542,35 @@ class InterfaceViewSet(PathEndpointMixin, ModelViewSet): 'device', 'parent', 'lag', '_path__destination', 'cable', '_cable_peer', 'ip_addresses', 'tags' ) serializer_class = serializers.InterfaceSerializer - filterset_class = filters.InterfaceFilterSet + filterset_class = filtersets.InterfaceFilterSet brief_prefetch_fields = ['device'] class FrontPortViewSet(PassThroughPortMixin, ModelViewSet): queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags') serializer_class = serializers.FrontPortSerializer - filterset_class = filters.FrontPortFilterSet + filterset_class = filtersets.FrontPortFilterSet brief_prefetch_fields = ['device'] class RearPortViewSet(PassThroughPortMixin, ModelViewSet): queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags') serializer_class = serializers.RearPortSerializer - filterset_class = filters.RearPortFilterSet + filterset_class = filtersets.RearPortFilterSet brief_prefetch_fields = ['device'] class DeviceBayViewSet(ModelViewSet): queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags') serializer_class = serializers.DeviceBaySerializer - filterset_class = filters.DeviceBayFilterSet + filterset_class = filtersets.DeviceBayFilterSet brief_prefetch_fields = ['device'] class InventoryItemViewSet(ModelViewSet): queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags') serializer_class = serializers.InventoryItemSerializer - filterset_class = filters.InventoryItemFilterSet + filterset_class = filtersets.InventoryItemFilterSet brief_prefetch_fields = ['device'] @@ -583,7 +583,7 @@ class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet): _path__destination_id__isnull=False ) serializer_class = serializers.ConsolePortSerializer - filterset_class = filters.ConsoleConnectionFilterSet + filterset_class = filtersets.ConsoleConnectionFilterSet class PowerConnectionViewSet(ListModelMixin, GenericViewSet): @@ -591,7 +591,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet): _path__destination_id__isnull=False ) serializer_class = serializers.PowerPortSerializer - filterset_class = filters.PowerConnectionFilterSet + filterset_class = filtersets.PowerConnectionFilterSet class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet): @@ -603,7 +603,7 @@ class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet): pk__lt=F('_path__destination_id') ) serializer_class = serializers.InterfaceConnectionSerializer - filterset_class = filters.InterfaceConnectionFilterSet + filterset_class = filtersets.InterfaceConnectionFilterSet # @@ -616,7 +616,7 @@ class CableViewSet(ModelViewSet): 'termination_a', 'termination_b' ) serializer_class = serializers.CableSerializer - filterset_class = filters.CableFilterSet + filterset_class = filtersets.CableFilterSet # @@ -628,7 +628,7 @@ class VirtualChassisViewSet(ModelViewSet): member_count=count_related(Device, 'virtual_chassis') ) serializer_class = serializers.VirtualChassisSerializer - filterset_class = filters.VirtualChassisFilterSet + filterset_class = filtersets.VirtualChassisFilterSet brief_prefetch_fields = ['master'] @@ -643,7 +643,7 @@ class PowerPanelViewSet(ModelViewSet): powerfeed_count=count_related(PowerFeed, 'power_panel') ) serializer_class = serializers.PowerPanelSerializer - filterset_class = filters.PowerPanelFilterSet + filterset_class = filtersets.PowerPanelFilterSet # @@ -655,7 +655,7 @@ class PowerFeedViewSet(PathEndpointMixin, CustomFieldModelViewSet): 'power_panel', 'rack', '_path__destination', 'cable', '_cable_peer', 'tags' ) serializer_class = serializers.PowerFeedSerializer - filterset_class = filters.PowerFeedFilterSet + filterset_class = filtersets.PowerFeedFilterSet # diff --git a/netbox/dcim/filters.py b/netbox/dcim/filtersets.py similarity index 99% rename from netbox/dcim/filters.py rename to netbox/dcim/filtersets.py index 3c81da460..62f59c352 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filtersets.py @@ -3,7 +3,7 @@ from django.contrib.auth.models import User from extras.filters import TagFilter from extras.filtersets import LocalConfigContextFilterSet -from tenancy.filters import TenancyFilterSet +from tenancy.filtersets import TenancyFilterSet from tenancy.models import Tenant from utilities.choices import ColorChoices from utilities.filters import ( diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py index 632a10c46..5e7b84463 100644 --- a/netbox/dcim/tests/test_filters.py +++ b/netbox/dcim/tests/test_filters.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import User from django.test import TestCase from dcim.choices import * -from dcim.filters import * +from dcim.filtersets import * from dcim.models import * from ipam.models import IPAddress from tenancy.models import Tenant, TenantGroup diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 69e043425..72438fc9a 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -24,7 +24,7 @@ from utilities.tables import paginate_table from utilities.utils import csv_format, count_related from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin from virtualization.models import VirtualMachine -from . import filters, forms, tables +from . import filtersets, forms, tables from .choices import DeviceFaceChoices from .constants import NONCONNECTABLE_IFACE_TYPES from .models import ( @@ -107,7 +107,7 @@ class RegionListView(generic.ObjectListView): 'site_count', cumulative=True ) - filterset = filters.RegionFilterSet + filterset = filtersets.RegionFilterSet filterset_form = forms.RegionFilterForm table = tables.RegionTable @@ -163,7 +163,7 @@ class RegionBulkEditView(generic.BulkEditView): 'site_count', cumulative=True ) - filterset = filters.RegionFilterSet + filterset = filtersets.RegionFilterSet table = tables.RegionTable form = forms.RegionBulkEditForm @@ -176,7 +176,7 @@ class RegionBulkDeleteView(generic.BulkDeleteView): 'site_count', cumulative=True ) - filterset = filters.RegionFilterSet + filterset = filtersets.RegionFilterSet table = tables.RegionTable @@ -192,7 +192,7 @@ class SiteGroupListView(generic.ObjectListView): 'site_count', cumulative=True ) - filterset = filters.SiteGroupFilterSet + filterset = filtersets.SiteGroupFilterSet filterset_form = forms.SiteGroupFilterForm table = tables.SiteGroupTable @@ -248,7 +248,7 @@ class SiteGroupBulkEditView(generic.BulkEditView): 'site_count', cumulative=True ) - filterset = filters.SiteGroupFilterSet + filterset = filtersets.SiteGroupFilterSet table = tables.SiteGroupTable form = forms.SiteGroupBulkEditForm @@ -261,7 +261,7 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView): 'site_count', cumulative=True ) - filterset = filters.SiteGroupFilterSet + filterset = filtersets.SiteGroupFilterSet table = tables.SiteGroupTable @@ -271,7 +271,7 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView): class SiteListView(generic.ObjectListView): queryset = Site.objects.all() - filterset = filters.SiteFilterSet + filterset = filtersets.SiteFilterSet filterset_form = forms.SiteFilterForm table = tables.SiteTable @@ -326,14 +326,14 @@ class SiteBulkImportView(generic.BulkImportView): class SiteBulkEditView(generic.BulkEditView): queryset = Site.objects.prefetch_related('region', 'tenant') - filterset = filters.SiteFilterSet + filterset = filtersets.SiteFilterSet table = tables.SiteTable form = forms.SiteBulkEditForm class SiteBulkDeleteView(generic.BulkDeleteView): queryset = Site.objects.prefetch_related('region', 'tenant') - filterset = filters.SiteFilterSet + filterset = filtersets.SiteFilterSet table = tables.SiteTable @@ -355,7 +355,7 @@ class LocationListView(generic.ObjectListView): 'rack_count', cumulative=True ) - filterset = filters.LocationFilterSet + filterset = filtersets.LocationFilterSet filterset_form = forms.LocationFilterForm table = tables.LocationTable @@ -414,7 +414,7 @@ class LocationBulkEditView(generic.BulkEditView): 'rack_count', cumulative=True ).prefetch_related('site') - filterset = filters.LocationFilterSet + filterset = filtersets.LocationFilterSet table = tables.LocationTable form = forms.LocationBulkEditForm @@ -427,7 +427,7 @@ class LocationBulkDeleteView(generic.BulkDeleteView): 'rack_count', cumulative=True ).prefetch_related('site') - filterset = filters.LocationFilterSet + filterset = filtersets.LocationFilterSet table = tables.LocationTable @@ -478,7 +478,7 @@ class RackRoleBulkEditView(generic.BulkEditView): queryset = RackRole.objects.annotate( rack_count=count_related(Rack, 'role') ) - filterset = filters.RackRoleFilterSet + filterset = filtersets.RackRoleFilterSet table = tables.RackRoleTable form = forms.RackRoleBulkEditForm @@ -500,7 +500,7 @@ class RackListView(generic.ObjectListView): ).annotate( device_count=count_related(Device, 'rack') ) - filterset = filters.RackFilterSet + filterset = filtersets.RackFilterSet filterset_form = forms.RackFilterForm table = tables.RackDetailTable @@ -513,7 +513,7 @@ class RackElevationListView(generic.ObjectListView): def get(self, request): - racks = filters.RackFilterSet(request.GET, self.queryset).qs + racks = filtersets.RackFilterSet(request.GET, self.queryset).qs total_count = racks.count() # Determine ordering @@ -602,14 +602,14 @@ class RackBulkImportView(generic.BulkImportView): class RackBulkEditView(generic.BulkEditView): queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role') - filterset = filters.RackFilterSet + filterset = filtersets.RackFilterSet table = tables.RackTable form = forms.RackBulkEditForm class RackBulkDeleteView(generic.BulkDeleteView): queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role') - filterset = filters.RackFilterSet + filterset = filtersets.RackFilterSet table = tables.RackTable @@ -619,7 +619,7 @@ class RackBulkDeleteView(generic.BulkDeleteView): class RackReservationListView(generic.ObjectListView): queryset = RackReservation.objects.all() - filterset = filters.RackReservationFilterSet + filterset = filtersets.RackReservationFilterSet filterset_form = forms.RackReservationFilterForm table = tables.RackReservationTable @@ -662,14 +662,14 @@ class RackReservationImportView(generic.BulkImportView): class RackReservationBulkEditView(generic.BulkEditView): queryset = RackReservation.objects.prefetch_related('rack', 'user') - filterset = filters.RackReservationFilterSet + filterset = filtersets.RackReservationFilterSet table = tables.RackReservationTable form = forms.RackReservationBulkEditForm class RackReservationBulkDeleteView(generic.BulkDeleteView): queryset = RackReservation.objects.prefetch_related('rack', 'user') - filterset = filters.RackReservationFilterSet + filterset = filtersets.RackReservationFilterSet table = tables.RackReservationTable @@ -722,7 +722,7 @@ class ManufacturerBulkEditView(generic.BulkEditView): queryset = Manufacturer.objects.annotate( devicetype_count=count_related(DeviceType, 'manufacturer') ) - filterset = filters.ManufacturerFilterSet + filterset = filtersets.ManufacturerFilterSet table = tables.ManufacturerTable form = forms.ManufacturerBulkEditForm @@ -742,7 +742,7 @@ class DeviceTypeListView(generic.ObjectListView): queryset = DeviceType.objects.prefetch_related('manufacturer').annotate( instance_count=count_related(Device, 'device_type') ) - filterset = filters.DeviceTypeFilterSet + filterset = filtersets.DeviceTypeFilterSet filterset_form = forms.DeviceTypeFilterForm table = tables.DeviceTypeTable @@ -848,7 +848,7 @@ class DeviceTypeBulkEditView(generic.BulkEditView): queryset = DeviceType.objects.prefetch_related('manufacturer').annotate( instance_count=count_related(Device, 'device_type') ) - filterset = filters.DeviceTypeFilterSet + filterset = filtersets.DeviceTypeFilterSet table = tables.DeviceTypeTable form = forms.DeviceTypeBulkEditForm @@ -857,7 +857,7 @@ class DeviceTypeBulkDeleteView(generic.BulkDeleteView): queryset = DeviceType.objects.prefetch_related('manufacturer').annotate( instance_count=count_related(Device, 'device_type') ) - filterset = filters.DeviceTypeFilterSet + filterset = filtersets.DeviceTypeFilterSet table = tables.DeviceTypeTable @@ -1190,7 +1190,7 @@ class DeviceRoleBulkEditView(generic.BulkEditView): device_count=count_related(Device, 'device_role'), vm_count=count_related(VirtualMachine, 'role') ) - filterset = filters.DeviceRoleFilterSet + filterset = filtersets.DeviceRoleFilterSet table = tables.DeviceRoleTable form = forms.DeviceRoleBulkEditForm @@ -1249,7 +1249,7 @@ class PlatformBulkImportView(generic.BulkImportView): class PlatformBulkEditView(generic.BulkEditView): queryset = Platform.objects.all() - filterset = filters.PlatformFilterSet + filterset = filtersets.PlatformFilterSet table = tables.PlatformTable form = forms.PlatformBulkEditForm @@ -1265,7 +1265,7 @@ class PlatformBulkDeleteView(generic.BulkDeleteView): class DeviceListView(generic.ObjectListView): queryset = Device.objects.all() - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet filterset_form = forms.DeviceFilterForm table = tables.DeviceTable template_name = 'dcim/device_list.html' @@ -1600,14 +1600,14 @@ class ChildDeviceBulkImportView(generic.BulkImportView): class DeviceBulkEditView(generic.BulkEditView): queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable form = forms.DeviceBulkEditForm class DeviceBulkDeleteView(generic.BulkDeleteView): queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable @@ -1617,7 +1617,7 @@ class DeviceBulkDeleteView(generic.BulkDeleteView): class ConsolePortListView(generic.ObjectListView): queryset = ConsolePort.objects.all() - filterset = filters.ConsolePortFilterSet + filterset = filtersets.ConsolePortFilterSet filterset_form = forms.ConsolePortFilterForm table = tables.ConsolePortTable action_buttons = ('import', 'export') @@ -1652,7 +1652,7 @@ class ConsolePortBulkImportView(generic.BulkImportView): class ConsolePortBulkEditView(generic.BulkEditView): queryset = ConsolePort.objects.all() - filterset = filters.ConsolePortFilterSet + filterset = filtersets.ConsolePortFilterSet table = tables.ConsolePortTable form = forms.ConsolePortBulkEditForm @@ -1667,7 +1667,7 @@ class ConsolePortBulkDisconnectView(BulkDisconnectView): class ConsolePortBulkDeleteView(generic.BulkDeleteView): queryset = ConsolePort.objects.all() - filterset = filters.ConsolePortFilterSet + filterset = filtersets.ConsolePortFilterSet table = tables.ConsolePortTable @@ -1677,7 +1677,7 @@ class ConsolePortBulkDeleteView(generic.BulkDeleteView): class ConsoleServerPortListView(generic.ObjectListView): queryset = ConsoleServerPort.objects.all() - filterset = filters.ConsoleServerPortFilterSet + filterset = filtersets.ConsoleServerPortFilterSet filterset_form = forms.ConsoleServerPortFilterForm table = tables.ConsoleServerPortTable action_buttons = ('import', 'export') @@ -1712,7 +1712,7 @@ class ConsoleServerPortBulkImportView(generic.BulkImportView): class ConsoleServerPortBulkEditView(generic.BulkEditView): queryset = ConsoleServerPort.objects.all() - filterset = filters.ConsoleServerPortFilterSet + filterset = filtersets.ConsoleServerPortFilterSet table = tables.ConsoleServerPortTable form = forms.ConsoleServerPortBulkEditForm @@ -1727,7 +1727,7 @@ class ConsoleServerPortBulkDisconnectView(BulkDisconnectView): class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView): queryset = ConsoleServerPort.objects.all() - filterset = filters.ConsoleServerPortFilterSet + filterset = filtersets.ConsoleServerPortFilterSet table = tables.ConsoleServerPortTable @@ -1737,7 +1737,7 @@ class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView): class PowerPortListView(generic.ObjectListView): queryset = PowerPort.objects.all() - filterset = filters.PowerPortFilterSet + filterset = filtersets.PowerPortFilterSet filterset_form = forms.PowerPortFilterForm table = tables.PowerPortTable action_buttons = ('import', 'export') @@ -1772,7 +1772,7 @@ class PowerPortBulkImportView(generic.BulkImportView): class PowerPortBulkEditView(generic.BulkEditView): queryset = PowerPort.objects.all() - filterset = filters.PowerPortFilterSet + filterset = filtersets.PowerPortFilterSet table = tables.PowerPortTable form = forms.PowerPortBulkEditForm @@ -1787,7 +1787,7 @@ class PowerPortBulkDisconnectView(BulkDisconnectView): class PowerPortBulkDeleteView(generic.BulkDeleteView): queryset = PowerPort.objects.all() - filterset = filters.PowerPortFilterSet + filterset = filtersets.PowerPortFilterSet table = tables.PowerPortTable @@ -1797,7 +1797,7 @@ class PowerPortBulkDeleteView(generic.BulkDeleteView): class PowerOutletListView(generic.ObjectListView): queryset = PowerOutlet.objects.all() - filterset = filters.PowerOutletFilterSet + filterset = filtersets.PowerOutletFilterSet filterset_form = forms.PowerOutletFilterForm table = tables.PowerOutletTable action_buttons = ('import', 'export') @@ -1832,7 +1832,7 @@ class PowerOutletBulkImportView(generic.BulkImportView): class PowerOutletBulkEditView(generic.BulkEditView): queryset = PowerOutlet.objects.all() - filterset = filters.PowerOutletFilterSet + filterset = filtersets.PowerOutletFilterSet table = tables.PowerOutletTable form = forms.PowerOutletBulkEditForm @@ -1847,7 +1847,7 @@ class PowerOutletBulkDisconnectView(BulkDisconnectView): class PowerOutletBulkDeleteView(generic.BulkDeleteView): queryset = PowerOutlet.objects.all() - filterset = filters.PowerOutletFilterSet + filterset = filtersets.PowerOutletFilterSet table = tables.PowerOutletTable @@ -1857,7 +1857,7 @@ class PowerOutletBulkDeleteView(generic.BulkDeleteView): class InterfaceListView(generic.ObjectListView): queryset = Interface.objects.all() - filterset = filters.InterfaceFilterSet + filterset = filtersets.InterfaceFilterSet filterset_form = forms.InterfaceFilterForm table = tables.InterfaceTable action_buttons = ('import', 'export') @@ -1927,7 +1927,7 @@ class InterfaceBulkImportView(generic.BulkImportView): class InterfaceBulkEditView(generic.BulkEditView): queryset = Interface.objects.all() - filterset = filters.InterfaceFilterSet + filterset = filtersets.InterfaceFilterSet table = tables.InterfaceTable form = forms.InterfaceBulkEditForm @@ -1942,7 +1942,7 @@ class InterfaceBulkDisconnectView(BulkDisconnectView): class InterfaceBulkDeleteView(generic.BulkDeleteView): queryset = Interface.objects.all() - filterset = filters.InterfaceFilterSet + filterset = filtersets.InterfaceFilterSet table = tables.InterfaceTable @@ -1952,7 +1952,7 @@ class InterfaceBulkDeleteView(generic.BulkDeleteView): class FrontPortListView(generic.ObjectListView): queryset = FrontPort.objects.all() - filterset = filters.FrontPortFilterSet + filterset = filtersets.FrontPortFilterSet filterset_form = forms.FrontPortFilterForm table = tables.FrontPortTable action_buttons = ('import', 'export') @@ -1987,7 +1987,7 @@ class FrontPortBulkImportView(generic.BulkImportView): class FrontPortBulkEditView(generic.BulkEditView): queryset = FrontPort.objects.all() - filterset = filters.FrontPortFilterSet + filterset = filtersets.FrontPortFilterSet table = tables.FrontPortTable form = forms.FrontPortBulkEditForm @@ -2002,7 +2002,7 @@ class FrontPortBulkDisconnectView(BulkDisconnectView): class FrontPortBulkDeleteView(generic.BulkDeleteView): queryset = FrontPort.objects.all() - filterset = filters.FrontPortFilterSet + filterset = filtersets.FrontPortFilterSet table = tables.FrontPortTable @@ -2012,7 +2012,7 @@ class FrontPortBulkDeleteView(generic.BulkDeleteView): class RearPortListView(generic.ObjectListView): queryset = RearPort.objects.all() - filterset = filters.RearPortFilterSet + filterset = filtersets.RearPortFilterSet filterset_form = forms.RearPortFilterForm table = tables.RearPortTable action_buttons = ('import', 'export') @@ -2047,7 +2047,7 @@ class RearPortBulkImportView(generic.BulkImportView): class RearPortBulkEditView(generic.BulkEditView): queryset = RearPort.objects.all() - filterset = filters.RearPortFilterSet + filterset = filtersets.RearPortFilterSet table = tables.RearPortTable form = forms.RearPortBulkEditForm @@ -2062,7 +2062,7 @@ class RearPortBulkDisconnectView(BulkDisconnectView): class RearPortBulkDeleteView(generic.BulkDeleteView): queryset = RearPort.objects.all() - filterset = filters.RearPortFilterSet + filterset = filtersets.RearPortFilterSet table = tables.RearPortTable @@ -2072,7 +2072,7 @@ class RearPortBulkDeleteView(generic.BulkDeleteView): class DeviceBayListView(generic.ObjectListView): queryset = DeviceBay.objects.all() - filterset = filters.DeviceBayFilterSet + filterset = filtersets.DeviceBayFilterSet filterset_form = forms.DeviceBayFilterForm table = tables.DeviceBayTable action_buttons = ('import', 'export') @@ -2172,7 +2172,7 @@ class DeviceBayBulkImportView(generic.BulkImportView): class DeviceBayBulkEditView(generic.BulkEditView): queryset = DeviceBay.objects.all() - filterset = filters.DeviceBayFilterSet + filterset = filtersets.DeviceBayFilterSet table = tables.DeviceBayTable form = forms.DeviceBayBulkEditForm @@ -2183,7 +2183,7 @@ class DeviceBayBulkRenameView(generic.BulkRenameView): class DeviceBayBulkDeleteView(generic.BulkDeleteView): queryset = DeviceBay.objects.all() - filterset = filters.DeviceBayFilterSet + filterset = filtersets.DeviceBayFilterSet table = tables.DeviceBayTable @@ -2193,7 +2193,7 @@ class DeviceBayBulkDeleteView(generic.BulkDeleteView): class InventoryItemListView(generic.ObjectListView): queryset = InventoryItem.objects.all() - filterset = filters.InventoryItemFilterSet + filterset = filtersets.InventoryItemFilterSet filterset_form = forms.InventoryItemFilterForm table = tables.InventoryItemTable action_buttons = ('import', 'export') @@ -2227,7 +2227,7 @@ class InventoryItemBulkImportView(generic.BulkImportView): class InventoryItemBulkEditView(generic.BulkEditView): queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer') - filterset = filters.InventoryItemFilterSet + filterset = filtersets.InventoryItemFilterSet table = tables.InventoryItemTable form = forms.InventoryItemBulkEditForm @@ -2252,7 +2252,7 @@ class DeviceBulkAddConsolePortView(generic.BulkComponentCreateView): form = forms.ConsolePortBulkCreateForm queryset = ConsolePort.objects.all() model_form = forms.ConsolePortForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2263,7 +2263,7 @@ class DeviceBulkAddConsoleServerPortView(generic.BulkComponentCreateView): form = forms.ConsoleServerPortBulkCreateForm queryset = ConsoleServerPort.objects.all() model_form = forms.ConsoleServerPortForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2274,7 +2274,7 @@ class DeviceBulkAddPowerPortView(generic.BulkComponentCreateView): form = forms.PowerPortBulkCreateForm queryset = PowerPort.objects.all() model_form = forms.PowerPortForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2285,7 +2285,7 @@ class DeviceBulkAddPowerOutletView(generic.BulkComponentCreateView): form = forms.PowerOutletBulkCreateForm queryset = PowerOutlet.objects.all() model_form = forms.PowerOutletForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2296,7 +2296,7 @@ class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView): form = forms.InterfaceBulkCreateForm queryset = Interface.objects.all() model_form = forms.InterfaceForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2307,7 +2307,7 @@ class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView): # form = forms.FrontPortBulkCreateForm # queryset = FrontPort.objects.all() # model_form = forms.FrontPortForm -# filterset = filters.DeviceFilterSet +# filterset = filtersets.DeviceFilterSet # table = tables.DeviceTable # default_return_url = 'dcim:device_list' @@ -2318,7 +2318,7 @@ class DeviceBulkAddRearPortView(generic.BulkComponentCreateView): form = forms.RearPortBulkCreateForm queryset = RearPort.objects.all() model_form = forms.RearPortForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2329,7 +2329,7 @@ class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView): form = forms.DeviceBayBulkCreateForm queryset = DeviceBay.objects.all() model_form = forms.DeviceBayForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2340,7 +2340,7 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView): form = forms.InventoryItemBulkCreateForm queryset = InventoryItem.objects.all() model_form = forms.InventoryItemForm - filterset = filters.DeviceFilterSet + filterset = filtersets.DeviceFilterSet table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -2351,7 +2351,7 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView): class CableListView(generic.ObjectListView): queryset = Cable.objects.all() - filterset = filters.CableFilterSet + filterset = filtersets.CableFilterSet filterset_form = forms.CableFilterForm table = tables.CableTable action_buttons = ('import', 'export') @@ -2484,14 +2484,14 @@ class CableBulkImportView(generic.BulkImportView): class CableBulkEditView(generic.BulkEditView): queryset = Cable.objects.prefetch_related('termination_a', 'termination_b') - filterset = filters.CableFilterSet + filterset = filtersets.CableFilterSet table = tables.CableTable form = forms.CableBulkEditForm class CableBulkDeleteView(generic.BulkDeleteView): queryset = Cable.objects.prefetch_related('termination_a', 'termination_b') - filterset = filters.CableFilterSet + filterset = filtersets.CableFilterSet table = tables.CableTable @@ -2501,7 +2501,7 @@ class CableBulkDeleteView(generic.BulkDeleteView): class ConsoleConnectionsListView(generic.ObjectListView): queryset = ConsolePort.objects.filter(_path__isnull=False).order_by('device') - filterset = filters.ConsoleConnectionFilterSet + filterset = filtersets.ConsoleConnectionFilterSet filterset_form = forms.ConsoleConnectionFilterForm table = tables.ConsoleConnectionTable template_name = 'dcim/connections_list.html' @@ -2531,7 +2531,7 @@ class ConsoleConnectionsListView(generic.ObjectListView): class PowerConnectionsListView(generic.ObjectListView): queryset = PowerPort.objects.filter(_path__isnull=False).order_by('device') - filterset = filters.PowerConnectionFilterSet + filterset = filtersets.PowerConnectionFilterSet filterset_form = forms.PowerConnectionFilterForm table = tables.PowerConnectionTable template_name = 'dcim/connections_list.html' @@ -2565,7 +2565,7 @@ class InterfaceConnectionsListView(generic.ObjectListView): _path__isnull=False, pk__lt=F('_path__destination_id') ).order_by('device') - filterset = filters.InterfaceConnectionFilterSet + filterset = filtersets.InterfaceConnectionFilterSet filterset_form = forms.InterfaceConnectionFilterForm table = tables.InterfaceConnectionTable template_name = 'dcim/connections_list.html' @@ -2604,7 +2604,7 @@ class VirtualChassisListView(generic.ObjectListView): member_count=count_related(Device, 'virtual_chassis') ) table = tables.VirtualChassisTable - filterset = filters.VirtualChassisFilterSet + filterset = filtersets.VirtualChassisFilterSet filterset_form = forms.VirtualChassisFilterForm @@ -2812,14 +2812,14 @@ class VirtualChassisBulkImportView(generic.BulkImportView): class VirtualChassisBulkEditView(generic.BulkEditView): queryset = VirtualChassis.objects.all() - filterset = filters.VirtualChassisFilterSet + filterset = filtersets.VirtualChassisFilterSet table = tables.VirtualChassisTable form = forms.VirtualChassisBulkEditForm class VirtualChassisBulkDeleteView(generic.BulkDeleteView): queryset = VirtualChassis.objects.all() - filterset = filters.VirtualChassisFilterSet + filterset = filtersets.VirtualChassisFilterSet table = tables.VirtualChassisTable @@ -2833,7 +2833,7 @@ class PowerPanelListView(generic.ObjectListView): ).annotate( powerfeed_count=count_related(PowerFeed, 'power_panel') ) - filterset = filters.PowerPanelFilterSet + filterset = filtersets.PowerPanelFilterSet filterset_form = forms.PowerPanelFilterForm table = tables.PowerPanelTable @@ -2873,7 +2873,7 @@ class PowerPanelBulkImportView(generic.BulkImportView): class PowerPanelBulkEditView(generic.BulkEditView): queryset = PowerPanel.objects.prefetch_related('site', 'location') - filterset = filters.PowerPanelFilterSet + filterset = filtersets.PowerPanelFilterSet table = tables.PowerPanelTable form = forms.PowerPanelBulkEditForm @@ -2884,7 +2884,7 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView): ).annotate( powerfeed_count=count_related(PowerFeed, 'power_panel') ) - filterset = filters.PowerPanelFilterSet + filterset = filtersets.PowerPanelFilterSet table = tables.PowerPanelTable @@ -2894,7 +2894,7 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView): class PowerFeedListView(generic.ObjectListView): queryset = PowerFeed.objects.all() - filterset = filters.PowerFeedFilterSet + filterset = filtersets.PowerFeedFilterSet filterset_form = forms.PowerFeedFilterForm table = tables.PowerFeedTable @@ -2920,7 +2920,7 @@ class PowerFeedBulkImportView(generic.BulkImportView): class PowerFeedBulkEditView(generic.BulkEditView): queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack') - filterset = filters.PowerFeedFilterSet + filterset = filtersets.PowerFeedFilterSet table = tables.PowerFeedTable form = forms.PowerFeedBulkEditForm @@ -2931,5 +2931,5 @@ class PowerFeedBulkDisconnectView(BulkDisconnectView): class PowerFeedBulkDeleteView(generic.BulkDeleteView): queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack') - filterset = filters.PowerFeedFilterSet + filterset = filtersets.PowerFeedFilterSet table = tables.PowerFeedTable diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index 3757d73f3..aef2046fd 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -6,6 +6,7 @@ from .choices import * __all__ = ( 'CustomFieldFilter', + 'TagFilter', ) EXACT_FILTER_TYPES = ( diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index d1725ac9d..c14424ba6 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError from django.urls import reverse from rest_framework import status -from dcim.filters import SiteFilterSet +from dcim.filtersets import SiteFilterSet from dcim.forms import SiteCSVForm from dcim.models import Site, Rack from extras.choices import * diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 1e1177772..f3f1335f7 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -10,7 +10,7 @@ from rest_framework.response import Response from rest_framework.routers import APIRootView from extras.api.views import CustomFieldModelViewSet -from ipam import filters +from ipam import filtersets from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF from netbox.api.views import ModelViewSet from utilities.constants import ADVISORY_LOCK_KEYS @@ -38,7 +38,7 @@ class VRFViewSet(CustomFieldModelViewSet): prefix_count=count_related(Prefix, 'vrf') ) serializer_class = serializers.VRFSerializer - filterset_class = filters.VRFFilterSet + filterset_class = filtersets.VRFFilterSet # @@ -48,7 +48,7 @@ class VRFViewSet(CustomFieldModelViewSet): class RouteTargetViewSet(CustomFieldModelViewSet): queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags') serializer_class = serializers.RouteTargetSerializer - filterset_class = filters.RouteTargetFilterSet + filterset_class = filtersets.RouteTargetFilterSet # @@ -60,7 +60,7 @@ class RIRViewSet(CustomFieldModelViewSet): aggregate_count=count_related(Aggregate, 'rir') ) serializer_class = serializers.RIRSerializer - filterset_class = filters.RIRFilterSet + filterset_class = filtersets.RIRFilterSet # @@ -70,7 +70,7 @@ class RIRViewSet(CustomFieldModelViewSet): class AggregateViewSet(CustomFieldModelViewSet): queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags') serializer_class = serializers.AggregateSerializer - filterset_class = filters.AggregateFilterSet + filterset_class = filtersets.AggregateFilterSet # @@ -83,7 +83,7 @@ class RoleViewSet(CustomFieldModelViewSet): vlan_count=count_related(VLAN, 'role') ) serializer_class = serializers.RoleSerializer - filterset_class = filters.RoleFilterSet + filterset_class = filtersets.RoleFilterSet # @@ -95,7 +95,7 @@ class PrefixViewSet(CustomFieldModelViewSet): 'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags' ) serializer_class = serializers.PrefixSerializer - filterset_class = filters.PrefixFilterSet + filterset_class = filtersets.PrefixFilterSet def get_serializer_class(self): if self.action == "available_prefixes" and self.request.method == "POST": @@ -275,7 +275,7 @@ class IPAddressViewSet(CustomFieldModelViewSet): 'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object' ) serializer_class = serializers.IPAddressSerializer - filterset_class = filters.IPAddressFilterSet + filterset_class = filtersets.IPAddressFilterSet # @@ -287,7 +287,7 @@ class VLANGroupViewSet(CustomFieldModelViewSet): vlan_count=count_related(VLAN, 'group') ) serializer_class = serializers.VLANGroupSerializer - filterset_class = filters.VLANGroupFilterSet + filterset_class = filtersets.VLANGroupFilterSet # @@ -301,7 +301,7 @@ class VLANViewSet(CustomFieldModelViewSet): prefix_count=count_related(Prefix, 'vlan') ) serializer_class = serializers.VLANSerializer - filterset_class = filters.VLANFilterSet + filterset_class = filtersets.VLANFilterSet # @@ -313,4 +313,4 @@ class ServiceViewSet(ModelViewSet): 'device', 'virtual_machine', 'tags', 'ipaddresses' ) serializer_class = serializers.ServiceSerializer - filterset_class = filters.ServiceFilterSet + filterset_class = filtersets.ServiceFilterSet diff --git a/netbox/ipam/filters.py b/netbox/ipam/filtersets.py similarity index 99% rename from netbox/ipam/filters.py rename to netbox/ipam/filtersets.py index db1b76f31..a4c6d0415 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filtersets.py @@ -7,7 +7,7 @@ from netaddr.core import AddrFormatError from dcim.models import Device, Interface, Region, Site, SiteGroup from extras.filters import TagFilter -from tenancy.filters import TenancyFilterSet +from tenancy.filtersets import TenancyFilterSet from utilities.filters import ( ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter, ) diff --git a/netbox/ipam/tests/test_filters.py b/netbox/ipam/tests/test_filters.py index 3ea54209c..d0b59ae39 100644 --- a/netbox/ipam/tests/test_filters.py +++ b/netbox/ipam/tests/test_filters.py @@ -2,7 +2,7 @@ from django.test import TestCase from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup from ipam.choices import * -from ipam.filters import * +from ipam.filtersets import * from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from tenancy.models import Tenant, TenantGroup diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index f7da1f583..95aa8c3c1 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -7,7 +7,7 @@ from netbox.views import generic from utilities.tables import paginate_table from utilities.utils import count_related from virtualization.models import VirtualMachine, VMInterface -from . import filters, forms, tables +from . import filtersets, forms, tables from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF from .utils import add_available_ipaddresses, add_available_prefixes, add_available_vlans @@ -19,7 +19,7 @@ from .utils import add_available_ipaddresses, add_available_prefixes, add_availa class VRFListView(generic.ObjectListView): queryset = VRF.objects.all() - filterset = filters.VRFFilterSet + filterset = filtersets.VRFFilterSet filterset_form = forms.VRFFilterForm table = tables.VRFTable @@ -65,14 +65,14 @@ class VRFBulkImportView(generic.BulkImportView): class VRFBulkEditView(generic.BulkEditView): queryset = VRF.objects.prefetch_related('tenant') - filterset = filters.VRFFilterSet + filterset = filtersets.VRFFilterSet table = tables.VRFTable form = forms.VRFBulkEditForm class VRFBulkDeleteView(generic.BulkDeleteView): queryset = VRF.objects.prefetch_related('tenant') - filterset = filters.VRFFilterSet + filterset = filtersets.VRFFilterSet table = tables.VRFTable @@ -82,7 +82,7 @@ class VRFBulkDeleteView(generic.BulkDeleteView): class RouteTargetListView(generic.ObjectListView): queryset = RouteTarget.objects.all() - filterset = filters.RouteTargetFilterSet + filterset = filtersets.RouteTargetFilterSet filterset_form = forms.RouteTargetFilterForm table = tables.RouteTargetTable @@ -123,14 +123,14 @@ class RouteTargetBulkImportView(generic.BulkImportView): class RouteTargetBulkEditView(generic.BulkEditView): queryset = RouteTarget.objects.prefetch_related('tenant') - filterset = filters.RouteTargetFilterSet + filterset = filtersets.RouteTargetFilterSet table = tables.RouteTargetTable form = forms.RouteTargetBulkEditForm class RouteTargetBulkDeleteView(generic.BulkDeleteView): queryset = RouteTarget.objects.prefetch_related('tenant') - filterset = filters.RouteTargetFilterSet + filterset = filtersets.RouteTargetFilterSet table = tables.RouteTargetTable @@ -142,7 +142,7 @@ class RIRListView(generic.ObjectListView): queryset = RIR.objects.annotate( aggregate_count=count_related(Aggregate, 'rir') ) - filterset = filters.RIRFilterSet + filterset = filtersets.RIRFilterSet filterset_form = forms.RIRFilterForm table = tables.RIRTable template_name = 'ipam/rir_list.html' @@ -184,7 +184,7 @@ class RIRBulkEditView(generic.BulkEditView): queryset = RIR.objects.annotate( aggregate_count=count_related(Aggregate, 'rir') ) - filterset = filters.RIRFilterSet + filterset = filtersets.RIRFilterSet table = tables.RIRTable form = forms.RIRBulkEditForm @@ -193,7 +193,7 @@ class RIRBulkDeleteView(generic.BulkDeleteView): queryset = RIR.objects.annotate( aggregate_count=count_related(Aggregate, 'rir') ) - filterset = filters.RIRFilterSet + filterset = filtersets.RIRFilterSet table = tables.RIRTable @@ -205,7 +205,7 @@ class AggregateListView(generic.ObjectListView): queryset = Aggregate.objects.annotate( child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ()) ) - filterset = filters.AggregateFilterSet + filterset = filtersets.AggregateFilterSet filterset_form = forms.AggregateFilterForm table = tables.AggregateDetailTable template_name = 'ipam/aggregate_list.html' @@ -280,14 +280,14 @@ class AggregateBulkImportView(generic.BulkImportView): class AggregateBulkEditView(generic.BulkEditView): queryset = Aggregate.objects.prefetch_related('rir') - filterset = filters.AggregateFilterSet + filterset = filtersets.AggregateFilterSet table = tables.AggregateTable form = forms.AggregateBulkEditForm class AggregateBulkDeleteView(generic.BulkDeleteView): queryset = Aggregate.objects.prefetch_related('rir') - filterset = filters.AggregateFilterSet + filterset = filtersets.AggregateFilterSet table = tables.AggregateTable @@ -337,7 +337,7 @@ class RoleBulkImportView(generic.BulkImportView): class RoleBulkEditView(generic.BulkEditView): queryset = Role.objects.all() - filterset = filters.RoleFilterSet + filterset = filtersets.RoleFilterSet table = tables.RoleTable form = forms.RoleBulkEditForm @@ -353,7 +353,7 @@ class RoleBulkDeleteView(generic.BulkDeleteView): class PrefixListView(generic.ObjectListView): queryset = Prefix.objects.annotate_tree() - filterset = filters.PrefixFilterSet + filterset = filtersets.PrefixFilterSet filterset_form = forms.PrefixFilterForm table = tables.PrefixDetailTable template_name = 'ipam/prefix_list.html' @@ -493,14 +493,14 @@ class PrefixBulkImportView(generic.BulkImportView): class PrefixBulkEditView(generic.BulkEditView): queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') - filterset = filters.PrefixFilterSet + filterset = filtersets.PrefixFilterSet table = tables.PrefixTable form = forms.PrefixBulkEditForm class PrefixBulkDeleteView(generic.BulkDeleteView): queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') - filterset = filters.PrefixFilterSet + filterset = filtersets.PrefixFilterSet table = tables.PrefixTable @@ -510,7 +510,7 @@ class PrefixBulkDeleteView(generic.BulkDeleteView): class IPAddressListView(generic.ObjectListView): queryset = IPAddress.objects.all() - filterset = filters.IPAddressFilterSet + filterset = filtersets.IPAddressFilterSet filterset_form = forms.IPAddressFilterForm table = tables.IPAddressDetailTable @@ -613,7 +613,7 @@ class IPAddressAssignView(generic.ObjectView): addresses = self.queryset.prefetch_related('vrf', 'tenant') # Limit to 100 results - addresses = filters.IPAddressFilterSet(request.POST, addresses).qs[:100] + addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100] table = tables.IPAddressAssignTable(addresses) return render(request, 'ipam/ipaddress_assign.html', { @@ -643,14 +643,14 @@ class IPAddressBulkImportView(generic.BulkImportView): class IPAddressBulkEditView(generic.BulkEditView): queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant') - filterset = filters.IPAddressFilterSet + filterset = filtersets.IPAddressFilterSet table = tables.IPAddressTable form = forms.IPAddressBulkEditForm class IPAddressBulkDeleteView(generic.BulkDeleteView): queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant') - filterset = filters.IPAddressFilterSet + filterset = filtersets.IPAddressFilterSet table = tables.IPAddressTable @@ -662,7 +662,7 @@ class VLANGroupListView(generic.ObjectListView): queryset = VLANGroup.objects.annotate( vlan_count=count_related(VLAN, 'group') ) - filterset = filters.VLANGroupFilterSet + filterset = filtersets.VLANGroupFilterSet filterset_form = forms.VLANGroupFilterForm table = tables.VLANGroupTable @@ -718,7 +718,7 @@ class VLANGroupBulkEditView(generic.BulkEditView): queryset = VLANGroup.objects.annotate( vlan_count=count_related(VLAN, 'group') ) - filterset = filters.VLANGroupFilterSet + filterset = filtersets.VLANGroupFilterSet table = tables.VLANGroupTable form = forms.VLANGroupBulkEditForm @@ -727,7 +727,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView): queryset = VLANGroup.objects.annotate( vlan_count=count_related(VLAN, 'group') ) - filterset = filters.VLANGroupFilterSet + filterset = filtersets.VLANGroupFilterSet table = tables.VLANGroupTable @@ -737,7 +737,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView): class VLANListView(generic.ObjectListView): queryset = VLAN.objects.all() - filterset = filters.VLANFilterSet + filterset = filtersets.VLANFilterSet filterset_form = forms.VLANFilterForm table = tables.VLANDetailTable @@ -805,14 +805,14 @@ class VLANBulkImportView(generic.BulkImportView): class VLANBulkEditView(generic.BulkEditView): queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role') - filterset = filters.VLANFilterSet + filterset = filtersets.VLANFilterSet table = tables.VLANTable form = forms.VLANBulkEditForm class VLANBulkDeleteView(generic.BulkDeleteView): queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role') - filterset = filters.VLANFilterSet + filterset = filtersets.VLANFilterSet table = tables.VLANTable @@ -822,7 +822,7 @@ class VLANBulkDeleteView(generic.BulkDeleteView): class ServiceListView(generic.ObjectListView): queryset = Service.objects.all() - filterset = filters.ServiceFilterSet + filterset = filtersets.ServiceFilterSet filterset_form = forms.ServiceFilterForm table = tables.ServiceTable action_buttons = ('import', 'export') @@ -863,12 +863,12 @@ class ServiceDeleteView(generic.ObjectDeleteView): class ServiceBulkEditView(generic.BulkEditView): queryset = Service.objects.prefetch_related('device', 'virtual_machine') - filterset = filters.ServiceFilterSet + filterset = filtersets.ServiceFilterSet table = tables.ServiceTable form = forms.ServiceBulkEditForm class ServiceBulkDeleteView(generic.BulkDeleteView): queryset = Service.objects.prefetch_related('device', 'virtual_machine') - filterset = filters.ServiceFilterSet + filterset = filtersets.ServiceFilterSet table = tables.ServiceTable diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py index 5568f4e70..b6da0b2de 100644 --- a/netbox/netbox/constants.py +++ b/netbox/netbox/constants.py @@ -1,9 +1,9 @@ from collections import OrderedDict -from circuits.filters import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet +from circuits.filtersets import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet from circuits.models import Circuit, ProviderNetwork, Provider from circuits.tables import CircuitTable, ProviderNetworkTable, ProviderTable -from dcim.filters import ( +from dcim.filtersets import ( CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet, SiteFilterSet, VirtualChassisFilterSet, ) @@ -12,17 +12,17 @@ from dcim.tables import ( CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, LocationTable, SiteTable, VirtualChassisTable, ) -from ipam.filters import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet +from ipam.filtersets import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable -from secrets.filters import SecretFilterSet +from secrets.filtersets import SecretFilterSet from secrets.models import Secret from secrets.tables import SecretTable -from tenancy.filters import TenantFilterSet +from tenancy.filtersets import TenantFilterSet from tenancy.models import Tenant from tenancy.tables import TenantTable from utilities.utils import count_related -from virtualization.filters import ClusterFilterSet, VirtualMachineFilterSet +from virtualization.filtersets import ClusterFilterSet, VirtualMachineFilterSet from virtualization.models import Cluster, VirtualMachine from virtualization.tables import ClusterTable, VirtualMachineDetailTable diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 3650abd30..7982d29f1 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -10,7 +10,7 @@ from rest_framework.viewsets import ViewSet from extras.api.views import CustomFieldModelViewSet from netbox.api.views import ModelViewSet -from secrets import filters +from secrets import filtersets from secrets.exceptions import InvalidKey from secrets.models import Secret, SecretRole, SessionKey, UserKey from utilities.utils import count_related @@ -39,7 +39,7 @@ class SecretRoleViewSet(CustomFieldModelViewSet): secret_count=count_related(Secret, 'role') ) serializer_class = serializers.SecretRoleSerializer - filterset_class = filters.SecretRoleFilterSet + filterset_class = filtersets.SecretRoleFilterSet # @@ -49,7 +49,7 @@ class SecretRoleViewSet(CustomFieldModelViewSet): class SecretViewSet(ModelViewSet): queryset = Secret.objects.prefetch_related('role', 'tags') serializer_class = serializers.SecretSerializer - filterset_class = filters.SecretFilterSet + filterset_class = filtersets.SecretFilterSet master_key = None diff --git a/netbox/secrets/filters.py b/netbox/secrets/filtersets.py similarity index 100% rename from netbox/secrets/filters.py rename to netbox/secrets/filtersets.py diff --git a/netbox/secrets/tests/test_filters.py b/netbox/secrets/tests/test_filters.py index 0be1ef594..1d9d18f37 100644 --- a/netbox/secrets/tests/test_filters.py +++ b/netbox/secrets/tests/test_filters.py @@ -1,7 +1,7 @@ from django.test import TestCase from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site -from secrets.filters import * +from secrets.filtersets import * from secrets.models import Secret, SecretRole from virtualization.models import Cluster, ClusterType, VirtualMachine diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index 57d64f064..091e16224 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -2,14 +2,14 @@ import base64 import logging from django.contrib import messages -from django.shortcuts import get_object_or_404, redirect, render +from django.shortcuts import redirect, render from django.utils.html import escape from django.utils.safestring import mark_safe from netbox.views import generic from utilities.tables import paginate_table from utilities.utils import count_related -from . import filters, forms, tables +from . import filtersets, forms, tables from .models import SecretRole, Secret, SessionKey, UserKey @@ -70,7 +70,7 @@ class SecretRoleBulkEditView(generic.BulkEditView): queryset = SecretRole.objects.annotate( secret_count=count_related(Secret, 'role') ) - filterset = filters.SecretRoleFilterSet + filterset = filtersets.SecretRoleFilterSet table = tables.SecretRoleTable form = forms.SecretRoleBulkEditForm @@ -88,7 +88,7 @@ class SecretRoleBulkDeleteView(generic.BulkDeleteView): class SecretListView(generic.ObjectListView): queryset = Secret.objects.all() - filterset = filters.SecretFilterSet + filterset = filtersets.SecretFilterSet filterset_form = forms.SecretFilterForm table = tables.SecretTable action_buttons = ('import', 'export') @@ -220,12 +220,12 @@ class SecretBulkImportView(generic.BulkImportView): class SecretBulkEditView(generic.BulkEditView): queryset = Secret.objects.prefetch_related('role') - filterset = filters.SecretFilterSet + filterset = filtersets.SecretFilterSet table = tables.SecretTable form = forms.SecretBulkEditForm class SecretBulkDeleteView(generic.BulkDeleteView): queryset = Secret.objects.prefetch_related('role') - filterset = filters.SecretFilterSet + filterset = filtersets.SecretFilterSet table = tables.SecretTable diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 3b57e1a02..2e049135d 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -4,7 +4,7 @@ from circuits.models import Circuit from dcim.models import Device, Rack, Site from extras.api.views import CustomFieldModelViewSet from ipam.models import IPAddress, Prefix, VLAN, VRF -from tenancy import filters +from tenancy import filtersets from tenancy.models import Tenant, TenantGroup from utilities.utils import count_related from virtualization.models import VirtualMachine @@ -32,7 +32,7 @@ class TenantGroupViewSet(CustomFieldModelViewSet): cumulative=True ) serializer_class = serializers.TenantGroupSerializer - filterset_class = filters.TenantGroupFilterSet + filterset_class = filtersets.TenantGroupFilterSet # @@ -54,4 +54,4 @@ class TenantViewSet(CustomFieldModelViewSet): vrf_count=count_related(VRF, 'tenant') ) serializer_class = serializers.TenantSerializer - filterset_class = filters.TenantFilterSet + filterset_class = filtersets.TenantFilterSet diff --git a/netbox/tenancy/filters.py b/netbox/tenancy/filtersets.py similarity index 100% rename from netbox/tenancy/filters.py rename to netbox/tenancy/filtersets.py diff --git a/netbox/tenancy/tests/test_filters.py b/netbox/tenancy/tests/test_filters.py index c78b25083..32c9cd186 100644 --- a/netbox/tenancy/tests/test_filters.py +++ b/netbox/tenancy/tests/test_filters.py @@ -1,6 +1,6 @@ from django.test import TestCase -from tenancy.filters import * +from tenancy.filtersets import * from tenancy.models import Tenant, TenantGroup diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 206ff6c7a..45dffb3c0 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -4,7 +4,7 @@ from ipam.models import IPAddress, Prefix, VLAN, VRF from netbox.views import generic from utilities.tables import paginate_table from virtualization.models import VirtualMachine, Cluster -from . import filters, forms, tables +from . import filtersets, forms, tables from .models import Tenant, TenantGroup @@ -63,7 +63,7 @@ class TenantGroupBulkEditView(generic.BulkEditView): 'tenant_count', cumulative=True ) - filterset = filters.TenantGroupFilterSet + filterset = filtersets.TenantGroupFilterSet table = tables.TenantGroupTable form = forms.TenantGroupBulkEditForm @@ -85,7 +85,7 @@ class TenantGroupBulkDeleteView(generic.BulkDeleteView): class TenantListView(generic.ObjectListView): queryset = Tenant.objects.all() - filterset = filters.TenantFilterSet + filterset = filtersets.TenantFilterSet filterset_form = forms.TenantFilterForm table = tables.TenantTable @@ -130,12 +130,12 @@ class TenantBulkImportView(generic.BulkImportView): class TenantBulkEditView(generic.BulkEditView): queryset = Tenant.objects.prefetch_related('group') - filterset = filters.TenantFilterSet + filterset = filtersets.TenantFilterSet table = tables.TenantTable form = forms.TenantBulkEditForm class TenantBulkDeleteView(generic.BulkDeleteView): queryset = Tenant.objects.prefetch_related('group') - filterset = filters.TenantFilterSet + filterset = filtersets.TenantFilterSet table = tables.TenantTable diff --git a/netbox/users/api/views.py b/netbox/users/api/views.py index 7773e54f4..b0443b87e 100644 --- a/netbox/users/api/views.py +++ b/netbox/users/api/views.py @@ -6,7 +6,7 @@ from rest_framework.routers import APIRootView from rest_framework.viewsets import ViewSet from netbox.api.views import ModelViewSet -from users import filters +from users import filtersets from users.models import ObjectPermission, UserConfig from utilities.querysets import RestrictedQuerySet from utilities.utils import deepmerge @@ -28,13 +28,13 @@ class UsersRootView(APIRootView): class UserViewSet(ModelViewSet): queryset = RestrictedQuerySet(model=User).prefetch_related('groups').order_by('username') serializer_class = serializers.UserSerializer - filterset_class = filters.UserFilterSet + filterset_class = filtersets.UserFilterSet class GroupViewSet(ModelViewSet): queryset = RestrictedQuerySet(model=Group).annotate(user_count=Count('user')).order_by('name') serializer_class = serializers.GroupSerializer - filterset_class = filters.GroupFilterSet + filterset_class = filtersets.GroupFilterSet # @@ -44,7 +44,7 @@ class GroupViewSet(ModelViewSet): class ObjectPermissionViewSet(ModelViewSet): queryset = ObjectPermission.objects.prefetch_related('object_types', 'groups', 'users') serializer_class = serializers.ObjectPermissionSerializer - filterset_class = filters.ObjectPermissionFilterSet + filterset_class = filtersets.ObjectPermissionFilterSet # diff --git a/netbox/users/filters.py b/netbox/users/filtersets.py similarity index 100% rename from netbox/users/filters.py rename to netbox/users/filtersets.py diff --git a/netbox/users/tests/test_filters.py b/netbox/users/tests/test_filters.py index c3774927c..2a3a8e24f 100644 --- a/netbox/users/tests/test_filters.py +++ b/netbox/users/tests/test_filters.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import Group, User from django.contrib.contenttypes.models import ContentType from django.test import TestCase -from users.filters import GroupFilterSet, ObjectPermissionFilterSet, UserFilterSet +from users.filtersets import GroupFilterSet, ObjectPermissionFilterSet, UserFilterSet from users.models import ObjectPermission diff --git a/netbox/utilities/tests/test_filters.py b/netbox/utilities/tests/test_filters.py index 21f020fb4..6be611885 100644 --- a/netbox/utilities/tests/test_filters.py +++ b/netbox/utilities/tests/test_filters.py @@ -7,7 +7,7 @@ from taggit.managers import TaggableManager from dcim.choices import * from dcim.fields import MACAddressField -from dcim.filters import DeviceFilterSet, SiteFilterSet +from dcim.filtersets import DeviceFilterSet, SiteFilterSet from dcim.models import ( Device, DeviceRole, DeviceType, Interface, Manufacturer, Platform, Rack, Region, Site ) diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index 1c4371ed0..8eebd2120 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -3,7 +3,7 @@ from rest_framework.routers import APIRootView from dcim.models import Device from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet, ModelViewSet from utilities.utils import count_related -from virtualization import filters +from virtualization import filtersets from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from . import serializers @@ -25,7 +25,7 @@ class ClusterTypeViewSet(CustomFieldModelViewSet): cluster_count=count_related(Cluster, 'type') ) serializer_class = serializers.ClusterTypeSerializer - filterset_class = filters.ClusterTypeFilterSet + filterset_class = filtersets.ClusterTypeFilterSet class ClusterGroupViewSet(CustomFieldModelViewSet): @@ -33,7 +33,7 @@ class ClusterGroupViewSet(CustomFieldModelViewSet): cluster_count=count_related(Cluster, 'group') ) serializer_class = serializers.ClusterGroupSerializer - filterset_class = filters.ClusterGroupFilterSet + filterset_class = filtersets.ClusterGroupFilterSet class ClusterViewSet(CustomFieldModelViewSet): @@ -44,7 +44,7 @@ class ClusterViewSet(CustomFieldModelViewSet): virtualmachine_count=count_related(VirtualMachine, 'cluster') ) serializer_class = serializers.ClusterSerializer - filterset_class = filters.ClusterFilterSet + filterset_class = filtersets.ClusterFilterSet # @@ -55,7 +55,7 @@ class VirtualMachineViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet) queryset = VirtualMachine.objects.prefetch_related( 'cluster__site', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'tags' ) - filterset_class = filters.VirtualMachineFilterSet + filterset_class = filtersets.VirtualMachineFilterSet def get_serializer_class(self): """ @@ -83,5 +83,5 @@ class VMInterfaceViewSet(ModelViewSet): 'virtual_machine', 'parent', 'tags', 'tagged_vlans', 'ip_addresses' ) serializer_class = serializers.VMInterfaceSerializer - filterset_class = filters.VMInterfaceFilterSet + filterset_class = filtersets.VMInterfaceFilterSet brief_prefetch_fields = ['virtual_machine'] diff --git a/netbox/virtualization/filters.py b/netbox/virtualization/filtersets.py similarity index 99% rename from netbox/virtualization/filters.py rename to netbox/virtualization/filtersets.py index 6c2e0a48a..023dfb13b 100644 --- a/netbox/virtualization/filters.py +++ b/netbox/virtualization/filtersets.py @@ -4,7 +4,7 @@ from django.db.models import Q from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from extras.filters import TagFilter from extras.filtersets import LocalConfigContextFilterSet -from tenancy.filters import TenancyFilterSet +from tenancy.filtersets import TenancyFilterSet from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from .choices import * diff --git a/netbox/virtualization/tests/test_filters.py b/netbox/virtualization/tests/test_filters.py index c11423663..40803cc07 100644 --- a/netbox/virtualization/tests/test_filters.py +++ b/netbox/virtualization/tests/test_filters.py @@ -4,7 +4,7 @@ from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from ipam.models import IPAddress from tenancy.models import Tenant, TenantGroup from virtualization.choices import * -from virtualization.filters import * +from virtualization.filtersets import * from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 6b316de0e..421278d6e 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -13,7 +13,7 @@ from netbox.views import generic from secrets.models import Secret from utilities.tables import paginate_table from utilities.utils import count_related -from . import filters, forms, tables +from . import filtersets, forms, tables from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface @@ -64,7 +64,7 @@ class ClusterTypeBulkEditView(generic.BulkEditView): queryset = ClusterType.objects.annotate( cluster_count=count_related(Cluster, 'type') ) - filterset = filters.ClusterTypeFilterSet + filterset = filtersets.ClusterTypeFilterSet table = tables.ClusterTypeTable form = forms.ClusterTypeBulkEditForm @@ -125,7 +125,7 @@ class ClusterGroupBulkEditView(generic.BulkEditView): queryset = ClusterGroup.objects.annotate( cluster_count=count_related(Cluster, 'group') ) - filterset = filters.ClusterGroupFilterSet + filterset = filtersets.ClusterGroupFilterSet table = tables.ClusterGroupTable form = forms.ClusterGroupBulkEditForm @@ -148,7 +148,7 @@ class ClusterListView(generic.ObjectListView): vm_count=count_related(VirtualMachine, 'cluster') ) table = tables.ClusterTable - filterset = filters.ClusterFilterSet + filterset = filtersets.ClusterFilterSet filterset_form = forms.ClusterFilterForm @@ -205,14 +205,14 @@ class ClusterBulkImportView(generic.BulkImportView): class ClusterBulkEditView(generic.BulkEditView): queryset = Cluster.objects.prefetch_related('type', 'group', 'site') - filterset = filters.ClusterFilterSet + filterset = filtersets.ClusterFilterSet table = tables.ClusterTable form = forms.ClusterBulkEditForm class ClusterBulkDeleteView(generic.BulkDeleteView): queryset = Cluster.objects.prefetch_related('type', 'group', 'site') - filterset = filters.ClusterFilterSet + filterset = filtersets.ClusterFilterSet table = tables.ClusterTable @@ -304,7 +304,7 @@ class ClusterRemoveDevicesView(generic.ObjectEditView): class VirtualMachineListView(generic.ObjectListView): queryset = VirtualMachine.objects.all() - filterset = filters.VirtualMachineFilterSet + filterset = filtersets.VirtualMachineFilterSet filterset_form = forms.VirtualMachineFilterForm table = tables.VirtualMachineDetailTable template_name = 'virtualization/virtualmachine_list.html' @@ -388,14 +388,14 @@ class VirtualMachineBulkImportView(generic.BulkImportView): class VirtualMachineBulkEditView(generic.BulkEditView): queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role') - filterset = filters.VirtualMachineFilterSet + filterset = filtersets.VirtualMachineFilterSet table = tables.VirtualMachineTable form = forms.VirtualMachineBulkEditForm class VirtualMachineBulkDeleteView(generic.BulkDeleteView): queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role') - filterset = filters.VirtualMachineFilterSet + filterset = filtersets.VirtualMachineFilterSet table = tables.VirtualMachineTable @@ -405,7 +405,7 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView): class VMInterfaceListView(generic.ObjectListView): queryset = VMInterface.objects.all() - filterset = filters.VMInterfaceFilterSet + filterset = filtersets.VMInterfaceFilterSet filterset_form = forms.VMInterfaceFilterForm table = tables.VMInterfaceTable action_buttons = ('export',) @@ -500,7 +500,7 @@ class VirtualMachineBulkAddInterfaceView(generic.BulkComponentCreateView): form = forms.VMInterfaceBulkCreateForm queryset = VMInterface.objects.all() model_form = forms.VMInterfaceForm - filterset = filters.VirtualMachineFilterSet + filterset = filtersets.VirtualMachineFilterSet table = tables.VirtualMachineTable def get_required_permission(self): From 3ef6284a0d1f9245c502592d1b762240a03bd32d Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 29 Apr 2021 16:53:48 -0400 Subject: [PATCH 10/67] Move base FilterSet classes under netbox core --- netbox/circuits/filtersets.py | 2 +- netbox/dcim/filtersets.py | 6 +++--- netbox/extras/filtersets.py | 2 +- netbox/ipam/filtersets.py | 2 +- netbox/{utilities => netbox}/filtersets.py | 2 +- netbox/secrets/filtersets.py | 2 +- netbox/tenancy/filtersets.py | 2 +- netbox/users/filtersets.py | 2 +- netbox/utilities/tests/test_filters.py | 2 +- netbox/virtualization/filtersets.py | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) rename netbox/{utilities => netbox}/filtersets.py (99%) diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py index cd0b720e6..6619500cd 100644 --- a/netbox/circuits/filtersets.py +++ b/netbox/circuits/filtersets.py @@ -4,9 +4,9 @@ from django.db.models import Q from dcim.filtersets import CableTerminationFilterSet from dcim.models import Region, Site, SiteGroup from extras.filters import TagFilter +from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet from tenancy.filtersets import TenancyFilterSet from utilities.filters import TreeNodeMultipleChoiceFilter -from utilities.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet from .choices import * from .models import * diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 62f59c352..b04c14ba9 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -3,15 +3,15 @@ from django.contrib.auth.models import User from extras.filters import TagFilter from extras.filtersets import LocalConfigContextFilterSet +from netbox.filtersets import ( + BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet, +) from tenancy.filtersets import TenancyFilterSet from tenancy.models import Tenant from utilities.choices import ColorChoices from utilities.filters import ( MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter, ) -from utilities.filtersets import ( - BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet, -) from virtualization.models import Cluster from .choices import * from .constants import * diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py index 1451a34c0..84522c0ad 100644 --- a/netbox/extras/filtersets.py +++ b/netbox/extras/filtersets.py @@ -4,9 +4,9 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import Q from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup +from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet from tenancy.models import Tenant, TenantGroup from utilities.filters import ContentTypeFilter -from utilities.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet from virtualization.models import Cluster, ClusterGroup from .choices import * from .models import * diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index a4c6d0415..5ab4994ea 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -7,11 +7,11 @@ from netaddr.core import AddrFormatError from dcim.models import Device, Interface, Region, Site, SiteGroup from extras.filters import TagFilter +from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from tenancy.filtersets import TenancyFilterSet from utilities.filters import ( ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter, ) -from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from virtualization.models import VirtualMachine, VMInterface from .choices import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF diff --git a/netbox/utilities/filtersets.py b/netbox/netbox/filtersets.py similarity index 99% rename from netbox/utilities/filtersets.py rename to netbox/netbox/filtersets.py index f738441dd..aa9e15385 100644 --- a/netbox/utilities/filtersets.py +++ b/netbox/netbox/filtersets.py @@ -29,7 +29,7 @@ __all__ = ( class BaseFilterSet(django_filters.FilterSet): """ - A base filterset which provides common functionaly to all NetBox filtersets + A base FilterSet which provides common functionality to all NetBox FilterSets """ FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS) FILTER_DEFAULTS.update({ diff --git a/netbox/secrets/filtersets.py b/netbox/secrets/filtersets.py index 1149fbd9b..644864ecb 100644 --- a/netbox/secrets/filtersets.py +++ b/netbox/secrets/filtersets.py @@ -3,7 +3,7 @@ from django.db.models import Q from dcim.models import Device from extras.filters import TagFilter -from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet +from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from virtualization.models import VirtualMachine from .models import Secret, SecretRole diff --git a/netbox/tenancy/filtersets.py b/netbox/tenancy/filtersets.py index 6a428f4b6..d00b78629 100644 --- a/netbox/tenancy/filtersets.py +++ b/netbox/tenancy/filtersets.py @@ -2,8 +2,8 @@ import django_filters from django.db.models import Q from extras.filters import TagFilter +from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from utilities.filters import TreeNodeMultipleChoiceFilter -from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from .models import Tenant, TenantGroup diff --git a/netbox/users/filtersets.py b/netbox/users/filtersets.py index 42f97bedc..6625cba36 100644 --- a/netbox/users/filtersets.py +++ b/netbox/users/filtersets.py @@ -2,8 +2,8 @@ import django_filters from django.contrib.auth.models import Group, User from django.db.models import Q +from netbox.filtersets import BaseFilterSet from users.models import ObjectPermission -from utilities.filtersets import BaseFilterSet __all__ = ( 'GroupFilterSet', diff --git a/netbox/utilities/tests/test_filters.py b/netbox/utilities/tests/test_filters.py index 6be611885..374167f1c 100644 --- a/netbox/utilities/tests/test_filters.py +++ b/netbox/utilities/tests/test_filters.py @@ -13,11 +13,11 @@ from dcim.models import ( ) from extras.filters import TagFilter from extras.models import TaggedItem +from netbox.filtersets import BaseFilterSet from utilities.filters import ( MACAddressFilter, MultiValueCharFilter, MultiValueDateFilter, MultiValueDateTimeFilter, MultiValueNumberFilter, MultiValueTimeFilter, TreeNodeMultipleChoiceFilter, ) -from utilities.filtersets import BaseFilterSet class TreeNodeMultipleChoiceFilterTest(TestCase): diff --git a/netbox/virtualization/filtersets.py b/netbox/virtualization/filtersets.py index 023dfb13b..6d930b69e 100644 --- a/netbox/virtualization/filtersets.py +++ b/netbox/virtualization/filtersets.py @@ -4,9 +4,9 @@ from django.db.models import Q from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from extras.filters import TagFilter from extras.filtersets import LocalConfigContextFilterSet +from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from tenancy.filtersets import TenancyFilterSet from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter -from utilities.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet from .choices import * from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface From 2a5b497d8abe5515d911fcca2dc7189715152ed7 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 30 Apr 2021 10:08:15 -0400 Subject: [PATCH 11/67] Fixes #6313: Fix device type instance count under manufacturer view --- docs/release-notes/version-2.11.md | 1 + netbox/dcim/views.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index f83875a4d..bad2ba7e3 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -10,6 +10,7 @@ * [#6308](https://github.com/netbox-community/netbox/issues/6308) - Fix linking of available VLANs in VLAN group view * [#6309](https://github.com/netbox-community/netbox/issues/6309) - Restrict parent VM interface assignment to the parent VM +* [#6313](https://github.com/netbox-community/netbox/issues/6313) - Fix device type instance count under manufacturer view --- diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 72438fc9a..734f9bd1a 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -692,6 +692,8 @@ class ManufacturerView(generic.ObjectView): def get_extra_context(self, request, instance): devicetypes = DeviceType.objects.restrict(request.user, 'view').filter( manufacturer=instance + ).annotate( + instance_count=count_related(Device, 'device_type') ) devicetypes_table = tables.DeviceTypeTable(devicetypes) From fd9d9d9d35a44ced0b857c661e8a6bab460fe72e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 30 Apr 2021 10:10:03 -0400 Subject: [PATCH 12/67] Closes #6318: Add OM5 MMF cable type --- docs/release-notes/version-2.11.md | 1 + netbox/dcim/choices.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index bad2ba7e3..feb05c8bc 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -5,6 +5,7 @@ ### Enhancements * [#6197](https://github.com/netbox-community/netbox/issues/6197) - Introduced `SESSION_COOKIE_NAME` config parameter +* [#6318](https://github.com/netbox-community/netbox/issues/6318) - Add OM5 MMF cable type ### Bug Fixes diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 802bd4986..1d3e698a5 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1001,6 +1001,7 @@ class CableTypeChoices(ChoiceSet): TYPE_MMF_OM2 = 'mmf-om2' TYPE_MMF_OM3 = 'mmf-om3' TYPE_MMF_OM4 = 'mmf-om4' + TYPE_MMF_OM5 = 'mmf-om5' TYPE_SMF = 'smf' TYPE_SMF_OS1 = 'smf-os1' TYPE_SMF_OS2 = 'smf-os2' @@ -1031,6 +1032,7 @@ class CableTypeChoices(ChoiceSet): (TYPE_MMF_OM2, 'Multimode Fiber (OM2)'), (TYPE_MMF_OM3, 'Multimode Fiber (OM3)'), (TYPE_MMF_OM4, 'Multimode Fiber (OM4)'), + (TYPE_MMF_OM5, 'Multimode Fiber (OM5)'), (TYPE_SMF, 'Singlemode Fiber'), (TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'), (TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'), From c59c4290f9d0fc03eea735a895aac573ed3de3c2 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Fri, 30 Apr 2021 09:50:14 -0700 Subject: [PATCH 13/67] Closes #6321: Re-add missing 'Add an IP Address' button in prefix view --- netbox/templates/ipam/prefix/base.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/netbox/templates/ipam/prefix/base.html b/netbox/templates/ipam/prefix/base.html index f1d5cbc99..4ebc0a64d 100644 --- a/netbox/templates/ipam/prefix/base.html +++ b/netbox/templates/ipam/prefix/base.html @@ -11,6 +11,16 @@
  • {{ object }}
  • {% endblock %} +{% block buttons %} + {% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %} + + + Add an IP Address + + {% endif %} + {{ block.super }} +{% endblock %} + {% block tabs %}