mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Merge branch 'develop' into 2921-tags-select2
This commit is contained in:
commit
52f7ef4864
@ -29,7 +29,7 @@ server {
|
|||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:8001;
|
proxy_pass http://127.0.0.1:8001;
|
||||||
proxy_set_header X-Forwarded-Host $server_name;
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
@ -110,8 +110,8 @@ AUTH_LDAP_USER_FLAGS_BY_GROUP = {
|
|||||||
AUTH_LDAP_FIND_GROUP_PERMS = True
|
AUTH_LDAP_FIND_GROUP_PERMS = True
|
||||||
|
|
||||||
# Cache groups for one hour to reduce LDAP traffic
|
# Cache groups for one hour to reduce LDAP traffic
|
||||||
AUTH_LDAP_CACHE_GROUPS = True
|
AUTH_LDAP_CACHE_TIMEOUT = 3600
|
||||||
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* `is_active` - All users must be mapped to at least this group to enable authentication. Without this, users cannot log in.
|
* `is_active` - All users must be mapped to at least this group to enable authentication. Without this, users cannot log in.
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
# v2.7.3 (FUTURE)
|
# v2.7.4 (FUTURE)
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#4043](https://github.com/netbox-community/netbox/issues/4043) - Fix toggling of required fields in custom scripts
|
||||||
|
* [#4049](https://github.com/netbox-community/netbox/issues/4049) - Restore missing `tags` field in IPAM service serializer
|
||||||
|
* [#4056](https://github.com/netbox-community/netbox/issues/4056) - Repair schema migration for Rack.outer_unit (from #3569)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# v2.7.3 (2020-01-28)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget
|
* [#2921](https://github.com/netbox-community/netbox/issues/2921) - Replace tags filter with Select2 widget
|
||||||
* [#3310](https://github.com/netbox-community/netbox/issues/3310) - Pre-select site/rack for B side when creating a new cable
|
* [#3310](https://github.com/netbox-community/netbox/issues/3310) - Pre-select site/rack for B side when creating a new cable
|
||||||
|
* [#3338](https://github.com/netbox-community/netbox/issues/3338) - Include circuit terminations in API representation of circuits
|
||||||
* [#3509](https://github.com/netbox-community/netbox/issues/3509) - Add IP address variables for custom scripts
|
* [#3509](https://github.com/netbox-community/netbox/issues/3509) - Add IP address variables for custom scripts
|
||||||
|
* [#3978](https://github.com/netbox-community/netbox/issues/3978) - Add VRF filtering to search NAT IP
|
||||||
* [#4005](https://github.com/netbox-community/netbox/issues/4005) - Include timezone context in webhook timestamps
|
* [#4005](https://github.com/netbox-community/netbox/issues/4005) - Include timezone context in webhook timestamps
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
@ -15,6 +27,14 @@
|
|||||||
* [#3989](https://github.com/netbox-community/netbox/issues/3989) - Correct HTTP content type assignment for webhooks
|
* [#3989](https://github.com/netbox-community/netbox/issues/3989) - Correct HTTP content type assignment for webhooks
|
||||||
* [#3999](https://github.com/netbox-community/netbox/issues/3999) - Do not filter child results by null if non-required parent fields are blank
|
* [#3999](https://github.com/netbox-community/netbox/issues/3999) - Do not filter child results by null if non-required parent fields are blank
|
||||||
* [#4008](https://github.com/netbox-community/netbox/issues/4008) - Toggle rack elevation face using front/rear strings
|
* [#4008](https://github.com/netbox-community/netbox/issues/4008) - Toggle rack elevation face using front/rear strings
|
||||||
|
* [#4017](https://github.com/netbox-community/netbox/issues/4017) - Remove redundant tenant field from cluster form
|
||||||
|
* [#4019](https://github.com/netbox-community/netbox/issues/4019) - Restore border around background devices in rack elevations
|
||||||
|
* [#4022](https://github.com/netbox-community/netbox/issues/4022) - Fix display of assigned IPs when filtering device interfaces
|
||||||
|
* [#4025](https://github.com/netbox-community/netbox/issues/4025) - Correct display of cable status (various places)
|
||||||
|
* [#4027](https://github.com/netbox-community/netbox/issues/4027) - Repair schema migration for #3569 to convert IP addresses with DHCP status
|
||||||
|
* [#4028](https://github.com/netbox-community/netbox/issues/4028) - Correct URL patterns to match Unicode characters in tag slugs
|
||||||
|
* [#4030](https://github.com/netbox-community/netbox/issues/4030) - Fix exception when setting interfaces to tagged mode in bulk
|
||||||
|
* [#4033](https://github.com/netbox-community/netbox/issues/4033) - Restore missing comments field label of various bulk edit forms
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -3,11 +3,11 @@ from taggit_serializer.serializers import TaggitSerializer, TagListSerializerFie
|
|||||||
|
|
||||||
from circuits.choices import CircuitStatusChoices
|
from circuits.choices import CircuitStatusChoices
|
||||||
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
||||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
from dcim.api.nested_serializers import NestedCableSerializer, NestedInterfaceSerializer, NestedSiteSerializer
|
||||||
from dcim.api.serializers import ConnectedEndpointSerializer
|
from dcim.api.serializers import ConnectedEndpointSerializer
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceField, ValidatedModelSerializer
|
from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
|
||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
|
|
||||||
|
|
||||||
@ -39,18 +39,30 @@ class CircuitTypeSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'name', 'slug', 'description', 'circuit_count']
|
fields = ['id', 'name', 'slug', 'description', 'circuit_count']
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
|
||||||
|
site = NestedSiteSerializer()
|
||||||
|
connected_endpoint = NestedInterfaceSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CircuitTermination
|
||||||
|
fields = ['id', 'url', 'site', 'connected_endpoint', 'port_speed', 'upstream_speed', 'xconnect_id']
|
||||||
|
|
||||||
|
|
||||||
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
provider = NestedProviderSerializer()
|
provider = NestedProviderSerializer()
|
||||||
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
||||||
type = NestedCircuitTypeSerializer()
|
type = NestedCircuitTypeSerializer()
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
|
termination_a = CircuitCircuitTerminationSerializer(read_only=True)
|
||||||
|
termination_z = CircuitCircuitTerminationSerializer(read_only=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Circuit
|
model = Circuit
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
|
'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
|
||||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
'termination_a', 'termination_z', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +62,9 @@ class CircuitTypeViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class CircuitViewSet(CustomFieldModelViewSet):
|
class CircuitViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Circuit.objects.prefetch_related('type', 'tenant', 'provider').prefetch_related('tags')
|
queryset = Circuit.objects.prefetch_related(
|
||||||
|
'type', 'tenant', 'provider', 'terminations__site', 'terminations__connected_endpoint__device'
|
||||||
|
).prefetch_related('tags')
|
||||||
serializer_class = serializers.CircuitSerializer
|
serializer_class = serializers.CircuitSerializer
|
||||||
filterset_class = filters.CircuitFilterSet
|
filterset_class = filters.CircuitFilterSet
|
||||||
|
|
||||||
|
@ -89,7 +89,8 @@ class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdi
|
|||||||
label='Admin contact'
|
label='Admin contact'
|
||||||
)
|
)
|
||||||
comments = CommentField(
|
comments = CommentField(
|
||||||
widget=SmallTextarea()
|
widget=SmallTextarea,
|
||||||
|
label='Comments'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -678,7 +678,8 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
|||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
)
|
)
|
||||||
comments = CommentField(
|
comments = CommentField(
|
||||||
widget=SmallTextarea
|
widget=SmallTextarea,
|
||||||
|
label='Comments'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -2758,7 +2759,7 @@ class InterfaceCSVForm(forms.ModelForm):
|
|||||||
return self.cleaned_data['enabled']
|
return self.cleaned_data['enabled']
|
||||||
|
|
||||||
|
|
||||||
class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(
|
pk = forms.ModelMultipleChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
@ -2839,6 +2840,18 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
|
|||||||
else:
|
else:
|
||||||
self.fields['lag'].choices = []
|
self.fields['lag'].choices = []
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
# Untagged interfaces cannot be assigned tagged VLANs
|
||||||
|
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
|
||||||
|
raise forms.ValidationError({
|
||||||
|
'mode': "An access interface cannot have tagged VLANs assigned."
|
||||||
|
})
|
||||||
|
|
||||||
|
# Remove all tagged VLAN assignments from "tagged all" interfaces
|
||||||
|
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
|
||||||
|
self.cleaned_data['tagged_vlans'] = []
|
||||||
|
|
||||||
|
|
||||||
class InterfaceBulkRenameForm(BulkRenameForm):
|
class InterfaceBulkRenameForm(BulkRenameForm):
|
||||||
pk = forms.ModelMultipleChoiceField(
|
pk = forms.ModelMultipleChoiceField(
|
||||||
@ -4421,8 +4434,9 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
|||||||
max_utilization = forms.IntegerField(
|
max_utilization = forms.IntegerField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
comments = forms.CharField(
|
comments = CommentField(
|
||||||
required=False
|
widget=SmallTextarea,
|
||||||
|
label='Comments'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -37,7 +37,7 @@ def rack_status_to_slug(apps, schema_editor):
|
|||||||
def rack_outer_unit_to_slug(apps, schema_editor):
|
def rack_outer_unit_to_slug(apps, schema_editor):
|
||||||
Rack = apps.get_model('dcim', 'Rack')
|
Rack = apps.get_model('dcim', 'Rack')
|
||||||
for id, slug in RACK_DIMENSION_CHOICES:
|
for id, slug in RACK_DIMENSION_CHOICES:
|
||||||
Rack.objects.filter(status=str(id)).update(status=slug)
|
Rack.objects.filter(outer_unit=str(id)).update(outer_unit=slug)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
27
netbox/dcim/migrations/0092_fix_rack_outer_unit.py
Normal file
27
netbox/dcim/migrations/0092_fix_rack_outer_unit.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
RACK_DIMENSION_CHOICES = (
|
||||||
|
(1000, 'mm'),
|
||||||
|
(2000, 'in'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def rack_outer_unit_to_slug(apps, schema_editor):
|
||||||
|
Rack = apps.get_model('dcim', 'Rack')
|
||||||
|
for id, slug in RACK_DIMENSION_CHOICES:
|
||||||
|
Rack.objects.filter(outer_unit=str(id)).update(outer_unit=slug)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0091_interface_type_other'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
# Fixes a missed field migration from #3569; see bug #4056. The original migration has also been fixed,
|
||||||
|
# so this can be omitted when squashing in the future.
|
||||||
|
migrations.RunPython(
|
||||||
|
code=rack_outer_unit_to_slug
|
||||||
|
),
|
||||||
|
]
|
@ -405,7 +405,7 @@ class RackElevationHelperMixin:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _draw_device_rear(drawing, device, start, end, text):
|
def _draw_device_rear(drawing, device, start, end, text):
|
||||||
rect = drawing.rect(start, end, class_="blocked")
|
rect = drawing.rect(start, end, class_="slot blocked")
|
||||||
rect.set_desc('{} — {} ({}U) {} {}'.format(
|
rect.set_desc('{} — {} ({}U) {} {}'.format(
|
||||||
device.device_role, device.device_type.display_name,
|
device.device_role, device.device_type.display_name,
|
||||||
device.device_type.u_height, device.asset_tag or '', device.serial or ''
|
device.device_type.u_height, device.asset_tag or '', device.serial or ''
|
||||||
|
@ -2,6 +2,7 @@ from django.test import TestCase
|
|||||||
|
|
||||||
from dcim.forms import *
|
from dcim.forms import *
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
|
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||||
|
|
||||||
|
|
||||||
def get_id(model, slug):
|
def get_id(model, slug):
|
||||||
@ -10,83 +11,108 @@ def get_id(model, slug):
|
|||||||
|
|
||||||
class DeviceTestCase(TestCase):
|
class DeviceTestCase(TestCase):
|
||||||
|
|
||||||
fixtures = ['dcim', 'ipam', 'virtualization']
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||||
|
rack = Rack.objects.create(name='Rack 1', site=site)
|
||||||
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
|
device_type = DeviceType.objects.create(
|
||||||
|
manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', u_height=1
|
||||||
|
)
|
||||||
|
device_role = DeviceRole.objects.create(
|
||||||
|
name='Device Role 1', slug='device-role-1', color='ff0000'
|
||||||
|
)
|
||||||
|
Platform.objects.create(name='Platform 1', slug='platform-1')
|
||||||
|
Device.objects.create(
|
||||||
|
name='Device 1', device_type=device_type, device_role=device_role, site=site, rack=rack, position=1
|
||||||
|
)
|
||||||
|
cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||||
|
cluster_group = ClusterGroup.objects.create(name='Cluster Group 1', slug='cluster-group-1')
|
||||||
|
Cluster.objects.create(name='Cluster 1', type=cluster_type, group=cluster_group)
|
||||||
|
|
||||||
def test_racked_device(self):
|
def test_racked_device(self):
|
||||||
test = DeviceForm(data={
|
form = DeviceForm(data={
|
||||||
'name': 'test',
|
'name': 'New Device',
|
||||||
'device_role': get_id(DeviceRole, 'leaf-switch'),
|
'device_role': DeviceRole.objects.first().pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
'manufacturer': get_id(Manufacturer, 'juniper'),
|
'manufacturer': Manufacturer.objects.first().pk,
|
||||||
'device_type': get_id(DeviceType, 'qfx5100-48s'),
|
'device_type': DeviceType.objects.first().pk,
|
||||||
'site': get_id(Site, 'test1'),
|
'site': Site.objects.first().pk,
|
||||||
'rack': '1',
|
'rack': Rack.objects.first().pk,
|
||||||
'face': DeviceFaceChoices.FACE_FRONT,
|
'face': DeviceFaceChoices.FACE_FRONT,
|
||||||
'position': 41,
|
'position': 2,
|
||||||
'platform': get_id(Platform, 'juniper-junos'),
|
'platform': Platform.objects.first().pk,
|
||||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
})
|
})
|
||||||
self.assertTrue(test.is_valid(), test.fields['position'].choices)
|
self.assertTrue(form.is_valid())
|
||||||
self.assertTrue(test.save())
|
self.assertTrue(form.save())
|
||||||
|
|
||||||
def test_racked_device_occupied(self):
|
def test_racked_device_occupied(self):
|
||||||
test = DeviceForm(data={
|
form = DeviceForm(data={
|
||||||
'name': 'test',
|
'name': 'test',
|
||||||
'device_role': get_id(DeviceRole, 'leaf-switch'),
|
'device_role': DeviceRole.objects.first().pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
'manufacturer': get_id(Manufacturer, 'juniper'),
|
'manufacturer': Manufacturer.objects.first().pk,
|
||||||
'device_type': get_id(DeviceType, 'qfx5100-48s'),
|
'device_type': DeviceType.objects.first().pk,
|
||||||
'site': get_id(Site, 'test1'),
|
'site': Site.objects.first().pk,
|
||||||
'rack': '1',
|
'rack': Rack.objects.first().pk,
|
||||||
'face': DeviceFaceChoices.FACE_FRONT,
|
'face': DeviceFaceChoices.FACE_FRONT,
|
||||||
'position': 1,
|
'position': 1,
|
||||||
'platform': get_id(Platform, 'juniper-junos'),
|
'platform': Platform.objects.first().pk,
|
||||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
})
|
})
|
||||||
self.assertFalse(test.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
|
self.assertIn('position', form.errors)
|
||||||
|
|
||||||
def test_non_racked_device(self):
|
def test_non_racked_device(self):
|
||||||
test = DeviceForm(data={
|
form = DeviceForm(data={
|
||||||
'name': 'test',
|
'name': 'New Device',
|
||||||
'device_role': get_id(DeviceRole, 'pdu'),
|
'device_role': DeviceRole.objects.first().pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
'manufacturer': get_id(Manufacturer, 'servertech'),
|
'manufacturer': Manufacturer.objects.first().pk,
|
||||||
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
|
'device_type': DeviceType.objects.first().pk,
|
||||||
'site': get_id(Site, 'test1'),
|
'site': Site.objects.first().pk,
|
||||||
'rack': '1',
|
'rack': None,
|
||||||
'face': '',
|
'face': None,
|
||||||
'position': None,
|
'position': None,
|
||||||
'platform': None,
|
'platform': Platform.objects.first().pk,
|
||||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
})
|
})
|
||||||
self.assertTrue(test.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
self.assertTrue(test.save())
|
self.assertTrue(form.save())
|
||||||
|
|
||||||
def test_non_racked_device_with_face(self):
|
def test_non_racked_device_with_face_position(self):
|
||||||
test = DeviceForm(data={
|
form = DeviceForm(data={
|
||||||
'name': 'test',
|
'name': 'New Device',
|
||||||
'device_role': get_id(DeviceRole, 'pdu'),
|
'device_role': DeviceRole.objects.first().pk,
|
||||||
'tenant': None,
|
'tenant': None,
|
||||||
'manufacturer': get_id(Manufacturer, 'servertech'),
|
'manufacturer': Manufacturer.objects.first().pk,
|
||||||
'device_type': get_id(DeviceType, 'cwg-24vym415c9'),
|
'device_type': DeviceType.objects.first().pk,
|
||||||
'site': get_id(Site, 'test1'),
|
'site': Site.objects.first().pk,
|
||||||
'rack': '1',
|
'rack': None,
|
||||||
'face': DeviceFaceChoices.FACE_REAR,
|
'face': DeviceFaceChoices.FACE_REAR,
|
||||||
'position': None,
|
'position': 10,
|
||||||
'platform': None,
|
'platform': None,
|
||||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
})
|
})
|
||||||
self.assertTrue(test.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertTrue(test.save())
|
self.assertIn('face', form.errors)
|
||||||
|
self.assertIn('position', form.errors)
|
||||||
|
|
||||||
def test_cloned_cluster_device_initial_data(self):
|
def test_initial_data_population(self):
|
||||||
|
device_type = DeviceType.objects.first()
|
||||||
|
cluster = Cluster.objects.first()
|
||||||
test = DeviceForm(initial={
|
test = DeviceForm(initial={
|
||||||
'device_type': get_id(DeviceType, 'poweredge-r640'),
|
'device_type': device_type.pk,
|
||||||
'device_role': get_id(DeviceRole, 'server'),
|
'device_role': DeviceRole.objects.first().pk,
|
||||||
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
'site': get_id(Site, 'test1'),
|
'site': Site.objects.first().pk,
|
||||||
"cluster": Cluster.objects.get(id=4).id,
|
'cluster': cluster.pk,
|
||||||
})
|
})
|
||||||
self.assertEqual(test.initial['manufacturer'], get_id(Manufacturer, 'dell'))
|
|
||||||
self.assertIn('cluster_group', test.initial)
|
# Check that the initial value for the manufacturer is set automatically when assigning the device type
|
||||||
self.assertEqual(test.initial['cluster_group'], get_id(ClusterGroup, 'vm-host'))
|
self.assertEqual(test.initial['manufacturer'], device_type.manufacturer.pk)
|
||||||
|
|
||||||
|
# Check that the initial value for the cluster group is set automatically when assigning the cluster
|
||||||
|
self.assertEqual(test.initial['cluster_group'], cluster.group.pk)
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"model": "extras.graph",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"type": 300,
|
|
||||||
"weight": 1000,
|
|
||||||
"name": "Site Test Graph",
|
|
||||||
"source": "http://localhost/na.png",
|
|
||||||
"link": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "extras.graph",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"type": 200,
|
|
||||||
"weight": 1000,
|
|
||||||
"name": "Provider Test Graph",
|
|
||||||
"source": "http://localhost/provider_graph.png",
|
|
||||||
"link": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "extras.graph",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"type": 100,
|
|
||||||
"weight": 1000,
|
|
||||||
"name": "Interface Test Graph",
|
|
||||||
"source": "http://localhost/interface_graph.png",
|
|
||||||
"link": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -53,14 +53,15 @@ class ScriptVariable:
|
|||||||
# Initialize field attributes
|
# Initialize field attributes
|
||||||
if not hasattr(self, 'field_attrs'):
|
if not hasattr(self, 'field_attrs'):
|
||||||
self.field_attrs = {}
|
self.field_attrs = {}
|
||||||
if description:
|
|
||||||
self.field_attrs['help_text'] = description
|
|
||||||
if label:
|
if label:
|
||||||
self.field_attrs['label'] = label
|
self.field_attrs['label'] = label
|
||||||
|
if description:
|
||||||
|
self.field_attrs['help_text'] = description
|
||||||
if default:
|
if default:
|
||||||
self.field_attrs['initial'] = default
|
self.field_attrs['initial'] = default
|
||||||
if required:
|
self.field_attrs['required'] = required
|
||||||
self.field_attrs['required'] = True
|
|
||||||
|
# Initialize the list of optional validators if none have already been defined
|
||||||
if 'validators' not in self.field_attrs:
|
if 'validators' not in self.field_attrs:
|
||||||
self.field_attrs['validators'] = []
|
self.field_attrs['validators'] = []
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ urlpatterns = [
|
|||||||
path(r'tags/', views.TagListView.as_view(), name='tag_list'),
|
path(r'tags/', views.TagListView.as_view(), name='tag_list'),
|
||||||
path(r'tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'),
|
path(r'tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'),
|
||||||
path(r'tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
path(r'tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
||||||
path(r'tags/<slug:slug>/', views.TagView.as_view(), name='tag'),
|
path(r'tags/<str:slug>/', views.TagView.as_view(), name='tag'),
|
||||||
path(r'tags/<slug:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),
|
path(r'tags/<str:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),
|
||||||
path(r'tags/<slug:slug>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
|
path(r'tags/<str:slug>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
|
||||||
path(r'tags/<slug:slug>/changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}),
|
path(r'tags/<str:slug>/changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}),
|
||||||
|
|
||||||
# Config contexts
|
# Config contexts
|
||||||
path(r'config-contexts/', views.ConfigContextListView.as_view(), name='configcontext_list'),
|
path(r'config-contexts/', views.ConfigContextListView.as_view(), name='configcontext_list'),
|
||||||
|
@ -237,7 +237,7 @@ class AvailableIPSerializer(serializers.Serializer):
|
|||||||
# Services
|
# Services
|
||||||
#
|
#
|
||||||
|
|
||||||
class ServiceSerializer(CustomFieldModelSerializer):
|
class ServiceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
device = NestedDeviceSerializer(required=False, allow_null=True)
|
device = NestedDeviceSerializer(required=False, allow_null=True)
|
||||||
virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True)
|
virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True)
|
||||||
protocol = ChoiceField(choices=ServiceProtocolChoices)
|
protocol = ChoiceField(choices=ServiceProtocolChoices)
|
||||||
@ -247,10 +247,11 @@ class ServiceSerializer(CustomFieldModelSerializer):
|
|||||||
required=False,
|
required=False,
|
||||||
many=True
|
many=True
|
||||||
)
|
)
|
||||||
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Service
|
model = Service
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description',
|
'id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description', 'tags',
|
||||||
'custom_fields', 'created', 'last_updated',
|
'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
@ -1,329 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"model": "ipam.rir",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"name": "RFC1918",
|
|
||||||
"slug": "rfc1918"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.aggregate",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"prefix": "10.0.0.0/8",
|
|
||||||
"rir": 1,
|
|
||||||
"date_added": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.role",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"name": "Lab Network",
|
|
||||||
"slug": "lab-network",
|
|
||||||
"weight": 1000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.prefix",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"prefix": "10.1.1.0/24",
|
|
||||||
"site": 1,
|
|
||||||
"vrf": null,
|
|
||||||
"vlan": null,
|
|
||||||
"status": "active",
|
|
||||||
"role": 1,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.prefix",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"prefix": "10.0.255.0/24",
|
|
||||||
"site": 1,
|
|
||||||
"vrf": null,
|
|
||||||
"vlan": null,
|
|
||||||
"status": "active",
|
|
||||||
"role": 1,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.0.255.1/32",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 3,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "169.254.254.1/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 4,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.0.255.2/32",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 185,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "169.254.1.1/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 213,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.0.254.1/24",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 12,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.15.21.1/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 218,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.15.21.2/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 9,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.15.22.1/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 8,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.15.20.1/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 7,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 12,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.16.20.1/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 216,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 13,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.15.22.2/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 206,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 14,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.16.22.1/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 217,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 15,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.16.22.2/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 205,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 16,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.16.20.2/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 211,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 17,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.15.22.2/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 212,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 19,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "10.0.254.2/32",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 188,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 20,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "169.254.1.1/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 200,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.ipaddress",
|
|
||||||
"pk": 21,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"family": 4,
|
|
||||||
"address": "169.254.1.2/31",
|
|
||||||
"vrf": null,
|
|
||||||
"interface_id": 194,
|
|
||||||
"nat_inside": null,
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "ipam.vlan",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-06-23",
|
|
||||||
"last_updated": "2016-06-23T03:19:56.521Z",
|
|
||||||
"site": 1,
|
|
||||||
"vid": 999,
|
|
||||||
"name": "TEST",
|
|
||||||
"status": "active",
|
|
||||||
"role": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -638,6 +638,17 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
nat_vrf = forms.ModelChoiceField(
|
||||||
|
queryset=VRF.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label='VRF',
|
||||||
|
widget=APISelect(
|
||||||
|
api_url="/api/ipam/vrfs/",
|
||||||
|
filter_for={
|
||||||
|
'nat_inside': 'vrf_id'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
nat_inside = ChainedModelChoiceField(
|
nat_inside = ChainedModelChoiceField(
|
||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
chains=(
|
chains=(
|
||||||
|
@ -2,10 +2,10 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
IPADDRESS_STATUS_CHOICES = (
|
IPADDRESS_STATUS_CHOICES = (
|
||||||
(0, 'container'),
|
|
||||||
(1, 'active'),
|
(1, 'active'),
|
||||||
(2, 'reserved'),
|
(2, 'reserved'),
|
||||||
(3, 'deprecated'),
|
(3, 'deprecated'),
|
||||||
|
(5, 'dhcp'),
|
||||||
)
|
)
|
||||||
|
|
||||||
IPADDRESS_ROLE_CHOICES = (
|
IPADDRESS_ROLE_CHOICES = (
|
||||||
|
21
netbox/ipam/migrations/0034_fix_ipaddress_status_dhcp.py
Normal file
21
netbox/ipam/migrations/0034_fix_ipaddress_status_dhcp.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def ipaddress_status_dhcp_to_slug(apps, schema_editor):
|
||||||
|
IPAddress = apps.get_model('ipam', 'IPAddress')
|
||||||
|
IPAddress.objects.filter(status='5').update(status='dhcp')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0033_deterministic_ordering'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
# Fixes a missed integer substitution from #3569; see bug #4027. The original migration has also been fixed,
|
||||||
|
# so this can be omitted when squashing in the future.
|
||||||
|
migrations.RunPython(
|
||||||
|
code=ipaddress_status_dhcp_to_slug
|
||||||
|
),
|
||||||
|
]
|
@ -1064,6 +1064,7 @@ class ServiceTest(APITestCase):
|
|||||||
'name': 'Test Service 4',
|
'name': 'Test Service 4',
|
||||||
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
|
'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
|
||||||
'port': 4,
|
'port': 4,
|
||||||
|
'tags': ['Foo', 'Bar'],
|
||||||
}
|
}
|
||||||
|
|
||||||
url = reverse('ipam-api:service-list')
|
url = reverse('ipam-api:service-list')
|
||||||
@ -1076,6 +1077,8 @@ class ServiceTest(APITestCase):
|
|||||||
self.assertEqual(service4.name, data['name'])
|
self.assertEqual(service4.name, data['name'])
|
||||||
self.assertEqual(service4.protocol, data['protocol'])
|
self.assertEqual(service4.protocol, data['protocol'])
|
||||||
self.assertEqual(service4.port, data['port'])
|
self.assertEqual(service4.port, data['port'])
|
||||||
|
tags = [tag.name for tag in service4.tags.all()]
|
||||||
|
self.assertEqual(sorted(tags), sorted(data['tags']))
|
||||||
|
|
||||||
def test_create_service_bulk(self):
|
def test_create_service_bulk(self):
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '2.7.3-dev'
|
VERSION = '2.7.4-dev'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
@ -503,6 +503,7 @@ SWAGGER_SETTINGS = {
|
|||||||
'utilities.custom_inspectors.IdInFilterInspector',
|
'utilities.custom_inspectors.IdInFilterInspector',
|
||||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||||
],
|
],
|
||||||
|
'DEFAULT_INFO': 'netbox.urls.openapi_info',
|
||||||
'DEFAULT_MODEL_DEPTH': 1,
|
'DEFAULT_MODEL_DEPTH': 1,
|
||||||
'DEFAULT_PAGINATOR_INSPECTORS': [
|
'DEFAULT_PAGINATOR_INSPECTORS': [
|
||||||
'utilities.custom_inspectors.NullablePaginatorInspector',
|
'utilities.custom_inspectors.NullablePaginatorInspector',
|
||||||
|
@ -9,14 +9,16 @@ from netbox.views import APIRootView, HomeView, SearchView
|
|||||||
from users.views import LoginView, LogoutView
|
from users.views import LoginView, LogoutView
|
||||||
from .admin import admin_site
|
from .admin import admin_site
|
||||||
|
|
||||||
|
openapi_info = openapi.Info(
|
||||||
|
title="NetBox API",
|
||||||
|
default_version='v2',
|
||||||
|
description="API to access NetBox",
|
||||||
|
terms_of_service="https://github.com/netbox-community/netbox",
|
||||||
|
license=openapi.License(name="Apache v2 License"),
|
||||||
|
)
|
||||||
|
|
||||||
schema_view = get_schema_view(
|
schema_view = get_schema_view(
|
||||||
openapi.Info(
|
openapi_info,
|
||||||
title="NetBox API",
|
|
||||||
default_version='v2',
|
|
||||||
description="API to access NetBox",
|
|
||||||
terms_of_service="https://github.com/netbox-community/netbox",
|
|
||||||
license=openapi.License(name="Apache v2 License"),
|
|
||||||
),
|
|
||||||
validators=['flex', 'ssv'],
|
validators=['flex', 'ssv'],
|
||||||
public=True,
|
public=True,
|
||||||
)
|
)
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
$('button.toggle-ips').click(function() {
|
$('button.toggle-ips').click(function() {
|
||||||
var selected = $(this).attr('selected');
|
var selected = $(this).attr('selected');
|
||||||
if (selected) {
|
if (selected) {
|
||||||
$('#interfaces_table tr.ipaddresses').hide();
|
$('#interfaces_table tr.interface:visible + tr.ipaddresses').hide();
|
||||||
} else {
|
} else {
|
||||||
$('#interfaces_table tr.ipaddresses').show();
|
$('#interfaces_table tr.interface:visible + tr.ipaddresses').show();
|
||||||
}
|
}
|
||||||
$(this).attr('selected', !selected);
|
$(this).attr('selected', !selected);
|
||||||
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
||||||
@ -14,17 +14,22 @@ $('button.toggle-ips').click(function() {
|
|||||||
// Inteface filtering
|
// Inteface filtering
|
||||||
$('input.interface-filter').on('input', function() {
|
$('input.interface-filter').on('input', function() {
|
||||||
var filter = new RegExp(this.value);
|
var filter = new RegExp(this.value);
|
||||||
|
var interface;
|
||||||
|
|
||||||
for (interface of $(this).closest('div.panel').find('tbody > tr')) {
|
for (interface of $('#interfaces_table > tbody > tr.interface')) {
|
||||||
// Slice off 'interface_' at the start of the ID
|
// Slice off 'interface_' at the start of the ID
|
||||||
if (filter && filter.test(interface.id.slice(10))) {
|
if (filter.test(interface.id.slice(10))) {
|
||||||
// Match the toggle in case the filter now matches the interface
|
// Match the toggle in case the filter now matches the interface
|
||||||
$(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked'));
|
$(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked'));
|
||||||
$(interface).show();
|
$(interface).show();
|
||||||
|
if ($('button.toggle-ips').attr('selected')) {
|
||||||
|
$(interface).next('tr.ipaddresses').show();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Uncheck to prevent actions from including it when it doesn't match
|
// Uncheck to prevent actions from including it when it doesn't match
|
||||||
$(interface).find('input:checkbox[name=pk]').prop('checked', false);
|
$(interface).find('input:checkbox[name=pk]').prop('checked', false);
|
||||||
$(interface).hide();
|
$(interface).hide();
|
||||||
|
$(interface).next('tr.ipaddresses').hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
{% if cable.label %}<code>{{ cable.label }}</code>{% else %}Cable #{{ cable.pk }}{% endif %}
|
{% if cable.label %}<code>{{ cable.label }}</code>{% else %}Cable #{{ cable.pk }}{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
<p><span class="label label-{% if cable.status %}success{% else %}info{% endif %}">{{ cable.get_status_display }}</span></p>
|
<p><span class="label label-{{ cable.get_status_class }}">{{ cable.get_status_display }}</span></p>
|
||||||
<p>{{ cable.get_type_display|default:"" }}</p>
|
<p>{{ cable.get_type_display|default:"" }}</p>
|
||||||
{% if cable.length %}{{ cable.length }} {{ cable.get_length_unit_display }}{% endif %}
|
{% if cable.length %}{{ cable.length }} {{ cable.get_length_unit_display }}{% endif %}
|
||||||
{% if cable.color %}
|
{% if cable.color %}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% if perms.dcim.change_cable %}
|
{% if perms.dcim.change_cable %}
|
||||||
{% if cable.status %}
|
{% if cable.status == 'connected' %}
|
||||||
<a href="#" class="btn btn-warning btn-xs cable-toggle connected" title="Mark planned" data="{{ cable.pk }}">
|
<a href="#" class="btn btn-warning btn-xs cable-toggle connected" title="Mark planned" data="{{ cable.pk }}">
|
||||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<tr class="consoleport{% if cp.cable.status %} success{% elif cp.cable %} info{% endif %}">
|
<tr class="consoleport{% if cp.cable %} {{ cp.cable.get_status_class }}{% endif %}">
|
||||||
|
|
||||||
{# Name #}
|
{# Name #}
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
<tr class="consoleserverport{% if csp.cable.status %} success{% elif csp.cable %} info{% endif %}">
|
<tr class="consoleserverport{% if csp.cable %} {{ csp.cable.get_status_class }}{% endif %}">
|
||||||
|
|
||||||
{# Checkbox #}
|
{# Checkbox #}
|
||||||
{% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
|
{% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
<tr class="frontport{% if frontport.cable.status %} success{% elif frontport.cable %} info{% endif %}">
|
<tr class="frontport{% if frontport.cable %} {{ frontport.cable.get_status_class }}{% endif %}">
|
||||||
|
|
||||||
{# Checkbox #}
|
{# Checkbox #}
|
||||||
{% if perms.dcim.change_frontport or perms.dcim.delete_frontport %}
|
{% if perms.dcim.change_frontport or perms.dcim.delete_frontport %}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
<tr class="interface{% if not iface.enabled %} danger{% elif iface.cable.status %} success{% elif iface.cable %} info{% elif iface.is_virtual %} warning{% endif %}" id="interface_{{ iface.name }}">
|
<tr class="interface{% if not iface.enabled %} danger{% elif iface.cable %} {{ iface.cable.get_status_class }}{% elif iface.is_virtual %} warning{% endif %}" id="interface_{{ iface.name }}">
|
||||||
|
|
||||||
{# Checkbox #}
|
{# Checkbox #}
|
||||||
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
<tr class="poweroutlet{% if po.cable.status %} success{% elif po.cable %} info{% endif %}">
|
<tr class="poweroutlet{% if po.cable %} {{ po.cable.get_status_class }}{% endif %}">
|
||||||
|
|
||||||
{# Checkbox #}
|
{# Checkbox #}
|
||||||
{% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
|
{% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<tr class="powerport{% if pp.cable.status %} success{% elif pp.cable %} info{% endif %}">
|
<tr class="powerport{% if pp.cable %} {{ pp.cable.get_status_class }}{% endif %}">
|
||||||
|
|
||||||
{# Name #}
|
{# Name #}
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
<tr class="rearport{% if rearport.cable.status %} success{% elif rearport.cable %} info{% endif %}">
|
<tr class="rearport{% if rearport.cable %} {{ rearport.cable.get_status_class }}{% endif %}">
|
||||||
|
|
||||||
{# Checkbox #}
|
{# Checkbox #}
|
||||||
{% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}
|
{% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
{% render_field form.nat_device %}
|
{% render_field form.nat_device %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="search">
|
<div class="tab-pane" id="search">
|
||||||
|
{% render_field form.nat_vrf %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.nat_inside %}
|
{% render_field form.nat_inside %}
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
{% render_field form.type %}
|
{% render_field form.type %}
|
||||||
{% render_field form.group %}
|
{% render_field form.group %}
|
||||||
{% render_field form.tenant %}
|
|
||||||
{% render_field form.site %}
|
{% render_field form.site %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,9 +7,6 @@ from django.urls import reverse
|
|||||||
|
|
||||||
from .views import server_error
|
from .views import server_error
|
||||||
|
|
||||||
BASE_PATH = getattr(settings, 'BASE_PATH', False)
|
|
||||||
LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False)
|
|
||||||
|
|
||||||
|
|
||||||
class LoginRequiredMiddleware(object):
|
class LoginRequiredMiddleware(object):
|
||||||
"""
|
"""
|
||||||
@ -19,7 +16,7 @@ class LoginRequiredMiddleware(object):
|
|||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
if LOGIN_REQUIRED and not request.user.is_authenticated:
|
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
|
||||||
# Redirect unauthenticated requests to the login page. API requests are exempt from redirection as the API
|
# Redirect unauthenticated requests to the login page. API requests are exempt from redirection as the API
|
||||||
# performs its own authentication. Also metrics can be read without login.
|
# performs its own authentication. Also metrics can be read without login.
|
||||||
api_path = reverse('api-root')
|
api_path = reverse('api-root')
|
||||||
|
@ -1,170 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"model": "virtualization.clustertype",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "Public Cloud",
|
|
||||||
"slug": "public-cloud"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.clustertype",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "vSphere",
|
|
||||||
"slug": "vsphere"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.clustertype",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "Hyper-V",
|
|
||||||
"slug": "hyper-v"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.clustertype",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "libvirt",
|
|
||||||
"slug": "libvirt"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.clustertype",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "LXD",
|
|
||||||
"slug": "lxd"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.clustertype",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "Docker",
|
|
||||||
"slug": "docker"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.clustergroup",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "VM Host",
|
|
||||||
"slug": "vm-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.cluster",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "Digital Ocean",
|
|
||||||
"type": 1,
|
|
||||||
"group": 1,
|
|
||||||
"tenant": null,
|
|
||||||
"site": null,
|
|
||||||
"comments": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.cluster",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "Amazon EC2",
|
|
||||||
"type": 1,
|
|
||||||
"group": 1,
|
|
||||||
"tenant": null,
|
|
||||||
"site": null,
|
|
||||||
"comments": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.cluster",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "Microsoft Azure",
|
|
||||||
"type": 1,
|
|
||||||
"group": 1,
|
|
||||||
"tenant": null,
|
|
||||||
"site": null,
|
|
||||||
"comments": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.cluster",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"created": "2016-08-01",
|
|
||||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
|
||||||
"name": "vSphere Cluster",
|
|
||||||
"type": 2,
|
|
||||||
"group": 1,
|
|
||||||
"tenant": null,
|
|
||||||
"site": null,
|
|
||||||
"comments": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.virtualmachine",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"local_context_data": null,
|
|
||||||
"created": "2019-12-19",
|
|
||||||
"last_updated": "2019-12-19T05:24:19.146Z",
|
|
||||||
"cluster": 2,
|
|
||||||
"tenant": null,
|
|
||||||
"platform": null,
|
|
||||||
"name": "vm1",
|
|
||||||
"status": "active",
|
|
||||||
"role": null,
|
|
||||||
"primary_ip4": null,
|
|
||||||
"primary_ip6": null,
|
|
||||||
"vcpus": null,
|
|
||||||
"memory": null,
|
|
||||||
"disk": null,
|
|
||||||
"comments": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "virtualization.virtualmachine",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"local_context_data": null,
|
|
||||||
"created": "2019-12-19",
|
|
||||||
"last_updated": "2019-12-19T05:24:41.478Z",
|
|
||||||
"cluster": 1,
|
|
||||||
"tenant": null,
|
|
||||||
"platform": null,
|
|
||||||
"name": "vm2",
|
|
||||||
"status": "active",
|
|
||||||
"role": null,
|
|
||||||
"primary_ip4": null,
|
|
||||||
"primary_ip6": null,
|
|
||||||
"vcpus": null,
|
|
||||||
"memory": null,
|
|
||||||
"disk": null,
|
|
||||||
"comments": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -171,7 +171,8 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
comments = CommentField(
|
comments = CommentField(
|
||||||
widget=SmallTextarea()
|
widget=SmallTextarea,
|
||||||
|
label='Comments'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -536,7 +537,8 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
|
|||||||
label='Disk (GB)'
|
label='Disk (GB)'
|
||||||
)
|
)
|
||||||
comments = CommentField(
|
comments = CommentField(
|
||||||
widget=SmallTextarea()
|
widget=SmallTextarea,
|
||||||
|
label='Comments'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
Loading…
Reference in New Issue
Block a user