File size: 6,338 Bytes
b4f2238 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | """Module for log."""
from __future__ import annotations
import logging
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar
from django.core.files.uploadedfile import UploadedFile
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.cases.utils import CASE_LOG_ALLOWED_EXTENSIONS, CASE_LOG_MAX_FILE_SIZE
from apps.core.filesystem.storage import KeepOriginalNameStorage
_SENTINEL = object()
from .case import Case
if TYPE_CHECKING:
from django.db.models.fields.related_descriptors import RelatedManager
# 案件日志附件存储
case_log_storage = KeepOriginalNameStorage()
logger = logging.getLogger(__name__)
def validate_log_attachment(file: UploadedFile) -> None:
"""验证日志附件"""
name = str(getattr(file, "name", ""))
size: int = int(getattr(file, "size", 0) or 0)
ext = Path(name).suffix.lower()
if ext not in CASE_LOG_ALLOWED_EXTENSIONS:
from django.core.exceptions import ValidationError
raise ValidationError(_("不支持的文件类型"))
if size and size > CASE_LOG_MAX_FILE_SIZE:
from django.core.exceptions import ValidationError
raise ValidationError(_("文件大小超过50MB限制"))
class CaseLog(models.Model):
id: int
case = models.ForeignKey(Case, on_delete=models.CASCADE, related_name="logs", verbose_name=_("案件"))
content = models.TextField(verbose_name=_("日志内容"))
actor = models.ForeignKey(
"organization.Lawyer", on_delete=models.PROTECT, related_name="case_logs", verbose_name=_("操作人")
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("创建日期"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("修改日期"))
if TYPE_CHECKING:
attachments: RelatedManager[CaseLogAttachment]
versions: RelatedManager[CaseLogVersion]
class Meta:
verbose_name = _("日志")
verbose_name_plural = _("日志")
indexes: ClassVar = [
models.Index(fields=["case", "-created_at"]),
models.Index(fields=["actor"]),
]
def __str__(self) -> str:
return f"{self.case_id}-{self.actor_id}-{self.created_at}"
def _exported_reminders(self) -> list[dict[str, Any]]:
cached = getattr(self, "_cached_exported_reminders", _SENTINEL)
if cached is not _SENTINEL:
return cached
if not getattr(self, "id", None):
self._cached_exported_reminders = []
return []
reminders: list[dict[str, Any]]
try:
from apps.core.interfaces import ServiceLocator
reminder_service = ServiceLocator.get_reminder_service()
reminders = reminder_service.export_case_log_reminders_internal(case_log_id=int(self.id))
except Exception:
logger.exception("case_log_export_reminders_failed", extra={"case_log_id": int(self.id)})
reminders = []
self._cached_exported_reminders = reminders
return reminders
@property
def reminder_entries(self) -> list[dict[str, Any]]:
return self._exported_reminders()
@property
def has_reminders(self) -> bool:
return bool(self._exported_reminders())
@property
def reminder_count(self) -> int:
return len(self._exported_reminders())
@property
def _latest_reminder(self) -> Any | None:
"""缓存最近的提醒记录,避免重复查询。"""
cached = getattr(self, "_cached_latest_reminder", _SENTINEL)
if cached is not _SENTINEL:
return cached
if not getattr(self, "id", None):
self._cached_latest_reminder = None
return None
reminder: dict[str, Any] | None = None
try:
from apps.core.interfaces import ServiceLocator
reminder_service = ServiceLocator.get_reminder_service()
reminder = reminder_service.get_latest_case_log_reminder_internal(case_log_id=int(self.id))
except Exception:
logger.exception("case_log_latest_reminder_failed", extra={"case_log_id": int(self.id)})
reminders = self._exported_reminders()
reminder = reminders[-1] if reminders else None
self._cached_latest_reminder = reminder
return reminder
@property
def reminder_type(self) -> str | None:
"""获取最近的提醒类型。"""
reminder = self._latest_reminder
if reminder is None:
return None
if isinstance(reminder, dict):
return str(reminder.get("reminder_type") or "")
return str(getattr(reminder, "reminder_type", ""))
@property
def reminder_time(self) -> datetime | None:
"""获取最近的提醒时间。"""
reminder = self._latest_reminder
if reminder is None:
return None
due_at = reminder.get("due_at") if isinstance(reminder, dict) else getattr(reminder, "due_at", None)
return due_at if isinstance(due_at, datetime) else None
class CaseLogAttachment(models.Model):
id: int
log = models.ForeignKey(CaseLog, on_delete=models.CASCADE, related_name="attachments", verbose_name=_("日志"))
file = models.FileField(
upload_to="case_logs/",
storage=case_log_storage,
validators=[validate_log_attachment],
verbose_name=_("相关文书"),
)
uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name=_("上传时间"))
class Meta:
verbose_name = _("日志附件")
verbose_name_plural = _("日志附件")
class CaseLogVersion(models.Model):
id: int
log = models.ForeignKey(CaseLog, on_delete=models.CASCADE, related_name="versions", verbose_name=_("日志"))
content = models.TextField(verbose_name=_("历史内容"))
version_at = models.DateTimeField(auto_now_add=True, verbose_name=_("版本时间"))
actor = models.ForeignKey(
"organization.Lawyer", on_delete=models.PROTECT, related_name="case_log_versions", verbose_name=_("操作者")
)
class Meta:
verbose_name = _("案件日志版本")
verbose_name_plural = _("案件日志版本")
def __str__(self) -> str:
return f"{self.log_id}-{self.version_at}"
|