[IMP] change placeholder inclusion logic
[odoo/odoo.git] / addons / web / controllers / main.py
index 9f43b4c..cfe25bb 100644 (file)
@@ -28,9 +28,10 @@ except ImportError:
     xlwt = None
 
 import openerp
+import openerp.modules.registry
+from openerp.tools.translate import _
 
 from .. import http
-from .. import nonliterals
 openerpweb = http
 
 #----------------------------------------------------------
@@ -169,7 +170,6 @@ def module_installed_bypass_session(dbname):
     loadable = openerpweb.addons_manifest.keys()
     modules = {}
     try:
-        import openerp.modules.registry
         registry = openerp.modules.registry.RegistryManager.get(dbname)
         with registry.cursor() as cr:
             m = registry.get('ir.module.module')
@@ -345,7 +345,13 @@ def make_conditional(req, response, last_modified=None, etag=None):
     return response.make_conditional(req.httprequest)
 
 def login_and_redirect(req, db, login, key, redirect_url='/'):
-    req.session.authenticate(db, login, key, {})
+    wsgienv = req.httprequest.environ
+    env = dict(
+        base_location=req.httprequest.url_root.rstrip('/'),
+        HTTP_HOST=wsgienv['HTTP_HOST'],
+        REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
+    )
+    req.session.authenticate(db, login, key, env)
     return set_cookie_and_redirect(req, redirect_url)
 
 def set_cookie_and_redirect(req, redirect_url):
@@ -442,39 +448,6 @@ def fix_view_modes(action):
 
     return action
 
-def parse_domain(domain, session):
-    """ Parses an arbitrary string containing a domain, transforms it
-    to either a literal domain or a :class:`nonliterals.Domain`
-
-    :param domain: the domain to parse, if the domain is not a string it
-                   is assumed to be a literal domain and is returned as-is
-    :param session: Current OpenERP session
-    :type session: openerpweb.OpenERPSession
-    """
-    if not isinstance(domain, basestring):
-        return domain
-    try:
-        return ast.literal_eval(domain)
-    except ValueError:
-        # not a literal
-        return nonliterals.Domain(session, domain)
-
-def parse_context(context, session):
-    """ Parses an arbitrary string containing a context, transforms it
-    to either a literal context or a :class:`nonliterals.Context`
-
-    :param context: the context to parse, if the context is not a string it
-           is assumed to be a literal domain and is returned as-is
-    :param session: Current OpenERP session
-    :type session: openerpweb.OpenERPSession
-    """
-    if not isinstance(context, basestring):
-        return context
-    try:
-        return ast.literal_eval(context)
-    except ValueError:
-        return nonliterals.Context(session, context)
-
 def _local_web_translations(trans_file):
     messages = []
     try:
@@ -549,22 +522,19 @@ html_template = """<!DOCTYPE html>
     </head>
     <body>
         <!--[if lte IE 8]>
-        <script src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
-        <script>
-            var test = function() {
-                CFInstall.check({
-                    mode: "overlay"
-                });
-            };
-            if (window.localStorage && false) {
-                if (! localStorage.getItem("hasShownGFramePopup")) {
-                    test();
-                    localStorage.setItem("hasShownGFramePopup", true);
-                }
-            } else {
-                test();
-            }
-        </script>
+            <script src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
+            <script>CFInstall.check({mode: "overlay"});</script>
+        <![endif]-->
+        
+         <!--[if lte IE 9]>
+            <script src="/web/static/lib/jquery.placeholder/jquery.placeholder.min.js"></script>
+            <script>
+                document.addEventListener("DOMNodeInserted",function(event){
+                    if ( $(event.target).is("input") || $(event.target).is("textarea") ) {
+                    $(event.target).placeholder();
+                    }
+                });    
+            </script>
         <![endif]-->
     </body>
 </html>
@@ -709,7 +679,7 @@ class WebClient(openerpweb.Controller):
         messages = ir_translation.search_read([('module','in',mods),('lang','=',lang),
                                                ('comments','like','openerp-web'),('value','!=',False),
                                                ('value','!=','')],
-                                              ['module','src','value','lang'], order='module') 
+                                              ['module','src','value','lang'], order='module')
         for mod, msg_group in itertools.groupby(messages, key=operator.itemgetter('module')):
             translations_per_module.setdefault(mod,{'messages':[]})
             translations_per_module[mod]['messages'].extend({'id': m['src'],
@@ -720,9 +690,7 @@ class WebClient(openerpweb.Controller):
 
     @openerpweb.jsonrequest
     def version_info(self, req):
-        return {
-            "version": openerp.release.version
-        }
+        return openerp.service.web_services.RPC_VERSION_1
 
 class Proxy(openerpweb.Controller):
     _cp_path = '/web/proxy'
@@ -790,7 +758,7 @@ class Database(openerpweb.Controller):
         except xmlrpclib.Fault, e:
             if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
                 return {'error': e.faultCode, 'title': 'Drop Database'}
-        return {'error': 'Could not drop database !', 'title': 'Drop Database'}
+        return {'error': _('Could not drop database !'), 'title': _('Drop Database')}
 
     @openerpweb.httprequest
     def backup(self, req, backup_db, backup_pwd, token):
@@ -808,7 +776,7 @@ class Database(openerpweb.Controller):
                {'fileToken': int(token)}
             )
         except xmlrpclib.Fault, e:
-            return simplejson.dumps([[],[{'error': e.faultCode, 'title': 'backup Database'}]])
+            return simplejson.dumps([[],[{'error': e.faultCode, 'title': _('Backup Database')}]])
 
     @openerpweb.httprequest
     def restore(self, req, db_file, restore_pwd, new_db):
@@ -829,8 +797,8 @@ class Database(openerpweb.Controller):
             return req.session.proxy("db").change_admin_password(old_password, new_password)
         except xmlrpclib.Fault, e:
             if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
-                return {'error': e.faultCode, 'title': 'Change Password'}
-        return {'error': 'Error, password not changed !', 'title': 'Change Password'}
+                return {'error': e.faultCode, 'title': _('Change Password')}
+        return {'error': _('Error, password not changed !'), 'title': _('Change Password')}
 
 class Session(openerpweb.Controller):
     _cp_path = "/web/session"
@@ -840,9 +808,9 @@ class Session(openerpweb.Controller):
         return {
             "session_id": req.session_id,
             "uid": req.session._uid,
-            "context": req.session.get_context() if req.session._uid else {},
+            "user_context": req.session.get_context() if req.session._uid else {},
             "db": req.session._db,
-            "login": req.session._login,
+            "username": req.session._login,
         }
 
     @openerpweb.jsonrequest
@@ -866,16 +834,16 @@ class Session(openerpweb.Controller):
         old_password, new_password,confirm_password = operator.itemgetter('old_pwd', 'new_password','confirm_pwd')(
                 dict(map(operator.itemgetter('name', 'value'), fields)))
         if not (old_password.strip() and new_password.strip() and confirm_password.strip()):
-            return {'error':'All passwords have to be filled.','title': 'Change Password'}
+            return {'error':_('You cannot leave any password empty.'),'title': _('Change Password')}
         if new_password != confirm_password:
-            return {'error': 'The new password and its confirmation must be identical.','title': 'Change Password'}
+            return {'error': _('The new password and its confirmation must be identical.'),'title': _('Change Password')}
         try:
             if req.session.model('res.users').change_password(
                 old_password, new_password):
                 return {'new_password':new_password}
         except Exception:
-            return {'error': 'Original password incorrect, your password was not changed.', 'title': 'Change Password'}
-        return {'error': 'Error, password not changed !', 'title': 'Change Password'}
+            return {'error': _('The old password you provided is incorrect, your password was not changed.'), 'title': _('Change Password')}
+        return {'error': _('Error, password not changed !'), 'title': _('Change Password')}
 
     @openerpweb.jsonrequest
     def sc_list(self, req):
@@ -887,7 +855,7 @@ class Session(openerpweb.Controller):
         try:
             return req.session.proxy("db").list_lang() or []
         except Exception, e:
-            return {"error": e, "title": "Languages"}
+            return {"error": e, "title": _("Languages")}
 
     @openerpweb.jsonrequest
     def modules(self, req):
@@ -947,14 +915,7 @@ class Menu(openerpweb.Controller):
     _cp_path = "/web/menu"
 
     @openerpweb.jsonrequest
-    def load(self, req):
-        return {'data': self.do_load(req)}
-
-    @openerpweb.jsonrequest
     def get_user_roots(self, req):
-        return self.do_get_user_roots(req)
-
-    def do_get_user_roots(self, req):
         """ Return all root menu ids visible for the session user.
 
         :param req: A request object, with an OpenERP session attribute
@@ -970,14 +931,15 @@ class Menu(openerpweb.Controller):
 
         menu_domain = [('parent_id', '=', False)]
         if user_menu_id:
-            domain_string = s.model('ir.actions.act_window').read([user_menu_id[0]], ['domain'],
-                                                                  req.context)[0]['domain']
+            domain_string = s.model('ir.actions.act_window').read(
+                [user_menu_id[0]], ['domain'],req.context)[0]['domain']
             if domain_string:
                 menu_domain = ast.literal_eval(domain_string)
 
         return Menus.search(menu_domain, 0, False, False, req.context)
 
-    def do_load(self, req):
+    @openerpweb.jsonrequest
+    def load(self, req):
         """ Loads all menu items (all applications and their sub-menus).
 
         :param req: A request object, with an OpenERP session attribute
@@ -987,24 +949,28 @@ class Menu(openerpweb.Controller):
         """
         Menus = req.session.model('ir.ui.menu')
 
-        fields = ['name', 'sequence', 'parent_id', 'action',
-                  'needaction_enabled', 'needaction_counter']
-        menu_roots = Menus.read(self.do_get_user_roots(req), fields, req.context)
+        fields = ['name', 'sequence', 'parent_id', 'action']
+        menu_root_ids = self.get_user_roots(req)
+        menu_roots = Menus.read(menu_root_ids, fields, req.context) if menu_root_ids else []
         menu_root = {
             'id': False,
             'name': 'root',
             'parent_id': [-1, ''],
-            'children': menu_roots
+            'children': menu_roots,
+            'all_menu_ids': menu_root_ids,
         }
+        if not menu_roots:
+            return menu_root
 
         # menus are loaded fully unlike a regular tree view, cause there are a
         # limited number of items (752 when all 6.1 addons are installed)
-        menu_ids = Menus.search([], 0, False, False, req.context)
+        menu_ids = Menus.search([('id', 'child_of', menu_root_ids)], 0, False, False, req.context)
         menu_items = Menus.read(menu_ids, fields, req.context)
         # adds roots at the end of the sequence, so that they will overwrite
         # equivalent menu items from full menu read when put into id:item
         # mapping, resulting in children being correctly set on the roots.
         menu_items.extend(menu_roots)
+        menu_root['all_menu_ids'] = menu_ids # includes menu_root_ids!
 
         # make a tree using parent_id
         menu_items_map = dict(
@@ -1026,7 +992,17 @@ class Menu(openerpweb.Controller):
         return menu_root
 
     @openerpweb.jsonrequest
+    def load_needaction(self, req, menu_ids):
+        """ Loads needaction counters for specific menu ids.
+
+            :return: needaction data
+            :rtype: dict(menu_id: {'needaction_enabled': boolean, 'needaction_counter': int})
+        """
+        return req.session.model('ir.ui.menu').get_needaction_data(menu_ids, req.context)
+
+    @openerpweb.jsonrequest
     def action(self, req, menu_id):
+        # still used by web_shortcut
         actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
                                              [('ir.ui.menu', menu_id)], False)
         return {"action": actions}
@@ -1092,43 +1068,22 @@ class DataSet(openerpweb.Controller):
 
     def _call_kw(self, req, model, method, args, kwargs):
         # Temporary implements future display_name special field for model#read()
-        if method == 'read' and kwargs.get('context') and kwargs['context'].get('future_display_name'):
+        if method == 'read' and kwargs.get('context', {}).get('future_display_name'):
             if 'display_name' in args[1]:
-                names = req.session.model(model).name_get(args[0], **kwargs)
+                names = dict(req.session.model(model).name_get(args[0], **kwargs))
                 args[1].remove('display_name')
-                r = getattr(req.session.model(model), method)(*args, **kwargs)
-                for i in range(len(r)):
-                    r[i]['display_name'] = names[i][1] or "%s#%d" % (model, names[i][0])
-                return r
+                records = req.session.model(model).read(*args, **kwargs)
+                for record in records:
+                    record['display_name'] = \
+                        names.get(record['id']) or "%s#%d" % (model, (record['id']))
+                return records
 
         return getattr(req.session.model(model), method)(*args, **kwargs)
 
     @openerpweb.jsonrequest
-    def onchange(self, req, model, method, args, context_id=None):
-        """ Support method for handling onchange calls: behaves much like call
-        with the following differences:
-
-        * Does not take a domain_id
-        * Is aware of the return value's structure, and will parse the domains
-          if needed in order to return either parsed literal domains (in JSON)
-          or non-literal domain instances, allowing those domains to be used
-          from JS
-
-        :param req:
-        :type req: web.common.http.JsonRequest
-        :param str model: object type on which to call the method
-        :param str method: name of the onchange handler method
-        :param list args: arguments to call the onchange handler with
-        :param int context_id: index of the context object in the list of
-                               arguments
-        :return: result of the onchange call with all domains parsed
-        """
-        return self._call_kw(req, model, method, args, {})
-
-    @openerpweb.jsonrequest
     def call(self, req, model, method, args, domain_id=None, context_id=None):
         return self._call_kw(req, model, method, args, {})
-    
+
     @openerpweb.jsonrequest
     def call_kw(self, req, model, method, args, kwargs):
         return self._call_kw(req, model, method, args, kwargs)
@@ -1170,68 +1125,6 @@ class DataSet(openerpweb.Controller):
 class View(openerpweb.Controller):
     _cp_path = "/web/view"
 
-    def fields_view_get(self, req, model, view_id, view_type,
-                        transform=True, toolbar=False, submenu=False):
-        Model = req.session.model(model)
-        fvg = Model.fields_view_get(view_id, view_type, req.context, toolbar, submenu)
-        # todo fme?: check that we should pass the evaluated context here
-        self.process_view(req.session, fvg, req.context, transform, (view_type == 'kanban'))
-        if toolbar and transform:
-            self.process_toolbar(req, fvg['toolbar'])
-        return fvg
-
-    def process_view(self, session, fvg, context, transform, preserve_whitespaces=False):
-        # depending on how it feels, xmlrpclib.ServerProxy can translate
-        # XML-RPC strings to ``str`` or ``unicode``. ElementTree does not
-        # enjoy unicode strings which can not be trivially converted to
-        # strings, and it blows up during parsing.
-
-        # So ensure we fix this retardation by converting view xml back to
-        # bit strings.
-        if isinstance(fvg['arch'], unicode):
-            arch = fvg['arch'].encode('utf-8')
-        else:
-            arch = fvg['arch']
-        fvg['arch_string'] = arch
-
-        if transform:
-            evaluation_context = session.evaluation_context(context or {})
-            xml = self.transform_view(arch, session, evaluation_context)
-        else:
-            xml = ElementTree.fromstring(arch)
-        fvg['arch'] = xml2json_from_elementtree(xml, preserve_whitespaces)
-
-        if 'id' in fvg['fields']:
-            # Special case for id's
-            id_field = fvg['fields']['id']
-            id_field['original_type'] = id_field['type']
-            id_field['type'] = 'id'
-
-        for field in fvg['fields'].itervalues():
-            if field.get('views'):
-                for view in field["views"].itervalues():
-                    self.process_view(session, view, None, transform)
-            if field.get('domain'):
-                field["domain"] = parse_domain(field["domain"], session)
-            if field.get('context'):
-                field["context"] = parse_context(field["context"], session)
-
-    def process_toolbar(self, req, toolbar):
-        """
-        The toolbar is a mapping of section_key: [action_descriptor]
-
-        We need to clean all those actions in order to ensure correct
-        round-tripping
-        """
-        for actions in toolbar.itervalues():
-            for action in actions:
-                if 'context' in action:
-                    action['context'] = parse_context(
-                        action['context'], req.session)
-                if 'domain' in action:
-                    action['domain'] = parse_domain(
-                        action['domain'], req.session)
-
     @openerpweb.jsonrequest
     def add_custom(self, req, view_id, arch):
         CustomView = req.session.model('ir.ui.view.custom')
@@ -1255,44 +1148,6 @@ class View(openerpweb.Controller):
             return {'result': True}
         return {'result': False}
 
-    def transform_view(self, view_string, session, context=None):
-        # transform nodes on the fly via iterparse, instead of
-        # doing it statically on the parsing result
-        parser = ElementTree.iterparse(StringIO(view_string), events=("start",))
-        root = None
-        for event, elem in parser:
-            if event == "start":
-                if root is None:
-                    root = elem
-                self.parse_domains_and_contexts(elem, session)
-        return root
-
-    def parse_domains_and_contexts(self, elem, session):
-        """ Converts domains and contexts from the view into Python objects,
-        either literals if they can be parsed by literal_eval or a special
-        placeholder object if the domain or context refers to free variables.
-
-        :param elem: the current node being parsed
-        :type param: xml.etree.ElementTree.Element
-        :param session: OpenERP session object, used to store and retrieve
-                        non-literal objects
-        :type session: openerpweb.openerpweb.OpenERPSession
-        """
-        for el in ['domain', 'filter_domain']:
-            domain = elem.get(el, '').strip()
-            if domain:
-                elem.set(el, parse_domain(domain, session))
-                elem.set(el + '_string', domain)
-        for el in ['context', 'default_get']:
-            context_string = elem.get(el, '').strip()
-            if context_string:
-                elem.set(el, parse_context(context_string, session))
-                elem.set(el + '_string', context_string)
-
-    @openerpweb.jsonrequest
-    def load(self, req, model, view_id, view_type, toolbar=False):
-        return self.fields_view_get(req, model, view_id, view_type, toolbar=toolbar)
-
 class TreeView(View):
     _cp_path = "/web/treeview"
 
@@ -1342,7 +1197,7 @@ class Binary(openerpweb.Controller):
                     if width > 500: width = 500
                     if height > 500: height = 500
                     image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG')
-            
+
             image_data = base64.b64decode(image_base64)
 
         except (TypeError, xmlrpclib.Fault):
@@ -1355,9 +1210,10 @@ class Binary(openerpweb.Controller):
         except:
             pass
         return req.make_response(image_data, headers)
-    def placeholder(self, req):
+
+    def placeholder(self, req, image='placeholder.png'):
         addons_path = openerpweb.addons_manifest['web']['addons_path']
-        return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
+        return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read()
 
     @openerpweb.httprequest
     def saveas(self, req, model, field, id=None, filename_field=None, **kw):
@@ -1399,6 +1255,7 @@ class Binary(openerpweb.Controller):
         jdata = simplejson.loads(data)
         model = jdata['model']
         field = jdata['field']
+        data = jdata['data']
         id = jdata.get('id', None)
         filename_field = jdata.get('filename_field', None)
         context = jdata.get('context', {})
@@ -1407,13 +1264,15 @@ class Binary(openerpweb.Controller):
         fields = [field]
         if filename_field:
             fields.append(filename_field)
-        if id:
+        if data:
+            res = { field: data }
+        elif id:
             res = Model.read([int(id)], fields, context)[0]
         else:
             res = Model.default_get(fields, context)
         filecontent = base64.b64decode(res.get(field, ''))
         if not filecontent:
-            raise ValueError("No content found for field '%s' on '%s:%s'" %
+            raise ValueError(_("No content found for field '%s' on '%s:%s'") %
                 (field, model, id))
         else:
             filename = '%s_%s' % (model.replace('.', '_'), id)
@@ -1427,11 +1286,11 @@ class Binary(openerpweb.Controller):
     @openerpweb.httprequest
     def upload(self, req, callback, ufile):
         # TODO: might be useful to have a configuration flag for max-length file uploads
+        out = """<script language="javascript" type="text/javascript">
+                    var win = window.top.window;
+                    win.jQuery(win).trigger(%s, %s);
+                </script>"""
         try:
-            out = """<script language="javascript" type="text/javascript">
-                        var win = window.top.window;
-                        win.jQuery(win).trigger(%s, %s);
-                    </script>"""
             data = ufile.read()
             args = [len(data), ufile.filename,
                     ufile.content_type, base64.b64encode(data)]
@@ -1442,11 +1301,11 @@ class Binary(openerpweb.Controller):
     @openerpweb.httprequest
     def upload_attachment(self, req, callback, model, id, ufile):
         Model = req.session.model('ir.attachment')
+        out = """<script language="javascript" type="text/javascript">
+                    var win = window.top.window;
+                    win.jQuery(win).trigger(%s, %s);
+                </script>"""
         try:
-            out = """<script language="javascript" type="text/javascript">
-                        var win = window.top.window;
-                        win.jQuery(win).trigger(%s, %s);
-                    </script>"""
             attachment_id = Model.create({
                 'name': ufile.filename,
                 'datas': base64.encodestring(ufile.read()),
@@ -1458,10 +1317,39 @@ class Binary(openerpweb.Controller):
                 'filename': ufile.filename,
                 'id':  attachment_id
             }
-        except Exception, e:
-            args = { 'error': e.message }
+        except xmlrpclib.Fault, e:
+            args = {'error':e.faultCode }
         return out % (simplejson.dumps(callback), simplejson.dumps(args))
 
+    @openerpweb.httprequest
+    def company_logo(self, req, dbname=None):
+        # TODO add etag, refactor to use /image code for etag
+        uid = None
+        if req.session._db:
+            dbname = req.session._db
+            uid = req.session._uid
+        elif dbname is None:
+            dbname = db_monodb(req)
+
+        if uid is None:
+            uid = openerp.SUPERUSER_ID
+
+        if not dbname:
+            image_data = self.placeholder(req, 'logo.png')
+        else:
+            registry = openerp.modules.registry.RegistryManager.get(dbname)
+            with registry.cursor() as cr:
+                user = registry.get('res.users').browse(cr, uid, uid)
+                if user.company_id.logo_web:
+                    image_data = user.company_id.logo_web.decode('base64')
+                else:
+                    image_data = self.placeholder(req, 'nologo.png')
+        headers = [
+            ('Content-Type', 'image/png'),
+            ('Content-Length', len(image_data)),
+        ]
+        return req.make_response(image_data, headers)
+
 class Action(openerpweb.Controller):
     _cp_path = "/web/action"
 
@@ -1488,7 +1376,7 @@ class Action(openerpweb.Controller):
             ctx.update(req.context)
             action = req.session.model(action_type).read([action_id], False, ctx)
             if action:
-                value = clean_action(req, action[0], do_not_eval)
+                value = clean_action(req, action[0])
         return value
 
     @openerpweb.jsonrequest
@@ -1589,6 +1477,8 @@ class Export(View):
     def fields_info(self, req, model, export_fields):
         info = {}
         fields = self.fields_get(req, model)
+        if ".id" in export_fields:
+            fields['.id'] = fields.pop('id', {'string': 'ID'})
 
         # To make fields retrieval more efficient, fetch all sub-fields of a
         # given field at the same time. Because the order in the export list is