diff --git a/docs/release-notes/version-3.1.md b/docs/release-notes/version-3.1.md index cb8831731..d8e56b1ee 100644 --- a/docs/release-notes/version-3.1.md +++ b/docs/release-notes/version-3.1.md @@ -13,6 +13,7 @@ * [#8187](https://github.com/netbox-community/netbox/issues/8187) - Fix rendering of tags column in object tables * [#8191](https://github.com/netbox-community/netbox/issues/8191) - Fix return URL when adding IP addresses to VM interfaces * [#8196](https://github.com/netbox-community/netbox/issues/8196) - Fix IndexError exception when viewing large IPv6 prefixes in UI +* [#8201](https://github.com/netbox-community/netbox/issues/8201) - Custom integer fields should allow negative integers as minimum/maximum values --- diff --git a/netbox/extras/migrations/0067_customfield_min_max_values.py b/netbox/extras/migrations/0067_customfield_min_max_values.py new file mode 100644 index 000000000..cec4f6ae0 --- /dev/null +++ b/netbox/extras/migrations/0067_customfield_min_max_values.py @@ -0,0 +1,21 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0066_customfield_name_validation'), + ] + + operations = [ + migrations.AlterField( + model_name='customfield', + name='validation_maximum', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='customfield', + name='validation_minimum', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 713ef6c93..8c817ad33 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -96,13 +96,13 @@ class CustomField(ChangeLoggedModel): default=100, help_text='Fields with higher weights appear lower in a form.' ) - validation_minimum = models.PositiveIntegerField( + validation_minimum = models.IntegerField( blank=True, null=True, verbose_name='Minimum value', help_text='Minimum allowed value (for numeric fields)' ) - validation_maximum = models.PositiveIntegerField( + validation_maximum = models.IntegerField( blank=True, null=True, verbose_name='Maximum value', diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 5a9c4257f..fdabe0fcf 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -25,49 +25,68 @@ class CustomFieldTest(TestCase): def test_simple_fields(self): DATA = ( { - 'field_type': CustomFieldTypeChoices.TYPE_TEXT, - 'field_value': 'Foobar!', - 'empty_value': '', + 'field': { + 'type': CustomFieldTypeChoices.TYPE_TEXT, + }, + 'value': 'Foobar!', }, { - 'field_type': CustomFieldTypeChoices.TYPE_LONGTEXT, - 'field_value': 'Text with **Markdown**', - 'empty_value': '', + 'field': { + 'type': CustomFieldTypeChoices.TYPE_LONGTEXT, + }, + 'value': 'Text with **Markdown**', }, { - 'field_type': CustomFieldTypeChoices.TYPE_INTEGER, - 'field_value': 0, - 'empty_value': None, + 'field': { + 'type': CustomFieldTypeChoices.TYPE_INTEGER, + }, + 'value': 0, }, { - 'field_type': CustomFieldTypeChoices.TYPE_INTEGER, - 'field_value': 42, - 'empty_value': None, + 'field': { + 'type': CustomFieldTypeChoices.TYPE_INTEGER, + 'validation_minimum': 1, + 'validation_maximum': 100, + }, + 'value': 42, }, { - 'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, - 'field_value': True, - 'empty_value': None, + 'field': { + 'type': CustomFieldTypeChoices.TYPE_INTEGER, + 'validation_minimum': -100, + 'validation_maximum': -1, + }, + 'value': -42, }, { - 'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, - 'field_value': False, - 'empty_value': None, + 'field': { + 'type': CustomFieldTypeChoices.TYPE_BOOLEAN, + }, + 'value': True, }, { - 'field_type': CustomFieldTypeChoices.TYPE_DATE, - 'field_value': '2016-06-23', - 'empty_value': None, + 'field': { + 'type': CustomFieldTypeChoices.TYPE_BOOLEAN, + }, + 'value': False, }, { - 'field_type': CustomFieldTypeChoices.TYPE_URL, - 'field_value': 'http://example.com/', - 'empty_value': '', + 'field': { + 'type': CustomFieldTypeChoices.TYPE_DATE, + }, + 'value': '2016-06-23', }, { - 'field_type': CustomFieldTypeChoices.TYPE_JSON, - 'field_value': '{"foo": 1, "bar": 2}', - 'empty_value': 'null', + 'field': { + 'type': CustomFieldTypeChoices.TYPE_URL, + }, + 'value': 'http://example.com/', + }, + { + 'field': { + 'type': CustomFieldTypeChoices.TYPE_JSON, + }, + 'value': '{"foo": 1, "bar": 2}', }, ) @@ -76,7 +95,7 @@ class CustomFieldTest(TestCase): for data in DATA: # Create a custom field - cf = CustomField(type=data['field_type'], name='my_field', required=False) + cf = CustomField(name='my_field', required=False, **data['field']) cf.save() cf.content_types.set([obj_type]) @@ -85,12 +104,12 @@ class CustomFieldTest(TestCase): self.assertIsNone(site.custom_field_data[cf.name]) # Assign a value to the first Site - site.custom_field_data[cf.name] = data['field_value'] + site.custom_field_data[cf.name] = data['value'] site.save() # Retrieve the stored value site.refresh_from_db() - self.assertEqual(site.custom_field_data[cf.name], data['field_value']) + self.assertEqual(site.custom_field_data[cf.name], data['value']) # Delete the stored value site.custom_field_data.pop(cf.name)