Closes #8453: Rename PrimaryModelFilterSet to NetBoxModelFilterSet & expose for plugins

This commit is contained in:
jeremystretch 2022-01-27 09:24:20 -05:00
parent c5650bb278
commit 083d1acb81
10 changed files with 112 additions and 52 deletions

View File

@ -0,0 +1,56 @@
# Filter Sets
Filter sets define the mechanisms available for filtering or searching through a set of objects in NetBox. For instance, sites can be filtered by their parent region or group, status, facility ID, and so on. The same filter set is used consistently for a model whether the request is made via the UI, REST API, or GraphQL API. NetBox employs the [django-filters2](https://django-tables2.readthedocs.io/en/latest/) library to define filter sets.
## FilterSet Classes
To support additional functionality standard to NetBox models, such as tag assignment and custom field support, the `NetBoxModelFilterSet` class is available for use by plugins. This should be used as the base filter set class for plugin models which inherit from `NetBoxModel`. Within this class, individual filters can be declared as directed by the `django-filters` documentation. An example is provided below.
```python
# filtersets.py
import django_filters
from netbox.filtersets import NetBoxModelFilterSet
from .models import MyModel
class MyFilterSet(NetBoxModelFilterSet):
status = django_filters.MultipleChoiceFilter(
choices=(
('foo', 'Foo'),
('bar', 'Bar'),
('baz', 'Baz'),
),
null_value=None
)
class Meta:
model = MyModel
fields = ('some', 'other', 'fields')
```
## Declaring Filter Sets
To utilize a filter set in the subclass of a generic view, such as `ObjectListView` or `BulkEditView`, set it as the `filterset` attribute on the view class:
```python
# views.py
from netbox.views.generic import ObjectListView
from .filtersets import MyModelFitlerSet
from .models import MyModel
class MyModelListView(ObjectListView):
queryset = MyModel.objects.all()
filterset = MyModelFitlerSet
```
To enable a filter on a REST API endpoint, set it as the `filterset_class` attribute on the API view:
```python
# api/views.py
from myplugin import models, filtersets
from . import serializers
class MyModelViewSet(...):
queryset = models.MyModel.objects.all()
serializer_class = serializers.MyModelSerializer
filterset_class = filtersets.MyModelFilterSet
```

View File

@ -45,7 +45,7 @@ For more background on schema migrations, see the [Django documentation](https:/
## Enabling NetBox Features
Plugin models can leverage certain NetBox features by inheriting from NetBox's `BaseModel` class. This class extends the plugin model to enable numerous feature, including:
Plugin models can leverage certain NetBox features by inheriting from NetBox's `NetBoxModel` class. This class extends the plugin model to enable numerous feature, including:
* Change logging
* Custom fields

View File

@ -104,6 +104,7 @@ nav:
- Getting Started: 'plugins/development/index.md'
- Database Models: 'plugins/development/models.md'
- Views: 'plugins/development/views.md'
- Filtersets: 'plugins/development/filtersets.md'
- REST API: 'plugins/development/rest-api.md'
- Background Tasks: 'plugins/development/background-tasks.md'
- Administration:

View File

@ -3,7 +3,7 @@ from django.db.models import Q
from dcim.filtersets import CableTerminationFilterSet
from dcim.models import Region, Site, SiteGroup
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet
from utilities.filters import TreeNodeMultipleChoiceFilter
from .choices import *
@ -18,7 +18,7 @@ __all__ = (
)
class ProviderFilterSet(PrimaryModelFilterSet):
class ProviderFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -77,7 +77,7 @@ class ProviderFilterSet(PrimaryModelFilterSet):
)
class ProviderNetworkFilterSet(PrimaryModelFilterSet):
class ProviderNetworkFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -115,7 +115,7 @@ class CircuitTypeFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug']
class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',

View File

@ -4,7 +4,7 @@ from django.contrib.auth.models import User
from extras.filtersets import LocalConfigContextFilterSet
from ipam.models import ASN, VRF
from netbox.filtersets import (
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet,
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet,
)
from tenancy.filtersets import TenancyFilterSet
from tenancy.models import Tenant
@ -101,7 +101,7 @@ class SiteGroupFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description']
class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -242,7 +242,7 @@ class RackRoleFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'color']
class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -339,7 +339,7 @@ class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
)
class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class RackReservationFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -405,7 +405,7 @@ class ManufacturerFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description']
class DeviceTypeFilterSet(PrimaryModelFilterSet):
class DeviceTypeFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -497,7 +497,7 @@ class DeviceTypeFilterSet(PrimaryModelFilterSet):
return queryset.exclude(devicebaytemplates__isnull=value)
class ModuleTypeFilterSet(PrimaryModelFilterSet):
class ModuleTypeFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -745,7 +745,7 @@ class PlatformFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -956,7 +956,7 @@ class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContex
return queryset.exclude(devicebays__isnull=value)
class ModuleFilterSet(PrimaryModelFilterSet):
class ModuleFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -1096,7 +1096,7 @@ class PathEndpointFilterSet(django_filters.FilterSet):
return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
class ConsolePortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class ConsolePortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypeChoices,
null_value=None
@ -1107,7 +1107,7 @@ class ConsolePortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, Cabl
fields = ['id', 'name', 'label', 'description']
class ConsoleServerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class ConsoleServerPortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=ConsolePortTypeChoices,
null_value=None
@ -1118,7 +1118,7 @@ class ConsoleServerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet
fields = ['id', 'name', 'label', 'description']
class PowerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class PowerPortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PowerPortTypeChoices,
null_value=None
@ -1129,7 +1129,7 @@ class PowerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description']
class PowerOutletFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class PowerOutletFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PowerOutletTypeChoices,
null_value=None
@ -1144,7 +1144,7 @@ class PowerOutletFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, Cabl
fields = ['id', 'name', 'label', 'feed_leg', 'description']
class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class InterfaceFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -1271,7 +1271,7 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
}.get(value, queryset.none())
class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
class FrontPortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PortTypeChoices,
null_value=None
@ -1282,7 +1282,7 @@ class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
fields = ['id', 'name', 'label', 'type', 'color', 'description']
class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
class RearPortFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=PortTypeChoices,
null_value=None
@ -1293,21 +1293,21 @@ class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTe
fields = ['id', 'name', 'label', 'type', 'color', 'positions', 'description']
class ModuleBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
class ModuleBayFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet):
class Meta:
model = ModuleBay
fields = ['id', 'name', 'label', 'description']
class DeviceBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
class DeviceBayFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet):
class Meta:
model = DeviceBay
fields = ['id', 'name', 'label', 'description']
class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
class InventoryItemFilterSet(NetBoxModelFilterSet, DeviceComponentFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -1366,7 +1366,7 @@ class InventoryItemRoleFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'color']
class VirtualChassisFilterSet(PrimaryModelFilterSet):
class VirtualChassisFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -1445,7 +1445,7 @@ class VirtualChassisFilterSet(PrimaryModelFilterSet):
return queryset.filter(qs_filter).distinct()
class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -1504,7 +1504,7 @@ class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
return queryset
class PowerPanelFilterSet(PrimaryModelFilterSet):
class PowerPanelFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -1565,7 +1565,7 @@ class PowerPanelFilterSet(PrimaryModelFilterSet):
return queryset.filter(qs_filter)
class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
class PowerFeedFilterSet(NetBoxModelFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',

View File

@ -6,7 +6,7 @@ from django.db.models import Q
from netaddr.core import AddrFormatError
from dcim.models import Device, Interface, Region, Site, SiteGroup
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet
from utilities.filters import (
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter,
@ -35,7 +35,7 @@ __all__ = (
)
class VRFFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class VRFFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -77,7 +77,7 @@ class VRFFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
fields = ['id', 'name', 'rd', 'enforce_unique']
class RouteTargetFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -125,7 +125,7 @@ class RIRFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'is_private', 'description']
class AggregateFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -219,7 +219,7 @@ class RoleFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug']
class PrefixFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -409,7 +409,7 @@ class PrefixFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
)
class IPRangeFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -475,7 +475,7 @@ class IPRangeFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
return queryset.none()
class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -640,7 +640,7 @@ class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
return queryset.exclude(assigned_object_id__isnull=value)
class FHRPGroupFilterSet(PrimaryModelFilterSet):
class FHRPGroupFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -748,7 +748,7 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet):
)
class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -843,7 +843,7 @@ class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
return queryset.get_for_virtualmachine(value)
class ServiceTemplateFilterSet(PrimaryModelFilterSet):
class ServiceTemplateFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -864,7 +864,7 @@ class ServiceTemplateFilterSet(PrimaryModelFilterSet):
return queryset.filter(qs_filter)
class ServiceFilterSet(PrimaryModelFilterSet):
class ServiceFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',

View File

@ -18,8 +18,8 @@ from utilities import filters
__all__ = (
'BaseFilterSet',
'ChangeLoggedModelFilterSet',
'NetBoxModelFilterSet',
'OrganizationalModelFilterSet',
'PrimaryModelFilterSet',
)
@ -29,7 +29,7 @@ __all__ = (
class BaseFilterSet(django_filters.FilterSet):
"""
A base FilterSet which provides common functionality to all NetBox FilterSets
A base FilterSet which provides some enhanced functionality over django-filter2's FilterSet class.
"""
FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS)
FILTER_DEFAULTS.update({
@ -217,7 +217,10 @@ class ChangeLoggedModelFilterSet(BaseFilterSet):
)
class PrimaryModelFilterSet(ChangeLoggedModelFilterSet):
class NetBoxModelFilterSet(ChangeLoggedModelFilterSet):
"""
Provides additional filtering functionality (e.g. tags, custom fields) for core NetBox models.
"""
tag = TagFilter()
def __init__(self, *args, **kwargs):
@ -244,7 +247,7 @@ class PrimaryModelFilterSet(ChangeLoggedModelFilterSet):
self.filters.update(custom_field_filters)
class OrganizationalModelFilterSet(PrimaryModelFilterSet):
class OrganizationalModelFilterSet(NetBoxModelFilterSet):
"""
A base class for adding the search method to models which only expose the `name` and `slug` fields
"""

View File

@ -1,7 +1,7 @@
import django_filters
from django.db.models import Q
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet
from utilities.filters import ContentTypeFilter, TreeNodeMultipleChoiceFilter
from .models import *
@ -38,7 +38,7 @@ class TenantGroupFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description']
class TenantFilterSet(PrimaryModelFilterSet):
class TenantFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -129,7 +129,7 @@ class ContactRoleFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug']
class ContactFilterSet(PrimaryModelFilterSet):
class ContactFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',

View File

@ -3,7 +3,7 @@ from django.db.models import Q
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
from extras.filtersets import LocalConfigContextFilterSet
from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet
from utilities.filters import MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter
from .choices import *
@ -32,7 +32,7 @@ class ClusterGroupFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description']
class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
class ClusterFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -107,7 +107,7 @@ class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
)
class VirtualMachineFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
class VirtualMachineFilterSet(NetBoxModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -233,7 +233,7 @@ class VirtualMachineFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConf
return queryset.exclude(params)
class VMInterfaceFilterSet(PrimaryModelFilterSet):
class VMInterfaceFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',

View File

@ -3,7 +3,7 @@ from django.db.models import Q
from dcim.choices import LinkStatusChoices
from ipam.models import VLAN
from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
from utilities.filters import MultiValueNumberFilter, TreeNodeMultipleChoiceFilter
from .choices import *
from .models import *
@ -30,7 +30,7 @@ class WirelessLANGroupFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description']
class WirelessLANFilterSet(PrimaryModelFilterSet):
class WirelessLANFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
@ -70,7 +70,7 @@ class WirelessLANFilterSet(PrimaryModelFilterSet):
return queryset.filter(qs_filter)
class WirelessLinkFilterSet(PrimaryModelFilterSet):
class WirelessLinkFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',