from odoo import api, fields, models from odoo.exceptions import ValidationError from datetime import datetime class CourseHomework(models.Model): _name = 'course.homework' _description = '课程作业' _rec_name = 'name' _inherit = ['mail.thread'] _order = 'course_id, deadline, id' name = fields.Char(string='作业名称', required=True) description = fields.Html(string='作业描述') requirement = fields.Html(string='作业要求') course_id = fields.Many2one('learning.course', string='所属课程', required=True, ondelete='cascade') chapter_id = fields.Many2one('course.chapter', string='关联章节') total_score = fields.Float(string='作业总分', default=100.0, required=True) publish_time = fields.Datetime(string='发布时间') deadline = fields.Datetime(string='提交截止时间', required=True) late_deadline = fields.Datetime(string='补交截止时间') state = fields.Selection([ ('draft', '草稿'), ('published', '已发布'), ('closed', '已关闭'), ], string='状态', default='draft', tracking=True) is_submit_open = fields.Boolean(string='提交通道开启', compute='_compute_submit_channel', store=True) allow_late = fields.Boolean(string='允许补交', default=False) total_students = fields.Integer(string='应提交人数', compute='_compute_submit_stats') submitted_count = fields.Integer(string='已提交人数', compute='_compute_submit_stats') late_count = fields.Integer(string='逾期提交人数', compute='_compute_submit_stats') unsubmitted_count = fields.Integer(string='未提交人数', compute='_compute_submit_stats') # 添加教学班关联 teaching_class_id = fields.Many2one('course.teaching_class', string='教学班', ondelete='cascade', domain="[('course_id', '=', course_id)]") submit_ids = fields.One2many('course.homework.submit', 'homework_id', string='提交记录') score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项') # ====================== 新增:自动计算提交通道是否开启 ====================== @api.depends('state', 'deadline') def _compute_submit_channel(self): now = fields.Datetime.now() for record in self: # 仅已发布、且当前时间未到截止时间,通道才开启 if record.state == 'published' and record.deadline and record.deadline > now: record.is_submit_open = True else: record.is_submit_open = False # ====================== 统计提交人数 ====================== def _compute_submit_stats(self): for record in self: domain = [('state', '=', 'enrolled')] if record.teaching_class_id: domain.append(('teaching_class_id', '=', record.teaching_class_id.id)) else: domain.append(('course_id', '=', record.course_id.id)) enrollments = self.env['course.teaching_class.enrollment'].search(domain) student_ids = enrollments.mapped('student_id') record.total_students = len(student_ids) base_domain = [('homework_id', '=', record.id)] all_submit = self.env['course.homework.submit'].search(base_domain) # 已提交 = 状态submitted/graded submit_ok = all_submit.filtered(lambda s: s.state in ['submitted', 'graded']) late_submit = submit_ok.filtered(lambda s: s.is_late) record.submitted_count = len(submit_ok) record.late_count = len(late_submit) # 未提交 = 总人数 - 已提交人数 record.unsubmitted_count = record.total_students - len(submit_ok) # ====================== 发布作业(核心逻辑修正) ====================== def action_publish(self): self.ensure_one() if self.state == 'published': return self.state = 'published' self.publish_time = fields.Datetime.now() # 1. 批量为本班学生生成空白未提交记录 self._auto_create_empty_submit() # 2. 推送作业通知给学生 self._send_homework_notice_to_students() def _send_homework_notice_to_students(self): """发布作业后推送消息给教学班学生""" self.ensure_one() if not self.teaching_class_id: return # 获取当前教学班所有已选课学生 enrolls = self.env['course.teaching_class.enrollment'].search([ ('teaching_class_id', '=', self.teaching_class_id.id), ('state', '=', 'enrolled') ]) student_users = enrolls.mapped('student_id.user_id').filtered(lambda u: u) if not student_users: return # 消息内容 subject = f"新作业发布:{self.name}" body = f"""
课程:{self.course_id.name}
作业名称:{self.name}
截止时间:{self.deadline or '无'}
作业要求:{self.requirement or '无'}
""" # 发送消息(关联当前作业记录,学生在消息中心可直接跳转) self.message_post( subject=subject, body=body, partner_ids=student_users.mapped('partner_id').ids, subtype_xmlid='mail.mt_comment' ) def _auto_create_empty_submit(self): """仅发布时执行:自动给教学班所有已选课学生生成空白未提交记录""" self.ensure_one() if not self.teaching_class_id: return # 获取当前教学班已注册学生 enroll_records = self.env['course.teaching_class.enrollment'].search([ ('teaching_class_id', '=', self.teaching_class_id.id), ('state', '=', 'enrolled') ]) student_ids = enroll_records.mapped('student_id').ids if not student_ids: return # 过滤掉已经存在提交记录的学生,避免重复生成 exist_student_ids = self.submit_ids.mapped('student_id').ids need_create_stu = [sid for sid in student_ids if sid not in exist_student_ids] if not need_create_stu: return submit_model = self.env['course.homework.submit'] batch_vals = [] for stu_id in need_create_stu: batch_vals.append({ 'homework_id': self.id, 'student_id': stu_id, 'state': 'unsubmit' # 默认未提交 }) # 批量创建空白提交记录 submit_model.create(batch_vals) # ====================== 草稿/关闭作业 ====================== def action_draft(self): self.state = 'draft' def action_close(self): self.state = 'closed' # 关闭作业强制关闭提交通道 self.is_submit_open = False # ====================== 时间校验约束 ====================== @api.constrains('deadline', 'late_deadline') def _check_deadline(self): for record in self: if record.late_deadline and record.late_deadline < record.deadline: raise ValidationError('补交截止时间必须晚于提交截止时间') # ====================== 学生提交相关计算字段 ====================== def _get_current_student(self): """获取当前登录的学生""" student = self.env['student.info'].search([('user_id', '=', self.env.uid)], limit=1) return student # 当前学生的提交记录 my_submit_id = fields.Many2one('course.homework.submit', string='我的提交', compute='_compute_my_submit') my_submit_state = fields.Char(string='提交状态', compute='_compute_my_submit') my_submit_time = fields.Datetime(string='提交时间', compute='_compute_my_submit') my_score = fields.Float(string='我的得分', compute='_compute_my_submit') my_grade_level = fields.Char(string='我的等级', compute='_compute_my_submit') my_comment = fields.Html(string='我的评语', compute='_compute_my_submit') my_submit_file = fields.Binary(string='我的提交文件', compute='_compute_my_submit') my_submit_filename = fields.Char(string='文件名', compute='_compute_my_submit') my_submit_content = fields.Html(string='提交内容', compute='_compute_my_submit') # 前端展示提交状态 submit_state = fields.Selection([ ('not_submitted', '未提交'), ('submitted', '已提交'), ('graded', '已批改'), ('late', '逾期提交'), ], string='提交状态', compute='_compute_my_submit') # 是否可以提交:严格依赖提交通道开启状态 can_submit = fields.Boolean(string='可以提交', compute='_compute_can_submit') @api.depends('submit_ids', 'submit_ids.student_id', 'submit_ids.state', 'submit_ids.is_late') def _compute_my_submit(self): student = self._get_current_student() for record in self: if not student: record.my_submit_id = False record.submit_state = 'not_submitted' continue submit = record.submit_ids.filtered(lambda s: s.student_id == student) if not submit: record.my_submit_id = False record.submit_state = 'not_submitted' continue submit = submit[0] record.my_submit_id = submit.id record.my_submit_time = submit.submit_time record.my_score = submit.score record.my_grade_level = submit.grade_level record.my_comment = submit.comment record.my_submit_file = submit.submit_file record.my_submit_filename = submit.submit_filename record.my_submit_content = submit.submit_content # 适配前端状态展示 if submit.state == 'unsubmit': record.submit_state = 'not_submitted' elif submit.state == 'graded': record.submit_state = 'graded' elif submit.is_late: record.submit_state = 'late' else: record.submit_state = 'submitted' @api.depends('state', 'is_submit_open', 'my_submit_state') def _compute_can_submit(self): for record in self: # 核心条件:提交通道必须开启,且未提交/未批改 can = False if record.is_submit_open and record.my_submit_state not in ['submitted', 'graded']: can = True record.can_submit = can # ====================== 提交作业按钮方法 ====================== def action_submit_homework(self): self.ensure_one() student = self._get_current_student() if not student: raise ValidationError('请先关联学生信息') # 核心校验:通道关闭直接报错,禁止进入提交弹窗 if not self.can_submit: raise ValidationError('当前提交通道已关闭,无法提交作业') # 后续打开提交表单逻辑不变 exist_submit = self.env['course.homework.submit'].search([ ('homework_id', '=', self.id), ('student_id', '=', student.id) ], limit=1) if exist_submit: return { 'type': 'ir.actions.act_window', 'name': '提交作业', 'res_model': 'course.homework.submit', 'res_id': exist_submit.id, 'view_mode': 'form', 'target': 'new', } else: return { 'type': 'ir.actions.act_window', 'name': '提交作业', 'res_model': 'course.homework.submit', 'view_mode': 'form', 'target': 'new', 'context': {'default_homework_id': self.id, 'default_student_id': student.id}, } # 移除原create里自动生成空白提交的代码,草稿不生成 def create(self, vals): homework_record = super().create(vals) # 草稿状态不执行自动生成提交记录,移到action_publish return homework_record