[MERGE] multilang

bzr revid: fme@openerp.com-20130926165324-2jfo9s8q3u4teor1
This commit is contained in:
Fabien Meghazi 2013-09-26 18:53:24 +02:00
commit e7d55014a4
26 changed files with 491 additions and 116 deletions

View File

@ -35,7 +35,7 @@ PIL_MIME_MAPPING = {'PNG': 'image/png', 'JPEG': 'image/jpeg', 'GIF': 'image/gif'
# Completely arbitrary limits
MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
class Website(openerp.addons.web.controllers.main.Home):
@website.route('/', type='http', auth="public")
@website.route('/', type='http', auth="public", multilang=True)
def index(self, **kw):
return self.page("website.homepage")
@ -85,7 +85,7 @@ class Website(openerp.addons.web.controllers.main.Home):
return werkzeug.wrappers.Response(url, mimetype='text/plain')
return werkzeug.utils.redirect(url)
@website.route('/website/theme_change', type='http', auth="admin") # FIXME: auth
@website.route('/website/theme_change', type='http', auth="admin")
def theme_change(self, theme_id=False, **kwargs):
imd = request.registry['ir.model.data']
view = request.registry['ir.ui.view']
@ -107,7 +107,7 @@ class Website(openerp.addons.web.controllers.main.Home):
return request.website.render('website.themes', {'theme_changed': True})
@website.route('/page/<path:path>', type='http', auth="public")
@website.route('/page/<path:path>', type='http', auth="public", multilang=True)
def page(self, path, **kwargs):
values = {
'path': path,
@ -160,6 +160,51 @@ class Website(openerp.addons.web.controllers.main.Home):
})
return result
@website.route('/website/get_view_translations', type='json', auth='admin')
def get_view_translations(self, xml_id, lang=None):
lang = lang or request.context.get('lang')
views = self.customize_template_get(xml_id, optional=False)
views_ids = [view.get('id') for view in views if view.get('active')]
domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)]
irt = request.registry.get('ir.translation')
return irt.search_read(request.cr, request.uid, domain, ['id', 'res_id', 'value'], context=request.context)
@website.route('/website/set_translations', type='json', auth='admin')
def set_translations(self, data, lang):
irt = request.registry.get('ir.translation')
for view_id, trans in data.items():
view_id = int(view_id)
for t in trans:
initial_content = t['initial_content'].strip()
new_content = t['new_content'].strip()
tid = t['translation_id']
if not tid:
old_trans = irt.search_read(
request.cr, request.uid,
[
('type', '=', 'view'),
('res_id', '=', view_id),
('lang', '=', lang),
('src', '=', initial_content),
])
if old_trans:
tid = old_trans[0]['id']
if tid:
vals = {'value': new_content}
irt.write(request.cr, request.uid, [tid], vals)
else:
new_trans = {
'name': 'website',
'res_id': view_id,
'lang': lang,
'type': 'view',
'source': initial_content,
'value': new_content,
}
irt.create(request.cr, request.uid, new_trans)
irt._get_source.clear_cache(irt) # FIXME: find why ir.translation does not invalidate
return True
# # FIXME: auth, anybody can upload an attachment if URL known/found
@website.route('/website/attach', type='http', auth='admin')
def attach(self, func, upload):

View File

@ -107,6 +107,31 @@ table.editorbar-panel td.selected {
background-color: #b1c9d9;
}
.oe_translate_examples li {
margin: 10px;
padding: 4px;
}
.oe_translatable_text {
outline: 1px solid black;
}
.oe_translatable_field {
outline: 1px dashed black;
}
.oe_translatable_text.oe_dirty, .oe_translatable_field.oe_dirty {
outline-color: red;
}
.oe_translatable_text.oe_dirty:empty {
padding: 0 10px;
}
.oe_translatable_todo {
background: #ffffb6;
}
/* ---- RTE ---- */
.oe_editable .btn {
-webkit-user-select: auto;

View File

@ -93,6 +93,22 @@ table.editorbar-panel
td.selected
background-color: #b1c9d9
// ---- TRANSLATIONS ---- {{{
.oe_translate_examples li
margin: 10px
padding: 4px
.oe_translatable_text
outline: 1px solid black
.oe_translatable_field
outline: 1px dashed black
.oe_translatable_text.oe_dirty, .oe_translatable_field.oe_dirty
outline-color: red
.oe_translatable_text.oe_dirty:empty
padding: 0 10px
.oe_translatable_todo
background: rgb(255, 255, 182)
// }}}
/* ---- RTE ---- */
// bootstrap makes .btn elements unselectable -> RTE double-click can't know
@ -485,3 +501,5 @@ $navbar_height: 51px
&.oe_ace_closed
width: 0
opacity: 0
// vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker:

View File

@ -282,7 +282,7 @@
});
}).get();
return $.when.apply(null, defs).then(function () {
window.location.href = window.location.href.replace(/unable_editor(=[^&]*)?|#.*/g, '');
website.reload();
});
},
/**

View File

@ -37,7 +37,9 @@
});
return $.when.apply(null, dones);
};
website.reload = function () {
window.location.href = window.location.href.replace(/unable_editor(=[^&]*)?|#.*/g, '');
};
var all_ready = null;
var dom_ready = website.dom_ready = $.Deferred();
@ -78,7 +80,7 @@
$pagination.last().before(col);
});
var page_start = page - parseInt(Math.floor((scope-1)/2));
var page_start = page - parseInt(Math.floor((scope-1)/2), 10);
if (page_start < 1 ) page_start = 1;
var page_end = page_start + (scope-1);
if (page_end > page_count ) page_end = page_count;
@ -106,10 +108,10 @@
*/
website.ready = function() {
if (!all_ready) {
all_ready = dom_ready.then(function () {
// TODO: load translations
return website.load_templates(templates);
});
var tpl = website.load_templates(templates);
// var session;
// var trads = openerp._t.database.load_translations(session, ['website'], website.get_context().lang);
all_ready = $.when(dom_ready, tpl);
}
return all_ready;
};

View File

@ -0,0 +1,192 @@
(function () {
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.translator.xml');
var nodialog = 'website_translator_nodialog';
website.EditorBar.include({
start: function () {
var self = this;
this.initial_content = {};
return this._super.apply(this, arguments).then(function () {
self.$('button[data-action=edit]').text("Translate");
self.$('[data-action=snippet]').hide();
self.$('#customize-menu-button').hide();
});
},
edit: function () {
var self = this;
var mysuper = this._super;
if (!localStorage[nodialog]) {
var dialog = new website.TranslatorDialog();
dialog.appendTo($(document.body));
dialog.on('activate', this, function () {
localStorage[nodialog] = dialog.$('input[name=do_not_show]').prop('checked') || '';
dialog.$el.modal('hide');
this.translate();
});
} else {
this.translate().then(function () {
// Disable non translatable t-fields
$('[data-oe-type][data-oe-translate=0]').removeAttr('data-oe-type');
mysuper.call(self);
});
}
},
translate: function () {
var self = this;
this.translations = null;
return openerp.jsonRpc('/website/get_view_translations', 'call', {
'xml_id': $(document.documentElement).data('view-xmlid'),
'lang': website.get_context().lang,
}).then(function (translations) {
self.translations = translations;
self.processTranslatableNodes();
});
},
processTranslatableNodes: function () {
var self = this;
var $editables = $('[data-oe-model="ir.ui.view"]')
.not('link, script')
.not('.oe_snippets,.oe_snippet, .oe_snippet *')
.not('[data-oe-type]');
$editables.each(function () {
var $node = $(this);
var view_id = $node.attr('data-oe-source-id') || $node.attr('data-oe-id');
self.transNode(this, view_id|0);
});
$('.oe_translatable_text').on('paste', function () {
var node = $(this);
setTimeout(function () {
self.sanitizeNode(node);
}, 0);
});
$(document).on('blur keyup paste', '.oe_translatable_text[contenteditable]', function(ev) {
var $node = $(this);
setTimeout(function () {
// Doing stuff next tick because paste and keyup events are
// fired before the content is changed
if (ev.type == 'paste') {
self.sanitizeNode($node[0]);
}
if (self.getInitialContent($node[0]) !== $node.text()) {
$node.addClass('oe_dirty').removeClass('oe_translatable_todo');
}
}, 0);
});
},
getInitialContent: function (node) {
return this.initial_content[node.attributes['data-oe-nodeid'].value];
},
sanitizeNode: function (node) {
node.text(node.text());
},
isTextNode: function (node) {
return node.nodeType === 3 || node.nodeType === 4;
},
isTranslatable: function (text) {
return text && _.str.trim(text) !== '';
},
markTranslatableNode: function (node, view_id) {
// TODO: link nodes with same content
node.className += ' oe_translatable_text';
node.setAttribute('data-oe-translation-view-id', view_id);
var content = node.childNodes[0].data.trim();
var trans = this.translations.filter(function (t) {
return t.res_id === view_id && t.value === content;
});
if (trans.length) {
node.setAttribute('data-oe-translation-id', trans[0].id);
} else {
node.className += ' oe_translatable_todo';
}
node.contentEditable = true;
var nid = _.uniqueId();
$(node).attr('data-oe-nodeid', nid);
this.initial_content[nid] = content;
},
save: function () {
var self = this;
var mysuper = this._super;
var trans = {};
// this._super.apply(this, arguments);
$('.oe_translatable_text.oe_dirty').each(function () {
var $node = $(this);
var data = $node.data();
if (!trans[data.oeTranslationViewId]) {
trans[data.oeTranslationViewId] = [];
}
trans[data.oeTranslationViewId].push({
initial_content: self.getInitialContent(this),
new_content: $node.text(),
translation_id: data.oeTranslationId || null
});
});
openerp.jsonRpc('/website/set_translations', 'call', {
'data': trans,
'lang': website.get_context()['lang'],
}).then(function () {
mysuper.call(self);
}).fail(function () {
// TODO: bootstrap alert with error message
alert("Could not save translation");
});
},
transNode: function (node, view_id) {
// Mostly handling text and cdata nodes here
// so avoid jquery usage in this function
if (node.attributes['data-oe-type']) {
if (node.attributes['data-oe-translate'].value == '1') {
node.className += ' oe_translatable_field';
}
return;
} else if (node.childNodes.length === 1
&& this.isTextNode(node.childNodes[0])
&& !node.getAttribute('data-oe-model')) {
this.markTranslatableNode(node, view_id);
} else {
for (var i = 0, l = node.childNodes.length; i < l; i ++) {
var n = node.childNodes[i];
if (this.isTextNode(n)) {
if (this.isTranslatable(n.data)) {
var container = document.createElement('span');
node.insertBefore(container, n);
container.appendChild(n);
this.markTranslatableNode(container, view_id);
}
} else {
this.transNode(n, view_id);
}
}
}
},
});
website.RTE.include({
start: function () {
this._super.apply(this, arguments);
this.$el.hide();
},
fetch_editables: function (root) {
$(root).click(function (ev) {
ev.preventDefault();
});
return $('[data-oe-translate=1]');
}
});
website.TranslatorDialog = openerp.Widget.extend({
events: _.extend({}, website.EditorBar.prototype.events, {
'hidden.bs.modal': 'destroy',
'click button[data-action=activate]': function (ev) {
this.trigger('activate');
},
}),
template: 'website.TranslatorDialog',
start: function () {
this.$el.modal();
},
});
})();

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.TranslatorDialog">
<div class="modal fade oe_website_translator" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button title="Close" type="button" class="close" data-dismiss="modal">×</button>
<h2 class="modal-title">Translate this page</h2>
</div>
<div class="modal-body">
<section>
<p>
You are about to enter the translation mode.
</p>
<p>
In this mode you can not change the structure of the document, you can only translate text.
</p>
<p>
Here are the visuals used in order to help you in your translation flow:
<ul class="oe_translate_examples">
<li class="oe_translatable_text">
Normal translatable content
</li>
<li class="oe_translatable_field">
ERP translatable object data
</li>
<li class="oe_translatable_todo">
Translation TODO
</li>
</ul>
</p>
</section>
<hr/>
<section class="row">
<div class="col-md-6">
<button type="button" data-action="activate" class="btn btn-primary">Ok</button>
or
<a data-action="discard" data-dismiss="modal" href="#">Cancel</a>
</div>
<div class="col-md-6">
<label>
<input type="checkbox" name="do_not_show"/>
Do not show this dialog later.
</label>
</div>
</section>
</div>
</div>
</div>
</div>
</t>
</templates>

View File

@ -22,7 +22,6 @@
<li><a data-action="show-mobile-preview" href="#"><span title="Mobile preview" class="icon-mobile-phone"/></a></li>
<li class="divider-vertical"></li>
<li><a data-action="promote-current-page" href="#"><span title="Promote page on the web">Promote</span></a></li>
<li><a href="#">Translate</a></li>
<li class="dropdown">
<a id="customize-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">Customize <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" id="customize-menu">

View File

@ -15,6 +15,7 @@
&lt;!DOCTYPE html&gt;
<html t-att-lang="lang.replace('_', '-')"
t-att-data-editable="'1' if editable else '0'"
t-att-data-translatable="'1' if translatable else '0'"
t-att-data-view-xmlid="str(__stack__[0])">
<head>
<title><t t-esc="title or res_company.name"/></title>
@ -30,6 +31,7 @@
</t>
<t t-call="website.theme"/>
<script type="text/javascript" src="/web/static/lib/es5-shim/es5-shim.min.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
@ -43,7 +45,7 @@
<t t-if="editable">
<script type="text/javascript" src="/website/static/lib/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/website/static/lib/ckeditor.sharedspace/plugin.js"></script>
<script type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
<script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
<script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
@ -53,8 +55,9 @@
<script type="text/javascript" src="/website/static/src/js/website.editor.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.seo.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.snippets.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.ace.js"></script>
<script t-if="not translatable" type="text/javascript" src="/website/static/src/js/website.snippets.js"></script>
<script t-if="not translatable" type="text/javascript" src="/website/static/src/js/website.ace.js"></script>
<script t-if="translatable" type="text/javascript" src="/website/static/src/js/website.translator.js"></script>
</t>
<t t-raw="head or ''"/>
@ -71,14 +74,14 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/page/website.homepage"><em>Your</em><b>Company</b></a>
<a class="navbar-brand" t-href="/page/website.homepage"><em>Your</em><b>Company</b></a>
</div>
<div class="collapse navbar-collapse navbar-top-collapse">
<ul class="nav navbar-nav navbar-right" id="top_menu">
<li name="contactus"><a href="/page/website.contactus">Contact us</a></li>
<li name="contactus"><a t-href="/page/website.contactus">Contact us</a></li>
<li t-if="user_id.id == website.public_user.id"><a href="/admin">Sign in</a></li>
<li t-if="user_id.id != website.public_user.id"><a href="/admin"><span t-field="user_id.name"/></a></li>
<li t-if="len(website.language_ids) &gt; 1" class="dropdown">
<li t-if="request.multilang and len(website.language_ids) &gt; 1" class="dropdown">
<!-- TODO: use flags for language selection -->
<t t-set="lang_selected" t-value="[lg for lg in website.language_ids if lg.code == lang]"/>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
@ -86,7 +89,7 @@
</a>
<ul class="dropdown-menu" role="menu">
<li t-foreach="website.language_ids" t-as="lg">
<a href="#" role="menuitem">
<a t-att-href="url_for('', lang=lg.code)" role="menuitem">
<strong t-att-class="'icon-check' if lg.code == lang
else 'icon-check-empty'"></strong>
<t t-esc="lg.name"/>
@ -106,13 +109,13 @@
<div class="col-md-3" name="product">
<h4>Our products &amp; Services</h4>
<ul class="list-unstyled" name="products">
<li><a href="/">Home</a></li>
<li><a t-href="/">Home</a></li>
</ul>
</div>
<div class="col-md-3" name="info">
<h4 name="info_title">Connect with us</h4>
<ul class="list-unstyled">
<li><a href="/page/website.contactus">Contact us</a></li>
<li><a t-href="/page/website.contactus">Contact us</a></li>
</ul>
<ul class="list-unstyled">
<li><i class="icon-phone"></i> <span t-field="res_company.phone"></span></li>
@ -127,7 +130,7 @@
<div class="col-md-5 col-lg-offset-1" name="about_us">
<h4>
<span t-field="res_company.name">Your Company</span>
<small> - <a href="/page/website.aboutus">About us</a></small>
<small> - <a t-href="/page/website.aboutus">About us</a></small>
</h4>
<p>
We are a team of passionated people whose goal is to improve everyone's
@ -147,7 +150,7 @@
<a class="label label-danger" href="https://openerp.com/apps/website">OpenERP</a>
</div>
<div class="pull-left text-muted">
Copyright &amp;copy; <span t-field="res_company.name">Company name</span> - <a href="/sitemap">Sitemap</a>
Copyright &amp;copy; <span t-field="res_company.name">Company name</span> - <a t-href="/sitemap">Sitemap</a>
</div>
</div>
</footer>
@ -164,7 +167,7 @@
<div class="col-md-4">
<h4 class="mt16">Subtitle</h4>
<p>
<a href="/">Homepage</a>
<a t-href="/">Homepage</a>
</p>
</div>
<div class="col-md-4">
@ -208,7 +211,7 @@
<div class="well mt32">
<p>This page does not exists, but you can create it as you are administrator of this site.</p>
<a class="btn btn-primary" t-att-href="'/pagenew/'+path">Create Page</a>
<span class="text-muted">or</span> <a href="/sitemap">Search a Page</a>
<span class="text-muted">or</span> <a t-href="/sitemap">Search a Page</a>
</div>
<div class="text-center text-muted">Edit the content bellow this line to adapt the default "page not found" page.</div>
</div>
@ -223,8 +226,8 @@
</p>
<p>Maybe you were looking for one of these popular pages ?</p>
<ul>
<li><a href="/">Homepage</a></li>
<li><a href="/">Contact Us</a></li>
<li><a t-href="/">Homepage</a></li>
<li><a t-href="/page/website.contactus/">Contact Us</a></li>
</ul>
</div>
</div>
@ -257,8 +260,8 @@
</p>
<pre t-if="editable" t-esc="error"/>
<ul>
<li><a href="/">Homepage</a></li>
<li><a href="/">Contact Us</a></li>
<li><a t-href="/">Homepage</a></li>
<li><a t-href="/page/website.contactus/">Contact Us</a></li>
</ul>
</div>
</div>
@ -281,7 +284,7 @@
<p>We'll do our best to get back to you as soon as possible.</p>
</div>
<div class="text-center mt64" name="mail_button">
<a t-att-href="'mailto:'+res_company.email" class="btn btn-primary">Send us an email</a>
<a t-attf-href="mailto:{{ res_company.email }}" class="btn btn-primary">Send us an email</a>
</div>
</div>
<div class="col-md-4">

View File

@ -7,22 +7,35 @@ from openerp.osv import osv, fields
from openerp.addons.web import http
from openerp.addons.web.http import request
import urllib
from urlparse import urljoin
import math
import traceback
from openerp.tools.safe_eval import safe_eval
from openerp.exceptions import AccessError, AccessDenied
import werkzeug
import logging
logger = logging.getLogger(__name__)
def route(*route_args, **route_kwargs):
def route(routes, *route_args, **route_kwargs):
def decorator(f):
@http.route(*route_args, **route_kwargs)
new_routes = routes if isinstance(routes, list) else [routes]
f.multilang = route_kwargs.get('multilang', False)
if f.multilang:
route_kwargs.pop('multilang')
for r in list(new_routes):
new_routes.append('/<string(length=5):lang_code>' + r)
@http.route(new_routes, *route_args, **route_kwargs)
@functools.wraps(f, assigned=functools.WRAPPER_ASSIGNMENTS + ('func_name',))
def wrap(*args, **kwargs):
request.route_lang = None # WIP: decorator will support lang argument
request.route_lang = kwargs.get('lang_code', None)
if not hasattr(request, 'website'):
request.multilang = f.multilang
request.website = request.registry['website'].get_current()
if request.route_lang:
lang_ok = [lg.code for lg in request.website.language_ids if lg.code == request.route_lang]
if not lang_ok:
return request.not_found()
request.website.preprocess_request(*args, **kwargs)
return f(*args, **kwargs)
return wrap
@ -36,6 +49,19 @@ def auth_method_public():
request.uid = request.session.uid
http.auth_methods['public'] = auth_method_public
def url_for(path, lang=None):
if request:
path = urljoin(request.httprequest.path, path)
langs = request.context.get('langs')
if path[0] == '/' and len(langs) > 1:
ps = path.split('/')
lang = lang or request.context.get('lang')
if ps[1] in langs:
ps[1] = lang
else:
ps.insert(1, lang)
path = '/'.join(ps)
return path
def urlplus(url, params):
if not params:
@ -77,12 +103,22 @@ class website(osv.osv):
return lang
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
def redirect(url):
return werkzeug.utils.redirect(url_for(url))
request.redirect = redirect
is_public_user = request.uid == self.get_public_user().id
lang = self.get_lang()
is_master_lang = lang == request.website.default_lang_id.code
request.context.update({
'lang': lang,
'langs': [lg.code for lg in request.website.language_ids],
'multilang': request.multilang,
'is_public_user': is_public_user,
'editable': not is_public_user, # TODO: check perms
'is_master_lang': is_master_lang,
'editable': not is_public_user,
'translatable': not is_public_user and not is_master_lang and request.multilang,
})
request.context['lang'] = self.get_lang()
def get_current(self):
# WIP, currently hard coded
@ -103,13 +139,15 @@ class website(osv.osv):
registry=request.registry,
json=simplejson,
website=request.website,
url_for=url_for,
res_company=request.website.company_id,
user_id=user.browse(cr, openerp.SUPERUSER_ID, uid),
)
context = {
'inherit_branding': qweb_context.setdefault('editable', False),
}
context = request.context.copy()
context.update(
inherit_branding=qweb_context.setdefault('editable', False),
)
# check if xmlid of the template exists
try:

View File

@ -45,7 +45,7 @@
</div>
<group col="4">
<field name="company_id" groups="base.group_multi_company"/>
<field name="default_lang_id"/>
<field name="default_lang_id" widget="selection"/>
</group>
<group string="Website languages">
<field name="language_ids" nolabel="1" mode="tree,form">

View File

@ -39,7 +39,7 @@
<div class='navbar'>
<div>
<t t-call="website.pager">
<t t-set="classname">pull-left</t>
<t t-set="classname" t-value="'pull-left'"/>
</t>
<form action="/references/" method="get" class="navbar-search pull-right pagination form-inline">
<div class="form-group">

View File

@ -8,7 +8,7 @@ from urllib import quote_plus
class contactus(http.Controller):
@website.route(['/crm/contactus'], type='http', auth="admin")
@website.route(['/crm/contactus'], type='http', auth="admin", multilang=True)
def contactus(self, *arg, **post):
post['user_id'] = False
request.registry['crm.lead'].create(request.cr, request.uid,

View File

@ -4,7 +4,7 @@
<template id="contactus_form" name="Contact Form" inherit_id="website.contactus" inherit_option_id="website.contactus">
<xpath expr="//div[@name='mail_button']" position="replace">
<form action="/crm/contactus" method="post" class="form-horizontal mt32" >
<form t-action="/crm/contactus" method="post" class="form-horizontal mt32" >
<div class="form-group">
<label class="col-md-3 col-sm-4 control-label" for="contact_name">Your Name</label>
<div class="col-md-7 col-sm-8">

View File

@ -9,7 +9,7 @@ import urllib
class website_crm_partner_assign(http.Controller):
@website.route(['/partners/', '/partners/page/<int:page>/'], type='http', auth="public")
@website.route(['/partners/', '/partners/page/<int:page>/'], type='http', auth="public", multilang=True)
def partners(self, page=0, **post):
partner_obj = request.registry['res.partner']
@ -84,7 +84,7 @@ class website_crm_partner_assign(http.Controller):
}
return request.website.render("website_crm_partner_assign.index", values)
@website.route(['/partners/<int:ref_id>/'], type='http', auth="public")
@website.route(['/partners/<int:ref_id>/'], type='http', auth="public", multilang=True)
def partners_ref(self, ref_id=0, **post):
partner_obj = request.registry['res.partner']
partner_ids = partner_obj.search(

View File

@ -7,7 +7,7 @@
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a href="/partners/">Partners</a></li>
<li><a t-href="/partners/">Partners</a></li>
</xpath>
</template>
@ -40,7 +40,7 @@
<t t-call="website.pager">
<t t-set="classname">pull-left</t>
</t>
<form action="/partners/" method="get" class="navbar-search pull-right pagination form-inline">
<form t-action="/partners/" method="get" class="navbar-search pull-right pagination form-inline">
<div class="form-group">
<input type="text" name="search" class="search-query col-md-2 mt4 form-control" placeholder="Search" t-att-value="searches.get('search') or '' or ''"/>
</div>
@ -77,11 +77,11 @@
</t>
<div class="media thumbnail" data-publish="">
<t t-call="website.publish_management"><t t-set="object" t-value="partner"/></t>
<a class="pull-left" t-attf-href="/partners/#{ partner.id }/">
<a class="pull-left" t-href="/partners/#{ partner.id }/">
<img class="media-object" t-att-src="partner.img('image_small')"/>
</a>
<div class="media-body" style="min-height: 64px;">
<a class="media-heading" t-attf-href="/partners/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a> - <span t-field="partner.grade_id"/>
<a class="media-heading" t-href="/partners/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a> - <span t-field="partner.grade_id"/>
<div t-field="partner.website_short_description"/>
</div>
</div>

View File

@ -36,7 +36,7 @@ import werkzeug
class website_event(http.Controller):
_order = 'website_published desc, date_begin desc'
@website.route(['/event/', '/event/page/<int:page>/'], type='http', auth="public")
@website.route(['/event/', '/event/page/<int:page>/'], type='http', auth="public", multilang=True)
def events(self, page=1, **searches):
cr, uid, context = request.cr, request.uid, request.context
event_obj = request.registry['event.event']
@ -152,7 +152,7 @@ class website_event(http.Controller):
return request.website.render("website_event.index", values)
@website.route(['/event/<int:event_id>'], type='http', auth="public")
@website.route(['/event/<int:event_id>'], type='http', auth="public", multilang=True)
def event(self, event_id=None, **post):
event_obj = request.registry['event.event']
values = {
@ -162,7 +162,7 @@ class website_event(http.Controller):
}
return request.website.render("website_event.event_description_full", values)
@website.route(['/event/<int:event_id>/add_cart'], type='http', auth="public")
@website.route(['/event/<int:event_id>/add_cart'], type='http', auth="public", multilang=True)
def add_cart(self, event_id=None, **post):
user_obj = request.registry['res.users']
order_line_obj = request.registry.get('sale.order.line')
@ -210,5 +210,5 @@ class website_event(http.Controller):
order.write({'order_line': [(4, order_line_id)]}, context=request.context)
if not _values:
return werkzeug.utils.redirect("/event/%s/" % event_id)
return werkzeug.utils.redirect("/shop/checkout")
return request.redirect("/event/%s/" % event_id)
return request.redirect("/shop/checkout")

View File

@ -6,10 +6,10 @@
<template id="header_footer" inherit_id="website_sale.header_footer">
<xpath expr="//header//ul[@id='top_menu']/li" position="before">
<li><a href="/event">Events</a></li>
<li><a t-href="/event">Events</a></li>
</xpath>
<xpath expr="//footer//ul[@name='products']/li" position="after">
<li><a href="/event">Events</a></li>
<li><a t-href="/event">Events</a></li>
</xpath>
</template>
@ -36,7 +36,7 @@
<li class="nav-header">Date</li>
<t t-foreach="dates" t-as="date">
<li t-att-class="searches.get('date') == date[0] and 'active' or ''">
<a t-attf-href="/event/#{ search_path }&amp;date=#{ date[0] }"><t t-esc="date[1]"/> <small t-if="date[3]">(<t t-esc="date[3]"/>)</small></a>
<a t-href="/event/#{ search_path }&amp;date=#{ date[0] }"><t t-esc="date[1]"/> <small t-if="date[3]">(<t t-esc="date[3]"/>)</small></a>
</li>
</t>
</ul>
@ -74,7 +74,7 @@
available.
</span>
</t>
<h4 class="media-heading"><a t-attf-href="/event/#{ event.id }/"><span t-field="event.name"> </span></a></h4>
<h4 class="media-heading"><a t-href="/event/#{ event.id }/"><span t-field="event.name"> </span></a></h4>
<div>
<span t-field="event.type">: </span>
<t t-if="event.organizer_id">
@ -107,7 +107,7 @@
<li class="nav-header">Category</li>
<t t-foreach="types">
<li t-if="type" t-att-class="searches.get('type') == str(type and type[0]) and 'active' or ''">
<a t-attf-href="/event/#{ search_path }&amp;type=#{ type[0] }"><t t-esc="type[1]"/> <small>(<t t-esc="type_count"/>)</small></a>
<a t-href="/event/#{ search_path }&amp;type=#{ type[0] }"><t t-esc="type[1]"/> <small>(<t t-esc="type_count"/>)</small></a>
</li>
</t>
</ul>
@ -119,7 +119,7 @@
<li class="nav-header">Location</li>
<t t-foreach="countries">
<li t-if="country_id" t-att-class="searches.get('country') == str(country_id and country_id[0]) and 'active' or ''">
<a t-attf-href="/event/#{ search_path }&amp;country=#{ country_id[0] }"><t t-esc="country_id[1]"/><small>(<t t-esc="country_id_count"/>)</small></a>
<a t-href="/event/#{ search_path }&amp;country=#{ country_id[0] }"><t t-esc="country_id[1]"/><small>(<t t-esc="country_id_count"/>)</small></a>
</li>
</t>
</ul>
@ -164,7 +164,7 @@
<div class="col-md-8">
<t t-if="event_id.event_ticket_ids">
<hr/>
<form t-attf-action="/event/#{ event_id.id }/add_cart" method="post">
<form t-action="/event/#{ event_id.id }/add_cart" method="post">
<table class="table">
<thead>
<tr>

View File

@ -6,7 +6,7 @@ from openerp.addons.website import website
class website_hr(http.Controller):
@website.route(['/page/website.aboutus'], type='http', auth="public")
@website.route(['/page/website.aboutus'], type='http', auth="public", multilang=True)
def blog(self, **post):
hr_obj = request.registry['hr.employee']
employee_ids = hr_obj.search(request.cr, request.uid, [(1, "=", 1)],

View File

@ -14,7 +14,7 @@
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a href="/blog/%(website_hr.website_mail_job)d/">Jobs</a></li>
<li><a t-href="/blog/%(website_hr.website_mail_job)d/">Jobs</a></li>
</xpath>
</template>

View File

@ -9,7 +9,7 @@ import urllib
class website_crm_partner_assign(http.Controller):
@website.route(['/members/', '/members/page/<int:page>/'], type='http', auth="public")
@website.route(['/members/', '/members/page/<int:page>/'], type='http', auth="public", multilang=True)
def members(self, page=0, **post):
membership_obj = request.registry['membership.membership_line']
@ -56,7 +56,7 @@ class website_crm_partner_assign(http.Controller):
}
return request.website.render("website_membership.index", values)
@website.route(['/members/<int:ref_id>/'], type='http', auth="public")
@website.route(['/members/<int:ref_id>/'], type='http', auth="public", multilang=True)
def partners_ref(self, ref_id=0, **post):
partner_obj = request.registry['res.partner']
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_published', '=', True), ('id', '=', ref_id)], context=request.context)

View File

@ -7,7 +7,7 @@
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a href="/members/">Members</a></li>
<li><a t-href="/members/">Members</a></li>
</xpath>
</template>
@ -41,7 +41,7 @@
<t t-call="website.pager">
<t t-set="classname">pull-left</t>
</t>
<form action="/members/" method="get" class="navbar-search pull-right pagination form-inline">
<form t-action="/members/" method="get" class="navbar-search pull-right pagination form-inline">
<div class="form-group">
<input type="text" name="search" class="search-query col-md-2 mt4 form-control" placeholder="Search" t-att-value="searches.get('search') or '' or ''"/>
</div>
@ -68,11 +68,11 @@
<t t-set="partner" t-value="membership_line_id.partner"/>
<div class="media thumbnail" data-publish="">
<t t-call="website.publish_management"><t t-set="object" t-value="partner"/></t>
<a class="pull-left" t-attf-href="/members/#{ partner.id }/">
<a class="pull-left" t-href="/members/#{ partner.id }/">
<img class="media-object" t-att-src="partner.img('image_small')"/>
</a>
<div class="media-body" style="min-height: 64px;">
<a class="media-heading" t-attf-href="/members/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a> - <span t-field="membership_line_id.membership_id"/>
<a class="media-heading" t-href="/members/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a> - <span t-field="membership_line_id.membership_id"/>
<div t-field="partner.website_short_description"/>
</div>
</div>

View File

@ -39,7 +39,7 @@ class Website(osv.Model):
class website_project(http.Controller):
@website.route(['/project/<int:project_id>/'], type='http', auth="public")
@website.route(['/project/<int:project_id>/'], type='http', auth="public", multilang=True)
def project(self, project_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
project_obj = request.registry['project.project']
@ -51,7 +51,7 @@ class website_project(http.Controller):
}
return request.website.render("website_project.index", render_values)
@website.route(['/project/task/<int:task_id>'], type='http', auth="public")
@website.route(['/project/task/<int:task_id>'], type='http', auth="public", multilang=True)
def task(self, task_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
task_obj = request.registry['project.task']

View File

@ -5,7 +5,7 @@
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<xpath expr="//footer//ul[@name='products']" position="inside">
<li t-foreach="website_project_ids" t-as="project"><a t-attf-href="/project/#{ project.id }/"><t t-esc="project.name"/></a></li>
<li t-foreach="website_project_ids" t-as="project"><a t-href="/project/#{ project.id }/"><t t-esc="project.name"/></a></li>
</xpath>
</template>
@ -24,7 +24,7 @@
<!-- Project -->
<template id="task_kanban_card" name="TaskKanban">
<div class="thumbnail">
<a t-attf-href="/project/task/#{object_id.id}/"><span t-field="object_id.name"/></a>
<a t-href="/project/task/#{object_id.id}/"><span t-field="object_id.name"/></a>
<div>
Assigned to <span t-field="object_id.user_id"/>
</div>

View File

@ -209,7 +209,7 @@ class Ecommerce(http.Controller):
product_ids = [id for id in product_ids if id in product_obj.search(request.cr, request.uid, [("id", 'in', product_ids)], context=request.context)]
return product_obj.browse(request.cr, SUPERUSER_ID, product_ids, context=request.context)
@website.route(['/shop/', '/shop/category/<cat_id>/', '/shop/category/<cat_id>/page/<int:page>/', '/shop/page/<int:page>/'], type='http', auth="public")
@website.route(['/shop/', '/shop/category/<cat_id>/', '/shop/category/<cat_id>/page/<int:page>/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
def category(self, cat_id=0, page=0, **post):
if 'promo' in post:
@ -250,7 +250,7 @@ class Ecommerce(http.Controller):
}
return request.website.render("website_sale.products", values)
@website.route(['/shop/product/<product_id>/'], type='http', auth="public")
@website.route(['/shop/product/<product_id>/'], type='http', auth="public", multilang=True)
def product(self, cat_id=0, product_id=0, **post):
if 'promo' in post:
@ -293,11 +293,11 @@ class Ecommerce(http.Controller):
}
return request.website.render("website_sale.product", values)
@website.route(['/shop/add_product/', '/shop/category/<cat_id>/add_product/'], type='http', auth="public")
@website.route(['/shop/add_product/', '/shop/category/<cat_id>/add_product/'], type='http', auth="public", multilang=True)
def add_product(self, cat_id=0, **post):
product_id = request.registry.get('product.product').create(request.cr, request.uid,
{'name': 'New Product', 'public_categ_id': cat_id}, request.context)
return werkzeug.utils.redirect("/shop/product/%s/?unable_editor=1" % product_id)
return request.redirect("/shop/product/%s/?unable_editor=1" % product_id)
def get_pricelist(self):
if not request.httprequest.session.get('ecommerce_pricelist'):
@ -385,7 +385,7 @@ class Ecommerce(http.Controller):
return [quantity, order.get_total_quantity()]
@website.route(['/shop/mycart/'], type='http', auth="public")
@website.route(['/shop/mycart/'], type='http', auth="public", multilang=True)
def mycart(self, **post):
order = get_current_order()
prod_obj = request.registry.get('product.product')
@ -411,10 +411,10 @@ class Ecommerce(http.Controller):
}
return request.website.render("website_sale.mycart", values)
@website.route(['/shop/<path:path>/add_cart/', '/shop/add_cart/'], type='http', auth="public")
def add_cart(self, path=None, product_id=None, order_line_id=None, remove=None):
@website.route(['/shop/<path:path>/add_cart/', '/shop/add_cart/'], type='http', auth="public", multilang=True)
def add_cart(self, path=None, product_id=None, order_line_id=None, remove=None, **kw):
self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
return werkzeug.utils.redirect("/shop/mycart/")
return request.redirect("/shop/mycart/")
@website.route(['/shop/add_cart_json/'], type='json', auth="public")
def add_cart_json(self, product_id=None, order_line_id=None, remove=None):
@ -424,7 +424,7 @@ class Ecommerce(http.Controller):
def set_cart_json(self, path=None, product_id=None, order_line_id=None, set_number=0, json=None):
return self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, set_number=set_number)
@website.route(['/shop/checkout/'], type='http', auth="public")
@website.route(['/shop/checkout/'], type='http', auth="public", multilang=True)
def checkout(self, **post):
classic_fields = ["name", "phone", "fax", "email", "street", "city", "state_id", "zip"]
rel_fields = ['country_id', 'state_id']
@ -465,7 +465,7 @@ class Ecommerce(http.Controller):
return request.website.render("website_sale.checkout", values)
@website.route(['/shop/confirm_order/'], type='http', auth="public")
@website.route(['/shop/confirm_order/'], type='http', auth="public", multilang=True)
def confirm_order(self, **post):
order = get_current_order()
@ -474,10 +474,10 @@ class Ecommerce(http.Controller):
user_obj = request.registry.get('res.users')
if order.state != 'draft':
return werkzeug.utils.redirect("/shop/checkout/")
return request.redirect("/shop/checkout/")
if not order.order_line:
error.append("empty_cart")
return werkzeug.utils.redirect("/shop/checkout/")
return request.redirect("/shop/checkout/")
# check values
request.session['checkout'] = post
@ -488,7 +488,7 @@ class Ecommerce(http.Controller):
if post.get('shipping_different') and key != 'email' and not post.get("shipping_%s" % key):
error.append("shipping_%s" % key)
if error:
return werkzeug.utils.redirect("/shop/checkout/?error=%s&shipping=%s" % (",".join(error), post.get('shipping_different') and 'on' or ''))
return request.redirect("/shop/checkout/?error=%s&shipping=%s" % (",".join(error), post.get('shipping_different') and 'on' or ''))
# search or create company
company_id = None
@ -548,9 +548,9 @@ class Ecommerce(http.Controller):
order_value.update(request.registry.get('sale.order').onchange_partner_id(request.cr, SUPERUSER_ID, [], order.partner_id.id, context=request.context)['value'])
order.write(order_value)
return werkzeug.utils.redirect("/shop/payment/")
return request.redirect("/shop/payment/")
@website.route(['/shop/payment/'], type='http', auth="public")
@website.route(['/shop/payment/'], type='http', auth="public", multilang=True)
def payment(self, **post):
order = get_current_order()
@ -571,11 +571,11 @@ class Ecommerce(http.Controller):
return request.website.render("website_sale.payment", values)
@website.route(['/shop/payment_validate/'], type='http', auth="public")
@website.route(['/shop/payment_validate/'], type='http', auth="public", multilang=True)
def payment_validate(self, **post):
request.httprequest.session['ecommerce_order_id'] = False
request.httprequest.session['ecommerce_pricelist'] = False
return werkzeug.utils.redirect("/shop/")
return request.redirect("/shop/")
@website.route(['/shop/change_sequence/'], type='json', auth="public")
def change_sequence(self, id, top):

View File

@ -45,9 +45,9 @@
<template id="header_footer" inherit_id="website.layout" name="Custom Footer">
<xpath expr="//header//ul[@id='top_menu']/li" position="before">
<li><a href="/shop/">Shop</a></li>
<li><a t-href="/shop/">Shop</a></li>
<li>
<a href="/shop/mycart/">
<a t-href="/shop/mycart/">
<i class="icon-shopping-cart"></i>
My cart <span t-attf-class="my_cart_quantity badge #{(not website_sale_order or not website_sale_order.get_total_quantity()) and 'hidden' or ''}"
t-esc="website_sale_order and website_sale_order.get_total_quantity() or ''"/>
@ -55,9 +55,9 @@
</li>
</xpath>
<xpath expr="//footer//ul[@name='products']" position="inside">
<li><a href="/shop/">Shop</a></li>
<li><a t-href="/shop/">Shop</a></li>
<li>
<a href="/shop/mycart/">
<a t-href="/shop/mycart/">
<i class="icon-shopping-cart"></i>
My cart <span t-attf-class="my_cart_quantity badge #{(not website_sale_order or not website_sale_order.get_total_quantity()) and 'hidden' or ''}"
t-esc="website_sale_order and website_sale_order.get_total_quantity() or ''"/>
@ -69,7 +69,7 @@
<template id="categories_recursive">
<li t-att-class="category.id == category_id and 'active' or ''">
<a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-attf-href="/shop/category/#{ category.id }/" t-field="category.name"></a>
<a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-href="/shop/category/#{ category.id }/" t-field="category.name"></a>
<ul t-if="category.child_id" class="nav nav-pills nav-stacked nav-hierarchy">
<t t-foreach="category.child_id" t-as="category">
<t t-if="category.id in categ[1] or editable">
@ -84,7 +84,7 @@
<template id="products_cart">
<div class="oe_product_description">
<a t-attf-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<a t-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<b t-field="product.name"/>
</a>
<!-- This should be an option -->
@ -104,7 +104,7 @@
</div>
<div class="oe_product_image text-center">
<a t-attf-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<a t-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<img class="img" t-att-src="product.img('image')"/>
</a>
</div>
@ -125,9 +125,9 @@
<div class="col-sm-4">
<h1>Our Products</h1>
</div><div class="col-sm-4 pagination text-center">
<a t-if="editable" t-attf-href="/shop/#{ category_id and ('category/%s/' % category_id) or ''}add_product/" class="btn btn-default">New Product</a>
<a t-if="editable" t-href="/shop/#{ category_id and ('category/%s/' % category_id) or ''}add_product/" class="btn btn-default">New Product</a>
</div><div class="col-sm-4">
<form t-attf-action="/shop/#{ category_id and ('category/%s/' % category_id) or ''}" method="get" class="pull-right pagination">
<form t-action="/shop/#{ category_id and ('category/%s/' % category_id) or ''}" method="get" class="pull-right pagination">
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-search"/></span>
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search or ''"/>
@ -193,7 +193,7 @@
<template id="add_to_basket" inherit_option_id="website_sale.products_cart" name="Add to Cart">
<xpath expr="//div[@class='product_price']" position="inside">
<a t-attf-href="./add_cart/?product_id=#{ product.id }">
<a t-href="./add_cart/?product_id=#{ product.id }">
<span class="icon-shopping-cart"/>
</a>
</xpath>
@ -229,8 +229,8 @@
<div class="row">
<div class="col-sm-5">
<ol class="breadcrumb">
<li><a href="/shop">Products</a></li>
<li t-if="category"><a t-att-href="'/shop/category/%s' % (category_id,)"><span t-field="category.name"/></a></li>
<li><a t-href="/shop">Products</a></li>
<li t-if="category"><a t-href="'/shop/category/%s' % (category_id,)"><span t-field="category.name"/></a></li>
<li class="active" t-field="product.name">Product Name</li>
</ol>
</div><div class="col-sm-3">
@ -253,7 +253,7 @@
</li>
</t>
</div><div class="col-sm-3 col-sm-offset-1">
<form t-attf-action="/shop/#{ category_id and ('category/%s/' % category_id) or ''}" method="get" class="pull-right">
<form t-action="/shop/#{ category_id and ('category/%s/' % category_id) or ''}" method="get" class="pull-right">
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-search"/></span>
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search or ''"/>
@ -270,7 +270,7 @@
</div><div class="col-sm-5 col-md-5 col-lg-4 col-lg-offset-1">
<h1 t-field="product.name">Product Name</h1>
<form action="./add_cart/">
<form t-action="./add_cart/">
<input type="hidden" t-if="len(product.product_variant_ids) &lt;= 1" name="product_id" t-att-value="product.id"/>
<t t-if="len(product.product_variant_ids) &gt; 1">
<label label-default="label-default" class="radio" t-foreach="product.product_variant_ids" t-as="product">
@ -319,7 +319,7 @@
<div class='mt16 text-center'>
<img t-att-src="product.img('image_small')"/>
<h5>
<a t-attf-href="/shop/product/#{ product.id }/"
<a t-href="/shop/product/#{ product.id }/"
style="display: block">
<span t-field='product.name'
style="display: block"/>
@ -368,10 +368,10 @@
<tr>
<td colspan="2" t-if="not line.product_id.product_tmpl_id"></td>
<td t-if="line.product_id.product_tmpl_id">
<a t-attf-href="/shop/product/#{ line.product_id.product_tmpl_id.id }/"><img class="img-rounded" t-att-src="line.product_id.img('image_small')"/></a>
<a t-href="/shop/product/#{ line.product_id.product_tmpl_id.id }/"><img class="img-rounded" t-att-src="line.product_id.img('image_small')"/></a>
</td>
<td t-if="line.product_id.product_tmpl_id">
<a t-attf-href="/shop/product/#{ line.product_id.product_tmpl_id.id }/"><span t-field="line.name"/></a><br/>
<a t-href="/shop/product/#{ line.product_id.product_tmpl_id.id }/"><span t-field="line.name"/></a><br/>
<small t-field="line.product_id.description_sale"/>
</td>
<td>
@ -383,8 +383,8 @@
<td>
<div class="pull-right">
<input type="text" class="js_quantity input-sm col-md-5" t-att-data-id="line.id" t-att-value="line.product_uom_qty"/>
<a t-attf-href="./remove_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="btn btn-default mb8 btn-sm btn-inverse">-</a>
<a t-attf-href="./add_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="btn btn-default mb8 btn-sm btn-success">+</a>
<a t-href="./remove_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="btn btn-default mb8 btn-sm btn-inverse">-</a>
<a t-href="./add_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="btn btn-default mb8 btn-sm btn-success">+</a>
</div>
</td>
</tr>
@ -404,7 +404,7 @@
<tr> <th colspan="3"><h4>Total</h4></th> <th><h4><t t-esc="website_sale_order and website_sale_order.amount_total or 0"/></h4></th></tr>
</thead>
</table>
<a t-if="website_sale_order and website_sale_order.order_line" href="/shop/checkout/" class="btn btn-success">Proceed To Payment</a>
<a t-if="website_sale_order and website_sale_order.order_line" t-href="/shop/checkout/" class="btn btn-success">Proceed To Payment</a>
</div>
</div>
<div class="oe_structure"/>
@ -418,7 +418,7 @@
<xpath expr="//div[@id='products_grid']" position="before">
<div class="col-md-3">
<ul class="nav nav-pills nav-stacked mt16">
<li t-att-class=" '' if category_id else 'active' "><a href="/shop/">All Products</a></li>
<li t-att-class=" '' if category_id else 'active' "><a t-href="/shop/">All Products</a></li>
<t t-set="categ" t-value="get_categories()"/>
<t t-foreach="categ[0]" t-as="category">
<t t-call="website_sale.categories_recursive"/>
@ -449,7 +449,7 @@
<t t-foreach="suggested_products" t-as="product">
<tr>
<td>
<a t-attf-href="/shop/product/#{ product.id }/"><span t-field="product.name"/></a><br/>
<a t-href="/shop/product/#{ product.id }/"><span t-field="product.name"/></a><br/>
<small t-field="product.description_sale"/>
</td>
<td>
@ -457,7 +457,7 @@
</td>
<td>
<div class="pull-right">
<a t-attf-href="./add_cart/?product_id=#{ product.id }" class="btn btn-sm btn-success">+</a>
<a t-href="./add_cart/?product_id=#{ product.id }" class="btn btn-sm btn-success">+</a>
</div>
</td>
</tr>
@ -474,11 +474,11 @@
<t t-foreach="suggested_products" t-as="product">
<div class='col-md-2 thumbnail'>
<div class='mt16 text-center'>
<a t-attf-href="/shop/product/#{ product.id }/">
<a t-href="/shop/product/#{ product.id }/">
<img t-att-src="product.img('image_small')"/>
</a>
<h5>
<a t-attf-href="/shop/product/#{ product.id }/" style="display: block;">
<a t-href="/shop/product/#{ product.id }/" style="display: block;">
<span t-field="product.name"/>
</a>
</h5>
@ -491,7 +491,7 @@
</template>
<template id="reduction_code" inherit_option_id="website_sale.mycart" name="Reduction Code">
<xpath expr="//table[@id='mycart_total']" position="after">
<form t-if="website_sale_order and website_sale_order.order_line" class="well" action="/shop/mycart/" method="post">
<form t-if="website_sale_order and website_sale_order.order_line" class="well" t-action="/shop/mycart/" method="post">
<input name="promo" class='input' type="text" placeholder="Reduction Code..." t-att-value="website_sale_order.pricelist_id.code or ''"/>
<button class="btn">Apply Code</button>
</form>
@ -536,7 +536,7 @@
</tfoot>
</table>
</div>
<form class="col-md-8 form-horizontal" action="/shop/confirm_order/" method="post">
<form class="col-md-8 form-horizontal" t-action="/shop/confirm_order/" method="post">
<div class=" row">
<a t-if="not partner" t-attf-href="/admin#action=redirect&amp;url=#{ request.httprequest.host_url }/shop/checkout/" class="btn btn-primary">Log me, I have an account</a>
<h3 class="col-md-10">Billing Information</h3>
@ -737,7 +737,7 @@
<t t-foreach="payments or []" t-as="payment">
<div t-att-data-id="payment.id" t-raw="payment._content" class="hidden col-md-6"/>
</t>
<a href="/shop/payment_validate/" class="hidden btn btn-default">I validate my payment</a>
<a t-href="/shop/payment_validate/" class="hidden btn btn-default">I validate my payment</a>
</div>
</div>
</div>