validators.py 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. from django.db import DataError
  2. from rest_framework.exceptions import ValidationError
  3. from rest_framework.validators import qs_exists, qs_filter, UniqueTogetherValidator
  4. def qs_exclude(queryset, **kwargs):
  5. try:
  6. return queryset.exclude(**kwargs)
  7. except (TypeError, ValueError, DataError):
  8. return queryset.none()
  9. class ExclusionConstraintValidator(UniqueTogetherValidator):
  10. """
  11. Validator that implements ExclusionConstraints, currently very basic with support for one field only.
  12. Should be applied to the serializer class, not to an individual field.
  13. No-op if parent serializer is a list serializer (many=True). We expect the list serializer to assure exclusivity.
  14. """
  15. message = 'This field violates an exclusion constraint.'
  16. def __init__(self, queryset, fields, exclusion_condition, message=None):
  17. super().__init__(queryset, fields, message)
  18. self.exclusion_condition = exclusion_condition
  19. def filter_queryset(self, attrs, queryset, serializer):
  20. qs = super().filter_queryset(attrs, queryset, serializer)
  21. # Determine the exclusion filters and prepare the queryset.
  22. field_name = self.exclusion_condition[0]
  23. value = self.exclusion_condition[1]
  24. source = serializer.fields[field_name].source
  25. if serializer.instance is not None:
  26. if source not in attrs:
  27. attrs[source] = getattr(serializer.instance, source)
  28. exclusion_method = qs_exclude if attrs[source] == value else qs_filter
  29. return exclusion_method(qs, **{field_name: value})
  30. def __call__(self, attrs, serializer, *args, **kwargs):
  31. # Ignore validation if the many flag is set
  32. if getattr(serializer.root, 'many', False):
  33. return
  34. self.enforce_required_fields(attrs, serializer)
  35. queryset = self.queryset
  36. queryset = self.filter_queryset(attrs, queryset, serializer)
  37. queryset = self.exclude_current_instance(attrs, queryset, serializer.instance)
  38. # Ignore validation if any field is None
  39. checked_values = [
  40. value for field, value in attrs.items() if field in self.fields
  41. ]
  42. if None not in checked_values and qs_exists(queryset):
  43. types = queryset.values_list('type', flat=True)
  44. types = ', '.join(types)
  45. message = self.message.format(types=types)
  46. raise ValidationError(message, code='exclusive')