# -*- coding: utf-8-*-import itertools
from odoo import models, fields, api, _
from lxml.builder importEfrom itertools import chain, repeat
from lxml import etree
from odoo.tools import partition
def name_boolean_group(id):return'in_group_'+str(id)
def name_selection_groups(ids):return'sel_groups_'+'_'.join(str(it)for it in ids)
def is_boolean_group(name):return name.startswith('in_group_')
def is_selection_groups(name):return name.startswith('sel_groups_')
def is_reified_group(name):returnis_boolean_group(name) or is_selection_groups(name)
def get_boolean_group(name):returnint(name[9:])
def get_selection_groups(name):return[int(v)for v in name[11:].split('_')]
def parse_m2m(commands):"return a list of ids corresponding to a many2many value"
ids =[]for command in commands:ifisinstance(command,(tuple, list)):if command[0]in(1,4):
ids.append(command[1])
elif command[0]==5:
ids =[]
elif command[0]==6:
ids =list(command[2])else:
ids.append(command)return ids
ACCESS_STATES={
u'审批中':[('readonly', True)],
u'审批通过':[('readonly', True)],}classaccess_request(models.Model):
_name ="access.request"
_inherit =['mail.thread','mail.activity.mixin']
_description ='权限申请'
_check_worflow_auto = True
def _default_groups(self):
group_ids = self.env.user.groups_id.ids
return[(4, rec)for rec in group_ids]
def _default_department(self):
department_ids = self.env.user.employee_ids
name =''for rec in department_ids:
name += rec.department_id.name +'|'return name
def _default_job(self):
department_ids = self.env.user.employee_ids
name =''for rec in department_ids:if rec.job_id:
name += rec.job_id.name +'|'return name
name = fields.Char(string='单据编号',default=lambda self: self.env['ir.sequence'].next_by_code(self._name) or 'New')
groups_id = fields.Many2many('res.groups', string='权限组',default=_default_groups, track_visibility='onchange',)
user_id = fields.Many2one('res.users',default=lambda self: self.env.user, string="申请人")
share = fields.Boolean(compute='_compute_share', compute_sudo=True, string='共享用户',)
department_ids = fields.Char(string="所在部门",default=_default_department)
job_ids = fields.Char(string='所在岗位',default=_default_job)
state = fields.Selection([('新建','新建'),('审批中','审批中'),('审批通过','审批通过'),], string="状态",default="新建", track_visibility='onchange',)
@api.depends('groups_id')
def _compute_share(self):for user in self:
user.share = not user.has_group('base.group_user')
category_id = fields.Many2many('res.partner.category', string='标签', index=True)
def action_home(self):
self.write({'state':'新建'})
def button_a1(self, context=None):if not self:
self = self.browse(context.get('active_id'))
#::::::::创作并分配一个任务
activity ={'type':'','line': self,'user_id': self.user_id.id,'summary':'授权申请','note':'请审批授权申请!','id_object':'%s,%s'%(self._name, self.id),'ref_menu':'Base.menu_0', # 顶部菜单
}
self.env.user.add_mail_activity(activity)if self.state =='审批通过':
self.user_id.groups_id =[(6,0, self.groups_id.ids)]
@api.model
def create(self, values):
values, body = self._remove_reified_groups(values)
user =super(access_request, self).create(values)
user.message_post(body=body, message_type='notification')
group_multi_company = self.env.ref('base.group_multi_company', False)if group_multi_company and 'company_ids'in values:iflen(user.company_ids)<=1 and user.id in group_multi_company.users.ids:
user.write({'groups_id':[(3, group_multi_company.id)]})
elif len(user.company_ids)>1 and user.id not in group_multi_company.users.ids:
user.write({'groups_id':[(4, group_multi_company.id)]})return user
def write(self, values):
values, body = self._remove_reified_groups(values)
res =super(access_request, self).write(values)
self.message_post(body=body, message_type='notification')
group_multi_company = self.env.ref('base.group_multi_company', False)if group_multi_company and 'company_ids'in values:for user in self:iflen(user.company_ids)<=1 and user.id in group_multi_company.users.ids:
user.write({'groups_id':[(3, group_multi_company.id)]})
elif len(user.company_ids)>1 and user.id not in group_multi_company.users.ids:
user.write({'groups_id':[(4, group_multi_company.id)]})return res
@api.model
def new(self, values={}, origin=None, ref=None):
values,body = self._remove_reified_groups(values)
user =super().new(values=values, origin=origin, ref=ref)
group_multi_company = self.env.ref('base.group_multi_company', False)if group_multi_company and 'company_ids'in values:iflen(user.company_ids)<=1 and user.id in group_multi_company.users.ids:
user.update({'groups_id':[(3, group_multi_company.id)]})
elif len(user.company_ids)>1 and user.id not in group_multi_company.users.ids:
user.update({'groups_id':[(4, group_multi_company.id)]})return user
def _remove_reified_groups(self, values):""" return `values` without reified group fields """
add, rem =[],[]
values1 ={}for key, val in values.items():ifis_boolean_group(key):(add if val else rem).append(get_boolean_group(key))
elif is_selection_groups(key):
rem +=get_selection_groups(key)if val:
add.append(val)else:
values1[key]= val
body =''if'groups_id' not in values and(add or rem):
# remove group ids in`rem` and add group ids in`add`
values1['groups_id']=list(itertools.chain(zip(repeat(3), rem),zip(repeat(4), add)))if add !=[]:for rec in add:
group_name = self.env['res.groups'].sudo().browse(rec)
body +='修改权限组:%s, 为:%s<br>'%(group_name.display_name, group_name.name)else:for rec in rem:
group_id = self.env['res.groups'].sudo().browse(rec)
body +='修改权限组:%s, 为:%s<br>'%(group_id.display_name,'False')
# if body=='':
# body=None
# print(body)return values1, body
@api.model
def default_get(self, fields):
group_fields, fields =partition(is_reified_group, fields)
fields1 =(fields +['groups_id'])if group_fields else fields
values =super(access_request, self).default_get(fields1)
self._add_reified_groups(group_fields, values)return values
def read(self, fields=None, load='_classic_read'):
# determine whether reified groups fields are required, and which ones
fields1 = fields or list(self.fields_get())
group_fields, other_fields =partition(is_reified_group, fields1)
# read regular fields(other_fields); add 'groups_id'if necessary
drop_groups_id = False
if group_fields and fields:if'groups_id' not in other_fields:
other_fields.append('groups_id')
drop_groups_id = True
else:
other_fields = fields
res =super(access_request, self).read(other_fields, load=load)
# post-process result to add reified group fields
if group_fields:for values in res:
self._add_reified_groups(group_fields, values)if drop_groups_id:
values.pop('groups_id', None)return res
def _add_reified_groups(self, fields, values):""" add the given reified group fields into `values` """
gids =set(parse_m2m(values.get('groups_id') or []))for f in fields:ifis_boolean_group(f):
values[f]=get_boolean_group(f)in gids
elif is_selection_groups(f):
selected =[gid for gid inget_selection_groups(f)if gid in gids]
# if'Internal User' is in the group,this is the "User Type" group
# and we need to show 'Internal User' selected, not Public/Portal.if self.env.ref('base.group_user').id in selected:
values[f]= self.env.ref('base.group_user').id
else:
values[f]= selected and selected[-1] or False
@api.model
def fields_get(self, allfields=None, attributes=None):
res =super(access_request, self).fields_get(allfields, attributes=attributes)
# add reified groups fields
for app, kind, gs, category_name in self.env['res.groups'].sudo().get_groups_by_application():if kind =='selection':
# 'User Type' should not be 'False'.A user is either 'employee','portal' or 'public'(required).
selection_vals =[(False,'')]if app.xml_id =='base.module_category_user_type':
selection_vals =[]
field_name =name_selection_groups(gs.ids)if allfields and field_name not in allfields:continue
# selection group field
tips =['%s: %s'%(g.name, g.comment)for g in gs if g.comment]
res[field_name]={'type':'selection','string': app.name or _('Other'),'selection': selection_vals +[(g.id, g.name)for g in gs],'help':'\n'.join(tips),'exportable': False,'selectable': False,'track_visibility':'onchange','states':ACCESS_STATES}else:
# boolean group fields
for g in gs:
field_name =name_boolean_group(g.id)if allfields and field_name not in allfields:continue
res[field_name]={'type':'boolean','string': g.name,'help': g.comment,'exportable': False,'selectable': False,'track_visibility':'onchange','states':ACCESS_STATES}return res
def action_pass(self):if self.groups_id:
self.user_id.groups_id =[(4, rec)for rec in self.groups_id.ids]classres_groups(models.Model):
_inherit ='res.groups'
@api.model
def create(self, values):
user =super(res_groups, self).create(values)
self._update_user_groups_view1()return user
def write(self, values):
res =super(res_groups, self).write(values)
self._update_user_groups_view1()return res
def unlink(self):
res =super(res_groups, self).unlink()
self._update_user_groups_view1()return res
@api.model
def _update_user_groups_view1(self):""" Modify the view with xmlid ``base.user_groups_view``, which inherits
the user form view, and introduces the reified group fields."""
# remove the language to avoid translations, it will be handled at the view level
self = self.with_context(lang=None)
# We have to try-catchthis, because at first init the view does not
# exist but we are already creating some basic groups.
view = self.env.ref('access_request.access_request_user_groups_view', raise_if_not_found=False)if view and view.exists() and view._name =='ir.ui.view':
group_no_one = view.env.ref('base.group_no_one')
group_employee = view.env.ref('base.group_user')
xml1, xml2, xml3 =[],[],[]
xml_by_category ={}
xml1.append(E.separator(string='User Type', colspan="2", groups='base.group_no_one'))
user_type_field_name =''
user_type_readonly =str({})
sorted_tuples =sorted(self.get_groups_by_application(),
key=lambda t: t[0].xml_id !='base.module_category_user_type')for app, kind, gs, category_name in sorted_tuples: # we process the user type first
attrs ={}
# hide groups in categories 'Hidden' and 'Extra'(except for group_no_one)if app.xml_id in self._get_hidden_extra_categories():
attrs['groups']='base.group_no_one'
# User type(employee, portal or public) is a separated group. This is the only 'selection'
# group of res.groups without implied groups(with each other).if app.xml_id =='base.module_category_user_type':
# application name with a selection field
field_name =name_selection_groups(gs.ids)
user_type_field_name = field_name
user_type_readonly =str({'readonly':['|',(user_type_field_name,'!=', group_employee.id),('state','!=','新建')]})
attrs['widget']='radio'
attrs['groups']='base.group_no_one'
xml1.append(E.field(name=field_name,**attrs, track_visibility='onchange'))
xml1.append(E.newline())
elif kind =='selection':
# application name with a selection field
field_name =name_selection_groups(gs.ids)
attrs['attrs']= user_type_readonly
if category_name not in xml_by_category:
xml_by_category[category_name]=[]
xml_by_category[category_name].append(E.newline())
xml_by_category[category_name].append(E.field(name=field_name,**attrs, track_visibility='onchange'))
xml_by_category[category_name].append(E.newline())else:
# application separator with boolean fields
app_name = app.name or 'Other'
xml3.append(E.separator(string=app_name, colspan="4",**attrs, track_visibility='onchange'))
attrs['attrs']= user_type_readonly
for g in gs:
field_name =name_boolean_group(g.id)if g == group_no_one:
# make the group_no_one invisible in the form view
xml3.append(E.field(name=field_name, invisible="1",**attrs, track_visibility='onchange'))else:
xml3.append(E.field(name=field_name,**attrs, track_visibility='onchange'))
xml3.append({'class':"o_label_nowrap"})if user_type_field_name:
user_type_attrs ={'invisible':[(user_type_field_name,'!=', group_employee.id)]}else:
user_type_attrs ={}for xml_cat insorted(xml_by_category.keys(), key=lambda it: it[0]):
xml_cat_name = xml_cat[1]
master_category_name =(_(xml_cat_name))
xml2.append(E.group(*(xml_by_category[xml_cat]), col="2", string=master_category_name))
xml =E.field(E.group(*(xml1), col="2"),E.group(*(xml2), col="2", attrs=str(user_type_attrs)),E.group(*(xml3), col="4", attrs=str(user_type_attrs)), name="groups_id", position="replace")
xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS"))
xml_content = etree.tostring(xml, pretty_print=True, encoding="unicode")
new_context =dict(view._context)
new_context.pop('install_filename', None) # don't set arch_fs forthis computed view
new_context['lang']= None
view.with_context(new_context).write({'arch': xml_content})classmodule_category(models.Model):
_inherit ="ir.module.category"
def write(self, values):
res =super().write(values)if"name"in values:
self.env["res.groups"]._update_user_groups_view1()return res
def unlink(self):
res =super().unlink()
self.env["res.groups"]._update_user_groups_view1()return res
# 视图
<?xml version="1.0" encoding="utf-8"?><odoo><data noupdate="1"><record id="access_request_user_groups_view" model="ir.ui.view"><field name="name">access.request.user.groups.view</field><field name="model">access.request</field><field name="inherit_id" ref="access_request_form_view"/><field name="arch" type="xml"><!-- dummy, will be modified by groups --><field name="groups_id" position="after"/></field></record></data></odoo>