Untitled
unknown
python
8 months ago
4.8 kB
29
Indexable
import operator
from enum import Enum
from functools import lru_cache
from typing import Any, Dict, Optional
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.serializers import BaseSerializer
def get_from_attr_or_serializer(field, attrs, serializer, allow_null=False):
"""Helper to retrieve a field from attrs or serializer instance."""
# Implementation assumed to exist as per original code.
def get_original_field_name(field, serializer):
"""Helper to get the original field name for error messages."""
# Implementation assumed to exist as per original code.
class DateComparisonValidator:
"""Validates two dates using a comparison operator with conditional checks.
Retrieves field values from serializer data or instance, checks optional
conditions, and validates the operator-based comparison. Uses caching to
optimize repeated comparisons and structured error reporting.
Args:
field1: First date field name.
field2: Second date field name.
operator: Comparison operator (gt, lt, etc.).
message: Optional custom error message.
condition_field: Optional field to enable validation conditionally.
condition_value: Required value(s) for condition_field to activate validation.
allow_null: If True, allows None values without validation.
"""
requires_context = True
class Operator(str, Enum):
"""Supported comparison operators with human-readable labels."""
gt = "gt", _("greater than")
lt = "lt", _("less than")
gte = "gte", _("greater than or equal to")
lte = "lte", _("less than or equal to")
eq = "eq", _("equal to")
ne = "ne", _("not equal to")
_OPERATOR_FUNCS = {
Operator.gt: operator.gt,
Operator.lt: operator.lt,
Operator.gte: operator.ge,
Operator.lte: operator.le,
Operator.eq: operator.eq,
Operator.ne: operator.ne,
}
def __init__(
self,
field1: str,
field2: str,
operator: Operator,
message: Optional[str] = None,
condition_field: Optional[str] = None,
condition_value: Optional[Any] = None,
allow_null: bool = False,
):
self.field1 = field1
self.field2 = field2
self.operator = operator
self.message = message
self.condition_field = condition_field
self.condition_value = condition_value
self.allow_null = allow_null
self.operator_func = self._OPERATOR_FUNCS[operator]
def __call__(self, attrs: Dict[str, Any], serializer: BaseSerializer) -> None:
"""Performs validation if conditions are met and fields are present."""
if not self._should_validate(attrs, serializer):
return
field1_val = self._get_field_value(self.field1, attrs, serializer)
field2_val = self._get_field_value(self.field2, attrs, serializer)
if None in (field1_val, field2_val):
return # Skip validation if any field is None
if not self._compare_cached(field1_val, field2_val):
self._raise_validation_error(serializer)
def _should_validate(self, attrs: Dict, serializer: BaseSerializer) -> bool:
"""Determines if validation should execute based on condition fields."""
if not self.condition_field:
return True # No condition → always validate
condition_val = get_from_attr_or_serializer(
self.condition_field, attrs, serializer, allow_null=True
)
if self.condition_value is None:
return True # No expected value → validate unconditionally
if isinstance(self.condition_value, list):
return condition_val in self.condition_value
return condition_val == self.condition_value
def _get_field_value(
self, field: str, attrs: Dict, serializer: BaseSerializer
) -> Any:
"""Retrieves a field's value from attrs or serializer instance."""
return get_from_attr_or_serializer(
field, attrs, serializer, allow_null=self.allow_null
)
@lru_cache(maxsize=128)
def _compare_cached(self, a: Any, b: Any) -> bool:
"""Caches comparison results to optimize repeated calls with same inputs."""
return self.operator_func(a, b)
def _raise_validation_error(self, serializer: BaseSerializer) -> None:
"""Constructs and raises a ValidationError with contextual message."""
field_name = get_original_field_name(self.field1, serializer)
message = self.message or _(
f"{field_name} must be {self.operator.label} {self.field2}."
)
raise serializers.ValidationError({field_name: message})Editor is loading...
Leave a Comment