# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
+import logging
+import random
+import time
+
+import tools
from osv import osv, fields
+from osv.expression import expression
from tools.translate import _
-import tools
+from tools.safe_eval import safe_eval
+
+FULL_ACCESS = ('perm_read', 'perm_write', 'perm_create', 'perm_unlink')
+READ_ONLY_ACCESS = ('perm_read',)
+
+
+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
+RANDOM_PASS_CHARACTERS.remove('1') #one, mistaken with lowercase-L or capital i
+def generate_random_pass():
+ pass_chars = RANDOM_PASS_CHARACTERS[:]
+ random.shuffle(pass_chars)
+ return ''.join(pass_chars[0:10])
-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
+ __logger = logging.getLogger('share.wizard')
+ _name = 'share.wizard'
+ _description = 'Share Wizard'
_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([('readwrite','READ & WRITE'),('readonly','READ ONLY')],'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'),
+ 'action_id': fields.many2one('ir.actions.act_window', 'Action to share', required=True,
+ help="The action that opens the screen containing the data you wish to share."),
+ 'domain': fields.char('Domain', size=256, help="Optional domain for further data filtering"),
+ 'user_type': fields.selection([('existing','Existing external users'),('new','New users (emails required)')],'Users to share with',
+ help="Select the type of user(s) you would like to share data with."),
+ 'user_ids': fields.one2many('share.wizard.user', 'share_wizard_id', 'Users'),
+ 'new_users': fields.text("New users"),
+ 'access_mode': fields.selection([('readwrite','Read & Write'),('readonly','Read-only')],'Access Mode'),
+ 'result_line_ids': fields.one2many('share.wizard.result.line', 'share_wizard_id', 'Summary'),
+ 'share_root_url': fields.char('Generic Share Access URL', size=512, tooltip='Main access page for users that are granted shared access')
}
_defaults = {
- 'user_type' : 'existing',
- 'domain': '[]',
+ 'user_type' : lambda self, cr, uid, *a: 'existing' if self.pool.get('res.users').search(cr, uid, [('share', '=', True)]) else 'new',
+ 'domain': lambda self, cr, uid, context, *a: context.get('domain', '[]'),
+ 'share_root_url': lambda self, cr, uid, context, *a: context.get('share_root_url') or _('Please specify "share_root_url" in context'),
+ 'action_id': lambda self, cr, uid, context, *a: context.get('action_id'),
'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,
- }
- step1_id = this.id
-
- context.update(vals)
- value = {
- 'name': _('Step:2 Sharing Wizard'),
- 'view_type': 'form',
- 'view_mode': 'form',
- 'res_model': 'share.create',
+ def go_step_1(self, cr, uid, ids, context=None):
+ dummy, step1_form_view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'share', 'share_step1_form')
+ return {
+ 'name': _('Sharing Wizard - Step 1'),
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'share.wizard',
'view_id': False,
- 'res_id': step1_id,
- '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,
- }
-
- 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,
+ 'res_id': ids[0],
+ 'views': [(step1_form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')],
+ 'type': 'ir.actions.act_window',
'target': 'new'
}
- return value
-
- def do_step_3(self, cr, uid, ids, context=None):
- """
- This action to excute step 3
-
- """
- if not context:
- context = {}
-
- for this in self.browse(cr, uid, ids, context=context):
- vals ={
- 'access_mode': this.access_mode,
- }
-
- context.update(vals)
-
+ def _create_share_group(self, cr, uid, wizard_data, context=None):
group_obj = self.pool.get('res.groups')
+ share_group_name = '%s: %s (%d-%s)' %('Sharing', wizard_data.action_id.res_model, uid, time.time())
+ # create share group without putting admin in it
+ return group_obj.create(cr, 1, {'name': share_group_name, 'share': True}, {'noadmin': True})
+
+ def _create_new_share_users(self, cr, uid, wizard_data, group_id, context=None):
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, {
+ if wizard_data.user_type == 'new':
+ for new_user in wizard_data.new_users.split('\n'):
+ # attempt to show more user-friendly msg than default constraint error
+ existing = user_obj.search(cr, 1, [('login', '=', new_user)])
+ if existing:
+ raise osv.except_osv(_('User already exists'),
+ _('This username (%s) already exists, perhaps data has already been shared with this person.\nYou may want to try selecting existing shared users instead.'))
+ user_id = user_obj.create(cr, 1, {
'login': new_user,
- 'password': password,
+ 'password': generate_random_pass(),
'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})
+ '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
-
- 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
-
- active_model = model_obj.browse(cr, uid, active_model_id, context=context)
- obj0 = [(None, active_model)]
- 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'])
+ return user_ids
+ def _setup_action_and_shortcut(self, cr, uid, wizard_data, user_ids, new_users, context=None):
+ menu_obj = self.pool.get('ir.ui.menu')
+ user_obj = self.pool.get('res.users')
+ menu_action_id = user_obj._get_menu(cr, uid, context=context)
+ values = {
+ 'name': (_('%s (Shared)') % wizard_data.action_id.name)[:64],
+ 'domain': wizard_data.domain,
+ 'context': wizard_data.action_id.context,
+ 'res_model': wizard_data.action_id.res_model,
+ 'view_mode': wizard_data.action_id.view_mode,
+ 'view_type': wizard_data.action_id.view_type,
+ 'search_view_id': wizard_data.action_id.search_view_id.id,
+ }
+ for user_id in user_ids:
+ action_id = menu_obj.create_shortcut(cr, user_id, values)
+ if new_users:
+ user_obj.write(cr, 1, [user_id], {'action_id': action_id})
+ else:
+ user_obj.write(cr, 1, [user_id], {'action_id': menu_action_id})
+
+ def _get_recursive_relations(self, cr, uid, model, ttypes, relation_fields=None, suffix=None, context=None):
+ """Returns list of tuples representing recursive relationships of type ``ttypes`` starting from
+ model with ID ``model_id``.
+
+ @param model: browsable model to start loading relationships from
+ @param ttypes: list of relationship types to follow (e.g: ['one2many','many2many'])
+ @param relation_fields: list of previously followed relationship tuples - to avoid duplicates
+ during recursion
+ @param suffix: optional suffix to append to the field path to reach the main object
+ """
+ if relation_fields is None:
+ relation_fields = []
+ local_rel_fields = []
+ models = [x[1].model for x in relation_fields]
+ model_obj = self.pool.get('ir.model')
+ model_osv = self.pool.get(model.model)
+ for field in model_osv._columns.values() + [x[2] for x in model_osv._inherit_fields]:
+ if field._type in ttypes and field._obj not in models:
+ relation_model_id = model_obj.search(cr, uid, [('model','=',field._obj)])[0]
+ if field._type == 'one2many':
+ relation_field = '%s.%s'%(field._fields_id, suffix) if suffix else field._fields_id
+ else:
+ relation_field = None # TODO: add some filtering for m2m and m2o - not always possible...
+ model_browse = model_obj.browse(cr, uid, relation_model_id, context=context)
+ local_rel_fields.append((relation_field, model_browse))
+ if relation_model_id != model.id and field._type in ['one2many', 'many2many']:
+ local_rel_fields += self._get_recursive_relations(cr, uid, model_browse,
+ [field._type], local_rel_fields, suffix=relation_field, context=context)
+ return local_rel_fields
+
+ def _get_relationship_classes(self, cr, uid, model, context=None):
+ obj0 = [(None, model)]
+ obj1 = self._get_recursive_relations(cr, uid, model, ['one2many'], context=context)
+ obj2 = self._get_recursive_relations(cr, uid, model, ['one2many', 'many2many'],
+ context=context)
+ obj3 = self._get_recursive_relations(cr, uid, model, ['many2one'], context=context)
+ for dummy, model in obj1:
+ obj3 += self._get_recursive_relations(cr, uid, model, ['many2one'], context=context)
+ return obj0, obj1, obj2, obj3
+
+ def _get_access_map_for_groups_and_models(self, cr, uid, group_ids, model_ids, context=None):
+ model_access_obj = self.pool.get('ir.model.access')
+ user_right_ids = model_access_obj.search(cr, uid,
+ [('group_id', 'in', group_ids), ('model_id', 'in', model_ids)],
+ context=context)
+ user_access_matrix = {}
+ if user_right_ids:
+ for access_right in model_access_obj.browse(cr, uid, user_right_ids, context=context):
+ access_line = user_access_matrix.setdefault(access_right.model_id.model, set())
+ for perm in FULL_ACCESS:
+ if getattr(access_right, perm, 0):
+ access_line.add(perm)
+ return user_access_matrix
+
+ def _add_access_rights_for_share_group(self, cr, uid, group_id, mode,
+ fields_relations, context=None):
+ """Adds access rights to group_id on object models referenced in ``fields_relations``,
+ intersecting with access rights of current user to avoid granting too much rights
+ """
+ model_access_obj = self.pool.get('ir.model.access')
+ user_obj = self.pool.get('res.users')
+ target_model_ids = [x[1].id for x in fields_relations]
+ perms_to_add = (mode == 'readonly') and READ_ONLY_ACCESS or FULL_ACCESS
current_user = user_obj.browse(cr, uid, uid, context=context)
- if access_mode == 'readonly':
- res = []
- # intersect with read access rights of user running the
- # wizard, to avoid adding more access than current
- for group in current_user.groups_id:
- for access_control in group.model_access:
- if access_control.model_id.id in res:
- continue
- if access_control.perm_read:
- res.append(access_control.model_id.id)
- model_access_obj.create(cr, uid, {
- 'name': 'Read Access of group %s on %s model'%(share_group_name, access_control.model_id.name),
- 'model_id' : access_control.model_id.id,
- 'group_id' : group_id,
- 'perm_read' : True
- })
- res = []
- for rel_field, model in obj0+obj1+obj2+obj3:
- if model.id in res:
- continue
- res.append(model.id)
- 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':
- res = []
- for rel_field, model in obj0+obj1:
- if model.id in res:
- continue
- res.append(model.id)
- model_access_obj.create(cr, uid, {
- 'name': 'Write 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,
- 'perm_unlink' : True,
- 'perm_create' : True,
- })
- # intersect with access rights of user
- # running the wizard, to avoid adding more access than current
-
- for group in current_user.groups_id:
- for access_control in group.model_access:
- if access_control.model_id.id in res:
- continue
- if access_control.perm_read:
- res.append(access_control.model_id.id)
- model_access_obj.create(cr, uid, {
- 'name': 'Read Access of group %s on %s model'%(share_group_name, access_control.model_id.name),
- 'model_id' : access_control.model_id.id,
- 'group_id' : group_id,
- 'perm_read' : True
- })
- for rel_field, model in obj2+obj3:
- if model.id in res:
- continue
- res.append(model.id)
- 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
- })
- #
- # 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)
+ current_user_access_map = self._get_access_map_for_groups_and_models(cr, uid,
+ [x.id for x in current_user.groups_id], target_model_ids, context=context)
+ group_access_map = self._get_access_map_for_groups_and_models(cr, uid,
+ [group_id], target_model_ids, context=context)
+ self.__logger.debug("Current user access matrix: %r", current_user_access_map)
+ self.__logger.debug("New group current access matrix: %r", group_access_map)
+
+ # Create required rights if allowed by current user rights and not
+ # already granted
+ for dummy, model in fields_relations:
+ values = {
+ 'name': _('Copied access for sharing'),
+ 'group_id': group_id,
+ 'model_id': model.id,
+ }
+ current_user_access_line = current_user_access_map.get(model.model,set())
+ existing_group_access_line = group_access_map.get(model.model,set())
+ need_creation = False
+ for perm in perms_to_add:
+ if perm in current_user_access_line \
+ and perm not in existing_group_access_line:
+ values.update({perm:True})
+ group_access_map.setdefault(model.model, set()).add(perm)
+ need_creation = True
+ if need_creation:
+ model_access_obj.create(cr, 1, values)
+ self.__logger.debug("Creating access right for model %s with values: %r", model.model, values)
+
+ def _link_or_copy_current_user_rules(self, cr, uid, group_id, fields_relations, context=None):
+ user_obj = self.pool.get('res.users')
+ rule_obj = self.pool.get('ir.rule')
+ current_user = user_obj.browse(cr, uid, uid, context=context)
+ completed_models = set()
for group in current_user.groups_id:
- res = []
- for rel_field, model in obj0+obj1+obj2+obj3:
- if model.id in res:
+ for dummy, model in fields_relations:
+ if model.id in completed_models:
continue
- res.append(model.id)
+ completed_models.add(model.id)
for rule in group.rule_groups:
- if rule.model_id == model.id:
- rule_obj.copy(cr, uid, rule.id, default={
- 'name': '%s-%s'%(share_group_name, model.model),
- 'groups': [(6,0,[group_id])]}, context=context)
-
- rule_obj.create(cr, uid, {
- 'name': '%s-%s'%(share_group_name, active_model.model),
- 'model_id': active_model.id,
- 'domain_force': domain,
- 'groups': [(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_force': obj1_domain,
- 'groups': [(6,0,[group_id])]
+ if rule.model_id == model.id:
+ if 'user.' in rule.domain_force:
+ # Above pattern means there is likely a condition
+ # specific to current user, so we must copy the rule using
+ # the evaluated version of the domain.
+ # And it's better to copy one time too much than too few
+ rule_obj.copy(cr, 1, rule.id, default={
+ 'name': '%s (%s)' %(rule.name, _('(Copy for sharing)')),
+ 'groups': [(6,0,[group_id])],
+ 'domain_force': rule.domain, # evaluated version!
+ })
+ self.__logger.debug("Copying rule %s (%s) on model %s with domain: %s", rule.name, rule.id, model.model, rule.domain_force)
+ else:
+ # otherwise we can simply link the rule to keep it dynamic
+ rule_obj.write(cr, 1, [rule.id], {
+ 'groups': [(4,group_id)]
+ })
+ self.__logger.debug("Linking rule %s (%s) on model %s with domain: %s", rule.name, rule.id, model.model, rule.domain_force)
+
+ def _create_indirect_sharing_rules(self, cr, uid, wizard_data, group_id, fields_relations, context=None):
+ user_obj = self.pool.get('res.users')
+ current_user = user_obj.browse(cr, uid, uid, context=context)
+ rule_obj = self.pool.get('ir.rule')
+ try:
+ domain = safe_eval(wizard_data.domain)
+ if domain:
+ domain_expr = expression(domain)
+ for rel_field, model in fields_relations:
+ related_domain = []
+ for element in domain:
+ if domain_expr._is_leaf(element):
+ left, operator, right = element
+ left = '%s.%s'%(rel_field, left)
+ element = left, operator, right
+ related_domain.append(element)
+ rule_obj.create(cr, 1, {
+ 'name': _('Indirect sharing filter created by user %s (%s) for group %s') % \
+ (current_user.name, current_user.login, group_id),
+ 'model_id': model.id,
+ 'domain_force': str(related_domain),
+ 'groups': [(4,group_id)]
+ })
+ self.__logger.debug("Created indirect rule on model %s with domain: %s", model.model, repr(related_domain))
+ except Exception:
+ self.__logger.exception('Failed to create share access')
+ raise osv.except_osv(_('Sharing access could not be setup'),
+ _('Sorry, the current screen and filter you are trying to share are not supported at the moment.\nYou may want to try a simpler filter.'))
+
+ def _create_result_lines(self, cr, uid, wizard_data, context=None):
+ user_obj = self.pool.get('res.users')
+ result_obj = self.pool.get('share.wizard.result.line')
+ share_root_url = wizard_data.share_root_url
+ format_url = '%(login)s' in share_root_url and '%(password)s' in share_root_url
+ existing_passwd_str = _('*usual password*')
+ if wizard_data.user_type == 'new':
+ for email in wizard_data.new_users.split('\n'):
+ user_id = user_obj.search(cr, 1, [('login', '=', email)], context=context)
+ password = user_obj.read(cr, 1, user_id[0], ['password'])['password']
+ share_url = share_root_url % \
+ {'login': email,
+ 'password': password} if format_url else share_root_url
+ result_obj.create(cr, uid, {
+ 'share_wizard_id': wizard_data.id,
+ 'login': email,
+ 'password': password,
+ 'share_url': share_url,
+ }, context=context)
+ else:
+ # existing users
+ for user in wizard_data.user_ids:
+ share_url = share_root_url % \
+ {'login': email,
+ 'password': ''} if format_url else share_root_url
+ result_obj.create(cr, uid, {
+ 'share_wizard_id': wizard_data.id,
+ 'login': user.user_id.login,
+ 'password': existing_passwd_str,
+ 'share_url': share_url,
+ 'newly_created': False,
+ }, context=context)
+
+ def go_step_2(self, cr, uid, ids, context=None):
+ wizard_data = self.browse(cr, uid, ids and ids[0], context=context)
+ assert wizard_data.action_id and wizard_data.access_mode and \
+ ((wizard_data.user_type == 'new' and wizard_data.new_users) or \
+ (wizard_data.user_type == 'existing' and wizard_data.user_ids))
+
+ # Create shared group and users
+ group_id = self._create_share_group(cr, uid, wizard_data, context=context)
+ user_obj = self.pool.get('res.users')
+ current_user = user_obj.browse(cr, uid, uid, context=context)
+ if wizard_data.user_type == 'new':
+ user_ids = self._create_new_share_users(cr, uid, wizard_data, group_id, context=context)
+ else:
+ user_ids = [x.user_id.id for x in wizard_data.user_ids]
+ # reset home action to regular menu as user needs access to multiple items
+ user_obj.write(cr, 1, user_ids, {
+ 'groups_id': [(4,group_id)],
+ })
+ self._setup_action_and_shortcut(cr, uid, wizard_data, user_ids,
+ (wizard_data.user_type == 'new'), context=context)
+
+
+ model_obj = self.pool.get('ir.model')
+ model_id = model_obj.search(cr, uid, [('model','=', wizard_data.action_id.res_model)])[0]
+ model = model_obj.browse(cr, uid, model_id, context=context)
+
+ # ACCESS RIGHTS
+ # We have several classes of objects that should receive different access rights:
+ # Let:
+ # - [obj0] be the target model itself
+ # - [obj1] be the target model and all other models recursively accessible from
+ # obj0 via one2many relationships
+ # - [obj2] be the target model and all other models recursively accessible from
+ # obj0 via one2many and many2many relationships
+ # - [obj3] be all models recursively accessible from obj1 via many2one relationships
+ obj0, obj1, obj2, obj3 = self._get_relationship_classes(cr, uid, model, context=context)
+ mode = wizard_data.access_mode
+
+ # Add access to [obj0] and [obj1] according to chosen mode
+ self._add_access_rights_for_share_group(cr, uid, group_id, mode, obj0, context=context)
+ self._add_access_rights_for_share_group(cr, uid, group_id, mode, obj1, context=context)
+
+ # Add read-only access (always) to [obj2] and [obj3]
+ self._add_access_rights_for_share_group(cr, uid, group_id, 'readonly', obj2, context=context)
+ self._add_access_rights_for_share_group(cr, uid, group_id, 'readonly', obj3, context=context)
+
+
+ # IR.RULES
+ # A. On [obj0]: 1 rule with domain of shared action
+ # B. For each model in [obj1]: 1 rule in the form:
+ # many2one_rel.domain_of_obj0
+ # where many2one_rel is the many2one used in the definition of the
+ # one2many, and domain_of_obj0 is the sharing domain
+ # For example if [obj0] is project.project with a domain of
+ # ['id', 'in', [1,2]]
+ # then we will have project.task in [obj1] and we need to create this
+ # ir.rule on project.task:
+ # ['project_id.id', 'in', [1,2]]
+ # C. And on [obj0], [obj1], [obj2], [obj3]: add all rules from all groups of
+ # the user that is sharing
+ # (Warning: rules must be copied instead of linked if they contain a reference
+ # to uid, and it must be replaced correctly)
+ rule_obj = self.pool.get('ir.rule')
+ # A.
+ rule_obj.create(cr, 1, {
+ 'name': _('Sharing filter created by user %s (%s) for group %s') % \
+ (current_user.name, current_user.login, group_id),
+ 'model_id': model.id,
+ 'domain_force': wizard_data.domain,
+ 'groups': [(4,group_id)]
})
- context['share_model'] = active_model.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,
+ # B.
+ self._create_indirect_sharing_rules(cr, uid, wizard_data, group_id, obj1, context=context)
+ # C.
+ all_relations = obj0 + obj1 + obj2 + obj3
+ self._link_or_copy_current_user_rules(cr, uid, group_id, all_relations, context=context)
+
+ # so far, so good -> populate summary results and return them
+ self._create_result_lines(cr, uid, wizard_data, context=context)
+
+ dummy, step2_form_view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'share', 'share_step2_form')
+ return {
+ 'name': _('Sharing Wizard - Step 2'),
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'share.wizard',
+ 'view_id': False,
+ 'res_id': ids[0],
+ 'views': [(step2_form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')],
+ 'type': 'ir.actions.act_window',
'target': 'new'
}
- return value
+
+ def send_emails(self, cr, uid, ids, context=None):
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ if not user.user_email:
+ raise osv.except_osv(_('Email required'), _('The current user must have an email address configured in User Preferences to be able to send outgoing emails.'))
+ for wizard_data in self.browse(cr, uid, ids, context=context):
+ for result_line in wizard_data.result_line_ids:
+ email_to = result_line.login
+ subject = _('%s has shared OpenERP %s information with you') % (user.name, wizard_data.action_id.name)
+ body = _("Dear,\n\n") + subject + "\n\n"
+ body += _("To access it, you can go to the following URL:\n %s") % wizard_data.share_root_url
+ body += "\n\n"
+ if result_line.newly_created:
+ body += _("You may use the following login and password to get access to this protected area:") + "\n"
+ body += "%s: %s" % (_("Username"), result_line.login) + "\n"
+ body += "%s: %s" % (_("Password"), result_line.password) + "\n"
+ else:
+ body += _("This additional data has been automatically added to your current access.\n")
+ body += _("You may use your existing login and password to view it. As a reminder, your login is %s.\n") % result_line.login
+
+ if not tools.email_send(
+ user.user_email,
+ email_to,
+ subject,
+ body):
+ self.__logger.warning('Failed to send sharing email from %s to %s', user.user_email, email_to)
+ return {'type': 'ir.actions.act_window_close'}
share_create()
+class share_user_ref(osv.osv_memory):
+ _name = 'share.wizard.user'
+ _rec_name = 'user_id'
+ _columns = {
+ 'user_id': fields.many2one('res.users', 'Users', required=True, domain=[('share', '=', True)]),
+ 'share_wizard_id': fields.many2one('share.wizard', 'Share Wizard', required=True),
+ }
+share_user_ref()
-class share_result(osv.osv_memory):
- _name = "share.result"
+class share_result_line(osv.osv_memory):
+ _name = 'share.wizard.result.line'
+ _rec_name = 'login'
_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()
+ 'login': fields.char('Username', size=64, required=True, readonly=True),
+ 'password': fields.char('Password', size=64, readonly=True),
+ 'share_url': fields.char('Share URL', size=512, required=True),
+ 'share_wizard_id': fields.many2one('share.wizard', 'Share Wizard', required=True),
+ 'newly_created': fields.boolean('Newly created', readonly=True),
+ }
+ _defaults = {
+ 'newly_created': True,
+ }
+share_result_line()