[FIX] share: corrected config message for share_root_url
[odoo/odoo.git] / addons / share / wizard / share_wizard.py
index 722fa98..a95fa5b 100644 (file)
 #    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()