mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-19 17:59:11 -06:00
commit
ac1e4b8e8f
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,3 +1,29 @@
|
||||
v2.5.7 (2019-02-21)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#2357](https://github.com/digitalocean/netbox/issues/2357) - Enable filtering of devices by rack face
|
||||
* [#2638](https://github.com/digitalocean/netbox/issues/2638) - Add button to copy unlocked secret to clipboard
|
||||
* [#2870](https://github.com/digitalocean/netbox/issues/2870) - Add Markdown rendering for provider NOC/admin contact fields
|
||||
* [#2878](https://github.com/digitalocean/netbox/issues/2878) - Add cable types for OS1/OS2 singlemode fiber
|
||||
* [#2890](https://github.com/digitalocean/netbox/issues/2890) - Add port types for APC fiber
|
||||
* [#2898](https://github.com/digitalocean/netbox/issues/2898) - Enable filtering cables list by connection status
|
||||
* [#2903](https://github.com/digitalocean/netbox/issues/2903) - Clarify purpose of tags field on interface edit form
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#2852](https://github.com/digitalocean/netbox/issues/2852) - Allow filtering devices by null rack position
|
||||
* [#2884](https://github.com/digitalocean/netbox/issues/2884) - Don't display connect button for wireless interfaces
|
||||
* [#2888](https://github.com/digitalocean/netbox/issues/2888) - Correct foreground color of device roles in rack elevations
|
||||
* [#2893](https://github.com/digitalocean/netbox/issues/2893) - Remove duplicate display of VRF RD on IP address view
|
||||
* [#2895](https://github.com/digitalocean/netbox/issues/2895) - Fix filtering of nullable character fields
|
||||
* [#2901](https://github.com/digitalocean/netbox/issues/2901) - Fix ordering regions by site count
|
||||
* [#2910](https://github.com/digitalocean/netbox/issues/2910) - Fix config context list and edit forms to use Select2 elements
|
||||
* [#2912](https://github.com/digitalocean/netbox/issues/2912) - Cable type in filter form should be blank by default
|
||||
* [#2913](https://github.com/digitalocean/netbox/issues/2913) - Fix assigned prefixes link on VRF view
|
||||
* [#2914](https://github.com/digitalocean/netbox/issues/2914) - Fix empty connected circuit link on device interfaces list
|
||||
* [#2915](https://github.com/digitalocean/netbox/issues/2915) - Fix bulk editing of pass-through ports
|
||||
|
||||
v2.5.6 (2019-02-13)
|
||||
|
||||
## Enhancements
|
||||
|
@ -128,4 +128,4 @@ Reports can be run on the CLI by invoking the management command:
|
||||
python3 manage.py runreport <module>
|
||||
```
|
||||
|
||||
One or more report modules may be specified.
|
||||
where ``<module>`` is the name of the python file in the ``reports`` directory without the ``.py`` extension. One or more report modules may be specified.
|
||||
|
91
netbox/circuits/tests/test_views.py
Normal file
91
netbox/circuits/tests/test_views.py
Normal file
@ -0,0 +1,91 @@
|
||||
import urllib.parse
|
||||
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from circuits.models import Circuit, CircuitType, Provider
|
||||
|
||||
|
||||
class ProviderTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
Provider.objects.bulk_create([
|
||||
Provider(name='Provider 1', slug='provider-1', asn=65001),
|
||||
Provider(name='Provider 2', slug='provider-2', asn=65002),
|
||||
Provider(name='Provider 3', slug='provider-3', asn=65003),
|
||||
])
|
||||
|
||||
def test_provider_list(self):
|
||||
|
||||
url = reverse('circuits:provider_list')
|
||||
params = {
|
||||
"q": "test",
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_provider(self):
|
||||
|
||||
provider = Provider.objects.first()
|
||||
response = self.client.get(provider.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class CircuitTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
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 3', slug='circuit-type-3'),
|
||||
])
|
||||
|
||||
def test_circuittype_list(self):
|
||||
|
||||
url = reverse('circuits:circuittype_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class CircuitTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
provider = Provider(name='Provider 1', slug='provider-1', asn=65001)
|
||||
provider.save()
|
||||
|
||||
circuittype = CircuitType(name='Circuit Type 1', slug='circuit-type-1')
|
||||
circuittype.save()
|
||||
|
||||
Circuit.objects.bulk_create([
|
||||
Circuit(cid='Circuit 1', provider=provider, type=circuittype),
|
||||
Circuit(cid='Circuit 2', provider=provider, type=circuittype),
|
||||
Circuit(cid='Circuit 3', provider=provider, type=circuittype),
|
||||
])
|
||||
|
||||
def test_circuit_list(self):
|
||||
|
||||
url = reverse('circuits:circuit_list')
|
||||
params = {
|
||||
"provider": Provider.objects.first().slug,
|
||||
"type": CircuitType.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_provider(self):
|
||||
|
||||
provider = Provider.objects.first()
|
||||
response = self.client.get(provider.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
@ -43,6 +43,12 @@ RACK_STATUS_CHOICES = [
|
||||
[RACK_STATUS_DEPRECATED, 'Deprecated'],
|
||||
]
|
||||
|
||||
# Device rack position
|
||||
DEVICE_POSITION_CHOICES = [
|
||||
# Rack.u_height is limited to 100
|
||||
(i, 'Unit {}'.format(i)) for i in range(1, 101)
|
||||
]
|
||||
|
||||
# Parent/child device roles
|
||||
SUBDEVICE_ROLE_PARENT = True
|
||||
SUBDEVICE_ROLE_CHILD = False
|
||||
@ -270,11 +276,14 @@ PORT_TYPE_8P8C = 1000
|
||||
PORT_TYPE_110_PUNCH = 1100
|
||||
PORT_TYPE_ST = 2000
|
||||
PORT_TYPE_SC = 2100
|
||||
PORT_TYPE_SC_APC = 2110
|
||||
PORT_TYPE_FC = 2200
|
||||
PORT_TYPE_LC = 2300
|
||||
PORT_TYPE_LC_APC = 2310
|
||||
PORT_TYPE_MTRJ = 2400
|
||||
PORT_TYPE_MPO = 2500
|
||||
PORT_TYPE_LSH = 2600
|
||||
PORT_TYPE_LSH_APC = 2610
|
||||
PORT_TYPE_CHOICES = [
|
||||
[
|
||||
'Copper',
|
||||
@ -288,10 +297,13 @@ PORT_TYPE_CHOICES = [
|
||||
[
|
||||
[PORT_TYPE_FC, 'FC'],
|
||||
[PORT_TYPE_LC, 'LC'],
|
||||
[PORT_TYPE_LC_APC, 'LC/APC'],
|
||||
[PORT_TYPE_LSH, 'LSH'],
|
||||
[PORT_TYPE_LSH_APC, 'LSH/APC'],
|
||||
[PORT_TYPE_MPO, 'MPO'],
|
||||
[PORT_TYPE_MTRJ, 'MTRJ'],
|
||||
[PORT_TYPE_SC, 'SC'],
|
||||
[PORT_TYPE_SC_APC, 'SC/APC'],
|
||||
[PORT_TYPE_ST, 'ST'],
|
||||
]
|
||||
]
|
||||
@ -355,11 +367,14 @@ CABLE_TYPE_CAT6A = 1610
|
||||
CABLE_TYPE_CAT7 = 1700
|
||||
CABLE_TYPE_DAC_ACTIVE = 1800
|
||||
CABLE_TYPE_DAC_PASSIVE = 1810
|
||||
CABLE_TYPE_MMF = 3000
|
||||
CABLE_TYPE_MMF_OM1 = 3010
|
||||
CABLE_TYPE_MMF_OM2 = 3020
|
||||
CABLE_TYPE_MMF_OM3 = 3030
|
||||
CABLE_TYPE_MMF_OM4 = 3040
|
||||
CABLE_TYPE_SMF = 3500
|
||||
CABLE_TYPE_SMF_OS1 = 3510
|
||||
CABLE_TYPE_SMF_OS2 = 3520
|
||||
CABLE_TYPE_AOC = 3800
|
||||
CABLE_TYPE_POWER = 5000
|
||||
CABLE_TYPE_CHOICES = (
|
||||
@ -377,11 +392,14 @@ CABLE_TYPE_CHOICES = (
|
||||
),
|
||||
(
|
||||
'Fiber', (
|
||||
(CABLE_TYPE_MMF, 'Multimode Fiber'),
|
||||
(CABLE_TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
|
||||
(CABLE_TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
|
||||
(CABLE_TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
|
||||
(CABLE_TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
|
||||
(CABLE_TYPE_SMF, 'Singlemode Fiber'),
|
||||
(CABLE_TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'),
|
||||
(CABLE_TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'),
|
||||
(CABLE_TYPE_AOC, 'Active Optical Cabling (AOC)'),
|
||||
),
|
||||
),
|
||||
|
@ -543,6 +543,10 @@ class DeviceFilter(CustomFieldFilterSet):
|
||||
queryset=Rack.objects.all(),
|
||||
label='Rack (ID)',
|
||||
)
|
||||
position = django_filters.ChoiceFilter(
|
||||
choices=DEVICE_POSITION_CHOICES,
|
||||
null_label='Non-racked'
|
||||
)
|
||||
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Cluster.objects.all(),
|
||||
label='VM cluster (ID)',
|
||||
@ -602,7 +606,7 @@ class DeviceFilter(CustomFieldFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = ['serial', 'position']
|
||||
fields = ['serial', 'face']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
|
@ -2362,7 +2362,7 @@ class FrontPortCreateForm(ComponentForm):
|
||||
|
||||
class FrontPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
queryset=FrontPort.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
type = forms.ChoiceField(
|
||||
@ -2436,7 +2436,7 @@ class RearPortCreateForm(ComponentForm):
|
||||
|
||||
class RearPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
queryset=RearPort.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
type = forms.ChoiceField(
|
||||
@ -2753,10 +2753,15 @@ class CableFilterForm(BootstrapMixin, forms.Form):
|
||||
label='Search'
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=CABLE_TYPE_CHOICES,
|
||||
choices=add_blank_choice(CABLE_TYPE_CHOICES),
|
||||
required=False,
|
||||
widget=StaticSelect2()
|
||||
)
|
||||
status = forms.ChoiceField(
|
||||
required=False,
|
||||
choices=add_blank_choice(CONNECTION_STATUS_CHOICES),
|
||||
widget=StaticSelect2()
|
||||
)
|
||||
color = forms.CharField(
|
||||
max_length=6,
|
||||
required=False,
|
||||
|
38
netbox/dcim/migrations/0069_deprecate_nullablecharfield.py
Normal file
38
netbox/dcim/migrations/0069_deprecate_nullablecharfield.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Generated by Django 2.1.5 on 2019-02-14 14:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0068_rack_new_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='asset_tag',
|
||||
field=models.CharField(blank=True, max_length=50, null=True, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, max_length=64, null=True, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventoryitem',
|
||||
name='asset_tag',
|
||||
field=models.CharField(blank=True, max_length=50, null=True, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='asset_tag',
|
||||
field=models.CharField(blank=True, max_length=50, null=True, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='facility_id',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
]
|
@ -16,7 +16,7 @@ from taggit.managers import TaggableManager
|
||||
from timezone_field import TimeZoneField
|
||||
|
||||
from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange
|
||||
from utilities.fields import ColorField, NullableCharField
|
||||
from utilities.fields import ColorField
|
||||
from utilities.managers import NaturalOrderingManager
|
||||
from utilities.models import ChangeLoggedModel
|
||||
from utilities.utils import serialize_object, to_meters
|
||||
@ -217,8 +217,7 @@ class Region(MPTTModel, ChangeLoggedModel):
|
||||
self.parent.name if self.parent else None,
|
||||
)
|
||||
|
||||
@property
|
||||
def site_count(self):
|
||||
def get_site_count(self):
|
||||
return Site.objects.filter(
|
||||
Q(region=self) |
|
||||
Q(region__in=self.get_descendants())
|
||||
@ -470,7 +469,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
||||
name = models.CharField(
|
||||
max_length=50
|
||||
)
|
||||
facility_id = NullableCharField(
|
||||
facility_id = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
null=True,
|
||||
@ -511,7 +510,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
||||
blank=True,
|
||||
verbose_name='Serial number'
|
||||
)
|
||||
asset_tag = NullableCharField(
|
||||
asset_tag = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
null=True,
|
||||
@ -1354,7 +1353,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
name = NullableCharField(
|
||||
name = models.CharField(
|
||||
max_length=64,
|
||||
blank=True,
|
||||
null=True,
|
||||
@ -1365,7 +1364,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
||||
blank=True,
|
||||
verbose_name='Serial number'
|
||||
)
|
||||
asset_tag = NullableCharField(
|
||||
asset_tag = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
null=True,
|
||||
@ -2389,7 +2388,7 @@ class InventoryItem(ComponentModel):
|
||||
verbose_name='Serial number',
|
||||
blank=True
|
||||
)
|
||||
asset_tag = NullableCharField(
|
||||
asset_tag = models.CharField(
|
||||
max_length=50,
|
||||
unique=True,
|
||||
blank=True,
|
||||
@ -2652,6 +2651,9 @@ class Cable(ChangeLoggedModel):
|
||||
self.length_unit,
|
||||
)
|
||||
|
||||
def get_status_class(self):
|
||||
return 'success' if self.status else 'info'
|
||||
|
||||
def get_path_endpoints(self):
|
||||
"""
|
||||
Traverse both ends of a cable path and return its connected endpoints. Note that one or both endpoints may be
|
||||
|
@ -647,6 +647,9 @@ class CableTable(BaseTable):
|
||||
orderable=False,
|
||||
verbose_name=''
|
||||
)
|
||||
status = tables.TemplateColumn(
|
||||
template_code=STATUS_LABEL
|
||||
)
|
||||
length = tables.TemplateColumn(
|
||||
template_code=CABLE_LENGTH,
|
||||
order_by='_abs_length'
|
||||
|
458
netbox/dcim/tests/test_views.py
Normal file
458
netbox/dcim/tests/test_views.py
Normal file
@ -0,0 +1,458 @@
|
||||
import urllib.parse
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from dcim.constants import CABLE_TYPE_CAT6, IFACE_FF_1GE_FIXED
|
||||
from dcim.models import (
|
||||
Cable, Device, DeviceRole, DeviceType, Interface, InventoryItem, Manufacturer, Platform, Rack, RackGroup,
|
||||
RackReservation, RackRole, Site, Region, VirtualChassis,
|
||||
)
|
||||
|
||||
|
||||
class RegionTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
# Create three Regions
|
||||
for i in range(1, 4):
|
||||
Region(name='Region {}'.format(i), slug='region-{}'.format(i)).save()
|
||||
|
||||
def test_region_list(self):
|
||||
|
||||
url = reverse('dcim:region_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class SiteTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
region = Region(name='Region 1', slug='region-1')
|
||||
region.save()
|
||||
|
||||
Site.objects.bulk_create([
|
||||
Site(name='Site 1', slug='site-1', region=region),
|
||||
Site(name='Site 2', slug='site-2', region=region),
|
||||
Site(name='Site 3', slug='site-3', region=region),
|
||||
])
|
||||
|
||||
def test_site_list(self):
|
||||
|
||||
url = reverse('dcim:site_list')
|
||||
params = {
|
||||
"region": Region.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_site(self):
|
||||
|
||||
site = Site.objects.first()
|
||||
response = self.client.get(site.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class RackGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
RackGroup.objects.bulk_create([
|
||||
RackGroup(name='Rack Group 1', slug='rack-group-1', site=site),
|
||||
RackGroup(name='Rack Group 2', slug='rack-group-2', site=site),
|
||||
RackGroup(name='Rack Group 3', slug='rack-group-3', site=site),
|
||||
])
|
||||
|
||||
def test_rackgroup_list(self):
|
||||
|
||||
url = reverse('dcim:rackgroup_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class RackTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
RackRole.objects.bulk_create([
|
||||
RackRole(name='Rack Role 1', slug='rack-role-1'),
|
||||
RackRole(name='Rack Role 2', slug='rack-role-2'),
|
||||
RackRole(name='Rack Role 3', slug='rack-role-3'),
|
||||
])
|
||||
|
||||
def test_rackrole_list(self):
|
||||
|
||||
url = reverse('dcim:rackrole_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class RackReservationTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
User = get_user_model()
|
||||
user = User(username='testuser', email='testuser@example.com')
|
||||
user.save()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
rack = Rack(name='Rack 1', site=site)
|
||||
rack.save()
|
||||
|
||||
RackReservation.objects.bulk_create([
|
||||
RackReservation(rack=rack, user=user, units=[1, 2, 3], description='Reservation 1'),
|
||||
RackReservation(rack=rack, user=user, units=[4, 5, 6], description='Reservation 2'),
|
||||
RackReservation(rack=rack, user=user, units=[7, 8, 9], description='Reservation 3'),
|
||||
])
|
||||
|
||||
def test_rackreservation_list(self):
|
||||
|
||||
url = reverse('dcim:rackreservation_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class RackTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
Rack.objects.bulk_create([
|
||||
Rack(name='Rack 1', site=site),
|
||||
Rack(name='Rack 2', site=site),
|
||||
Rack(name='Rack 3', site=site),
|
||||
])
|
||||
|
||||
def test_rack_list(self):
|
||||
|
||||
url = reverse('dcim:rack_list')
|
||||
params = {
|
||||
"site": Site.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_rack(self):
|
||||
|
||||
rack = Rack.objects.first()
|
||||
response = self.client.get(rack.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class ManufacturerTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
Manufacturer.objects.bulk_create([
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||
Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
|
||||
])
|
||||
|
||||
def test_manufacturer_list(self):
|
||||
|
||||
url = reverse('dcim:manufacturer_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class DeviceTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1')
|
||||
manufacturer.save()
|
||||
|
||||
DeviceType.objects.bulk_create([
|
||||
DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturer),
|
||||
DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturer),
|
||||
DeviceType(model='Device Type 3', slug='device-type-3', manufacturer=manufacturer),
|
||||
])
|
||||
|
||||
def test_devicetype_list(self):
|
||||
|
||||
url = reverse('dcim:devicetype_list')
|
||||
params = {
|
||||
"manufacturer": Manufacturer.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_devicetype(self):
|
||||
|
||||
devicetype = DeviceType.objects.first()
|
||||
response = self.client.get(devicetype.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class DeviceRoleTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
DeviceRole.objects.bulk_create([
|
||||
DeviceRole(name='Device Role 1', slug='device-role-1'),
|
||||
DeviceRole(name='Device Role 2', slug='device-role-2'),
|
||||
DeviceRole(name='Device Role 3', slug='device-role-3'),
|
||||
])
|
||||
|
||||
def test_devicerole_list(self):
|
||||
|
||||
url = reverse('dcim:devicerole_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class PlatformTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
Platform.objects.bulk_create([
|
||||
Platform(name='Platform 1', slug='platform-1'),
|
||||
Platform(name='Platform 2', slug='platform-2'),
|
||||
Platform(name='Platform 3', slug='platform-3'),
|
||||
])
|
||||
|
||||
def test_platform_list(self):
|
||||
|
||||
url = reverse('dcim:platform_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class DeviceTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1')
|
||||
manufacturer.save()
|
||||
|
||||
devicetype = DeviceType(model='Device Type 1', manufacturer=manufacturer)
|
||||
devicetype.save()
|
||||
|
||||
devicerole = DeviceRole(name='Device Role 1', slug='device-role-1')
|
||||
devicerole.save()
|
||||
|
||||
Device.objects.bulk_create([
|
||||
Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole),
|
||||
Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole),
|
||||
Device(name='Device 3', site=site, device_type=devicetype, device_role=devicerole),
|
||||
])
|
||||
|
||||
def test_device_list(self):
|
||||
|
||||
url = reverse('dcim:device_list')
|
||||
params = {
|
||||
"device_type_id": DeviceType.objects.first().pk,
|
||||
"role": DeviceRole.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_device(self):
|
||||
|
||||
device = Device.objects.first()
|
||||
response = self.client.get(device.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class InventoryItemTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1')
|
||||
manufacturer.save()
|
||||
|
||||
devicetype = DeviceType(model='Device Type 1', manufacturer=manufacturer)
|
||||
devicetype.save()
|
||||
|
||||
devicerole = DeviceRole(name='Device Role 1', slug='device-role-1')
|
||||
devicerole.save()
|
||||
|
||||
device = Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole)
|
||||
device.save()
|
||||
|
||||
InventoryItem.objects.bulk_create([
|
||||
InventoryItem(device=device, name='Inventory Item 1'),
|
||||
InventoryItem(device=device, name='Inventory Item 2'),
|
||||
InventoryItem(device=device, name='Inventory Item 3'),
|
||||
])
|
||||
|
||||
def test_inventoryitem_list(self):
|
||||
|
||||
url = reverse('dcim:inventoryitem_list')
|
||||
params = {
|
||||
"device_id": Device.objects.first().pk,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_inventoryitem(self):
|
||||
|
||||
inventoryitem = InventoryItem.objects.first()
|
||||
response = self.client.get(inventoryitem.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class CableTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1')
|
||||
manufacturer.save()
|
||||
|
||||
devicetype = DeviceType(model='Device Type 1', manufacturer=manufacturer)
|
||||
devicetype.save()
|
||||
|
||||
devicerole = DeviceRole(name='Device Role 1', slug='device-role-1')
|
||||
devicerole.save()
|
||||
|
||||
device1 = Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole)
|
||||
device1.save()
|
||||
device2 = Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole)
|
||||
device2.save()
|
||||
|
||||
iface1 = Interface(device=device1, name='Interface 1', form_factor=IFACE_FF_1GE_FIXED)
|
||||
iface1.save()
|
||||
iface2 = Interface(device=device1, name='Interface 2', form_factor=IFACE_FF_1GE_FIXED)
|
||||
iface2.save()
|
||||
iface3 = Interface(device=device1, name='Interface 3', form_factor=IFACE_FF_1GE_FIXED)
|
||||
iface3.save()
|
||||
iface4 = Interface(device=device2, name='Interface 1', form_factor=IFACE_FF_1GE_FIXED)
|
||||
iface4.save()
|
||||
iface5 = Interface(device=device2, name='Interface 2', form_factor=IFACE_FF_1GE_FIXED)
|
||||
iface5.save()
|
||||
iface6 = Interface(device=device2, name='Interface 3', form_factor=IFACE_FF_1GE_FIXED)
|
||||
iface6.save()
|
||||
|
||||
Cable(termination_a=iface1, termination_b=iface4, type=CABLE_TYPE_CAT6).save()
|
||||
Cable(termination_a=iface2, termination_b=iface5, type=CABLE_TYPE_CAT6).save()
|
||||
Cable(termination_a=iface3, termination_b=iface6, type=CABLE_TYPE_CAT6).save()
|
||||
|
||||
def test_cable_list(self):
|
||||
|
||||
url = reverse('dcim:cable_list')
|
||||
params = {
|
||||
"type": CABLE_TYPE_CAT6,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_cable(self):
|
||||
|
||||
cable = Cable.objects.first()
|
||||
response = self.client.get(cable.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class VirtualMachineTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||
manufacturer = Manufacturer.objects.create(name='Manufacturer', slug='manufacturer-1')
|
||||
device_type = DeviceType.objects.create(
|
||||
manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
|
||||
)
|
||||
device_role = DeviceRole.objects.create(
|
||||
name='Device Role', slug='device-role-1'
|
||||
)
|
||||
|
||||
# Create 9 member Devices
|
||||
device1 = Device.objects.create(
|
||||
device_type=device_type, device_role=device_role, name='Device 1', site=site
|
||||
)
|
||||
device2 = Device.objects.create(
|
||||
device_type=device_type, device_role=device_role, name='Device 2', site=site
|
||||
)
|
||||
device3 = Device.objects.create(
|
||||
device_type=device_type, device_role=device_role, name='Device 3', site=site
|
||||
)
|
||||
device4 = Device.objects.create(
|
||||
device_type=device_type, device_role=device_role, name='Device 4', site=site
|
||||
)
|
||||
device5 = Device.objects.create(
|
||||
device_type=device_type, device_role=device_role, name='Device 5', site=site
|
||||
)
|
||||
device6 = Device.objects.create(
|
||||
device_type=device_type, device_role=device_role, name='Device 6', site=site
|
||||
)
|
||||
|
||||
# Create three VirtualChassis with two members each
|
||||
vc1 = VirtualChassis.objects.create(master=device1, domain='test-domain-1')
|
||||
Device.objects.filter(pk=device2.pk).update(virtual_chassis=vc1, vc_position=2)
|
||||
vc2 = VirtualChassis.objects.create(master=device3, domain='test-domain-2')
|
||||
Device.objects.filter(pk=device4.pk).update(virtual_chassis=vc2, vc_position=2)
|
||||
vc3 = VirtualChassis.objects.create(master=device5, domain='test-domain-3')
|
||||
Device.objects.filter(pk=device6.pk).update(virtual_chassis=vc3, vc_position=2)
|
||||
|
||||
def test_virtualchassis_list(self):
|
||||
|
||||
url = reverse('dcim:virtualchassis_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_virtualchassis(self):
|
||||
|
||||
virtualchassis = VirtualChassis.objects.first()
|
||||
response = self.client.get(virtualchassis.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
@ -135,7 +135,13 @@ class BulkDisconnectView(GetReturnURLMixin, View):
|
||||
#
|
||||
|
||||
class RegionListView(ObjectListView):
|
||||
queryset = Region.objects.all()
|
||||
queryset = Region.objects.add_related_count(
|
||||
Region.objects.all(),
|
||||
Site,
|
||||
'region',
|
||||
'site_count',
|
||||
cumulative=True
|
||||
)
|
||||
filter = filters.RegionFilter
|
||||
filter_form = forms.RegionFilterForm
|
||||
table = tables.RegionTable
|
||||
|
@ -11,8 +11,8 @@ from taggit.models import Tag
|
||||
from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect, FilterChoiceField,
|
||||
FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField,
|
||||
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect,
|
||||
FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField,
|
||||
)
|
||||
from .constants import (
|
||||
CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
|
||||
@ -221,10 +221,6 @@ class TagFilterForm(BootstrapMixin, forms.Form):
|
||||
#
|
||||
|
||||
class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
||||
regions = TreeNodeMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False
|
||||
)
|
||||
data = JSONField()
|
||||
|
||||
class Meta:
|
||||
@ -233,6 +229,26 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
||||
'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'tenant_groups',
|
||||
'tenants', 'data',
|
||||
]
|
||||
widgets = {
|
||||
'regions': APISelectMultiple(
|
||||
api_url="/api/dcim/regions/"
|
||||
),
|
||||
'sites': APISelectMultiple(
|
||||
api_url="/api/dcim/sites/"
|
||||
),
|
||||
'roles': APISelectMultiple(
|
||||
api_url="/api/dcim/device-roles/"
|
||||
),
|
||||
'platforms': APISelectMultiple(
|
||||
api_url="/api/dcim/platforms/"
|
||||
),
|
||||
'tenant_groups': APISelectMultiple(
|
||||
api_url="/api/tenancy/tenant-groups/"
|
||||
),
|
||||
'tenants': APISelectMultiple(
|
||||
api_url="/api/tenancy/tenants/"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
@ -264,29 +280,53 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
region = FilterTreeNodeMultipleChoiceField(
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/sites/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
role = FilterChoiceField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/device-roles/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
platform = FilterChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/platforms/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
tenant_group = FilterChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/tenancy/tenant-groups/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
tenant = FilterChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/tenancy/tenants/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
105
netbox/extras/tests/test_views.py
Normal file
105
netbox/extras/tests/test_views.py
Normal file
@ -0,0 +1,105 @@
|
||||
import urllib.parse
|
||||
import uuid
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from taggit.models import Tag
|
||||
|
||||
from dcim.models import Site
|
||||
from extras.models import ConfigContext, ObjectChange
|
||||
|
||||
|
||||
class TagTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
Tag.objects.bulk_create([
|
||||
Tag(name='Tag 1', slug='tag-1'),
|
||||
Tag(name='Tag 2', slug='tag-2'),
|
||||
Tag(name='Tag 3', slug='tag-3'),
|
||||
])
|
||||
|
||||
def test_tag_list(self):
|
||||
|
||||
url = reverse('extras:tag_list')
|
||||
params = {
|
||||
"q": "tag",
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class ConfigContextTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
# Create three ConfigContexts
|
||||
for i in range(1, 4):
|
||||
configcontext = ConfigContext(
|
||||
name='Config Context {}'.format(i),
|
||||
data='{{"foo": {}}}'.format(i)
|
||||
)
|
||||
configcontext.save()
|
||||
configcontext.sites.add(site)
|
||||
|
||||
def test_configcontext_list(self):
|
||||
|
||||
url = reverse('extras:configcontext_list')
|
||||
params = {
|
||||
"q": "foo",
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_configcontext(self):
|
||||
|
||||
configcontext = ConfigContext.objects.first()
|
||||
response = self.client.get(configcontext.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class ObjectChangeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
user = User(username='testuser', email='testuser@example.com')
|
||||
user.save()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
# Create three ObjectChanges
|
||||
for i in range(1, 4):
|
||||
site.log_change(
|
||||
user=user,
|
||||
request_id=uuid.uuid4(),
|
||||
action=2
|
||||
)
|
||||
|
||||
def test_objectchange_list(self):
|
||||
|
||||
url = reverse('extras:objectchange_list')
|
||||
params = {
|
||||
"user": User.objects.first(),
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_objectchange(self):
|
||||
|
||||
objectchange = ObjectChange.objects.first()
|
||||
response = self.client.get(objectchange.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
282
netbox/ipam/tests/test_views.py
Normal file
282
netbox/ipam/tests/test_views.py
Normal file
@ -0,0 +1,282 @@
|
||||
from netaddr import IPNetwork
|
||||
import urllib.parse
|
||||
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||
from ipam.constants import IP_PROTOCOL_TCP
|
||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||
|
||||
|
||||
class VRFTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
VRF.objects.bulk_create([
|
||||
VRF(name='VRF 1', rd='65000:1'),
|
||||
VRF(name='VRF 2', rd='65000:2'),
|
||||
VRF(name='VRF 3', rd='65000:3'),
|
||||
])
|
||||
|
||||
def test_vrf_list(self):
|
||||
|
||||
url = reverse('ipam:vrf_list')
|
||||
params = {
|
||||
"q": "65000",
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_configcontext(self):
|
||||
|
||||
vrf = VRF.objects.first()
|
||||
response = self.client.get(vrf.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class RIRTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
RIR.objects.bulk_create([
|
||||
RIR(name='RIR 1', slug='rir-1'),
|
||||
RIR(name='RIR 2', slug='rir-2'),
|
||||
RIR(name='RIR 3', slug='rir-3'),
|
||||
])
|
||||
|
||||
def test_rir_list(self):
|
||||
|
||||
url = reverse('ipam:rir_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_rir(self):
|
||||
|
||||
rir = RIR.objects.first()
|
||||
response = self.client.get(rir.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class AggregateTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
rir = RIR(name='RIR 1', slug='rir-1')
|
||||
rir.save()
|
||||
|
||||
Aggregate.objects.bulk_create([
|
||||
Aggregate(family=4, prefix=IPNetwork('10.1.0.0/16'), rir=rir),
|
||||
Aggregate(family=4, prefix=IPNetwork('10.2.0.0/16'), rir=rir),
|
||||
Aggregate(family=4, prefix=IPNetwork('10.3.0.0/16'), rir=rir),
|
||||
])
|
||||
|
||||
def test_aggregate_list(self):
|
||||
|
||||
url = reverse('ipam:aggregate_list')
|
||||
params = {
|
||||
"rir": RIR.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_aggregate(self):
|
||||
|
||||
aggregate = Aggregate.objects.first()
|
||||
response = self.client.get(aggregate.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class RoleTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
Role.objects.bulk_create([
|
||||
Role(name='Role 1', slug='role-1'),
|
||||
Role(name='Role 2', slug='role-2'),
|
||||
Role(name='Role 3', slug='role-3'),
|
||||
])
|
||||
|
||||
def test_role_list(self):
|
||||
|
||||
url = reverse('ipam:role_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class PrefixTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
Prefix.objects.bulk_create([
|
||||
Prefix(family=4, prefix=IPNetwork('10.1.0.0/16'), site=site),
|
||||
Prefix(family=4, prefix=IPNetwork('10.2.0.0/16'), site=site),
|
||||
Prefix(family=4, prefix=IPNetwork('10.3.0.0/16'), site=site),
|
||||
])
|
||||
|
||||
def test_prefix_list(self):
|
||||
|
||||
url = reverse('ipam:prefix_list')
|
||||
params = {
|
||||
"site": Site.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_prefix(self):
|
||||
|
||||
prefix = Prefix.objects.first()
|
||||
response = self.client.get(prefix.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class IPAddressTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
vrf = VRF(name='VRF 1', rd='65000:1')
|
||||
vrf.save()
|
||||
|
||||
IPAddress.objects.bulk_create([
|
||||
IPAddress(family=4, address=IPNetwork('10.1.0.0/16'), vrf=vrf),
|
||||
IPAddress(family=4, address=IPNetwork('10.2.0.0/16'), vrf=vrf),
|
||||
IPAddress(family=4, address=IPNetwork('10.3.0.0/16'), vrf=vrf),
|
||||
])
|
||||
|
||||
def test_ipaddress_list(self):
|
||||
|
||||
url = reverse('ipam:ipaddress_list')
|
||||
params = {
|
||||
"vrf": VRF.objects.first().rd,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_ipaddress(self):
|
||||
|
||||
ipaddress = IPAddress.objects.first()
|
||||
response = self.client.get(ipaddress.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class VLANGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
VLANGroup.objects.bulk_create([
|
||||
VLANGroup(name='VLAN Group 1', slug='vlan-group-1', site=site),
|
||||
VLANGroup(name='VLAN Group 2', slug='vlan-group-2', site=site),
|
||||
VLANGroup(name='VLAN Group 3', slug='vlan-group-3', site=site),
|
||||
])
|
||||
|
||||
def test_vlangroup_list(self):
|
||||
|
||||
url = reverse('ipam:vlangroup_list')
|
||||
params = {
|
||||
"site": Site.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class VLANTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
vlangroup = VLANGroup(name='VLAN Group 1', slug='vlan-group-1')
|
||||
vlangroup.save()
|
||||
|
||||
VLAN.objects.bulk_create([
|
||||
VLAN(group=vlangroup, vid=101, name='VLAN101'),
|
||||
VLAN(group=vlangroup, vid=102, name='VLAN102'),
|
||||
VLAN(group=vlangroup, vid=103, name='VLAN103'),
|
||||
])
|
||||
|
||||
def test_vlan_list(self):
|
||||
|
||||
url = reverse('ipam:vlan_list')
|
||||
params = {
|
||||
"group": VLANGroup.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_vlan(self):
|
||||
|
||||
vlan = VLAN.objects.first()
|
||||
response = self.client.get(vlan.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class ServiceTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1')
|
||||
manufacturer.save()
|
||||
|
||||
devicetype = DeviceType(manufacturer=manufacturer, model='Device Type 1')
|
||||
devicetype.save()
|
||||
|
||||
devicerole = DeviceRole(name='Device Role 1', slug='device-role-1')
|
||||
devicerole.save()
|
||||
|
||||
device = Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole)
|
||||
device.save()
|
||||
|
||||
Service.objects.bulk_create([
|
||||
Service(device=device, name='Service 1', protocol=IP_PROTOCOL_TCP, port=101),
|
||||
Service(device=device, name='Service 2', protocol=IP_PROTOCOL_TCP, port=102),
|
||||
Service(device=device, name='Service 3', protocol=IP_PROTOCOL_TCP, port=103),
|
||||
])
|
||||
|
||||
def test_service_list(self):
|
||||
|
||||
url = reverse('ipam:service_list')
|
||||
params = {
|
||||
"device_id": Device.objects.first(),
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_service(self):
|
||||
|
||||
service = Service.objects.first()
|
||||
response = self.client.get(service.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
@ -22,7 +22,7 @@ except ImportError:
|
||||
)
|
||||
|
||||
|
||||
VERSION = '2.5.6'
|
||||
VERSION = '2.5.7'
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
7
netbox/project-static/clipboard-2.0.4.min.js
vendored
Executable file
7
netbox/project-static/clipboard-2.0.4.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -1,4 +1,6 @@
|
||||
$(document).ready(function() {
|
||||
// Instantiate ClipboardJS on all copy buttons
|
||||
new ClipboardJS('button.copy-secret');
|
||||
|
||||
// Unlocking a secret
|
||||
$('button.unlock-secret').click(function(event) {
|
||||
@ -45,6 +47,7 @@ $(document).ready(function() {
|
||||
console.log("Secret retrieved successfully");
|
||||
$('#secret_' + secret_id).text(response.plaintext);
|
||||
$('button.unlock-secret[secret-id=' + secret_id + ']').hide();
|
||||
$('button.copy-secret[secret-id=' + secret_id + ']').show();
|
||||
$('button.lock-secret[secret-id=' + secret_id + ']').show();
|
||||
} else {
|
||||
console.log("Secret was not decrypted. Prompt user for private key.");
|
||||
@ -67,6 +70,7 @@ $(document).ready(function() {
|
||||
var secret_div = $('#secret_' + secret_id);
|
||||
secret_div.html('********');
|
||||
$('button.lock-secret[secret-id=' + secret_id + ']').hide();
|
||||
$('button.copy-secret[secret-id=' + secret_id + ']').hide();
|
||||
$('button.unlock-secret[secret-id=' + secret_id + ']').show();
|
||||
}
|
||||
|
||||
|
82
netbox/secrets/tests/test_views.py
Normal file
82
netbox/secrets/tests/test_views.py
Normal file
@ -0,0 +1,82 @@
|
||||
import urllib.parse
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||
from secrets.models import Secret, SecretRole
|
||||
|
||||
|
||||
class SecretRoleTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
TEST_USERNAME = 'testuser'
|
||||
TEST_PASSWORD = 'testpassword'
|
||||
|
||||
User = get_user_model()
|
||||
User.objects.create(username=TEST_USERNAME, email='testuser@example.com', password=TEST_PASSWORD)
|
||||
|
||||
self.client = Client()
|
||||
self.client.login(username=TEST_USERNAME, password=TEST_PASSWORD)
|
||||
|
||||
SecretRole.objects.bulk_create([
|
||||
SecretRole(name='Secret Role 1', slug='secret-role-1'),
|
||||
SecretRole(name='Secret Role 2', slug='secret-role-2'),
|
||||
SecretRole(name='Secret Role 3', slug='secret-role-3'),
|
||||
])
|
||||
|
||||
def test_secretrole_list(self):
|
||||
|
||||
url = reverse('secrets:secret_list')
|
||||
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class SecretTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
||||
manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1')
|
||||
manufacturer.save()
|
||||
|
||||
devicetype = DeviceType(manufacturer=manufacturer, model='Device Type 1')
|
||||
devicetype.save()
|
||||
|
||||
devicerole = DeviceRole(name='Device Role 1', slug='device-role-1')
|
||||
devicerole.save()
|
||||
|
||||
device = Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole)
|
||||
device.save()
|
||||
|
||||
secretrole = SecretRole(name='Secret Role 1', slug='secret-role-1')
|
||||
secretrole.save()
|
||||
|
||||
Secret.objects.bulk_create([
|
||||
Secret(device=device, role=secretrole, name='Secret 1', ciphertext=b'1234567890'),
|
||||
Secret(device=device, role=secretrole, name='Secret 2', ciphertext=b'1234567890'),
|
||||
Secret(device=device, role=secretrole, name='Secret 3', ciphertext=b'1234567890'),
|
||||
])
|
||||
|
||||
def test_secret_list(self):
|
||||
|
||||
url = reverse('secrets:secret_list')
|
||||
params = {
|
||||
"role": SecretRole.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_configcontext(self):
|
||||
|
||||
secret = Secret.objects.first()
|
||||
response = self.client.get(secret.get_absolute_url(), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
@ -69,6 +69,7 @@
|
||||
<script src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
|
||||
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
|
||||
<script src="{% static 'select2-4.0.5/js/select2.min.js' %}"></script>
|
||||
<script src="{% static 'clipboard-2.0.4.min.js' %}"></script>
|
||||
<script src="{% static 'js/forms.js' %}?v{{ settings.VERSION }}"></script>
|
||||
<script type="text/javascript">
|
||||
var netbox_api_path = "/{{ settings.BASE_PATH }}api/";
|
||||
|
@ -85,11 +85,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>NOC Contact</td>
|
||||
<td>{{ provider.noc_contact|linebreaksbr|placeholder }}</td>
|
||||
<td class="rendered-markdown">{{ provider.noc_contact|gfm|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Admin Contact</td>
|
||||
<td>{{ provider.admin_contact|linebreaksbr|placeholder }}</td>
|
||||
<td class="rendered-markdown">{{ provider.admin_contact|gfm|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Circuits</td>
|
||||
|
@ -96,7 +96,7 @@
|
||||
{{ peer_termination.connected_endpoint.device }}
|
||||
</a><br/>
|
||||
<small>via <i class="fa fa-fw fa-globe" title="Circuit"></i>
|
||||
<a href="{{ iface.connected_endpoint.circuit.get_absolure_url }}">
|
||||
<a href="{{ iface.connected_endpoint.circuit.get_absolute_url }}">
|
||||
{{ iface.connected_endpoint.circuit.provider }}
|
||||
{{ iface.connected_endpoint.circuit }}
|
||||
</a>
|
||||
@ -150,7 +150,7 @@
|
||||
{% if perms.dcim.change_interface %}
|
||||
{% if iface.cable %}
|
||||
{% include 'dcim/inc/cable_toggle_buttons.html' with cable=iface.cable %}
|
||||
{% elif not iface.is_virtual and perms.dcim.add_cable %}
|
||||
{% elif iface.is_connectable and perms.dcim.add_cable %}
|
||||
<a href="{% url 'dcim:interface_connect' termination_a_id=iface.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
@ -25,7 +25,7 @@
|
||||
{% if u.device %}
|
||||
<li class="occupied h{{ u.device.device_type.u_height }}u"{% ifequal u.device.face face_id %} style="background-color: #{{ u.device.device_role.color }}"{% endifequal %}>
|
||||
{% ifequal u.device.face face_id %}
|
||||
<a href="{% url 'dcim:device' pk=u.device.pk %}" data-toggle="popover" data-trigger="hover" data-container="body" data-html="true"
|
||||
<a href="{% url 'dcim:device' pk=u.device.pk %}" style="color: {{ u.device.device_role.color|fgcolor }}" data-toggle="popover" data-trigger="hover" data-container="body" data-html="true"
|
||||
data-content="{{ u.device.device_role }}<br />{{ u.device.device_type.display_name }} ({{ u.device.device_type.u_height }}U){% if u.device.asset_tag %}<br />{{ u.device.asset_tag }}{% endif %}{% if u.device.serial %}<br />{{ u.device.serial }}{% endif %}">
|
||||
{{ u.device }}
|
||||
{% if u.device.devicebay_count %}
|
||||
|
@ -14,20 +14,29 @@
|
||||
{% render_field form.mgmt_only %}
|
||||
{% render_field form.description %}
|
||||
{% render_field form.mode %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Tags</strong></div>
|
||||
<div class="panel-body">
|
||||
{% render_field form.tags %}
|
||||
</div>
|
||||
</div>
|
||||
{% if obj.mode %}
|
||||
<div class="panel panel-default" id="vlans_panel">
|
||||
<div class="panel-heading"><strong>802.1Q VLANs</strong></div>
|
||||
<div class="panel panel-default" id="vlans_panel">
|
||||
<div class="panel-heading"><strong>802.1Q VLANs</strong></div>
|
||||
{% if obj.mode %}
|
||||
{% include 'dcim/inc/interface_vlans_table.html' %}
|
||||
<div class="panel-footer text-right">
|
||||
<a href="{% url 'dcim:interface_assign_vlans' pk=obj.pk %}?return_url={% url 'dcim:interface_edit' pk=obj.pk %}" class="btn btn-primary btn-xs{% if form.instance.mode == 100 and form.instance.untagged_vlan %} disabled{% endif %}">
|
||||
<i class="glyphicon glyphicon-plus"></i> Add VLANs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="panel-body text-center text-muted">
|
||||
<p>802.1Q mode not set</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
|
@ -67,7 +67,7 @@
|
||||
<td>VRF</td>
|
||||
<td>
|
||||
{% if ipaddress.vrf %}
|
||||
<a href="{% url 'ipam:vrf' pk=ipaddress.vrf.pk %}">{{ ipaddress.vrf }}</a> ({{ ipaddress.vrf.rd }})
|
||||
<a href="{% url 'ipam:vrf' pk=ipaddress.vrf.pk %}">{{ ipaddress.vrf }}</a>
|
||||
{% else %}
|
||||
<span>Global</span>
|
||||
{% endif %}
|
||||
|
@ -87,7 +87,7 @@
|
||||
<tr>
|
||||
<td>Prefixes</td>
|
||||
<td>
|
||||
<a href="{% url 'ipam:prefix_list' %}?vrf={{ vrf.rd }}">{{ prefix_count }}</a>
|
||||
<a href="{% url 'ipam:prefix_list' %}?vrf_id={{ vrf.pk }}">{{ prefix_count }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -8,6 +8,9 @@
|
||||
<button class="btn btn-xs btn-success unlock-secret" secret-id="{{ secret.pk }}">
|
||||
<i class="fa fa-lock"></i> Unlock
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default copy-secret collapse" secret-id="{{ secret.pk }}" data-clipboard-target="#secret_{{ secret.pk }}">
|
||||
<i class="fa fa-copy"></i> Copy
|
||||
</button>
|
||||
<button class="btn btn-xs btn-danger lock-secret collapse" secret-id="{{ secret.pk }}">
|
||||
<i class="fa fa-unlock-alt"></i> Lock
|
||||
</button>
|
||||
|
@ -77,11 +77,14 @@
|
||||
</form>
|
||||
<div class="row">
|
||||
<div class="col-md-2">Secret</div>
|
||||
<div class="col-md-8" id="secret_{{ secret.pk }}">********</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<div class="col-md-6" id="secret_{{ secret.pk }}">********</div>
|
||||
<div class="col-md-4 text-right">
|
||||
<button class="btn btn-xs btn-success unlock-secret" secret-id="{{ secret.pk }}">
|
||||
<i class="fa fa-lock"></i> Unlock
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default copy-secret collapse" secret-id="{{ secret.pk }}" data-clipboard-target="#secret_{{ secret.pk }}">
|
||||
<i class="fa fa-copy"></i> Copy
|
||||
</button>
|
||||
<button class="btn btn-xs btn-danger lock-secret collapse" secret-id="{{ secret.pk }}">
|
||||
<i class="fa fa-unlock-alt"></i> Lock
|
||||
</button>
|
||||
|
@ -4,6 +4,14 @@
|
||||
|
||||
{% block content %}
|
||||
<h1>{% block title %}Editing {{ table.rows|length }} {{ obj_type_plural|bettertitle }}{% endblock %}</h1>
|
||||
{% if form.errors %}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading"><strong>Errors</strong></div>
|
||||
<div class="panel-body">
|
||||
{{ form.errors }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form action="" method="post" class="form form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% if request.POST.return_url %}
|
||||
|
58
netbox/tenancy/tests/test_views.py
Normal file
58
netbox/tenancy/tests/test_views.py
Normal file
@ -0,0 +1,58 @@
|
||||
import urllib.parse
|
||||
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
|
||||
|
||||
class TenantGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
TenantGroup.objects.bulk_create([
|
||||
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
|
||||
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
|
||||
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
|
||||
])
|
||||
|
||||
def test_tenantgroup_list(self):
|
||||
|
||||
url = reverse('tenancy:tenantgroup_list')
|
||||
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TenantTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
tenantgroup = TenantGroup(name='Tenant Group 1', slug='tenant-group-1')
|
||||
tenantgroup.save()
|
||||
|
||||
Tenant.objects.bulk_create([
|
||||
Tenant(name='Tenant 1', slug='tenant-1', group=tenantgroup),
|
||||
Tenant(name='Tenant 2', slug='tenant-2', group=tenantgroup),
|
||||
Tenant(name='Tenant 3', slug='tenant-3', group=tenantgroup),
|
||||
])
|
||||
|
||||
def test_tenant_list(self):
|
||||
|
||||
url = reverse('tenancy:tenant_list')
|
||||
params = {
|
||||
"group": TenantGroup.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_tenant(self):
|
||||
|
||||
tenant = Tenant.objects.first()
|
||||
response = self.client.get(tenant.get_absolute_url(), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
@ -10,6 +10,8 @@ ColorValidator = RegexValidator(
|
||||
)
|
||||
|
||||
|
||||
# Deprecated: Retained only to ensure successful migration from early releases
|
||||
# Use models.CharField(null=True) instead
|
||||
class NullableCharField(models.CharField):
|
||||
description = "Stores empty values as NULL rather than ''"
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import django_filters
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from taggit.models import Tag
|
||||
|
||||
@ -14,12 +15,11 @@ class NullableCharFieldFilter(django_filters.CharFilter):
|
||||
"""
|
||||
Allow matching on null field values by passing a special string used to signify NULL.
|
||||
"""
|
||||
null_value = 'NULL'
|
||||
|
||||
def filter(self, qs, value):
|
||||
if value != self.null_value:
|
||||
if value != settings.FILTERS_NULL_CHOICE_VALUE:
|
||||
return super().filter(qs, value)
|
||||
qs = self.get_method(qs)(**{'{}__isnull'.format(self.name): True})
|
||||
qs = self.get_method(qs)(**{'{}__isnull'.format(self.field_name): True})
|
||||
return qs.distinct() if self.distinct else qs
|
||||
|
||||
|
||||
|
117
netbox/virtualization/tests/test_views.py
Normal file
117
netbox/virtualization/tests/test_views.py
Normal file
@ -0,0 +1,117 @@
|
||||
import urllib.parse
|
||||
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||
|
||||
|
||||
class ClusterGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
ClusterGroup.objects.bulk_create([
|
||||
ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
|
||||
ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'),
|
||||
ClusterGroup(name='Cluster Group 3', slug='cluster-group-3'),
|
||||
])
|
||||
|
||||
def test_clustergroup_list(self):
|
||||
|
||||
url = reverse('virtualization:clustergroup_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class ClusterTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
ClusterType.objects.bulk_create([
|
||||
ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
|
||||
ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
|
||||
ClusterType(name='Cluster Type 3', slug='cluster-type-3'),
|
||||
])
|
||||
|
||||
def test_clustertype_list(self):
|
||||
|
||||
url = reverse('virtualization:clustertype_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class ClusterTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
clustergroup = ClusterGroup(name='Cluster Group 1', slug='cluster-group-1')
|
||||
clustergroup.save()
|
||||
|
||||
clustertype = ClusterType(name='Cluster Type 1', slug='cluster-type-1')
|
||||
clustertype.save()
|
||||
|
||||
Cluster.objects.bulk_create([
|
||||
Cluster(name='Cluster 1', group=clustergroup, type=clustertype),
|
||||
Cluster(name='Cluster 2', group=clustergroup, type=clustertype),
|
||||
Cluster(name='Cluster 3', group=clustergroup, type=clustertype),
|
||||
])
|
||||
|
||||
def test_cluster_list(self):
|
||||
|
||||
url = reverse('virtualization:cluster_list')
|
||||
params = {
|
||||
"group": ClusterGroup.objects.first().slug,
|
||||
"type": ClusterType.objects.first().slug,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_cluster(self):
|
||||
|
||||
cluster = Cluster.objects.first()
|
||||
response = self.client.get(cluster.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class VirtualMachineTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.client = Client()
|
||||
|
||||
clustertype = ClusterType(name='Cluster Type 1', slug='cluster-type-1')
|
||||
clustertype.save()
|
||||
|
||||
cluster = Cluster(name='Cluster 1', type=clustertype)
|
||||
cluster.save()
|
||||
|
||||
VirtualMachine.objects.bulk_create([
|
||||
VirtualMachine(name='Virtual Machine 1', cluster=cluster),
|
||||
VirtualMachine(name='Virtual Machine 2', cluster=cluster),
|
||||
VirtualMachine(name='Virtual Machine 3', cluster=cluster),
|
||||
])
|
||||
|
||||
def test_virtualmachine_list(self):
|
||||
|
||||
url = reverse('virtualization:virtualmachine_list')
|
||||
params = {
|
||||
"cluster_id": Cluster.objects.first().pk,
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_virtualmachine(self):
|
||||
|
||||
virtualmachine = VirtualMachine.objects.first()
|
||||
response = self.client.get(virtualmachine.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
Loading…
Reference in New Issue
Block a user