Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion rest_framework/utils/field_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ def get_unique_validators(field_name, model_field):
for condition in conditions:
yield UniqueValidator(
queryset=queryset if condition is None else queryset.filter(condition),
message=unique_error_message
message=unique_error_message,
condition=condition,
)


Expand Down
27 changes: 26 additions & 1 deletion rest_framework/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.db.models import Exists
from django.utils.translation import gettext_lazy as _

from rest_framework.compat import get_referenced_base_fields_from_q
from rest_framework.exceptions import ValidationError
from rest_framework.utils.representation import smart_repr

Expand Down Expand Up @@ -52,10 +53,11 @@ class UniqueValidator:
message = _('This field must be unique.')
requires_context = True

def __init__(self, queryset, message=None, lookup='exact'):
def __init__(self, queryset, message=None, lookup='exact', condition=None):
self.queryset = queryset
self.message = message or self.message
self.lookup = lookup
self.condition = condition

def filter_queryset(self, value, queryset, field_name):
"""
Expand All @@ -80,6 +82,29 @@ def __call__(self, value, serializer_field):
# Determine the existing instance, if this is an update operation.
instance = getattr(serializer_field.parent, 'instance', None)

# For create operations with a conditional unique constraint, check
# whether the condition applies to the new object. If the condition
# references other fields (e.g. Q(global_id__gte=3)), and the new
# object's values don't satisfy it, the constraint does not apply.
if self.condition is not None and instance is None:
parent = serializer_field.parent
if hasattr(parent, 'initial_data'):
condition_source_fields = get_referenced_base_fields_from_q(self.condition)
against = {}
for f in parent._writable_fields:
if f.source in condition_source_fields:
raw_val = parent.initial_data.get(f.field_name)
if raw_val is not None:
try:
against[f.source] = f.to_internal_value(raw_val)
except Exception:
against[f.source] = raw_val
try:
if not self.condition.check(against):
return
except Exception:
pass

queryset = self.queryset
queryset = self.filter_queryset(value, queryset, field_name)
queryset = self.exclude_current_instance(queryset, instance)
Expand Down
Loading