Download apps/documents/models/document_template.py from jun2024/fc: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/documents/models/document_template.py
- Command line
-
hf download hf://spaces/jun2024/fc/apps/documents/models/document_template.py
-
curl -L -o document_template.py https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/documents/models/document_template.py
10.8 kB
| """ | |
| 法律文书生成系统 - 文件模板模型 | |
| 本模块定义文件模板相关的数据模型. | |
| """ | |
| from __future__ import annotations | |
| from typing import TYPE_CHECKING, Any, ClassVar | |
| from django.core.exceptions import ValidationError | |
| from django.db import models | |
| from django.utils.translation import gettext_lazy as _ | |
| from apps.core.utils.path import Path | |
| from apps.documents.storage import document_template_storage, resolve_docx_template_path | |
| from .choices import ( | |
| DocumentCaseFileSubType, | |
| DocumentCaseStage, | |
| DocumentCaseType, | |
| DocumentContractSubType, | |
| DocumentContractType, | |
| DocumentTemplateType, | |
| LegalStatusMatchMode, | |
| ) | |
| if TYPE_CHECKING: | |
| from django.db.models.fields.related_descriptors import RelatedManager | |
| from .evidence import EvidenceList | |
| class DocumentTemplate(models.Model): | |
| """ | |
| 文书模板 | |
| 基于 docx 格式的法律文书模板文件. | |
| 支持两种模式:文件上传和路径引用. | |
| 支持两种模板类型:单个文书和全套文书. | |
| Requirements: 2.1, 2.4, 2.6, 7.2 | |
| """ | |
| id: int | |
| name: Any = models.CharField(max_length=200, verbose_name=_("模板名称")) | |
| template_type: Any = models.CharField( | |
| max_length=20, | |
| choices=DocumentTemplateType.choices, | |
| default=DocumentTemplateType.CONTRACT, | |
| verbose_name=_("模板类型"), | |
| help_text=_("选择此模板用于合同还是案件"), | |
| ) | |
| contract_sub_type: Any = models.CharField( | |
| max_length=30, | |
| choices=DocumentContractSubType.choices, | |
| blank=True, | |
| null=True, | |
| verbose_name=_("合同子类型"), | |
| help_text=_("仅在选择'合同文件模板'时有效,必须选择合同模板或补充协议模板"), | |
| ) | |
| case_sub_type: Any = models.CharField( | |
| max_length=50, | |
| choices=DocumentCaseFileSubType.choices, | |
| blank=True, | |
| null=True, | |
| verbose_name=_("案件文件子类型"), | |
| help_text=_("仅在选择'案件文件模板'时有效,可选择诉状材料、证据材料、授权委托材料等"), | |
| ) | |
| file = models.FileField( | |
| storage=document_template_storage, | |
| upload_to="", # 存储类会自动处理路径 | |
| blank=True, | |
| null=True, | |
| verbose_name=_("上传文件"), | |
| ) | |
| file_path: Any = models.CharField( | |
| max_length=500, blank=True, verbose_name=_("文件路径"), help_text=_("相对于模板基础目录的路径") | |
| ) | |
| # 适用范围字段(与文件夹模板保持一致) | |
| case_types: Any = models.JSONField( | |
| default=list, verbose_name=_("案件类型"), help_text=_("JSON 数组,如 ['civil', 'criminal'],支持多选") | |
| ) | |
| case_stages: Any = models.JSONField( | |
| default=list, verbose_name=_("案件阶段"), help_text=_("JSON 数组,如 ['first_trial', 'second_trial'],支持多选") | |
| ) | |
| contract_types: Any = models.JSONField( | |
| default=list, verbose_name=_("合同类型"), help_text=_("JSON 数组,如 ['civil', 'criminal'],支持多选") | |
| ) | |
| legal_statuses: Any = models.JSONField( | |
| default=list, | |
| blank=True, | |
| verbose_name=_("我方诉讼地位"), | |
| help_text=_("可单选或多选;为空表示匹配任意诉讼地位"), | |
| ) | |
| legal_status_match_mode: Any = models.CharField( | |
| max_length=16, | |
| choices=LegalStatusMatchMode.choices, | |
| default=LegalStatusMatchMode.ANY, | |
| verbose_name=_("诉讼地位匹配模式"), | |
| ) | |
| applicable_institutions: Any = models.JSONField( | |
| default=list, | |
| blank=True, | |
| verbose_name=_("适用机构"), | |
| help_text=_("JSON 数组,如 ['北京市第一中级人民法院'],支持多选"), | |
| ) | |
| is_active: Any = models.BooleanField(default=True, verbose_name=_("是否启用")) | |
| created_at: Any = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间")) | |
| updated_at: Any = models.DateTimeField(auto_now=True, verbose_name=_("更新时间")) | |
| if TYPE_CHECKING: | |
| folder_bindings: RelatedManager[DocumentTemplateFolderBinding] | |
| evidence_lists: RelatedManager[EvidenceList] | |
| class Meta: | |
| app_label = "documents" | |
| verbose_name = _("文件模板") | |
| verbose_name_plural = _("文件模板") | |
| ordering: ClassVar = ["-updated_at"] | |
| indexes: ClassVar = [ | |
| models.Index(fields=["template_type"]), | |
| models.Index(fields=["is_active"]), | |
| ] | |
| def __str__(self) -> str: | |
| return str(self.name) | |
| def clean(self) -> None: | |
| file_present = bool(self.file) | |
| file_path_present = bool((self.file_path or "").strip()) | |
| if file_present and file_path_present: | |
| message = _("不能同时提供上传文件和文件路径") | |
| raise ValidationError({"file": message, "file_path": message}) | |
| if not file_present and not file_path_present: | |
| raise ValidationError(_("必须提供上传文件或文件路径")) | |
| def get_file_location(self) -> str: | |
| """获取文件实际位置""" | |
| if self.file and self.file.name: | |
| return str(self.file.storage.path(self.file.name)) | |
| if self.file_path: | |
| return str(resolve_docx_template_path(self.file_path)) | |
| return "" | |
| def _get_types_display(self, types_list: list[Any], choices_class: type[models.TextChoices]) -> str: | |
| """获取类型列表的显示文本""" | |
| if not types_list: | |
| return "-" | |
| if len(types_list) == 1: | |
| return str(dict(choices_class.choices).get(types_list[0], types_list[0])) | |
| return f"{len(types_list)}种类型" | |
| def template_type_display(self) -> str: | |
| """模板类型显示""" | |
| base_type = str(dict(DocumentTemplateType.choices).get(self.template_type, self.template_type)) | |
| if self.template_type == DocumentTemplateType.CONTRACT and self.contract_sub_type: | |
| sub_type = str(dict(DocumentContractSubType.choices).get(self.contract_sub_type, self.contract_sub_type)) | |
| return f"{base_type} - {sub_type}" | |
| if self.template_type == DocumentTemplateType.CASE and self.case_sub_type: | |
| sub_type = str(dict(DocumentCaseFileSubType.choices).get(self.case_sub_type, self.case_sub_type)) | |
| return f"{base_type} - {sub_type}" | |
| return base_type | |
| def case_types_display(self) -> str: | |
| """案件类型显示""" | |
| return self._get_types_display(self.case_types, DocumentCaseType) | |
| def case_stages_display(self) -> str: | |
| """案件阶段显示""" | |
| return self._get_types_display(self.case_stages, DocumentCaseStage) | |
| def contract_types_display(self) -> str: | |
| """合同类型显示""" | |
| return self._get_types_display(self.contract_types, DocumentContractType) | |
| def get_legal_statuses_display(self) -> str: | |
| """获取诉讼地位显示文本""" | |
| from apps.core.models.enums import LegalStatus | |
| choices = dict(LegalStatus.choices) | |
| statuses = self.legal_statuses or [] | |
| labels = [str(choices.get(code, code)) for code in statuses] | |
| return "、".join([x for x in labels if x]) or "任意" | |
| def absolute_file_path(self) -> str: | |
| """文件的绝对路径""" | |
| if not self.file_path: | |
| return "" | |
| return str(resolve_docx_template_path(self.file_path)) | |
| class DocumentTemplateFolderBinding(models.Model): | |
| """ | |
| 文件模板与文件夹节点绑定 | |
| 建立文件模板和文件夹模板中具体节点的多对多关系. | |
| 支持: | |
| - 一个文件模板绑定到多个文件夹模板的不同节点 | |
| - 同一文件夹模板中,一个文件模板可放在不同位置 | |
| Requirements: 2.8 | |
| """ | |
| id: int | |
| document_template_id: int # 外键ID字段 | |
| folder_template_id: int # 外键ID字段 | |
| document_template: Any = models.ForeignKey( | |
| "documents.DocumentTemplate", | |
| on_delete=models.CASCADE, | |
| related_name="folder_bindings", | |
| verbose_name=_("文件模板"), | |
| ) | |
| folder_template: Any = models.ForeignKey( | |
| "documents.FolderTemplate", | |
| on_delete=models.CASCADE, | |
| related_name="document_bindings", | |
| verbose_name=_("文件夹模板"), | |
| ) | |
| folder_node_id: Any = models.CharField( | |
| max_length=100, | |
| verbose_name=_("文件夹节点ID"), | |
| help_text=_("文件夹结构JSON中的节点ID"), | |
| ) | |
| folder_node_path: Any = models.CharField( | |
| max_length=500, | |
| blank=True, | |
| verbose_name=_("文件夹路径"), | |
| help_text=_("自动计算的文件夹路径,如:一审/1-立案材料/1-起诉状和反诉答辩状"), | |
| ) | |
| is_active: Any = models.BooleanField(default=True, verbose_name=_("是否启用")) | |
| created_at: Any = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间")) | |
| updated_at: Any = models.DateTimeField(auto_now=True, verbose_name=_("更新时间")) | |
| class Meta: | |
| app_label = "documents" | |
| verbose_name = _("文件模板文件夹绑定") | |
| verbose_name_plural = _("文件模板文件夹绑定") | |
| ordering: ClassVar = ["folder_template", "document_template"] | |
| # 同一文件模板在同一文件夹模板的同一节点只能绑定一次 | |
| unique_together: ClassVar = ["document_template", "folder_template", "folder_node_id"] | |
| indexes: ClassVar = [ | |
| models.Index(fields=["folder_template", "folder_node_id"]), | |
| models.Index(fields=["document_template"]), | |
| models.Index(fields=["is_active"]), | |
| ] | |
| def __str__(self) -> str: | |
| return ( | |
| f"{self.document_template.name} → " | |
| f"{self.folder_template.name}/{self.folder_node_path or self.folder_node_id}" | |
| ) | |
| def save(self, *args: Any, **kwargs: Any) -> None: | |
| """保存时自动根据 folder_node_id 计算 folder_node_path""" | |
| if self.folder_node_id and self.folder_template_id: | |
| try: | |
| structure = self.folder_template.structure or {} | |
| path = self._find_node_path(structure.get("children", []), self.folder_node_id, []) | |
| self.folder_node_path = "/".join(path) if path else "" | |
| except Exception: | |
| pass | |
| super().save(*args, **kwargs) | |
| def _find_node_path(self, children: list[Any], target_id: str, current_path: list[str]) -> list[str]: | |
| for child in children: | |
| path = current_path + [child.get("name", "")] | |
| if child.get("id") == target_id: | |
| return path | |
| result = self._find_node_path(child.get("children", []), target_id, path) | |
| if result: | |
| return result | |
| return [] | |