[MERGE]Merge lp:~openerp-dev/openobject-addons/trunk-website-al.

bzr revid: bth@tinyerp.com-20131108112213-3yhd0lg7j1zy0qut
This commit is contained in:
bth-openerp 2013-11-08 16:52:13 +05:30
commit 723c47907a
67 changed files with 4030 additions and 1329 deletions

View File

@ -155,7 +155,6 @@
<field name="type" ref="event_type_4"/>
<field name="user_id" ref="base.user_root"/>
<field name="address_id" ref="base.res_partner_2"/>
<field name="organizer_id" ref="base.res_partner_address_4"/>
<field name="description"><![CDATA[
<div class="oe_structure">
<center><strong>5-days Technical Training</strong></center>

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import base64
import cStringIO
import contextlib
import hashlib
@ -38,7 +37,7 @@ logger = logging.getLogger(__name__)
def auth_method_public():
registry = openerp.modules.registry.RegistryManager.get(request.db)
if not request.session.uid:
request.uid = registry['website'].get_public_user().id
request.uid = registry['website'].get_public_user(request.cr, openerp.SUPERUSER_ID, request.context).id
else:
request.uid = request.session.uid
http.auth_methods['public'] = auth_method_public
@ -49,7 +48,19 @@ MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
class Website(openerp.addons.web.controllers.main.Home):
@website.route('/', type='http', auth="public", multilang=True)
def index(self, **kw):
return self.page("website.homepage")
# TODO: check if plain SQL is needed
menu = request.registry['website.menu']
root_domain = [('parent_id', '=', False)] # TODO: multiwebsite ('website_id', '=', request.website.id),
root_id = menu.search(request.cr, request.uid, root_domain, limit=1, context=request.context)[0]
first_menu = menu.search_read(
request.cr, request.uid, [('parent_id', '=', root_id)], ['url'],
limit=1, order='sequence', context=request.context)
if first_menu:
first_menu = first_menu[0]['url']
if first_menu and first_menu != '/':
return request.redirect(first_menu)
else:
return self.page("website.homepage")
@website.route('/pagenew/<path:path>', type='http', auth="user")
def pagenew(self, path, noredirect=NOPE):
@ -124,11 +135,8 @@ class Website(openerp.addons.web.controllers.main.Home):
values = {
'path': path,
}
try:
html = request.website.render(path, values)
except ValueError:
html = request.website.render('website.404', values)
return html
return request.website.render(path, values)
@website.route('/website/customize_template_toggle', type='json', auth='user')
def customize_template_set(self, view_id):
@ -269,7 +277,8 @@ class Website(openerp.addons.web.controllers.main.Home):
@website.route(['/robots.txt'], type='http', auth="public")
def robots(self):
return request.website.render('website.robots', {'url_root': request.httprequest.url_root})
body = request.website.render('website.robots', {'url_root': request.httprequest.url_root})
return request.make_response(body, headers=[('Content-Type', 'text/plain')])
@website.route('/sitemap', type='http', auth='public', multilang=True)
def sitemap(self, **kwargs):

View File

@ -8,6 +8,24 @@
<field name="default_lang_id" ref="base.lang_en"/>
</record>
<record id="main_menu" model="website.menu">
<field name="name">Main Menu</field>
</record>
<record id="menu_homepage" model="website.menu">
<field name="name">Homepage</field>
<field name="url">/</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">10</field>
</record>
<record id="menu_contactus" model="website.menu">
<field name="name">Contact us</field>
<field name="url">/page/website.contactus</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">20</field>
</record>
<record id="public_user" model="res.users">
<field name="name">public</field>
<field name="login">public</field>

View File

@ -1,22 +1,24 @@
# -*- coding: utf-8 -*-
import fnmatch
import functools
import logging
import math
import simplejson
import traceback
import urllib
import urlparse
import werkzeug
import werkzeug.exceptions
import werkzeug.wrappers
import openerp
from openerp.exceptions import AccessError, AccessDenied
from openerp.osv import osv, fields
from openerp.tools.safe_eval import safe_eval
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
from openerp.addons.base.ir.ir_qweb import QWebException
import logging
logger = logging.getLogger(__name__)
def route(routes, *route_args, **route_kwargs):
@ -33,47 +35,46 @@ def route(routes, *route_args, **route_kwargs):
request.route_lang = kwargs.get('lang_code', None)
if not hasattr(request, 'website'):
request.multilang = f.multilang
request.website = request.registry['website'].get_current()
# TODO: Select website, currently hard coded
request.website = request.registry['website'].browse(
request.cr, request.uid, 1, context=request.context)
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)
request.website.preprocess_request(request)
return f(*args, **kwargs)
return wrap
return decorator
def auth_method_public():
registry = openerp.modules.registry.RegistryManager.get(request.db)
if not request.session.uid:
request.uid = registry['website'].get_public_user().id
else:
request.uid = request.session.uid
http.auth_methods['public'] = auth_method_public
def url_for(path, lang=None, keep_query=None):
if request:
path = urljoin(request.httprequest.path, path)
def url_for(path_or_uri, lang=None, keep_query=None):
location = path_or_uri.strip()
url = urlparse.urlparse(location)
if request and not url.netloc and not url.scheme:
location = urlparse.urljoin(request.httprequest.path, location)
langs = request.context.get('langs')
if path[0] == '/' and (len(langs) > 1 or lang):
ps = path.split('/')
if location[0] == '/' and (len(langs) > 1 or lang):
ps = location.split('/')
lang = lang or request.context.get('lang')
if ps[1] in langs:
ps[1] = lang
else:
ps.insert(1, lang)
path = '/'.join(ps)
location = '/'.join(ps)
if keep_query:
keep = []
params = werkzeug.url_decode(request.httprequest.query_string)
params_keys = tuple(params.keys())
url = urlparse.urlparse(location)
location = url.path
params = werkzeug.url_decode(url.query)
query_params = frozenset(werkzeug.url_decode(request.httprequest.query_string).keys())
for kq in keep_query:
keep += fnmatch.filter(params_keys, kq)
if keep:
params = dict([(k, params[k]) for k in keep])
path += u'?%s' % werkzeug.urls.url_encode(params)
for param in fnmatch.filter(query_params, kq):
params[param] = request.params[param]
params = werkzeug.urls.url_encode(params)
if params:
location += '?%s' % params
return path
return location
def urlplus(url, params):
if not params:
@ -103,32 +104,28 @@ class website(osv.osv):
public_user = None
def get_public_user(self):
def get_public_user(self, cr, uid, context=None):
if not self.public_user:
ref = request.registry['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'website', 'public_user')
self.public_user = request.registry[ref[0]].browse(request.cr, openerp.SUPERUSER_ID, ref[1])
uid = openerp.SUPERUSER_ID
ref = self.pool['ir.model.data'].get_object_reference(cr, uid, 'website', 'public_user')
self.public_user = self.pool[ref[0]].browse(cr, uid, ref[1])
return self.public_user
def get_lang(self):
website = request.registry['website'].get_current()
if hasattr(request, 'route_lang'):
lang = request.route_lang
else:
lang = request.params.get('lang', None) or request.httprequest.cookies.get('lang', None)
if lang not in [lg.code for lg in website.language_ids]:
lang = website.default_lang_id.code
return lang
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
def preprocess_request(self, cr, uid, ids, request, context=None):
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_public_user = request.uid == self.get_public_user(cr, uid, context).id
# Select current language
if hasattr(request, 'route_lang'):
lang = request.route_lang
else:
lang = request.params.get('lang', None) or request.httprequest.cookies.get('lang', None)
if lang not in [lg.code for lg in request.website.language_ids]:
lang = request.website.default_lang_id.code
is_master_lang = lang == request.website.default_lang_id.code
request.context.update({
'lang': lang,
@ -141,16 +138,15 @@ class website(osv.osv):
'translatable': not is_public_user and not is_master_lang and request.multilang,
})
def get_current(self):
# WIP, currently hard coded
return self.browse(request.cr, request.uid, 1)
def render(self, cr, uid, ids, template, values=None, context=None):
view = self.pool.get("ir.ui.view")
IMD = self.pool.get("ir.model.data")
user = self.pool.get("res.users")
def render(self, cr, uid, ids, template, values=None):
view = request.registry.get("ir.ui.view")
IMD = request.registry.get("ir.model.data")
user = request.registry.get("res.users")
if not context:
context = {}
qweb_context = request.context.copy()
qweb_context = context.copy()
if values:
qweb_context.update(values)
@ -164,7 +160,6 @@ class website(osv.osv):
user_id=user.browse(cr, uid, uid),
)
context = request.context.copy()
context.update(
inherit_branding=qweb_context.setdefault('editable', False),
)
@ -179,12 +174,11 @@ class website(osv.osv):
try:
view_ref = IMD.get_object_reference(cr, uid, module, xmlid)
except ValueError:
logger.error("Website Rendering Error.\n\n%s" % traceback.format_exc())
return self.render(cr, uid, ids, 'website.404', qweb_context)
return self.error(cr, uid, 404, qweb_context, context=context)
if 'main_object' not in qweb_context:
try:
main_object = request.registry[view_ref[0]].browse(cr, uid, view_ref[1])
main_object = self.pool[view_ref[0]].browse(cr, uid, view_ref[1])
qweb_context['main_object'] = main_object
except Exception:
pass
@ -197,26 +191,25 @@ class website(osv.osv):
logger.error(err)
qweb_context['error'] = err[1]
logger.warn("Website Rendering Error.\n\n%s" % traceback.format_exc())
return self.render(cr, uid, ids, 'website.401', qweb_context)
except (QWebException,), err:
return self.error(cr, uid, 401, qweb_context, context=context)
except Exception, e:
qweb_context['template'] = getattr(e, 'qweb_template', '')
node = getattr(e, 'qweb_node', None)
qweb_context['node'] = node and node.toxml()
qweb_context['expr'] = getattr(e, 'qweb_eval', '')
qweb_context['traceback'] = traceback.format_exc()
qweb_context['template'] = err.template
qweb_context['message'] = err.message
qweb_context['node'] = err.node and err.node.toxml()
logger.error("Website Rendering Error.\n%(message)s\n%(node)s\n\n%(traceback)s" % qweb_context)
return view.render(
cr, uid,
'website.500' if qweb_context['editable'] else 'website.404',
qweb_context, context=context)
except Exception:
logger.exception("Website Rendering Error.")
qweb_context['traceback'] = traceback.format_exc()
return view.render(
cr, uid,
'website.500' if qweb_context['editable'] else 'website.404',
qweb_context, context=context)
logger.exception("Website Rendering Error.\n%(template)s\n%(expr)s\n%(node)s" % qweb_context)
return self.error(cr, uid, 500 if qweb_context['editable'] else 404,
qweb_context, context=context)
def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None):
def error(self, cr, uid, code, qweb_context, context=None):
View = request.registry['ir.ui.view']
return werkzeug.wrappers.Response(
View.render(cr, uid, 'website.%d' % code, qweb_context),
status=code,
content_type='text/html;charset=utf-8')
def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None, context=None):
# Compute Pager
page_count = int(math.ceil(float(total) / step))
@ -287,15 +280,15 @@ class website(osv.osv):
if xids[view['id']]
]
def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None):
def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None, context=None):
step = step and int(step) or 10
scope = scope and int(scope) or 5
orderby = orderby or "name"
get_args = dict(request.httprequest.args or {})
model_obj = request.registry[model]
model_obj = self.pool[model]
relation = model_obj._columns.get(column)._obj
relation_obj = request.registry[relation]
relation_obj = self.pool[relation]
get_args.setdefault('kanban', "")
kanban = get_args.pop('kanban')
@ -349,9 +342,9 @@ class website(osv.osv):
}
return request.website.render("website.kanban_contain", values)
def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby):
def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby, context=None):
html = ""
model_obj = request.registry[model]
model_obj = self.pool[model]
domain = safe_eval(domain)
step = int(step)
offset = (int(page)-1) * step
@ -361,6 +354,74 @@ class website(osv.osv):
html += request.website.render(template, {'object_id': object_id})
return html
def get_menu(self, cr, uid, ids, context=None):
return self.pool['website.menu'].get_menu(cr, uid, ids[0], context=context)
class website_menu(osv.osv):
_name = "website.menu"
_description = "Website Menu"
_columns = {
'name': fields.char('Menu', size=64, required=True, translate=True),
'url': fields.char('Url', required=True, translate=True),
'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),
}
_defaults = {
'url': '',
'sequence': 0,
}
_parent_store = True
_parent_order = 'sequence, name'
_order = "parent_left"
def get_menu(self, cr, uid, website_id, context=None):
root_domain = [('parent_id', '=', False)] # ('website_id', '=', website_id),
menu_ids = self.search(cr, uid, root_domain, context=context)
menu = self.browse(cr, uid, menu_ids, context=context)
return menu[0]
def get_tree(self, cr, uid, website_id, context=None):
def make_tree(node):
menu_node = dict(
id=node.id,
name=node.name,
url=node.url,
new_window=node.new_window,
sequence=node.sequence,
parent_id=node.parent_id.id,
children=[],
)
for child in node.child_id:
menu_node['children'].append(make_tree(child))
return menu_node
menu = self.get_menu(cr, uid, website_id, context=context)
return make_tree(menu)
def save(self, cr, uid, website_id, data, context=None):
def replace_id(old_id, new_id):
for menu in data['data']:
if menu['id'] == old_id:
menu['id'] = new_id
if menu['parent_id'] == old_id:
menu['parent_id'] = new_id
to_delete = data['to_delete']
if to_delete:
self.unlink(cr, uid, to_delete, context=context)
for menu in data['data']:
mid = menu['id']
if isinstance(mid, str):
new_id = self.create(cr, uid, {'name': menu['name']}, context=context)
replace_id(mid, new_id)
for menu in data['data']:
self.write(cr, uid, [menu['id']], menu, context=context)
return True
class ir_attachment(osv.osv):
_inherit = "ir.attachment"
def _website_url_get(self, cr, uid, ids, name, arg, context=None):
@ -401,6 +462,15 @@ class res_partner(osv.osv):
}
return urlplus('https://maps.google.be/maps' , params)
class res_company(osv.osv):
_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).parent_id
return partner and partner.google_map_img(zoom, width, height, context=context) 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).parent_id
return partner and partner.google_map_link(zoom, context=context) or None
class base_language_install(osv.osv):
_inherit = "base.language.install"
_columns = {

View File

@ -1,6 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_website_public,website,website.model_website,base.group_public,1,0,0,0
access_website,website,website.model_website,base.group_user,1,0,0,0
access_website_menu,access_website_menu,model_website_menu,,1,0,0,0
access_website_converter_test,access_website_converter_test,model_website_converter_test,,1,1,1,1
access_website_converter_test_sub,access_website_converter_test_sub,model_website_converter_test_sub,,1,1,1,1
access_website_qweb,access_website_qweb,model_website_qweb,,0,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_website_public website website.model_website base.group_public 1 0 0 0
3 access_website website website.model_website base.group_user 1 0 0 0
4 access_website_menu access_website_menu model_website_menu 1 0 0 0
5 access_website_converter_test access_website_converter_test model_website_converter_test 1 1 1 1
6 access_website_converter_test_sub access_website_converter_test_sub model_website_converter_test_sub 1 1 1 1
7 access_website_qweb access_website_qweb model_website_qweb 0 0 0 0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,429 @@
/*
* jQuery UI Nested Sortable
* v 1.3.5 / 21 jun 2012
* http://mjsarfatti.com/code/nestedSortable
*
* Depends on:
* jquery.ui.sortable.js 1.8+
*
* Copyright (c) 2010-2012 Manuele J Sarfatti
* Licensed under the MIT License
* http://www.opensource.org/licenses/mit-license.php
*/
(function($) {
$.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
options: {
tabSize: 20,
disableNesting: 'mjs-nestedSortable-no-nesting',
errorClass: 'mjs-nestedSortable-error',
doNotClear: false,
listType: 'ol',
maxLevels: 0,
protectRoot: false,
rootID: null,
rtl: false,
isAllowed: function(item, parent) { return true; }
},
_create: function() {
this.element.data('sortable', this.element.data('nestedSortable'));
if (!this.element.is(this.options.listType))
throw new Error('nestedSortable: Please check the listType option is set to your actual list type');
return $.ui.sortable.prototype._create.apply(this, arguments);
},
destroy: function() {
this.element
.removeData("nestedSortable")
.unbind(".nestedSortable");
return $.ui.sortable.prototype.destroy.apply(this, arguments);
},
_mouseDrag: function(event) {
//Compute the helpers position
this.position = this._generatePosition(event);
this.positionAbs = this._convertPositionTo("absolute");
if (!this.lastPositionAbs) {
this.lastPositionAbs = this.positionAbs;
}
var o = this.options;
//Do scrolling
if(this.options.scroll) {
var scrolled = false;
if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
} else {
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
}
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
$.ui.ddmanager.prepareOffsets(this, event);
}
//Regenerate the absolute position used for position checks
this.positionAbs = this._convertPositionTo("absolute");
// Find the top offset before rearrangement,
var previousTopOffset = this.placeholder.offset().top;
//Set the helper position
if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
//Rearrange
for (var i = this.items.length - 1; i >= 0; i--) {
//Cache variables and intersection, continue if no intersection
var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
if (!intersection) continue;
if(itemElement != this.currentItem[0] //cannot intersect with itself
&& this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
&& !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
&& (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
) {
$(itemElement).mouseenter();
this.direction = intersection == 1 ? "down" : "up";
if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
$(itemElement).mouseleave();
this._rearrange(event, item);
} else {
break;
}
// Clear emtpy ul's/ol's
this._clearEmpty(itemElement);
this._trigger("change", event, this._uiHash());
break;
}
}
var parentItem = (this.placeholder[0].parentNode.parentNode &&
$(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length)
? $(this.placeholder[0].parentNode.parentNode)
: null,
level = this._getLevel(this.placeholder),
childLevels = this._getChildLevels(this.helper);
// To find the previous sibling in the list, keep backtracking until we hit a valid list item.
var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null;
if (previousItem != null) {
while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0] || previousItem[0] == this.helper[0]) {
if (previousItem[0].previousSibling) {
previousItem = $(previousItem[0].previousSibling);
} else {
previousItem = null;
break;
}
}
}
// To find the next sibling in the list, keep stepping forward until we hit a valid list item.
var nextItem = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null;
if (nextItem != null) {
while (nextItem[0].nodeName.toLowerCase() != 'li' || nextItem[0] == this.currentItem[0] || nextItem[0] == this.helper[0]) {
if (nextItem[0].nextSibling) {
nextItem = $(nextItem[0].nextSibling);
} else {
nextItem = null;
break;
}
}
}
var newList = document.createElement(o.listType);
this.beyondMaxLevels = 0;
// If the item is moved to the left, send it to its parent's level unless there are siblings below it.
if (parentItem != null && nextItem == null &&
(o.rtl && (this.positionAbs.left + this.helper.outerWidth() > parentItem.offset().left + parentItem.outerWidth()) ||
!o.rtl && (this.positionAbs.left < parentItem.offset().left))) {
parentItem.after(this.placeholder[0]);
this._clearEmpty(parentItem[0]);
this._trigger("change", event, this._uiHash());
}
// If the item is below a sibling and is moved to the right, make it a child of that sibling.
else if (previousItem != null &&
(o.rtl && (this.positionAbs.left + this.helper.outerWidth() < previousItem.offset().left + previousItem.outerWidth() - o.tabSize) ||
!o.rtl && (this.positionAbs.left > previousItem.offset().left + o.tabSize))) {
this._isAllowed(previousItem, level, level+childLevels+1);
if (!previousItem.children(o.listType).length) {
previousItem[0].appendChild(newList);
}
// If this item is being moved from the top, add it to the top of the list.
if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) {
previousItem.children(o.listType).prepend(this.placeholder);
}
// Otherwise, add it to the bottom of the list.
else {
previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
}
this._trigger("change", event, this._uiHash());
}
else {
this._isAllowed(parentItem, level, level+childLevels);
}
//Post events to containers
this._contactContainers(event);
//Interconnect with droppables
if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
//Call callbacks
this._trigger('sort', event, this._uiHash());
this.lastPositionAbs = this.positionAbs;
return false;
},
_mouseStop: function(event, noPropagation) {
// If the item is in a position not allowed, send it back
if (this.beyondMaxLevels) {
this.placeholder.removeClass(this.options.errorClass);
if (this.domPosition.prev) {
$(this.domPosition.prev).after(this.placeholder);
} else {
$(this.domPosition.parent).prepend(this.placeholder);
}
this._trigger("revert", event, this._uiHash());
}
// Clean last empty ul/ol
for (var i = this.items.length - 1; i >= 0; i--) {
var item = this.items[i].item[0];
this._clearEmpty(item);
}
$.ui.sortable.prototype._mouseStop.apply(this, arguments);
},
serialize: function(options) {
var o = $.extend({}, this.options, options),
items = this._getItemsAsjQuery(o && o.connected),
str = [];
$(items).each(function() {
var res = ($(o.item || this).attr(o.attribute || 'id') || '')
.match(o.expression || (/(.+)[-=_](.+)/)),
pid = ($(o.item || this).parent(o.listType)
.parent(o.items)
.attr(o.attribute || 'id') || '')
.match(o.expression || (/(.+)[-=_](.+)/));
if (res) {
str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']')
+ '='
+ (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID));
}
});
if(!str.length && o.key) {
str.push(o.key + '=');
}
return str.join('&');
},
toHierarchy: function(options) {
var o = $.extend({}, this.options, options),
sDepth = o.startDepthCount || 0,
ret = [];
$(this.element).children(o.items).each(function () {
var level = _recursiveItems(this);
ret.push(level);
});
return ret;
function _recursiveItems(item) {
var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
if (id) {
var currentItem = {"id" : id[2]};
if ($(item).children(o.listType).children(o.items).length > 0) {
currentItem.children = [];
$(item).children(o.listType).children(o.items).each(function() {
var level = _recursiveItems(this);
currentItem.children.push(level);
});
}
return currentItem;
}
}
},
toArray: function(options) {
var o = $.extend({}, this.options, options),
sDepth = o.startDepthCount || 0,
ret = [],
left = 2;
ret.push({
"item_id": o.rootID,
"parent_id": 'none',
"depth": sDepth,
"left": '1',
"right": ($(o.items, this.element).length + 1) * 2
});
$(this.element).children(o.items).each(function () {
left = _recursiveArray(this, sDepth + 1, left);
});
ret = ret.sort(function(a,b){ return (a.left - b.left); });
return ret;
function _recursiveArray(item, depth, left) {
var right = left + 1,
id,
pid;
if ($(item).children(o.listType).children(o.items).length > 0) {
depth ++;
$(item).children(o.listType).children(o.items).each(function () {
right = _recursiveArray($(this), depth, right);
});
depth --;
}
id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/));
if (depth === sDepth + 1) {
pid = o.rootID;
} else {
var parentItem = ($(item).parent(o.listType)
.parent(o.items)
.attr(o.attribute || 'id'))
.match(o.expression || (/(.+)[-=_](.+)/));
pid = parentItem[2];
}
if (id) {
ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right});
}
left = right + 1;
return left;
}
},
_clearEmpty: function(item) {
var emptyList = $(item).children(this.options.listType);
if (emptyList.length && !emptyList.children().length && !this.options.doNotClear) {
emptyList.remove();
}
},
_getLevel: function(item) {
var level = 1;
if (this.options.listType) {
var list = item.closest(this.options.listType);
while (list && list.length > 0 &&
!list.is('.ui-sortable')) {
level++;
list = list.parent().closest(this.options.listType);
}
}
return level;
},
_getChildLevels: function(parent, depth) {
var self = this,
o = this.options,
result = 0;
depth = depth || 0;
$(parent).children(o.listType).children(o.items).each(function (index, child) {
result = Math.max(self._getChildLevels(child, depth + 1), result);
});
return depth ? result + 1 : result;
},
_isAllowed: function(parentItem, level, levels) {
var o = this.options,
isRoot = $(this.domPosition.parent).hasClass('ui-sortable') ? true : false,
maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list
// Is the root protected?
// Are we trying to nest under a no-nest?
// Are we nesting too deep?
if (!o.isAllowed(this.currentItem, parentItem) ||
parentItem && parentItem.hasClass(o.disableNesting) ||
o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) {
this.placeholder.addClass(o.errorClass);
if (maxLevels < levels && maxLevels != 0) {
this.beyondMaxLevels = levels - maxLevels;
} else {
this.beyondMaxLevels = 1;
}
} else {
if (maxLevels < levels && maxLevels != 0) {
this.placeholder.addClass(o.errorClass);
this.beyondMaxLevels = levels - maxLevels;
} else {
this.placeholder.removeClass(o.errorClass);
this.beyondMaxLevels = 0;
}
}
}
}));
$.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options);
})(jQuery);

View File

@ -1,4 +1,3 @@
@charset "utf-8";
/* ---- CKEditor Minimal Reset ---- */
.navbar.navbar-inverse .cke_chrome {
border: none;
@ -114,7 +113,7 @@ table.editorbar-panel td.selected {
.oe_translate_or {
color: white;
padding: 0 0.2em;
padding: 0 0 0 1em;
}
.oe_translate_examples li {
@ -142,6 +141,84 @@ table.editorbar-panel td.selected {
background: #ffffb6;
}
div.oe_menu_buttons {
top: -8px;
right: -8px;
}
ul.oe_menu_editor > li:first-child > div > i:before {
content: "\f015";
}
ul.oe_menu_editor, ul.oe_menu_editor ul {
list-style-type: none;
margin: 0;
padding: 0;
}
ul.oe_menu_editor ul {
padding-left: 30px;
}
ul.oe_menu_editor button {
margin-left: 4px;
}
ul.oe_menu_editor li {
margin: 5px 0 0 0;
padding: 0;
}
ul.oe_menu_editor .oe_menu_placeholder {
outline: 1px dashed #4183c4;
}
ul.oe_menu_editor .mjs-nestedSortable-error {
background: #fbe3e4;
border-color: transparent;
}
ul.oe_menu_editor li div {
border: 1px solid #d4d4d4;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
border-color: #d4d4d4 #d4d4d4 #bcbcbc;
padding: 6px;
margin: 0;
cursor: move;
background: #f6f6f6;
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(47%, #f6f6f6), color-stop(100%, #ededed));
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
background: -o-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
background: -ms-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
background: linear-gradient(to bottom, #ffffff 0%, #f6f6f6 47%, #ededed 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 );
}
ul.oe_menu_editor li.mjs-nestedSortable-branch div {
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #f0ece9 100%);
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #f0ece9 100%);
}
ul.oe_menu_editor li.mjs-nestedSortable-leaf div {
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #bcccbc 100%);
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #bcccbc 100%);
}
ul.oe_menu_editor li.mjs-nestedSortable-collapsed.mjs-nestedSortable-hovering div {
border-color: #999999;
background: #fafafa;
}
ul.oe_menu_editor .disclose {
cursor: pointer;
width: 10px;
display: none;
}
ul.oe_menu_editor li.mjs-nestedSortable-collapsed > ul {
display: none;
}
ul.oe_menu_editor li.mjs-nestedSortable-branch > div > .disclose {
display: inline-block;
}
ul.oe_menu_editor li.mjs-nestedSortable-collapsed > div > .disclose > span:before {
content: "+ ";
}
ul.oe_menu_editor li.mjs-nestedSortable-expanded > div > .disclose > span:before {
content: "- ";
}
/* ---- RTE ---- */
.oe_editable .btn {
-webkit-user-select: auto;
@ -161,6 +238,14 @@ table.editorbar-panel td.selected {
margin-bottom: 0.5em;
}
.modal.nosave .modal-footer button.save {
display: none;
}
.modal.nosave .modal-footer button.wait {
display: inline-block !important;
visibility: visible !important;
}
.cke_widget_wrapper {
position: static !important;
}

View File

@ -108,7 +108,7 @@ table.editorbar-panel
// ---- TRANSLATIONS ---- {{{
.oe_translate_or
color: white
padding: 0 0.2em
padding: 0 0 0 1em
.oe_translate_examples li
margin: 10px
padding: 4px
@ -124,6 +124,84 @@ table.editorbar-panel
background: rgb(255, 255, 182)
// }}}
// -------- MENU -------- {{{
div.oe_menu_buttons
top: -8px
right: -8px
ul.oe_menu_editor
> li:first-child > div > i:before
content: "\f015"
&, & ul
list-style-type: none
margin: 0
padding: 0
& ul
padding-left: 30px
button
margin-left: 4px
li
margin: 5px 0 0 0
padding: 0
.oe_menu_placeholder
outline: 1px dashed #4183C4
.mjs-nestedSortable-error
background: #fbe3e4
border-color: transparent
// TODO: use compass mixins
li div
border: 1px solid #d4d4d4
-webkit-border-radius: 3px
-moz-border-radius: 3px
border-radius: 3px
border-color: #D4D4D4 #D4D4D4 #BCBCBC
padding: 6px
margin: 0
cursor: move
background: #f6f6f6
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%)
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(47%,#f6f6f6), color-stop(100%,#ededed))
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
background: -o-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
background: -ms-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
background: linear-gradient(to bottom, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 )
li.mjs-nestedSortable-branch div
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #f0ece9 100%)
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#f0ece9 100%)
li.mjs-nestedSortable-leaf div
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #bcccbc 100%)
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#bcccbc 100%)
li.mjs-nestedSortable-collapsed.mjs-nestedSortable-hovering div
border-color: #999
background: #fafafa
.disclose
cursor: pointer
width: 10px
display: none
li.mjs-nestedSortable-collapsed > ul
display: none
li.mjs-nestedSortable-branch > div > .disclose
display: inline-block
li.mjs-nestedSortable-collapsed > div > .disclose > span:before
content: '+ '
li.mjs-nestedSortable-expanded > div > .disclose > span:before
content: '- '
// }}}
/* ---- RTE ---- */
// bootstrap makes .btn elements unselectable -> RTE double-click can't know
@ -141,6 +219,13 @@ table.editorbar-panel
.modal .image-preview
margin-bottom: 0.5em
.modal.nosave .modal-footer
button.save
display: none
button.wait
display: inline-block !important
visibility: visible !important
// wrapper positioned relatively for drag&drop widget which is disabled below.
// Breaks completely horribly crazy products listing page, so take it out.
.cke_widget_wrapper

View File

@ -239,7 +239,7 @@ footer {
}
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child:before, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child:before {
content: "Click Edit To Create Content";
content: "Press The Top-Left Edit Button";
text-align: center;
display: block;
padding-top: 160px;

View File

@ -164,7 +164,7 @@ footer
position: static
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child:before, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child:before
content: 'Click Edit To Create Content'
content: 'Press The Top-Left Edit Button'
text-align: center
display: block
padding-top: 160px

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -6,7 +6,7 @@
var hash = "#advanced-view-editor";
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.ace.xml');
website.add_template_file('/website/static/src/xml/website.ace.xml');
website.ready().then(function () {
if (window.location.hash.indexOf(hash) >= 0) {

View File

@ -4,17 +4,26 @@
var website = openerp.website;
// $.fn.data automatically parses value, '0'|'1' -> 0|1
website.templates.push('/website/static/src/xml/website.editor.xml');
website.add_template_file('/website/static/src/xml/website.editor.xml');
website.dom_ready.done(function () {
var is_smartphone = $(document.body)[0].clientWidth < 767;
if (!is_smartphone) {
website.ready().then(website.init_editor);
}
$(document).on('hide.bs.dropdown', '.dropdown', function (ev) {
// Prevent dropdown closing when a contenteditable children is focused
if (ev.originalEvent
&& $(ev.target).has(ev.originalEvent.target).length
&& $(ev.originalEvent.target).is('[contenteditable]')) {
ev.preventDefault();
}
});
});
function link_dialog(editor) {
return new website.editor.LinkDialog(editor).appendTo(document.body);
return new website.editor.RTELinkDialog(editor).appendTo(document.body);
}
function image_dialog(editor) {
return new website.editor.RTEImageDialog(editor).appendTo(document.body);
@ -54,6 +63,39 @@
link_dialog(editor);
}, null, null, 500);
var previousSelection;
editor.on('selectionChange', function (evt) {
var selected = evt.data.path.lastElement;
if (previousSelection) {
// cleanup previous selection
$(previousSelection).next().remove();
previousSelection = null;
}
if (!selected.is('img')
|| selected.data('cke-realelement')
|| selected.isReadOnly()
|| selected.data('oe-model') === 'ir.ui.view') {
return;
}
// display button
var $el = $(previousSelection = selected.$);
var $btn = $('<button type="button" class="btn btn-primary" contenteditable="false">Edit</button>')
.insertAfter($el)
.click(function (e) {
e.preventDefault();
e.stopPropagation();
image_dialog(editor);
});
var position = $el.position();
$btn.css({
position: 'absolute',
top: $el.height() / 2 + position.top - $btn.outerHeight() / 2,
left: $el.width() / 2 + position.left - $btn.outerWidth() / 2,
});
});
//noinspection JSValidateTypes
editor.addCommand('link', {
exec: function (editor) {
@ -361,7 +403,7 @@
});
// Adding Static Menus
menu.append('<li class="divider"></li><li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
menu.append('<li class="divider"></li><li><a data-action="ace" href="#">Advanced view editor</a></li>');
menu.append('<li class="divider"></li><li><a data-action="ace" href="#">HTML Editor</a></li>');
self.trigger('rte:customize_menu_ready');
}
);
@ -805,6 +847,106 @@
this.pages = Object.create(null);
this.text = null;
},
start: function () {
var self = this;
return $.when(
this.fetch_pages().done(this.proxy('fill_pages')),
this._super()
).done(function () {
self.bind_data();
});
},
save: function () {
var self = this, _super = this._super.bind(this);
var $e = this.$('.list-group-item.active .url-source');
var val = $e.val();
if (!val || !$e[0].checkValidity()) {
// FIXME: error message
$e.closest('.form-group').addClass('has-error');
$e.focus();
return;
}
var done = $.when();
if ($e.hasClass('email-address')) {
this.make_link('mailto:' + val, false, val);
} else if ($e.hasClass('existing')) {
self.make_link(val, false, this.pages[val]);
} else if ($e.hasClass('pages')) {
// Create the page, get the URL back
done = $.get(_.str.sprintf(
'/pagenew/%s?noredirect', encodeURI(val)))
.then(function (response) {
self.make_link(response, false, val);
});
} else {
this.make_link(val, this.$('input.window-new').prop('checked'));
}
done.then(_super);
},
make_link: function (url, new_window, label) {
},
bind_data: function (text, href, new_window) {
href = href || this.element && (this.element.data( 'cke-saved-href')
|| this.element.getAttribute('href'));
if (!href) { return; }
if (new_window === undefined) {
new_window = this.element.getAttribute('target') === '_blank';
}
if (text === undefined) {
text = this.element.getText();
}
var match, $control;
if ((match = /mailto:(.+)/.exec(href))) {
$control = this.$('input.email-address').val(match[1]);
} else if (href in this.pages) {
$control = this.$('select.existing').val(href);
} else if ((match = /\/page\/(.+)/.exec(href))) {
var actual_href = '/page/website.' + match[1];
if (actual_href in this.pages) {
$control = this.$('select.existing').val(actual_href);
}
}
if (!$control) {
$control = this.$('input.url').val(href);
}
this.changed($control);
this.$('input#link-text').val(text);
this.$('input.window-new').prop('checked', new_window);
},
changed: function ($e) {
this.$('.url-source').not($e).val('');
$e.closest('.list-group-item')
.addClass('active')
.siblings().removeClass('active')
.addBack().removeClass('has-error');
},
fetch_pages: function () {
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'website',
method: 'list_pages',
args: [null],
kwargs: {
context: website.get_context()
},
});
},
fill_pages: function (results) {
var self = this;
var pages = this.$('select.existing')[0];
_(results).each(function (result) {
self.pages[result.url] = result.name;
pages.options[pages.options.length] =
new Option(result.name, result.url);
});
},
});
website.editor.RTELinkDialog = website.editor.LinkDialog.extend({
start: function () {
var element;
if ((element = this.get_selected_link()) && element.hasAttribute('href')) {
@ -815,10 +957,7 @@
this.add_removal_button();
}
return $.when(
this.fetch_pages().done(this.proxy('fill_pages')),
this._super()
).done(this.proxy('bind_data'));
return this._super();
},
add_removal_button: function () {
this.$('.modal-footer').prepend(
@ -884,66 +1023,6 @@
}, 0);
}
},
save: function () {
var self = this, _super = this._super.bind(this);
var $e = this.$('.list-group-item.active .url-source');
var val = $e.val();
if (!val || !$e[0].checkValidity()) {
// FIXME: error message
$e.closest('.form-group').addClass('has-error');
return;
}
var done = $.when();
if ($e.hasClass('email-address')) {
this.make_link('mailto:' + val, false, val);
} else if ($e.hasClass('existing')) {
self.make_link(val, false, this.pages[val]);
} else if ($e.hasClass('pages')) {
// Create the page, get the URL back
done = $.get(_.str.sprintf(
'/pagenew/%s?noredirect', encodeURI(val)))
.then(function (response) {
self.make_link(response, false, val);
});
} else {
this.make_link(val, this.$('input.window-new').prop('checked'));
}
done.then(_super);
},
bind_data: function () {
var href = this.element && (this.element.data( 'cke-saved-href')
|| this.element.getAttribute('href'));
if (!href) { return; }
var match, $control;
if (match = /mailto:(.+)/.exec(href)) {
$control = this.$('input.email-address').val(match[1]);
} else if (href in this.pages) {
$control = this.$('select.existing').val(href);
} else if (match = /\/page\/(.+)/.exec(href)) {
var actual_href = '/page/website.' + match[1];
if (actual_href in this.pages) {
$control = this.$('select.existing').val(actual_href);
}
}
if (!$control) {
$control = this.$('input.url').val(href);
}
this.changed($control);
this.$('input#link-text').val(this.element.getText());
this.$('input.window-new').prop(
'checked', this.element.getAttribute('target') === '_blank');
},
changed: function ($e) {
this.$('.url-source').not($e).val('');
$e.closest('.list-group-item')
.addClass('active')
.siblings().removeClass('active')
.addBack().removeClass('has-error');
},
/**
* CKEDITOR.plugins.link.getSelectedLink ignores the editor's root,
* if the editor is set directly on a link it will thus not work.
@ -951,27 +1030,8 @@
get_selected_link: function () {
return get_selected_link(this.editor);
},
fetch_pages: function () {
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'website',
method: 'list_pages',
args: [null],
kwargs: {
context: website.get_context()
},
});
},
fill_pages: function (results) {
var self = this;
var pages = this.$('select.existing')[0];
_(results).each(function (result) {
self.pages[result.url] = result.name;
pages.options[pages.options.length] =
new Option(result.name, result.url);
});
},
});
/**
* ImageDialog widget. Lets users change an image, including uploading a
* new image in OpenERP or selecting the image style (if supported by
@ -1003,6 +1063,7 @@
}),
start: function () {
this.$('.modal-footer [disabled]').text("Uploading…");
var $options = this.$('.image-style').children();
this.image_styles = $options.map(function () { return this.value; }).get();
@ -1038,6 +1099,7 @@
},
file_selection: function () {
this.$el.addClass('nosave');
this.$('button.filepicker').removeClass('btn-danger btn-success');
var self = this;
@ -1060,6 +1122,7 @@
this.set_image(url);
},
preview_image: function () {
this.$el.removeClass('nosave');
var image = this.$('input.url').val();
if (!image) { return; }

View File

@ -5,35 +5,30 @@
// The following line can be removed in 2017
openerp.website = website;
var templates = website.templates = [
'/website/static/src/xml/website.xml'
];
website.get_context = function (dict) {
var html = document.documentElement;
return _.extend({
lang: html.getAttribute('lang').replace('-', '_')
lang: html.getAttribute('lang').replace('-', '_'),
website_id: html.getAttribute('data-website-id')|0
}, dict);
};
/* ----- TEMPLATE LOADING ---- */
website.add_template = function(template) {
templates.push(template);
};
website.load_templates = function(templates) {
var dones = _(templates).map(function (t) {
return new $.Deferred(function (d) {
openerp.qweb.add_template(t, function(err) {
if (err) {
d.reject(err);
} else {
d.resolve();
}
});
var templates_def = $.Deferred().resolve();
website.add_template_file = function(template) {
templates_def = templates_def.then(function() {
var def = $.Deferred();
openerp.qweb.add_template(template, function(err) {
if (err) {
def.reject(err);
} else {
def.resolve();
}
});
return def;
});
return $.when.apply(null, dones);
};
website.add_template_file('/website/static/src/xml/website.xml');
website.reload = function () {
location.hash = "scrollTop=" + window.document.body.scrollTop;
if (location.search.indexOf("enable_editor") > -1) {
@ -110,20 +105,33 @@
*/
website.ready = function() {
if (!all_ready) {
var tpl = website.load_templates(templates);
all_ready = dom_ready.then(function () {
all_ready = $.when(dom_ready, templates_def).then(function () {
if ($('html').data('editable')) {
website.id = $('html').data('website-id');
website.session = new openerp.Session();
var modules = ['website'];
return openerp._t.database.load_translations(website.session, modules, website.get_context().lang);
}
}).then(tpl).promise();
}).promise();
}
return all_ready;
};
website.error = function(data, url) {
var $error = $(openerp.qweb.render('website.error_dialog', {
'title': data.data ? data.data.arguments[0] : data.statusText,
'message': data.data ? data.data.arguments[1] : "",
'backend_url': url
}));
$error.appendTo("body");
$error.modal('show');
};
dom_ready.then(function () {
/* ----- BOOTSTRAP STUFF ---- */
$('.js_tooltip').bstooltip();
/* ----- PUBLISHING STUFF ---- */
$('[data-publish]:has(.js_publish)').each(function () {
var $pub = $("[data-publish]", this);
@ -140,12 +148,15 @@
$(document).on('click', '.js_publish', function (e) {
e.preventDefault();
var $data = $(":first", this).parents("[data-publish]");
$data.attr("data-publish", $data.first().attr("data-publish") == 'off' ? 'on' : 'off');
openerp.jsonRpc('/website/publish', 'call', {'id': $(this).data('id'), 'object': $(this).data('object')})
var $a = $(this);
var $data = $a.find(":first").parents("[data-publish]");
openerp.jsonRpc($a.data('controller') || '/website/publish', 'call', {'id': +$a.data('id'), 'object': $a.data('object')})
.then(function (result) {
$data.attr("data-publish", +result ? 'on' : 'off');
}).fail(function (err, data) {
website.error(data, '/web#model='+$a.data('object')+'&id='+$a.data('id'));
});
return false;
});
$(document).on('click', '.js_publish_management .js_publish_btn', function () {
@ -156,11 +167,13 @@
$data.toggleClass("css_unpublish css_publish");
$btn.removeClass("btn-default btn-success");
openerp.jsonRpc('/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')})
openerp.jsonRpc($data.data('controller') || '/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')})
.then(function (result) {
$btn.toggleClass("btn-default", !result).toggleClass("btn-success", result);
$data.toggleClass("css_unpublish", !result).toggleClass("css_publish", result);
$data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off');
}).fail(function (err, data) {
website.error(data, '/web#model='+$data.data('object')+'&id='+$data.data('id'));
});
});

View File

@ -0,0 +1,192 @@
(function () {
'use strict';
var website = openerp.website;
website.menu = {};
website.add_template_file('/website/static/src/xml/website.menu.xml');
website.EditorBar.include({
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action="edit-structure"]': 'editStructure',
}),
editStructure: function () {
var context = website.get_context();
openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'website.menu',
method: 'get_tree',
args: [[context.website_id]],
kwargs: {
context: context
},
}).then(function (menu) {
return new website.menu.EditMenuDialog(menu).appendTo(document.body);
});
},
});
website.menu.EditMenuDialog = website.editor.Dialog.extend({
template: 'website.menu.dialog.edit',
events: _.extend({}, website.editor.Dialog.prototype.events, {
'click button.js_add_menu': 'add_menu',
'click button.js_edit_menu': 'edit_menu',
'click button.js_delete_menu': 'delete_menu',
}),
init: function (menu) {
this.menu = menu;
this.root_menu_id = menu.id;
this.flat = this.flatenize(menu);
this.to_delete = [];
this._super();
},
start: function () {
var r = this._super.apply(this, arguments);
var button = openerp.qweb.render('website.menu.dialog.footer-button');
this.$('.modal-footer').prepend(button);
this.$('.oe_menu_editor').nestedSortable({
listType: 'ul',
handle: 'div',
items: 'li',
maxLevels: 2,
toleranceElement: '> div',
forcePlaceholderSize: true,
opacity: 0.6,
placeholder: 'oe_menu_placeholder',
tolerance: 'pointer',
attribute: 'data-menu-id',
expression: '()(.+)', // nestedSortable takes the second match of an expression (*sigh*)
});
return r;
},
flatenize: function (node, dict) {
dict = dict || {};
var self = this;
dict[node.id] = node;
node.children.forEach(function (child) {
self.flatenize(child, dict);
});
return dict;
},
add_menu: function () {
var self = this;
var dialog = new website.menu.MenuEntryDialog();
dialog.on('add-menu', this, function (link) {
var new_menu = {
id: _.uniqueId('new-'),
name: link[2] || link[0],
url: link[0],
new_window: link[1],
parent_id: false,
sequence: 0,
children: [],
};
self.flat[new_menu.id] = new_menu;
self.$('.oe_menu_editor').append(
openerp.qweb.render(
'website.menu.dialog.submenu', { submenu: new_menu }));
});
dialog.appendTo(document.body);
},
edit_menu: function (ev) {
var self = this;
var menu_id = $(ev.currentTarget).closest('[data-menu-id]').data('menu-id');
var menu = self.flat[menu_id];
if (menu) {
var dialog = new website.menu.MenuEntryDialog(undefined, menu);
dialog.on('update-menu', this, function (link) {
var id = link.shift();
var menu_obj = self.flat[id];
_.extend(menu_obj, {
name: link[2],
url: link[0],
new_window: link[1],
});
var $menu = self.$('[data-menu-id="' + id + '"]');
$menu.find('> div > span').text(menu_obj.name);
});
dialog.appendTo(document.body);
} else {
alert("Could not find menu entry");
}
},
delete_menu: function (ev) {
var self = this;
var $menu = $(ev.currentTarget).closest('[data-menu-id]');
var mid = $menu.data('menu-id')|0;
if (mid) {
this.to_delete.push(mid);
}
$menu.remove();
},
save: function () {
var self = this;
var new_menu = this.$('.oe_menu_editor').nestedSortable('toArray', {startDepthCount: 0});
var levels = [];
var data = [];
var context = website.get_context();
// Resquence, re-tree and remove useless data
new_menu.forEach(function (menu) {
if (menu.item_id) {
levels[menu.depth] = (levels[menu.depth] || 0) + 1;
var mobj = self.flat[menu.item_id];
mobj.sequence = levels[menu.depth];
mobj.parent_id = (menu.parent_id|0) || menu.parent_id || self.root_menu_id;
delete(mobj.children);
data.push(mobj);
}
});
openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'website.menu',
method: 'save',
args: [[context.website_id], { data: data, to_delete: self.to_delete }],
kwargs: {
context: context
},
}).then(function (menu) {
self.close();
website.reload();
});
},
});
website.menu.MenuEntryDialog = website.editor.LinkDialog.extend({
template: 'website.menu.dialog.add',
init: function (editor, data) {
this.data = data;
this.update_mode = !!this.data;
return this._super.apply(this, arguments);
},
start: function () {
var self = this;
return $.when(this._super.apply(this, arguments)).then(function () {
if (self.data) {
self.bind_data(self.data.name, self.data.url, self.data.new_window);
}
var $link_text = self.$('#link-text').focus();
self.$('#link-existing').change(function () {
if (!$link_text.val()) {
$link_text.val($(this).find('option:selected').text());
$link_text.focus();
}
});
});
},
save: function () {
var $e = this.$('#link-text');
if (!$e.val() || !$e[0].checkValidity()) {
$e.closest('.form-group').addClass('has-error');
$e.focus();
return;
}
return this._super.apply(this, arguments);
},
make_link: function (url, new_window, label) {
var menu_label = this.$('input#link-text').val() || label;
if (this.data) {
this.trigger('update-menu', [this.data.id, url, new_window, menu_label]);
} else {
this.trigger('add-menu', [url, new_window, menu_label]);
}
},
});
})();

View File

@ -2,7 +2,7 @@
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.seo.xml');
website.add_template_file('/website/static/src/xml/website.seo.xml');
website.EditorBar.include({
events: _.extend({}, website.EditorBar.prototype.events, {

View File

@ -2,7 +2,7 @@
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.snippets.xml');
website.add_template_file('/website/static/src/xml/website.snippets.xml');
website.EditorBar.include({
start: function () {
@ -17,9 +17,15 @@
return this._super();
},
edit: function () {
var self = this;
$("body").off('click');
window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
this.snippets.appendTo(this.$el);
this.rte.on('rte:ready', this, function () {
self.snippets.$button.removeClass("hidden");
});
return this._super.apply(this, arguments);
},
save: function () {
@ -118,10 +124,11 @@
start: function() {
var self = this;
var $ul = this.parent.$("#website-top-edit ul");
this.$button = $(openerp.qweb.render('website.snippets_button'))
.prependTo(this.parent.$("#website-top-edit ul"))
.find("button");
var $button = $(openerp.qweb.render('website.snippets_button')).prependTo($ul);
$button.find('button').click(function () {
this.$button.click(function () {
self.make_active(false);
self.$el.toggleClass("hidden");
});
@ -237,6 +244,7 @@
}
if (this.$active_snipped_id) {
this.snippet_blur(this.$active_snipped_id);
this.$active_snipped_id = false;
}
if ($snipped_id) {
if(_.indexOf(this.snippets, $snipped_id.get(0)) === -1) {
@ -245,8 +253,6 @@
this.$active_snipped_id = $snipped_id;
this.create_overlay(this.$active_snipped_id);
this.snippet_focus($snipped_id);
} else {
self.$active_snipped_id = false;
}
},
create_overlay: function ($snipped_id) {
@ -361,7 +367,7 @@
}
self.create_overlay($target);
$target.data("snippet-editor").build_snippet($target);
$target.data("snippet-editor").drop_and_build_snippet($target);
} else {
$target = $(this).data('target');
@ -369,7 +375,7 @@
self.create_overlay($target);
if (website.snippet.editorRegistry[snipped_id]) {
var snippet = new website.snippet.editorRegistry[snipped_id](self, $target);
snippet.build_snippet($target);
snippet.drop_and_build_snippet($target);
}
}
setTimeout(function () {self.make_active($target);},0);
@ -589,7 +595,8 @@
* Displayed into the overlay options on focus
*/
_readXMLData: function() {
this.$el = this.parent.$snippets.siblings("[data-snippet-id='"+this.snippet_id+"']").clone();
var self = this;
this.$el = this.parent.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone();
this.$editor = this.$el.find(".oe_snippet_options");
var $options = this.$overlay.find(".oe_overlay_options");
this.$editor.prependTo($options.find(".oe_options ul"));
@ -710,7 +717,7 @@
if (website.snippet.editorRegistry[snipped_id]) {
var snippet = new website.snippet.editorRegistry[snipped_id](this, this.$target);
snippet.build_snippet(this.$target);
snippet.drop_and_build_snippet(this.$target);
}
var _class = "oe_snippet_" + snipped_id + " " + ($li.data("class") || "");
if (active) {
@ -749,6 +756,7 @@
var index = _.indexOf(self.parent.snippets, self.$target.get(0));
delete self.parent.snippets[index];
self.$target.remove();
self.$overlay.remove();
return false;
});
this._drag_and_drop();
@ -756,18 +764,18 @@
},
/*
* build_snippet
* drop_and_build_snippet
* This method is called just after that a thumbnail is drag and dropped into a drop zone
* (after the insertion of this.$body, if this.$body exists)
*/
build_snippet: function ($target) {
drop_and_build_snippet: function ($target) {
},
/* onFocus
* This method is called when the user click inside the snippet in the dom
*/
onFocus : function () {
this.$overlay.addClass('oe_active');
this.$overlay.addClass('oe_active').effect('bounce', {distance: 18, times: 5}, 250);
},
/* onFocus
@ -1026,7 +1034,7 @@
},
});
website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.resize.extend({
build_snippet: function() {
drop_and_build_snippet: function() {
var id = 0;
$("body .carousel").each(function () {
var _id = +$(this).attr("id").replace(/^myCarousel/, '');
@ -1038,10 +1046,6 @@
this.$target.find(".carousel-control").attr("href", "#myCarousel" + id);
this.$target.find("[data-target='#myCarousel']").attr("data-target", "#myCarousel" + id);
this.$target.attr('contentEditable', 'false')
.find('.content, .carousel-image img')
.attr('contentEditable', 'true');
this.rebind_event();
},
onFocus: function () {
@ -1095,6 +1099,10 @@
self.$target.carousel($(this).data('slide')); });
this.$target.off('click').on('click', '.carousel-indicators [data-target]', function () {
self.$target.carousel(+$(this).data('slide-to')); });
this.$target.attr('contentEditable', 'false')
.find('.content, .carousel-image img')
.attr('contentEditable', 'true');
},
on_add: function (e) {
e.preventDefault();

View File

@ -2,7 +2,7 @@
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.tour.xml');
website.add_template_file('/website/static/src/xml/website.tour.xml');
website.tour = {
render: function render (template, dict) {

View File

@ -2,7 +2,7 @@
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.translator.xml');
website.add_template_file('/website/static/src/xml/website.translator.xml');
var nodialog = 'website_translator_nodialog';
website.EditorBar.include({
@ -16,8 +16,7 @@
self.$('button[data-action=edit]')
.text("Translate")
.after(openerp.qweb.render('website.TranslatorAdditionalButtons'));
self.$('[data-action=snippet]').hide();
self.$('#customize-menu-button').hide();
self.$('.js_hide_on_translate').hide();
});
},
edit: function () {

View File

@ -24,6 +24,7 @@
<div class="modal-body"><t t-raw="__content__"/></div>
<div class="modal-footer">
<button type="button" class="btn" data-dismiss="modal" aria-hidden="true">Discard</button>
<button type="button" class="btn hidden wait" disabled="disabled"/>
<button type="button" class="btn btn-primary save">Save Changes</button>
</div>
</div>
@ -39,22 +40,22 @@
<form>
<ul class="list-group">
<li class="list-group-item form-group active">
<h3 class="list-group-item-heading">
<h4 class="list-group-item-heading">
<label for="link-existing" class="control-label">
Existing page
</label>
</h3>
</h4>
<select class="existing form-control url-source"
id="link-existing">
<option/>
</select>
</li>
<li class="list-group-item form-group">
<h3 class="list-group-item-heading">
<h4 class="list-group-item-heading">
<label for="link-new" class="control-label">
New page
</label>
</h3>
</h4>
<input type="text" class="form-control pages url-source"
id="link-new" placeholder="Page Name"/>
</li>
@ -65,20 +66,20 @@
Open in new window
</label>
</div>
<h3 class="list-group-item-heading">
<h4 class="list-group-item-heading">
<label for="link-external" class="control-label">
External Website
URL
</label>
</h3>
</h4>
<input type="text" class="form-control url url-source"
id="link-external" placeholder="http://openerp.com"/>
</li>
<li class="list-group-item form-group">
<h3 class="list-group-item-heading">
<h4 class="list-group-item-heading">
<label for="link-email" class="control-label">
Email Address
</label>
</h3>
</h4>
<input type="email" class="form-control email-address url-source"
id="link-email" placeholder="you@yourwebsite.com"/>
</li>

View File

@ -0,0 +1,59 @@
<templates id="template" xml:space="preserve">
<t t-name="website.menu.dialog.submenu">
<li t-att-data-menu-id="submenu.id">
<div>
<i class="icon-reorder"/>
<span><t t-esc="submenu.name"/></span>
<div class="btn-group btn-group-xs pull-right oe_menu_buttons">
<button type="button" class="btn js_edit_menu">
<i class="icon-edit"/>
</button>
<button type="button" class="btn js_delete_menu">
<i class="icon-trash"/>
</button>
</div>
</div>
<t t-set="children" t-value="submenu.children"/>
<ul t-if="children">
<t t-foreach="children" t-as="submenu">
<t t-call="website.menu.dialog.submenu"/>
</t>
</ul>
</li>
</t>
<t t-name="website.menu.dialog.footer-button">
<button type="button" class="btn pull-left js_add_menu btn-success">
<i class="icon-plus-sign"/> Add Menu Entry
</button>
</t>
<t t-name="website.menu.dialog.edit">
<t t-call="website.editor.dialog">
<t t-set="title">Edit Structure</t>
<ul class="oe_menu_editor">
<t t-foreach="widget.menu.children" t-as="submenu">
<t t-call="website.menu.dialog.submenu"/>
</t>
</ul>
</t>
</t>
<t t-name="website.menu.dialog.add" t-extend="website.editor.dialog.link">
<t t-jquery="t[t-set='title']" t-operation="inner">
<t t-if="!widget.update_mode">Add Menu Entry</t>
<t t-if="widget.update_mode">Edit Menu Entry</t>
</t>
<t t-jquery="form > div.form-horizontal" t-operation="replace"/>
<t t-jquery="ul.list-group" t-operation="before">
<div class="list-group">
<div class="form-group list-group-item active">
<h3 class="list-group-item-heading">
<label for="link-new" class="control-label">
Menu Label
</label>
</h3>
<input type="text" class="form-control"
id="link-text" required="required"/>
</div>
</div>
</t>
</t>
</templates>

View File

@ -32,7 +32,7 @@
<!-- filled in JS -->
</section>
<section>
<h3 class="page-header">2. Reference Your Page <small>using above suggestions</small></h3>
<h3 class="page-header">2. Reference Your Page <small>using above suggested keywords</small></h3>
<div class="form-horizontal mt16" role="form">
<div class="form-group">
<label for="seo_page_title" class="col-lg-2 control-label">Title</label>

View File

@ -3,7 +3,7 @@
<!-- Snippet loader -->
<t t-name="website.snippets_button">
<li class="navbar-form"><button type="button" data-action="snippet" class="btn btn-primary">Insert Blocks</button></li>
<li class="navbar-form js_hide_on_translate"><button type="button" data-action="snippet" class="hidden btn btn-primary">Insert Blocks</button></li>
</t>
<t t-name="website.snippets_style">
<li class="navbar-form">
@ -63,7 +63,7 @@
<div class="btn-group">
<a href="#" class="btn btn-default btn-sm oe_snippet_parent" title="Select Container Block"><span class="icon-upload-alt"/></a>
<div class="dropdown oe_options hidden btn-group">
<a href="#" data-toggle="dropdown" class="btn btn-default btn-sm" title="Customize">Customize <span class="caret"/></a>
<a href="#" data-toggle="dropdown" class="btn btn-primary btn-sm" title="Customize">Customize <span class="caret"/></a>
<ul class="dropdown-menu" role="menu"></ul>
</div>
<a href="#" class="btn btn-default btn-sm oe_snippet_move" title="Drag to Move">&amp;nbsp; <span class="icon-move"/> &amp;nbsp;</a>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.TranslatorAdditionalButtons">
<strong class="oe_translate_or">or</strong>
<a class="btn btn-default" data-action="edit_master" href="#">Edit Master</a>
<span class="oe_translate_or">or</span>
<a class="btn btn-link" data-action="edit_master" href="#">Edit Master</a>
</t>
<t t-name="website.TranslatorDialog">
<div class="modal fade oe_website_translator" tabindex="-1" role="dialog">
@ -18,22 +18,23 @@
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:
Here are the visuals used to help you translate efficiently:
<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
Content to translate
</li>
<li class="oe_translatable_text">
Translated content
</li>
</ul>
</p>
<p>
In this mode, you can only translate texts. To
change the structure of the page, you must edit the
master page. Each modification on the master page
is automatically applied to all translated
versions.
</p>
</section>
<hr/>
<section class="row">

View File

@ -21,8 +21,15 @@
<ul class="nav navbar-nav navbar-right">
<li><a data-action="show-mobile-preview" href="#"><span title="Mobile preview" class="icon-mobile-phone"/></a></li>
<li class="divider-vertical"></li>
<li class="dropdown js_hide_on_translate">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Structure <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a data-action="edit-structure" href="#"><span title="Edit Top Menu">Top Menu</span></a></li>
<!-- <a data-action="edit-footer" href="#"><span title="Edit Footer">Footer</span></a></li> -->
</ul>
</li>
<li><a data-action="promote-current-page" href="#"><span title="Promote page on the web">Promote</span></a></li>
<li class="dropdown">
<li class="dropdown js_hide_on_translate">
<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">
<!-- filled in JS -->
@ -61,4 +68,27 @@
</div>
</t>
<t t-name="website.error_dialog">
<div class="modal fade" 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>
<div class="h2"><t t-esc="title"/></div>
</div>
<div class="modal-body">
<section>
<t t-esc="message"/>
</section>
<section class="mt32">
You have encountered an error. You can edit in the <a t-att-href="backend_url">backend</a> this data.
</section>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" href="#" class="close btn btn-default">Close</button>
</div>
</div>
</div>
</div>
</t>
</templates>

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
import test_views, test_converter
import test_views, test_converter, test_requests
checks = [ test_views, test_converter, ]

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
import unittest2
class URLCase(unittest2.TestCase):
"""
URLCase moved out of test_requests, otherwise discovery attempts to
instantiate and run it
"""
def __init__(self, user, url, result):
super(URLCase, self).__init__()
self.user = user
self.url = url
self.result = result
@property
def username(self):
return self.user or "Anonymous Coward"
def __str__(self):
return "%s (as %s)" % (self.url, self.username)
__repr__ = __str__
def shortDescription(self):
return ""
def runTest(self):
code = self.result.getcode()
self.assertIn(
code, xrange(200, 300),
"Fetching %s as %s returned an error response (%d)" % (
self.url, self.username, code))

View File

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
import collections
import urllib
import urlparse
import unittest2
import urllib2
import lxml.html
from openerp import tools
from . import cases
__all__ = ['load_tests', 'CrawlSuite']
class RedirectHandler(urllib2.HTTPRedirectHandler):
"""
HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and
works by intercepting 3xy "errors".
Inherit from it to handle 3xy non-error responses instead, as we're not
using the error processor
"""
def http_response(self, request, response):
code, msg, hdrs = response.code, response.msg, response.info()
if 300 <= code < 400:
return self.parent.error(
'http', request, response, code, msg, hdrs)
return response
https_response = http_response
class CrawlSuite(unittest2.TestSuite):
""" Test suite crawling an openerp 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
starting the crawl
"""
def __init__(self, user=None, password=None):
super(CrawlSuite, self).__init__()
self.opener = urllib2.OpenerDirector()
self.opener.add_handler(urllib2.UnknownHandler())
self.opener.add_handler(urllib2.HTTPHandler())
self.opener.add_handler(urllib2.HTTPSHandler())
self.opener.add_handler(RedirectHandler())
self.opener.add_handler(urllib2.HTTPCookieProcessor())
self._authenticate(user, password)
self.user = user
def _request(self, path):
return self.opener.open(urlparse.urlunsplit([
'http', 'localhost:%s' % tools.config['xmlrpc_port'],
path, '', ''
]))
def _authenticate(self, user, password):
# force tools.config['db_name'] in user session so opening `/` doesn't
# blow up in multidb situations
self.opener.open('http://localhost:{port}/web/?db={db}'.format(
port=tools.config['xmlrpc_port'],
db=urllib.quote_plus(tools.config['db_name']),
))
if user is not None:
url = 'http://localhost:{port}/login?{query}'.format(
port=tools.config['xmlrpc_port'],
query=urllib.urlencode({
'db': tools.config['db_name'],
'login': user,
'key': password,
})
)
auth = self.opener.open(url)
assert auth.getcode() < 400, "Auth failure %d" % auth.getcode()
def _wrapped_run(self, result, debug=False):
paths = collections.deque(['/'])
seen = set(paths)
while paths:
url = paths.popleft()
r = self._request(url)
cases.URLCase(self.user, url, r).run(result)
if r.info().gettype() != 'text/html':
continue
doc = lxml.html.fromstring(r.read())
for link in doc.xpath('//a[@href]'):
href = link.get('href')
# avoid repeats, even for links we won't crawl no need to
# bother splitting them if we've already ignored them
# previously
if href in seen: continue
seen.add(href)
parts = urlparse.urlsplit(href)
if parts.netloc or \
not parts.path.startswith('/') or \
parts.path == '/web' or\
parts.path.startswith('/web/') or \
(parts.scheme and parts.scheme not in ('http', 'https')):
continue
paths.append(href)
def load_tests(loader, base, _):
base.addTest(CrawlSuite())
# blog duplicate (&al?) are on links
base.addTest(CrawlSuite('admin', tools.config['admin_passwd']))
base.addTest(CrawlSuite('demo', 'demo'))
return base

View File

@ -109,7 +109,7 @@
</p>
</div>
<div class="col-md-6 mt16 mb16">
<img class="img-responsive shadow" src="/website/static/src/img/text_image.png"/>
<img class="img img-responsive shadow" src="/website/static/src/img/text_image.png"/>
</div>
</div>
</div>
@ -126,7 +126,7 @@
<div class="container">
<div class="row">
<div class="col-md-6 mt16 mb16">
<img class="img-responsive shadow" src="/website/static/src/img/image_text.jpg"/>
<img class="img img-responsive shadow" src="/website/static/src/img/image_text.jpg"/>
</div>
<div class="col-md-6 mt32">
<h3>A Section Subtitle</h3>
@ -243,7 +243,7 @@
<h2>A Punchy Headline</h2>
</div>
<div class="col-md-12">
<img class="img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;"/>
<img class="img img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;"/>
</div>
<div class="col-md-6 col-md-offset-3 mb16 mt16">
<p class="text-center">
@ -277,7 +277,7 @@
<h3 class="text-muted">And a good subtitle</h3>
</div>
<div class="col-md-4">
<img class="img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
<h4 class="mt16">Streamline Recruitments</h4>
<p>
Post job offers and keep track of each application
@ -290,7 +290,7 @@
</p>
</div>
<div class="col-md-4">
<img class="img-rounded img-responsive" src="/website/static/src/img/desert_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/desert_thumb.jpg"/>
<h4 class="mt16">Enterprise Social Network</h4>
<p>
Break down information silos. Share knowledge and best
@ -302,7 +302,7 @@
</p>
</div>
<div class="col-md-4">
<img class="img-rounded img-responsive" src="/website/static/src/img/deers_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/deers_thumb.jpg"/>
<h4 class="mt16">Leaves Management</h4>
<p>
Keep track of the vacation days accrued by each
@ -323,6 +323,7 @@
<div data-snippet-id="well" data-selector-siblings="p, h1, h2, h3, blockquote">
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_well.png"/>
<span class="oe_snippet_thumbnail_title">Well</span>
</div>
<div class="oe_snippet_body well">
@ -381,19 +382,19 @@
<h4 class="text-muted">More than 500 successful projects</h4>
</div>
<div class="col-md-4">
<img class="img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
</div>
<div class="col-md-4">
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
</div>
<div class="col-md-4">
<img class="img-thumbnail img-responsive" src="/website/static/src/img/landscape.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/landscape.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
</div>
</div>
</div>
@ -414,28 +415,28 @@
<h4 class="text-muted">More than 500 successful projects</h4>
</div>
<div class="col-md-6">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/desert.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/desert.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/desert_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/desert_thumb.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
</div>
<div class="col-md-6">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/landscape.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/landscape.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
</div>
</div>
</div>
@ -554,7 +555,7 @@
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_button.png"/>
<span class="oe_snippet_thumbnail_title">Button</span>
</div>
<section class="oe_snippet_body dark">
<section class="oe_snippet_body oe_snippet_darken dark">
<div class="container">
<div class="row">
<div class="col-md-12 text-center mt16 mb16">
@ -649,19 +650,19 @@
</blockquote>
</div>
<div class="col-md-2 col-md-offset-1">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
</div>
</div>
@ -716,7 +717,7 @@
<div data-snippet-id="parallax_quote" data-selector-children=".oe_structure, [data-oe-type=html]">
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_parallax.png"/>
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_quotes_slider.png"/>
<span class="oe_snippet_thumbnail_title">Quotes Slider</span>
</div>
<section class="oe_snippet_body parallax_quote oe_structure">

View File

@ -10,9 +10,27 @@
<link id="website_css" rel='stylesheet' href='/website/static/src/css/website.css' t-ignore="true"/>
</template>
<template id="layout" name="Main layout">
&lt;!DOCTYPE html&gt;
<template id="website.submenu" name="Submenu">
<li t-if="not submenu.child_id">
<a t-att-href="url_for(submenu.url)" t-ignore="true">
<span t-field="submenu.name"/>
</a>
</li>
<li t-if="submenu.child_id" class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span t-field="submenu.name"/> <span class="caret" t-ignore="true"></span>
</a>
<ul class="dropdown-menu" role="menu">
<t t-foreach="submenu.child_id" t-as="submenu">
<t t-call="website.submenu"/>
</t>
</ul>
</li>
</template>
<template id="layout" name="Main layout">&lt;!DOCTYPE html&gt;
<html t-att-lang="lang.replace('_', '-')"
t-att-data-website-id="website.id if editable else None"
t-att-data-editable="'1' if editable else None"
t-att-data-translatable="'1' if translatable else None"
t-att-data-view-xmlid="xmlid if editable else None"
@ -49,6 +67,11 @@
<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>
<script type="text/javascript" src="/website/static/lib/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript">
// Bootstrap and jQuery UI conflicts
$.fn.bstooltip = $.fn.tooltip;
$.fn.bsbutton = $.fn.button;
</script>
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
@ -65,11 +88,13 @@
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>
<script type="text/javascript" src='/website/static/lib/nearest/jquery.nearest.js'></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/MutationObserver.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.editor.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.menu.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.tour.js"></script>
@ -93,38 +118,28 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" t-href="/page/website.homepage"><em>Your</em><b>Company</b></a>
<a class="navbar-brand" href="/"><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 t-href="/page/website.contactus">Contact us</a></li>
<li t-if="user_id.id == website.public_user.id"><a href="/web">Sign in</a></li>
<li t-if="user_id.id != website.public_user.id"><a href="/web"><span t-field="user_id.name"/></a></li>
<li t-if="request.multilang and
(len(website.language_ids) &gt; 1 or editable)" class="dropdown">
<!-- TODO: use flags for language selection -->
<a class="dropdown-toggle js_language_selected" data-toggle="dropdown" href="#">
<t t-esc="lang_selected[0]['name'].split('/').pop()"/> <span class="caret"></span>
<t t-foreach="website.get_menu().child_id" t-as="submenu">
<t t-call="website.submenu"/>
</t>
<li class="active" t-if="user_id.id == website.public_user.id">
<a t-attf-href="/web#redirect=#{ url_for('') }">
Sign in
</a>
<ul class="dropdown-menu js_language_selector" role="menu">
<li t-foreach="website.language_ids" t-as="lg">
<a t-att-href="url_for('', lang=lg.code)" role="menuitem"
t-att-data-default-lang="editable and 'true' if lg.code == website.default_lang_id.code else None">
<strong t-att-class="'icon-circle' if lg.code == lang
else 'icon-circle-blank'"></strong>
<t t-esc="lg.name.split('/').pop()"/>
</a>
</li>
<li t-if="editable" class="divider"/>
<li t-if="editable">
<t t-set="url_return" t-value="request.multilang and url_for(request.httprequest.path, '[lang]') or request.httprequest.path"/>
<t t-if="request.httprequest.query_string">
<t t-set="url_return" t-value="url_return + '?' + request.httprequest.query_string"/>
</t>
<a t-attf-href="/web#action=base.action_view_base_language_install&amp;website_id=#{website.id}&amp;url_return=#{url_return}">
Add a language...
</a>
</li>
</li>
<li class="active dropdown" t-ignore="true" t-if="user_id.id != website.public_user.id">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span t-esc="user_id.name"/>
<span class="caret"></span>
</a>
<ul class="dropdown-menu js_usermenu" role="menu">
<li><a href="/web" role="menuitem">Administration</a></li>
<li class="divider"/>
<li><a t-attf-href="/web/session/logout?redirect=#{ url_for('') }" role="menuitem">Logout</a></li>
</ul>
</li>
</ul>
@ -161,24 +176,42 @@
</h2>
</div>
<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>
</h4>
<p>
We are a team of passionated people whose goal is to improve everyone's
life through disruptive products. We build great products to solve your
business problems.
</p>
<p>
Our products are designed for small to medium companies willing to optimize
their performance.
</p>
<div>
<h4>
<span t-field="res_company.name">Your Company</span>
<small> - <a href="/page/website.aboutus">About us</a></small>
</h4>
<p>
We are a team of passionated people whose goal is to improve everyone's
life through disruptive products. We build great products to solve your
business problems.
</p>
<p>
Our products are designed for small to medium companies willing to optimize
their performance.
</p>
</div>
<ul class="nav nav-pills js_language_selector" t-if="request.multilang and
(len(website.language_ids) &gt; 1 or editable)">
<li t-foreach="website.language_ids" t-as="lg">
<a t-att-href="url_for('', lang=lg.code)"
t-att-data-default-lang="editable and 'true' if lg.code == website.default_lang_id.code else None">
<t t-esc="lg.name.split('/').pop()"/>
</a>
</li>
<li t-if="editable">
<t t-set="url_return" t-value="request.multilang and url_for(request.httprequest.path, '[lang]') or request.httprequest.path"/>
<a t-attf-href="/web#action=base.action_view_base_language_install&amp;website_id=#{website.id}&amp;url_return=#{url_return}">
<i class="icon-plus-sign"/>
Add a language...
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container mt16">
<div class="pull-right" t-ignore="1">
<div class="pull-right" t-ignore="true" t-if="not editable">
Create a <a href="http://openerp.com/apps/website">free website</a> with
<a class="label label-danger" href="https://openerp.com/apps/website">OpenERP</a>
</div>
@ -194,30 +227,32 @@
<template id="footer_custom" inherit_option_id="website.layout" name="Custom Footer">
<xpath expr="//div[@id='footer_container']" position="before">
<section data-snippet-id='three-columns' class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-4">
<h4 class="mt16">Subtitle</h4>
<p>
<a t-href="/">Homepage</a>
</p>
</div>
<div class="col-md-4">
<h4 class="mt16">Subtitle 2</h4>
<p>
...
</p>
</div>
<div class="col-md-4">
<h4 class="mt16">Subtitle 3</h4>
<p>
...
</p>
<div class="oe_structure">
<section data-snippet-id='three-columns' class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-4">
<h4 class="mt16">Subtitle</h4>
<p>
<a href="/">Homepage</a>
</p>
</div>
<div class="col-md-4">
<h4 class="mt16">Subtitle 2</h4>
<p>
...
</p>
</div>
<div class="col-md-4">
<h4 class="mt16">Subtitle 3</h4>
<p>
...
</p>
</div>
</div>
</div>
</div>
</section>
</section>
</div>
</xpath>
<xpath expr="//div[@id='footer_container']" position="attributes">
<attribute name="style">display: none</attribute>
@ -225,9 +260,8 @@
</template>
<template id="publish_management">
<t t-if="editable" t-ignore="true">
<div class="pull-right">
<div t-attf-class="btn-group dropdown js_publish_management #{object.id and object.website_published and 'css_publish' or 'css_unpublish'}" t-att-data-id="object.id" t-att-data-object="object._name">
<div t-if="editable" t-ignore="true" class="pull-right hidden-xs" t-att-style="style or ''">
<div t-attf-class="btn-group dropdown js_publish_management #{object.id and object.website_published and 'css_publish' or 'css_unpublish'}" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-controller="publish_controller">
<a t-attf-class="btn btn-sm btn-#{object.id and object.website_published and 'success' or 'default'}" t-att-id="'dopprod-%s' % object.id" role="button" data-toggle="dropdown">Options <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" t-att-aria-labelledby="'dopprod-%s' % object.id">
<t t-raw="0"/>
@ -235,9 +269,6 @@
<a href="#" class="js_publish_btn css_unpublish">Unpublish</a>
<a href="#" class="js_publish_btn css_publish">Publish</a>
</li>
<li t-if="publish_duplicate">
<a t-att-href="publish_duplicate">Duplicate</a>
</li>
<li t-if="publish_edit">
<a t-att-href="'/web#model=%s&amp;id=%s' % (object._name, object.id)"
title='Edit in backend'>Edit</a>
@ -245,7 +276,6 @@
</ul>
</div>
</div>
</t>
</template>
<template id="publish_short">
@ -262,7 +292,7 @@
</template>
<template id="pager" name="Pager">
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination">
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination" t-att-style="style or ''">
<li t-att-class=" 'disabled' if pager['page']['num'] == 1 else '' ">
<a t-att-href=" pager['page_previous']['url'] if pager['page']['num'] != 1 else '' ">Prev</a>
</li>
@ -364,8 +394,8 @@
<h1 class="container mt32">500: Internal Server Error!</h1>
</div>
<t t-if="editable">
<h3>An exception appear in the template: <span id="exception_template" t-esc="template"/></h3>
<b id="exception_message" t-esc="message"/>
<h3>Exception in template: <span id="exception_template" t-esc="template"/></h3>
<h4 t-if="expr">Expression: <t t-esc="expr"/></h4>
<pre id="exception_node" t-esc="node"/>
<pre id="exception_traceback" t-esc="traceback"/>
</t>
@ -401,7 +431,7 @@ User-agent: *
Sitemap: <t t-esc="url_root"/>sitemap.xml
</template>
<template id="sitemap" name="Site Map" page="True">
<template id="sitemap" name="Site Map">
<t t-call="website.layout">
<ul>
<li t-foreach="pages" t-as="page">
@ -439,8 +469,8 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
<span>&amp;#x2706; <span t-field="res_company.phone"></span></span><br />
<i class="icon-envelope"></i> <span t-field="res_company.email"></span>
</address>
<a t-att-href="res_company.partner_id.google_map_link()" target="_BLANK">
<img class="thumbnail img-responsive" t-att-src="res_company.partner_id.google_map_img()" />
<a t-att-href="res_company.google_map_link()" target="_BLANK">
<img class="thumbnail img-responsive" t-att-src="res_company.google_map_img()" />
</a>
</template>

View File

@ -159,7 +159,9 @@ class WebsiteBlog(http.Controller):
return request.website.render("website_blog.index", values)
@website.route(['/blog/nav'], type='http', auth="public")
# TODO: Refactor (used in website_blog.js for archive links)
# => the archive links should be generated server side
@website.route(['/blog/nav'], type='http', auth="public", multilang=True)
def nav(self, **post):
cr, uid, context = request.cr, request.uid, request.context
blog_post_ids = request.registry['blog.post'].search(
@ -171,32 +173,25 @@ class WebsiteBlog(http.Controller):
blog_post_data = [
{
'id': blog_post.id,
'name': blog_post.name,
'website_published': blog_post.website_published,
'category_id': blog_post.category_id and blog_post.category_id.id or False,
'fragment': request.website.render("website_blog.blog_archive_link", {
'blog_post': blog_post
}),
}
for blog_post in request.registry['blog.post'].browse(cr, uid, blog_post_ids, context=context)
]
return simplejson.dumps(blog_post_data)
@website.route(['/blog/<int:blog_post_id>/post'], type='http', auth="public")
@website.route(['/blog/<int:blog_post_id>/comment'], type='http', auth="public")
def blog_post_comment(self, blog_post_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
url = request.httprequest.host_url
request.session.body = post.get('body')
if request.context['is_public_user']: # purpose of this ?
return '%s/web#action=redirect&url=%s/blog/%s/post' % (url, url, blog_post_id)
if request.session.get('body') and blog_post_id:
request.registry['blog.post'].message_post(
cr, uid, blog_post_id,
body=request.session.body,
type='comment',
subtype='mt_comment',
context=dict(context, mail_create_nosubcribe=True))
request.session.body = False
return werkzeug.utils.redirect("/blog/%s/?enable_editor=1" % (blog_post_id))
request.registry['blog.post'].message_post(
cr, uid, blog_post_id,
body=post.get('comment'),
type='comment',
subtype='mt_comment',
context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
@website.route(['/blog/<int:category_id>/new'], type='http', auth="public")
def blog_post_create(self, category_id=None, **post):

View File

@ -1,6 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<!-- <data noupdate="1"> -->
<data noupdate="1">
<record id="menu_blog" model="website.menu">
<field name="name">News</field>
<field name="url">/blog</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">50</field>
</record>
</data>
<data>
<!-- jump to blog at install -->

View File

@ -8,15 +8,14 @@ $(document).ready(function () {
e.preventDefault();
var $ul = $(this).next("ul");
if (!$ul.find('li').length) {
// TODO: Why POST? (to pass the domain) A GET would be more appropriate...
// This should be done server side anyway...
$.post('/blog/nav', {'domain': $(this).data("domain")}, function (result) {
var blog_id = +window.location.pathname.split("/").pop();
$(JSON.parse(result)).each(function () {
var $a = $('<a href="/blog/' + this.id + '"/>').text(this.name);
var $li = $("<li/>").append($a);
if (blog_id == this.id)
$li.addClass("active");
if (!this.website_published)
$a.css("color", "red");
var $li = $($.parseHTML(this.fragment));
if (blog_id == this.id) $li.addClass("active");
if (!this.website_published) $li.find('a').css("color", "red");
$ul.append($li);
});
@ -26,16 +25,4 @@ $(document).ready(function () {
}
});
var $form = $('.js_website_blog form#comment');
$form.submit(function (e) {
e.preventDefault();
var error = $form.find("textarea").val().length < 3;
$form.find("textarea").toggleClass("has-error", error);
if (!error) {
$form.css("visibility", "hidden");
$.post(window.location.pathname + '/post', {'body': $form.find("textarea").val()}, function (url) {
window.location.href = url
});
}
});
});
});

View File

@ -2,21 +2,12 @@
<openerp>
<data>
<!-- Layout add nav and footer -->
<template id="header_footer_custom" inherit_id="website.layout">
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
<li><a t-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
</xpath>
<template id="header_footer_custom" inherit_id="website.layout" name="Footer News Blog Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
</xpath>
</template>
<template id="footer_custom" inherit_option_id="website.layout" name="Job Announces">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/blog/%(website_blog.blog_category_2)d/">Jobs</a></li>
</xpath>
</template>
<!-- Blog Post Summary -->
<template id="blog_post_short" name="Blog Post Summary">
<div>
@ -24,7 +15,14 @@
<t t-call="website.publish_management">
<t t-set="object" t-value="blog_post"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_duplicate" t-value="'/blog/#{blog_post.id}/duplicate'"/>
<li>
<a href="#" t-attf-data-href="/blog/#{blog_post.id}/duplicate">Duplicate</a>
<script>
var $a=$("[data-href$='/duplicate']");
$a.attr("href", $a.data('href')).removeAttr('data-href');
$a.next("script").remove();
</script>
</li>
</t>
</div>
<h2 class="text-center">
@ -81,7 +79,14 @@
<t t-call="website.publish_management">
<t t-set="object" t-value="blog_post"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_duplicate" t-value="'/blog/%s/duplicate' % (blog_post.id)"/>
<li>
<a href="#" t-attf-data-href="/blog/#{blog_post.id}/duplicate">Duplicate</a>
<script>
var $a=$("[data-href$='/duplicate']");
$a.attr("href", $a.data('href')).removeAttr('data-href');
$a.next("script").remove();
</script>
</li>
</t>
</div>
<div class="clearfix"/>
@ -132,17 +137,19 @@
<template id="opt_blog_post_complete_comment" name="Comment Form"
inherit_option_id="website_blog.blog_post_complete" inherit_id="website_blog.blog_post_complete">
<xpath expr="//ul[last()]" position="after">
<section groups="group_website_blog_reply" class="mb32">
<h4>Leave a Comment</h4>
<form id="comment" t-attf-action="/blog/#{blog_post.id}/post#post"
method="POST">
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&amp;field=image_small&amp;id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
<div class="pull-left mb32" style="width: 75%%">
<textarea rows="3" class="form-control" placeholder="Write a comment..."></textarea>
<button type="submit" class="btn btn-danger mt8">Post</button>
</div>
</form>
</section>
<t t-if="not is_public_user">
<section groups="group_website_blog_reply" class="mb32">
<h4>Leave a Comment</h4>
<form id="comment" t-attf-action="/blog/#{blog_post.id}/comment"
method="POST">
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&amp;field=image_small&amp;id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
<div class="pull-left mb32" style="width: 75%%">
<textarea rows="3" name="comment" class="form-control" placeholder="Write a comment..."></textarea>
<button type="submit" class="btn btn-danger mt8">Post</button>
</div>
</form>
</section>
</t>
</xpath>
</template>
@ -176,7 +183,7 @@
</template>
<!-- Page -->
<template id="index" name="Blog" page="True">
<template id="index" name="Blog">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"></script>
@ -187,7 +194,7 @@
<div class="row">
<div class="col-lg-12 col-sm-12" t-if="not blog_post" id="blog_post">
<t t-if="category and editable">
<div class="row">
<div class="row hidden-xs">
<a t-href="/blog/#{category.id}/new" class="btn btn-primary pull-right">New Blog Post</a>
</div>
</t>
@ -311,5 +318,11 @@
</xpath>
</template>
<template id="blog_archive_link">
<li>
<a t-href="/blog/#{blog_post.id}"><t t-esc="blog_post.name"/></a>
</li>
</template>
</data>
</openerp>

View File

@ -55,7 +55,7 @@
</xpath>
</template>
<template id="contactus_thanks" name="Contact us" page="True">
<template id="contactus_thanks" name="Contact us">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>

View File

@ -28,7 +28,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
if request.context['is_public_user']:
base_partner_domain += [('website_published', '=', True)]
partner_domain = list(base_partner_domain)
if grade_id and grade_id != 'all':
if grade_id and grade_id != "all":
partner_domain += [('grade_id', '=', int(grade_id))] # try/catch int
if country_id:
country = country_obj.browse(request.cr, request.uid, country_id, request.context)

View File

@ -4,7 +4,7 @@
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<template id="footer_custom" inherit_id="website.layout" name="Footer Partners Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/partners/">Partners</a></li>
</xpath>

View File

@ -14,7 +14,7 @@ class WebsiteCustomer(http.Controller):
@website.route([
'/customers/', '/customers/page/<int:page>/',
'/customers/country/<int:country_id>', '/customers/country/<int:country_id>/page/<int:page>/'
], type='http', auth="public")
], type='http', auth="public", multilang=True)
def customers(self, country_id=None, page=0, **post):
cr, uid, context = request.cr, request.uid, request.context
partner_obj = request.registry['res.partner']
@ -70,7 +70,7 @@ class WebsiteCustomer(http.Controller):
}
return request.website.render("website_customer.index", values)
@website.route(['/customers/<int:partner_id>/'], type='http', auth="public")
@website.route(['/customers/<int:partner_id>/'], type='http', auth="public", multilang=True)
def customer(self, partner_id=None, **post):
""" Route for displaying a single partner / customer.

View File

@ -3,13 +3,13 @@
<data>
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<template id="footer_custom" inherit_id="website.layout" name="Footer Customer References Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a href="/customers/">Customer References</a></li>
<li><a t-href="/customers/">Customer References</a></li>
</xpath>
</template>
<!-- Page -->
<!-- Page -->
<template id="layout" name="Customer References Layout">
<t t-call="website.layout">
<t t-set="additional_title">Customer References</t>
@ -53,7 +53,7 @@
<t t-set="classname" t-value="'pull-left'"/>
</t>
<form action="/customers/" method="get" class="navbar-search pull-right pagination form-inline">
<div class="form-group">
<div class="form-group">
<input type="text" name="search" class="search-query form-control"
placeholder="Search" t-att-value="post.get('search', '')"/>
</div>
@ -63,11 +63,11 @@
<div>
<div t-foreach="partner_ids" t-as="partner" 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="/customers/#{ partner.id }/">
<a class="pull-left" t-href="/customers/#{ 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="/customers/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
<a class="media-heading" t-href="/customers/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
<t t-if="partner.website_short_description"><div t-field="partner.website_short_description"/></t><a t-if="partner.website_short_description" class="pull-right" t-attf-href="/customers/#{ partner.id }/">Read More</a>
</div>
</div>

View File

@ -15,6 +15,7 @@ Online Events
'data': [
'data/event_data.xml',
'views/website_event.xml',
'views/website_event_sale_backend.xml',
'security/ir.model.access.csv',
'security/website_event.xml',
],

View File

@ -25,6 +25,9 @@ from openerp.addons.web.http import request
from openerp.tools.translate import _
from openerp.addons import website_sale
from openerp.addons.website.models import website
from openerp.addons.website.controllers.main import Website as controllers
controllers = controllers()
from datetime import datetime
from dateutil.relativedelta import relativedelta
@ -216,3 +219,15 @@ class website_event(http.Controller):
if not _values:
return request.redirect("/event/%s/" % event_id)
return request.redirect("/shop/checkout")
@website.route(['/event/publish'], type='json', auth="public")
def publish(self, id, object):
# if a user publish an event, he publish all linked res.partner
event = request.registry[object].browse(request.cr, request.uid, int(id))
if not event.website_published:
if event.organizer_id and not event.organizer_id.website_published:
event.organizer_id.write({'website_published': True})
if event.address_id and not event.address_id.website_published:
event.address_id.write({'website_published': True})
return controllers.publish(id, object)

View File

@ -2,6 +2,12 @@
<openerp>
<data noupdate="1">
<record id="menu_events" model="website.menu">
<field name="name">Events</field>
<field name="url">/event</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">40</field>
</record>
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Home</field>
<field name="target">self</field>

View File

@ -2,6 +2,9 @@
<openerp>
<data>
<record id="base.res_partner_6" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="event.event_0" model="event.event">
<field name="website_published">True</field>
<field name="twitter_hashtag">openerp</field>
@ -162,6 +165,9 @@
</div>
]]></field> </record>
<record id="base.res_partner_5" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="event.event_1" model="event.event">
<field name="website_published">True</field>
<field name="twitter_hashtag">openerp</field>
@ -323,11 +329,20 @@
]]></field>
</record>
<record id="base.res_partner_14" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="event.event_2" model="event.event">
<field name="website_published">True</field>
<field name="twitter_hashtag">openerp</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="base.res_partner_address_4" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="event.event_3" model="event.event">
<field name="website_published">True</field>
<field name="twitter_hashtag">openerp</field>

View File

@ -51,6 +51,21 @@ class event(osv.osv):
'website_published': False,
}
def _check_organizer_id_published(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.website_published and obj.organizer_id and not obj.organizer_id.website_published:
return False
return True
def _check_address_id_published(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.website_published and obj.address_id and not obj.address_id.website_published:
return False
return True
_constraints = [
(_check_organizer_id_published, "This event can't be published if the field Orginizer is not website published.", ['organizer_id','website_published']),
(_check_address_id_published, "This event can't be published if the field Location is not website published.", ['address_id','website_published']),
]
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)
if partner.address_id:

View File

@ -3,17 +3,14 @@
<data>
<!-- Layout add nav and footer -->
<template id="header_footer_custom" inherit_id="website.layout">
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
<li><a t-href="/event">Events</a></li>
</xpath>
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<template id="header_footer_custom" inherit_id="website.layout" name="Footer Events Link">
<xpath expr="//footer//ul[@name='products']" position="inside">
<li><a t-href="/event">Events</a></li>
</xpath>
</template>
<!-- Page -->
<template id="index" name="Events" page="True">
<template id="index" name="Events">
<t t-call="website.layout">
<div id="wrap">
<div class="container">
@ -48,7 +45,10 @@
</div>
<ul class="media-list">
<li t-foreach="event_ids" t-as="event" class="media" data-publish="">
<t t-call="website.publish_management"><t t-set="object" t-value="event"/></t>
<t t-call="website.publish_management">
<t t-set="object" t-value="event"/>
<t t-set="publish_controller">/event/publish</t>
</t>
<div class="media-body">
<span t-if="not event.event_ticket_ids" class="label label-danger pull-right">Registration Closed</span>
<t t-if="event.event_ticket_ids">
@ -177,7 +177,11 @@
<ul class="media-list" id="comment">
<li t-foreach="event_id.website_message_ids" t-as="comment" class="media">
<div class="media-body">
<t t-call="website.publish_management"><t t-set="object" t-value="comment"/></t>
<t t-call="website.publish_management">
<t t-set="object" t-value="comment"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_controller">/event/publish</t>
</t>
<t t-raw="comment.body"/>
<small class="pull-right muted text-right">
<div t-field="comment.author_id"/>
@ -192,7 +196,12 @@
<div class="panel panel-default" t-if="event_id.address_id">
<div class="panel-heading">
<t t-call="website.publish_management"><t t-set="object" t-value="event_id"/></t>
<t t-call="website.publish_management">
<t t-set="object" t-value="event_id"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_controller">/event/publish</t>
<t t-set="style">position: relative; top: -80px;</t>
</t>
<h4>Where</h4>
</div>
<div class="panel-body">

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_event_form">
<field name="name">event.event.website.form</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event_sale.view_event_form"/>
<field name="arch" type="xml">
<field name="organizer_id" position="after">
<field name="website_published"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@ -11,4 +11,4 @@ class hr(osv.osv):
}
def img(self, cr, uid, ids, field='image_small', context=None):
return "/web/binary/image?model=%s&field=%s&id=%s" % (self._name, field, ids[0])
return "/website/image?model=%s&field=%s&id=%s" % (self._name, field, ids[0])

View File

@ -2,6 +2,9 @@
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
from openerp.addons.website.controllers.main import Website as controllers
controllers = controllers()
import base64
@ -48,7 +51,7 @@ class website_hr_recruitment(http.Controller):
return request.website.render("website_hr_recruitment.index", values)
@website.route(['/job/detail/<model("hr.job"):job>'], type='http', auth="public", multilang=True)
def detail(self, job=None):
def detail(self, job=None, **kwargs):
values = {
'job': job,
'vals_date': job.write_date.split(' ')[0],
@ -68,28 +71,23 @@ class website_hr_recruitment(http.Controller):
'res_id': id
}
request.registry['ir.attachment'].create(request.cr, request.uid, attachment_values)
website = request.registry['website']
values = {
'jobid': post['job_id']
}
return request.website.render("website_hr_recruitment.thankyou", values)
@website.route(['/apply/<int:id>'], type='http', auth="public", multilang=True)
def applyjobpost(self, id=0, **kwargs):
id = id and int(id) or 0
job = request.registry['hr.job'].browse(request.cr, request.uid, id)
values = {
'job': job
}
return request.website.render("website_hr_recruitment.applyjobpost", values)
@website.route(['/apply/<model("hr.job"):job>'], type='http', auth="public", multilang=True)
def applyjobpost(self, job=None, **kwargs):
return request.website.render("website_hr_recruitment.applyjobpost", { 'job': job })
@website.route('/recruitment/published', type='json', auth="admin", multilang=True)
def published (self, id, **post):
hr_job = request.registry['hr.job']
@website.route('/job/publish', type='json', auth="admin", multilang=True)
def publish (self, id, object):
res = controllers.publish(id, object)
hr_job = request.registry[object]
id = int(id)
rec = hr_job.browse(request.cr, request.uid, id)
vals = {}
if rec.website_published:
vals['state'] = 'recruit'
if not rec.no_of_recruitment:
@ -98,6 +96,5 @@ class website_hr_recruitment(http.Controller):
vals['state'] = 'open'
hr_job.write(request.cr, request.uid, [rec.id], vals, context=request.context)
obj = hr_job.browse(request.cr, request.uid, id, context=request.context)
return { 'count': obj.no_of_recruitment, 'state': obj.state, 'published': obj.website_published }
return res
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="menu_jobs" model="website.menu">
<field name="name">Jobs</field>
<field name="url">/jobs</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">60</field>
</record>
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Recruitment Form</field>
<field name="target">self</field>

View File

@ -1,11 +0,0 @@
$(function () {
$(this).ajaxComplete(function(event, xhr, settings) {
var data = JSON.parse(settings.data).params;
if (settings.url == "/website/publish") {
var $data = $(".oe_website_hr_recruitment .js_publish_management[data-id='" + data.id + "'][data-object='" + data.object + "']");
if ($data.length) {
settings.jsonpCallback = openerp.jsonRpc('/recruitment/published', 'call', data);
}
}
});
});

View File

@ -7,16 +7,13 @@
<field name="description">Job Posts on your website</field>
</record>
<template id="job_footer_custom" inherit_id="website.layout" name="Custom Footer Job">
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
<li><a t-href="/jobs">Jobs</a></li>
</xpath>
<template id="job_footer_custom" inherit_id="website.layout" name="Footer Job Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/jobs">Jobs</a></li>
</xpath>
</template>
<template id="index" name="Jobs" page="True">
<template id="index" name="Jobs">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>
@ -56,10 +53,6 @@
<template id="detail">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_hr_recruitment/static/src/js/recruitment.js"></script>
<t t-raw="head or ''"/>
</t>
<t t-set="additional_title">Job Detail</t>
<div id="wrap">
<div class="container oe_website_hr_recruitment">
@ -73,6 +66,7 @@
<t t-call="website.publish_management">
<t t-set="object" t-value="job"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_controller">/job/publish</t>
</t>
</div>
</div>

View File

@ -3,7 +3,7 @@
<data>
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<template id="footer_custom" inherit_id="website.layout" name="Footer Associations Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/members/">Associations</a></li>
</xpath>

View File

@ -28,13 +28,13 @@ from openerp.osv import osv
class Website(osv.Model):
_inherit = "website"
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
def preprocess_request(self, cr, uid, ids, request, context=None):
project_obj = request.registry['project.project']
project_ids = project_obj.search(cr, uid, [('privacy_visibility', "=", "public")], context=request.context)
request.context['website_project_ids'] = project_obj.browse(cr, uid, project_ids, request.context)
return super(Website, self).preprocess_request(cr, uid, ids, *args, **kwargs)
return super(Website, self).preprocess_request(cr, uid, ids, request, context)
class website_project(http.Controller):

View File

@ -3,9 +3,9 @@
<data>
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<template id="footer_custom" inherit_id="website.layout" name="Footer Project's Links">
<xpath expr="//footer//ul[@name='products']" position="inside">
<li t-foreach="website_project_ids" t-as="project"><a t-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-field="project.name"/></li>
</xpath>
</template>

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
import random
import uuid
import urllib
import simplejson
import werkzeug.exceptions
@ -54,11 +53,11 @@ def get_current_order():
class Website(osv.osv):
_inherit = "website"
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
def preprocess_request(self, cr, uid, ids, request, context=None):
request.context.update({
'website_sale_order': get_current_order(),
})
return super(Website, self).preprocess_request(cr, uid, ids, *args, **kwargs)
return super(Website, self).preprocess_request(cr, uid, ids, request, context=None)
class Ecommerce(http.Controller):
@ -214,7 +213,6 @@ class Ecommerce(http.Controller):
col += 1
line += 1
print bin_packing_list
return bin_packing_list
def get_products(self, product_ids):
@ -224,20 +222,24 @@ 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, request.uid, product_ids, context=request.context)
def has_search_attributes(self, attribute_id, value_id=None):
if request.httprequest.args.get('attributes'):
attributes = simplejson.loads(request.httprequest.args['attributes'])
def has_search_filter(self, attribute_id, value_id=None):
if request.httprequest.args.get('filter'):
filter = simplejson.loads(request.httprequest.args['filter'])
else:
attributes = []
for key_val in attributes:
filter = []
for key_val in filter:
if key_val[0] == attribute_id and (not value_id or value_id in key_val[1:]):
return key_val
return False
@website.route(['/shop/attributes/'], type='http', auth="public", multilang=True)
def attributes(self, **post):
attributes = []
@website.route(['/shop/filter/'], type='http', auth="public", multilang=True)
def filter(self, add_filter="", **post):
index = []
filter = []
if add_filter:
filter = simplejson.loads(add_filter)
for filt in filter:
index.append(filt[0])
for key, val in post.items():
cat = key.split("-")
if len(cat) < 3 or cat[2] in ('max','minmem','maxmem'):
@ -249,16 +251,23 @@ class Ecommerce(http.Controller):
_max = int(post.pop("att-%s-max" % cat[1]))
_min = int(val)
if (minmem != _min or maxmem != _max) and cat_id not in index:
attributes.append([cat_id , [_min, _max] ])
filter.append([cat_id , [_min, _max] ])
index.append(cat_id)
elif cat_id not in index:
attributes.append([ cat_id, int(cat[2]) ])
filter.append([ cat_id, int(cat[2]) ])
index.append(cat_id)
else:
attributes[index.index(cat_id)].append( int(cat[2]) )
cat[2] = int(cat[2])
if cat[2] not in filter[index.index(cat_id)][1:]:
filter[index.index(cat_id)].append( cat[2] )
post.pop(key)
return request.redirect("/shop/?attributes=%s&%s" % (simplejson.dumps(attributes).replace(" ", ""), urllib.urlencode(post)))
return request.redirect("/shop/?filter=%s%s%s%s" % (
simplejson.dumps(filter),
add_filter and "&add_filter=%s" % add_filter or "",
post.get("search") and "&search=%s" % post.get("search") or "",
post.get("category") and "&category=%s" % post.get("category") or ""
))
def attributes_to_ids(self, attributes):
obj = request.registry.get('product.attribute.product')
@ -274,7 +283,7 @@ class Ecommerce(http.Controller):
return [r["product_id"][0] for r in att]
@website.route(['/shop/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
def category(self, category=0, attributes="", page=0, **post):
def category(self, category=0, filter="", page=0, **post):
# TDE-NOTE: shouldn't we do somethign about product_template without variants ???
# TDE-NOTE: is there a reason to call a method category when the route is
# basically a shop without category_id speceified ?
@ -296,10 +305,10 @@ class Ecommerce(http.Controller):
cat_id = int(category)
domain = [('product_variant_ids.public_categ_id.id', 'child_of', cat_id)] + domain
if attributes:
attributes = simplejson.loads(attributes)
if attributes:
ids = self.attributes_to_ids(attributes)
if filter:
filter = simplejson.loads(filter)
if filter:
ids = self.attributes_to_ids(filter)
domain = [('id', 'in', ids or [0] )] + domain
step = 20
@ -321,7 +330,11 @@ class Ecommerce(http.Controller):
'Ecommerce': self,
'product_ids': product_ids,
'product_ids_for_holes': fill_hole,
'search': post or dict(),
'search': {
'search': post.get('search') or '',
'category': category,
'filter': filter or '',
},
'pager': pager,
'styles': styles,
'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids],
@ -342,8 +355,8 @@ class Ecommerce(http.Controller):
category_list = sorted(category_list, key=lambda category: category[1])
category = None
if post.get('category_id') and int(post.get('category_id')):
category = category_obj.browse(request.cr, request.uid, int(post.get('category_id')), context=request.context)
if post.get('category') and int(post.get('category')):
category = category_obj.browse(request.cr, request.uid, int(post.get('category')), context=request.context)
request.context['pricelist'] = self.get_pricelist()
product = product_obj.browse(request.cr, request.uid, product_id, context=request.context)
@ -354,7 +367,11 @@ class Ecommerce(http.Controller):
'category_list': category_list,
'main_object': product,
'product': product,
'search': post or dict(),
'search': {
'search': post.get('search') or '',
'category': post.get('category') or '',
'filter': post.get('filter') or '',
}
}
return request.website.render("website_sale.product", values)
@ -452,8 +469,7 @@ class Ecommerce(http.Controller):
else:
order_line_id = order_line_obj.create(request.cr, SUPERUSER_ID, values, context=request.context)
order_obj.write(request.cr, SUPERUSER_ID, [order.id], {'order_line': [(4, order_line_id)]}, context=request.context)
return [quantity, order.get_total_quantity()]
return quantity
@website.route(['/shop/mycart/'], type='http', auth="public", multilang=True)
def mycart(self, **post):
@ -493,7 +509,11 @@ class Ecommerce(http.Controller):
@website.route(['/shop/add_cart_json/'], type='json', auth="public")
def add_cart_json(self, product_id=None, order_line_id=None, remove=None):
return self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
quantity = self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
order = get_current_order()
return [quantity, order.get_total_quantity(), order.amount_total, request.website.render("website_sale.total", {
'website_sale_order': order
}).strip()]
@website.route(['/shop/set_cart_json/'], type='json', auth="public")
def set_cart_json(self, path=None, product_id=None, order_line_id=None, set_number=0, json=None):

View File

@ -38,7 +38,7 @@ class attributes(osv.Model):
_columns = {
'name': fields.char('Name', size=64, translate=True, required=True),
'type': fields.selection([('distinct', 'Distinct'), ('float', 'Float')], "Type", required=True),
'type': fields.selection([('distinct', 'Textual Value'), ('float', 'Numeric Value')], "Type", required=True),
'value_ids': fields.one2many('product.attribute.value', 'attribute_id', 'Values'),
'product_ids': fields.one2many('product.attribute.product', 'attribute_id', 'Products'),
@ -48,9 +48,11 @@ class attributes(osv.Model):
'float_min': fields.function(_get_float_min, type='float', string="Min", store={
'product.attribute.product': (_get_min_max, ['value','attribute_id'], 20),
}),
'visible': fields.boolean('Display Filter on Website'),
}
_defaults = {
'type': 'distinct'
'type': 'distinct',
'visible': True,
}
class attributes_value(osv.Model):
@ -63,9 +65,10 @@ class attributes_value(osv.Model):
class attributes_product(osv.Model):
_name = "product.attribute.product"
_order = 'attribute_id, value_id, value'
_columns = {
'value': fields.float('Value'),
'value_id': fields.many2one('product.attribute.value', 'Distinct Value'),
'value': fields.float('Numeric Value'),
'value_id': fields.many2one('product.attribute.value', 'Textual Value'),
'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True),
'product_id': fields.many2one('product.template', 'Product', required=True),

View File

@ -4,6 +4,10 @@ access_product_template_public,product.template.public,product.model_product_tem
access_product_category_,product.category.public,product.model_product_category,base.group_public,1,0,0,0
access_product_category_public,product.category.public,product.model_product_public_category,base.group_public,1,0,0,0
access_product_pricelist_version_public,product.pricelist.version.public,product.model_product_pricelist_version,base.group_public,1,0,0,0
access_product_pricelist_public,product.pricelist.public,product.model_product_pricelist,base.group_public,1,0,0,0
access_product_product_price_type_public,product.price.type.public,product.model_product_price_type,base.group_public,1,0,0,0
access_sale_order_public,sale.order.public,model_sale_order,base.group_public,1,0,0,0
access_sale_order_line_public,sale.order.line.public,model_sale_order_line,base.group_public,1,0,0,0
access_sale_order_line_public,sale.order.line.public,model_sale_order_line,base.group_public,1,0,0,0
access_product_attribute,product.attribute.public,website_sale.model_product_attribute,base.group_public,1,0,0,0
access_product_attribute_value,product.attribute.value.public,website_sale.model_product_attribute_value,base.group_public,1,0,0,0
access_product_attribute_product,product.attribute.product.public,website_sale.model_product_attribute_product,base.group_public,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 access_product_category_ product.category.public product.model_product_category base.group_public 1 0 0 0
5 access_product_category_public product.category.public product.model_product_public_category base.group_public 1 0 0 0
6 access_product_pricelist_version_public product.pricelist.version.public product.model_product_pricelist_version base.group_public 1 0 0 0
7 access_product_pricelist_public product.pricelist.public product.model_product_pricelist base.group_public 1 0 0 0
8 access_product_product_price_type_public product.price.type.public product.model_product_price_type base.group_public 1 0 0 0
9 access_sale_order_public sale.order.public model_sale_order base.group_public 1 0 0 0
10 access_sale_order_line_public sale.order.line.public model_sale_order_line base.group_public 1 0 0 0
11 access_product_attribute product.attribute.public website_sale.model_product_attribute base.group_public 1 0 0 0
12 access_product_attribute_value product.attribute.value.public website_sale.model_product_attribute_value base.group_public 1 0 0 0
13 access_product_attribute_product product.attribute.product.public website_sale.model_product_attribute_product base.group_public 1 0 0 0

View File

@ -42,5 +42,16 @@
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="product_pricelist_public" model="ir.rule">
<field name="name">Public product pricelist</field>
<field name="model_id" ref="product.model_product_pricelist"/>
<field name="domain_force">[('id','=',session.get('ecommerce_pricelist'))]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</data>
</openerp>
</openerp>

View File

@ -133,7 +133,7 @@
height: 560px;
}
@media (max-width: 600px) {
@media (max-width: 768px) {
#products_grid table, #products_grid tbody, #products_grid tr, #products_grid td {
float: left;
width: 100%;
@ -145,6 +145,16 @@
height: 300px;
display: inline-block;
}
.products_pager {
text-align: center;
margin: 10px 0;
}
.products_pager .pagination {
float: none !important;
margin: 0 !important;
padding: 0 !important;
}
}
@media (max-width: 400px) {
#products_grid .oe_product {

View File

@ -121,7 +121,7 @@
.oe-height-8
height: 560px
@media (max-width: 600px)
@media (max-width: 768px)
#products_grid
table, tbody,tr, td
float: left
@ -132,6 +132,13 @@
width: 100%
height: 300px
display: inline-block
.products_pager
text-align: center
margin: 10px 0
.pagination
float: none !important
margin: 0 !important
padding: 0 !important
@media (max-width: 400px)
#products_grid

View File

@ -17,7 +17,7 @@ $(document).ready(function () {
function set_my_cart_quantity(qty) {
var $q = $(".my_cart_quantity");
$q.parent().parent().toggleClass("hidden", !qty);
$q.parent().parent().removeClass("hidden", !qty);
$q.html(qty)
.hide()
.fadeIn(600);
@ -43,13 +43,21 @@ $(document).ready(function () {
var $link = $(ev.currentTarget);
var product = $link.attr("href").match(/product_id=([0-9]+)/);
var product_id = product ? +product[1] : 0;
openerp.jsonRpc("/shop/add_cart_json/", 'call', {'product_id': product_id, 'order_line_id': $link.data('id'), 'remove': $link.is('[href*="/remove_cart/"]')})
if (!product) {
var line = $link.attr("href").match(/order_line_id=([0-9]+)/);
order_line_id = line ? +line[1] : 0;
}
openerp.jsonRpc("/shop/add_cart_json/", 'call', {
'product_id': product_id,
'order_line_id': order_line_id,
'remove': $link.is('[href*="remove"]')})
.then(function (data) {
if (!data[0]) {
location.reload();
}
set_my_cart_quantity(data[1]);
$link.parents(".input-group:first").find(".js_quantity").val(data[0]);
$('[data-oe-model="sale.order"][data-oe-field="amount_total"]').replaceWith(data[3]);
});
return false;
});

View File

@ -4,9 +4,8 @@
<!-- Layout add nav and footer -->
<template id="header" inherit_id="website.layout" name="Custom Header">
<template id="header" inherit_id="website.layout" name="Header Shop My Cart Link">
<xpath expr="//header//ul[@id='top_menu']/li" position="before">
<li><a t-href="/shop/">Shop</a></li>
<li t-att-class="(not website_sale_order or not website_sale_order.get_total_quantity()) and 'hidden' or ''">
<a t-href="/shop/mycart/">
<i class="icon-shopping-cart"></i>
@ -20,7 +19,7 @@
<!-- List of categories -->
<template id="categories_recursive" name="Category list">
<li t-att-class="category.id == search.get('category') and 'active' or ''">
<li t-att-class="str(category.id) == search.get('category') and 'active' or ''">
<a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-href="/shop/?category=#{ category.id }" t-field="category.name" t-keep-query="search,facettes"></a>
<ul t-if="category.child_id" class="nav nav-pills nav-stacked nav-hierarchy">
<t t-foreach="category.child_id" t-as="category">
@ -34,6 +33,12 @@
<!-- Product list -->
<template id="search" name="Search hidden fields">
<input type="hidden" name="category" t-att-value="search.get('category') or ''"/>
<input type="hidden" name="filter" t-att-value="search.get('filter') or ''"/>
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
</template>
<template id="products_cart" name="Shopping cart">
<div class="ribbon-wrapper">
<div class="ribbon">Promo</div>
@ -65,9 +70,11 @@
</div>
</template>
<template id="products" name="Products" page="True">
<template id="products" name="Products">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
@ -77,26 +84,27 @@
<div class="oe_structure"/>
<div class="container oe_website_sale">
<div class="row">
<div class="col-sm-6 pagination" style="padding-left: 15px;">
<div class="col-sm-6 pagination hidden-xs" style="padding-left: 15px;">
<form t-if="editable" t-keep-query="category,search,facettes"
method="POST" t-action="/shop/add_product">
<button class="btn btn-primary">New Product</button>
</form>
</div>
<div class="col-sm-6">
<div class="col-sm-6 products_pager">
<t t-call="website.pager">
<t t-set="classname">pull-right</t>
<t t-set="style">padding-left: 5px;</t>
</t>
<form t-action="/shop/" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;" t-keep-query="category,search,facettes">
<div class="form-group">
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
</div>
<form t-action="/shop/" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;">
<t t-call="website_sale.search" />
</form>
</div>
</div>
<div class='style_default row'>
<div class="hidden" id="products_grid_before"></div>
<div class="col-md-12" id="products_grid">
<t t-if="product_ids">
<table width="100%">
<tbody>
<t t-set="table_products" t-value="Ecommerce.get_bin_packing_products(product_ids, product_ids_for_holes, 4)"/>
@ -170,9 +178,13 @@
</tr>
</tbody>
</table>
</t>
<t t-if="not product_ids">
<h3 class="text-center text-muted">No product found for this search</h3>
</t>
</div>
</div>
<div>
<div class="products_pager">
<t t-call="website.pager">
<t t-set="classname">pull-right</t>
</t>
@ -234,7 +246,7 @@
<div class="col-sm-5">
<ol class="breadcrumb">
<li><a t-href="/shop">Products</a></li>
<li t-if="search.get('category')"><a t-att-href="'/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
<li t-if="search.get('category')"><a t-href="/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
<li class="active"><span t-field="product.name"></span></li>
</ol>
</div><div class="col-sm-3">
@ -250,17 +262,9 @@
</li>
</t>
</div><div class="col-sm-3 col-sm-offset-1">
<form t-action="/shop/" method="get" class="pull-right" t-keep-query="category,facettes">
<div class="input-group">
<t t-if="search">
<t t-foreach="search.items()" t-as="key">
<input t-att-name="key[0]" t-att-value="key[1]"/>
</t>
</t>
<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 ''"/>
</div>
</form>
<form t-action="/shop/" method="get" class="pull-right">
<t t-call="website_sale.search" />
</form>
</div>
</div>
</section>
@ -268,7 +272,7 @@
<section class="container oe_website_sale mb16" id="product_detail">
<div class="row">
<div class="col-sm-7 col-md-7 col-lg-7">
<span t-field="product.image" style="max-height: 500px" t-field-options='{"widget": "image"}'/>
<span t-field="product.image" style="max-height: 500px" t-field-options='{"widget": "image", "class": "img img-responsive"}'/>
</div><div class="col-sm-5 col-md-5 col-lg-4 col-lg-offset-1">
<h1 t-field="product.name">Product Name</h1>
@ -341,9 +345,19 @@
</xpath>
</template>
<template id="product_attributes" inherit_option_id="website_sale.product" name="Product Attributes">
<xpath expr="//p[@t-field='product.description_sale']" position="after">
<hr t-if="product.website_attribute_ids"/>
<p class="text-muted">
<t t-set="attr" t-value="None"/>
<t t-foreach="product.website_attribute_ids" t-as="attribute"><br t-if="attr and attribute.attribute_id.id != attr"/><t t-if="attribute.attribute_id.id != attr"><span t-field="attribute.attribute_id"/>: </t><t t-if="attribute.attribute_id.id == attr">, </t><t t-if="attribute.attribute_id.type == 'distinct'"><span t-field="attribute.value_id"/></t><t t-if="attribute.attribute_id.type == 'float'"><span t-field="attribute.value"/></t><t t-set="attr" t-value="attribute.attribute_id.id"/></t>
</p>
</xpath>
</template>
<!-- Page Shop my cart -->
<template id="mycart" name="Your Cart" page="True">
<template id="mycart" name="Your Cart">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
@ -361,18 +375,6 @@
</ul>
<h1 class="mb32">Shopping Cart</h1>
<div class="row">
<div class="col-md-3 text-muted" id="right_column">
<h4>Policies</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 30-days money-back guarantee</li>
<li>&#9745; Invoice sent by e-Mail</li>
</ul>
<h4>Secure Payment</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 256 bit encryption</li>
<li>&#9745; Processed by Ogone</li>
</ul>
</div>
<div class="col-md-8 col-md-offset-1 oe_mycart">
<div t-if="not website_sale_order or not website_sale_order.order_line" class="well well-lg">
Your cart is empty!
@ -421,14 +423,14 @@
<td>
<div class="input-group">
<span class="input-group-addon">
<a t-href="./remove_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="mb8 js_add_cart_json">
<a t-href="./add_cart/?remove=True&amp;order_line_id=#{ line.id }" class="mb8 js_add_cart_json">
<span class="icon-minus"/>
</a>
</span>
<input type="text" class="js_quantity form-control"
t-att-data-id="line.id" t-att-value="int(line.product_uom_qty)"/>
<span class="input-group-addon">
<a t-href="./add_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="mb8 float_left js_add_cart_json">
<a t-href="./add_cart/?order_line_id=#{ line.id }" class="mb8 float_left js_add_cart_json">
<span class="icon-plus"/>
</a>
</span>
@ -438,7 +440,7 @@
</tr>
</tbody>
</table>
<table class='pull-right mb16' id="mycart_total">
<table class='pull-right mb16' id="mycart_total" t-if="website_sale_order">
<colgroup>
<col width="100"/>
<col width="120"/>
@ -446,11 +448,8 @@
<thead>
<tr style="border-top: 1px solid #000">
<th><h3>Total:</h3></th>
<th class="text-right"><h3>
<span t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/></h3>
<th class="text-right">
<h3><t t-call="website_sale.total"/></h3>
</th>
</tr>
<tr class="text-muted">
@ -469,6 +468,18 @@
<a t-if="website_sale_order and website_sale_order.order_line" t-href="/shop/checkout/" class="btn btn-primary pull-right mb32">Process Checkout <span class="icon-long-arrow-right"/></a>
<div class="oe_structure"/>
</div>
<div class="col-md-3 text-muted" id="right_column">
<h4>Policies</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 30-days money-back guarantee</li>
<li>&#9745; Invoice sent by e-Mail</li>
</ul>
<h4>Secure Payment</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 256 bit encryption</li>
<li>&#9745; Processed by Ogone</li>
</ul>
</div>
</div>
</div>
@ -480,36 +491,38 @@
<!-- Page Shop -->
<template id="products_categories" inherit_option_id="website_sale.products" name="Product Categories">
<xpath expr="//div[@id='products_grid']" position="before">
<div id="categories" class="col-md-3">
<ul class="nav nav-pills nav-stacked mt16">
<li t-att-class=" '' if search.get('category') else 'active' "><a t-href="/shop/">All Products</a></li>
<t t-set="categ" t-value="Ecommerce.get_categories()"/>
<t t-foreach="categ[0]" t-as="category">
<t t-call="website_sale.categories_recursive"/>
</t>
</ul>
</div>
<xpath expr="//div[@id='products_grid_before']" position="inside">
<ul class="nav nav-pills nav-stacked mt16">
<li t-att-class=" '' if search.get('category') else 'active' "><a t-href="/shop/">All Products</a></li>
<t t-set="categ" t-value="Ecommerce.get_categories()"/>
<t t-foreach="categ[0]" t-as="category">
<t t-call="website_sale.categories_recursive"/>
</t>
</ul>
</xpath>
<xpath expr="//div[@id='products_grid_before']" position="attributes">
<attribute name="class">col-md-3</attribute>
</xpath>
<xpath expr="//div[@id='products_grid']" position="attributes">
<attribute name="class">col-md-9</attribute>
</xpath>
</template>
<template id="products_attributes" inherit_option_id="website_sale.products_categories" name="Product Attributes">
<xpath expr="//div[@id='categories']" position="inside">
<form t-action="/shop/attributes/" method="post" t-keep-query="category,search">
<template id="products_attributes" inherit_option_id="website_sale.products" name="Product Filters and Attributes">
<xpath expr="//div[@id='products_grid_before']" position="inside">
<form t-action="/shop/filter/" method="post" t-keep-query="category,search,add_filter">
<ul class="nav nav-pills nav-stacked mt16">
<t t-set="attribute_ids" t-value="Ecommerce.get_attribute_ids()"/>
<t t-foreach="attribute_ids" t-as="attribute_id">
<t t-if="attribute_id.visible">
<li t-if="attribute_id.value_ids and attribute_id.type == 'distinct'">
<div t-field="attribute_id.name"/>
<ul class="nav nav-pills nav-stacked">
<t t-foreach="attribute_id.value_ids" t-as="value_id">
<li t-att-class="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'active' or ''">
<li t-att-class="Ecommerce.has_search_filter(attribute_id.id, value_id.id) and 'active' or ''">
<label style="margin: 0 20px;">
<input type="checkbox" t-att-name="'att-%s-%s' % (attribute_id.id, value_id.id)"
t-att-checked="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'checked' or ''"/>
t-att-checked="Ecommerce.has_search_filter(attribute_id.id, value_id.id) and 'checked' or ''"/>
<span style="font-weight: normal" t-field="value_id.name"/>
</label>
</li>
@ -518,7 +531,7 @@
</li>
<li t-if="attribute_id.type == 'float' and attribute_id.float_min != attribute_id.float_max">
<div t-field="attribute_id.name"/>
<t t-set="attribute" t-value="Ecommerce.has_search_attributes(attribute_id.id)"/>
<t t-set="attribute" t-value="Ecommerce.has_search_filter(attribute_id.id)"/>
<div style="margin: 0 20px;" class="js_slider"
t-att-data-id="attribute_id.id"
t-att-data-value-min="attribute and attribute[1][0] or attribute_id.float_min"
@ -526,14 +539,22 @@
t-att-data-min="attribute_id.float_min"
t-att-data-max="attribute_id.float_max"></div>
</li>
</t>
</t>
</ul>
<button class="btn btn-xs btn-primary mt16">Apply filter</button>
<a t-href="/shop/" t-keep-query="category,search,add_filter" class="btn btn-xs btn-default mt16">Cancel filter</a>
</form>
</xpath>
<xpath expr="//div[@id='products_grid_before']" position="attributes">
<attribute name="class">col-md-3</attribute>
</xpath>
<xpath expr="//div[@id='products_grid']" position="attributes">
<attribute name="class">col-md-9</attribute>
</xpath>
</template>
<template id="suggested_products_list" inherit_id="website_sale.mycart" inherit_option_id="website_sale.mycart" name="Suggested Products in list view">
<template id="suggested_products_list" inherit_id="website_sale.mycart" inherit_option_id="website_sale.mycart" name="Suggested Products in my cart">
<xpath expr="//table[@id='mycart_products']" position="after">
<table t-if="suggested_products" class='table table-striped table-condensed'>
<colgroup>
@ -622,7 +643,7 @@
<div class="col-md-8 oe_mycart">
<h3 class="page-header mt16">Billing Information
<small t-if="user_id.id == website.public_user.id"> or
<a t-if="not partner" t-attf-href="/web#action=redirect&amp;url=#{ request.httprequest.host_url }/shop/checkout/">sign in</a>
<a t-if="not partner" t-attf-href="/web#action=redirect&amp;url=#{ request.httprequest.url }">sign in</a>
</small>
</h3>
<div class="row">
@ -639,7 +660,7 @@
<input type="email" name="email" class="form-control" t-att-value="checkout.get('email')"/>
</div>
<div t-attf-class="form-group #{ error.get('phone') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="phone">Telephone</label>
<label class="control-label" for="phone">Phone</label>
<input type="tel" name="phone" class="form-control" t-att-value="checkout.get('phone')"/>
</div>
@ -695,7 +716,7 @@
<input type="text" name="shipping_name" class="form-control" t-att-value="checkout.get('shipping_name', '')"/>
</div>
<div t-attf-class="form-group #{error.get('shipping_phone') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">Telephone</label>
<label class="control-label" for="contact_name">Phone</label>
<input type="tel" name="shipping_phone" class="form-control" t-att-value="checkout.get('shipping_phone', '')"/>
</div>
<div t-attf-class="form-group #{error.get('shipping_street') and 'has-error' or ''} col-lg-6">
@ -879,5 +900,12 @@
</t>
</template>
<template id="total">
<span t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</template>
</data>
</openerp>

View File

@ -64,7 +64,7 @@
<field name="website_published"/>
</xpath>
<xpath expr="//page[@string='Information']" position="inside">
<group colspan="4" string="Products On Ecommerce">
<group colspan="4" string="Website Options">
<field name="suggested_product_ids" widget="many2many_tags"/>
<field name="website_style_ids" widget="many2many_tags"/>
<field colspan="4" name="website_attribute_ids" nolabel="1">
@ -81,9 +81,22 @@
</group>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="view_product_attribute_form">
<field name="name">product.attribute.form</field>
<field name="model">product.attribute</field>
<field name="arch" type="xml">
<form string="Product Attributes" version="7.0">
<group>
<field name="name"/>
<field name="type"/>
<field name="visible"/>
</group>
</form>
</field>
</record>
</data>
</openerp>

View File

@ -2,6 +2,12 @@
<openerp>
<data noupdate="1">
<record id="menu_shop" model="website.menu">
<field name="name">Shop</field>
<field name="url">/shop</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">30</field>
</record>
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Shop</field>
<field name="target">self</field>