diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index b1d22ee0b4..d976910269 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -535,6 +535,15 @@ def to_form_field(self, set_initial=True, enforce_required=True, enforce_visibil # URL elif self.type == CustomFieldTypeChoices.TYPE_URL: field = LaxURLField(assume_scheme='https', required=required, initial=initial) + if self.validation_regex: + field.validators = [ + RegexValidator( + regex=self.validation_regex, + message=mark_safe(_("Values must match this regex: {regex}").format( + regex=escape(self.validation_regex) + )) + ) + ] # JSON elif self.type == CustomFieldTypeChoices.TYPE_JSON: @@ -684,6 +693,13 @@ def validate(self, value): if self.validation_regex and not re.match(self.validation_regex, value): raise ValidationError(_("Value must match regex '{regex}'").format(regex=self.validation_regex)) + # Validate URL field + elif self.type == CustomFieldTypeChoices.TYPE_URL: + if type(value) is not str: + raise ValidationError(_("Value must be a string.")) + if self.validation_regex and not re.match(self.validation_regex, value): + raise ValidationError(_("Value must match regex '{regex}'").format(regex=self.validation_regex)) + # Validate integer elif self.type == CustomFieldTypeChoices.TYPE_INTEGER: if type(value) is not int: diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 04e30aa0c1..502759ab92 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -1300,6 +1300,28 @@ def test_regex_validation(self): response = self.client.patch(url, data, format='json', **self.header) self.assertHttpStatus(response, status.HTTP_200_OK) + def test_url_regex_validation(self): + """ + Test that validation_regex is applied to URL custom fields (fixes #20498). + """ + site2 = Site.objects.get(name='Site 2') + url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk}) + self.add_permissions('dcim.change_site') + + cf_url = CustomField.objects.get(name='url_field') + cf_url.validation_regex = r'^https://' # Require HTTPS + cf_url.save() + + # Test invalid URL (http instead of https) + data = {'custom_fields': {'url_field': 'http://example.com'}} + response = self.client.patch(url, data, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + + # Test valid URL (https) + data = {'custom_fields': {'url_field': 'https://example.com'}} + response = self.client.patch(url, data, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + def test_uniqueness_validation(self): # Create a unique custom field cf_text = CustomField.objects.get(name='text_field')