From ea1467add7ce308fc3203468cf0262e5cd75b2e0 Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 22 Aug 2022 09:24:52 -0700 Subject: [PATCH 01/21] fix for #10086 - change capitalization on wireless link table for Interface A, B and Auth Type --- netbox/wireless/models.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index 0540e9c45..fe3cdc1e2 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -23,7 +23,8 @@ class WirelessAuthenticationBase(models.Model): auth_type = models.CharField( max_length=50, choices=WirelessAuthTypeChoices, - blank=True + blank=True, + verbose_name="Auth Type", ) auth_cipher = models.CharField( max_length=50, @@ -134,13 +135,15 @@ class WirelessLink(WirelessAuthenticationBase, NetBoxModel): to='dcim.Interface', limit_choices_to={'type__in': WIRELESS_IFACE_TYPES}, on_delete=models.PROTECT, - related_name='+' + related_name='+', + verbose_name="Interface A", ) interface_b = models.ForeignKey( to='dcim.Interface', limit_choices_to={'type__in': WIRELESS_IFACE_TYPES}, on_delete=models.PROTECT, - related_name='+' + related_name='+', + verbose_name="Interface B", ) ssid = models.CharField( max_length=SSID_MAX_LENGTH, From 0c7c61b685560cfa886ff10fbb18853a27ad7587 Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 22 Aug 2022 14:56:31 -0700 Subject: [PATCH 02/21] #10037 add Child Interface to context menu --- netbox/dcim/tables/template_code.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index 3403f9392..62a189b63 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -238,6 +238,9 @@ INTERFACE_BUTTONS = """ {% if perms.dcim.add_inventoryitem %}
  • Inventory Item
  • {% endif %} + {% if perms.dcim.add_interface %} +
  • Child Interface
  • + {% endif %} {% endif %} From 9fddd193b92b4cb5b035502a9f8d5dc60fadf49d Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 22 Aug 2022 16:31:41 -0700 Subject: [PATCH 03/21] #10094 fix Contact AddAnother --- netbox/netbox/views/generic/object_views.py | 4 ++++ netbox/tenancy/views.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 5ff0cfdff..433e70b63 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -327,6 +327,9 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView): """ return obj + def get_extra_addanother_params(self, request, params: dict): + return params + # # Request handlers # @@ -399,6 +402,7 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView): # If cloning is supported, pre-populate a new instance of the form params = prepare_cloned_fields(obj) + params = self.get_extra_addanother_params(request, params) if params: if 'return_url' in request.GET: params['return_url'] = request.GET.get('return_url') diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 9a2fe6ab9..8b0f90f88 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -1,4 +1,5 @@ from django.contrib.contenttypes.models import ContentType +from django.http import QueryDict from django.shortcuts import get_object_or_404 from circuits.models import Circuit @@ -365,6 +366,15 @@ class ContactAssignmentEditView(generic.ObjectEditView): instance.object = get_object_or_404(content_type.model_class(), pk=request.GET.get('object_id')) return instance + def get_extra_addanother_params(self, request, params: dict): + if not params: + params = QueryDict(mutable=True) + + params['content_type'] = request.GET.get('content_type') + params['object_id'] = request.GET.get('object_id') + + return params + class ContactAssignmentDeleteView(generic.ObjectDeleteView): queryset = ContactAssignment.objects.all() From 41499b189c29bf9c7b07b3d7601637312b13d141 Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 22 Aug 2022 16:33:50 -0700 Subject: [PATCH 04/21] #10094 fix Contact AddAnother --- netbox/utilities/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 1dece76c8..69ab615fc 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -285,7 +285,7 @@ def prepare_cloned_fields(instance): """ # Generate the clone attributes from the instance if not hasattr(instance, 'clone'): - return QueryDict() + return QueryDict(mutable=True) attrs = instance.clone() # Prepare querydict parameters From f48aaf1c465d0048606f486d9530631a33920a65 Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 22 Aug 2022 16:47:40 -0700 Subject: [PATCH 05/21] #10094 fix Contact AddAnother --- netbox/netbox/views/generic/object_views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 433e70b63..89f52e475 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -327,7 +327,10 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView): """ return obj - def get_extra_addanother_params(self, request, params: dict): + def get_extra_addanother_params(self, request, params): + """ + Return a QueryDict of extra params to use on the Add Another button. + """ return params # From c11ca543e2f31723da7ce29c7062a4f99339398f Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 23 Aug 2022 09:16:48 -0700 Subject: [PATCH 06/21] #10037 default type to virtual --- netbox/dcim/tables/template_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index 62a189b63..8c23f327c 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -239,7 +239,7 @@ INTERFACE_BUTTONS = """
  • Inventory Item
  • {% endif %} {% if perms.dcim.add_interface %} -
  • Child Interface
  • +
  • Child Interface
  • {% endif %} From 8b1a462a6070cb6054af8bb59589c9a2e785afc2 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 23 Aug 2022 09:29:55 -0700 Subject: [PATCH 07/21] #10094 changes from code review --- netbox/netbox/views/generic/object_views.py | 6 +++--- netbox/tenancy/views.py | 13 +++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 89f52e475..6ef88bb2f 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -329,9 +329,9 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView): def get_extra_addanother_params(self, request, params): """ - Return a QueryDict of extra params to use on the Add Another button. + Return a dictionary of extra parameters to use on the Add Another button. """ - return params + return {} # # Request handlers @@ -405,7 +405,7 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView): # If cloning is supported, pre-populate a new instance of the form params = prepare_cloned_fields(obj) - params = self.get_extra_addanother_params(request, params) + params.update(self.get_extra_addanother_params(request)) if params: if 'return_url' in request.GET: params['return_url'] = request.GET.get('return_url') diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 8b0f90f88..e582c15d1 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -366,14 +366,11 @@ class ContactAssignmentEditView(generic.ObjectEditView): instance.object = get_object_or_404(content_type.model_class(), pk=request.GET.get('object_id')) return instance - def get_extra_addanother_params(self, request, params: dict): - if not params: - params = QueryDict(mutable=True) - - params['content_type'] = request.GET.get('content_type') - params['object_id'] = request.GET.get('object_id') - - return params + def get_extra_addanother_params(self, request): + return { + 'content_type': request.GET.get('content_type'), + 'object_id': request.GET.get('object_id'), + } class ContactAssignmentDeleteView(generic.ObjectDeleteView): From 374abe52149c5804c605a781aee73b9542f6ac36 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 23 Aug 2022 10:34:06 -0700 Subject: [PATCH 08/21] #10033 disable Add a Termination button if 2 terminations on L2VPN P2P --- netbox/ipam/models/l2vpn.py | 6 ++++++ netbox/templates/ipam/l2vpn.html | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/netbox/ipam/models/l2vpn.py b/netbox/ipam/models/l2vpn.py index 0e948b18e..db6f47924 100644 --- a/netbox/ipam/models/l2vpn.py +++ b/netbox/ipam/models/l2vpn.py @@ -67,6 +67,12 @@ class L2VPN(NetBoxModel): def get_absolute_url(self): return reverse('ipam:l2vpn', args=[self.pk]) + def can_add_termination(self): + if self.type in L2VPNTypeChoices.P2P and self.terminations.count() >= 2: + return False + else: + return True + class L2VPNTermination(NetBoxModel): l2vpn = models.ForeignKey( diff --git a/netbox/templates/ipam/l2vpn.html b/netbox/templates/ipam/l2vpn.html index 44a1da818..32013400b 100644 --- a/netbox/templates/ipam/l2vpn.html +++ b/netbox/templates/ipam/l2vpn.html @@ -59,7 +59,7 @@ {% if perms.ipam.add_l2vpntermination %} From 439cf1a30874bffce105e0d0dea05b416f603487 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 23 Aug 2022 16:17:40 -0700 Subject: [PATCH 09/21] #10033 changes from code review --- netbox/ipam/models/l2vpn.py | 2 ++ netbox/templates/ipam/l2vpn.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox/ipam/models/l2vpn.py b/netbox/ipam/models/l2vpn.py index db6f47924..ab29ab048 100644 --- a/netbox/ipam/models/l2vpn.py +++ b/netbox/ipam/models/l2vpn.py @@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse +from django.utils.functional import cached_property from ipam.choices import L2VPNTypeChoices from ipam.constants import L2VPN_ASSIGNMENT_MODELS @@ -67,6 +68,7 @@ class L2VPN(NetBoxModel): def get_absolute_url(self): return reverse('ipam:l2vpn', args=[self.pk]) + @cached_property def can_add_termination(self): if self.type in L2VPNTypeChoices.P2P and self.terminations.count() >= 2: return False diff --git a/netbox/templates/ipam/l2vpn.html b/netbox/templates/ipam/l2vpn.html index 32013400b..c19363d33 100644 --- a/netbox/templates/ipam/l2vpn.html +++ b/netbox/templates/ipam/l2vpn.html @@ -59,7 +59,7 @@ {% if perms.ipam.add_l2vpntermination %} From 1c46102c4a672079e4e61f33d5405ccb505fe54f Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 23 Aug 2022 16:19:43 -0700 Subject: [PATCH 10/21] #10094 changes from code review --- netbox/netbox/views/generic/object_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 6ef88bb2f..ece299f21 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -327,7 +327,7 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView): """ return obj - def get_extra_addanother_params(self, request, params): + def get_extra_addanother_params(self, request): """ Return a dictionary of extra parameters to use on the Add Another button. """ From 18d5576997d029f3c67c323e3e88912e08d1f542 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 Aug 2022 08:59:40 -0400 Subject: [PATCH 11/21] Changelog for #10033, #10037, #10094 --- docs/release-notes/version-3.3.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 7ed635bc2..4d7c482e5 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -5,6 +5,8 @@ ### Enhancements * [#6454](https://github.com/netbox-community/netbox/issues/6454) - Include contextual help when creating first objects in UI +* [#10033](https://github.com/netbox-community/netbox/issues/10033) - Disable "add termination" button for point-to-point L2VPNs with two terminations +* [#10037](https://github.com/netbox-community/netbox/issues/10037) - Add link to create child interface to interface context menu * [#10061](https://github.com/netbox-community/netbox/issues/10061) - Replicate type when cloning L2VPN instances * [#10066](https://github.com/netbox-community/netbox/issues/10066) - Use fixed column widths for custom field values in UI @@ -16,6 +18,7 @@ * [#10057](https://github.com/netbox-community/netbox/issues/10057) - Fix AttributeError exception when global search results include rack reservations * [#10059](https://github.com/netbox-community/netbox/issues/10059) - Add identifier column to L2VPN table * [#10089](https://github.com/netbox-community/netbox/issues/10089) - `linkify` template filter should escape object representation +* [#10094](https://github.com/netbox-community/netbox/issues/10094) - Fix 404 when using "create and add another" to add contact assignments * [#10108](https://github.com/netbox-community/netbox/issues/10108) - Linkify inside NAT IPs for primary device IPs in UI * [#10109](https://github.com/netbox-community/netbox/issues/10109) - Fix available prefixes calculation for container prefixes in the global table * [#10111](https://github.com/netbox-community/netbox/issues/10111) - Wrap search QS to catch ValueError on identifier field From 36729fb6aec9ee856945a1ac5d48b75df8aa7178 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 Aug 2022 13:08:21 -0400 Subject: [PATCH 12/21] Fixes #10134: Custom fields data serializer should return a 400 response for invalid data --- docs/release-notes/version-3.3.md | 1 + netbox/extras/api/customfields.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 4d7c482e5..e23438478 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -22,6 +22,7 @@ * [#10108](https://github.com/netbox-community/netbox/issues/10108) - Linkify inside NAT IPs for primary device IPs in UI * [#10109](https://github.com/netbox-community/netbox/issues/10109) - Fix available prefixes calculation for container prefixes in the global table * [#10111](https://github.com/netbox-community/netbox/issues/10111) - Wrap search QS to catch ValueError on identifier field +* [#10134](https://github.com/netbox-community/netbox/issues/10134) - Custom fields data serializer should return a 400 response for invalid data --- diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index b7fd1e129..cb35b4e73 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -1,5 +1,6 @@ from django.contrib.contenttypes.models import ContentType from rest_framework.fields import Field +from rest_framework.serializers import ValidationError from extras.choices import CustomFieldTypeChoices from extras.models import CustomField @@ -62,6 +63,12 @@ class CustomFieldsDataField(Field): return data def to_internal_value(self, data): + if type(data) is not dict: + raise ValidationError( + "Invalid data format. Custom field data must be passed as a dictionary mapping field names to their " + "values." + ) + # If updating an existing instance, start with existing custom_field_data if self.parent.instance: data = {**self.parent.instance.custom_field_data, **data} From c2c8bd0a761dde32bc2438abf6c05fd27fab58c3 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 Aug 2022 13:25:54 -0400 Subject: [PATCH 13/21] Closes #10133: Enable nullifying device location during bulk edit --- docs/release-notes/version-3.3.md | 1 + netbox/dcim/forms/bulk_edit.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index e23438478..5c4c8c654 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -9,6 +9,7 @@ * [#10037](https://github.com/netbox-community/netbox/issues/10037) - Add link to create child interface to interface context menu * [#10061](https://github.com/netbox-community/netbox/issues/10061) - Replicate type when cloning L2VPN instances * [#10066](https://github.com/netbox-community/netbox/issues/10066) - Use fixed column widths for custom field values in UI +* [#10133](https://github.com/netbox-community/netbox/issues/10133) - Enable nullifying device location during bulk edit ### Bug Fixes diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 8f765ae9b..396f7e59b 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -480,7 +480,7 @@ class DeviceBulkEditForm(NetBoxModelBulkEditForm): ('Hardware', ('manufacturer', 'device_type', 'airflow', 'serial')), ) nullable_fields = ( - 'tenant', 'platform', 'serial', 'airflow', + 'location', 'tenant', 'platform', 'serial', 'airflow', ) From 2baf06e012032ea0915a32df13d953acc5bf8500 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 Aug 2022 14:46:42 -0400 Subject: [PATCH 14/21] Add unique slugs to L2VPNs in relevant tests --- netbox/ipam/tests/test_api.py | 6 +++--- netbox/ipam/tests/test_filtersets.py | 6 +++--- netbox/ipam/tests/test_models.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 3fef04194..4c07e0a90 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -973,9 +973,9 @@ class L2VPNTerminationTest(APIViewTestCases.APIViewTestCase): VLAN.objects.bulk_create(vlans) l2vpns = ( - L2VPN(name='L2VPN 1', type='vxlan', identifier=650001), - L2VPN(name='L2VPN 2', type='vpws', identifier=650002), - L2VPN(name='L2VPN 3', type='vpls'), # No RD + L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001), + L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002), + L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD ) L2VPN.objects.bulk_create(l2vpns) diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index 081f6e11d..5c4113786 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -1485,9 +1485,9 @@ class L2VPNTestCase(TestCase, ChangeLoggedFilterSetTests): RouteTarget.objects.bulk_create(route_targets) l2vpns = ( - L2VPN(name='L2VPN 1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=65001), - L2VPN(name='L2VPN 2', type=L2VPNTypeChoices.TYPE_VPWS, identifier=65002), - L2VPN(name='L2VPN 3', type=L2VPNTypeChoices.TYPE_VPLS), + L2VPN(name='L2VPN 1', slug='l2vpn-1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=65001), + L2VPN(name='L2VPN 2', slug='l2vpn-2', type=L2VPNTypeChoices.TYPE_VPWS, identifier=65002), + L2VPN(name='L2VPN 3', slug='l2vpn-3', type=L2VPNTypeChoices.TYPE_VPLS), ) L2VPN.objects.bulk_create(l2vpns) l2vpns[0].import_targets.add(route_targets[0]) diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py index 3bd7e8ccb..94a315be5 100644 --- a/netbox/ipam/tests/test_models.py +++ b/netbox/ipam/tests/test_models.py @@ -581,9 +581,9 @@ class TestL2VPNTermination(TestCase): VLAN.objects.bulk_create(vlans) l2vpns = ( - L2VPN(name='L2VPN 1', type='vxlan', identifier=650001), - L2VPN(name='L2VPN 2', type='vpws', identifier=650002), - L2VPN(name='L2VPN 3', type='vpls'), # No RD + L2VPN(name='L2VPN 1', slug='l2vpn-1', type='vxlan', identifier=650001), + L2VPN(name='L2VPN 2', slug='l2vpn-2', type='vpws', identifier=650002), + L2VPN(name='L2VPN 3', slug='l2vpn-3', type='vpls'), # No RD ) L2VPN.objects.bulk_create(l2vpns) From bfbf97aec9119539f7f42cf16f52d0ca8203ba60 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 Aug 2022 15:49:36 -0400 Subject: [PATCH 15/21] Closes #10031: Enforce 'application/json' content type for REST API requests --- docs/release-notes/version-3.3.md | 1 + netbox/ipam/tests/test_api.py | 4 ++-- netbox/netbox/settings.py | 4 +++- netbox/users/tests/test_api.py | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 5c4c8c654..765944950 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -5,6 +5,7 @@ ### Enhancements * [#6454](https://github.com/netbox-community/netbox/issues/6454) - Include contextual help when creating first objects in UI +* [#10031](https://github.com/netbox-community/netbox/issues/10031) - Enforce `application/json` content type for REST API requests * [#10033](https://github.com/netbox-community/netbox/issues/10033) - Disable "add termination" button for point-to-point L2VPNs with two terminations * [#10037](https://github.com/netbox-community/netbox/issues/10037) - Add link to create child interface to interface context menu * [#10061](https://github.com/netbox-community/netbox/issues/10061) - Replicate type when cloning L2VPN instances diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 4c07e0a90..5dc708cd0 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -390,7 +390,7 @@ class PrefixTest(APIViewTestCases.APIViewTestCase): self.assertEqual(response.data['description'], data['description']) # Try to create one more IP - response = self.client.post(url, {}, **self.header) + response = self.client.post(url, {}, format='json', **self.header) self.assertHttpStatus(response, status.HTTP_409_CONFLICT) self.assertIn('detail', response.data) @@ -487,7 +487,7 @@ class IPRangeTest(APIViewTestCases.APIViewTestCase): self.assertEqual(response.data['description'], data['description']) # Try to create one more IP - response = self.client.post(url, {}, **self.header) + response = self.client.post(url, {}, format='json', **self.header) self.assertHttpStatus(response, status.HTTP_409_CONFLICT) self.assertIn('detail', response.data) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 0edce8f69..4438d338b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -533,6 +533,9 @@ REST_FRAMEWORK = { ), 'DEFAULT_METADATA_CLASS': 'netbox.api.metadata.BulkOperationMetadata', 'DEFAULT_PAGINATION_CLASS': 'netbox.api.pagination.OptionalLimitOffsetPagination', + 'DEFAULT_PARSER_CLASSES': ( + 'rest_framework.parsers.JSONParser', + ), 'DEFAULT_PERMISSION_CLASSES': ( 'netbox.api.authentication.TokenPermissions', ), @@ -542,7 +545,6 @@ REST_FRAMEWORK = { ), 'DEFAULT_VERSION': REST_FRAMEWORK_VERSION, 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', - # 'PAGE_SIZE': PAGINATE_COUNT, 'SCHEMA_COERCE_METHOD_NAMES': { # Default mappings 'retrieve': 'read', diff --git a/netbox/users/tests/test_api.py b/netbox/users/tests/test_api.py index bcfc9cf14..a0bf8a49e 100644 --- a/netbox/users/tests/test_api.py +++ b/netbox/users/tests/test_api.py @@ -124,7 +124,7 @@ class TokenTest( user = User.objects.create_user(**data) url = reverse('users-api:token_provision') - response = self.client.post(url, **self.header, data=data) + response = self.client.post(url, data, format='json', **self.header) self.assertEqual(response.status_code, 201) self.assertIn('key', response.data) self.assertEqual(len(response.data['key']), 40) @@ -141,7 +141,7 @@ class TokenTest( } url = reverse('users-api:token_provision') - response = self.client.post(url, **self.header, data=data) + response = self.client.post(url, data, format='json', **self.header) self.assertEqual(response.status_code, 403) From eb2bf3469ec2b28e687c2dbfb35191bd256f80bb Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 24 Aug 2022 13:36:38 -0700 Subject: [PATCH 16/21] #9935 add new wireless choices for interfaces (#10116) * #9935 add new wireless choices for interfaces * #9935 add new wireless interfaces to constants * #9935 oops - remove login.html changes --- netbox/dcim/choices.py | 4 +++ netbox/dcim/constants.py | 3 +++ ...alter_wirelesslink_interface_a_and_more.py | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 netbox/wireless/migrations/0005_alter_wirelesslink_interface_a_and_more.py diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 79049384a..019ae09a4 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -790,7 +790,9 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_80211AC = 'ieee802.11ac' TYPE_80211AD = 'ieee802.11ad' TYPE_80211AX = 'ieee802.11ax' + TYPE_80211AY = 'ieee802.11ay' TYPE_802151 = 'ieee802.15.1' + TYPE_OTHER_WIRELESS = 'other-wireless' # Cellular TYPE_GSM = 'gsm' @@ -918,7 +920,9 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_80211AC, 'IEEE 802.11ac'), (TYPE_80211AD, 'IEEE 802.11ad'), (TYPE_80211AX, 'IEEE 802.11ax'), + (TYPE_80211AY, 'IEEE 802.11ay'), (TYPE_802151, 'IEEE 802.15.1 (Bluetooth)'), + (TYPE_OTHER_WIRELESS, 'Other (Wireless)'), ) ), ( diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 9e41ed113..80d7558c9 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -45,6 +45,9 @@ WIRELESS_IFACE_TYPES = [ InterfaceTypeChoices.TYPE_80211AC, InterfaceTypeChoices.TYPE_80211AD, InterfaceTypeChoices.TYPE_80211AX, + InterfaceTypeChoices.TYPE_80211AY, + InterfaceTypeChoices.TYPE_802151, + InterfaceTypeChoices.TYPE_OTHER_WIRELESS, ] NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES diff --git a/netbox/wireless/migrations/0005_alter_wirelesslink_interface_a_and_more.py b/netbox/wireless/migrations/0005_alter_wirelesslink_interface_a_and_more.py new file mode 100644 index 000000000..64e375e43 --- /dev/null +++ b/netbox/wireless/migrations/0005_alter_wirelesslink_interface_a_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.7 on 2022-08-24 17:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0161_cabling_cleanup'), + ('wireless', '0004_wireless_tenancy'), + ] + + operations = [ + migrations.AlterField( + model_name='wirelesslink', + name='interface_a', + field=models.ForeignKey(limit_choices_to={'type__in': ['ieee802.11a', 'ieee802.11g', 'ieee802.11n', 'ieee802.11ac', 'ieee802.11ad', 'ieee802.11ax', 'ieee802.11ay', 'ieee802.15.1', 'other-wireless']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dcim.interface'), + ), + migrations.AlterField( + model_name='wirelesslink', + name='interface_b', + field=models.ForeignKey(limit_choices_to={'type__in': ['ieee802.11a', 'ieee802.11g', 'ieee802.11n', 'ieee802.11ac', 'ieee802.11ad', 'ieee802.11ax', 'ieee802.11ay', 'ieee802.15.1', 'other-wireless']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dcim.interface'), + ), + ] From f70ef7a585d142813850c14853ff2cba122cb31f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 Aug 2022 16:44:24 -0400 Subject: [PATCH 17/21] Changelog and cleanup for #9935 --- docs/release-notes/version-3.3.md | 1 + ...alter_wirelesslink_interface_a_and_more.py | 25 ------------------- .../0005_wirelesslink_interface_types.py | 24 ++++++++++++++++++ netbox/wireless/models.py | 10 ++++++-- 4 files changed, 33 insertions(+), 27 deletions(-) delete mode 100644 netbox/wireless/migrations/0005_alter_wirelesslink_interface_a_and_more.py create mode 100644 netbox/wireless/migrations/0005_wirelesslink_interface_types.py diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 765944950..1421bb2c7 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -5,6 +5,7 @@ ### Enhancements * [#6454](https://github.com/netbox-community/netbox/issues/6454) - Include contextual help when creating first objects in UI +* [#9935](https://github.com/netbox-community/netbox/issues/9935) - Add 802.11ay and "other" wireless interface types * [#10031](https://github.com/netbox-community/netbox/issues/10031) - Enforce `application/json` content type for REST API requests * [#10033](https://github.com/netbox-community/netbox/issues/10033) - Disable "add termination" button for point-to-point L2VPNs with two terminations * [#10037](https://github.com/netbox-community/netbox/issues/10037) - Add link to create child interface to interface context menu diff --git a/netbox/wireless/migrations/0005_alter_wirelesslink_interface_a_and_more.py b/netbox/wireless/migrations/0005_alter_wirelesslink_interface_a_and_more.py deleted file mode 100644 index 64e375e43..000000000 --- a/netbox/wireless/migrations/0005_alter_wirelesslink_interface_a_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.0.7 on 2022-08-24 17:18 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0161_cabling_cleanup'), - ('wireless', '0004_wireless_tenancy'), - ] - - operations = [ - migrations.AlterField( - model_name='wirelesslink', - name='interface_a', - field=models.ForeignKey(limit_choices_to={'type__in': ['ieee802.11a', 'ieee802.11g', 'ieee802.11n', 'ieee802.11ac', 'ieee802.11ad', 'ieee802.11ax', 'ieee802.11ay', 'ieee802.15.1', 'other-wireless']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dcim.interface'), - ), - migrations.AlterField( - model_name='wirelesslink', - name='interface_b', - field=models.ForeignKey(limit_choices_to={'type__in': ['ieee802.11a', 'ieee802.11g', 'ieee802.11n', 'ieee802.11ac', 'ieee802.11ad', 'ieee802.11ax', 'ieee802.11ay', 'ieee802.15.1', 'other-wireless']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dcim.interface'), - ), - ] diff --git a/netbox/wireless/migrations/0005_wirelesslink_interface_types.py b/netbox/wireless/migrations/0005_wirelesslink_interface_types.py new file mode 100644 index 000000000..0b3f88c5b --- /dev/null +++ b/netbox/wireless/migrations/0005_wirelesslink_interface_types.py @@ -0,0 +1,24 @@ +from django.db import migrations, models +import django.db.models.deletion +import wireless.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0161_cabling_cleanup'), + ('wireless', '0004_wireless_tenancy'), + ] + + operations = [ + migrations.AlterField( + model_name='wirelesslink', + name='interface_a', + field=models.ForeignKey(limit_choices_to=wireless.models.get_wireless_interface_types, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dcim.interface'), + ), + migrations.AlterField( + model_name='wirelesslink', + name='interface_b', + field=models.ForeignKey(limit_choices_to=wireless.models.get_wireless_interface_types, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dcim.interface'), + ), + ] diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index d8166fe9a..c383ad642 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -128,20 +128,26 @@ class WirelessLAN(WirelessAuthenticationBase, NetBoxModel): return reverse('wireless:wirelesslan', args=[self.pk]) +def get_wireless_interface_types(): + # Wrap choices in a callable to avoid generating dummy migrations + # when the choices are updated. + return {'type__in': WIRELESS_IFACE_TYPES} + + class WirelessLink(WirelessAuthenticationBase, NetBoxModel): """ A point-to-point connection between two wireless Interfaces. """ interface_a = models.ForeignKey( to='dcim.Interface', - limit_choices_to={'type__in': WIRELESS_IFACE_TYPES}, + limit_choices_to=get_wireless_interface_types, on_delete=models.PROTECT, related_name='+', verbose_name="Interface A", ) interface_b = models.ForeignKey( to='dcim.Interface', - limit_choices_to={'type__in': WIRELESS_IFACE_TYPES}, + limit_choices_to=get_wireless_interface_types, on_delete=models.PROTECT, related_name='+', verbose_name="Interface B", From 4132027ada8344fd2125562ac4331b3bc1fdfcaa Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 24 Aug 2022 14:12:14 -0700 Subject: [PATCH 18/21] fixes for #10070 make l2vpn slug unique (#10119) Co-authored-by: jeremystretch --- .../ipam/migrations/0060_alter_l2vpn_slug.py | 18 ++++++++++++++++++ netbox/ipam/models/l2vpn.py | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 netbox/ipam/migrations/0060_alter_l2vpn_slug.py diff --git a/netbox/ipam/migrations/0060_alter_l2vpn_slug.py b/netbox/ipam/migrations/0060_alter_l2vpn_slug.py new file mode 100644 index 000000000..9e70c2063 --- /dev/null +++ b/netbox/ipam/migrations/0060_alter_l2vpn_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.7 on 2022-08-22 15:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0059_l2vpn'), + ] + + operations = [ + migrations.AlterField( + model_name='l2vpn', + name='slug', + field=models.SlugField(max_length=100, unique=True), + ), + ] diff --git a/netbox/ipam/models/l2vpn.py b/netbox/ipam/models/l2vpn.py index 9718e9cab..a457f334b 100644 --- a/netbox/ipam/models/l2vpn.py +++ b/netbox/ipam/models/l2vpn.py @@ -21,7 +21,10 @@ class L2VPN(NetBoxModel): max_length=100, unique=True ) - slug = models.SlugField() + slug = models.SlugField( + max_length=100, + unique=True + ) type = models.CharField( max_length=50, choices=L2VPNTypeChoices From 6c686af1b70acdc56a8fdbe84451f7012e2b4197 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 24 Aug 2022 17:13:09 -0400 Subject: [PATCH 19/21] Changelog for #10070 --- docs/release-notes/version-3.3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 1421bb2c7..a1ac5b40f 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -20,6 +20,7 @@ * [#10055](https://github.com/netbox-community/netbox/issues/10055) - Fix extraneous NAT indicator by device primary IP * [#10057](https://github.com/netbox-community/netbox/issues/10057) - Fix AttributeError exception when global search results include rack reservations * [#10059](https://github.com/netbox-community/netbox/issues/10059) - Add identifier column to L2VPN table +* [#10070](https://github.com/netbox-community/netbox/issues/10070) - Add unique constraint for L2VPN slug * [#10089](https://github.com/netbox-community/netbox/issues/10089) - `linkify` template filter should escape object representation * [#10094](https://github.com/netbox-community/netbox/issues/10094) - Fix 404 when using "create and add another" to add contact assignments * [#10108](https://github.com/netbox-community/netbox/issues/10108) - Linkify inside NAT IPs for primary device IPs in UI From ec2e8ad18466f024ad6bb22c3412b6dd69ede4c5 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Thu, 25 Aug 2022 05:21:55 -0700 Subject: [PATCH 20/21] #10139 update development documents for githooks and web-ui (#10141) * #10139 update development documents for githooks and web-ui * Remove redudant phrase Co-authored-by: Jeremy Stretch --- docs/development/getting-started.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md index 38d521de6..bac2b4dca 100644 --- a/docs/development/getting-started.md +++ b/docs/development/getting-started.md @@ -54,6 +54,12 @@ NetBox ships with a [git pre-commit hook](https://githooks.com/) script that aut cd .git/hooks/ ln -s ../../scripts/git-hooks/pre-commit ``` +For the pre-commit hooks to work, you will also need to install the pycodestyle package: + +```no-highlight +python -m pip install pycodestyle +``` +...and setup the yarn packages as shown in the [Web UI Development Guide](web-ui.md) ### 3. Create a Python Virtual Environment @@ -118,6 +124,10 @@ This ensures that your development environment is now complete and operational. !!! tip "IDE Integration" Some IDEs, such as the highly-recommended [PyCharm](https://www.jetbrains.com/pycharm/), will integrate with Django's development server and allow you to run it directly within the IDE. This is strongly encouraged as it makes for a much more convenient development environment. +## UI Development + +For UI development you will need to review the [Web UI Development Guide](web-ui.md) + ## Populating Demo Data Once you have your development environment up and running, it might be helpful to populate some "dummy" data to make interacting with the UI and APIs more convenient. Check out the [netbox-demo-data](https://github.com/netbox-community/netbox-demo-data) repo on GitHub, which houses a collection of sample data that can be easily imported to any new NetBox deployment. (This sample data is used to populate the public demo instance at .) From 482b4b6e95ce4e32eba2ceb38b359fb66d65e4ee Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 25 Aug 2022 08:37:43 -0400 Subject: [PATCH 21/21] Fixes #10147: Permit the creation of 0U device types via REST API --- docs/release-notes/version-3.3.md | 1 + netbox/dcim/api/serializers.py | 2 +- netbox/dcim/tests/test_api.py | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index a1ac5b40f..6e8846e4a 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -27,6 +27,7 @@ * [#10109](https://github.com/netbox-community/netbox/issues/10109) - Fix available prefixes calculation for container prefixes in the global table * [#10111](https://github.com/netbox-community/netbox/issues/10111) - Wrap search QS to catch ValueError on identifier field * [#10134](https://github.com/netbox-community/netbox/issues/10134) - Custom fields data serializer should return a 400 response for invalid data +* [#10147](https://github.com/netbox-community/netbox/issues/10147) - Permit the creation of 0U device types via REST API --- diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 249a3f167..af806acb8 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -310,7 +310,7 @@ class DeviceTypeSerializer(NetBoxModelSerializer): max_digits=4, decimal_places=1, label='Position (U)', - min_value=decimal.Decimal(0.5), + min_value=0, default=1.0 ) subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index a78a98ae5..acd52178d 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -461,16 +461,19 @@ class DeviceTypeTest(APIViewTestCases.APIViewTestCase): 'manufacturer': manufacturers[1].pk, 'model': 'Device Type 4', 'slug': 'device-type-4', + 'u_height': 0, }, { 'manufacturer': manufacturers[1].pk, 'model': 'Device Type 5', 'slug': 'device-type-5', + 'u_height': 0.5, }, { 'manufacturer': manufacturers[1].pk, 'model': 'Device Type 6', 'slug': 'device-type-6', + 'u_height': 1, }, ]