[MERGE] forward port of branch 8.0 up to ed1c173
[odoo/odoo.git] / addons / website / controllers / main.py
index db9c4bd..0f08a9e 100644 (file)
@@ -3,12 +3,15 @@ import cStringIO
 import datetime
 from itertools import islice
 import json
+import xml.etree.ElementTree as ET
+
 import logging
 import re
 
 from sys import maxint
 
 import werkzeug.utils
+import urllib2
 import werkzeug.wrappers
 from PIL import Image
 
@@ -32,14 +35,15 @@ class Website(openerp.addons.web.controllers.main.Home):
         page = 'homepage'
         try:
             main_menu = request.registry['ir.model.data'].get_object(request.cr, request.uid, 'website', 'main_menu')
+        except Exception:
+            pass
+        else:
             first_menu = main_menu.child_id and main_menu.child_id[0]
             if first_menu:
                 if not (first_menu.url.startswith(('/page/', '/?', '/#')) or (first_menu.url=='/')):
                     return request.redirect(first_menu.url)
                 if first_menu.url.startswith('/page/'):
-                    page = first_menu[6:]
-        except:
-            pass
+                    return request.registry['ir.http'].reroute(first_menu.url)
         return self.page(page)
 
     @http.route(website=True, auth="public")
@@ -47,13 +51,15 @@ class Website(openerp.addons.web.controllers.main.Home):
         # TODO: can't we just put auth=public, ... in web client ?
         return super(Website, self).web_login(*args, **kw)
 
-    @http.route('/page/<path:page>', type='http', auth="public", website=True)
+    @http.route('/page/<page:page>', type='http', auth="public", website=True)
     def page(self, page, **opt):
         values = {
             'path': page,
         }
-        # allow shortcut for /page/<website_xml_id>
-        if '.' not in page:
+        # /page/website.XXX --> /page/XXX
+        if page.startswith('website.'):
+            return request.redirect('/page/' + page[8:], code=301)
+        elif '.' not in page:
             page = 'website.%s' % page
 
         try:
@@ -135,6 +141,22 @@ class Website(openerp.addons.web.controllers.main.Home):
 
         return request.make_response(content, [('Content-Type', mimetype)])
 
+    @http.route('/website/info', type='http', auth="public", website=True)
+    def website_info(self):
+        try:
+            request.website.get_template('website.info').name
+        except Exception, e:
+            return request.registry['ir.http']._handle_exception(e, 404)
+        irm = request.env()['ir.module.module'].sudo()
+        apps = irm.search([('state','=','installed'),('application','=',True)])
+        modules = irm.search([('state','=','installed'),('application','=',False)])
+        values = {
+            'apps': apps,
+            'modules': modules,
+            'version': openerp.service.common.exp_version()
+        }
+        return request.render('website.info', values)
+
     #------------------------------------------------------
     # Edit
     #------------------------------------------------------
@@ -155,28 +177,6 @@ class Website(openerp.addons.web.controllers.main.Home):
             return werkzeug.wrappers.Response(url, mimetype='text/plain')
         return werkzeug.utils.redirect(url)
 
-    @http.route('/website/theme_change', type='http', auth="user", website=True)
-    def theme_change(self, theme_id=False, **kwargs):
-        imd = request.registry['ir.model.data']
-        view = request.registry['ir.ui.view']
-
-        view_model, view_option_id = imd.get_object_reference(
-            request.cr, request.uid, 'website', 'theme')
-        views = view.search(
-            request.cr, request.uid, [('inherit_id', '=', view_option_id)],
-            context=request.context)
-        view.write(request.cr, request.uid, views, {'inherit_id': False},
-                   context=request.context)
-
-        if theme_id:
-            module, xml_id = theme_id.split('.')
-            view_model, view_id = imd.get_object_reference(
-                request.cr, request.uid, module, xml_id)
-            view.write(request.cr, request.uid, [view_id],
-                       {'inherit_id': view_option_id}, context=request.context)
-
-        return request.render('website.themes', {'theme_changed': True})
-
     @http.route(['/website/snippets'], type='json', auth="public", website=True)
     def snippets(self):
         return request.website._render('website.snippets')
@@ -187,71 +187,68 @@ class Website(openerp.addons.web.controllers.main.Home):
         modules_to_update = []
         for temp_id in templates:
             view = request.registry['ir.ui.view'].browse(request.cr, request.uid, int(temp_id), context=request.context)
+            if view.page:
+                continue
             view.model_data_id.write({
                 'noupdate': False
             })
             if view.model_data_id.module not in modules_to_update:
                 modules_to_update.append(view.model_data_id.module)
-        module_obj = request.registry['ir.module.module']
-        module_ids = module_obj.search(request.cr, request.uid, [('name', 'in', modules_to_update)], context=request.context)
-        module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
-        return request.redirect(redirect)
 
-    @http.route('/website/customize_template_toggle', type='json', auth='user', website=True)
-    def customize_template_set(self, view_id):
-        view_obj = request.registry.get("ir.ui.view")
-        view = view_obj.browse(request.cr, request.uid, int(view_id),
-                               context=request.context)
-        if view.inherit_id:
-            value = False
-        else:
-            value = view.inherit_option_id and view.inherit_option_id.id or False
-        view_obj.write(request.cr, request.uid, [view_id], {
-            'inherit_id': value
-        }, context=request.context)
-        return True
+        if modules_to_update:
+            module_obj = request.registry['ir.module.module']
+            module_ids = module_obj.search(request.cr, request.uid, [('name', 'in', modules_to_update)], context=request.context)
+            if module_ids:
+                module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
+        return request.redirect(redirect)
 
     @http.route('/website/customize_template_get', type='json', auth='user', website=True)
-    def customize_template_get(self, xml_id, optional=True):
+    def customize_template_get(self, xml_id, full=False, bundles=False):
+        """ Lists the templates customizing ``xml_id``. By default, only
+        returns optional templates (which can be toggled on and off), if
+        ``full=True`` returns all templates customizing ``xml_id``
+        ``bundles=True`` returns also the asset bundles
+        """
         imd = request.registry['ir.model.data']
         view_model, view_theme_id = imd.get_object_reference(
             request.cr, request.uid, 'website', 'theme')
 
-        user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, request.context)
+        user = request.registry['res.users']\
+            .browse(request.cr, request.uid, request.uid, request.context)
         user_groups = set(user.groups_id)
 
-        view = request.registry["ir.ui.view"]
-        views = view._views_get(request.cr, request.uid, xml_id, context=request.context)
+        views = request.registry["ir.ui.view"]\
+            ._views_get(request.cr, request.uid, xml_id, bundles=bundles, context=dict(request.context or {}, active_test=False))
         done = set()
         result = []
         for v in views:
             if not user_groups.issuperset(v.groups_id):
                 continue
-            if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional:
-                if v.inherit_option_id.id not in done:
+            if full or (v.customize_show and v.inherit_id.id != view_theme_id):
+                if v.inherit_id not in done:
                     result.append({
-                        'name': v.inherit_option_id.name,
+                        'name': v.inherit_id.name,
                         'id': v.id,
                         'xml_id': v.xml_id,
                         'inherit_id': v.inherit_id.id,
                         'header': True,
                         'active': False
                     })
-                    done.add(v.inherit_option_id.id)
+                    done.add(v.inherit_id)
                 result.append({
                     'name': v.name,
                     'id': v.id,
                     'xml_id': v.xml_id,
                     'inherit_id': v.inherit_id.id,
                     'header': False,
-                    'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
+                    'active': v.active,
                 })
         return result
 
     @http.route('/website/get_view_translations', type='json', auth='public', website=True)
     def get_view_translations(self, xml_id, lang=None):
         lang = lang or request.context.get('lang')
-        views = self.customize_template_get(xml_id, optional=False)
+        views = self.customize_template_get(xml_id, full=True)
         views_ids = [view.get('id') for view in views if view.get('active')]
         domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
         irt = request.registry.get('ir.translation')
@@ -297,46 +294,51 @@ class Website(openerp.addons.web.controllers.main.Home):
 
     @http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True)
     def attach(self, func, upload=None, url=None):
-        Attachments = request.registry['ir.attachment']
-
-        website_url = message = None
-        if not upload:
-            website_url = url
-            name = url.split("/").pop()
+        # the upload argument doesn't allow us to access the files if more than
+        # one file is uploaded, as upload references the first file
+        # therefore we have to recover the files from the request object
+        Attachments = request.registry['ir.attachment']  # registry for the attachment table
+
+        uploads = []
+        message = None
+        if not upload: # no image provided, storing the link and the image name
+            uploads.append({'website_url': url})
+            name = url.split("/").pop()                       # recover filename
             attachment_id = Attachments.create(request.cr, request.uid, {
                 'name':name,
                 'type': 'url',
                 'url': url,
                 'res_model': 'ir.ui.view',
             }, request.context)
-        else:
+        else:                                                  # images provided
             try:
-                image_data = upload.read()
-                image = Image.open(cStringIO.StringIO(image_data))
-                w, h = image.size
-                if w*h > 42e6: # Nokia Lumia 1020 photo resolution
-                    raise ValueError(
-                        u"Image size excessive, uploaded images must be smaller "
-                        u"than 42 million pixel")
-
-                attachment_id = Attachments.create(request.cr, request.uid, {
-                    'name': upload.filename,
-                    'datas': image_data.encode('base64'),
-                    'datas_fname': upload.filename,
-                    'res_model': 'ir.ui.view',
-                }, request.context)
-
-                [attachment] = Attachments.read(
-                    request.cr, request.uid, [attachment_id], ['website_url'],
-                    context=request.context)
-                website_url = attachment['website_url']
+                for c_file in request.httprequest.files.getlist('upload'):
+                    image_data = c_file.read()
+                    image = Image.open(cStringIO.StringIO(image_data))
+                    w, h = image.size
+                    if w*h > 42e6: # Nokia Lumia 1020 photo resolution
+                        raise ValueError(
+                            u"Image size excessive, uploaded images must be smaller "
+                            u"than 42 million pixel")
+    
+                    attachment_id = Attachments.create(request.cr, request.uid, {
+                        'name': c_file.filename,
+                        'datas': image_data.encode('base64'),
+                        'datas_fname': c_file.filename,
+                        'res_model': 'ir.ui.view',
+                    }, request.context)
+    
+                    [attachment] = Attachments.read(
+                        request.cr, request.uid, [attachment_id], ['website_url'],
+                        context=request.context)
+                    uploads.append(attachment)
             except Exception, e:
                 logger.exception("Failed to upload image to attachment")
                 message = unicode(e)
 
         return """<script type='text/javascript'>
             window.parent['%s'](%s, %s);
-        </script>""" % (func, json.dumps(website_url), json.dumps(message))
+        </script>""" % (func, json.dumps(uploads), json.dumps(message))
 
     @http.route(['/website/publish'], type='json', auth="public", website=True)
     def publish(self, id, object):
@@ -353,6 +355,69 @@ class Website(openerp.addons.web.controllers.main.Home):
         obj = _object.browse(request.cr, request.uid, _id)
         return bool(obj.website_published)
 
+    @http.route(['/website/seo_suggest/<keywords>'], type='http', auth="public", website=True)
+    def seo_suggest(self, keywords):
+        url = "http://google.com/complete/search"
+        try:
+            req = urllib2.Request("%s?%s" % (url, werkzeug.url_encode({
+                'ie': 'utf8', 'oe': 'utf8', 'output': 'toolbar', 'q': keywords})))
+            request = urllib2.urlopen(req)
+        except (urllib2.HTTPError, urllib2.URLError):
+            return []
+        xmlroot = ET.fromstring(request.read())
+        return json.dumps([sugg[0].attrib['data'] for sugg in xmlroot if len(sugg) and sugg[0].attrib['data']])
+
+    #------------------------------------------------------
+    # Themes
+    #------------------------------------------------------
+
+    def get_view_ids(self, xml_ids):
+        ids = []
+        imd = request.registry['ir.model.data']
+        for xml_id in xml_ids:
+            if "." in xml_id:
+                xml = xml_id.split(".")
+                view_model, id = imd.get_object_reference(request.cr, request.uid, xml[0], xml[1])
+            else:
+                id = int(xml_id)
+            ids.append(id)
+        return ids
+
+    @http.route(['/website/theme_customize_get'], type='json', auth="public", website=True)
+    def theme_customize_get(self, xml_ids):
+        view = request.registry["ir.ui.view"]
+        enable = []
+        disable = []
+        ids = self.get_view_ids(xml_ids)
+        context = dict(request.context or {}, active_test=True)
+        for v in view.browse(request.cr, request.uid, ids, context=context):
+            if v.active:
+                enable.append(v.xml_id)
+            else:
+                disable.append(v.xml_id)
+        return [enable, disable]
+
+    @http.route(['/website/theme_customize'], type='json', auth="public", website=True)
+    def theme_customize(self, enable, disable):
+        """ enable or Disable lists of ``xml_id`` of the inherit templates
+        """
+        cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
+        view = pool["ir.ui.view"]
+
+        def set_active(ids, active):
+            if ids:
+                view.write(cr, uid, ids, {'active': active}, context=dict(request.context or {}, active_test=True))
+
+        set_active(disable, False)
+        set_active(enable, True)
+
+        return True
+
+    @http.route(['/website/theme_customize_reload'], type='http', auth="public", website=True)
+    def theme_customize_reload(self, href, enable, disable):
+        self.theme_customize(enable and enable.split(",") or [],disable and disable.split(",") or [])
+        return request.redirect(href + ("&theme=true" if "#" in href else "#theme=true"))
+
     #------------------------------------------------------
     # Helpers
     #------------------------------------------------------
@@ -365,9 +430,12 @@ class Website(openerp.addons.web.controllers.main.Home):
 
     @http.route([
         '/website/image',
-        '/website/image/<model>/<id>/<field>'
+        '/website/image/<xmlid>',
+        '/website/image/<xmlid>/<field>',
+        '/website/image/<model>/<id>/<field>',
+        '/website/image/<model>/<id>/<field>/<int:max_width>x<int:max_height>'
         ], auth="public", website=True)
-    def website_image(self, model, id, field, max_width=None, max_height=None):
+    def website_image(self, model=None, id=None, field=None, xmlid=None, max_width=None, max_height=None):
         """ Fetches the requested field and ensures it does not go above
         (max_width, max_height), resizing it if necessary.
 
@@ -381,11 +449,30 @@ class Website(openerp.addons.web.controllers.main.Home):
 
         The requested field is assumed to be base64-encoded image data in
         all cases.
+
+        xmlid can be used to load the image. But the field image must by base64-encoded
         """
-        response = werkzeug.wrappers.Response()
-        return request.registry['website']._image(
-                    request.cr, request.uid, model, id, field, response)
+        if xmlid and "." in xmlid:
+            xmlid = xmlid.split(".", 1)
+            try:
+                model, id = request.registry['ir.model.data'].get_object_reference(request.cr, request.uid, xmlid[0], xmlid[1])
+            except:
+                raise werkzeug.exceptions.NotFound()
+            if model == 'ir.attachment':
+                field = "datas"
 
+        if not model or not id or not field:
+            raise werkzeug.exceptions.NotFound()
+
+        try:
+            response = werkzeug.wrappers.Response()
+            return request.registry['website']._image(
+                request.cr, request.uid, model, id, field, response, max_width, max_height)
+        except Exception:
+            logger.exception("Cannot render image field %r of record %s[%s] at size(%s,%s)",
+                             field, model, id, max_width, max_height)
+            response = werkzeug.wrappers.Response()
+            return self.placeholder(response)
 
     #------------------------------------------------------
     # Server actions
@@ -417,7 +504,7 @@ class Website(openerp.addons.web.controllers.main.Home):
             action = ServerActions.browse(cr, uid, action_id, context=context)
             if action.state == 'code' and action.website_published:
                 action_res = ServerActions.run(cr, uid, [action_id], context=context)
-                if isinstance(action_res, Response):
+                if isinstance(action_res, werkzeug.wrappers.Response):
                     res = action_res
         if res:
             return res