From 9615dc0638829be293c0ca32ebddeabaaed2c18a Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Mon, 14 Dec 2020 16:30:10 -0600 Subject: [PATCH 001/302] Fixes: #5458 - Fix devicetype_component_table.html --- docs/release-notes/version-2.10.md | 6 ++++++ netbox/templates/dcim/inc/devicetype_component_table.html | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index 15ac1fc04..7cc15552e 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -1,5 +1,11 @@ # NetBox v2.10 +## v2.10.1 (Future Release) + +### Bug Fixes + +* [#5458](https://github.com/netbox-community/netbox/issues/5458) - Creating a component template throws an exception + ## v2.10.0 (2020-12-14) **NOTE:** This release completely removes support for embedded graphs. diff --git a/netbox/templates/dcim/inc/devicetype_component_table.html b/netbox/templates/dcim/inc/devicetype_component_table.html index a8e04e9dc..c0a2ff22a 100644 --- a/netbox/templates/dcim/inc/devicetype_component_table.html +++ b/netbox/templates/dcim/inc/devicetype_component_table.html @@ -9,18 +9,18 @@ {% include 'responsive_table.html' %} {% endif %} -
-
Tags
-
- {% render_field form.tags %} -
-
Comments
diff --git a/netbox/templates/generic/object_edit.html b/netbox/templates/generic/object_edit.html index 7056256ec..a07b2dfaf 100644 --- a/netbox/templates/generic/object_edit.html +++ b/netbox/templates/generic/object_edit.html @@ -50,14 +50,6 @@
{% endif %} - {% if form.tags %} -
-
Tags
-
- {% render_field form.tags %} -
-
- {% endif %} {% if form.comments %}
Comments
diff --git a/netbox/templates/ipam/ipaddress_bulk_add.html b/netbox/templates/ipam/ipaddress_bulk_add.html index f019d48b8..db103b43b 100644 --- a/netbox/templates/ipam/ipaddress_bulk_add.html +++ b/netbox/templates/ipam/ipaddress_bulk_add.html @@ -17,6 +17,7 @@ {% render_field model_form.role %} {% render_field model_form.vrf %} {% render_field model_form.description %} + {% render_field model_form.tags %}
@@ -26,12 +27,6 @@ {% render_field model_form.tenant %}
-
-
Tags
-
- {% render_field model_form.tags %} -
-
{% if model_form.custom_fields %}
Custom Fields
diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index eb15a3059..33e58ce09 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -19,6 +19,7 @@ {% render_field form.vrf %} {% render_field form.dns_name %} {% render_field form.description %} + {% render_field form.tags %}
@@ -78,12 +79,6 @@ {% render_field form.nat_inside %}
-
-
Tags
-
- {% render_field form.tags %} -
-
{% if form.custom_fields %}
Custom Fields
diff --git a/netbox/templates/ipam/service_edit.html b/netbox/templates/ipam/service_edit.html index 8d6fde9e9..7cc74f450 100644 --- a/netbox/templates/ipam/service_edit.html +++ b/netbox/templates/ipam/service_edit.html @@ -31,6 +31,7 @@
{% render_field form.ipaddresses %} {% render_field form.description %} + {% render_field form.tags %} {% if form.custom_fields %} @@ -41,10 +42,4 @@ {% endif %} -
-
Tags
-
- {% render_field form.tags %} -
-
{% endblock %} diff --git a/netbox/templates/secrets/secret_edit.html b/netbox/templates/secrets/secret_edit.html index 4e2d78042..b7e7ab785 100644 --- a/netbox/templates/secrets/secret_edit.html +++ b/netbox/templates/secrets/secret_edit.html @@ -39,6 +39,7 @@ {% render_field form.role %} {% render_field form.name %} {% render_field form.userkeys %} + {% render_field form.tags %}
@@ -72,12 +73,6 @@
{% endif %} -
-
Tags
-
- {% render_field form.tags %} -
-
diff --git a/netbox/templates/virtualization/vminterface_edit.html b/netbox/templates/virtualization/vminterface_edit.html index d1fe6d104..d4869aaf9 100644 --- a/netbox/templates/virtualization/vminterface_edit.html +++ b/netbox/templates/virtualization/vminterface_edit.html @@ -20,6 +20,7 @@ {% render_field form.mac_address %} {% render_field form.mtu %} {% render_field form.description %} + {% render_field form.tags %}
@@ -30,12 +31,6 @@ {% render_field form.tagged_vlans %}
-
-
Tags
-
- {% render_field form.tags %} -
-
{% endblock %} {% block buttons %} diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index ee9adefc0..97e72bb02 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -65,7 +65,7 @@ class TenantForm(BootstrapMixin, CustomFieldModelForm): 'name', 'slug', 'group', 'description', 'comments', 'tags', ) fieldsets = ( - ('Tenant', ('name', 'slug', 'group', 'description')), + ('Tenant', ('name', 'slug', 'group', 'description', 'tags')), ) diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 7e0c45378..addfb05cd 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -105,7 +105,7 @@ class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): 'name', 'type', 'group', 'tenant', 'region', 'site', 'comments', 'tags', ) fieldsets = ( - ('Cluster', ('name', 'type', 'group', 'region', 'site')), + ('Cluster', ('name', 'type', 'group', 'region', 'site', 'tags')), ('Tenancy', ('tenant_group', 'tenant')), ) @@ -326,7 +326,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm): 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data', ] fieldsets = ( - ('Virtual Machine', ('name', 'role', 'status')), + ('Virtual Machine', ('name', 'role', 'status', 'tags')), ('Cluster', ('cluster_group', 'cluster')), ('Management', ('platform', 'primary_ip4', 'primary_ip6')), ('Resources', ('vcpus', 'memory', 'disk')), From d6cf385a3c5363c63007b3d40b8e3152e708170a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 25 Feb 2021 14:12:34 -0500 Subject: [PATCH 127/302] Update CircuitTermination fields to use SelectSpeedWidget --- netbox/circuits/forms.py | 2 + .../circuits/circuittermination_edit.html | 130 +++++------------- .../templates/circuits/inc/speed_widget.html | 17 --- 3 files changed, 40 insertions(+), 109 deletions(-) delete mode 100644 netbox/templates/circuits/inc/speed_widget.html diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 6a2577edb..18d49d08e 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -338,4 +338,6 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm): } widgets = { 'term_side': forms.HiddenInput(), + 'port_speed': SelectSpeedWidget(), + 'upstream_speed': SelectSpeedWidget(), } diff --git a/netbox/templates/circuits/circuittermination_edit.html b/netbox/templates/circuits/circuittermination_edit.html index 43dba8438..917fca970 100644 --- a/netbox/templates/circuits/circuittermination_edit.html +++ b/netbox/templates/circuits/circuittermination_edit.html @@ -1,97 +1,43 @@ -{% extends 'base.html' %} +{% extends 'generic/object_edit.html' %} {% load static %} {% load form_helpers %} -{% block content %} -
- {% csrf_token %} - {% for field in form.hidden_fields %} - {{ field }} - {% endfor %} -
-
-

{% block title %}{{ obj.circuit.provider }} {{ obj.circuit }} - Side {{ form.term_side.value }}{% endblock %}

- {% if form.non_field_errors %} -
-
Errors
-
- {{ form.non_field_errors }} -
-
- {% endif %} -
-
Location
-
-
- -
-

{{ obj.circuit.provider }}

-
-
-
- -
-

{{ obj.circuit.cid }}

-
-
-
- -
-

{{ form.term_side.value }}

-
-
- {% render_field form.region %} - {% render_field form.site %} -
-
-
-
Termination Details
-
-
- -
-
- {{ form.port_speed }} - {% include 'circuits/inc/speed_widget.html' with target_field='port_speed' %} -
- {{ form.port_speed.help_text }} -
-
-
- -
-
- {{ form.upstream_speed }} - {% include 'circuits/inc/speed_widget.html' with target_field='upstream_speed' %} -
- {{ form.upstream_speed.help_text }} -
-
- {% render_field form.xconnect_id %} - {% render_field form.pp_info %} - {% render_field form.description %} -
-
-
-
-
-
- {% if obj.pk %} - - {% else %} - - {% endif %} - Cancel -
-
-
-{% endblock %} +{% block title %}{{ obj.circuit.provider }} {{ obj.circuit }} - Side {{ form.term_side.value }}{% endblock %} -{% block javascript %} - +{% block form %} +
+
Location
+
+
+ +
+

{{ obj.circuit.provider }}

+
+
+
+ +
+

{{ obj.circuit.cid }}

+
+
+
+ +
+

{{ form.term_side.value }}

+
+
+ {% render_field form.region %} + {% render_field form.site %} +
+
+
+
Termination Details
+
+ {% render_field form.port_speed %} + {% render_field form.upstream_speed %} + {% render_field form.xconnect_id %} + {% render_field form.pp_info %} + {% render_field form.description %} +
+
{% endblock %} diff --git a/netbox/templates/circuits/inc/speed_widget.html b/netbox/templates/circuits/inc/speed_widget.html deleted file mode 100644 index 988418945..000000000 --- a/netbox/templates/circuits/inc/speed_widget.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - From 664a39911cf28c22c45253e2077693bc2cd39ba6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 25 Feb 2021 15:58:13 -0500 Subject: [PATCH 128/302] Add custom field support for organizational models to API serializers --- netbox/circuits/api/serializers.py | 6 ++-- netbox/circuits/api/views.py | 2 +- netbox/dcim/api/serializers.py | 37 +++++++++++++++--------- netbox/dcim/api/views.py | 12 ++++---- netbox/ipam/api/serializers.py | 25 +++++++++++----- netbox/ipam/api/views.py | 6 ++-- netbox/secrets/api/serializers.py | 6 ++-- netbox/secrets/api/views.py | 3 +- netbox/tenancy/api/serializers.py | 7 +++-- netbox/tenancy/api/views.py | 3 +- netbox/virtualization/api/serializers.py | 12 +++++--- netbox/virtualization/api/views.py | 4 +-- 12 files changed, 77 insertions(+), 46 deletions(-) diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 12ec9ba7f..17dce6624 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -31,13 +31,15 @@ class ProviderSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # Circuits # -class CircuitTypeSerializer(ValidatedModelSerializer): +class CircuitTypeSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail') circuit_count = serializers.IntegerField(read_only=True) class Meta: model = CircuitType - fields = ['id', 'url', 'name', 'slug', 'description', 'circuit_count'] + fields = [ + 'id', 'url', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated', 'circuit_count', + ] class CircuitCircuitTerminationSerializer(WritableNestedSerializer, ConnectedEndpointSerializer): diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 736871a73..c2fe3d089 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -34,7 +34,7 @@ class ProviderViewSet(CustomFieldModelViewSet): # Circuit Types # -class CircuitTypeViewSet(ModelViewSet): +class CircuitTypeViewSet(CustomFieldModelViewSet): queryset = CircuitType.objects.annotate( circuit_count=count_related(Circuit, 'type') ) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 6f497bfa6..3aa5dea2f 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -82,7 +82,7 @@ class ConnectedEndpointSerializer(ValidatedModelSerializer): # Regions/sites # -class RegionSerializer(serializers.ModelSerializer): +class RegionSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail') parent = NestedRegionSerializer(required=False, allow_null=True) site_count = serializers.IntegerField(read_only=True) @@ -90,7 +90,10 @@ class RegionSerializer(serializers.ModelSerializer): class Meta: model = Region - fields = ['id', 'url', 'name', 'slug', 'parent', 'description', 'site_count', '_depth'] + fields = [ + 'id', 'url', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated', + 'site_count', '_depth', + ] class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): @@ -120,7 +123,7 @@ class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # Racks # -class RackGroupSerializer(ValidatedModelSerializer): +class RackGroupSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail') site = NestedSiteSerializer() parent = NestedRackGroupSerializer(required=False, allow_null=True) @@ -129,16 +132,22 @@ class RackGroupSerializer(ValidatedModelSerializer): class Meta: model = RackGroup - fields = ['id', 'url', 'name', 'slug', 'site', 'parent', 'description', 'rack_count', '_depth'] + fields = [ + 'id', 'url', 'name', 'slug', 'site', 'parent', 'description', 'custom_fields', 'created', 'last_updated', + 'rack_count', '_depth', + ] -class RackRoleSerializer(ValidatedModelSerializer): +class RackRoleSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail') rack_count = serializers.IntegerField(read_only=True) class Meta: model = RackRole - fields = ['id', 'url', 'name', 'slug', 'color', 'description', 'rack_count'] + fields = [ + 'id', 'url', 'name', 'slug', 'color', 'description', 'custom_fields', 'created', 'last_updated', + 'rack_count', + ] class RackSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): @@ -242,7 +251,7 @@ class RackElevationDetailFilterSerializer(serializers.Serializer): # Device types # -class ManufacturerSerializer(ValidatedModelSerializer): +class ManufacturerSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail') devicetype_count = serializers.IntegerField(read_only=True) inventoryitem_count = serializers.IntegerField(read_only=True) @@ -251,7 +260,8 @@ class ManufacturerSerializer(ValidatedModelSerializer): class Meta: model = Manufacturer fields = [ - 'id', 'url', 'name', 'slug', 'description', 'devicetype_count', 'inventoryitem_count', 'platform_count', + 'id', 'url', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated', 'devicetype_count', + 'inventoryitem_count', 'platform_count', ] @@ -378,7 +388,7 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer): # Devices # -class DeviceRoleSerializer(ValidatedModelSerializer): +class DeviceRoleSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail') device_count = serializers.IntegerField(read_only=True) virtualmachine_count = serializers.IntegerField(read_only=True) @@ -386,11 +396,12 @@ class DeviceRoleSerializer(ValidatedModelSerializer): class Meta: model = DeviceRole fields = [ - 'id', 'url', 'name', 'slug', 'color', 'vm_role', 'description', 'device_count', 'virtualmachine_count', + 'id', 'url', 'name', 'slug', 'color', 'vm_role', 'description', 'custom_fields', 'created', 'last_updated', + 'device_count', 'virtualmachine_count', ] -class PlatformSerializer(ValidatedModelSerializer): +class PlatformSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail') manufacturer = NestedManufacturerSerializer(required=False, allow_null=True) device_count = serializers.IntegerField(read_only=True) @@ -399,8 +410,8 @@ class PlatformSerializer(ValidatedModelSerializer): class Meta: model = Platform fields = [ - 'id', 'url', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description', 'device_count', - 'virtualmachine_count', + 'id', 'url', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description', 'custom_fields', + 'created', 'last_updated', 'device_count', 'virtualmachine_count', ] diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index ae39f6ad0..ae488a1e4 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -99,7 +99,7 @@ class PassThroughPortMixin(object): # Regions # -class RegionViewSet(ModelViewSet): +class RegionViewSet(CustomFieldModelViewSet): queryset = Region.objects.add_related_count( Region.objects.all(), Site, @@ -134,7 +134,7 @@ class SiteViewSet(CustomFieldModelViewSet): # Rack groups # -class RackGroupViewSet(ModelViewSet): +class RackGroupViewSet(CustomFieldModelViewSet): queryset = RackGroup.objects.add_related_count( RackGroup.objects.all(), Rack, @@ -150,7 +150,7 @@ class RackGroupViewSet(ModelViewSet): # Rack roles # -class RackRoleViewSet(ModelViewSet): +class RackRoleViewSet(CustomFieldModelViewSet): queryset = RackRole.objects.annotate( rack_count=count_related(Rack, 'role') ) @@ -238,7 +238,7 @@ class RackReservationViewSet(ModelViewSet): # Manufacturers # -class ManufacturerViewSet(ModelViewSet): +class ManufacturerViewSet(CustomFieldModelViewSet): queryset = Manufacturer.objects.annotate( devicetype_count=count_related(DeviceType, 'manufacturer'), inventoryitem_count=count_related(InventoryItem, 'manufacturer'), @@ -317,7 +317,7 @@ class DeviceBayTemplateViewSet(ModelViewSet): # Device roles # -class DeviceRoleViewSet(ModelViewSet): +class DeviceRoleViewSet(CustomFieldModelViewSet): queryset = DeviceRole.objects.annotate( device_count=count_related(Device, 'device_role'), virtualmachine_count=count_related(VirtualMachine, 'role') @@ -330,7 +330,7 @@ class DeviceRoleViewSet(ModelViewSet): # Platforms # -class PlatformViewSet(ModelViewSet): +class PlatformViewSet(CustomFieldModelViewSet): queryset = Platform.objects.annotate( device_count=count_related(Device, 'platform'), virtualmachine_count=count_related(VirtualMachine, 'platform') diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 9b8d36590..719ea51e2 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -67,13 +67,16 @@ class RouteTargetSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # RIRs/aggregates # -class RIRSerializer(ValidatedModelSerializer): +class RIRSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail') aggregate_count = serializers.IntegerField(read_only=True) class Meta: model = RIR - fields = ['id', 'url', 'name', 'slug', 'is_private', 'description', 'aggregate_count'] + fields = [ + 'id', 'url', 'name', 'slug', 'is_private', 'description', 'custom_fields', 'created', 'last_updated', + 'aggregate_count', + ] class AggregateSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): @@ -85,8 +88,8 @@ class AggregateSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): class Meta: model = Aggregate fields = [ - 'id', 'url', 'family', 'prefix', 'rir', 'tenant', 'date_added', 'description', 'tags', 'custom_fields', 'created', - 'last_updated', + 'id', 'url', 'family', 'prefix', 'rir', 'tenant', 'date_added', 'description', 'tags', 'custom_fields', + 'created', 'last_updated', ] read_only_fields = ['family'] @@ -95,24 +98,30 @@ class AggregateSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # VLANs # -class RoleSerializer(ValidatedModelSerializer): +class RoleSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:role-detail') prefix_count = serializers.IntegerField(read_only=True) vlan_count = serializers.IntegerField(read_only=True) class Meta: model = Role - fields = ['id', 'url', 'name', 'slug', 'weight', 'description', 'prefix_count', 'vlan_count'] + fields = [ + 'id', 'url', 'name', 'slug', 'weight', 'description', 'custom_fields', 'created', 'last_updated', + 'prefix_count', 'vlan_count', + ] -class VLANGroupSerializer(ValidatedModelSerializer): +class VLANGroupSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail') site = NestedSiteSerializer(required=False, allow_null=True) vlan_count = serializers.IntegerField(read_only=True) class Meta: model = VLANGroup - fields = ['id', 'url', 'name', 'slug', 'site', 'description', 'vlan_count'] + fields = [ + 'id', 'url', 'name', 'slug', 'site', 'description', 'custom_fields', 'created', 'last_updated', + 'vlan_count', + ] validators = [] def validate(self, data): diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 16db8f04f..b6f0a7463 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -55,7 +55,7 @@ class RouteTargetViewSet(CustomFieldModelViewSet): # RIRs # -class RIRViewSet(ModelViewSet): +class RIRViewSet(CustomFieldModelViewSet): queryset = RIR.objects.annotate( aggregate_count=count_related(Aggregate, 'rir') ) @@ -77,7 +77,7 @@ class AggregateViewSet(CustomFieldModelViewSet): # Roles # -class RoleViewSet(ModelViewSet): +class RoleViewSet(CustomFieldModelViewSet): queryset = Role.objects.annotate( prefix_count=count_related(Prefix, 'role'), vlan_count=count_related(VLAN, 'role') @@ -282,7 +282,7 @@ class IPAddressViewSet(CustomFieldModelViewSet): # VLAN groups # -class VLANGroupViewSet(ModelViewSet): +class VLANGroupViewSet(CustomFieldModelViewSet): queryset = VLANGroup.objects.prefetch_related('site').annotate( vlan_count=count_related(VLAN, 'group') ) diff --git a/netbox/secrets/api/serializers.py b/netbox/secrets/api/serializers.py index b08b87bc5..627685de0 100644 --- a/netbox/secrets/api/serializers.py +++ b/netbox/secrets/api/serializers.py @@ -15,13 +15,15 @@ from .nested_serializers import * # Secrets # -class SecretRoleSerializer(ValidatedModelSerializer): +class SecretRoleSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='secrets-api:secretrole-detail') secret_count = serializers.IntegerField(read_only=True) class Meta: model = SecretRole - fields = ['id', 'url', 'name', 'slug', 'description', 'secret_count'] + fields = [ + 'id', 'url', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated', 'secret_count', + ] class SecretSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 8c959f90d..3650abd30 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -8,6 +8,7 @@ from rest_framework.response import Response from rest_framework.routers import APIRootView from rest_framework.viewsets import ViewSet +from extras.api.views import CustomFieldModelViewSet from netbox.api.views import ModelViewSet from secrets import filters from secrets.exceptions import InvalidKey @@ -33,7 +34,7 @@ class SecretsRootView(APIRootView): # Secret Roles # -class SecretRoleViewSet(ModelViewSet): +class SecretRoleViewSet(CustomFieldModelViewSet): queryset = SecretRole.objects.annotate( secret_count=count_related(Secret, 'role') ) diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index 4c2f9faee..d301ee3fe 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -11,7 +11,7 @@ from .nested_serializers import * # Tenants # -class TenantGroupSerializer(ValidatedModelSerializer): +class TenantGroupSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail') parent = NestedTenantGroupSerializer(required=False, allow_null=True) tenant_count = serializers.IntegerField(read_only=True) @@ -19,7 +19,10 @@ class TenantGroupSerializer(ValidatedModelSerializer): class Meta: model = TenantGroup - fields = ['id', 'url', 'name', 'slug', 'parent', 'description', 'tenant_count', '_depth'] + fields = [ + 'id', 'url', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated', + 'tenant_count', '_depth', + ] class TenantSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 2b7ae8365..3b57e1a02 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -4,7 +4,6 @@ from circuits.models import Circuit from dcim.models import Device, Rack, Site from extras.api.views import CustomFieldModelViewSet from ipam.models import IPAddress, Prefix, VLAN, VRF -from netbox.api.views import ModelViewSet from tenancy import filters from tenancy.models import Tenant, TenantGroup from utilities.utils import count_related @@ -24,7 +23,7 @@ class TenancyRootView(APIRootView): # Tenant Groups # -class TenantGroupViewSet(ModelViewSet): +class TenantGroupViewSet(CustomFieldModelViewSet): queryset = TenantGroup.objects.add_related_count( TenantGroup.objects.all(), Tenant, diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 518b7086c..4883f9f62 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -18,22 +18,26 @@ from .nested_serializers import * # Clusters # -class ClusterTypeSerializer(ValidatedModelSerializer): +class ClusterTypeSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustertype-detail') cluster_count = serializers.IntegerField(read_only=True) class Meta: model = ClusterType - fields = ['id', 'url', 'name', 'slug', 'description', 'cluster_count'] + fields = [ + 'id', 'url', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated', 'cluster_count', + ] -class ClusterGroupSerializer(ValidatedModelSerializer): +class ClusterGroupSerializer(CustomFieldModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustergroup-detail') cluster_count = serializers.IntegerField(read_only=True) class Meta: model = ClusterGroup - fields = ['id', 'url', 'name', 'slug', 'description', 'cluster_count'] + fields = [ + 'id', 'url', 'name', 'slug', 'description', 'custom_fields', 'created', 'last_updated', 'cluster_count', + ] class ClusterSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index 586ad5028..ea2b33e4f 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -20,7 +20,7 @@ class VirtualizationRootView(APIRootView): # Clusters # -class ClusterTypeViewSet(ModelViewSet): +class ClusterTypeViewSet(CustomFieldModelViewSet): queryset = ClusterType.objects.annotate( cluster_count=count_related(Cluster, 'type') ) @@ -28,7 +28,7 @@ class ClusterTypeViewSet(ModelViewSet): filterset_class = filters.ClusterTypeFilterSet -class ClusterGroupViewSet(ModelViewSet): +class ClusterGroupViewSet(CustomFieldModelViewSet): queryset = ClusterGroup.objects.annotate( cluster_count=count_related(Cluster, 'group') ) From ed059d80d696e355624b24cc744388f6da11ede6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 25 Feb 2021 16:44:16 -0500 Subject: [PATCH 129/302] Introduce OrganizationalModelSerializer --- netbox/circuits/api/serializers.py | 7 ++++--- netbox/dcim/api/serializers.py | 15 ++++++--------- netbox/ipam/api/serializers.py | 9 +++++---- netbox/netbox/api/serializers.py | 4 ++++ netbox/virtualization/api/serializers.py | 7 ++++--- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 17dce6624..74d8136e0 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -2,11 +2,12 @@ from rest_framework import serializers from circuits.choices import CircuitStatusChoices from circuits.models import Provider, Circuit, CircuitTermination, CircuitType -from dcim.api.nested_serializers import NestedCableSerializer, NestedInterfaceSerializer, NestedSiteSerializer +from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer from dcim.api.serializers import CableTerminationSerializer, ConnectedEndpointSerializer from extras.api.customfields import CustomFieldModelSerializer from extras.api.serializers import TaggedObjectSerializer -from netbox.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer +from netbox.api import ChoiceField +from netbox.api.serializers import OrganizationalModelSerializer, WritableNestedSerializer from tenancy.api.nested_serializers import NestedTenantSerializer from .nested_serializers import * @@ -31,7 +32,7 @@ class ProviderSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # Circuits # -class CircuitTypeSerializer(CustomFieldModelSerializer): +class CircuitTypeSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail') circuit_count = serializers.IntegerField(read_only=True) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 3aa5dea2f..7d86d18a1 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -13,15 +13,12 @@ from dcim.models import ( PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, ) -from dcim.utils import decompile_path_node from extras.api.customfields import CustomFieldModelSerializer from extras.api.serializers import TaggedObjectSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.models import VLAN -from netbox.api import ( - ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer, - WritableNestedSerializer, -) +from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField +from netbox.api.serializers import OrganizationalModelSerializer, ValidatedModelSerializer, WritableNestedSerializer from tenancy.api.nested_serializers import NestedTenantSerializer from users.api.nested_serializers import NestedUserSerializer from utilities.api import get_serializer_for_model @@ -138,7 +135,7 @@ class RackGroupSerializer(CustomFieldModelSerializer): ] -class RackRoleSerializer(CustomFieldModelSerializer): +class RackRoleSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail') rack_count = serializers.IntegerField(read_only=True) @@ -251,7 +248,7 @@ class RackElevationDetailFilterSerializer(serializers.Serializer): # Device types # -class ManufacturerSerializer(CustomFieldModelSerializer): +class ManufacturerSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail') devicetype_count = serializers.IntegerField(read_only=True) inventoryitem_count = serializers.IntegerField(read_only=True) @@ -388,7 +385,7 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer): # Devices # -class DeviceRoleSerializer(CustomFieldModelSerializer): +class DeviceRoleSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail') device_count = serializers.IntegerField(read_only=True) virtualmachine_count = serializers.IntegerField(read_only=True) @@ -401,7 +398,7 @@ class DeviceRoleSerializer(CustomFieldModelSerializer): ] -class PlatformSerializer(CustomFieldModelSerializer): +class PlatformSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail') manufacturer = NestedManufacturerSerializer(required=False, allow_null=True) device_count = serializers.IntegerField(read_only=True) diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 719ea51e2..8688da8f2 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -11,7 +11,8 @@ from extras.api.serializers import TaggedObjectSerializer from ipam.choices import * from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF -from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer +from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField +from netbox.api.serializers import OrganizationalModelSerializer from tenancy.api.nested_serializers import NestedTenantSerializer from utilities.api import get_serializer_for_model from virtualization.api.nested_serializers import NestedVirtualMachineSerializer @@ -67,7 +68,7 @@ class RouteTargetSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # RIRs/aggregates # -class RIRSerializer(CustomFieldModelSerializer): +class RIRSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:rir-detail') aggregate_count = serializers.IntegerField(read_only=True) @@ -98,7 +99,7 @@ class AggregateSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # VLANs # -class RoleSerializer(CustomFieldModelSerializer): +class RoleSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:role-detail') prefix_count = serializers.IntegerField(read_only=True) vlan_count = serializers.IntegerField(read_only=True) @@ -111,7 +112,7 @@ class RoleSerializer(CustomFieldModelSerializer): ] -class VLANGroupSerializer(CustomFieldModelSerializer): +class VLANGroupSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail') site = NestedSiteSerializer(required=False, allow_null=True) vlan_count = serializers.IntegerField(read_only=True) diff --git a/netbox/netbox/api/serializers.py b/netbox/netbox/api/serializers.py index 3a8641efc..02e382d4b 100644 --- a/netbox/netbox/api/serializers.py +++ b/netbox/netbox/api/serializers.py @@ -35,6 +35,10 @@ class ValidatedModelSerializer(serializers.ModelSerializer): return data +class OrganizationalModelSerializer(ValidatedModelSerializer): + pass + + class WritableNestedSerializer(serializers.ModelSerializer): """ Returns a nested representation of an object on read, but accepts only a primary key on write. diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 4883f9f62..b8ccb99b0 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -7,7 +7,8 @@ from extras.api.customfields import CustomFieldModelSerializer from extras.api.serializers import TaggedObjectSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.models import VLAN -from netbox.api import ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer +from netbox.api import ChoiceField, SerializedPKRelatedField +from netbox.api.serializers import OrganizationalModelSerializer, ValidatedModelSerializer from tenancy.api.nested_serializers import NestedTenantSerializer from virtualization.choices import * from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface @@ -18,7 +19,7 @@ from .nested_serializers import * # Clusters # -class ClusterTypeSerializer(CustomFieldModelSerializer): +class ClusterTypeSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustertype-detail') cluster_count = serializers.IntegerField(read_only=True) @@ -29,7 +30,7 @@ class ClusterTypeSerializer(CustomFieldModelSerializer): ] -class ClusterGroupSerializer(CustomFieldModelSerializer): +class ClusterGroupSerializer(OrganizationalModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:clustergroup-detail') cluster_count = serializers.IntegerField(read_only=True) From 12fbd349628b94c737cf35b1284f076d87c2ef6c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 25 Feb 2021 17:15:07 -0500 Subject: [PATCH 130/302] Introduce NestedGroupModelSerializer --- netbox/circuits/api/serializers.py | 2 +- netbox/dcim/api/serializers.py | 12 +++---- netbox/extras/api/customfields.py | 35 +------------------- netbox/ipam/api/serializers.py | 2 +- netbox/netbox/api/serializers.py | 41 +++++++++++++++++++++++- netbox/secrets/api/serializers.py | 2 +- netbox/tenancy/api/serializers.py | 6 ++-- netbox/virtualization/api/serializers.py | 2 +- 8 files changed, 53 insertions(+), 49 deletions(-) diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 74d8136e0..5688bf1c7 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -4,7 +4,7 @@ from circuits.choices import CircuitStatusChoices from circuits.models import Provider, Circuit, CircuitTermination, CircuitType from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer from dcim.api.serializers import CableTerminationSerializer, ConnectedEndpointSerializer -from extras.api.customfields import CustomFieldModelSerializer +from netbox.api.serializers import CustomFieldModelSerializer from extras.api.serializers import TaggedObjectSerializer from netbox.api import ChoiceField from netbox.api.serializers import OrganizationalModelSerializer, WritableNestedSerializer diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 7d86d18a1..b4ac77e2d 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -13,12 +13,14 @@ from dcim.models import ( PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, ) -from extras.api.customfields import CustomFieldModelSerializer +from netbox.api.serializers import CustomFieldModelSerializer from extras.api.serializers import TaggedObjectSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.models import VLAN from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField -from netbox.api.serializers import OrganizationalModelSerializer, ValidatedModelSerializer, WritableNestedSerializer +from netbox.api.serializers import ( + NestedGroupModelSerializer, OrganizationalModelSerializer, ValidatedModelSerializer, WritableNestedSerializer, +) from tenancy.api.nested_serializers import NestedTenantSerializer from users.api.nested_serializers import NestedUserSerializer from utilities.api import get_serializer_for_model @@ -79,11 +81,10 @@ class ConnectedEndpointSerializer(ValidatedModelSerializer): # Regions/sites # -class RegionSerializer(CustomFieldModelSerializer): +class RegionSerializer(NestedGroupModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail') parent = NestedRegionSerializer(required=False, allow_null=True) site_count = serializers.IntegerField(read_only=True) - _depth = serializers.IntegerField(source='level', read_only=True) class Meta: model = Region @@ -120,12 +121,11 @@ class SiteSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): # Racks # -class RackGroupSerializer(CustomFieldModelSerializer): +class RackGroupSerializer(NestedGroupModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail') site = NestedSiteSerializer() parent = NestedRackGroupSerializer(required=False, allow_null=True) rack_count = serializers.IntegerField(read_only=True) - _depth = serializers.IntegerField(source='level', read_only=True) class Meta: model = RackGroup diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index c8c4ba89e..5cb1fc276 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -1,9 +1,7 @@ from django.contrib.contenttypes.models import ContentType -from rest_framework.fields import CreateOnlyDefault, Field +from rest_framework.fields import Field -from extras.choices import * from extras.models import CustomField -from netbox.api import ValidatedModelSerializer # @@ -56,34 +54,3 @@ class CustomFieldsDataField(Field): data = {**self.parent.instance.custom_field_data, **data} return data - - -class CustomFieldModelSerializer(ValidatedModelSerializer): - """ - Extends ModelSerializer to render any CustomFields and their values associated with an object. - """ - custom_fields = CustomFieldsDataField( - source='custom_field_data', - default=CreateOnlyDefault(CustomFieldDefaultValues()) - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - if self.instance is not None: - - # Retrieve the set of CustomFields which apply to this type of object - content_type = ContentType.objects.get_for_model(self.Meta.model) - fields = CustomField.objects.filter(content_types=content_type) - - # Populate CustomFieldValues for each instance from database - if type(self.instance) in (list, tuple): - for obj in self.instance: - self._populate_custom_fields(obj, fields) - else: - self._populate_custom_fields(self.instance, fields) - - def _populate_custom_fields(self, instance, custom_fields): - instance.custom_fields = {} - for field in custom_fields: - instance.custom_fields[field.name] = instance.cf.get(field.name) diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 8688da8f2..002ad3b89 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -6,7 +6,7 @@ from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerializer -from extras.api.customfields import CustomFieldModelSerializer +from netbox.api.serializers import CustomFieldModelSerializer from extras.api.serializers import TaggedObjectSerializer from ipam.choices import * from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS diff --git a/netbox/netbox/api/serializers.py b/netbox/netbox/api/serializers.py index 02e382d4b..18fc112c8 100644 --- a/netbox/netbox/api/serializers.py +++ b/netbox/netbox/api/serializers.py @@ -1,8 +1,12 @@ +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist from django.db.models import ManyToManyField from rest_framework import serializers from rest_framework.exceptions import ValidationError +from rest_framework.fields import CreateOnlyDefault +from extras.api.customfields import CustomFieldsDataField, CustomFieldDefaultValues +from extras.models import CustomField from utilities.utils import dict_to_filter_params @@ -35,10 +39,45 @@ class ValidatedModelSerializer(serializers.ModelSerializer): return data -class OrganizationalModelSerializer(ValidatedModelSerializer): +class CustomFieldModelSerializer(ValidatedModelSerializer): + """ + Extends ModelSerializer to render any CustomFields and their values associated with an object. + """ + custom_fields = CustomFieldsDataField( + source='custom_field_data', + default=CreateOnlyDefault(CustomFieldDefaultValues()) + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + if self.instance is not None: + + # Retrieve the set of CustomFields which apply to this type of object + content_type = ContentType.objects.get_for_model(self.Meta.model) + fields = CustomField.objects.filter(content_types=content_type) + + # Populate CustomFieldValues for each instance from database + if type(self.instance) in (list, tuple): + for obj in self.instance: + self._populate_custom_fields(obj, fields) + else: + self._populate_custom_fields(self.instance, fields) + + def _populate_custom_fields(self, instance, custom_fields): + instance.custom_fields = {} + for field in custom_fields: + instance.custom_fields[field.name] = instance.cf.get(field.name) + + +class OrganizationalModelSerializer(CustomFieldModelSerializer): pass +class NestedGroupModelSerializer(CustomFieldModelSerializer): + _depth = serializers.IntegerField(source='level', read_only=True) + + class WritableNestedSerializer(serializers.ModelSerializer): """ Returns a nested representation of an object on read, but accepts only a primary key on write. diff --git a/netbox/secrets/api/serializers.py b/netbox/secrets/api/serializers.py index 627685de0..207836c4c 100644 --- a/netbox/secrets/api/serializers.py +++ b/netbox/secrets/api/serializers.py @@ -2,7 +2,7 @@ from django.contrib.contenttypes.models import ContentType from drf_yasg.utils import swagger_serializer_method from rest_framework import serializers -from extras.api.customfields import CustomFieldModelSerializer +from netbox.api.serializers import CustomFieldModelSerializer from extras.api.serializers import TaggedObjectSerializer from secrets.constants import SECRET_ASSIGNMENT_MODELS from secrets.models import Secret, SecretRole diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index d301ee3fe..c701e6b3b 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -1,8 +1,7 @@ from rest_framework import serializers -from extras.api.customfields import CustomFieldModelSerializer +from netbox.api.serializers import CustomFieldModelSerializer, NestedGroupModelSerializer from extras.api.serializers import TaggedObjectSerializer -from netbox.api import ValidatedModelSerializer from tenancy.models import Tenant, TenantGroup from .nested_serializers import * @@ -11,11 +10,10 @@ from .nested_serializers import * # Tenants # -class TenantGroupSerializer(CustomFieldModelSerializer): +class TenantGroupSerializer(NestedGroupModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='tenancy-api:tenantgroup-detail') parent = NestedTenantGroupSerializer(required=False, allow_null=True) tenant_count = serializers.IntegerField(read_only=True) - _depth = serializers.IntegerField(source='level', read_only=True) class Meta: model = TenantGroup diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index b8ccb99b0..e3b82e40a 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -3,7 +3,7 @@ from rest_framework import serializers from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer from dcim.choices import InterfaceModeChoices -from extras.api.customfields import CustomFieldModelSerializer +from netbox.api.serializers import CustomFieldModelSerializer from extras.api.serializers import TaggedObjectSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer from ipam.models import VLAN From cf7830757724c18c42f827e8db560a16146e4703 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 26 Feb 2021 11:25:23 -0500 Subject: [PATCH 131/302] Update organizational models to use custom field forms --- netbox/circuits/forms.py | 2 +- netbox/dcim/forms.py | 24 ++++++++++++------------ netbox/ipam/forms.py | 16 ++++++++-------- netbox/secrets/forms.py | 4 ++-- netbox/tenancy/forms.py | 4 ++-- netbox/virtualization/forms.py | 8 ++++---- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 18d49d08e..2e66f99f1 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -141,7 +141,7 @@ class CircuitTypeForm(BootstrapMixin, CustomFieldModelForm): ] -class CircuitTypeCSVForm(CSVModelForm): +class CircuitTypeCSVForm(CustomFieldModelCSVForm): slug = SlugField() class Meta: diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 3428dec57..01704ff5a 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -173,7 +173,7 @@ class MACAddressField(forms.Field): # Regions # -class RegionForm(BootstrapMixin, forms.ModelForm): +class RegionForm(BootstrapMixin, CustomFieldModelForm): parent = DynamicModelChoiceField( queryset=Region.objects.all(), required=False @@ -187,7 +187,7 @@ class RegionForm(BootstrapMixin, forms.ModelForm): ) -class RegionCSVForm(CSVModelForm): +class RegionCSVForm(CustomFieldModelCSVForm): parent = CSVModelChoiceField( queryset=Region.objects.all(), required=False, @@ -360,7 +360,7 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): # Rack groups # -class RackGroupForm(BootstrapMixin, forms.ModelForm): +class RackGroupForm(BootstrapMixin, CustomFieldModelForm): region = DynamicModelChoiceField( queryset=Region.objects.all(), required=False, @@ -390,7 +390,7 @@ class RackGroupForm(BootstrapMixin, forms.ModelForm): ) -class RackGroupCSVForm(CSVModelForm): +class RackGroupCSVForm(CustomFieldModelCSVForm): site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', @@ -440,7 +440,7 @@ class RackGroupFilterForm(BootstrapMixin, forms.Form): # Rack roles # -class RackRoleForm(BootstrapMixin, forms.ModelForm): +class RackRoleForm(BootstrapMixin, CustomFieldModelForm): slug = SlugField() class Meta: @@ -450,7 +450,7 @@ class RackRoleForm(BootstrapMixin, forms.ModelForm): ] -class RackRoleCSVForm(CSVModelForm): +class RackRoleCSVForm(CustomFieldModelCSVForm): slug = SlugField() class Meta: @@ -913,7 +913,7 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm): # Manufacturers # -class ManufacturerForm(BootstrapMixin, forms.ModelForm): +class ManufacturerForm(BootstrapMixin, CustomFieldModelForm): slug = SlugField() class Meta: @@ -923,7 +923,7 @@ class ManufacturerForm(BootstrapMixin, forms.ModelForm): ] -class ManufacturerCSVForm(CSVModelForm): +class ManufacturerCSVForm(CustomFieldModelCSVForm): class Meta: model = Manufacturer @@ -1705,7 +1705,7 @@ class DeviceBayTemplateImportForm(ComponentTemplateImportForm): # Device roles # -class DeviceRoleForm(BootstrapMixin, forms.ModelForm): +class DeviceRoleForm(BootstrapMixin, CustomFieldModelForm): slug = SlugField() class Meta: @@ -1715,7 +1715,7 @@ class DeviceRoleForm(BootstrapMixin, forms.ModelForm): ] -class DeviceRoleCSVForm(CSVModelForm): +class DeviceRoleCSVForm(CustomFieldModelCSVForm): slug = SlugField() class Meta: @@ -1730,7 +1730,7 @@ class DeviceRoleCSVForm(CSVModelForm): # Platforms # -class PlatformForm(BootstrapMixin, forms.ModelForm): +class PlatformForm(BootstrapMixin, CustomFieldModelForm): manufacturer = DynamicModelChoiceField( queryset=Manufacturer.objects.all(), required=False @@ -1749,7 +1749,7 @@ class PlatformForm(BootstrapMixin, forms.ModelForm): } -class PlatformCSVForm(CSVModelForm): +class PlatformCSVForm(CustomFieldModelCSVForm): slug = SlugField() manufacturer = CSVModelChoiceField( queryset=Manufacturer.objects.all(), diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 7e45c30b3..bf3fd1491 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -8,8 +8,8 @@ from extras.models import Tag from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( - add_blank_choice, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField, CSVModelChoiceField, CSVModelForm, - DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, NumericArrayField, + add_blank_choice, BootstrapMixin, BulkEditNullBooleanSelect, CSVChoiceField, CSVModelChoiceField, DatePicker, + DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableIPAddressField, NumericArrayField, ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import Cluster, VirtualMachine, VMInterface @@ -195,7 +195,7 @@ class RouteTargetFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilter # RIRs # -class RIRForm(BootstrapMixin, forms.ModelForm): +class RIRForm(BootstrapMixin, CustomFieldModelForm): slug = SlugField() class Meta: @@ -205,7 +205,7 @@ class RIRForm(BootstrapMixin, forms.ModelForm): ] -class RIRCSVForm(CSVModelForm): +class RIRCSVForm(CustomFieldModelCSVForm): slug = SlugField() class Meta: @@ -332,7 +332,7 @@ class AggregateFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo # Roles # -class RoleForm(BootstrapMixin, forms.ModelForm): +class RoleForm(BootstrapMixin, CustomFieldModelForm): slug = SlugField() class Meta: @@ -342,7 +342,7 @@ class RoleForm(BootstrapMixin, forms.ModelForm): ] -class RoleCSVForm(CSVModelForm): +class RoleCSVForm(CustomFieldModelCSVForm): slug = SlugField() class Meta: @@ -1081,7 +1081,7 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo # VLAN groups # -class VLANGroupForm(BootstrapMixin, forms.ModelForm): +class VLANGroupForm(BootstrapMixin, CustomFieldModelForm): region = DynamicModelChoiceField( queryset=Region.objects.all(), required=False, @@ -1105,7 +1105,7 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm): ] -class VLANGroupCSVForm(CSVModelForm): +class VLANGroupCSVForm(CustomFieldModelCSVForm): site = CSVModelChoiceField( queryset=Site.objects.all(), required=False, diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index cdd843e2d..2aa3243ee 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -43,7 +43,7 @@ def validate_rsa_key(key, is_secret=True): # Secret roles # -class SecretRoleForm(BootstrapMixin, forms.ModelForm): +class SecretRoleForm(BootstrapMixin, CustomFieldModelForm): slug = SlugField() class Meta: @@ -51,7 +51,7 @@ class SecretRoleForm(BootstrapMixin, forms.ModelForm): fields = ('name', 'slug', 'description') -class SecretRoleCSVForm(CSVModelForm): +class SecretRoleCSVForm(CustomFieldModelCSVForm): slug = SlugField() class Meta: diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index 97e72bb02..1d57f5da8 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -15,7 +15,7 @@ from .models import Tenant, TenantGroup # Tenant groups # -class TenantGroupForm(BootstrapMixin, forms.ModelForm): +class TenantGroupForm(BootstrapMixin, CustomFieldModelForm): parent = DynamicModelChoiceField( queryset=TenantGroup.objects.all(), required=False @@ -29,7 +29,7 @@ class TenantGroupForm(BootstrapMixin, forms.ModelForm): ] -class TenantGroupCSVForm(CSVModelForm): +class TenantGroupCSVForm(CustomFieldModelCSVForm): parent = CSVModelChoiceField( queryset=TenantGroup.objects.all(), required=False, diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index addfb05cd..c68f2db44 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -27,7 +27,7 @@ from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterf # Cluster types # -class ClusterTypeForm(BootstrapMixin, forms.ModelForm): +class ClusterTypeForm(BootstrapMixin, CustomFieldModelForm): slug = SlugField() class Meta: @@ -37,7 +37,7 @@ class ClusterTypeForm(BootstrapMixin, forms.ModelForm): ] -class ClusterTypeCSVForm(CSVModelForm): +class ClusterTypeCSVForm(CustomFieldModelCSVForm): slug = SlugField() class Meta: @@ -49,7 +49,7 @@ class ClusterTypeCSVForm(CSVModelForm): # Cluster groups # -class ClusterGroupForm(BootstrapMixin, forms.ModelForm): +class ClusterGroupForm(BootstrapMixin, CustomFieldModelForm): slug = SlugField() class Meta: @@ -59,7 +59,7 @@ class ClusterGroupForm(BootstrapMixin, forms.ModelForm): ] -class ClusterGroupCSVForm(CSVModelForm): +class ClusterGroupCSVForm(CustomFieldModelCSVForm): slug = SlugField() class Meta: From 7e6cb9d18696eb312954796cda22f19fd1b51c7c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 26 Feb 2021 16:12:52 -0500 Subject: [PATCH 132/302] Closes #1638: Migrate all primary keys to 64-bit integers --- docs/release-notes/index.md | 2 +- docs/release-notes/version-2.11.md | 9 + .../migrations/0025_standardize_models.py | 8 +- .../migrations/0123_standardize_models.py | 288 +----------------- .../dcim/models/device_component_templates.py | 4 +- netbox/dcim/models/device_components.py | 4 +- .../migrations/0054_standardize_models.py | 8 +- netbox/extras/models/models.py | 4 +- netbox/extras/models/tags.py | 4 +- .../migrations/0044_standardize_models.py | 18 +- netbox/netbox/models.py | 54 ++-- .../migrations/0013_standardize_models.py | 8 +- .../migrations/0012_standardize_models.py | 8 +- .../migrations/0011_standardize_models.py | 7 +- netbox/users/models.py | 2 +- .../migrations/0020_standardize_models.py | 28 +- netbox/virtualization/models.py | 4 +- 17 files changed, 72 insertions(+), 388 deletions(-) create mode 100644 docs/release-notes/version-2.11.md diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index 8990f83e0..f7f6f36e9 120000 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -1 +1 @@ -version-2.10.md \ No newline at end of file +version-2.11.md \ No newline at end of file diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md new file mode 100644 index 000000000..4daeab8bc --- /dev/null +++ b/docs/release-notes/version-2.11.md @@ -0,0 +1,9 @@ +# NetBox v2.11 + +## v2.11-beta1 (FUTURE) + +**WARNING:** This is a beta release and is not suitable for production use. It is intended for development and evaluation purposes only. No upgrade path to the final v2.11 release will be provided from this beta, and users should assume that all data entered into the application will be lost. + +### Other Changes + +* [#1638](https://github.com/netbox-community/netbox/issues/1638) - Migrate all primary keys to 64-bit integers diff --git a/netbox/circuits/migrations/0025_standardize_models.py b/netbox/circuits/migrations/0025_standardize_models.py index 2b1d2664e..f5d348ff3 100644 --- a/netbox/circuits/migrations/0025_standardize_models.py +++ b/netbox/circuits/migrations/0025_standardize_models.py @@ -1,4 +1,5 @@ -import django.core.serializers.json +# Generated by Django 3.2b1 on 2021-02-26 21:11 + from django.db import migrations, models @@ -9,11 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='circuittype', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), migrations.AlterField( model_name='circuit', name='id', diff --git a/netbox/dcim/migrations/0123_standardize_models.py b/netbox/dcim/migrations/0123_standardize_models.py index 5050e1a26..e9af8c464 100644 --- a/netbox/dcim/migrations/0123_standardize_models.py +++ b/netbox/dcim/migrations/0123_standardize_models.py @@ -1,4 +1,5 @@ -import django.core.serializers.json +# Generated by Django 3.2b1 on 2021-02-26 21:11 + from django.db import migrations, models @@ -9,291 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='consoleport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='consoleport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='consoleport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='consoleporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='consoleporttemplate', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='consoleporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='consoleserverport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='consoleserverport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='consoleserverport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='consoleserverporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='devicebay', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='devicebay', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='devicebay', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='devicebaytemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='devicerole', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='frontport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='frontport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='frontport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='frontporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='frontporttemplate', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='frontporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='interface', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='interface', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='interface', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='interfacetemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='interfacetemplate', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='interfacetemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='inventoryitem', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='inventoryitem', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='inventoryitem', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='manufacturer', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='platform', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='poweroutlet', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='poweroutlet', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='poweroutlet', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='poweroutlettemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='powerport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='powerport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='powerport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='powerporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='powerporttemplate', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='powerporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='rackgroup', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rackrole', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rearport', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='rearport', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rearport', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='rearporttemplate', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='rearporttemplate', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='rearporttemplate', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), - migrations.AddField( - model_name='region', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), migrations.AlterField( model_name='cable', name='id', diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 7b718936d..d250227b7 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -6,7 +6,7 @@ from dcim.choices import * from dcim.constants import * from extras.models import ObjectChange from extras.utils import extras_features -from netbox.models import PrimaryModel +from netbox.models import BigIDModel from utilities.fields import NaturalOrderingField from utilities.querysets import RestrictedQuerySet from utilities.ordering import naturalize_interface @@ -28,7 +28,7 @@ __all__ = ( ) -class ComponentTemplateModel(PrimaryModel): +class ComponentTemplateModel(BigIDModel): device_type = models.ForeignKey( to='dcim.DeviceType', on_delete=models.CASCADE, diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index f78363ba9..01d2712a7 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -13,7 +13,7 @@ from dcim.constants import * from dcim.fields import MACAddressField from extras.models import ObjectChange, TaggedItem from extras.utils import extras_features -from netbox.models import PrimaryModel +from netbox.models import BigIDModel from utilities.fields import NaturalOrderingField from utilities.mptt import TreeManager from utilities.ordering import naturalize_interface @@ -38,7 +38,7 @@ __all__ = ( ) -class ComponentModel(PrimaryModel): +class ComponentModel(BigIDModel): """ An abstract model inherited by any model which has a parent Device. """ diff --git a/netbox/extras/migrations/0054_standardize_models.py b/netbox/extras/migrations/0054_standardize_models.py index a836f365f..b6c95e77d 100644 --- a/netbox/extras/migrations/0054_standardize_models.py +++ b/netbox/extras/migrations/0054_standardize_models.py @@ -1,4 +1,5 @@ -import django.core.serializers.json +# Generated by Django 3.2b1 on 2021-02-26 21:11 + from django.db import migrations, models @@ -9,11 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='configcontext', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), migrations.AlterField( model_name='configcontext', name='id', diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index d60a2cc96..bf0dfc873 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -16,7 +16,7 @@ from extras.choices import * from extras.constants import * from extras.querysets import ConfigContextQuerySet from extras.utils import extras_features, FeatureQuery, image_upload -from netbox.models import BigIDModel, PrimaryModel +from netbox.models import BigIDModel, ChangeLoggingMixin from utilities.querysets import RestrictedQuerySet from utilities.utils import deepmerge, render_jinja2 @@ -361,7 +361,7 @@ class ImageAttachment(BigIDModel): # Config contexts # -class ConfigContext(PrimaryModel): +class ConfigContext(ChangeLoggingMixin, BigIDModel): """ A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B diff --git a/netbox/extras/models/tags.py b/netbox/extras/models/tags.py index 22e9815a9..3a5b539b1 100644 --- a/netbox/extras/models/tags.py +++ b/netbox/extras/models/tags.py @@ -2,7 +2,7 @@ from django.db import models from django.utils.text import slugify from taggit.models import TagBase, GenericTaggedItemBase -from netbox.models import BigIDModel, CoreModel +from netbox.models import BigIDModel, ChangeLoggingMixin from utilities.choices import ColorChoices from utilities.fields import ColorField from utilities.querysets import RestrictedQuerySet @@ -12,7 +12,7 @@ from utilities.querysets import RestrictedQuerySet # Tags # -class Tag(TagBase, CoreModel): +class Tag(ChangeLoggingMixin, BigIDModel, TagBase): color = ColorField( default=ColorChoices.COLOR_GREY ) diff --git a/netbox/ipam/migrations/0044_standardize_models.py b/netbox/ipam/migrations/0044_standardize_models.py index 2762c9973..a4ad77053 100644 --- a/netbox/ipam/migrations/0044_standardize_models.py +++ b/netbox/ipam/migrations/0044_standardize_models.py @@ -1,4 +1,5 @@ -import django.core.serializers.json +# Generated by Django 3.2b1 on 2021-02-26 21:11 + from django.db import migrations, models @@ -9,21 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='rir', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='role', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='vlangroup', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), migrations.AlterField( model_name='aggregate', name='id', diff --git a/netbox/netbox/models.py b/netbox/netbox/models.py index c0b0ce7ae..ad11dd18f 100644 --- a/netbox/netbox/models.py +++ b/netbox/netbox/models.py @@ -16,23 +16,13 @@ __all__ = ( ) -class BigIDModel(models.Model): - """ - Abstract base model for all Schematic data objects. Ensures the use of a 64-bit PK. - """ - id = models.BigAutoField( - primary_key=True - ) +# +# Mixins +# - class Meta: - abstract = True - - -class CoreModel(BigIDModel): +class ChangeLoggingMixin(models.Model): """ - Base class for all core objects. Provides the following: - - Change logging - - Custom field support + Provides change logging support. """ created = models.DateField( auto_now_add=True, @@ -62,9 +52,9 @@ class CoreModel(BigIDModel): ) -class PrimaryModel(CoreModel): +class CustomFieldsMixin(models.Model): """ - Primary models represent real objects within the infrastructure being modeled. + Provides support for custom fields. """ custom_field_data = models.JSONField( encoder=DjangoJSONEncoder, @@ -114,7 +104,33 @@ class PrimaryModel(CoreModel): raise ValidationError(f"Missing required custom field '{cf.name}'.") -class NestedGroupModel(PrimaryModel, MPTTModel): +# +# Base model classes + +class BigIDModel(models.Model): + """ + Abstract base model for all data objects. Ensures the use of a 64-bit PK. + """ + id = models.BigAutoField( + primary_key=True + ) + + class Meta: + abstract = True + + +class PrimaryModel(ChangeLoggingMixin, CustomFieldsMixin, BigIDModel): + """ + Primary models represent real objects within the infrastructure being modeled. + """ + # TODO + # tags = TaggableManager(through=TaggedItem) + + class Meta: + abstract = True + + +class NestedGroupModel(ChangeLoggingMixin, BigIDModel, MPTTModel): """ Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest recursively using MPTT. Within each parent, each child instance must have a unique name. @@ -157,7 +173,7 @@ class NestedGroupModel(PrimaryModel, MPTTModel): ) -class OrganizationalModel(PrimaryModel): +class OrganizationalModel(ChangeLoggingMixin, BigIDModel): """ Organizational models are those which are used solely to categorize and qualify other objects, and do not convey any real information about the infrastructure being modeled (for example, functional device roles). Organizational diff --git a/netbox/secrets/migrations/0013_standardize_models.py b/netbox/secrets/migrations/0013_standardize_models.py index 9de8dec95..ad18536dc 100644 --- a/netbox/secrets/migrations/0013_standardize_models.py +++ b/netbox/secrets/migrations/0013_standardize_models.py @@ -1,4 +1,5 @@ -import django.core.serializers.json +# Generated by Django 3.2b1 on 2021-02-26 21:11 + from django.db import migrations, models @@ -9,11 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='secretrole', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), migrations.AlterField( model_name='secret', name='id', diff --git a/netbox/tenancy/migrations/0012_standardize_models.py b/netbox/tenancy/migrations/0012_standardize_models.py index 7ce55cf42..6a277fa7c 100644 --- a/netbox/tenancy/migrations/0012_standardize_models.py +++ b/netbox/tenancy/migrations/0012_standardize_models.py @@ -1,4 +1,5 @@ -import django.core.serializers.json +# Generated by Django 3.2b1 on 2021-02-26 21:11 + from django.db import migrations, models @@ -9,11 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='tenantgroup', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), migrations.AlterField( model_name='tenant', name='id', diff --git a/netbox/users/migrations/0011_standardize_models.py b/netbox/users/migrations/0011_standardize_models.py index abfbd8702..3c514dd2a 100644 --- a/netbox/users/migrations/0011_standardize_models.py +++ b/netbox/users/migrations/0011_standardize_models.py @@ -1,3 +1,5 @@ +# Generated by Django 3.2b1 on 2021-02-26 21:11 + from django.db import migrations, models @@ -18,9 +20,4 @@ class Migration(migrations.Migration): name='id', field=models.BigAutoField(primary_key=True, serialize=False), ), - migrations.AlterField( - model_name='userconfig', - name='id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), ] diff --git a/netbox/users/models.py b/netbox/users/models.py index 529732e65..00e18148c 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -51,7 +51,7 @@ class AdminUser(User): # User preferences # -class UserConfig(BigIDModel): +class UserConfig(models.Model): """ This model stores arbitrary user-specific preferences in a JSON data structure. """ diff --git a/netbox/virtualization/migrations/0020_standardize_models.py b/netbox/virtualization/migrations/0020_standardize_models.py index 15585100d..932403698 100644 --- a/netbox/virtualization/migrations/0020_standardize_models.py +++ b/netbox/virtualization/migrations/0020_standardize_models.py @@ -1,4 +1,5 @@ -import django.core.serializers.json +# Generated by Django 3.2b1 on 2021-02-26 21:11 + from django.db import migrations, models @@ -9,31 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='clustergroup', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='clustertype', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='vminterface', - name='created', - field=models.DateField(auto_now_add=True, null=True), - ), - migrations.AddField( - model_name='vminterface', - name='custom_field_data', - field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), - ), - migrations.AddField( - model_name='vminterface', - name='last_updated', - field=models.DateTimeField(auto_now=True, null=True), - ), migrations.AlterField( model_name='cluster', name='id', diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index a6a63ab3b..364dde30b 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -9,7 +9,7 @@ from dcim.models import BaseInterface, Device from extras.models import ConfigContextModel, ObjectChange, TaggedItem from extras.querysets import ConfigContextModelQuerySet from extras.utils import extras_features -from netbox.models import OrganizationalModel, PrimaryModel +from netbox.models import BigIDModel, OrganizationalModel, PrimaryModel from utilities.fields import NaturalOrderingField from utilities.ordering import naturalize_interface from utilities.query_functions import CollateAsChar @@ -374,7 +374,7 @@ class VirtualMachine(PrimaryModel, ConfigContextModel): # @extras_features('export_templates', 'webhooks') -class VMInterface(PrimaryModel, BaseInterface): +class VMInterface(BigIDModel, BaseInterface): virtual_machine = models.ForeignKey( to='virtualization.VirtualMachine', on_delete=models.CASCADE, From 1dcd857ca6d287d5c14d94e0c1a849283d8a0cbd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 26 Feb 2021 16:25:37 -0500 Subject: [PATCH 133/302] Closes #5370: Extend custom field support to organizational models --- docs/release-notes/version-2.11.md | 4 +++ .../migrations/0025_standardize_models.py | 8 +++-- .../migrations/0123_standardize_models.py | 33 +++++++++++++++++-- .../migrations/0054_standardize_models.py | 2 -- .../migrations/0044_standardize_models.py | 18 ++++++++-- netbox/netbox/models.py | 4 +-- .../migrations/0013_standardize_models.py | 8 +++-- .../migrations/0012_standardize_models.py | 8 +++-- .../migrations/0011_standardize_models.py | 2 -- .../migrations/0020_standardize_models.py | 13 ++++++-- 10 files changed, 82 insertions(+), 18 deletions(-) diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index 4daeab8bc..4c12ed40a 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -4,6 +4,10 @@ **WARNING:** This is a beta release and is not suitable for production use. It is intended for development and evaluation purposes only. No upgrade path to the final v2.11 release will be provided from this beta, and users should assume that all data entered into the application will be lost. +### Enhancements + +* [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models + ### Other Changes * [#1638](https://github.com/netbox-community/netbox/issues/1638) - Migrate all primary keys to 64-bit integers diff --git a/netbox/circuits/migrations/0025_standardize_models.py b/netbox/circuits/migrations/0025_standardize_models.py index f5d348ff3..2b1d2664e 100644 --- a/netbox/circuits/migrations/0025_standardize_models.py +++ b/netbox/circuits/migrations/0025_standardize_models.py @@ -1,5 +1,4 @@ -# Generated by Django 3.2b1 on 2021-02-26 21:11 - +import django.core.serializers.json from django.db import migrations, models @@ -10,6 +9,11 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='circuittype', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), migrations.AlterField( model_name='circuit', name='id', diff --git a/netbox/dcim/migrations/0123_standardize_models.py b/netbox/dcim/migrations/0123_standardize_models.py index e9af8c464..4a6fc87b6 100644 --- a/netbox/dcim/migrations/0123_standardize_models.py +++ b/netbox/dcim/migrations/0123_standardize_models.py @@ -1,5 +1,4 @@ -# Generated by Django 3.2b1 on 2021-02-26 21:11 - +import django.core.serializers.json from django.db import migrations, models @@ -10,6 +9,36 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='devicerole', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + migrations.AddField( + model_name='manufacturer', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + migrations.AddField( + model_name='platform', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + migrations.AddField( + model_name='rackgroup', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + migrations.AddField( + model_name='rackrole', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + migrations.AddField( + model_name='region', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), migrations.AlterField( model_name='cable', name='id', diff --git a/netbox/extras/migrations/0054_standardize_models.py b/netbox/extras/migrations/0054_standardize_models.py index b6c95e77d..c73043345 100644 --- a/netbox/extras/migrations/0054_standardize_models.py +++ b/netbox/extras/migrations/0054_standardize_models.py @@ -1,5 +1,3 @@ -# Generated by Django 3.2b1 on 2021-02-26 21:11 - from django.db import migrations, models diff --git a/netbox/ipam/migrations/0044_standardize_models.py b/netbox/ipam/migrations/0044_standardize_models.py index a4ad77053..2762c9973 100644 --- a/netbox/ipam/migrations/0044_standardize_models.py +++ b/netbox/ipam/migrations/0044_standardize_models.py @@ -1,5 +1,4 @@ -# Generated by Django 3.2b1 on 2021-02-26 21:11 - +import django.core.serializers.json from django.db import migrations, models @@ -10,6 +9,21 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='rir', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + migrations.AddField( + model_name='role', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + migrations.AddField( + model_name='vlangroup', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), migrations.AlterField( model_name='aggregate', name='id', diff --git a/netbox/netbox/models.py b/netbox/netbox/models.py index ad11dd18f..74d018420 100644 --- a/netbox/netbox/models.py +++ b/netbox/netbox/models.py @@ -130,7 +130,7 @@ class PrimaryModel(ChangeLoggingMixin, CustomFieldsMixin, BigIDModel): abstract = True -class NestedGroupModel(ChangeLoggingMixin, BigIDModel, MPTTModel): +class NestedGroupModel(ChangeLoggingMixin, CustomFieldsMixin, BigIDModel, MPTTModel): """ Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest recursively using MPTT. Within each parent, each child instance must have a unique name. @@ -173,7 +173,7 @@ class NestedGroupModel(ChangeLoggingMixin, BigIDModel, MPTTModel): ) -class OrganizationalModel(ChangeLoggingMixin, BigIDModel): +class OrganizationalModel(ChangeLoggingMixin, CustomFieldsMixin, BigIDModel): """ Organizational models are those which are used solely to categorize and qualify other objects, and do not convey any real information about the infrastructure being modeled (for example, functional device roles). Organizational diff --git a/netbox/secrets/migrations/0013_standardize_models.py b/netbox/secrets/migrations/0013_standardize_models.py index ad18536dc..9de8dec95 100644 --- a/netbox/secrets/migrations/0013_standardize_models.py +++ b/netbox/secrets/migrations/0013_standardize_models.py @@ -1,5 +1,4 @@ -# Generated by Django 3.2b1 on 2021-02-26 21:11 - +import django.core.serializers.json from django.db import migrations, models @@ -10,6 +9,11 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='secretrole', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), migrations.AlterField( model_name='secret', name='id', diff --git a/netbox/tenancy/migrations/0012_standardize_models.py b/netbox/tenancy/migrations/0012_standardize_models.py index 6a277fa7c..7ce55cf42 100644 --- a/netbox/tenancy/migrations/0012_standardize_models.py +++ b/netbox/tenancy/migrations/0012_standardize_models.py @@ -1,5 +1,4 @@ -# Generated by Django 3.2b1 on 2021-02-26 21:11 - +import django.core.serializers.json from django.db import migrations, models @@ -10,6 +9,11 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='tenantgroup', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), migrations.AlterField( model_name='tenant', name='id', diff --git a/netbox/users/migrations/0011_standardize_models.py b/netbox/users/migrations/0011_standardize_models.py index 3c514dd2a..08d1103ed 100644 --- a/netbox/users/migrations/0011_standardize_models.py +++ b/netbox/users/migrations/0011_standardize_models.py @@ -1,5 +1,3 @@ -# Generated by Django 3.2b1 on 2021-02-26 21:11 - from django.db import migrations, models diff --git a/netbox/virtualization/migrations/0020_standardize_models.py b/netbox/virtualization/migrations/0020_standardize_models.py index 932403698..8ccb3df0d 100644 --- a/netbox/virtualization/migrations/0020_standardize_models.py +++ b/netbox/virtualization/migrations/0020_standardize_models.py @@ -1,5 +1,4 @@ -# Generated by Django 3.2b1 on 2021-02-26 21:11 - +import django.core.serializers.json from django.db import migrations, models @@ -10,6 +9,16 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='clustergroup', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), + migrations.AddField( + model_name='clustertype', + name='custom_field_data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), + ), migrations.AlterField( model_name='cluster', name='id', From 6a9b50f95db660415af4f94fe7bfc4923b104aff Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 26 Feb 2021 17:23:23 -0500 Subject: [PATCH 134/302] Closes #5873: Use numeric IDs in all object URLs --- docs/release-notes/version-2.11.md | 1 + netbox/circuits/models.py | 2 +- netbox/circuits/tables.py | 7 +++-- netbox/circuits/urls.py | 14 +++++----- netbox/dcim/models/sites.py | 2 +- netbox/dcim/tables/devices.py | 4 +-- netbox/dcim/tables/devicetypes.py | 2 +- netbox/dcim/tables/power.py | 5 ++-- netbox/dcim/tables/racks.py | 6 ++--- netbox/dcim/urls.py | 26 +++++++++---------- netbox/extras/tables.py | 2 +- netbox/extras/urls.py | 6 ++--- netbox/ipam/tables.py | 18 ++++++------- netbox/ipam/urls.py | 12 ++++----- netbox/secrets/tables.py | 2 +- netbox/secrets/urls.py | 6 ++--- netbox/templates/circuits/circuit.html | 2 +- .../circuits/inc/circuit_termination.html | 2 +- netbox/templates/circuits/provider.html | 2 +- netbox/templates/dcim/device.html | 4 +-- netbox/templates/dcim/devicetype.html | 4 +-- netbox/templates/dcim/rack.html | 2 +- netbox/templates/dcim/rackreservation.html | 2 +- netbox/templates/dcim/site.html | 2 +- netbox/templates/extras/tag.html | 2 +- netbox/templates/ipam/prefix.html | 2 +- netbox/templates/ipam/vlan.html | 2 +- netbox/templates/ipam/vlangroup_vlans.html | 2 +- netbox/templates/tenancy/tenant.html | 2 +- netbox/tenancy/models.py | 2 +- netbox/tenancy/tables.py | 4 +-- netbox/tenancy/urls.py | 14 +++++----- netbox/utilities/tables.py | 10 +++---- netbox/utilities/templatetags/buttons.py | 22 +++------------- netbox/utilities/testing/views.py | 10 +------ netbox/virtualization/tables.py | 4 +-- netbox/virtualization/urls.py | 12 ++++----- 37 files changed, 97 insertions(+), 126 deletions(-) diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index 4c12ed40a..1dcf49759 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -11,3 +11,4 @@ ### Other Changes * [#1638](https://github.com/netbox-community/netbox/issues/1638) - Migrate all primary keys to 64-bit integers +* [#5873](https://github.com/netbox-community/netbox/issues/5873) - Use numeric IDs in all object URLs diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index e371df547..693fe5ae6 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -79,7 +79,7 @@ class Provider(PrimaryModel): return self.name def get_absolute_url(self): - return reverse('circuits:provider', args=[self.slug]) + return reverse('circuits:provider', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index 782b02394..e4b859d3c 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -39,7 +39,7 @@ class CircuitTypeTable(BaseTable): circuit_count = tables.Column( verbose_name='Circuits' ) - actions = ButtonsColumn(CircuitType, pk_field='slug') + actions = ButtonsColumn(CircuitType) class Meta(BaseTable.Meta): model = CircuitType @@ -56,9 +56,8 @@ class CircuitTable(BaseTable): cid = tables.LinkColumn( verbose_name='ID' ) - provider = tables.LinkColumn( - viewname='circuits:provider', - args=[Accessor('provider__slug')] + provider = tables.Column( + linkify=True ) status = ChoiceFieldColumn() tenant = tables.TemplateColumn( diff --git a/netbox/circuits/urls.py b/netbox/circuits/urls.py index d757fd90d..a8306efa8 100644 --- a/netbox/circuits/urls.py +++ b/netbox/circuits/urls.py @@ -14,19 +14,19 @@ urlpatterns = [ path('providers/import/', views.ProviderBulkImportView.as_view(), name='provider_import'), path('providers/edit/', views.ProviderBulkEditView.as_view(), name='provider_bulk_edit'), path('providers/delete/', views.ProviderBulkDeleteView.as_view(), name='provider_bulk_delete'), - path('providers//', views.ProviderView.as_view(), name='provider'), - path('providers//edit/', views.ProviderEditView.as_view(), name='provider_edit'), - path('providers//delete/', views.ProviderDeleteView.as_view(), name='provider_delete'), - path('providers//changelog/', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}), + path('providers//', views.ProviderView.as_view(), name='provider'), + path('providers//edit/', views.ProviderEditView.as_view(), name='provider_edit'), + path('providers//delete/', views.ProviderDeleteView.as_view(), name='provider_delete'), + path('providers//changelog/', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}), # Circuit types path('circuit-types/', views.CircuitTypeListView.as_view(), name='circuittype_list'), path('circuit-types/add/', views.CircuitTypeEditView.as_view(), name='circuittype_add'), path('circuit-types/import/', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'), path('circuit-types/delete/', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'), - path('circuit-types//edit/', views.CircuitTypeEditView.as_view(), name='circuittype_edit'), - path('circuit-types//delete/', views.CircuitTypeDeleteView.as_view(), name='circuittype_delete'), - path('circuit-types//changelog/', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}), + path('circuit-types//edit/', views.CircuitTypeEditView.as_view(), name='circuittype_edit'), + path('circuit-types//delete/', views.CircuitTypeDeleteView.as_view(), name='circuittype_delete'), + path('circuit-types//changelog/', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}), # Circuits path('circuits/', views.CircuitListView.as_view(), name='circuit_list'), diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index 92d8e1b26..417e0b914 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -192,7 +192,7 @@ class Site(PrimaryModel): return self.name def get_absolute_url(self): - return reverse('dcim:site', args=[self.slug]) + return reverse('dcim:site', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index edd9e7a43..20e04e7cc 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -61,7 +61,7 @@ class DeviceRoleTable(BaseTable): ) color = ColorColumn() vm_role = BooleanColumn() - actions = ButtonsColumn(DeviceRole, pk_field='slug') + actions = ButtonsColumn(DeviceRole) class Meta(BaseTable.Meta): model = DeviceRole @@ -85,7 +85,7 @@ class PlatformTable(BaseTable): url_params={'platform': 'slug'}, verbose_name='VMs' ) - actions = ButtonsColumn(Platform, pk_field='slug') + actions = ButtonsColumn(Platform) class Meta(BaseTable.Meta): model = Platform diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index c4d618cd7..c5b8bb70d 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -37,7 +37,7 @@ class ManufacturerTable(BaseTable): verbose_name='Platforms' ) slug = tables.Column() - actions = ButtonsColumn(Manufacturer, pk_field='slug') + actions = ButtonsColumn(Manufacturer) class Meta(BaseTable.Meta): model = Manufacturer diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index ae5c2a5c8..6d6e2541b 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -19,9 +19,8 @@ __all__ = ( class PowerPanelTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() - site = tables.LinkColumn( - viewname='dcim:site', - args=[Accessor('site__slug')] + site = tables.Column( + linkify=True ) powerfeed_count = LinkedCountColumn( viewname='dcim:powerfeed_list', diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py index 775e90076..1ac0d17bb 100644 --- a/netbox/dcim/tables/racks.py +++ b/netbox/dcim/tables/racks.py @@ -29,10 +29,8 @@ class RackGroupTable(BaseTable): orderable=False, attrs={'td': {'class': 'text-nowrap'}} ) - site = tables.LinkColumn( - viewname='dcim:site', - args=[Accessor('site__slug')], - verbose_name='Site' + site = tables.Column( + linkify=True ) rack_count = tables.Column( verbose_name='Racks' diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index d167ebdb7..6cf61df24 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -27,10 +27,10 @@ urlpatterns = [ path('sites/import/', views.SiteBulkImportView.as_view(), name='site_import'), path('sites/edit/', views.SiteBulkEditView.as_view(), name='site_bulk_edit'), path('sites/delete/', views.SiteBulkDeleteView.as_view(), name='site_bulk_delete'), - path('sites//', views.SiteView.as_view(), name='site'), - path('sites//edit/', views.SiteEditView.as_view(), name='site_edit'), - path('sites//delete/', views.SiteDeleteView.as_view(), name='site_delete'), - path('sites//changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}), + path('sites//', views.SiteView.as_view(), name='site'), + path('sites//edit/', views.SiteEditView.as_view(), name='site_edit'), + path('sites//delete/', views.SiteDeleteView.as_view(), name='site_delete'), + path('sites//changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}), path('sites//images/add/', ImageAttachmentEditView.as_view(), name='site_add_image', kwargs={'model': Site}), # Rack groups @@ -80,9 +80,9 @@ urlpatterns = [ path('manufacturers/add/', views.ManufacturerEditView.as_view(), name='manufacturer_add'), path('manufacturers/import/', views.ManufacturerBulkImportView.as_view(), name='manufacturer_import'), path('manufacturers/delete/', views.ManufacturerBulkDeleteView.as_view(), name='manufacturer_bulk_delete'), - path('manufacturers//edit/', views.ManufacturerEditView.as_view(), name='manufacturer_edit'), - path('manufacturers//delete/', views.ManufacturerDeleteView.as_view(), name='manufacturer_delete'), - path('manufacturers//changelog/', ObjectChangeLogView.as_view(), name='manufacturer_changelog', kwargs={'model': Manufacturer}), + path('manufacturers//edit/', views.ManufacturerEditView.as_view(), name='manufacturer_edit'), + path('manufacturers//delete/', views.ManufacturerDeleteView.as_view(), name='manufacturer_delete'), + path('manufacturers//changelog/', ObjectChangeLogView.as_view(), name='manufacturer_changelog', kwargs={'model': Manufacturer}), # Device types path('device-types/', views.DeviceTypeListView.as_view(), name='devicetype_list'), @@ -164,18 +164,18 @@ urlpatterns = [ path('device-roles/add/', views.DeviceRoleEditView.as_view(), name='devicerole_add'), path('device-roles/import/', views.DeviceRoleBulkImportView.as_view(), name='devicerole_import'), path('device-roles/delete/', views.DeviceRoleBulkDeleteView.as_view(), name='devicerole_bulk_delete'), - path('device-roles//edit/', views.DeviceRoleEditView.as_view(), name='devicerole_edit'), - path('device-roles//delete/', views.DeviceRoleDeleteView.as_view(), name='devicerole_delete'), - path('device-roles//changelog/', ObjectChangeLogView.as_view(), name='devicerole_changelog', kwargs={'model': DeviceRole}), + path('device-roles//edit/', views.DeviceRoleEditView.as_view(), name='devicerole_edit'), + path('device-roles//delete/', views.DeviceRoleDeleteView.as_view(), name='devicerole_delete'), + path('device-roles//changelog/', ObjectChangeLogView.as_view(), name='devicerole_changelog', kwargs={'model': DeviceRole}), # Platforms path('platforms/', views.PlatformListView.as_view(), name='platform_list'), path('platforms/add/', views.PlatformEditView.as_view(), name='platform_add'), path('platforms/import/', views.PlatformBulkImportView.as_view(), name='platform_import'), path('platforms/delete/', views.PlatformBulkDeleteView.as_view(), name='platform_bulk_delete'), - path('platforms//edit/', views.PlatformEditView.as_view(), name='platform_edit'), - path('platforms//delete/', views.PlatformDeleteView.as_view(), name='platform_delete'), - path('platforms//changelog/', ObjectChangeLogView.as_view(), name='platform_changelog', kwargs={'model': Platform}), + path('platforms//edit/', views.PlatformEditView.as_view(), name='platform_edit'), + path('platforms//delete/', views.PlatformDeleteView.as_view(), name='platform_delete'), + path('platforms//changelog/', ObjectChangeLogView.as_view(), name='platform_changelog', kwargs={'model': Platform}), # Devices path('devices/', views.DeviceListView.as_view(), name='device_list'), diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index cccb56c0a..7aeddb48f 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -37,7 +37,7 @@ OBJECTCHANGE_REQUEST_ID = """ class TagTable(BaseTable): pk = ToggleColumn() color = ColorColumn() - actions = ButtonsColumn(Tag, pk_field='slug') + actions = ButtonsColumn(Tag) class Meta(BaseTable.Meta): model = Tag diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index 36077dc7a..d2f0a2eb2 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -13,9 +13,9 @@ urlpatterns = [ path('tags/import/', views.TagBulkImportView.as_view(), name='tag_import'), path('tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'), path('tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'), - path('tags//edit/', views.TagEditView.as_view(), name='tag_edit'), - path('tags//delete/', views.TagDeleteView.as_view(), name='tag_delete'), - path('tags//changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}), + path('tags//edit/', views.TagEditView.as_view(), name='tag_edit'), + path('tags//delete/', views.TagDeleteView.as_view(), name='tag_delete'), + path('tags//changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}), # Config contexts path('config-contexts/', views.ConfigContextListView.as_view(), name='configcontext_list'), diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 86d7e21eb..8101a581d 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -111,9 +111,9 @@ VLAN_MEMBER_TAGGED = """ TENANT_LINK = """ {% if record.tenant %} - {{ record.tenant }} + {{ record.tenant }} {% elif record.vrf.tenant %} - {{ record.vrf.tenant }}* + {{ record.vrf.tenant }}* {% else %} — {% endif %} @@ -191,7 +191,7 @@ class RIRTable(BaseTable): url_params={'rir': 'slug'}, verbose_name='Aggregates' ) - actions = ButtonsColumn(RIR, pk_field='slug') + actions = ButtonsColumn(RIR) class Meta(BaseTable.Meta): model = RIR @@ -254,7 +254,7 @@ class RoleTable(BaseTable): url_params={'role': 'slug'}, verbose_name='VLANs' ) - actions = ButtonsColumn(Role, pk_field='slug') + actions = ButtonsColumn(Role) class Meta(BaseTable.Meta): model = Role @@ -444,9 +444,8 @@ class InterfaceIPAddressTable(BaseTable): class VLANGroupTable(BaseTable): pk = ToggleColumn() name = tables.Column(linkify=True) - site = tables.LinkColumn( - viewname='dcim:site', - args=[Accessor('site__slug')] + site = tables.Column( + linkify=True ) vlan_count = LinkedCountColumn( viewname='ipam:vlan_list', @@ -474,9 +473,8 @@ class VLANTable(BaseTable): template_code=VLAN_LINK, verbose_name='ID' ) - site = tables.LinkColumn( - viewname='dcim:site', - args=[Accessor('site__slug')] + site = tables.Column( + linkify=True ) group = tables.LinkColumn( viewname='ipam:vlangroup_vlans', diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index 9b0dc581b..db0eec157 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -34,9 +34,9 @@ urlpatterns = [ path('rirs/add/', views.RIREditView.as_view(), name='rir_add'), path('rirs/import/', views.RIRBulkImportView.as_view(), name='rir_import'), path('rirs/delete/', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'), - path('rirs//edit/', views.RIREditView.as_view(), name='rir_edit'), - path('rirs//delete/', views.RIRDeleteView.as_view(), name='rir_delete'), - path('vrfs//changelog/', ObjectChangeLogView.as_view(), name='rir_changelog', kwargs={'model': RIR}), + path('rirs//edit/', views.RIREditView.as_view(), name='rir_edit'), + path('rirs//delete/', views.RIRDeleteView.as_view(), name='rir_delete'), + path('rirs//changelog/', ObjectChangeLogView.as_view(), name='rir_changelog', kwargs={'model': RIR}), # Aggregates path('aggregates/', views.AggregateListView.as_view(), name='aggregate_list'), @@ -54,9 +54,9 @@ urlpatterns = [ path('roles/add/', views.RoleEditView.as_view(), name='role_add'), path('roles/import/', views.RoleBulkImportView.as_view(), name='role_import'), path('roles/delete/', views.RoleBulkDeleteView.as_view(), name='role_bulk_delete'), - path('roles//edit/', views.RoleEditView.as_view(), name='role_edit'), - path('roles//delete/', views.RoleDeleteView.as_view(), name='role_delete'), - path('roles//changelog/', ObjectChangeLogView.as_view(), name='role_changelog', kwargs={'model': Role}), + path('roles//edit/', views.RoleEditView.as_view(), name='role_edit'), + path('roles//delete/', views.RoleDeleteView.as_view(), name='role_delete'), + path('roles//changelog/', ObjectChangeLogView.as_view(), name='role_changelog', kwargs={'model': Role}), # Prefixes path('prefixes/', views.PrefixListView.as_view(), name='prefix_list'), diff --git a/netbox/secrets/tables.py b/netbox/secrets/tables.py index 0d8559a2b..dd91985ec 100644 --- a/netbox/secrets/tables.py +++ b/netbox/secrets/tables.py @@ -16,7 +16,7 @@ class SecretRoleTable(BaseTable): url_params={'role': 'slug'}, verbose_name='Secrets' ) - actions = ButtonsColumn(SecretRole, pk_field='slug') + actions = ButtonsColumn(SecretRole) class Meta(BaseTable.Meta): model = SecretRole diff --git a/netbox/secrets/urls.py b/netbox/secrets/urls.py index 9dbb5d044..62a285875 100644 --- a/netbox/secrets/urls.py +++ b/netbox/secrets/urls.py @@ -12,9 +12,9 @@ urlpatterns = [ path('secret-roles/add/', views.SecretRoleEditView.as_view(), name='secretrole_add'), path('secret-roles/import/', views.SecretRoleBulkImportView.as_view(), name='secretrole_import'), path('secret-roles/delete/', views.SecretRoleBulkDeleteView.as_view(), name='secretrole_bulk_delete'), - path('secret-roles//edit/', views.SecretRoleEditView.as_view(), name='secretrole_edit'), - path('secret-roles//delete/', views.SecretRoleDeleteView.as_view(), name='secretrole_delete'), - path('secret-roles//changelog/', ObjectChangeLogView.as_view(), name='secretrole_changelog', kwargs={'model': SecretRole}), + path('secret-roles//edit/', views.SecretRoleEditView.as_view(), name='secretrole_edit'), + path('secret-roles//delete/', views.SecretRoleDeleteView.as_view(), name='secretrole_delete'), + path('secret-roles//changelog/', ObjectChangeLogView.as_view(), name='secretrole_changelog', kwargs={'model': SecretRole}), # Secrets path('secrets/', views.SecretListView.as_view(), name='secret_list'), diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index c104b154b..47125da14 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -74,7 +74,7 @@ Provider - {{ object.provider }} + {{ object.provider }} diff --git a/netbox/templates/circuits/inc/circuit_termination.html b/netbox/templates/circuits/inc/circuit_termination.html index 477788931..e4854e5ab 100644 --- a/netbox/templates/circuits/inc/circuit_termination.html +++ b/netbox/templates/circuits/inc/circuit_termination.html @@ -32,7 +32,7 @@ {% if termination.site.region %} {{ termination.site.region }} / {% endif %} - {{ termination.site }} + {{ termination.site }} diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 8778c3ac2..7fa512ec6 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -51,7 +51,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 55be343ac..917384b98 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -23,7 +23,7 @@ {% if object.site.region %} {{ object.site.region }} / {% endif %} - {{ object.site }} + {{ object.site }} @@ -74,7 +74,7 @@ Device Type - {{ object.device_type.display_name }} ({{ object.device_type.u_height }}U) + {{ object.device_type.display_name }} ({{ object.device_type.u_height }}U) diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 6bc970174..110d42c04 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -55,10 +55,10 @@ {% clone_button object %} {% endif %} {% if perms.dcim.change_devicetype %} - {% edit_button object use_pk=True %} + {% edit_button object %} {% endif %} {% if perms.dcim.delete_devicetype %} - {% delete_button object use_pk=True %} + {% delete_button object %} {% endif %}

{{ object.manufacturer }} {{ object.model }}

diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 6a00308f3..9c0d72a04 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -85,7 +85,7 @@ {% if object.site.region %} {{ object.site.region }} / {% endif %} - {{ object.site }} + {{ object.site }} diff --git a/netbox/templates/dcim/rackreservation.html b/netbox/templates/dcim/rackreservation.html index 5273b2a39..4760f4192 100644 --- a/netbox/templates/dcim/rackreservation.html +++ b/netbox/templates/dcim/rackreservation.html @@ -68,7 +68,7 @@ {% if rack.site.region %} {{ rack.site.region }} / {% endif %} - {{ rack.site }} + {{ rack.site }} diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index a0e713fcf..0ffc7e265 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -56,7 +56,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/extras/tag.html b/netbox/templates/extras/tag.html index 2ad7cf814..2cee8f882 100644 --- a/netbox/templates/extras/tag.html +++ b/netbox/templates/extras/tag.html @@ -44,7 +44,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index 4b382636b..d195affb2 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -129,7 +129,7 @@ {% if object.site.region %} {{ object.site.region }} / {% endif %} - {{ object.site }} + {{ object.site }} {% else %} None {% endif %} diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index d2967ca56..52286e29d 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -81,7 +81,7 @@ {% if object.site.region %} {{ object.site.region }} / {% endif %} - {{ object.site }} + {{ object.site }} {% else %} None {% endif %} diff --git a/netbox/templates/ipam/vlangroup_vlans.html b/netbox/templates/ipam/vlangroup_vlans.html index 490b7ab2c..ffb75d3b8 100644 --- a/netbox/templates/ipam/vlangroup_vlans.html +++ b/netbox/templates/ipam/vlangroup_vlans.html @@ -8,7 +8,7 @@ diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index e2e66e6b2..00044f1c3 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -51,7 +51,7 @@ {% if perms.extras.view_objectchange %} {% endif %} diff --git a/netbox/tenancy/models.py b/netbox/tenancy/models.py index e83f4bdd2..757afb09e 100644 --- a/netbox/tenancy/models.py +++ b/netbox/tenancy/models.py @@ -105,7 +105,7 @@ class Tenant(PrimaryModel): return self.name def get_absolute_url(self): - return reverse('tenancy:tenant', args=[self.slug]) + return reverse('tenancy:tenant', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index 9e8be6b18..8872a7012 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -12,7 +12,7 @@ MPTT_LINK = """ COL_TENANT = """ {% if record.tenant %} - {{ record.tenant }} + {{ record.tenant }} {% else %} — {% endif %} @@ -35,7 +35,7 @@ class TenantGroupTable(BaseTable): url_params={'group': 'slug'}, verbose_name='Tenants' ) - actions = ButtonsColumn(TenantGroup, pk_field='slug') + actions = ButtonsColumn(TenantGroup) class Meta(BaseTable.Meta): model = TenantGroup diff --git a/netbox/tenancy/urls.py b/netbox/tenancy/urls.py index 372308bb8..c07df97a6 100644 --- a/netbox/tenancy/urls.py +++ b/netbox/tenancy/urls.py @@ -12,9 +12,9 @@ urlpatterns = [ path('tenant-groups/add/', views.TenantGroupEditView.as_view(), name='tenantgroup_add'), path('tenant-groups/import/', views.TenantGroupBulkImportView.as_view(), name='tenantgroup_import'), path('tenant-groups/delete/', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'), - path('tenant-groups//edit/', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'), - path('tenant-groups//delete/', views.TenantGroupDeleteView.as_view(), name='tenantgroup_delete'), - path('tenant-groups//changelog/', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}), + path('tenant-groups//edit/', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'), + path('tenant-groups//delete/', views.TenantGroupDeleteView.as_view(), name='tenantgroup_delete'), + path('tenant-groups//changelog/', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}), # Tenants path('tenants/', views.TenantListView.as_view(), name='tenant_list'), @@ -22,9 +22,9 @@ urlpatterns = [ path('tenants/import/', views.TenantBulkImportView.as_view(), name='tenant_import'), path('tenants/edit/', views.TenantBulkEditView.as_view(), name='tenant_bulk_edit'), path('tenants/delete/', views.TenantBulkDeleteView.as_view(), name='tenant_bulk_delete'), - path('tenants//', views.TenantView.as_view(), name='tenant'), - path('tenants//edit/', views.TenantEditView.as_view(), name='tenant_edit'), - path('tenants//delete/', views.TenantDeleteView.as_view(), name='tenant_delete'), - path('tenants//changelog/', ObjectChangeLogView.as_view(), name='tenant_changelog', kwargs={'model': Tenant}), + path('tenants//', views.TenantView.as_view(), name='tenant'), + path('tenants//edit/', views.TenantEditView.as_view(), name='tenant_edit'), + path('tenants//delete/', views.TenantDeleteView.as_view(), name='tenant_delete'), + path('tenants//changelog/', ObjectChangeLogView.as_view(), name='tenant_changelog', kwargs={'model': Tenant}), ] diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index bf087f2c9..f2f567783 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -147,24 +147,23 @@ class ButtonsColumn(tables.TemplateColumn): # Note that braces are escaped to allow for string formatting prior to template rendering template_code = """ {{% if "changelog" in buttons %}} - + {{% endif %}} {{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}} - + {{% endif %}} {{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}} - + {{% endif %}} """ - def __init__(self, model, *args, pk_field='pk', buttons=None, prepend_template=None, return_url_extra='', - **kwargs): + def __init__(self, model, *args, buttons=None, prepend_template=None, return_url_extra='', **kwargs): if prepend_template: prepend_template = prepend_template.replace('{', '{{') prepend_template = prepend_template.replace('}', '}}') @@ -173,7 +172,6 @@ class ButtonsColumn(tables.TemplateColumn): template_code = self.template_code.format( app_label=model._meta.app_label, model_name=model._meta.model_name, - pk_field=pk_field, buttons=buttons ) diff --git a/netbox/utilities/templatetags/buttons.py b/netbox/utilities/templatetags/buttons.py index e7f00cacf..c486dd2e5 100644 --- a/netbox/utilities/templatetags/buttons.py +++ b/netbox/utilities/templatetags/buttons.py @@ -40,16 +40,9 @@ def clone_button(instance): @register.inclusion_tag('buttons/edit.html') -def edit_button(instance, use_pk=False): +def edit_button(instance): viewname = _get_viewname(instance, 'edit') - - # Assign kwargs - if hasattr(instance, 'slug') and not use_pk: - kwargs = {'slug': instance.slug} - else: - kwargs = {'pk': instance.pk} - - url = reverse(viewname, kwargs=kwargs) + url = reverse(viewname, kwargs={'pk': instance.pk}) return { 'url': url, @@ -57,16 +50,9 @@ def edit_button(instance, use_pk=False): @register.inclusion_tag('buttons/delete.html') -def delete_button(instance, use_pk=False): +def delete_button(instance): viewname = _get_viewname(instance, 'delete') - - # Assign kwargs - if hasattr(instance, 'slug') and not use_pk: - kwargs = {'slug': instance.slug} - else: - kwargs = {'pk': instance.pk} - - url = reverse(viewname, kwargs=kwargs) + url = reverse(viewname, kwargs={'pk': instance.pk}) return { 'url': url, diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index d380996b7..89bac51cf 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -5,7 +5,7 @@ from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist from django.db.models import ManyToManyField from django.forms.models import model_to_dict from django.test import Client, TestCase as _TestCase, override_settings -from django.urls import reverse, NoReverseMatch +from django.urls import reverse from django.utils.text import slugify from netaddr import IPNetwork from taggit.managers import TaggableManager @@ -205,14 +205,6 @@ class ModelViewTestCase(ModelTestCase): if instance is None: return reverse(url_format.format(action)) - # Attempt to resolve using slug as the unique identifier if one exists - if hasattr(self.model, 'slug'): - try: - return reverse(url_format.format(action), kwargs={'slug': instance.slug}) - except NoReverseMatch: - pass - - # Default to using the numeric PK to retrieve the URL for an object return reverse(url_format.format(action), kwargs={'pk': instance.pk}) diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index 34a070623..808832224 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -36,7 +36,7 @@ class ClusterTypeTable(BaseTable): cluster_count = tables.Column( verbose_name='Clusters' ) - actions = ButtonsColumn(ClusterType, pk_field='slug') + actions = ButtonsColumn(ClusterType) class Meta(BaseTable.Meta): model = ClusterType @@ -54,7 +54,7 @@ class ClusterGroupTable(BaseTable): cluster_count = tables.Column( verbose_name='Clusters' ) - actions = ButtonsColumn(ClusterGroup, pk_field='slug') + actions = ButtonsColumn(ClusterGroup) class Meta(BaseTable.Meta): model = ClusterGroup diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index 3d6f07566..703f99b65 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -13,18 +13,18 @@ urlpatterns = [ path('cluster-types/add/', views.ClusterTypeEditView.as_view(), name='clustertype_add'), path('cluster-types/import/', views.ClusterTypeBulkImportView.as_view(), name='clustertype_import'), path('cluster-types/delete/', views.ClusterTypeBulkDeleteView.as_view(), name='clustertype_bulk_delete'), - path('cluster-types//edit/', views.ClusterTypeEditView.as_view(), name='clustertype_edit'), - path('cluster-types//delete/', views.ClusterTypeDeleteView.as_view(), name='clustertype_delete'), - path('cluster-types//changelog/', ObjectChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}), + path('cluster-types//edit/', views.ClusterTypeEditView.as_view(), name='clustertype_edit'), + path('cluster-types//delete/', views.ClusterTypeDeleteView.as_view(), name='clustertype_delete'), + path('cluster-types//changelog/', ObjectChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}), # Cluster groups path('cluster-groups/', views.ClusterGroupListView.as_view(), name='clustergroup_list'), path('cluster-groups/add/', views.ClusterGroupEditView.as_view(), name='clustergroup_add'), path('cluster-groups/import/', views.ClusterGroupBulkImportView.as_view(), name='clustergroup_import'), path('cluster-groups/delete/', views.ClusterGroupBulkDeleteView.as_view(), name='clustergroup_bulk_delete'), - path('cluster-groups//edit/', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'), - path('cluster-groups//delete/', views.ClusterGroupDeleteView.as_view(), name='clustergroup_delete'), - path('cluster-groups//changelog/', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}), + path('cluster-groups//edit/', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'), + path('cluster-groups//delete/', views.ClusterGroupDeleteView.as_view(), name='clustergroup_delete'), + path('cluster-groups//changelog/', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}), # Clusters path('clusters/', views.ClusterListView.as_view(), name='cluster_list'), From cfddf570b90d692201284ed16e60b5cfdf97cbf8 Mon Sep 17 00:00:00 2001 From: Julian Jacobi Date: Mon, 1 Mar 2021 09:27:21 +0100 Subject: [PATCH 135/302] add custom links to device components --- netbox/dcim/models/device_components.py | 18 +++++++++--------- netbox/templates/dcim/device_component.html | 4 ++++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 452aacb56..40063234f 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -198,7 +198,7 @@ class PathEndpoint(models.Model): # Console ports # -@extras_features('export_templates', 'webhooks') +@extras_features('export_templates', 'webhooks', 'custom_links') class ConsolePort(CableTermination, PathEndpoint, ComponentModel): """ A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. @@ -234,7 +234,7 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel): # Console server ports # -@extras_features('webhooks') +@extras_features('webhooks', 'custom_links') class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel): """ A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. @@ -270,7 +270,7 @@ class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel): # Power ports # -@extras_features('export_templates', 'webhooks') +@extras_features('export_templates', 'webhooks', 'custom_links') class PowerPort(CableTermination, PathEndpoint, ComponentModel): """ A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. @@ -379,7 +379,7 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel): # Power outlets # -@extras_features('webhooks') +@extras_features('webhooks', 'custom_links') class PowerOutlet(CableTermination, PathEndpoint, ComponentModel): """ A physical power outlet (output) within a Device which provides power to a PowerPort. @@ -479,7 +479,7 @@ class BaseInterface(models.Model): return super().save(*args, **kwargs) -@extras_features('export_templates', 'webhooks') +@extras_features('export_templates', 'webhooks', 'custom_links') class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface): """ A network interface within a Device. A physical Interface can connect to exactly one other Interface. @@ -624,7 +624,7 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface): # Pass-through ports # -@extras_features('webhooks') +@extras_features('webhooks', 'custom_links') class FrontPort(CableTermination, ComponentModel): """ A pass-through port on the front of a Device. @@ -687,7 +687,7 @@ class FrontPort(CableTermination, ComponentModel): }) -@extras_features('webhooks') +@extras_features('webhooks', 'custom_links') class RearPort(CableTermination, ComponentModel): """ A pass-through port on the rear of a Device. @@ -740,7 +740,7 @@ class RearPort(CableTermination, ComponentModel): # Device bays # -@extras_features('webhooks') +@extras_features('webhooks', 'custom_links') class DeviceBay(ComponentModel): """ An empty space within a Device which can house a child device @@ -800,7 +800,7 @@ class DeviceBay(ComponentModel): # Inventory items # -@extras_features('export_templates', 'webhooks') +@extras_features('export_templates', 'webhooks', 'custom_links') class InventoryItem(MPTTModel, ComponentModel): """ An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply. diff --git a/netbox/templates/dcim/device_component.html b/netbox/templates/dcim/device_component.html index c0ce453b3..234d10fdd 100644 --- a/netbox/templates/dcim/device_component.html +++ b/netbox/templates/dcim/device_component.html @@ -1,6 +1,7 @@ {% extends 'base.html' %} {% load helpers %} {% load perms %} +{% load custom_links %} {% load plugins %} {% block header %} @@ -30,6 +31,9 @@ {% endif %}

{% block title %}{{ object.device }} / {{ object }}{% endblock %}

+
+ {% custom_links object %} +