Skip to content

Commit 0f477a4

Browse files
committed
Check if unique flag raise validation error if non unique data
1 parent b5d2d03 commit 0f477a4

File tree

1 file changed

+53
-5
lines changed

1 file changed

+53
-5
lines changed

netbox_custom_objects/models.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# from django.contrib.contenttypes.management import create_contenttypes
1313
from django.contrib.contenttypes.models import ContentType
1414
from django.core.validators import RegexValidator, ValidationError
15-
from django.db import connection, models
15+
from django.db import connection, models, IntegrityError
1616
from django.db.models import Q
1717
from django.db.models.functions import Lower
1818
from django.db.models.signals import pre_delete
@@ -52,6 +52,13 @@
5252
from netbox_custom_objects.constants import APP_LABEL
5353
from netbox_custom_objects.field_types import FIELD_TYPE_CLASS
5454

55+
56+
class UniquenessConstraintTestError(Exception):
57+
"""Custom exception used to signal successful uniqueness constraint test."""
58+
59+
pass
60+
61+
5562
USER_TABLE_DATABASE_NAME_PREFIX = "custom_objects_"
5663

5764

@@ -310,7 +317,10 @@ def _fetch_and_generate_field_attrs(
310317
for field in fields:
311318
field_type = FIELD_TYPE_CLASS[field.type]()
312319
if skip_object_fields:
313-
if field.type in [CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT]:
320+
if field.type in [
321+
CustomFieldTypeChoices.TYPE_OBJECT,
322+
CustomFieldTypeChoices.TYPE_MULTIOBJECT,
323+
]:
314324
continue
315325

316326
field_name = field.name
@@ -453,7 +463,9 @@ def get_model(
453463
"custom_object_type_id": self.id,
454464
}
455465

456-
field_attrs = self._fetch_and_generate_field_attrs(fields, skip_object_fields=skip_object_fields)
466+
field_attrs = self._fetch_and_generate_field_attrs(
467+
fields, skip_object_fields=skip_object_fields
468+
)
457469

458470
attrs.update(**field_attrs)
459471

@@ -587,7 +599,7 @@ class CustomObjectTypeField(CloningMixin, ExportTemplatesMixin, ChangeLoggedMode
587599
name = models.CharField(
588600
verbose_name=_("name"),
589601
max_length=50,
590-
help_text=_("Internal field name, e.g. \"vendor_label\""),
602+
help_text=_('Internal field name, e.g. "vendor_label"'),
591603
validators=(
592604
RegexValidator(
593605
regex=r"^[a-z0-9_]+$",
@@ -616,7 +628,9 @@ class CustomObjectTypeField(CloningMixin, ExportTemplatesMixin, ChangeLoggedMode
616628
verbose_name=_("group name"),
617629
max_length=50,
618630
blank=True,
619-
help_text=_("Custom object fields within the same group will be displayed together"),
631+
help_text=_(
632+
"Custom object fields within the same group will be displayed together"
633+
),
620634
)
621635
description = models.CharField(
622636
verbose_name=_("description"), max_length=200, blank=True
@@ -862,6 +876,40 @@ def clean(self):
862876
{"unique": _("Uniqueness cannot be enforced for boolean fields")}
863877
)
864878

879+
# Check if uniqueness constraint can be applied when changing from non-unique to unique
880+
if (
881+
self.pk
882+
and self.unique
883+
and not self.original.unique
884+
and not self._state.adding
885+
):
886+
field_type = FIELD_TYPE_CLASS[self.type]()
887+
model_field = field_type.get_model_field(self)
888+
model = self.custom_object_type.get_model()
889+
model_field.contribute_to_class(model, self.name)
890+
891+
old_field = field_type.get_model_field(self.original)
892+
old_field.contribute_to_class(model, self._original_name)
893+
894+
try:
895+
with connection.schema_editor() as test_schema_editor:
896+
test_schema_editor.alter_field(model, old_field, model_field)
897+
# If we get here, the constraint was applied successfully
898+
# Now raise a custom exception to rollback the test transaction
899+
raise UniquenessConstraintTestError()
900+
except UniquenessConstraintTestError:
901+
# The constraint can be applied, validation passes
902+
pass
903+
except IntegrityError:
904+
# The constraint cannot be applied due to existing non-unique values
905+
raise ValidationError(
906+
{
907+
"unique": _(
908+
"Custom objects with non-unique values already exist so this action isn't permitted"
909+
)
910+
}
911+
)
912+
865913
# Choice set must be set on selection fields, and *only* on selection fields
866914
if self.type in (
867915
CustomFieldTypeChoices.TYPE_SELECT,

0 commit comments

Comments
 (0)