Filters enable users to request only a specific subset of objects matching a query; when filtering the sites list by status or region, for instance. NetBox employs the django-filters
library to build and apply filter sets for models. We can create filter sets to enable this same functionality for our plugin as well.
🟦 Note: If you skipped the previous step, run git checkout step07-navigation
.
Begin by creating filtersets.py
in the netbox_access_lists/
directory.
$ cd netbox_access_lists/
$ edit filtersets.py
At the top of this file, we'll import NetBox's NetBoxModelFilterSet
class, which will serve as the base class for our filter set, as well as our AccessListRule
model. (In the interest of brevity, we're only going to create a filter set for one model, but it should be clear how to replicate this approach for the AccessList
model as well.)
from netbox.filtersets import NetBoxModelFilterSet
from .models import AccessListRule
Next, create a class named AccessListRuleFilterSet
subclassing NetBoxModelFilterSet
. Within this class, create a child Meta
class and define the filter set's model
and fields
attributes. (You may notice this looks familiar; it is very similar to the process for building a model form.) The fields
parameter should list all the model fields against which we might want to filter.
class AccessListRuleFilterSet(NetBoxModelFilterSet):
class Meta:
model = AccessListRule
fields = ('id', 'access_list', 'index', 'protocol', 'action')
NetBoxModelFilterSet
handles some important functions for us, including support for filtering by custom field values and tags. It also creates a general-purpose q
filter which invokes the search()
method. (By default, this does nothing.) We can override this method to define our general-purpose search logic. Let's add a search
method after the Meta
child class to override the default behavior.
def search(self, queryset, name, value):
return queryset.filter(description__icontains=value)
This will return all rules whose description contains the queried string. Of course, you're free to extend this to match other fields as well, but for our purposes this should be sufficient.
The filter set handles the "behind the scenes" process of filtering queries, but we also need to create a form class to render the filter fields in the UI. We'll add this to forms.py
. First, import Django's forms
module (which will provide the field classes we need) and append NetBoxModelFilterSetForm
to the existing import statement for netbox.forms
:
from django import forms
# ...
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm
Then create a form class named AccessListRuleFilterForm
subclassing NetBoxModelFilterSetForm
and declare an attribute named model
referencing AccessListRule
(which has already been imported for one of the existing forms).
class AccessListRuleFilterForm(NetBoxModelFilterSetForm):
model = AccessListRule
🟦 Note: Note that the model
attribute is declared directly under the class: We don't need a Meta
child class.
Next, we need to define a form field for each filter that we want to appear in the UI. Let's start with the access_list
filter: This references a related object, so we'll want to use ModelMultipleChoiceField
(to allow users to filter by multiple objects). Add the form field with the same name as its peer filter, specifying the queryset to use when fetching related objects.
access_list = forms.ModelMultipleChoiceField(
queryset=AccessList.objects.all(),
required=False
)
Notice that we've also set required=False
: This should be the case for all fields in a filter form, because filters are never mandatory.
🟦 Note: We're using Django's ModelMultipleChoiceField
class for this field instead of NetBox's DynamicModelChoiceField
because the latter requires a functional REST API endpoint for the model. Once we implement a REST API in step nine, you're free to revisit this form and change access_list
to a DynamicModelChoiceField
.
Next we'll add a field for the position
filter: This is an integer field, so IntegerField
should work nicely:
index = forms.IntegerField(
required=False
)
Finally, we'll add fields for the protocol
and action
choice-based filters. MultipleChoiceField
should be used to allow users to select one or more choices. We must pass the set of valid choices when declaring these fields, so first extend the relevant import statement at the top of forms.py
:
from .models import AccessList, AccessListRule, ActionChoices, ProtocolChoices
Then add the form fields to AccessListRuleFilterForm
:
protocol = forms.MultipleChoiceField(
choices=ProtocolChoices,
required=False
)
action = forms.MultipleChoiceField(
choices=ActionChoices,
required=False
)
The last step before we can use our new filter set and form is to enable them under the model's list view. Open views.py
and extend the last import statement to include the filtersets
module:
from . import filtersets, forms, models, tables
Then, add the filterset
and filterset_form
attributes to AccessListRuleListView
:
class AccessListRuleListView(generic.ObjectListView):
queryset = models.AccessListRule.objects.all()
table = tables.AccessListRuleTable
filterset = filtersets.AccessListRuleFilterSet
filterset_form = forms.AccessListRuleFilterForm
After ensuring the development server has restarted, navigate to the rules list view in the browser. You should now see a "Filters" tab next to the "Results" tab. Under it we'll find the four fields we created on AccessListRuleFilterForm
, as well as the built-in "search" field.
If you haven't already, create a few more access lists and rules, and experiment with the filters. Consider how you might filter by additional fields, or add more complex logic to the filter set.
🟢 Tip: You may notice that we did not add a form field for the model's id
filter: This is because it is unlikely to be useful for a human utilizing the UI. However, we still want to support filtering object by their primary keys, because it is very helpful for consumers of NetBox's REST API, which we'll cover next.
⬅️ Step 7: Navigation | Step 9: REST API ➡️