[IMP] share: add optional message, simplify screens, auto-send email
bzr revid: odo@openerp.com-20110614110718-b1g040q5mk17htrj
This commit is contained in:
@@ -10,9 +10,9 @@ Members of this groups have access to the sharing wizard, which allows them to i
|
||||
<record id="group_share_user_extended" model="res.groups">
|
||||
<field name="name">Sharing / Share With Groups</field>
|
||||
<field name="comment">
|
||||
Members of this group are allowed to share documents with Groups in addition to Users. This is useful for setting up Portals
|
||||
Members of this group are allowed to share documents with Groups in addition to Users, and have a few more options in the share wizard. This is useful for setting up Portals
|
||||
with the portal module, but this could result in an alteration of the system's security policy.
|
||||
Therefore this should be granted only to trustworthy users who know the security policy very well.
|
||||
Therefore this should be granted only to trustworthy users who know the security policy very well.
|
||||
This group should not be deleted.</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
+236
-164
@@ -37,17 +37,43 @@ UID_ROOT = 1
|
||||
# osv.expression's DUMMY_LEAF
|
||||
DOMAIN_ALL = [(1, '=', 1)]
|
||||
|
||||
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
|
||||
# A good selection of easy to read password characters (e.g. no '0' vs 'O', etc.)
|
||||
RANDOM_PASS_CHARACTERS = 'aaaabcdeeeefghjkmnpqrstuvwxyzAAAABCDEEEEFGHJKLMNPQRSTUVWXYZ23456789'
|
||||
def generate_random_pass():
|
||||
pass_chars = RANDOM_PASS_CHARACTERS[:]
|
||||
random.shuffle(pass_chars)
|
||||
return ''.join(pass_chars[0:10])
|
||||
return ''.join(random.sample(RANDOM_PASS_CHARACTERS,10))
|
||||
|
||||
|
||||
# Utils for introspecting the ORM - could be moved to server someday
|
||||
class column_info(object):
|
||||
"""Struct containing details about an osv column, either one local to
|
||||
its model, or one inherited via _inherits.
|
||||
|
||||
:attr name: name of the column
|
||||
:attr column: column instance, subclass of osv.fields.column
|
||||
:attr parent_model: if the column is inherited, name of the model
|
||||
that contains it, None for local columns.
|
||||
:attr parent_column: the name of the column containing the m2o
|
||||
relationship to the parent model that contains
|
||||
this column, None for local columns.
|
||||
"""
|
||||
def __init__(self, name, column, parent_model=None, parent_column=None):
|
||||
self.name = name
|
||||
self.column = column
|
||||
self.parent_model = parent_model
|
||||
self.parent_column = parent_column
|
||||
|
||||
def get_column_infos(osv_model):
|
||||
"""Returns a dict mapping all fields names (direct fields and
|
||||
inherited field via _inherits) to a ``column_info`` struct
|
||||
giving detailed columns """
|
||||
result = {}
|
||||
for k, (parent,m2o,col) in osv_model._inherit_fields:
|
||||
result[k] = column_info(k, col, parent, m2o)
|
||||
for k, v in osv_model._columns.iteritems():
|
||||
result[k] = column_info(k,v)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class share_create(osv.osv_memory):
|
||||
__logger = logging.getLogger('share.wizard')
|
||||
@@ -76,16 +102,20 @@ class share_create(osv.osv_memory):
|
||||
def has_extended_share(self, cr, uid, context=None):
|
||||
return self.has_group(cr, uid, module='share', group_xml_id='group_share_user_extended', context=context)
|
||||
|
||||
def _has_email(self, cr, uid, ids, name, arg, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
has_email = bool(user.user_email)
|
||||
return dict([(id,has_email) for id in ids])
|
||||
def has_email(self, cr, uid, context=None):
|
||||
return bool(self.pool.get('res.users').browse(cr, uid, uid, context=context).user_email)
|
||||
|
||||
def view_init(self, cr, uid, fields_list, context=None):
|
||||
if not self.has_email(cr, uid, context=context):
|
||||
raise osv.except_osv(_('No e-mail address configured'),
|
||||
_('You must configure your e-mail address in the user preferences before using the Share button.'))
|
||||
return super(share_create, self).view_init(cr, uid, fields_list, context=context)
|
||||
|
||||
def _user_type_selection(self, cr, uid, context=None):
|
||||
result = [('new','New users (emails required)'),
|
||||
('existing','Users you already shared with')]
|
||||
result = [('new','New users (emails required)')]
|
||||
if self.has_extended_share(cr, uid, context=context):
|
||||
result.append(('groups','Existing groups (Portals setup only)'))
|
||||
result.extend([('groups','Existing groups (Portals setup only)'),
|
||||
('existing','Users you already shared with')])
|
||||
return result
|
||||
|
||||
"""Override of create() to auto-compute the action name"""
|
||||
@@ -104,16 +134,15 @@ class share_create(osv.osv_memory):
|
||||
'user_ids': fields.many2many('res.users', 'share_wizard_res_user_rel', 'share_id', 'user_id', 'Existing users', domain=[('share', '=', True)]),
|
||||
'group_ids': fields.many2many('res.groups', 'share_wizard_res_group_rel', 'share_id', 'group_id', 'Existing groups', domain=[('share', '=', False)]),
|
||||
'new_users': fields.text("New users"),
|
||||
'access_mode': fields.selection([('readwrite','Read & Write'),('readonly','Read-only')],'Access Mode'),
|
||||
'access_mode': fields.selection([('readonly','Can view'),('readwrite','Can edit')],'Access Mode', required=True,
|
||||
help="Access rights to be granted on the shared documents."),
|
||||
'result_line_ids': fields.one2many('share.wizard.result.line', 'share_wizard_id', 'Summary', readonly=True),
|
||||
'share_root_url': fields.char('Generic Share Access URL', size=512, readonly=True, tooltip='Main access page for users that are granted shared access'),
|
||||
'name': fields.char('Title', size=64, required=True, help="Title for the shared view, to be displayed as the name of the shortcut in the user's menu"),
|
||||
|
||||
# used to display a warning message at first step
|
||||
'has_user_email': fields.function(_has_email, string='Has email', method=True, type="boolean"),
|
||||
'name': fields.char('Share Title', size=64, required=True, help="Title for the share (displayed to users as menu and shortcut name)"),
|
||||
'message': fields.text("Personal Message", help="An optional personal message, to be included in the e-mail notification."),
|
||||
}
|
||||
_defaults = {
|
||||
'user_type' : lambda self, cr, uid, *a: 'existing' if self.pool.get('res.users').search(cr, uid, [('share', '=', True)]) else 'new',
|
||||
'user_type' : lambda self, cr, uid, *a: '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'),
|
||||
@@ -129,39 +158,57 @@ class share_create(osv.osv_memory):
|
||||
'res_model': 'share.wizard',
|
||||
'view_id': False,
|
||||
'res_id': ids[0],
|
||||
'views': [(step1_form_view_id, 'form'), (False, 'tree'), (False, 'calendar'), (False, 'graph')],
|
||||
'views': [(step1_form_view_id, 'form')],
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new'
|
||||
}
|
||||
|
||||
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())
|
||||
share_group_name = '%s: %s (%d-%s)' %('Shared', wizard_data.name, uid, time.time())
|
||||
# create share group without putting admin in it
|
||||
return group_obj.create(cr, UID_ROOT, {'name': share_group_name, 'share': True}, {'noadmin': True})
|
||||
|
||||
def _create_new_share_users(self, cr, uid, wizard_data, group_id, context=None):
|
||||
"""Create one new res.users record for each email address provided in
|
||||
wizard_data.new_users, ignoring already existing users.
|
||||
Populates wizard_data.result_line_ids with one new line for
|
||||
each user (existing or not). New users will also have a value
|
||||
for the password field, so they can receive it by email.
|
||||
Returns the ids of the created users, and the ids of the
|
||||
ignored, existing ones."""
|
||||
user_obj = self.pool.get('res.users')
|
||||
current_user = user_obj.browse(cr, uid, uid, context=context)
|
||||
user_ids = []
|
||||
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, UID_ROOT, [('login', '=', new_user)])
|
||||
self._assert(not existing,
|
||||
_('This username (%s) already exists, perhaps data has already been shared with this person.\nYou may want to try selecting existing shared users instead.') % new_user,
|
||||
context=context)
|
||||
user_id = user_obj.create(cr, UID_ROOT, {
|
||||
'login': new_user,
|
||||
'password': generate_random_pass(),
|
||||
'name': new_user,
|
||||
'user_email': new_user,
|
||||
'groups_id': [(6,0,[group_id])],
|
||||
'share': True,
|
||||
'company_id': current_user.company_id and current_user.company_id.id
|
||||
})
|
||||
user_ids.append(user_id)
|
||||
return user_ids
|
||||
current_user = user_obj.browse(cr, UID_ROOT, uid, context=context)
|
||||
created_ids = []
|
||||
existing_ids = []
|
||||
for new_user in wizard_data.new_users.split('\n'):
|
||||
# Ignore blank lines
|
||||
new_user = new_user.strip()
|
||||
if not new_user: continue
|
||||
# Ignore the user if it already exists.
|
||||
existing = user_obj.search(cr, UID_ROOT, [('login', '=', new_user)])
|
||||
existing_ids.extend(existing)
|
||||
if existing:
|
||||
new_line = { 'login': new_user,
|
||||
'newly_created': False}
|
||||
wizard_data.write({'result_line_ids': [(0,0,new_line)]})
|
||||
continue
|
||||
new_pass = generate_random_pass()
|
||||
user_id = user_obj.create(cr, UID_ROOT, {
|
||||
'login': new_user,
|
||||
'password': new_pass,
|
||||
'name': new_user,
|
||||
'user_email': new_user,
|
||||
'groups_id': [(6,0,[group_id])],
|
||||
'share': True,
|
||||
'company_id': current_user.company_id.id
|
||||
})
|
||||
new_line = { 'login': new_user,
|
||||
'password': new_pass,
|
||||
'newly_created': True}
|
||||
wizard_data.write({'result_line_ids': [(0,0,new_line)]})
|
||||
created_ids.append(user_id)
|
||||
return created_ids, existing_ids
|
||||
|
||||
def _create_shortcut(self, cr, uid, values, context=None):
|
||||
if context is None:
|
||||
@@ -183,7 +230,7 @@ class share_create(osv.osv_memory):
|
||||
menu_obj = self.pool.get('ir.ui.menu')
|
||||
menu_id = menu_obj.create(cr, UID_ROOT, menu_data)
|
||||
sc_data = {'name': values['name'], 'sequence': UID_ROOT,'res_id': menu_id }
|
||||
sc_menu_id = self.pool.get('ir.ui.view_sc').create(cr, uid, sc_data, new_context)
|
||||
self.pool.get('ir.ui.view_sc').create(cr, uid, sc_data, new_context)
|
||||
|
||||
# update menu cache
|
||||
user_groups = set(self.pool.get('res.users').read(cr, UID_ROOT, uid, ['groups_id'])['groups_id'])
|
||||
@@ -217,26 +264,34 @@ class share_create(osv.osv_memory):
|
||||
return result
|
||||
|
||||
def _shared_action_def(self, cr, uid, wizard_data, context=None):
|
||||
return {
|
||||
copied_action = wizard_data.action_id
|
||||
action_def = {
|
||||
'name': wizard_data.name,
|
||||
'domain': '[]',
|
||||
'context': self._cleanup_action_context(wizard_data.action_id.context, uid),
|
||||
'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,
|
||||
'res_model': copied_action.res_model,
|
||||
'view_mode': copied_action.view_mode,
|
||||
'view_type': copied_action.view_type,
|
||||
'search_view_id': copied_action.search_view_id.id,
|
||||
'view_id': copied_action.view_id.id,
|
||||
}
|
||||
if copied_action.view_ids:
|
||||
action_def['view_ids'] = [(0,0,{'sequence': x.sequence,
|
||||
'view_mode': x.view_mode,
|
||||
'view_id': x.view_id.id })
|
||||
for x in copied_action.view_ids]
|
||||
return action_def
|
||||
|
||||
def _setup_action_and_shortcut(self, cr, uid, wizard_data, user_ids, new_users, context=None):
|
||||
def _setup_action_and_shortcut(self, cr, uid, wizard_data, user_ids, make_home, context=None):
|
||||
"""Create a shortcut to reach the shared data, as well as the corresponding action, for
|
||||
each user in ``user_ids``, and assign it as their home action.
|
||||
each user in ``user_ids``, and assign it as their home action if ``make_home`` is True.
|
||||
Meant to be overridden for special cases.
|
||||
"""
|
||||
user_obj = self.pool.get('res.users')
|
||||
values = self._shared_action_def(cr, uid, wizard_data, context=None)
|
||||
user_obj = self.pool.get('res.users')
|
||||
for user_id in user_ids:
|
||||
action_id = self._create_shortcut(cr, user_id, values)
|
||||
if new_users:
|
||||
if make_home:
|
||||
# We do this only for new share users, as existing ones already have their initial home
|
||||
# action. Resetting to the default menu does not work well as the menu is rather empty
|
||||
# and does not contain the shortcuts in most cases.
|
||||
@@ -252,41 +307,61 @@ class share_create(osv.osv_memory):
|
||||
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.itervalues()]:
|
||||
if field._type in ttypes and field._obj not in models:
|
||||
relation_model_id = model_obj.search(cr, UID_ROOT, [('model','=',field._obj)])[0]
|
||||
if field._type == 'one2many':
|
||||
relation_field = '%s.%s'%(field._fields_id, suffix) if suffix else field._fields_id
|
||||
for colinfo in get_column_infos(model_osv).itervalues():
|
||||
coldef = colinfo.column
|
||||
coltype = coldef._type
|
||||
if coltype in ttypes and colinfo.column._obj not in models:
|
||||
relation_model_id = model_obj.search(cr, UID_ROOT, [('model','=',coldef._obj)])[0]
|
||||
relation_model_browse = model_obj.browse(cr, UID_ROOT, relation_model_id, context=context)
|
||||
relation_osv = self.pool.get(coldef._obj)
|
||||
if coltype == 'one2many':
|
||||
relation_field = '%s.%s'%(coldef._fields_id, suffix) if suffix else coldef._fields_id
|
||||
else:
|
||||
# TODO: add some filtering for m2m and m2o - not always possible...
|
||||
relation_field = None
|
||||
model_browse = model_obj.browse(cr, UID_ROOT, relation_model_id, context=context)
|
||||
local_rel_fields.append((relation_field, model_browse))
|
||||
for parent in self.pool.get(model_browse.model)._inherits:
|
||||
local_rel_fields.append((relation_field, relation_model_browse))
|
||||
for parent in relation_osv._inherits:
|
||||
if parent not in models:
|
||||
parent_model = self.pool.get(parent)
|
||||
parent_colinfos = get_column_infos(parent_model)
|
||||
parent_model_browse = model_obj.browse(cr, UID_ROOT,
|
||||
model_obj.search(cr, UID_ROOT, [('model','=',parent)]))[0]
|
||||
if relation_field and (field._fields_id in parent_model._columns or \
|
||||
field._fields_id in parent_model._inherit_fields):
|
||||
if coldef._fields_id in parent_colinfos:
|
||||
# inverse relationship is available in the parent
|
||||
local_rel_fields.append((relation_field, parent_model_browse))
|
||||
else:
|
||||
# TODO: can we setup a proper rule to restrict inherited models
|
||||
# in case the parent does not contain the reverse m2o?
|
||||
local_rel_fields.append((None, parent_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], relation_fields + local_rel_fields, suffix=relation_field, context=context)
|
||||
if relation_model_id != model.id and coltype in ['one2many', 'many2many']:
|
||||
local_rel_fields += self._get_recursive_relations(cr, uid, relation_model_browse,
|
||||
[coltype], relation_fields + local_rel_fields, suffix=relation_field, context=context)
|
||||
return local_rel_fields
|
||||
|
||||
def _get_relationship_classes(self, cr, uid, model, context=None):
|
||||
"""Computes the *relationship classes* reachable from the given
|
||||
model. The 4 relationship classes are:
|
||||
- [obj0]: the given model itself (and its parents via _inherits, if any)
|
||||
- [obj1]: obj0 and all other models recursively accessible from
|
||||
obj0 via one2many relationships
|
||||
- [obj2]: obj0 and all other models recursively accessible from
|
||||
obj0 via one2many and many2many relationships
|
||||
- [obj3]: all models recursively accessible from obj1 via many2one
|
||||
relationships
|
||||
|
||||
Each class is returned as a list of pairs [(field,model_browse)], where
|
||||
``model`` is the browse_record of a reachable ir.model, and ``field`` is
|
||||
the dot-notation reverse relationship path coming from that model to obj0,
|
||||
or None if there is no reverse path.
|
||||
|
||||
:return: ([obj0], [obj1], [obj2], [obj3])
|
||||
"""
|
||||
# obj0 class and its parents
|
||||
obj0 = [(None, model)]
|
||||
model_obj = self.pool.get(model.model)
|
||||
@@ -430,62 +505,20 @@ class share_create(osv.osv_memory):
|
||||
raise osv.except_osv(_('Sharing access could not be created'),
|
||||
_('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')
|
||||
def _finish_result_lines(self, cr, uid, wizard_data, context=None):
|
||||
share_root_url = wizard_data.share_root_url
|
||||
format_url = '%(login)s' in share_root_url\
|
||||
and '%(password)s' in share_root_url\
|
||||
and '%(dbname)s' in share_root_url
|
||||
existing_passwd_str = _('*usual password*')
|
||||
if wizard_data.user_type == 'new':
|
||||
# new users
|
||||
for email in wizard_data.new_users.split('\n'):
|
||||
user_id = user_obj.search(cr, UID_ROOT, [('login', '=', email)], context=context)
|
||||
password = user_obj.read(cr, UID_ROOT, user_id[0], ['password'])['password']
|
||||
format_url = '%(dbname)s' in share_root_url
|
||||
for result in wizard_data.result_line_ids:
|
||||
# share_root_url may contain placeholders for dbname, login and password
|
||||
share_url = share_root_url % \
|
||||
{'login': email,
|
||||
'password': password,
|
||||
{'login': result.login,
|
||||
'password': '', # kept empty for security reasons
|
||||
'dbname': cr.dbname} 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)
|
||||
elif wizard_data.user_type == 'existing':
|
||||
# existing users
|
||||
for user in wizard_data.user_ids:
|
||||
share_url = share_root_url % \
|
||||
{'login': user.login,
|
||||
'password': '',
|
||||
'dbname': cr.dbname} if format_url else share_root_url
|
||||
result_obj.create(cr, uid, {
|
||||
'share_wizard_id': wizard_data.id,
|
||||
'login': user.login,
|
||||
'password': existing_passwd_str,
|
||||
'share_url': share_url,
|
||||
'newly_created': False,
|
||||
}, context=context)
|
||||
else:
|
||||
# existing groups
|
||||
for group in wizard_data.group_ids:
|
||||
for user in group.users:
|
||||
share_url = share_root_url % \
|
||||
{'login': user.login,
|
||||
'password': '',
|
||||
'dbname': cr.dbname} if format_url else share_root_url
|
||||
result_obj.create(cr, uid, {
|
||||
'share_wizard_id': wizard_data.id,
|
||||
'login': user.login,
|
||||
'password': existing_passwd_str,
|
||||
'share_url': share_url,
|
||||
'newly_created': False,
|
||||
}, context=context)
|
||||
result.write({'share_url': share_url}, context=context)
|
||||
|
||||
def _check_preconditions(self, cr, uid, wizard_data, context=None):
|
||||
self._assert(wizard_data.action_id and wizard_data.access_mode,
|
||||
_('Action and Access Mode are required to create a shared access point'),
|
||||
_('Action and Access Mode are required to create a shared access'),
|
||||
context=context)
|
||||
self._assert(self.has_share(cr, uid, context=context),
|
||||
_('You must be a member of the Share/User group to use the share wizard'),
|
||||
@@ -504,36 +537,51 @@ class share_create(osv.osv_memory):
|
||||
context=context)
|
||||
|
||||
def _create_share_users_groups(self, cr, uid, wizard_data, context=None):
|
||||
"""Create the appropriate shared users and groups, and return the new group_ids)
|
||||
to which the shared access should be granted.
|
||||
"""Creates the appropriate shared users and groups, and populates
|
||||
result_line_ids of wizard_data with one line for each user.
|
||||
|
||||
:return: group_ids (to which the shared access should be granted),
|
||||
new_user_ids, and existing_user_ids.
|
||||
"""
|
||||
group_ids = []
|
||||
user_ids = []
|
||||
group_ids, new_ids, existing_ids = [], [], []
|
||||
|
||||
if wizard_data.user_type == 'groups':
|
||||
group_id = None
|
||||
group_ids.extend([g.id for g in wizard_data.group_ids])
|
||||
# populate result lines with the users of each group
|
||||
for group in wizard_data.group_ids:
|
||||
for user in group.users:
|
||||
new_line = {'login': user.login,
|
||||
'newly_created': False}
|
||||
wizard_data.write({'result_line_ids': [(0,0,new_line)]})
|
||||
|
||||
else:
|
||||
group_id = self._create_share_group(cr, uid, wizard_data, context=context)
|
||||
group_ids.append(group_id)
|
||||
if wizard_data.user_type == 'new':
|
||||
user_ids = self._create_new_share_users(cr, uid, wizard_data, group_id, context=context)
|
||||
elif wizard_data.user_type == 'existing':
|
||||
user_ids = [x.id for x in wizard_data.user_ids]
|
||||
# reset home action to regular menu as user needs access to multiple items
|
||||
self.pool.get('res.users').write(cr, UID_ROOT, user_ids, {
|
||||
'groups_id': [(4,group_id)],
|
||||
})
|
||||
if user_ids:
|
||||
self._setup_action_and_shortcut(cr, uid, wizard_data, user_ids,
|
||||
(wizard_data.user_type == 'new'), context=context)
|
||||
return group_ids
|
||||
# First create any missing user, based on the email addresses provided
|
||||
new_ids, exist_ids = self._create_new_share_users(cr, uid, wizard_data, group_id, context=context)
|
||||
existing_ids.extend(exist_ids)
|
||||
# Then, add any existing users to the share group
|
||||
existing_ids.extend([x.id for x in wizard_data.user_ids]) # manually selected users
|
||||
self.pool.get('res.users').write(cr, UID_ROOT, existing_ids, {
|
||||
'groups_id': [(4,group_id)],
|
||||
})
|
||||
# Finally, setup the new action and shortcut for the users.
|
||||
if existing_ids:
|
||||
# Existing users will only need a shortcut
|
||||
self._setup_action_and_shortcut(cr, uid, wizard_data, existing_ids, False, context=context)
|
||||
if new_ids:
|
||||
# New users need the shortcut AND a home action
|
||||
self._setup_action_and_shortcut(cr, uid, wizard_data, new_ids, True, context=context)
|
||||
|
||||
return group_ids, new_ids, existing_ids
|
||||
|
||||
def go_step_2(self, cr, uid, ids, context=None):
|
||||
wizard_data = self.browse(cr, uid, ids and ids[0], context=context)
|
||||
wizard_data = self.browse(cr, uid, ids[0], context=context)
|
||||
self._check_preconditions(cr, uid, wizard_data, context=context)
|
||||
|
||||
# Create shared group and users
|
||||
group_ids = self._create_share_users_groups(cr, uid, wizard_data, context=context)
|
||||
group_ids, new_user_ids, existing_user_ids = self._create_share_users_groups(cr, uid, wizard_data, context=context)
|
||||
|
||||
current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
|
||||
@@ -550,10 +598,10 @@ class share_create(osv.osv_memory):
|
||||
# - [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
|
||||
# (currently not used)
|
||||
obj0, obj1, obj2, obj3 = self._get_relationship_classes(cr, uid, model, context=context)
|
||||
mode = wizard_data.access_mode
|
||||
|
||||
rule_obj = self.pool.get('ir.rule')
|
||||
for group_id in group_ids:
|
||||
# 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)
|
||||
@@ -589,8 +637,20 @@ class share_create(osv.osv_memory):
|
||||
all_relations = obj0 + obj1 + obj2
|
||||
self._link_or_copy_current_user_rules(cr, current_user, 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)
|
||||
# so far, so good -> check summary results and return them
|
||||
self._finish_result_lines(cr, uid, wizard_data, context=context)
|
||||
|
||||
# update share URL to make it generic
|
||||
generic_share_url = wizard_data.share_root_url % {'dbname': cr.dbname,
|
||||
'login': '',
|
||||
'password': ''}
|
||||
wizard_data.write({'share_root_url': generic_share_url}, context=context)
|
||||
|
||||
# refresh wizard_data
|
||||
wizard_data = self.browse(cr, uid, ids[0], context=context)
|
||||
|
||||
# send the confirmation emails:
|
||||
self.send_emails(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 {
|
||||
@@ -605,33 +665,45 @@ class share_create(osv.osv_memory):
|
||||
'target': 'new'
|
||||
}
|
||||
|
||||
def send_emails(self, cr, uid, ids, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
def send_emails(self, cr, uid, wizard_data, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, UID_ROOT, uid)
|
||||
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%s\n\n") % subject
|
||||
body += _("To access it, you can go to the following URL:\n %s") % result_line.share_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"
|
||||
body += "%s: %s" % (_("Database"), cr.dbname) + "\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'}
|
||||
# TODO: also send an HTML version of this mail
|
||||
for result_line in wizard_data.result_line_ids:
|
||||
email_to = result_line.login
|
||||
subject = wizard_data.name
|
||||
body = _("Hello,")
|
||||
body += "\n\n"
|
||||
body += _("I've shared %s with you!") % wizard_data.name
|
||||
body += "\n\n"
|
||||
body += _("The documents are not attached, you can view them online directly on my OpenERP server at:")
|
||||
body += "\n " + result_line.share_url
|
||||
body += "\n\n"
|
||||
if wizard_data.message:
|
||||
body += wizard_data.message
|
||||
body += "\n\n"
|
||||
if result_line.newly_created:
|
||||
body += _("These are your credentials to access this protected area:\n")
|
||||
body += "%s: %s" % (_("Username"), result_line.login) + "\n"
|
||||
body += "%s: %s" % (_("Password"), result_line.password) + "\n"
|
||||
body += "%s: %s" % (_("Database"), cr.dbname) + "\n"
|
||||
else:
|
||||
body += _("The documents have been automatically added to your current OpenERP documents.\n")
|
||||
body += _("You may use your current login (%s) and password to view them.\n") % result_line.login
|
||||
body += "\n\n"
|
||||
body += user.signature
|
||||
body += "\n\n"
|
||||
body += "--\n"
|
||||
body += _("OpenERP is a powerful and user-friendly suite of Business Applications (CRM, Sales, HR, etc.)\n"
|
||||
"It is open source and can be found on http://www.openerp.com.")
|
||||
|
||||
if not tools.email_send(user.user_email,
|
||||
[email_to],
|
||||
subject,
|
||||
body):
|
||||
self.__logger.warning('Failed to send sharing email from %s to %s, ignored', user.user_email, email_to)
|
||||
share_create()
|
||||
|
||||
class share_result_line(osv.osv_memory):
|
||||
|
||||
@@ -29,14 +29,11 @@
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Grant instant access to your documents">
|
||||
<group colspan="4" attrs="{'invisible':[('has_user_email','!=',False)]}">
|
||||
<label colspan="4" string="/!\ E-mail notifications are unavailable until you setup an email address in your preferences"/>
|
||||
<field name="has_user_email" invisible="1"/>
|
||||
<group colspan="4" col="4" groups="share.group_share_user_extended">
|
||||
<separator colspan="4" string="Who do you want to share with?"/>
|
||||
<field name="user_type" />
|
||||
</group>
|
||||
<separator colspan="4" string="Who would you want to share this data with?"/>
|
||||
<field name="user_type"/>
|
||||
<group colspan="4" attrs="{'invisible':[('user_type','!=','existing')]}">
|
||||
<separator colspan="4" string="Existing External Users"/>
|
||||
<field colspan="4" nolabel="1" name="user_ids" mode="tree">
|
||||
<tree string="Existing users">
|
||||
<field name="login"/>
|
||||
@@ -44,7 +41,7 @@
|
||||
</field>
|
||||
</group>
|
||||
<group colspan="4" attrs="{'invisible':[('user_type','!=','new')]}">
|
||||
<separator colspan="4" string="New Users (please provide one e-mail address per line"/>
|
||||
<separator colspan="4" string="Share with these people (one e-mail per line)"/>
|
||||
<field colspan="4" nolabel="1" name="new_users"/>
|
||||
</group>
|
||||
<group colspan="4" attrs="{'invisible':[('user_type','!=','groups')]}">
|
||||
@@ -55,10 +52,14 @@
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<separator colspan="4" string="Additional Options"/>
|
||||
<group colspan="4">
|
||||
<field name="access_mode"/>
|
||||
<field name="name"/>
|
||||
<separator colspan="4" string="Optional: include a personal message"/>
|
||||
<group colspan="4" col="4">
|
||||
<field name="message" colspan="4" nolabel="1"/>
|
||||
</group>
|
||||
<separator colspan="4" string="Extra Options"/>
|
||||
<group colspan="4" col="4">
|
||||
<field name="name" colspan="4"/>
|
||||
<field name="access_mode" colspan="4"/>
|
||||
</group>
|
||||
<separator colspan="4"/>
|
||||
<group colspan="4">
|
||||
@@ -74,35 +75,22 @@
|
||||
<field name="model">share.wizard</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Shared access created!">
|
||||
<separator colspan="4" string="Congratulations, you have successfully setup a new shared access!"/>
|
||||
<label colspan="4" string="Here is a summary of the access points you have just created:"/>
|
||||
<form string="Access granted!">
|
||||
<separator colspan="4" string="An e-mail notification with instructions has been sent to the following people:"/>
|
||||
<field name="result_line_ids" nolabel="1" colspan="4" mode="tree">
|
||||
<tree string="Summary">
|
||||
<field name="login"/>
|
||||
<field name="password"/>
|
||||
<field name="share_url"/>
|
||||
<field name="share_url" groups="share.group_share_user_extended"/>
|
||||
</tree>
|
||||
<form string="Access info">
|
||||
<field name="login"/>
|
||||
<field name="password"/>
|
||||
<field name="share_url" colspan="4"/>
|
||||
<field name="share_url" colspan="4" groups="share.group_share_user_extended"/>
|
||||
</form>
|
||||
</field>
|
||||
<field colspan="4" name="share_root_url"/>
|
||||
<group colspan="4" attrs="{'invisible':[('has_user_email','!=',False)]}">
|
||||
<label colspan="4" string="/!\ Sharing via e-mail is disabled because you don't have an email address in your preferences"/>
|
||||
<field name="has_user_email" invisible="1"/>
|
||||
</group>
|
||||
<field colspan="4" name="share_root_url" groups="share.group_share_user_extended"/>
|
||||
<separator colspan="4"/>
|
||||
<group colspan="4">
|
||||
<button special="cancel" string="Close" icon='gtk-ok'/>
|
||||
<button name="send_emails" string="Send Email Notification(s)"
|
||||
colspan="1" type="object"
|
||||
icon="gtk-go-forward"
|
||||
help="Email notifications can only be sent if you don't have an email address in your preferences"
|
||||
attrs="{'readonly':[('has_user_email','!=',True)]}"
|
||||
/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user