From b072b7f973bb669fbf822d1e9bf0123d3bce9765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Fri, 21 Aug 2026 19:25:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=95=99=E8=82=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/__init__.py | 1 + college/__manifest__.py | 8 +- college/models/__init__.py | 2 + college/models/chapter.py | 21 +++ college/models/course.py | 1 + college/models/course_import_wizard.py | 163 +++++++++++++----- college/security/groups.xml | 23 +++ college/security/ir.model.access.csv | 21 +++ college/static/src/img/shixun_module.png | Bin 0 -> 5526 bytes college/static/src/scss/college_theme.scss | 1 + .../static/src/scss/college_theme_dark.scss | 1 + college/views/chapter_views.xml | 19 +- college/views/course_views.xml | 8 + college/views/menus.xml | 42 ++++- 14 files changed, 256 insertions(+), 55 deletions(-) create mode 100644 college/static/src/img/shixun_module.png diff --git a/college/__init__.py b/college/__init__.py index 0650744..91c5580 100644 --- a/college/__init__.py +++ b/college/__init__.py @@ -1 +1,2 @@ +from . import controllers from . import models diff --git a/college/__manifest__.py b/college/__manifest__.py index 304ef9f..d6660ab 100644 --- a/college/__manifest__.py +++ b/college/__manifest__.py @@ -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': [ diff --git a/college/models/__init__.py b/college/models/__init__.py index 6d33cbb..9fa0adb 100644 --- a/college/models/__init__.py +++ b/college/models/__init__.py @@ -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 diff --git a/college/models/chapter.py b/college/models/chapter.py index 0a2bee6..c29abaa 100644 --- a/college/models/chapter.py +++ b/college/models/chapter.py @@ -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) diff --git a/college/models/course.py b/college/models/course.py index ea5e44f..8ef6e9e 100644 --- a/college/models/course.py +++ b/college/models/course.py @@ -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( diff --git a/college/models/course_import_wizard.py b/college/models/course_import_wizard.py index 268716c..9634616 100644 --- a/college/models/course_import_wizard.py +++ b/college/models/course_import_wizard.py @@ -1,7 +1,13 @@ -"""课程批量导入向导 —— CSV 模板下载 + 批量创建课程(导入大纲即可)。 +"""课程批量导入向导 —— CSV 模板下载 + 批量创建课程(可选连带章节与章节任务)。 -模板列:课程名称、课程编号、所属学院、学期、课程类型、课时、课程大纲。 -导入时直接创建 yuthon.school.course 记录。 +模板列(前 8 列为课程本体,后 5 列为可选章节/任务): +课程名称、课程编号、所属学院、学期、课程类型、课时、课程大纲、业务情景、 +章节标题、章节说明、章节课时、任务名称、是否完成。 + +- 仅填前 8 列:行为与旧版一致,只创建课程; +- 填了「章节标题」:自动按 (课程, 章节标题) 找/建章节; +- 填了「任务名称」:在对应章节下创建任务列表明细行(is_done 由「是否完成」决定)。 +同一课程 / 同一章节多行时自动复用,不重复创建。 """ import base64 @@ -58,10 +64,13 @@ class CourseImportWizard(models.TransientModel): '
  • 点击下方「下载模板」按钮下载 CSV 模板;
  • ' '
  • 用 Excel / WPS 打开模板,按示例填写数据:
    ' '课程名称、课程编号 为必填;
    ' - '所属学院、学期、课程类型、课时、课程大纲 为选填;
  • ' + '所属学院、学期、课程类型、课时、课程大纲、业务情景 为选填;' '
  • 课程类型可选:理论 / 实践
  • ' + '
  • 章节 / 任务(可选):填「章节标题」会自动建/复用章节,' + '填「任务名称」会在该章节下建一条任务列表明细(是否完成填 是/否);' + '同一课程、同一章节多行会自动合并,不会重复创建;
  • ' '
  • 保存为 CSV 后上传并点击「开始导入」;
  • ' - '
  • 若课程编号已存在则跳过,不会重复创建。
  • ' + '
  • 若课程名称已存在则复用,不会重复创建课程。
  • ' '' ), ) @@ -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 diff --git a/college/security/groups.xml b/college/security/groups.xml index 7f0c04a..0961089 100644 --- a/college/security/groups.xml +++ b/college/security/groups.xml @@ -34,4 +34,27 @@ + + + + 考试答卷:学生仅能访问自己的答卷 + + + [('user_id', '=', user.id)] + + + + + + + + 考试答题:学生仅能访问自己的答题记录 + + + [('paper_id.user_id', '=', user.id)] + + + + + diff --git a/college/security/ir.model.access.csv b/college/security/ir.model.access.csv index c8f8acd..d9ab7a6 100644 --- a/college/security/ir.model.access.csv +++ b/college/security/ir.model.access.csv @@ -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 diff --git a/college/static/src/img/shixun_module.png b/college/static/src/img/shixun_module.png new file mode 100644 index 0000000000000000000000000000000000000000..be5ba8fc666a31e8cdc36e14c14990087b122ca7 GIT binary patch literal 5526 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 literal 0 HcmV?d00001 diff --git a/college/static/src/scss/college_theme.scss b/college/static/src/scss/college_theme.scss index f05bbbf..ac999bf 100644 --- a/college/static/src/scss/college_theme.scss +++ b/college/static/src/scss/college_theme.scss @@ -266,3 +266,4 @@ h4 { font-size: 15px; margin: 16px 0 8px; } p, li { color: $o-main-text-color; } } + diff --git a/college/static/src/scss/college_theme_dark.scss b/college/static/src/scss/college_theme_dark.scss index 231a017..1779238 100644 --- a/college/static/src/scss/college_theme_dark.scss +++ b/college/static/src/scss/college_theme_dark.scss @@ -299,3 +299,4 @@ h4 { font-size: 15px; margin: 16px 0 8px; color: #f8fafc; } p, li { color: #cbd5e1; } } + diff --git a/college/views/chapter_views.xml b/college/views/chapter_views.xml index de0dd00..37772f1 100644 --- a/college/views/chapter_views.xml +++ b/college/views/chapter_views.xml @@ -8,9 +8,14 @@
    - - - + + + + + + + + @@ -29,6 +34,14 @@ title="查看本题所有学员的回答"/> + + + + + + + + diff --git a/college/views/course_views.xml b/college/views/course_views.xml index 0bfef32..8320390 100644 --- a/college/views/course_views.xml +++ b/college/views/course_views.xml @@ -62,6 +62,7 @@ +