Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion validity/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
LocationSerializer,
ManufacturerSerializer,
PlatformSerializer,
RegionSerializer,
SiteSerializer,
)
from dcim.models import Device, DeviceType, Location, Manufacturer, Platform, Site
from dcim.models import Device, DeviceType, Location, Manufacturer, Platform, Region, Site
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from extras.api.serializers import TagSerializer
Expand Down Expand Up @@ -67,6 +68,9 @@ class ComplianceSelectorSerializer(NetBoxModelSerializer):
site_filter = SerializedPKRelatedField(
serializer=SiteSerializer, many=True, nested=True, required=False, queryset=Site.objects.all()
)
region_filter = SerializedPKRelatedField(
serializer=RegionSerializer, many=True, nested=True, required=False, queryset=Region.objects.all()
)
tenant_filter = SerializedPKRelatedField(
serializer=TenantSerializer, many=True, nested=True, required=False, queryset=Tenant.objects.all()
)
Expand All @@ -88,6 +92,7 @@ class Meta:
"status_filter",
"location_filter",
"site_filter",
"region_filter",
"tenant_filter",
"dynamic_pairs",
"dp_tag_prefix",
Expand Down
9 changes: 8 additions & 1 deletion validity/forms/bulk_import.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from core.models import DataFile, DataSource
from dcim.choices import DeviceStatusChoices
from dcim.models import DeviceRole, DeviceType, Location, Manufacturer, Platform, Site
from dcim.models import DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site
from django.forms import Form
from django.utils.translation import gettext_lazy as _
from extras.models import Tag
Expand Down Expand Up @@ -158,6 +158,12 @@ class ComplianceSelectorImportForm(NetBoxModelImportForm):
help_text=_("Site slugs separated by commas, encased with double quotes"),
required=False,
)
region_filter = CSVModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name="slug",
help_text=_("Region slugs separated by commas, encased with double quotes"),
required=False,
)
tenant_filter = CSVModelMultipleChoiceField(
queryset=Tenant.objects.all(),
to_field_name="slug",
Expand All @@ -182,6 +188,7 @@ class Meta:
"status_filter",
"location_filter",
"site_filter",
"region_filter",
"tenant_filter",
"dynamic_pairs",
"dp_tag_prefix",
Expand Down
4 changes: 3 additions & 1 deletion validity/forms/general.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from core.forms.mixins import SyncedDataMixin
from core.models import DataSource
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Site
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site
from django.forms import BooleanField, CharField, ChoiceField, IntegerField, Select, Textarea, ValidationError
from django.utils.translation import gettext_lazy as _
from extras.forms import ScriptForm
Expand Down Expand Up @@ -56,6 +56,7 @@ class ComplianceSelectorForm(NetBoxModelForm):
platform_filter = DynamicModelMultipleChoiceField(queryset=Platform.objects.all(), required=False)
location_filter = DynamicModelMultipleChoiceField(queryset=Location.objects.all(), required=False)
site_filter = DynamicModelMultipleChoiceField(queryset=Site.objects.all(), required=False)
region_filter = DynamicModelMultipleChoiceField(queryset=Region.objects.all(), required=False)
tenant_filter = DynamicModelMultipleChoiceField(queryset=Tenant.objects.all(), required=False)

fieldsets = (
Expand All @@ -70,6 +71,7 @@ class ComplianceSelectorForm(NetBoxModelForm):
"manufacturer_filter",
"platform_filter",
"site_filter",
"region_filter",
"status_filter",
"tag_filter",
"tenant_filter",
Expand Down
18 changes: 18 additions & 0 deletions validity/migrations/0014_add_region_to_selector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 6.1 on 2026-09-13 22:09

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('validity', '0013_compliancetest_enabled'),
]

operations = [
migrations.AddField(
model_name='complianceselector',
name='region_filter',
field=models.ManyToManyField(blank=True, related_name='+', to='dcim.region'),
),
]
5 changes: 4 additions & 1 deletion validity/models/selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Generator

from dcim.choices import DeviceStatusChoices
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Site
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -38,6 +38,7 @@ class ComplianceSelector(BaseModel):
status_filter = models.CharField(max_length=50, choices=DeviceStatusChoices, blank=True)
location_filter = models.ManyToManyField(Location, verbose_name=_("Location Filter"), blank=True, related_name="+")
site_filter = models.ManyToManyField(Site, verbose_name=_("Site Filter"), blank=True, related_name="+")
region_filter = models.ManyToManyField(Region, verbose_name=_("Region Filter"), blank=True, related_name="+")
tenant_filter = models.ManyToManyField(Tenant, verbose_name=_("Tenant Filter"), blank=True, related_name="+")
dynamic_pairs = models.CharField(
_("Dynamic Pairs"), max_length=20, choices=DynamicPairsChoices.choices, default="NO"
Expand All @@ -57,6 +58,7 @@ class ComplianceSelector(BaseModel):
"status_filter",
"location_filter",
"site_filter",
"region_filter",
"tenant_filter",
"dynamic_pairs",
)
Expand All @@ -71,6 +73,7 @@ class ComplianceSelector(BaseModel):
"status_filter": "status",
"location_filter": "location",
"site_filter": "site",
"region_filter": "site__region",
"tenant_filter": "tenant",
}

Expand Down
2 changes: 2 additions & 0 deletions validity/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class SelectorTable(NetBoxTable):
status_filter = BooleanColumn(empty_values=())
location_filter = BooleanColumn(accessor="location_filter__all")
site_filter = BooleanColumn(accessor="site_filter__all")
region_filter = BooleanColumn(accessor="region_filter__all")
tenant_filter = BooleanColumn(accessor="tenant_filter__all")
dynamic_pairs = ChoiceFieldColumn()

Expand All @@ -48,6 +49,7 @@ class Meta(NetBoxTable.Meta):
"status_filter",
"location_filter",
"site_filter",
"region_filter",
"tenant_filter",
"dynamic_pairs",
)
Expand Down
5 changes: 5 additions & 0 deletions validity/templates/validity/complianceselector.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ <h5 class="card-header">Filters</h5>
<th scope="row">Site Filter</th>
<td>{{ object.site_filter.all | linkify_list }}</td>
</tr>
<tr>
<tr>
<th scope="row">Region Filter</th>
<td>{{ object.region_filter.all | linkify_list }}</td>
</tr>
<tr>
<th scope="row">Status Filter</th>
<td>{{ object | colored_choice:"status_filter" | placeholder }}</td>
Expand Down
10 changes: 9 additions & 1 deletion validity/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import django
import factory
from core.models import Job
from dcim.models import DeviceRole, DeviceType, Location, Manufacturer, Platform, Site
from dcim.models import DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from extras.models import Tag
Expand Down Expand Up @@ -168,6 +168,14 @@ class Meta:
model = Site


class RegionFactory(DjangoModelFactory):
name = factory.Sequence(lambda n: f"region-{n}")
slug = factory.Sequence(lambda n: f"region-{n}")

class Meta:
model = Region


class TenantFactory(DjangoModelFactory):
name = factory.Sequence(lambda n: f"tenant-{n}")
slug = factory.Sequence(lambda n: f"tenant-{n}")
Expand Down
1 change: 1 addition & 0 deletions validity/tests/test_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ def test_prefetch_filters(monkeypatch):
"platform_filter",
"location_filter",
"site_filter",
"region_filter",
"tenant_filter",
}
18 changes: 18 additions & 0 deletions validity/tests/test_models/test_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
LocationFactory,
ManufacturerFactory,
PlatformFactory,
RegionFactory,
SelectorFactory,
SiteFactory,
TagFactory,
Expand All @@ -34,6 +35,12 @@
("status_filter", "ACTIVE", "OR", "(AND: ('status', 'ACTIVE'))"),
("location_filter", [LocationFactory], "AND", "(AND: ('location', <Location: location-0>))"),
("site_filter", [SiteFactory], "AND", "(AND: ('site', <Site: site-0>))"),
(
"region_filter",
[RegionFactory],
"AND",
"(AND: ('site__region', <Region: region-0>))",
),
],
)
@pytest.mark.django_db(transaction=True, reset_sequences=True)
Expand All @@ -49,6 +56,17 @@ def test_filter(attr, attr_value, filter_operation, expected_filter):
assert str(model.filter) == expected_filter


@pytest.mark.django_db
def test_region_filter():
reg1 = RegionFactory()
reg2 = RegionFactory()
sel = SelectorFactory()
sel.region_filter.set([reg1])
d1 = DeviceFactory(name="d1", site__region=reg1)
DeviceFactory(name="d2", site__region=reg2)
assert list(sel.devices) == [d1]


@pytest.mark.django_db
def test_multi_filter():
model = SelectorFactory()
Expand Down
Loading