diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index d38b4b948b6..c5628765ed8 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -50,10 +50,9 @@ class Website(openerp.addons.web.controllers.main.Home): def pagenew(self, path, noredirect=NOPE): web = request.registry['website'] try: - path = web.new_page(request.cr, request.uid, path, request.context) + path = web.new_page(request.cr, request.uid, path, context=request.context) except psycopg2.IntegrityError: logger.exception('Unable to create ir_model_data for page %s', path) - request.cr.execute('ROLLBACK TO SAVEPOINT pagenew') return werkzeug.exceptions.InternalServerError() url = "/page/" + path if noredirect is not NOPE: @@ -91,7 +90,13 @@ class Website(openerp.addons.web.controllers.main.Home): values = { 'path': page, } - + try: + request.website.get_template(page) + except (Exception), e: + if request.context['editable']: + page = 'website.page_404' + else: + return request.registry['ir.http']._handle_404(e) return request.website.render(page, values) @website.route('/website/customize_template_toggle', type='json', auth='user') @@ -227,7 +232,7 @@ class Website(openerp.addons.web.controllers.main.Home): obj = _object.browse(request.cr, request.uid, _id) return bool(obj.website_published) - @website.route(['/website/kanban/'], type='http', auth="public") + @website.route(['/website/kanban/'], type='http', auth="public", methods=['POST']) def kanban(self, **post): return request.website.kanban_col(**post) @@ -239,7 +244,9 @@ 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.enumerate_pages()}) + return request.website.render('website.sitemap', { + 'pages': request.website.enumerate_pages() + }) @website.route('/sitemap.xml', type='http', auth="public") def sitemap_xml(self): diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 72fa455dbbf..005b45f1d6a 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- +import traceback import werkzeug.routing import openerp -from openerp.osv import orm -from openerp.http import request from openerp.addons.base import ir +from openerp.http import request +from openerp.osv import orm from ..utils import slugify +from website import get_current_website class ir_http(orm.AbstractModel): _inherit = 'ir.http' @@ -24,6 +26,33 @@ class ir_http(orm.AbstractModel): else: request.uid = request.session.uid + def _handle_403(self, exception): + return self._render_error(403, { + 'error': exception.message + }) + + def _handle_404(self, exception): + return self._render_error(404) + + def _handle_500(self, exception): + # TODO: proper logging + return self._render_error(500, { + 'exception': exception, + 'traceback': traceback.format_exc(), + 'qweb_template': getattr(exception, 'qweb_template', None), + 'qweb_node': getattr(exception, 'qweb_node', None), + 'qweb_eval': getattr(exception, 'qweb_eval', None), + }) + + def _render_error(self, code, values=None): + self._auth_method_public() + if not hasattr(request, 'website'): + request.website = get_current_website() + request.website.preprocess_request(request) + return werkzeug.wrappers.Response( + request.website._render('website.%s' % code, values), + status=code, + content_type='text/html;charset=utf-8') class ModelConverter(ir.ir_http.ModelConverter): def __init__(self, url_map, model=False): @@ -38,25 +67,22 @@ class ModelConverter(ir.ir_http.ModelConverter): id, name = value return "%s-%d" % (slugify(name), id) - def generate(self, query=None): + def generate(self, cr, uid, query=None, context=None): return request.registry[self.model].name_search( - request.cr, request.uid, - name=query or '', - context=request.context) + cr, uid, name=query or '', context=context) class PageConverter(werkzeug.routing.PathConverter): """ Only point of this converter is to bundle pages enumeration logic Sads got: no way to get the view's human-readable name even if one exists """ - def generate(self, query=None): + def generate(self, cr, uid, query=None, context=None): View = request.registry['ir.ui.view'] views = View.search_read( - request.cr, request.uid, [['page', '=', True]], - fields=[], order='name', context=request.context) + cr, uid, [['page', '=', True]], + fields=[], order='name', context=context) xids = View.get_external_id( - request.cr, request.uid, [view['id'] for view in views], - context=request.context) + cr, uid, [view['id'] for view in views], context=context) for view in views: xid = xids[view['id']] diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 3bd49c32aed..43b0be03831 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -6,7 +6,6 @@ import logging import psycopg2 import math import itertools -import traceback import urllib import urlparse @@ -16,7 +15,6 @@ import werkzeug.exceptions import werkzeug.wrappers import openerp -from openerp.exceptions import AccessError, AccessDenied from openerp.osv import orm, osv, fields from openerp.tools.safe_eval import safe_eval @@ -41,29 +39,22 @@ def route(routes, *route_args, **route_kwargs): request.route_lang = kwargs.pop('lang_code', None) if not hasattr(request, 'website'): request.multilang = f.multilang - # TODO: Select website, currently hard coded - request.website = request.registry['website'].browse( - request.cr, request.uid, 1, context=request.context) + request.website = get_current_website() if request.route_lang: lang_ok = [lg.code for lg in request.website.language_ids if lg.code == request.route_lang] if not lang_ok: return request.not_found() request.website.preprocess_request(request) - try: - return f(*args, **kwargs) - except Exception, err: - logger.exception("Website Rendering Error.") - if request.context['is_public_user']: - return request.website.render("website.401") - else: - return request.website.render("website.500", { - 'traceback': traceback.format_exc(), - 'controller': [f.__module__, "%s.%s" % (args[0].__class__.__name__, f.__name__)], - }) + return f(*args, **kwargs) return wrap return decorator +def get_current_website(): + # TODO: Select website, currently hard coded + return request.registry['website'].browse(request.cr, request.uid, 1, context=request.context) + + def url_for(path_or_uri, lang=None, keep_query=None): location = path_or_uri.strip() url = urlparse.urlparse(location) @@ -124,29 +115,33 @@ class website(osv.osv): # completely arbitrary max_length idname = slugify(name, max_length=50) - cr.execute('SAVEPOINT pagenew') imd = self.pool.get('ir.model.data') view = self.pool.get('ir.ui.view') module, tmp_page = template.split('.') view_model, view_id = imd.get_object_reference(cr, uid, module, tmp_page) - newview_id = view.copy(cr, uid, view_id, context=context) - newview = view.browse(cr, uid, newview_id, context=context) - newview.write({ - 'arch': newview.arch.replace(template, "%s.%s" % (module, idname)), - 'name': name, - 'page': ispage, - }) - imd.create(cr, uid, { - 'name': idname, - 'module': module, - 'model': 'ir.ui.view', - 'res_id': newview_id, - 'noupdate': True - }, context=context) - cr.execute('RELEASE SAVEPOINT pagenew') - return "%s.%s" % (module, idname) + cr.execute('SAVEPOINT new_page') + try: + newview_id = view.copy(cr, uid, view_id, context=context) + newview = view.browse(cr, uid, newview_id, context=context) + newview.write({ + 'arch': newview.arch.replace(template, "%s.%s" % (module, idname)), + 'name': name, + 'page': ispage, + }) + imd.create(cr, uid, { + 'name': idname, + 'module': module, + 'model': 'ir.ui.view', + 'res_id': newview_id, + 'noupdate': True + }, context=context) + cr.execute('RELEASE SAVEPOINT new_page') + return "%s.%s" % (module, idname) + except: + cr.execute("ROLLBACK TO SAVEPOINT new_page") + raise def get_public_user(self, cr, uid, context=None): if not self.public_user: @@ -159,6 +154,9 @@ class website(osv.osv): def redirect(url): return werkzeug.utils.redirect(url_for(url)) request.redirect = redirect + if not hasattr(request, 'multilang'): + # TODO: try to get rid of this multilang attribute + request.multilang = False is_public_user = request.uid == self.get_public_user(cr, uid, context).id @@ -186,9 +184,17 @@ class website(osv.osv): 'translatable': not is_public_user and not is_master_lang and request.multilang, }) - def _render(self, cr, uid, ids, template, values=None, context=None): - view = self.pool.get("ir.ui.view") + def get_template(self, cr, uid, ids, template, context=None): IMD = self.pool.get("ir.model.data") + try: + module, xmlid = template.split('.', 1) + view_ref = IMD.get_object_reference(cr, uid, module, xmlid) + except ValueError: # catches both unpack errors and gor errors + module, xmlid = 'website', template + view_ref = IMD.get_object_reference(cr, uid, module, xmlid) + return self.pool.get("ir.ui.view").browse(cr, uid, view_ref[1]) + + def _render(self, cr, uid, ids, template, values=None, context=None): user = self.pool.get("res.users") if not context: @@ -212,43 +218,12 @@ class website(osv.osv): inherit_branding=qweb_context.setdefault('editable', False), ) - view_ref = None - # check if xmlid of the template exists - try: - module, xmlid = template.split('.', 1) - view_ref = IMD.get_object_reference(cr, uid, module, xmlid) - except ValueError: # catches both unpack errors and gor errors - module, xmlid = 'website', template - try: - view_ref = IMD.get_object_reference(cr, uid, module, xmlid) - except ValueError: - return self.error(cr, uid, 404, qweb_context, context=context) + view = self.get_template(cr, uid, ids, template) if 'main_object' not in qweb_context: - try: - main_object = self.pool[view_ref[0]].browse(cr, uid, view_ref[1]) - qweb_context['main_object'] = main_object - except Exception: - pass + qweb_context['main_object'] = view - try: - return view.render( - cr, uid, "%s.%s" % (module, xmlid), qweb_context, - engine='website.qweb', context=context) - except (AccessError, AccessDenied), err: - logger.error(err) - qweb_context['error'] = err[1] - logger.warn("Website Rendering Error.\n\n%s" % traceback.format_exc()) - return self.error(cr, uid, 401, qweb_context, context=context) - except Exception, e: - qweb_context['template'] = getattr(e, 'qweb_template', '') - node = getattr(e, 'qweb_node', None) - qweb_context['node'] = node and node.toxml() - qweb_context['expr'] = getattr(e, 'qweb_eval', '') - qweb_context['traceback'] = traceback.format_exc() - logger.exception("Website Rendering Error.\n%(template)s\n%(expr)s\n%(node)s" % qweb_context) - return self.error(cr, uid, 500 if qweb_context['editable'] else 404, - qweb_context, context=context) + return view.render(qweb_context, engine='website.qweb', context=context) def render(self, cr, uid, ids, template, values=None, context=None): def callback(template, values, context): @@ -257,13 +232,6 @@ class website(osv.osv): values = {} return LazyResponse(callback, template=template, values=values, context=context) - def error(self, cr, uid, code, qweb_context, context=None): - View = request.registry['ir.ui.view'] - return werkzeug.wrappers.Response( - View.render(cr, uid, 'website.%d' % code, qweb_context), - status=code, - content_type='text/html;charset=utf-8') - def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None, context=None): # Compute Pager page_count = int(math.ceil(float(total) / step)) @@ -386,6 +354,8 @@ class website(osv.osv): :rtype: list({name: str, url: str}) """ router = request.httprequest.app.get_db_router(request.db) + # Force enumeration to be performed as public user + uid = self.get_public_user(cr, uid, context=context).id for rule in router.iter_rules(): if not self.rule_is_enumerable(rule): continue @@ -396,10 +366,10 @@ class website(osv.osv): # allow single converter as decided by fp, checked by # rule_is_enumerable [(name, converter)] = converters.items() - # FIXME: perform generation as public user + converter_values = converter.generate( + request.cr, uid, query=query_string, context=context) generated = ({k: v} for k, v in itertools.izip( - itertools.repeat(name), - converter.generate(query=query_string))) + itertools.repeat(name), converter_values)) else: # force single iteration for literal urls generated = [{}] diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index cee4409240d..52b3f19e1ba 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -103,7 +103,6 @@ top: $el.height() / 2 + image_top - $btn.outerHeight() / 2, left: $el.width() / 2 + image_left - $btn.outerWidth() / 2, }); - $el.css('opacity', 0.75); }).on('mouseleave', 'img', function (e) { var $previous = $(previousSelection.$); var $button = $previous.next('button'); @@ -112,14 +111,11 @@ $button.css('visibility', ''); if (el === this) { return; } $button.remove(); - $previous.css('opacity', ''); previousSelection = null; }); - editor.on('destroy', function (evt) { + editor.on('destroy', function () { if (!previousSelection) { return; } - $('.image-edit-button') - .prev().css('opacity', '').end() - .remove(); + $('.image-edit-button').remove(); }); //noinspection JSValidateTypes @@ -566,7 +562,9 @@ window_title: "New Page", input: "Page Title", }).then(function (val) { - document.location = '/pagenew/' + encodeURI(val); + if (val) { + document.location = '/pagenew/' + encodeURI(val); + } }); }, }); @@ -895,10 +893,9 @@ start: function () { var self = this; this.$('#link-page').select2({ - minimumInputLength: 3, + minimumInputLength: 1, 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, }; @@ -912,6 +909,8 @@ more: false, results: rs }); + }, function () { + q.callback({more: false, results: []}); }); }, }); @@ -995,8 +994,8 @@ limit: 9, context: website.get_context() }, - }).done(function () { - // request completed successfully -> unstore it + }).always(function () { + // request completed -> unstore it self.req = null; }); }, diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js index 34edfe9d259..e491912da15 100644 --- a/addons/website/static/src/js/website.snippets.animation.js +++ b/addons/website/static/src/js/website.snippets.animation.js @@ -53,7 +53,7 @@ website.snippet.animationRegistry.carousel = website.snippet.Animation.extend({ start: function () { - this.$target.carousel({interval: false}); + this.$target.carousel({interval: 10000}); }, }); diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index a4f51c6ab39..73691ad65d5 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -1125,7 +1125,8 @@ id = _id + 1; } }); - this.$target.attr("id", "myCarousel" + id); + this.id = "myCarousel" + id; + this.$target.attr("id", this.id); this.$target.find(".carousel-control").attr("href", "#myCarousel" + id); this.$target.find("[data-target='#myCarousel']").attr("data-target", "#myCarousel" + id); diff --git a/addons/website/static/src/js/website.tour.app.js b/addons/website/static/src/js/website.tour.app.js deleted file mode 100644 index 442a72c145c..00000000000 --- a/addons/website/static/src/js/website.tour.app.js +++ /dev/null @@ -1,52 +0,0 @@ -(function () { - 'use strict'; - - var website = openerp.website; - - website.EditorBar.include({ - start: function () { - this.registerTour(new website.AppTour(this)); - return this._super(); - }, - }); - - website.AppTour = website.Tour.extend({ - id: 'app-tutorial', - name: "Install a new application", - init: function (editor) { - var self = this; - self.steps = [ - { - stepId: 'welcome-install-app', - orphan: true, - backdrop: true, - title: "Install an App", - content: "You can intall some apps to manage your website content.", - template: self.popover({ next: "Start Tutorial", end: "Skip It" }), - }, - { - stepId: 'customize', - element: '#customize-menu-button', - placement: 'left', - title: "Install an app", - content: "Add new apps by customizing your website.", - triggers: function () { - editor.on('rte:customize_menu_ready', editor, function () { - self.movetoStep('install-app'); - }); - }, - }, - { - stepId: 'install-app', - element: '#customize-menu li:last a', - placement: 'left', - orphan: true, - title: "Install an app", - content: "Click 'Install Apps' to select and install the application you would like to manage from your website.", - }, - ]; - return this._super(); - }, - }); - -}()); diff --git a/addons/website/static/src/js/website.tour.banner.js b/addons/website/static/src/js/website.tour.banner.js index d0ca68c8a6e..864c46c2c7d 100644 --- a/addons/website/static/src/js/website.tour.banner.js +++ b/addons/website/static/src/js/website.tour.banner.js @@ -32,9 +32,7 @@ title: "Edit this page", content: "Every page of your website can be modified through the Edit button.", triggers: function () { - editor.on('tour:editor_bar_loaded', editor, function () { - self.movetoStep('add-block'); - }); + editor.on('tour:editor_bar_loaded', self, self.moveToNextStep); }, }, { @@ -44,8 +42,8 @@ title: "Insert building blocks", content: "To add content in a page, you can insert building blocks.", triggers: function () { - $('button[data-action=snippet]').click(function () { - self.movetoStep('drag-banner'); + $('button[data-action=snippet]').one('click', function () { + self.moveToNextStep(); }); } }, @@ -56,7 +54,7 @@ title: "Drag & Drop a Banner", content: "Drag the Banner block and drop it in your page.", triggers: function () { - self.onSnippetDraggedMoveTo('edit-title'); + self.onSnippetDraggedAdvance('carousel'); }, }, { @@ -80,41 +78,98 @@ title: "Customize the banner", content: "You can customize characteristic of any blocks through the Customize menu. For instance, change the background of the banner.", template: self.popover({ next: "Continue" }), + }, + { + stepId: 'add-three-cols', + element: 'button[data-action=snippet]', + placement: 'bottom', + title: "Add Another Block", + content: "Let's add another building block to your page.", triggers: function () { - $('.dropdown-menu [name=carousel-background]').click(function () { - self.movetoStep('save-changes'); + $('button[data-action=snippet]').one('click', function () { + self.moveToNextStep(); + }); + } + }, + { + stepId: 'drag-three-columns', + element: '#website-top-navbar [data-snippet-id=three-columns].ui-draggable', + placement: 'bottom', + title: "Drag & Drop a Block", + content: "Drag the '3 Columns' block and drop it below the banner.", + triggers: function () { + self.onSnippetDraggedAdvance('three-columns'); + }, + }, + { + stepId: 'activate-text-block-title', + element: '#wrap [data-snippet-id=three-columns] .text-center[data-snippet-id=colmd]', + placement: 'top', + title: "Edit an Area", + content: "Select any area of the page to modify it. Click on this subtitle.", + triggers: function () { + $('#wrap [data-snippet-id=three-columns] .text-center[data-snippet-id=colmd]').one('click', function () { + self.moveToNextStep(); }); }, }, + { + stepId: 'remove-text-block-title', + element: '.oe_snippet_remove:last', + placement: 'top', + reflex: true, + title: "Delete the Title", + content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the cross to delete the title.", + }, { 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.", + content: "Publish your page by clicking on the 'Save' button.", }, { stepId: 'part-2', orphan: true, title: "Congratulation!", - content: "Your homepage have been updated. Now, we suggest you insert another snippet to overview possible customization.", + content: "Your homepage has been updated.", template: self.popover({ next: "Continue" }), }, + { + stepId: 'show-mobile', + element: 'a[data-action=show-mobile-preview]', + placement: 'bottom', + title: "Test Your Mobile Version", + content: "Let's check how your homepage looks like on mobile devices.", + triggers: function () { + $(document).one('shown.bs.modal', function () { + self.moveToNextStep(); + }); + } + }, + { + stepId: 'show-mobile-close', + element: 'button[data-dismiss=modal]', + placement: 'right', + reflex: true, + title: "Close Mobile Preview", + content: "Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog.", + }, { stepId: 'show-tutorials', element: '#help-menu-button', placement: 'left', reflex: true, - title: "Help is always available", - content: "You can always click here if you want more helps or continue to build and get more tips about your website contents like page menu, ...", - template: self.popover({ end: "Close" }), + title: "More Tutorials", + content: "Get more tutorials through this 'Help' menu or click on the left 'Edit' button to continue modifying this page.", + template: self.popover({ end: "Close Tutorial" }), } ]; return this._super(); }, resume: function () { - return (this.isCurrentStep('part-2') || this.isCurrentStep('show-tutorials')) && !this.tour.ended(); + return this.isCurrentStep('part-2') && this._super(); }, }); diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js index e17e2d83227..ad0280b0b35 100644 --- a/addons/website/static/src/js/website.tour.js +++ b/addons/website/static/src/js/website.tour.js @@ -48,13 +48,21 @@ isCurrentStep: function (stepId) { return this.currentStepIndex() === this.indexOfStep(stepId); }, - movetoStep: function (stepId) { - $('.popover.tour').remove(); - var index = this.indexOfStep(stepId); - if (index > -1) { - this.tour.goto(index); + moveToStep: function (step) { + var index = _.isNumber(step) ? step : this.indexOfStep(step); + if (index >= this.steps.length) { + this.stop(); + } else if (index >= 0) { + var self = this; + setTimeout(function () { + $('.popover.tour').remove(); + self.tour.goto(index); + }, 0); } }, + moveToNextStep: function () { + this.moveToStep(this.currentStepIndex() + 1); + }, stop: function () { this.tour.end(); }, @@ -65,9 +73,12 @@ window.location.replace(newUrl); } }, + ended: function () { + return this.tourStorage.getItem(this.id+'_end') === "yes"; + }, resume: function () { // Override if necessary - return this.currentStepIndex() === 0; + return !this.ended(); }, trigger: function (url) { // Override if necessary @@ -82,25 +93,21 @@ popover: function (options) { return openerp.qweb.render('website.tour_popover', options); }, - onSnippetDraggedMoveTo: function (stepId) { + onSnippetDraggedAdvance: function (snippetId, stepId) { var self = this; - var $body = $(document.body); function beginDrag () { $('.popover.tour').remove(); - function goToNextStep () { - $('#snippets').toggle(); + function advance () { if (stepId) { - self.movetoStep(stepId); + self.moveToStep(stepId); } else { - self.stop(); + self.moveToNextStep() } - $body.off('mouseup', goToNextStep); } - $body.off('mousedown', beginDrag); - $body.on('mouseup', goToNextStep); + $(document.body).one('mouseup', advance); } - $body.on('mousedown', beginDrag); - } + $('#website-top-navbar [data-snippet-id='+snippetId+'].ui-draggable').one('mousedown', beginDrag); + }, }); website.UrlParser = openerp.Class.extend({ diff --git a/addons/website/static/src/js/website.tour.page.js b/addons/website/static/src/js/website.tour.page.js deleted file mode 100644 index 267afe4bebd..00000000000 --- a/addons/website/static/src/js/website.tour.page.js +++ /dev/null @@ -1,100 +0,0 @@ -(function () { - 'use strict'; - - var website = openerp.website; - - website.EditorBar.include({ - start: function () { - this.registerTour(new website.PageTour(this)); - return this._super(); - }, - }); - - website.PageTour = website.Tour.extend({ - id: 'page-tutorial', - name: "Add a page", - init: function (editor) { - var self = this; - self.steps = [ - { - stepId: 'welcome-menu-editor', - orphan: true, - backdrop: true, - title: "Menu editor", - content: "We will show how to edit your website's menu.", - template: self.popover({ next: "Start Tutorial", end: "Skip It" }), - }, - { - stepId: 'content-menu', - element: '#content-menu-button', - placement: 'left', - reflex: true, - title: "Edit the content", - content: "Click here to edit the menu.", - }, - { - stepId: 'edit-entry', - element: 'a[data-action=edit-structure]', - placement: 'left', - title: "Edit menu", - content: "Click here to create a new menu entry and manage options.", - triggers: function () { - $(document).one('shown.bs.modal', function () { - self.movetoStep('add-menu-entry'); - }); - }, - }, - { - stepId: 'add-menu-entry', - element: 'a.js_add_menu', - placement: 'left', - title: "Add menu entry", - content: "Click here to create a new menu entry.", - triggers: function () { - $(document).one('shown.bs.modal', function () { - self.movetoStep('enter-entry-name'); - }); - }, - }, - { - stepId: 'enter-entry-name', - element: '#link-text', - placement: 'left', - title: "Choose a label", - content: "This label will appear in the top menu and will be visible by all your audience.\nGive a meaningful name to help your visitors. For instance, 'Photos Gallery'.", - template: self.popover({ next: "Continue" }), - }, - { - stepId: 'enter-page-name', - element: '.modal .select2-container', - placement: 'left', - title: "Link your menu to a 'gallery' page", - content: "This page does not exist. Create it by filling the name here. For instance, 'gallery'.", - template: self.popover({ next: "Continue" }), - }, - { - stepId: 'save-page', - element: '.modal-footer:last button.btn-primary', - placement: 'right', - title: "Save the page", - content: "Save your new page.", - triggers: function () { - $(document).one('hidden.bs.modal', function () { - self.movetoStep('save-menu'); - }); - }, - }, - { - stepId: 'save-menu', - element: '.modal-footer button.btn-primary', - placement: 'right', - reflex: true, - title: "Save the menu", - content: "Save the menu to edit the Gallery content directly from the interface.", - }, - ]; - return this._super(); - }, - }); - -}()); diff --git a/addons/website/static/src/xml/website.xml b/addons/website/static/src/xml/website.xml index 1b4254ca19b..b35dd8a9cfc 100644 --- a/addons/website/static/src/xml/website.xml +++ b/addons/website/static/src/xml/website.xml @@ -57,16 +57,7 @@ diff --git a/addons/website/tests/cases.py b/addons/website/tests/cases.py index 246fe6f2320..3e777293ff9 100644 --- a/addons/website/tests/cases.py +++ b/addons/website/tests/cases.py @@ -6,10 +6,11 @@ class URLCase(unittest2.TestCase): URLCase moved out of test_requests, otherwise discovery attempts to instantiate and run it """ - def __init__(self, user, url, result): + def __init__(self, user, url, source, result): super(URLCase, self).__init__() self.user = user self.url = url + self.source = source self.result = result @property @@ -17,6 +18,8 @@ class URLCase(unittest2.TestCase): return self.user or "Anonymous Coward" def __str__(self): + if self.source: + return "%s (from %s, as %s)" % (self.url, self.source, self.username) return "%s (as %s)" % (self.url, self.username) __repr__ = __str__ diff --git a/addons/website/tests/test_requests.py b/addons/website/tests/test_requests.py index 8af4bb7de60..fc56a93158c 100644 --- a/addons/website/tests/test_requests.py +++ b/addons/website/tests/test_requests.py @@ -80,13 +80,13 @@ class CrawlSuite(unittest2.TestSuite): assert auth.getcode() < 400, "Auth failure %d" % auth.getcode() def _wrapped_run(self, result, debug=False): - paths = collections.deque(['/']) + paths = [URL('/'), URL('/sitemap')] seen = set(paths) while paths: - url = paths.popleft() - r = self._request(url) - cases.URLCase(self.user, url, r).run(result) + url = paths.pop(0) + r = self._request(url.url) + url.to_case(self.user, r).run(result) if r.info().gettype() != 'text/html': continue @@ -110,7 +110,15 @@ class CrawlSuite(unittest2.TestSuite): (parts.scheme and parts.scheme not in ('http', 'https')): continue - paths.append(href) + paths.append(URL(href, url.url)) + +class URL(object): + def __init__(self, url, source=None): + self.url = url + self.source = source + + def to_case(self, user, result): + return cases.URLCase(user, self.url, self.source, result) def load_tests(loader, base, _): base.addTest(CrawlSuite()) diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index c58a43e86dc..375cac64dfb 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -29,7 +29,7 @@ - -