Implement list filtering forms (#57)

Closes #54

Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com>
This commit is contained in:
wkoot
2025-02-13 22:16:32 +01:00
committed by GitHub
parent c3ed952b5f
commit 193f04a208
11 changed files with 193 additions and 33 deletions
+1
View File
@@ -7,6 +7,7 @@
* Toggle to show plugin as top level menu item (#42) * Toggle to show plugin as top level menu item (#42)
* Bulk import except `LaxURLFields` (#4) * Bulk import except `LaxURLFields` (#4)
* Changelog with backdated changes (#51) * Changelog with backdated changes (#51)
* List filtering (#54)
### Changed ### Changed
+1
View File
@@ -60,6 +60,7 @@ class SoftwareProductSerializer(NetBoxModelSerializer):
brief_fields = ("id", "display", "url", "name", "description") brief_fields = ("id", "display", "url", "name", "description")
def get_display(self, obj): def get_display(self, obj):
# TODO - does not match form and filter views, display manufacturer separately?
return f"{obj.manufacturer} - {obj}" return f"{obj.manufacturer} - {obj}"
+63 -4
View File
@@ -1,12 +1,22 @@
from django_filters import CharFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter
from django.db.models import Q from django.db.models import Q
from dcim.models import Device, Manufacturer
from netbox.filtersets import NetBoxModelFilterSet from netbox.filtersets import NetBoxModelFilterSet
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense, SoftwareReleaseTypes
from virtualization.models import VirtualMachine, Cluster
class SoftwareProductFilterSet(NetBoxModelFilterSet): class SoftwareProductFilterSet(NetBoxModelFilterSet):
"""Filter capabilities for SoftwareProduct instances.""" """Filter capabilities for SoftwareProduct instances."""
name = CharFilter(lookup_expr="icontains")
description = CharFilter(lookup_expr="icontains")
manufacturer = ModelMultipleChoiceFilter(
queryset=Manufacturer.objects.all()
)
class Meta: class Meta:
model = SoftwareProduct model = SoftwareProduct
fields = tuple() fields = tuple()
@@ -27,9 +37,24 @@ class SoftwareProductFilterSet(NetBoxModelFilterSet):
class SoftwareProductVersionFilterSet(NetBoxModelFilterSet): class SoftwareProductVersionFilterSet(NetBoxModelFilterSet):
"""Filter capabilities for SoftwareProductVersion instances.""" """Filter capabilities for SoftwareProductVersion instances."""
name = CharFilter(lookup_expr="icontains")
filename = CharFilter(lookup_expr="icontains")
release_type = MultipleChoiceFilter(choices=SoftwareReleaseTypes.choices)
manufacturer = ModelMultipleChoiceFilter(
field_name="software_product__manufacturer",
queryset=Manufacturer.objects.all(),
)
software_product = ModelMultipleChoiceFilter(
queryset=SoftwareProduct.objects.all(),
label="Software Product",
)
class Meta: class Meta:
model = SoftwareProductVersion model = SoftwareProductVersion
fields = ("software_product",) fields = tuple()
def search(self, queryset, name, value): def search(self, queryset, name, value):
"""Perform the filtered search.""" """Perform the filtered search."""
@@ -47,9 +72,27 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet):
class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet): class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet):
"""Filter capabilities for SoftwareProductInstallation instances.""" """Filter capabilities for SoftwareProductInstallation instances."""
device = ModelMultipleChoiceFilter(
queryset=Device.objects.all()
)
virtualmachine = ModelMultipleChoiceFilter(
queryset=VirtualMachine.objects.all(),
label="Virtual Machine",
)
cluster = ModelMultipleChoiceFilter(
queryset=Cluster.objects.all()
)
software_product = ModelMultipleChoiceFilter(
queryset=SoftwareProduct.objects.all(),
label="Software Product",
)
version = ModelMultipleChoiceFilter(
queryset=SoftwareProductVersion.objects.all()
)
class Meta: class Meta:
model = SoftwareProductInstallation model = SoftwareProductInstallation
fields = ("software_product",) fields = tuple()
def search(self, queryset, name, value): def search(self, queryset, name, value):
"""Perform the filtered search.""" """Perform the filtered search."""
@@ -67,9 +110,25 @@ class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet):
class SoftwareLicenseFilterSet(NetBoxModelFilterSet): class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
"""Filter capabilities for SoftwareLicense instances.""" """Filter capabilities for SoftwareLicense instances."""
name = CharFilter(lookup_expr="icontains")
description = CharFilter(lookup_expr="icontains")
type = CharFilter(lookup_expr="icontains")
stored_location = CharFilter(lookup_expr="icontains")
software_product = ModelMultipleChoiceFilter(
queryset=SoftwareProduct.objects.all(),
label="Software Product",
)
version = ModelMultipleChoiceFilter(
queryset=SoftwareProductVersion.objects.all()
)
installation = ModelMultipleChoiceFilter(
queryset=SoftwareProductInstallation.objects.all()
)
class Meta: class Meta:
model = SoftwareLicense model = SoftwareLicense
fields = tuple() fields = ("support",)
def search(self, queryset, name, value): def search(self, queryset, name, value):
"""Perform the filtered search.""" """Perform the filtered search."""
+30 -3
View File
@@ -1,9 +1,10 @@
from django.forms import DateField from django.forms import CharField, DateField, ChoiceField
from django.urls import reverse_lazy from django.urls import reverse_lazy
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, LaxURLField from utilities.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, LaxURLField, DynamicModelMultipleChoiceField
from utilities.forms.rendering import FieldSet from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import APISelect, DatePicker from utilities.forms.widgets import APISelect, DatePicker
@@ -18,6 +19,7 @@ class SoftwareLicenseForm(NetBoxModelForm):
software_product = DynamicModelChoiceField( software_product = DynamicModelChoiceField(
queryset=SoftwareProduct.objects.all(), queryset=SoftwareProduct.objects.all(),
required=True, required=True,
label="Software Product",
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}), widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
) )
version = DynamicModelChoiceField( version = DynamicModelChoiceField(
@@ -63,9 +65,34 @@ class SoftwareLicenseForm(NetBoxModelForm):
class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm): class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
model = SoftwareLicense model = SoftwareLicense
fieldsets = (FieldSet(None, ("q", "tag")),) fieldsets = (
FieldSet("q", "filter_id", "tag"),
FieldSet("name", "description", "type", "stored_location", "support", "software_product", "version", "installation"),
)
selector_fields = ("q", "filter_id", "name")
tag = TagFilterField(model) tag = TagFilterField(model)
name = CharField(required=False)
description = CharField(required=False)
type = CharField(required=False)
stored_location = CharField(required=False)
support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES)
software_product = DynamicModelMultipleChoiceField(
queryset=SoftwareProduct.objects.all(),
required=False,
label="Software Product",
)
version = DynamicModelMultipleChoiceField(
queryset=SoftwareProductVersion.objects.all(),
required=False,
)
installation = DynamicModelMultipleChoiceField(
queryset=SoftwareProductInstallation.objects.all(),
required=False,
)
class SoftwareLicenseBulkImportForm(NetBoxModelImportForm): class SoftwareLicenseBulkImportForm(NetBoxModelImportForm):
class Meta: class Meta:
+16 -2
View File
@@ -1,7 +1,9 @@
from django.forms import CharField
from dcim.models import Manufacturer from dcim.models import Manufacturer
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm
from netbox_slm.models import SoftwareProduct from netbox_slm.models import SoftwareProduct
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, DynamicModelMultipleChoiceField
from utilities.forms.rendering import FieldSet from utilities.forms.rendering import FieldSet
@@ -28,9 +30,21 @@ class SoftwareProductForm(NetBoxModelForm):
class SoftwareProductFilterForm(NetBoxModelFilterSetForm): class SoftwareProductFilterForm(NetBoxModelFilterSetForm):
model = SoftwareProduct model = SoftwareProduct
fieldsets = (FieldSet(None, ("q", "tag")),) fieldsets = (
FieldSet("q", "filter_id", "tag"),
FieldSet("name", "description", "manufacturer"),
)
selector_fields = ("q", "filter_id", "name")
tag = TagFilterField(model) tag = TagFilterField(model)
name = CharField(required=False)
description = CharField(required=False)
manufacturer = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
)
class SoftwareProductBulkImportForm(NetBoxModelImportForm): class SoftwareProductBulkImportForm(NetBoxModelImportForm):
class Meta: class Meta:
@@ -4,7 +4,7 @@ from django.urls import reverse_lazy
from dcim.models import Device from dcim.models import Device
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm
from netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion from netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, DynamicModelMultipleChoiceField
from utilities.forms.rendering import FieldSet from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import APISelect from utilities.forms.widgets import APISelect
from virtualization.models import VirtualMachine, Cluster from virtualization.models import VirtualMachine, Cluster
@@ -16,11 +16,16 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
comments = CommentField() comments = CommentField()
device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
virtualmachine = DynamicModelChoiceField(queryset=VirtualMachine.objects.all(), required=False) virtualmachine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
label="Virtual Machine",
)
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False) cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
software_product = DynamicModelChoiceField( software_product = DynamicModelChoiceField(
queryset=SoftwareProduct.objects.all(), queryset=SoftwareProduct.objects.all(),
required=True, required=True,
label="Software Product",
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}), widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
) )
version = DynamicModelChoiceField( version = DynamicModelChoiceField(
@@ -57,9 +62,37 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm): class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
model = SoftwareProductInstallation model = SoftwareProductInstallation
fieldsets = (FieldSet(None, ("q", "tag")),) fieldsets = (
FieldSet("q", "filter_id", "tag"),
FieldSet("device", "virtualmachine", "cluster", "software_product", "version"),
)
selector_fields = ("filter_id", "q")
tag = TagFilterField(model) tag = TagFilterField(model)
device = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
)
virtualmachine = DynamicModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
label="Virtual Machine",
)
cluster = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
)
software_product = DynamicModelMultipleChoiceField(
queryset=SoftwareProduct.objects.all(),
required=False,
label="Software Product",
)
version = DynamicModelMultipleChoiceField(
queryset=SoftwareProductVersion.objects.all(),
required=False,
)
class SoftwareProductInstallationBulkImportForm(NetBoxModelImportForm): class SoftwareProductInstallationBulkImportForm(NetBoxModelImportForm):
class Meta: class Meta:
+27 -4
View File
@@ -1,9 +1,10 @@
from django.forms import DateField from django.forms import DateField, CharField, MultipleChoiceField
from django.urls import reverse_lazy from django.urls import reverse_lazy
from dcim.models import Manufacturer
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareReleaseTypes
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, DynamicModelMultipleChoiceField
from utilities.forms.rendering import FieldSet from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import APISelect, DatePicker from utilities.forms.widgets import APISelect, DatePicker
@@ -18,6 +19,8 @@ class SoftwareProductVersionForm(NetBoxModelForm):
software_product = DynamicModelChoiceField( software_product = DynamicModelChoiceField(
queryset=SoftwareProduct.objects.all(), queryset=SoftwareProduct.objects.all(),
required=True,
label="Software Product",
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}), widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
) )
@@ -40,9 +43,29 @@ class SoftwareProductVersionForm(NetBoxModelForm):
class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm): class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm):
model = SoftwareProductVersion model = SoftwareProductVersion
fieldsets = (FieldSet(None, ("q", "tag")),) fieldsets = (
FieldSet("q", "filter_id", "tag"),
FieldSet("name", "filename", "release_type", "manufacturer", "software_product"),
)
selector_fields = ("q", "filter_id", "name")
tag = TagFilterField(model) tag = TagFilterField(model)
name = CharField(required=False)
filename = CharField(required=False)
release_type = MultipleChoiceField(required=False, choices=SoftwareReleaseTypes.choices)
manufacturer = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
)
software_product = DynamicModelMultipleChoiceField(
queryset=SoftwareProduct.objects.all(),
required=False,
label="Software Product",
)
class SoftwareProductVersionBulkImportForm(NetBoxModelImportForm): class SoftwareProductVersionBulkImportForm(NetBoxModelImportForm):
class Meta: class Meta:
+2
View File
@@ -145,6 +145,7 @@ class SoftwareProductInstallationTable(NetBoxTable):
return queryset_union.order_by(f"{'-' if is_descending else ''}render_type"), True return queryset_union.order_by(f"{'-' if is_descending else ''}render_type"), True
def render_software_product(self, value, **kwargs): def render_software_product(self, value, **kwargs):
# TODO - does not match form and filter views, display manufacturer separately?
return f"{kwargs['record'].software_product.manufacturer.name} - {value}" return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
@@ -190,6 +191,7 @@ class SoftwareLicenseTable(NetBoxTable):
) )
def render_software_product(self, value, **kwargs): def render_software_product(self, value, **kwargs):
# TODO - does not match form and filter views, display manufacturer separately?
return f"{kwargs['record'].software_product.manufacturer.name} - {value}" return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
def render_installation(self, **kwargs): def render_installation(self, **kwargs):
@@ -20,6 +20,18 @@
<th scope="row">Description</th> <th scope="row">Description</th>
<td>{{ object.description }}</td> <td>{{ object.description }}</td>
</tr> </tr>
<tr>
<th scope="row">Software Product</th>
<td>{{ object.software_product|linkify }}</td>
</tr>
<tr>
<th scope="row">Version</th>
<td>{{ object.version|linkify }}</td>
</tr>
<tr>
<th scope="row">Installation</th>
<td>{{ object.installation|linkify }}</td>
</tr>
<tr> <tr>
<th scope="row">Type</th> <th scope="row">Type</th>
<td>{{ object.type }}</td> <td>{{ object.type }}</td>
@@ -40,14 +52,6 @@
<th scope="row">Expiration date</th> <th scope="row">Expiration date</th>
<td>{{ object.expiration_date }}</td> <td>{{ object.expiration_date }}</td>
</tr> </tr>
<tr>
<th scope="row">Software Product</th>
<td>{{ object.software_product|linkify }}</td>
</tr>
<tr>
<th scope="row">Version</th>
<td>{{ object.version|linkify }}</td>
</tr>
<tr> <tr>
<th scope="row">Support</th> <th scope="row">Support</th>
<td>{{ object.support }}</td> <td>{{ object.support }}</td>
@@ -56,10 +60,6 @@
<th scope="row">License amount</th> <th scope="row">License amount</th>
<td>{{ object.license_amount }}</td> <td>{{ object.license_amount }}</td>
</tr> </tr>
<tr>
<th scope="row">Installation</th>
<td>{{ object.installation|linkify }}</td>
</tr>
</table> </table>
</div> </div>
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/custom_fields.html' %}
@@ -19,7 +19,7 @@
</tr> </tr>
{% elif object.virtualmachine %} {% elif object.virtualmachine %}
<tr> <tr>
<th scope="row">Virtualmachine</th> <th scope="row">Virtual Machine</th>
<td>{{ object.virtualmachine|linkify }}</td> <td>{{ object.virtualmachine|linkify }}</td>
</tr> </tr>
{% else %} {% else %}
@@ -16,14 +16,14 @@
<th scope="row">Name</th> <th scope="row">Name</th>
<td>{{ object.name }}</td> <td>{{ object.name }}</td>
</tr> </tr>
<tr>
<th scope="row">Software Product</th>
<td>{{ object.software_product|linkify }}</td>
</tr>
<tr> <tr>
<th scope="row">Manufacturer</th> <th scope="row">Manufacturer</th>
<td>{{ object.software_product.manufacturer|linkify }}</td> <td>{{ object.software_product.manufacturer|linkify }}</td>
</tr> </tr>
<tr>
<th scope="row">Software Product</th>
<td>{{ object.software_product|linkify }}</td>
</tr>
<tr> <tr>
<th scope="row">Release date</th> <th scope="row">Release date</th>
<td>{{ object.release_date }}</td> <td>{{ object.release_date }}</td>