完善教育

This commit is contained in:
李鹏宇
2026-08-21 19:25:35 +08:00
parent 87f2003caa
commit b072b7f973
14 changed files with 256 additions and 55 deletions
+1
View File
@@ -1 +1,2 @@
from . import controllers
from . import models
+7 -1
View File
@@ -1,6 +1,6 @@
{
'name': '教育',
'version': '18.0.3.0.0',
'version': '18.0.3.0.4',
'summary': 'Odoo 18 教学管理平台 — 师生管理 + 课程管理 + 作业/进展(老师·学生双角色)',
'description': """
Odoo 18 教学管理平台(面向老师与学生,双角色权限)
@@ -40,11 +40,17 @@
'views/assignment_batch_wizard_views.xml',
'views/import_wizard_views.xml',
'views/homework_views.xml',
'views/exam_views.xml',
'views/exam_take_templates.xml',
'views/training_views.xml',
'views/course_import_wizard_views.xml',
'data/about_pages.xml',
'views/about_page_views.xml',
'views/menus.xml',
'views/shixun_kanban_view.xml',
'data/shixun_module_data.xml',
'data/module_info_data.xml',
'views/module_info_views.xml',
],
'assets': {
'web.assets_backend': [
+2
View File
@@ -10,3 +10,5 @@ from . import import_wizard
from . import about_page
from . import training
from . import course_import_wizard
from . import ir_module_module_info
from . import exam
+21
View File
@@ -240,6 +240,21 @@ class CourseChapterExerciseAnswer(models.Model):
return super().write(vals)
class CourseChapterTask(models.Model):
"""章节任务清单——老师按章节维护的学习任务,学生端可见但不可编辑。"""
_name = 'yuthon.school.course.chapter.task'
_description = '章节任务'
_order = 'chapter_id, sequence, id'
chapter_id = fields.Many2one(
'yuthon.school.course.chapter', string='章节',
required=True, ondelete='cascade', index=True)
sequence = fields.Integer(string='排序', default=10)
name = fields.Char(string='任务名称', required=True)
is_done = fields.Boolean(string='已完成', default=False)
class CourseChapter(models.Model):
"""课程章节——老师端在课程内容里维护,学生端作为学习目录。
@@ -256,6 +271,7 @@ class CourseChapter(models.Model):
name = fields.Char(string='章节标题', required=True, index=True)
sequence = fields.Integer(string='排序', default=10)
description = fields.Html(string='章节说明', sanitize=True)
class_hours = fields.Integer(string='课时', default=1, help='本章所占课时数')
# 视频与附件
video_file = fields.Binary(string='视频文件', attachment=True)
@@ -280,6 +296,11 @@ class CourseChapter(models.Model):
for chapter in self:
chapter.exercise_count = len(chapter.exercise_ids)
# 任务列表:老师按章节维护的任务清单
task_ids = fields.One2many(
'yuthon.school.course.chapter.task', 'chapter_id',
string='任务列表')
# 随堂笔记:老师按章节记录的富文本笔记(与章节编辑表单共用同一字段)
notes = fields.Html(string='随堂笔记', sanitize=True)
+1
View File
@@ -76,6 +76,7 @@ class SchoolCourse(models.Model):
objectives = fields.Html(string='课程目标', sanitize=True)
syllabus = fields.Html(string='课程大纲', sanitize=True)
features = fields.Html(string='课程特色', sanitize=True)
business_scenario = fields.Html(string='业务情景', sanitize=True)
# 学员分配
student_ids = fields.Many2many(
+118 -45
View File
@@ -1,7 +1,13 @@
"""课程批量导入向导 —— CSV 模板下载 + 批量创建课程(导入大纲即可)。
"""课程批量导入向导 —— CSV 模板下载 + 批量创建课程(可选连带章节与章节任务)。
模板列:课程名称、课程编号、所属学院、学期、课程类型、课时、课程大纲。
导入时直接创建 yuthon.school.course 记录。
模板列(前 8 列为课程本体,后 5 列为可选章节/任务):
课程名称、课程编号、所属学院、学期、课程类型、课时、课程大纲、业务情景、
章节标题、章节说明、章节课时、任务名称、是否完成。
- 仅填前 8 列:行为与旧版一致,只创建课程;
- 填了「章节标题」:自动按 (课程, 章节标题) 找/建章节;
- 填了「任务名称」:在对应章节下创建任务列表明细行(is_done 由「是否完成」决定)。
同一课程 / 同一章节多行时自动复用,不重复创建。
"""
import base64
@@ -58,10 +64,13 @@ class CourseImportWizard(models.TransientModel):
'<li>点击下方「下载模板」按钮下载 CSV 模板;</li>'
'<li>用 Excel / WPS 打开模板,按示例填写数据:<br/>'
'<b>课程名称</b>、<b>课程编号</b> 为必填;<br/>'
'<b>所属学院、学期、课程类型、课时、课程大纲</b> 为选填;</li>'
'<b>所属学院、学期、课程类型、课时、课程大纲、业务情景</b> 为选填;</li>'
'<li>课程类型可选:理论 / 实践</li>'
'<li><b>章节 / 任务(可选)</b>:填「章节标题」会自动建/复用章节,'
'填「任务名称」会在该章节下建一条任务列表明细(是否完成填 是/否);'
'同一课程、同一章节多行会自动合并,不会重复创建;</li>'
'<li>保存为 CSV 后上传并点击「开始导入」;</li>'
'<li>若课程编号已存在则跳过,不会重复创建。</li>'
'<li>若课程名称已存在则复用,不会重复创建课程。</li>'
'</ol>'
),
)
@@ -71,10 +80,15 @@ class CourseImportWizard(models.TransientModel):
def action_download_template(self):
"""生成 CSV 模板并提供下载。"""
header = ['课程名称', '课程编号', '所属学院', '学期',
'课程类型(理论/实践)', '课时', '课程大纲']
'课程类型(理论/实践)', '课时', '课程大纲', '业务情景',
'章节标题', '章节说明', '章节课时',
'任务名称', '是否完成']
sample = ['Python 编程基础', 'PY101', '计算机学院',
'2025-2026第1学期', '理论', '48',
'Python基础语法、数据结构、面向对象编程']
'Python基础语法、数据结构、面向对象编程',
'通过电商订单管理场景贯穿课程',
'第1章 环境搭建', '本章介绍开发环境', '4',
'安装Python', '否']
buf = io.StringIO()
writer = csv.writer(buf)
@@ -122,6 +136,10 @@ class CourseImportWizard(models.TransientModel):
col_map = self._normalize_columns(reader.fieldnames)
created = 0
reused_course = 0
created_chapter = 0
reused_chapter = 0
created_task = 0
skipped = 0
errors = []
@@ -135,46 +153,86 @@ class CourseImportWizard(models.TransientModel):
errors.append(_('第 %d 行:课程名称为必填,跳过。') % row_idx)
continue
# 编号去重
if code:
existing = self.env['yuthon.school.course'].sudo().search(
[('code', '=', code)], limit=1)
if existing:
skipped += 1
continue
try:
vals = {
'name': name,
'code': code,
'state': 'draft',
}
# 课程:按名称优先复用,其次按编号;都不存在才新建
course = self.env['yuthon.school.course'].sudo().search(
[('name', '=', name)], limit=1)
if not course and code:
course = self.env['yuthon.school.course'].sudo().search(
[('code', '=', code)], limit=1)
college_raw = row.get('college', '')
if college_raw:
vals['college'] = COLLEGE_NAME_MAP.get(college_raw, 'other')
if not course:
vals = {
'name': name,
'code': code,
'state': 'draft',
}
college_raw = row.get('college', '')
if college_raw:
vals['college'] = COLLEGE_NAME_MAP.get(college_raw, 'other')
semester = row.get('semester', '')
if semester:
vals['semester'] = semester
course_type_raw = row.get('course_type', '')
if course_type_raw:
vals['course_type'] = COURSE_TYPE_MAP.get(
course_type_raw, 'theory')
hours_raw = row.get('hours', '')
if hours_raw:
try:
vals['hours'] = int(float(hours_raw))
except ValueError:
pass
syllabus = row.get('syllabus', '')
if syllabus:
vals['syllabus'] = syllabus
business_scenario = row.get('business_scenario', '')
if business_scenario:
vals['business_scenario'] = business_scenario
course = self.env['yuthon.school.course'].sudo().create(vals)
created += 1
else:
reused_course += 1
semester = row.get('semester', '')
if semester:
vals['semester'] = semester
# 章节:填了章节标题才处理(按 课程+标题 复用)
chapter_title = row.get('chapter', '').strip()
chapter = None
if chapter_title:
chapter = self.env['yuthon.school.course.chapter'].sudo().search([
('course_id', '=', course.id),
('name', '=', chapter_title),
], limit=1)
if not chapter:
chapter_vals = {'course_id': course.id, 'name': chapter_title}
chapter_desc = row.get('chapter_desc', '').strip()
if chapter_desc:
chapter_vals['description'] = chapter_desc
chapter_hours_raw = row.get('chapter_hours', '').strip()
if chapter_hours_raw:
try:
chapter_vals['class_hours'] = int(float(chapter_hours_raw))
except ValueError:
pass
chapter = self.env['yuthon.school.course.chapter'].sudo().create(
chapter_vals)
created_chapter += 1
else:
reused_chapter += 1
course_type_raw = row.get('course_type', '')
if course_type_raw:
vals['course_type'] = COURSE_TYPE_MAP.get(course_type_raw, 'theory')
hours_raw = row.get('hours', '')
if hours_raw:
try:
vals['hours'] = float(hours_raw)
except ValueError:
pass
syllabus = row.get('syllabus', '')
if syllabus:
vals['syllabus'] = syllabus
self.env['yuthon.school.course'].sudo().create(vals)
created += 1
# 任务列表:填了任务名称才在对应章节下创建明细行
task_name = row.get('task_name', '').strip()
if task_name and chapter:
is_done = row.get('is_done', '').strip() in (
'是', 'y', 'yes', 'true', '1', '完成')
task_vals = {
'chapter_id': chapter.id,
'name': task_name,
'is_done': is_done,
'sequence': 10,
}
self.env['yuthon.school.course.chapter.task'].sudo().create(
task_vals)
created_task += 1
except Exception as exc:
skipped += 1
@@ -183,9 +241,12 @@ class CourseImportWizard(models.TransientModel):
summary = _(
'导入完成!\n'
'新建课程:%d\n'
'新建课程:%d(复用 %d)\n'
'新建章节:%d(复用 %d)\n'
'新建任务:%d\n'
'跳过:%d'
) % (created, skipped)
) % (created, reused_course, created_chapter, reused_chapter,
created_task, skipped)
if errors:
summary += '\n' + _('注意事项:\n') + '\n'.join(errors[:10])
@@ -223,6 +284,18 @@ class CourseImportWizard(models.TransientModel):
mapping[f] = 'hours'
elif f_lower in ('课程大纲', 'syllabus', '大纲'):
mapping[f] = 'syllabus'
elif f_lower in ('业务情景', 'business_scenario', 'scenario'):
mapping[f] = 'business_scenario'
elif f_lower in ('章节标题', 'chapter', '章节', 'chapter_name'):
mapping[f] = 'chapter'
elif f_lower in ('章节说明', 'chapter_desc', 'chapter_description'):
mapping[f] = 'chapter_desc'
elif f_lower in ('章节课时', 'chapter_hours', 'chapter_class_hours'):
mapping[f] = 'chapter_hours'
elif f_lower in ('任务名称', 'task_name', '任务', 'task'):
mapping[f] = 'task_name'
elif f_lower in ('是否完成', 'is_done', '完成', 'done'):
mapping[f] = 'is_done'
else:
mapping[f] = f # 保留原名
return mapping
+23
View File
@@ -34,4 +34,27 @@
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- 考试模块:学生仅能访问自己的答卷与答题记录 -->
<record id="rule_exam_paper_student_own" model="ir.rule">
<field name="name">考试答卷:学生仅能访问自己的答卷</field>
<field name="model_id" ref="model_yuthon_school_exam_paper"/>
<field name="groups" eval="[(4, ref('college.group_student'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="rule_exam_answer_student_own" model="ir.rule">
<field name="name">考试答题:学生仅能访问自己的答题记录</field>
<field name="model_id" ref="model_yuthon_school_exam_answer"/>
<field name="groups" eval="[(4, ref('college.group_student'))]"/>
<field name="domain_force">[('paper_id.user_id', '=', user.id)]</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
</odoo>
+21
View File
@@ -38,6 +38,9 @@ access_yuthon_school_course_import_wizard_teacher,yuthon.school.course.import.wi
access_yuthon_school_course_chapter_exercise_answer_admin,yuthon.school.course.chapter.exercise.answer.admin,model_yuthon_school_course_chapter_exercise_answer,base.group_system,1,1,1,1
access_yuthon_school_course_chapter_exercise_answer_teacher,yuthon.school.course.chapter.exercise.answer.teacher,model_yuthon_school_course_chapter_exercise_answer,college.group_teacher,1,0,0,0
access_yuthon_school_course_chapter_exercise_answer_student,yuthon.school.course.chapter.exercise.answer.student,model_yuthon_school_course_chapter_exercise_answer,college.group_student,1,1,1,0
access_yuthon_school_course_chapter_task_admin,yuthon.school.course.chapter.task.admin,model_yuthon_school_course_chapter_task,base.group_system,1,1,1,1
access_yuthon_school_course_chapter_task_teacher,yuthon.school.course.chapter.task.teacher,model_yuthon_school_course_chapter_task,college.group_teacher,1,1,1,1
access_yuthon_school_course_chapter_task_student,yuthon.school.course.chapter.task.student,model_yuthon_school_course_chapter_task,college.group_student,1,0,0,0
access_yuthon_school_training_admin,yuthon.school.training.admin,model_yuthon_school_training,base.group_system,1,1,1,1
access_yuthon_school_training_teacher,yuthon.school.training.teacher,model_yuthon_school_training,college.group_teacher,1,1,1,1
access_yuthon_school_training_student,yuthon.school.training.student,model_yuthon_school_training,college.group_student,1,1,1,1
@@ -46,3 +49,21 @@ access_yuthon_about_page_teacher,yuthon.about.page.teacher,model_yuthon_about_pa
access_yuthon_about_page_student,yuthon.about.page.student,model_yuthon_about_page,college.group_student,1,0,0,0
access_ir_module_module_teacher,ir.module.module.teacher,base.model_ir_module_module,college.group_teacher,1,1,1,1
access_ir_module_module_student,ir.module.module.student.read,base.model_ir_module_module,college.group_student,1,0,0,0
access_yuthon_school_exam_question_admin,yuthon.school.exam.question.admin,model_yuthon_school_exam_question,base.group_system,1,1,1,1
access_yuthon_school_exam_question_teacher,yuthon.school.exam.question.teacher,model_yuthon_school_exam_question,college.group_teacher,1,1,1,1
access_yuthon_school_exam_question_student,yuthon.school.exam.question.student,model_yuthon_school_exam_question,college.group_student,1,0,0,0
access_yuthon_school_exam_question_option_admin,yuthon.school.exam.question.option.admin,model_yuthon_school_exam_question_option,base.group_system,1,1,1,1
access_yuthon_school_exam_question_option_teacher,yuthon.school.exam.question.option.teacher,model_yuthon_school_exam_question_option,college.group_teacher,1,1,1,1
access_yuthon_school_exam_question_option_student,yuthon.school.exam.question.option.student,model_yuthon_school_exam_question_option,college.group_student,1,0,0,0
access_yuthon_school_exam_admin,yuthon.school.exam.admin,model_yuthon_school_exam,base.group_system,1,1,1,1
access_yuthon_school_exam_teacher,yuthon.school.exam.teacher,model_yuthon_school_exam,college.group_teacher,1,1,1,1
access_yuthon_school_exam_student,yuthon.school.exam.student,model_yuthon_school_exam,college.group_student,1,0,0,0
access_yuthon_school_exam_line_admin,yuthon.school.exam.line.admin,model_yuthon_school_exam_line,base.group_system,1,1,1,1
access_yuthon_school_exam_line_teacher,yuthon.school.exam.line.teacher,model_yuthon_school_exam_line,college.group_teacher,1,1,1,1
access_yuthon_school_exam_line_student,yuthon.school.exam.line.student,model_yuthon_school_exam_line,college.group_student,1,0,0,0
access_yuthon_school_exam_paper_admin,yuthon.school.exam.paper.admin,model_yuthon_school_exam_paper,base.group_system,1,1,1,1
access_yuthon_school_exam_paper_teacher,yuthon.school.exam.paper.teacher,model_yuthon_school_exam_paper,college.group_teacher,1,1,1,1
access_yuthon_school_exam_paper_student,yuthon.school.exam.paper.student,model_yuthon_school_exam_paper,college.group_student,1,1,1,0
access_yuthon_school_exam_answer_admin,yuthon.school.exam.answer.admin,model_yuthon_school_exam_answer,base.group_system,1,1,1,1
access_yuthon_school_exam_answer_teacher,yuthon.school.exam.answer.teacher,model_yuthon_school_exam_answer,college.group_teacher,1,1,1,1
access_yuthon_school_exam_answer_student,yuthon.school.exam.answer.student,model_yuthon_school_exam_answer,college.group_student,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
38 access_yuthon_school_course_chapter_exercise_answer_admin yuthon.school.course.chapter.exercise.answer.admin model_yuthon_school_course_chapter_exercise_answer base.group_system 1 1 1 1
39 access_yuthon_school_course_chapter_exercise_answer_teacher yuthon.school.course.chapter.exercise.answer.teacher model_yuthon_school_course_chapter_exercise_answer college.group_teacher 1 0 0 0
40 access_yuthon_school_course_chapter_exercise_answer_student yuthon.school.course.chapter.exercise.answer.student model_yuthon_school_course_chapter_exercise_answer college.group_student 1 1 1 0
41 access_yuthon_school_course_chapter_task_admin yuthon.school.course.chapter.task.admin model_yuthon_school_course_chapter_task base.group_system 1 1 1 1
42 access_yuthon_school_course_chapter_task_teacher yuthon.school.course.chapter.task.teacher model_yuthon_school_course_chapter_task college.group_teacher 1 1 1 1
43 access_yuthon_school_course_chapter_task_student yuthon.school.course.chapter.task.student model_yuthon_school_course_chapter_task college.group_student 1 0 0 0
44 access_yuthon_school_training_admin yuthon.school.training.admin model_yuthon_school_training base.group_system 1 1 1 1
45 access_yuthon_school_training_teacher yuthon.school.training.teacher model_yuthon_school_training college.group_teacher 1 1 1 1
46 access_yuthon_school_training_student yuthon.school.training.student model_yuthon_school_training college.group_student 1 1 1 1
49 access_yuthon_about_page_student yuthon.about.page.student model_yuthon_about_page college.group_student 1 0 0 0
50 access_ir_module_module_teacher ir.module.module.teacher base.model_ir_module_module college.group_teacher 1 1 1 1
51 access_ir_module_module_student ir.module.module.student.read base.model_ir_module_module college.group_student 1 0 0 0
52 access_yuthon_school_exam_question_admin yuthon.school.exam.question.admin model_yuthon_school_exam_question base.group_system 1 1 1 1
53 access_yuthon_school_exam_question_teacher yuthon.school.exam.question.teacher model_yuthon_school_exam_question college.group_teacher 1 1 1 1
54 access_yuthon_school_exam_question_student yuthon.school.exam.question.student model_yuthon_school_exam_question college.group_student 1 0 0 0
55 access_yuthon_school_exam_question_option_admin yuthon.school.exam.question.option.admin model_yuthon_school_exam_question_option base.group_system 1 1 1 1
56 access_yuthon_school_exam_question_option_teacher yuthon.school.exam.question.option.teacher model_yuthon_school_exam_question_option college.group_teacher 1 1 1 1
57 access_yuthon_school_exam_question_option_student yuthon.school.exam.question.option.student model_yuthon_school_exam_question_option college.group_student 1 0 0 0
58 access_yuthon_school_exam_admin yuthon.school.exam.admin model_yuthon_school_exam base.group_system 1 1 1 1
59 access_yuthon_school_exam_teacher yuthon.school.exam.teacher model_yuthon_school_exam college.group_teacher 1 1 1 1
60 access_yuthon_school_exam_student yuthon.school.exam.student model_yuthon_school_exam college.group_student 1 0 0 0
61 access_yuthon_school_exam_line_admin yuthon.school.exam.line.admin model_yuthon_school_exam_line base.group_system 1 1 1 1
62 access_yuthon_school_exam_line_teacher yuthon.school.exam.line.teacher model_yuthon_school_exam_line college.group_teacher 1 1 1 1
63 access_yuthon_school_exam_line_student yuthon.school.exam.line.student model_yuthon_school_exam_line college.group_student 1 0 0 0
64 access_yuthon_school_exam_paper_admin yuthon.school.exam.paper.admin model_yuthon_school_exam_paper base.group_system 1 1 1 1
65 access_yuthon_school_exam_paper_teacher yuthon.school.exam.paper.teacher model_yuthon_school_exam_paper college.group_teacher 1 1 1 1
66 access_yuthon_school_exam_paper_student yuthon.school.exam.paper.student model_yuthon_school_exam_paper college.group_student 1 1 1 0
67 access_yuthon_school_exam_answer_admin yuthon.school.exam.answer.admin model_yuthon_school_exam_answer base.group_system 1 1 1 1
68 access_yuthon_school_exam_answer_teacher yuthon.school.exam.answer.teacher model_yuthon_school_exam_answer college.group_teacher 1 1 1 1
69 access_yuthon_school_exam_answer_student yuthon.school.exam.answer.student model_yuthon_school_exam_answer college.group_student 1 1 1 0
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -266,3 +266,4 @@
h4 { font-size: 15px; margin: 16px 0 8px; }
p, li { color: $o-main-text-color; }
}
@@ -299,3 +299,4 @@
h4 { font-size: 15px; margin: 16px 0 8px; color: #f8fafc; }
p, li { color: #cbd5e1; }
}
+16 -3
View File
@@ -8,9 +8,14 @@
<field name="arch" type="xml">
<form>
<group>
<field name="name"/>
<field name="course_id" readonly="1"/>
<field name="sequence" invisible="1"/>
<group>
<field name="name"/>
<field name="course_id" readonly="1"/>
<field name="sequence" invisible="1"/>
</group>
<group>
<field name="class_hours" string="课时"/>
</group>
</group>
<group>
<field name="description" widget="html" string="章节说明"/>
@@ -29,6 +34,14 @@
title="查看本题所有学员的回答"/>
</list>
</field>
<separator string="任务列表"/>
<field name="task_ids" string="任务列表">
<list editable="bottom" decoration-muted="is_done == True">
<field name="sequence" widget="handle" string="排序"/>
<field name="name" string="任务名称"/>
<field name="is_done" string="已完成" optional="show"/>
</list>
</field>
<group>
<field name="video_file" filename="video_filename"/>
<field name="video_filename" invisible="1"/>
+8
View File
@@ -62,6 +62,7 @@
<field name="chapter_ids">
<list editable="top">
<field name="name"/>
<field name="class_hours" string="课时"/>
<field name="video_filename_display" string="视频"/>
<button name="action_open_edit" type="object"
string="编辑章节" icon="fa-pencil"
@@ -103,6 +104,9 @@
<page string="课程特色">
<field name="features"/>
</page>
<page string="业务情景">
<field name="business_scenario"/>
</page>
</notebook>
</sheet>
<chatter/>
@@ -144,6 +148,7 @@
<field name="chapter_ids" readonly="1">
<list>
<field name="name"/>
<field name="class_hours" string="课时"/>
</list>
</field>
</page>
@@ -159,6 +164,9 @@
<page string="课程特色">
<field name="features" readonly="1"/>
</page>
<page string="业务情景">
<field name="business_scenario" readonly="1"/>
</page>
</notebook>
</sheet>
<chatter/>
+36 -6
View File
@@ -2,8 +2,6 @@
<!-- ===================== 产品介绍(顶级菜单,置顶,位于「我的实训」上方) ===================== -->
<!-- 在当前页面的内容区以表单形式打开预置记录 about_page_product,
其 content 富文本字段即为 H5 页面内容(介绍本教育产品本身)。 -->
<!-- 先删除上一轮遗留的 act_url 记录,避免同 external id 模型冲突(ParseError) -->
<delete model="ir.actions.act_url" id="action_yuthon_product_intro"/>
<record id="action_yuthon_product_intro" model="ir.actions.act_window">
<field name="name">产品介绍</field>
<field name="res_model">yuthon.about.page</field>
@@ -163,6 +161,42 @@ else:
sequence="10"
groups="college.group_teacher,base.group_system"/>
<!-- ===================== 考试(与课程管理同级,顶级菜单,无 parent 才能带 web_icon) ===================== -->
<menuitem id="menu_yuthon_exam_root"
name="考试"
web_icon="college,static/src/img/exam.png"
sequence="15"/>
<menuitem id="menu_yuthon_exam_question"
name="题库"
parent="menu_yuthon_exam_root"
action="action_yuthon_school_exam_question"
sequence="10"
groups="college.group_teacher,base.group_system"/>
<menuitem id="menu_yuthon_exam_manage"
name="考试管理"
parent="menu_yuthon_exam_root"
action="action_yuthon_school_exam"
sequence="20"
groups="college.group_teacher,base.group_system"/>
<menuitem id="menu_yuthon_exam_grade"
name="评阅试卷"
parent="menu_yuthon_exam_root"
action="action_yuthon_school_exam_paper_grade"
sequence="30"
groups="college.group_teacher,base.group_system"/>
<menuitem id="menu_yuthon_exam_result"
name="考试结果"
parent="menu_yuthon_exam_root"
action="action_yuthon_school_exam_result"
sequence="40"
groups="college.group_teacher,base.group_system"/>
<menuitem id="menu_yuthon_exam_my"
name="我的考试"
parent="menu_yuthon_exam_root"
action="action_yuthon_school_exam_paper_student"
sequence="50"
groups="college.group_student"/>
<!-- 我的学习:学生端专用(简单表单 + 去学习按钮) -->
<menuitem id="menu_yuthon_course_study"
name="我的学习"
@@ -239,10 +273,6 @@ else:
groups="college.group_teacher,college.group_student,base.group_system"/>
<!-- ===================== 关于系统说明 ===================== -->
<!-- 移除早期占位的 act_url 记录 -->
<delete model="ir.actions.act_url" id="action_yuthon_about_system"/>
<delete model="ir.actions.act_url" id="action_yuthon_about_operation"/>
<!-- 系统说明:打开预置记录 about_page_system -->
<record id="action_yuthon_about_system" model="ir.actions.act_window">
<field name="name">系统说明</field>