[MERGE] Merged lp:~openerp-dev/openobject-addons/trunk-website-al
bzr revid: psa@tinyerp.com-20131022115302-t5a9ftki0yt2pxvk bzr revid: psa@tinyerp.com-20131024051639-ijmy82htxvy3c23r bzr revid: psa@tinyerp.com-20131028063631-7ndbt6c4x09ad283
This commit is contained in:
@@ -22,9 +22,9 @@
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.osv import orm, fields
|
||||
|
||||
class decimal_precision(osv.osv):
|
||||
class decimal_precision(orm.Model):
|
||||
_name = 'decimal.precision'
|
||||
_columns = {
|
||||
'name': fields.char('Usage', size=50, select=True, required=True),
|
||||
@@ -71,4 +71,29 @@ def get_precision(application):
|
||||
return (16, res)
|
||||
return change_digit
|
||||
|
||||
class DecimalPrecisionFloat(orm.AbstractModel):
|
||||
""" Override qweb.field.float to add a `decimal_precision` domain option
|
||||
and use that instead of the column's own value if it is specified
|
||||
"""
|
||||
_inherit = 'ir.qweb.field.float'
|
||||
|
||||
|
||||
def precision(self, cr, uid, column, options=None, context=None):
|
||||
dp = options and options.get('decimal_precision')
|
||||
if dp:
|
||||
return self.pool['decimal.precision'].precision_get(
|
||||
cr, uid, dp)
|
||||
|
||||
return super(DecimalPrecisionFloat, self).precision(
|
||||
cr, uid, column, options=options, context=context)
|
||||
|
||||
class DecimalPrecisionTestModel(orm.Model):
|
||||
_name = 'decimal.precision.test'
|
||||
|
||||
_columns = {
|
||||
'float': fields.float(),
|
||||
'float_2': fields.float(digits=(16, 2)),
|
||||
'float_4': fields.float(digits=(16, 4)),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import test_qweb_float
|
||||
|
||||
checks = [
|
||||
test_qweb_float
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.tests import common
|
||||
|
||||
class TestFloatExport(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestFloatExport, self).setUp()
|
||||
self.Model = self.registry('decimal.precision.test')
|
||||
|
||||
def get_converter(self, name):
|
||||
converter = self.registry('ir.qweb.field.float')
|
||||
column = self.Model._all_columns[name].column
|
||||
|
||||
return lambda value, options=None: converter.value_to_html(
|
||||
self.cr, self.uid, value, column, options=options, context=None)
|
||||
|
||||
def test_basic_float(self):
|
||||
converter = self.get_converter('float')
|
||||
self.assertEqual(
|
||||
converter(42.0),
|
||||
"42.0")
|
||||
self.assertEqual(
|
||||
converter(42.12345),
|
||||
"42.12345")
|
||||
|
||||
converter = self.get_converter('float_2')
|
||||
self.assertEqual(
|
||||
converter(42.0),
|
||||
"42.00")
|
||||
self.assertEqual(
|
||||
converter(42.12345),
|
||||
"42.12")
|
||||
|
||||
converter = self.get_converter('float_4')
|
||||
self.assertEqual(
|
||||
converter(42.0),
|
||||
'42.0000')
|
||||
self.assertEqual(
|
||||
converter(42.12345),
|
||||
'42.1234')
|
||||
|
||||
def test_precision_domain(self):
|
||||
DP = self.registry('decimal.precision')
|
||||
DP.create(self.cr, self.uid, {
|
||||
'name': 'A',
|
||||
'digits': 2,
|
||||
})
|
||||
DP.create(self.cr, self.uid, {
|
||||
'name': 'B',
|
||||
'digits': 6,
|
||||
})
|
||||
|
||||
converter = self.get_converter('float')
|
||||
self.assertEqual(
|
||||
converter(42.0, {'decimal_precision': 'A'}),
|
||||
'42.00')
|
||||
self.assertEqual(
|
||||
converter(42.0, {'decimal_precision': 'B'}),
|
||||
'42.000000')
|
||||
|
||||
converter = self.get_converter('float_4')
|
||||
self.assertEqual(
|
||||
converter(42.12345, {'decimal_precision': 'A'}),
|
||||
'42.12')
|
||||
self.assertEqual(
|
||||
converter(42.12345, {'decimal_precision': 'B'}),
|
||||
'42.123450')
|
||||
@@ -51,8 +51,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
def index(self, **kw):
|
||||
return self.page("website.homepage")
|
||||
|
||||
# FIXME: auth, if /pagenew known anybody can create new empty page
|
||||
@website.route('/pagenew/<path:path>', type='http', auth="admin")
|
||||
@website.route('/pagenew/<path:path>', type='http', auth="user")
|
||||
def pagenew(self, path, noredirect=NOPE):
|
||||
module = 'website'
|
||||
# completely arbitrary max_length
|
||||
@@ -131,7 +130,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
html = request.website.render('website.404', values)
|
||||
return html
|
||||
|
||||
@website.route('/website/customize_template_toggle', type='json', auth='admin') # FIXME: auth
|
||||
@website.route('/website/customize_template_toggle', type='json', auth='user')
|
||||
def customize_template_set(self, view_id):
|
||||
view_obj = request.registry.get("ir.ui.view")
|
||||
view = view_obj.browse(request.cr, request.uid, int(view_id),
|
||||
@@ -145,7 +144,7 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
}, context=request.context)
|
||||
return True
|
||||
|
||||
@website.route('/website/customize_template_get', type='json', auth='admin') # FIXME: auth
|
||||
@website.route('/website/customize_template_get', type='json', auth='user')
|
||||
def customize_template_get(self, xml_id, optional=True):
|
||||
imd = request.registry['ir.model.data']
|
||||
view_model, view_theme_id = imd.get_object_reference(
|
||||
@@ -215,11 +214,9 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
'value': new_content,
|
||||
}
|
||||
irt.create(request.cr, request.uid, new_trans)
|
||||
irt._get_source.clear_cache(irt) # FIXME: find why ir.translation does not invalidate
|
||||
return True
|
||||
|
||||
# # FIXME: auth, anybody can upload an attachment if URL known/found
|
||||
@website.route('/website/attach', type='http', auth='admin')
|
||||
@website.route('/website/attach', type='http', auth='user')
|
||||
def attach(self, func, upload):
|
||||
req = request.httprequest
|
||||
if req.method != 'POST':
|
||||
@@ -229,12 +226,18 @@ class Website(openerp.addons.web.controllers.main.Home):
|
||||
try:
|
||||
attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
|
||||
'name': upload.filename,
|
||||
'datas': base64.encodestring(upload.read()),
|
||||
'datas': upload.read().encode('base64'),
|
||||
'datas_fname': upload.filename,
|
||||
'res_model': 'ir.ui.view',
|
||||
}, request.context)
|
||||
# FIXME: auth=user... no good.
|
||||
url = '/website/attachment/%d' % attachment_id
|
||||
|
||||
url = website.urlplus('/website/image', {
|
||||
'model': 'ir.attachment',
|
||||
'id': attachment_id,
|
||||
'field': 'datas',
|
||||
'max_height': MAX_IMAGE_HEIGHT,
|
||||
'max_width': MAX_IMAGE_WIDTH,
|
||||
})
|
||||
except Exception, e:
|
||||
logger.exception("Failed to upload image to attachment")
|
||||
message = str(e)
|
||||
@@ -283,7 +286,7 @@ class Images(http.Controller):
|
||||
return response.make_conditional(request.httprequest)
|
||||
|
||||
@website.route('/website/image', auth="public")
|
||||
def image(self, model, id, field):
|
||||
def image(self, model, id, field, max_width=maxint, max_height=maxint):
|
||||
Model = request.registry[model]
|
||||
|
||||
response = werkzeug.wrappers.Response()
|
||||
@@ -291,7 +294,7 @@ class Images(http.Controller):
|
||||
id = int(id)
|
||||
|
||||
ids = Model.search(request.cr, request.uid,
|
||||
[('id', '=', id)], context=request.context)\
|
||||
[('id', '=', id)], context=request.context) \
|
||||
or Model.search(request.cr, openerp.SUPERUSER_ID,
|
||||
[('id', '=', id), ('website_published', '=', True)], context=request.context)
|
||||
|
||||
@@ -312,8 +315,8 @@ class Images(http.Controller):
|
||||
response.last_modified = datetime.datetime.strptime(
|
||||
record[concurrency], server_format)
|
||||
|
||||
# Field does not exist on model or field set to False
|
||||
if not record.get(field):
|
||||
# Field does not exist on model or field set to False
|
||||
# FIXME: maybe a field which does not exist should be a 404?
|
||||
return self.placeholder(response)
|
||||
|
||||
@@ -324,31 +327,13 @@ class Images(http.Controller):
|
||||
if response.status_code == 304:
|
||||
return response
|
||||
|
||||
return self.set_image_data(response, record[field].decode('base64'))
|
||||
data = record[field].decode('base64')
|
||||
fit = int(max_width), int(max_height)
|
||||
|
||||
# FIXME: auth
|
||||
# FIXME: delegate to image?
|
||||
@website.route('/website/attachment/<int:id>', auth='admin')
|
||||
def attachment(self, id):
|
||||
attachment = request.registry['ir.attachment'].browse(
|
||||
request.cr, request.uid, id, request.context)
|
||||
|
||||
return self.set_image_data(
|
||||
werkzeug.wrappers.Response(),
|
||||
attachment.datas.decode('base64'),
|
||||
fit=IMAGE_LIMITS,)
|
||||
|
||||
def set_image_data(self, response, data, fit=(maxint, maxint)):
|
||||
""" Sets an inferred mime type on the response object, and puts the
|
||||
provided image's data in it, possibly after resizing if requested
|
||||
|
||||
Returns the response object after setting its mime and content, so
|
||||
the result of ``get_final_image`` can be returned directly.
|
||||
"""
|
||||
buf = cStringIO.StringIO(data)
|
||||
|
||||
# FIXME: unknown format or not an image
|
||||
image = Image.open(buf)
|
||||
image.load()
|
||||
response.mimetype = Image.MIME[image.format]
|
||||
|
||||
w, h = image.size
|
||||
@@ -356,10 +341,13 @@ class Images(http.Controller):
|
||||
|
||||
if w < max_w and h < max_h:
|
||||
response.set_data(data)
|
||||
return response
|
||||
else:
|
||||
image.thumbnail(fit, Image.ANTIALIAS)
|
||||
image.save(response.stream, image.format)
|
||||
# invalidate content-length computed by make_conditional as writing
|
||||
# to response.stream does not do it (as of werkzeug 0.9.3)
|
||||
del response.headers['Content-Length']
|
||||
|
||||
image.thumbnail(fit, Image.ANTIALIAS)
|
||||
image.save(response.stream, image.format)
|
||||
return response
|
||||
|
||||
|
||||
|
||||
@@ -7,17 +7,20 @@ Also, adds methods to convert values back to openerp models.
|
||||
"""
|
||||
|
||||
import cStringIO
|
||||
import datetime
|
||||
import itertools
|
||||
import logging
|
||||
import re
|
||||
import urllib2
|
||||
import urlparse
|
||||
|
||||
import werkzeug.utils
|
||||
from dateutil import parser
|
||||
from lxml import etree, html
|
||||
from PIL import Image as I
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools import ustr
|
||||
from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
REMOTE_CONNECTION_TIMEOUT = 2.5
|
||||
|
||||
@@ -39,12 +42,12 @@ class Field(orm.AbstractModel):
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
def attributes(self, cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context):
|
||||
source_element, g_att, t_att, qweb_context, context=None):
|
||||
column = record._model._all_columns[field_name].column
|
||||
return itertools.chain(
|
||||
super(Field, self).attributes(cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att,
|
||||
qweb_context),
|
||||
qweb_context, context=context),
|
||||
[('data-oe-translate', 1 if column.translate else 0)]
|
||||
)
|
||||
|
||||
@@ -67,7 +70,56 @@ class Float(orm.AbstractModel):
|
||||
_name = 'website.qweb.field.float'
|
||||
_inherit = ['website.qweb.field', 'ir.qweb.field.float']
|
||||
|
||||
value_from_string = float
|
||||
def from_html(self, cr, uid, model, column, element, context=None):
|
||||
lang = self.user_lang(cr, uid, context=context)
|
||||
|
||||
value = element.text_content().strip()
|
||||
|
||||
return float(value.replace(lang.thousands_sep, '')
|
||||
.replace(lang.decimal_point, '.'))
|
||||
|
||||
|
||||
def parse_fuzzy(in_format, value):
|
||||
day_first = in_format.find('%d') < in_format.find('%m')
|
||||
|
||||
if '%y' in in_format:
|
||||
year_first = in_format.find('%y') < in_format.find('%d')
|
||||
else:
|
||||
year_first = in_format.find('%Y') < in_format.find('%d')
|
||||
|
||||
return parser.parse(value, dayfirst=day_first, yearfirst=year_first)
|
||||
|
||||
class Date(orm.AbstractModel):
|
||||
_name = 'website.qweb.field.date'
|
||||
_inherit = ['website.qweb.field', 'ir.qweb.field.date']
|
||||
|
||||
def from_html(self, cr, uid, model, column, element, context=None):
|
||||
lang = self.user_lang(cr, uid, context=context)
|
||||
in_format = lang.date_format.encode('utf-8')
|
||||
|
||||
value = element.text_content().strip()
|
||||
try:
|
||||
dt = datetime.datetime.strptime(in_format, value)
|
||||
except ValueError:
|
||||
dt = parse_fuzzy(in_format, value)
|
||||
|
||||
return dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
class DateTime(orm.AbstractModel):
|
||||
_name = 'website.qweb.field.datetime'
|
||||
_inherit = ['website.qweb.field', 'ir.qweb.field.datetime']
|
||||
|
||||
def from_html(self, cr, uid, model, column, element, context=None):
|
||||
lang = self.user_lang(cr, uid, context=context)
|
||||
in_format = (u"%s %s" % (lang.date_format, lang.time_format)).encode('utf-8')
|
||||
|
||||
value = element.text_content().strip()
|
||||
try:
|
||||
dt = datetime.datetime.strptime(in_format, value)
|
||||
except ValueError:
|
||||
dt = parse_fuzzy(in_format, value)
|
||||
|
||||
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
class Text(orm.AbstractModel):
|
||||
_name = 'website.qweb.field.text'
|
||||
@@ -127,7 +179,7 @@ class Image(orm.AbstractModel):
|
||||
_inherit = ['website.qweb.field', 'ir.qweb.field.image']
|
||||
|
||||
def to_html(self, cr, uid, field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context):
|
||||
source_element, t_att, g_att, qweb_context, context=None):
|
||||
assert source_element.nodeName != 'img',\
|
||||
"Oddly enough, the root tag of an image field can not be img. " \
|
||||
"That is because the image goes into the tag, or it gets the " \
|
||||
@@ -135,9 +187,9 @@ class Image(orm.AbstractModel):
|
||||
|
||||
return super(Image, self).to_html(
|
||||
cr, uid, field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context)
|
||||
source_element, t_att, g_att, qweb_context, context=context)
|
||||
|
||||
def record_to_html(self, cr, uid, field_name, record, column, options=None):
|
||||
def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
|
||||
cls = ''
|
||||
if 'class' in options:
|
||||
cls = ' class="%s"' % werkzeug.utils.escape(options['class'])
|
||||
@@ -148,10 +200,11 @@ class Image(orm.AbstractModel):
|
||||
def from_html(self, cr, uid, model, column, element, context=None):
|
||||
url = element.find('img').get('src')
|
||||
|
||||
m = re.match(r'^/website/attachment/(\d+)$', url)
|
||||
if m:
|
||||
url_object = urlparse.urlsplit(url)
|
||||
query = urlparse.parse_qs(url_object.query)
|
||||
if url_object.path == '/website/image' and query['model'] == 'ir.attachment':
|
||||
attachment = self.pool['ir.attachment'].browse(
|
||||
cr, uid, int(m.group(1)), context=context)
|
||||
cr, uid, int(query['id']), context=context)
|
||||
return attachment.datas
|
||||
|
||||
# remote URL?
|
||||
@@ -183,4 +236,9 @@ class Monetary(orm.AbstractModel):
|
||||
_inherit = ['website.qweb.field', 'ir.qweb.field.monetary']
|
||||
|
||||
def from_html(self, cr, uid, model, column, element, context=None):
|
||||
return float(element.find('span').text)
|
||||
lang = self.user_lang(cr, uid, context=context)
|
||||
|
||||
value = element.find('span').text.strip()
|
||||
|
||||
return float(value.replace(lang.thousands_sep, '')
|
||||
.replace(lang.decimal_point, '.'))
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from urlparse import urlparse
|
||||
|
||||
from lxml import etree, html
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.addons.base import ir
|
||||
|
||||
class view(osv.osv):
|
||||
_inherit = "ir.ui.view"
|
||||
@@ -82,15 +84,41 @@ class view(osv.osv):
|
||||
return out
|
||||
|
||||
def replace_arch_section(self, cr, uid, view_id, section_xpath, replacement, context=None):
|
||||
arch = replacement
|
||||
if section_xpath:
|
||||
previous_arch = etree.fromstring(self.browse(cr, uid, view_id, context=context).arch.encode('utf-8'))
|
||||
# remove branding from replacement section
|
||||
for att in ir.ir_ui_view.MOVABLE_BRANDING:
|
||||
replacement.attrib.pop(att, None)
|
||||
|
||||
if not section_xpath:
|
||||
# replace all of the arch, not just a fragment
|
||||
arch = replacement
|
||||
else:
|
||||
arch = etree.fromstring(self.browse(cr, uid, view_id, context=context).arch.encode('utf-8'))
|
||||
# ensure there's only one match
|
||||
[previous_section] = previous_arch.xpath(section_xpath)
|
||||
[previous_section] = arch.xpath(section_xpath)
|
||||
|
||||
previous_section.getparent().replace(previous_section, replacement)
|
||||
arch = previous_arch
|
||||
|
||||
return arch
|
||||
|
||||
URL_ATTRS = {
|
||||
'form': 'action',
|
||||
'a': 'href',
|
||||
'link': 'href',
|
||||
'frame': 'src',
|
||||
'iframe': 'src',
|
||||
'script': 'src',
|
||||
}
|
||||
def _normalize_urls(self, root):
|
||||
for element in root.iter():
|
||||
attr = self.URL_ATTRS.get(element.tag)
|
||||
if attr is None or attr not in element.attrib:
|
||||
continue
|
||||
|
||||
value = element.get(attr)
|
||||
if not urlparse(value).scheme:
|
||||
element.attrib.pop(attr)
|
||||
element.set('t-' + attr, value)
|
||||
|
||||
def save(self, cr, uid, res_id, value, xpath=None, context=None):
|
||||
""" Update a view section. The view section may embed fields to write
|
||||
|
||||
@@ -103,6 +131,8 @@ class view(osv.osv):
|
||||
arch_section = html.fromstring(
|
||||
value, parser=html.HTMLParser(encoding='utf-8'))
|
||||
|
||||
self._normalize_urls(arch_section)
|
||||
|
||||
if xpath is None:
|
||||
# value is an embedded field on its own, not a view section
|
||||
self.save_embedded_field(cr, uid, arch_section, context=context)
|
||||
|
||||
@@ -238,10 +238,26 @@ class website(osv.osv):
|
||||
return {
|
||||
"page_count": page_count,
|
||||
"offset": (page - 1) * step,
|
||||
"page": {'url': get_url(page), 'num': page},
|
||||
"page_start": {'url': get_url(pmin), 'num': pmin},
|
||||
"page_end": {'url': get_url(min(pmax, page + 1)),
|
||||
'num': min(pmax, page + 1)},
|
||||
"page": {
|
||||
'url': get_url(page),
|
||||
'num': page
|
||||
},
|
||||
"page_start": {
|
||||
'url': get_url(pmin),
|
||||
'num': pmin
|
||||
},
|
||||
"page_previous": {
|
||||
'url': get_url(max(pmin, page - 1)),
|
||||
'num': max(pmin, page - 1)
|
||||
},
|
||||
"page_next": {
|
||||
'url': get_url(min(pmax, page + 1)),
|
||||
'num': min(pmax, page + 1)
|
||||
},
|
||||
"page_end": {
|
||||
'url': get_url(pmax),
|
||||
'num': pmax
|
||||
},
|
||||
"pages": [
|
||||
{'url': get_url(page), 'num': page}
|
||||
for page in xrange(pmin, pmax+1)
|
||||
@@ -348,13 +364,18 @@ class website(osv.osv):
|
||||
class ir_attachment(osv.osv):
|
||||
_inherit = "ir.attachment"
|
||||
def _website_url_get(self, cr, uid, ids, name, arg, context=None):
|
||||
context = context or {}
|
||||
result = {}
|
||||
for attach in self.browse(cr, uid, ids, context=context):
|
||||
if attach.type=='url':
|
||||
if attach.type == 'url':
|
||||
result[attach.id] = attach.url
|
||||
else:
|
||||
result[attach.id] = "/website/attachment/"+str(attach.id)
|
||||
result[attach.id] = urlplus('/website/image', {
|
||||
'model': 'ir.attachment',
|
||||
'field': 'datas',
|
||||
'id': attach.id,
|
||||
'max_width': 1024,
|
||||
'max_height': 768,
|
||||
})
|
||||
return result
|
||||
_columns = {
|
||||
'website_url': fields.function(_website_url_get, string="Attachment URL", type='char')
|
||||
@@ -386,6 +407,17 @@ class base_language_install(osv.osv):
|
||||
'website_ids': fields.many2many('website', string='Websites to translate'),
|
||||
}
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
defaults = super(base_language_install, self).default_get(cr, uid, fields, context)
|
||||
website_id = context.get('params', {}).get('website_id')
|
||||
if website_id:
|
||||
if 'website_ids' not in defaults:
|
||||
defaults['website_ids'] = []
|
||||
defaults['website_ids'].append(website_id)
|
||||
return defaults
|
||||
|
||||
def lang_install(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
@@ -5,7 +5,10 @@ access_website_converter_test,access_website_converter_test,model_website_conver
|
||||
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
|
||||
access_website_qweb_field,access_website_qweb_field,model_website_qweb_field,,0,0,0,0
|
||||
access_website_qweb_field_integer,access_website_qweb_field_integer,model_website_qweb_field_integer,,0,0,0,0
|
||||
access_website_qweb_field_float,access_website_qweb_field_float,model_website_qweb_field_float,,0,0,0,0
|
||||
access_website_qweb_field_date,access_website_qweb_field_date,model_website_qweb_field_date,,0,0,0,0
|
||||
access_website_qweb_field_datetime,access_website_qweb_field_datetime,model_website_qweb_field_datetime,,0,0,0,0
|
||||
access_website_qweb_field_text,access_website_qweb_field_text,model_website_qweb_field_text,,0,0,0,0
|
||||
access_website_qweb_field_selection,access_website_qweb_field_selection,model_website_qweb_field_selection,,0,0,0,0
|
||||
access_website_qweb_field_many2one,access_website_qweb_field_many2one,model_website_qweb_field_many2one,,0,0,0,0
|
||||
|
||||
|
@@ -1360,7 +1360,7 @@ CKEDITOR.dom.range = function( root ) {
|
||||
* * {@link CKEDITOR#SHRINK_TEXT} - Shrink the range boudaries to anchor by the side of enclosed text
|
||||
* node, range remains if there's no text nodes on boundaries at all.
|
||||
*
|
||||
* @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.
|
||||
* @param {Boolean} [selectContents] Whether result range anchors at the inner OR outer boundary of the node.
|
||||
*/
|
||||
shrink: function( mode, selectContents, shrinkOnBlockBoundary ) {
|
||||
// Unable to shrink a collapsed range.
|
||||
|
||||
@@ -798,7 +798,7 @@
|
||||
*
|
||||
* @method
|
||||
* @member CKEDITOR.editor
|
||||
* @param {Boolean} forceRealSelection Return real selection, instead of saved or fake one.
|
||||
* @param {Boolean} [forceRealSelection] Return real selection, instead of saved or fake one.
|
||||
* @returns {CKEDITOR.dom.selection} A selection object or null if not available for the moment.
|
||||
*/
|
||||
CKEDITOR.editor.prototype.getSelection = function( forceRealSelection ) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@charset "utf-8";
|
||||
/* ---- CKEditor Minimal Reset ---- */
|
||||
.navbar.navbar-inverse .cke_chrome {
|
||||
border: none;
|
||||
@@ -112,6 +111,11 @@ table.editorbar-panel td.selected {
|
||||
background-color: #b1c9d9;
|
||||
}
|
||||
|
||||
.oe_translate_or {
|
||||
color: white;
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
|
||||
.oe_translate_examples li {
|
||||
margin: 10px;
|
||||
padding: 4px;
|
||||
@@ -169,6 +173,11 @@ table.editorbar-panel td.selected {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.oe_carlos_danger {
|
||||
outline: 1px solid red !important;
|
||||
background-color: #ffd9dd !important;
|
||||
}
|
||||
|
||||
/* ---- SNIPPET EDITOR ---- */
|
||||
#oe_snippets {
|
||||
position: fixed;
|
||||
@@ -608,7 +617,6 @@ table.editorbar-panel td.selected {
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
height: 100%;
|
||||
width: 720px;
|
||||
}
|
||||
.oe_ace_view_editor .oe_ace_view_editor_title {
|
||||
width: 100%;
|
||||
@@ -637,11 +645,13 @@ table.editorbar-panel td.selected {
|
||||
right: 0;
|
||||
bottom: 51px;
|
||||
left: 0;
|
||||
width: 720px;
|
||||
}
|
||||
.oe_ace_view_editor .ace_editor .ace_gutter {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.oe_ace_view_editor.oe_ace_open {
|
||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
|
||||
opacity: 1;
|
||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=97);
|
||||
opacity: 0.97;
|
||||
}
|
||||
.oe_ace_view_editor.oe_ace_closed {
|
||||
z-index: -1000;
|
||||
|
||||
@@ -106,6 +106,9 @@ table.editorbar-panel
|
||||
background-color: #b1c9d9
|
||||
|
||||
// ---- TRANSLATIONS ---- {{{
|
||||
.oe_translate_or
|
||||
color: white
|
||||
padding: 0 0.2em
|
||||
.oe_translate_examples li
|
||||
margin: 10px
|
||||
padding: 4px
|
||||
@@ -151,6 +154,10 @@ table.editorbar-panel
|
||||
content: " "
|
||||
white-space: pre-wrap
|
||||
|
||||
.oe_carlos_danger
|
||||
outline: 1px solid red !important
|
||||
background-color: #ffd9dd !important
|
||||
|
||||
/* ---- SNIPPET EDITOR ---- */
|
||||
|
||||
#oe_snippets
|
||||
@@ -466,7 +473,6 @@ $highlighted_text_color: #ffffff
|
||||
|
||||
/* ---- ACE EDITOR ---- */
|
||||
|
||||
$ace_width: 720px
|
||||
$editorbar_height: 30px
|
||||
// TODO Fix => might break with themes
|
||||
$navbar_height: 51px
|
||||
@@ -481,7 +487,6 @@ $navbar_height: 51px
|
||||
right: 0
|
||||
z-index: 1000
|
||||
height: 100%
|
||||
width: $ace_width
|
||||
.oe_ace_view_editor_title
|
||||
width: 100%
|
||||
padding-top: 0
|
||||
@@ -502,9 +507,10 @@ $navbar_height: 51px
|
||||
right: 0
|
||||
bottom: $navbar_height
|
||||
left: 0
|
||||
width: $ace_width
|
||||
.ace_gutter
|
||||
cursor: ew-resize
|
||||
&.oe_ace_open
|
||||
+opacity(1)
|
||||
+opacity(0.97)
|
||||
&.oe_ace_closed
|
||||
z-index: -1000
|
||||
+opacity(0)
|
||||
|
||||
@@ -100,6 +100,13 @@
|
||||
self.aceEditor.resize();
|
||||
self.$el.width(width);
|
||||
}
|
||||
function storeEditorWidth() {
|
||||
window.localStorage.setItem('ace_editor_width', self.$el.width());
|
||||
}
|
||||
function readEditorWidth() {
|
||||
var width = window.localStorage.getItem('ace_editor_width');
|
||||
return parseInt(width || 720, 10);
|
||||
}
|
||||
function startResizing (e) {
|
||||
self.refX = e.pageX;
|
||||
self.resizing = true;
|
||||
@@ -113,16 +120,23 @@
|
||||
var width = self.$el.width() - offset;
|
||||
self.refX = e.pageX;
|
||||
resizeEditor(width);
|
||||
storeEditorWidth();
|
||||
}
|
||||
}
|
||||
document.body.addEventListener('mouseup', stopResizing, true);
|
||||
self.$('.ace_gutter').mouseup(stopResizing).mousedown(startResizing).click(stopResizing);
|
||||
$(document).mousemove(updateWidth);
|
||||
$('button[data-action=edit]').click(function () {
|
||||
self.close();
|
||||
});
|
||||
resizeEditor(readEditorWidth());
|
||||
},
|
||||
loadViews: function (views) {
|
||||
var self = this;
|
||||
var activeViews = _.filter(views, function (view) {
|
||||
var activeViews = _.uniq(_.filter(views, function (view) {
|
||||
return view.active;
|
||||
}), false, function (view) {
|
||||
return view.id;
|
||||
});
|
||||
var $viewList = self.$('#ace-view-list');
|
||||
_.each(activeViews, function (view) {
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
//noinspection JSValidateTypes
|
||||
editor.addCommand('link', {
|
||||
exec: function (editor, data) {
|
||||
exec: function (editor) {
|
||||
link_dialog(editor);
|
||||
return true;
|
||||
},
|
||||
@@ -65,7 +65,7 @@
|
||||
});
|
||||
//noinspection JSValidateTypes
|
||||
editor.addCommand('image', {
|
||||
exec: function (editor, data) {
|
||||
exec: function (editor) {
|
||||
image_dialog(editor);
|
||||
return true;
|
||||
},
|
||||
@@ -177,7 +177,7 @@
|
||||
var editor = new website.EditorBar();
|
||||
var $body = $(document.body);
|
||||
editor.prependTo($body).then(function () {
|
||||
if (location.search.indexOf("unable_editor") >= 0) {
|
||||
if (location.search.indexOf("enable_editor") >= 0) {
|
||||
editor.edit();
|
||||
}
|
||||
});
|
||||
@@ -219,7 +219,7 @@
|
||||
var view_id = $(event.currentTarget).data('view-id');
|
||||
openerp.jsonRpc('/website/customize_template_toggle', 'call', {
|
||||
'view_id': view_id
|
||||
}).then( function(result) {
|
||||
}).then( function() {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
@@ -253,7 +253,6 @@
|
||||
);
|
||||
},
|
||||
edit: function () {
|
||||
var self = this;
|
||||
this.$buttons.edit.prop('disabled', true);
|
||||
this.$('#website-top-view').hide();
|
||||
this.$('#website-top-edit').show();
|
||||
@@ -273,30 +272,58 @@
|
||||
editor.destroy();
|
||||
// FIXME: select editables then filter by dirty?
|
||||
var defs = this.rte.fetch_editables(root)
|
||||
.removeClass('oe_editable cke_focus')
|
||||
.removeAttr('contentEditable')
|
||||
.filter('.oe_dirty')
|
||||
.removeAttr('contentEditable')
|
||||
.removeClass('oe_dirty oe_editable cke_focus oe_carlos_danger')
|
||||
.map(function () {
|
||||
var $el = $(this);
|
||||
// TODO: Add a queue with concurrency limit in webclient
|
||||
// https://github.com/medikoo/deferred/blob/master/lib/ext/function/gate.js
|
||||
return self.saving_mutex.exec(function () {
|
||||
return self.saveElement($el)
|
||||
.fail(function () {
|
||||
var data = $el.data();
|
||||
console.error(_.str.sprintf('Could not save %s(%d).%s', data.oeModel, data.oeId, data.oeField));
|
||||
.then(undefined, function (thing, response) {
|
||||
// because ckeditor regenerates all the dom,
|
||||
// we can't just setup the popover here as
|
||||
// everything will be destroyed by the DOM
|
||||
// regeneration. Add markings instead, and
|
||||
// returns a new rejection with all relevant
|
||||
// info
|
||||
var id = _.uniqueId('carlos_danger_');
|
||||
$el.addClass('oe_dirty oe_carlos_danger');
|
||||
$el.addClass(id);
|
||||
return $.Deferred().reject({
|
||||
id: id,
|
||||
error: response.data,
|
||||
});
|
||||
});
|
||||
});
|
||||
}).get();
|
||||
return $.when.apply(null, defs).then(function () {
|
||||
website.reload();
|
||||
}, function (failed) {
|
||||
// If there were errors, re-enable edition
|
||||
self.rte.start_edition(true).then(function () {
|
||||
// jquery's deferred being a pain in the ass
|
||||
if (!_.isArray(failed)) { failed = [failed]; }
|
||||
|
||||
_(failed).each(function (failure) {
|
||||
$(root).find('.' + failure.id)
|
||||
.removeClass(failure.id)
|
||||
.popover({
|
||||
trigger: 'hover',
|
||||
content: failure.error.message,
|
||||
placement: 'auto top',
|
||||
})
|
||||
// Force-show popovers so users will notice them.
|
||||
.popover('show');
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Saves an RTE content, which always corresponds to a view section (?).
|
||||
*/
|
||||
saveElement: function ($el) {
|
||||
$el.removeClass('oe_dirty');
|
||||
var markup = $el.prop('outerHTML');
|
||||
return openerp.jsonRpc('/web/dataset/call', 'call', {
|
||||
model: 'ir.ui.view',
|
||||
@@ -350,6 +377,7 @@
|
||||
// The detail attribute indicates the number of times a mouse button has been pressed
|
||||
// we just want the triple click
|
||||
if (e.detail !== 3) { return; }
|
||||
e.preventDefault();
|
||||
|
||||
// Get closest block-level element to the triple-clicked
|
||||
// element (using ckeditor's block list because why not)
|
||||
@@ -422,15 +450,25 @@
|
||||
|
||||
return true;
|
||||
},
|
||||
start_edition: function ($elements) {
|
||||
/**
|
||||
* Makes the page editable
|
||||
*
|
||||
* @param {Boolean} [restart=false] in case the edition was already set
|
||||
* up once and is being re-enabled.
|
||||
* @returns {$.Deferred} deferred indicating when the RTE is ready
|
||||
*/
|
||||
start_edition: function (restart) {
|
||||
var self = this;
|
||||
// create a single editor for the whole page
|
||||
var root = document.getElementById('wrapwrap');
|
||||
$(root).on('dragstart', 'img', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
this.webkitSelectionFixer(root);
|
||||
this.tableNavigation(root);
|
||||
if (!restart) {
|
||||
$(root).on('dragstart', 'img', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
this.webkitSelectionFixer(root);
|
||||
this.tableNavigation(root);
|
||||
}
|
||||
var def = $.Deferred();
|
||||
var editor = this.editor = CKEDITOR.inline(root, self._config());
|
||||
editor.on('instanceReady', function () {
|
||||
editor.setReadOnly(false);
|
||||
@@ -446,7 +484,9 @@
|
||||
document.execCommand("enableInlineTableEditing", false, "false");
|
||||
|
||||
self.trigger('rte:ready');
|
||||
def.resolve();
|
||||
});
|
||||
return def;
|
||||
},
|
||||
|
||||
setup_editables: function (root) {
|
||||
@@ -477,13 +517,12 @@
|
||||
|
||||
fetch_editables: function (root) {
|
||||
return $(root).find('[data-oe-model]')
|
||||
// FIXME: propagation should make "meta" blocks non-editable in the first place...
|
||||
.not('link, script')
|
||||
.not('.oe_snippet_editor')
|
||||
.filter(function () {
|
||||
var $this = $(this);
|
||||
// keep view sections and fields which are *not* in
|
||||
// view sections for toplevel editables
|
||||
// view sections for top-level editables
|
||||
return $this.data('oe-model') === 'ir.ui.view'
|
||||
|| !$this.closest('[data-oe-model = "ir.ui.view"]').length;
|
||||
});
|
||||
@@ -854,7 +893,7 @@
|
||||
this.preview_image();
|
||||
},
|
||||
|
||||
file_selection: function (e) {
|
||||
file_selection: function () {
|
||||
this.$('button.filepicker').removeClass('btn-danger btn-success');
|
||||
|
||||
var self = this;
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
templates.push(template);
|
||||
};
|
||||
website.load_templates = function(templates) {
|
||||
var def = $.Deferred();
|
||||
var count = templates.length;
|
||||
|
||||
var dones = _(templates).map(function (t) {
|
||||
return new $.Deferred(function (d) {
|
||||
openerp.qweb.add_template(t, function(err) {
|
||||
@@ -39,8 +36,8 @@
|
||||
};
|
||||
website.reload = function () {
|
||||
location.hash = "scrollTop=" + window.document.body.scrollTop;
|
||||
if (location.search.indexOf("unable_editor") > -1) {
|
||||
window.location.href = window.location.href.replace(/unable_editor(=[^&]*)?/g, '');
|
||||
if (location.search.indexOf("enable_editor") > -1) {
|
||||
window.location.href = window.location.href.replace(/enable_editor(=[^&]*)?/g, '');
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
@@ -151,7 +148,7 @@
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.js_publish_management .js_publish_btn', function (e) {
|
||||
$(document).on('click', '.js_publish_management .js_publish_btn', function () {
|
||||
var $data = $(this).parents(".js_publish_management:first");
|
||||
var $btn = $data.find('.btn:first');
|
||||
var publish = $btn.hasClass("btn-success");
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
save: function () {
|
||||
this.snippets.make_active(false);
|
||||
|
||||
// TODO TOFIX: call clean_for_save on all snippets of the page, not only modified ones
|
||||
// important for banner of parallax that changes data automatically.
|
||||
// FIXME: call clean_for_save on all snippets of the page, not only modified ones
|
||||
// important for banner of parallax that changes data automatically.
|
||||
this.snippets.clean_for_save();
|
||||
remove_added_snippet_id();
|
||||
this._super();
|
||||
@@ -196,7 +196,7 @@
|
||||
}
|
||||
self.make_active($target);
|
||||
});
|
||||
$("[data-oe-model]").on('click', function (ev) {
|
||||
$("[data-oe-model]").on('click', function () {
|
||||
if (!snipped_event_flag && self.$active_snipped_id && !self.$active_snipped_id.parents("[data-snippet-id]:first")) {
|
||||
self.make_active(false);
|
||||
}
|
||||
@@ -224,6 +224,7 @@
|
||||
},
|
||||
clean_for_save: function () {
|
||||
for (var k in this.snippets) {
|
||||
if (!this.snippets.hasOwnProperty(k)) { continue; }
|
||||
var editor = $(this.snippets[k]).data("snippet-editor");
|
||||
if (editor) {
|
||||
editor.clean_for_save();
|
||||
@@ -341,13 +342,13 @@
|
||||
});
|
||||
},
|
||||
stop: function(ev, ui){
|
||||
if (action === 'insert' && ! dropped) {
|
||||
var el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first()
|
||||
if (el) {
|
||||
el.after($toInsert)
|
||||
dropped = true;
|
||||
}
|
||||
}
|
||||
if (action === 'insert' && ! dropped) {
|
||||
var $el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
|
||||
if ($el) {
|
||||
$el.after($toInsert);
|
||||
dropped = true;
|
||||
}
|
||||
}
|
||||
|
||||
$('.oe_drop_zone').droppable('destroy').remove();
|
||||
if (dropped) {
|
||||
@@ -460,7 +461,7 @@
|
||||
// count += $zones.length;
|
||||
// $zones.remove();
|
||||
|
||||
$zones = $('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove(); // no recusrive zones
|
||||
$zones = $('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove(); // no recursive zones
|
||||
count += $zones.length;
|
||||
$zones.remove();
|
||||
} while (count > 0);
|
||||
@@ -471,14 +472,13 @@
|
||||
var zone = $(this);
|
||||
var prev = zone.prev();
|
||||
var next = zone.next();
|
||||
var float_prev = zone.prev().css('float') || 'none';
|
||||
var float_next = zone.next().css('float') || 'none';
|
||||
var disp_prev = zone.prev().css('display') || null;
|
||||
var disp_next = zone.next().css('display') || null;
|
||||
var float_prev = prev.css('float') || 'none';
|
||||
var float_next = next.css('float') || 'none';
|
||||
var disp_prev = prev.css('display') || null;
|
||||
var disp_next = next.css('display') || null;
|
||||
if( (float_prev === 'left' || float_prev === 'right')
|
||||
&& (float_next === 'left' || float_next === 'right') ){
|
||||
zone.remove();
|
||||
return;
|
||||
}else if( !( disp_prev === null
|
||||
|| disp_next === null
|
||||
|| disp_prev === 'block'
|
||||
@@ -689,7 +689,7 @@
|
||||
var self = this;
|
||||
var $styles = this.$overlay.find('.oe_options');
|
||||
var $ul = $styles.find('ul:first');
|
||||
_.each(this.parent.style_templates, function (val, key) {
|
||||
_.each(this.parent.style_templates, function (val) {
|
||||
if (!self.parent.dom_filter(val.selector).is(self.$target)) {
|
||||
return;
|
||||
}
|
||||
@@ -757,7 +757,7 @@
|
||||
|
||||
/*
|
||||
* build_snippet
|
||||
* This method is called just after that a thumbnail is drag and droped into a drop zone
|
||||
* 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) {
|
||||
@@ -771,7 +771,7 @@
|
||||
},
|
||||
|
||||
/* onFocus
|
||||
* This method is called when the user click outide the snippet in the dom, after a focus
|
||||
* This method is called when the user click outside the snippet in the dom, after a focus
|
||||
*/
|
||||
onBlur : function () {
|
||||
this.$overlay.removeClass('oe_active');
|
||||
@@ -790,7 +790,7 @@
|
||||
var $ul = this.$editor.find(ul_options);
|
||||
var bg_value = (typeof bg === 'string' ? this.$target.find(bg) : $(bg)).css("background-image").replace(/url\(['"]*|['"]*\)/g, "");
|
||||
|
||||
// bind envent on options
|
||||
// bind event on options
|
||||
var $li = $ul.find("li");
|
||||
$li.on('click', function (event) {
|
||||
if ($(this).data("value")) {
|
||||
@@ -893,7 +893,8 @@
|
||||
var regClass = new RegExp("\\s*" + resize[0][begin].replace(/[-]*[0-9]+/, '[-]*[0-9]+'), 'g');
|
||||
|
||||
var cursor = $handle.css("cursor")+'-important';
|
||||
$("body").addClass(cursor);
|
||||
var $body = $(document.body);
|
||||
$body.addClass(cursor);
|
||||
|
||||
var body_mousemove = function (event){
|
||||
event.preventDefault();
|
||||
@@ -920,15 +921,15 @@
|
||||
self.parent.cover_target(self.$overlay, self.$target);
|
||||
}
|
||||
};
|
||||
$('body').mousemove(body_mousemove);
|
||||
|
||||
var body_mouseup = function(){
|
||||
$('body').unbind('mousemove', body_mousemove);
|
||||
$('body').unbind('mouseup', body_mouseup);
|
||||
$("body").removeClass(cursor);
|
||||
$body.unbind('mousemove', body_mousemove);
|
||||
$body.unbind('mouseup', body_mouseup);
|
||||
$body.removeClass(cursor);
|
||||
self.parent.editor_busy = false;
|
||||
};
|
||||
$('body').mouseup(body_mouseup);
|
||||
$body.mousemove(body_mousemove);
|
||||
$body.mouseup(body_mouseup);
|
||||
});
|
||||
},
|
||||
getSize: function () {
|
||||
@@ -998,7 +999,7 @@
|
||||
if (compass !== 'w')
|
||||
return;
|
||||
|
||||
// don't change the rigth border position when we change the offset (replace col size)
|
||||
// don't change the right border position when we change the offset (replace col size)
|
||||
var beginCol = Number(beginClass.match(/col-md-([0-9]+)|$/)[1] || 0);
|
||||
var beginOffset = Number(beginClass.match(/col-md-offset-([0-9-]+)|$/)[1] || beginClass.match(/col-lg-offset-([0-9-]+)|$/)[1] || 0);
|
||||
var offset = Number(this.grid.w[0][current].match(/col-md-offset-([0-9-]+)|$/)[1] || 0);
|
||||
@@ -1077,14 +1078,6 @@
|
||||
self.$target.carousel();
|
||||
});
|
||||
|
||||
this.$target.on('dblclick', '.item.active .carousel-image img', function (event) {
|
||||
var $img = $(event.currentTarget);
|
||||
var editor = new website.editor.ImageDialog();
|
||||
editor.on('start', self, function (o) {o.url = $img.attr("src");});
|
||||
editor.on('save', self, function (o) {$img.attr("src", o.url);});
|
||||
editor.appendTo($('body'));
|
||||
});
|
||||
|
||||
this.rebind_event();
|
||||
},
|
||||
// rebind event to active carousel on edit mode
|
||||
@@ -1136,7 +1129,6 @@
|
||||
var style = false;
|
||||
var $el = this.$inner.find('.item.active');
|
||||
var $ul = this.$editor.find('ul[name="carousel-style"]');
|
||||
var $li = $ul.find("li");
|
||||
|
||||
if ($el.hasClass('text_only'))
|
||||
style = 'text_only';
|
||||
@@ -1168,7 +1160,6 @@
|
||||
});
|
||||
},
|
||||
change_size: function () {
|
||||
var self = this;
|
||||
var $el = this.$target;
|
||||
|
||||
var size = 'oe_big';
|
||||
@@ -1205,7 +1196,6 @@
|
||||
this.change_size();
|
||||
},
|
||||
scroll: function(){
|
||||
var self = this;
|
||||
var $ul = this.$editor.find('ul[name="parallax-scroll"]');
|
||||
var $li = $ul.find("li");
|
||||
var $parallax = this.$target.find('.parallax');
|
||||
@@ -1215,7 +1205,7 @@
|
||||
$li.on('click', function (event) {
|
||||
$li.removeClass("active");
|
||||
$(this).addClass("active");
|
||||
var speed = $(this).data('value')
|
||||
var speed = $(this).data('value');
|
||||
$parallax.attr('data-stellar-background-ratio', speed);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
|
||||
var render = website.tour.render;
|
||||
|
||||
website.EditorBasicTour = website.EditorTour.extend({
|
||||
id: 'add_banner_tour',
|
||||
name: "Insert a banner",
|
||||
init: function (editor) {
|
||||
var self = this;
|
||||
var $body = $(document.body);
|
||||
self.steps = [
|
||||
{
|
||||
stepId: 'welcome',
|
||||
orphan: true,
|
||||
backdrop: true,
|
||||
title: "Welcome to your website!",
|
||||
content: "This tutorial will guide you to build your first page. We will start by adding a banner.",
|
||||
template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }),
|
||||
},
|
||||
{
|
||||
stepId: 'edit-page',
|
||||
element: 'button[data-action=edit]',
|
||||
placement: 'bottom',
|
||||
reflex: true,
|
||||
title: "Edit this page",
|
||||
content: "Every page of your website can be modified through the <i>Edit</i> button.",
|
||||
template: render('website.tour_popover'),
|
||||
},
|
||||
{
|
||||
stepId: 'add-block',
|
||||
element: 'button[data-action=snippet]',
|
||||
placement: 'bottom',
|
||||
title: "Insert building blocks",
|
||||
content: "To add content in a page, you can insert building blocks.",
|
||||
template: render('website.tour_popover'),
|
||||
onShow: function () {
|
||||
function refreshAddBlockStep () {
|
||||
self.tour.showStep(self.indexOfStep('add-block'));
|
||||
editor.off('rte:ready', editor, refreshAddBlockStep);
|
||||
}
|
||||
editor.on('rte:ready', editor, refreshAddBlockStep);
|
||||
$('button[data-action=snippet]').click(function () {
|
||||
self.movetoStep('drag-banner');
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
stepId: 'drag-banner',
|
||||
element: '#website-top-navbar [data-snippet-id=carousel].ui-draggable',
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Banner",
|
||||
content: "Drag the <em>Banner</em> block and drop it in your page. <p class='text-muted'>Tip: release the mouse button when you are in a valid zone, with a preview of the banner.</p>",
|
||||
template: render('website.tour_popover'),
|
||||
onShow: function () {
|
||||
function beginDrag () {
|
||||
$('.popover.tour').remove();
|
||||
function goToNextStep () {
|
||||
$('#oe_snippets').hide();
|
||||
self.movetoStep('edit-title');
|
||||
$body.off('mouseup', goToNextStep);
|
||||
}
|
||||
$body.off('mousedown', beginDrag);
|
||||
$body.on('mouseup', goToNextStep);
|
||||
}
|
||||
|
||||
$body.on('mousedown', beginDrag);
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'edit-title',
|
||||
element: '#wrap [data-snippet-id=carousel]:first .carousel-caption',
|
||||
placement: 'left',
|
||||
title: "Customize banner's text",
|
||||
content: "Click in the text and start editing it. Click continue once it's done.",
|
||||
template: render('website.tour_popover', { next: "Continue" }),
|
||||
onHide: function () {
|
||||
var $banner = $("#wrap [data-snippet-id=carousel]:first");
|
||||
if ($banner.length) {
|
||||
$banner.click();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'customize-banner',
|
||||
element: '.oe_overlay_options .oe_options',
|
||||
placement: 'left',
|
||||
title: "Customize the banner",
|
||||
content: "You can customize components of your page through the <em>Customize</em> menu. Try to change the background of your banner.",
|
||||
template: render('website.tour_popover', { next: "Continue" }),
|
||||
onShow: function () {
|
||||
$('.dropdown-menu [name=carousel-background]').click(function () {
|
||||
self.movetoStep('save-changes');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'save-changes',
|
||||
element: 'button[data-action=save]',
|
||||
placement: 'right',
|
||||
reflex: true,
|
||||
title: "Save your modifications",
|
||||
content: "Once you click on save, your website page is updated.",
|
||||
template: render('website.tour_popover'),
|
||||
onHide: function () {
|
||||
self.saveStep('part-2');
|
||||
},
|
||||
|
||||
},
|
||||
{
|
||||
stepId: 'part-2',
|
||||
orphan: true,
|
||||
title: "Congratulation!",
|
||||
content: "Your homepage have been updated. Now, we suggest you to insert others building blocks like texts and images to structure your page.",
|
||||
template: render('website.tour_popover', { next: "Continue" }),
|
||||
},
|
||||
{
|
||||
stepId: 'show-tutorials',
|
||||
element: '#help-menu-button',
|
||||
placement: 'left',
|
||||
title: "Help is always available",
|
||||
content: "But you can always click here if you want more tutorials.",
|
||||
template: render('website.tour_popover', { end: "Close" }),
|
||||
}
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
startOfPart2: function () {
|
||||
var currentStepIndex = this.currentStepIndex();
|
||||
var secondPartIndex = this.indexOfStep('part-2');
|
||||
var showTutorialsIndex = this.indexOfStep('show-tutorials');
|
||||
return (currentStepIndex === secondPartIndex || currentStepIndex === showTutorialsIndex) && !this.tour.ended();
|
||||
},
|
||||
canResume: function () {
|
||||
return this.startOfPart2() || this._super();
|
||||
},
|
||||
});
|
||||
|
||||
website.EditorBar.include({
|
||||
start: function () {
|
||||
var menu = $('#help-menu');
|
||||
var basicTour = new website.EditorBasicTour(this);
|
||||
var $menuItem = $($.parseHTML('<li><a href="#">'+basicTour.name+'</a></li>'));
|
||||
$menuItem.click(function () {
|
||||
basicTour.reset();
|
||||
basicTour.start();
|
||||
});
|
||||
menu.append($menuItem);
|
||||
var url = new website.UrlParser(window.location.href);
|
||||
if (url.search.indexOf('?tutorial=true') === 0 || basicTour.startOfPart2()) {
|
||||
basicTour.start();
|
||||
}
|
||||
$('.tour-backdrop').click(function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
|
||||
}());
|
||||
@@ -4,9 +4,11 @@
|
||||
var website = openerp.website;
|
||||
website.templates.push('/website/static/src/xml/website.tour.xml');
|
||||
|
||||
function render (template, dict) {
|
||||
return openerp.qweb.render(template, dict);
|
||||
}
|
||||
website.tour = {
|
||||
render: function render (template, dict) {
|
||||
return openerp.qweb.render(template, dict);
|
||||
}
|
||||
};
|
||||
|
||||
website.EditorTour = openerp.Class.extend({
|
||||
tour: undefined,
|
||||
@@ -19,7 +21,7 @@
|
||||
keyboard: false,
|
||||
});
|
||||
this.tour.addSteps(_.map(this.steps, function (step) {
|
||||
step.title = render('website.tour_popover_title', { title: step.title });
|
||||
step.title = website.tour.render('website.tour_popover_title', { title: step.title });
|
||||
return step;
|
||||
}));
|
||||
this.monkeyPatchTour();
|
||||
@@ -81,137 +83,6 @@
|
||||
},
|
||||
});
|
||||
|
||||
website.EditorBasicTour = website.EditorTour.extend({
|
||||
id: 'add_banner_tour',
|
||||
name: "Insert a banner",
|
||||
init: function (editor) {
|
||||
var self = this;
|
||||
self.steps = [
|
||||
{
|
||||
stepId: 'welcome',
|
||||
orphan: true,
|
||||
backdrop: true,
|
||||
title: "Welcome to your website!",
|
||||
content: "This tutorial will guide you to build your first page. We will start by adding a banner.",
|
||||
template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }),
|
||||
},
|
||||
{
|
||||
stepId: 'edit-page',
|
||||
element: 'button[data-action=edit]',
|
||||
placement: 'bottom',
|
||||
reflex: true,
|
||||
title: "Edit this page",
|
||||
content: "Every page of your website can be modified through the <i>Edit</i> button.",
|
||||
template: render('website.tour_popover'),
|
||||
},
|
||||
{
|
||||
stepId: 'add-block',
|
||||
element: 'button[data-action=snippet]',
|
||||
placement: 'bottom',
|
||||
title: "Insert building blocks",
|
||||
content: "To add content in a page, you can insert building blocks.",
|
||||
template: render('website.tour_popover'),
|
||||
onShow: function () {
|
||||
function refreshAddBlockStep () {
|
||||
self.tour.showStep(self.indexOfStep('add-block'));
|
||||
editor.off('rte:ready', editor, refreshAddBlockStep);
|
||||
}
|
||||
editor.on('rte:ready', editor, refreshAddBlockStep);
|
||||
$('button[data-action=snippet]').click(function () {
|
||||
self.movetoStep('drag-banner');
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
stepId: 'drag-banner',
|
||||
element: '#website-top-navbar [data-snippet-id=carousel].ui-draggable',
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Banner",
|
||||
content: "Drag the <em>Banner</em> block and drop it in your page. <p class='text-muted'>Tip: release the mouse button when you are in a valid zone, with a preview of the banner.</p>",
|
||||
template: render('website.tour_popover'),
|
||||
onShow: function () {
|
||||
function beginDrag () {
|
||||
$('.popover.tour').remove();
|
||||
$('body').off('mousedown', beginDrag);
|
||||
function goToNextStep () {
|
||||
$('#oe_snippets').hide();
|
||||
self.movetoStep('edit-title');
|
||||
$('body').off('mouseup', goToNextStep);
|
||||
}
|
||||
$('body').on('mouseup', goToNextStep);
|
||||
}
|
||||
$('body').on('mousedown', beginDrag);
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'edit-title',
|
||||
element: '#wrap [data-snippet-id=carousel]:first .carousel-caption',
|
||||
placement: 'left',
|
||||
title: "Customize banner's text",
|
||||
content: "Click in the text and start editing it. Click continue once it's done.",
|
||||
template: render('website.tour_popover', { next: "Continue" }),
|
||||
onHide: function () {
|
||||
var $banner = $("#wrap [data-snippet-id=carousel]:first");
|
||||
if ($banner.length) {
|
||||
$banner.click();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'customize-banner',
|
||||
element: '.oe_overlay_options .oe_options',
|
||||
placement: 'left',
|
||||
title: "Customize the banner",
|
||||
content: "You can customize components of your page through the <em>Customize</em> menu. Try to change the background of your banner.",
|
||||
template: render('website.tour_popover', { next: "Continue" }),
|
||||
onShow: function () {
|
||||
$('.dropdown-menu [name=carousel-background]').click(function () {
|
||||
self.movetoStep('save-changes');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'save-changes',
|
||||
element: 'button[data-action=save]',
|
||||
placement: 'right',
|
||||
reflex: true,
|
||||
title: "Save your modifications",
|
||||
content: "Once you click on save, your website page is updated.",
|
||||
template: render('website.tour_popover'),
|
||||
onHide: function () {
|
||||
self.saveStep('part-2');
|
||||
},
|
||||
|
||||
},
|
||||
{
|
||||
stepId: 'part-2',
|
||||
orphan: true,
|
||||
title: "Congratulation!",
|
||||
content: "Your homepage have been updated. Now, we suggest you to insert others building blocks like texts and images to structure your page.",
|
||||
template: render('website.tour_popover', { next: "Continue" }),
|
||||
},
|
||||
{
|
||||
stepId: 'show-tutorials',
|
||||
element: '#help-menu-button',
|
||||
placement: 'left',
|
||||
title: "Help is always available",
|
||||
content: "But you can always click here if you want more tutorials.",
|
||||
template: render('website.tour_popover', { end: "Close" }),
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
startOfPart2: function () {
|
||||
var currentStepIndex = this.currentStepIndex();
|
||||
var secondPartIndex = this.indexOfStep('part-2');
|
||||
var showTutorialsIndex = this.indexOfStep('show-tutorials');
|
||||
return (currentStepIndex === secondPartIndex || currentStepIndex === showTutorialsIndex) && !this.tour.ended();
|
||||
},
|
||||
canResume: function () {
|
||||
return this.startOfPart2() || this._super();
|
||||
},
|
||||
});
|
||||
|
||||
website.UrlParser = openerp.Class.extend({
|
||||
init: function (url) {
|
||||
var a = document.createElement('a');
|
||||
@@ -227,30 +98,4 @@
|
||||
},
|
||||
});
|
||||
|
||||
website.EditorBar.include({
|
||||
start: function () {
|
||||
website.tutorials = {
|
||||
basic: new website.EditorBasicTour(this),
|
||||
};
|
||||
var menu = $('#help-menu');
|
||||
_.each(website.tutorials, function (tutorial) {
|
||||
var $menuItem = $($.parseHTML('<li><a href="#">'+tutorial.name+'</a></li>'));
|
||||
$menuItem.click(function () {
|
||||
tutorial.reset();
|
||||
tutorial.start();
|
||||
});
|
||||
menu.append($menuItem);
|
||||
});
|
||||
var url = new website.UrlParser(window.location.href);
|
||||
if (url.search.indexOf('?tutorial=true') === 0 || website.tutorials.basic.startOfPart2()) {
|
||||
website.tutorials.basic.start();
|
||||
}
|
||||
$('.tour-backdrop').click(function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
|
||||
}());
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
var nodialog = 'website_translator_nodialog';
|
||||
|
||||
website.EditorBar.include({
|
||||
events: _.extend({}, website.EditorBar.prototype.events, {
|
||||
'click a[data-action=edit_master]': 'edit_master',
|
||||
}),
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.initial_content = {};
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
self.$('button[data-action=edit]').text("Translate");
|
||||
self.$('button[data-action=edit]')
|
||||
.text("Translate")
|
||||
.after(openerp.qweb.render('website.TranslatorAdditionalButtons'));
|
||||
self.$('[data-action=snippet]').hide();
|
||||
self.$('#customize-menu-button').hide();
|
||||
});
|
||||
@@ -24,7 +29,7 @@
|
||||
dialog.on('activate', this, function () {
|
||||
localStorage[nodialog] = dialog.$('input[name=do_not_show]').prop('checked') || '';
|
||||
dialog.$el.modal('hide');
|
||||
this.translate().then(function () {
|
||||
self.translate().then(function () {
|
||||
mysuper.call(self);
|
||||
});
|
||||
});
|
||||
@@ -34,6 +39,14 @@
|
||||
});
|
||||
}
|
||||
},
|
||||
edit_master: function (ev) {
|
||||
ev.preventDefault();
|
||||
var link = $('.js_language_selector a[data-default-lang]')[0];
|
||||
if (link) {
|
||||
link.search += (link.search ? '&' : '?') + 'enable_editor=1';
|
||||
window.location = link.attributes.href.value;
|
||||
}
|
||||
},
|
||||
translate: function () {
|
||||
var self = this;
|
||||
this.translations = null;
|
||||
@@ -44,7 +57,7 @@
|
||||
self.translations = translations;
|
||||
self.processTranslatableNodes();
|
||||
// Disable non translatable t-fields
|
||||
$('[data-oe-type][data-oe-translate=0]').removeAttr('data-oe-type');
|
||||
$('[data-oe-type][data-oe-translate="0"]').removeAttr('data-oe-type');
|
||||
});
|
||||
},
|
||||
processTranslatableNodes: function () {
|
||||
@@ -143,7 +156,6 @@
|
||||
if (node.attributes['data-oe-translate'].value == '1') {
|
||||
node.className += ' oe_translatable_field';
|
||||
}
|
||||
return;
|
||||
} else if (node.childNodes.length === 1
|
||||
&& this.isTextNode(node.childNodes[0])
|
||||
&& !node.getAttribute('data-oe-model')) {
|
||||
@@ -175,7 +187,7 @@
|
||||
$(root).click(function (ev) {
|
||||
ev.preventDefault();
|
||||
});
|
||||
return $('[data-oe-translate=1]');
|
||||
return $('[data-oe-translate="1"]');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?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>
|
||||
</t>
|
||||
<t t-name="website.TranslatorDialog">
|
||||
<div class="modal fade oe_website_translator" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestConvertBack(common.TransactionCase):
|
||||
e.setAttribute('t-field', field_value)
|
||||
|
||||
rendered = self.registry('website.qweb').render_tag_field(
|
||||
e, {'field': field_value}, '', ir_qweb.QWebContext({
|
||||
e, {'field': field_value}, '', ir_qweb.QWebContext(self.cr, self.uid, {
|
||||
'record': record,
|
||||
}))
|
||||
element = html.fromstring(
|
||||
@@ -53,6 +53,7 @@ class TestConvertBack(common.TransactionCase):
|
||||
|
||||
def test_float(self):
|
||||
self.field_roundtrip('float', 42.567890)
|
||||
self.field_roundtrip('float', 324542.567890)
|
||||
|
||||
def test_numeric(self):
|
||||
self.field_roundtrip('numeric', 42.77)
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<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>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.tour.basic.js"></script>
|
||||
<script t-if="not translatable" type="text/javascript" src="/website/static/src/js/website.snippets.js"></script>
|
||||
<script t-if="not translatable" type="text/javascript" src="/website/static/src/js/website.ace.js"></script>
|
||||
<script t-if="translatable" type="text/javascript" src="/website/static/src/js/website.translator.js"></script>
|
||||
@@ -102,15 +103,16 @@
|
||||
<li t-if="request.multilang and
|
||||
(len(website.language_ids) > 1 or editable)" class="dropdown">
|
||||
<!-- TODO: use flags for language selection -->
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<t t-esc="lang_selected[0]['name']"/> <span class="caret"></span>
|
||||
<a class="dropdown-toggle js_language_selected" data-toggle="dropdown" href="#">
|
||||
<t t-esc="lang_selected[0]['name'].split('/').pop()"/> <span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<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">
|
||||
<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"/>
|
||||
<t t-esc="lg.name.split('/').pop()"/>
|
||||
</a>
|
||||
</li>
|
||||
<li t-if="editable" class="divider"/>
|
||||
@@ -119,7 +121,7 @@
|
||||
<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&url_return=#{url_return}">
|
||||
<a t-attf-href="/web#action=base.action_view_base_language_install&website_id=#{website.id}&url_return=#{url_return}">
|
||||
Add a language...
|
||||
</a>
|
||||
</li>
|
||||
@@ -224,23 +226,24 @@
|
||||
|
||||
<template id="publish_management">
|
||||
<t t-if="editable" t-ignore="true">
|
||||
<div class="pull-right btn-group">
|
||||
<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">
|
||||
<a t-attf-class="btn btn-xs 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"/>
|
||||
<li>
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
<t t-if="publish_edit">
|
||||
<a class="btn btn-xs btn-default" title="Edit in backend" t-att-href="'/web#model=%s&id=%s' % (object._name, object.id)"><span t-attf-class="glyphicon glyphicon-wrench"></span></a>
|
||||
</t>
|
||||
<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">
|
||||
<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"/>
|
||||
<li>
|
||||
<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&id=%s' % (object._name, object.id)"
|
||||
title='Edit in backend'>Edit</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
@@ -258,16 +261,16 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="pager">
|
||||
<template id="pager" name="Pager">
|
||||
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination">
|
||||
<li t-att-class=" 'disabled' if pager['page']['num'] == 1 else '' ">
|
||||
<a t-att-href=" pager['page_start']['url'] if pager['page']['num'] != 1 else '' ">Prev</a>
|
||||
<a t-att-href=" pager['page_previous']['url'] if pager['page']['num'] != 1 else '' ">Prev</a>
|
||||
</li>
|
||||
<t t-foreach="pager['pages']" t-as="page">
|
||||
<li t-att-class=" 'active' if page['num'] == pager['page']['num'] else '' "> <a t-att-href="page['url']" t-raw="page['num']"></a></li>
|
||||
</t>
|
||||
<li t-att-class=" 'disabled' if pager['page']['num'] == pager['page_count'] else '' ">
|
||||
<a t-att-href=" pager['page_end']['url'] if pager['page']['num'] != pager['page_count'] else '' ">Next</a>
|
||||
<a t-att-href=" pager['page_next']['url'] if pager['page']['num'] != pager['page_count'] else '' ">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
@@ -57,7 +57,7 @@ class WebsiteBlog(http.Controller):
|
||||
post pager.
|
||||
:param dict post: kwargs, may contain
|
||||
|
||||
- 'unable_editor': editor control
|
||||
- 'enable_editor': editor control
|
||||
|
||||
:return dict values: values for the templates, containing
|
||||
|
||||
@@ -147,7 +147,7 @@ class WebsiteBlog(http.Controller):
|
||||
'blog_posts': blog_posts,
|
||||
'pager': pager,
|
||||
'nav_list': nav,
|
||||
'unable_editor': post.get('unable_editor')
|
||||
'enable_editor': post.get('enable_editor')
|
||||
}
|
||||
|
||||
if blog_post:
|
||||
@@ -196,7 +196,7 @@ class WebsiteBlog(http.Controller):
|
||||
context=dict(context, mail_create_nosubcribe=True))
|
||||
request.session.body = False
|
||||
|
||||
return werkzeug.utils.redirect("/blog/%s/?unable_editor=1" % (blog_post_id))
|
||||
return werkzeug.utils.redirect("/blog/%s/?enable_editor=1" % (blog_post_id))
|
||||
|
||||
@website.route(['/blog/<int:category_id>/new'], type='http', auth="public")
|
||||
def blog_post_create(self, category_id=None, **post):
|
||||
@@ -209,11 +209,11 @@ class WebsiteBlog(http.Controller):
|
||||
'content': '',
|
||||
'website_published': False,
|
||||
}, context=create_context)
|
||||
return werkzeug.utils.redirect("/blog/%s/?unable_editor=1" % (new_blog_post_id))
|
||||
return werkzeug.utils.redirect("/blog/%s/?enable_editor=1" % (new_blog_post_id))
|
||||
|
||||
@website.route(['/blog/<int:blog_post_id>/duplicate'], type='http', auth="public")
|
||||
def blog_post_copy(self, blog_post_id=None, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
create_context = dict(context, mail_create_nosubscribe=True)
|
||||
new_blog_post_id = request.registry['blog.post'].copy(cr, uid, blog_post_id, {}, context=create_context)
|
||||
return werkzeug.utils.redirect("/blog/%s/?unable_editor=1" % (new_blog_post_id))
|
||||
return werkzeug.utils.redirect("/blog/%s/?enable_editor=1" % (new_blog_post_id))
|
||||
|
||||
@@ -79,7 +79,7 @@ class BlogPost(osv.Model):
|
||||
'oe_expand_container_tag': 'div',
|
||||
'oe_expand_container_class': 'oe_mail_expand text-center',
|
||||
'oe_expand_container_content': '',
|
||||
'oe_expand_a_href': '/blog/%d/%d' % (page.category_id.id, page.id),
|
||||
'oe_expand_a_href': '/blog/%d/' % page.id,
|
||||
'oe_expand_a_class': 'oe_mail_expand btn btn-info',
|
||||
'oe_expand_separator_node': 'br',
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
blog_category_all,blog.category,model_blog_category,,1,0,0,0
|
||||
blog_post_all,blog.post,model_blog_post,,1,0,0,0
|
||||
blog_post,blog.post,model_blog_post,base.group_user,1,1,1,1
|
||||
blog_post_history,blog.post.history,model_blog_post_history,base.group_user,1,0,1,0
|
||||
blog_post,blog.post,model_blog_post,base.group_document_user,1,1,1,1
|
||||
blog_post_history,blog.post.history,model_blog_post_history,base.group_document_user,1,0,1,0
|
||||
blog_tag,blog.tag,model_blog_tag,,1,0,0,0
|
||||
blog_tag_edition,blog.tag,model_blog_tag,base.group_document_user,1,1,1,1
|
||||
|
||||
|
@@ -1,17 +1,16 @@
|
||||
$(document).ready(function () {
|
||||
$form = $('.js_nav_year a:first').on('click', function (e) {
|
||||
$('.js_nav_year a:first').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$(this).next("ul").toggle();
|
||||
});
|
||||
|
||||
$form = $('.js_nav_month a:first').on('click', function (e) {
|
||||
$('.js_nav_month a:first').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var $ul = $(this).next("ul");
|
||||
if (!$ul.find('li').length) {
|
||||
$.post('/blog/nav', {'domain': $(this).data("domain")}, function (result) {
|
||||
var result = JSON.parse(result);
|
||||
var blog_id = +window.location.pathname.split("/").pop();
|
||||
$(result).each(function () {
|
||||
$(JSON.parse(result)).each(function () {
|
||||
var $a = $('<a href="/blog/' + this.category_id + '/' + this.id + '"/>').text(this.name);
|
||||
var $li = $("<li/>").append($a);
|
||||
if (blog_id == this.id)
|
||||
@@ -27,7 +26,7 @@ $(document).ready(function () {
|
||||
}
|
||||
});
|
||||
|
||||
$form = $('.js_website_blog form#comment');
|
||||
var $form = $('.js_website_blog form#comment');
|
||||
$form.submit(function (e) {
|
||||
e.preventDefault();
|
||||
var error = $form.find("textarea").val().length < 3;
|
||||
@@ -39,4 +38,4 @@ $(document).ready(function () {
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
through your <b>blog entries</b>, referenced in Google.
|
||||
</p>
|
||||
<div>
|
||||
<button src="/contactus" class="btn btn-primary">Contact us</button>
|
||||
<a t-href="/page/website.contactus"><button class="btn btn-primary">Contact us</button></a>
|
||||
</div>
|
||||
</section>
|
||||
</xpath>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="website.layout" inherit_id="website.layout">
|
||||
<template id="website.layout" name="Mail customization" inherit_id="website.layout">
|
||||
<!-- Load stylesheets before scripts to avoid blocking -->
|
||||
<xpath expr="//head/link" position="after">
|
||||
<link rel='stylesheet' href='/website_mail/static/src/css/website_mail.css'/>
|
||||
|
||||
@@ -28,7 +28,7 @@ def get_order(order_id=None):
|
||||
order_value['pricelist_id'] = request.httprequest.session['ecommerce_pricelist']
|
||||
order_value['partner_id'] = request.registry.get('res.users').browse(request.cr, SUPERUSER_ID, request.uid, context=request.context).partner_id.id
|
||||
order_value.update(order_obj.onchange_partner_id(request.cr, SUPERUSER_ID, [], order_value['partner_id'], context=request.context)['value'])
|
||||
|
||||
|
||||
# add website_session_id key for access rules
|
||||
if not request.httprequest.session.get('website_session_id'):
|
||||
request.httprequest.session['website_session_id'] = str(uuid.uuid4())
|
||||
@@ -113,7 +113,7 @@ class Ecommerce(http.Controller):
|
||||
request.context['pricelist'] = self.get_pricelist()
|
||||
|
||||
# search for checking of access rules and keep order
|
||||
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)]
|
||||
product_ids = product_obj.search(request.cr, request.uid, [("id", 'in', product_ids)], context=request.context)
|
||||
|
||||
size_ids = {}
|
||||
style_ids = style_obj.search(request.cr, SUPERUSER_ID, [('html_class', 'like', 'size_%')], context=request.context)
|
||||
@@ -182,7 +182,7 @@ class Ecommerce(http.Controller):
|
||||
bin_packing[line + copy_y][col + copy_x] = False
|
||||
bin_packing[line + copy_y][col + copy_x] = product_list[index]
|
||||
break
|
||||
|
||||
|
||||
if not insert:
|
||||
line += 1
|
||||
else:
|
||||
@@ -212,6 +212,7 @@ class Ecommerce(http.Controller):
|
||||
col += 1
|
||||
line += 1
|
||||
|
||||
print bin_packing_list
|
||||
return bin_packing_list
|
||||
|
||||
def get_products(self, product_ids):
|
||||
@@ -272,6 +273,9 @@ class Ecommerce(http.Controller):
|
||||
|
||||
@website.route(['/shop/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
|
||||
def category(self, category=0, attributes="", 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 ?
|
||||
|
||||
if 'promo' in post:
|
||||
self.change_pricelist(post.get('promo'))
|
||||
@@ -281,7 +285,7 @@ class Ecommerce(http.Controller):
|
||||
|
||||
if post.get("search"):
|
||||
domain += ['|', '|', '|',
|
||||
('name', 'ilike', "%%%s%%" % post.get("search")),
|
||||
('name', 'ilike', "%%%s%%" % post.get("search")),
|
||||
('description', 'ilike', "%%%s%%" % post.get("search")),
|
||||
('website_description', 'ilike', "%%%s%%" % post.get("search")),
|
||||
('product_variant_ids.public_categ_id.name', 'ilike', "%%%s%%" % post.get("search"))]
|
||||
@@ -298,7 +302,7 @@ class Ecommerce(http.Controller):
|
||||
|
||||
step = 20
|
||||
product_count = len(product_obj.search(request.cr, request.uid, domain, context=request.context))
|
||||
pager = request.website.pager(url="/shop/%s" % post, total=product_count, page=page, step=step, scope=7, url_args=post)
|
||||
pager = request.website.pager(url="/shop/", total=product_count, page=page, step=step, scope=7, url_args=post)
|
||||
|
||||
request.context['pricelist'] = self.get_pricelist()
|
||||
|
||||
@@ -354,9 +358,9 @@ class Ecommerce(http.Controller):
|
||||
|
||||
@website.route(['/shop/add_product/', '/shop/category/<int:cat_id>/add_product/'], type='http', auth="public", multilang=True)
|
||||
def add_product(self, cat_id=0, **post):
|
||||
product_id = request.registry.get('product.product').create(request.cr, request.uid,
|
||||
product_id = request.registry.get('product.product').create(request.cr, request.uid,
|
||||
{'name': 'New Product', 'public_categ_id': cat_id}, request.context)
|
||||
return request.redirect("/shop/product/%s/?unable_editor=1" % product_id)
|
||||
return request.redirect("/shop/product/%s/?enable_editor=1" % product_id)
|
||||
|
||||
def get_pricelist(self):
|
||||
if not request.httprequest.session.get('ecommerce_pricelist'):
|
||||
@@ -376,7 +380,7 @@ class Ecommerce(http.Controller):
|
||||
if not pricelist_id:
|
||||
partner_id = request.registry.get('res.users').browse(request.cr, SUPERUSER_ID, request.uid, request.context).partner_id.id
|
||||
pricelist_id = request.registry['sale.order'].onchange_partner_id(request.cr, SUPERUSER_ID, [], partner_id, context=request.context)['value']['pricelist_id']
|
||||
|
||||
|
||||
request.httprequest.session['ecommerce_pricelist'] = pricelist_id
|
||||
|
||||
order = get_current_order()
|
||||
|
||||
@@ -35,10 +35,17 @@ class product_template(osv.Model):
|
||||
_name = 'product.template'
|
||||
|
||||
def _website_url(self, cr, uid, ids, field_name, arg, context=None):
|
||||
res = {}
|
||||
""" TDE-NOTE: as someone wrote this method without any clue on about what it
|
||||
does, fixing bugs about product_variant_ids not existing will be done
|
||||
based on the weather, the sun and Lilo's feelings about pigs.
|
||||
|
||||
If you see this comment in trunk, this means that the website branch has
|
||||
been merged without any review, which is quite questionable. """
|
||||
res = dict.fromkeys(ids, '')
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
for prod in self.browse(cr, uid, ids, context=context):
|
||||
res[prod.id] = "%s/shop/product/%s/" % (base_url, prod.product_variant_ids[0].id)
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
if product.product_variant_ids:
|
||||
res[product.id] = "%s/shop/product/%s/" % (base_url, product.product_variant_ids[0].id)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
@@ -48,7 +55,7 @@ class product_template(osv.Model):
|
||||
'suggested_product_ids': fields.one2many('product.template', 'suggested_product_id', 'Suggested Products'),
|
||||
'website_size_x': fields.integer('Size X'),
|
||||
'website_size_y': fields.integer('Size Y'),
|
||||
'website_style_ids' : fields.many2many('website.product.style','product_website_style_rel', 'product_id', 'style_id', 'Styles'),
|
||||
'website_style_ids': fields.many2many('website.product.style', 'product_website_style_rel', 'product_id', 'style_id', 'Styles'),
|
||||
'website_sequence': fields.integer('Sequence', help="Determine the display order in the Website E-commerce"),
|
||||
'website_url': fields.function(_website_url, string="Website url"),
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ $(document).ready(function () {
|
||||
$(".oe_website_sale .js_shipping").toggle();
|
||||
});
|
||||
|
||||
$payment = $(".oe_website_sale .js_payment");
|
||||
$("input[name='payment_type']", $payment).click(function (ev) {
|
||||
var $payment = $(".oe_website_sale .js_payment");
|
||||
$payment.find("input[name='payment_type']").click(function (ev) {
|
||||
var payment_id = $(ev.currentTarget).val();
|
||||
$("div[data-id]", $payment).addClass("hidden");
|
||||
$("a.btn:last, div[data-id='"+payment_id+"']", $payment).removeClass("hidden");
|
||||
@@ -114,7 +114,7 @@ $(document).ready(function () {
|
||||
var y = $td.parent().index()+1;
|
||||
openerp.jsonRpc('/shop/change_size/', 'call', {'id': $data.data('id'), 'x': x, 'y': y})
|
||||
.then(function () {
|
||||
var search = location.search.replace(/\?|$/, '?unable_editor=1&');
|
||||
var search = location.search.replace(/\?|$/, '?enable_editor=1&');
|
||||
location.href = location.href.replace(/(\?|#).*/, search + location.hash);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<!-- List of categories -->
|
||||
|
||||
<template id="categories_recursive">
|
||||
<template id="categories_recursive" name="Category list">
|
||||
<li t-att-class="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">
|
||||
@@ -32,9 +32,9 @@
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<!-- Product list -->
|
||||
<!-- Product list -->
|
||||
|
||||
<template id="products_cart">
|
||||
<template id="products_cart" name="Shopping cart">
|
||||
<div class="ribbon-wrapper">
|
||||
<div class="ribbon">Promo</div>
|
||||
</div>
|
||||
@@ -43,7 +43,7 @@
|
||||
<b t-field="product.name"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="product_price">
|
||||
<div class="product_price" t-if="product.product_variant_ids">
|
||||
<b>
|
||||
<t t-if="product.product_variant_ids[0].lst_price != product.product_variant_ids[0].price">
|
||||
<del class="text-danger"
|
||||
@@ -60,28 +60,27 @@
|
||||
</div>
|
||||
<div class="oe_product_image text-center">
|
||||
<a t-href="/shop/product/#{ product.id }/" t-keep-query="category,search,facettes">
|
||||
<img class="img" t-att-src="product.img('image')"/>
|
||||
<span t-field="product.image" t-field-options='{"widget": "image"}'/>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="products" page="True">
|
||||
<template id="products" name="Products" page="True">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="head">
|
||||
<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 ''"/>
|
||||
</t>
|
||||
<t t-set="additional_title">Our Products</t>
|
||||
<t t-set="additional_title">Shop</t>
|
||||
<div id="wrap">
|
||||
<div class="oe_structure"/>
|
||||
<div class="container oe_website_sale">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<h1>Our Products</h1>
|
||||
</div><div class="col-sm-2 pagination text-center">
|
||||
<div class="col-sm-6 pagination" style="padding-left: 15px;">
|
||||
<a t-if="editable" t-href="/shop/add_product/" class="btn btn-primary btn-default" t-keep-query="category,search,facettes">New Product</a>
|
||||
</div><div class="col-sm-6">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<t t-call="website.pager">
|
||||
<t t-set="classname">pull-right</t>
|
||||
</t>
|
||||
@@ -108,7 +107,7 @@
|
||||
t-attf-class="oe_product oe-height-#{td_product['y']*2} #{ td_product['class'] }">
|
||||
|
||||
<div class="oe_product_cart" t-att-data-publish="product.website_published and 'on' or 'off'">
|
||||
|
||||
|
||||
<div class="css_options" t-ignore="true" t-if="editable">
|
||||
<div t-attf-class="dropdown js_options" t-att-data-id="product.id">
|
||||
<a class="btn btn-default" t-att-id="'dopprod-%s' % product.id" role="button" data-toggle="dropdown">Options <span class="caret"></span></a>
|
||||
@@ -170,8 +169,10 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<t t-call="website.pager" />
|
||||
<div>
|
||||
<t t-call="website.pager">
|
||||
<t t-set="classname">pull-right</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure mb32"/>
|
||||
@@ -215,7 +216,7 @@
|
||||
</template>
|
||||
|
||||
|
||||
<!-- product -->
|
||||
<!-- product -->
|
||||
|
||||
<template id="product" name="Product">
|
||||
<t t-call="website.layout">
|
||||
@@ -284,10 +285,10 @@
|
||||
<br/>
|
||||
</t>
|
||||
|
||||
<div class="product_price mt16">
|
||||
<div class="product_price mt16" t-if="product.product_variant_ids">
|
||||
<h4>
|
||||
<b><span class="oe_price" t-esc="product.product_variant_ids[0].price" /> €</b>
|
||||
|
||||
|
||||
<t t-if="product.product_variant_ids[0].lst_price != product.product_variant_ids[0].price">
|
||||
<span class="text-danger" style="text-decoration: line-through;">
|
||||
<t t-esc="product.product_variant_ids[0].lst_price" /> €
|
||||
@@ -338,7 +339,7 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Page Shop my cart -->
|
||||
<!-- Page Shop my cart -->
|
||||
|
||||
<template id="mycart" name="Your Cart" page="True">
|
||||
<t t-call="website.layout">
|
||||
@@ -356,120 +357,116 @@
|
||||
<li class="text-muted">Payment<span class="chevron"></span></li>
|
||||
<li class="text-muted">Confirmation<span class="chevron"></span></li>
|
||||
</ul>
|
||||
<h1 class="mb32">Your Cart</h1>
|
||||
|
||||
<h1 class="mb32">Shopping Cart</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-8 oe_mycart">
|
||||
<div t-if="not website_sale_order or not website_sale_order.order_line" class="well well-lg">
|
||||
Your cart is empty!
|
||||
<div class="col-md-3 text-muted" id="right_column">
|
||||
<h4>Policies</h4>
|
||||
<ul class="list-unstyled mb32">
|
||||
<li>☑ 30-days money-back guarantee</li>
|
||||
<li>☑ Invoice sent by e-Mail</li>
|
||||
</ul>
|
||||
<h4>Secure Payment</h4>
|
||||
<ul class="list-unstyled mb32">
|
||||
<li>☑ 256 bit encryption</li>
|
||||
<li>☑ Processed by Ogone</li>
|
||||
</ul>
|
||||
</div>
|
||||
<table class='table table-striped table-condensed' id="mycart_products" t-if="website_sale_order and website_sale_order.order_line">
|
||||
<colgroup>
|
||||
<col width="80"/>
|
||||
<col/>
|
||||
<col width="100"/>
|
||||
<col width="120"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Product</th>
|
||||
<th>Price</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="website_sale_order.order_line" t-as="line">
|
||||
<td colspan="2" t-if="not line.product_id.product_tmpl_id"></td>
|
||||
<td t-if="line.product_id.product_tmpl_id">
|
||||
<span t-field="line.product_id.image_small"
|
||||
t-field-options='{"widget": "image", "class": "img-rounded"}'/>
|
||||
</td>
|
||||
<td t-if="line.product_id.product_tmpl_id">
|
||||
<div>
|
||||
<a t-href="/shop/product/#{ line.product_id.product_tmpl_id.id }/">
|
||||
<strong t-field="line.product_id.name"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-muted" t-field="line.product_id.description_sale"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<del class="text-danger" t-if="line.product_id.lst_price > line.price_unit">
|
||||
<span t-field="line.product_id.lst_price" t-field-options='{
|
||||
<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!
|
||||
</div>
|
||||
<table class='table table-striped table-condensed' id="mycart_products" t-if="website_sale_order and website_sale_order.order_line">
|
||||
<colgroup>
|
||||
<col width="80"/>
|
||||
<col/>
|
||||
<col width="100"/>
|
||||
<col width="120"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Product</th>
|
||||
<th>Price</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="website_sale_order.order_line" t-as="line">
|
||||
<td colspan="2" t-if="not line.product_id.product_tmpl_id"></td>
|
||||
<td t-if="line.product_id.product_tmpl_id">
|
||||
<span t-field="line.product_id.image_small"
|
||||
t-field-options='{"widget": "image", "class": "img-rounded"}'/>
|
||||
</td>
|
||||
<td t-if="line.product_id.product_tmpl_id">
|
||||
<div>
|
||||
<a t-href="/shop/product/#{ line.product_id.product_tmpl_id.id }/">
|
||||
<strong t-field="line.product_id.name"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-muted" t-field="line.product_id.description_sale"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<del class="text-danger" t-if="line.product_id.lst_price > line.price_unit">
|
||||
<span t-field="line.product_id.lst_price" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
</del>
|
||||
<span t-field="line.price_unit" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
</del>
|
||||
<span t-field="line.price_unit" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
</td>
|
||||
<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">
|
||||
<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">
|
||||
<span class="icon-plus"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class='pull-right mb16' id="mycart_total">
|
||||
<colgroup>
|
||||
<col width="100"/>
|
||||
<col width="120"/>
|
||||
</colgroup>
|
||||
<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>
|
||||
</tr>
|
||||
<tr class="text-muted">
|
||||
<td><abbr title="Taxes may be updated after providing shipping address">Incl. Taxes:</abbr></td>
|
||||
<td class="text-right">
|
||||
<span t-field="website_sale_order.amount_tax" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div class="clearfix"/>
|
||||
</td>
|
||||
<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">
|
||||
<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">
|
||||
<span class="icon-plus"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a t-href="/shop" class="btn btn-default"><span class="icon-long-arrow-left"/> Continue Shopping</a>
|
||||
<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 col-md-offset-1 text-muted" id="right_column">
|
||||
<h4>Policies</h4>
|
||||
<ul class="list-unstyled mb32">
|
||||
<li>☑ 30-days money-back guarantee</li>
|
||||
<li>☑ Invoice sent by e-Mail</li>
|
||||
</ul>
|
||||
|
||||
<h4>Secure Payment</h4>
|
||||
<ul class="list-unstyled mb32">
|
||||
<li>☑ Transation 256bit encrypted</li>
|
||||
<li>☑ Processed by Ogone</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class='pull-right mb16' id="mycart_total">
|
||||
<colgroup>
|
||||
<col width="100"/>
|
||||
<col width="120"/>
|
||||
</colgroup>
|
||||
<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>
|
||||
</tr>
|
||||
<tr class="text-muted">
|
||||
<td><abbr title="Taxes may be updated after providing shipping address">Incl. Taxes:</abbr></td>
|
||||
<td class="text-right">
|
||||
<span t-field="website_sale_order.amount_tax" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div class="clearfix"/>
|
||||
<a t-href="/shop" class="btn btn-default mb32"><span class="icon-long-arrow-left"/> Continue Shopping</a>
|
||||
<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>
|
||||
|
||||
</div>
|
||||
@@ -478,7 +475,7 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Page Shop -->
|
||||
<!-- Page Shop -->
|
||||
|
||||
<template id="products_categories" inherit_option_id="website_sale.products" name="Product Categories">
|
||||
<xpath expr="//div[@id='products_grid']" position="before">
|
||||
@@ -621,8 +618,8 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 oe_mycart">
|
||||
<h3 class="page-header mt16">Set Billing Information
|
||||
<small t-if="user_id.id == website.public_user.id"> or
|
||||
<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&url=#{ request.httprequest.host_url }/shop/checkout/">sign in</a>
|
||||
</small>
|
||||
</h3>
|
||||
@@ -752,10 +749,6 @@
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/></h4></div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default btn-primary pull-right mt16" t-if="user_id.id != website.public_user.id">
|
||||
Confirm <span class="icon-long-arrow-right"/>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user