shcool/learning_center/models/course_homework.py
2026-06-16 19:21:41 +08:00

272 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"""
<p>课程:{self.course_id.name}</p>
<p>作业名称:{self.name}</p>
<p>截止时间:{self.deadline or ''}</p>
<p>作业要求:{self.requirement or ''}</p>
"""
# 发送消息(关联当前作业记录,学生在消息中心可直接跳转)
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