From b4dd57f3c78649e1c63b59a0b192522be924008f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 30 Aug 2024 13:44:03 -0400 Subject: [PATCH] #8198: Rename CustomField.validation_unique to unique (#17325) * #8198: Rename CustomField.validation_unique to unique * Update CustomField model documentation --- docs/models/extras/customfield.md | 10 ++++---- .../extras/api/serializers_/customfields.py | 7 +++--- netbox/extras/filtersets.py | 4 ++-- netbox/extras/forms/bulk_edit.py | 16 ++++++------- netbox/extras/forms/bulk_import.py | 7 +++--- netbox/extras/forms/filtersets.py | 23 +++++++++---------- netbox/extras/forms/model_forms.py | 4 ++-- .../migrations/0118_customfield_uniqueness.py | 2 +- netbox/extras/models/customfields.py | 22 +++++++++--------- netbox/extras/tables/tables.py | 17 ++++++++------ netbox/extras/tests/test_customfields.py | 2 +- netbox/netbox/models/features.py | 2 +- netbox/templates/extras/customfield.html | 8 +++---- 13 files changed, 62 insertions(+), 62 deletions(-) diff --git a/docs/models/extras/customfield.md b/docs/models/extras/customfield.md index 626f320be57..9aab66a36ca 100644 --- a/docs/models/extras/customfield.md +++ b/docs/models/extras/customfield.md @@ -57,7 +57,11 @@ A numeric weight used to override alphabetic ordering of fields by name. Custom ### Required -If checked, this custom field must be populated with a valid value for the object to pass validation. +If enabled, this custom field must be populated with a valid value for the object to pass validation. + +### Unique + +If enabled, each object must have a unique value set for this custom field (per object type). ### Description @@ -116,7 +120,3 @@ For numeric custom fields only. The maximum valid value (optional). ### Validation Regex For string-based custom fields only. A regular expression used to validate the field's value (optional). - -### Uniqueness Validation - -If enabled, each object must have a unique value set for this custom field (per object type). diff --git a/netbox/extras/api/serializers_/customfields.py b/netbox/extras/api/serializers_/customfields.py index 2c8e7c127c2..a65fafc4e91 100644 --- a/netbox/extras/api/serializers_/customfields.py +++ b/netbox/extras/api/serializers_/customfields.py @@ -61,9 +61,10 @@ class Meta: model = CustomField fields = [ 'id', 'url', 'display_url', 'display', 'object_types', 'type', 'related_object_type', 'data_type', - 'name', 'label', 'group_name', 'description', 'required', 'search_weight', 'filter_logic', 'ui_visible', - 'ui_editable', 'is_cloneable', 'default', 'related_object_filter', 'weight', 'validation_minimum', 'validation_maximum', - 'validation_regex', 'validation_unique', 'choice_set', 'comments', 'created', 'last_updated', + 'name', 'label', 'group_name', 'description', 'required', 'unique', 'search_weight', 'filter_logic', + 'ui_visible', 'ui_editable', 'is_cloneable', 'default', 'related_object_filter', 'weight', + 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set', 'comments', 'created', + 'last_updated', ] brief_fields = ('id', 'url', 'display', 'name', 'description') diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py index 38e7dfc9de9..4f40ce50017 100644 --- a/netbox/extras/filtersets.py +++ b/netbox/extras/filtersets.py @@ -158,9 +158,9 @@ class CustomFieldFilterSet(ChangeLoggedModelFilterSet): class Meta: model = CustomField fields = ( - 'id', 'name', 'label', 'group_name', 'required', 'search_weight', 'filter_logic', 'ui_visible', + 'id', 'name', 'label', 'group_name', 'required', 'unique', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable', 'description', 'validation_minimum', 'validation_maximum', - 'validation_regex', 'validation_unique', + 'validation_regex', ) def search(self, queryset, name, value): diff --git a/netbox/extras/forms/bulk_edit.py b/netbox/extras/forms/bulk_edit.py index 74cf65c321b..30d06683b83 100644 --- a/netbox/extras/forms/bulk_edit.py +++ b/netbox/extras/forms/bulk_edit.py @@ -44,6 +44,11 @@ class CustomFieldBulkEditForm(BulkEditForm): required=False, widget=BulkEditNullBooleanSelect() ) + unique = forms.NullBooleanField( + label=_('Must be unique'), + required=False, + widget=BulkEditNullBooleanSelect() + ) weight = forms.IntegerField( label=_('Weight'), required=False @@ -79,19 +84,12 @@ class CustomFieldBulkEditForm(BulkEditForm): label=_('Validation regex'), required=False ) - validation_unique = forms.NullBooleanField( - label=_('Must be unique'), - required=False, - widget=BulkEditNullBooleanSelect() - ) comments = CommentField() fieldsets = ( - FieldSet('group_name', 'description', 'weight', 'choice_set', name=_('Attributes')), + FieldSet('group_name', 'description', 'weight', 'required', 'unique', 'choice_set', name=_('Attributes')), FieldSet('ui_visible', 'ui_editable', 'is_cloneable', name=_('Behavior')), - FieldSet( - 'validation_minimum', 'validation_maximum', 'validation_regex', 'validation_unique', name=_('Validation') - ), + FieldSet('validation_minimum', 'validation_maximum', 'validation_regex', name=_('Validation')), ) nullable_fields = ('group_name', 'description', 'choice_set') diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py index 780adb0d198..55c9cd764f4 100644 --- a/netbox/extras/forms/bulk_import.py +++ b/netbox/extras/forms/bulk_import.py @@ -72,10 +72,9 @@ class CustomFieldImportForm(CSVModelForm): class Meta: model = CustomField fields = ( - 'name', 'label', 'group_name', 'type', 'object_types', 'related_object_type', 'required', 'description', - 'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum', - 'validation_maximum', 'validation_regex', 'validation_unique', 'ui_visible', 'ui_editable', 'is_cloneable', - 'comments', + 'name', 'label', 'group_name', 'type', 'object_types', 'related_object_type', 'required', 'unique', + 'description', 'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum', + 'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable', 'comments', ) diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py index bd3883877b8..05dcf96c476 100644 --- a/netbox/extras/forms/filtersets.py +++ b/netbox/extras/forms/filtersets.py @@ -40,12 +40,11 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm): fieldsets = ( FieldSet('q', 'filter_id'), FieldSet( - 'type', 'related_object_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visible', - 'ui_editable', 'is_cloneable', name=_('Attributes') - ), - FieldSet( - 'validation_minimum', 'validation_maximum', 'validation_regex', 'validation_unique', name=_('Validation') + 'type', 'related_object_type_id', 'group_name', 'weight', 'required', 'unique', 'choice_set_id', + name=_('Attributes') ), + FieldSet('ui_visible', 'ui_editable', 'is_cloneable', name=_('Behavior')), + FieldSet('validation_minimum', 'validation_maximum', 'validation_regex', name=_('Validation')), ) related_object_type_id = ContentTypeMultipleChoiceField( queryset=ObjectType.objects.with_feature('custom_fields'), @@ -72,6 +71,13 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm): choices=BOOLEAN_WITH_BLANK_CHOICES ) ) + unique = forms.NullBooleanField( + label=_('Must be unique'), + required=False, + widget=forms.Select( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) choice_set_id = DynamicModelMultipleChoiceField( queryset=CustomFieldChoiceSet.objects.all(), required=False, @@ -106,13 +112,6 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm): label=_('Validation regex'), required=False ) - validation_unique = forms.NullBooleanField( - label=_('Must be unique'), - required=False, - widget=forms.Select( - choices=BOOLEAN_WITH_BLANK_CHOICES - ) - ) class CustomFieldChoiceSetFilterForm(SavedFiltersMixin, FilterForm): diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index e2a95281e72..a45daaf7084 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -69,8 +69,8 @@ class CustomFieldForm(forms.ModelForm): fieldsets = ( FieldSet( - 'object_types', 'name', 'label', 'group_name', 'description', 'type', 'required', 'validation_unique', - 'default', name=_('Custom Field') + 'object_types', 'name', 'label', 'group_name', 'description', 'type', 'required', 'unique', 'default', + name=_('Custom Field') ), FieldSet( 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable', name=_('Behavior') diff --git a/netbox/extras/migrations/0118_customfield_uniqueness.py b/netbox/extras/migrations/0118_customfield_uniqueness.py index 8babca696c6..b7693aa24e8 100644 --- a/netbox/extras/migrations/0118_customfield_uniqueness.py +++ b/netbox/extras/migrations/0118_customfield_uniqueness.py @@ -10,7 +10,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='customfield', - name='validation_unique', + name='unique', field=models.BooleanField(default=False), ), ] diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 6c39080b7c6..839a6ace91c 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -129,7 +129,12 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): required = models.BooleanField( verbose_name=_('required'), default=False, - help_text=_("If true, this field is required when creating new objects or editing an existing object.") + help_text=_("This field is required when creating new objects or editing an existing object.") + ) + unique = models.BooleanField( + verbose_name=_('must be unique'), + default=False, + help_text=_("The value of this field must be unique for the assigned object") ) search_weight = models.PositiveSmallIntegerField( verbose_name=_('search weight'), @@ -189,11 +194,6 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): 'example, ^[A-Z]{3}$ will limit values to exactly three uppercase letters.' ) ) - validation_unique = models.BooleanField( - verbose_name=_('must be unique'), - default=False, - help_text=_('The value of this field must be unique for the assigned object') - ) choice_set = models.ForeignKey( to='CustomFieldChoiceSet', on_delete=models.PROTECT, @@ -229,9 +229,9 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): objects = CustomFieldManager() clone_fields = ( - 'object_types', 'type', 'related_object_type', 'group_name', 'description', 'required', 'search_weight', - 'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', - 'validation_unique', 'choice_set', 'ui_visible', 'ui_editable', 'is_cloneable', + 'object_types', 'type', 'related_object_type', 'group_name', 'description', 'required', 'unique', + 'search_weight', 'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum', + 'validation_regex', 'choice_set', 'ui_visible', 'ui_editable', 'is_cloneable', ) class Meta: @@ -349,9 +349,9 @@ def clean(self): }) # Uniqueness can not be enforced for boolean fields - if self.validation_unique and self.type == CustomFieldTypeChoices.TYPE_BOOLEAN: + if self.unique and self.type == CustomFieldTypeChoices.TYPE_BOOLEAN: raise ValidationError({ - 'validation_unique': _("Uniqueness cannot be enforced for boolean fields") + 'unique': _("Uniqueness cannot be enforced for boolean fields") }) # Choice set must be set on selection fields, and *only* on selection fields diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 8a4e802096a..e538c488ec0 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -65,6 +65,10 @@ class CustomFieldTable(NetBoxTable): verbose_name=_('Required'), false_mark=None ) + unique = columns.BooleanColumn( + verbose_name=_('Validate Uniqueness'), + false_mark=None + ) ui_visible = columns.ChoiceFieldColumn( verbose_name=_('Visible') ) @@ -99,19 +103,18 @@ class CustomFieldTable(NetBoxTable): validation_regex = tables.Column( verbose_name=_('Validation Regex'), ) - validation_unique = columns.BooleanColumn( - verbose_name=_('Validate Uniqueness'), - ) class Meta(NetBoxTable.Meta): model = CustomField fields = ( 'pk', 'id', 'name', 'object_types', 'label', 'type', 'related_object_type', 'group_name', 'required', - 'default', 'description', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable', - 'weight', 'choice_set', 'choices', 'validation_minimum', 'validation_maximum', 'validation_regex', - 'validation_unique', 'comments', 'created', 'last_updated', + 'unique', 'default', 'description', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', + 'is_cloneable', 'weight', 'choice_set', 'choices', 'validation_minimum', 'validation_maximum', + 'validation_regex', 'comments', 'created', 'last_updated', + ) + default_columns = ( + 'pk', 'name', 'object_types', 'label', 'group_name', 'type', 'required', 'unique', 'description', ) - default_columns = ('pk', 'name', 'object_types', 'label', 'group_name', 'type', 'required', 'description') class CustomFieldChoiceSetTable(NetBoxTable): diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 009ae8798a2..697b756ecb3 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -1143,7 +1143,7 @@ def test_regex_validation(self): def test_uniqueness_validation(self): # Create a unique custom field cf_text = CustomField.objects.get(name='text_field') - cf_text.validation_unique = True + cf_text.unique = True cf_text.save() # Set a value on site 1 diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 43eb089d6ee..45eb7008112 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -288,7 +288,7 @@ def clean(self): )) # Validate uniqueness if enforced - if custom_fields[field_name].validation_unique and value not in CUSTOMFIELD_EMPTY_VALUES: + if custom_fields[field_name].unique and value not in CUSTOMFIELD_EMPTY_VALUES: if self._meta.model.objects.exclude(pk=self.pk).filter(**{ f'custom_field_data__{field_name}': value }).exists(): diff --git a/netbox/templates/extras/customfield.html b/netbox/templates/extras/customfield.html index 13c2e869178..dbdbd057f76 100644 --- a/netbox/templates/extras/customfield.html +++ b/netbox/templates/extras/customfield.html @@ -38,6 +38,10 @@

{% trans "Custom Field" %}

{% trans "Required" %} {% checkmark object.required %} + + {% trans "Must be Unique" %} + {% checkmark object.unique %} + {% trans "Cloneable" %} {% checkmark object.is_cloneable %} @@ -128,10 +132,6 @@

{% trans "Validation Rules" %}

{% endif %} - - {% trans "Must be Unique" %} - {% checkmark object.validation_unique %} -