summaryrefslogtreecommitdiff
path: root/bin/addons
diff options
context:
space:
mode:
authorOlivier Dony <odo@openerp.com>2011-01-06 18:38:28 +0100
committerOlivier Dony <odo@openerp.com>2011-01-06 18:38:28 +0100
commit7d3375f49f6d4ef47b148dc3f229f3012a358643 (patch)
treefadd5ac0486560e6f7ce70c23d93fc5a76a3e585 /bin/addons
parentf68dbc723c2a74aed5bb2c4add9ccb76bb82d1bf (diff)
parentbf5765b3ef0f22dcbca413e86fc39dfef9f1d87f (diff)
[MERGE] bugfixes: default language for partners, password change, validation of custom selection fields, ...
See the bug links lp bug: https://launchpad.net/bugs/670264 fixed lp bug: https://launchpad.net/bugs/692891 fixed lp bug: https://launchpad.net/bugs/695678 fixed lp bug: https://launchpad.net/bugs/631547 fixed lp bug: https://launchpad.net/bugs/632927 fixed bzr revid: odo@openerp.com-20110106173828-kdv1gtdvbws1ceb5
Diffstat (limited to 'bin/addons')
-rw-r--r--bin/addons/base/base_data.xml2
-rw-r--r--bin/addons/base/base_update.xml32
-rw-r--r--bin/addons/base/ir/ir_model.py53
-rw-r--r--bin/addons/base/ir/ir_ui_menu.py16
-rw-r--r--bin/addons/base/res/partner/partner.py2
-rw-r--r--bin/addons/base/res/res_lang.py66
-rw-r--r--bin/addons/base/res/res_user.py65
7 files changed, 143 insertions, 93 deletions
diff --git a/bin/addons/base/base_data.xml b/bin/addons/base/base_data.xml
index 9a17e4bc7c7..db74b9b1b1e 100644
--- a/bin/addons/base/base_data.xml
+++ b/bin/addons/base/base_data.xml
@@ -28,6 +28,8 @@
<field name="translatable">True</field>
</record>
+ <function name="install_lang" model="res.lang"/>
+
<record id="ad" model="res.country">
<field name="name">Andorra, Principality of</field>
<field name="code">ad</field>
diff --git a/bin/addons/base/base_update.xml b/bin/addons/base/base_update.xml
index 969b8d49bdd..7432018fd94 100644
--- a/bin/addons/base/base_update.xml
+++ b/bin/addons/base/base_update.xml
@@ -70,34 +70,6 @@
======================
-->
- <record id="view_change_user_password_form" model="ir.ui.view">
- <field name="name">change.user.password</field>
- <field name="model">change.user.password</field>
- <field name="type">form</field>
- <field name="arch" type="xml">
- <form string="Change Password">
- <field name="current_password" password="True" readonly="0" colspan="4"/>
- <field name="new_password" password="True" readonly="0" colspan="4"/>
- <field name="confirm_password" password="True" readonly="0" colspan="4"/>
- <label colspan="1" string=""/>
- <label colspan="3" string="You must logout and login again after changing your password."/>
- <separator colspan="4" />
- <label align="0.0" colspan="2" string=""/>
- <button colspan="1" icon="gtk-cancel" special="cancel" string="Cancel"/>
- <button colspan="1" icon="gtk-ok" name="change_password" string="Change" type="object"/>
- </form>
- </field>
- </record>
-
- <record id="action_view_change_password_form" model="ir.actions.act_window">
- <field name="name">Change Password</field>
- <field name="type">ir.actions.act_window</field>
- <field name="res_model">change.user.password</field>
- <field name="view_type">form</field>
- <field name="view_mode">form</field>
- <field name="target">new</field>
- </record>
-
<record id="view_users_form_simple_modif" model="ir.ui.view">
<field name="name">res.users.form.modif</field>
<field name="model">res.users</field>
@@ -119,8 +91,6 @@
<field name="context_lang" completion="1" readonly="0"/>
<field name="context_tz" completion="1" readonly="0"/>
<field name="menu_tips" colspan="2" readonly="0"/>
- <label string="" colspan="1"/>
- <button name="%(action_view_change_password_form)d" string="Change Password" type="action" icon="gtk-execute"/>
<separator string="Email &amp; Signature" colspan="4"/>
<group colspan="4"><field name="user_email" widget="email" readonly="0"/></group>
<field colspan="4" name="signature" readonly="0" nolabel="1"/>
@@ -139,7 +109,7 @@
<field name="name" select="1"/>
<field name="active"/>
<field name="login" select="1"/>
- <field name="password" password="True"/>
+ <field name="new_password" password="True"/>
<newline/>
<notebook colspan="4">
<page string="User">
diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py
index 4dfe39a7762..5cd65a65d25 100644
--- a/bin/addons/base/ir/ir_model.py
+++ b/bin/addons/base/ir/ir_model.py
@@ -19,14 +19,16 @@
#
##############################################################################
import logging
+import re
+import time
from operator import itemgetter
+
from osv import fields,osv
-import ir, re
+import ir
import netsvc
from osv.orm import except_orm, browse_record
-
-import time
import tools
+from tools.safe_eval import safe_eval as eval
from tools import config
from tools.translate import _
import pooler
@@ -152,21 +154,29 @@ class ir_model_fields(osv.osv):
_description = "Fields"
_columns = {
'name': fields.char('Name', required=True, size=64, select=1),
- 'model': fields.char('Object Name', size=64, required=True, select=1),
- 'relation': fields.char('Object Relation', size=64),
- 'relation_field': fields.char('Relation Field', size=64),
- 'model_id': fields.many2one('ir.model', 'Object ID', required=True, select=True, ondelete='cascade'),
+ 'model': fields.char('Object Name', size=64, required=True, select=1,
+ help="The technical name of the model this field belongs to"),
+ 'relation': fields.char('Object Relation', size=64,
+ help="For relationship fields, the technical name of the target model"),
+ 'relation_field': fields.char('Relation Field', size=64,
+ help="For one2many fields, the field on the target model that implement the opposite many2one relationship"),
+ 'model_id': fields.many2one('ir.model', 'Model', required=True, select=True, ondelete='cascade',
+ help="The model this field belongs to"),
'field_description': fields.char('Field Label', required=True, size=256),
'ttype': fields.selection(_get_fields_type, 'Field Type',size=64, required=True),
- 'selection': fields.char('Field Selection',size=128),
+ 'selection': fields.char('Selection Options',size=128, help="List of options for a selection field, "
+ "specified as a Python expression defining a list of (key, label) pairs. "
+ "For example: [('blue','Blue'),('yellow','Yellow')]"),
'required': fields.boolean('Required'),
'readonly': fields.boolean('Readonly'),
- 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search')],'Searchable', required=True),
- 'translate': fields.boolean('Translate'),
+ 'select_level': fields.selection([('0','Not Searchable'),('1','Always Searchable'),('2','Advanced Search (deprecated)')],'Searchable', required=True),
+ 'translate': fields.boolean('Translate', help="Whether values for this field can be translated (enables the translation mechanism for that field)"),
'size': fields.integer('Size'),
'state': fields.selection([('manual','Custom Field'),('base','Base Field')],'Type', required=True, readonly=True, select=1),
'on_delete': fields.selection([('cascade','Cascade'),('set null','Set NULL')], 'On delete', help='On delete property for many2one fields'),
- 'domain': fields.char('Domain', size=256),
+ 'domain': fields.char('Domain', size=256, help="The optional domain to restrict possible values for relationship fields, "
+ "specified as a Python expression defining a list of triplets. "
+ "For example: [('color','=','red')]"),
'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
'view_load': fields.boolean('View Auto-Load'),
'selectable': fields.boolean('Selectable'),
@@ -174,7 +184,7 @@ class ir_model_fields(osv.osv):
_rec_name='field_description'
_defaults = {
'view_load': lambda *a: 0,
- 'selection': lambda *a: "[]",
+ 'selection': lambda *a: "[('key','label')]",
'domain': lambda *a: "[]",
'name': lambda *a: 'x_',
'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
@@ -185,6 +195,25 @@ class ir_model_fields(osv.osv):
'selectable': lambda *a: 1,
}
_order = "name"
+
+ def _check_selection(self, cr, uid, ids, context=None):
+ selection_field = self.browse(cr, uid, ids[0], context=context)
+ try:
+ selection_list = eval(selection_field.selection)
+ except Exception:
+ logging.getLogger('ir.model').error('Invalid selection list definition for fields.selection %s', selection_field.name , exc_info=True)
+ return False
+ if not (isinstance(selection_list, list) and selection_list):
+ return False
+ for selection_item in selection_list:
+ if not (isinstance(selection_item, (tuple,list)) and len(selection_item) == 2):
+ return False
+ return True
+
+ _constraints = [
+ (_check_selection, "Wrong list of values specified for a field of type selection, it should be written as [('key','value')]", ['selection'])
+ ]
+
def _size_gt_zero_msg(self, cr, user, ids, context=None):
return _('Size of the field can never be less than 1 !')
diff --git a/bin/addons/base/ir/ir_ui_menu.py b/bin/addons/base/ir/ir_ui_menu.py
index 08725fbb9cb..ef9f7f28308 100644
--- a/bin/addons/base/ir/ir_ui_menu.py
+++ b/bin/addons/base/ir/ir_ui_menu.py
@@ -257,12 +257,14 @@ class ir_ui_menu(osv.osv):
def read_image(self, path):
path_info = path.split(',')
icon_path = addons.get_module_resource(path_info[0],path_info[1])
- icon_file = tools.file_open(icon_path,'rb')
- try:
- icon = icon_file.read()
- return base64.encodestring(icon)
- finally:
- icon_file.close()
+ icon_image = False
+ if icon_path:
+ try:
+ icon_file = tools.file_open(icon_path,'rb')
+ icon_image = base64.encodestring(icon_file.read())
+ finally:
+ icon_file.close()
+ return icon_image
def _get_image_icon(self, cr, uid, ids, name, args, context=None):
res = {}
@@ -304,7 +306,7 @@ class ir_ui_menu(osv.osv):
('ir.actions.server', 'ir.actions.server'),
]),
}
-
+
def _rec_message(self, cr, uid, ids, context=None):
return _('Error ! You can not create recursive Menu.')
diff --git a/bin/addons/base/res/partner/partner.py b/bin/addons/base/res/partner/partner.py
index 3aed1c50aef..8a6a6197ab3 100644
--- a/bin/addons/base/res/partner/partner.py
+++ b/bin/addons/base/res/partner/partner.py
@@ -138,7 +138,7 @@ class res_partner(osv.osv):
'email': fields.related('address', 'email', type='char', size=240, string='E-mail'),
'company_id': fields.many2one('res.company', 'Company', select=1),
}
-
+
def _default_category(self, cr, uid, context={}):
if 'category_id' in context and context['category_id']:
return [context['category_id']]
diff --git a/bin/addons/base/res/res_lang.py b/bin/addons/base/res/res_lang.py
index 64bdb948d14..f05c174a62a 100644
--- a/bin/addons/base/res/res_lang.py
+++ b/bin/addons/base/res/res_lang.py
@@ -24,10 +24,66 @@ from locale import localeconv
import tools
from tools.safe_eval import safe_eval as eval
from tools.translate import _
+import locale
+import logging
+
class lang(osv.osv):
_name = "res.lang"
_description = "Languages"
+ def install_lang(self, cr, uid, **args):
+ lang_ids = self.search(cr, uid, [('code','=', tools.config.get('lang'))])
+ values_obj = self.pool.get('ir.values')
+ if not lang_ids:
+ lang_id = self.load_lang(cr, uid, tools.config.get('lang'))
+ default_value = values_obj.get(cr, uid, 'default', False, 'res.partner')
+ if not default_value:
+ values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], tools.config.get('lang'))
+ return True
+
+ def load_lang(self, cr, uid, lang, lang_name=None):
+ # create the language with locale information
+ fail = True
+ logger = logging.getLogger('i18n')
+ iso_lang = tools.get_iso_codes(lang)
+ for ln in tools.get_locales(lang):
+ try:
+ locale.setlocale(locale.LC_ALL, str(ln))
+ fail = False
+ break
+ except locale.Error:
+ continue
+ if fail:
+ lc = locale.getdefaultlocale()[0]
+ msg = 'Unable to get information for locale %s. Information from the default locale (%s) have been used.'
+ logger.warning(msg, lang, lc)
+
+ if not lang_name:
+ lang_name = tools.get_languages().get(lang, lang)
+
+
+ def fix_xa0(s):
+ if s == '\xa0':
+ return '\xc2\xa0'
+ return s
+
+ lang_info = {
+ 'code': lang,
+ 'iso_code': iso_lang,
+ 'name': lang_name,
+ 'translatable': 1,
+ 'date_format' : str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y')),
+ 'time_format' : str(locale.nl_langinfo(locale.T_FMT)),
+ 'decimal_point' : fix_xa0(str(locale.localeconv()['decimal_point'])),
+ 'thousands_sep' : fix_xa0(str(locale.localeconv()['thousands_sep'])),
+ }
+ lang_id = False
+ try:
+ lang_id = self.create(cr, uid, lang_info)
+ finally:
+ tools.resetlocale()
+ return lang_id
+
def _get_default_date_format(self,cursor,user,context={}):
return '%m/%d/%Y'
@@ -69,7 +125,7 @@ class lang(osv.osv):
thousands_sep = lang_obj.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep']
decimal_point = lang_obj.decimal_point
grouping = lang_obj.grouping
- return (grouping, thousands_sep, decimal_point)
+ return (grouping, thousands_sep, decimal_point)
def write(self, cr, uid, ids, vals, context=None):
for lang_id in ids :
@@ -95,19 +151,19 @@ class lang(osv.osv):
def _group(self, cr, uid, ids, s, monetary=False, grouping=False, thousands_sep=''):
grouping = eval(grouping)
-
+
if not grouping:
return (s, 0)
result = ""
seps = 0
spaces = ""
-
+
if s[-1] == ' ':
sp = s.find(' ')
spaces = s[sp:]
s = s[:sp]
-
+
while s and grouping:
# if grouping is -1, we are done
if grouping[0] == -1:
@@ -139,7 +195,7 @@ class lang(osv.osv):
if percent[0] != '%':
raise ValueError("format() must be given exactly one %char format specifier")
- lang_grouping, thousands_sep, decimal_point = self._lang_data_get(cr, uid, ids[0], monetary)
+ lang_grouping, thousands_sep, decimal_point = self._lang_data_get(cr, uid, ids[0], monetary)
formatted = percent % value
# floats and decimal ints need special action!
diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py
index c8aa83f3253..0356aeac4d2 100644
--- a/bin/addons/base/res/res_user.py
+++ b/bin/addons/base/res/res_user.py
@@ -194,6 +194,16 @@ class users(osv.osv):
self.write(cr, uid, ids, {'address_id': address_id}, context)
return True
+ def _set_new_password(self, cr, uid, id, name, value, args, context=None):
+ if not value:
+ raise osv.except_osv(_('Empty new password'), _('Please provide a new password value'))
+ if uid == id:
+ # To change their own password users must use the client-specific change password wizard,
+ # so that the new password is immediately used for further RPC requests, otherwise the user
+ # will face unexpected 'Access Denied' exceptions.
+ raise osv.except_osv(_('Operation Canceled'), _('Please use the change password wizard (in User Preferences or User menu) to change your own password.'))
+ self.write(cr, uid, id, {'password': value})
+
_columns = {
'name': fields.char('User Name', size=64, required=True, select=True,
help="The new user's real name, used for searching"
@@ -201,6 +211,10 @@ class users(osv.osv):
'login': fields.char('Login', size=64, required=True,
help="Used to log into the system"),
'password': fields.char('Password', size=64, invisible=True, help="Keep empty if you don't want the user to be able to connect on the system."),
+ 'new_password': fields.function(lambda *a:'', method=True, type='char', size=64,
+ fnct_inv=_set_new_password,
+ string='Change password', help="Only specify a value if you want to change the user password. "
+ "This user will have to logout and login again!"),
'email': fields.char('E-mail', size=64,
help='If an email is provided, the user will be sent a message '
'welcoming him.\n\nWarning: if "email_from" and "smtp_server"'
@@ -452,7 +466,7 @@ class users(osv.osv):
(int(uid), passwd, True))
res = cr.fetchone()[0]
if not bool(res):
- raise security.ExceptionNoTb('AccessDenied')
+ raise security.ExceptionNoTb('Accessenied')
if res:
if self._uid_cache.has_key(db):
ulist = self._uid_cache[db]
@@ -476,6 +490,18 @@ class users(osv.osv):
finally:
cr.close()
+ def change_password(self, cr, uid, old_passwd, new_passwd):
+ """Change current user password. Old password must be provided explicitly
+ to prevent hijacking an existing user session, or for cases where the cleartext
+ password is not used to authenticate requests.
+
+ :return: True
+ :raise: security.ExceptionNoTb when old password is wrong
+ """
+ if self.check(cr.dbname, uid, old_passwd):
+ self.write(cr, uid, uid, {'password': new_passwd})
+ return True
+
users()
class config_users(osv.osv_memory):
@@ -574,39 +600,4 @@ class res_config_view(osv.osv_memory):
res_config_view()
-class change_user_password(osv.osv_memory):
- _name = 'change.user.password'
- _columns = {
- 'current_password':fields.char('Current Password', size=64, required=True, help="Enter your current password."),
- 'new_password': fields.char('New Password', size=64, required=True, help="Enter the new password."),
- 'confirm_password': fields.char('Confirm Password', size=64, required=True, help="Enter the new password again for confirmation."),
- }
- _defaults={
- 'current_password' : '',
- 'new_password' : '',
- 'confirm_password' : '',
- }
-
- def change_password(self, cr, uid, ids, context=None):
- for form_id in ids:
- password_rec = self.browse(cr, uid, form_id, context)
- if password_rec.new_password != password_rec.confirm_password:
- raise osv.except_osv(_('Error !'), _('The new and confirmation passwords do not match, please double-check them.'))
-
- # Validate current password without reading it from database,
- # as it could be stored differently (LDAP, encrypted/hashed, etc.)
- is_correct_password = False
- try:
- user_obj = self.pool.get('res.users')
- is_correct_password = user_obj.check(cr.dbname, uid, password_rec.current_password)
- except Exception:
- pass
- if not is_correct_password:
- raise osv.except_osv(_('Error !'), _('The current password does not match, please double-check it.'))
- user_obj.write(cr, uid, [uid], {'password': password_rec.new_password}, context=context)
- return {}
-
-change_user_password()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file