from django.db import models from django.contrib.auth.models import User import json class Task(models.Model): """任务模型""" TASK_TYPES = [ ('single', '单次任务'), ('scheduled', '周期任务'), ] TASK_STATUSES = [ ('running', '运行中'), # 当前任务在运行 ('generating', '生成中'), # 当前任务有报告正在生成 ('error', '异常'), # 报告生成异常 ('paused', '暂停'), # 任务暂停 ] title = models.CharField(max_length=255, verbose_name='任务标题') description = models.TextField(blank=True, verbose_name='任务描述') requirement = models.TextField(verbose_name='用户需求描述') type = models.CharField(max_length=20, choices=TASK_TYPES, verbose_name='任务类型') status = models.CharField(max_length=20, choices=TASK_STATUSES, default='running', verbose_name='任务状态') # JSON字段存储配置 schedule_config = models.JSONField(null=True, blank=True, verbose_name='周期任务配置') sources_config = models.JSONField(default=dict, verbose_name='信息源配置') web_search_enabled = models.BooleanField(default=True, verbose_name='是否启用联网搜索') user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户') # 时间字段 last_report_time = models.DateTimeField(null=True, blank=True, verbose_name='最新报告生成时间') next_run_time = models.DateTimeField(null=True, blank=True, verbose_name='下次执行时间') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'tasks' verbose_name = '任务' verbose_name_plural = '任务' ordering = ['-last_report_time', '-created_at'] indexes = [ models.Index(fields=['user', 'status']), models.Index(fields=['last_report_time']), models.Index(fields=['status']), ] def __str__(self): return f"{self.title} ({self.get_type_display()})" @property def report_count(self): """获取报告数量""" return self.reports.count() @property def has_new_report(self): """是否有新报告(用于排序)""" if not self.last_report_time: return False # 如果最新报告是24小时内生成的,认为是新报告 from django.utils import timezone from datetime import timedelta return timezone.now() - self.last_report_time < timedelta(hours=24) class Report(models.Model): """报告模型""" REPORT_STATUSES = [ ('generating', '生成中'), ('completed', '已完成'), ('failed', '失败'), ] task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='reports', verbose_name='任务') title = models.CharField(max_length=255, verbose_name='报告标题') summary = models.TextField(blank=True, verbose_name='报告摘要') content = models.TextField(verbose_name='报告内容') source_tag = models.CharField(max_length=100, blank=True, verbose_name='信息来源标签') word_count = models.IntegerField(default=0, verbose_name='字数统计') status = models.CharField(max_length=20, choices=REPORT_STATUSES, default='generating', verbose_name='生成状态') generated_at = models.DateTimeField(null=True, blank=True, verbose_name='生成完成时间') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'reports' verbose_name = '报告' verbose_name_plural = '报告' ordering = ['-generated_at', '-created_at'] indexes = [ models.Index(fields=['task', 'generated_at']), models.Index(fields=['status']), models.Index(fields=['generated_at']), ] def __str__(self): return f"{self.title} - {self.task.title}" def save(self, *args, **kwargs): # 自动计算字数 if self.content: self.word_count = len(self.content.replace(' ', '').replace('\n', '')) # 如果状态变为完成,更新生成时间和任务的最新报告时间 if self.status == 'completed' and not self.generated_at: from django.utils import timezone self.generated_at = timezone.now() self.task.last_report_time = self.generated_at self.task.save(update_fields=['last_report_time']) super().save(*args, **kwargs) class ReportSource(models.Model): """报告来源数据模型""" SOURCE_TYPES = [ ('wechat', '微信'), ('official-account', '公众号'), ('feishu', '飞书'), ('dingtalk', '钉钉'), ('email', '邮箱'), ('website', '网页'), ('baidu-pan', '百度网盘'), ('web-search', '联网搜索'), ] report = models.ForeignKey(Report, on_delete=models.CASCADE, related_name='sources', verbose_name='报告') source_type = models.CharField(max_length=20, choices=SOURCE_TYPES, verbose_name='信息源类型') source_name = models.CharField(max_length=255, verbose_name='信息源名称') source_url = models.URLField(blank=True, verbose_name='原始链接') raw_content = models.TextField(blank=True, verbose_name='原始内容') extracted_content = models.TextField(blank=True, verbose_name='提取的关键内容') collected_at = models.DateTimeField(auto_now_add=True, verbose_name='采集时间') class Meta: db_table = 'report_sources' verbose_name = '报告来源数据' verbose_name_plural = '报告来源数据' indexes = [ models.Index(fields=['report']), models.Index(fields=['source_type']), ] def __str__(self): return f"{self.source_name} - {self.report.title}" class TaskLog(models.Model): """任务执行日志模型""" ACTION_TYPES = [ ('created', '创建'), ('started', '开始'), ('paused', '暂停'), ('resumed', '恢复'), ('completed', '完成'), ('error', '错误'), ] task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='logs', verbose_name='任务') action_type = models.CharField(max_length=20, choices=ACTION_TYPES, verbose_name='操作类型') message = models.TextField(blank=True, verbose_name='日志信息') error_details = models.JSONField(null=True, blank=True, verbose_name='错误详情') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') class Meta: db_table = 'task_logs' verbose_name = '任务执行日志' verbose_name_plural = '任务执行日志' ordering = ['-created_at'] indexes = [ models.Index(fields=['task', 'created_at']), models.Index(fields=['action_type']), ] def __str__(self): return f"{self.task.title} - {self.get_action_type_display()}"