[MERGE] forward port of branch 7.0 up to 33e0910
authorDenis Ledoux <dle@odoo.com>
Tue, 9 Sep 2014 16:02:30 +0000 (18:02 +0200)
committerDenis Ledoux <dle@odoo.com>
Tue, 9 Sep 2014 16:02:30 +0000 (18:02 +0200)
1  2 
addons/hr_expense/hr_expense.py
addons/mail/mail_thread.py
addons/stock/stock_view.xml
addons/web/controllers/main.py
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
openerp/addons/base/ir/ir_attachment.py
openerp/tools/mail.py

Simple merge
Simple merge
Simple merge
@@@ -91,85 -85,55 +91,85 @@@ def rjsmin(script)
      ).strip()
      return result
  
 -def db_list(req, force=False):
 -    proxy = req.session.proxy("db")
 -    dbs = proxy.list(force)
 -    h = req.httprequest.environ['HTTP_HOST'].split(':')[0]
 -    d = h.split('.')[0]
 -    r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
 -    dbs = [i for i in dbs if re.match(r, i)]
 -    return dbs
 -
 -def db_monodb_redirect(req):
 -    return db_redirect(req, not config['list_db'])
 -
 -def db_redirect(req, match_first_only_if_unique):
 -    db = False
 -    redirect = False
 -
 -    dbs = db_list(req, True)
 -
 -    # 1 try the db in the url
 -    db_url = req.params.get('db')
 -    if db_url and db_url in dbs:
 -        return (db_url, False)
 -
 -    # 2 use the database from the cookie if it's listable and still listed
 -    cookie_db = req.httprequest.cookies.get('last_used_database')
 -    if cookie_db in dbs:
 -        db = cookie_db
 -
 -    # 3 use the first db if user can list databases
 -    if dbs and not db and (not match_first_only_if_unique or len(dbs) == 1):
 -        db = dbs[0]
 -
 -    # redirect to the chosen db if multiple are available
 -    if db and len(dbs) > 1:
 -        query = dict(urlparse.parse_qsl(req.httprequest.query_string, keep_blank_values=True))
 -        query.update({'db': db})
 -        redirect = req.httprequest.path + '?' + urllib.urlencode(query)
 -    return (db, redirect)
 -
 -def db_monodb(req):
 -    # if only one db exists, return it else return False
 -    return db_redirect(req, True)[0]
 -
 -def redirect_with_hash(req, url, code=303):
 -    # Most IE and Safari versions decided not to preserve location.hash upon
 -    # redirect. And even if IE10 pretends to support it, it still fails
 -    # inexplicably in case of multiple redirects (and we do have some).
 -    # See extensive test page at http://greenbytes.de/tech/tc/httpredirects/
 -    return "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % url
 +db_list = http.db_list
 +
 +db_monodb = http.db_monodb
 +
 +def serialize_exception(f):
 +    @functools.wraps(f)
 +    def wrap(*args, **kwargs):
 +        try:
 +            return f(*args, **kwargs)
 +        except Exception, e:
 +            _logger.exception("An exception occured during an http request")
 +            se = _serialize_exception(e)
 +            error = {
 +                'code': 200,
 +                'message': "OpenERP Server Error",
 +                'data': se
 +            }
 +            return werkzeug.exceptions.InternalServerError(simplejson.dumps(error))
 +    return wrap
 +
 +def redirect_with_hash(*args, **kw):
 +    """
 +        .. deprecated:: 8.0
 +
 +        Use the ``http.redirect_with_hash()`` function instead.
 +    """
 +    return http.redirect_with_hash(*args, **kw)
 +
 +def ensure_db(redirect='/web/database/selector'):
 +    # This helper should be used in web client auth="none" routes
 +    # if those routes needs a db to work with.
 +    # If the heuristics does not find any database, then the users will be
 +    # redirected to db selector or any url specified by `redirect` argument.
 +    # If the db is taken out of a query parameter, it will be checked against
 +    # `http.db_filter()` in order to ensure it's legit and thus avoid db
 +    # forgering that could lead to xss attacks.
 +    db = request.params.get('db')
 +
 +    # Ensure db is legit
 +    if db and db not in http.db_filter([db]):
 +        db = None
 +
 +    if db and not request.session.db:
 +        # User asked a specific database on a new session.
 +        # That mean the nodb router has been used to find the route
 +        # Depending on installed module in the database, the rendering of the page
 +        # may depend on data injected by the database route dispatcher.
 +        # Thus, we redirect the user to the same page but with the session cookie set.
 +        # This will force using the database route dispatcher...
 +        r = request.httprequest
 +        url_redirect = r.base_url
 +        if r.query_string:
 +            # Can't use werkzeug.wrappers.BaseRequest.url with encoded hashes:
 +            # https://github.com/amigrave/werkzeug/commit/b4a62433f2f7678c234cdcac6247a869f90a7eb7
 +            url_redirect += '?' + r.query_string
 +        response = werkzeug.utils.redirect(url_redirect, 302)
 +        request.session.db = db
 +        response = r.app.get_response(r, response, explicit_session=False)
 +        werkzeug.exceptions.abort(response)
 +        return
 +
 +    # if db not provided, use the session one
-     if not db:
++    if not db and http.db_filter([request.session.db]):
 +        db = request.session.db
 +
 +    # if no database provided and no database in session, use monodb
 +    if not db:
 +        db = db_monodb(request.httprequest)
 +
 +    # if no db can be found til here, send to the database selector
 +    # the database selector will redirect to database manager if needed
 +    if not db:
 +        werkzeug.exceptions.abort(werkzeug.utils.redirect(redirect, 303))
 +
 +    # always switch the session to the computed db
 +    if db != request.session.db:
 +        request.session.logout()
 +
 +    request.session.db = db
  
  def module_topological_sort(modules):
      """ Return a list of module names sorted so that their dependencies of the
@@@ -611,98 -562,52 +611,100 @@@ html_template = """<!DOCTYPE html
  </html>
  """
  
 -class Home(openerpweb.Controller):
 -    _cp_path = '/'
 +def render_bootstrap_template(db, template, values=None, debug=False, lazy=False, **kw):
 +    if request and request.debug:
 +        debug = True
 +    if values is None:
 +        values = {}
 +    values.update(kw)
 +    values['debug'] = debug
 +    values['current_db'] = db
 +    try:
 +        values['databases'] = http.db_list()
 +    except openerp.exceptions.AccessDenied:
 +        values['databases'] = None
  
 -    @openerpweb.httprequest
 -    def index(self, req, s_action=None, db=None, **kw):
 -        db, redir = db_monodb_redirect(req)
 -        if redir:
 -            return redirect_with_hash(req, redir)
 +    for res in ['js', 'css']:
 +        if res not in values:
 +            values[res] = manifest_list(res, db=db, debug=debug)
  
 -        js = "\n        ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list(req, 'js', db=db))
 -        css = "\n        ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list(req, 'css', db=db))
 +    if 'modules' not in values:
 +        values['modules'] = module_boot(db=db)
 +    values['modules'] = simplejson.dumps(values['modules'])
  
 -        r = html_template % {
 -            'js': js,
 -            'css': css,
 -            'modules': simplejson.dumps(module_boot(req, db=db)),
 -            'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
 -        }
 -        return r
 +    def callback(template, values):
 +        registry = openerp.modules.registry.RegistryManager.get(db)
 +        with registry.cursor() as cr:
 +            view_obj = registry["ir.ui.view"]
 +            uid = request.uid or openerp.SUPERUSER_ID
 +            return view_obj.render(cr, uid, template, values)
 +    if lazy:
 +        return LazyResponse(callback, template=template, values=values)
 +    else:
 +        return callback(template, values)
 +
 +class Home(http.Controller):
 +
 +    @http.route('/', type='http', auth="none")
 +    def index(self, s_action=None, db=None, **kw):
 +        return http.local_redirect('/web', query=request.params, keep_hash=True)
  
 -    @openerpweb.httprequest
 -    def login(self, req, db, login, key):
 -        if db not in db_list(req, True):
 +    @http.route('/web', type='http', auth="none")
 +    def web_client(self, s_action=None, **kw):
 +        ensure_db()
 +
 +        if request.session.uid:
 +            if kw.get('redirect'):
 +                return werkzeug.utils.redirect(kw.get('redirect'), 303)
 +
 +            html = render_bootstrap_template(request.session.db, "web.webclient_bootstrap")
 +            return request.make_response(html, {'Cache-Control': 'no-cache', 'Content-Type': 'text/html; charset=utf-8'})
 +        else:
 +            return login_redirect()
 +
 +    @http.route('/web/login', type='http', auth="none")
 +    def web_login(self, redirect=None, **kw):
 +        ensure_db()
 +
 +        if request.httprequest.method == 'GET' and redirect and request.session.uid:
 +            return http.redirect_with_hash(redirect)
 +
 +        values = request.params.copy()
 +        if not redirect:
 +            redirect = '/web?' + request.httprequest.query_string
 +        values['redirect'] = redirect
 +        if request.httprequest.method == 'POST':
 +            uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password'])
 +            if uid is not False:
 +                return http.redirect_with_hash(redirect)
 +            values['error'] = "Wrong login/password"
 +        return render_bootstrap_template(request.session.db, 'web.login', values, lazy=True)
 +
 +    @http.route('/login', type='http', auth="none")
 +    def login(self, db, login, key, redirect="/web", **kw):
++        if not http.db_filter([db]):
+             return werkzeug.utils.redirect('/', 303)
 -        return login_and_redirect(req, db, login, key)
 +        return login_and_redirect(db, login, key, redirect_url=redirect)
  
 -class WebClient(openerpweb.Controller):
 -    _cp_path = "/web/webclient"
 +class WebClient(http.Controller):
  
 -    @openerpweb.jsonrequest
 -    def csslist(self, req, mods=None):
 -        return manifest_list(req, 'css', mods=mods)
 +    @http.route('/web/webclient/csslist', type='json', auth="none")
 +    def csslist(self, mods=None):
 +        return manifest_list('css', mods=mods)
  
 -    @openerpweb.jsonrequest
 -    def jslist(self, req, mods=None):
 -        return manifest_list(req, 'js', mods=mods)
 +    @http.route('/web/webclient/jslist', type='json', auth="none")
 +    def jslist(self, mods=None):
 +        return manifest_list('js', mods=mods)
  
 -    @openerpweb.jsonrequest
 -    def qweblist(self, req, mods=None):
 -        return manifest_list(req, 'qweb', mods=mods)
 +    @http.route('/web/webclient/qweblist', type='json', auth="none")
 +    def qweblist(self, mods=None):
 +        return manifest_list('qweb', mods=mods)
  
 -    @openerpweb.httprequest
 -    def css(self, req, mods=None, db=None):
 -        files = list(manifest_glob(req, 'css', addons=mods, db=db))
 +    @http.route('/web/webclient/css', type='http', auth="none")
 +    def css(self, mods=None, db=None):
 +        files = list(manifest_glob('css', addons=mods, db=db))
          last_modified = get_last_modified(f[0] for f in files)
 -        if req.httprequest.if_modified_since and req.httprequest.if_modified_since >= last_modified:
 +        if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified:
              return werkzeug.wrappers.Response(status=304)
  
          file_map = dict(files)
    font-size: 95%;
    line-height: 1.2em;
  }
 -.openerp .oe_debug_view_log label {
 -  display: block;
 -  width: 49%;
 -  text-align: right;
 -  float: left;
 +.openerp .navbar {
 +  min-height: 32px;
 +  margin-bottom: 0px;
 +  border: none;
 +  z-index: 1;
 +  position: static;
 +  background-color: #414141;
 +  background-color: #454343;
 +  background-image: -webkit-gradient(linear, left top, left bottom, from(#646060), to(#262626));
 +  background-image: -webkit-linear-gradient(top, #646060, #262626);
 +  background-image: -moz-linear-gradient(top, #646060, #262626);
 +  background-image: -ms-linear-gradient(top, #646060, #262626);
 +  background-image: -o-linear-gradient(top, #646060, #262626);
 +  background-image: linear-gradient(to bottom, #646060, #262626);
 +}
 +.openerp .navbar-default .navbar-nav li a:hover, .openerp .navbar-default .navbar-nav li a:focus {
 +  background: rgba(0, 0, 0, 0.3);
 +}
 +.openerp .navbar-default .navbar-nav .open > a, .openerp .navbar-default .navbar-nav a:hover, .openerp .navbar-default .navbar-nav a:focus {
 +  background: rgba(0, 0, 0, 0.3) !important;
 +}
 +.openerp .navbar-default .navbar-nav .dropdown > a .caret {
 +  border-top-color: #777777 !important;
 +  border-bottom-color: #777777 !important;
 +}
 +.openerp .navbar-nav li a {
 +  padding: 4px 32px 4px 12px;
 +}
 +.openerp .oe_navbar .dropdown-menu {
 +  font-size: 13px;
 +  padding: 4px 0;
 +  background: #333333 !important;
 +  background: rgba(37, 37, 37, 0.9) !important;
 +  border-color: #999999;
 +  border-color: rgba(0, 0, 0, 0.2);
 +  background-color: #414141;
 +  text-shadow: none;
 +  background-color: #454343;
 +  background-image: -webkit-gradient(linear, left top, left bottom, from(#646060), to(#262626));
 +  background-image: -webkit-linear-gradient(top, #646060, #262626);
 +  background-image: -moz-linear-gradient(top, #646060, #262626);
 +  background-image: -ms-linear-gradient(top, #646060, #262626);
 +  background-image: -o-linear-gradient(top, #646060, #262626);
 +  background-image: linear-gradient(to bottom, #646060, #262626);
 +  -moz-border-radius: 3px;
 +  -webkit-border-radius: 3px;
 +  border-radius: 3px;
 +}
 +.openerp .oe_navbar .dropdown-menu li a, .openerp .oe_navbar .dropdown-menu li a:hover, .openerp .oe_navbar .dropdown-menu li a:focus {
 +  color: #eeeeee;
 +}
 +.openerp .oe_view_manager_new .oe_form_nosheet {
 +  margin-top: 8px;
 +}
 +.openerp .oe_view_manager_new .oe_form_nosheet .oe_form_label {
 +  font-weight: normal;
 +}
 +.openerp .nav li > a {
 +  padding: 3px 4px 2px 18px;
 +  color: #4c4c4c;
 +}
 +.openerp .nav nav-pills.nav-stacked > li > ul {
 +  padding-left: 16px;
 +}
 +.openerp .nav-pills > li.active > a, .openerp a.list-group-item.active > a {
 +  background-color: #7c7bad;
 +  color: white;
 +  border-radius: 0;
 +}
 +.openerp .nav-pills > li.active a:hover, .openerp .nav-pills > li.active a:focus, .openerp a.list-group-item.active a:hover, .openerp a.list-group-item.active a:focus {
 +  background-color: #7c7bad;
 +}
 +.openerp .nav-pills > li.active .badge, .openerp a.list-group-item.active .badge {
 +  background-color: white;
 +  color: #7c7bad;
 +  text-shadow: none;
 +}
 +.openerp .badge {
 +  font-weight: normal;
 +  font-size: 11px;
 +  background-color: #7c7bad;
 +}
 +.openerp button, .openerp body {
 +  line-height: normal;
 +}
 +.openerp h1, .openerp h2 {
    font-weight: bold;
 -  color: #000099;
  }
 -.openerp .oe_debug_view_log span {
 +.openerp h3 {
 +  font-size: 1.17em;
 +  font-weight: bold;
 +}
 +.openerp p {
    display: block;
 -  width: 49%;
 -  float: right;
 -  color: #333333;
 +  -webkit-margin-before: 1em;
 +  -webkit-margin-after: 1em;
 +  -webkit-margin-start: 0px;
 +  -webkit-margin-end: 0px;
 +}
 +.openerp pre {
 +  background-color: white;
 +  border: none;
 +  padding: 10px 0 3px 0;
 +}
 +.openerp h5 {
 +  font-weight: bold;
 +  font-size: smaller;
 +}
 +.openerp .oe_form .oe_subtype label, .openerp .oe_subtype label {
 +  font-weight: normal;
 +}
 +.openerp .oe_msg_subtype_check {
 +  margin: 3px 3px 0 !important;
  }
  
 +.jqstooltip {
 +  height: auto !important;
 +  width: auto !important;
 +  padding: 0;
 +}
 +
 +@-moz-document url-prefix() {
 +  .openerp .oe_searchview .oe_searchview_search {
 +    top: -1px;
 +  }
 +  .openerp .oe_form_field_many2one .oe_m2o_cm_button {
 +    line-height: 18px;
 +  }
 +  .openerp .oe_webclient .oe_star_on, .openerp .oe_webclient .oe_star_off {
 +    top: 0px;
 +  }
 +}
  .kitten-mode-activated {
    background-size: cover;
    background-attachment: fixed;
Simple merge
@@@ -206,9 -205,11 +206,11 @@@ class ir_attachment(osv.osv)
          for model, mids in res_ids.items():
              # ignore attachments that are not attached to a resource anymore when checking access rights
              # (resource was deleted but attachment was not)
+             if not self.pool.get(model):
+                 continue
 -            mids = self.pool.get(model).exists(cr, uid, mids)
 +            mids = self.pool[model].exists(cr, uid, mids)
              ima.check(cr, uid, model, mode)
 -            self.pool.get(model).check_access_rule(cr, uid, mids, mode, context=context)
 +            self.pool[model].check_access_rule(cr, uid, mids, mode, context=context)
  
      def _search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
          ids = super(ir_attachment, self)._search(cr, uid, args, offset=offset,
@@@ -597,13 -312,8 +597,13 @@@ command_re = re.compile("^Set-([a-z]+) 
  # Updated in 7.0 to match the model name as well
  # Typical form of references is <timestamp-openerp-record_id-model_name@domain>
  # group(1) = the record ID ; group(2) = the model (if any) ; group(3) = the domain
- reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?.*@(.*)>", re.UNICODE)
+ reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?[^>]*@([^>]*)>", re.UNICODE)
  
 +# Bounce regex
 +# Typical form of bounce is bounce-128-crm.lead-34@domain
 +# group(1) = the mail ID; group(2) = the model (if any); group(3) = the record ID
 +bounce_re = re.compile("[\w]+-(\d+)-?([\w.]+)?-?(\d+)?", re.UNICODE)
 +
  def generate_tracking_message_id(res_id):
      """Returns a string that can be used in the Message-ID RFC822 header field