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

This commit is contained in:
Jeremy Stretch 2025-10-14 12:19:10 -04:00 committed by GitHub
parent cfbd9632ac
commit 2edfde5753
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 1 deletions

View File

@ -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
netbox/netbox/monkey.py Normal file
View File

@ -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
)

View File

@ -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