dev_haoran #9
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -14,6 +14,7 @@ class CourseHomework(models.Model):
|
||||
requirement = fields.Html(string='作业要求')
|
||||
course_id = fields.Many2one('learning.course', string='所属课程', required=True, ondelete='cascade')
|
||||
chapter_id = fields.Many2one('course.chapter', string='关联章节')
|
||||
|
||||
total_score = fields.Float(string='作业总分', default=100.0, required=True)
|
||||
publish_time = fields.Datetime(string='发布时间')
|
||||
deadline = fields.Datetime(string='提交截止时间', required=True)
|
||||
@ -28,12 +29,13 @@ class CourseHomework(models.Model):
|
||||
total_students = fields.Integer(string='应提交人数', compute='_compute_submit_stats')
|
||||
submitted_count = fields.Integer(string='已提交人数', compute='_compute_submit_stats')
|
||||
late_count = fields.Integer(string='逾期提交人数', compute='_compute_submit_stats')
|
||||
course_score_item_id=fields.Many2one('course.score.item',string='作业类型')
|
||||
unsubmitted_count = fields.Integer(string='未提交人数', compute='_compute_submit_stats')
|
||||
# 添加教学班关联
|
||||
teaching_class_id = fields.Many2one('course.teaching_class', string='教学班',
|
||||
ondelete='cascade', domain="[('course_id', '=', course_id)]")
|
||||
submit_ids = fields.One2many('course.homework.submit', 'homework_id', string='提交记录')
|
||||
score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项')
|
||||
score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项目')
|
||||
|
||||
# ====================== 新增:自动计算提交通道是否开启 ======================
|
||||
@api.depends('state', 'deadline')
|
||||
@ -80,37 +82,61 @@ class CourseHomework(models.Model):
|
||||
self.publish_time = fields.Datetime.now()
|
||||
# 1. 批量为本班学生生成空白未提交记录
|
||||
self._auto_create_empty_submit()
|
||||
# 新增:自动生成对应作业的学生成绩明细
|
||||
self._auto_create_homework_score_detail()
|
||||
# 2. 推送作业通知给学生
|
||||
self._send_homework_notice_to_students()
|
||||
|
||||
def _send_homework_notice_to_students(self):
|
||||
"""发布作业后推送消息给教学班学生"""
|
||||
def _auto_create_homework_score_detail(self):
|
||||
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
|
||||
# 获取当前教学班所有已选课学生
|
||||
enrolls = self.env['course.teaching_class.enrollment'].search([
|
||||
score_item = self.course_score_item_id
|
||||
|
||||
# 获取教学班在册学生
|
||||
enroll_records = 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:
|
||||
student_ids = enroll_records.mapped('student_id').ids
|
||||
if not student_ids:
|
||||
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_comment'
|
||||
)
|
||||
|
||||
# 查询当前作业+当前分项已存在明细
|
||||
exist_details = self.env['course.student.score.detail'].search([
|
||||
('course_id', '=', self.course_id.id),
|
||||
('score_item_id', '=', score_item.id),
|
||||
('homework_id', '=', self.id),
|
||||
('student_id', 'in', student_ids)
|
||||
])
|
||||
exist_student_ids = exist_details.mapped('student_id').ids
|
||||
target_student_ids = [sid for sid in student_ids if sid not in exist_student_ids]
|
||||
if not target_student_ids:
|
||||
return
|
||||
|
||||
# 批量查询学生总成绩,避免循环查库
|
||||
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):
|
||||
"""仅发布时执行:自动给教学班所有已选课学生生成空白未提交记录"""
|
||||
@ -138,11 +164,41 @@ class CourseHomework(models.Model):
|
||||
batch_vals.append({
|
||||
'homework_id': self.id,
|
||||
'student_id': stu_id,
|
||||
'course_score_item_id':self.course_score_item_id.id,
|
||||
'state': 'unsubmit' # 默认未提交
|
||||
})
|
||||
# 批量创建空白提交记录
|
||||
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):
|
||||
self.state = 'draft'
|
||||
@ -269,4 +325,15 @@ class CourseHomework(models.Model):
|
||||
def create(self, vals):
|
||||
homework_record = super().create(vals)
|
||||
# 草稿状态不执行自动生成提交记录,移到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
|
||||
@ -28,6 +28,7 @@ class CourseHomeworkSubmit(models.Model):
|
||||
student_no = fields.Char(related="student_id.stu_num", string="学号", readonly=True)
|
||||
student_class_id = fields.Many2one(related="student_id.class_id", string="班级", readonly=True)
|
||||
student_major_id = fields.Many2one(related="student_id.major_id", string="专业", readonly=True)
|
||||
course_score_item_id = fields.Many2one('course.score.item', string='作业类型')
|
||||
student_phone = fields.Char(related="student_id.stu_phone", string="手机号", readonly=True)
|
||||
submit_file = fields.Binary(string='提交文件', attachment=True)
|
||||
submit_filename = fields.Char(string='文件名')
|
||||
@ -92,6 +93,7 @@ class CourseHomeworkSubmit(models.Model):
|
||||
def create(self, vals):
|
||||
submit_rec = super().create(vals)
|
||||
self._check_submit_change_state(submit_rec)
|
||||
|
||||
return submit_rec
|
||||
|
||||
def write(self, vals):
|
||||
|
||||
@ -15,7 +15,7 @@ class CourseScoreItem(models.Model):
|
||||
course_code = fields.Char(related="course_id.code", string="课程代码", readonly=True)
|
||||
course_credit = fields.Float(related="course_id.credit", string="学分", readonly=True)
|
||||
course_exam_type = fields.Selection(related="course_id.exam_type", string="考核方式", readonly=True)
|
||||
homework_id = fields.Many2one('course.homework', string='作业', required=True, ondelete='cascade')
|
||||
homework_id = fields.Many2one('course.homework', string='作业', ondelete='cascade')
|
||||
sequence = fields.Integer(string="排序", default=10)
|
||||
score_type = fields.Selection([
|
||||
('homework', '作业'),
|
||||
@ -30,7 +30,7 @@ class CourseScoreItem(models.Model):
|
||||
full_score = fields.Float(string='满分', default=100.0)
|
||||
is_active = fields.Boolean(string='是否启用', default=True)
|
||||
homework_ids = fields.Many2many('course.homework', string='关联作业')
|
||||
|
||||
course_student_score=fields.Many2one('course.student.score',string='学生成绩')
|
||||
# 统计字段
|
||||
student_count = fields.Integer(string='学生人数', compute='_compute_stats')
|
||||
avg_score = fields.Float(string='平均分', compute='_compute_stats')
|
||||
|
||||
@ -24,6 +24,7 @@ class CourseStudentScore(models.Model):
|
||||
student_class_id = fields.Many2one( string="班级", related="student_id.class_id", readonly=True)
|
||||
student_major_id = fields.Many2one( string="专业", related="student_id.major_id", readonly=True)
|
||||
stu_name=fields.Char(related='student_id.stu_name',required=True, ondelete='cascade',string='学生姓名')
|
||||
score_item_ids=fields.One2many('course.score.item','course_student_score',string='成绩类型')
|
||||
score_ids = fields.One2many('course.student.score.detail', 'student_score_id', string='成绩明细')
|
||||
teaching_class_id=fields.Many2one('course.teaching_class',string='教学班')
|
||||
# 教学班相关只读关联字段
|
||||
@ -32,7 +33,7 @@ class CourseStudentScore(models.Model):
|
||||
teaching_class_main_teacher = fields.Many2one('hr.employee', string="主讲教师",
|
||||
related="teaching_class_id.main_teacher_id", readonly=True)
|
||||
total_score = fields.Float(string='综合成绩', compute='_compute_total_score', store=True)
|
||||
|
||||
course_score_item_id = fields.Many2one('course.score.item', string='成绩分项')
|
||||
grade_level = fields.Selection([
|
||||
('A', '优秀(A)'),
|
||||
('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_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='该项目实际得分')
|
||||
|
||||
|
||||
@ -215,12 +215,32 @@ class CourseTeachingClassEnrollment(models.Model):
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
"""新增选课记录校验人数上限"""
|
||||
"""新增选课记录校验人数上限 + 自动生成学生总成绩记录"""
|
||||
res = super().create(vals)
|
||||
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'))
|
||||
if cls.max_students and valid_num > cls.max_students:
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="course_id"/>
|
||||
<field name="chapter_id"/>
|
||||
<!-- <field name="chapter_id"/>-->
|
||||
<field name="total_score"/>
|
||||
<field name="deadline"/>
|
||||
<field name="submitted_count" sum="合计"/>
|
||||
@ -38,9 +38,10 @@
|
||||
</div>
|
||||
<group name="basic_info" string="基本信息">
|
||||
<group>
|
||||
<field name="teaching_class_id"/>
|
||||
<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="state" widget="statusbar"/>
|
||||
</group>
|
||||
|
||||
@ -45,17 +45,11 @@
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="作业明细">
|
||||
<field name="score_ids" view_id="list_course_student_score_score_ids_om"/>
|
||||
</page>
|
||||
<page string="成绩明细">
|
||||
<field name="score_ids">
|
||||
<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>
|
||||
<field name="score_item_ids" view_id="list_course_student_score_item_om"/>
|
||||
</page>
|
||||
<page string="课程信息">
|
||||
<group string="课程详情">
|
||||
@ -89,6 +83,74 @@
|
||||
</field>
|
||||
</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">
|
||||
@ -148,15 +210,17 @@
|
||||
</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 id="view_course_student_score_detail_search" model="ir.ui.view">
|
||||
<field name="name">view.course.student.score.detail.search</field>
|
||||
<field name="model">course.student.score.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<searchpanel>
|
||||
<field name="score_item_id" select="multi"/>
|
||||
</searchpanel>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ==================== 学生成绩明细动作 ==================== -->
|
||||
<record id="act_course_student_score_detail" model="ir.actions.act_window">
|
||||
<field name="name">成绩明细</field>
|
||||
@ -165,34 +229,6 @@
|
||||
|
||||
</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">
|
||||
<field name="name">成绩明细</field>
|
||||
|
||||
@ -36,6 +36,8 @@
|
||||
<group name="basic_info" string="基本信息">
|
||||
<group>
|
||||
<field name="homework_id" options="{'no_create': True}"/>
|
||||
<field name="course_score_item_id" />
|
||||
|
||||
<field name="student_id" options="{'no_create': True}"/>
|
||||
<field name="submit_time" readonly="1"/>
|
||||
<field name="is_late" widget="boolean_toggle" readonly="1"/>
|
||||
|
||||
@ -104,18 +104,18 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="view_course_syllabus_list" model="ir.ui.view">-->
|
||||
<!-- <field name="name">course.syllabus.list</field>-->
|
||||
<!-- <field name="model">course.syllabus</field>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <list>-->
|
||||
<!-- <field name="title"/>-->
|
||||
<!-- <field name="syllabus_type" widget="badge"/>-->
|
||||
<!-- <field name="version"/>-->
|
||||
<!-- <field name="is_published" widget="boolean_toggle"/>-->
|
||||
<!-- </list>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
<record id="view_course_syllabus_list" model="ir.ui.view">
|
||||
<field name="name">course.syllabus.list</field>
|
||||
<field name="model">course.syllabus</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="title"/>
|
||||
<field name="syllabus_type" widget="badge"/>
|
||||
<field name="version"/>
|
||||
<field name="is_published" widget="boolean_toggle"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_course_chapter_resource_list" model="ir.ui.view">
|
||||
<field name="name">course.chapter.resource.list</field>
|
||||
|
||||
1
permission_manager/__init__.py
Normal file
1
permission_manager/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import models
|
||||
12
permission_manager/__manifest__.py
Normal file
12
permission_manager/__manifest__.py
Normal 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,
|
||||
}
|
||||
BIN
permission_manager/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
permission_manager/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
1
permission_manager/models/__init__.py
Normal file
1
permission_manager/models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import permission_role
|
||||
BIN
permission_manager/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
permission_manager/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
134
permission_manager/models/permission_role.py
Normal file
134
permission_manager/models/permission_role.py
Normal 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'}
|
||||
4
permission_manager/security/ir.model.access.csv
Normal file
4
permission_manager/security/ir.model.access.csv
Normal 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
|
||||
|
166
permission_manager/views/role_views.xml
Normal file
166
permission_manager/views/role_views.xml
Normal 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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user