From 122ddb8e310df0a4cfd597cb49cbe8863f862d8b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 13 Nov 2013 11:18:24 +0100 Subject: [PATCH 01/35] [IMP] rename list_pages to enumerate_pages, add a search method and a basic non-contiguous substring match bzr revid: xmo@openerp.com-20131113101824-uqatvqokkzhvi0ei --- addons/website/controllers/main.py | 4 +- addons/website/models/website.py | 37 ++++++++++++++++++- .../website/static/src/js/website.editor.js | 3 +- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index aa214b8c998..f22f9c2b052 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -273,12 +273,12 @@ class Website(openerp.addons.web.controllers.main.Home): @website.route('/sitemap', type='http', auth='public', multilang=True) def sitemap(self): - return request.website.render('website.sitemap', {'pages': request.website.list_pages()}) + return request.website.render('website.sitemap', {'pages': request.website.enumerate_pages()}) @website.route('/sitemap.xml', type='http', auth="public") def sitemap_xml(self): body = request.website.render('website.sitemap_xml', { - 'pages': request.website.list_pages() + 'pages': request.website.enumerate_pages() }) return request.make_response(body, [ diff --git a/addons/website/models/website.py b/addons/website/models/website.py index e34139273f1..6efa419c892 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -323,7 +323,7 @@ class website(osv.osv): (arg == 'self' or arg in rule._converters) for arg in args) - def list_pages(self, cr, uid, ids, context=None): + def enumerate_pages(self, cr, uid, ids, context=None): """ Available pages in the website/CMS. This is mostly used for links generation and can be overridden by modules setting up new HTML controllers for dynamic pages (e.g. blog). @@ -363,6 +363,41 @@ class website(osv.osv): domain_part, url = rule.build(values, append_unknown=False) yield {'name': url, 'url': url } + def search_pages(self, cr, uid, ids, needle=None, limit=None, context=None): + pages = self.enumerate_pages(cr, uid, ids, context=context) + + if needle: + pages = itertools.ifilter(lambda v: self.page_matches( + cr, uid, v, needle, context=None), pages) + + return list(itertools.islice(pages, limit)) + + def page_matches(self, cr, uid, page, needle, context=None): + """ Checks that a "page" matches a user-provide search string. + + The default implementation attempts to perform a non-contiguous + substring match of the page's name. + + :param page: {'name': str, 'url': str} + :param needle: str + :rtype: bool + """ + haystack = page['name'].lower() + + needle = iter(needle.lower()) + n = next(needle) + end = object() + + for char in haystack: + if char != n: continue + + n = next(needle, end) + # found all characters of needle in haystack in order + if n is end: + return True + + return False + def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None, context=None): step = step and int(step) or 10 scope = scope and int(scope) or 5 diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 5915ee5b359..af5763b9500 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -930,9 +930,10 @@ fetch_pages: function () { return openerp.jsonRpc('/web/dataset/call_kw', 'call', { model: 'website', - method: 'list_pages', + method: 'search_pages', args: [null], kwargs: { + limit: 10, context: website.get_context() }, }); From d15a6f426ddbd75a17cfd491e127966c7a1c03c6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 13 Nov 2013 15:39:34 +0100 Subject: [PATCH 02/35] [IMP] replace selection list of existing CMS pages by FAYT via select2 bzr revid: xmo@openerp.com-20131113143934-je7dusgzj15f7li0 --- addons/website/models/website.py | 1 + .../website/static/src/js/website.editor.js | 83 ++++++++++--------- .../website/static/src/xml/website.editor.xml | 19 +---- addons/website/views/website_templates.xml | 2 + 4 files changed, 50 insertions(+), 55 deletions(-) diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 6efa419c892..c85bf02c48e 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -355,6 +355,7 @@ class website(osv.osv): continue generated = map(dict, itertools.product(*( + # generate w/ pattern using name_search itertools.izip(itertools.repeat(name), converter.generate()) for name, converter in rule._converters.iteritems() ))) diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index af5763b9500..994876d6c05 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -2,7 +2,7 @@ 'use strict'; var website = openerp.website; - // $.fn.data automatically parses value, '0'|'1' -> 0|1 + var _t = openerp._t; website.add_template_file('/website/static/src/xml/website.editor.xml'); website.dom_ready.done(function () { @@ -828,7 +828,7 @@ website.editor.LinkDialog = website.editor.Dialog.extend({ template: 'website.editor.dialog.link', events: _.extend({}, website.editor.Dialog.prototype.events, { - 'change .url-source': function (e) { this.changed($(e.target)); }, + 'change :input.url-source': function (e) { this.changed($(e.target)); }, 'mousedown': function (e) { var $target = $(e.target).closest('.list-group-item'); if (!$target.length || $target.hasClass('active')) { @@ -836,7 +836,7 @@ return; } - this.changed($target.find('.url-source')); + this.changed($target.find('.url-source').filter(':input')); }, 'click button.remove': 'remove_link', 'change input#link-text': function (e) { @@ -846,21 +846,36 @@ init: function (editor) { this._super(editor); // url -> name mapping for existing pages - this.pages = Object.create(null); this.text = null; }, start: function () { var self = this; - return $.when( - this.fetch_pages().done(this.proxy('fill_pages')), - this._super() - ).done(function () { - self.bind_data(); + this.$('#link-page').select2({ + minimumInputLength: 3, + placeholder: _t("New or existing page"), + query: function (q) { + // FIXME: out-of-order, abort + self.fetch_pages(q.term).then(function (results) { + var rs = _.map(results, function (r) { + return { id: r.url, text: r.name, }; + }); + rs.push({ + create: true, + id: q.term, + text: "Create " + q.term, + }); + q.callback({ + more: false, + results: rs + }); + }); + }, }); + return this._super().then(this.proxy('bind_data')); }, save: function () { var self = this, _super = this._super.bind(this); - var $e = this.$('.list-group-item.active .url-source'); + var $e = this.$('.list-group-item.active .url-source').filter(':input'); var val = $e.val(); if (!val || !$e[0].checkValidity()) { // FIXME: error message @@ -872,15 +887,18 @@ var done = $.when(); if ($e.hasClass('email-address')) { this.make_link('mailto:' + val, false, val); - } else if ($e.hasClass('existing')) { - self.make_link(val, false, this.pages[val]); - } else if ($e.hasClass('pages')) { - // Create the page, get the URL back - done = $.get(_.str.sprintf( - '/pagenew/%s?noredirect', encodeURI(val))) - .then(function (response) { - self.make_link(response, false, val); - }); + } else if ($e.hasClass('page')) { + var data = $e.select2('data'); + if (!data.create) { + self.make_link(data.id, false, data.text); + } else { + // Create the page, get the URL back + done = $.get(_.str.sprintf( + '/pagenew/%s?noredirect', encodeURI(data.id))) + .then(function (response) { + self.make_link(response, false, data.id); + }); + } } else { this.make_link(val, this.$('input.window-new').prop('checked')); } @@ -903,13 +921,6 @@ var match, $control; if ((match = /mailto:(.+)/.exec(href))) { $control = this.$('input.email-address').val(match[1]); - } else if (href in this.pages) { - $control = this.$('select.existing').val(href); - } else if ((match = /\/page\/(.+)/.exec(href))) { - var actual_href = '/page/website.' + match[1]; - if (actual_href in this.pages) { - $control = this.$('select.existing').val(actual_href); - } } if (!$control) { $control = this.$('input.url').val(href); @@ -921,33 +932,25 @@ this.$('input.window-new').prop('checked', new_window); }, changed: function ($e) { - this.$('.url-source').not($e).val(''); + this.$('.url-source').filter(':input').not($e).val('') + .filter(function () { return !!$(this).data('select2'); }) + .select2('data', null); $e.closest('.list-group-item') .addClass('active') .siblings().removeClass('active') .addBack().removeClass('has-error'); }, - fetch_pages: function () { + fetch_pages: function (term) { return openerp.jsonRpc('/web/dataset/call_kw', 'call', { model: 'website', method: 'search_pages', - args: [null], + args: [null, term], kwargs: { - limit: 10, + limit: 9, context: website.get_context() }, }); }, - fill_pages: function (results) { - var self = this; - var pages = this.$('select.existing')[0]; - _(results).each(function (result) { - self.pages[result.url] = result.name; - - pages.options[pages.options.length] = - new Option(result.name, result.url); - }); - }, }); website.editor.RTELinkDialog = website.editor.LinkDialog.extend({ start: function () { diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 66b82ae7cbe..01ed1ca9950 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -41,23 +41,12 @@