From a6eb2d0103220d251c4f4f7dc33f849cb2d272fc Mon Sep 17 00:00:00 2001 From: Fabian Geisberger Date: Mon, 10 Mar 2025 17:47:08 -0400 Subject: [PATCH 1/2] Correctly reject invalid falsy local context data. --- netbox/dcim/tests/test_api.py | 28 ++++++++++++++++++++++++++++ netbox/extras/models/configs.py | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 08f93f6ea9d..bfc427edbfc 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1396,6 +1396,34 @@ def test_render_config(self): self.assertHttpStatus(response, status.HTTP_200_OK) self.assertEqual(response.data['content'], f'Config for device {device.name}') + def test_valid_local_context_data(self): + self.add_permissions('dcim.change_device') + device = Device.objects.first() + url = reverse('dcim-api:device-detail', kwargs={'pk': device.pk}) + + response = self.client.patch(url, {"local_context_data": None}, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + + response = self.client.patch(url, {"local_context_data": {"foo": "bar"}}, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + + def test_invalid_local_context_data(self): + self.add_permissions('dcim.change_device') + device = Device.objects.first() + url = reverse('dcim-api:device-detail', kwargs={'pk': device.pk}) + + response = self.client.patch(url, {"local_context_data": ""}, format='json', **self.header) + self.assertContains(response, 'JSON data must be in object form.', status_code=status.HTTP_400_BAD_REQUEST) + + response = self.client.patch(url, {"local_context_data": 0}, format='json', **self.header) + self.assertContains(response, 'JSON data must be in object form.', status_code=status.HTTP_400_BAD_REQUEST) + + response = self.client.patch(url, {"local_context_data": False}, format='json', **self.header) + self.assertContains(response, 'JSON data must be in object form.', status_code=status.HTTP_400_BAD_REQUEST) + + response = self.client.patch(url, {"local_context_data": "foo"}, format='json', **self.header) + self.assertContains(response, 'JSON data must be in object form.', status_code=status.HTTP_400_BAD_REQUEST) + class ModuleTest(APIViewTestCases.APIViewTestCase): model = Module diff --git a/netbox/extras/models/configs.py b/netbox/extras/models/configs.py index 6b52d4c0211..773f5a70ef8 100644 --- a/netbox/extras/models/configs.py +++ b/netbox/extras/models/configs.py @@ -200,7 +200,7 @@ def clean(self): super().clean() # Verify that JSON data is provided as an object - if self.local_context_data and type(self.local_context_data) is not dict: + if self.local_context_data is not None and type(self.local_context_data) is not dict: raise ValidationError( {'local_context_data': _('JSON data must be in object form. Example:') + ' {"foo": 123}'} ) From 4cbb17d2e35e971b88906745ebef9cf7b628ac2d Mon Sep 17 00:00:00 2001 From: Fabian Geisberger Date: Tue, 11 Mar 2025 14:24:46 -0400 Subject: [PATCH 2/2] move tests --- netbox/dcim/tests/test_api.py | 28 ---------------------------- netbox/extras/tests/test_models.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index bfc427edbfc..08f93f6ea9d 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1396,34 +1396,6 @@ def test_render_config(self): self.assertHttpStatus(response, status.HTTP_200_OK) self.assertEqual(response.data['content'], f'Config for device {device.name}') - def test_valid_local_context_data(self): - self.add_permissions('dcim.change_device') - device = Device.objects.first() - url = reverse('dcim-api:device-detail', kwargs={'pk': device.pk}) - - response = self.client.patch(url, {"local_context_data": None}, format='json', **self.header) - self.assertHttpStatus(response, status.HTTP_200_OK) - - response = self.client.patch(url, {"local_context_data": {"foo": "bar"}}, format='json', **self.header) - self.assertHttpStatus(response, status.HTTP_200_OK) - - def test_invalid_local_context_data(self): - self.add_permissions('dcim.change_device') - device = Device.objects.first() - url = reverse('dcim-api:device-detail', kwargs={'pk': device.pk}) - - response = self.client.patch(url, {"local_context_data": ""}, format='json', **self.header) - self.assertContains(response, 'JSON data must be in object form.', status_code=status.HTTP_400_BAD_REQUEST) - - response = self.client.patch(url, {"local_context_data": 0}, format='json', **self.header) - self.assertContains(response, 'JSON data must be in object form.', status_code=status.HTTP_400_BAD_REQUEST) - - response = self.client.patch(url, {"local_context_data": False}, format='json', **self.header) - self.assertContains(response, 'JSON data must be in object form.', status_code=status.HTTP_400_BAD_REQUEST) - - response = self.client.patch(url, {"local_context_data": "foo"}, format='json', **self.header) - self.assertContains(response, 'JSON data must be in object form.', status_code=status.HTTP_400_BAD_REQUEST) - class ModuleTest(APIViewTestCases.APIViewTestCase): model = Module diff --git a/netbox/extras/tests/test_models.py b/netbox/extras/tests/test_models.py index c90390dd179..34882537dab 100644 --- a/netbox/extras/tests/test_models.py +++ b/netbox/extras/tests/test_models.py @@ -1,3 +1,4 @@ +from django.forms import ValidationError from django.test import TestCase from core.models import ObjectType @@ -478,3 +479,30 @@ def test_multiple_tags_return_distinct_objects_with_seperate_config_contexts(sel annotated_queryset = Device.objects.filter(name=device.name).annotate_config_context_data() self.assertEqual(ConfigContext.objects.get_for_object(device).count(), 2) self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context()) + + def test_valid_local_context_data(self): + device = Device.objects.first() + device.local_context_data = None + device.clean() + + device.local_context_data = {"foo": "bar"} + device.clean() + + def test_invalid_local_context_data(self): + device = Device.objects.first() + + device.local_context_data = "" + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = 0 + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = False + with self.assertRaises(ValidationError): + device.clean() + + device.local_context_data = 'foo' + with self.assertRaises(ValidationError): + device.clean()