[ADD] share module:

The goal is to implement a generic sharing mechanism, where user of OpenERP
can share data from OpenERP to their colleagues, customers, or friends.
The system will work by creating new users and groups on the fly, and by
combining the appropriate access rights and ir.rules to ensure that the /shared
users/ will only have access to the correct data.

bzr revid: hmo@tinyerp.com-20100608114049-5q4q4trmbkizppxt
This commit is contained in:
Harry (OpenERP) 2010-06-08 17:10:49 +05:30
parent 6cb4f3175b
commit f4633e226c
7 changed files with 689 additions and 0 deletions

22
addons/share/__init__.py Normal file
View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import share
import wizard

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
"name" : "Share Management",
"version" : "1.1",
"depends" : ["base"],
"author" : "Tiny",
"category": 'Generic Modules',
"description": """The goal is to implement a generic sharing mechanism, where user of OpenERP
can share data from OpenERP to their colleagues, customers, or friends.
The system will work by creating new users and groups on the fly, and by
combining the appropriate access rights and ir.rules to ensure that the /shared
users/ will only have access to the correct data.
""",
'website': 'http://www.openerp.com',
'init_xml': [],
'update_xml': [
'share_view.xml',
'wizard/share_wizard_view.xml'
],
'installable': True,
'active': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

36
addons/share/share.py Normal file
View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields, osv, orm
class res_groups(osv.osv):
_name = "res.groups"
_inherit = 'res.groups'
_columns = {
'share': fields.boolean('Share')
}
res_groups()
class res_users(osv.osv):
_name = 'res.users'
_inherit = 'res.users'
_columns = {
'share': fields.boolean('Share')
}
res_users()

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_groups_form" model="ir.ui.view">
<field name="name">res.groups.form</field>
<field name="model">res.groups</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_groups_form"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="share"/>
</field>
</field>
</record>
<record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.form</field>
<field name="model">res.users</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<field name="password" position="after">
<field name="share"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import share_wizard

View File

@ -0,0 +1,430 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
from tools.translate import _
import tools
def _generate_random_number():
import random
RANDOM_PASS_CHARACTERS = [chr(x) for x in range(48,58) + range(97,123) + range(65,91)]
RANDOM_PASS_CHARACTERS.remove('l') #lowercase l, easily mistaken as one or capital i
RANDOM_PASS_CHARACTERS.remove('I') #uppercase i, easily mistaken as one or lowercase l
RANDOM_PASS_CHARACTERS.remove('O') #uppercase o, mistaken with zero
RANDOM_PASS_CHARACTERS.remove('o') #lowercase o, mistaken with zero
RANDOM_PASS_CHARACTERS.remove('0') #zero, mistaken with o-letter
def generate_random_pass():
pass_chars = RANDOM_PASS_CHARACTERS[:]
random.shuffle(pass_chars)
return ''.join(pass_chars[0:10])
return generate_random_pass()
class share_create(osv.osv_memory):
_name = 'share.create'
_description = 'Create share'
def _access(self, cr, uid, ids, field_name, arg, context=None):
if context is None:
context = {}
res = {}
action_id = context.get('action_id', False)
access_obj = self.pool.get('ir.model.access')
action_obj = self.pool.get('ir.actions.act_window')
model_obj = self.pool.get('ir.model')
user_obj = self.pool.get('res.users')
current_user = user_obj.browse(cr, uid, uid)
access_ids = []
if action_id:
action = action_obj.browse(cr, uid, action_id, context=context)
active_model_ids = model_obj.search(cr, uid, [('model','=',action.res_model)])
active_model_id = active_model_ids and active_model_ids[0] or False
access_ids = access_obj.search(cr, uid, [
('group_id','in',map(lambda x:x.id, current_user.groups_id)),
('model_id','',active_model_id)])
for rec_id in ids:
write_access = False
read_access = False
for access in access_obj.browse(cr, uid, access_ids, context=context):
if access.perm_write:
write_access = True
if access.perm_read:
read_access = True
res[rec_id]['write_access'] = write_access
res[rec_id]['read_access'] = read_access
return res
_columns = {
'action_id': fields.many2one('ir.actions.act_window', 'Action', required=True),
'domain': fields.char('Domain', size=64),
'user_type': fields.selection( [ ('existing','Existing'),('new','New')],'User Type'),
'user_ids': fields.many2many('res.users', 'share_user_rel', 'share_id','user_id', 'Share Users'),
'new_user': fields.text("New user"),
'access_mode': fields.selection( [ ('readonly','READ ONLY'),('readwrite','READ & WRITE')],'Access Mode'),
'write_access': fields.function(_access, method=True, string='Write Access',type='boolean', multi='write_access'),
'read_access': fields.function(_access, method=True, string='Write Access',type='boolean', multi='read_access'),
}
_defaults = {
'user_type' : 'existing',
'domain': '[]',
'access_mode': 'readonly'
}
def default_get(self, cr, uid, fields, context=None):
"""
To get default values for the object.
"""
res = super(share_create, self).default_get(cr, uid, fields, context=context)
if not context:
context={}
action_id = context.get('action_id', False)
domain = context.get('domain', '[]')
if 'action_id' in fields:
res['action_id'] = action_id
if 'domain' in fields:
res['domain'] = domain
return res
def do_step_1(self, cr, uid, ids, context=None):
"""
This action to excute step 1
"""
if not context:
context = {}
data_obj = self.pool.get('ir.model.data')
step1_form_view = data_obj._get_id(cr, uid, 'share', 'share_step1_form')
if step1_form_view:
step1_form_view_id = data_obj.browse(cr, uid, step1_form_view, context=context).res_id
step1_id = False
for this in self.browse(cr, uid, ids, context=context):
vals ={
'domain': this.domain,
'action_id': this.action_id and this.action_id.id or False,
}
context.update(vals)
value = {
'name': _('Step:2 Sharing Wizard'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'share.create',
'view_id': False,
'views': [(step1_form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')],
'type': 'ir.actions.act_window',
'context': context,
'target': 'new'
}
return value
def do_step_2(self, cr, uid, ids, context=None):
"""
This action to excute step 2
"""
if not context:
context = {}
data_obj = self.pool.get('ir.model.data')
step2_form_view = data_obj._get_id(cr, uid, 'share', 'share_step2_form')
if step2_form_view:
step2_form_view_id = data_obj.browse(cr, uid, step2_form_view, context=context).res_id
step1_id = False
for this in self.browse(cr, uid, ids, context=context):
vals ={
'user_type': this.user_type,
'existing_user_ids': map(lambda x:x.id, this.user_ids),
'new_user': this.new_user,
'domain': this.domain,
'access_mode': this.access_mode,
'action_id': this.action_id and this.action_id.id or False
}
context.update(vals)
value = {
'name': _('Step:3 Sharing Wizard'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'share.create',
'view_id': False,
'views': [(step2_form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')],
'type': 'ir.actions.act_window',
'context': context,
'target': 'new'
}
return value
def do_step_3(self, cr, uid, ids, context=None):
"""
This action to excute step 3
"""
if not context:
context = {}
group_obj = self.pool.get('res.groups')
user_obj = self.pool.get('res.users')
fields_obj = self.pool.get('ir.model.fields')
model_access_obj = self.pool.get('ir.model.access')
model_obj = self.pool.get('ir.model')
rule_obj = self.pool.get('ir.rule')
action_obj = self.pool.get('ir.actions.act_window')
new_users = context.get('new_user', False)
action_id = context.get('action_id', False)
user_type = context.get('user_type', False)
access_mode = context.get('access_mode', False)
action = action_obj.browse(cr, uid, action_id, context=context)
active_model = action.res_model
active_id = False #TODO: Pass record id of res_model of action
existing_user_ids = context.get('existing_user_ids', False)
domain = eval(context.get('domain', '[]'))
# Create Share Group
share_group_name = '%s: %s' %('Sharing', active_model)
group_ids = group_obj.search(cr, uid, [('name','=',share_group_name)])
group_id = group_ids and group_ids[0] or False
if not group_id:
group_id = group_obj.create(cr, uid, {'name': share_group_name, 'share': True})
else:
group = group_obj.browse(cr, uid, group_id, context=context)
if not group.share:
raise osv.except_osv(_('Error'), _("Share Group is exits without sharing !"))
# Create new user
current_user = user_obj.browse(cr, uid, uid)
user_ids = []
if user_type == 'new' and new_users:
for new_user in new_users.split('\n'):
password = _generate_random_number()
user_id = user_obj.create(cr, uid, {
'login': new_user,
'password': password,
'name': new_user,
'user_email': new_user,
'groups_id': [(6,0,[group_id])],
'action_id': action_id,
'share': True,
'company_id': current_user.company_id and current_user.company_id.id})
user_ids.append(user_id)
context['new_user_ids'] = user_ids
# Modify existing user
if user_type == 'existing':
user_obj.write(cr, uid, existing_user_ids , {
'groups_id': [(4,group_id)],
'action_id': action_id
})
#ACCESS RIGHTS / IR.RULES COMPUTATION
#TODO: TO Resolve Recurrent Problems
active_model_ids = model_obj.search(cr, uid, [('model','=',active_model)])
active_model_id = active_model_ids and active_model_ids[0] or False
def _get_relation(model_id, ttypes, new_obj=[]):
obj = []
models = map(lambda x:x[1].model, new_obj)
field_ids = fields_obj.search(cr, uid, [('model_id','=',model_id),('ttype','in', ttypes)])
for field in fields_obj.browse(cr, uid, field_ids, context=context):
if field.relation not in models:
relation_model_ids = model_obj.search(cr, uid, [('model','=',field.relation)])
relation_model_id = relation_model_ids and relation_model_ids[0] or False
relation_model = model_obj.browse(cr, uid, relation_model_id, context=context)
obj.append((field.relation_field, relation_model))
if relation_model_id != model_id and field.ttype in ['one2many', 'many2many']:
obj += _get_relation(relation_model_id, [field.ttype], obj)
return obj
obj0 = model_obj.browse(cr, uid, active_model_id, context=context)
obj1 = _get_relation(active_model_id, ['one2many'])
obj2 = _get_relation(active_model_id, ['one2many', 'many2many'])
obj3 = _get_relation(active_model_id, ['many2one'])
for rel_field, model in obj1:
obj3 += _get_relation(model.id, ['many2one'])
if access_mode == 'readonly':
for rel_field, model in obj1+obj2+obj3:
model_access_obj.create(cr, uid, {
'name': 'Read Access of group %s on %s model'%(share_group_name, model.name),
'model_id' : model.id,
'group_id' : group_id,
'perm_read' : True
})
if access_mode == 'readwrite':
for rel_field, model in obj1+obj2+obj3:
model_access_obj.create(cr, uid, {
'name': 'Read Access of group %s on %s model'%(share_group_name, model.name),
'model_id' : model.id,
'group_id' : group_id,
'perm_read' : True,
'perm_write' : True
})
for rel_field, model in obj2+obj3:
model_access_obj.create(cr, uid, {
'name': 'Read Access of group %s on %s model'%(share_group_name, model.name),
'model_id' : model.id,
'group_id' : group_id,
'perm_read' : True
})
rule_obj.create(cr, uid, {
'name': '%s-%s'%(share_group_name, obj0.model),
'model_id': obj0.id,
'domain': domain,
'group_ids': [(6,0,[group_id])]
})
for rel_field, model in obj1:
obj1_domain = []
for opr1, opt, opr2 in domain:
new_opr1 = '%s.%s'%(rel_field, opr1)
obj1_domain.append((new_opr1, opt, opr2))
rule_obj.create(cr, uid, {
'name': '%s-%s'%(share_group_name, model.model),
'model_id': model.id,
'domain': obj1_domain,
'groups': [(6,0,[group_id])]
})
# TODO:
# And on OBJ0, OBJ1, OBJ2, OBJ3: add all rules from groups of the user
# that is sharing in the many2many of the rules on the new group
# (rule must be copied instead of adding it if it contains a reference to uid
# or user.xxx so it can be replaced correctly)
context['share_model'] = active_model
context['share_rec_id'] = active_id
data_obj = self.pool.get('ir.model.data')
form_view = data_obj._get_id(cr, uid, 'share', 'share_result_form')
form_view_id = False
if form_view:
form_view_id = data_obj.browse(cr, uid, form_view, context=context).res_id
value = {
'name': _('Step:4 Share Users Detail'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'share.result',
'view_id': False,
'views': [(form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')],
'type': 'ir.actions.act_window',
'context': context,
'target': 'new'
}
return value
share_create()
class share_result(osv.osv_memory):
_name = "share.result"
_columns = {
'users': fields.text("Users", readonly=True),
}
def do_send_email(self, cr, uid, ids, context=None):
user_obj = self.pool.get('res.users')
if not context:
context={}
existing_user_ids = context.get('existing_user_ids', [])
new_user_ids = context.get('new_user_ids', [])
share_url = tools.config.get('share_root_url', False)
user = user_obj.browse(cr, uid, uid, context=context)
for share_user in user_obj.browse(cr, uid, new_user_ids+existing_user_ids, context=context):
email_to = share_user.user_email
subject = '%s wants to share private data with you' %(user.name)
body = """
Dear,
%s wants to share private data from OpenERP with you!
"""%(user.name)
if share_url:
body += """
To view it, you can access the following URL:
%s
"""%(user.name, share_url)
if share_user.id in new_user_ids:
body += """
You may use the following login and password to get access to this
protected area:
login: %s
password: %s
"""%(user.login, user.password)
elif share_user.id in existing_user_ids:
body += """
You may use your existing login and password to get access to this
additional data. As a reminder, your login is %s.
"""%(user.name)
flag = tools.email_send(
user.user_email,
email_to,
subject,
body
)
return flag
def default_get(self, cr, uid, fields, context=None):
"""
To get default values for the object.
"""
res = super(share_result, self).default_get(cr, uid, fields, context=context)
user_obj = self.pool.get('res.users')
if not context:
context={}
existing_user_ids = context.get('existing_user_ids', [])
new_user_ids = context.get('new_user_ids', [])
share_url = tools.config.get('share_root_url', False)
if 'users' in fields:
users = []
for user in user_obj.browse(cr, uid, new_user_ids):
txt = 'Login: %s Password: %s' %(user.login, user.password)
if share_url:
txt += ' Share URL: %s' %(share_url)
users.append(txt)
for user in user_obj.browse(cr, uid, existing_user_ids):
txt = 'Login: %s' %(user.login)
if share_url:
txt += ' Share URL: %s' %(share_url)
users.append(txt)
res['users'] = '\n'.join(users)
return res
share_result()

View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="share_step0_form" model="ir.ui.view">
<field name="name">share.step0.form</field>
<field name="model">share.create</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Share">
<separator colspan="4" string="Step 1: Chose the action and the additional domain"/>
<field name="action_id"/>
<field name="domain"/>
<separator colspan="4"/>
<group col="2" colspan="4">
<button special="cancel" string="Cancel" icon='gtk-cancel'/>
<button name="do_step_1" string="Next" colspan="1" type="object" icon="gtk-go-forward"/>
</group>
</form>
</field>
</record>
<record id="share_step1_form" model="ir.ui.view">
<field name="name">share.step1.form</field>
<field name="model">share.create</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Share">
<separator colspan="4" string="Step 2: Chose the User type"/>
<field name="user_type"/>
<newline/>
<group colspan="4" attrs="{'invisible':[('user_type','!=','existing')]}">
<separator colspan="4" string="Exising Users"/>
<field colspan="4" nolabel="1" name="user_ids" domain="[('share_user','=',True)]"/>
</group>
<group colspan="4" attrs="{'invisible':[('user_type','!=','new')]}">
<separator colspan="4" string="New Users"/>
<field colspan="4" nolabel="1" name="new_user"/>
</group>
<separator colspan="4"/>
<group col="3" colspan="4">
<button special="cancel" string="Cancel" icon='gtk-cancel'/>
<button name="do_step_1" string="Previous" colspan="1" type="object" icon="gtk-go-back"/>
<button name="do_step_2" string="Next" colspan="1" type="object" icon="gtk-go-forward"/>
</group>
</form>
</field>
</record>
<record id="share_step2_form" model="ir.ui.view">
<field name="name">share.step2.form</field>
<field name="model">share.create</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Share">
<separator colspan="4" string="Step 3: Chose Access Mode"/>
<group colspan="4" attrs="{'invisible':[('read_access','!=',True)]}">
<field name="access_mode" attrs="{'readonly':[('write_access','!=',True)]}"/>
</group>
<separator colspan="4"/>
<group col="3" colspan="4">
<button special="cancel" string="Cancel" icon='gtk-cancel'/>
<button name="do_step_2" string="Previous" colspan="1" type="object" icon="gtk-go-back"/>
<button name="do_step_3" string="Next" colspan="1" type="object" icon="gtk-go-forward"/>
</group>
</form>
</field>
</record>
<record id="action_share_wizard" model="ir.actions.act_window">
<field name="name">Share Access Rules</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">share.create</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="share_step0_form"/>
<field name="target">new</field>
</record>
<menuitem action="action_share_wizard" id="menu_action_share_wizard" parent="base.menu_security"/>
<record id="share_result_form" model="ir.ui.view">
<field name="name">share.result.form</field>
<field name="model">share.result</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Share">
<separator colspan="4" string="Step 4: Share Users Detail"/>
<field name="users" colspan="4" nolabel="1"/>
<separator colspan="4"/>
<group col="2" colspan="4">
<button special="cancel" string="Cancel" icon='gtk-cancel'/>
<button name="do_send_email" string="Send Email" type="object" icon="gtk-apply"/>
</group>
</form>
</field>
</record>
</data>
</openerp>