[MERGE]Merge trunk-website-al.
authorbth-openerp <bth@tinyerp.com>
Thu, 24 Oct 2013 05:15:43 +0000 (10:45 +0530)
committerbth-openerp <bth@tinyerp.com>
Thu, 24 Oct 2013 05:15:43 +0000 (10:45 +0530)
bzr revid: bth@tinyerp.com-20131024051543-zow19or2yqh5kczn

addons/website/models/ir_qweb.py
addons/website/models/ir_ui_view.py
addons/website/models/website.py
addons/website/static/src/css/editor.css
addons/website/static/src/css/editor.sass
addons/website/static/src/js/website.editor.js
addons/website/views/website_templates.xml
addons/website_sale/views/website_sale.xml

index 948422e..d6bd6a5 100644 (file)
@@ -7,17 +7,19 @@ Also, adds methods to convert values back to openerp models.
 """
 
 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
 
@@ -75,19 +77,48 @@ class Float(orm.AbstractModel):
         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'
index c85245b..ab09336 100644 (file)
@@ -100,22 +100,24 @@ class view(osv.osv):
 
         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
index b6379bc..6d8d5f5 100644 (file)
@@ -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)
index 26032aa..8fa0ad8 100644 (file)
@@ -1,4 +1,3 @@
-@charset "utf-8";
 /* ---- CKEditor Minimal Reset ---- */
 .navbar.navbar-inverse .cke_chrome {
   border: none;
@@ -174,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;
index e4ef24d..5dbe7a8 100644 (file)
@@ -154,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
index 2249de2..f9d2aef 100644 (file)
             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) {
index 8862e17..e1b7944 100644 (file)
         <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>
index 4f2cc38..c8585af 100644 (file)
                             </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>