[MIG] website: migration to new api

Migration of website module to new API.
The tricky phase is ir_http.py : we need to use `request.env`
only when the authenfication phase is done. Normally by
calling `super` of `_dispatch` method, but website module
required it to be done before. This is important since
`env`is a lazy property of `request` object.

Some hack were kept since this commit is a migration ('RequestUID'
in ir.http, ...)

Some docstrings were added.
This commit is contained in:
Ravi Gadhia
2016-08-04 12:02:14 +02:00
committed by Jérome Maes
parent 7c20b4785b
commit fd09ddb6f3
23 changed files with 761 additions and 759 deletions
+3
View File
@@ -1,2 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import backend
import main
+3 -8
View File
@@ -1,13 +1,8 @@
# -*- coding: utf-8 -*-
from openerp import http
from openerp.http import request
from dateutil.relativedelta import relativedelta
from datetime import datetime, date, timedelta
from math import floor
import time
import operator
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
from odoo import http
from odoo.http import request
class WebsiteBackend(http.Controller):
+109 -125
View File
@@ -1,22 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from itertools import islice
import json
import xml.etree.ElementTree as ET
import logging
import re
import werkzeug.utils
import urllib2
import werkzeug.utils
import werkzeug.wrappers
import openerp
from openerp.addons.web.controllers.main import WebClient, Binary
from openerp import http
from openerp.http import request
from openerp.osv.orm import browse_record
from openerp.addons.website.models.website import slug
import odoo
from odoo import http
from odoo import fields
from odoo.http import request
from odoo.osv.orm import browse_record
from odoo.addons.website.models.website import slug
from odoo.addons.web.controllers.main import WebClient, Binary, Home
logger = logging.getLogger(__name__)
@@ -34,8 +36,8 @@ class QueryURL(object):
def __call__(self, path=None, path_args=None, **kw):
path = path or self.path
for k, v in self.args.items():
kw.setdefault(k, v)
for key, value in self.args.items():
kw.setdefault(key, value)
path_args = set(path_args or []).union(self.path_args)
paths, fragments = [], []
for key, value in kw.items():
@@ -56,40 +58,40 @@ class QueryURL(object):
return path
class Website(openerp.addons.web.controllers.main.Home):
#------------------------------------------------------
# View
#------------------------------------------------------
class Website(Home):
@http.route('/', type='http', auth="public", website=True)
def index(self, **kw):
page = 'homepage'
try:
main_menu = request.env.ref('website.main_menu')
except Exception:
pass
else:
main_menu = request.env.ref('website.main_menu', raise_if_not_found=False)
if main_menu:
first_menu = main_menu.child_id and main_menu.child_id[0]
if first_menu:
if first_menu.url and (not (first_menu.url.startswith(('/page/', '/?', '/#')) or (first_menu.url == '/'))):
return request.redirect(first_menu.url)
if first_menu.url and first_menu.url.startswith('/page/'):
return request.registry['ir.http'].reroute(first_menu.url)
return request.env['ir.http'].reroute(first_menu.url)
return self.page(page)
#------------------------------------------------------
# Login - overwrite of the web login so that regular users are redirected to the backend
# Login - overwrite of the web login so that regular users are redirected to the backend
# while portal users are redirected to the frontend by default
#------------------------------------------------------
@http.route(website=True, auth="public")
def web_login(self, redirect=None, *args, **kw):
r = super(Website, self).web_login(redirect=redirect, *args, **kw)
response = super(Website, self).web_login(redirect=redirect, *args, **kw)
if not redirect and request.params['login_success']:
if request.registry['res.users'].has_group(request.cr, request.uid, 'base.group_user'):
if request.env.user.has_group('base.group_user'):
redirect = '/web?' + request.httprequest.query_string
else:
redirect = '/'
return http.redirect_with_hash(redirect)
return r
return response
#------------------------------------------------------
# Business
#------------------------------------------------------
@http.route('/website/lang/<lang>', type='http', auth="public", website=True, multilang=False)
def change_lang(self, lang, r='/', **kwargs):
@@ -104,7 +106,7 @@ class Website(openerp.addons.web.controllers.main.Home):
def page(self, page, **opt):
values = {
'path': page,
'deletable': True, # used to add 'delete this page' in content menu
'deletable': True, # used to add 'delete this page' in content menu
}
# /page/website.XXX --> /page/XXX
if page.startswith('website.'):
@@ -120,7 +122,7 @@ class Website(openerp.addons.web.controllers.main.Home):
values.pop('deletable')
page = 'website.page_404'
else:
return request.registry['ir.http']._handle_exception(e, 404)
return request.env['ir.http']._handle_exception(e, 404)
return request.render(page, values)
@@ -135,35 +137,32 @@ class Website(openerp.addons.web.controllers.main.Home):
@http.route('/sitemap.xml', type='http', auth="public", website=True)
def sitemap_xml_index(self):
cr, uid, context = request.cr, openerp.SUPERUSER_ID, request.context
ira = request.registry['ir.attachment']
iuv = request.registry['ir.ui.view']
mimetype ='application/xml;charset=utf-8'
Attachment = request.env['ir.attachment'].sudo()
View = request.env['ir.ui.view'].sudo()
mimetype = 'application/xml;charset=utf-8'
content = None
def create_sitemap(url, content):
return ira.create(cr, uid, dict(
datas=content.encode('base64'),
mimetype=mimetype,
type='binary',
name=url,
url=url,
), context=context)
return Attachment.create({
'datas': content.encode('base64'),
'mimetype': mimetype,
'type': 'binary',
'name': url,
'url': url,
})
sitemap = ira.search_read(cr, uid, [('url', '=' , '/sitemap.xml'), ('type', '=', 'binary')], ('datas', 'create_date'), context=context)
sitemap = Attachment.search([('url', '=', '/sitemap.xml'), ('type', '=', 'binary')], limit=1)
if sitemap:
# Check if stored version is still valid
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
create_date = datetime.datetime.strptime(sitemap[0]['create_date'], server_format)
create_date = fields.Datetime.from_string(sitemap.create_date)
delta = datetime.datetime.now() - create_date
if delta < SITEMAP_CACHE_TIME:
content = sitemap[0]['datas'].decode('base64')
content = sitemap.datas.decode('base64')
if not content:
# Remove all sitemaps in ir.attachments as we're going to regenerated them
sitemap_ids = ira.search(cr, uid, [('url', '=like' , '/sitemap%.xml'), ('type', '=', 'binary')], context=context)
if sitemap_ids:
ira.unlink(cr, uid, sitemap_ids, context=context)
sitemaps = Attachment.search([('url', '=like', '/sitemap%.xml'), ('type', '=', 'binary')])
sitemaps.unlink()
pages = 0
locs = request.website.sudo(user=request.website.user_id.id).enumerate_pages()
@@ -172,23 +171,27 @@ class Website(openerp.addons.web.controllers.main.Home):
'locs': islice(locs, 0, LOC_PER_SITEMAP),
'url_root': request.httprequest.url_root[:-1],
}
urls = iuv.render_template(cr, uid, 'website.sitemap_locs', values, context=context)
urls = View.render_template('website.sitemap_locs', values)
if urls.strip():
content = iuv.render_template(cr, uid, 'website.sitemap_xml', dict(content=urls), context=context)
content = View.render_template('website.sitemap_xml', {'content': urls})
pages += 1
last = create_sitemap('/sitemap-%d.xml' % pages, content)
last_sitemap = create_sitemap('/sitemap-%d.xml' % pages, content)
else:
break
if not pages:
return request.not_found()
elif pages == 1:
ira.write(cr, uid, last, dict(url="/sitemap.xml", name="/sitemap.xml"), context=context)
last_sitemap.write({
'url': "/sitemap.xml",
'name': "/sitemap.xml"
})
else:
# Sitemaps must be split in several smaller files with a sitemap index
content = iuv.render_template(cr, uid, 'website.sitemap_index_xml', dict(
pages=range(1, pages + 1),
url_root=request.httprequest.url_root,
), context=context)
content = View.render_template('website.sitemap_index_xml', {
'pages': range(1, pages + 1),
'url_root': request.httprequest.url_root,
})
create_sitemap('/sitemap.xml', content)
return request.make_response(content, [('Content-Type', mimetype)])
@@ -198,34 +201,34 @@ class Website(openerp.addons.web.controllers.main.Home):
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)])
return request.env['ir.http']._handle_exception(e, 404)
Module = request.env['ir.module.module'].sudo()
apps = Module.search([('state', '=', 'installed'), ('application', '=', True)])
modules = Module.search([('state', '=', 'installed'), ('application', '=', False)])
values = {
'apps': apps,
'modules': modules,
'version': openerp.service.common.exp_version()
'version': odoo.service.common.exp_version()
}
return request.render('website.info', values)
#------------------------------------------------------
# Edit
#------------------------------------------------------
@http.route('/website/add/<path:path>', type='http', auth="user", website=True)
def pagenew(self, path, noredirect=False, add_menu=None, template=False):
if template:
xml_id = request.registry['website'].new_page(request.cr, request.uid, path, template=template, context=request.context)
xml_id = request.env['website'].new_page(path, template=template)
else:
xml_id = request.registry['website'].new_page(request.cr, request.uid, path, context=request.context)
xml_id = request.env['website'].new_page(path)
if add_menu:
request.registry['website.menu'].create(
request.cr, request.uid, {
'name': path,
'url': "/page/" + xml_id[8:],
'parent_id': request.website.menu_id.id,
'website_id': request.website.id,
}, context=request.context)
request.env['website.menu'].create({
'name': path,
'url': "/page/" + xml_id[8:],
'parent_id': request.website.menu_id.id,
'website_id': request.website.id,
})
# Reverse action in order to allow shortcut for /page/<website_xml_id>
url = "/page/" + re.sub(r"^website\.", '', xml_id)
@@ -242,7 +245,7 @@ class Website(openerp.addons.web.controllers.main.Home):
templates = request.httprequest.form.getlist('templates')
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)
view = request.env['ir.ui.view'].browse(int(temp_id))
if view.page:
continue
view.model_data_id.write({
@@ -252,8 +255,7 @@ class Website(openerp.addons.web.controllers.main.Home):
modules_to_update.append(view.model_data_id.module)
if modules_to_update:
module_obj = request.env['ir.module.module'].sudo()
modules = module_obj.search([('name', 'in', modules_to_update)])
modules = request.env['ir.module.module'].sudo().search([('name', 'in', modules_to_update)])
if modules:
modules.button_immediate_upgrade()
return request.redirect(redirect)
@@ -261,13 +263,11 @@ class Website(openerp.addons.web.controllers.main.Home):
@http.route('/website/customize_template_get', type='json', auth='user', website=True)
def customize_template_get(self, key, full=False, bundles=False):
""" Get inherit view's informations of the template ``key``. By default, only
returns ``customize_show`` templates (which can be active or not), if
``full=True`` returns inherit view's informations of the template ``key``.
``bundles=True`` returns also the asset bundles
:returns ``customize_show`` templates (which can be active or not), if
``full=True`` returns inherit view's informations of the template ``key``.
``bundles=True`` returns also the asset bundles
"""
return request.registry["ir.ui.view"].customize_template_get(
request.cr, request.uid, key, full=full, bundles=bundles,
context=request.context)
return request.env["ir.ui.view"].customize_template_get(key, full=full, bundles=bundles)
@http.route('/website/translations', type='json', auth="public", website=True)
def get_website_translations(self, lang, mods=None):
@@ -282,18 +282,14 @@ class Website(openerp.addons.web.controllers.main.Home):
@http.route(['/website/publish'], type='json', auth="public", website=True)
def publish(self, id, object):
_id = int(id)
_object = request.registry[object]
obj = _object.browse(request.cr, request.uid, _id)
Model = request.env[object]
record = Model.browse(int(id))
values = {}
if 'website_published' in _object._fields:
values['website_published'] = not obj.website_published
_object.write(request.cr, request.uid, [_id],
values, context=request.context)
obj = _object.browse(request.cr, request.uid, _id)
return bool(obj.website_published)
if 'website_published' in Model._fields:
values['website_published'] = not record.website_published
record.write(values)
return bool(record.website_published)
@http.route(['/website/seo_suggest'], type='json', auth="user", website=True)
def seo_suggest(self, keywords=None, lang=None):
@@ -302,10 +298,10 @@ class Website(openerp.addons.web.controllers.main.Home):
try:
req = urllib2.Request("%s?%s" % (url, werkzeug.url_encode({
'ie': 'utf8', 'oe': 'utf8', 'output': 'toolbar', 'q': keywords, 'hl': language[0], 'gl': language[1]})))
request = urllib2.urlopen(req)
response = urllib2.urlopen(req)
except (urllib2.HTTPError, urllib2.URLError):
return []
xmlroot = ET.fromstring(request.read())
xmlroot = ET.fromstring(response.read())
return json.dumps([sugg[0].attrib['data'] for sugg in xmlroot if len(sugg) and sugg[0].attrib['data']])
#------------------------------------------------------
@@ -314,54 +310,46 @@ class Website(openerp.addons.web.controllers.main.Home):
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])
record_id = request.env.ref(xml_id).id
else:
id = int(xml_id)
ids.append(id)
record_id = int(xml_id)
ids.append(record_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)
for view in request.env['ir.ui.view'].with_context(active_test=True).browse(ids):
if view.active:
enable.append(view.xml_id)
else:
disable.append(v.xml_id)
disable.append(view.xml_id)
return [enable, disable]
@http.route(['/website/theme_customize'], type='json', auth="public", website=True)
def theme_customize(self, enable, disable, get_bundle=False):
""" 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"]
context = dict(request.context or {}, active_test=True)
""" enable or Disable lists of ``xml_id`` of the inherit templates """
def set_active(ids, active):
if ids:
view.write(cr, uid, self.get_view_ids(ids), {'active': active}, context=context)
real_ids = self.get_view_ids(ids)
request.env['ir.ui.view'].with_context(active_test=True).browse(real_ids).write({'active': active})
set_active(disable, False)
set_active(enable, True)
if get_bundle:
context = dict(request.context, active_test=True)
return request.env["ir.qweb"]._get_asset('web.assets_frontend', options=context)
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 [])
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"))
@http.route(['/website/multi_render'], type='json', auth="public", website=True)
@@ -375,22 +363,20 @@ class Website(openerp.addons.web.controllers.main.Home):
#------------------------------------------------------
# Server actions
#------------------------------------------------------
@http.route([
'/website/action/<path_or_xml_id_or_id>',
'/website/action/<path_or_xml_id_or_id>/<path:path>',
], type='http', auth="public", website=True)
def actions_server(self, path_or_xml_id_or_id, **post):
cr, uid, context = request.cr, request.uid, request.context
res, action_id, action = None, None, None
ServerActions = request.registry['ir.actions.server']
ServerActions = request.env['ir.actions.server']
# find the action_id: either an xml_id, the path, or an ID
if isinstance(path_or_xml_id_or_id, basestring) and '.' in path_or_xml_id_or_id:
action_id = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, request.uid, path_or_xml_id_or_id, raise_if_not_found=False)
if not action_id:
action_ids = ServerActions.search(cr, uid, [('website_path', '=', path_or_xml_id_or_id), ('website_published', '=', True)], context=context)
action_id = action_ids and action_ids[0] or None
if not action_id:
action = request.env.ref(path_or_xml_id_or_id, raise_if_not_found=False)
if not action:
action = ServerActions.search([('website_path', '=', path_or_xml_id_or_id), ('website_published', '=', True)], limit=1)
if not action:
try:
action_id = int(path_or_xml_id_or_id)
except ValueError:
@@ -398,24 +384,22 @@ class Website(openerp.addons.web.controllers.main.Home):
# check it effectively exists
if action_id:
action_ids = ServerActions.exists(cr, uid, [action_id], context=context)
action_id = action_ids and action_ids[0] or None
action = ServerActions.browse(action_id).exists()
# run it, return only if we got a Response object
if action_id:
action = ServerActions.browse(cr, uid, action_id, context=context)
if action:
if action.state == 'code' and action.website_published:
action_res = ServerActions.run(cr, uid, [action_id], context=context)
action_res = action.run()
if isinstance(action_res, werkzeug.wrappers.Response):
res = action_res
if res:
return res
return action_res
return request.redirect('/')
#------------------------------------------------------
# Retrocompatibility routes
#------------------------------------------------------
class WebsiteBinary(openerp.http.Controller):
class WebsiteBinary(http.Controller):
@http.route([
'/website/image',
'/website/image/<xmlid>',
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<record id="default_website" model="website">
<field name="name">Website localhost</field>
@@ -368,4 +368,4 @@
<field name="url">/web/image/website.s_background_image_01</field>
</record>
</data>
</openerp>
</odoo>
+17 -19
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data>
<record id="default_website" model="website">
@@ -15,28 +15,26 @@
</data>
<data noupdate="1">
<!--
Some demo data for front-end server action generating templates
-->
-->
<template id="template_partner_post" name="Partner Post and Thanks (Website Server Action Demo)">
<t t-call="website.layout">
<t t-set="additional_title">Partner Detail</t>
<div id="wrap">
<div class="oe_structure">
<section>
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Thank you for posting a message !</h2>
</div>
<t t-call="website.layout">
<t t-set="additional_title">Partner Detail</t>
<div id="wrap">
<div class="oe_structure">
<section>
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Thank you for posting a message !</h2>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
</div>
</div>
</t>
</t>
</template>
<record id="action_partner_post" model="ir.actions.server">
@@ -207,4 +205,4 @@ response = request.render("website.template_partner_comment", values)
</record>
</data>
</openerp>
</odoo>
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import ir_actions
+12 -10
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import urlparse
@@ -6,16 +7,16 @@ from odoo import api, fields, models
from odoo.http import request
class actions_server(models.Model):
class ServerAction(models.Model):
""" Add website option in server actions. """
_name = 'ir.actions.server'
_inherit = 'ir.actions.server'
xml_id = fields.Char(compute='_compute_xml_id', string="External ID",
help="ID of the action if defined in a XML file")
website_path = fields.Char()
website_url = fields.Char(compute='_get_website_url', help='The full URL to access the server action through the website.')
website_published = fields.Boolean(string='Available on the Website', copy=False,
xml_id = fields.Char('External ID', compute='_compute_xml_id', help="ID of the action if defined in a XML file")
website_path = fields.Char('Website Path')
website_url = fields.Char('Website Url', compute='_get_website_url', help='The full URL to access the server action through the website.')
website_published = fields.Boolean('Available on the Website', copy=False,
help='A code server action can be executed from the website, using a dedicated '
'controller. The address is <base>/website/action/<website_path>. '
'Set this field as True to allow users to run this action. If it '
@@ -43,7 +44,7 @@ class actions_server(models.Model):
@api.model
def _get_eval_context(self, action):
""" Override to add the request object in eval_context. """
eval_context = super(actions_server, self)._get_eval_context(action)
eval_context = super(ServerAction, self)._get_eval_context(action)
if action.state == 'code':
eval_context['request'] = request
return eval_context
@@ -51,7 +52,8 @@ class actions_server(models.Model):
@api.model
def run_action_code_multi(self, action, eval_context=None):
""" Override to allow returning response the same way action is already
returned by the basic server action behavior. Note that response has
priority over action, avoid using both. """
res = super(actions_server, self).run_action_code_multi(action, eval_context)
returned by the basic server action behavior. Note that response has
priority over action, avoid using both.
"""
res = super(ServerAction, self).run_action_code_multi(action, eval_context)
return eval_context.get('response', res)
+6 -7
View File
@@ -1,13 +1,12 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import openerp
from openerp.osv import osv, fields
from odoo import fields, models
class ir_attachment(osv.osv):
class Attachment(models.Model):
_inherit = "ir.attachment"
_columns = {
'website_url': fields.related("local_url", string="Attachment URL", type='char', deprecated=True), # related for backward compatibility with saas-6
}
# related for backward compatibility with saas-6
website_url = fields.Char(string="Attachment URL", related='local_url', deprecated=True)
+79 -49
View File
@@ -1,67 +1,84 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import os
import re
import time
import traceback
import werkzeug
import werkzeug.routing
import werkzeug.utils
import openerp
from openerp.addons.base import ir
import odoo
from odoo import api, models
from odoo import SUPERUSER_ID
from odoo.http import request
from odoo.tools import config
from odoo.exceptions import QWebException
from openerp.addons.website.models.website import slug, url_for, _UNSLUG_RE
from openerp.http import request
from openerp.tools import config
from openerp.osv import orm
from openerp.tools.safe_eval import safe_eval as eval
from odoo.tools.safe_eval import safe_eval
from odoo.addons.base import ir
from odoo.addons.website.models.website import slug, url_for, _UNSLUG_RE
logger = logging.getLogger(__name__)
class RequestUID(object):
def __init__(self, **kw):
self.__dict__.update(kw)
class ir_http(orm.AbstractModel):
class Http(models.AbstractModel):
_inherit = 'ir.http'
rerouting_limit = 10
_geoip_resolver = None
def _get_converters(self):
""" Get the converters list for custom url pattern werkzeug need to
match Rule. This override adds the website ones.
"""
return dict(
super(ir_http, self)._get_converters(),
super(Http, self)._get_converters(),
model=ModelConverter,
page=PageConverter,
)
def _auth_method_public(self):
""" If no user logged, set the public user of current website, or default
public user as request uid.
After this method `request.env` can be called, since the `request.uid` is
set. The `env` lazy property of `request` will be correct.
"""
if not request.session.uid:
website = self.pool['website'].get_current_website(request.cr, openerp.SUPERUSER_ID, context=request.context)
env = api.Environment(request.cr, SUPERUSER_ID, request.context)
website = env['website'].get_current_website()
if website:
request.uid = website.user_id.id
else:
request.uid = self.pool['ir.model.data'].xmlid_to_res_id(request.cr, openerp.SUPERUSER_ID, 'base', 'public_user')
request.uid = env.ref('base.public_user').id
else:
request.uid = request.session.uid
bots = "bot|crawl|slurp|spider|curl|wget|facebookexternalhit".split("|")
def is_a_bot(self):
# We don't use regexp and ustr voluntarily
# timeit has been done to check the optimum method
ua = request.httprequest.environ.get('HTTP_USER_AGENT', '').lower()
user_agent = request.httprequest.environ.get('HTTP_USER_AGENT', '').lower()
try:
return any(bot in ua for bot in self.bots)
return any(bot in user_agent for bot in self.bots)
except UnicodeDecodeError:
return any(bot in ua.encode('ascii', 'ignore') for bot in self.bots)
return any(bot in user_agent.encode('ascii', 'ignore') for bot in self.bots)
def get_nearest_lang(self, lang):
# Try to find a similar lang. Eg: fr_BE and fr_FR
short = lang.partition('_')[0]
short_match = False
for code, name in request.website.get_languages():
for code, dummy in request.website.get_languages():
if code == lang:
return lang
if not short_match and code.startswith(short):
@@ -94,6 +111,14 @@ class ir_http(orm.AbstractModel):
return (self._name, "cache", request.uid, request.lang, request.httprequest.full_path)
def _dispatch(self):
""" Before executing the endpoint method, add website params on request, such as
- current website (record)
- multilang support (set on cookies)
- geoip dict data are added in the session
Then follow the parent dispatching.
Reminder : Do not use `request.env` before authentication phase, otherwise the env
set on request will be created with uid=None (and it is a lazy property)
"""
first_pass = not hasattr(request, 'website')
request.website = None
func = None
@@ -116,6 +141,7 @@ class ir_http(orm.AbstractModel):
self._geoip_setup_resolver()
self._geoip_resolve()
# For website routes (only), add website params on `request`
cook_lang = request.httprequest.cookies.get('website_lang')
if request.website_enabled:
try:
@@ -127,7 +153,7 @@ class ir_http(orm.AbstractModel):
return self._handle_exception(e)
request.redirect = lambda url, code=302: werkzeug.utils.redirect(url_for(url), code)
request.website = request.registry['website'].get_current_website(request.cr, request.uid, context=request.context)
request.website = request.env['website'].get_current_website() # can use `request.env` since auth methods are called
request.context['website_id'] = request.website.id
langs = [lg[0] for lg in request.website.get_languages()]
path = request.httprequest.path.split('/')
@@ -170,7 +196,7 @@ class ir_http(orm.AbstractModel):
# removed cache for auth public
request.cache_save = False
resp = super(ir_http, self)._dispatch()
resp = super(Http, self)._dispatch()
if request.website_enabled and cook_lang != request.lang and hasattr(resp, 'set_cookie'):
resp.set_cookie('website_lang', request.lang)
@@ -192,11 +218,11 @@ class ir_http(orm.AbstractModel):
return self._dispatch()
def _postprocess_args(self, arguments, rule):
super(ir_http, self)._postprocess_args(arguments, rule)
super(Http, self)._postprocess_args(arguments, rule)
for key, val in arguments.items():
# Replace uid placeholder by the current request.uid
if isinstance(val, orm.BaseModel) and isinstance(val._uid, RequestUID):
if isinstance(val, models.BaseModel) and isinstance(val._uid, RequestUID):
arguments[key] = val.sudo(request.uid)
try:
@@ -219,10 +245,10 @@ class ir_http(orm.AbstractModel):
is_website_request = bool(getattr(request, 'website_enabled', False) and request.website)
if not is_website_request:
# Don't touch non website requests exception handling
return super(ir_http, self)._handle_exception(exception)
return super(Http, self)._handle_exception(exception)
else:
try:
response = super(ir_http, self)._handle_exception(exception)
response = super(Http, self)._handle_exception(exception)
if isinstance(response, Exception):
exception = response
else:
@@ -246,21 +272,20 @@ class ir_http(orm.AbstractModel):
else:
code = exception.code
if isinstance(exception, openerp.exceptions.AccessError):
if isinstance(exception, odoo.exceptions.AccessError):
code = 403
if isinstance(exception, QWebException):
values.update(qweb_exception=exception)
if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
if isinstance(exception.qweb.get('cause'), odoo.exceptions.AccessError):
code = 403
if code == 500:
logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
if 'qweb_exception' in values:
view = request.registry.get("ir.ui.view")
views = view._views_get(request.cr, request.uid, exception.qweb['template'],
context=request.context)
to_reset = [v for v in views if v.model_data_id.noupdate is True and not v.page]
view = request.env["ir.ui.view"]
views = view._views_get(exception.qweb['template'])
to_reset = views.filtered(lambda view: view.model_data_id.noupdate is True and not view.page)
values['views'] = to_reset
elif code == 403:
logger.warn("403 Forbidden:\n\n%s", values['traceback'])
@@ -288,10 +313,12 @@ class ir_http(orm.AbstractModel):
obj = env[model].browse(int(id))
if obj and 'website_published' in obj._fields:
if env[obj._name].sudo().search([('id', '=', obj.id), ('website_published', '=', True)]):
env = env(user=openerp.SUPERUSER_ID)
return super(ir_http, self).binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype=default_mimetype, env=env)
env = env(user=SUPERUSER_ID)
return super(Http, self).binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype=default_mimetype, env=env)
class ModelConverter(ir.ir_http.ModelConverter):
def __init__(self, url_map, model=False, domain='[]'):
super(ModelConverter, self).__init__(url_map, model)
self.domain = domain
@@ -301,43 +328,46 @@ class ModelConverter(ir.ir_http.ModelConverter):
return slug(value)
def to_python(self, value):
m = re.match(self.regex, value)
_uid = RequestUID(value=value, match=m, converter=self)
record_id = int(m.group(2))
matching = re.match(self.regex, value)
_uid = RequestUID(value=value, match=matching, converter=self)
record_id = int(matching.group(2))
env = api.Environment(request.cr, _uid, request.context)
if record_id < 0:
# limited support for negative IDs due to our slug pattern, assume abs() if not found
if not request.registry[self.model].exists(request.cr, _uid, [record_id]):
if not env[self.model].browse(record_id).exists():
record_id = abs(record_id)
return request.registry[self.model].browse(
request.cr, _uid, record_id, context=request.context)
return env[self.model].browse(record_id)
def generate(self, cr, uid, query=None, args=None, context=None):
obj = request.registry[self.model]
domain = eval( self.domain, (args or {}).copy())
def generate(self, query=None, args=None):
Model = request.env[self.model]
domain = safe_eval(self.domain, (args or {}).copy())
if query:
domain.append((obj._rec_name, 'ilike', '%'+query+'%'))
for record in obj.search_read(cr, uid, domain=domain, fields=['write_date',obj._rec_name], context=context):
if record.get(obj._rec_name, False):
yield {'loc': (record['id'], record[obj._rec_name])}
domain.append((Model._rec_name, 'ilike', '%' + query + '%'))
for record in Model.search_read(domain=domain, fields=['write_date', Model._rec_name]):
if record.get(Model._rec_name, False):
yield {'loc': (record['id'], record[Model._rec_name])}
class PageConverter(werkzeug.routing.PathConverter):
""" Only point of this converter is to bundle pages enumeration logic """
def generate(self, cr, uid, query=None, args={}, context=None):
View = request.registry['ir.ui.view']
def generate(self, query=None, args={}):
View = request.env['ir.ui.view']
domain = [('page', '=', True)]
query = query and query.startswith('website.') and query[8:] or query
if query:
domain += [('key', 'like', query)]
views = View.search_read(cr, uid, domain, fields=['key', 'priority', 'write_date'], order='name', context=context)
views = View.search_read(domain, fields=['key', 'priority', 'write_date'], order='name')
for view in views:
xid = view['key'].startswith('website.') and view['key'][8:] or view['key']
# the 'page/homepage' url is indexed as '/', avoid aving the same page referenced twice
# when we will have an url mapping mechanism, replace this by a rule: page/homepage --> /
if xid=='homepage': continue
if xid == 'homepage':
continue
record = {'loc': xid}
if view['priority'] <> 16:
record['__priority'] = min(round(view['priority'] / 32.0,1), 1)
if view['priority'] != 16:
record['__priority'] = min(round(view['priority'] / 32.0, 1), 1)
if view['write_date']:
record['__lastmod'] = view['write_date'][:10]
yield record
+8 -5
View File
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
from openerp.http import request
from openerp.osv import orm
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import ast
from odoo import models
from odoo.http import request
class QWeb(orm.AbstractModel):
""" QWeb object for rendering stuff in the website context
"""
class QWeb(models.AbstractModel):
""" QWeb object for rendering stuff in the website context """
_inherit = 'ir.qweb'
@@ -36,6 +38,7 @@ class QWeb(orm.AbstractModel):
url_att = self.URL_ATTRS.get(el.tag)
cdn_att = self.CDN_TRIGGERS.get(el.tag)
def process(item):
if isinstance(item, tuple) and (item[0] in (url_att, cdn_att)):
return (item[0], ast.Call(
+114 -124
View File
@@ -1,100 +1,96 @@
# -*- coding: ascii -*-
import copy
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from itertools import groupby
from lxml import etree, html
from lxml import etree
from openerp import SUPERUSER_ID, api, tools
from openerp.addons.website.models import website
from openerp.http import request
from openerp.osv import osv, fields
from odoo import api, fields, models
from odoo import tools
from odoo.addons.website.models import website
from odoo.http import request
_logger = logging.getLogger(__name__)
class view(osv.osv):
class View(models.Model):
_name = "ir.ui.view"
_inherit = ["ir.ui.view", "website.seo.metadata"]
_columns = {
'page': fields.boolean("Whether this view is a web page template (complete)"),
'customize_show': fields.boolean("Show As Optional Inherit"),
'website_id': fields.many2one('website', ondelete='cascade', string="Website"),
}
_defaults = {
'page': False,
'customize_show': False,
}
page = fields.Boolean("Whether this view is a web page template (complete)", default=False)
customize_show = fields.Boolean("Show As Optional Inherit", default=False)
website_id = fields.Many2one('website', ondelete='cascade', string="Website")
def unlink(self, cr, uid, ids, context=None):
res = super(view, self).unlink(cr, uid, ids, context=context)
@api.multi
def unlink(self):
result = super(View, self).unlink()
self.clear_caches()
return res
return result
@api.multi
def _sort_suitability_key(self):
""" Key function to sort views by descending suitability
Suitability of a view is defined as follow:
* if the view and request website_id are matched
* then if the view has no set website
"""
Key function to sort views by descending suitability
Suitability of a view is defined as follow:
* if the view and request website_id are matched
* then if the view has no set website
"""
self.ensure_one()
context_website_id = self.env.context.get('website_id', 1)
website_id = self.website_id.id or 0
different_website = context_website_id != website_id
return (different_website, website_id)
def filter_duplicate(self):
"""
Filter current recordset only keeping the most suitable view per distinct key
"""
filtered = self.browse([])
for _, group in groupby(self, key=lambda r:r.key):
filtered += sorted(group, key=lambda r:r._sort_suitability_key())[0]
""" Filter current recordset only keeping the most suitable view per distinct key """
filtered = self.env['ir.ui.view']
for dummy, group in groupby(self, key=lambda record: record.key):
filtered += sorted(group, key=lambda record: record._sort_suitability_key())[0]
return filtered
def _view_obj(self, cr, uid, view_id, context=None):
@api.model
def _view_obj(self, view_id):
if isinstance(view_id, basestring):
if 'website_id' in (context or {}):
domain = [('key', '=', view_id), '|', ('website_id', '=', False), ('website_id', '=', context.get('website_id'))]
rec_id = self.search(cr, uid, domain, order='website_id', context=context)
if 'website_id' in self._context:
domain = [('key', '=', view_id), '|', ('website_id', '=', False), ('website_id', '=', self._context.get('website_id'))]
order = 'website_id'
else:
rec_id = self.search(cr, uid, [('key', '=', view_id)], context=context)
if rec_id:
return self.browse(cr, uid, rec_id, context=context).filter_duplicate()
domain = [('key', '=', view_id)]
order = self._order
views = self.search(domain, order=order)
if views:
return views.filter_duplicate()
else:
return self.pool['ir.model.data'].xmlid_to_object(
cr, uid, view_id, raise_if_not_found=True, context=context)
return self.env.ref(view_id)
elif isinstance(view_id, (int, long)):
return self.browse(cr, uid, view_id, context=context)
return self.browse(view_id)
# assume it's already a view object (WTF?)
return view_id
# Returns all views (called and inherited) related to a view
# Used by translation mechanism, SEO and optional templates
def _views_get(self, cr, uid, view_id, options=True, bundles=False, context=None, root=True):
@api.model
def _views_get(self, view_id, options=True, bundles=False, root=True):
""" For a given view ``view_id``, should return:
* the view itself
* all views inheriting from it, enabled or not
- but not the optional children of a non-enabled child
* all views called from it (via t-call)
* the view itself
* all views inheriting from it, enabled or not
- but not the optional children of a non-enabled child
* all views called from it (via t-call)
:returns recordset of ir.ui.view
"""
try:
view = self._view_obj(cr, uid, view_id, context=context)
view = self._view_obj(view_id)
except ValueError:
_logger.warning("Could not find view object with view_id '%s'" % (view_id))
# Shall we log that ? Yes, you should !
_logger.warning("Could not find view object with view_id '%s'", view_id)
return []
while root and view.inherit_id:
view = view.inherit_id
result = [view]
views_to_return = view
node = etree.fromstring(view.arch)
xpath = "//t[@t-call]"
@@ -102,49 +98,44 @@ class view(osv.osv):
xpath += "| //t[@t-call-assets]"
for child in node.xpath(xpath):
try:
called_view = self._view_obj(cr, uid, child.get('t-call', child.get('t-call-assets')), context=context)
called_view = self._view_obj(child.get('t-call', child.get('t-call-assets')))
except ValueError:
continue
if called_view not in result:
result += self._views_get(cr, uid, called_view, options=options, bundles=bundles, context=context)
if called_view not in views_to_return:
views_to_return += self._views_get(called_view, options=options, bundles=bundles)
extensions = view.inherit_children_ids
if not options:
# only active children
extensions = (v for v in view.inherit_children_ids if v.active)
extensions = view.inherit_children_ids.filtered(lambda view: view.active)
# Keep options in a deterministic order regardless of their applicability
for extension in sorted(extensions, key=lambda v: v.id):
for r in self._views_get(
cr, uid, extension,
# only return optional grandchildren if this child is enabled
options=extension.active,
context=context, root=False):
if r not in result:
result.append(r)
return result
for extension in extensions.sorted(key=lambda v: v.id):
# only return optional grandchildren if this child is enabled
for ext_view in self._views_get(extension, options=extension.active, root=False):
if ext_view not in views_to_return:
views_to_return += ext_view
return views_to_return
@tools.ormcache_context('uid', 'xml_id', keys=('website_id',))
def get_view_id(self, cr, uid, xml_id, context=None):
if context and 'website_id' in context and not isinstance(xml_id, (int, long)):
domain = [('key', '=', xml_id), '|', ('website_id', '=', context['website_id']), ('website_id', '=', False)]
[view_id] = self.search(cr, uid, domain, order='website_id', limit=1, context=context) or [None]
if not view_id:
_logger.warning("Could not find view object with xml_id '%s'" % (xml_id))
raise ValueError('View %r in website %r not found' % (xml_id, context['website_id']))
else:
view_id = super(view, self).get_view_id(cr, uid, xml_id, context=context)
return view_id
@api.model
@tools.ormcache_context('self._uid', 'xml_id', keys=('website_id',))
def get_view_id(self, xml_id):
if 'website_id' in self._context and not isinstance(xml_id, (int, long)):
domain = [('key', '=', xml_id), '|', ('website_id', '=', self._context['website_id']), ('website_id', '=', False)]
view = self.search(domain, order='website_id', limit=1)
if not view:
_logger.warning("Could not find view object with xml_id '%s'", xml_id)
raise ValueError('View %r in website %r not found' % (xml_id, self._context['website_id']))
return view.id
return super(View, self).get_view_id(xml_id)
@api.cr_uid_ids_context
def render(self, cr, uid, id_or_xml_id, values=None, engine='ir.qweb', context=None):
@api.multi
def render(self, values=None, engine='ir.qweb'):
""" Render the template. If website is enabled on request, then extend rendering context with website values. """
new_context = dict(self._context)
if request and getattr(request, 'website_enabled', False):
engine = 'ir.qweb'
if isinstance(id_or_xml_id, list):
id_or_xml_id = id_or_xml_id[0]
qcontext = self._prepare_qcontext(cr, uid, context=context)
qcontext = self._prepare_qcontext()
# add some values
if values:
@@ -153,78 +144,77 @@ class view(osv.osv):
# in edit mode ir.ui.view will tag nodes
if not qcontext.get('translatable') and not qcontext.get('rendering_bundle'):
if qcontext.get('editable'):
context = dict(context, inherit_branding=True)
elif request.registry['res.users'].has_group(cr, uid, 'base.group_website_publisher'):
context = dict(context, inherit_branding_auto=True)
new_context = dict(self._context, inherit_branding=True)
elif request.env.user.has_group('base.group_website_publisher'):
new_context = dict(self._context, inherit_branding_auto=True)
view_obj = request.website.get_template(id_or_xml_id)
if 'main_object' not in qcontext:
qcontext['main_object'] = view_obj
qcontext['main_object'] = self
values = qcontext
return super(view, self).render(cr, uid, id_or_xml_id, values=values, engine=engine, context=context)
return super(View, self.with_context(new_context)).render(values, engine=engine)
def _prepare_qcontext(self, cr, uid, context=None):
if not context:
context = {}
company = self.pool['res.company'].browse(cr, SUPERUSER_ID, request.website.company_id.id, context=context)
@api.model
def _prepare_qcontext(self):
""" Returns the qcontext : rendering context with website specific value (required
to render website layout template)
"""
company = request.website.company_id.sudo()
editable = request.website.is_publisher()
translatable = editable and context.get('lang') != request.website.default_lang_code
translatable = editable and self._context.get('lang') != request.website.default_lang_code
editable = not translatable and editable
qcontext = dict(
context.copy(),
self._context.copy(),
website=request.website,
url_for=website.url_for,
slug=website.slug,
res_company=company,
user_id=self.pool.get("res.users").browse(cr, uid, uid),
user_id=self.env["res.users"].browse(self.env.user.id),
default_lang_code=request.website.default_lang_code,
languages=request.website.get_languages(),
translatable=translatable,
editable=editable,
menu_data=self.pool['ir.ui.menu'].load_menus_root(cr, uid, context=context) if request.website.is_user() else None,
menu_data=self.env['ir.ui.menu'].load_menus_root() if request.website.is_user() else None,
)
return qcontext
def customize_template_get(self, cr, uid, key, full=False, bundles=False, context=None):
@api.model
def customize_template_get(self, key, full=False, bundles=False):
""" Get inherit view's informations of the template ``key``. By default, only
returns ``customize_show`` templates (which can be active or not), if
``full=True`` returns inherit view's informations of the template ``key``.
``bundles=True`` returns also the asset bundles
returns ``customize_show`` templates (which can be active or not), if
``full=True`` returns inherit view's informations of the template ``key``.
``bundles=True`` returns also the asset bundles
"""
imd = self.pool['ir.model.data']
theme_view_id = imd.xmlid_to_res_id(cr, uid, 'website.theme')
user = self.pool['res.users'].browse(cr, uid, context=context)
imd = self.env['ir.model.data']
view_theme_id = imd.xmlid_to_res_id('website.theme')
user = self.env.user
user_groups = set(user.groups_id)
views = self._views_get(
cr, uid, key, bundles=bundles,
context=dict(context or {}, active_test=False))
views = self.with_context(active_test=False)._views_get(key, bundles=bundles)
done = set()
result = []
for v in views:
if not user_groups.issuperset(v.groups_id):
for view in views:
if not user_groups.issuperset(view.groups_id):
continue
if full or (v.customize_show and v.inherit_id.id != theme_view_id):
if v.inherit_id not in done:
if full or (view.customize_show and view.inherit_id.id != view_theme_id):
if view.inherit_id not in done:
result.append({
'name': v.inherit_id.name,
'id': v.id,
'key': v.key,
'inherit_id': v.inherit_id.id,
'name': view.inherit_id.name,
'id': view.id,
'key': view.key,
'inherit_id': view.inherit_id.id,
'header': True,
'active': False
})
done.add(v.inherit_id)
done.add(view.inherit_id)
result.append({
'name': v.name,
'id': v.id,
'key': v.key,
'inherit_id': v.inherit_id.id,
'name': view.name,
'id': view.id,
'key': view.key,
'inherit_id': view.inherit_id.id,
'header': False,
'active': v.active,
'active': view.active,
})
return result
+11 -10
View File
@@ -1,18 +1,19 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import openerp
from openerp.osv import osv, fields
from odoo import api, models
class res_company(osv.osv):
class Company(models.Model):
_inherit = "res.company"
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).partner_id
return partner and partner.google_map_img(zoom, width, height, context=context) or None
@api.multi
def google_map_img(self, zoom=8, width=298, height=298):
partner = self.sudo().partner_id
return partner and partner.google_map_img(zoom, width, height) or None
def google_map_link(self, cr, uid, ids, zoom=8, context=None):
partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).partner_id
return partner and partner.google_map_link(zoom, context=context) or None
@api.multi
def google_map_link(self, zoom=8):
partner = self.sudo().partner_id
return partner and partner.google_map_link(zoom) or None
+27 -44
View File
@@ -1,52 +1,35 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from odoo import fields, models
class WebsiteConfigSettings(models.TransientModel):
class website_config_settings(osv.osv_memory):
_name = 'website.config.settings'
_inherit = 'res.config.settings'
_columns = {
'website_id': fields.many2one('website', string="website", required=True),
'website_name': fields.related('website_id', 'name', type="char", string="Website Name"),
def _default_website(self):
return self.env['website'].search([], limit=1)
'language_ids': fields.related('website_id', 'language_ids', type='many2many', relation='res.lang', string='Languages'),
'default_lang_id': fields.related('website_id', 'default_lang_id', type='many2one', relation='res.lang', string='Default language'),
'default_lang_code': fields.related('website_id', 'default_lang_code', type="char", string="Default language code"),
'google_analytics_key': fields.related('website_id', 'google_analytics_key', type="char", string='Google Analytics Key'),
'social_twitter': fields.related('website_id', 'social_twitter', type="char", string='Twitter Account'),
'social_facebook': fields.related('website_id', 'social_facebook', type="char", string='Facebook Account'),
'social_github': fields.related('website_id', 'social_github', type="char", string='GitHub Account'),
'social_linkedin': fields.related('website_id', 'social_linkedin', type="char", string='LinkedIn Account'),
'social_youtube': fields.related('website_id', 'social_youtube', type="char", string='Youtube Account'),
'social_googleplus': fields.related('website_id', 'social_googleplus', type="char", string='Google+ Account'),
'compress_html': fields.related('website_id', 'compress_html', type="boolean", string='Compress rendered HTML for a better Google PageSpeed result'),
'cdn_activated': fields.related('website_id', 'cdn_activated', type="boolean", string='Use a Content Delivery Network (CDN)'),
'cdn_url': fields.related('website_id', 'cdn_url', type="char", string='CDN Base URL'),
'cdn_filters': fields.related('website_id', 'cdn_filters', type="text", string='CDN Filters'),
'module_website_form_editor': fields.boolean("Form builder: create and customize forms"),
'module_website_version': fields.boolean("A/B testing and versioning"),
'favicon': fields.related('website_id', 'favicon', type="binary", string="Favicon", help="This field holds the image used to display a favicon on the website."),
}
website_id = fields.Many2one('website', string="website", default=_default_website, required=True)
website_name = fields.Char('Website Name', related='website_id.name')
def on_change_website_id(self, cr, uid, ids, website_id, context=None):
if not website_id:
return {'value': {}}
website_data = self.pool.get('website').read(cr, uid, [website_id], [], context=context)[0]
values = {'website_name': website_data['name']}
for fname, v in website_data.items():
if fname in self._columns:
values[fname] = v[0] if v and self._columns[fname]._type == 'many2one' else v
return {'value' : values}
language_ids = fields.Many2many(related='website_id.language_ids', relation='res.lang')
default_lang_id = fields.Many2one(related='website_id.default_lang_id', relation='res.lang')
default_lang_code = fields.Char('Default language code', related='website_id.default_lang_code')
google_analytics_key = fields.Char('Google Analytics Key', related='website_id.google_analytics_key')
# FIXME in trunk for god sake. Change the fields above to fields.char instead of fields.related,
# and create the function set_website who will set the value on the website_id
# create does not forward the values to the related many2one. Write does.
def create(self, cr, uid, vals, context=None):
config_id = super(website_config_settings, self).create(cr, uid, vals, context=context)
self.write(cr, uid, config_id, vals, context=context)
return config_id
_defaults = {
'website_id': lambda self,cr,uid,c: self.pool.get('website').search(cr, uid, [], context=c)[0],
}
social_twitter = fields.Char(related='website_id.social_twitter')
social_facebook = fields.Char(related='website_id.social_facebook')
social_github = fields.Char(related='website_id.social_github')
social_linkedin = fields.Char(related='website_id.social_linkedin')
social_youtube = fields.Char(related='website_id.social_youtube')
social_googleplus = fields.Char(related='website_id.social_googleplus')
compress_html = fields.Boolean('Compress rendered HTML for a better Google PageSpeed result', related='website_id.compress_html')
cdn_activated = fields.Boolean('Use a Content Delivery Network (CDN)', related='website_id.cdn_activated')
cdn_url = fields.Char(related='website_id.cdn_url')
cdn_filters = fields.Text(related='website_id.cdn_filters')
module_website_form_editor = fields.Boolean("Form builde = create and customize forms")
module_website_version = fields.Boolean("A/B testing and versioning")
favicon = fields.Binary('Favicon', related='website_id.favicon')
+12 -12
View File
@@ -1,33 +1,33 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import werkzeug
import openerp
from openerp.osv import orm, osv, fields
from odoo import api, models
def urlplus(url, params):
return werkzeug.Href(url)(params or None)
class res_partner(osv.osv):
class Partner(models.Model):
_inherit = "res.partner"
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
partner = self.browse(cr, uid, ids[0], context=context)
@api.multi
def google_map_img(self, zoom=8, width=298, height=298):
params = {
'center': '%s, %s %s, %s' % (partner.street or '', partner.city or '', partner.zip or '', partner.country_id and partner.country_id.name_get()[0][1] or ''),
'center': '%s, %s %s, %s' % (self.street or '', self.city or '', self.zip or '', self.country_id and self.country_id.name_get()[0][1] or ''),
'size': "%sx%s" % (height, width),
'zoom': zoom,
'sensor': 'false',
}
return urlplus('//maps.googleapis.com/maps/api/staticmap' , params)
return urlplus('//maps.googleapis.com/maps/api/staticmap', params)
def google_map_link(self, cr, uid, ids, zoom=10, context=None):
partner = self.browse(cr, uid, ids[0], context=context)
@api.multi
def google_map_link(self, zoom=10):
params = {
'q': '%s, %s %s, %s' % (partner.street or '', partner.city or '', partner.zip or '', partner.country_id and partner.country_id.name_get()[0][1] or ''),
'q': '%s, %s %s, %s' % (self.street or '', self.city or '', self.zip or '', self.country_id and self.country_id.name_get()[0][1] or ''),
'z': zoom,
}
return urlplus('https://maps.google.com/maps' , params)
return urlplus('https://maps.google.com/maps', params)
+3 -1
View File
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
from openerp import api, models
from odoo import api, models
class WebsitePlanner(models.Model):
+318 -293
View File
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import inspect
import logging
import math
@@ -6,26 +8,34 @@ import unicodedata
import re
import urlparse
import hashlib
from sys import maxint
import werkzeug
from werkzeug.exceptions import NotFound
# optional python-slugify import (https://github.com/un33k/python-slugify)
try:
import slugify as slugify_lib
except ImportError:
slugify_lib = None
import openerp
from openerp.tools.translate import _
from openerp.osv import orm, osv, fields
from openerp.tools import html_escape as escape, ustr, image_resize_and_sharpen, image_save_for_web
from openerp.tools.safe_eval import safe_eval
from openerp.http import request
from werkzeug.exceptions import NotFound
from odoo import api, fields, models
from odoo import tools
from odoo.tools import ustr
from odoo.http import request
from odoo.tools.translate import _
logger = logging.getLogger(__name__)
DEFAULT_CDN_FILTERS = [
"^/[^/]+/static/",
"^/web/(css|js)/",
"^/web/image",
"^/web/content",
# retrocompatibility
"^/website/image/",
]
def url_for(path_or_uri, lang=None):
if isinstance(path_or_uri, unicode):
path_or_uri = path_or_uri.encode('utf-8')
@@ -58,6 +68,7 @@ def url_for(path_or_uri, lang=None):
return location.decode('utf-8')
def is_multilang_url(local_url, langs=None):
if not langs:
langs = [lg[0] for lg in request.website.get_languages()]
@@ -79,17 +90,20 @@ def is_multilang_url(local_url, langs=None):
except Exception:
return False
####################################################
# Slug API
####################################################
def slugify(s, max_length=None):
""" Transform a string to a slug that can be used in a url path.
This method will first try to do the job with python-slugify if present.
Otherwise it will process string by stripping leading and ending spaces,
converting unicode chars to ascii, lowering all chars and replacing spaces
and underscore with hyphen "-".
:param s: str
:param max_length: int
:rtype: str
This method will first try to do the job with python-slugify if present.
Otherwise it will process string by stripping leading and ending spaces,
converting unicode chars to ascii, lowering all chars and replacing spaces
and underscore with hyphen "-".
:param s: str
:param max_length: int
:rtype: str
"""
s = ustr(s)
if slugify_lib:
@@ -99,35 +113,27 @@ def slugify(s, max_length=None):
except TypeError:
pass
uni = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('ascii')
slug = re.sub('[\W_]', ' ', uni).strip().lower()
slug = re.sub('[-\s]+', '-', slug)
slug_str = re.sub('[\W_]', ' ', uni).strip().lower()
slug_str = re.sub('[-\s]+', '-', slug_str)
return slug_str[:max_length]
return slug[:max_length]
def slug(value):
if isinstance(value, orm.browse_record):
if isinstance(value, models.BaseModel):
# [(id, name)] = value.name_get()
id, name = value.id, value.display_name
identifier, name = value.id, value.display_name
else:
# assume name_search result tuple
id, name = value
identifier, name = value
slugname = slugify(name or '').strip().strip('-')
if not slugname:
return str(id)
return "%s-%d" % (slugname, id)
return str(identifier)
return "%s-%d" % (slugname, identifier)
# NOTE: as the pattern is used as it for the ModelConverter (ir_http.py), do not use any flags
_UNSLUG_RE = re.compile(r'(?:(\w{1,2}|\w[A-Za-z0-9-_]+?\w)-)?(-?\d+)(?=$|/)')
DEFAULT_CDN_FILTERS = [
"^/[^/]+/static/",
"^/web/(css|js)/",
"^/web/image",
"^/web/content",
# retrocompatibility
"^/website/image/",
]
def unslug(s):
"""Extract slug and id from a string.
@@ -139,72 +145,70 @@ def unslug(s):
return m.group(1), int(m.group(2))
class website(osv.osv):
def _get_menu(self, cr, uid, ids, name, arg, context=None):
res = {}
menu_obj = self.pool.get('website.menu')
for id in ids:
menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False), ('website_id', '=', id)], order='id', context=context)
res[id] = menu_ids and menu_ids[0] or False
return res
class Website(models.Model):
def _active_languages(self, cr, uid, context=None):
return self.pool['res.lang'].search(cr, uid, [], context=context)
def _default_language(self, cr, uid, context=None):
lang_code = self.pool['ir.values'].get_default(cr, uid, 'res.partner', 'lang')
def_langs = self.pool['res.lang'].search(cr, uid, [('code', '=', lang_code)], context=context)
return def_langs[0] if def_langs else self._active_languages(cr, uid, context=context)[0]
_name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
_name = "website" # Avoid website.website convention for conciseness (for new api). Got a special authorization from xmo and rco
_description = "Website"
_columns = {
'name': fields.char('Website Name'),
'domain': fields.char('Website Domain'),
'company_id': fields.many2one('res.company', string="Company"),
'language_ids': fields.many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages'),
'default_lang_id': fields.many2one('res.lang', string="Default language"),
'default_lang_code': fields.related('default_lang_id', 'code', type="char", string="Default language code", store=True),
'social_twitter': fields.char('Twitter Account'),
'social_facebook': fields.char('Facebook Account'),
'social_github': fields.char('GitHub Account'),
'social_linkedin': fields.char('LinkedIn Account'),
'social_youtube': fields.char('Youtube Account'),
'social_googleplus': fields.char('Google+ Account'),
'google_analytics_key': fields.char('Google Analytics Key'),
'user_id': fields.many2one('res.users', string='Public User'),
'compress_html': fields.boolean('Compress HTML'),
'cdn_activated': fields.boolean('Activate CDN for assets'),
'cdn_url': fields.char('CDN Base URL'),
'cdn_filters': fields.text('CDN Filters', help="URL matching those filters will be rewritten using the CDN Base URL"),
'partner_id': fields.related('user_id','partner_id', type='many2one', relation='res.partner', string='Public Partner'),
'menu_id': fields.function(_get_menu, relation='website.menu', type='many2one', string='Main Menu'),
'favicon': fields.binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website."),
}
_defaults = {
'user_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID, 'base.public_user'),
'company_id': lambda self,cr,uid,c: self.pool['ir.model.data'].xmlid_to_res_id(cr, openerp.SUPERUSER_ID,'base.main_company'),
'compress_html': False,
'cdn_activated': False,
'cdn_url': '',
'cdn_filters': '\n'.join(DEFAULT_CDN_FILTERS),
'language_ids': _active_languages,
'default_lang_id': _default_language,
}
def _active_languages(self):
return self.env['res.lang'].search([]).ids
def _default_language(self):
lang_code = self.env['ir.values'].get_default('res.partner', 'lang')
def_lang = self.env['res.lang'].search([('code', '=', lang_code)], limit=1)
return def_lang.id if def_lang else self._active_languages()[0]
name = fields.Char('Website Name')
domain = fields.Char('Website Domain')
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.ref('base.main_company').id)
language_ids = fields.Many2many('res.lang', 'website_lang_rel', 'website_id', 'lang_id', 'Languages', default=_active_languages)
default_lang_id = fields.Many2one('res.lang', string="Default language", default=_default_language)
default_lang_code = fields.Char(related='default_lang_id.code', string="Default language code", store=True)
social_twitter = fields.Char('Twitter Account')
social_facebook = fields.Char('Facebook Account')
social_github = fields.Char('GitHub Account')
social_linkedin = fields.Char('LinkedIn Account')
social_youtube = fields.Char('Youtube Account')
social_googleplus = fields.Char('Google+ Account')
google_analytics_key = fields.Char('Google Analytics Key')
user_id = fields.Many2one('res.users', string='Public User', default=lambda self: self.env.ref('base.public_user').id)
compress_html = fields.Boolean('Compress HTML')
cdn_activated = fields.Boolean('Activate CDN for assets')
cdn_url = fields.Char('CDN Base URL', default='')
cdn_filters = fields.Text('CDN Filters', default=lambda s: '\n'.join(DEFAULT_CDN_FILTERS), help="URL matching those filters will be rewritten using the CDN Base URL")
partner_id = fields.Many2one(related='user_id.partner_id', relation='res.partner', string='Public Partner')
menu_id = fields.Many2one('website.menu', compute='_compute_menu', string='Main Menu')
favicon = fields.Binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website.")
@api.multi
def _compute_menu(self):
Menu = self.env['website.menu']
for website in self:
website.menu_id = Menu.search([('parent_id', '=', False), ('website_id', '=', website.id)], order='id', limit=1).id
# cf. Wizard hack in website_views.xml
def noop(self, *args, **kwargs):
pass
def write(self, cr, uid, ids, vals, context=None):
@api.multi
def write(self, values):
self._get_languages.clear_cache(self)
return super(website, self).write(cr, uid, ids, vals, context)
return super(Website, self).write(values)
def new_page(self, cr, uid, name, template='website.default_page', ispage=True, context=None):
context = context or {}
imd = self.pool.get('ir.model.data')
view = self.pool.get('ir.ui.view')
template_module, template_name = template.split('.')
#----------------------------------------------------------
# Page Management
#----------------------------------------------------------
@api.model
def new_page(self, name, template='website.default_page', ispage=True):
""" Create a new website page, and assign it a xmlid based on the given one
:param name : the name of the page
:param template : potential xml_id of the page to create
"""
template_module, dummy = template.split('.')
website_id = self._context.get('website_id')
# completely arbitrary max_length
page_name = slugify(name, max_length=50)
@@ -212,77 +216,76 @@ class website(osv.osv):
# find a free xmlid
inc = 0
dom = [('website_id', '=', False), ('website_id', '=', context.get('website_id'))]
while view.search(cr, openerp.SUPERUSER_ID, [('key', '=', page_xmlid), '|'] + dom, context=dict(context or {}, active_test=False)):
domain_static = [('website_id', '=', False), ('website_id', '=', website_id)]
while self.env['ir.ui.view'].with_context(active_test=False).sudo().search([('key', '=', page_xmlid), '|'] + domain_static):
inc += 1
page_xmlid = "%s.%s" % (template_module, page_name + (inc and "-%s" % inc or ""))
page_xmlid = "%s.%s" % (template_module, page_name + ("-%s" % inc if inc else ""))
page_name += (inc and "-%s" % inc or "")
# new page
_, template_id = imd.get_object_reference(cr, uid, template_module, template_name)
website_id = context.get('website_id')
key = template_module+'.'+page_name
page_id = view.copy(cr, uid, template_id, {'website_id': website_id, 'key': key}, context=context)
page = view.browse(cr, uid, page_id, context=dict(context, lang=None))
page.write({
template_record = self.env.ref(template)
key = '%s.%s' % (template_module, page_name)
page = template_record.copy({'website_id': website_id, 'key': key})
page.with_context(lang=None).write({
'arch': page.arch.replace(template, page_xmlid),
'name': page_name,
'page': ispage,
})
return page_xmlid
def key_to_view_id(self, cr, uid, view_id, context=None):
View = self.pool.get('ir.ui.view')
return View.search(cr, uid, [
def key_to_view_id(self, view_id):
return self.env['ir.ui.view'].search([
('id', '=', view_id),
"|", ('website_id', '=', context.get('website_id')), ('website_id', '=', False),
'|', ('website_id', '=', self._context.get('website_id')), ('website_id', '=', False),
('page', '=', True),
('type', '=', 'qweb')
], context=context)
])
def delete_page(self, cr, uid, view_id, context=None):
if context is None:
context = {}
View = self.pool.get('ir.ui.view')
view_find = self.key_to_view_id(cr, uid, view_id, context=context)
if view_find:
View.unlink(cr, uid, view_find, context=context)
def rename_page(self, cr, uid, view_id, new_name, context=None):
if context is None:
context = {}
View = self.pool.get('ir.ui.view')
view_find = self.key_to_view_id(cr, uid, view_id, context=context)
if view_find:
v = View.browse(cr, uid, view_find, context=context)
@api.model
def delete_page(self, view_id):
""" Delete a page, given its identifier
:param view_id : ir.ui.view identifier
"""
view = self.key_to_view_id(view_id)
if view:
view.unlink()
@api.model
def rename_page(self, view_id, new_name):
""" Change the name of the given page
:param view_id : id of the view to rename
:param new_name : name to use
"""
view = self.key_to_view_id(view_id)
if view:
# slugify the new name and prefix by module if
# not already done by end user
new_name = slugify(new_name, max_length=50)
# Prefix by module if not already done by end user
prefix = v.key.split('.')[0]
prefix = view.key.split('.')[0]
if not new_name.startswith(prefix):
new_name = "%s.%s" % (prefix, new_name)
View.write(cr, uid, view_find, {
view.write({
'key': new_name,
'arch_db': v.arch_db.replace(v.key, new_name, 1)
'arch_db': view.arch_db.replace(view.key, new_name, 1)
})
return new_name
return False
def page_search_dependencies(self, cr, uid, view_id=False, context=None):
dep = {}
@api.model
def page_search_dependencies(self, view_id=False):
""" Search dependencies just for information. It will not catch 100%
of dependencies and False positive is more than possible
Each module could add dependences in this dict
:returns a dictionnary where key is the 'categorie' of object related to the given
view, and the value is the list of text and link to the resource using given page
"""
dependencies = {}
if not view_id:
return dep
return dependencies
# search dependencies just for information.
# It will not catch 100% of dependencies and False positive is more than possible
# Each module could add dependences in this dict
if context is None:
context = {}
View = self.pool.get('ir.ui.view')
Menu = self.pool.get('website.menu')
view = View.browse(cr, uid, view_id, context=context)
website_id = context.get('website_id')
view = self.env['ir.ui.view'].browse(view_id)
website_id = self._context.get('website_id')
name = view.key.replace("website.", "")
fullname = "website.%s" % name
@@ -292,18 +295,18 @@ class website(osv.osv):
'|', ('website_id', '=', website_id), ('website_id', '=', False),
'|', ('arch_db', 'ilike', '/page/%s' % name), ('arch_db', 'ilike', '/page/%s' % fullname)
]
pages = View.search(cr, uid, page_search_dom, context=context)
if pages:
page_key = _('Page')
dep[page_key] = []
for page in View.browse(cr, uid, pages, context=context):
page_key = _('Page')
pages = self.env['ir.ui.view'].search(page_search_dom)
for page in pages:
dependencies.setdefault(page_key, [])
if page.page:
dep[page_key].append({
dependencies[page_key].append({
'text': _('Page <b>%s</b> contains a link to this page') % page.key,
'link': '/page/%s' % page.key
})
else:
dep[page_key].append({
dependencies[page_key].append({
'text': _('Template <b>%s (id:%s)</b> contains a link to this page') % (page.key, page.id),
'link': '#'
})
@@ -314,73 +317,65 @@ class website(osv.osv):
'|', ('url', 'ilike', '/page/%s' % name), ('url', 'ilike', '/page/%s' % fullname)
]
menus = Menu.search(cr, uid, menu_search_dom, context=context)
if menus:
menu_key = _('Menu')
dep[menu_key] = []
for menu in Menu.browse(cr, uid, menus, context=context):
dep[menu_key].append({
menu_key = _('Menu')
menus = self.env['website.menu'].search(menu_search_dom)
for menu in menus:
dependencies.setdefault(menu_key, []).append({
'text': _('This page is in the menu <b>%s</b>') % menu.name,
'link': False
})
return dep
return dependencies
def page_for_name(self, cr, uid, ids, name, module='website', context=None):
# whatever
return '%s.%s' % (module, slugify(name, max_length=50))
def page_exists(self, cr, uid, ids, name, module='website', context=None):
@api.model
def page_exists(self, name, module='website'):
try:
name = (name or "").replace("/page/website.", "").replace("/page/", "")
if not name:
return False
return self.pool["ir.model.data"].get_object_reference(cr, uid, module, name)
return self.env.ref('%s.%s' % module, name)
except:
return False
@openerp.tools.ormcache('id')
def _get_languages(self, cr, uid, id, context=None):
website = self.browse(cr, uid, id)
return [(lg.code, lg.name) for lg in website.language_ids]
#----------------------------------------------------------
# Languages
#----------------------------------------------------------
def get_cdn_url(self, cr, uid, uri, context=None):
# Currently only usable in a website_enable request context
if request and request.website and not request.debug and request.website.user_id.id == request.uid:
cdn_url = request.website.cdn_url
cdn_filters = (request.website.cdn_filters or '').splitlines()
for flt in cdn_filters:
if flt and re.match(flt, uri):
return urlparse.urljoin(cdn_url, uri)
return uri
@api.multi
def get_languages(self):
self.ensure_one()
return self._get_languages()
def get_languages(self, cr, uid, ids, context=None):
return self._get_languages(cr, uid, ids[0])
@tools.cache('self.id')
def _get_languages(self):
return [(lg.code, lg.name) for lg in self.language_ids]
def get_alternate_languages(self, cr, uid, ids, req=None, context=None):
@api.multi
def get_alternate_languages(self, req=None):
langs = []
if req is None:
req = request.httprequest
default = self.get_current_website(cr, uid, context=context).default_lang_code
default = self.get_current_website().default_lang_code
shorts = []
def get_url_localized(router, lang):
arguments = dict(request.endpoint_arguments)
for k, v in arguments.items():
if isinstance(v, orm.browse_record):
arguments[k] = v.with_context(lang=lang)
for key, val in arguments.items():
if isinstance(val, models.BaseModel):
arguments[key] = val.with_context(lang=lang)
return router.build(request.endpoint, arguments)
router = request.httprequest.app.get_db_router(request.db).bind('')
for code, name in self.get_languages(cr, uid, ids, context=context):
for code, dummy in self.get_languages():
lg_path = ('/' + code) if code != default else ''
lg = code.split('_')
shorts.append(lg[0])
uri = request.endpoint and get_url_localized(router, code) or request.httprequest.path
lg_codes = code.split('_')
shorts.append(lg_codes[0])
uri = get_url_localized(router, code) if request.endpoint else request.httprequest.path
if req.query_string:
uri += '?' + req.query_string
lang = {
'hreflang': ('-'.join(lg)).lower(),
'short': lg[0],
'hreflang': ('-'.join(lg_codes)).lower(),
'short': lg_codes[0],
'href': req.url_root[0:-1] + lg_path + uri,
}
langs.append(lang)
@@ -389,47 +384,68 @@ class website(osv.osv):
lang['hreflang'] = lang['short']
return langs
@openerp.tools.ormcache('domain_name')
def _get_current_website_id(self, cr, uid, domain_name, context=None):
ids = self.search(cr, uid, [('domain', '=', domain_name)], limit=1, context=context)
return ids and ids[0] or self.search(cr, uid, [], limit=1)[0]
#----------------------------------------------------------
# Utilities
#----------------------------------------------------------
def get_current_website(self, cr, uid, context=None):
@api.model
def get_current_website(self):
domain_name = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0]
website_id = self._get_current_website_id(cr, uid, domain_name)
website_id = self._get_current_website_id(domain_name)
request.context['website_id'] = website_id
return self.browse(cr, uid, website_id, context=context)
return self.browse(website_id)
def is_publisher(self, cr, uid, ids, context=None):
Access = self.pool['ir.model.access']
is_website_publisher = Access.check(cr, uid, 'ir.ui.view', 'write', False, context=context)
return is_website_publisher
@tools.cache('domain_name')
def _get_current_website_id(self, domain_name):
""" Reminder : cached method should be return record, since they will use a closed cursor. """
website = self.search([('domain', '=', domain_name)], limit=1)
if not website:
website = self.search([], limit=1)
return website.id
def is_user(self, cr, uid, ids, context=None):
Access = self.pool['ir.model.access']
return Access.check(cr, uid, 'ir.ui.menu', 'read', False, context=context)
@api.model
def is_publisher(self):
return self.env['ir.model.access'].check('ir.ui.view', 'write', False)
def get_template(self, cr, uid, ids, template, context=None):
View = self.pool['ir.ui.view']
@api.model
def is_user(self):
return self.env['ir.model.access'].check('ir.ui.menu', 'read', False)
@api.model
def get_template(self, template):
View = self.env['ir.ui.view']
if isinstance(template, (int, long)):
view_id = template
else:
if '.' not in template:
template = 'website.%s' % template
view_id = View.get_view_id(cr, uid, template, context=context)
view_id = View.get_view_id(template)
if not view_id:
raise NotFound
return View.browse(cr, uid, view_id, context=context)
return View.browse(view_id)
def _render(self, cr, uid, ids, template, values=None, context=None):
@api.model
def render(self, template, values=None, status_code=None):
# TODO: remove this. (just kept for backward api compatibility for saas-3)
return self.pool['ir.ui.view'].render_template(cr, uid, template, values=values, context=context)
return request.render(template, values, uid=self._uid)
def render(self, cr, uid, ids, template, values=None, status_code=None, context=None):
def _render(self, template, values=None):
# TODO: remove this. (just kept for backward api compatibility for saas-3)
return request.render(template, values, uid=uid)
return self.env['ir.ui.view'].render_template(template, values=values)
def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None, context=None):
@api.model
def pager(self, url, total, page=1, step=30, scope=5, url_args=None):
""" Generate a dict with required value to render `website.pager` template. This method compute
url, page range to display, ... in the pager.
:param url : base url of the page link
:param total : number total of item to be splitted into pages
:param page : current page
:param step : item per page
:param scope : number of page to display on pager
:param url_args : additionnal parameters to add as query params to page url
:type url_args : dict
:returns dict
"""
# Compute Pager
page_count = int(math.ceil(float(total) / step))
@@ -472,17 +488,15 @@ class website(osv.osv):
'num': pmax
},
"pages": [
{'url': get_url(page), 'num': page}
for page in xrange(pmin, pmax+1)
{'url': get_url(page), 'num': page} for page in xrange(pmin, pmax+1)
]
}
def rule_is_enumerable(self, rule):
""" Checks that it is possible to generate sensible GET queries for
a given rule (if the endpoint matches its own requirements)
:type rule: werkzeug.routing.Rule
:rtype: bool
a given rule (if the endpoint matches its own requirements)
:type rule: werkzeug.routing.Rule
:rtype: bool
"""
endpoint = rule.endpoint
methods = endpoint.routing.get('methods') or ['GET']
@@ -504,21 +518,20 @@ class website(osv.osv):
args = spec.args[1:(-defaults_count or None)]
# check that all args have a converter
return all( (arg in rule._converters) for arg in args)
return all((arg in rule._converters) for arg in args)
def enumerate_pages(self, cr, uid, ids, query_string=None, context=None):
@api.multi
def enumerate_pages(self, query_string=None):
""" Available pages in the website/CMS. This is mostly used for links
generation and can be overridden by modules setting up new HTML
controllers for dynamic pages (e.g. blog).
By default, returns template views marked as pages.
:param str query_string: a (user-provided) string, fetches pages
matching the string
:returns: a list of mappings with two keys: ``name`` is the displayable
name of the resource (page), ``url`` is the absolute URL
of the same.
:rtype: list({name: str, url: str})
generation and can be overridden by modules setting up new HTML
controllers for dynamic pages (e.g. blog).
By default, returns template views marked as pages.
:param str query_string: a (user-provided) string, fetches pages
matching the string
:returns: a list of mappings with two keys: ``name`` is the displayable
name of the resource (page), ``url`` is the absolute URL
of the same.
:rtype: list({name: str, url: str})
"""
router = request.httprequest.app.get_db_router(request.db)
# Force enumeration to be performed as public user
@@ -533,23 +546,23 @@ class website(osv.osv):
values = [{}]
convitems = converters.items()
# converters with a domain are processed after the other ones
gd = lambda x: hasattr(x[1], 'domain') and (x[1].domain <> '[]')
gd = lambda x: hasattr(x[1], 'domain') and (x[1].domain != '[]')
convitems.sort(lambda x, y: cmp(gd(x), gd(y)))
for (i,(name, converter)) in enumerate(convitems):
for (i, (name, converter)) in enumerate(convitems):
newval = []
for val in values:
query = i==(len(convitems)-1) and query_string
for v in converter.generate(request.cr, uid, query=query, args=val, context=context):
newval.append( val.copy() )
v[name] = v['loc']
del v['loc']
newval[-1].update(v)
query = i == len(convitems)-1 and query_string
for value_dict in converter.generate(query=query, args=val):
newval.append(val.copy())
value_dict[name] = value_dict['loc']
del value_dict['loc']
newval[-1].update(value_dict)
values = newval
for value in values:
domain_part, url = rule.build(value, append_unknown=False)
page = {'loc': url}
for key,val in value.items():
for key, val in value.items():
if key.startswith('__'):
page[key[2:]] = val
if url in ('/sitemap.xml',):
@@ -560,54 +573,63 @@ class website(osv.osv):
yield page
def search_pages(self, cr, uid, ids, needle=None, limit=None, context=None):
@api.multi
def search_pages(self, needle=None, limit=None):
name = re.sub(r"^/p(a(g(e(/(w(e(b(s(i(t(e(\.)?)?)?)?)?)?)?)?)?)?)?)?", "", needle or "")
res = []
for page in self.enumerate_pages(cr, uid, ids, query_string=name, context=context):
for page in self.enumerate_pages(query_string=name):
if needle in page['loc']:
res.append(page)
if len(res) == limit:
break
return res
def image_url(self, cr, uid, record, field, size=None, context=None):
"""Returns a local url that points to the image field of a given browse record."""
@api.model
def image_url(self, record, field, size=None):
""" Returns a local url that points to the image field of a given browse record. """
sudo_record = record.sudo()
sha = hashlib.sha1(getattr(sudo_record, '__last_update')).hexdigest()[0:7]
size = '' if size is None else '/%s' % size
return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
class website_menu(osv.osv):
@api.model
def get_cdn_url(self, uri):
# Currently only usable in a website_enable request context
if request and request.website and not request.debug and request.website.user_id.id == request.uid:
cdn_url = request.website.cdn_url
cdn_filters = (request.website.cdn_filters or '').splitlines()
for flt in cdn_filters:
if flt and re.match(flt, uri):
return urlparse.urljoin(cdn_url, uri)
return uri
class Menu(models.Model):
_name = "website.menu"
_description = "Website Menu"
_columns = {
'name': fields.char('Menu', required=True, translate=True),
'url': fields.char('Url'),
'new_window': fields.boolean('New Window'),
'sequence': fields.integer('Sequence'),
# TODO: support multiwebsite once done for ir.ui.views
'website_id': fields.many2one('website', 'Website'),
'parent_id': fields.many2one('website.menu', 'Parent Menu', select=True, ondelete="cascade"),
'child_id': fields.one2many('website.menu', 'parent_id', string='Child Menus'),
'parent_left': fields.integer('Parent Left', select=True),
'parent_right': fields.integer('Parent Right', select=True),
}
def __defaults_sequence(self, cr, uid, context):
menu = self.search_read(cr, uid, [(1,"=",1)], ["sequence"], limit=1, order="sequence DESC", context=context)
return menu and menu[0]["sequence"] or 0
_defaults = {
'url': '',
'sequence': __defaults_sequence,
'new_window': False,
}
_parent_store = True
_parent_order = 'sequence'
_order = "sequence"
def _default_sequence(self):
menu = self.search(["sequence"], limit=1, order="sequence DESC")
return menu.sequence or 0
name = fields.Char('Menu', required=True, translate=True)
url = fields.Char('Url', default='')
new_window = fields.Boolean('New Window')
sequence = fields.Integer(defualt=_default_sequence)
website_id = fields.Many2one('website', 'Website') # TODO: support multiwebsite once done for ir.ui.views
parent_id = fields.Many2one('website.menu', 'Parent Menu', index=True, ondelete="cascade")
child_id = fields.One2many('website.menu', 'parent_id', string='Child Menus')
parent_left = fields.Integer('Parent Left', index=True)
parent_right = fields.Integer('Parent Rigth', index=True)
# would be better to take a menu_id as argument
def get_tree(self, cr, uid, website_id, menu_id=None, context=None):
@api.model
def get_tree(self, website_id, menu_id=None):
def make_tree(node):
menu_node = dict(
id=node.id,
@@ -622,12 +644,13 @@ class website_menu(osv.osv):
menu_node['children'].append(make_tree(child))
return menu_node
if menu_id:
menu = self.browse(cr, uid, menu_id, context=context)
menu = self.browse(menu_id)
else:
menu = self.pool.get('website').browse(cr, uid, website_id, context=context).menu_id
menu = self.env['website'].browse(website_id).menu_id
return make_tree(menu)
def save(self, cr, uid, website_id, data, context=None):
@api.model
def save(self, website_id, data):
def replace_id(old_id, new_id):
for menu in data['data']:
if menu['id'] == old_id:
@@ -636,52 +659,54 @@ class website_menu(osv.osv):
menu['parent_id'] = new_id
to_delete = data['to_delete']
if to_delete:
self.unlink(cr, uid, to_delete, context=context)
self.browse(to_delete).unlink()
for menu in data['data']:
mid = menu['id']
if isinstance(mid, basestring):
new_id = self.create(cr, uid, {'name': menu['name']}, context=context)
replace_id(mid, new_id)
new_menu = self.create({'name': menu['name']})
replace_id(mid, new_menu.id)
for menu in data['data']:
self.write(cr, uid, [menu['id']], menu, context=context)
self.browse(menu['id']).write(menu)
return True
class website_seo_metadata(osv.AbstractModel):
class SeoMetadata(models.AbstractModel):
_name = 'website.seo.metadata'
_description = 'SEO metadata'
_columns = {
'website_meta_title': fields.char("Website meta title", translate=True),
'website_meta_description': fields.text("Website meta description", translate=True),
'website_meta_keywords': fields.char("Website meta keywords", translate=True),
}
website_meta_title = fields.Char("Website meta title", translate=True)
website_meta_description = fields.Text("Website meta description", translate=True)
website_meta_keywords = fields.Char("Website meta keywords", translate=True)
class website_published_mixin(osv.AbstractModel):
class WebsitePublishedMixin(models.AbstractModel):
_name = "website.published.mixin"
_website_url_proxy = lambda self, *a, **kw: self._website_url(*a, **kw)
website_published = fields.Boolean('Visible in Website', copy=False)
website_url = fields.Char('Website URL', compute='_website_url_proxy', help='The full URL to access the document through the website.')
_columns = {
'website_published': fields.boolean('Visible in Website', copy=False),
'website_url': fields.function(_website_url_proxy, type='char', string='Website URL',
help='The full URL to access the document through the website.'),
}
# TODO remove proxy and _website_url unused arguments
def _website_url_proxy(self):
result = self._website_url(False, False)
for website in self:
website.website_url = result.get(website.id)
def _website_url(self, cr, uid, ids, field_name, arg, context=None):
return dict.fromkeys(ids, '#')
@api.multi
def _website_url(self, field_name, arg):
return dict.fromkeys(self.ids, '#')
def website_publish_button(self, cr, uid, ids, context=None):
for i in self.browse(cr, uid, ids, context):
if self.pool['res.users'].has_group(cr, uid, 'base.group_website_publisher') and i.website_url != '#':
return self.open_website_url(cr, uid, ids, context)
i.write({'website_published': not i.website_published})
return True
@api.multi
def website_publish_button(self):
self.ensure_one()
if self.env.user.has_group('base.group_website_publisher') and self.website_url != '#':
return self.open_website_url()
return self.write({'website_published': not self.website_published})
def open_website_url(self, cr, uid, ids, context=None):
def open_website_url(self):
return {
'type': 'ir.actions.act_url',
'url': self.browse(cr, uid, ids[0]).website_url,
'url': self.website_url,
'target': 'self',
}
+2 -4
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="website_designer_edit_qweb" model="ir.rule">
<field name="name">website_designer: Manage Website and qWeb view</field>
<field name="model_id" ref="base.model_ir_ui_view"/>
@@ -31,5 +30,4 @@
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
</data>
</openerp>
</odoo>
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import unittest
from openerp.addons.website.models.website import slugify, unslug
from odoo.addons.website.models.website import slugify, unslug
class TestUnslug(unittest.TestCase):
def test_unslug(self):
+8 -9
View File
@@ -5,13 +5,13 @@ import time
import lxml.html
import openerp
import odoo
import re
_logger = logging.getLogger(__name__)
class Crawler(openerp.tests.HttpCase):
""" Test suite crawling an openerp CMS instance and checking that all
class Crawler(odoo.tests.HttpCase):
""" Test suite crawling an Odoo CMS instance and checking that all
internal links lead to a 200 response.
If a username and a password are provided, authenticates the user before
@@ -22,7 +22,7 @@ class Crawler(openerp.tests.HttpCase):
post_install = True
def crawl(self, url, seen=None, msg=''):
if seen == None:
if seen is None:
seen = set()
url_slug = re.sub(r"[/](([^/=?&]+-)?[0-9]+)([/]|$)", '/<slug>/', url)
@@ -35,7 +35,7 @@ class Crawler(openerp.tests.HttpCase):
_logger.info("%s %s", msg, url)
r = self.url_open(url)
code = r.getcode()
self.assertIn( code, xrange(200, 300), "%s Fetching %s returned error response (%d)" % (msg, url, code))
self.assertIn(code, xrange(200, 300), "%s Fetching %s returned error response (%d)" % (msg, url, code))
if r.info().gettype() == 'text/html':
doc = lxml.html.fromstring(r.read())
@@ -64,7 +64,6 @@ class Crawler(openerp.tests.HttpCase):
self.crawl(href, seen, msg)
return seen
def test_10_crawl_public(self):
t0 = time.time()
t0_sql = self.registry.test_cr.sql_log_count
@@ -72,7 +71,7 @@ class Crawler(openerp.tests.HttpCase):
count = len(seen)
duration = time.time() - t0
sql = self.registry.test_cr.sql_log_count - t0_sql
_logger.log(25, "public crawled %s urls in %.2fs %s queries, %.3fs %.2fq per request, ", count, duration, sql, duration/count, float(sql)/count)
_logger.log(25, "public crawled %s urls in %.2fs %s queries, %.3fs %.2fq per request, ", count, duration, sql, duration / count, float(sql) / count)
def test_20_crawl_demo(self):
t0 = time.time()
@@ -82,7 +81,7 @@ class Crawler(openerp.tests.HttpCase):
count = len(seen)
duration = time.time() - t0
sql = self.registry.test_cr.sql_log_count - t0_sql
_logger.log(25, "demo crawled %s urls in %.2fs %s queries, %.3fs %.2fq per request", count, duration, sql, duration/count, float(sql)/count)
_logger.log(25, "demo crawled %s urls in %.2fs %s queries, %.3fs %.2fq per request", count, duration, sql, duration / count, float(sql) / count)
def test_30_crawl_admin(self):
t0 = time.time()
@@ -92,4 +91,4 @@ class Crawler(openerp.tests.HttpCase):
count = len(seen)
duration = time.time() - t0
sql = self.registry.test_cr.sql_log_count - t0_sql
_logger.log(25, "admin crawled %s urls in %.2fs %s queries, %.3fs %.2fq per request", count, duration, sql, duration/count, float(sql)/count)
_logger.log(25, "admin crawled %s urls in %.2fs %s queries, %.3fs %.2fq per request", count, duration, sql, duration / count, float(sql) / count)
+2 -2
View File
@@ -1,7 +1,7 @@
import openerp.tests
import odoo.tests
class TestUi(openerp.tests.HttpCase):
class TestUi(odoo.tests.HttpCase):
def test_01_public_homepage(self):
self.phantom_js("/", "console.log('ok')", "'website.snippets.animation' in odoo.__DEBUG__.services")
+3 -5
View File
@@ -208,18 +208,16 @@ class TestViewSaving(common.TransactionCase):
def test_save_only_embedded(self):
Company = self.registry('res.company')
company_id = 1
Company.write(self.cr, self.uid, company_id, {'name': "Foo Corporation"})
company = Company.browse(company_id)
company.write({'name': "Foo Corporation"})
node = html.tostring(h.SPAN(
"Acme Corporation",
attrs(model='res.company', id=company_id, field="name", expression='bob', type='char')))
View = self.env['ir.ui.view']
View.browse(company_id).save(value=node)
company = Company.browse(self.cr, self.uid, company_id)
self.assertEqual(company.name, "Acme Corporation")
def test_field_tail(self):
View = self.env['ir.ui.view']
replacement = ET.tostring(
@@ -231,7 +229,7 @@ class TestViewSaving(common.TransactionCase):
View.browse(self.view_id).save(value=replacement, xpath='/div/div[2]/ul/li[3]')
self.eq(
ET.fromstring(View.browse(self.view_id).arch.encode('utf-8')),
ET.fromstring(self.view_id.arch.encode('utf-8')),
h.DIV(
h.DIV(
h.H3("Column 1"),
+3 -5
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_website_config_settings" model="ir.ui.view">
<field name="name">Website settings</field>
@@ -12,7 +11,7 @@
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
</header>
<div>
<field name="website_id" on_change="on_change_website_id(website_id)" invisible="1"/>
<field name="website_id" invisible="1"/>
<group string="Domain">
<field name="website_name" />
<label for="google_analytics_key"/>
@@ -159,5 +158,4 @@
sequence="100" name="Configuration" groups="base.group_system"/>
<menuitem id="menu_website_website_settings" parent="menu_website_global_configuration" name="Settings" action="action_website_configuration"/>
</data>
</openerp>
</odoo>
+17 -23
View File
@@ -1,41 +1,35 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import openerp
from openerp.osv import orm, osv, fields
from odoo import api, fields, models
class base_language_install(osv.osv_memory):
class BaseLanguageInstall(models.TransientModel):
_inherit = "base.language.install"
_columns = {
'website_ids': fields.many2many('website', string='Websites to translate'),
}
def default_get(self, cr, uid, fields, context=None):
if context is None:
context = {}
defaults = super(base_language_install, self).default_get(cr, uid, fields, context)
website_id = context.get('params', {}).get('website_id')
website_ids = fields.Many2many('website', string='Websites to translate')
@api.model
def default_get(self, fields):
defaults = super(BaseLanguageInstall, self).default_get(fields)
website_id = self._context.get('params', {}).get('website_id')
if website_id:
if 'website_ids' not in defaults:
defaults['website_ids'] = []
defaults['website_ids'].append(website_id)
return defaults
def lang_install(self, cr, uid, ids, context=None):
if context is None:
context = {}
action = super(base_language_install, self).lang_install(cr, uid, ids, context)
language_obj = self.browse(cr, uid, ids)[0]
website_ids = [website.id for website in language_obj['website_ids']]
lang_id = self.pool['res.lang'].search(cr, uid, [('code', '=', language_obj['lang'])])
if website_ids and lang_id:
data = {'language_ids': [(4, lang_id[0])]}
self.pool['website'].write(cr, uid, website_ids, data)
params = context.get('params', {})
@api.multi
def lang_install(self):
action = super(BaseLanguageInstall, self).lang_install()
lang = self.env['res.lang'].search([('code', '=', self.lang)], limit=1)
if self.website_ids and lang:
self.website_ids.write({'language_ids': [(4, lang.id)]})
params = self._context.get('params', {})
if 'url_return' in params:
return {
'url': params['url_return'].replace('[lang]', language_obj['lang']),
'url': params['url_return'].replace('[lang]', self.lang),
'type': 'ir.actions.act_url',
'target': 'self'
}