Merge branch 'develop' into feature

This commit is contained in:
jeremystretch 2022-02-25 14:06:45 -05:00
commit fab4d95156
28 changed files with 279 additions and 104 deletions

View File

@ -2,6 +2,20 @@
## v3.1.9 (FUTURE)
### Enhancements
* [#8594](https://github.com/netbox-community/netbox/issues/8594) - Enable filtering by exact description match for all applicable models
* [#8629](https://github.com/netbox-community/netbox/issues/8629) - Add description to tag table search function
### Bug Fixes
* [#8546](https://github.com/netbox-community/netbox/issues/8546) - Fix bulk import to restrict bridge, parent, and LAG to device interfaces
* [#8633](https://github.com/netbox-community/netbox/issues/8633) - Prevent navigation sidebar pin from disappearing at certain breakpoints
* [#8674](https://github.com/netbox-community/netbox/issues/8674) - Fix rendering of tabbed content in documentation
* [#8710](https://github.com/netbox-community/netbox/issues/8710) - Fix dynamic scope selection form fields when creating a VLAN group
* [#8713](https://github.com/netbox-community/netbox/issues/8713) - Restore missing "add" button on services list view
* [#8717](https://github.com/netbox-community/netbox/issues/8717) - Fix redirection after bulk edit/delete of prefixes from aggregate view
---
## v3.1.8 (2022-02-15)

View File

@ -50,7 +50,8 @@ markdown_extensions:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
- pymdownx.superfences
- pymdownx.tabbed
- pymdownx.tabbed:
alternate_style: true
nav:
- Introduction: 'index.md'
- Installation:

View File

@ -95,7 +95,7 @@ class ProviderNetworkFilterSet(NetBoxModelFilterSet):
class Meta:
model = ProviderNetwork
fields = ['id', 'name', 'service_id']
fields = ['id', 'name', 'service_id', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -112,7 +112,7 @@ class CircuitTypeFilterSet(OrganizationalModelFilterSet):
class Meta:
model = CircuitType
fields = ['id', 'name', 'slug']
fields = ['id', 'name', 'slug', 'description']
class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
@ -189,7 +189,7 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
class Meta:
model = Circuit
fields = ['id', 'cid', 'install_date', 'commit_rate']
fields = ['id', 'cid', 'description', 'install_date', 'commit_rate']
def search(self, queryset, name, value):
if not value.strip():
@ -230,7 +230,7 @@ class CircuitTerminationFilterSet(ChangeLoggedModelFilterSet, CableTerminationFi
class Meta:
model = CircuitTermination
fields = ['id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id']
fields = ['id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'description']
def search(self, queryset, name, value):
if not value.strip():

View File

@ -108,8 +108,8 @@ class CircuitTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
def setUpTestData(cls):
CircuitType.objects.bulk_create((
CircuitType(name='Circuit Type 1', slug='circuit-type-1'),
CircuitType(name='Circuit Type 2', slug='circuit-type-2'),
CircuitType(name='Circuit Type 1', slug='circuit-type-1', description='foobar1'),
CircuitType(name='Circuit Type 2', slug='circuit-type-2', description='foobar2'),
CircuitType(name='Circuit Type 3', slug='circuit-type-3'),
))
@ -121,6 +121,10 @@ class CircuitTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'slug': ['circuit-type-1']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = Circuit.objects.all()
@ -187,8 +191,8 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
ProviderNetwork.objects.bulk_create(provider_networks)
circuits = (
Circuit(provider=providers[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', commit_rate=1000, status=CircuitStatusChoices.STATUS_ACTIVE),
Circuit(provider=providers[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', commit_rate=2000, status=CircuitStatusChoices.STATUS_ACTIVE),
Circuit(provider=providers[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', commit_rate=1000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar1'),
Circuit(provider=providers[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', commit_rate=2000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar2'),
Circuit(provider=providers[0], tenant=tenants[1], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', commit_rate=3000, status=CircuitStatusChoices.STATUS_PLANNED),
Circuit(provider=providers[1], tenant=tenants[1], type=circuit_types[1], cid='Test Circuit 4', install_date='2020-01-04', commit_rate=4000, status=CircuitStatusChoices.STATUS_PLANNED),
Circuit(provider=providers[1], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 5', install_date='2020-01-05', commit_rate=5000, status=CircuitStatusChoices.STATUS_OFFLINE),
@ -241,6 +245,10 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'status': [CircuitStatusChoices.STATUS_ACTIVE, CircuitStatusChoices.STATUS_PLANNED]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_region(self):
regions = Region.objects.all()[:2]
params = {'region_id': [regions[0].pk, regions[1].pk]}
@ -319,8 +327,8 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
Circuit.objects.bulk_create(circuits)
circuit_terminations = ((
CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000, upstream_speed=1000, xconnect_id='ABC'),
CircuitTermination(circuit=circuits[0], site=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF'),
CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000, upstream_speed=1000, xconnect_id='ABC', description='foobar1'),
CircuitTermination(circuit=circuits[0], site=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF', description='foobar2'),
CircuitTermination(circuit=circuits[1], site=sites[1], term_side='A', port_speed=2000, upstream_speed=2000, xconnect_id='GHI'),
CircuitTermination(circuit=circuits[1], site=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'),
CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'),
@ -349,6 +357,10 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'xconnect_id': ['ABC', 'DEF']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_circuit_id(self):
circuits = Circuit.objects.all()[:2]
params = {'circuit_id': [circuits[0].pk, circuits[1].pk]}
@ -386,8 +398,8 @@ class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests):
Provider.objects.bulk_create(providers)
provider_networks = (
ProviderNetwork(name='Provider Network 1', provider=providers[0]),
ProviderNetwork(name='Provider Network 2', provider=providers[1]),
ProviderNetwork(name='Provider Network 1', provider=providers[0], description='foobar1'),
ProviderNetwork(name='Provider Network 2', provider=providers[1], description='foobar2'),
ProviderNetwork(name='Provider Network 3', provider=providers[2]),
)
ProviderNetwork.objects.bulk_create(provider_networks)
@ -396,6 +408,10 @@ class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'name': ['Provider Network 1', 'Provider Network 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_provider(self):
providers = Provider.objects.all()[:2]
params = {'provider_id': [providers[0].pk, providers[1].pk]}

View File

@ -149,7 +149,7 @@ class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
class Meta:
model = Site
fields = (
'id', 'name', 'slug', 'facility', 'latitude', 'longitude',
'id', 'name', 'slug', 'facility', 'latitude', 'longitude', 'description'
)
def search(self, queryset, name, value):
@ -239,7 +239,7 @@ class RackRoleFilterSet(OrganizationalModelFilterSet):
class Meta:
model = RackRole
fields = ['id', 'name', 'slug', 'color']
fields = ['id', 'name', 'slug', 'color', 'description']
class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
@ -385,7 +385,7 @@ class RackReservationFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
class Meta:
model = RackReservation
fields = ['id', 'created']
fields = ['id', 'created', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -724,7 +724,7 @@ class DeviceRoleFilterSet(OrganizationalModelFilterSet):
class Meta:
model = DeviceRole
fields = ['id', 'name', 'slug', 'color', 'vm_role']
fields = ['id', 'name', 'slug', 'color', 'vm_role', 'description']
class PlatformFilterSet(OrganizationalModelFilterSet):

View File

@ -647,6 +647,19 @@ class InterfaceCSVForm(NetBoxModelCSVForm):
'rf_channel_width', 'tx_power',
)
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
if data:
# Limit interface choices for parent, bridge and lag to device only
params = {}
if data.get('device'):
params[f"device__{self.fields['device'].to_field_name}"] = data.get('device')
if params:
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)
self.fields['bridge'].queryset = self.fields['bridge'].queryset.filter(**params)
self.fields['lag'].queryset = self.fields['lag'].queryset.filter(**params)
def clean_enabled(self):
# Make sure enabled is True when it's not included in the uploaded data
if 'enabled' not in self.data:

View File

@ -151,8 +151,8 @@ class SiteTestCase(TestCase, ChangeLoggedFilterSetTests):
ASN.objects.bulk_create(asns)
sites = (
Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0], tenant=tenants[0], status=SiteStatusChoices.STATUS_ACTIVE, facility='Facility 1', latitude=10, longitude=10),
Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1], tenant=tenants[1], status=SiteStatusChoices.STATUS_PLANNED, facility='Facility 2', latitude=20, longitude=20),
Site(name='Site 1', slug='site-1', region=regions[0], group=groups[0], tenant=tenants[0], status=SiteStatusChoices.STATUS_ACTIVE, facility='Facility 1', latitude=10, longitude=10, description='foobar1'),
Site(name='Site 2', slug='site-2', region=regions[1], group=groups[1], tenant=tenants[1], status=SiteStatusChoices.STATUS_PLANNED, facility='Facility 2', latitude=20, longitude=20, description='foobar1'),
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2], tenant=tenants[2], status=SiteStatusChoices.STATUS_RETIRED, facility='Facility 3', latitude=30, longitude=30),
)
Site.objects.bulk_create(sites)
@ -189,6 +189,10 @@ class SiteTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'longitude': [10, 20]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_status(self):
params = {'status': [SiteStatusChoices.STATUS_ACTIVE, SiteStatusChoices.STATUS_PLANNED]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@ -317,8 +321,8 @@ class RackRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
def setUpTestData(cls):
rack_roles = (
RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000'),
RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00'),
RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000', description='foobar1'),
RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00', description='foobar2'),
RackRole(name='Rack Role 3', slug='rack-role-3', color='0000ff'),
)
RackRole.objects.bulk_create(rack_roles)
@ -335,6 +339,10 @@ class RackRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'color': ['ff0000', '00ff00']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = Rack.objects.all()
@ -558,8 +566,8 @@ class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants)
reservations = (
RackReservation(rack=racks[0], units=[1, 2, 3], user=users[0], tenant=tenants[0]),
RackReservation(rack=racks[1], units=[4, 5, 6], user=users[1], tenant=tenants[1]),
RackReservation(rack=racks[0], units=[1, 2, 3], user=users[0], tenant=tenants[0], description='foobar1'),
RackReservation(rack=racks[1], units=[4, 5, 6], user=users[1], tenant=tenants[1], description='foobar2'),
RackReservation(rack=racks[2], units=[7, 8, 9], user=users[2], tenant=tenants[2]),
)
RackReservation.objects.bulk_create(reservations)
@ -592,6 +600,10 @@ class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'tenant': [tenants[0].slug, tenants[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_tenant_group(self):
tenant_groups = TenantGroup.objects.all()[:2]
params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]}
@ -1302,8 +1314,8 @@ class DeviceRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
def setUpTestData(cls):
device_roles = (
DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000', vm_role=True),
DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00', vm_role=True),
DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000', vm_role=True, description='foobar1'),
DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00', vm_role=True, description='foobar2'),
DeviceRole(name='Device Role 3', slug='device-role-3', color='0000ff', vm_role=False),
)
DeviceRole.objects.bulk_create(device_roles)
@ -1326,6 +1338,10 @@ class DeviceRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'vm_role': 'false'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = Platform.objects.all()

View File

@ -62,7 +62,7 @@ class CustomFieldFilterSet(BaseFilterSet):
class Meta:
model = CustomField
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight']
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -105,7 +105,7 @@ class ExportTemplateFilterSet(BaseFilterSet):
class Meta:
model = ExportTemplate
fields = ['id', 'content_type', 'name']
fields = ['id', 'content_type', 'name', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -179,14 +179,15 @@ class TagFilterSet(ChangeLoggedModelFilterSet):
class Meta:
model = Tag
fields = ['id', 'name', 'slug', 'color']
fields = ['id', 'name', 'slug', 'color', 'description']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(slug__icontains=value)
Q(slug__icontains=value) |
Q(description__icontains=value)
)
def _content_type(self, queryset, name, values):

View File

@ -162,8 +162,8 @@ class ExportTemplateTestCase(TestCase, BaseFilterSetTests):
content_types = ContentType.objects.filter(model__in=['site', 'rack', 'device'])
export_templates = (
ExportTemplate(name='Export Template 1', content_type=content_types[0], template_code='TESTING'),
ExportTemplate(name='Export Template 2', content_type=content_types[1], template_code='TESTING'),
ExportTemplate(name='Export Template 1', content_type=content_types[0], template_code='TESTING', description='foobar1'),
ExportTemplate(name='Export Template 2', content_type=content_types[1], template_code='TESTING', description='foobar2'),
ExportTemplate(name='Export Template 3', content_type=content_types[2], template_code='TESTING'),
)
ExportTemplate.objects.bulk_create(export_templates)
@ -176,6 +176,10 @@ class ExportTemplateTestCase(TestCase, BaseFilterSetTests):
params = {'content_type': ContentType.objects.get(model='site').pk}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class ImageAttachmentTestCase(TestCase, BaseFilterSetTests):
queryset = ImageAttachment.objects.all()
@ -565,8 +569,8 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
def setUpTestData(cls):
tags = (
Tag(name='Tag 1', slug='tag-1', color='ff0000'),
Tag(name='Tag 2', slug='tag-2', color='00ff00'),
Tag(name='Tag 1', slug='tag-1', color='ff0000', description='foobar1'),
Tag(name='Tag 2', slug='tag-2', color='00ff00', description='foobar2'),
Tag(name='Tag 3', slug='tag-3', color='0000ff'),
)
Tag.objects.bulk_create(tags)
@ -590,6 +594,10 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'color': ['ff0000', '00ff00']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_content_type(self):
params = {'content_type': ['dcim.site', 'circuits.provider']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

View File

@ -74,7 +74,7 @@ class VRFFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
class Meta:
model = VRF
fields = ['id', 'name', 'rd', 'enforce_unique']
fields = ['id', 'name', 'rd', 'enforce_unique', 'description']
class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
@ -115,7 +115,7 @@ class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
class Meta:
model = RouteTarget
fields = ['id', 'name']
fields = ['id', 'name', 'description']
class RIRFilterSet(OrganizationalModelFilterSet):
@ -151,7 +151,7 @@ class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
class Meta:
model = Aggregate
fields = ['id', 'date_added']
fields = ['id', 'date_added', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -199,7 +199,7 @@ class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
class Meta:
model = ASN
fields = ['id', 'asn']
fields = ['id', 'asn', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -220,7 +220,7 @@ class RoleFilterSet(OrganizationalModelFilterSet):
class Meta:
model = Role
fields = ['id', 'name', 'slug']
fields = ['id', 'name', 'slug', 'description']
class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
@ -348,7 +348,7 @@ class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
class Meta:
model = Prefix
fields = ['id', 'is_pool', 'mark_utilized']
fields = ['id', 'is_pool', 'mark_utilized', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -453,7 +453,7 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
class Meta:
model = IPRange
fields = ['id']
fields = ['id', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -828,7 +828,7 @@ class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
class Meta:
model = VLAN
fields = ['id', 'vid', 'name']
fields = ['id', 'vid', 'name', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -900,7 +900,7 @@ class ServiceFilterSet(NetBoxModelFilterSet):
class Meta:
model = Service
fields = ['id', 'name', 'protocol']
fields = ['id', 'name', 'protocol', 'description']
def search(self, queryset, name, value):
if not value.strip():

View File

@ -35,8 +35,8 @@ class ASNTestCase(TestCase, ChangeLoggedFilterSetTests):
]
asns = (
ASN(asn=64512, rir=rirs[0], tenant=tenants[0]),
ASN(asn=64513, rir=rirs[0], tenant=tenants[0]),
ASN(asn=64512, rir=rirs[0], tenant=tenants[0], description='foobar1'),
ASN(asn=64513, rir=rirs[0], tenant=tenants[0], description='foobar2'),
ASN(asn=64514, rir=rirs[0], tenant=tenants[1]),
ASN(asn=64515, rir=rirs[0], tenant=tenants[2]),
ASN(asn=64516, rir=rirs[0], tenant=tenants[3]),
@ -86,6 +86,10 @@ class ASNTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'site': [sites[0].slug, sites[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 9)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class VRFTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = VRF.objects.all()
@ -117,8 +121,8 @@ class VRFTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants)
vrfs = (
VRF(name='VRF 1', rd='65000:100', tenant=tenants[0], enforce_unique=False),
VRF(name='VRF 2', rd='65000:200', tenant=tenants[0], enforce_unique=False),
VRF(name='VRF 1', rd='65000:100', tenant=tenants[0], enforce_unique=False, description='foobar1'),
VRF(name='VRF 2', rd='65000:200', tenant=tenants[0], enforce_unique=False, description='foobar2'),
VRF(name='VRF 3', rd='65000:300', tenant=tenants[1], enforce_unique=False),
VRF(name='VRF 4', rd='65000:400', tenant=tenants[1], enforce_unique=True),
VRF(name='VRF 5', rd='65000:500', tenant=tenants[2], enforce_unique=True),
@ -174,6 +178,10 @@ class VRFTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = RouteTarget.objects.all()
@ -198,8 +206,8 @@ class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants)
route_targets = (
RouteTarget(name='65000:1001', tenant=tenants[0]),
RouteTarget(name='65000:1002', tenant=tenants[0]),
RouteTarget(name='65000:1001', tenant=tenants[0], description='foobar1'),
RouteTarget(name='65000:1002', tenant=tenants[0], description='foobar2'),
RouteTarget(name='65000:1003', tenant=tenants[0]),
RouteTarget(name='65000:1004', tenant=tenants[0]),
RouteTarget(name='65000:2001', tenant=tenants[1]),
@ -256,6 +264,10 @@ class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class RIRTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = RIR.objects.all()
@ -323,8 +335,8 @@ class AggregateTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants)
aggregates = (
Aggregate(prefix='10.1.0.0/16', rir=rirs[0], tenant=tenants[0], date_added='2020-01-01'),
Aggregate(prefix='10.2.0.0/16', rir=rirs[0], tenant=tenants[1], date_added='2020-01-02'),
Aggregate(prefix='10.1.0.0/16', rir=rirs[0], tenant=tenants[0], date_added='2020-01-01', description='foobar1'),
Aggregate(prefix='10.2.0.0/16', rir=rirs[0], tenant=tenants[1], date_added='2020-01-02', description='foobar2'),
Aggregate(prefix='10.3.0.0/16', rir=rirs[1], tenant=tenants[2], date_added='2020-01-03'),
Aggregate(prefix='2001:db8:1::/48', rir=rirs[1], tenant=tenants[0], date_added='2020-01-04'),
Aggregate(prefix='2001:db8:2::/48', rir=rirs[2], tenant=tenants[1], date_added='2020-01-05'),
@ -340,6 +352,10 @@ class AggregateTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'date_added': ['2020-01-01', '2020-01-02']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
# TODO: Test for multiple values
def test_prefix(self):
params = {'prefix': '10.1.0.0/16'}
@ -375,8 +391,8 @@ class RoleTestCase(TestCase, ChangeLoggedFilterSetTests):
def setUpTestData(cls):
roles = (
Role(name='Role 1', slug='role-1'),
Role(name='Role 2', slug='role-2'),
Role(name='Role 1', slug='role-1', description='foobar1'),
Role(name='Role 2', slug='role-2', description='foobar2'),
Role(name='Role 3', slug='role-3'),
)
Role.objects.bulk_create(roles)
@ -389,6 +405,10 @@ class RoleTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'slug': ['role-1', 'role-2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class PrefixTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = Prefix.objects.all()
@ -467,8 +487,8 @@ class PrefixTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants)
prefixes = (
Prefix(prefix='10.0.0.0/24', tenant=None, site=None, vrf=None, vlan=None, role=None, is_pool=True, mark_utilized=True),
Prefix(prefix='10.0.1.0/24', tenant=tenants[0], site=sites[0], vrf=vrfs[0], vlan=vlans[0], role=roles[0]),
Prefix(prefix='10.0.0.0/24', tenant=None, site=None, vrf=None, vlan=None, role=None, is_pool=True, mark_utilized=True, description='foobar1'),
Prefix(prefix='10.0.1.0/24', tenant=tenants[0], site=sites[0], vrf=vrfs[0], vlan=vlans[0], role=roles[0], description='foobar2'),
Prefix(prefix='10.0.2.0/24', tenant=tenants[1], site=sites[1], vrf=vrfs[1], vlan=vlans[1], role=roles[1], status=PrefixStatusChoices.STATUS_DEPRECATED),
Prefix(prefix='10.0.3.0/24', tenant=tenants[2], site=sites[2], vrf=vrfs[2], vlan=vlans[2], role=roles[2], status=PrefixStatusChoices.STATUS_RESERVED),
Prefix(prefix='2001:db8::/64', tenant=None, site=None, vrf=None, vlan=None, role=None, is_pool=True, mark_utilized=True),
@ -601,6 +621,10 @@ class PrefixTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class IPRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = IPRange.objects.all()
@ -639,8 +663,8 @@ class IPRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants)
ip_ranges = (
IPRange(start_address='10.0.1.100/24', end_address='10.0.1.199/24', size=100, vrf=None, tenant=None, role=None, status=IPRangeStatusChoices.STATUS_ACTIVE),
IPRange(start_address='10.0.2.100/24', end_address='10.0.2.199/24', size=100, vrf=vrfs[0], tenant=tenants[0], role=roles[0], status=IPRangeStatusChoices.STATUS_ACTIVE),
IPRange(start_address='10.0.1.100/24', end_address='10.0.1.199/24', size=100, vrf=None, tenant=None, role=None, status=IPRangeStatusChoices.STATUS_ACTIVE, description='foobar1'),
IPRange(start_address='10.0.2.100/24', end_address='10.0.2.199/24', size=100, vrf=vrfs[0], tenant=tenants[0], role=roles[0], status=IPRangeStatusChoices.STATUS_ACTIVE, description='foobar2'),
IPRange(start_address='10.0.3.100/24', end_address='10.0.3.199/24', size=100, vrf=vrfs[1], tenant=tenants[1], role=roles[1], status=IPRangeStatusChoices.STATUS_DEPRECATED),
IPRange(start_address='10.0.4.100/24', end_address='10.0.4.199/24', size=100, vrf=vrfs[2], tenant=tenants[2], role=roles[2], status=IPRangeStatusChoices.STATUS_RESERVED),
IPRange(start_address='2001:db8:0:1::1/64', end_address='2001:db8:0:1::100/64', size=100, vrf=None, tenant=None, role=None, status=IPRangeStatusChoices.STATUS_ACTIVE),
@ -692,6 +716,10 @@ class IPRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = IPAddress.objects.all()
@ -1201,8 +1229,8 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
vlans = (
# Create one VLAN per VLANGroup
VLAN(vid=1, name='Region 1', group=groups[0]),
VLAN(vid=2, name='Region 2', group=groups[1]),
VLAN(vid=1, name='Region 1', group=groups[0], description='foobar1'),
VLAN(vid=2, name='Region 2', group=groups[1], description='foobar2'),
VLAN(vid=3, name='Region 3', group=groups[2]),
VLAN(vid=4, name='Site Group 1', group=groups[3]),
VLAN(vid=5, name='Site Group 2', group=groups[4]),
@ -1271,6 +1299,10 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'group': [groups[0].slug, groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_role(self):
roles = Role.objects.all()[:2]
params = {'role_id': [roles[0].pk, roles[1].pk]}
@ -1366,8 +1398,8 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
VirtualMachine.objects.bulk_create(virtual_machines)
services = (
Service(device=devices[0], name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1001]),
Service(device=devices[1], name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1002]),
Service(device=devices[0], name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1001], description='foobar1'),
Service(device=devices[1], name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1002], description='foobar2'),
Service(device=devices[2], name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_UDP, ports=[1003]),
Service(virtual_machine=virtual_machines[0], name='Service 4', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[2001]),
Service(virtual_machine=virtual_machines[1], name='Service 5', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[2002]),
@ -1383,6 +1415,10 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'protocol': ServiceProtocolChoices.PROTOCOL_TCP}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_port(self):
params = {'port': '1001'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

View File

@ -1080,7 +1080,6 @@ class ServiceListView(generic.ObjectListView):
filterset = filtersets.ServiceFilterSet
filterset_form = forms.ServiceFilterForm
table = tables.ServiceTable
actions = ('import', 'export', 'bulk_edit', 'bulk_delete')
class ServiceView(generic.ObjectView):

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,16 @@ type ShowHideMap = {
*
* @example vlangroup_edit
*/
[view: string]: {
[view: string]: string;
};
type ShowHideLayout = {
/**
* Name of layout config
*
* @example vlangroup
*/
[config: string]: {
/**
* Default layout.
*/
@ -19,15 +28,15 @@ type ShowHideMap = {
};
/**
* Mapping of scope names to arrays of object types whose fields should be hidden or shown when
* Mapping of layout names to arrays of object types whose fields should be hidden or shown when
* the scope type (key) is selected.
*
* For example, if `region` is the scope type, the fields with IDs listed in
* showHideMap.region.hide should be hidden, and the fields with IDs listed in
* showHideMap.region.show should be shown.
*/
const showHideMap: ShowHideMap = {
vlangroup_edit: {
const showHideLayout: ShowHideLayout = {
vlangroup: {
region: {
hide: ['id_sitegroup', 'id_site', 'id_location', 'id_rack', 'id_clustergroup', 'id_cluster'],
show: ['id_region'],
@ -70,6 +79,17 @@ const showHideMap: ShowHideMap = {
},
},
};
/**
* Mapping of view names to layout configurations
*
* For example, if `vlangroup_add` is the view, use the layout configuration `vlangroup`.
*/
const showHideMap: ShowHideMap = {
vlangroup_add: 'vlangroup',
vlangroup_edit: 'vlangroup',
};
/**
* Toggle visibility of a given element's parent.
* @param query CSS Query.
@ -94,8 +114,9 @@ function toggleParentVisibility(query: string, action: 'show' | 'hide') {
function handleScopeChange<P extends keyof ShowHideMap>(view: P, element: HTMLSelectElement) {
// Scope type's innerText looks something like `DCIM > region`.
const scopeType = element.options[element.selectedIndex].innerText.toLowerCase();
const layoutConfig = showHideMap[view];
for (const [scope, fields] of Object.entries(showHideMap[view])) {
for (const [scope, fields] of Object.entries(showHideLayout[layoutConfig])) {
// If the scope type ends with the specified scope, toggle its field visibility according to
// the show/hide values.
if (scopeType.endsWith(scope)) {
@ -109,7 +130,7 @@ function handleScopeChange<P extends keyof ShowHideMap>(view: P, element: HTMLSe
break;
} else {
// Otherwise, hide all fields.
for (const field of showHideMap[view].default.hide) {
for (const field of showHideLayout[layoutConfig].default.hide) {
toggleParentVisibility(`#${field}`, 'hide');
}
}

View File

@ -23,7 +23,6 @@ $theme-colors: (
'danger': $danger,
'light': $light,
'dark': $dark,
// General-purpose palette
'blue': $blue-300,
'indigo': $indigo-300,
@ -37,7 +36,7 @@ $theme-colors: (
'cyan': $cyan-300,
'gray': $gray-300,
'black': $black,
'white': $white,
'white': $white
);
// Gradient
@ -146,7 +145,7 @@ $nav-tabs-link-active-border-color: $gray-800 $gray-800 $nav-tabs-link-active-bg
$nav-pills-link-active-color: $component-active-color;
$nav-pills-link-active-bg: $component-active-bg;
$navbar-light-color: $darkest;
$navbar-light-color: $navbar-dark-color;
$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>");
$navbar-light-toggler-border-color: $gray-700;

View File

@ -139,7 +139,7 @@
<body>
<script type="text/javascript">
(function() {
function checkSideNav() {
// Check localStorage to see if the sidebar should be pinned.
var sideNavRaw = localStorage.getItem('netbox-sidenav');
// Determine if the device has a small screeen. This media query is equivalent to
@ -154,11 +154,15 @@
// jumpy/glitchy behavior on page reloads.
document.body.setAttribute('data-sidenav-pinned', '');
document.body.setAttribute('data-sidenav-show', '');
document.body.removeAttribute('data-sidenav-hidden');
} else {
document.body.removeAttribute('data-sidenav-pinned');
document.body.setAttribute('data-sidenav-hidden', '');
}
}
})();
}
window.addEventListener('resize', function(){ checkSideNav() });
checkSideNav();
</script>
{# Page layout #}

View File

@ -25,12 +25,12 @@
<div class="noprint bulk-buttons">
<div class="bulk-button-group">
{% if perms.ipam.change_prefix %}
<button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
<button type="submit" name="_edit" formaction="{% url 'ipam:prefix_bulk_edit' %}?return_url={% url 'ipam:aggregate_prefixes' pk=object.pk %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
</button>
{% endif %}
{% if perms.ipam.delete_prefix %}
<button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:prefix_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
<button type="submit" name="_delete" formaction="{% url 'ipam:prefix_bulk_delete' %}?return_url={% url 'ipam:aggregate_prefixes' pk=object.pk %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button>
{% endif %}

View File

@ -59,7 +59,7 @@ class TenantFilterSet(NetBoxModelFilterSet):
class Meta:
model = Tenant
fields = ['id', 'name', 'slug']
fields = ['id', 'name', 'slug', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -126,7 +126,7 @@ class ContactRoleFilterSet(OrganizationalModelFilterSet):
class Meta:
model = ContactRole
fields = ['id', 'name', 'slug']
fields = ['id', 'name', 'slug', 'description']
class ContactFilterSet(NetBoxModelFilterSet):

View File

@ -64,8 +64,8 @@ class TenantTestCase(TestCase, ChangeLoggedFilterSetTests):
tenantgroup.save()
tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[1]),
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0], description='foobar1'),
Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[1], description='foobar2'),
Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[2]),
)
Tenant.objects.bulk_create(tenants)
@ -85,6 +85,10 @@ class TenantTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'group': [group[0].slug, group[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class ContactGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = ContactGroup.objects.all()
@ -137,8 +141,8 @@ class ContactRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
def setUpTestData(cls):
contact_roles = (
ContactRole(name='Contact Role 1', slug='contact-role-1'),
ContactRole(name='Contact Role 2', slug='contact-role-2'),
ContactRole(name='Contact Role 1', slug='contact-role-1', description='foobar1'),
ContactRole(name='Contact Role 2', slug='contact-role-2', description='foobar2'),
ContactRole(name='Contact Role 3', slug='contact-role-3'),
)
ContactRole.objects.bulk_create(contact_roles)
@ -151,6 +155,10 @@ class ContactRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'slug': ['contact-role-1', 'contact-role-2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class ContactTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = Contact.objects.all()

View File

@ -97,7 +97,7 @@ class TokenFilterSet(BaseFilterSet):
class Meta:
model = Token
fields = ['id', 'key', 'write_enabled']
fields = ['id', 'key', 'write_enabled', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -138,7 +138,7 @@ class ObjectPermissionFilterSet(BaseFilterSet):
class Meta:
model = ObjectPermission
fields = ['id', 'name', 'enabled', 'object_types']
fields = ['id', 'name', 'enabled', 'object_types', 'description']
def search(self, queryset, name, value):
if not value.strip():

View File

@ -142,8 +142,8 @@ class ObjectPermissionTestCase(TestCase, BaseFilterSetTests):
)
permissions = (
ObjectPermission(name='Permission 1', actions=['view', 'add', 'change', 'delete']),
ObjectPermission(name='Permission 2', actions=['view', 'add', 'change', 'delete']),
ObjectPermission(name='Permission 1', actions=['view', 'add', 'change', 'delete'], description='foobar1'),
ObjectPermission(name='Permission 2', actions=['view', 'add', 'change', 'delete'], description='foobar2'),
ObjectPermission(name='Permission 3', actions=['view', 'add', 'change', 'delete']),
ObjectPermission(name='Permission 4', actions=['view'], enabled=False),
ObjectPermission(name='Permission 5', actions=['add'], enabled=False),
@ -183,6 +183,10 @@ class ObjectPermissionTestCase(TestCase, BaseFilterSetTests):
params = {'object_types': [object_types[0].pk, object_types[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class TokenTestCase(TestCase, BaseFilterSetTests):
queryset = Token.objects.all()
@ -201,8 +205,8 @@ class TokenTestCase(TestCase, BaseFilterSetTests):
future_date = make_aware(datetime.datetime(3000, 1, 1))
past_date = make_aware(datetime.datetime(2000, 1, 1))
tokens = (
Token(user=users[0], key=Token.generate_key(), expires=future_date, write_enabled=True),
Token(user=users[1], key=Token.generate_key(), expires=future_date, write_enabled=True),
Token(user=users[0], key=Token.generate_key(), expires=future_date, write_enabled=True, description='foobar1'),
Token(user=users[1], key=Token.generate_key(), expires=future_date, write_enabled=True, description='foobar2'),
Token(user=users[2], key=Token.generate_key(), expires=past_date, write_enabled=False),
)
Token.objects.bulk_create(tokens)
@ -232,3 +236,7 @@ class TokenTestCase(TestCase, BaseFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'write_enabled': False}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

View File

@ -288,7 +288,7 @@ class VMInterfaceFilterSet(NetBoxModelFilterSet):
class Meta:
model = VMInterface
fields = ['id', 'name', 'enabled', 'mtu']
fields = ['id', 'name', 'enabled', 'mtu', 'description']
def search(self, queryset, name, value):
if not value.strip():

View File

@ -429,8 +429,8 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
VirtualMachine.objects.bulk_create(vms)
interfaces = (
VMInterface(virtual_machine=vms[0], name='Interface 1', enabled=True, mtu=100, mac_address='00-00-00-00-00-01', vrf=vrfs[0]),
VMInterface(virtual_machine=vms[1], name='Interface 2', enabled=True, mtu=200, mac_address='00-00-00-00-00-02', vrf=vrfs[1]),
VMInterface(virtual_machine=vms[0], name='Interface 1', enabled=True, mtu=100, mac_address='00-00-00-00-00-01', vrf=vrfs[0], description='foobar1'),
VMInterface(virtual_machine=vms[1], name='Interface 2', enabled=True, mtu=200, mac_address='00-00-00-00-00-02', vrf=vrfs[1], description='foobar2'),
VMInterface(virtual_machine=vms[2], name='Interface 3', enabled=False, mtu=300, mac_address='00-00-00-00-00-03', vrf=vrfs[2]),
)
VMInterface.objects.bulk_create(interfaces)
@ -492,3 +492,7 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'vrf': [vrfs[0].rd, vrfs[1].rd]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

View File

@ -58,7 +58,7 @@ class WirelessLANFilterSet(NetBoxModelFilterSet):
class Meta:
model = WirelessLAN
fields = ['id', 'ssid', 'auth_psk']
fields = ['id', 'ssid', 'auth_psk', 'description']
def search(self, queryset, name, value):
if not value.strip():
@ -89,7 +89,7 @@ class WirelessLinkFilterSet(NetBoxModelFilterSet):
class Meta:
model = WirelessLink
fields = ['id', 'ssid', 'auth_psk']
fields = ['id', 'ssid', 'auth_psk', 'description']
def search(self, queryset, name, value):
if not value.strip():

View File

@ -25,8 +25,8 @@ class WirelessLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
group.save()
child_groups = (
WirelessLANGroup(name='Wireless LAN Group 1A', slug='wireless-lan-group-1a', parent=groups[0]),
WirelessLANGroup(name='Wireless LAN Group 1B', slug='wireless-lan-group-1b', parent=groups[0]),
WirelessLANGroup(name='Wireless LAN Group 1A', slug='wireless-lan-group-1a', parent=groups[0], description='foobar1'),
WirelessLANGroup(name='Wireless LAN Group 1B', slug='wireless-lan-group-1b', parent=groups[0], description='foobar2'),
WirelessLANGroup(name='Wireless LAN Group 2A', slug='wireless-lan-group-2a', parent=groups[1]),
WirelessLANGroup(name='Wireless LAN Group 2B', slug='wireless-lan-group-2b', parent=groups[1]),
WirelessLANGroup(name='Wireless LAN Group 3A', slug='wireless-lan-group-3a', parent=groups[2]),
@ -54,6 +54,10 @@ class WirelessLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = WirelessLAN.objects.all()
@ -147,7 +151,8 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
status=LinkStatusChoices.STATUS_CONNECTED,
auth_type=WirelessAuthTypeChoices.TYPE_OPEN,
auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO,
auth_psk='PSK1'
auth_psk='PSK1',
description='foobar1'
).save()
WirelessLink(
interface_a=interfaces[1],
@ -156,7 +161,8 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
status=LinkStatusChoices.STATUS_PLANNED,
auth_type=WirelessAuthTypeChoices.TYPE_WEP,
auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP,
auth_psk='PSK2'
auth_psk='PSK2',
description='foobar2'
).save()
WirelessLink(
interface_a=interfaces[4],
@ -192,3 +198,7 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
def test_auth_psk(self):
params = {'auth_psk': ['PSK1', 'PSK2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

View File

@ -10,6 +10,23 @@ cd "$(dirname "$0")"
VIRTUALENV="$(pwd -P)/venv"
PYTHON="${PYTHON:-python3}"
# Validate the minimum required Python version
COMMAND="${PYTHON} -c 'import sys; exit(1 if sys.version_info < (3, 7) else 0)'"
PYTHON_VERSION=$(eval "${PYTHON} -V")
eval $COMMAND || {
echo "--------------------------------------------------------------------"
echo "ERROR: Unsupported Python version: ${PYTHON_VERSION}. NetBox requires"
echo "Python 3.7 or later. To specify an alternate Python executable, set"
echo "the PYTHON environment variable. For example:"
echo ""
echo " sudo PYTHON=/usr/bin/python3.7 ./upgrade.sh"
echo ""
echo "To show your current Python version: ${PYTHON} -V"
echo "--------------------------------------------------------------------"
exit 1
}
echo "Using ${PYTHON_VERSION}"
# Remove the existing virtual environment (if any)
if [ -d "$VIRTUALENV" ]; then
COMMAND="rm -rf ${VIRTUALENV}"