diff --git a/docs/additional-features/custom-scripts.md b/docs/additional-features/custom-scripts.md index c4dffb4b9f7..6fac5b63df7 100644 --- a/docs/additional-features/custom-scripts.md +++ b/docs/additional-features/custom-scripts.md @@ -124,7 +124,7 @@ Arbitrary text of any length. Renders as multi-line text input field. Stored a numeric integer. Options include: -* `min_value:` - Minimum value +* `min_value` - Minimum value * `max_value` - Maximum value ### BooleanVar @@ -158,9 +158,20 @@ A NetBox object. The list of available objects is defined by the queryset parame An uploaded file. Note that uploaded files are present in memory only for the duration of the script's execution: They will not be save for future use. +### IPAddressVar + +An IPv4 or IPv6 address, without a mask. Returns a `netaddr.IPAddress` object. + +### IPAddressWithMaskVar + +An IPv4 or IPv6 address with a mask. Returns a `netaddr.IPNetwork` object which includes the mask. + ### IPNetworkVar -An IPv4 or IPv6 network with a mask. +An IPv4 or IPv6 network with a mask. Returns a `netaddr.IPNetwork` object. Two attributes are available to validate the provided mask: + +* `min_prefix_length` - Minimum length of the mask (default: none) +* `max_prefix_length` - Maximum length of the mask (default: none) ### Default Options diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index fb544d8a8b2..a6a6b65cfd6 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -1,3 +1,32 @@ +# v2.7.3 (2020-01-28) + +## Enhancements + +* [#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 +* [#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 + +## Bug Fixes + +* [#3950](https://github.com/netbox-community/netbox/issues/3950) - Automatically select parent manufacturer when specifying initial device type during device creation +* [#3982](https://github.com/netbox-community/netbox/issues/3982) - Restore tooltip for reservations on rack elevations +* [#3983](https://github.com/netbox-community/netbox/issues/3983) - Permit the creation of multiple unnamed devices +* [#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 +* [#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 + +--- + # v2.7.2 (2020-01-21) ## Enhancements diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index b22135b3fa7..6bac48a59cf 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -3,11 +3,11 @@ from circuits.choices import CircuitStatusChoices 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 extras.api.customfields import CustomFieldModelSerializer from tenancy.api.nested_serializers import NestedTenantSerializer -from utilities.api import ChoiceField, ValidatedModelSerializer +from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer from .nested_serializers import * @@ -39,18 +39,30 @@ class Meta: 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): provider = NestedProviderSerializer() status = ChoiceField(choices=CircuitStatusChoices, required=False) type = NestedCircuitTypeSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True) + termination_a = CircuitCircuitTerminationSerializer(read_only=True) + termination_z = CircuitCircuitTerminationSerializer(read_only=True) tags = TagListSerializerField(required=False) class Meta: model = Circuit fields = [ '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', ] diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 98b7c918460..75f7e0e3ed2 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -62,7 +62,9 @@ class CircuitTypeViewSet(ModelViewSet): # 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 filterset_class = filters.CircuitFilterSet diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index d5d78e7bd75..165e32eb3dd 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -89,7 +89,8 @@ class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdi label='Admin contact' ) comments = CommentField( - widget=SmallTextarea() + widget=SmallTextarea, + label='Comments' ) class Meta: diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 3a6f8e5e912..0e05867e482 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -4,17 +4,30 @@ # -# Rack elevation rendering +# Racks # +RACK_U_HEIGHT_DEFAULT = 42 + RACK_ELEVATION_UNIT_WIDTH_DEFAULT = 230 RACK_ELEVATION_UNIT_HEIGHT_DEFAULT = 20 # -# Interface type groups +# RearPorts # +REARPORT_POSITIONS_MIN = 1 +REARPORT_POSITIONS_MAX = 64 + + +# +# Interfaces +# + +INTERFACE_MTU_MIN = 1 +INTERFACE_MTU_MAX = 32767 # Max value of a signed 16-bit integer + VIRTUAL_IFACE_TYPES = [ InterfaceTypeChoices.TYPE_VIRTUAL, InterfaceTypeChoices.TYPE_LAG, @@ -31,6 +44,17 @@ NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES +# +# PowerFeeds +# + +POWERFEED_VOLTAGE_DEFAULT = 120 + +POWERFEED_AMPERAGE_DEFAULT = 20 + +POWERFEED_MAX_UTILIZATION_DEFAULT = 80 # Percentage + + # # Cabling and connections # diff --git a/netbox/dcim/fixtures/dcim.json b/netbox/dcim/fixtures/dcim.json deleted file mode 100644 index b9f41edb57a..00000000000 --- a/netbox/dcim/fixtures/dcim.json +++ /dev/null @@ -1,5732 +0,0 @@ -[ -{ - "model": "dcim.site", - "pk": 1, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "name": "TEST1", - "slug": "test1", - "facility": "Test Facility", - "asn": 65535, - "physical_address": "555 Test Ave.\r\nTest, NY 55555", - "shipping_address": "", - "comments": "" - } -}, -{ - "model": "dcim.rack", - "pk": 1, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "name": "A1R1", - "facility_id": "T23A01", - "site": 1, - "group": null, - "u_height": 42, - "comments": "" - } -}, -{ - "model": "dcim.rack", - "pk": 2, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "name": "A1R2", - "facility_id": "T24A01", - "site": 1, - "group": null, - "u_height": 42, - "comments": "" - } -}, -{ - "model": "dcim.manufacturer", - "pk": 1, - "fields": { - "name": "Juniper", - "slug": "juniper" - } -}, -{ - "model": "dcim.manufacturer", - "pk": 2, - "fields": { - "name": "Opengear", - "slug": "opengear" - } -}, -{ - "model": "dcim.manufacturer", - "pk": 3, - "fields": { - "name": "ServerTech", - "slug": "servertech" - } -}, -{ - "model": "dcim.devicetype", - "pk": 1, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "manufacturer": 1, - "model": "MX960", - "slug": "mx960", - "u_height": 16, - "is_full_depth": true - } -}, -{ - "model": "dcim.devicetype", - "pk": 2, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "manufacturer": 1, - "model": "EX9214", - "slug": "ex9214", - "u_height": 16, - "is_full_depth": true - } -}, -{ - "model": "dcim.devicetype", - "pk": 3, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "manufacturer": 1, - "model": "QFX5100-24Q", - "slug": "qfx5100-24q", - "u_height": 1, - "is_full_depth": true - } -}, -{ - "model": "dcim.devicetype", - "pk": 4, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "manufacturer": 1, - "model": "QFX5100-48S", - "slug": "qfx5100-48s", - "u_height": 1, - "is_full_depth": true - } -}, -{ - "model": "dcim.devicetype", - "pk": 5, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "manufacturer": 2, - "model": "CM4148", - "slug": "cm4148", - "u_height": 1, - "is_full_depth": true - } -}, -{ - "model": "dcim.devicetype", - "pk": 6, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "manufacturer": 3, - "model": "CWG-24VYM415C9", - "slug": "cwg-24vym415c9", - "u_height": 0, - "is_full_depth": false - } -}, -{ - "model": "dcim.consoleporttemplate", - "pk": 1, - "fields": { - "device_type": 1, - "name": "Console (RE0)" - } -}, -{ - "model": "dcim.consoleporttemplate", - "pk": 2, - "fields": { - "device_type": 1, - "name": "Console (RE1)" - } -}, -{ - "model": "dcim.consoleporttemplate", - "pk": 3, - "fields": { - "device_type": 2, - "name": "Console (RE0)" - } -}, -{ - "model": "dcim.consoleporttemplate", - "pk": 4, - "fields": { - "device_type": 2, - "name": "Console (RE1)" - } -}, -{ - "model": "dcim.consoleporttemplate", - "pk": 5, - "fields": { - "device_type": 3, - "name": "Console" - } -}, -{ - "model": "dcim.consoleporttemplate", - "pk": 6, - "fields": { - "device_type": 5, - "name": "Console" - } -}, -{ - "model": "dcim.consoleporttemplate", - "pk": 7, - "fields": { - "device_type": 6, - "name": "Serial" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 1, - "fields": { - "device_type": 3, - "name": "Console" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 3, - "fields": { - "device_type": 4, - "name": "Console" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 4, - "fields": { - "device_type": 5, - "name": "Port 1" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 5, - "fields": { - "device_type": 5, - "name": "Port 2" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 6, - "fields": { - "device_type": 5, - "name": "Port 3" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 7, - "fields": { - "device_type": 5, - "name": "Port 4" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 8, - "fields": { - "device_type": 5, - "name": "Port 5" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 9, - "fields": { - "device_type": 5, - "name": "Port 6" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 10, - "fields": { - "device_type": 5, - "name": "Port 7" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 11, - "fields": { - "device_type": 5, - "name": "Port 8" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 12, - "fields": { - "device_type": 5, - "name": "Port 9" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 13, - "fields": { - "device_type": 5, - "name": "Port 10" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 14, - "fields": { - "device_type": 5, - "name": "Port 11" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 15, - "fields": { - "device_type": 5, - "name": "Port 12" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 16, - "fields": { - "device_type": 5, - "name": "Port 13" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 17, - "fields": { - "device_type": 5, - "name": "Port 14" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 18, - "fields": { - "device_type": 5, - "name": "Port 15" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 19, - "fields": { - "device_type": 5, - "name": "Port 16" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 20, - "fields": { - "device_type": 5, - "name": "Port 17" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 21, - "fields": { - "device_type": 5, - "name": "Port 18" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 22, - "fields": { - "device_type": 5, - "name": "Port 19" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 23, - "fields": { - "device_type": 5, - "name": "Port 20" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 24, - "fields": { - "device_type": 5, - "name": "Port 21" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 25, - "fields": { - "device_type": 5, - "name": "Port 22" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 26, - "fields": { - "device_type": 5, - "name": "Port 23" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 27, - "fields": { - "device_type": 5, - "name": "Port 24" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 28, - "fields": { - "device_type": 5, - "name": "Port 25" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 29, - "fields": { - "device_type": 5, - "name": "Port 26" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 30, - "fields": { - "device_type": 5, - "name": "Port 27" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 31, - "fields": { - "device_type": 5, - "name": "Port 28" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 32, - "fields": { - "device_type": 5, - "name": "Port 29" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 33, - "fields": { - "device_type": 5, - "name": "Port 30" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 34, - "fields": { - "device_type": 5, - "name": "Port 31" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 35, - "fields": { - "device_type": 5, - "name": "Port 32" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 36, - "fields": { - "device_type": 5, - "name": "Port 33" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 37, - "fields": { - "device_type": 5, - "name": "Port 34" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 38, - "fields": { - "device_type": 5, - "name": "Port 35" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 39, - "fields": { - "device_type": 5, - "name": "Port 36" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 40, - "fields": { - "device_type": 5, - "name": "Port 37" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 41, - "fields": { - "device_type": 5, - "name": "Port 38" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 42, - "fields": { - "device_type": 5, - "name": "Port 39" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 43, - "fields": { - "device_type": 5, - "name": "Port 40" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 44, - "fields": { - "device_type": 5, - "name": "Port 41" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 45, - "fields": { - "device_type": 5, - "name": "Port 42" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 46, - "fields": { - "device_type": 5, - "name": "Port 43" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 47, - "fields": { - "device_type": 5, - "name": "Port 44" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 48, - "fields": { - "device_type": 5, - "name": "Port 45" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 49, - "fields": { - "device_type": 5, - "name": "Port 46" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 50, - "fields": { - "device_type": 5, - "name": "Port 47" - } -}, -{ - "model": "dcim.consoleserverporttemplate", - "pk": 51, - "fields": { - "device_type": 5, - "name": "Port 48" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 1, - "fields": { - "device_type": 1, - "name": "PEM0" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 2, - "fields": { - "device_type": 1, - "name": "PEM1" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 3, - "fields": { - "device_type": 1, - "name": "PEM2" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 4, - "fields": { - "device_type": 1, - "name": "PEM3" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 5, - "fields": { - "device_type": 2, - "name": "PEM0" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 6, - "fields": { - "device_type": 2, - "name": "PEM1" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 7, - "fields": { - "device_type": 2, - "name": "PEM2" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 8, - "fields": { - "device_type": 2, - "name": "PEM3" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 9, - "fields": { - "device_type": 4, - "name": "PSU0" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 11, - "fields": { - "device_type": 3, - "name": "PSU0" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 12, - "fields": { - "device_type": 3, - "name": "PSU1" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 13, - "fields": { - "device_type": 4, - "name": "PSU1" - } -}, -{ - "model": "dcim.powerporttemplate", - "pk": 14, - "fields": { - "device_type": 5, - "name": "PSU" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 4, - "fields": { - "device_type": 6, - "name": "AA1" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 5, - "fields": { - "device_type": 6, - "name": "AA2" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 6, - "fields": { - "device_type": 6, - "name": "AA3" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 7, - "fields": { - "device_type": 6, - "name": "AA4" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 8, - "fields": { - "device_type": 6, - "name": "AA5" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 9, - "fields": { - "device_type": 6, - "name": "AA6" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 10, - "fields": { - "device_type": 6, - "name": "AA7" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 11, - "fields": { - "device_type": 6, - "name": "AA8" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 12, - "fields": { - "device_type": 6, - "name": "AB1" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 13, - "fields": { - "device_type": 6, - "name": "AB2" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 14, - "fields": { - "device_type": 6, - "name": "AB3" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 15, - "fields": { - "device_type": 6, - "name": "AB4" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 16, - "fields": { - "device_type": 6, - "name": "AB5" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 17, - "fields": { - "device_type": 6, - "name": "AB6" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 18, - "fields": { - "device_type": 6, - "name": "AB7" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 19, - "fields": { - "device_type": 6, - "name": "AB8" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 20, - "fields": { - "device_type": 6, - "name": "AC1" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 21, - "fields": { - "device_type": 6, - "name": "AC2" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 22, - "fields": { - "device_type": 6, - "name": "AC3" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 23, - "fields": { - "device_type": 6, - "name": "AC4" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 24, - "fields": { - "device_type": 6, - "name": "AC5" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 25, - "fields": { - "device_type": 6, - "name": "AC6" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 26, - "fields": { - "device_type": 6, - "name": "AC7" - } -}, -{ - "model": "dcim.poweroutlettemplate", - "pk": 27, - "fields": { - "device_type": 6, - "name": "AC8" - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 1, - "fields": { - "device_type": 1, - "name": "fxp0 (RE0)", - "type": 1000, - "mgmt_only": true - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 2, - "fields": { - "device_type": 1, - "name": "fxp0 (RE1)", - "type": 800, - "mgmt_only": true - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 3, - "fields": { - "device_type": 1, - "name": "lo0", - "type": 0, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 4, - "fields": { - "device_type": 2, - "name": "fxp0 (RE0)", - "type": 1000, - "mgmt_only": true - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 5, - "fields": { - "device_type": 2, - "name": "fxp0 (RE1)", - "type": 1000, - "mgmt_only": true - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 6, - "fields": { - "device_type": 2, - "name": "lo0", - "type": 0, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 7, - "fields": { - "device_type": 3, - "name": "em0", - "type": 800, - "mgmt_only": true - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 8, - "fields": { - "device_type": 3, - "name": "et-0/0/0", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 9, - "fields": { - "device_type": 3, - "name": "et-0/0/1", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 10, - "fields": { - "device_type": 3, - "name": "et-0/0/2", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 11, - "fields": { - "device_type": 3, - "name": "et-0/0/3", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 12, - "fields": { - "device_type": 3, - "name": "et-0/0/4", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 13, - "fields": { - "device_type": 3, - "name": "et-0/0/5", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 14, - "fields": { - "device_type": 3, - "name": "et-0/0/6", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 15, - "fields": { - "device_type": 3, - "name": "et-0/0/7", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 16, - "fields": { - "device_type": 3, - "name": "et-0/0/8", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 17, - "fields": { - "device_type": 3, - "name": "et-0/0/9", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 18, - "fields": { - "device_type": 3, - "name": "et-0/0/10", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 19, - "fields": { - "device_type": 3, - "name": "et-0/0/11", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 20, - "fields": { - "device_type": 3, - "name": "et-0/0/12", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 21, - "fields": { - "device_type": 3, - "name": "et-0/0/13", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 22, - "fields": { - "device_type": 3, - "name": "et-0/0/14", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 23, - "fields": { - "device_type": 3, - "name": "et-0/0/15", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 24, - "fields": { - "device_type": 3, - "name": "et-0/0/16", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 25, - "fields": { - "device_type": 3, - "name": "et-0/0/17", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 26, - "fields": { - "device_type": 3, - "name": "et-0/0/18", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 27, - "fields": { - "device_type": 3, - "name": "et-0/0/19", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 28, - "fields": { - "device_type": 3, - "name": "et-0/0/20", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 29, - "fields": { - "device_type": 3, - "name": "et-0/0/21", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 30, - "fields": { - "device_type": 3, - "name": "et-0/0/22", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 31, - "fields": { - "device_type": 3, - "name": "et-0/1/0", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 32, - "fields": { - "device_type": 3, - "name": "et-0/1/1", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 33, - "fields": { - "device_type": 3, - "name": "et-0/1/2", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 34, - "fields": { - "device_type": 3, - "name": "et-0/1/3", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 35, - "fields": { - "device_type": 3, - "name": "et-0/2/0", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 36, - "fields": { - "device_type": 3, - "name": "et-0/2/1", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 37, - "fields": { - "device_type": 3, - "name": "et-0/2/2", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 38, - "fields": { - "device_type": 3, - "name": "et-0/2/3", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 138, - "fields": { - "device_type": 4, - "name": "em0", - "type": 1000, - "mgmt_only": true - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 139, - "fields": { - "device_type": 4, - "name": "xe-0/0/0", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 140, - "fields": { - "device_type": 4, - "name": "xe-0/0/1", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 141, - "fields": { - "device_type": 4, - "name": "xe-0/0/2", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 142, - "fields": { - "device_type": 4, - "name": "xe-0/0/3", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 143, - "fields": { - "device_type": 4, - "name": "xe-0/0/4", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 144, - "fields": { - "device_type": 4, - "name": "xe-0/0/5", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 145, - "fields": { - "device_type": 4, - "name": "xe-0/0/6", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 146, - "fields": { - "device_type": 4, - "name": "xe-0/0/7", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 147, - "fields": { - "device_type": 4, - "name": "xe-0/0/8", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 148, - "fields": { - "device_type": 4, - "name": "xe-0/0/9", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 149, - "fields": { - "device_type": 4, - "name": "xe-0/0/10", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 150, - "fields": { - "device_type": 4, - "name": "xe-0/0/11", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 151, - "fields": { - "device_type": 4, - "name": "xe-0/0/12", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 152, - "fields": { - "device_type": 4, - "name": "xe-0/0/13", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 153, - "fields": { - "device_type": 4, - "name": "xe-0/0/14", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 154, - "fields": { - "device_type": 4, - "name": "xe-0/0/15", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 155, - "fields": { - "device_type": 4, - "name": "xe-0/0/16", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 156, - "fields": { - "device_type": 4, - "name": "xe-0/0/17", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 157, - "fields": { - "device_type": 4, - "name": "xe-0/0/18", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 158, - "fields": { - "device_type": 4, - "name": "xe-0/0/19", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 159, - "fields": { - "device_type": 4, - "name": "xe-0/0/20", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 160, - "fields": { - "device_type": 4, - "name": "xe-0/0/21", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 161, - "fields": { - "device_type": 4, - "name": "xe-0/0/22", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 162, - "fields": { - "device_type": 4, - "name": "xe-0/0/23", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 163, - "fields": { - "device_type": 4, - "name": "xe-0/0/24", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 164, - "fields": { - "device_type": 4, - "name": "xe-0/0/25", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 165, - "fields": { - "device_type": 4, - "name": "xe-0/0/26", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 166, - "fields": { - "device_type": 4, - "name": "xe-0/0/27", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 167, - "fields": { - "device_type": 4, - "name": "xe-0/0/28", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 168, - "fields": { - "device_type": 4, - "name": "xe-0/0/29", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 169, - "fields": { - "device_type": 4, - "name": "xe-0/0/30", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 170, - "fields": { - "device_type": 4, - "name": "xe-0/0/31", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 171, - "fields": { - "device_type": 4, - "name": "xe-0/0/32", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 172, - "fields": { - "device_type": 4, - "name": "xe-0/0/33", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 173, - "fields": { - "device_type": 4, - "name": "xe-0/0/34", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 174, - "fields": { - "device_type": 4, - "name": "xe-0/0/35", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 175, - "fields": { - "device_type": 4, - "name": "xe-0/0/36", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 176, - "fields": { - "device_type": 4, - "name": "xe-0/0/37", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 177, - "fields": { - "device_type": 4, - "name": "xe-0/0/38", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 178, - "fields": { - "device_type": 4, - "name": "xe-0/0/39", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 179, - "fields": { - "device_type": 4, - "name": "xe-0/0/40", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 180, - "fields": { - "device_type": 4, - "name": "xe-0/0/41", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 181, - "fields": { - "device_type": 4, - "name": "xe-0/0/42", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 182, - "fields": { - "device_type": 4, - "name": "xe-0/0/43", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 183, - "fields": { - "device_type": 4, - "name": "xe-0/0/44", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 184, - "fields": { - "device_type": 4, - "name": "xe-0/0/45", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 185, - "fields": { - "device_type": 4, - "name": "xe-0/0/46", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 186, - "fields": { - "device_type": 4, - "name": "xe-0/0/47", - "type": 1200, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 187, - "fields": { - "device_type": 4, - "name": "et-0/0/48", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 188, - "fields": { - "device_type": 4, - "name": "et-0/0/49", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 189, - "fields": { - "device_type": 4, - "name": "et-0/0/50", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 190, - "fields": { - "device_type": 4, - "name": "et-0/0/51", - "type": 1400, - "mgmt_only": false - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 191, - "fields": { - "device_type": 5, - "name": "eth0", - "type": 1000, - "mgmt_only": true - } -}, -{ - "model": "dcim.interfacetemplate", - "pk": 192, - "fields": { - "device_type": 6, - "name": "Net", - "type": 800, - "mgmt_only": true - } -}, -{ - "model": "dcim.devicerole", - "pk": 1, - "fields": { - "name": "Router", - "slug": "router", - "color": "purple" - } -}, -{ - "model": "dcim.devicerole", - "pk": 2, - "fields": { - "name": "Spine Switch", - "slug": "spine-switch", - "color": "green" - } -}, -{ - "model": "dcim.devicerole", - "pk": 3, - "fields": { - "name": "Core Switch", - "slug": "core-switch", - "color": "red" - } -}, -{ - "model": "dcim.devicerole", - "pk": 4, - "fields": { - "name": "Leaf Switch", - "slug": "leaf-switch", - "color": "teal" - } -}, -{ - "model": "dcim.devicerole", - "pk": 5, - "fields": { - "name": "OOB Switch", - "slug": "oob-switch", - "color": "purple" - } -}, -{ - "model": "dcim.devicerole", - "pk": 6, - "fields": { - "name": "PDU", - "slug": "pdu", - "color": "yellow" - } -}, -{ - "model": "dcim.platform", - "pk": 1, - "fields": { - "name": "Juniper Junos", - "slug": "juniper-junos" - } -}, -{ - "model": "dcim.platform", - "pk": 2, - "fields": { - "name": "Opengear", - "slug": "opengear" - } -}, -{ - "model": "dcim.device", - "pk": 1, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 1, - "device_role": 1, - "platform": 1, - "name": "test1-edge1", - "serial": "5555555555", - "site": 1, - "rack": 1, - "position": 1, - "face": "front", - "status": true, - "primary_ip4": 1, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 2, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 2, - "device_role": 3, - "platform": 1, - "name": "test1-core1", - "serial": "", - "site": 1, - "rack": 1, - "position": 17, - "face": "rear", - "status": true, - "primary_ip4": 5, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 3, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 3, - "device_role": 2, - "platform": 1, - "name": "test1-spine1", - "serial": "", - "site": 1, - "rack": 1, - "position": 33, - "face": "rear", - "status": true, - "primary_ip4": null, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 4, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 4, - "device_role": 4, - "platform": 1, - "name": "test1-leaf1", - "serial": "", - "site": 1, - "rack": 1, - "position": 34, - "face": "rear", - "status": true, - "primary_ip4": null, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 5, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 4, - "device_role": 4, - "platform": 1, - "name": "test1-leaf2", - "serial": "9823478293748", - "site": 1, - "rack": 2, - "position": 34, - "face": "rear", - "status": true, - "primary_ip4": null, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 6, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 3, - "device_role": 2, - "platform": 1, - "name": "test1-spine2", - "serial": "45649818158", - "site": 1, - "rack": 2, - "position": 33, - "face": "rear", - "status": true, - "primary_ip4": null, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 7, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 1, - "device_role": 1, - "platform": 1, - "name": "test1-edge2", - "serial": "7567356345", - "site": 1, - "rack": 2, - "position": 1, - "face": "rear", - "status": true, - "primary_ip4": 3, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 8, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 2, - "device_role": 3, - "platform": 1, - "name": "test1-core2", - "serial": "67856734534", - "site": 1, - "rack": 2, - "position": 17, - "face": "rear", - "status": true, - "primary_ip4": 19, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 9, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 5, - "device_role": 5, - "platform": 2, - "name": "test1-oob1", - "serial": "98273942938", - "site": 1, - "rack": 1, - "position": 42, - "face": "rear", - "status": true, - "primary_ip4": null, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 11, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 6, - "device_role": 6, - "platform": null, - "name": "test1-pdu1", - "serial": "", - "site": 1, - "rack": 1, - "position": null, - "face": "", - "status": true, - "primary_ip4": null, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.device", - "pk": 12, - "fields": { - "created": "2016-06-23", - "last_updated": "2016-06-23T03:19:56.521Z", - "device_type": 6, - "device_role": 6, - "platform": null, - "name": "test1-pdu2", - "serial": "", - "site": 1, - "rack": 2, - "position": null, - "face": "", - "status": true, - "primary_ip4": null, - "primary_ip6": null, - "comments": "" - } -}, -{ - "model": "dcim.consoleport", - "pk": 1, - "fields": { - "device": 1, - "name": "Console (RE0)", - "connected_endpoint": 27, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 2, - "fields": { - "device": 1, - "name": "Console (RE1)", - "connected_endpoint": 38, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 3, - "fields": { - "device": 2, - "name": "Console (RE0)", - "connected_endpoint": 5, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 4, - "fields": { - "device": 2, - "name": "Console (RE1)", - "connected_endpoint": 16, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 5, - "fields": { - "device": 3, - "name": "Console", - "connected_endpoint": 49, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 6, - "fields": { - "device": 4, - "name": "Console", - "connected_endpoint": 48, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 7, - "fields": { - "device": 5, - "name": "Console", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 8, - "fields": { - "device": 6, - "name": "Console", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 9, - "fields": { - "device": 7, - "name": "Console (RE0)", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 10, - "fields": { - "device": 7, - "name": "Console (RE1)", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 11, - "fields": { - "device": 8, - "name": "Console (RE0)", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 12, - "fields": { - "device": 8, - "name": "Console (RE1)", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 13, - "fields": { - "device": 9, - "name": "Console", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 15, - "fields": { - "device": 11, - "name": "Serial", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleport", - "pk": 16, - "fields": { - "device": 12, - "name": "Serial", - "connected_endpoint": null, - "connection_status": true - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 5, - "fields": { - "device": 9, - "name": "Port 1" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 6, - "fields": { - "device": 9, - "name": "Port 10" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 7, - "fields": { - "device": 9, - "name": "Port 11" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 8, - "fields": { - "device": 9, - "name": "Port 12" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 9, - "fields": { - "device": 9, - "name": "Port 13" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 10, - "fields": { - "device": 9, - "name": "Port 14" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 11, - "fields": { - "device": 9, - "name": "Port 15" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 12, - "fields": { - "device": 9, - "name": "Port 16" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 13, - "fields": { - "device": 9, - "name": "Port 17" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 14, - "fields": { - "device": 9, - "name": "Port 18" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 15, - "fields": { - "device": 9, - "name": "Port 19" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 16, - "fields": { - "device": 9, - "name": "Port 2" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 17, - "fields": { - "device": 9, - "name": "Port 20" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 18, - "fields": { - "device": 9, - "name": "Port 21" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 19, - "fields": { - "device": 9, - "name": "Port 22" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 20, - "fields": { - "device": 9, - "name": "Port 23" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 21, - "fields": { - "device": 9, - "name": "Port 24" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 22, - "fields": { - "device": 9, - "name": "Port 25" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 23, - "fields": { - "device": 9, - "name": "Port 26" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 24, - "fields": { - "device": 9, - "name": "Port 27" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 25, - "fields": { - "device": 9, - "name": "Port 28" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 26, - "fields": { - "device": 9, - "name": "Port 29" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 27, - "fields": { - "device": 9, - "name": "Port 3" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 28, - "fields": { - "device": 9, - "name": "Port 30" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 29, - "fields": { - "device": 9, - "name": "Port 31" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 30, - "fields": { - "device": 9, - "name": "Port 32" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 31, - "fields": { - "device": 9, - "name": "Port 33" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 32, - "fields": { - "device": 9, - "name": "Port 34" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 33, - "fields": { - "device": 9, - "name": "Port 35" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 34, - "fields": { - "device": 9, - "name": "Port 36" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 35, - "fields": { - "device": 9, - "name": "Port 37" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 36, - "fields": { - "device": 9, - "name": "Port 38" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 37, - "fields": { - "device": 9, - "name": "Port 39" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 38, - "fields": { - "device": 9, - "name": "Port 4" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 39, - "fields": { - "device": 9, - "name": "Port 40" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 40, - "fields": { - "device": 9, - "name": "Port 41" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 41, - "fields": { - "device": 9, - "name": "Port 42" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 42, - "fields": { - "device": 9, - "name": "Port 43" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 43, - "fields": { - "device": 9, - "name": "Port 44" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 44, - "fields": { - "device": 9, - "name": "Port 45" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 45, - "fields": { - "device": 9, - "name": "Port 46" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 46, - "fields": { - "device": 9, - "name": "Port 47" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 47, - "fields": { - "device": 9, - "name": "Port 48" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 48, - "fields": { - "device": 9, - "name": "Port 5" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 49, - "fields": { - "device": 9, - "name": "Port 6" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 50, - "fields": { - "device": 9, - "name": "Port 7" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 51, - "fields": { - "device": 9, - "name": "Port 8" - } -}, -{ - "model": "dcim.consoleserverport", - "pk": 52, - "fields": { - "device": 9, - "name": "Port 9" - } -}, -{ - "model": "dcim.powerport", - "pk": 1, - "fields": { - "device": 1, - "name": "PEM0", - "_connected_poweroutlet": 25, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 2, - "fields": { - "device": 1, - "name": "PEM1", - "_connected_poweroutlet": 49, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 3, - "fields": { - "device": 1, - "name": "PEM2", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 4, - "fields": { - "device": 1, - "name": "PEM3", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 5, - "fields": { - "device": 2, - "name": "PEM0", - "_connected_poweroutlet": 26, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 6, - "fields": { - "device": 2, - "name": "PEM1", - "_connected_poweroutlet": 50, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 7, - "fields": { - "device": 2, - "name": "PEM2", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 8, - "fields": { - "device": 2, - "name": "PEM3", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 9, - "fields": { - "device": 4, - "name": "PSU0", - "_connected_poweroutlet": 28, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 10, - "fields": { - "device": 4, - "name": "PSU1", - "_connected_poweroutlet": 52, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 11, - "fields": { - "device": 5, - "name": "PSU0", - "_connected_poweroutlet": 56, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 12, - "fields": { - "device": 5, - "name": "PSU1", - "_connected_poweroutlet": 32, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 13, - "fields": { - "device": 3, - "name": "PSU0", - "_connected_poweroutlet": 27, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 14, - "fields": { - "device": 3, - "name": "PSU1", - "_connected_poweroutlet": 51, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 15, - "fields": { - "device": 7, - "name": "PEM0", - "_connected_poweroutlet": 53, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 16, - "fields": { - "device": 7, - "name": "PEM1", - "_connected_poweroutlet": 29, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 17, - "fields": { - "device": 7, - "name": "PEM2", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 18, - "fields": { - "device": 7, - "name": "PEM3", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 19, - "fields": { - "device": 8, - "name": "PEM0", - "_connected_poweroutlet": 54, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 20, - "fields": { - "device": 8, - "name": "PEM1", - "_connected_poweroutlet": 30, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 21, - "fields": { - "device": 8, - "name": "PEM2", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 22, - "fields": { - "device": 8, - "name": "PEM3", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 23, - "fields": { - "device": 6, - "name": "PSU0", - "_connected_poweroutlet": 55, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 24, - "fields": { - "device": 6, - "name": "PSU1", - "_connected_poweroutlet": 31, - "connection_status": true - } -}, -{ - "model": "dcim.powerport", - "pk": 25, - "fields": { - "device": 9, - "name": "PSU", - "_connected_poweroutlet": null, - "connection_status": true - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 25, - "fields": { - "device": 11, - "name": "AA1" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 26, - "fields": { - "device": 11, - "name": "AA2" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 27, - "fields": { - "device": 11, - "name": "AA3" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 28, - "fields": { - "device": 11, - "name": "AA4" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 29, - "fields": { - "device": 11, - "name": "AA5" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 30, - "fields": { - "device": 11, - "name": "AA6" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 31, - "fields": { - "device": 11, - "name": "AA7" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 32, - "fields": { - "device": 11, - "name": "AA8" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 33, - "fields": { - "device": 11, - "name": "AB1" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 34, - "fields": { - "device": 11, - "name": "AB2" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 35, - "fields": { - "device": 11, - "name": "AB3" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 36, - "fields": { - "device": 11, - "name": "AB4" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 37, - "fields": { - "device": 11, - "name": "AB5" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 38, - "fields": { - "device": 11, - "name": "AB6" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 39, - "fields": { - "device": 11, - "name": "AB7" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 40, - "fields": { - "device": 11, - "name": "AB8" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 41, - "fields": { - "device": 11, - "name": "AC1" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 42, - "fields": { - "device": 11, - "name": "AC2" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 43, - "fields": { - "device": 11, - "name": "AC3" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 44, - "fields": { - "device": 11, - "name": "AC4" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 45, - "fields": { - "device": 11, - "name": "AC5" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 46, - "fields": { - "device": 11, - "name": "AC6" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 47, - "fields": { - "device": 11, - "name": "AC7" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 48, - "fields": { - "device": 11, - "name": "AC8" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 49, - "fields": { - "device": 12, - "name": "AA1" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 50, - "fields": { - "device": 12, - "name": "AA2" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 51, - "fields": { - "device": 12, - "name": "AA3" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 52, - "fields": { - "device": 12, - "name": "AA4" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 53, - "fields": { - "device": 12, - "name": "AA5" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 54, - "fields": { - "device": 12, - "name": "AA6" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 55, - "fields": { - "device": 12, - "name": "AA7" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 56, - "fields": { - "device": 12, - "name": "AA8" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 57, - "fields": { - "device": 12, - "name": "AB1" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 58, - "fields": { - "device": 12, - "name": "AB2" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 59, - "fields": { - "device": 12, - "name": "AB3" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 60, - "fields": { - "device": 12, - "name": "AB4" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 61, - "fields": { - "device": 12, - "name": "AB5" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 62, - "fields": { - "device": 12, - "name": "AB6" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 63, - "fields": { - "device": 12, - "name": "AB7" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 64, - "fields": { - "device": 12, - "name": "AB8" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 65, - "fields": { - "device": 12, - "name": "AC1" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 66, - "fields": { - "device": 12, - "name": "AC2" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 67, - "fields": { - "device": 12, - "name": "AC3" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 68, - "fields": { - "device": 12, - "name": "AC4" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 69, - "fields": { - "device": 12, - "name": "AC5" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 70, - "fields": { - "device": 12, - "name": "AC6" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 71, - "fields": { - "device": 12, - "name": "AC7" - } -}, -{ - "model": "dcim.poweroutlet", - "pk": 72, - "fields": { - "device": 12, - "name": "AC8" - } -}, -{ - "model": "dcim.interface", - "pk": 1, - "fields": { - "device": 1, - "name": "fxp0 (RE0)", - "type": 1000, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 2, - "fields": { - "device": 1, - "name": "fxp0 (RE1)", - "type": 800, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 3, - "fields": { - "device": 1, - "name": "lo0", - "type": 0, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 4, - "fields": { - "device": 1, - "name": "xe-0/0/0", - "type": 1200, - "mgmt_only": false, - "description": "TEST" - } -}, -{ - "model": "dcim.interface", - "pk": 5, - "fields": { - "device": 1, - "name": "xe-0/0/1", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 6, - "fields": { - "device": 1, - "name": "xe-0/0/2", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 7, - "fields": { - "device": 1, - "name": "xe-0/0/3", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 8, - "fields": { - "device": 1, - "name": "xe-0/0/4", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 9, - "fields": { - "device": 1, - "name": "xe-0/0/5", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 10, - "fields": { - "device": 2, - "name": "fxp0 (RE0)", - "type": 1000, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 11, - "fields": { - "device": 2, - "name": "fxp0 (RE1)", - "type": 1000, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 12, - "fields": { - "device": 2, - "name": "lo0", - "type": 0, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 13, - "fields": { - "device": 3, - "name": "em0", - "mac_address": "00-00-00-AA-BB-CC", - "type": 800, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 14, - "fields": { - "device": 3, - "name": "et-0/0/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 15, - "fields": { - "device": 3, - "name": "et-0/0/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 16, - "fields": { - "device": 3, - "name": "et-0/0/10", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 17, - "fields": { - "device": 3, - "name": "et-0/0/11", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 18, - "fields": { - "device": 3, - "name": "et-0/0/12", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 19, - "fields": { - "device": 3, - "name": "et-0/0/13", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 20, - "fields": { - "device": 3, - "name": "et-0/0/14", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 21, - "fields": { - "device": 3, - "name": "et-0/0/15", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 22, - "fields": { - "device": 3, - "name": "et-0/0/16", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 23, - "fields": { - "device": 3, - "name": "et-0/0/17", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 24, - "fields": { - "device": 3, - "name": "et-0/0/18", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 25, - "fields": { - "device": 3, - "name": "et-0/0/19", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 26, - "fields": { - "device": 3, - "name": "et-0/0/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 27, - "fields": { - "device": 3, - "name": "et-0/0/20", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 28, - "fields": { - "device": 3, - "name": "et-0/0/21", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 29, - "fields": { - "device": 3, - "name": "et-0/0/22", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 30, - "fields": { - "device": 3, - "name": "et-0/0/3", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 31, - "fields": { - "device": 3, - "name": "et-0/0/4", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 32, - "fields": { - "device": 3, - "name": "et-0/0/5", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 33, - "fields": { - "device": 3, - "name": "et-0/0/6", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 34, - "fields": { - "device": 3, - "name": "et-0/0/7", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 35, - "fields": { - "device": 3, - "name": "et-0/0/8", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 36, - "fields": { - "device": 3, - "name": "et-0/0/9", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 37, - "fields": { - "device": 3, - "name": "et-0/1/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 38, - "fields": { - "device": 3, - "name": "et-0/1/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 39, - "fields": { - "device": 3, - "name": "et-0/1/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 40, - "fields": { - "device": 3, - "name": "et-0/1/3", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 41, - "fields": { - "device": 3, - "name": "et-0/2/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 42, - "fields": { - "device": 3, - "name": "et-0/2/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 43, - "fields": { - "device": 3, - "name": "et-0/2/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 44, - "fields": { - "device": 3, - "name": "et-0/2/3", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 45, - "fields": { - "device": 4, - "name": "em0", - "type": 1000, - "mac_address": "ff-ee-dd-33-22-11", - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 46, - "fields": { - "device": 4, - "name": "et-0/0/48", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 47, - "fields": { - "device": 4, - "name": "et-0/0/49", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 48, - "fields": { - "device": 4, - "name": "et-0/0/50", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 49, - "fields": { - "device": 4, - "name": "et-0/0/51", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 50, - "fields": { - "device": 4, - "name": "xe-0/0/0", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 51, - "fields": { - "device": 4, - "name": "xe-0/0/1", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 52, - "fields": { - "device": 4, - "name": "xe-0/0/10", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 53, - "fields": { - "device": 4, - "name": "xe-0/0/11", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 54, - "fields": { - "device": 4, - "name": "xe-0/0/12", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 55, - "fields": { - "device": 4, - "name": "xe-0/0/13", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 56, - "fields": { - "device": 4, - "name": "xe-0/0/14", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 57, - "fields": { - "device": 4, - "name": "xe-0/0/15", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 58, - "fields": { - "device": 4, - "name": "xe-0/0/16", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 59, - "fields": { - "device": 4, - "name": "xe-0/0/17", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 60, - "fields": { - "device": 4, - "name": "xe-0/0/18", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 61, - "fields": { - "device": 4, - "name": "xe-0/0/19", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 62, - "fields": { - "device": 4, - "name": "xe-0/0/2", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 63, - "fields": { - "device": 4, - "name": "xe-0/0/20", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 64, - "fields": { - "device": 4, - "name": "xe-0/0/21", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 65, - "fields": { - "device": 4, - "name": "xe-0/0/22", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 66, - "fields": { - "device": 4, - "name": "xe-0/0/23", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 67, - "fields": { - "device": 4, - "name": "xe-0/0/24", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 68, - "fields": { - "device": 4, - "name": "xe-0/0/25", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 69, - "fields": { - "device": 4, - "name": "xe-0/0/26", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 70, - "fields": { - "device": 4, - "name": "xe-0/0/27", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 71, - "fields": { - "device": 4, - "name": "xe-0/0/28", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 72, - "fields": { - "device": 4, - "name": "xe-0/0/29", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 73, - "fields": { - "device": 4, - "name": "xe-0/0/3", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 74, - "fields": { - "device": 4, - "name": "xe-0/0/30", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 75, - "fields": { - "device": 4, - "name": "xe-0/0/31", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 76, - "fields": { - "device": 4, - "name": "xe-0/0/32", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 77, - "fields": { - "device": 4, - "name": "xe-0/0/33", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 78, - "fields": { - "device": 4, - "name": "xe-0/0/34", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 79, - "fields": { - "device": 4, - "name": "xe-0/0/35", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 80, - "fields": { - "device": 4, - "name": "xe-0/0/36", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 81, - "fields": { - "device": 4, - "name": "xe-0/0/37", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 82, - "fields": { - "device": 4, - "name": "xe-0/0/38", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 83, - "fields": { - "device": 4, - "name": "xe-0/0/39", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 84, - "fields": { - "device": 4, - "name": "xe-0/0/4", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 85, - "fields": { - "device": 4, - "name": "xe-0/0/40", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 86, - "fields": { - "device": 4, - "name": "xe-0/0/41", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 87, - "fields": { - "device": 4, - "name": "xe-0/0/42", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 88, - "fields": { - "device": 4, - "name": "xe-0/0/43", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 89, - "fields": { - "device": 4, - "name": "xe-0/0/44", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 90, - "fields": { - "device": 4, - "name": "xe-0/0/45", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 91, - "fields": { - "device": 4, - "name": "xe-0/0/46", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 92, - "fields": { - "device": 4, - "name": "xe-0/0/47", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 93, - "fields": { - "device": 4, - "name": "xe-0/0/5", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 94, - "fields": { - "device": 4, - "name": "xe-0/0/6", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 95, - "fields": { - "device": 4, - "name": "xe-0/0/7", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 96, - "fields": { - "device": 4, - "name": "xe-0/0/8", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 97, - "fields": { - "device": 4, - "name": "xe-0/0/9", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 98, - "fields": { - "device": 5, - "name": "em0", - "type": 1000, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 99, - "fields": { - "device": 5, - "name": "et-0/0/48", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 100, - "fields": { - "device": 5, - "name": "et-0/0/49", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 101, - "fields": { - "device": 5, - "name": "et-0/0/50", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 102, - "fields": { - "device": 5, - "name": "et-0/0/51", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 103, - "fields": { - "device": 5, - "name": "xe-0/0/0", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 104, - "fields": { - "device": 5, - "name": "xe-0/0/1", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 105, - "fields": { - "device": 5, - "name": "xe-0/0/10", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 106, - "fields": { - "device": 5, - "name": "xe-0/0/11", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 107, - "fields": { - "device": 5, - "name": "xe-0/0/12", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 108, - "fields": { - "device": 5, - "name": "xe-0/0/13", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 109, - "fields": { - "device": 5, - "name": "xe-0/0/14", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 110, - "fields": { - "device": 5, - "name": "xe-0/0/15", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 111, - "fields": { - "device": 5, - "name": "xe-0/0/16", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 112, - "fields": { - "device": 5, - "name": "xe-0/0/17", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 113, - "fields": { - "device": 5, - "name": "xe-0/0/18", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 114, - "fields": { - "device": 5, - "name": "xe-0/0/19", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 115, - "fields": { - "device": 5, - "name": "xe-0/0/2", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 116, - "fields": { - "device": 5, - "name": "xe-0/0/20", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 117, - "fields": { - "device": 5, - "name": "xe-0/0/21", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 118, - "fields": { - "device": 5, - "name": "xe-0/0/22", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 119, - "fields": { - "device": 5, - "name": "xe-0/0/23", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 120, - "fields": { - "device": 5, - "name": "xe-0/0/24", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 121, - "fields": { - "device": 5, - "name": "xe-0/0/25", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 122, - "fields": { - "device": 5, - "name": "xe-0/0/26", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 123, - "fields": { - "device": 5, - "name": "xe-0/0/27", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 124, - "fields": { - "device": 5, - "name": "xe-0/0/28", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 125, - "fields": { - "device": 5, - "name": "xe-0/0/29", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 126, - "fields": { - "device": 5, - "name": "xe-0/0/3", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 127, - "fields": { - "device": 5, - "name": "xe-0/0/30", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 128, - "fields": { - "device": 5, - "name": "xe-0/0/31", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 129, - "fields": { - "device": 5, - "name": "xe-0/0/32", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 130, - "fields": { - "device": 5, - "name": "xe-0/0/33", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 131, - "fields": { - "device": 5, - "name": "xe-0/0/34", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 132, - "fields": { - "device": 5, - "name": "xe-0/0/35", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 133, - "fields": { - "device": 5, - "name": "xe-0/0/36", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 134, - "fields": { - "device": 5, - "name": "xe-0/0/37", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 135, - "fields": { - "device": 5, - "name": "xe-0/0/38", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 136, - "fields": { - "device": 5, - "name": "xe-0/0/39", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 137, - "fields": { - "device": 5, - "name": "xe-0/0/4", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 138, - "fields": { - "device": 5, - "name": "xe-0/0/40", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 139, - "fields": { - "device": 5, - "name": "xe-0/0/41", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 140, - "fields": { - "device": 5, - "name": "xe-0/0/42", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 141, - "fields": { - "device": 5, - "name": "xe-0/0/43", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 142, - "fields": { - "device": 5, - "name": "xe-0/0/44", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 143, - "fields": { - "device": 5, - "name": "xe-0/0/45", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 144, - "fields": { - "device": 5, - "name": "xe-0/0/46", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 145, - "fields": { - "device": 5, - "name": "xe-0/0/47", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 146, - "fields": { - "device": 5, - "name": "xe-0/0/5", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 147, - "fields": { - "device": 5, - "name": "xe-0/0/6", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 148, - "fields": { - "device": 5, - "name": "xe-0/0/7", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 149, - "fields": { - "device": 5, - "name": "xe-0/0/8", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 150, - "fields": { - "device": 5, - "name": "xe-0/0/9", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 151, - "fields": { - "device": 6, - "name": "em0", - "type": 800, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 152, - "fields": { - "device": 6, - "name": "et-0/0/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 153, - "fields": { - "device": 6, - "name": "et-0/0/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 154, - "fields": { - "device": 6, - "name": "et-0/0/10", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 155, - "fields": { - "device": 6, - "name": "et-0/0/11", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 156, - "fields": { - "device": 6, - "name": "et-0/0/12", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 157, - "fields": { - "device": 6, - "name": "et-0/0/13", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 158, - "fields": { - "device": 6, - "name": "et-0/0/14", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 159, - "fields": { - "device": 6, - "name": "et-0/0/15", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 160, - "fields": { - "device": 6, - "name": "et-0/0/16", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 161, - "fields": { - "device": 6, - "name": "et-0/0/17", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 162, - "fields": { - "device": 6, - "name": "et-0/0/18", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 163, - "fields": { - "device": 6, - "name": "et-0/0/19", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 164, - "fields": { - "device": 6, - "name": "et-0/0/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 165, - "fields": { - "device": 6, - "name": "et-0/0/20", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 166, - "fields": { - "device": 6, - "name": "et-0/0/21", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 167, - "fields": { - "device": 6, - "name": "et-0/0/22", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 168, - "fields": { - "device": 6, - "name": "et-0/0/3", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 169, - "fields": { - "device": 6, - "name": "et-0/0/4", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 170, - "fields": { - "device": 6, - "name": "et-0/0/5", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 171, - "fields": { - "device": 6, - "name": "et-0/0/6", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 172, - "fields": { - "device": 6, - "name": "et-0/0/7", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 173, - "fields": { - "device": 6, - "name": "et-0/0/8", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 174, - "fields": { - "device": 6, - "name": "et-0/0/9", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 175, - "fields": { - "device": 6, - "name": "et-0/1/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 176, - "fields": { - "device": 6, - "name": "et-0/1/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 177, - "fields": { - "device": 6, - "name": "et-0/1/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 178, - "fields": { - "device": 6, - "name": "et-0/1/3", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 179, - "fields": { - "device": 6, - "name": "et-0/2/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 180, - "fields": { - "device": 6, - "name": "et-0/2/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 181, - "fields": { - "device": 6, - "name": "et-0/2/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 182, - "fields": { - "device": 6, - "name": "et-0/2/3", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 183, - "fields": { - "device": 7, - "name": "fxp0 (RE0)", - "type": 1000, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 184, - "fields": { - "device": 7, - "name": "fxp0 (RE1)", - "type": 800, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 185, - "fields": { - "device": 7, - "name": "lo0", - "type": 0, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 186, - "fields": { - "device": 8, - "name": "fxp0 (RE0)", - "type": 1000, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 187, - "fields": { - "device": 8, - "name": "fxp0 (RE1)", - "type": 1000, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 188, - "fields": { - "device": 8, - "name": "lo0", - "type": 0, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 189, - "fields": { - "device": 2, - "name": "et-0/0/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 190, - "fields": { - "device": 2, - "name": "et-0/0/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 191, - "fields": { - "device": 2, - "name": "et-0/0/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 192, - "fields": { - "device": 2, - "name": "et-0/1/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 193, - "fields": { - "device": 2, - "name": "et-0/1/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 194, - "fields": { - "device": 2, - "name": "et-0/1/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 195, - "fields": { - "device": 8, - "name": "et-0/0/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 196, - "fields": { - "device": 8, - "name": "et-0/0/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 197, - "fields": { - "device": 8, - "name": "et-0/0/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 198, - "fields": { - "device": 8, - "name": "et-0/1/0", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 199, - "fields": { - "device": 8, - "name": "et-0/1/1", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 200, - "fields": { - "device": 8, - "name": "et-0/1/2", - "type": 1400, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 201, - "fields": { - "device": 2, - "name": "xe-0/0/0", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 202, - "fields": { - "device": 2, - "name": "xe-0/0/1", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 203, - "fields": { - "device": 2, - "name": "xe-0/0/2", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 204, - "fields": { - "device": 2, - "name": "xe-0/0/3", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 205, - "fields": { - "device": 2, - "name": "xe-0/0/4", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 206, - "fields": { - "device": 2, - "name": "xe-0/0/5", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 207, - "fields": { - "device": 8, - "name": "xe-0/0/0", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 208, - "fields": { - "device": 8, - "name": "xe-0/0/1", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 209, - "fields": { - "device": 8, - "name": "xe-0/0/2", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 210, - "fields": { - "device": 8, - "name": "xe-0/0/3", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 211, - "fields": { - "device": 8, - "name": "xe-0/0/4", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 212, - "fields": { - "device": 8, - "name": "xe-0/0/5", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 213, - "fields": { - "device": 7, - "name": "xe-0/0/0", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 214, - "fields": { - "device": 7, - "name": "xe-0/0/1", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 215, - "fields": { - "device": 7, - "name": "xe-0/0/2", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 216, - "fields": { - "device": 7, - "name": "xe-0/0/3", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 217, - "fields": { - "device": 7, - "name": "xe-0/0/4", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 218, - "fields": { - "device": 7, - "name": "xe-0/0/5", - "type": 1200, - "mgmt_only": false, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 219, - "fields": { - "device": 9, - "name": "eth0", - "type": 1000, - "mac_address": "44-55-66-77-88-99", - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 221, - "fields": { - "device": 11, - "name": "Net", - "type": 800, - "mgmt_only": true, - "description": "" - } -}, -{ - "model": "dcim.interface", - "pk": 222, - "fields": { - "device": 12, - "name": "Net", - "type": 800, - "mgmt_only": true, - "description": "" - } -} -] diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index cc18981c475..ca5d25389d7 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -5,7 +5,6 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.forms.array import SimpleArrayField from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Q from mptt.forms import TreeNodeChoiceField from netaddr import EUI from netaddr.core import AddrFormatError @@ -677,7 +676,8 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor widget=StaticSelect2() ) comments = CommentField( - widget=SmallTextarea + widget=SmallTextarea, + label='Comments' ) class Meta: @@ -1301,8 +1301,8 @@ class RearPortTemplateCreateForm(ComponentForm): widget=StaticSelect2(), ) positions = forms.IntegerField( - min_value=1, - max_value=64, + min_value=REARPORT_POSITIONS_MIN, + max_value=REARPORT_POSITIONS_MAX, initial=1, help_text='The number of front ports which may be mapped to each rear port' ) @@ -1642,6 +1642,16 @@ def __init__(self, *args, **kwargs): if instance and instance.cluster is not None: kwargs['initial']['cluster_group'] = instance.cluster.group + if 'device_type' in kwargs['initial'] and 'manufacturer' not in kwargs['initial']: + device_type_id = kwargs['initial']['device_type'] + manufacturer_id = DeviceType.objects.filter(pk=device_type_id).values_list('manufacturer__pk', flat=True).first() + kwargs['initial']['manufacturer'] = manufacturer_id + + if 'cluster' in kwargs['initial'] and 'cluster_group' not in kwargs['initial']: + cluster_id = kwargs['initial']['cluster'] + cluster_group_id = Cluster.objects.filter(pk=cluster_id).values_list('group__pk', flat=True).first() + kwargs['initial']['cluster_group'] = cluster_group_id + super().__init__(*args, **kwargs) if self.instance.pk: @@ -2123,8 +2133,8 @@ class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm): ) mtu = forms.IntegerField( required=False, - min_value=1, - max_value=32767, + min_value=INTERFACE_MTU_MIN, + max_value=INTERFACE_MTU_MAX, label='MTU' ) mgmt_only = forms.BooleanField( @@ -2610,8 +2620,8 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form): ) mtu = forms.IntegerField( required=False, - min_value=1, - max_value=32767, + min_value=INTERFACE_MTU_MIN, + max_value=INTERFACE_MTU_MAX, label='MTU' ) mac_address = forms.CharField( @@ -2739,7 +2749,7 @@ def clean_enabled(self): return self.cleaned_data['enabled'] -class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsForm, BulkEditForm): +class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput() @@ -2765,8 +2775,8 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo ) mtu = forms.IntegerField( required=False, - min_value=1, - max_value=32767, + min_value=INTERFACE_MTU_MIN, + max_value=INTERFACE_MTU_MAX, label='MTU' ) mgmt_only = forms.NullBooleanField( @@ -2820,6 +2830,18 @@ def __init__(self, *args, **kwargs): else: 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): pk = forms.ModelMultipleChoiceField( @@ -3045,8 +3067,8 @@ class RearPortCreateForm(ComponentForm): widget=StaticSelect2(), ) positions = forms.IntegerField( - min_value=1, - max_value=64, + min_value=REARPORT_POSITIONS_MIN, + max_value=REARPORT_POSITIONS_MAX, initial=1, help_text='The number of front ports which may be mapped to each rear port' ) @@ -3168,6 +3190,11 @@ class Meta: 'termination_b_site', 'termination_b_rack', 'termination_b_device', 'termination_b_id', 'type', 'status', 'label', 'color', 'length', 'length_unit', ] + widgets = { + 'status': StaticSelect2, + 'type': StaticSelect2, + 'length_unit': StaticSelect2, + } class ConnectCableToConsolePortForm(ConnectCableToDeviceForm): @@ -3363,6 +3390,11 @@ class Meta: fields = [ 'type', 'status', 'label', 'color', 'length', 'length_unit', ] + widgets = { + 'status': StaticSelect2, + 'type': StaticSelect2, + 'length_unit': StaticSelect2, + } class CableCSVForm(forms.ModelForm): @@ -3513,7 +3545,7 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm): required=False ) color = forms.CharField( - max_length=6, + max_length=6, # RGB color code required=False, widget=ColorSelect() ) @@ -3592,7 +3624,7 @@ class CableFilterForm(BootstrapMixin, forms.Form): widget=StaticSelect2() ) color = forms.CharField( - max_length=6, + max_length=6, # RGB color code required=False, widget=ColorSelect() ) @@ -4387,8 +4419,9 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd max_utilization = forms.IntegerField( required=False ) - comments = forms.CharField( - required=False + comments = CommentField( + widget=SmallTextarea, + label='Comments' ) class Meta: diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index d1b596c2284..3503307578d 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -405,7 +405,7 @@ def _draw_device_front(drawing, device, start, end, text): @staticmethod 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( device.device_role, device.device_type.display_name, device.device_type.u_height, device.asset_tag or '', device.serial or '' @@ -414,7 +414,7 @@ def _draw_device_rear(drawing, device, start, end, text): drawing.add(drawing.text(str(device), insert=text)) @staticmethod - def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_): + def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation): link = drawing.add( drawing.a( href='{}?{}'.format( @@ -424,6 +424,10 @@ def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_): target='_top' ) ) + if reservation: + link.set_desc('{} — {} · {}'.format( + reservation.description, reservation.user, reservation.created + )) link.add(drawing.rect(start, end, class_=class_)) link.add(drawing.text("add device", insert=text, class_='add-device')) @@ -453,12 +457,13 @@ def _draw_elevations(self, elevation, reserved_units, face, unit_width, unit_hei else: # Draw shallow devices, reservations, or empty units class_ = 'slot' + reservation = reserved_units.get(unit["id"]) if device: class_ += ' occupied' - if unit["id"] in reserved_units: + if reservation: class_ += ' reserved' self._draw_empty( - drawing, self, start_cordinates, end_cordinates, text_cordinates, unit["id"], face, class_ + drawing, self, start_cordinates, end_cordinates, text_cordinates, unit["id"], face, class_, reservation ) unit_cursor += height @@ -483,7 +488,12 @@ def merge_elevations(self, face): return elevation - def get_elevation_svg(self, face=DeviceFaceChoices.FACE_FRONT, unit_width=230, unit_height=20): + def get_elevation_svg( + self, + face=DeviceFaceChoices.FACE_FRONT, + unit_width=RACK_ELEVATION_UNIT_WIDTH_DEFAULT, + unit_height=RACK_ELEVATION_UNIT_HEIGHT_DEFAULT + ): """ Return an SVG of the rack elevation @@ -493,7 +503,7 @@ def get_elevation_svg(self, face=DeviceFaceChoices.FACE_FRONT, unit_width=230, u height of the elevation """ elevation = self.merge_elevations(face) - reserved_units = self.get_reserved_units().keys() + reserved_units = self.get_reserved_units() return self._draw_elevations(elevation, reserved_units, face, unit_width, unit_height) @@ -569,7 +579,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel, RackElevationHelperMixin): help_text='Rail-to-rail width' ) u_height = models.PositiveSmallIntegerField( - default=42, + default=RACK_U_HEIGHT_DEFAULT, verbose_name='Height (U)', validators=[MinValueValidator(1), MaxValueValidator(100)] ) @@ -1445,10 +1455,11 @@ def validate_unique(self, exclude=None): # Check for a duplicate name on a device assigned to the same Site and no Tenant. This is necessary # because Django does not consider two NULL fields to be equal, and thus will not trigger a violation # of the uniqueness constraint without manual intervention. - if self.tenant is None and Device.objects.exclude(pk=self.pk).filter(name=self.name, tenant__isnull=True): - raise ValidationError({ - 'name': 'A device with this name already exists.' - }) + if self.name and self.tenant is None: + if Device.objects.exclude(pk=self.pk).filter(name=self.name, tenant__isnull=True): + raise ValidationError({ + 'name': 'A device with this name already exists.' + }) super().validate_unique(exclude) @@ -1858,15 +1869,15 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel): ) voltage = models.PositiveSmallIntegerField( validators=[MinValueValidator(1)], - default=120 + default=POWERFEED_VOLTAGE_DEFAULT ) amperage = models.PositiveSmallIntegerField( validators=[MinValueValidator(1)], - default=20 + default=POWERFEED_AMPERAGE_DEFAULT ) max_utilization = models.PositiveSmallIntegerField( validators=[MinValueValidator(1), MaxValueValidator(100)], - default=80, + default=POWERFEED_MAX_UTILIZATION_DEFAULT, help_text="Maximum permissible draw (percentage)" ) available_power = models.PositiveIntegerField( diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index a515df13ce2..a3a072bc9b4 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -4,6 +4,7 @@ from rest_framework import status from circuits.models import Circuit, CircuitTermination, CircuitType, Provider +from dcim.api import serializers from dcim.choices import * from dcim.constants import * from dcim.models import ( @@ -595,6 +596,21 @@ def test_get_rack_units(self): self.assertEqual(response.data['count'], 42) + def test_get_rack_elevation(self): + + url = reverse('dcim-api:rack-elevation', kwargs={'pk': self.rack1.pk}) + response = self.client.get(url, **self.header) + + self.assertEqual(response.data['count'], 42) + + def test_get_rack_elevation_svg(self): + + url = '{}?render=svg'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': self.rack1.pk})) + response = self.client.get(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(response.get('Content-Type'), 'image/svg+xml') + def test_list_racks(self): url = reverse('dcim-api:rack-list') @@ -1900,6 +1916,31 @@ def test_get_device(self): self.assertEqual(response.data['device_role']['id'], self.devicerole1.pk) self.assertEqual(response.data['cluster']['id'], self.cluster1.pk) + def test_get_device_graphs(self): + + device_ct = ContentType.objects.get_for_model(Device) + self.graph1 = Graph.objects.create( + type=device_ct, + name='Test Graph 1', + source='http://example.com/graphs.py?device={{ obj.name }}&foo=1' + ) + self.graph2 = Graph.objects.create( + type=device_ct, + name='Test Graph 2', + source='http://example.com/graphs.py?device={{ obj.name }}&foo=2' + ) + self.graph3 = Graph.objects.create( + type=device_ct, + name='Test Graph 3', + source='http://example.com/graphs.py?device={{ obj.name }}&foo=3' + ) + + url = reverse('dcim-api:device-graphs', kwargs={'pk': self.device1.pk}) + response = self.client.get(url, **self.header) + + self.assertEqual(len(response.data), 3) + self.assertEqual(response.data[0]['embed_url'], 'http://example.com/graphs.py?device=Test Device 1&foo=1') + def test_list_devices(self): url = reverse('dcim-api:device-list') @@ -2134,6 +2175,31 @@ def test_delete_consoleport(self): self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertEqual(ConsolePort.objects.count(), 2) + def test_trace_consoleport(self): + + peer_device = Device.objects.create( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + device_role=DeviceRole.objects.first(), + name='Peer Device' + ) + console_server_port = ConsoleServerPort.objects.create( + device=peer_device, + name='Console Server Port 1' + ) + cable = Cable(termination_a=self.consoleport1, termination_b=console_server_port, label='Cable 1') + cable.save() + + url = reverse('dcim-api:consoleport-trace', kwargs={'pk': self.consoleport1.pk}) + response = self.client.get(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + segment1 = response.data[0] + self.assertEqual(segment1[0]['name'], self.consoleport1.name) + self.assertEqual(segment1[1]['label'], cable.label) + self.assertEqual(segment1[2]['name'], console_server_port.name) + class ConsoleServerPortTest(APITestCase): @@ -2245,6 +2311,31 @@ def test_delete_consoleserverport(self): self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertEqual(ConsoleServerPort.objects.count(), 2) + def test_trace_consoleserverport(self): + + peer_device = Device.objects.create( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + device_role=DeviceRole.objects.first(), + name='Peer Device' + ) + console_port = ConsolePort.objects.create( + device=peer_device, + name='Console Port 1' + ) + cable = Cable(termination_a=self.consoleserverport1, termination_b=console_port, label='Cable 1') + cable.save() + + url = reverse('dcim-api:consoleserverport-trace', kwargs={'pk': self.consoleserverport1.pk}) + response = self.client.get(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + segment1 = response.data[0] + self.assertEqual(segment1[0]['name'], self.consoleserverport1.name) + self.assertEqual(segment1[1]['label'], cable.label) + self.assertEqual(segment1[2]['name'], console_port.name) + class PowerPortTest(APITestCase): @@ -2358,6 +2449,31 @@ def test_delete_powerport(self): self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertEqual(PowerPort.objects.count(), 2) + def test_trace_powerport(self): + + peer_device = Device.objects.create( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + device_role=DeviceRole.objects.first(), + name='Peer Device' + ) + power_outlet = PowerOutlet.objects.create( + device=peer_device, + name='Power Outlet 1' + ) + cable = Cable(termination_a=self.powerport1, termination_b=power_outlet, label='Cable 1') + cable.save() + + url = reverse('dcim-api:powerport-trace', kwargs={'pk': self.powerport1.pk}) + response = self.client.get(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + segment1 = response.data[0] + self.assertEqual(segment1[0]['name'], self.powerport1.name) + self.assertEqual(segment1[1]['label'], cable.label) + self.assertEqual(segment1[2]['name'], power_outlet.name) + class PowerOutletTest(APITestCase): @@ -2469,6 +2585,31 @@ def test_delete_poweroutlet(self): self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertEqual(PowerOutlet.objects.count(), 2) + def test_trace_poweroutlet(self): + + peer_device = Device.objects.create( + site=Site.objects.first(), + device_type=DeviceType.objects.first(), + device_role=DeviceRole.objects.first(), + name='Peer Device' + ) + power_port = PowerPort.objects.create( + device=peer_device, + name='Power Port 1' + ) + cable = Cable(termination_a=self.poweroutlet1, termination_b=power_port, label='Cable 1') + cable.save() + + url = reverse('dcim-api:poweroutlet-trace', kwargs={'pk': self.poweroutlet1.pk}) + response = self.client.get(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + segment1 = response.data[0] + self.assertEqual(segment1[0]['name'], self.poweroutlet1.name) + self.assertEqual(segment1[1]['label'], cable.label) + self.assertEqual(segment1[2]['name'], power_port.name) + class InterfaceTest(APITestCase): @@ -2673,6 +2814,262 @@ def test_delete_interface(self): self.assertEqual(Interface.objects.count(), 2) +class FrontPortTest(APITestCase): + + def setUp(self): + + super().setUp() + + site = Site.objects.create(name='Test Site 1', slug='test-site-1') + manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') + devicetype = DeviceType.objects.create( + manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1' + ) + devicerole = DeviceRole.objects.create( + name='Test Device Role 1', slug='test-device-role-1', color='ff0000' + ) + self.device = Device.objects.create( + device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site + ) + rear_ports = RearPort.objects.bulk_create(( + RearPort(device=self.device, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C), + RearPort(device=self.device, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C), + RearPort(device=self.device, name='Rear Port 3', type=PortTypeChoices.TYPE_8P8C), + RearPort(device=self.device, name='Rear Port 4', type=PortTypeChoices.TYPE_8P8C), + RearPort(device=self.device, name='Rear Port 5', type=PortTypeChoices.TYPE_8P8C), + RearPort(device=self.device, name='Rear Port 6', type=PortTypeChoices.TYPE_8P8C), + )) + self.frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]) + self.frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]) + self.frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[2]) + + def test_get_frontport(self): + + url = reverse('dcim-api:frontport-detail', kwargs={'pk': self.frontport1.pk}) + response = self.client.get(url, **self.header) + + self.assertEqual(response.data['name'], self.frontport1.name) + + def test_list_frontports(self): + + url = reverse('dcim-api:frontport-list') + response = self.client.get(url, **self.header) + + self.assertEqual(response.data['count'], 3) + + def test_list_frontports_brief(self): + + url = reverse('dcim-api:frontport-list') + response = self.client.get('{}?brief=1'.format(url), **self.header) + + self.assertEqual( + sorted(response.data['results'][0]), + ['cable', 'device', 'id', 'name', 'url'] + ) + + def test_create_frontport(self): + + rear_port = RearPort.objects.get(name='Rear Port 4') + data = { + 'device': self.device.pk, + 'name': 'Front Port 4', + 'type': PortTypeChoices.TYPE_8P8C, + 'rear_port': rear_port.pk, + 'rear_port_position': 1, + } + + url = reverse('dcim-api:frontport-list') + response = self.client.post(url, data, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_201_CREATED) + self.assertEqual(FrontPort.objects.count(), 4) + frontport4 = FrontPort.objects.get(pk=response.data['id']) + self.assertEqual(frontport4.device_id, data['device']) + self.assertEqual(frontport4.name, data['name']) + + def test_create_frontport_bulk(self): + + rear_ports = RearPort.objects.filter(frontports__isnull=True) + data = [ + { + 'device': self.device.pk, + 'name': 'Front Port 4', + 'type': PortTypeChoices.TYPE_8P8C, + 'rear_port': rear_ports[0].pk, + 'rear_port_position': 1, + }, + { + 'device': self.device.pk, + 'name': 'Front Port 5', + 'type': PortTypeChoices.TYPE_8P8C, + 'rear_port': rear_ports[1].pk, + 'rear_port_position': 1, + }, + { + 'device': self.device.pk, + 'name': 'Front Port 6', + 'type': PortTypeChoices.TYPE_8P8C, + 'rear_port': rear_ports[2].pk, + 'rear_port_position': 1, + }, + ] + + url = reverse('dcim-api:frontport-list') + response = self.client.post(url, data, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_201_CREATED) + self.assertEqual(FrontPort.objects.count(), 6) + self.assertEqual(response.data[0]['name'], data[0]['name']) + self.assertEqual(response.data[1]['name'], data[1]['name']) + self.assertEqual(response.data[2]['name'], data[2]['name']) + + def test_update_frontport(self): + + rear_port = RearPort.objects.get(name='Rear Port 4') + data = { + 'device': self.device.pk, + 'name': 'Front Port X', + 'type': PortTypeChoices.TYPE_110_PUNCH, + 'rear_port': rear_port.pk, + 'rear_port_position': 1, + } + + url = reverse('dcim-api:frontport-detail', kwargs={'pk': self.frontport1.pk}) + response = self.client.put(url, data, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(FrontPort.objects.count(), 3) + frontport1 = FrontPort.objects.get(pk=response.data['id']) + self.assertEqual(frontport1.name, data['name']) + self.assertEqual(frontport1.type, data['type']) + self.assertEqual(frontport1.rear_port, rear_port) + + def test_delete_frontport(self): + + url = reverse('dcim-api:frontport-detail', kwargs={'pk': self.frontport1.pk}) + response = self.client.delete(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) + self.assertEqual(FrontPort.objects.count(), 2) + + +class RearPortTest(APITestCase): + + def setUp(self): + + super().setUp() + + site = Site.objects.create(name='Test Site 1', slug='test-site-1') + manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') + devicetype = DeviceType.objects.create( + manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1' + ) + devicerole = DeviceRole.objects.create( + name='Test Device Role 1', slug='test-device-role-1', color='ff0000' + ) + self.device = Device.objects.create( + device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site + ) + self.rearport1 = RearPort.objects.create(device=self.device, type=PortTypeChoices.TYPE_8P8C, name='Rear Port 1') + self.rearport3 = RearPort.objects.create(device=self.device, type=PortTypeChoices.TYPE_8P8C, name='Rear Port 2') + self.rearport1 = RearPort.objects.create(device=self.device, type=PortTypeChoices.TYPE_8P8C, name='Rear Port 3') + + def test_get_rearport(self): + + url = reverse('dcim-api:rearport-detail', kwargs={'pk': self.rearport1.pk}) + response = self.client.get(url, **self.header) + + self.assertEqual(response.data['name'], self.rearport1.name) + + def test_list_rearports(self): + + url = reverse('dcim-api:rearport-list') + response = self.client.get(url, **self.header) + + self.assertEqual(response.data['count'], 3) + + def test_list_rearports_brief(self): + + url = reverse('dcim-api:rearport-list') + response = self.client.get('{}?brief=1'.format(url), **self.header) + + self.assertEqual( + sorted(response.data['results'][0]), + ['cable', 'device', 'id', 'name', 'url'] + ) + + def test_create_rearport(self): + + data = { + 'device': self.device.pk, + 'name': 'Front Port 4', + 'type': PortTypeChoices.TYPE_8P8C, + } + + url = reverse('dcim-api:rearport-list') + response = self.client.post(url, data, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_201_CREATED) + self.assertEqual(RearPort.objects.count(), 4) + rearport4 = RearPort.objects.get(pk=response.data['id']) + self.assertEqual(rearport4.device_id, data['device']) + self.assertEqual(rearport4.name, data['name']) + + def test_create_rearport_bulk(self): + + data = [ + { + 'device': self.device.pk, + 'name': 'Rear Port 4', + 'type': PortTypeChoices.TYPE_8P8C, + }, + { + 'device': self.device.pk, + 'name': 'Rear Port 5', + 'type': PortTypeChoices.TYPE_8P8C, + }, + { + 'device': self.device.pk, + 'name': 'Rear Port 6', + 'type': PortTypeChoices.TYPE_8P8C, + }, + ] + + url = reverse('dcim-api:rearport-list') + response = self.client.post(url, data, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_201_CREATED) + self.assertEqual(RearPort.objects.count(), 6) + self.assertEqual(response.data[0]['name'], data[0]['name']) + self.assertEqual(response.data[1]['name'], data[1]['name']) + self.assertEqual(response.data[2]['name'], data[2]['name']) + + def test_update_rearport(self): + + data = { + 'device': self.device.pk, + 'name': 'Front Port X', + 'type': PortTypeChoices.TYPE_110_PUNCH + } + + url = reverse('dcim-api:rearport-detail', kwargs={'pk': self.rearport1.pk}) + response = self.client.put(url, data, format='json', **self.header) + + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(RearPort.objects.count(), 3) + rearport1 = RearPort.objects.get(pk=response.data['id']) + self.assertEqual(rearport1.name, data['name']) + self.assertEqual(rearport1.type, data['type']) + + def test_delete_rearport(self): + + url = reverse('dcim-api:rearport-detail', kwargs={'pk': self.rearport1.pk}) + response = self.client.delete(url, **self.header) + + self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) + self.assertEqual(RearPort.objects.count(), 2) + + class DeviceBayTest(APITestCase): def setUp(self): diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py index d7a94656849..29e741560db 100644 --- a/netbox/dcim/tests/test_forms.py +++ b/netbox/dcim/tests/test_forms.py @@ -2,6 +2,7 @@ from dcim.forms import * from dcim.models import * +from virtualization.models import Cluster, ClusterGroup, ClusterType def get_id(model, slug): @@ -10,71 +11,108 @@ def get_id(model, slug): class DeviceTestCase(TestCase): - fixtures = ['dcim', 'ipam'] + @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): - test = DeviceForm(data={ - 'name': 'test', - 'device_role': get_id(DeviceRole, 'leaf-switch'), + form = DeviceForm(data={ + 'name': 'New Device', + 'device_role': DeviceRole.objects.first().pk, 'tenant': None, - 'manufacturer': get_id(Manufacturer, 'juniper'), - 'device_type': get_id(DeviceType, 'qfx5100-48s'), - 'site': get_id(Site, 'test1'), - 'rack': '1', + 'manufacturer': Manufacturer.objects.first().pk, + 'device_type': DeviceType.objects.first().pk, + 'site': Site.objects.first().pk, + 'rack': Rack.objects.first().pk, 'face': DeviceFaceChoices.FACE_FRONT, - 'position': 41, - 'platform': get_id(Platform, 'juniper-junos'), + 'position': 2, + 'platform': Platform.objects.first().pk, 'status': DeviceStatusChoices.STATUS_ACTIVE, }) - self.assertTrue(test.is_valid(), test.fields['position'].choices) - self.assertTrue(test.save()) + self.assertTrue(form.is_valid()) + self.assertTrue(form.save()) def test_racked_device_occupied(self): - test = DeviceForm(data={ + form = DeviceForm(data={ 'name': 'test', - 'device_role': get_id(DeviceRole, 'leaf-switch'), + 'device_role': DeviceRole.objects.first().pk, 'tenant': None, - 'manufacturer': get_id(Manufacturer, 'juniper'), - 'device_type': get_id(DeviceType, 'qfx5100-48s'), - 'site': get_id(Site, 'test1'), - 'rack': '1', + 'manufacturer': Manufacturer.objects.first().pk, + 'device_type': DeviceType.objects.first().pk, + 'site': Site.objects.first().pk, + 'rack': Rack.objects.first().pk, 'face': DeviceFaceChoices.FACE_FRONT, 'position': 1, - 'platform': get_id(Platform, 'juniper-junos'), + 'platform': Platform.objects.first().pk, '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): - test = DeviceForm(data={ - 'name': 'test', - 'device_role': get_id(DeviceRole, 'pdu'), + form = DeviceForm(data={ + 'name': 'New Device', + 'device_role': DeviceRole.objects.first().pk, 'tenant': None, - 'manufacturer': get_id(Manufacturer, 'servertech'), - 'device_type': get_id(DeviceType, 'cwg-24vym415c9'), - 'site': get_id(Site, 'test1'), - 'rack': '1', - 'face': '', + 'manufacturer': Manufacturer.objects.first().pk, + 'device_type': DeviceType.objects.first().pk, + 'site': Site.objects.first().pk, + 'rack': None, + 'face': None, 'position': None, - 'platform': None, + 'platform': Platform.objects.first().pk, 'status': DeviceStatusChoices.STATUS_ACTIVE, }) - self.assertTrue(test.is_valid()) - self.assertTrue(test.save()) + self.assertTrue(form.is_valid()) + self.assertTrue(form.save()) - def test_non_racked_device_with_face(self): - test = DeviceForm(data={ - 'name': 'test', - 'device_role': get_id(DeviceRole, 'pdu'), + def test_non_racked_device_with_face_position(self): + form = DeviceForm(data={ + 'name': 'New Device', + 'device_role': DeviceRole.objects.first().pk, 'tenant': None, - 'manufacturer': get_id(Manufacturer, 'servertech'), - 'device_type': get_id(DeviceType, 'cwg-24vym415c9'), - 'site': get_id(Site, 'test1'), - 'rack': '1', + 'manufacturer': Manufacturer.objects.first().pk, + 'device_type': DeviceType.objects.first().pk, + 'site': Site.objects.first().pk, + 'rack': None, 'face': DeviceFaceChoices.FACE_REAR, - 'position': None, + 'position': 10, 'platform': None, 'status': DeviceStatusChoices.STATUS_ACTIVE, }) - self.assertTrue(test.is_valid()) - self.assertTrue(test.save()) + self.assertFalse(form.is_valid()) + self.assertIn('face', form.errors) + self.assertIn('position', form.errors) + + def test_initial_data_population(self): + device_type = DeviceType.objects.first() + cluster = Cluster.objects.first() + test = DeviceForm(initial={ + 'device_type': device_type.pk, + 'device_role': DeviceRole.objects.first().pk, + 'status': DeviceStatusChoices.STATUS_ACTIVE, + 'site': Site.objects.first().pk, + 'cluster': cluster.pk, + }) + + # Check that the initial value for the manufacturer is set automatically when assigning the device type + 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) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 7573d2cc451..32d864a5199 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -285,7 +285,28 @@ def test_device_creation(self): name='Device Bay 1' ) - def test_device_duplicate_name_per_site(self): + def test_multiple_unnamed_devices(self): + + device1 = Device( + site=self.site, + device_type=self.device_type, + device_role=self.device_role, + name='' + ) + device1.save() + + device2 = Device( + site=device1.site, + device_type=device1.device_type, + device_role=device1.device_role, + name='' + ) + device2.full_clean() + device2.save() + + self.assertEqual(Device.objects.filter(name='').count(), 2) + + def test_device_duplicate_names(self): device1 = Device( site=self.site, diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e41d44d95b4..fd3d09ab7f2 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -30,6 +30,7 @@ ) from virtualization.models import VirtualMachine from . import filters, forms, tables +from .choices import DeviceFaceChoices from .models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, @@ -376,16 +377,15 @@ def get(self, request): page = paginator.page(paginator.num_pages) # Determine rack face - if request.GET.get('face') == '1': - face_id = 1 - else: - face_id = 0 + rack_face = request.GET.get('face', DeviceFaceChoices.FACE_FRONT) + if rack_face not in DeviceFaceChoices.values(): + rack_face = DeviceFaceChoices.FACE_FRONT return render(request, 'dcim/rack_elevation_list.html', { 'paginator': paginator, 'page': page, 'total_count': total_count, - 'face_id': face_id, + 'rack_face': rack_face, 'filter_form': forms.RackElevationFilterForm(request.GET), }) @@ -1945,6 +1945,12 @@ def get(self, request, *args, **kwargs): # Parse initial data manually to avoid setting field values as lists initial_data = {k: request.GET[k] for k in request.GET} + # Set initial site and rack based on side A termination (if not already set) + if 'termination_b_site' not in initial_data: + initial_data['termination_b_site'] = getattr(self.obj.termination_a.parent, 'site', None) + if 'termination_b_rack' not in initial_data: + initial_data['termination_b_rack'] = getattr(self.obj.termination_a.parent, 'rack', None) + form = self.form_class(instance=self.obj, initial=initial_data) return render(request, self.template_name, { diff --git a/netbox/extras/fixtures/extras.json b/netbox/extras/fixtures/extras.json deleted file mode 100644 index 83b947cb24d..00000000000 --- a/netbox/extras/fixtures/extras.json +++ /dev/null @@ -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": "" - } -} -] \ No newline at end of file diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index fed003bedbf..bd7e864e1d8 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -14,10 +14,10 @@ from mptt.forms import TreeNodeChoiceField, TreeNodeMultipleChoiceField from mptt.models import MPTTModel -from ipam.formfields import IPFormField -from utilities.exceptions import AbortTransaction -from utilities.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator +from ipam.formfields import IPAddressFormField, IPNetworkFormField +from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator from .constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING +from utilities.exceptions import AbortTransaction from .forms import ScriptForm from .signals import purge_changelog @@ -27,6 +27,8 @@ 'ChoiceVar', 'FileVar', 'IntegerVar', + 'IPAddressVar', + 'IPAddressWithMaskVar', 'IPNetworkVar', 'MultiObjectVar', 'ObjectVar', @@ -48,15 +50,19 @@ class ScriptVariable: def __init__(self, label='', description='', default=None, required=True): - # Default field attributes - self.field_attrs = { - 'help_text': description, - 'required': required - } + # Initialize field attributes + if not hasattr(self, 'field_attrs'): + self.field_attrs = {} + if description: + self.field_attrs['help_text'] = description if label: self.field_attrs['label'] = label if default: self.field_attrs['initial'] = default + if required: + self.field_attrs['required'] = True + if 'validators' not in self.field_attrs: + self.field_attrs['validators'] = [] def as_field(self): """ @@ -196,17 +202,32 @@ class FileVar(ScriptVariable): form_field = forms.FileField +class IPAddressVar(ScriptVariable): + """ + An IPv4 or IPv6 address without a mask. + """ + form_field = IPAddressFormField + + +class IPAddressWithMaskVar(ScriptVariable): + """ + An IPv4 or IPv6 address with a mask. + """ + form_field = IPNetworkFormField + + class IPNetworkVar(ScriptVariable): """ An IPv4 or IPv6 prefix. """ - form_field = IPFormField + form_field = IPNetworkFormField + field_attrs = { + 'validators': [prefix_validator] + } def __init__(self, min_prefix_length=None, max_prefix_length=None, *args, **kwargs): super().__init__(*args, **kwargs) - self.field_attrs['validators'] = list() - # Optional minimum/maximum prefix lengths if min_prefix_length is not None: self.field_attrs['validators'].append( diff --git a/netbox/extras/tests/test_scripts.py b/netbox/extras/tests/test_scripts.py index 26e12772fc0..6237d1d952a 100644 --- a/netbox/extras/tests/test_scripts.py +++ b/netbox/extras/tests/test_scripts.py @@ -1,6 +1,6 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase -from netaddr import IPNetwork +from netaddr import IPAddress, IPNetwork from dcim.models import DeviceRole from extras.scripts import * @@ -186,6 +186,54 @@ class TestScript(Script): self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['var1'], testfile) + def test_ipaddressvar(self): + + class TestScript(Script): + + var1 = IPAddressVar() + + # Validate IP network enforcement + data = {'var1': '1.2.3'} + form = TestScript().as_form(data, None) + self.assertFalse(form.is_valid()) + self.assertIn('var1', form.errors) + + # Validate IP mask exclusion + data = {'var1': '192.0.2.0/24'} + form = TestScript().as_form(data, None) + self.assertFalse(form.is_valid()) + self.assertIn('var1', form.errors) + + # Validate valid data + data = {'var1': '192.0.2.1'} + form = TestScript().as_form(data, None) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['var1'], IPAddress(data['var1'])) + + def test_ipaddresswithmaskvar(self): + + class TestScript(Script): + + var1 = IPAddressWithMaskVar() + + # Validate IP network enforcement + data = {'var1': '1.2.3'} + form = TestScript().as_form(data, None) + self.assertFalse(form.is_valid()) + self.assertIn('var1', form.errors) + + # Validate IP mask requirement + data = {'var1': '192.0.2.0'} + form = TestScript().as_form(data, None) + self.assertFalse(form.is_valid()) + self.assertIn('var1', form.errors) + + # Validate valid data + data = {'var1': '192.0.2.0/24'} + form = TestScript().as_form(data, None) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['var1'], IPNetwork(data['var1'])) + def test_ipnetworkvar(self): class TestScript(Script): @@ -198,6 +246,12 @@ class TestScript(Script): self.assertFalse(form.is_valid()) self.assertIn('var1', form.errors) + # Validate host IP check + data = {'var1': '192.0.2.1/24'} + form = TestScript().as_form(data, None) + self.assertFalse(form.is_valid()) + self.assertIn('var1', form.errors) + # Validate valid data data = {'var1': '192.0.2.0/24'} form = TestScript().as_form(data, None) diff --git a/netbox/extras/tests/test_webhooks.py b/netbox/extras/tests/test_webhooks.py index 02698b7dde1..026a82bb85d 100644 --- a/netbox/extras/tests/test_webhooks.py +++ b/netbox/extras/tests/test_webhooks.py @@ -1,11 +1,19 @@ +import json +import uuid +from unittest.mock import patch + import django_rq from django.contrib.contenttypes.models import ContentType +from django.http import HttpResponse from django.urls import reverse +from requests import Session from rest_framework import status from dcim.models import Site from extras.choices import ObjectChangeActionChoices from extras.models import Webhook +from extras.webhooks import enqueue_webhooks, generate_signature +from extras.webhooks_worker import process_webhook from utilities.testing import APITestCase @@ -22,11 +30,13 @@ def setUp(self): def setUpTestData(cls): site_ct = ContentType.objects.get_for_model(Site) - PAYLOAD_URL = "http://localhost/" + DUMMY_URL = "http://localhost/" + DUMMY_SECRET = "LOOKATMEIMASECRETSTRING" + webhooks = Webhook.objects.bulk_create(( - Webhook(name='Site Create Webhook', type_create=True, payload_url=PAYLOAD_URL), - Webhook(name='Site Update Webhook', type_update=True, payload_url=PAYLOAD_URL), - Webhook(name='Site Delete Webhook', type_delete=True, payload_url=PAYLOAD_URL), + Webhook(name='Site Create Webhook', type_create=True, payload_url=DUMMY_URL, secret=DUMMY_SECRET, additional_headers={'X-Foo': 'Bar'}), + Webhook(name='Site Update Webhook', type_update=True, payload_url=DUMMY_URL, secret=DUMMY_SECRET), + Webhook(name='Site Delete Webhook', type_delete=True, payload_url=DUMMY_URL, secret=DUMMY_SECRET), )) for webhook in webhooks: webhook.obj_type.set([site_ct]) @@ -87,3 +97,47 @@ def test_enqueue_webhook_delete(self): self.assertEqual(job.args[1]['id'], site.pk) self.assertEqual(job.args[2], 'site') self.assertEqual(job.args[3], ObjectChangeActionChoices.ACTION_DELETE) + + def test_webhooks_worker(self): + + request_id = uuid.uuid4() + + def dummy_send(_, request): + """ + A dummy implementation of Session.send() to be used for testing. + Always returns a 200 HTTP response. + """ + webhook = Webhook.objects.get(type_create=True) + signature = generate_signature(request.body, webhook.secret) + + # Validate the outgoing request headers + self.assertEqual(request.headers['Content-Type'], webhook.http_content_type) + self.assertEqual(request.headers['X-Hook-Signature'], signature) + self.assertEqual(request.headers['X-Foo'], 'Bar') + + # Validate the outgoing request body + body = json.loads(request.body) + self.assertEqual(body['event'], 'created') + self.assertEqual(body['timestamp'], job.args[4]) + self.assertEqual(body['model'], 'site') + self.assertEqual(body['username'], 'testuser') + self.assertEqual(body['request_id'], str(request_id)) + self.assertEqual(body['data']['name'], 'Site 1') + + return HttpResponse() + + # Enqueue a webhook for processing + site = Site.objects.create(name='Site 1', slug='site-1') + enqueue_webhooks( + instance=site, + user=self.user, + request_id=request_id, + action=ObjectChangeActionChoices.ACTION_CREATE + ) + + # Retrieve the job from queue + job = self.queue.jobs[0] + + # Patch the Session object with our dummy_send() method, then process the webhook for sending + with patch.object(Session, 'send', dummy_send) as mock_send: + process_webhook(*job.args) diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index edc3ffcad0e..653fe7c7f2e 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -11,10 +11,10 @@ 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/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'), - path(r'tags//', views.TagView.as_view(), name='tag'), - path(r'tags//edit/', views.TagEditView.as_view(), name='tag_edit'), - path(r'tags//delete/', views.TagDeleteView.as_view(), name='tag_delete'), - path(r'tags//changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}), + path(r'tags//', views.TagView.as_view(), name='tag'), + path(r'tags//edit/', views.TagEditView.as_view(), name='tag_edit'), + path(r'tags//delete/', views.TagDeleteView.as_view(), name='tag_delete'), + path(r'tags//changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}), # Config contexts path(r'config-contexts/', views.ConfigContextListView.as_view(), name='configcontext_list'), diff --git a/netbox/extras/webhooks.py b/netbox/extras/webhooks.py index 5017582cc19..cfa05d0f604 100644 --- a/netbox/extras/webhooks.py +++ b/netbox/extras/webhooks.py @@ -1,6 +1,9 @@ import datetime +import hashlib +import hmac from django.contrib.contenttypes.models import ContentType +from django.utils import timezone from extras.models import Webhook from utilities.api import get_serializer_for_model @@ -8,6 +11,18 @@ from .constants import * +def generate_signature(request_body, secret): + """ + Return a cryptographic signature that can be used to verify the authenticity of webhook data. + """ + hmac_prep = hmac.new( + key=secret.encode('utf8'), + msg=request_body.encode('utf8'), + digestmod=hashlib.sha512 + ) + return hmac_prep.hexdigest() + + def enqueue_webhooks(instance, user, request_id, action): """ Find Webhook(s) assigned to this instance + action and enqueue them @@ -48,7 +63,7 @@ def enqueue_webhooks(instance, user, request_id, action): serializer.data, instance._meta.model_name, action, - str(datetime.datetime.now()), + str(timezone.now()), user.username, request_id ) diff --git a/netbox/extras/webhooks_worker.py b/netbox/extras/webhooks_worker.py index 6f7ede4e46f..e48d8a2d701 100644 --- a/netbox/extras/webhooks_worker.py +++ b/netbox/extras/webhooks_worker.py @@ -1,5 +1,3 @@ -import hashlib -import hmac import json import requests @@ -7,6 +5,7 @@ from rest_framework.utils.encoders import JSONEncoder from .choices import ObjectChangeActionChoices, WebhookContentTypeChoices +from .webhooks import generate_signature @job('default') @@ -23,7 +22,7 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque 'data': data } headers = { - 'Content-Type': webhook.get_http_content_type_display(), + 'Content-Type': webhook.http_content_type, } if webhook.additional_headers: headers.update(webhook.additional_headers) @@ -43,12 +42,7 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque if webhook.secret != '': # Sign the request with a hash of the secret key and its content. - hmac_prep = hmac.new( - key=webhook.secret.encode('utf8'), - msg=prepared_request.body.encode('utf8'), - digestmod=hashlib.sha512 - ) - prepared_request.headers['X-Hook-Signature'] = hmac_prep.hexdigest() + prepared_request.headers['X-Hook-Signature'] = generate_signature(prepared_request.body, webhook.secret) with requests.Session() as session: session.verify = webhook.ssl_verification @@ -56,7 +50,7 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque session.verify = webhook.ca_file_path response = session.send(prepared_request) - if response.status_code >= 200 and response.status_code <= 299: + if 200 <= response.status_code <= 299: return 'Status {} returned, webhook successfully processed.'.format(response.status_code) else: raise requests.exceptions.RequestException( diff --git a/netbox/ipam/constants.py b/netbox/ipam/constants.py index cf6eb2a2a8e..41075e54abc 100644 --- a/netbox/ipam/constants.py +++ b/netbox/ipam/constants.py @@ -4,10 +4,34 @@ BGP_ASN_MIN = 1 BGP_ASN_MAX = 2**32 - 1 + +# +# VRFs +# + +# Per RFC 4364 section 4.2, a route distinguisher may be encoded as one of the following: +# * Type 0 (16-bit AS number : 32-bit integer) +# * Type 1 (32-bit IPv4 address : 16-bit integer) +# * Type 2 (32-bit AS number : 16-bit integer) +# 21 characters are sufficient to convey the longest possible string value (255.255.255.255:65535) +VRF_RD_MAX_LENGTH = 21 + + +# +# Prefixes +# + +PREFIX_LENGTH_MIN = 1 +PREFIX_LENGTH_MAX = 127 # IPv6 + + # -# IP addresses +# IPAddresses # +IPADDRESS_MASK_LENGTH_MIN = 1 +IPADDRESS_MASK_LENGTH_MAX = 128 # IPv6 + IPADDRESS_ROLES_NONUNIQUE = ( # IPAddress roles which are exempt from unique address enforcement IPAddressRoleChoices.ROLE_ANYCAST, @@ -17,3 +41,21 @@ IPAddressRoleChoices.ROLE_GLBP, IPAddressRoleChoices.ROLE_CARP, ) + + +# +# VLANs +# + +# 12-bit VLAN ID (values 0 and 4095 are reserved) +VLAN_VID_MIN = 1 +VLAN_VID_MAX = 4094 + + +# +# Services +# + +# 16-bit port number +SERVICE_PORT_MIN = 1 +SERVICE_PORT_MAX = 65535 diff --git a/netbox/ipam/fields.py b/netbox/ipam/fields.py index 72600d1b998..456a7debc42 100644 --- a/netbox/ipam/fields.py +++ b/netbox/ipam/fields.py @@ -2,13 +2,8 @@ from django.db import models from netaddr import AddrFormatError, IPNetwork -from . import lookups -from .formfields import IPFormField - - -def prefix_validator(prefix): - if prefix.ip != prefix.cidr.ip: - raise ValidationError("{} is not a valid prefix. Did you mean {}?".format(prefix, prefix.cidr)) +from . import lookups, validators +from .formfields import IPNetworkFormField class BaseIPField(models.Field): @@ -38,7 +33,7 @@ def get_prep_value(self, value): return str(self.to_python(value)) def form_class(self): - return IPFormField + return IPNetworkFormField def formfield(self, **kwargs): defaults = {'form_class': self.form_class()} @@ -51,7 +46,7 @@ class IPNetworkField(BaseIPField): IP prefix (network and mask) """ description = "PostgreSQL CIDR field" - default_validators = [prefix_validator] + default_validators = [validators.prefix_validator] def db_type(self, connection): return 'cidr' diff --git a/netbox/ipam/fixtures/ipam.json b/netbox/ipam/fixtures/ipam.json deleted file mode 100644 index e722b3629dc..00000000000 --- a/netbox/ipam/fixtures/ipam.json +++ /dev/null @@ -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 - } -} -] \ No newline at end of file diff --git a/netbox/ipam/formfields.py b/netbox/ipam/formfields.py index 2909a54b175..e8d171d7f95 100644 --- a/netbox/ipam/formfields.py +++ b/netbox/ipam/formfields.py @@ -1,13 +1,44 @@ from django import forms from django.core.exceptions import ValidationError -from netaddr import IPNetwork, AddrFormatError +from django.core.validators import validate_ipv4_address, validate_ipv6_address +from netaddr import IPAddress, IPNetwork, AddrFormatError # # Form fields # -class IPFormField(forms.Field): +class IPAddressFormField(forms.Field): + default_error_messages = { + 'invalid': "Enter a valid IPv4 or IPv6 address (without a mask).", + } + + def to_python(self, value): + if not value: + return None + + if isinstance(value, IPAddress): + return value + + # netaddr is a bit too liberal with what it accepts as a valid IP address. For example, '1.2.3' will become + # IPAddress('1.2.0.3'). Here, we employ Django's built-in IPv4 and IPv6 address validators as a sanity check. + try: + validate_ipv4_address(value) + except ValidationError: + try: + validate_ipv6_address(value) + except ValidationError: + raise ValidationError("Invalid IPv4/IPv6 address format: {}".format(value)) + + try: + return IPAddress(value) + except ValueError: + raise ValidationError('This field requires an IP address without a mask.') + except AddrFormatError: + raise ValidationError("Please specify a valid IPv4 or IPv6 address.") + + +class IPNetworkFormField(forms.Field): default_error_messages = { 'invalid': "Enter a valid IPv4 or IPv6 address (with CIDR mask).", } diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 265ddcb7bb9..183fcb71747 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -13,17 +13,18 @@ SlugField, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES ) from virtualization.models import VirtualMachine +from .constants import * from .choices import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF -IP_FAMILY_CHOICES = [ - ('', 'All'), - (4, 'IPv4'), - (6, 'IPv6'), -] -PREFIX_MASK_LENGTH_CHOICES = add_blank_choice([(i, i) for i in range(1, 128)]) -IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([(i, i) for i in range(1, 129)]) +PREFIX_MASK_LENGTH_CHOICES = add_blank_choice([ + (i, i) for i in range(PREFIX_LENGTH_MIN, PREFIX_LENGTH_MAX + 1) +]) + +IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([ + (i, i) for i in range(IPADDRESS_MASK_LENGTH_MIN, IPADDRESS_MASK_LENGTH_MAX + 1) +]) # @@ -218,7 +219,7 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm): ) family = forms.ChoiceField( required=False, - choices=IP_FAMILY_CHOICES, + choices=add_blank_choice(IPAddressFamilyChoices), label='Address family', widget=StaticSelect2() ) @@ -450,8 +451,8 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF ) ) prefix_length = forms.IntegerField( - min_value=1, - max_value=127, + min_value=PREFIX_LENGTH_MIN, + max_value=PREFIX_LENGTH_MAX, required=False ) tenant = forms.ModelChoiceField( @@ -510,7 +511,7 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) ) family = forms.ChoiceField( required=False, - choices=IP_FAMILY_CHOICES, + choices=add_blank_choice(IPAddressFamilyChoices), label='Address family', widget=StaticSelect2() ) @@ -634,6 +635,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( queryset=IPAddress.objects.all(), chains=( @@ -896,8 +908,8 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd ) ) mask_length = forms.IntegerField( - min_value=1, - max_value=128, + min_value=IPADDRESS_MASK_LENGTH_MIN, + max_value=IPADDRESS_MASK_LENGTH_MAX, required=False ) tenant = forms.ModelChoiceField( @@ -969,7 +981,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo ) family = forms.ChoiceField( required=False, - choices=IP_FAMILY_CHOICES, + choices=add_blank_choice(IPAddressFamilyChoices), label='Address family', widget=StaticSelect2() ) @@ -1300,8 +1312,8 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): class ServiceForm(BootstrapMixin, CustomFieldForm): port = forms.IntegerField( - min_value=1, - max_value=65535 + min_value=SERVICE_PORT_MIN, + max_value=SERVICE_PORT_MAX ) tags = TagField( required=False diff --git a/netbox/ipam/migrations/0029_3569_ipaddress_fields.py b/netbox/ipam/migrations/0029_3569_ipaddress_fields.py index 528efb4fb3b..195b630db7c 100644 --- a/netbox/ipam/migrations/0029_3569_ipaddress_fields.py +++ b/netbox/ipam/migrations/0029_3569_ipaddress_fields.py @@ -2,10 +2,10 @@ IPADDRESS_STATUS_CHOICES = ( - (0, 'container'), (1, 'active'), (2, 'reserved'), (3, 'deprecated'), + (5, 'dhcp'), ) IPADDRESS_ROLE_CHOICES = ( diff --git a/netbox/ipam/migrations/0034_fix_ipaddress_status_dhcp.py b/netbox/ipam/migrations/0034_fix_ipaddress_status_dhcp.py new file mode 100644 index 00000000000..9e496153efa --- /dev/null +++ b/netbox/ipam/migrations/0034_fix_ipaddress_status_dhcp.py @@ -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 + ), + ] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 885d5d6171c..b4ba92fb562 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -14,7 +14,7 @@ from utilities.utils import serialize_object from virtualization.models import VirtualMachine from .choices import * -from .constants import IPADDRESS_ROLES_NONUNIQUE +from .constants import * from .fields import IPNetworkField, IPAddressField from .managers import IPAddressManager from .querysets import PrefixQuerySet @@ -44,7 +44,7 @@ class VRF(ChangeLoggedModel, CustomFieldModel): max_length=50 ) rd = models.CharField( - max_length=21, + max_length=VRF_RD_MAX_LENGTH, unique=True, blank=True, null=True, @@ -1006,7 +1006,7 @@ class Service(ChangeLoggedModel, CustomFieldModel): choices=ServiceProtocolChoices ) port = models.PositiveIntegerField( - validators=[MinValueValidator(1), MaxValueValidator(65535)], + validators=[MinValueValidator(SERVICE_PORT_MIN), MaxValueValidator(SERVICE_PORT_MAX)], verbose_name='Port number' ) ipaddresses = models.ManyToManyField( diff --git a/netbox/ipam/validators.py b/netbox/ipam/validators.py index 96067564360..879e20e6a02 100644 --- a/netbox/ipam/validators.py +++ b/netbox/ipam/validators.py @@ -1,4 +1,26 @@ -from django.core.validators import RegexValidator +from django.core.exceptions import ValidationError +from django.core.validators import BaseValidator, RegexValidator + + +def prefix_validator(prefix): + if prefix.ip != prefix.cidr.ip: + raise ValidationError("{} is not a valid prefix. Did you mean {}?".format(prefix, prefix.cidr)) + + +class MaxPrefixLengthValidator(BaseValidator): + message = 'The prefix length must be less than or equal to %(limit_value)s.' + code = 'max_prefix_length' + + def compare(self, a, b): + return a.prefixlen > b + + +class MinPrefixLengthValidator(BaseValidator): + message = 'The prefix length must be greater than or equal to %(limit_value)s.' + code = 'min_prefix_length' + + def compare(self, a, b): + return a.prefixlen < b DNSValidator = RegexValidator( diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 5cbd55bf450..c8c7d40ca0e 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -15,6 +15,7 @@ from virtualization.models import VirtualMachine from . import filters, forms, tables from .choices import * +from .constants import * from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF @@ -86,23 +87,20 @@ def add_available_vlans(vlan_group, vlans): """ Create fake records for all gaps between used VLANs """ - MIN_VLAN = 1 - MAX_VLAN = 4094 - if not vlans: - return [{'vid': MIN_VLAN, 'available': MAX_VLAN - MIN_VLAN + 1}] + return [{'vid': VLAN_VID_MIN, 'available': VLAN_VID_MAX - VLAN_VID_MIN + 1}] - prev_vid = MAX_VLAN + prev_vid = VLAN_VID_MAX new_vlans = [] for vlan in vlans: if vlan.vid - prev_vid > 1: new_vlans.append({'vid': prev_vid + 1, 'available': vlan.vid - prev_vid - 1}) prev_vid = vlan.vid - if vlans[0].vid > MIN_VLAN: - new_vlans.append({'vid': MIN_VLAN, 'available': vlans[0].vid - MIN_VLAN}) - if prev_vid < MAX_VLAN: - new_vlans.append({'vid': prev_vid + 1, 'available': MAX_VLAN - prev_vid}) + if vlans[0].vid > VLAN_VID_MIN: + new_vlans.append({'vid': VLAN_VID_MIN, 'available': vlans[0].vid - VLAN_VID_MIN}) + if prev_vid < VLAN_VID_MAX: + new_vlans.append({'vid': prev_vid + 1, 'available': VLAN_VID_MAX - prev_vid}) vlans = list(vlans) + new_vlans vlans.sort(key=lambda v: v.vid if type(v) == VLAN else v['vid']) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index d558acc62c1..3a780f55a7f 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ # Environment setup # -VERSION = '2.7.2' +VERSION = '2.7.3' # Hostname HOSTNAME = platform.node() @@ -503,6 +503,7 @@ def _setting(name, default=None): 'utilities.custom_inspectors.IdInFilterInspector', 'drf_yasg.inspectors.CoreAPICompatInspector', ], + 'DEFAULT_INFO': 'netbox.urls.openapi_info', 'DEFAULT_MODEL_DEPTH': 1, 'DEFAULT_PAGINATOR_INSPECTORS': [ 'utilities.custom_inspectors.NullablePaginatorInspector', diff --git a/netbox/netbox/tests/__init__.py b/netbox/netbox/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/netbox/netbox/tests/test_api.py b/netbox/netbox/tests/test_api.py new file mode 100644 index 00000000000..0ee2d78dc1d --- /dev/null +++ b/netbox/netbox/tests/test_api.py @@ -0,0 +1,13 @@ +from django.urls import reverse + +from utilities.testing import APITestCase + + +class AppTest(APITestCase): + + def test_root(self): + + url = reverse('api-root') + response = self.client.get('{}?format=api'.format(url), **self.header) + + self.assertEqual(response.status_code, 200) diff --git a/netbox/netbox/tests/test_views.py b/netbox/netbox/tests/test_views.py new file mode 100644 index 00000000000..db84dcd1abb --- /dev/null +++ b/netbox/netbox/tests/test_views.py @@ -0,0 +1,24 @@ +import urllib.parse + +from django.test import TestCase +from django.urls import reverse + + +class HomeViewTestCase(TestCase): + + def test_home(self): + + url = reverse('home') + + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_search(self): + + url = reverse('search') + params = { + 'q': 'foo', + } + + response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params))) + self.assertEqual(response.status_code, 200) diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 6b6dfe22d83..66ab982eb16 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -9,14 +9,16 @@ from users.views import LoginView, LogoutView 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( - 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"), - ), + openapi_info, validators=['flex', 'ssv'], public=True, ) diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index 58783b5d0d6..b1ba8a37c95 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -158,14 +158,17 @@ $(document).ready(function() { filter_for_elements.each(function(index, filter_for_element) { var param_name = $(filter_for_element).attr(attr_name); + var is_required = $(filter_for_element).attr("required"); var is_nullable = $(filter_for_element).attr("nullable"); var is_visible = $(filter_for_element).is(":visible"); var value = $(filter_for_element).val(); - if (param_name && is_visible && value) { - parameters[param_name] = value; - } else if (param_name && is_visible && is_nullable) { - parameters[param_name] = "null"; + if (param_name && is_visible) { + if (value) { + parameters[param_name] = value; + } else if (is_required && is_nullable) { + parameters[param_name] = "null"; + } } }); diff --git a/netbox/project-static/js/interface_toggles.js b/netbox/project-static/js/interface_toggles.js index a46d3185cd0..df8ac064b71 100644 --- a/netbox/project-static/js/interface_toggles.js +++ b/netbox/project-static/js/interface_toggles.js @@ -2,9 +2,9 @@ $('button.toggle-ips').click(function() { var selected = $(this).attr('selected'); if (selected) { - $('#interfaces_table tr.ipaddresses').hide(); + $('#interfaces_table tr.interface:visible + tr.ipaddresses').hide(); } else { - $('#interfaces_table tr.ipaddresses').show(); + $('#interfaces_table tr.interface:visible + tr.ipaddresses').show(); } $(this).attr('selected', !selected); $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked'); @@ -14,17 +14,22 @@ $('button.toggle-ips').click(function() { // Inteface filtering $('input.interface-filter').on('input', function() { 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 - 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 $(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked')); $(interface).show(); + if ($('button.toggle-ips').attr('selected')) { + $(interface).next('tr.ipaddresses').show(); + } } else { // Uncheck to prevent actions from including it when it doesn't match $(interface).find('input:checkbox[name=pk]').prop('checked', false); $(interface).hide(); + $(interface).next('tr.ipaddresses').hide(); } } }); diff --git a/netbox/secrets/constants.py b/netbox/secrets/constants.py new file mode 100644 index 00000000000..a1c3cb3da4c --- /dev/null +++ b/netbox/secrets/constants.py @@ -0,0 +1,5 @@ +# +# Secrets +# + +SECRET_PLAINTEXT_MAX_LENGTH = 65535 diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index c937e6c9227..3b81f9586e5 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -9,6 +9,7 @@ APISelect, APISelectMultiple, BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField, StaticSelect2Multiple ) +from .constants import * from .models import Secret, SecretRole, UserKey @@ -69,7 +70,7 @@ class Meta: class SecretForm(BootstrapMixin, CustomFieldForm): plaintext = forms.CharField( - max_length=65535, + max_length=SECRET_PLAINTEXT_MAX_LENGTH, required=False, label='Plaintext', widget=forms.PasswordInput( @@ -79,7 +80,7 @@ class SecretForm(BootstrapMixin, CustomFieldForm): ) ) plaintext2 = forms.CharField( - max_length=65535, + max_length=SECRET_PLAINTEXT_MAX_LENGTH, required=False, label='Plaintext (verify)', widget=forms.PasswordInput() diff --git a/netbox/secrets/tests/test_form.py b/netbox/secrets/tests/test_form.py index 42111abbf56..d122358ccea 100644 --- a/netbox/secrets/tests/test_form.py +++ b/netbox/secrets/tests/test_form.py @@ -29,5 +29,4 @@ def test_upload_sshkey(self): data={'public_key': SSH_PUBLIC_KEY}, instance=self.userkey, ) - print(form.is_valid()) self.assertFalse(form.is_valid()) diff --git a/netbox/templates/dcim/cable_connect.html b/netbox/templates/dcim/cable_connect.html index b1609f5785e..aa4c4bf8c2b 100644 --- a/netbox/templates/dcim/cable_connect.html +++ b/netbox/templates/dcim/cable_connect.html @@ -144,25 +144,8 @@

{% block title %}Connect {{ termination_a.device }} {{ termination_a }} to {
-
-
-
Cable
-
- {% render_field form.status %} - {% render_field form.type %} - {% render_field form.label %} - {% render_field form.color %} -
- -
- {{ form.length }} -
-
- {{ form.length_unit }} -
-
-
-
+
+ {% include 'dcim/inc/cable_form.html' %}
diff --git a/netbox/templates/dcim/cable_edit.html b/netbox/templates/dcim/cable_edit.html index 17403a07d41..685b68206fc 100644 --- a/netbox/templates/dcim/cable_edit.html +++ b/netbox/templates/dcim/cable_edit.html @@ -1,23 +1,5 @@ {% extends 'utilities/obj_edit.html' %} -{% load form_helpers %} {% block form %} -
-
Cable
-
- {% render_field form.type %} - {% render_field form.status %} - {% render_field form.label %} - {% render_field form.color %} -
- -
- {{ form.length }} -
-
- {{ form.length_unit }} -
-
-
-
+ {% include 'dcim/inc/cable_form.html' %} {% endblock %} diff --git a/netbox/templates/dcim/cable_trace.html b/netbox/templates/dcim/cable_trace.html index 4dd14505884..8c7b69f2641 100644 --- a/netbox/templates/dcim/cable_trace.html +++ b/netbox/templates/dcim/cable_trace.html @@ -32,7 +32,7 @@

{% if cable.label %}{{ cable.label }}{% else %}Cable #{{ cable.pk }}{% endif %}

-

{{ cable.get_status_display }}

+

{{ cable.get_status_display }}

{{ cable.get_type_display|default:"" }}

{% if cable.length %}{{ cable.length }} {{ cable.get_length_unit_display }}{% endif %} {% if cable.color %} diff --git a/netbox/templates/dcim/inc/cable_form.html b/netbox/templates/dcim/inc/cable_form.html new file mode 100644 index 00000000000..0799eb130cb --- /dev/null +++ b/netbox/templates/dcim/inc/cable_form.html @@ -0,0 +1,19 @@ +{% load form_helpers %} +
+
Cable
+
+ {% render_field form.status %} + {% render_field form.type %} + {% render_field form.label %} + {% render_field form.color %} +
+ +
+ {{ form.length }} +
+
+ {{ form.length_unit }} +
+
+
+
diff --git a/netbox/templates/dcim/inc/cable_toggle_buttons.html b/netbox/templates/dcim/inc/cable_toggle_buttons.html index 3e0209e01ec..507aab3beb2 100644 --- a/netbox/templates/dcim/inc/cable_toggle_buttons.html +++ b/netbox/templates/dcim/inc/cable_toggle_buttons.html @@ -1,5 +1,5 @@ {% if perms.dcim.change_cable %} - {% if cable.status %} + {% if cable.status == 'connected' %} diff --git a/netbox/templates/dcim/inc/consoleport.html b/netbox/templates/dcim/inc/consoleport.html index f9fc40fee30..9089f19b454 100644 --- a/netbox/templates/dcim/inc/consoleport.html +++ b/netbox/templates/dcim/inc/consoleport.html @@ -1,4 +1,4 @@ - + {# Name #} diff --git a/netbox/templates/dcim/inc/consoleserverport.html b/netbox/templates/dcim/inc/consoleserverport.html index f5b19ed75e0..0d649f81246 100644 --- a/netbox/templates/dcim/inc/consoleserverport.html +++ b/netbox/templates/dcim/inc/consoleserverport.html @@ -1,6 +1,6 @@ {% load helpers %} - + {# Checkbox #} {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} diff --git a/netbox/templates/dcim/inc/frontport.html b/netbox/templates/dcim/inc/frontport.html index 1b7f85e2cad..12915f64d5b 100644 --- a/netbox/templates/dcim/inc/frontport.html +++ b/netbox/templates/dcim/inc/frontport.html @@ -1,5 +1,5 @@ {% load helpers %} - + {# Checkbox #} {% if perms.dcim.change_frontport or perms.dcim.delete_frontport %} diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index 6ec46824bdc..2fe970fd713 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -1,5 +1,5 @@ {% load helpers %} - + {# Checkbox #} {% if perms.dcim.change_interface or perms.dcim.delete_interface %} diff --git a/netbox/templates/dcim/inc/poweroutlet.html b/netbox/templates/dcim/inc/poweroutlet.html index 5691608b481..1c063031095 100644 --- a/netbox/templates/dcim/inc/poweroutlet.html +++ b/netbox/templates/dcim/inc/poweroutlet.html @@ -1,6 +1,6 @@ {% load helpers %} - + {# Checkbox #} {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %} diff --git a/netbox/templates/dcim/inc/powerport.html b/netbox/templates/dcim/inc/powerport.html index 67908265447..045b25dfd86 100644 --- a/netbox/templates/dcim/inc/powerport.html +++ b/netbox/templates/dcim/inc/powerport.html @@ -1,4 +1,4 @@ - + {# Name #} diff --git a/netbox/templates/dcim/inc/rearport.html b/netbox/templates/dcim/inc/rearport.html index 27609e7267d..73ccd6b709f 100644 --- a/netbox/templates/dcim/inc/rearport.html +++ b/netbox/templates/dcim/inc/rearport.html @@ -1,5 +1,5 @@ {% load helpers %} - + {# Checkbox #} {% if perms.dcim.change_rearport or perms.dcim.delete_rearport %} diff --git a/netbox/templates/dcim/rack_elevation_list.html b/netbox/templates/dcim/rack_elevation_list.html index de7c559194b..da4f002d6c0 100644 --- a/netbox/templates/dcim/rack_elevation_list.html +++ b/netbox/templates/dcim/rack_elevation_list.html @@ -3,8 +3,8 @@ {% block content %}
- Front - Rear + Front + Rear

{% block title %}Rack Elevations{% endblock %}

@@ -17,11 +17,7 @@

{% block title %}Rack Elevations{% endblock %}

{{ rack.name|truncatechars:"25" }}

{{ rack.facility_id|truncatechars:"30" }}

- {% if face_id %} - {% include 'dcim/inc/rack_elevation.html' with face='rear' %} - {% else %} - {% include 'dcim/inc/rack_elevation.html' with face='front' %} - {% endif %} + {% include 'dcim/inc/rack_elevation.html' with face=rack_face %}
{{ rack.name|truncatechars:"25" }} diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index c24c94c87a3..e3f694fe3f8 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -61,7 +61,7 @@ {% render_field form.nat_device %}
{% render_field form.nat_inside %} diff --git a/netbox/templates/virtualization/cluster_edit.html b/netbox/templates/virtualization/cluster_edit.html index bf81ffd42ae..c4d39d12e58 100644 --- a/netbox/templates/virtualization/cluster_edit.html +++ b/netbox/templates/virtualization/cluster_edit.html @@ -8,7 +8,6 @@ {% render_field form.name %} {% render_field form.type %} {% render_field form.group %} - {% render_field form.tenant %} {% render_field form.site %}
diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 7c37a5b2065..5ef4156aaed 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -13,7 +13,6 @@ from rest_framework.serializers import Field, ModelSerializer, ValidationError from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet -from utilities.choices import ChoiceSet from .utils import dict_to_filter_params, dynamic_import diff --git a/netbox/utilities/choices.py b/netbox/utilities/choices.py index 6e6afe3e233..19082dbb663 100644 --- a/netbox/utilities/choices.py +++ b/netbox/utilities/choices.py @@ -18,7 +18,7 @@ class ChoiceSet(metaclass=ChoiceSetMeta): @classmethod def values(cls): - return [c[0] for c in cls.CHOICES] + return [c[0] for c in unpack_grouped_choices(cls.CHOICES)] @classmethod def as_dict(cls): diff --git a/netbox/utilities/middleware.py b/netbox/utilities/middleware.py index a44273ab0eb..564771821b2 100644 --- a/netbox/utilities/middleware.py +++ b/netbox/utilities/middleware.py @@ -7,9 +7,6 @@ from .views import server_error -BASE_PATH = getattr(settings, 'BASE_PATH', False) -LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False) - class LoginRequiredMiddleware(object): """ @@ -19,7 +16,7 @@ def __init__(self, get_response): self.get_response = get_response 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 # performs its own authentication. Also metrics can be read without login. api_path = reverse('api-root') diff --git a/netbox/utilities/tests/test_choices.py b/netbox/utilities/tests/test_choices.py new file mode 100644 index 00000000000..adff4eb608c --- /dev/null +++ b/netbox/utilities/tests/test_choices.py @@ -0,0 +1,50 @@ +from django.test import TestCase + +from utilities.choices import ChoiceSet + + +class ExampleChoices(ChoiceSet): + + CHOICE_A = 'a' + CHOICE_B = 'b' + CHOICE_C = 'c' + CHOICE_1 = 1 + CHOICE_2 = 2 + CHOICE_3 = 3 + CHOICES = ( + ('Letters', ( + (CHOICE_A, 'A'), + (CHOICE_B, 'B'), + (CHOICE_C, 'C'), + )), + ('Digits', ( + (CHOICE_1, 'One'), + (CHOICE_2, 'Two'), + (CHOICE_3, 'Three'), + )), + ) + LEGACY_MAP = { + CHOICE_A: 101, + CHOICE_B: 102, + CHOICE_C: 103, + CHOICE_1: 201, + CHOICE_2: 202, + CHOICE_3: 203, + } + + +class ChoiceSetTestCase(TestCase): + + def test_values(self): + self.assertListEqual(ExampleChoices.values(), ['a', 'b', 'c', 1, 2, 3]) + + def test_as_dict(self): + self.assertEqual(ExampleChoices.as_dict(), { + 'a': 'A', 'b': 'B', 'c': 'C', 1: 'One', 2: 'Two', 3: 'Three' + }) + + def test_slug_to_id(self): + self.assertEqual(ExampleChoices.slug_to_id('a'), 101) + + def test_id_to_slug(self): + self.assertEqual(ExampleChoices.id_to_slug(101), 'a') diff --git a/netbox/utilities/validators.py b/netbox/utilities/validators.py index fb7a5bba753..cfa733208d3 100644 --- a/netbox/utilities/validators.py +++ b/netbox/utilities/validators.py @@ -1,6 +1,6 @@ import re -from django.core.validators import _lazy_re_compile, BaseValidator, URLValidator +from django.core.validators import _lazy_re_compile, URLValidator class EnhancedURLValidator(URLValidator): @@ -26,19 +26,3 @@ def __contains__(self, item): r'(?:[/?#][^\s]*)?' # Path r'\Z', re.IGNORECASE) schemes = AnyURLScheme() - - -class MaxPrefixLengthValidator(BaseValidator): - message = 'The prefix length must be less than or equal to %(limit_value)s.' - code = 'max_prefix_length' - - def compare(self, a, b): - return a.prefixlen > b - - -class MinPrefixLengthValidator(BaseValidator): - message = 'The prefix length must be greater than or equal to %(limit_value)s.' - code = 'min_prefix_length' - - def compare(self, a, b): - return a.prefixlen < b diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 910e9a39f34..cbabc20c43e 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -3,6 +3,7 @@ from taggit.forms import TagField from dcim.choices import InterfaceModeChoices +from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN from dcim.forms import INTERFACE_MODE_HELP_TEXT from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm @@ -170,7 +171,8 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit ) ) comments = CommentField( - widget=SmallTextarea() + widget=SmallTextarea, + label='Comments' ) class Meta: @@ -534,7 +536,8 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB label='Disk (GB)' ) comments = CommentField( - widget=SmallTextarea() + widget=SmallTextarea, + label='Comments' ) class Meta: @@ -745,8 +748,8 @@ class InterfaceCreateForm(ComponentForm): ) mtu = forms.IntegerField( required=False, - min_value=1, - max_value=32767, + min_value=INTERFACE_MTU_MIN, + max_value=INTERFACE_MTU_MAX, label='MTU' ) mac_address = forms.CharField( @@ -834,8 +837,8 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): ) mtu = forms.IntegerField( required=False, - min_value=1, - max_value=32767, + min_value=INTERFACE_MTU_MIN, + max_value=INTERFACE_MTU_MAX, label='MTU' ) description = forms.CharField( @@ -931,8 +934,8 @@ class VirtualMachineBulkAddInterfaceForm(VirtualMachineBulkAddComponentForm): ) mtu = forms.IntegerField( required=False, - min_value=1, - max_value=32767, + min_value=INTERFACE_MTU_MIN, + max_value=INTERFACE_MTU_MAX, label='MTU' ) description = forms.CharField(