Sfoglia il codice sorgente

Merge pull request #4035 from netbox-community/develop

Release v2.7.3
Jeremy Stretch 6 anni fa
parent
commit
3143f75a38
61 ha cambiato i file con 1176 aggiunte e 6363 eliminazioni
  1. 13 2
      docs/additional-features/custom-scripts.md
  2. 29 0
      docs/release-notes/version-2.7.md
  3. 15 3
      netbox/circuits/api/serializers.py
  4. 3 1
      netbox/circuits/api/views.py
  5. 2 1
      netbox/circuits/forms.py
  6. 26 2
      netbox/dcim/constants.py
  7. 0 5732
      netbox/dcim/fixtures/dcim.json
  8. 50 17
      netbox/dcim/forms.py
  9. 25 14
      netbox/dcim/models/__init__.py
  10. 397 0
      netbox/dcim/tests/test_api.py
  11. 80 42
      netbox/dcim/tests/test_forms.py
  12. 22 1
      netbox/dcim/tests/test_models.py
  13. 11 5
      netbox/dcim/views.py
  14. 0 35
      netbox/extras/fixtures/extras.json
  15. 32 11
      netbox/extras/scripts.py
  16. 55 1
      netbox/extras/tests/test_scripts.py
  17. 58 4
      netbox/extras/tests/test_webhooks.py
  18. 4 4
      netbox/extras/urls.py
  19. 16 1
      netbox/extras/webhooks.py
  20. 4 10
      netbox/extras/webhooks_worker.py
  21. 43 1
      netbox/ipam/constants.py
  22. 4 9
      netbox/ipam/fields.py
  23. 0 329
      netbox/ipam/fixtures/ipam.json
  24. 33 2
      netbox/ipam/formfields.py
  25. 28 16
      netbox/ipam/forms.py
  26. 1 1
      netbox/ipam/migrations/0029_3569_ipaddress_fields.py
  27. 21 0
      netbox/ipam/migrations/0034_fix_ipaddress_status_dhcp.py
  28. 3 3
      netbox/ipam/models.py
  29. 23 1
      netbox/ipam/validators.py
  30. 7 9
      netbox/ipam/views.py
  31. 2 1
      netbox/netbox/settings.py
  32. 0 0
      netbox/netbox/tests/__init__.py
  33. 13 0
      netbox/netbox/tests/test_api.py
  34. 24 0
      netbox/netbox/tests/test_views.py
  35. 9 7
      netbox/netbox/urls.py
  36. 7 4
      netbox/project-static/js/forms.js
  37. 9 4
      netbox/project-static/js/interface_toggles.js
  38. 5 0
      netbox/secrets/constants.py
  39. 3 2
      netbox/secrets/forms.py
  40. 0 1
      netbox/secrets/tests/test_form.py
  41. 2 19
      netbox/templates/dcim/cable_connect.html
  42. 1 19
      netbox/templates/dcim/cable_edit.html
  43. 1 1
      netbox/templates/dcim/cable_trace.html
  44. 19 0
      netbox/templates/dcim/inc/cable_form.html
  45. 1 1
      netbox/templates/dcim/inc/cable_toggle_buttons.html
  46. 1 1
      netbox/templates/dcim/inc/consoleport.html
  47. 1 1
      netbox/templates/dcim/inc/consoleserverport.html
  48. 1 1
      netbox/templates/dcim/inc/frontport.html
  49. 1 1
      netbox/templates/dcim/inc/interface.html
  50. 1 1
      netbox/templates/dcim/inc/poweroutlet.html
  51. 1 1
      netbox/templates/dcim/inc/powerport.html
  52. 1 1
      netbox/templates/dcim/inc/rearport.html
  53. 3 7
      netbox/templates/dcim/rack_elevation_list.html
  54. 1 1
      netbox/templates/ipam/ipaddress_edit.html
  55. 0 1
      netbox/templates/virtualization/cluster_edit.html
  56. 0 1
      netbox/utilities/api.py
  57. 1 1
      netbox/utilities/choices.py
  58. 1 4
      netbox/utilities/middleware.py
  59. 50 0
      netbox/utilities/tests/test_choices.py
  60. 1 17
      netbox/utilities/validators.py
  61. 11 8
      netbox/virtualization/forms.py

+ 13 - 2
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
 

+ 29 - 0
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

+ 15 - 3
netbox/circuits/api/serializers.py

@@ -3,11 +3,11 @@ from taggit_serializer.serializers import TaggitSerializer, TagListSerializerFie
 
 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 CircuitTypeSerializer(ValidatedModelSerializer):
         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',
         ]
 
 

+ 3 - 1
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
 

+ 2 - 1
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:

+ 26 - 2
netbox/dcim/constants.py

@@ -4,17 +4,30 @@ from .choices import InterfaceTypeChoices
 
 
 #
-# 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 @@ WIRELESS_IFACE_TYPES = [
 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
 #

+ 0 - 5732
netbox/dcim/fixtures/dcim.json

@@ -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": ""
-    }
-}
-]

+ 50 - 17
netbox/dcim/forms.py

@@ -5,7 +5,6 @@ from django.contrib.auth.models import User
 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 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
         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 @@ class InterfaceCSVForm(forms.ModelForm):
             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 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
         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 ConnectCableToDeviceForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelFo
             '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 CableForm(BootstrapMixin, forms.ModelForm):
         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:

+ 25 - 14
netbox/dcim/models/__init__.py

@@ -405,7 +405,7 @@ class RackElevationHelperMixin:
 
     @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 @@ class RackElevationHelperMixin:
         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 @@ class RackElevationHelperMixin:
                 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 @@ class RackElevationHelperMixin:
             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 @@ class RackElevationHelperMixin:
 
         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 @@ class RackElevationHelperMixin:
             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 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
         # 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(

+ 397 - 0
netbox/dcim/tests/test_api.py

@@ -4,6 +4,7 @@ from netaddr import IPNetwork
 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 @@ class RackTest(APITestCase):
 
         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 @@ class DeviceTest(APITestCase):
         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 @@ class ConsolePortTest(APITestCase):
         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 @@ class ConsoleServerPortTest(APITestCase):
         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 @@ class PowerPortTest(APITestCase):
         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 @@ class PowerOutletTest(APITestCase):
         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 @@ class InterfaceTest(APITestCase):
         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):

+ 80 - 42
netbox/dcim/tests/test_forms.py

@@ -2,6 +2,7 @@ from django.test import TestCase
 
 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)

+ 22 - 1
netbox/dcim/tests/test_models.py

@@ -285,7 +285,28 @@ class DeviceTestCase(TestCase):
             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,

+ 11 - 5
netbox/dcim/views.py

@@ -30,6 +30,7 @@ from utilities.views import (
 )
 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 @@ class RackElevationListView(PermissionRequiredMixin, View):
             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 @@ class CableCreateView(PermissionRequiredMixin, GetReturnURLMixin, View):
         # 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, {

+ 0 - 35
netbox/extras/fixtures/extras.json

@@ -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": ""
-    }
-}
-]

+ 32 - 11
netbox/extras/scripts.py

@@ -14,10 +14,10 @@ from django.db import transaction
 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 @@ __all__ = [
     '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(

+ 55 - 1
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 ScriptVariablesTest(TestCase):
         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 ScriptVariablesTest(TestCase):
         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)

+ 58 - 4
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 @@ class WebhookTest(APITestCase):
     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 @@ class WebhookTest(APITestCase):
         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)

+ 4 - 4
netbox/extras/urls.py

@@ -11,10 +11,10 @@ urlpatterns = [
     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/<slug:slug>/', views.TagView.as_view(), name='tag'),
-    path(r'tags/<slug:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),
-    path(r'tags/<slug:slug>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
-    path(r'tags/<slug:slug>/changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}),
+    path(r'tags/<str:slug>/', views.TagView.as_view(), name='tag'),
+    path(r'tags/<str:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),
+    path(r'tags/<str:slug>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
+    path(r'tags/<str:slug>/changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}),
 
     # Config contexts
     path(r'config-contexts/', views.ConfigContextListView.as_view(), name='configcontext_list'),

+ 16 - 1
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 .choices import *
 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
             )

+ 4 - 10
netbox/extras/webhooks_worker.py

@@ -1,5 +1,3 @@
-import hashlib
-import hmac
 import json
 
 import requests
@@ -7,6 +5,7 @@ from django_rq import job
 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(

+ 43 - 1
netbox/ipam/constants.py

@@ -4,10 +4,34 @@ from .choices import IPAddressRoleChoices
 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 @@ IPADDRESS_ROLES_NONUNIQUE = (
     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

+ 4 - 9
netbox/ipam/fields.py

@@ -2,13 +2,8 @@ from django.core.exceptions import ValidationError
 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 @@ class BaseIPField(models.Field):
         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'

+ 0 - 329
netbox/ipam/fixtures/ipam.json

@@ -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
-    }
-}
-]

+ 33 - 2
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).",
     }

+ 28 - 16
netbox/ipam/forms.py

@@ -13,17 +13,18 @@ from utilities.forms import (
     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

+ 1 - 1
netbox/ipam/migrations/0029_3569_ipaddress_fields.py

@@ -2,10 +2,10 @@ from django.db import migrations, models
 
 
 IPADDRESS_STATUS_CHOICES = (
-    (0, 'container'),
     (1, 'active'),
     (2, 'reserved'),
     (3, 'deprecated'),
+    (5, 'dhcp'),
 )
 
 IPADDRESS_ROLE_CHOICES = (

+ 21 - 0
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
+        ),
+    ]

+ 3 - 3
netbox/ipam/models.py

@@ -14,7 +14,7 @@ from utilities.models import ChangeLoggedModel
 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(

+ 23 - 1
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(

+ 7 - 9
netbox/ipam/views.py

@@ -15,6 +15,7 @@ from utilities.views import (
 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'])

+ 2 - 1
netbox/netbox/settings.py

@@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured
 # Environment setup
 #
 
-VERSION = '2.7.2'
+VERSION = '2.7.3'
 
 # Hostname
 HOSTNAME = platform.node()
@@ -503,6 +503,7 @@ SWAGGER_SETTINGS = {
         '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',

+ 0 - 0
netbox/netbox/tests/__init__.py


+ 13 - 0
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)

+ 24 - 0
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)

+ 9 - 7
netbox/netbox/urls.py

@@ -9,14 +9,16 @@ from netbox.views import APIRootView, HomeView, SearchView
 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,
 )

+ 7 - 4
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";
+                        }
                     }
                 });
 

+ 9 - 4
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();
         }
     }
 });

+ 5 - 0
netbox/secrets/constants.py

@@ -0,0 +1,5 @@
+#
+# Secrets
+#
+
+SECRET_PLAINTEXT_MAX_LENGTH = 65535

+ 3 - 2
netbox/secrets/forms.py

@@ -9,6 +9,7 @@ from utilities.forms import (
     APISelect, APISelectMultiple, BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField,
     StaticSelect2Multiple
 )
+from .constants import *
 from .models import Secret, SecretRole, UserKey
 
 
@@ -69,7 +70,7 @@ class SecretRoleCSVForm(forms.ModelForm):
 
 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()

+ 0 - 1
netbox/secrets/tests/test_form.py

@@ -29,5 +29,4 @@ class UserKeyFormTestCase(TestCase):
             data={'public_key': SSH_PUBLIC_KEY},
             instance=self.userkey,
         )
-        print(form.is_valid())
         self.assertFalse(form.is_valid())

+ 2 - 19
netbox/templates/dcim/cable_connect.html

@@ -144,25 +144,8 @@
             </div>
         </div>
         <div class="row">
-            <div class="col-md-4 col-md-offset-4">
-                <div class="panel panel-default">
-                    <div class="panel-heading"><strong>Cable</strong></div>
-                    <div class="panel-body">
-                        {% render_field form.status %}
-                        {% render_field form.type %}
-                        {% render_field form.label %}
-                        {% render_field form.color %}
-                        <div class="form-group">
-                            <label class="col-md-3 control-label" for="id_length">{{ form.length.label }}</label>
-                            <div class="col-md-6">
-                                {{ form.length }}
-                            </div>
-                            <div class="col-md-3">
-                                {{ form.length_unit }}
-                            </div>
-                        </div>
-                    </div>
-                </div>
+            <div class="col-md-6 col-md-offset-3">
+                {% include 'dcim/inc/cable_form.html' %}
             </div>
         </div>
         <div class="form-group">

+ 1 - 19
netbox/templates/dcim/cable_edit.html

@@ -1,23 +1,5 @@
 {% extends 'utilities/obj_edit.html' %}
-{% load form_helpers %}
 
 {% block form %}
-    <div class="panel panel-default">
-        <div class="panel-heading"><strong>Cable</strong></div>
-        <div class="panel-body">
-            {% render_field form.type %}
-            {% render_field form.status %}
-            {% render_field form.label %}
-            {% render_field form.color %}
-            <div class="form-group">
-                <label class="col-md-3 control-label" for="id_length">{{ form.length.label }}</label>
-                <div class="col-md-6">
-                    {{ form.length }}
-                </div>
-                <div class="col-md-3">
-                    {{ form.length_unit }}
-                </div>
-            </div>
-        </div>
-    </div>
+    {% include 'dcim/inc/cable_form.html' %}
 {% endblock %}

+ 1 - 1
netbox/templates/dcim/cable_trace.html

@@ -32,7 +32,7 @@
                             {% if cable.label %}<code>{{ cable.label }}</code>{% else %}Cable #{{ cable.pk }}{% endif %}
                         </a>
                     </h4>
-                    <p><span class="label label-{% if cable.status %}success{% else %}info{% endif %}">{{ cable.get_status_display }}</span></p>
+                    <p><span class="label label-{{ cable.get_status_class }}">{{ cable.get_status_display }}</span></p>
                     <p>{{ cable.get_type_display|default:"" }}</p>
                     {% if cable.length %}{{ cable.length }} {{ cable.get_length_unit_display }}{% endif %}
                     {% if cable.color %}

+ 19 - 0
netbox/templates/dcim/inc/cable_form.html

@@ -0,0 +1,19 @@
+{% load form_helpers %}
+<div class="panel panel-default">
+    <div class="panel-heading"><strong>Cable</strong></div>
+    <div class="panel-body">
+        {% render_field form.status %}
+        {% render_field form.type %}
+        {% render_field form.label %}
+        {% render_field form.color %}
+        <div class="form-group">
+            <label class="col-md-3 control-label" for="id_length">{{ form.length.label }}</label>
+            <div class="col-md-5">
+                {{ form.length }}
+            </div>
+            <div class="col-md-4">
+                {{ form.length_unit }}
+            </div>
+        </div>
+    </div>
+</div>

+ 1 - 1
netbox/templates/dcim/inc/cable_toggle_buttons.html

@@ -1,5 +1,5 @@
 {% if perms.dcim.change_cable %}
-    {% if cable.status %}
+    {% if cable.status == 'connected' %}
         <a href="#" class="btn btn-warning btn-xs cable-toggle connected" title="Mark planned" data="{{ cable.pk }}">
             <i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
         </a>

+ 1 - 1
netbox/templates/dcim/inc/consoleport.html

@@ -1,4 +1,4 @@
-<tr class="consoleport{% if cp.cable.status %} success{% elif cp.cable %} info{% endif %}">
+<tr class="consoleport{% if cp.cable %} {{ cp.cable.get_status_class }}{% endif %}">
 
     {# Name #}
     <td>

+ 1 - 1
netbox/templates/dcim/inc/consoleserverport.html

@@ -1,6 +1,6 @@
 {% load helpers %}
 
-<tr class="consoleserverport{% if csp.cable.status %} success{% elif csp.cable %} info{% endif %}">
+<tr class="consoleserverport{% if csp.cable %} {{ csp.cable.get_status_class }}{% endif %}">
 
     {# Checkbox #}
     {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}

+ 1 - 1
netbox/templates/dcim/inc/frontport.html

@@ -1,5 +1,5 @@
 {% load helpers %}
-<tr class="frontport{% if frontport.cable.status %} success{% elif frontport.cable %} info{% endif %}">
+<tr class="frontport{% if frontport.cable %} {{ frontport.cable.get_status_class }}{% endif %}">
 
     {# Checkbox #}
     {% if perms.dcim.change_frontport or perms.dcim.delete_frontport %}

+ 1 - 1
netbox/templates/dcim/inc/interface.html

@@ -1,5 +1,5 @@
 {% load helpers %}
-<tr class="interface{% if not iface.enabled %} danger{% elif iface.cable.status %} success{% elif iface.cable %} info{% elif iface.is_virtual %} warning{% endif %}" id="interface_{{ iface.name }}">
+<tr class="interface{% if not iface.enabled %} danger{% elif iface.cable %} {{ iface.cable.get_status_class }}{% elif iface.is_virtual %} warning{% endif %}" id="interface_{{ iface.name }}">
 
     {# Checkbox #}
     {% if perms.dcim.change_interface or perms.dcim.delete_interface %}

+ 1 - 1
netbox/templates/dcim/inc/poweroutlet.html

@@ -1,6 +1,6 @@
 {% load helpers %}
 
-<tr class="poweroutlet{% if po.cable.status %} success{% elif po.cable %} info{% endif %}">
+<tr class="poweroutlet{% if po.cable %} {{ po.cable.get_status_class }}{% endif %}">
 
     {# Checkbox #}
     {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}

+ 1 - 1
netbox/templates/dcim/inc/powerport.html

@@ -1,4 +1,4 @@
-<tr class="powerport{% if pp.cable.status %} success{% elif pp.cable %} info{% endif %}">
+<tr class="powerport{% if pp.cable %} {{ pp.cable.get_status_class }}{% endif %}">
 
     {# Name #}
     <td>

+ 1 - 1
netbox/templates/dcim/inc/rearport.html

@@ -1,5 +1,5 @@
 {% load helpers %}
-<tr class="rearport{% if rearport.cable.status %} success{% elif rearport.cable %} info{% endif %}">
+<tr class="rearport{% if rearport.cable %} {{ rearport.cable.get_status_class }}{% endif %}">
 
     {# Checkbox #}
     {% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}

+ 3 - 7
netbox/templates/dcim/rack_elevation_list.html

@@ -3,8 +3,8 @@
 
 {% block content %}
 <div class="btn-group pull-right noprint" role="group">
-    <a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face=0 %}" class="btn btn-default{% if request.GET.face != '1' %} active{% endif %}">Front</a>
-    <a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face=1 %}" class="btn btn-default{% if request.GET.face == '1' %} active{% endif %}">Rear</a>
+    <a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-default{% if rack_face == 'front' %} active{% endif %}">Front</a>
+    <a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-default{% if rack_face == 'rear' %} active{% endif %}">Rear</a>
 </div>
 <h1>{% block title %}Rack Elevations{% endblock %}</h1>
 <div class="row">
@@ -17,11 +17,7 @@
                             <strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name|truncatechars:"25" }}</a></strong>
                             <p><small class="text-muted">{{ rack.facility_id|truncatechars:"30" }}</small></p>
                         </div>
-                        {% 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 %}
                         <div class="clearfix"></div>
                         <div class="rack_header">
                             <strong><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack.name|truncatechars:"25" }}</a></strong>

+ 1 - 1
netbox/templates/ipam/ipaddress_edit.html

@@ -61,7 +61,7 @@
                     {% render_field form.nat_device %}
                 </div>
                 <div class="tab-pane" id="search">
-                    &nbsp;
+                  {% render_field form.nat_vrf %}
                 </div>
             </div>
             {% render_field form.nat_inside %}

+ 0 - 1
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 %}
         </div>
     </div>

+ 0 - 1
netbox/utilities/api.py

@@ -13,7 +13,6 @@ from rest_framework.response import Response
 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
 
 

+ 1 - 1
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):

+ 1 - 4
netbox/utilities/middleware.py

@@ -7,9 +7,6 @@ from django.urls import reverse
 
 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 @@ class LoginRequiredMiddleware(object):
         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')

+ 50 - 0
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')

+ 1 - 17
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 @@ class EnhancedURLValidator(URLValidator):
         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

+ 11 - 8
netbox/virtualization/forms.py

@@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError
 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(