272 lines
12 KiB
Python
272 lines
12 KiB
Python
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 |