diff --git a/edu_base/__init__.py b/edu_base/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/edu_base/__manifest__.py b/edu_base/__manifest__.py new file mode 100644 index 0000000..e12edf1 --- /dev/null +++ b/edu_base/__manifest__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +{ + 'name': '教育基础权限', + 'version': '18.0.1.0.0', + 'summary': '通用权限组:学生、教师、教务管理员', + 'description': '统一管理全项目通用角色权限组,供其他业务模块依赖使用', + 'author': '', + 'depends': ['base'], # 只依赖系统基础模块 + 'data': [ + 'security/groups.xml', + ], + 'installable': True, + 'auto_install': False, # 不自动安装,手动安装一次即可 + 'application': False, +} \ No newline at end of file diff --git a/edu_base/__pycache__/__init__.cpython-312.pyc b/edu_base/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..665b658 Binary files /dev/null and b/edu_base/__pycache__/__init__.cpython-312.pyc differ diff --git a/edu_base/security/groups.xml b/edu_base/security/groups.xml new file mode 100644 index 0000000..5d36a5c --- /dev/null +++ b/edu_base/security/groups.xml @@ -0,0 +1,26 @@ + + + + + + 学生 + + + + + + + + 教师 + + + + + + + 教务管理员 + + + + + \ No newline at end of file diff --git a/learning_center/__init__.py b/learning_center/__init__.py new file mode 100644 index 0000000..9a7e03e --- /dev/null +++ b/learning_center/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/learning_center/__manifest__.py b/learning_center/__manifest__.py new file mode 100644 index 0000000..22aa1ac --- /dev/null +++ b/learning_center/__manifest__.py @@ -0,0 +1,34 @@ +{ + 'name': 'Learning Course', + 'version': '18.0.1.0.0', + 'category': 'Education', + 'summary': '课程管理 - 课程基础信息', + 'description': """ + 学习中心 + """, + 'author': 'hrrr', + 'depends': [ + 'base', + 'mail', + 'student_organization', + ], + 'data': [ + 'security/ir.model.access.csv', + 'security/record_rules.xml', + 'views/view_learning_course.xml', + 'views/view_course_chapter.xml', + 'views/view_course_chapter_resource.xml', + 'views/view_course_homework.xml', + 'views/view_homework_submit.xml', + 'views/view_course_score_item.xml', + 'views/view_course_syllabus.xml', + 'views/view_course_teaching_class.xml', + 'views/view_course_student_score.xml', + 'views/view_course_homework_student.xml', + 'views/view_teach_class.xml', + + + ], + 'installable': True, + 'application': True, +} \ No newline at end of file diff --git a/learning_center/__pycache__/__init__.cpython-312.pyc b/learning_center/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..8865f24 Binary files /dev/null and b/learning_center/__pycache__/__init__.cpython-312.pyc differ diff --git a/learning_center/models/__init__.py b/learning_center/models/__init__.py new file mode 100644 index 0000000..043df1a --- /dev/null +++ b/learning_center/models/__init__.py @@ -0,0 +1,10 @@ +from . import learning_course +from . import course_chapter +from . import course_chapter_resource +from . import course_syllabus +from . import course_homework +from . import course_homework_submit +from . import course_score_item +from . import course_student_score +from . import course_teaching_class +from . import teach_class \ No newline at end of file diff --git a/learning_center/models/__pycache__/__init__.cpython-312.pyc b/learning_center/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..69332b4 Binary files /dev/null and b/learning_center/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_chapter.cpython-312.pyc b/learning_center/models/__pycache__/course_chapter.cpython-312.pyc new file mode 100644 index 0000000..6c803a3 Binary files /dev/null and b/learning_center/models/__pycache__/course_chapter.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_chapter_resource.cpython-312.pyc b/learning_center/models/__pycache__/course_chapter_resource.cpython-312.pyc new file mode 100644 index 0000000..1a09887 Binary files /dev/null and b/learning_center/models/__pycache__/course_chapter_resource.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_homework.cpython-312.pyc b/learning_center/models/__pycache__/course_homework.cpython-312.pyc new file mode 100644 index 0000000..dce2bc4 Binary files /dev/null and b/learning_center/models/__pycache__/course_homework.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_homework_submit.cpython-312.pyc b/learning_center/models/__pycache__/course_homework_submit.cpython-312.pyc new file mode 100644 index 0000000..076fe2e Binary files /dev/null and b/learning_center/models/__pycache__/course_homework_submit.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_score_item.cpython-312.pyc b/learning_center/models/__pycache__/course_score_item.cpython-312.pyc new file mode 100644 index 0000000..26bd81a Binary files /dev/null and b/learning_center/models/__pycache__/course_score_item.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_student_score.cpython-312.pyc b/learning_center/models/__pycache__/course_student_score.cpython-312.pyc new file mode 100644 index 0000000..dba182a Binary files /dev/null and b/learning_center/models/__pycache__/course_student_score.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_syllabus.cpython-312.pyc b/learning_center/models/__pycache__/course_syllabus.cpython-312.pyc new file mode 100644 index 0000000..25acc02 Binary files /dev/null and b/learning_center/models/__pycache__/course_syllabus.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_teaching_class.cpython-312.pyc b/learning_center/models/__pycache__/course_teaching_class.cpython-312.pyc new file mode 100644 index 0000000..08725f7 Binary files /dev/null and b/learning_center/models/__pycache__/course_teaching_class.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/learning_course.cpython-312.pyc b/learning_center/models/__pycache__/learning_course.cpython-312.pyc new file mode 100644 index 0000000..ddb1005 Binary files /dev/null and b/learning_center/models/__pycache__/learning_course.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/teach_class.cpython-312.pyc b/learning_center/models/__pycache__/teach_class.cpython-312.pyc new file mode 100644 index 0000000..9652096 Binary files /dev/null and b/learning_center/models/__pycache__/teach_class.cpython-312.pyc differ diff --git a/learning_center/models/course_chapter.py b/learning_center/models/course_chapter.py new file mode 100644 index 0000000..87967fb --- /dev/null +++ b/learning_center/models/course_chapter.py @@ -0,0 +1,32 @@ +from odoo import api, fields, models + +class CourseChapter(models.Model): + _name = 'course.chapter' + _description = '课程章节' + _rec_name = 'name' + _inherit = ['mail.thread'] + _order = 'course_id, sequence, id' + + name = fields.Char(string='章节名称', required=True) + sequence = fields.Integer(string='序号', default=10) + description = fields.Html(string='章节描述') + course_id = fields.Many2one('learning.course', string='所属课程', required=True, ondelete='cascade') + is_published = fields.Boolean(string='是否发布', default=True) + resource_count = fields.Integer(string='资源数量', compute='_compute_resource_count') + video_count = fields.Integer(string='视频数量', compute='_compute_resource_count') + doc_count = fields.Integer(string='文档数量', compute='_compute_resource_count') + resource_ids = fields.One2many('course.chapter.resource', 'chapter_id', string='章节资源') + # homework_ids = fields.One2many('course.homework', 'chapter_id', string='关联作业') + + syllabus_ids = fields.One2many('course.syllabus', 'chapter_id', string='教学大纲/教案') + def _compute_resource_count(self): + for record in self: + record.resource_count = len(record.resource_ids) + record.video_count = len(record.resource_ids.filtered(lambda r: r.resource_type == 'video')) + record.doc_count = len(record.resource_ids.filtered(lambda r: r.resource_type in ['pdf', 'ppt'])) + + def action_publish(self): + self.is_published = True + + def action_hide(self): + self.is_published = False \ No newline at end of file diff --git a/learning_center/models/course_chapter_resource.py b/learning_center/models/course_chapter_resource.py new file mode 100644 index 0000000..a780594 --- /dev/null +++ b/learning_center/models/course_chapter_resource.py @@ -0,0 +1,80 @@ +from odoo import api, fields, models + +class CourseChapterResource(models.Model): + _name = 'course.chapter.resource' + _description = '章节资源' + _rec_name = 'name' + _order = 'chapter_id, sequence, id' + + name = fields.Char(string='资源名称', required=True) + sequence = fields.Integer(string='序号', default=10) + description = fields.Text(string='备注说明') + chapter_id = fields.Many2one('course.chapter', string='所属章节', required=True) + course_id = fields.Many2one(related="chapter_id.course_id", string='所属课程', required=True, ondelete='cascade') + chapter_name = fields.Char(related="chapter_id.name", string="章节名称", readonly=True) + chapter_sequence = fields.Integer(related="chapter_id.sequence", string="章节序号", readonly=True) + chapter_is_published = fields.Boolean(related="chapter_id.is_published", string="章节发布状态", readonly=True) + resource_type = fields.Selection([ + ('video', '视频'), + ('pdf', 'PDF文档'), + ('ppt', 'PPT演示文稿'), + ('other', '其他'), + ], string='资源类型', required=True) + video_file = fields.Binary(string='视频文件', attachment=True) + video_filename = fields.Char(string='视频文件名') + video_url = fields.Char(string='视频链接') + video_duration = fields.Integer(string='时长(秒)') + doc_file = fields.Binary(string='文档文件', attachment=True) + doc_filename = fields.Char(string='文档文件名') + doc_page_count = fields.Integer(string='页数') + # 添加教学班关联(可选,资源可共享给所有教学班) + teaching_class_id = fields.Many2one('course.teaching_class', string='教学班', + ondelete='cascade') + preview_url = fields.Char(string='预览链接', compute='_compute_preview_url') + is_hidden = fields.Boolean(string='是否隐藏', default=False) + + course_name = fields.Char(related="chapter_id.course_id.name", string="课程名称", readonly=True) + course_code = fields.Char(related="chapter_id.course_id.code", string="课程代码", readonly=True) + course_credit = fields.Float(related="chapter_id.course_id.credit", string="学分", readonly=True) + + # 新增:批量上传附件字段(核心) + resource_attachment_ids = fields.Many2many( + comodel_name='ir.attachment', + relation='course_chapter_resource_attachment_rel', + column1='resource_id', + column2='attachment_id', + string='批量上传资源附件', + ) + # 新增:附件数量统计(可选,用于列表显示) + attachment_count = fields.Integer(string="附件数", compute="_compute_attachment_count") + + @api.depends('resource_attachment_ids') + def _compute_attachment_count(self): + for rec in self: + rec.attachment_count = len(rec.resource_attachment_ids) + + def _compute_preview_url(self): + for record in self: + # 优先使用批量附件中的第一个文件作为预览链接 + if record.resource_attachment_ids: + first_attachment = record.resource_attachment_ids[0] + record.preview_url = f'/web/content/{first_attachment.id}?download=false' + elif record.resource_type == 'video' and record.video_file: + record.preview_url = f'/web/content/course.chapter.resource/{record.id}/video_file' + elif record.resource_type in ['pdf', 'ppt'] and record.doc_file: + record.preview_url = f'/web/content/course.chapter.resource/{record.id}/doc_file' + else: + record.preview_url = False + + def action_toggle_hide(self): + self.is_hidden = not self.is_hidden + + def action_preview(self): + self.ensure_one() + if self.preview_url: + return { + 'type': 'ir.actions.act_url', + 'url': self.preview_url, + 'target': 'new' + } + return False \ No newline at end of file diff --git a/learning_center/models/course_homework.py b/learning_center/models/course_homework.py new file mode 100644 index 0000000..bfab775 --- /dev/null +++ b/learning_center/models/course_homework.py @@ -0,0 +1,179 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + +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='发布时间', default=fields.Datetime.now) + 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='提交通道开启', default=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='提交记录') + + 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) + record.late_count = len(late_submit) + else: + record.submitted_count = total_submit + record.late_count = 0 + + # 未提交 = 班级总人数 - 所有提交人数 + record.unsubmitted_count = record.total_students - total_submit + + def action_publish(self): + self.state = 'published' + self.publish_time = fields.Datetime.now() + + 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') + def _compute_my_submit(self): + for record in self: + student = self._get_current_student() + if student: + submit = record.submit_ids.filtered(lambda s: s.student_id == student) + if submit: + 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 + 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 == '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' + + 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') + 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('当前无法提交作业') + + # 打开提交表单 + 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, + }, + } \ No newline at end of file diff --git a/learning_center/models/course_homework_submit.py b/learning_center/models/course_homework_submit.py new file mode 100644 index 0000000..681e245 --- /dev/null +++ b/learning_center/models/course_homework_submit.py @@ -0,0 +1,102 @@ +from odoo import api, fields, models + +class CourseHomeworkSubmit(models.Model): + _name = 'course.homework.submit' + _description = '作业提交记录' + _rec_name = 'display_name' + _inherit = ['mail.thread'] + _order = 'homework_id, student_id' + + display_name = fields.Char(string='显示名称', compute='_compute_display_name', store=True) + student_id = fields.Many2one('student.info', string='学生', required=True) + homework_id = fields.Many2one('course.homework', string='作业', required=True, ondelete='cascade') + @api.depends('homework_id.name', 'student_id.stu_name') + def _compute_display_name(self): + for record in self: + record.display_name = f"{record.student_id.stu_name} - {record.homework_id.name}" + + course_name = fields.Char(related="homework_id.course_id.name", string="课程名称", readonly=True) + course_code = fields.Char(related="homework_id.course_id.code", string="课程代码", readonly=True) + course_credit = fields.Float(related="homework_id.course_id.credit", string="学分", readonly=True) + + homework_name = fields.Char(related="homework_id.name", string="作业名称", readonly=True) + homework_total_score = fields.Float(related="homework_id.total_score", string="作业总分", readonly=True) + homework_deadline = fields.Datetime(related="homework_id.deadline", string="截止时间", readonly=True) + homework_late_deadline = fields.Datetime(related="homework_id.late_deadline", string="补交截止时间", readonly=True) + + student_name = fields.Char(related="student_id.stu_name", string="姓名", readonly=True) + student_no = fields.Char(related="student_id.stu_num", string="学号", readonly=True) + student_class_id = fields.Many2one(related="student_id.class_id", string="班级", readonly=True) + student_major_id = fields.Many2one(related="student_id.major_id", string="专业", readonly=True) + student_phone = fields.Char(related="student_id.stu_phone", string="手机号", readonly=True) + submit_file = fields.Binary(string='提交文件', attachment=True) + submit_filename = fields.Char(string='文件名') + submit_content = fields.Html(string='提交内容') + submit_time = fields.Datetime(string='提交时间') + score = fields.Float(string='得分') + comment = fields.Html(string='批改评语') + grader_id = fields.Many2one('res.users', string='批改人') + grade_time = fields.Datetime(string='批改时间') + state = fields.Selection([ + ('submitted', '已提交'), + ('graded', '已批改'), + ], string='状态', default='submitted') + is_late = fields.Boolean(string='是否逾期', compute='_compute_is_late') + + + def _compute_is_late(self): + for record in self: + deadline = record.homework_id.deadline + if deadline and record.submit_time: + record.is_late = record.submit_time > deadline + else: + record.is_late = False + + def action_grade(self): + self.state = 'graded' + self.grade_time = fields.Datetime.now() + + def action_regrade(self): + self.state = 'submitted' + self.grade_time = False + + # 在 CourseHomeworkSubmit 类中添加 + grade_level = fields.Selection([ + ('A', '优秀(A)'), + ('B', '良好(B)'), + ('C', '中等(C)'), + ('D', '及格(D)'), + ('F', '不及格(F)'), + ], string='等级', compute='_compute_grade_level', store=True) + + @api.depends('score', 'homework_id.total_score') + def _compute_grade_level(self): + for record in self: + if record.score and record.homework_id.total_score: + percent = record.score / record.homework_id.total_score * 100 + if percent >= 90: + record.grade_level = 'A' + elif percent >= 80: + record.grade_level = 'B' + elif percent >= 70: + record.grade_level = 'C' + elif percent >= 60: + record.grade_level = 'D' + else: + record.grade_level = 'F' + else: + record.grade_level = False + +# class CourseHomeworkSubmitBatchGrade(models.TransientModel): +# _name = 'course.homework.submit.batch.grade' +# _description = '作业批量批改向导' +# +# homework_id = fields.Many2one('course.homework', string='作业') +# total_count = fields.Integer(string='待批改总数') +# score = fields.Float(string='统一分数') +# comment = fields.Text(string='统一评语') +# +# # 批量批改执行方法 +# def action_batch_grade(self): +# # 自行补充批量更新分数、评语、状态的业务逻辑 +# return {'type': 'ir.actions.act_window_close'} \ No newline at end of file diff --git a/learning_center/models/course_score_item.py b/learning_center/models/course_score_item.py new file mode 100644 index 0000000..d04617c --- /dev/null +++ b/learning_center/models/course_score_item.py @@ -0,0 +1,54 @@ +from odoo import api, fields, models + +class CourseScoreItem(models.Model): + _name = 'course.score.item' + _description = '成绩项目' + _rec_name = 'name' + _order = 'course_id, weight desc, id' + _inherit = ['mail.thread'] + + + name = fields.Char(string='项目名称', required=True) + description = fields.Char(string='项目描述') + course_id = fields.Many2one('learning.course', string='课程', required=True, ondelete='cascade') + course_name = fields.Char(related="course_id.name", string="课程名称", readonly=True) + course_code = fields.Char(related="course_id.code", string="课程代码", readonly=True) + course_credit = fields.Float(related="course_id.credit", string="学分", readonly=True) + course_exam_type = fields.Selection(related="course_id.exam_type", string="考核方式", readonly=True) + homework_id = fields.Many2one('course.homework', string='作业', required=True, ondelete='cascade') + sequence = fields.Integer(string="排序", default=10) + score_type = fields.Selection([ + ('homework', '作业'), + ('exam', '考试'), + ('experiment', '实验'), + ('attendance', '考勤'), + ('midterm', '期中考试'), + ('final', '期末考试'), + ('other', '其他'), + ], string='成绩类型', required=True) + weight = fields.Float(string='权重(%)', required=True) + full_score = fields.Float(string='满分', default=100.0) + is_active = fields.Boolean(string='是否启用', default=True) + homework_ids = fields.Many2many('course.homework', string='关联作业') + + # 统计字段 + student_count = fields.Integer(string='学生人数', compute='_compute_stats') + avg_score = fields.Float(string='平均分', compute='_compute_stats') + max_score = fields.Float(string='最高分', compute='_compute_stats') + min_score = fields.Float(string='最低分', compute='_compute_stats') + score_detail_ids = fields.One2many('course.student.score.detail', 'score_item_id', string='成绩明细') + + def action_toggle_active(self): + for record in self: + record.is_active = not record.is_active + + def _compute_stats(self): + for record in self: + details = self.env['course.student.score.detail'].search([ + ('score_item_id', '=', record.id) + ]) + scores = details.mapped('score') + record.student_count = len(details) + record.avg_score = sum(scores) / len(scores) if scores else 0 + record.max_score = max(scores) if scores else 0 + record.min_score = min(scores) if scores else 0 \ No newline at end of file diff --git a/learning_center/models/course_student_score.py b/learning_center/models/course_student_score.py new file mode 100644 index 0000000..e01ba2e --- /dev/null +++ b/learning_center/models/course_student_score.py @@ -0,0 +1,112 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError +class CourseStudentScore(models.Model): + _name = 'course.student.score' + _description = '学生成绩' + _rec_name = 'display_name' + _order = 'course_id, student_id' + _inherit = ['mail.thread'] + display_name = fields.Char(string='显示名称', compute='_compute_display_name', store=True) + + @api.depends('course_name', 'stu_name') + def _compute_display_name(self): + for record in self: + record.display_name = f"{record.stu_name} - {record.course_name}" + + course_id = fields.Many2one('learning.course', string='课程', required=True, ondelete='cascade') + # 课程扩展只读字段(替代视图嵌套写法) + course_code = fields.Char(string="课程代码", related="course_id.code", readonly=True) + course_credit = fields.Float(string="学分", related="course_id.credit", readonly=True) + course_exam_type = fields.Selection(string="考核方式", related="course_id.exam_type", readonly=True) + course_name = fields.Char(related='course_id.name', string='课程', required=True, ondelete='cascade') + student_id = fields.Many2one('student.info', string='学生', required=True) + student_no = fields.Char(string="学号", related="student_id.stu_num", readonly=True) + student_class_id = fields.Many2one( string="班级", related="student_id.class_id", readonly=True) + student_major_id = fields.Many2one( string="专业", related="student_id.major_id", readonly=True) + stu_name=fields.Char(related='student_id.stu_name',required=True, ondelete='cascade',string='学生姓名') + score_ids = fields.One2many('course.student.score.detail', 'student_score_id', string='成绩明细') + teaching_class_id=fields.Many2one('course.teaching_class',string='教学班') + # 教学班相关只读关联字段 + teaching_class_name = fields.Char(string="教学班名称", related="teaching_class_id.display_name", readonly=True) + teaching_class_semester = fields.Selection(string="学期", related="teaching_class_id.semester", readonly=True) + teaching_class_main_teacher = fields.Many2one('hr.employee', string="主讲教师", + related="teaching_class_id.main_teacher_id", readonly=True) + total_score = fields.Float(string='综合成绩', compute='_compute_total_score', store=True) + + grade_level = fields.Selection([ + ('A', '优秀(A)'), + ('B', '良好(B)'), + ('C', '中等(C)'), + ('D', '及格(D)'), + ('F', '不及格(F)'), + ], string='等级', compute='_compute_total_score', store=True) + + @api.depends('score_ids.score', 'score_ids.weight') + def _compute_total_score(self): + for record in self: + total = 0.0 + for detail in record.score_ids: + if detail.score and detail.weight: + total += detail.score * detail.weight / 100 + record.total_score = round(total, 2) + + if record.total_score >= 90: + record.grade_level = 'A' + elif record.total_score >= 80: + record.grade_level = 'B' + elif record.total_score >= 70: + record.grade_level = 'C' + elif record.total_score >= 60: + record.grade_level = 'D' + else: + record.grade_level = 'F' + + +class CourseStudentScoreDetail(models.Model): + _name = 'course.student.score.detail' + _description = '学生成绩明细' + _rec_name = 'display_name' + _order = 'student_score_id, score_item_id' + _inherit = ['mail.thread'] + display_name = fields.Char(string='显示名称', compute='_compute_display_name', store=True) + @api.depends('student_score_displayname', 'score_item_id.name') + def _compute_display_name(self): + for record in self: + record.display_name = f"{record.student_score_displayname} - {record.score_item_id.name}" + # 关联学生成绩主表 + student_score_id = fields.Many2one('course.student.score', string='学生成绩', required=True, ondelete='cascade') + student_score_displayname=fields.Char(related='student_score_id.display_name',required=True, ondelete='cascade',string='显示名称') + # 关联成绩项目 + score_item_id = fields.Many2one('course.score.item', string='成绩项目', required=True, ondelete='restrict') + + # 得分 + score = fields.Float(string='得分', help='该项目实际得分') + + # 从成绩项目继承的字段(用于显示和计算) + weight = fields.Float(string='权重(%)', related='score_item_id.weight', store=True, readonly=True) + full_score = fields.Float(string='满分', related='score_item_id.full_score', store=True, readonly=True) + score_type = fields.Selection(string='成绩类型', related='score_item_id.score_type', store=True, readonly=True) + # 备注 + remark = fields.Char(string='备注') + # 关联课程和学生(用于视图显示) + course_id = fields.Many2one('learning.course', string='课程', related='student_score_id.course_id', store=True, + readonly=True) + student_id = fields.Many2one('student.info', string='学生', related='student_score_id.student_id', store=True, + readonly=True) + # 完成率(得分/满分) + completion_rate = fields.Float(string='完成率(%)', compute='_compute_completion_rate', store=True) + @api.depends('score', 'full_score') + def _compute_completion_rate(self): + for record in self: + if record.full_score and record.score: + record.completion_rate = round(record.score / record.full_score * 100, 1) + else: + record.completion_rate = 0.0 + + @api.constrains('score', 'full_score') + def _check_score(self): + for record in self: + if record.score and record.full_score and record.score > record.full_score: + raise ValidationError(f'得分({record.score})不能超过满分({record.full_score})') + if record.score and record.score < 0: + raise ValidationError('得分不能为负数') \ No newline at end of file diff --git a/learning_center/models/course_syllabus.py b/learning_center/models/course_syllabus.py new file mode 100644 index 0000000..0d0a861 --- /dev/null +++ b/learning_center/models/course_syllabus.py @@ -0,0 +1,80 @@ +from odoo import api, fields, models +from odoo.tools import human_size +import base64 +import os + + +class CourseSyllabus(models.Model): + _name = 'course.syllabus' + _description = '教学大纲/教案' + _rec_name = 'title' + _order = 'course_id, syllabus_type, version desc' + _inherit = ['mail.thread'] + + title = fields.Char(string='标题', required=True) + syllabus_type = fields.Selection([ + ('syllabus', '教学大纲'), + ('lesson_plan', '教案'), + ], string='类型', required=True) + course_id = fields.Many2one('learning.course', string='所属课程', required=True, ondelete='cascade') + course_name = fields.Char(related="course_id.name", string="课程名称", readonly=True) + course_code = fields.Char(related="course_id.code", string="课程代码", readonly=True) + course_credit = fields.Float(related="course_id.credit", string="学分", readonly=True) + course_exam_type = fields.Selection(related="course_id.exam_type", string="考核方式", readonly=True) + chapter_id = fields.Many2one('course.chapter', string='关联章节') + chapter_name = fields.Char(related="chapter_id.name", string="章节名称", readonly=True) + chapter_sequence = fields.Integer(related="chapter_id.sequence", string="章节序号", readonly=True) + chapter_is_published = fields.Boolean(related="chapter_id.is_published", string="发布状态", readonly=True) + + # 文件核心字段 + file = fields.Binary(string='文件', attachment=True, required=True) + file_name = fields.Char(string='文件名') + file_size = fields.Integer(string='文件大小(字节)', readonly=True) + file_size_str = fields.Char(string='文件大小', readonly=True) # 友好显示:KB/MB + file_suffix = fields.Char(string='文件格式', readonly=True) # 后缀:docx/pptx/pdf + + version = fields.Char(string='版本号', default='1.0') + version_note = fields.Char(string='版本说明') + publish_date = fields.Date(string='发布日期', default=fields.Date.today) + is_published = fields.Boolean(string='是否发布', default=True) + download_count = fields.Integer(string='下载次数', default=0) + teaching_class_id = fields.Many2one( + 'course.teaching_class', + string='教学班', + ondelete='cascade', + domain="[('course_id', '=', course_id)]" + ) + + # 监听文件上传,自动赋值文件名、大小、后缀 + @api.onchange('file', 'file_name') + def _onchange_file_upload(self): + for record in self: + if record.file and record.file_name: + # 计算文件大小(base64 换算) + file_data = base64.b64decode(record.file) + size_bytes = len(file_data) + record.file_size = size_bytes + record.file_size_str = human_size(size_bytes) + + # 提取文件后缀 + name, suffix = os.path.splitext(record.file_name) + record.file_suffix = suffix.lower().strip('.') + else: + record.file_size = 0 + record.file_size_str = '' + record.file_suffix = '' + + # 修复批量下载问题 + 优化下载响应 + def action_download(self): + for rec in self: + rec.download_count += 1 + if rec.file_name: + # 携带真实文件名,浏览器正常识别 + url = f'/web/content/course.syllabus/{rec.id}/file/{rec.file_name}?download=true' + else: + url = f'/web/content/course.syllabus/{rec.id}/file?download=true' + return { + 'type': 'ir.actions.act_url', + 'url': url, + 'target': 'self', # self 强制下载,new 新标签页打开 + } \ No newline at end of file diff --git a/learning_center/models/course_teaching_class.py b/learning_center/models/course_teaching_class.py new file mode 100644 index 0000000..515af67 --- /dev/null +++ b/learning_center/models/course_teaching_class.py @@ -0,0 +1,291 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError +import base64 +from io import BytesIO + +try: + import pandas as pd +except ImportError: + pd = None + + +class CourseTeachingClass(models.Model): + _name = 'course.teaching_class' + _description = '教学班' + _rec_name = 'display_name' + _order = 'course_id, semester, name' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + # ==================== 显示名称 ==================== + display_name = fields.Char(string='显示名称', compute='_compute_display_name', store=True) + name = fields.Char(string='教学班名称', required=True, help='如:1班、2班、周一班') + code = fields.Char(string='班级代码', help='如:CS101-01') + semester = fields.Selection([ + ('2024-2025-1', '2024-2025学年第一学期'), + ('2024-2025-2', '2024-2025学年第二学期'), + ('2025-2026-1', '2025-2026学年第一学期'), + ('2025-2026-2', '2025-2026学年第二学期'), + ], string='学期', required=True, default=lambda self: self._get_current_semester()) + + # ==================== 关联课程 ==================== + course_id = fields.Many2one('learning.course', string='课程', required=True, ondelete='cascade') + course_name = fields.Char(related='course_id.name', string='课程名称') + + @api.depends('course_name', 'name', 'semester') + def _compute_display_name(self): + for record in self: + semester_map = dict(self._fields['semester'].selection) + semester_name = semester_map.get(record.semester, '') + record.display_name = f"{record.course_name} - {record.name}({semester_name})" + + # ==================== 基本信息 ==================== + course_code = fields.Char(string='课程代码', related='course_id.code', store=True) + course_credit = fields.Float(string='学分', related='course_id.credit', store=True) + course_exam_type = fields.Selection(string='考核方式', related='course_id.exam_type', store=True) + + # ==================== 教师信息 ==================== + teacher_ids = fields.Many2many('hr.employee', string='授课教师', + relation='teaching_class_teacher_rel') + main_teacher_id = fields.Many2one('hr.employee', string='主讲教师') + assistant_ids = fields.Many2many('hr.employee', string='助教', + relation='teaching_class_assistant_rel') + + # ==================== 学生信息(保留仅做展示,数据以 enrollment 为准) ==================== + student_ids = fields.Many2many('student.info', string='选课学生', + relation='teaching_class_student_rel') + student_count = fields.Integer(string='选课人数', compute='_compute_student_count') + + # ==================== 上课信息 ==================== + schedule = fields.Text(string='上课时间地点', help='如:周一 1-2节 教学楼101') + start_date = fields.Date(string='开课日期') + end_date = fields.Date(string='结课日期') + total_weeks = fields.Integer(string='教学周数', default=16) + + # ==================== 人数限制 ==================== + max_students = fields.Integer(string='人数上限', default=60) + min_students = fields.Integer(string='开课人数下限', default=20) + is_full = fields.Boolean(string='是否满员', compute='_compute_is_full') + + # ==================== 状态 ==================== + state = fields.Selection([ + ('draft', '未开放'), + ('open', '选课中'), + ('teaching', '教学中'), + ('closed', '已结课'), + ('cancelled', '已取消'), + ], string='状态', default='draft', tracking=True) + + # ==================== 教学进度 ==================== + current_chapter = fields.Integer(string='当前进度章节', help='当前教学进度') + progress_rate = fields.Float(string='教学进度(%)', compute='_compute_progress_rate') + + # ==================== 统计字段 ==================== + homework_count = fields.Integer(string='作业数量', compute='_compute_homework_count') + submitted_count = fields.Integer(string='已提交作业数', compute='_compute_homework_count') + resource_count = fields.Integer(string='资源数量', compute='_compute_resource_count') + + # ==================== 关联子表(核心:所有业务依赖 enrollment_ids) ==================== + homework_ids = fields.One2many('course.homework', 'teaching_class_id', string='班级作业') + student_score_ids = fields.One2many('course.student.score', 'teaching_class_id', string='班级成绩') + enrollment_ids = fields.One2many('course.teaching_class.enrollment', 'teaching_class_id', string='选课记录') + course_syllabus_ids = fields.One2many('course.syllabus', 'teaching_class_id', string='教案/大纲') + + # ==================== 计算方法(全部走选课记录) ==================== + def _compute_student_count(self): + for record in self: + valid_enroll = record.enrollment_ids.filtered(lambda e: e.state == 'enrolled') + record.student_count = len(valid_enroll) + + def _compute_is_full(self): + for record in self: + record.is_full = record.max_students > 0 and record.student_count >= record.max_students + + def _compute_progress_rate(self): + for record in self: + if record.total_weeks and record.current_chapter: + record.progress_rate = record.current_chapter / record.total_weeks * 100 + else: + record.progress_rate = 0 + + def _compute_homework_count(self): + for record in self: + homeworks = self.env['course.homework'].search([('teaching_class_id', '=', record.id)]) + record.homework_count = len(homeworks) + record.submitted_count = 0 + + def _compute_resource_count(self): + for record in self: + resources = self.env['course.chapter.resource'].search([ + ('teaching_class_id', '=', record.id) + ]) + record.resource_count = len(resources) + + # ==================== 业务按钮方法 ==================== + def action_open_enroll(self): + self.state = 'open' + + def action_start_teaching(self): + self.state = 'teaching' + + def action_close(self): + self.state = 'closed' + + def action_cancel(self): + self.state = 'cancelled' + + def action_add_students(self): + """打开批量添加学生向导(底层批量生成选课记录)""" + return { + 'type': 'ir.actions.act_window', + 'name': '添加学生', + 'res_model': 'course.teaching_class.enrollment.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_teaching_class_id': self.id}, + } + + def action_import_students(self): + """导入入口,复用向导""" + return self.action_add_students() + + @api.model + def _get_current_semester(self): + import datetime + now = datetime.datetime.now() + year = now.year + if now.month >= 9: + return f'{year}-{year + 1}-1' + elif now.month >= 2: + return f'{year - 1}-{year}-2' + else: + return f'{year - 1}-{year}-1' + + # 可选:双向同步 student_ids 和 enrollment_ids(保证两边显示一致,后期可删除 student_ids) + @api.depends('enrollment_ids.student_id') + def _sync_student_ids(self): + for rec in self: + stu_ids = rec.enrollment_ids.mapped('student_id').ids + rec.student_ids = [(6, 0, stu_ids)] + + student_ids = fields.Many2many( + 'student.info', + string='选课学生', + relation='teaching_class_student_rel', + compute='_sync_student_ids', + store=True + ) + + +class CourseTeachingClassEnrollment(models.Model): + _name = 'course.teaching_class.enrollment' + _description = '教学班选课记录' + _rec_name = 'display_name' + _order = 'teaching_class_id, student_id' + + display_name = fields.Char(string='显示名称', compute='_compute_display_name', store=True) + course_id = fields.Many2one(related="teaching_class_id.course_id", string="课程", store=True) + teaching_class_id = fields.Many2one('course.teaching_class', string='教学班', required=True, ondelete='cascade') + student_id = fields.Many2one('student.info', string='学生', required=True, ondelete='cascade') + stu_name = fields.Char(related='student_id.stu_name', string='学生姓名') + stu_phone = fields.Char(related='student_id.stu_phone', string='学生电话') + + # 选课信息 + enroll_date = fields.Date(string='选课日期', default=fields.Date.today) + state = fields.Selection([ + ('enrolled', '已选课'), + ('dropped', '已退课'), + ('failed', '不及格'), + ('passed', '已通过'), + ], string='状态', default='enrolled', tracking=True) + + # 成绩(最终成绩) + final_score = fields.Float(string='最终成绩') + grade_level = fields.Selection([ + ('A', '优秀'), + ('B', '良好'), + ('C', '中等'), + ('D', '及格'), + ('F', '不及格'), + ], string='等级') + + @api.depends('teaching_class_id.display_name', 'student_id.stu_name') + def _compute_display_name(self): + for record in self: + record.display_name = f"{record.student_id.stu_name} - {record.teaching_class_id.display_name}" + + @api.model + def create(self, vals): + """新增选课记录校验人数上限""" + res = super().create(vals) + cls = res.teaching_class_id + valid_num = len(cls.enrollment_ids.filtered(lambda e: e.state == 'enrolled')) + if cls.max_students and valid_num > cls.max_students: + raise ValidationError("该教学班人数已达上限,无法继续添加学生!") + return res + + +# ==================== 批量添加/导入学生 向导(临时模型) ==================== +class CourseTeachingClassEnrollmentWizard(models.TransientModel): + _name = 'course.teaching_class.enrollment.wizard' + _description = '批量添加&导入学生向导' + + teaching_class_id = fields.Many2one( + 'course.teaching_class', string='教学班', required=True + ) + student_ids = fields.Many2many('student.info', string='选择学生') + file = fields.Binary(string='上传Excel文件') + filename = fields.Char(string='文件名') + + def action_add_students(self): + """手动勾选学生 → 批量生成选课记录""" + self.ensure_one() + cls = self.teaching_class_id + exist_stu_ids = set(cls.enrollment_ids.mapped('student_id').ids) + create_list = [] + + for stu in self.student_ids: + if stu.id not in exist_stu_ids: + create_list.append({ + 'teaching_class_id': cls.id, + 'student_id': stu.id, + 'state': 'enrolled' + }) + + if create_list: + self.env['course.teaching_class.enrollment'].create(create_list) + return {'type': 'ir.actions.act_window_close'} + + def action_import_excel(self): + """Excel导入 → 批量生成选课记录""" + self.ensure_one() + if not self.file: + raise ValidationError("请先选择Excel文件!") + if not pd: + raise ValidationError("缺少 pandas 库,请先安装:pip install pandas openpyxl") + + cls = self.teaching_class_id + exist_stu_ids = set(cls.enrollment_ids.mapped('student_id').ids) + create_list = [] + + try: + file_data = base64.b64decode(self.file) + excel_file = BytesIO(file_data) + df = pd.read_excel(excel_file, engine='openpyxl') + + for _, row in df.iterrows(): + stu_code = str(row.get('学号', '')).strip() + if not stu_code: + continue + student = self.env['student.info'].search([('stu_id', '=', stu_code)], limit=1) + if student and student.id not in exist_stu_ids: + create_list.append({ + 'teaching_class_id': cls.id, + 'student_id': student.id, + 'state': 'enrolled' + }) + except Exception as e: + raise ValidationError(f"解析Excel失败:{str(e)}") + + if create_list: + self.env['course.teaching_class.enrollment'].create(create_list) + return {'type': 'ir.actions.act_window_close'} \ No newline at end of file diff --git a/learning_center/models/learning_course.py b/learning_center/models/learning_course.py new file mode 100644 index 0000000..b5f66b5 --- /dev/null +++ b/learning_center/models/learning_course.py @@ -0,0 +1,113 @@ +from odoo import api, fields, models + + +class LearningCourse(models.Model): + _name = 'learning.course' + _description = '课程' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string='课程名称', required=True) + code = fields.Char(string='课程代码', required=True, index=True) + english_name = fields.Char(string='英文名称') + credit = fields.Float(string='学分', required=True) + exam_type = fields.Selection([ + ('exam', '考试'), + ('assessment', '考查'), + ], string='考核方式') + description = fields.Html(string='课程简介') + objectives = fields.Html(string='教学目标') + state = fields.Selection([ + ('draft', '草稿'), + ('published', '已发布'), + ], default='draft', required=True) + # ==================== 关联章节 ==================== + chapter_ids = fields.One2many('course.chapter', 'course_id', string='章节') + chapter_count = fields.Integer(string='章节数', compute='_compute_chapter_count', store=True) + # ==================== 关联教学大纲/教案 ==================== + syllabus_ids = fields.One2many('course.syllabus', 'course_id', string='教学大纲/教案') + syllabus_count = fields.Integer(string='大纲/教案数', compute='_compute_syllabus_count', store=True) + # 按类型统计 + syllabus_count_total = fields.Integer(string='大纲总数', compute='_compute_syllabus_count') + lesson_plan_count = fields.Integer(string='教案总数', compute='_compute_syllabus_count') + # ==================== 统计资源(通过章节关联) ==================== + resource_count = fields.Integer(string='资源总数', compute='_compute_resource_count', store=True) + video_count = fields.Integer(string='视频数', compute='_compute_resource_count', store=True) + doc_count = fields.Integer(string='文档数', compute='_compute_resource_count', store=True) + + # 汇总所有章节的资源 + resource_all_ids = fields.One2many( + comodel_name='course.chapter.resource', + inverse_name=None, + compute='_compute_resource_all_ids', + string='资源列表', + readonly=True + ) + + @api.depends('chapter_ids', 'chapter_ids.resource_ids') + def _compute_resource_all_ids(self): + for record in self: + # 取出所有章节下的资源记录 + all_resources = record.chapter_ids.mapped('resource_ids') + record.resource_all_ids = all_resources + + # ==================== 计算字段方法 ==================== + @api.depends('chapter_ids') + def _compute_chapter_count(self): + for record in self: + record.chapter_count = len(record.chapter_ids) + + @api.depends('syllabus_ids', 'syllabus_ids.syllabus_type') + def _compute_syllabus_count(self): + for record in self: + record.syllabus_count = len(record.syllabus_ids) + record.syllabus_count_total = len(record.syllabus_ids.filtered(lambda s: s.syllabus_type == 'syllabus')) + record.lesson_plan_count = len(record.syllabus_ids.filtered(lambda s: s.syllabus_type == 'lesson_plan')) + + @api.depends('chapter_ids.resource_ids', 'chapter_ids.resource_ids.resource_type') + def _compute_resource_count(self): + for record in self: + resources = record.chapter_ids.mapped('resource_ids') + record.resource_count = len(resources) + record.video_count = len(resources.filtered(lambda r: r.resource_type == 'video')) + record.doc_count = len(resources.filtered(lambda r: r.resource_type in ['pdf', 'ppt'])) + + # ==================== 业务方法 ==================== + def action_publish(self): + """发布课程""" + self.state = 'published' + + def action_draft(self): + """退回草稿""" + self.state = 'draft' + + def action_view_chapters(self): + """查看章节""" + return { + 'type': 'ir.actions.act_window', + 'name': '课程章节', + 'res_model': 'course.chapter', + 'view_mode': 'list,form', + 'domain': [('course_id', '=', self.id)], + 'context': {'default_course_id': self.id}, + } + + def action_view_syllabus(self): + """查看教学大纲/教案""" + return { + 'type': 'ir.actions.act_window', + 'name': '教学大纲/教案', + 'res_model': 'course.syllabus', + 'view_mode': 'list,form', + 'domain': [('course_id', '=', self.id)], + 'context': {'default_course_id': self.id}, + } + + def action_view_resources(self): + """查看所有资源""" + return { + 'type': 'ir.actions.act_window', + 'name': '教学资源', + 'res_model': 'course.chapter.resource', + 'view_mode': 'list,form', + 'domain': [('chapter_id.course_id', '=', self.id)], + } \ No newline at end of file diff --git a/learning_center/models/teach_class.py b/learning_center/models/teach_class.py new file mode 100644 index 0000000..454f957 --- /dev/null +++ b/learning_center/models/teach_class.py @@ -0,0 +1,13 @@ +from odoo import api,fields,models + + + +class TeachClass(models.Model): + _name = 'teach.class' + course_teaching_class=fields.Many2one('course.teaching_class',string="教学班") + student_id=fields.Many2one('student.info',string='学生') + stu_num=fields.Char(related='student_id.stu_num',string='学号') + stu_name=fields.Char(related='student_id.stu_name',string='姓名') + 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="年级") \ No newline at end of file diff --git a/learning_center/security/ir.model.access.csv b/learning_center/security/ir.model.access.csv new file mode 100644 index 0000000..41f39ec --- /dev/null +++ b/learning_center/security/ir.model.access.csv @@ -0,0 +1,24 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_learning_course_sys,learning.course.access.sys,model_learning_course,base.group_system,1,1,1,1 +access_course_chapter_sys,course.chapter.access.sys,model_course_chapter,base.group_system,1,1,1,1 +access_course_chapter_resource_sys,course.chapter.resource.access.sys,model_course_chapter_resource,base.group_system,1,1,1,1 +access_course_homework_sys,course.homework.access.sys,model_course_homework,base.group_system,1,1,1,1 +access_course_homework_submit_sys,course.homework.submit.access.sys,model_course_homework_submit,base.group_system,1,1,1,1 +access_course_score_item_sys,course.score.item.access.sys,model_course_score_item,base.group_system,1,1,1,1 +access_course_student_score_sys,course.student.score.access.sys,model_course_student_score,base.group_system,1,1,1,1 +access_course_student_score_detail_sys,course.student.score.detail.access.sys,model_course_student_score_detail,base.group_system,1,1,1,1 +access_course_syllabus_sys,course.syllabus.access.sys,model_course_syllabus,base.group_system,1,1,1,1 +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 +access_course_homework_submit_teacher,course.homework.submit.access.teacher,model_course_homework_submit,edu_base.group_teacher,1,1,1,0 +access_learning_course_teacher,learning.course.access.teacher,model_learning_course,edu_base.group_teacher,1,0,0,0 +access_course_chapter_teacher,course.chapter.access.teacher,model_course_chapter,edu_base.group_teacher,1,1,1,0 +access_course_chapter_resource_teacher,course.chapter.resource.access.teacher,model_course_chapter_resource,edu_base.group_teacher,1,1,1,0 + + diff --git a/learning_center/security/record_rules.xml b/learning_center/security/record_rules.xml new file mode 100644 index 0000000..f4ff887 --- /dev/null +++ b/learning_center/security/record_rules.xml @@ -0,0 +1,41 @@ + + + + 教师-仅查看自己的班级数据 + + + + + [('main_teacher_id.user_id', '=', user.id)] + + + + + + + + 作业发布-仅查看自己发布的作业 + + + + + [('create_uid', '=', user.id)] + + + + + + + + 作业提价-仅查看自己发布的作业的提交情况 + + + + + [('homework_id.create_uid', '=', user.id)] + + + + + + \ No newline at end of file diff --git a/learning_center/views/view_course_chapter.xml b/learning_center/views/view_course_chapter.xml new file mode 100644 index 0000000..cdda94b --- /dev/null +++ b/learning_center/views/view_course_chapter.xml @@ -0,0 +1,135 @@ + + + course.chapter.list + course.chapter + + + + + + + + + + + + + + + course.chapter.form + course.chapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + course.chapter.search + course.chapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + course.chapter.syllabus.sub.list + course.syllabus + + + + + + + + + + + + 章节管理 + course.chapter + list,form + + + + 章节管理 + + + + 20 + + \ No newline at end of file diff --git a/learning_center/views/view_course_chapter_resource.xml b/learning_center/views/view_course_chapter_resource.xml new file mode 100644 index 0000000..1222e09 --- /dev/null +++ b/learning_center/views/view_course_chapter_resource.xml @@ -0,0 +1,107 @@ + + + course.chapter.resource.list + course.chapter.resource + + + + + + + + + + + + + + course.chapter.resource.form + course.chapter.resource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 教学资源 + course.chapter.resource + list,form + + + + 教学资源 + + + + 30 + + \ No newline at end of file diff --git a/learning_center/views/view_course_homework.xml b/learning_center/views/view_course_homework.xml new file mode 100644 index 0000000..28cf3db --- /dev/null +++ b/learning_center/views/view_course_homework.xml @@ -0,0 +1,123 @@ + + + course.homework.list + course.homework + + + + + + + + + + + + + + + + + course.homework.form + course.homework + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 作业管理 + course.homework + list,form + + + + 作业发布 + + + + 40 + + diff --git a/learning_center/views/view_course_homework_student.xml b/learning_center/views/view_course_homework_student.xml new file mode 100644 index 0000000..16199d4 --- /dev/null +++ b/learning_center/views/view_course_homework_student.xml @@ -0,0 +1,103 @@ + + + + + + course.homework.student.list + course.homework + + + + + + + + + + + + + + + + course.homework.student.form + course.homework + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 我的作业 + course.homework + list,form + + + + + + + 学生作业 + 20 + + + + + + + + + + + + + \ No newline at end of file diff --git a/learning_center/views/view_course_score_item.xml b/learning_center/views/view_course_score_item.xml new file mode 100644 index 0000000..24c9ed7 --- /dev/null +++ b/learning_center/views/view_course_score_item.xml @@ -0,0 +1,119 @@ + + + course.score.item.list + course.score.item + + + + + + + + + + + + + + + course.score.item.form + course.score.item + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + course.student.score.detail.list + course.student.score.detail + + + + + + + + + + + 成绩项目 + course.score.item + list,form + + + + 成绩项目 + course.score.item + list,form + + + + + + + 成绩项目 + + + 50 + + \ No newline at end of file diff --git a/learning_center/views/view_course_student_score.xml b/learning_center/views/view_course_student_score.xml new file mode 100644 index 0000000..4b7f81b --- /dev/null +++ b/learning_center/views/view_course_student_score.xml @@ -0,0 +1,204 @@ + + + + + + course.student.score.list + course.student.score + + + + + + + + + + + + + + + + course.student.score.form + course.student.score + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + course.student.score.detail.list + course.student.score.detail + + + + + + + + + + + + + + + + + course.student.score.detail.form + course.student.score.detail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 学生成绩 + course.student.score + list,form + + + + + + 成绩明细 + course.student.score.detail + list,form + + + + + + 学生成绩 + course.student.score + list,form + + [('course_id', '=', active_id)] + {'default_course_id': active_id} + + + + + 学生成绩 + course.student.score + list,form + + [('teaching_class_id', '=', active_id)] + {'default_teaching_class_id': active_id} + + + + + 学生成绩 + + + 60 + + + + + 成绩明细 + + + 65 + + + \ No newline at end of file diff --git a/learning_center/views/view_course_syllabus.xml b/learning_center/views/view_course_syllabus.xml new file mode 100644 index 0000000..8ddf2f0 --- /dev/null +++ b/learning_center/views/view_course_syllabus.xml @@ -0,0 +1,134 @@ + + + + course.syllabus.list + course.syllabus + + + + + + + + + + + + + + + + + + + + + course.syllabus.form + course.syllabus + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 教学大纲/教案 + course.syllabus + form + + + + + + + + + + + + + + + + 教学大纲/教案 + + + 35 + + \ No newline at end of file diff --git a/learning_center/views/view_course_teaching_class.xml b/learning_center/views/view_course_teaching_class.xml new file mode 100644 index 0000000..772069e --- /dev/null +++ b/learning_center/views/view_course_teaching_class.xml @@ -0,0 +1,215 @@ + + + + course.teaching_class.list + course.teaching_class + + + + + + + + + + + + + + + course.teaching_class.form + course.teaching_class + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + student.info.list.in.class + student.info + + + + + + + + + + + + + course.teaching.class.enrollment.list + course.teaching_class.enrollment + + + + + + + + + + + + + course.homework.list + course.homework + + + + + + + + + + + + + course.syllabus.list.inside + course.syllabus + + + + + + + + + + + course.student.score.list + course.student.score + + + + + + + + + + + + course.teaching_class.enrollment.wizard.form + course.teaching_class.enrollment.wizard + + + + + + + + + + + Excel模板要求:首列标题为【学号】,按学号匹配学生 + + + + + + + + + 教学班管理 + course.teaching_class + list,form + + + + + 教学班 + course.teaching_class + list,form + + [('course_id', '=', active_id)] + {'default_course_id': active_id} + + + + + 教学班管理 + + + 15 + + + + \ No newline at end of file diff --git a/learning_center/views/view_homework_submit.xml b/learning_center/views/view_homework_submit.xml new file mode 100644 index 0000000..217931d --- /dev/null +++ b/learning_center/views/view_homework_submit.xml @@ -0,0 +1,140 @@ + + + course.homework.submit.list + course.homework.submit + + + + + + + + + + + + + + + + course.homework.submit.form + course.homework.submit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 作业提交 + course.homework.submit + list,form + + + + + + + + + + + + 作业提交 + + + + 45 + + \ No newline at end of file diff --git a/learning_center/views/view_learning_course.xml b/learning_center/views/view_learning_course.xml new file mode 100644 index 0000000..aecf80c --- /dev/null +++ b/learning_center/views/view_learning_course.xml @@ -0,0 +1,170 @@ + + + + + + learning.course.list + learning.course + + + + + + + + + + + + + + + + learning.course.form + learning.course + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + course.chapter.list + course.chapter + + + + + + + + + + + + + + + + + + + + + + + + + course.chapter.resource.list + course.chapter.resource + + + + + + + + + + + + course.chapter.resource.list + course.chapter.resource + + + + + + + + + + + + + + + 课程管理 + learning.course + list,form + + + + 学习中心 + 11 + + + + + + 课程管理 + + + 10 + + + + \ No newline at end of file diff --git a/learning_center/views/view_teach_class.xml b/learning_center/views/view_teach_class.xml new file mode 100644 index 0000000..1c9ffa5 --- /dev/null +++ b/learning_center/views/view_teach_class.xml @@ -0,0 +1,51 @@ + + + view.teach.class.list + teach.class + + + + + + + + + + + + + + view.teach.class.form + teach.class + + + + + + + + + teach.class.search + teach.class + + + + + + + + + + + 我的班级 + teach.class + list + + + + 我的班级 + + 11 + + + \ No newline at end of file
Excel模板要求:首列标题为【学号】,按学号匹配学生