fc / apps /reminders /services /validators.py
jun2024's picture
Upload 2241 files
b4f2238 verified
Raw History Blame Contribute Delete
4.05 kB
"""Validation helpers for reminder services."""
from __future__ import annotations
import json
from datetime import datetime
from typing import TYPE_CHECKING, Any
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from apps.core.exceptions import ValidationException
from ..models import ReminderType
if TYPE_CHECKING:
from django.utils.functional import Promise
from ..ports import CaseLogTargetQueryPort, CaseTargetQueryPort, ContractTargetQueryPort
def normalize_target_id(value: int | None, *, field_name: str | Promise) -> int | None:
if value is None:
return None
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
raise ValidationException(_("%(field_name)s 必须为正整数") % {"field_name": field_name})
return value
def validate_positive_id(value: int, *, field_name: str | Promise) -> None:
"""校验 ID 为正整数(非 bool)。"""
if isinstance(value, bool) or value <= 0:
raise ValidationException(_("%(field_name)s 必须为正整数") % {"field_name": field_name})
def validate_binding_exclusive(
*,
contract_id: int | None,
case_id: int | None,
case_log_id: int | None,
) -> None:
bound_count = sum(target_id is not None for target_id in (contract_id, case_id, case_log_id))
if bound_count > 1:
raise ValidationException(_("合同、案件、案件日志最多只能绑定一个"))
def validate_fk_exists(
*,
contract_id: int | None,
case_id: int | None,
case_log_id: int | None,
contract_target_query: ContractTargetQueryPort | None = None,
case_target_query: CaseTargetQueryPort | None = None,
case_log_target_query: CaseLogTargetQueryPort | None = None,
) -> None:
"""校验外键引用的记录是否存在。"""
if contract_id is not None:
if contract_target_query is None:
raise RuntimeError("ContractTargetQueryPort not provided")
if not contract_target_query.exists(contract_id):
raise ValidationException(_("合同 %(id)s 不存在") % {"id": contract_id})
if case_id is not None:
if case_target_query is None:
raise RuntimeError("CaseTargetQueryPort not provided")
if not case_target_query.exists(case_id):
raise ValidationException(_("案件 %(id)s 不存在") % {"id": case_id})
if case_log_id is not None:
if case_log_target_query is None:
raise RuntimeError("CaseLogTargetQueryPort not provided")
if not case_log_target_query.exists(case_log_id):
raise ValidationException(_("案件日志 %(id)s 不存在") % {"id": case_log_id})
def normalize_reminder_type(reminder_type: str) -> str:
value = str(reminder_type).strip()
if not value:
raise ValidationException(_("提醒类型不能为空"))
if value not in ReminderType.values:
raise ValidationException(_("无效的提醒类型"))
return value
# 与 Reminder.content max_length=255 保持同步
_CONTENT_MAX_LENGTH = 255
def normalize_content(content: str) -> str:
value = str(content).strip()
if not value:
raise ValidationException(_("提醒事项不能为空"))
if len(value) > _CONTENT_MAX_LENGTH:
raise ValidationException(_("提醒事项不能超过 %(max)d 个字符") % {"max": _CONTENT_MAX_LENGTH})
return value
def normalize_due_at(due_at: datetime) -> datetime:
if not isinstance(due_at, datetime):
raise ValidationException(_("到期时间格式不正确"))
if timezone.is_naive(due_at):
return timezone.make_aware(due_at)
return due_at
def normalize_metadata(metadata: Any) -> dict[str, Any]:
if metadata is None:
return {}
if not isinstance(metadata, dict):
raise ValidationException(_("扩展数据必须为 JSON 对象"))
try:
json.dumps(metadata)
except (TypeError, ValueError):
raise ValidationException(_("扩展数据包含不可序列化的值"))
return metadata