diff options
| author | Olivier Dony <odo@openerp.com> | 2011-01-06 18:38:28 +0100 |
|---|---|---|
| committer | Olivier Dony <odo@openerp.com> | 2011-01-06 18:38:28 +0100 |
| commit | 7d3375f49f6d4ef47b148dc3f229f3012a358643 (patch) | |
| tree | fadd5ac0486560e6f7ce70c23d93fc5a76a3e585 /bin/addons | |
| parent | f68dbc723c2a74aed5bb2c4add9ccb76bb82d1bf (diff) | |
| parent | bf5765b3ef0f22dcbca413e86fc39dfef9f1d87f (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.xml | 2 | ||||
| -rw-r--r-- | bin/addons/base/base_update.xml | 32 | ||||
| -rw-r--r-- | bin/addons/base/ir/ir_model.py | 53 | ||||
| -rw-r--r-- | bin/addons/base/ir/ir_ui_menu.py | 16 | ||||
| -rw-r--r-- | bin/addons/base/res/partner/partner.py | 2 | ||||
| -rw-r--r-- | bin/addons/base/res/res_lang.py | 66 | ||||
| -rw-r--r-- | bin/addons/base/res/res_user.py | 65 |
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 & 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 |
