dev_haoran #9

Merged
liuhaoran merged 6 commits from dev_haoran into master 2026-06-20 20:04:34 +08:00
23 changed files with 540 additions and 93 deletions
Showing only changes of commit 45fbe4fbba - Show all commits

View File

@ -14,6 +14,7 @@ class CourseHomework(models.Model):
requirement = fields.Html(string='作业要求') requirement = fields.Html(string='作业要求')
course_id = fields.Many2one('learning.course', string='所属课程', required=True, ondelete='cascade') course_id = fields.Many2one('learning.course', string='所属课程', required=True, ondelete='cascade')
chapter_id = fields.Many2one('course.chapter', string='关联章节') chapter_id = fields.Many2one('course.chapter', string='关联章节')
total_score = fields.Float(string='作业总分', default=100.0, required=True) total_score = fields.Float(string='作业总分', default=100.0, required=True)
publish_time = fields.Datetime(string='发布时间') publish_time = fields.Datetime(string='发布时间')
deadline = fields.Datetime(string='提交截止时间', required=True) deadline = fields.Datetime(string='提交截止时间', required=True)
@ -28,12 +29,13 @@ class CourseHomework(models.Model):
total_students = fields.Integer(string='应提交人数', compute='_compute_submit_stats') total_students = fields.Integer(string='应提交人数', compute='_compute_submit_stats')
submitted_count = 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') late_count = fields.Integer(string='逾期提交人数', compute='_compute_submit_stats')
course_score_item_id=fields.Many2one('course.score.item',string='作业类型')
unsubmitted_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='教学班', teaching_class_id = fields.Many2one('course.teaching_class', string='教学班',
ondelete='cascade', domain="[('course_id', '=', course_id)]") ondelete='cascade', domain="[('course_id', '=', course_id)]")
submit_ids = fields.One2many('course.homework.submit', 'homework_id', string='提交记录') submit_ids = fields.One2many('course.homework.submit', 'homework_id', string='提交记录')
score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项') score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项')
# ====================== 新增:自动计算提交通道是否开启 ====================== # ====================== 新增:自动计算提交通道是否开启 ======================
@api.depends('state', 'deadline') @api.depends('state', 'deadline')
@ -80,37 +82,61 @@ class CourseHomework(models.Model):
self.publish_time = fields.Datetime.now() self.publish_time = fields.Datetime.now()
# 1. 批量为本班学生生成空白未提交记录 # 1. 批量为本班学生生成空白未提交记录
self._auto_create_empty_submit() self._auto_create_empty_submit()
# 新增:自动生成对应作业的学生成绩明细
self._auto_create_homework_score_detail()
# 2. 推送作业通知给学生 # 2. 推送作业通知给学生
self._send_homework_notice_to_students() self._send_homework_notice_to_students()
def _send_homework_notice_to_students(self): def _auto_create_homework_score_detail(self):
"""发布作业后推送消息给教学班学生"""
self.ensure_one() self.ensure_one()
if not self.teaching_class_id: # 修正字段名 course_score_item_id
if not self.teaching_class_id or not self.course_score_item_id:
return return
# 获取当前教学班所有已选课学生 score_item = self.course_score_item_id
enrolls = self.env['course.teaching_class.enrollment'].search([
# 获取教学班在册学生
enroll_records = self.env['course.teaching_class.enrollment'].search([
('teaching_class_id', '=', self.teaching_class_id.id), ('teaching_class_id', '=', self.teaching_class_id.id),
('state', '=', 'enrolled') ('state', '=', 'enrolled')
]) ])
student_users = enrolls.mapped('student_id.user_id').filtered(lambda u: u) student_ids = enroll_records.mapped('student_id').ids
if not student_users: if not student_ids:
return return
# 消息内容
subject = f"新作业发布:{self.name}" # 查询当前作业+当前分项已存在明细
body = f""" exist_details = self.env['course.student.score.detail'].search([
<p>课程{self.course_id.name}</p> ('course_id', '=', self.course_id.id),
<p>作业名称{self.name}</p> ('score_item_id', '=', score_item.id),
<p>截止时间{self.deadline or ''}</p> ('homework_id', '=', self.id),
<p>作业要求{self.requirement or ''}</p> ('student_id', 'in', student_ids)
""" ])
# 发送消息(关联当前作业记录,学生在消息中心可直接跳转) exist_student_ids = exist_details.mapped('student_id').ids
self.message_post( target_student_ids = [sid for sid in student_ids if sid not in exist_student_ids]
subject=subject, if not target_student_ids:
body=body, return
partner_ids=student_users.mapped('partner_id').ids,
subtype_xmlid='mail.mt_comment' # 批量查询学生总成绩,避免循环查库
) all_student_scores = self.env['course.student.score'].search([
('course_id', '=', self.course_id.id),
('student_id', 'in', target_student_ids)
])
score_map = {rec.student_id.id: rec for rec in all_student_scores}
detail_vals_list = []
for stu_id in target_student_ids:
student_score = score_map.get(stu_id)
if not student_score:
continue
detail_vals_list.append({
'student_score_id': student_score.id,
'homework_id': self.id,
'score_item_id': score_item.id,
'score': 0.0,
'remark': f'{self.course_score_item_id.name}{self.name}'
})
if detail_vals_list:
self.env['course.student.score.detail'].create(detail_vals_list)
def _auto_create_empty_submit(self): def _auto_create_empty_submit(self):
"""仅发布时执行:自动给教学班所有已选课学生生成空白未提交记录""" """仅发布时执行:自动给教学班所有已选课学生生成空白未提交记录"""
@ -138,11 +164,41 @@ class CourseHomework(models.Model):
batch_vals.append({ batch_vals.append({
'homework_id': self.id, 'homework_id': self.id,
'student_id': stu_id, 'student_id': stu_id,
'course_score_item_id':self.course_score_item_id.id,
'state': 'unsubmit' # 默认未提交 'state': 'unsubmit' # 默认未提交
}) })
# 批量创建空白提交记录 # 批量创建空白提交记录
submit_model.create(batch_vals) submit_model.create(batch_vals)
def _send_homework_notice_to_students(self):
"""发布作业后推送消息给教学班学生"""
self.ensure_one()
if not self.teaching_class_id:
return
# 获取当前教学班所有已选课学生
enrolls = self.env['course.teaching_class.enrollment'].search([
('teaching_class_id', '=', self.teaching_class_id.id),
('state', '=', 'enrolled')
])
student_users = enrolls.mapped('student_id.user_id').filtered(lambda u: u)
if not student_users:
return
# 消息内容
subject = f"新作业发布:{self.name}"
body = f"""
<p>课程{self.course_id.name}</p>
<p>作业名称{self.name}</p>
<p>截止时间{self.deadline or ''}</p>
<p>作业要求{self.requirement or ''}</p>
"""
# 发送消息(关联当前作业记录,学生在消息中心可直接跳转)
self.message_post(
subject=subject,
body=body,
partner_ids=student_users.mapped('partner_id').ids,
subtype_xmlid='mail.mt_note'
)
# ====================== 草稿/关闭作业 ====================== # ====================== 草稿/关闭作业 ======================
def action_draft(self): def action_draft(self):
self.state = 'draft' self.state = 'draft'
@ -269,4 +325,15 @@ class CourseHomework(models.Model):
def create(self, vals): def create(self, vals):
homework_record = super().create(vals) homework_record = super().create(vals)
# 草稿状态不执行自动生成提交记录移到action_publish # 草稿状态不执行自动生成提交记录移到action_publish
target_a = self.env["course.score.item"].search([
("name", "=", "作业"),
("create_uid", "=", self.env.uid)
], limit=1)
if target_a:
# 将刚新建的B追加到A的多对多字段
target_a.write({
"homework_ids": [(4, homework_record.id)]
})
return homework_record return homework_record

View File

@ -28,6 +28,7 @@ class CourseHomeworkSubmit(models.Model):
student_no = fields.Char(related="student_id.stu_num", 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_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_major_id = fields.Many2one(related="student_id.major_id", string="专业", readonly=True)
course_score_item_id = fields.Many2one('course.score.item', string='作业类型')
student_phone = fields.Char(related="student_id.stu_phone", string="手机号", readonly=True) student_phone = fields.Char(related="student_id.stu_phone", string="手机号", readonly=True)
submit_file = fields.Binary(string='提交文件', attachment=True) submit_file = fields.Binary(string='提交文件', attachment=True)
submit_filename = fields.Char(string='文件名') submit_filename = fields.Char(string='文件名')
@ -92,6 +93,7 @@ class CourseHomeworkSubmit(models.Model):
def create(self, vals): def create(self, vals):
submit_rec = super().create(vals) submit_rec = super().create(vals)
self._check_submit_change_state(submit_rec) self._check_submit_change_state(submit_rec)
return submit_rec return submit_rec
def write(self, vals): def write(self, vals):

View File

@ -15,7 +15,7 @@ class CourseScoreItem(models.Model):
course_code = fields.Char(related="course_id.code", 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_credit = fields.Float(related="course_id.credit", string="学分", readonly=True)
course_exam_type = fields.Selection(related="course_id.exam_type", 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') homework_id = fields.Many2one('course.homework', string='作业', ondelete='cascade')
sequence = fields.Integer(string="排序", default=10) sequence = fields.Integer(string="排序", default=10)
score_type = fields.Selection([ score_type = fields.Selection([
('homework', '作业'), ('homework', '作业'),
@ -30,7 +30,7 @@ class CourseScoreItem(models.Model):
full_score = fields.Float(string='满分', default=100.0) full_score = fields.Float(string='满分', default=100.0)
is_active = fields.Boolean(string='是否启用', default=True) is_active = fields.Boolean(string='是否启用', default=True)
homework_ids = fields.Many2many('course.homework', string='关联作业') homework_ids = fields.Many2many('course.homework', string='关联作业')
course_student_score=fields.Many2one('course.student.score',string='学生成绩')
# 统计字段 # 统计字段
student_count = fields.Integer(string='学生人数', compute='_compute_stats') student_count = fields.Integer(string='学生人数', compute='_compute_stats')
avg_score = fields.Float(string='平均分', compute='_compute_stats') avg_score = fields.Float(string='平均分', compute='_compute_stats')

View File

@ -24,15 +24,16 @@ class CourseStudentScore(models.Model):
student_class_id = fields.Many2one( string="班级", related="student_id.class_id", 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) 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='学生姓名') stu_name=fields.Char(related='student_id.stu_name',required=True, ondelete='cascade',string='学生姓名')
score_item_ids=fields.One2many('course.score.item','course_student_score',string='成绩类型')
score_ids = fields.One2many('course.student.score.detail', 'student_score_id', string='成绩明细') score_ids = fields.One2many('course.student.score.detail', 'student_score_id', string='成绩明细')
teaching_class_id=fields.Many2one('course.teaching_class',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_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_semester = fields.Selection(string="学期", related="teaching_class_id.semester", readonly=True)
teaching_class_main_teacher = fields.Many2one('hr.employee', string="主讲教师", teaching_class_main_teacher = fields.Many2one('hr.employee', string="主讲教师",
related="teaching_class_id.main_teacher_id", readonly=True) related="teaching_class_id.main_teacher_id", readonly=True)
total_score = fields.Float(string='综合成绩', compute='_compute_total_score', store=True) total_score = fields.Float(string='综合成绩', compute='_compute_total_score', store=True)
course_score_item_id = fields.Many2one('course.score.item', string='成绩分项')
grade_level = fields.Selection([ grade_level = fields.Selection([
('A', '优秀(A)'), ('A', '优秀(A)'),
('B', '良好(B)'), ('B', '良好(B)'),
@ -77,8 +78,8 @@ class CourseStudentScoreDetail(models.Model):
student_score_id = fields.Many2one('course.student.score', string='学生成绩', required=True, ondelete='cascade') 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='显示名称') 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_item_id = fields.Many2one('course.score.item', string='成绩项目', ondelete='restrict')
homework_id=fields.Many2one('course.homework',string='作业')
# 得分 # 得分
score = fields.Float(string='得分', help='该项目实际得分') score = fields.Float(string='得分', help='该项目实际得分')

View File

@ -215,12 +215,32 @@ class CourseTeachingClassEnrollment(models.Model):
@api.model @api.model
def create(self, vals): def create(self, vals):
"""新增选课记录校验人数上限""" """新增选课记录校验人数上限 + 自动生成学生总成绩记录"""
res = super().create(vals) res = super().create(vals)
cls = res.teaching_class_id cls = res.teaching_class_id
student = res.student_id
course = cls.course_id
# 1. 人数上限校验原有逻辑保留
valid_num = len(cls.enrollment_ids.filtered(lambda e: e.state == 'enrolled')) valid_num = len(cls.enrollment_ids.filtered(lambda e: e.state == 'enrolled'))
if cls.max_students and valid_num > cls.max_students: if cls.max_students and valid_num > cls.max_students:
raise ValidationError("该教学班人数已达上限,无法继续添加学生!") raise ValidationError("该教学班人数已达上限,无法继续添加学生!")
# 2. 查找是否已经存在该学生+该课程+该教学班的成绩主记录,避免重复创建
exist_score = self.env['course.student.score'].search([
('student_id', '=', student.id),
('course_id', '=', course.id),
('teaching_class_id', '=', cls.id)
], limit=1)
# 不存在则自动创建一条空成绩主记录
if not exist_score:
self.env['course.student.score'].create({
'course_id': course.id,
'student_id': student.id,
'teaching_class_id': cls.id
})
return res return res

View File

@ -6,7 +6,7 @@
<list> <list>
<field name="name"/> <field name="name"/>
<field name="course_id"/> <field name="course_id"/>
<field name="chapter_id"/> <!-- <field name="chapter_id"/>-->
<field name="total_score"/> <field name="total_score"/>
<field name="deadline"/> <field name="deadline"/>
<field name="submitted_count" sum="合计"/> <field name="submitted_count" sum="合计"/>
@ -38,9 +38,10 @@
</div> </div>
<group name="basic_info" string="基本信息"> <group name="basic_info" string="基本信息">
<group> <group>
<field name="teaching_class_id"/>
<field name="course_id" options="{'no_create': True}"/> <field name="course_id" options="{'no_create': True}"/>
<field name="chapter_id" options="{'no_create': True}"/> <field name="teaching_class_id"/>
<field name="course_score_item_id" />
<!-- <field name="chapter_id" options="{'no_create': True}"/>-->
<field name="total_score"/> <field name="total_score"/>
<field name="state" widget="statusbar"/> <field name="state" widget="statusbar"/>
</group> </group>

View File

@ -45,17 +45,11 @@
</group> </group>
</group> </group>
<notebook> <notebook>
<page string="作业明细">
<field name="score_ids" view_id="list_course_student_score_score_ids_om"/>
</page>
<page string="成绩明细"> <page string="成绩明细">
<field name="score_ids"> <field name="score_item_ids" view_id="list_course_student_score_item_om"/>
<list editable="bottom">
<field name="score_item_id" options="{'no_create': True}"/>
<field name="score"/>
<field name="full_score" readonly="1"/>
<field name="weight" readonly="1"/>
<field name="completion_rate" widget="progressbar"/>
<field name="remark"/>
</list>
</field>
</page> </page>
<page string="课程信息"> <page string="课程信息">
<group string="课程详情"> <group string="课程详情">
@ -89,6 +83,74 @@
</field> </field>
</record> </record>
<record id="list_course_student_score_detail_ids_om" model="ir.ui.view">
<field name="name">list.course.student.score.detail.ids.om</field>
<field name="model">course.student.score.detail</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="display_name"/>
<field name="score"/>
<field name="full_score" readonly="1"/>
<!-- <field name="weight" readonly="1"/>-->
<!-- <field name="completion_rate" widget="progressbar"/>-->
<field name="remark"/>
</list>
</field>
</record>
<record id="list_course_student_score_item_om" model="ir.ui.view">
<field name="name">list.course.student.score.item.om</field>
<field name="model">course.score.item</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="course_id"/>
<field name="weight"/>
<field name="full_score"/>
</list>
</field>
</record>
<!-- ==================== 学生成绩动作 ==================== -->
<record id="act_course_student_score" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
</record>
<!-- ==================== 按课程查看学生成绩的动作 ==================== -->
<record id="act_course_student_score_by_course" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="list_course_student_score"/>
<field name="domain">[('course_id', '=', active_id)]</field>
<field name="context">{'default_course_id': active_id}</field>
</record>
<!-- ==================== 按教学班查看学生成绩的动作 ==================== -->
<record id="act_course_student_score_by_teaching_class" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="list_course_student_score"/>
<field name="domain">[('teaching_class_id', '=', active_id)]</field>
<field name="context">{'default_teaching_class_id': active_id}</field>
</record>
<!-- ==================== 二级菜单 ==================== -->
<record id="menu_course_student_score" model="ir.ui.menu">
<field name="name">学生成绩</field>
<field name="parent_id" ref="menu_learning_center_root"/>
<field name="action" ref="act_course_student_score"/>
<field name="sequence">60</field>
</record>
<!-- ==================== 学生成绩明细列表视图 ==================== --> <!-- ==================== 学生成绩明细列表视图 ==================== -->
<record id="view_course_student_score_detail_list" model="ir.ui.view"> <record id="view_course_student_score_detail_list" model="ir.ui.view">
@ -148,16 +210,18 @@
</field> </field>
</record> </record>
<record id="view_course_student_score_detail_search" model="ir.ui.view">
<!-- ==================== 学生成绩动作 ==================== --> <field name="name">view.course.student.score.detail.search</field>
<record id="act_course_student_score" model="ir.actions.act_window"> <field name="model">course.student.score.detail</field>
<field name="name">学生成绩</field> <field name="arch" type="xml">
<field name="res_model">course.student.score</field> <search>
<field name="view_mode">list,form</field> <searchpanel>
<field name="score_item_id" select="multi"/>
</searchpanel>
</search>
</field>
</record> </record>
<!-- ==================== 学生成绩明细动作 ==================== -->
<!-- ==================== 学生成绩明细动作 ==================== -->
<record id="act_course_student_score_detail" model="ir.actions.act_window"> <record id="act_course_student_score_detail" model="ir.actions.act_window">
<field name="name">成绩明细</field> <field name="name">成绩明细</field>
<field name="res_model">course.student.score.detail</field> <field name="res_model">course.student.score.detail</field>
@ -165,35 +229,7 @@
</record> </record>
<!-- ==================== 按课程查看学生成绩的动作 ==================== --> <!-- ==================== 成绩明细二级菜单 ==================== -->
<record id="act_course_student_score_by_course" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="list_course_student_score"/>
<field name="domain">[('course_id', '=', active_id)]</field>
<field name="context">{'default_course_id': active_id}</field>
</record>
<!-- ==================== 按教学班查看学生成绩的动作 ==================== -->
<record id="act_course_student_score_by_teaching_class" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="list_course_student_score"/>
<field name="domain">[('teaching_class_id', '=', active_id)]</field>
<field name="context">{'default_teaching_class_id': active_id}</field>
</record>
<!-- ==================== 二级菜单 ==================== -->
<record id="menu_course_student_score" model="ir.ui.menu">
<field name="name">学生成绩</field>
<field name="parent_id" ref="menu_learning_center_root"/>
<field name="action" ref="act_course_student_score"/>
<field name="sequence">60</field>
</record>
<!-- ==================== 成绩明细二级菜单 ==================== -->
<record id="menu_course_student_score_detail" model="ir.ui.menu"> <record id="menu_course_student_score_detail" model="ir.ui.menu">
<field name="name">成绩明细</field> <field name="name">成绩明细</field>
<field name="parent_id" ref="menu_learning_center_root"/> <field name="parent_id" ref="menu_learning_center_root"/>

View File

@ -36,6 +36,8 @@
<group name="basic_info" string="基本信息"> <group name="basic_info" string="基本信息">
<group> <group>
<field name="homework_id" options="{'no_create': True}"/> <field name="homework_id" options="{'no_create': True}"/>
<field name="course_score_item_id" />
<field name="student_id" options="{'no_create': True}"/> <field name="student_id" options="{'no_create': True}"/>
<field name="submit_time" readonly="1"/> <field name="submit_time" readonly="1"/>
<field name="is_late" widget="boolean_toggle" readonly="1"/> <field name="is_late" widget="boolean_toggle" readonly="1"/>

View File

@ -104,18 +104,18 @@
</field> </field>
</record> </record>
<!-- <record id="view_course_syllabus_list" model="ir.ui.view">--> <record id="view_course_syllabus_list" model="ir.ui.view">
<!-- <field name="name">course.syllabus.list</field>--> <field name="name">course.syllabus.list</field>
<!-- <field name="model">course.syllabus</field>--> <field name="model">course.syllabus</field>
<!-- <field name="arch" type="xml">--> <field name="arch" type="xml">
<!-- <list>--> <list>
<!-- <field name="title"/>--> <field name="title"/>
<!-- <field name="syllabus_type" widget="badge"/>--> <field name="syllabus_type" widget="badge"/>
<!-- <field name="version"/>--> <field name="version"/>
<!-- <field name="is_published" widget="boolean_toggle"/>--> <field name="is_published" widget="boolean_toggle"/>
<!-- </list>--> </list>
<!-- </field>--> </field>
<!-- </record>--> </record>
<record id="view_course_chapter_resource_list" model="ir.ui.view"> <record id="view_course_chapter_resource_list" model="ir.ui.view">
<field name="name">course.chapter.resource.list</field> <field name="name">course.chapter.resource.list</field>

View File

@ -0,0 +1 @@
from . import models

View File

@ -0,0 +1,12 @@
{
'name': 'Permission Manager',
'version': '18.0.1.0.0',
'category': 'Tools',
'summary': '权限管理',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/role_views.xml',
],
'installable': True,
}

View File

@ -0,0 +1 @@
from . import permission_role

View File

@ -0,0 +1,134 @@
from odoo import api, fields, models
class PermissionRole(models.Model):
_name = 'permission.role'
_description = '权限角色'
_rec_name = 'name'
_order = 'sequence, id'
name = fields.Char(string='角色名称', required=True)
code = fields.Char(string='角色代码', required=True, help='student, teacher, admin')
sequence = fields.Integer(string='序号', default=10)
description = fields.Text(string='角色描述')
active = fields.Boolean(string='启用', default=True)
# 角色类型
role_type = fields.Selection([
('student', '学生'),
('teacher', '教师'),
('admin', '管理员'),
('custom', '自定义'),
], string='角色类型', default='custom')
# 关联的权限组
group_ids = fields.Many2many('res.groups', string='权限组')
# 可见菜单
menu_ids = fields.Many2many('ir.ui.menu', string='可见菜单')
# 用户
user_ids = fields.Many2many('res.users', string='用户')
user_count = fields.Integer(string='用户数', compute='_compute_user_count')
# 统计字段
group_count = fields.Integer(string='权限组数', compute='_compute_group_count')
menu_count = fields.Integer(string='菜单数', compute='_compute_menu_count')
@api.depends('user_ids')
def _compute_user_count(self):
for record in self:
record.user_count = len(record.user_ids)
@api.depends('group_ids')
def _compute_group_count(self):
for record in self:
record.group_count = len(record.group_ids)
@api.depends('menu_ids')
def _compute_menu_count(self):
for record in self:
record.menu_count = len(record.menu_ids)
def action_assign_users(self):
"""分配用户"""
return {
'type': 'ir.actions.act_window',
'name': '分配用户',
'res_model': 'res.users',
'view_mode': 'list,form',
'target': 'new',
'domain': [('id', 'not in', self.user_ids.ids)],
'context': {
'default_role_ids': [(4, self.id)],
},
}
def action_view_users(self):
"""查看角色下的用户"""
return {
'type': 'ir.actions.act_window',
'name': f'角色用户 - {self.name}',
'res_model': 'res.users',
'view_mode': 'list,form',
'domain': [('id', 'in', self.user_ids.ids)],
}
def action_copy_permissions(self):
"""复制权限(从其他角色)"""
return {
'type': 'ir.actions.act_window',
'name': '复制权限',
'res_model': 'permission.copy.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_target_role_id': self.id},
}
class ResUsersInherit(models.Model):
_inherit = 'res.users'
role_ids = fields.Many2many('permission.role', string='权限角色')
role_count = fields.Integer(string='角色数', compute='_compute_role_count')
@api.depends('role_ids')
def _compute_role_count(self):
for record in self:
record.role_count = len(record.role_ids)
def action_view_roles(self):
"""查看用户角色"""
return {
'type': 'ir.actions.act_window',
'name': '用户角色',
'res_model': 'permission.role',
'view_mode': 'list,form',
'domain': [('id', 'in', self.role_ids.ids)],
}
def action_sync_permissions(self):
"""同步权限:将角色的权限组和菜单同步到用户"""
for user in self:
groups = user.role_ids.mapped('group_ids')
menus = user.role_ids.mapped('menu_ids')
user.write({
'groups_id': [(6, 0, groups.ids)],
})
# 菜单权限需要单独处理
for menu in menus:
# 这里可以添加自定义菜单权限逻辑
pass
class PermissionCopyWizard(models.TransientModel):
_name = 'permission.copy.wizard'
_description = '复制权限向导'
target_role_id = fields.Many2one('permission.role', string='目标角色', readonly=True)
source_role_id = fields.Many2one('permission.role', string='源角色', required=True)
copy_groups = fields.Boolean(string='复制权限组', default=True)
copy_menus = fields.Boolean(string='复制菜单权限', default=True)
def action_copy(self):
self.ensure_one()
vals = {}
if self.copy_groups:
vals['group_ids'] = [(6, 0, self.source_role_id.group_ids.ids)]
if self.copy_menus:
vals['menu_ids'] = [(6, 0, self.source_role_id.menu_ids.ids)]
self.target_role_id.write(vals)
return {'type': 'ir.actions.act_window_close'}

View File

@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_permission_role,permission.role,model_permission_role,base.group_system,1,1,1,1
access_res_users,res.users.access,model_res_users,base.group_system,1,1,1,1
access_permission_copy_wizard,permission.copy.wizard.access,model_permission_copy_wizard,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_permission_role permission.role model_permission_role base.group_system 1 1 1 1
3 access_res_users res.users.access model_res_users base.group_system 1 1 1 1
4 access_permission_copy_wizard permission.copy.wizard.access model_permission_copy_wizard base.group_system 1 1 1 1

View File

@ -0,0 +1,166 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ==================== 角色列表视图 ==================== -->
<record id="view_permission_role_list" model="ir.ui.view">
<field name="name">permission.role.list</field>
<field name="model">permission.role</field>
<field name="arch" type="xml">
<list>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="code"/>
<field name="role_type" widget="badge"/>
<field name="user_count" sum="合计"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</record>
<!-- ==================== 角色表单视图 ==================== -->
<record id="view_permission_role_form" model="ir.ui.view">
<field name="name">permission.role.form</field>
<field name="model">permission.role</field>
<field name="arch" type="xml">
<form string="权限角色">
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="角色名称"/>
</h1>
</div>
<div class="oe_button_box" name="button_box">
<button name="action_copy_permissions" type="object" string="复制权限" class="btn-secondary"/>
</div>
<group name="basic_info" string="基本信息">
<group>
<field name="code"/>
<field name="role_type"/>
<field name="sequence"/>
<field name="active" widget="boolean_toggle"/>
</group>
<group>
<field name="description" nolabel="1" placeholder="角色描述"/>
</group>
</group>
<notebook>
<page string="权限组">
<field name="group_ids" widget="many2many_tags"/>
<div class="alert alert-info" role="alert">
<i class="fa fa-info-circle"/> 权限组决定用户对模型的操作权限(增删改查)
</div>
</page>
<page string="可见菜单">
<field name="menu_ids">
<list>
<field name="name"/>
<field name="parent_id"/>
<field name="sequence"/>
</list>
</field>
</page>
<page string="用户">
<group>
<field name="user_count"/>
<div class="oe_button_box">
<button name="action_assign_users" type="object" string="分配用户" class="btn-primary"/>
<button name="action_view_users" type="object" string="查看用户" class="btn-secondary"/>
</div>
</group>
<field name="user_ids" widget="many2many_tags"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- ==================== 角色搜索视图 ==================== -->
<record id="view_permission_role_search" model="ir.ui.view">
<field name="name">permission.role.search</field>
<field name="model">permission.role</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="code"/>
<filter name="active" string="启用" domain="[('active', '=', True)]"/>
<filter name="inactive" string="禁用" domain="[('active', '=', False)]"/>
<separator/>
<filter name="group_by_type" string="角色类型" context="{'group_by': 'role_type'}"/>
<filter name="group_by_active" string="状态" context="{'group_by': 'active'}"/>
</search>
</field>
</record>
<!-- ==================== 角色动作 ==================== -->
<record id="act_permission_role" model="ir.actions.act_window">
<field name="name">角色管理</field>
<field name="res_model">permission.role</field>
<field name="view_mode">list,form</field>
</record>
<!-- ==================== 复制权限向导视图 ==================== -->
<record id="view_permission_copy_wizard" model="ir.ui.view">
<field name="name">permission.copy.wizard.form</field>
<field name="model">permission.copy.wizard</field>
<field name="arch" type="xml">
<form string="复制权限">
<sheet>
<group>
<field name="target_role_id" readonly="1"/>
<field name="source_role_id" required="1"/>
<field name="copy_groups"/>
<field name="copy_menus"/>
</group>
<footer>
<button name="action_copy" type="object" string="复制" class="btn-primary"/>
<button string="取消" class="btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
<!-- ==================== 继承用户表单,添加角色选项卡 ==================== -->
<record id="view_res_users_form_inherit" model="ir.ui.view">
<field name="name">res.users.form.inherit</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page string="权限角色" name="permission_role_page">
<group string="角色分配">
<field name="role_ids" widget="many2many_tags"/>
<field name="role_count"/>
<div class="oe_button_box">
<button name="action_view_roles" type="object" string="查看角色" class="btn-secondary"/>
<button name="action_sync_permissions" type="object" string="同步权限" class="btn-primary"/>
</div>
</group>
</page>
</xpath>
</field>
</record>
<!-- 权限管理根菜单 -->
<record id="menu_permission_root" model="ir.ui.menu">
<field name="name">权限管理</field>
<field name="sequence">100</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
</record>
<!-- 角色管理子菜单 -->
<record id="menu_permission_role" model="ir.ui.menu">
<field name="name">角色管理</field>
<field name="parent_id" ref="menu_permission_root"/>
<field name="action" ref="act_permission_role"/>
<field name="sequence">10</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
</record>
</odoo>