school_gengxin #2
@ -25,7 +25,7 @@
|
||||
'views/view_course_teaching_class.xml',
|
||||
'views/view_course_student_score.xml',
|
||||
'views/view_course_homework_student.xml',
|
||||
'views/view_teach_class.xml',
|
||||
# 'views/view_teach_class.xml',
|
||||
|
||||
|
||||
],
|
||||
|
||||
@ -7,4 +7,4 @@ from . import course_homework_submit
|
||||
from . import course_score_item
|
||||
from . import course_student_score
|
||||
from . import course_teaching_class
|
||||
from . import teach_class
|
||||
# from . import teach_class
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,5 +1,6 @@
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from datetime import datetime
|
||||
|
||||
class CourseHomework(models.Model):
|
||||
_name = 'course.homework'
|
||||
@ -14,7 +15,7 @@ class CourseHomework(models.Model):
|
||||
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='发布时间', default=fields.Datetime.now)
|
||||
publish_time = fields.Datetime(string='发布时间')
|
||||
deadline = fields.Datetime(string='提交截止时间', required=True)
|
||||
late_deadline = fields.Datetime(string='补交截止时间')
|
||||
state = fields.Selection([
|
||||
@ -22,7 +23,7 @@ class CourseHomework(models.Model):
|
||||
('published', '已发布'),
|
||||
('closed', '已关闭'),
|
||||
], string='状态', default='draft', tracking=True)
|
||||
is_submit_open = fields.Boolean(string='提交通道开启', default=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')
|
||||
@ -32,60 +33,133 @@ class CourseHomework(models.Model):
|
||||
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='提交记录')
|
||||
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)
|
||||
total_submit = len(all_submit)
|
||||
|
||||
deadline_val = record.deadline
|
||||
if deadline_val:
|
||||
# 按时提交
|
||||
on_time_submit = all_submit.filtered(lambda s: s.submit_time and s.submit_time <= deadline_val)
|
||||
# 逾期提交
|
||||
late_submit = all_submit.filtered(lambda s: s.submit_time and s.submit_time > deadline_val)
|
||||
record.submitted_count = len(on_time_submit)
|
||||
# 已提交 = 状态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)
|
||||
else:
|
||||
record.submitted_count = total_submit
|
||||
record.late_count = 0
|
||||
|
||||
# 未提交 = 班级总人数 - 所有提交人数
|
||||
record.unsubmitted_count = record.total_students - total_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)
|
||||
@ -103,7 +177,7 @@ class CourseHomework(models.Model):
|
||||
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', '已提交'),
|
||||
@ -111,18 +185,24 @@ class CourseHomework(models.Model):
|
||||
('late', '逾期提交'),
|
||||
], string='提交状态', compute='_compute_my_submit')
|
||||
|
||||
# 是否可以提交
|
||||
# 是否可以提交:严格依赖提交通道开启状态
|
||||
can_submit = fields.Boolean(string='可以提交', compute='_compute_can_submit')
|
||||
|
||||
@api.depends('submit_ids')
|
||||
@api.depends('submit_ids', 'submit_ids.student_id', 'submit_ids.state', 'submit_ids.is_late')
|
||||
def _compute_my_submit(self):
|
||||
for record in self:
|
||||
student = self._get_current_student()
|
||||
if 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 submit:
|
||||
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_state = submit.state
|
||||
record.my_submit_time = submit.submit_time
|
||||
record.my_score = submit.score
|
||||
record.my_grade_level = submit.grade_level
|
||||
@ -131,49 +211,62 @@ class CourseHomework(models.Model):
|
||||
record.my_submit_filename = submit.submit_filename
|
||||
record.my_submit_content = submit.submit_content
|
||||
|
||||
# 计算提交状态
|
||||
if submit.state == 'graded':
|
||||
# 适配前端状态展示
|
||||
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'
|
||||
else:
|
||||
record.submit_state = 'not_submitted'
|
||||
else:
|
||||
record.submit_state = 'not_submitted'
|
||||
|
||||
@api.depends('state', 'is_submit_open', 'my_submit_state')
|
||||
def _compute_can_submit(self):
|
||||
for record in self:
|
||||
# 作业已发布、提交通道开启、未截止、且未提交
|
||||
can = (record.state == 'published' and
|
||||
record.is_submit_open and
|
||||
record.deadline and
|
||||
record.deadline > fields.Datetime.now() and
|
||||
record.my_submit_state != 'submitted' and
|
||||
record.my_submit_state != 'graded')
|
||||
# 核心条件:提交通道必须开启,且未提交/未批改
|
||||
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('当前无法提交作业')
|
||||
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,
|
||||
},
|
||||
'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
|
||||
@ -38,6 +38,7 @@ class CourseHomeworkSubmit(models.Model):
|
||||
grader_id = fields.Many2one('res.users', string='批改人')
|
||||
grade_time = fields.Datetime(string='批改时间')
|
||||
state = fields.Selection([
|
||||
('unsubmit', '未提交'),
|
||||
('submitted', '已提交'),
|
||||
('graded', '已批改'),
|
||||
], string='状态', default='submitted')
|
||||
@ -87,6 +88,32 @@ class CourseHomeworkSubmit(models.Model):
|
||||
else:
|
||||
record.grade_level = False
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
submit_rec = super().create(vals)
|
||||
self._check_submit_change_state(submit_rec)
|
||||
return submit_rec
|
||||
|
||||
def write(self, vals):
|
||||
res = super().write(vals)
|
||||
for rec in self:
|
||||
self._check_submit_change_state(rec)
|
||||
return res
|
||||
|
||||
def _check_submit_change_state(self, submit_rec):
|
||||
"""统一提取状态判断逻辑,create和write共用"""
|
||||
# 仅空白未提交状态才自动切换
|
||||
if submit_rec.state != 'unsubmit':
|
||||
return
|
||||
# 判断:有提交文件 或者 填写了提交内容 = 完成提交
|
||||
has_file = bool(submit_rec.submit_file)
|
||||
has_content = bool(submit_rec.submit_content)
|
||||
if has_file or has_content:
|
||||
submit_rec.write({
|
||||
'state': 'submitted',
|
||||
'submit_time': fields.Datetime.now()
|
||||
})
|
||||
|
||||
# class CourseHomeworkSubmitBatchGrade(models.TransientModel):
|
||||
# _name = 'course.homework.submit.batch.grade'
|
||||
# _description = '作业批量批改向导'
|
||||
|
||||
@ -289,3 +289,6 @@ class CourseTeachingClassEnrollmentWizard(models.TransientModel):
|
||||
if create_list:
|
||||
self.env['course.teaching_class.enrollment'].create(create_list)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -11,3 +11,4 @@ class TeachClass(models.Model):
|
||||
stu_sex = fields.Selection(related='student_id.stu_sex', string="性别")
|
||||
stu_phone = fields.Char(related='student_id.stu_phone', string="手机号")
|
||||
stu_grade_xx = fields.Many2one(related='student_id.stu_grade_xx', string="年级")
|
||||
|
||||
|
||||
@ -11,8 +11,6 @@ access_course_syllabus_sys,course.syllabus.access.sys,model_course_syllabus,base
|
||||
access_course_teaching_class_sys,course.teaching.class.access.sys,model_course_teaching_class,base.group_system,1,1,1,1
|
||||
access_course_teaching_class_enrollment_sys,course.teaching.class.enrollment.access.sys,model_course_teaching_class_enrollment,base.group_system,1,1,1,1
|
||||
access_course_teaching_class_enrollment_wizard_sys,course.teaching.class.enrollment.access.sys,model_course_teaching_class_enrollment_wizard,base.group_system,1,1,1,1
|
||||
access_teach_class_teacher,teach.class.access.teacher,model_teach_class,edu_base.group_teacher,1,1,0,0
|
||||
access_teach_class_sys,teach.class.access.sys,model_teach_class,base.group_system,1,1,1,1
|
||||
access_course_teaching_class_teacher,course.teaching.class.access.teacher,model_course_teaching_class,edu_base.group_teacher,1,0,0,0
|
||||
access_course_teaching_class_enrollment_teacher,course.teaching.class.enrollment.access.teacher,model_course_teaching_class_enrollment,edu_base.group_teacher,1,0,0,0
|
||||
access_course_homework_teacher,course.homework.access.teacher,model_course_homework,edu_base.group_teacher,1,1,1,0
|
||||
|
||||
|
@ -204,9 +204,14 @@
|
||||
</record>
|
||||
|
||||
<!-- 二级菜单 -->
|
||||
<record id="menu_course_teaching_class_root" model="ir.ui.menu">
|
||||
<field name="name">我的班级</field>
|
||||
<field name="groups_id" eval="[(6,0,[ref('base.group_system'),ref('edu_base.group_teacher')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="menu_course_teaching_class" model="ir.ui.menu">
|
||||
<field name="name">教学班管理</field>
|
||||
<field name="parent_id" ref="menu_learning_center_root"/>
|
||||
<field name="name">我的班级</field>
|
||||
<field name="parent_id" ref="menu_course_teaching_class_root"/>
|
||||
<field name="action" ref="act_course_teaching_class"/>
|
||||
<field name="sequence">15</field>
|
||||
<field name="groups_id" eval="[(6,0,[ref('base.group_system'),ref('edu_base.group_teacher')])]"/>
|
||||
|
||||
Binary file not shown.
@ -1,4 +1,6 @@
|
||||
from odoo import api,models,fields
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class StudentInfo(models.Model):
|
||||
_name="student.info"
|
||||
@ -24,7 +26,7 @@ class StudentInfo(models.Model):
|
||||
stu_begin_date = fields.Date(string="入学日期", required=True, tracking=True)
|
||||
stu_end_date = fields.Date(string="毕业日期")
|
||||
stu_grade_xx = fields.Many2one('basic.grade.xx',string="年级", tracking=True,domain = lambda self: self.env['basic.grade.xx']._get_current_year_range_domain())
|
||||
stu_grade_bx=fields.Many2one('basic.grade.xx',string='年级',readonle=1)
|
||||
stu_grade_bx=fields.Many2one('basic.grade.xx',string='年级',readonly=1)
|
||||
stu_status = fields.Selection([
|
||||
('studying', '在读'),
|
||||
('graduated', '已毕业'),
|
||||
@ -42,13 +44,57 @@ class StudentInfo(models.Model):
|
||||
major_name = fields.Char(string="专业名称", related='major_id.name', store=True)
|
||||
department_id = fields.Many2one('student.organization', string="院系",store=True,domain="[('org_type', '=', 'college')]")
|
||||
|
||||
# 新增:关联系统登录用户(解决之前作业通知报错缺少user_id的问题)
|
||||
user_id = fields.Many2one('res.users', string="系统登录账号", ondelete='restrict')
|
||||
|
||||
# user_id = fields.Many2one('res.users', string="系统用户", ondelete='restrict')
|
||||
@api.depends('parent_path')
|
||||
def _compute_org_path(self):
|
||||
for record in self:
|
||||
if record.class_id:
|
||||
# 直接使用班级的 parent_path + 班级ID
|
||||
record.org_path = f"{record.class_id.parent_path}{record.class_id.id}/"
|
||||
else:
|
||||
record.org_path = False
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
# 1. 先校验学号,提前取出学号避免多次读取
|
||||
stu_num = vals.get('stu_num', '').strip()
|
||||
new_user = None
|
||||
|
||||
# 2. 学号不为空才执行创建用户逻辑
|
||||
if stu_num:
|
||||
# 校验学号对应的登录账号是否已存在
|
||||
exist_user = self.env['res.users'].search([('login', '=', stu_num)], limit=1)
|
||||
if not exist_user:
|
||||
# 获取两个权限组,并双重校验存在性
|
||||
internal_group = self.env.ref('base.group_user', raise_if_not_found=False)
|
||||
student_group = self.env.ref('edu_base.group_student', raise_if_not_found=False)
|
||||
|
||||
if not internal_group:
|
||||
raise ValidationError("系统内置内部用户组 base.group_user 缺失,无法创建登录账号!")
|
||||
if not student_group:
|
||||
raise ValidationError("权限组 edu_base.group_student 不存在,请先创建该学生权限组!")
|
||||
|
||||
# 组装用户参数,优先取学生填写的邮箱,无则自动生成
|
||||
stu_email = vals.get('stu_email')
|
||||
if not stu_email:
|
||||
stu_email = f"{stu_num}@edu.local"
|
||||
|
||||
user_vals = {
|
||||
'name': vals.get('stu_name', ''),
|
||||
'login': stu_num,
|
||||
'password': stu_num,
|
||||
'email': stu_email,
|
||||
'phone': vals.get('stu_phone', ''),
|
||||
# 同时分配后台登录组 + 学生业务权限组
|
||||
'groups_id': [(6, 0, [student_group.id, internal_group.id])],
|
||||
}
|
||||
# 先创建用户,事务内失败会整体回滚
|
||||
new_user = self.env['res.users'].create(user_vals)
|
||||
|
||||
# 3. 创建学生档案,如果已生成用户则绑定user_id
|
||||
if new_user:
|
||||
vals['user_id'] = new_user.id
|
||||
student_info = super().create(vals)
|
||||
|
||||
return student_info
|
||||
@ -3,7 +3,7 @@
|
||||
<field name="name">student.info.list</field>
|
||||
<field name="model">student.info</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<list >
|
||||
<field name="stu_num"/>
|
||||
<field name="stu_name"/>
|
||||
<field name="stu_sex"/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user