"""
import cStringIO
+import datetime
import itertools
import logging
import re
import urllib2
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
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):
- raise NotImplementedError("Can not parse and save localized dates")
+ 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):
- raise NotImplementedError("Can not parse and save localized datetimes")
+ 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'
return arch
- def _normalize_urls(self, element):
- attr = None
- if element.tag == 'form':
- attr = 'action' if 'action' in element.attrib else None
- elif element.tag in ['a', 'link']:
- attr = 'href' if 'href' in element.attrib else None
- elif element.tag in ['frame', 'iframe', 'script']:
- attr = 'src' if 'src' in element.attrib else None
- if attr:
- value = element.attrib[attr]
+ 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.attrib['t-' + attr] = value
- for el in list(element):
- self._normalize_urls(el)
- return element
+ 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
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)
-@charset "utf-8";
/* ---- CKEditor Minimal Reset ---- */
.navbar.navbar-inverse .cke_chrome {
border: none;
white-space: pre-wrap;
}
+.oe_carlos_danger {
+ outline: 1px solid red !important;
+ background-color: #ffd9dd !important;
+}
+
/* ---- SNIPPET EDITOR ---- */
#oe_snippets {
position: fixed;
content: " "
white-space: pre-wrap
+.oe_carlos_danger
+ outline: 1px solid red !important
+ background-color: #ffd9dd !important
+
/* ---- SNIPPET EDITOR ---- */
#oe_snippets
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',
return true;
},
- start_edition: function () {
+ /**
+ * 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);
document.execCommand("enableInlineTableEditing", false, "false");
self.trigger('rte:ready');
+ def.resolve();
});
+ return def;
},
setup_editables: function (root) {
<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>
</thead>
</table>
<div class="clearfix"/>
- <a t-href="/shop" class="btn btn-default"><span class="icon-long-arrow-left"/> Continue Shopping</a>
+ <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>