Files
tryton/trytond/res/user.py
T

509 lines
18 KiB
Python

#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
"User"
import copy
from trytond.model import ModelView, ModelSQL, fields
from trytond.wizard import Wizard
from lxml import etree
from trytond.tools import Cache, safe_eval
from trytond.backend import TableHandler
from trytond.security import get_connections
import string
import random
try:
import hashlib
except ImportError:
hashlib = None
import sha
class User(ModelSQL, ModelView):
"User"
_name = "res.user"
_description = __doc__
name = fields.Char('Name', required=True, select=1, translate=True)
login = fields.Char('Login', required=True)
password = fields.Sha('Password')
salt = fields.Char('Salt', size=8)
signature = fields.Text('Signature')
active = fields.Boolean('Active')
action = fields.Many2One('ir.action', 'Home Action')
menu = fields.Many2One('ir.action', 'Menu Action',
domain=[('usage','=','menu')], required=True)
groups = fields.Many2Many('res.user-res.group',
'uid', 'gid', 'Groups')
rule_groups = fields.Many2Many('ir.rule.group-res.user',
'user_id', 'rule_group_id', 'Rules',
domain=[('global_p', '!=', True), ('default_p', '!=', True)])
language = fields.Many2One('ir.lang', 'Language',
domain=['OR', ('translatable', '=', True), ('code', '=', 'en_US')])
language_direction = fields.Function('get_language_direction', type='char',
string='Language Direction')
timezone = fields.Selection('timezones', 'Timezone')
email = fields.Char('Email')
status_bar = fields.Function('get_status_bar', type='char',
string="Status Bar")
warnings = fields.One2Many('res.user.warning', 'user', 'Warnings')
connections = fields.Function('get_connections', type='integer',
string='Connections')
def __init__(self):
super(User, self).__init__()
self._rpc.update({
'get_preferences': False,
'set_preferences': True,
'get_preferences_fields_view': False,
})
self._sql_constraints += [
('login_key', 'UNIQUE (login)',
'You can not have two users with the same login!')
]
self._preferences_fields = [
'name',
'password',
'email',
'signature',
'menu',
'action',
'status_bar',
'warnings',
]
self._context_fields = [
'language',
'language_direction',
'timezone',
'groups',
]
self._error_messages.update({
'rm_root': 'You can not remove the root user\n' \
'as it is used internally for resources\n' \
'created by the system ' \
'(updates, module installation, ...)',
'wrong_password': 'Wrong password!',
})
def default_password(self, cursor, user, context=None):
return False
def default_active(self, cursor, user, context=None):
return 1
def default_menu(self, cursor, user, context=None):
action_obj = self.pool.get('ir.action')
action_ids = action_obj.search(cursor, user, [
('usage', '=', 'menu'),
], limit=1, context=context)
if action_ids:
return action_ids[0]
return False
def default_action(self, cursor, user, context=None):
return self.default_menu(cursor, user, context=context)
def get_language_direction(self, cursor, user, ids, name, arg, context=None):
res = {}
lang_obj = self.pool.get('ir.lang')
default_direction = lang_obj.default_direction(cursor, user, context=context)
for user in self.browse(cursor, user, ids, context=context):
if user.language:
res[user.id] = user.language.direction
else:
res[user.id] = default_direction
return res
def get_status_bar(self, cursor, user_id, ids, name, arg, context=None):
res = {}
for user in self.browse(cursor, user_id, ids, context=context):
res[user.id] = user.name
return res
def get_connections(self, cursor, user, ids, name, arg, context=None):
res = {}
for user_id in ids:
res[user_id] = get_connections(cursor.database_name, user_id)
return res
def _convert_vals(self, cursor, user, vals, context=None):
vals = vals.copy()
action_obj = self.pool.get('ir.action')
if 'action' in vals:
vals['action'] = action_obj.get_action_id(cursor, user,
vals['action'], context=context)
if 'menu' in vals:
vals['menu'] = action_obj.get_action_id(cursor, user,
vals['menu'], context=context)
if 'password' in vals:
if vals['password'] == 'x' * 10:
del vals['password']
elif vals['password']:
vals['salt'] = ''.join(random.sample(
string.ascii_letters + string.digits, 8))
vals['password'] += vals['salt']
return vals
def create(self, cursor, user, vals, context=None):
vals = self._convert_vals(cursor, user, vals, context=context)
res = super(User, self).create(cursor, user, vals, context=context)
# Restart the cache for _get_login
self._get_login(cursor.dbname)
return res
def write(self, cursor, user, ids, vals, context=None):
vals = self._convert_vals(cursor, user, vals, context=context)
res = super(User, self).write(cursor, user, ids, vals, context=context)
# Restart the cache for domain_get method
self.pool.get('ir.rule').domain_get(cursor.dbname)
# Restart the cache for get_groups
self.get_groups(cursor.dbname)
# Restart the cache for _get_login
self._get_login(cursor.dbname)
# Restart the cache for get_preferences
self.get_preferences(cursor.dbname)
# Restart the cache of check
self.pool.get('ir.model.access').check(cursor.dbname)
# Restart the cache
for _, model in self.pool.iterobject():
try:
model.fields_view_get(cursor.dbname)
except:
pass
return res
def delete(self, cursor, user, ids, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
if 0 in ids:
self.raise_user_error(cursor, 'rm_root', context=context)
res = super(User, self).delete(cursor, user, ids, context=context)
# Restart the cache for _get_login
self._get_login(cursor.dbname)
return res
def read(self, cursor, user, ids, fields_names=None, context=None):
res = super(User, self).read(cursor, user, ids, fields_names=fields_names,
context=context)
if isinstance(ids, (int, long)):
if 'password' in res:
res['password'] = 'x' * 10
else:
for val in res:
if 'password' in val:
val['password'] = 'x' * 10
return res
def search_rec_name(self, cursor, user, name, args, context=None):
args2 = []
i = 0
while i < len(args):
ids = self.search(cursor, user, [
('login', '=', args[i][2]),
], context=context)
if len(ids) == 1:
args2.append(('id', '=', ids[0]))
else:
args2.append((self._rec_name, args[i][1], args[i][2]))
i += 1
return args2
def copy(self, cursor, user, ids, default=None, context=None):
if default is None:
default = {}
default = default.copy()
int_id = False
if isinstance(ids, (int, long)):
int_id = True
ids = [ids]
default['password'] = ''
new_ids = []
user_id = user
for user in self.browse(cursor, user_id, ids, context=context):
default['login'] = user.login + ' (copy)'
new_id = super(User, self).copy(cursor, user_id, user.id, default,
context=context)
new_ids.append(new_id)
if int_id:
return new_ids[0]
return new_ids
def _get_preferences(self, cursor, user_id, user, context_only=False,
context=None):
res = {}
if context_only:
fields = self._context_fields
else:
fields = self._preferences_fields + self._context_fields
for field in fields:
if self._columns[field]._type in ('many2one',):
if field == 'language':
if user.language:
res['language'] = user.language.code
else:
res['language'] = 'en_US'
else:
res[field] = user[field].id
elif self._columns[field]._type in ('one2many', 'many2many'):
res[field] = [x.id for x in user[field]]
else:
res[field] = user[field]
if user.language:
date = user.language.date
for i, j in [('%a', ''), ('%A', ''), ('%b', '%m'), ('%B', '%m'),
('%j', ''), ('%U', ''), ('%w', ''), ('%W', '')]:
date = date.replace(i, j)
res['locale'] = {
'date': date,
'grouping': safe_eval(user.language.grouping),
'decimal_point': user.language.decimal_point,
'thousands_sep': user.language.thousands_sep,
}
return res
def get_preferences(self, cursor, user_id, context_only=False, context=None):
user = self.browse(cursor, 0, user_id, context=context)
res = self._get_preferences(cursor, user_id, user,
context_only=context_only, context=context)
return res
get_preferences = Cache('res_user.get_preferences')(get_preferences)
def set_preferences(self, cursor, user_id, values, old_password=False,
context=None):
'''
Set user preferences.
:param cursor: the database cursor
:param user_id: the user id
:param values: a dictionary with values
:param old_password: the previous password if password is in values
:param context: the context
'''
lang_obj = self.pool.get('ir.lang')
values_clean = values.copy()
fields = self._preferences_fields + self._context_fields
for field in values:
if field not in fields or field == 'groups':
del values_clean[field]
if field == 'password':
user = self.browse(cursor, 0, user_id, context=context)
if not self.get_login(cursor, 0, user.login, old_password,
context=context):
self.raise_user_error(cursor, 'wrong_password',
context=context)
if field == 'language':
lang_ids = lang_obj.search(cursor, user_id, [
('code', '=', values['language']),
], context=context)
if lang_ids:
values_clean['language'] = lang_ids[0]
else:
del values_clean['language']
self.write(cursor, 0, user_id, values_clean, context=context)
def get_preferences_fields_view(self, cursor, user, context=None):
model_data_obj = self.pool.get('ir.model.data')
lang_obj = self.pool.get('ir.lang')
if context is None:
context = {}
model_data_ids = model_data_obj.search(cursor, user, [
('fs_id', '=', 'user_view_form_preferences'),
('module', '=', 'res'),
('inherit', '=', False),
], limit=1, context=context)
model_data = model_data_obj.browse(cursor, user, model_data_ids[0],
context=context)
res = self.fields_view_get(cursor, user, view_id=model_data.db_id,
context=context)
res = copy.deepcopy(res)
for field in res['fields']:
if field not in ('groups', 'language_direction'):
res['fields'][field]['readonly'] = False
else:
res['fields'][field]['readonly'] = True
if 'language' in res['fields']:
del res['fields']['language']['relation']
res['fields']['language']['type'] = 'selection'
res['fields']['language']['selection'] = []
lang_ids = lang_obj.search(cursor, user, ['OR',
('translatable', '=', True),
('code', '=', 'en_US'),
], context=None)
ctx = context.copy()
ctx['translate_name'] = True
for lang in lang_obj.browse(cursor, user, lang_ids, context=ctx):
res['fields']['language']['selection'].append(
(lang.code, lang.name))
return res
def timezones(self, cursor, user, context=None):
try:
import pytz
res = [(x, x) for x in pytz.common_timezones]
except ImportError:
res = []
return res
def get_groups(self, cursor, user, context=None):
'''
Return a list of group ids for the user
:param cursor: the database cursor
:param user: the user id
:param context: the context
:return: a list of group ids
'''
return self.read(cursor, user, user, ['groups'],
context=context)['groups']
get_groups = Cache('res_user.get_groups')(get_groups)
def _get_login(self, cursor, user, login, context=None):
cursor.execute('SELECT id, password, salt ' \
'FROM "' + self._table + '" '
'WHERE login = %s AND active', (login,))
res = cursor.fetchone()
if not res:
return None, None, None
return res
_get_login = Cache('res_user._get_login')(_get_login)
def get_login(self, cursor, user, login, password, context=None):
'''
Return user id if password matches
:param cursor: the database cursor
:param user: the user id
:param login: the login name
:param password: the password
:param context: the context
:return: integer
'''
user_id, user_password, salt = self._get_login(cursor, user,
login, context=context)
if not user_id:
return 0
password += salt or ''
if isinstance(password, unicode):
password = password.encode('utf-8')
if hashlib:
password_sha = hashlib.sha1(password).hexdigest()
else:
password_sha = sha.new(password).hexdigest()
if password_sha == user_password:
return user_id
return 0
User()
class UserGroup(ModelSQL):
'User - Group'
_name = 'res.user-res.group'
_description = __doc__
uid = fields.Many2One('res.user', 'User', ondelete='CASCADE', select=1,
required=True)
gid = fields.Many2One('res.group', 'Group', ondelete='CASCADE', select=1,
required=True)
def init(self, cursor, module_name):
# Migration from 1.0 table name change
TableHandler.table_rename(cursor, 'res_group_user_rel', self._table)
TableHandler.sequence_rename(cursor, 'res_group_user_rel_id_seq',
self._table + '_id_seq')
super(UserGroup, self).init(cursor, module_name)
UserGroup()
class Group(ModelSQL, ModelView):
"Group"
_name = "res.group"
users = fields.Many2Many('res.user-res.group', 'gid', 'uid', 'Users')
Group()
class Warning(ModelSQL, ModelView):
'User Warning'
_name = 'res.user.warning'
_description = __doc__
user = fields.Many2One('res.user', 'User', required=True, select=1)
name = fields.Char('Name', required=True, select=1)
always = fields.Boolean('Always')
def check(self, cursor, user, warning_name, context=None):
if not user:
return False
warning_ids = self.search(cursor, user, [
('user', '=', user),
('name', '=', warning_name),
], context=context)
if not warning_ids:
return True
warnings = self.browse(cursor, user, warning_ids, context=context)
self.delete(cursor, user, [x.id for x in warnings if not x.always],
context=context)
return False
Warning()
class UserConfigInit(ModelView):
'User Config Init'
_name = 'res.user.config.init'
_description = __doc__
UserConfigInit()
class UserConfig(Wizard):
'Configure users'
_name = 'res.user.config'
states = {
'init': {
'result': {
'type': 'form',
'object': 'res.user.config.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('user', 'Ok', 'tryton-ok', True),
],
},
},
'user': {
'actions': ['_reset'],
'result': {
'type': 'form',
'object': 'res.user',
'state': [
('end', 'End', 'tryton-cancel'),
('add', 'Add', 'tryton-ok', True),
],
},
},
'add': {
'result': {
'type': 'action',
'action': '_add',
'state': 'user',
},
},
}
def _reset(self, cursor, user, data, context=None):
return {}
def _add(self, cursor, user, data, context=None):
res_obj = self.pool.get('res.user')
res_obj.create(cursor, user, data['form'], context=context)
return {}
UserConfig()