[MERGE] forward port of branch saas-2 up to revid 5020 chs@openerp.com-20140312174526...
authorChristophe Simonis <chs@openerp.com>
Wed, 12 Mar 2014 18:06:14 +0000 (19:06 +0100)
committerChristophe Simonis <chs@openerp.com>
Wed, 12 Mar 2014 18:06:14 +0000 (19:06 +0100)
bzr revid: chs@openerp.com-20140312180614-8yb454s3mkjwnk2q

1  2 
openerp/addons/base/ir/ir_model.py
openerp/addons/base/res/res_users.py
openerp/osv/orm.py
openerp/tools/safe_eval.py

Simple merge
@@@ -3,7 -3,7 +3,7 @@@
  #
  #    OpenERP, Open Source Management Solution
  #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
--#    Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
++#    Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>).
  #
  #    This program is free software: you can redistribute it and/or modify
  #    it under the terms of the GNU Affero General Public License as
@@@ -169,9 -167,17 +169,10 @@@ class res_users(osv.osv)
      }
  
      def on_change_login(self, cr, uid, ids, login, context=None):
-         v = {'email': login} if tools.single_email_re.match(login) else {}
-         return {'value': v}
+         if login and tools.single_email_re.match(login):
+             return {'value': {'email': login}}
+         return {}
  
 -    def on_change_company_id(self, cr, uid, ids, company_id):
 -        return {'warning' : {
 -                    'title': _("Company Switch Warning"),
 -                    'message': _("Please keep in mind that documents currently displayed may not be relevant after switching to another company. If you have unsaved changes, please make sure to save and close all forms before switching to a different company. (You can click on Cancel in the User Preferences now)"),
 -                }
 -        }
 -
      def onchange_state(self, cr, uid, ids, state_id, context=None):
          partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
          return self.pool.get('res.partner').onchange_state(cr, uid, partner_ids, state_id, context=context)
@@@ -869,69 -877,4 +870,69 @@@ class users_view(osv.osv)
                      }
          return res
  
 +#----------------------------------------------------------
 +# change password wizard
 +#----------------------------------------------------------
 +
 +class change_password_wizard(osv.TransientModel):
 +    """
 +        A wizard to manage the change of users' passwords
 +    """
 +
 +    _name = "change.password.wizard"
 +    _description = "Change Password Wizard"
 +    _columns = {
 +        'user_ids': fields.one2many('change.password.user', 'wizard_id', string='Users'),
 +    }
 +
 +    def default_get(self, cr, uid, fields, context=None):
 +        if context == None:
 +            context = {}
 +        user_ids = context.get('active_ids', [])
 +        wiz_id = context.get('active_id', None)
 +        res = []
 +        users = self.pool.get('res.users').browse(cr, uid, user_ids, context=context)
 +        for user in users:
 +            res.append((0, 0, {
 +                'wizard_id': wiz_id,
 +                'user_id': user.id,
 +                'user_login': user.login,
 +            }))
 +        return {'user_ids': res}
 +
 +
 +    def change_password_button(self, cr, uid, id, context=None):
 +        wizard = self.browse(cr, uid, id, context=context)[0]
 +        user_ids = []
 +        for user in wizard.user_ids:
 +            user_ids.append(user.id)
 +        self.pool.get('change.password.user').change_password_button(cr, uid, user_ids, context=context)
 +        # don't keep temporary password copies in the database longer than necessary
-         self.pool.get('change.password.user').unlink(cr, uid, user_ids)
++        self.pool.get('change.password.user').write(cr, uid, user_ids, {'new_passwd': False}, context=context)
 +        return {
 +            'type': 'ir.actions.act_window_close',
 +        }
 +
 +class change_password_user(osv.TransientModel):
 +    """
 +        A model to configure users in the change password wizard
 +    """
 +
 +    _name = 'change.password.user'
 +    _description = 'Change Password Wizard User'
 +    _columns = {
 +        'wizard_id': fields.many2one('change.password.wizard', string='Wizard', required=True),
 +        'user_id': fields.many2one('res.users', string='User', required=True),
 +        'user_login': fields.char('User Login', readonly=True),
 +        'new_passwd': fields.char('New Password'),
 +    }
 +    _defaults = {
 +        'new_passwd': '',
 +    }
 +
 +    def change_password_button(self, cr, uid, ids, context=None):
 +        for user in self.browse(cr, uid, ids, context=context):
 +            self.pool.get('res.users').write(cr, uid, user.user_id.id, {'password': user.new_passwd})
 +
 +
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@@ -75,10 -74,11 +75,10 @@@ _schema = logging.getLogger(__name__ + 
  # List of etree._Element subclasses that we choose to ignore when parsing XML.
  from openerp.tools import SKIPPED_ELEMENT_TYPES
  
- regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
+ regex_order = re.compile('^( *([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
  regex_object_name = re.compile(r'^[a-z0-9_.]+$')
  
 -# TODO for trunk, raise the value to 1000
 -AUTOINIT_RECALCULATE_STORED_FIELDS = 40
 +AUTOINIT_RECALCULATE_STORED_FIELDS = 1000
  
  def transfer_field_to_modifiers(field, modifiers):
      default_values = {}
@@@ -2282,35 -2708,24 +2288,32 @@@ class BaseModel(object)
              if fget.get(groupby):
                  groupby_type = fget[groupby]['type']
                  if groupby_type in ('date', 'datetime'):
 -                    if context.get('datetime_format') and isinstance(context['datetime_format'], dict) \
 -                            and context['datetime_format'].get(groupby) and isinstance(context['datetime_format'][groupby], dict):
 -                        groupby_format = context['datetime_format'][groupby].get('groupby_format', 'yyyy-mm')
 -                        display_format = context['datetime_format'][groupby].get('display_format', 'MMMM yyyy')
 -                        interval = context['datetime_format'][groupby].get('interval', 'month')
 +                    if groupby_function:
 +                        interval = groupby_function
                      else:
 -                        groupby_format = 'yyyy-mm'
 -                        display_format = 'MMMM yyyy'
                          interval = 'month'
 -                    group_by_params = {
 -                        'groupby_format': groupby_format,
 -                        'display_format': display_format,
 -                        'interval': interval,
 -                    }
 -                    qualified_groupby_field = "to_char(%s,%%s)" % qualified_groupby_field
 +
 +                    if interval == 'day':
 +                        display_format = 'dd MMM YYYY' 
 +                    elif interval == 'week':
 +                        display_format = "'W'w YYYY"
 +                    elif interval == 'month':
 +                        display_format = 'MMMM YYYY'
 +                    elif interval == 'quarter':
 +                        display_format = 'QQQ YYYY'
 +                    elif interval == 'year':
 +                        display_format = 'YYYY'
 +
 +                    if groupby_type == 'datetime' and context.get('tz') in pytz.all_timezones:
 +                        # Convert groupby result to user TZ to avoid confusion!
 +                        # PostgreSQL is compatible with all pytz timezone names, so we can use them
 +                        # directly for conversion, starting with timestamps stored in UTC. 
 +                        timezone = context.get('tz', 'UTC')
 +                        qualified_groupby_field = "timezone('%s', timezone('UTC',%s))" % (timezone, qualified_groupby_field)
 +                    qualified_groupby_field = "date_trunc('%s', %s)" % (interval, qualified_groupby_field)
-                     flist = "%s as %s " % (qualified_groupby_field, groupby)
                  elif groupby_type == 'boolean':
                      qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
-                     flist = "%s as %s " % (qualified_groupby_field, groupby)
-                 else:
-                     flist = qualified_groupby_field
+                 select_terms.append("%s as %s " % (qualified_groupby_field, groupby))
              else:
                  # Don't allow arbitrary values, as this would be a SQL injection vector!
                  raise except_orm(_('Invalid group_by'),
              if (f in self._all_columns and getattr(self._all_columns[f].column, '_classic_write'))]
          for f in aggregated_fields:
              group_operator = fget[f].get('group_operator', 'sum')
-             if flist:
-                 flist += ', '
              qualified_field = self._inherits_join_calc(f, query)
-             flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
-         order = orderby or groupby
-         orderby_clause = ''
-         ob = ''
-         if order:
-             orderby_clause, ob = self._read_group_generate_order_by(order, aggregated_fields, groupby, query)
+             select_terms.append("%s(%s) AS %s" % (group_operator, qualified_field, f))
  
-         gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
+         order = orderby or groupby or ''
+         groupby_terms, orderby_terms = self._read_group_prepare(order, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type)
  
          from_clause, where_clause, where_clause_params = query.get_sql()
-         where_clause = where_clause and ' WHERE ' + where_clause
-         limit_str = limit and ' limit %d' % limit or ''
-         offset_str = offset and ' offset %d' % offset or ''
 -        if group_by_params and group_by_params.get('groupby_format'):
 -            where_clause_params = [group_by_params['groupby_format']] + where_clause_params + [group_by_params['groupby_format']]
          if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
-             group_count = '_'
-         cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + (ob and ',') + ob + orderby_clause + limit_str + offset_str, where_clause_params)
+             count_field = '_'
+         else:
+             count_field = groupby
+         prefix_terms = lambda prefix, terms: (prefix + " " + ",".join(terms)) if terms else ''
+         prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else ''
+         query = """
+             SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s_count
+                    %(extra_fields)s
+             FROM %(from)s
+             %(where)s
+             %(groupby)s
+             %(orderby)s
+             %(limit)s
+             %(offset)s
+         """ % {
+             'table': self._table,
+             'count_field': count_field,
+             'extra_fields': prefix_terms(',', select_terms),
+             'from': from_clause,
+             'where': prefix_term('WHERE', where_clause),
+             'groupby': prefix_terms('GROUP BY', groupby_terms),
+             'orderby': prefix_terms('ORDER BY', orderby_terms),
+             'limit': prefix_term('LIMIT', int(limit) if limit else None),
+             'offset': prefix_term('OFFSET', int(offset) if limit else None),
+         }
+         cr.execute(query, where_clause_params)
          alldata = {}
-         groupby = group_by
          fetched_data = cr.dictfetchall()
  
          data_ids = []
@@@ -1,6 -1,6 +1,6 @@@
  # -*- coding: utf-8 -*-
  ##############################################################################
--#    Copyright (C) 2004-2012 OpenERP s.a. (<http://www.openerp.com>).
++#    Copyright (C) 2004-2014 OpenERP s.a. (<http://www.openerp.com>).
  #
  #    This program is free software: you can redistribute it and/or modify
  #    it under the terms of the GNU Affero General Public License as
@@@ -219,37 -219,45 +219,50 @@@ def safe_eval(expr, globals_dict=None, 
              locals_dict = dict(locals_dict)
  
      globals_dict.update(
 -            __builtins__ = {
 -                '__import__': _import,
 -                'True': True,
 -                'False': False,
 -                'None': None,
 -                'str': str,
 -                'unicode': unicode,
 -                'globals': locals,
 -                'locals': locals,
 -                'bool': bool,
 -                'int': int,
 -                'float': float,
 -                'long': long,
 -                'enumerate': enumerate,
 -                'dict': dict,
 -                'list': list,
 -                'tuple': tuple,
 -                'map': map,
 -                'abs': abs,
 -                'min': min,
 -                'max': max,
 -                'sum': sum,
 -                'reduce': reduce,
 -                'filter': filter,
 -                'round': round,
 -                'len': len,
 -                'set': set,
 -                'all': all,
 -                'any': any,
 -                'ord': ord,
 -                'chr': chr,
 -                'cmp': cmp,
 -                'divmod': divmod,
 -                'isinstance': isinstance,
 -                'range': range,
 -                'xrange': xrange,
 -                'zip': zip,
 -            }
 +        __builtins__={
 +            '__import__': _import,
 +            'True': True,
 +            'False': False,
 +            'None': None,
 +            'str': str,
++            'unicode': unicode,
 +            'globals': locals,
 +            'locals': locals,
 +            'bool': bool,
++            'int': int,
++            'float': float,
++            'long': long,
++            'enumerate': enumerate,
 +            'dict': dict,
 +            'list': list,
 +            'tuple': tuple,
 +            'map': map,
 +            'abs': abs,
 +            'min': min,
 +            'max': max,
++            'sum': sum,
 +            'reduce': reduce,
 +            'filter': filter,
 +            'round': round,
 +            'len': len,
-             'set': set,
 +            'repr': repr,
-             'int': int,
-             'float': float,
++            'set': set,
++            'all': all,
++            'any': any,
++            'ord': ord,
++            'chr': chr,
++            'cmp': cmp,
++            'divmod': divmod,
++            'isinstance': isinstance,
 +            'range': range,
++            'xrange': xrange,
++            'zip': zip,
 +        }
      )
 +    if locals_builtins:
 +        if locals_dict is None:
 +            locals_dict = {}
 +        locals_dict.update(globals_dict.get('__builtins__'))
      c = test_expr(expr, _SAFE_OPCODES, mode=mode)
      try:
          return eval(c, globals_dict, locals_dict)