diff --git a/base_requirements.txt b/base_requirements.txt index 1e6f2ad29d3..1dfd2a4ce0f 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -71,7 +71,8 @@ django-timezone-field # A REST API framework for Django projects # 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. # https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst diff --git a/netbox/netbox/monkey.py b/netbox/netbox/monkey.py new file mode 100644 index 00000000000..6a8f515df8e --- /dev/null +++ b/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 + ) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index ce358b66cd6..194567b47aa 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -11,6 +11,7 @@ from django.core.validators import URLValidator from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ +from rest_framework.utils import field_mapping from core.exceptions import IncompatiblePluginError from netbox.config import PARAMS as CONFIG_PARAMS @@ -20,6 +21,17 @@ import storages.utils # type: ignore from utilities.release import load_release_data 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