Skip to content

Commit 2edfde5

Browse files
Fixes #19302: Fix uniqueness validation in REST API for nullable fields (#20549)
1 parent cfbd963 commit 2edfde5

File tree

3 files changed

+53
-1
lines changed

3 files changed

+53
-1
lines changed

base_requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ django-timezone-field
7171

7272
# A REST API framework for Django projects
7373
# https://www.django-rest-framework.org/community/release-notes/
74-
djangorestframework
74+
# TODO: Re-evaluate the monkey-patch of get_unique_validators() before upgrading
75+
djangorestframework==3.16.1
7576

7677
# Sane and flexible OpenAPI 3 schema generation for Django REST framework.
7778
# https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst

netbox/netbox/monkey.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from django.db.models import UniqueConstraint
2+
from rest_framework.utils.field_mapping import get_unique_error_message
3+
from rest_framework.validators import UniqueValidator
4+
5+
__all__ = (
6+
'get_unique_validators',
7+
)
8+
9+
10+
def get_unique_validators(field_name, model_field):
11+
"""
12+
Extend Django REST Framework's get_unique_validators() function to attach a UniqueValidator to a field *only* if the
13+
associated UniqueConstraint does NOT have a condition which references another field. See bug #19302.
14+
"""
15+
field_set = {field_name}
16+
conditions = {
17+
c.condition
18+
for c in model_field.model._meta.constraints
19+
if isinstance(c, UniqueConstraint) and set(c.fields) == field_set
20+
}
21+
22+
# START custom logic
23+
conditions = {
24+
cond for cond in conditions
25+
if cond.referenced_base_fields == field_set
26+
}
27+
# END custom logic
28+
29+
if getattr(model_field, 'unique', False):
30+
conditions.add(None)
31+
if not conditions:
32+
return
33+
unique_error_message = get_unique_error_message(model_field)
34+
queryset = model_field.model._default_manager
35+
for condition in conditions:
36+
yield UniqueValidator(
37+
queryset=queryset if condition is None else queryset.filter(condition),
38+
message=unique_error_message
39+
)

netbox/netbox/settings.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from django.core.validators import URLValidator
1212
from django.utils.module_loading import import_string
1313
from django.utils.translation import gettext_lazy as _
14+
from rest_framework.utils import field_mapping
1415

1516
from core.exceptions import IncompatiblePluginError
1617
from netbox.config import PARAMS as CONFIG_PARAMS
@@ -20,6 +21,17 @@
2021
import storages.utils # type: ignore
2122
from utilities.release import load_release_data
2223
from utilities.string import trailing_slash
24+
from .monkey import get_unique_validators
25+
26+
27+
#
28+
# Monkey-patching
29+
#
30+
31+
# TODO: Remove this once #20547 has been implemented
32+
# Override DRF's get_unique_validators() function with our own (see bug #19302)
33+
field_mapping.get_unique_validators = get_unique_validators
34+
2335

2436
#
2537
# Environment setup

0 commit comments

Comments
 (0)