Sfoglia il codice sorgente

Fixes #19302: Fix uniqueness validation in REST API for nullable fields (#20549)

Jeremy Stretch 4 mesi fa
parent
commit
2edfde5753
3 ha cambiato i file con 53 aggiunte e 1 eliminazioni
  1. 2 1
      base_requirements.txt
  2. 39 0
      netbox/netbox/monkey.py
  3. 12 0
      netbox/netbox/settings.py

+ 2 - 1
base_requirements.txt

@@ -71,7 +71,8 @@ django-timezone-field
 
 
 # A REST API framework for Django projects
 # A REST API framework for Django projects
 # https://www.django-rest-framework.org/community/release-notes/
 # https://www.django-rest-framework.org/community/release-notes/
-djangorestframework
+# TODO: Re-evaluate the monkey-patch of get_unique_validators() before upgrading
+djangorestframework==3.16.1
 
 
 # Sane and flexible OpenAPI 3 schema generation for Django REST framework.
 # Sane and flexible OpenAPI 3 schema generation for Django REST framework.
 # https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst
 # https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst

+ 39 - 0
netbox/netbox/monkey.py

@@ -0,0 +1,39 @@
+from django.db.models import UniqueConstraint
+from rest_framework.utils.field_mapping import get_unique_error_message
+from rest_framework.validators import UniqueValidator
+
+__all__ = (
+    'get_unique_validators',
+)
+
+
+def get_unique_validators(field_name, model_field):
+    """
+    Extend Django REST Framework's get_unique_validators() function to attach a UniqueValidator to a field *only* if the
+     associated UniqueConstraint does NOT have a condition which references another field. See bug #19302.
+    """
+    field_set = {field_name}
+    conditions = {
+        c.condition
+        for c in model_field.model._meta.constraints
+        if isinstance(c, UniqueConstraint) and set(c.fields) == field_set
+    }
+
+    # START custom logic
+    conditions = {
+        cond for cond in conditions
+        if cond.referenced_base_fields == field_set
+    }
+    # END custom logic
+
+    if getattr(model_field, 'unique', False):
+        conditions.add(None)
+    if not conditions:
+        return
+    unique_error_message = get_unique_error_message(model_field)
+    queryset = model_field.model._default_manager
+    for condition in conditions:
+        yield UniqueValidator(
+            queryset=queryset if condition is None else queryset.filter(condition),
+            message=unique_error_message
+        )

+ 12 - 0
netbox/netbox/settings.py

@@ -11,6 +11,7 @@ from django.core.exceptions import ImproperlyConfigured, ValidationError
 from django.core.validators import URLValidator
 from django.core.validators import URLValidator
 from django.utils.module_loading import import_string
 from django.utils.module_loading import import_string
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
+from rest_framework.utils import field_mapping
 
 
 from core.exceptions import IncompatiblePluginError
 from core.exceptions import IncompatiblePluginError
 from netbox.config import PARAMS as CONFIG_PARAMS
 from netbox.config import PARAMS as CONFIG_PARAMS
@@ -20,6 +21,17 @@ from netbox.registry import registry
 import storages.utils  # type: ignore
 import storages.utils  # type: ignore
 from utilities.release import load_release_data
 from utilities.release import load_release_data
 from utilities.string import trailing_slash
 from utilities.string import trailing_slash
+from .monkey import get_unique_validators
+
+
+#
+# Monkey-patching
+#
+
+# TODO: Remove this once #20547 has been implemented
+# Override DRF's get_unique_validators() function with our own (see bug #19302)
+field_mapping.get_unique_validators = get_unique_validators
+
 
 
 #
 #
 # Environment setup
 # Environment setup