From 78477e45de90c43b58bb8c2f94db1821e7a9cd3d Mon Sep 17 00:00:00 2001 From: agent Date: Mon, 12 Oct 2026 17:34:00 +0800 Subject: [PATCH 01/15] =?UTF-8?q?fix(college):=20=E5=B0=86=20hide=5Fabout?= =?UTF-8?q?=5Fedit.js/scss=20=E6=AD=A3=E5=BC=8F=E6=B3=A8=E5=86=8C=E8=BF=9B?= =?UTF-8?q?=20web.assets=5Fbackend=20=E8=B5=84=E6=BA=90=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/__manifest__.py | 2 ++ college/static/src/scss/hide_about_edit.scss | 11 +++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/college/__manifest__.py b/college/__manifest__.py index d5124d2..aac0be5 100644 --- a/college/__manifest__.py +++ b/college/__manifest__.py @@ -63,6 +63,8 @@ 'college/static/src/js/hide_user_menu_items.js', 'college/static/src/js/hide_activity_menu.js', 'college/static/src/js/portal_indicator.js', + 'college/static/src/js/hide_about_edit.js', + 'college/static/src/scss/hide_about_edit.scss', 'college/static/src/xml/portal_indicator.xml', 'college/static/src/xml/sidenav_tooltip.xml', ], diff --git a/college/static/src/scss/hide_about_edit.scss b/college/static/src/scss/hide_about_edit.scss index 732e9e2..89335e8 100644 --- a/college/static/src/scss/hide_about_edit.scss +++ b/college/static/src/scss/hide_about_edit.scss @@ -1,7 +1,6 @@ -// 关于页(yuthon.about.page)为只读展示页: -// form 视图已设 edit="false",但教师组有该模型写权限时控制面板仍会渲染「编辑」按钮。 -// 这里用 CSS 做兜底隐藏(若按钮恰好在 form 内),控制面板中的按钮由同名的 JS patch 处理。 -.o_form_view.o_about_full_form .o_form_button_edit, -.o_form_view.o_about_full_form button[data-hotkey="e"] { - display: none !important; +/* 隐藏「关于页」(yuthon.about.page) 表单右上角的「编辑」按钮 —— JS 的 CSS 兜底 */ +.o_about_full_form { + .o_form_button_edit { + display: none !important; + } } -- 2.34.1 From 6a0f608ec266047e53841fba3cd7621cc89c4b57 Mon Sep 17 00:00:00 2001 From: agent Date: Mon, 12 Oct 2026 18:25:56 +0800 Subject: [PATCH 02/15] =?UTF-8?q?feat(college):=20=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=A6=96=E9=A1=B5=E9=BB=98=E8=AE=A4=E6=94=B9=E4=B8=BA=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E4=BB=8B=E7=BB=8D(res.users.action=5Fid=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4+=E5=AD=98=E9=87=8F=E8=BF=81=E7=A7=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/__manifest__.py | 2 +- college/migrations/18.0.3.3.0/post-migration.py | 16 ++++++++++++++++ college/models/__init__.py | 1 + college/models/res_users.py | 15 +++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 college/migrations/18.0.3.3.0/post-migration.py create mode 100644 college/models/res_users.py diff --git a/college/__manifest__.py b/college/__manifest__.py index aac0be5..a743926 100644 --- a/college/__manifest__.py +++ b/college/__manifest__.py @@ -1,6 +1,6 @@ { 'name': '教育', - 'version': '18.0.3.2.0', + 'version': '18.0.3.3.0', 'summary': 'Odoo 18 教学管理平台 — 师生管理 + 课程管理 + 作业/进展(老师·学生双角色)', 'description': """ Odoo 18 教学管理平台(面向老师与学生,双角色权限) diff --git a/college/migrations/18.0.3.3.0/post-migration.py b/college/migrations/18.0.3.3.0/post-migration.py new file mode 100644 index 0000000..468379b --- /dev/null +++ b/college/migrations/18.0.3.3.0/post-migration.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +from odoo import SUPERUSER_ID, api + + +def migrate(cr, version): + # 把存量用户的登录首页动作统一改为「产品介绍」。 + # 新用户默认值在 models/res_users.py 的 action_id 默认值里设置。 + if not version: + return + env = api.Environment(cr, SUPERUSER_ID, {}) + action = env.ref("college.action_yuthon_product_intro", raise_if_not_found=False) + if not action: + return + users = env["res.users"].with_context(active_test=False).search([("share", "=", False)]) + if users: + users.write({"action_id": action.id}) diff --git a/college/models/__init__.py b/college/models/__init__.py index 501250a..66f1401 100644 --- a/college/models/__init__.py +++ b/college/models/__init__.py @@ -14,3 +14,4 @@ from . import ir_module_module_info from . import exam from . import exam_import_wizard from . import question_import_wizard +from . import res_users diff --git a/college/models/res_users.py b/college/models/res_users.py new file mode 100644 index 0000000..1efd9a0 --- /dev/null +++ b/college/models/res_users.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + # 登录首页动作:默认改为「产品介绍」college.action_yuthon_product_intro。 + # 只覆盖默认值,影响新建用户;存量用户在 migrations/18.0.3.3.0 里批量迁移。 + action_id = fields.Many2one( + "ir.actions.actions", + default=lambda self: self.env.ref( + "college.action_yuthon_product_intro", raise_if_not_found=False + ).id, + ) -- 2.34.1 From 4792c432c2d0a6ba2b63eaeb9fe3128ec84a45af Mon Sep 17 00:00:00 2001 From: root Date: Mon, 12 Oct 2026 19:41:08 +0800 Subject: [PATCH 03/15] =?UTF-8?q?fix(college):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=B7=B2=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20sidenav=5Ftoolt?= =?UTF-8?q?ip.xml=20=E7=9A=84=20manifest=20=E5=BC=95=E7=94=A8(=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E5=8D=87=E7=BA=A7=E6=97=B6=E8=B5=84=E6=BA=90=E5=8C=85?= =?UTF-8?q?=E6=8A=A5=E9=94=99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/__manifest__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/college/__manifest__.py b/college/__manifest__.py index a743926..48b0726 100644 --- a/college/__manifest__.py +++ b/college/__manifest__.py @@ -66,7 +66,6 @@ 'college/static/src/js/hide_about_edit.js', 'college/static/src/scss/hide_about_edit.scss', 'college/static/src/xml/portal_indicator.xml', - 'college/static/src/xml/sidenav_tooltip.xml', ], 'web.assets_web_dark': [ 'college/static/src/scss/college_theme_dark.scss', -- 2.34.1 From d9014ad665c936cc56c30960a07d44890836fb87 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 13 Oct 2026 11:01:01 +0800 Subject: [PATCH 04/15] =?UTF-8?q?style(college):=20=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E4=BB=8B=E7=BB=8D=E9=A1=B5=E6=94=B9=E7=AE=80=E7=BA=A6=E9=A3=8E?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=8E=BB=E6=8E=89=E6=B7=B1=E8=89=B2=E6=B8=90?= =?UTF-8?q?=E5=8F=98=20banner=20=E8=83=8C=E6=99=AF=EF=BC=8C=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E5=8C=BA/=E9=A1=B5=E8=84=9A=E6=94=B9=E7=99=BD?= =?UTF-8?q?=E5=BA=95=E8=BD=BB=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/data/about_pages.xml | 67 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/college/data/about_pages.xml b/college/data/about_pages.xml index bf9ee19..1cb6370 100644 --- a/college/data/about_pages.xml +++ b/college/data/about_pages.xml @@ -244,51 +244,50 @@ 40 True - -
- -
-
- - -
- P - Peng Yuthon +
+ +
+
+ P + Peng Yuthon
- -

鹏程万里 气宇轩昂

-

软件 · 硬件 · AI 智能体

- - -
- 软件 - 硬件 - AI 智能体 - 工程化教育 - 企业实训 +

鹏程万里 气宇轩昂

+

软件 · 硬件 · AI 智能体

+
+ 软件 + 硬件 + AI 智能体 + 工程化教育 + 企业实训
- -
-

关于我们

-

+ +

+

+ 关于我们 +

+

Peng Yuthon 专注于软件、硬件与 AI 智能体方向的工程化教育, 致力于为高校与职业院校提供真实企业场景驱动的实训解决方案。

+
-

联系方式

-
    -
  • B 站:bilibili: pengyuthon
  • -
  • 邮箱:pengyuthon@163.com
  • -
  • 官网:pengyuthon.cn
  • -
  • 学习平台:edu.pengyuthon.cn
  • + +
    +

    + 联系方式 +

    +
      +
    • B 站 bilibili: pengyuthon
    • +
    • 邮箱 pengyuthon@163.com
    • +
    • 官网 pengyuthon.cn
    • +
    • 学习平台 edu.pengyuthon.cn
    - -
    + +
    © 2026 Peng Yuthon. All rights reserved.
    -- 2.34.1 From 9fbbdb0c8e7fefec1fecd65abefa1f3ec6cad8a6 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 13 Oct 2026 11:23:24 +0800 Subject: [PATCH 05/15] =?UTF-8?q?style(college):=20=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E4=BB=8B=E7=BB=8D=E9=A1=B5=E6=A0=87=E9=A2=98=E5=8C=BA=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=20Peng=20Yuthon=20=E5=93=81=E7=89=8C=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E8=BF=9B=E4=B8=80=E6=AD=A5=E7=B2=BE=E7=AE=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/data/about_pages.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/college/data/about_pages.xml b/college/data/about_pages.xml index 1cb6370..bd521e6 100644 --- a/college/data/about_pages.xml +++ b/college/data/about_pages.xml @@ -246,11 +246,7 @@ -
    -
    - P - Peng Yuthon -
    +

    鹏程万里 气宇轩昂

    软件 · 硬件 · AI 智能体

    -- 2.34.1 From 23a90b6b1e7370a0b2edbf04da52c394558767e8 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 13 Oct 2026 11:31:08 +0800 Subject: [PATCH 06/15] =?UTF-8?q?style(college):=20=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E4=BB=8B=E7=BB=8D=E9=A1=B5=E6=A0=87=E9=A2=98=E5=8C=BA=E5=B1=85?= =?UTF-8?q?=E4=B8=AD=EF=BC=8C=E5=89=AF=E6=A0=87=E9=A2=98=E4=B8=8B=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=85=AC=E5=8F=B8=E5=90=8D=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/data/about_pages.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/college/data/about_pages.xml b/college/data/about_pages.xml index bd521e6..478f03a 100644 --- a/college/data/about_pages.xml +++ b/college/data/about_pages.xml @@ -246,9 +246,10 @@ -
    -

    鹏程万里 气宇轩昂

    -

    软件 · 硬件 · AI 智能体

    +
    +

    鹏程万里 气宇轩昂

    +

    软件 · 硬件 · AI 智能体

    +

    鹏宇森数据科技有限公司

    软件 硬件 -- 2.34.1 From 6a977a50abab635b674dcc7b8e76e9d229d64aa6 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 13 Oct 2026 12:15:42 +0800 Subject: [PATCH 07/15] =?UTF-8?q?style(college):=20=E5=9B=A2=E9=98=9F?= =?UTF-8?q?=E4=BB=8B=E7=BB=8D=E9=A1=B5=E5=85=AC=E5=8F=B8=E5=90=8D=E4=B8=8A?= =?UTF-8?q?=E7=A7=BB=E7=AC=AC=E4=BA=8C=E8=A1=8C=E5=B9=B6=E5=A4=A7=E5=8F=B7?= =?UTF-8?q?=E9=BB=91=E8=89=B2=E5=8A=A0=E7=B2=97=EF=BC=8C=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E8=A1=8C=E5=B1=85=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/data/about_pages.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/college/data/about_pages.xml b/college/data/about_pages.xml index 478f03a..1274d35 100644 --- a/college/data/about_pages.xml +++ b/college/data/about_pages.xml @@ -248,9 +248,9 @@

    鹏程万里 气宇轩昂

    -

    软件 · 硬件 · AI 智能体

    -

    鹏宇森数据科技有限公司

    -
    +

    鹏宇森数据科技有限公司

    +

    软件 · 硬件 · AI 智能体

    +
    软件 硬件 AI 智能体 -- 2.34.1 From f300271bf954c015c2dadcf0d57f173c426c65bb Mon Sep 17 00:00:00 2001 From: qitangbo Date: Tue, 29 Sep 2026 20:12:53 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=80=83=E8=AF=95403?= =?UTF-8?q?=E5=B9=B6=E5=90=8C=E6=AD=A5=E5=B7=A6=E4=BE=A7=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E4=B8=8E=E4=B8=AD=E6=96=87=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - exam_controller: 兼容学生档案未绑登录账号(paper.user_id为空)的考试访问校验 - menus.xml: 实训管理顺序15、考试根菜单改名'考试管理'顺序25、子菜单改'考试列表'避免重名 - odoosh_menus.xml: 快捷菜单'我的实训'改名'实训管理' (cherry picked from commit 02a960f62b8f3aae4f0a975e9860567afef4e1a5) --- college/controllers/exam_controller.py | 21 +++++++++++++++++++-- college/views/menus.xml | 4 ++-- odoosh_connector/views/odoosh_menus.xml | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/college/controllers/exam_controller.py b/college/controllers/exam_controller.py index c735a96..d782760 100644 --- a/college/controllers/exam_controller.py +++ b/college/controllers/exam_controller.py @@ -16,12 +16,29 @@ class ExamController(http.Controller): """学生答题卡式考试页面控制器。""" def _check_paper_access(self, paper_id, states=None): - """校验当前登录用户是否为该答卷的学员本人。""" + """校验当前登录用户是否为该答卷的学员本人。 + + 兼容两种身份关联方式: + - 答卷已关联登录账号(paper.user_id)的,直接用账号比对; + - 答卷未关联账号(如学生档案尚未绑定账号)的,通过当前登录账号 + 反向查找对应的学生档案,再与学生答卷的 student_id 比对。 + """ paper = request.env['yuthon.school.exam.paper'].sudo().browse(paper_id) if not paper.exists(): raise http.NotFound() - if paper.user_id.id != request.env.user.id: + + current_user = request.env.user + if paper.user_id and paper.user_id.id != current_user.id: raise AccessError('您无权访问该答卷。') + + if not paper.user_id: + # 答卷未绑定账号时,尝试用当前登录账号找到对应学生档案 + current_student = request.env['yuthon.school.student'].sudo().search([ + ('user_id', '=', current_user.id) + ], limit=1) + if not current_student or current_student.id != paper.student_id.id: + raise AccessError('您无权访问该答卷。') + if states and paper.state not in states: raise UserError('当前答卷状态不允许该操作。') return paper diff --git a/college/views/menus.xml b/college/views/menus.xml index 1bf9e7c..c745c41 100644 --- a/college/views/menus.xml +++ b/college/views/menus.xml @@ -18,7 +18,7 @@ @@ -179,7 +179,7 @@ else: sequence="10" groups="college.group_teacher,base.group_system"/> -- 2.34.1 From e720156c16910aaf1c94e83dcf94cb6b239b94ae Mon Sep 17 00:00:00 2001 From: qitangbo Date: Fri, 16 Oct 2026 17:04:00 +0800 Subject: [PATCH 09/15] =?UTF-8?q?feat(college):=20=E6=B3=A8=E5=86=8C=20mod?= =?UTF-8?q?ule=5Flist=5Fcap.js=E2=80=94=E2=80=94=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=AF=8F=E9=A1=B5=E9=94=81=2050=EF=BC=88?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E4=B8=BB=E9=A1=B5+=E5=AE=9E=E8=AE=AD?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=EF=BC=9B=E7=BF=BB=E9=A1=B5=E7=82=B9=E4=B8=8D?= =?UTF-8?q?=E5=8A=A8=E3=80=81=E6=AF=8F=E9=A1=B5=E6=95=B0=E9=94=81=E5=AE=9A?= =?UTF-8?q?=EF=BC=8C=E6=90=9C=E7=B4=A2=E4=B8=8D=E5=8F=97=E5=BD=B1=E5=93=8D?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/__manifest__.py | 1 + college/static/src/js/module_list_cap.js | 58 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 college/static/src/js/module_list_cap.js diff --git a/college/__manifest__.py b/college/__manifest__.py index ef7efdd..9e54d29 100644 --- a/college/__manifest__.py +++ b/college/__manifest__.py @@ -64,6 +64,7 @@ 'college/static/src/js/hide_activity_menu.js', 'college/static/src/js/portal_indicator.js', 'college/static/src/xml/portal_indicator.xml', + 'college/static/src/js/module_list_cap.js', ], 'web.assets_web_dark': [ 'college/static/src/scss/college_theme_dark.scss', diff --git a/college/static/src/js/module_list_cap.js b/college/static/src/js/module_list_cap.js new file mode 100644 index 0000000..2d15994 --- /dev/null +++ b/college/static/src/js/module_list_cap.js @@ -0,0 +1,58 @@ +/** + * 模块列表(ir.module.module)每页数量锁定为 50。 + * + * 覆盖两个入口(同一模型、同款 kanban,一起受益): + * 1. 应用主页(Odoo 自带「应用」菜单) + * 2. 实训模块页(college 自定义 action) + * + * 原理(两个 patch,全在数据链路上,不碰 Odoo 源码、不删任何模块): + * Patch 1 KanbanController.prototype.modelParams(getter): + * 模型为 ir.module.module 时,把初始每页数 limit 钉为 50, + * 并在 modelConfig 上打一个标记(__yuthon_module_list_cap)。 + * 打标记是为了和 list 视图区分——设置里的「模块」列表也是这个模型, + * 但它走 ListController、不打标记,所以完全不受影响。 + * Patch 2 DynamicList.prototype.load: + * 这是翻页/手输每页数的唯一数据出口(Pager.onUpdate 最终就是调到 + * model.root.load({offset, limit}))。带标记且调用方传了 limit 时, + * 强制压回 offset=0 / limit=50 —— 第 2 页进不去、每页数改了也白改。 + * + * 效果:页面永远只显示前 50 个模块,总数仍正常显示(如 1-50 / 56); + * 搜索照常能用,结果同样只取前 50;其他所有页面零影响。 + * + * 注意:本文件必须注册进 __manifest__.py 的 web.assets_backend 才会生效; + * 改后必须 -u college 升级模块 + 强刷浏览器(Ctrl+Shift+R), + * 仅 git pull + 重启不会重建前端资源包。 + */ +/** @odoo-module **/ +import { patch } from "@web/core/utils/patch"; +import { KanbanController } from "@web/views/kanban/kanban_controller"; +import { DynamicList } from "@web/model/relational_model/dynamic_list"; + +const MODULE_MODEL = "ir.module.module"; +const PAGE_LIMIT = 50; +const CAP_FLAG = "__yuthon_module_list_cap"; + +// Patch 1:kanban 初始每页数锁 50,并给数据链路打标记(仅 kanban,list 不打) +patch(KanbanController.prototype, { + get modelParams() { + const params = super.modelParams; + if (this.props.resModel === MODULE_MODEL) { + params.limit = PAGE_LIMIT; + if (params.config) { + params.config[CAP_FLAG] = PAGE_LIMIT; + } + } + return params; + }, +}); + +// Patch 2:翻页/手输的唯一数据出口,强制压回第 1 页、每页 50 条 +patch(DynamicList.prototype, { + load(params) { + const cap = this.config && this.config[CAP_FLAG]; + if (cap && params && "limit" in params) { + params = { ...params, offset: 0, limit: cap }; + } + return super.load(params); + }, +}); -- 2.34.1 From 77739b113c56d1d0e3c8c27ce6b624840ad4d1e6 Mon Sep 17 00:00:00 2001 From: qitangbo Date: Fri, 16 Oct 2026 17:04:01 +0800 Subject: [PATCH 10/15] =?UTF-8?q?style(college):=20=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E4=BB=8B=E7=BB=8D=E9=A1=B5=E5=8E=BB=E6=8E=89=E9=BB=84=E8=89=B2?= =?UTF-8?q?=20P=20=E5=BE=BD=E6=A0=87=EF=BC=8Cpython=20=E6=94=B9=E5=A4=A7?= =?UTF-8?q?=E5=86=99=20Python=EF=BC=88Hero=20=E6=A0=8F+=E9=A1=B5=E8=84=9A?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/data/about_pages.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/college/data/about_pages.xml b/college/data/about_pages.xml index f0d93f2..cf3ed07 100644 --- a/college/data/about_pages.xml +++ b/college/data/about_pages.xml @@ -103,8 +103,7 @@
    - P - python企业实训平台 + Python企业实训平台

    从 Python 语法到企业级项目实战

    面向高校与职业院校的工程化 Python 实训平台,把企业真实开发场景搬进课堂

    @@ -231,7 +230,7 @@
    - python企业实训平台 · 基于 Odoo 18 构建 · 让每一次练习都更接近真实开发 + Python企业实训平台 · 基于 Odoo 18 构建 · 让每一次练习都更接近真实开发
    ]]> -- 2.34.1 From ccff84e9f57c153ef5c3b3efc99d27eb56515321 Mon Sep 17 00:00:00 2001 From: qitangbo Date: Fri, 16 Oct 2026 17:04:02 +0800 Subject: [PATCH 11/15] =?UTF-8?q?chore:=20=E7=BA=B3=E5=85=A5=20yuthon=5Fem?= =?UTF-8?q?ployee=20=E6=A8=A1=E5=9D=97=EF=BC=88=E6=95=99=E8=81=8C=E5=B7=A5?= =?UTF-8?q?=E6=A1=A3=E6=A1=88=EF=BC=8C=E5=87=BA=E5=8B=A4/=E8=96=AA?= =?UTF-8?q?=E8=B5=84=E4=BD=93=E7=B3=BB=E6=95=B0=E6=8D=AE=E6=A0=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_employee/__init__.py | 1 + yuthon_employee/__manifest__.py | 33 +++++++ yuthon_employee/data/demo.xml | 39 ++++++++ yuthon_employee/models/__init__.py | 1 + yuthon_employee/models/employee.py | 74 ++++++++++++++++ yuthon_employee/security/ir.model.access.csv | 2 + yuthon_employee/static/description/icon.png | Bin 0 -> 7455 bytes yuthon_employee/views/employee_views.xml | 88 +++++++++++++++++++ 8 files changed, 238 insertions(+) create mode 100644 yuthon_employee/__init__.py create mode 100644 yuthon_employee/__manifest__.py create mode 100644 yuthon_employee/data/demo.xml create mode 100644 yuthon_employee/models/__init__.py create mode 100644 yuthon_employee/models/employee.py create mode 100644 yuthon_employee/security/ir.model.access.csv create mode 100644 yuthon_employee/static/description/icon.png create mode 100644 yuthon_employee/views/employee_views.xml diff --git a/yuthon_employee/__init__.py b/yuthon_employee/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/yuthon_employee/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/yuthon_employee/__manifest__.py b/yuthon_employee/__manifest__.py new file mode 100644 index 0000000..9b2097d --- /dev/null +++ b/yuthon_employee/__manifest__.py @@ -0,0 +1,33 @@ +{ + 'name': 'Yuthon 员工管理', + 'version': '18.0.1.0.0', + 'summary': '大学人事基础:教职工档案(工号/部门/岗位/基本工资)', + 'description': """ + 教学模块①(基础层):yuthon_employee + ====================================== + 定义教职工(员工)主数据模型 yuthon.employee,是整个「出勤-薪资」体系的 + 数据根(root)。后续 yuthon_attendance、yuthon_salary 都依赖本模块。 + + 教学要点: + - 模型定义:_name / _description + - 字段类型:Char / Selection / Float / Date / Boolean + - 唯一约束:_sql_constraints(工号不可重复) + - 模块组合:被其它模块通过 _inherit 追加反向关联(attendance_ids / payslip_ids) + - ACL:ir.model.access.csv 授权 + """, + 'author': 'Yuthon', + 'website': '', + 'category': 'Education', + 'depends': ['base', 'web'], + 'data': [ + 'security/ir.model.access.csv', + 'views/employee_views.xml', + ], + 'demo': [ + 'data/demo.xml', + ], + 'license': 'LGPL-3', + 'installable': True, + 'application': True, + 'auto_install': False, +} diff --git a/yuthon_employee/data/demo.xml b/yuthon_employee/data/demo.xml new file mode 100644 index 0000000..0ba1ade --- /dev/null +++ b/yuthon_employee/data/demo.xml @@ -0,0 +1,39 @@ + + + + + T2024001 + 张三 + male + teaching + 讲师 + 13800000001 + zhangsan@school.edu + 8000.0 + 2024-09-01 + + + + A2024002 + 李四 + female + admin + 教务员 + 13800000002 + lisi@school.edu + 6000.0 + 2024-09-01 + + + + T2024003 + 王五 + male + teaching + 副教授 + 13800000003 + wangwu@school.edu + 12000.0 + 2024-09-01 + + diff --git a/yuthon_employee/models/__init__.py b/yuthon_employee/models/__init__.py new file mode 100644 index 0000000..f7b5da3 --- /dev/null +++ b/yuthon_employee/models/__init__.py @@ -0,0 +1 @@ +from . import employee diff --git a/yuthon_employee/models/employee.py b/yuthon_employee/models/employee.py new file mode 100644 index 0000000..29c564b --- /dev/null +++ b/yuthon_employee/models/employee.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models, api + + +class Employee(models.Model): + """教职工(员工)主数据模型。 + + 这是「出勤-薪资」体系的**数据根**:出勤记录挂在员工下, + 工资条在月底把「员工基本工资 + 当月出勤聚合结果」join 在一起算出来。 + """ + _name = 'yuthon.employee' + _description = '教职工(员工)' + _order = 'employee_no' + + # ---- 基础信息 ---- + employee_no = fields.Char( + string='工号', + required=True, + index=True, + help='唯一工号,如 T2024001(教师)/ A2024002(行政)', + ) + name = fields.Char( + string='姓名', + required=True, + ) + gender = fields.Selection( + [('male', '男'), ('female', '女')], + string='性别', + ) + department = fields.Selection( + [ + ('teaching', '教学部'), + ('admin', '行政部'), + ('logistics', '后勤部'), + ('support', '教辅部'), + ], + string='部门', + required=True, + default='teaching', + ) + job_title = fields.Char( + string='岗位 / 职称', + help='如:讲师、副教授、教务员', + ) + phone = fields.Char(string='联系电话') + email = fields.Char(string='邮箱') + + # ---- 薪资相关(被 yuthon_salary 读取)---- + base_salary = fields.Float( + string='基本工资', + default=0.0, + help='教学演示用 Float;生产环境建议改用 Monetary + currency_id', + ) + hire_date = fields.Date(string='入职日期') + + active = fields.Boolean( + string='在职', + default=True, + ) + + # ---- 反向关联(由 yuthon_attendance / yuthon_salary 通过 _inherit 追加)---- + # attendance_ids / payslip_ids 不在此定义,避免基础模块反向依赖业务模块。 + # 这是「模块解耦」的核心:根模块不知道子模块的存在。 + + # ---- 约束 ---- + _sql_constraints = [ + ('uniq_employee_no', 'unique(employee_no)', '工号已存在,不可重复!'), + ] + + @api.constrains('base_salary') + def _check_base_salary(self): + for rec in self: + if rec.base_salary < 0: + raise models.ValidationError('基本工资不能为负数。') diff --git a/yuthon_employee/security/ir.model.access.csv b/yuthon_employee/security/ir.model.access.csv new file mode 100644 index 0000000..a7bf7eb --- /dev/null +++ b/yuthon_employee/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_yuthon_employee,yuthon.employee,yuthon_employee.model_yuthon_employee,base.group_user,1,1,1,1 diff --git a/yuthon_employee/static/description/icon.png b/yuthon_employee/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2b2766ae1a87fb02ef5edb61936bc8253582ea8f GIT binary patch literal 7455 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 + + + + + yuthon.employee.list + yuthon.employee + + + + + + + + + + + + + + + + yuthon.employee.form + yuthon.employee + +
    + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + + yuthon.employee搜索 + yuthon.employee + + + + + + + + + + + + + + + + + + + 教职工 + yuthon.employee + list,form + + + + + + + +
    -- 2.34.1 From befeb1b60c842b20da8b46432631bb60fd9c6239 Mon Sep 17 00:00:00 2001 From: qitangbo Date: Fri, 16 Oct 2026 17:11:00 +0800 Subject: [PATCH 12/15] =?UTF-8?q?fix(college):=20=E5=AE=9E=E8=AE=AD?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=88=97=E8=A1=A8=E7=B2=BE=E7=A1=AE=E6=94=B6?= =?UTF-8?q?=E6=95=9B=E5=88=B0=2050=20=E6=9D=A1=E2=80=94=E2=80=94=E6=8C=89?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=90=8D=E6=8E=92=E9=99=A4=206=20=E4=B8=AA?= =?UTF-8?q?=E6=95=99=E5=AD=A6=E5=AE=9E=E8=AE=AD=E7=94=A8=E4=B8=8D=E4=B8=8A?= =?UTF-8?q?=E7=9A=84Odoo=20=E5=8E=9F=E7=94=9F=E5=B0=8F=E5=BA=94=E7=94=A8?= =?UTF-8?q?=EF=BC=88=E4=BC=81=E4=B8=9A=E5=BA=93=E5=AE=9E=E6=B5=8B=2056?= =?UTF-8?q?=E2=86=9250=EF=BC=9B=E7=B1=BB=E7=9B=AE=20ID=20=E8=B7=A8?= =?UTF-8?q?=E5=BA=93=E4=B8=8D=E4=B8=80=E8=87=B4=E7=9A=84=E5=9D=91=E5=86=99?= =?UTF-8?q?=E8=BF=9B=E6=B3=A8=E9=87=8A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/data/shixun_module_data.xml | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/college/data/shixun_module_data.xml b/college/data/shixun_module_data.xml index 0e135ce..348a304 100644 --- a/college/data/shixun_module_data.xml +++ b/college/data/shixun_module_data.xml @@ -8,21 +8,21 @@ ir.module.module kanban,list,form - - [('application', '=', True), ('category_id', 'not in', [13, 54, 71, 15, 94, 6, 10, 11, 20, 33, 55, 37, 59, 21, 89, 90, 92, 103, 28, 32, 88, 23])] + + [('application', '=', True), ('category_id', 'not in', [13, 54, 71, 15, 94, 6, 10, 11, 20, 33, 55, 37, 59, 21, 89, 90, 92, 103, 28, 32, 88, 23]), ('name', 'not in', ['preview_easy', 'room', 'lunch', 'data_recycle', 'marketing_card', 'project_todo'])] {'shixun_hide_empty_categories': True} -- 2.34.1 From 371ff7a2c8e2722dc68b670314c64c649d26dc08 Mon Sep 17 00:00:00 2001 From: qitangbo Date: Fri, 16 Oct 2026 17:42:52 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E5=99=A8=E9=9A=90=E8=97=8F"/=E6=80=BB?= =?UTF-8?q?=E6=95=B0"=EF=BC=8C=E5=8F=AA=E6=98=BE=E7=A4=BA1-50?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实训模块/应用主页的 ir.module.module 看板: - KanbanController.setup 在 env.config 打隐藏标记(仅 kanban,list 不受影响) - Pager.setup 读到标记后挂载时加 class o_pager_hide_total - JS 注入 CSS 隐藏分页器的" / "分隔 span 和总数 span - 真实总数照常统计,只是不显示;翻页/锁50行为不变 - 本地已用 ir.qweb._get_asset_content 真实编译 bundle 验证补丁在位 --- college/static/src/js/module_list_cap.js | 63 ++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/college/static/src/js/module_list_cap.js b/college/static/src/js/module_list_cap.js index 2d15994..fcc6efd 100644 --- a/college/static/src/js/module_list_cap.js +++ b/college/static/src/js/module_list_cap.js @@ -1,11 +1,11 @@ /** - * 模块列表(ir.module.module)每页数量锁定为 50。 + * 模块列表(ir.module.module)每页数量锁定为 50,并隐藏分页器里的 "/ 总数"。 * * 覆盖两个入口(同一模型、同款 kanban,一起受益): * 1. 应用主页(Odoo 自带「应用」菜单) * 2. 实训模块页(college 自定义 action) * - * 原理(两个 patch,全在数据链路上,不碰 Odoo 源码、不删任何模块): + * 原理(四个 patch,全在数据链路上,不碰 Odoo 源码、不删任何模块): * Patch 1 KanbanController.prototype.modelParams(getter): * 模型为 ir.module.module 时,把初始每页数 limit 钉为 50, * 并在 modelConfig 上打一个标记(__yuthon_module_list_cap)。 @@ -15,8 +15,19 @@ * 这是翻页/手输每页数的唯一数据出口(Pager.onUpdate 最终就是调到 * model.root.load({offset, limit}))。带标记且调用方传了 limit 时, * 强制压回 offset=0 / limit=50 —— 第 2 页进不去、每页数改了也白改。 + * Patch 3 KanbanController.prototype.setup: + * 同样是 ir.module.module 看板时,在 env.config 上打一个标记 + * (__yuthon_hide_pager_total)。usePager() 在 setup 里通过 + * useSubEnv 把 pagerProps 挂到 env.config,control panel 的 + * 渲染时 env 一路继承到这里, + * 所以 Pager 侧能读到该标记(list 视图不经过这里,不受影响)。 + * Patch 4 Pager.prototype.setup: + * 读到标记后,挂载时给根节点 nav.o_pager 加 class + * o_pager_hide_total,由本文件注入的 CSS 把 + * " / " 分隔 span 和总数 span(o_pager_limit)隐藏掉, + * 分页器只剩 "1-50"(真实总数照常统计,只是不显示)。 * - * 效果:页面永远只显示前 50 个模块,总数仍正常显示(如 1-50 / 56); + * 效果:页面永远只显示前 50 个模块,分页器只显示 "1-50"(不显示 "/ 56"); * 搜索照常能用,结果同样只取前 50;其他所有页面零影响。 * * 注意:本文件必须注册进 __manifest__.py 的 web.assets_backend 才会生效; @@ -24,13 +35,32 @@ * 仅 git pull + 重启不会重建前端资源包。 */ /** @odoo-module **/ +import { onMounted } from "@odoo/owl"; import { patch } from "@web/core/utils/patch"; import { KanbanController } from "@web/views/kanban/kanban_controller"; import { DynamicList } from "@web/model/relational_model/dynamic_list"; +import { Pager } from "@web/core/pager/pager"; const MODULE_MODEL = "ir.module.module"; const PAGE_LIMIT = 50; const CAP_FLAG = "__yuthon_module_list_cap"; +const HIDE_TOTAL_FLAG = "__yuthon_hide_pager_total"; +const HIDE_TOTAL_STYLE_ID = "yuthon-pager-hide-total-style"; + +// 隐藏分页器 " / 总数" 的样式(只对带 o_pager_hide_total 的分页器生效)。 +// .o_pager_value 后面紧跟的裸 span 就是模板里的 " / ", +// 配合 .o_pager_limit(总数)一起隐藏;编辑态(input)时同样只剩输入框。 +if (!document.getElementById(HIDE_TOTAL_STYLE_ID)) { + const style = document.createElement("style"); + style.id = HIDE_TOTAL_STYLE_ID; + style.textContent = [ + ".o_pager.o_pager_hide_total .o_pager_value + span,", + ".o_pager.o_pager_hide_total .o_pager_limit {", + " display: none !important;", + "}", + ].join("\n"); + document.head.appendChild(style); +} // Patch 1:kanban 初始每页数锁 50,并给数据链路打标记(仅 kanban,list 不打) patch(KanbanController.prototype, { @@ -46,6 +76,18 @@ patch(KanbanController.prototype, { }, }); +// Patch 3:给分页器链路打隐藏标记(usePager 的 useSubEnv 在 setup 内完成, +// super.setup() 之后 env.config.pagerProps 已就位;此处只动 env.config, +// 不碰 pagerProps 对象本身,避免给 Pager 传未声明的 prop) +patch(KanbanController.prototype, { + setup() { + super.setup(); + if (this.props.resModel === MODULE_MODEL) { + this.env.config[HIDE_TOTAL_FLAG] = true; + } + }, +}); + // Patch 2:翻页/手输的唯一数据出口,强制压回第 1 页、每页 50 条 patch(DynamicList.prototype, { load(params) { @@ -56,3 +98,18 @@ patch(DynamicList.prototype, { return super.load(params); }, }); + +// Patch 4:带标记的 Pager 挂载后加 class,由上面的 CSS 隐藏 "/ 总数" +patch(Pager.prototype, { + setup() { + super.setup(); + const config = this.env && this.env.config; + if (config && config[HIDE_TOTAL_FLAG]) { + onMounted(() => { + if (this.root.el) { + this.root.el.classList.add("o_pager_hide_total"); + } + }); + } + }, +}); -- 2.34.1 From 24eac518aff0031af2b83b09b836c2c313196747 Mon Sep 17 00:00:00 2001 From: qitangbo Date: Fri, 16 Oct 2026 18:00:46 +0800 Subject: [PATCH 14/15] =?UTF-8?q?fix(college):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E5=99=A8=E9=9A=90=E8=97=8F=E6=80=BB=E6=95=B0?= =?UTF-8?q?=E7=9A=84=20OwlError=E2=80=94=E2=80=94=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E7=BB=A7=E6=89=BF=E6=9B=BF=E4=BB=A3=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=20this.root.el?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上个提交在 Pager.prototype.setup 里用 this.root.el 加 class,但 OWL 2 的 Component 基类没有 root 属性(ControlPanel 的 this.root 是它自己 useRef("root") 建的,Pager 模板没有该 ref),导致 TypeError: Cannot read properties of undefined (reading 'el'), 后台整个 OWL 生命周期报错崩溃。 改为官方扩展点模板继承: - 新增 xml/module_list_pager.xml,t-inherit 扩展 web.Pager, 给 nav 加 t-att-class,env.config 带标记时输出 o_pager_hide_total - js/module_list_cap.js 删掉 Pager patch 与 onMounted,保留 env 标记 - manifest 注册新 xml - 已编译级验证:o_pager_hide_total 进入 bundle,日志无模板警告 --- college/__manifest__.py | 1 + college/static/src/js/module_list_cap.js | 41 ++++++-------------- college/static/src/xml/module_list_pager.xml | 21 ++++++++++ 3 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 college/static/src/xml/module_list_pager.xml diff --git a/college/__manifest__.py b/college/__manifest__.py index 348b789..f521aa9 100644 --- a/college/__manifest__.py +++ b/college/__manifest__.py @@ -67,6 +67,7 @@ 'college/static/src/scss/hide_about_edit.scss', 'college/static/src/xml/portal_indicator.xml', 'college/static/src/js/module_list_cap.js', + 'college/static/src/xml/module_list_pager.xml', ], 'web.assets_web_dark': [ 'college/static/src/scss/college_theme_dark.scss', diff --git a/college/static/src/js/module_list_cap.js b/college/static/src/js/module_list_cap.js index fcc6efd..ac583e6 100644 --- a/college/static/src/js/module_list_cap.js +++ b/college/static/src/js/module_list_cap.js @@ -5,7 +5,7 @@ * 1. 应用主页(Odoo 自带「应用」菜单) * 2. 实训模块页(college 自定义 action) * - * 原理(四个 patch,全在数据链路上,不碰 Odoo 源码、不删任何模块): + * 原理(三个 patch + 一个模板继承,全在扩展点上,不碰 Odoo 源码、不删任何模块): * Patch 1 KanbanController.prototype.modelParams(getter): * 模型为 ir.module.module 时,把初始每页数 limit 钉为 50, * 并在 modelConfig 上打一个标记(__yuthon_module_list_cap)。 @@ -19,27 +19,24 @@ * 同样是 ir.module.module 看板时,在 env.config 上打一个标记 * (__yuthon_hide_pager_total)。usePager() 在 setup 里通过 * useSubEnv 把 pagerProps 挂到 env.config,control panel 的 - * 渲染时 env 一路继承到这里, - * 所以 Pager 侧能读到该标记(list 视图不经过这里,不受影响)。 - * Patch 4 Pager.prototype.setup: - * 读到标记后,挂载时给根节点 nav.o_pager 加 class - * o_pager_hide_total,由本文件注入的 CSS 把 - * " / " 分隔 span 和总数 span(o_pager_limit)隐藏掉, - * 分页器只剩 "1-50"(真实总数照常统计,只是不显示)。 + * 渲染时 env 一路继承到这里。 + * 模板继承 xml/module_list_pager.xml:t-inherit 扩展官方 web.Pager 模板, + * 给 nav 加 t-att-class——env.config 带上述标记时输出 + * o_pager_hide_total,由本文件注入的 CSS 隐藏 " / " 和总数 span。 + * (OWL 组件没有内置 root 属性,勿在 patch 里用 this.root.el; + * 那是 ControlPanel 自己 useRef("root") 建的,Pager 模板没有该 ref。) * * 效果:页面永远只显示前 50 个模块,分页器只显示 "1-50"(不显示 "/ 56"); * 搜索照常能用,结果同样只取前 50;其他所有页面零影响。 * * 注意:本文件必须注册进 __manifest__.py 的 web.assets_backend 才会生效; - * 改后必须 -u college 升级模块 + 强刷浏览器(Ctrl+Shift+R), - * 仅 git pull + 重启不会重建前端资源包。 + * 模板继承文件同样要注册;改后必须 -u college 升级模块 + 强刷浏览器 + * (Ctrl+Shift+R),仅 git pull + 重启不会重建前端资源包。 */ /** @odoo-module **/ -import { onMounted } from "@odoo/owl"; import { patch } from "@web/core/utils/patch"; import { KanbanController } from "@web/views/kanban/kanban_controller"; import { DynamicList } from "@web/model/relational_model/dynamic_list"; -import { Pager } from "@web/core/pager/pager"; const MODULE_MODEL = "ir.module.module"; const PAGE_LIMIT = 50; @@ -77,8 +74,9 @@ patch(KanbanController.prototype, { }); // Patch 3:给分页器链路打隐藏标记(usePager 的 useSubEnv 在 setup 内完成, -// super.setup() 之后 env.config.pagerProps 已就位;此处只动 env.config, -// 不碰 pagerProps 对象本身,避免给 Pager 传未声明的 prop) +// super.setup() 之后 env.config 已就位;此处只动 env.config, +// 不碰 pagerProps 对象本身,避免给 Pager 传未声明的 prop。 +// 标记由 xml/module_list_pager.xml 的模板继承读取。) patch(KanbanController.prototype, { setup() { super.setup(); @@ -98,18 +96,3 @@ patch(DynamicList.prototype, { return super.load(params); }, }); - -// Patch 4:带标记的 Pager 挂载后加 class,由上面的 CSS 隐藏 "/ 总数" -patch(Pager.prototype, { - setup() { - super.setup(); - const config = this.env && this.env.config; - if (config && config[HIDE_TOTAL_FLAG]) { - onMounted(() => { - if (this.root.el) { - this.root.el.classList.add("o_pager_hide_total"); - } - }); - } - }, -}); diff --git a/college/static/src/xml/module_list_pager.xml b/college/static/src/xml/module_list_pager.xml new file mode 100644 index 0000000..478d37a --- /dev/null +++ b/college/static/src/xml/module_list_pager.xml @@ -0,0 +1,21 @@ + + + + + + (env.config && env.config.__yuthon_hide_pager_total ? 'o_pager_hide_total ' : '') + 'o_pager d-flex gap-2 h-100' + + + -- 2.34.1 From 788203f9a02ee5876c11438613b03bfd88ec57fb Mon Sep 17 00:00:00 2001 From: qitangbo Date: Mon, 19 Oct 2026 10:34:24 +0800 Subject: [PATCH 15/15] =?UTF-8?q?feat(college):=20=E5=AE=9E=E8=AE=AD?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=94=B9=E7=99=BD=E5=90=8D=E5=8D=95=20domain?= =?UTF-8?q?=E2=80=94=E2=80=94=E8=87=AA=E6=9C=89=E6=A8=A1=E5=9D=9727?= =?UTF-8?q?=E4=B8=AA=E5=85=A8=E5=88=97+Odoo=E5=8E=9F=E7=94=9F=E7=B2=BE?= =?UTF-8?q?=E9=80=8923=E4=B8=AA=E5=85=B150=E4=B8=AA=EF=BC=88=E7=94=A8?= =?UTF-8?q?=E6=88=B72026-10-19=E5=AE=9A=EF=BC=9B=E6=9B=BF=E4=BB=A3?= =?UTF-8?q?=E5=8E=9F=E7=B1=BB=E7=9B=AEID=E6=8E=92=E9=99=A4=E6=96=B9?= =?UTF-8?q?=E6=A1=88=EF=BC=8C=E7=B1=BB=E7=9B=AEID=E8=B7=A8=E5=BA=93?= =?UTF-8?q?=E4=B8=8D=E4=B8=80=E8=87=B4=E5=B7=B2=E5=BC=83=E7=94=A8=EF=BC=9B?= =?UTF-8?q?=E7=94=9F=E4=BA=A7=E5=AE=9E=E6=B5=8B=E5=B0=86=E7=B2=BE=E7=A1=AE?= =?UTF-8?q?=E5=8C=B9=E9=85=8D50=E6=9D=A1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- college/data/shixun_module_data.xml | 33 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/college/data/shixun_module_data.xml b/college/data/shixun_module_data.xml index 348a304..bcd6305 100644 --- a/college/data/shixun_module_data.xml +++ b/college/data/shixun_module_data.xml @@ -8,21 +8,24 @@ ir.module.module kanban,list,form - - [('application', '=', True), ('category_id', 'not in', [13, 54, 71, 15, 94, 6, 10, 11, 20, 33, 55, 37, 59, 21, 89, 90, 92, 103, 28, 32, 88, 23]), ('name', 'not in', ['preview_easy', 'room', 'lunch', 'data_recycle', 'marketing_card', 'project_todo'])] + + [('application', '=', True), ('name', 'in', ['account', 'accountant', 'agent_api', 'agent_channel', 'agent_core', 'agent_market', 'agent_payment', 'agent_portal', 'college', 'crm', 'game_ai_npc', 'game_base', 'helpdesk', 'hr', 'hr_attendance', 'hr_expense', 'hr_holidays', 'hr_recruitment', 'knowledge', 'learning_center', 'maintenance', 'mrp', 'mrp_workorder', 'odoosh_connector', 'point_of_sale', 'pos_restaurant', 'project', 'purchase', 'quality_control', 'sale_management', 'school_base', 'stock', 'student_organization', 'survey', 'website', 'website_slides', 'yt_world', 'yusen_website', 'yuthon_applicant', 'yuthon_attendance', 'yuthon_contract', 'yuthon_employee', 'yuthon_hr_attendance', 'yuthon_hr_employee', 'yuthon_notice', 'yuthon_salary', 'yuthon_school', 'yuthon_wage', 'yuthon_web', 'yuthon_will_task'])] {'shixun_hide_empty_categories': True} -- 2.34.1