[MERGE] Merge parents

bzr revid: rim@openerp.com-20131126130141-lu5r2gy5h5l28irv
This commit is contained in:
Richard Mathot (OpenERP) 2013-11-26 14:01:41 +01:00
commit fe139acc4d
34 changed files with 628 additions and 527 deletions

View File

@ -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):

View File

@ -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']]

View File

@ -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 = [{}]

View File

@ -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;
});
},

View File

@ -53,7 +53,7 @@
website.snippet.animationRegistry.carousel = website.snippet.Animation.extend({
start: function () {
this.$target.carousel({interval: false});
this.$target.carousel({interval: 10000});
},
});

View File

@ -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);

View File

@ -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();
},
});
}());

View File

@ -32,9 +32,7 @@
title: "Edit this page",
content: "Every page of your website can be modified through the <i>Edit</i> 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 <em>'3 Columns'</em> 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 <em>'Save'</em> 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 <em>'Help'</em> menu or click on the left <em>'Edit'</em> 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();
},
});

View File

@ -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({

View File

@ -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();
},
});
}());

View File

@ -57,16 +57,7 @@
<h3 class="modal-title">Mobile preview</h3>
</div>
<div class="modal-body">
<iframe id="mobile-viewport" class="oe_mobile_viewport" src="?mobile-preview=true"></iframe>
<script>
var $o = $('<div style="width:50px; overflow:scroll;"><div style="width:100%;"></div></div>');
$o.appendTo("body");
var scrollSize = 50 - $o.find("div").innerWidth();
$o.remove();
$(".modal-body").css({'overflow': 'hidden', 'margin-right': '5px'});
$(".oe_mobile_viewport").css({'width': (320 + scrollSize + 5) + 'px'});
</script>
<iframe id="mobile-viewport" class="oe_mobile_viewport" src="?mobile-preview=true" style="width: 326px"></iframe>
</div>
</div>
</div>

View File

@ -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__

View File

@ -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())

View File

@ -29,7 +29,7 @@
</template>
<template id="layout" name="Main layout">&lt;!DOCTYPE html&gt;
<html t-att-lang="lang.replace('_', '-')"
<html t-att-lang="lang and lang.replace('_', '-')"
t-att-data-website-id="website.id if editable else None"
t-att-data-editable="'1' if editable else None"
t-att-data-translatable="'1' if translatable else None"
@ -66,7 +66,6 @@
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.form/jquery.form.js"></script>
<script type="text/javascript" src="/website/static/lib/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript">
// Bootstrap and jQuery UI conflicts
@ -103,8 +102,6 @@
<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.banner.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.tour.page.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.tour.app.js"></script>
<script t-if="not translatable" type="text/javascript" src="/website/static/src/js/website.snippets.editor.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>
@ -216,7 +213,7 @@
<a class="label label-danger" href="https://openerp.com/apps/website">OpenERP</a>
</div>
<div class="pull-left text-muted">
Copyright &amp;copy; <span t-field="res_company.name">Company name</span> - <a href="/sitemap">Sitemap</a>
Copyright &amp;copy; <span t-field="res_company.name">Company name</span>
</div>
</div>
</footer>
@ -366,54 +363,40 @@
</t>
</template>
<template id="403">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure oe_empty">
<div class="container">
<h1 class="mt32">403: Forbidden!</h1>
<p>
Access Denied.
</p>
<p>Maybe you were looking for one of these popular pages ?</p>
<ul>
<li><a href="/">Homepage</a></li>
<li><a href="/page/website.contactus/">Contact Us</a></li>
</ul>
<template id="page_404">
<t t-call="website.404">
<div class="container">
<div class="well mt32">
<p>This page does not exists, but you can create it as you are administrator of this site.</p>
<a class="btn btn-primary" t-attf-href="/pagenew/#{ path }">Create Page</a>
<span class="text-muted">or</span> <a href="/sitemap">Search a Page</a>
</div>
<div class="text-center text-muted">Edit the content bellow this line to adapt the default "page not found" page.</div>
</div>
</div>
<hr/>
</t>
</template>
<template id="404">
<t t-call="website.layout">
<div id="wrap">
<div class="container" t-if="editable and path">
<div class="well mt32">
<p>This page does not exists, but you can create it as you are administrator of this site.</p>
<a class="btn btn-primary" t-att-href="'/pagenew/'+path">Create Page</a>
<span class="text-muted">or</span> <a href="/sitemap">Search a Page</a>
</div>
<div class="text-center text-muted">Edit the content bellow this line to adapt the default "page not found" page.</div>
</div>
<hr />
<div class="oe_structure oe_empty">
<div class="container">
<h1 class="mt32">404: Page not found!</h1>
<p>
The page you were looking for could not be found; it is possible you have
typed the address incorrectly, but it has most probably been removed due
to the recent website reorganisation.
</p>
<p>Maybe you were looking for one of these popular pages ?</p>
<ul>
<li><a href="/">Homepage</a></li>
<li><a href="/page/website.contactus/">Contact Us</a></li>
</ul>
<div id="wrap">
<t t-raw="0"/>
<div class="oe_structure oe_empty">
<div class="container">
<h1 class="mt32">404: Page not found!</h1>
<p>
The page you were looking for could not be found; it is possible you have
typed the address incorrectly, but it has most probably been removed due
to the recent website reorganisation.
</p>
<p>Maybe you were looking for one of these popular pages ?</p>
<ul>
<li><a href="/">Homepage</a></li>
<li><a href="/page/website.contactus/">Contact Us</a></li>
</ul>
</div>
</div>
</div>
</div>
</t>
</template>
@ -423,22 +406,67 @@
<div class="oe_structure">
<h1 class="container mt32">500: Internal Server Error!</h1>
</div>
<t t-if="editable">
<h3 t-if="template">Exception in template: <span id="exception_template" t-esc="template"/></h3>
<h3 t-if="controller">Exception in controller: <span id="exception_controller" t-esc="controller[1]"/> <small>(Module: <span id="exception_module" t-esc="controller[0]"/>)</small> </h3>
<h4 t-if="expr">Expression: <t t-esc="expr"/></h4>
<t t-if="node"><pre id="exception_node" t-esc="node"/></t>
<pre id="exception_traceback" t-esc="traceback"/>
</t>
<div class="container panel-group" id="debug_infos" t-if="editable">
<div class="panel panel-default" t-if="exception">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#debug_infos" href="#error_main">
Error
</a>
</h4>
</div>
<div id="error_main" class="panel-collapse collapse in">
<div class="panel-body">
<p t-if="website_controller">The following error was raised in the website controller <code t-esc="website_controller"/></p>
<p><strong>Error message:</strong> <t t-esc="exception.message"/></p>
</div>
</div>
</div>
<div class="panel panel-default" t-if="qweb_template">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#adebug_infos" href="#error_qweb">
QWeb
</a>
</h4>
</div>
<div id="error_qweb" class="panel-collapse collapse">
<div class="panel-body">
<p>
The error occured while rendering the template <code t-esc="qweb_template"/>
<t t-if="qweb_eval">and evaluating the following expression: <code t-esc="qweb_eval"/></t>
</p>
<t t-if="qweb_node">
<pre id="exception_node" t-esc="qweb_node.toxml()"/>
</t>
</div>
</div>
</div>
<div class="panel panel-default" t-if="traceback">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#adebug_infos" href="#error_traceback">
Traceback
</a>
</h4>
</div>
<div id="error_traceback" class="panel-collapse collapse">
<div class="panel-body">
<pre id="exception_traceback" t-esc="traceback"/>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<template id="401">
<template id="403">
<t t-call="website.layout">
<div id="wrap">
<div class="container">
<h1 class="mt32">401: Unauthorized Access!</h1>
<h1 class="mt32">403: Forbidden</h1>
<p>
The page you were looking for could not be
authorized.

View File

@ -215,7 +215,7 @@ class WebsiteBlog(http.Controller):
return request.website.render("website_blog.blog_post_complete", values)
@website.route(['/blog/<int:blog_post_id>/comment'], type='http', auth="public")
def blog_post_comment(self, blog_post_id=None, **post):
def blog_post_comment(self, blog_post_id, **post):
cr, uid, context = request.cr, request.uid, request.context
if post.get('comment'):
request.registry['blog.post'].message_post(
@ -226,8 +226,8 @@ class WebsiteBlog(http.Controller):
context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
@website.route(['/blog/new'], type='http', auth="public", multilang=True)
def blog_post_create(self, category_id=None, **post):
@website.route('/blog/new', type='http', auth="public", multilang=True)
def blog_post_create(self, category_id, **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'].create(
@ -239,8 +239,8 @@ class WebsiteBlog(http.Controller):
}, context=create_context)
return werkzeug.utils.redirect("/blog/%s/?enable_editor=1" % new_blog_post_id)
@website.route(['/blog/duplicate'], type='http', auth="public")
def blog_post_copy(self, blog_post_id=None, **post):
@website.route('/blog/duplicate', type='http', auth="public")
def blog_post_copy(self, blog_post_id, **post):
""" Duplicate a blog.
:param blog_post_id: id of the blog post currently browsed.

View File

@ -16,34 +16,40 @@
init: function (editor) {
var self = this;
self.steps = [
{
{
stepId: 'welcome-blog',
orphan: true,
backdrop: true,
title: "Blog",
content: "We will show how to create a new blog post.",
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
title: "New Blog Post",
content: "Let's go through the first steps to write beautiful blog posts.",
template: self.popover({ next: "Start Tutorial", end: "Skip" }),
},
{
stepId: 'content-menu',
element: '#content-menu-button',
placement: 'left',
reflex: true,
title: "Edit the content",
content: "Click here to add content to your site.",
title: "Add Content",
content: "Create new pages, blogs, menu items and products through the <em>'Content'</em> menu.",
},
{
stepId: 'new-post-entry',
element: 'a[data-action=new_blog_post]',
placement: 'left',
title: "New blog post",
content: "Click here to create a blog post.",
title: "New Blog Post",
content: "Select this entry to create a new blog post.",
triggers: function () {
$(document).one('shown.bs.modal', function () {
$('.modal button.btn-primary').click(function () {
self.movetoStep('post-page');
var $doc = $(document);
function stopNewBlog () {
self.stop();
}
$doc.on('hide.bs.modal', stopNewBlog);
$doc.one('shown.bs.modal', function () {
$('.modal button.btn-primary').one('click', function () {
$doc.off('hide.bs.modal', stopNewBlog);
self.moveToStep('post-page');
});
self.movetoStep('choose-category');
self.moveToNextStep();
});
},
},
@ -51,72 +57,72 @@
stepId: 'choose-category',
element: '.modal select',
placement: 'right',
title: "Choose the post category",
content: "Select the 'News' category and click 'Continue'.",
title: "Which Blog?",
content: "Blog posts are organized in multiple categories (news, job offers, events, etc). Select <em>News</em> and click <em>Continue</em>.",
triggers: function () {
$('.modal select').change(function () {
function newsSelected () {
var $this = $(this);
var selected = $this.find("[value="+$this.val()+"]").text();
if (selected.toLowerCase() === 'news') {
self.movetoStep('continue-category');
if ($this.find('[value='+$this.val()+']').text().toLowerCase() === 'news') {
self.moveToNextStep();
$('.modal select').off('change', newsSelected);
}
});
}
$('.modal select').on('change', newsSelected);
},
},
{
stepId: 'continue-category',
element: '.modal button.btn-primary',
placement: 'right',
title: "Choose the post category",
content: "Click 'Continue' to create the post.",
title: "Create Blog Post",
content: "Click <em>Continue</em> to create the blog post.",
},
{
stepId: 'post-page',
orphan: true,
backdrop: true,
title: "New blog post created",
content: "You just created a new blog post. We are now going to edit it.",
template: self.popover({ next: "OK" }),
title: "Blog Post Created",
content: "This is your new blog post. We will edit your pages inline. What You See Is What You Get. No need for a complex backend.",
template: self.popover({ next: "Continue" }),
},
{
stepId: 'post-title',
element: 'h1[data-oe-expression="blog_post.name"]',
placement: 'top',
title: "Pick a title",
content: "Choose a catchy title for your blog post.",
title: "Pick a Title",
content: "Click on this area and set a catchy title.",
template: self.popover({ next: "OK" }),
},
{
stepId: 'add-image-block',
stepId: 'add-image-text',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Layout your blog post",
content: "Insert blocks like text-image to layout the body of your blog post.",
title: "Layout Your Blog Post",
content: "Use well designed building blocks to structure the content of your blog.",
triggers: function () {
$('button[data-action=snippet]').click(function () {
self.movetoStep('drag-image-text');
$('button[data-action=snippet]').one('click', function () {
self.moveToNextStep();
});
}
},
},
{
stepId: 'drag-image-text',
element: '#website-top-navbar [data-snippet-id=image-text].ui-draggable',
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the 'Image Text' block and drop it in your page.",
title: "Drag & Drop a Block",
content: "Drag the <em>'Image-Text'</em> block and drop it in your page.",
triggers: function () {
self.onSnippetDraggedMoveTo('add-text-block');
self.onSnippetDraggedAdvance('image-text');
},
},
{
stepId: 'add-text-block',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Add another block",
content: "Let's add another blog to your post.",
title: "Add Another Block",
content: "Let's add another block to your post.",
triggers: function () {
$('button[data-action=snippet]').click(function () {
self.movetoStep('drag-text-block');
$('button[data-action=snippet]').on('click', function () {
self.moveToNextStep();
});
}
},
@ -125,20 +131,20 @@
element: '#website-top-navbar [data-snippet-id=text-block].ui-draggable',
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the 'Text Block' block and drop it below the image block.",
content: "Drag the <em>'Text Block'</em> block and drop it below the image block.",
triggers: function () {
self.onSnippetDraggedMoveTo('activate-text-block-title');
self.onSnippetDraggedAdvance('text-block');
},
},
{
stepId: 'activate-text-block-title',
element: '#wrap [data-snippet-id=text-block] .text-center[data-snippet-id=colmd]',
placement: 'top',
title: "Activate on the title",
content: "Click on the title to activate it.",
title: "Edit an Area",
content: "Select any area of the page to modify it. Click on this subtitle.",
triggers: function () {
$('#wrap [data-snippet-id=text-block] .text-center[data-snippet-id=colmd]').click(function () {
self.movetoStep('remove-text-block-title');
$('#wrap [data-snippet-id=text-block] .text-center[data-snippet-id=colmd]').one('click', function () {
self.moveToNextStep();
});
},
},
@ -147,33 +153,41 @@
element: '.oe_snippet_remove:last',
placement: 'top',
reflex: true,
title: "Delete the title",
content: "Click on the cross to delete the title.",
},
{
stepId: 'publish-post',
element: 'button.js_publish_btn',
placement: 'right',
reflex: true,
title: "Publish your blog post",
content: "Click to publish your blog post.",
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 post is updated.",
title: "Save Your Blog",
content: "Click the <em>Save</em> button to record changes on the page.",
},
{
stepId: 'publish-post',
element: 'button.js_publish_btn',
placement: 'right',
reflex: true,
title: "Publish Your Post",
content: "Your blog post is not yet published. You can update this draft version and publish it once you are ready.",
},
{
stepId: 'end-tutorial',
orphan: true,
backdrop: true,
title: "Thanks!",
content: "This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu.",
template: self.popover({ end: "Close Tutorial" }),
},
];
return this._super();
},
resume: function () {
return this.isCurrentStep('post-page') && !this.tour.ended();
return (this.isCurrentStep('post-page') || this.isCurrentStep('publish-post')) && this._super();
},
trigger: function () {
return (this.resume() && this.testUrl(/^\/blog\/[0-9]+\/\?enable_editor=1/)) || this._super();
return (this.resume() && this.testUrl(/^\/blog\/[0-9]+\//)) || this._super();
},
});

View File

@ -167,13 +167,11 @@
<t t-call="website.publish_management">
<t t-set="object" t-value="blog_post"/>
<t t-set="publish_edit" t-value="True"/>
<li>
<a href="#" t-attf-data-href="/blog/duplicate?blog_post_id=#{blog_post.id}">Duplicate</a>
<script>
var $a=$("[data-href$='/duplicate']");
$a.attr("href", $a.data('href')).removeAttr('data-href');
$a.next("script").remove();
</script>
<li t-ignore="True">
<form class="duplicate hidden" action="/blog/duplicate">
<input name="blog_post_id" t-att-value="blog_post.id"/>
</form>
<a href="#" class="duplicate" onclick="$(this).prev('form').submit()">Duplicate</a>
</li>
</t>
</div>

View File

@ -23,10 +23,8 @@ from openerp import SUPERUSER_ID
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.tools.translate import _
from openerp.addons import website_sale
from openerp.addons.website.models import website
from openerp.addons.website.controllers.main import Website as controllers
from openerp.addons.website_sale.controllers.main import Ecommerce as Ecommerce
controllers = controllers()
@ -174,9 +172,9 @@ class website_event(http.Controller):
def event(self, event=None, **post):
if event.menu_id and event.menu_id.child_id:
return request.redirect(event.menu_id.child_id[0].url)
return request.redirect('/event/register/'+str(event.id))
return request.redirect('/event/%s/register' % str(event.id))
@website.route(['/event/register/<model("event.event"):event>'], type='http', auth="public", multilang=True)
@website.route(['/event/<model("event.event"):event>/register'], type='http', auth="public", multilang=True)
def event_register(self, event=None, **post):
values = {
'event': event,
@ -185,15 +183,14 @@ class website_event(http.Controller):
return request.website.render("website_event.event_description_full", values)
@website.route(['/event/add_cart'], type='http', auth="public", multilang=True)
def add_cart(self, event_id=None, **post):
assert event_id, 'An event is required'
def add_cart(self, event_id, **post):
user_obj = request.registry['res.users']
order_line_obj = request.registry.get('sale.order.line')
ticket_obj = request.registry.get('event.event.ticket')
order = request.context['website_sale_order']
if not order:
order = website_sale.controllers.main.get_order()
order = request.registry['website']._get_order(request.cr, request.uid, context=request.context)
partner_id = user_obj.browse(request.cr, SUPERUSER_ID, request.uid,
context=request.context).partner_id.id

View File

@ -63,7 +63,7 @@ class event(osv.osv):
'name': event.name
}, context=context)
tocreate = self._get_new_menu_pages(cr, uid, event, context)
tocreate.append((_('Register'), '/event/register/'+str(event.id)))
tocreate.append((_('Register'), '/event/%s/register' % str(event.id)))
sequence = 0
for name,url in tocreate:
menuobj.create(cr, uid, {

View File

@ -20,8 +20,8 @@
stepId: 'welcome-event',
orphan: true,
backdrop: true,
title: "Event",
content: "We will show how to create a new event.",
title: "Create an Event",
content: "Let's go through the firsts step to publish a new event.",
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
},
{
@ -29,21 +29,27 @@
element: '#content-menu-button',
placement: 'left',
reflex: true,
title: "Edit the content",
content: "Click here to add content to your site.",
title: "Add Content",
content: "The <em>Content</em> menu allows to create new pages, events, menus, etc.",
},
{
stepId: 'new-post-entry',
element: 'a[data-action=new_event]',
placement: 'left',
title: "New event",
content: "Click here to create an event.",
title: "New Event",
content: "Click here to create a new event.",
triggers: function () {
$(document).one('shown.bs.modal', function () {
$('.modal button.btn-primary').click(function () {
self.movetoStep('event-page');
var $doc = $(document);
function stopNewEvent () {
self.stop();
}
$doc.on('hide.bs.modal', stopNewEvent);
$doc.one('shown.bs.modal', function () {
$('.modal button.btn-primary').one('click', function () {
$doc.off('hide.bs.modal', stopNewEvent);
self.moveToStep('event-page');
});
self.movetoStep('choose-category');
self.moveToNextStep();
});
},
},
@ -51,33 +57,72 @@
stepId: 'choose-name',
element: '.modal input',
placement: 'right',
title: "Choose the event name",
content: "Choose a name for the new event and click 'Continue'.",
title: "Choose an Event Name",
content: "Choose a name for your new event and click <em>'Continue'</em>. e.g: Technical Training",
},
{
stepId: 'event-page',
orphan: true,
backdrop: true,
title: "New event created",
content: "You just created a new event. We are now going to edit it.",
title: "New Event Created",
content: "This is your new event page. We will edit the event presentation page.",
template: self.popover({ next: "OK" }),
},
{
stepId: 'add-block',
stepId: 'edit-page',
element: 'button[data-action=edit]',
placement: 'bottom',
title: "Edit the event desciption",
content: "Edit the page to modify the event description.",
triggers: function () {
editor.on('tour:editor_bar_loaded', self, self.moveToNextStep);
},
},
{
stepId: 'add-image-text',
element: 'button[data-action=snippet]',
placement: 'bottom',
reflex: true,
title: "Layout your event",
content: "Insert blocks like text-image to layout the body of your event.",
triggers: function () {
$('button[data-action=snippet]').one('click', function () {
self.moveToNextStep();
});
},
},
{
stepId: 'drag-image-text',
element: '#website-top-navbar [data-snippet-id=text-block].ui-draggable',
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the 'Text Block' block and drop it in your page.",
triggers: function () {
self.onSnippetDraggedAdvance('text-block');
},
},
{
stepId: 'save-changes',
element: 'button[data-action=save]',
placement: 'right',
reflex: true,
title: "Save your modifications",
content: "Once you click on save, your event is updated.",
},
{
stepId: 'publish-post',
element: 'button.js_publish_btn',
placement: 'right',
reflex: true,
title: "Publish your event",
content: "Click to publish your event.",
},
];
return this._super();
},
resume: function () {
return this.isCurrentStep('event-page') && !this.tour.ended();
return (this.isCurrentStep('event-page') || this.isCurrentStep('publish-post')) && this._super();
},
trigger: function () {
return (this.resume() && this.testUrl(/^\/event\/[0-9]+\/\?enable_editor=1/)) || this._super();
return (this.resume() && this.testUrl(/^\/event\/[0-9]+\/register/)) || this._super();
},
});

View File

@ -72,7 +72,7 @@
</span>
</t>
<h4 class="media-heading">
<a t-href="/event/#{(not event.menu_id) and 'register/' or ''}#{ event.id }/"><span t-field="event.name"> </span></a>
<a t-href="/event/#{ event.id }/#{(not event.menu_id) and 'register/' or ''}"><span t-field="event.name"> </span></a>
<small t-if="not event.website_published" class="label label-danger">not published</small>
</h4>
<div>

View File

@ -21,36 +21,36 @@
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.tools.translate import _
from openerp.addons import website_sale
from openerp.addons.website.models import website
from openerp.addons.website.controllers.main import Website as controllers
controllers = controllers()
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from openerp import tools
import urllib
class website_event(http.Controller):
@website.route(['/event/track_view/<model("event.track"):track>'], type='http', auth="public", multilang=True)
def event_track_view(self, track, **post):
@website.route(['/event/<model("event.event"):event>/track/<model("event.track"):track>'], type='http', auth="public", multilang=True)
def event_track_view(self, event, track, **post):
# TODO: not implemented
values = { 'track': track, 'event': track.event_id}
return request.website.render("website_event_track.track_view", values)
@website.route(['/event/tracks/<model("event.event"):event>'], type='http', auth="public", multilang=True)
@website.route([
'/event/<model("event.event"):event>/track/',
'/event/<model("event.event"):event>/track/tag/<model("event.track.tag"):tag>'
], type='http', auth="public", multilang=True)
def event_tracks(self, event, tag=None, **post):
# TODO: filter on tracks: tags, search keywords
if tag:
track_obj = request.registry.get('event.track')
track_ids = track_obj.search(request.cr, request.uid,
[("id", "in", [track.id for track in event.track_ids]), ("tag_ids", "=", tag.id)], context=request.context)
tracks = track_obj.browse(request.cr, request.uid, track_ids, context=request.context)
else:
tracks = event.track_ids
values = {
'event': event,
'tracks': event.track_ids,
'tracks': tracks,
'tags': event.track_tag_ids,
'searches': {}
}
print 'ICI'
return request.website.render("website_event_track.tracks", values)
@website.route(['/event/detail/<model("event.event"):event>'], type='http', auth="public", multilang=True)
@ -58,8 +58,12 @@ class website_event(http.Controller):
values = { 'event': event }
return request.website.render("website_event_track.event_home", values)
@website.route(['/event/track_proposal/<model("event.event"):event>'], type='http', auth="public", multilang=True)
def event_detail(self, event=None, **post):
@website.route(['/event/<model("event.event"):event>/track_proposal/'], type='http', auth="public", multilang=True)
def event_track_proposal(self, event, **post):
values = { 'event': event }
return request.website.render("website_event_track.event_track_proposal", values)
@website.route(['/event/<model("event.event"):event>/track_proposal/success/'], type='http', auth="public", multilang=True)
def event_track_proposal_success(self, event, **post):
values = { 'event': event }
return request.website.render("website_event_track.event_track_proposal_success", values)

View File

@ -148,7 +148,7 @@
<xpath expr="//div[@class='oe_right oe_button_box']" position="inside">
<button name="%(website_event_track.act_event_list_tracks)d" type="action" string="Tracks"/>
</xpath>
<xpath expr="//field[@name='twitter_hashtag']" position="after">
<xpath expr="//field[@name='organizer_id']" position="after">
<field name="track_tag_ids" widget="many2many_tags"/>
</xpath>
</field>

View File

@ -123,10 +123,9 @@ class event_event(osv.osv):
context = context or {}
result = super(event_event, self)._get_new_menu_pages(cr, uid, event, context=context)
if event.show_tracks:
result.append( (_('Agenda'), '/event/tracks/'+str(event.id)))
result.append( (_('Agenda'), '/event/%s/track/' % event.id))
if event.blog_id:
result.append( (_('News'), '/blog/cat/'+str(event.blog_ig.id)))
if event.show_track_proposal:
result.append( (_('Talk Proposals'), '/event/track_proposal/'+str(event.id)))
result.append( (_('Talk Proposals'), '/event/%s/track_proposal/' % event.id))
return result

View File

@ -1,4 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event_tag_manager,event.tag manager,model_event_tag,event.group_event_user,1,1,1,1
access_event_location_manager,event.track.location manager,model_event_track_location,event.group_event_user,1,1,1,1
access_event_track_manager,event.track manager,model_event_track,event.group_event_user,1,1,1,1
access_event_tag_public,event.tag public,model_event_tag,,1,0,0,0
access_event_tag_manager,event.tag manager,model_event_tag,event.group_event_user,1,1,1,1
access_event_track_stage_public,event.track.stage public,model_event_track_stage,,1,0,0,0
access_event_track_stage_manager,event.track.stage manager,model_event_track_stage,event.group_event_user,1,1,1,1
access_event_track_tag_public,event.track.tag public,model_event_track_tag,,1,0,0,0
access_event_track_tag_manager,event.track.tag manager,model_event_track_tag,event.group_event_user,1,1,1,1
access_event_track_location_public,event.track.location public,model_event_track_location,,1,0,0,0
access_event_track_location_manager,event.track.location manager,model_event_track_location,event.group_event_user,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_event_tag_manager event.tag manager model_event_tag event.group_event_user 1 1 1 1
2 access_event_location_manager event.track.location manager model_event_track_location event.group_event_user 1 1 1 1
3 access_event_track_manager event.track manager model_event_track event.group_event_user 1 1 1 1
4 access_event_tag_public event.tag public model_event_tag 1 0 0 0
5 access_event_tag_manager event.tag manager model_event_tag event.group_event_user 1 1 1 1
6 access_event_track_stage_public event.track.stage public model_event_track_stage 1 0 0 0
7 access_event_track_stage_manager event.track.stage manager model_event_track_stage event.group_event_user 1 1 1 1
8 access_event_track_tag_public event.track.tag public model_event_track_tag 1 0 0 0
9 access_event_track_tag_manager event.track.tag manager model_event_track_tag event.group_event_user 1 1 1 1
10 access_event_track_location_public event.track.location public model_event_track_location 1 0 0 0
11 access_event_track_location_manager event.track.location manager model_event_track_location event.group_event_user 1 1 1 1

View File

@ -43,10 +43,10 @@
<div class="col-md-3 css_noprint">
<ul class="nav nav-pills nav-stacked">
<li t-att-class="(not searches.get('tag')) and 'active' or ''"><a href="/event/tracks/#{ event.id }">All</a></li>
<li t-att-class="'' if searches.get('tag') else 'active'"><a t-href="/event/#{ event.id }/track/">All</a></li>
<t t-foreach="tags" t-as="tag">
<li t-att-class="searches.get('tag') == tag.id and 'active' or ''">
<a t-href="/event/tracks/#{ event.id }&amp;tag=#{ tag.id }">
<a t-href="/event/#{ event.id }/track/tag/#{ tag.id }">
<t t-esc="tag.name"/>
</a>
</li>
@ -59,7 +59,7 @@
<li t-foreach="event.track_ids" t-as="track" class="media">
<div class="media-body">
<h4 class="media-heading">
<a t-href="/event/track_view/#{track.id}"><span t-field="track.name"> </span></a>
<a t-href="/event/#{ event.id }/track/#{track.id}"><span t-field="track.name"> </span></a>
<!-- <small t-if="not track.website_published" class="label label-danger">not published</small> -->
</h4>
<p>TODO: NOT Implemented</p>
@ -125,8 +125,7 @@
</section>
</div>
<section id="forms" t-if="event.show_track_proposal">
<form class="form-horizontal mt32" action="/event/track_proposal/success" method="post" enctype="multipart/form-data">
<input type="hidden" t-att-value="event.id" name="event"/>
<form class="form-horizontal mt32" action="/event/#{event.id}/track_proposal/success" method="post" enctype="multipart/form-data">
<div class="form-group">
<label class="col-md-3 col-sm-4 control-label" for="partner_name">Speaker Name</label>
<div class="col-md-7 col-sm-8">
@ -221,5 +220,14 @@
<div class="oe_structure"/>
</t>
</template>
<template id="event_track_proposal_success">
<t t-call="website_event.layout">
<div class="container">
Thanks
</div>
</t>
</template>
</data>
</openerp>

View File

@ -16,29 +16,24 @@ class website_hr_recruitment(http.Controller):
'/jobs/department/<model("hr.department"):department>',
'/jobs/office/<model("res.partner"):office>'
], type='http', auth="public", multilang=True)
def jobs(self, department=None, office=None, page=0):
hr_job_obj = request.registry['hr.job']
domain = []
jobpost_ids = hr_job_obj.search(request.cr, request.uid, domain, order="website_published desc,no_of_recruitment desc", context=request.context)
jobs = hr_job_obj.browse(request.cr, request.uid, jobpost_ids, request.context)
def jobs(self, department=None, office=None):
jobs = self._browse_jobs([])
departments = set()
offices = set()
for job in jobs:
if job.department_id:
departments.add(job.department_id)
offices = set()
for job in jobs:
if job.address_id:
offices.add(job.address_id)
if department or office:
if office:
domain += [('address_id','=', office.id)]
domain = []
if department:
domain += [('department_id','=', department.id)]
jobpost_ids = hr_job_obj.search(request.cr, request.uid, domain, order="website_published desc,no_of_recruitment desc", context=request.context)
jobs = hr_job_obj.browse(request.cr, request.uid, jobpost_ids, request.context)
domain.append(('department_id','=', department.id))
if office:
domain.append(('address_id','=', office.id))
jobs = self._browse_jobs(domain)
return request.website.render("website_hr_recruitment.index", {
'jobs': jobs,
@ -52,7 +47,7 @@ class website_hr_recruitment(http.Controller):
def detail(self, job, **kwargs):
return request.website.render("website_hr_recruitment.detail", {'job': job})
@website.route(['/job/success'], type='http', auth="admin", multilang=True)
@website.route(['/job/success'], methods=['POST'], type='http', auth="admin", multilang=True)
def success(self, **post):
data = {
'name': _('Online Form'),
@ -69,7 +64,7 @@ class website_hr_recruitment(http.Controller):
try:
model, source_id = imd.get_object_reference(request.cr, request.uid, 'hr_recruitment', 'source_website_company')
data['source_id'] = source_id
except ValueError, e:
except ValueError:
pass
jobid = request.registry['hr.applicant'].create(request.cr, request.uid, data, context=request.context)
@ -79,15 +74,20 @@ class website_hr_recruitment(http.Controller):
'datas': base64.encodestring(post['ufile'].read()),
'datas_fname': post['ufile'].filename,
'res_model': 'hr.applicant',
'res_name': post['name'],
'res_name': data['partner_name'],
'res_id': jobid
}
request.registry['ir.attachment'].create(request.cr, request.uid, attachment_values, context=request.context)
return request.website.render("website_hr_recruitment.thankyou", {})
@website.route(['/job/apply'], type='http', auth="public", multilang=True)
def applyjobpost(self, job=None):
return request.website.render("website_hr_recruitment.applyjobpost", { 'job': job })
def applyjobpost(self, job):
[job_object] = request.registry['hr.job'].browse(
request.cr, request.uid, [int(job)], context=request.context)
return request.website.render("website_hr_recruitment.applyjobpost", {
'job': job_object
})
@website.route('/job/publish', type='json', auth="admin", multilang=True)
def publish(self, id, object):
@ -102,3 +102,11 @@ class website_hr_recruitment(http.Controller):
}, context=request.context)
return request.redirect("/job/detail/%s/?enable_editor=1" % job_id)
def _browse_jobs(self, domain):
Jobs = request.registry['hr.job']
jobpost_ids = Jobs.search(request.cr, request.uid, domain,
order="website_published desc,no_of_recruitment desc",
context=request.context)
jobs = Jobs.browse(request.cr, request.uid, jobpost_ids, context=request.context)
return jobs

View File

@ -1,6 +1,6 @@
(function() {
"use strict";
var website = openerp.website;
website.add_template_file('/website_hr_recruitment/static/src/xml/website_hr_recruitment.xml');
openerp.website.add_template_file(
'/website_hr_recruitment/static/src/xml/website_hr_recruitment.xml');
})();

View File

@ -86,7 +86,7 @@
</div>
<div class="oe_structure">
<section data-snippet-id="cta" class="oe_dark">
<section data-snippet-id="cta" class="oe_dark mb32">
<div class="container">
<div class="row">
<div class="col-md-12 text-center mt16 mb16">
@ -138,7 +138,7 @@
<div class="container">
<div class="row">
<div class="col-md-12 text-center mt16 mb16">
<a t-href="/job/apply/?job=#{ job.id }/" class="btn btn-primary btn-lg">Apply</a>
<a t-href="/job/apply/?job=#{ job.id }" class="btn btn-primary btn-lg">Apply</a>
</div>
</div>
</div>

View File

@ -6,7 +6,8 @@
<div class="input-group js_follow" t-att-data-id="object.id"
t-att-data-object="object._name"
t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'">
<input type="email" name="email"
<input t-if="is_public_user"
type="email" name="email"
class="js_follow_email form-control"
t-att-value="email or ''"
placeholder="your email..."/>

View File

@ -9,7 +9,7 @@ from openerp.addons.web.http import request
from openerp.addons.website.models import website
class CheckoutInfo:
class CheckoutInfo(object):
mandatory_billing_fields = ["name", "phone", "email", "street", "city", "country_id", "zip"]
optional_billing_fields = ["company", "state_id"]
string_billing_fields = ["name", "phone", "email", "street", "city", "zip"]
@ -27,7 +27,7 @@ class CheckoutInfo:
return self.mandatory_fields() + self.optional_fields()
def empty(self):
return dict((field_name, '') for field_name in self.all_fields())
return dict.fromkeys(self.all_fields(), '')
def from_partner(self, partner):
result = dict((field_name, getattr(partner, field_name)) for field_name in self.string_billing_fields if getattr(partner, field_name))
@ -464,6 +464,8 @@ class Ecommerce(http.Controller):
if 'promo' in post:
self.change_pricelist(post.get('promo'))
else:
self.get_pricelist()
suggested_ids = []
product_ids = []
@ -489,17 +491,18 @@ class Ecommerce(http.Controller):
return request.website.render("website_sale.mycart", values)
@website.route(['/shop/add_cart/'], type='http', auth="public", multilang=True)
def add_cart(self, path=None, product_id=None, order_line_id=None, remove=None, **kw):
def add_cart(self, product_id=None, order_line_id=None, remove=None, **kw):
self.add_product_to_cart(product_id=product_id and int(product_id), order_line_id=order_line_id and int(order_line_id), number=(remove and -1 or 1))
return request.redirect("/shop/mycart/")
@website.route(['/shop/add_cart_json/'], type='json', auth="public")
def add_cart_json(self, product_id=None, order_line_id=None, remove=None):
quantity = self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
order = self.registry['website'].get_current_order(request.cr, request.uid, context=request.context)
return [quantity, order.get_total_quantity(), order.amount_total, request.website.render("website_sale.total", {
'website_sale_order': order
}).strip()]
order = request.registry['website'].get_current_order(request.cr, request.uid, context=request.context)
return [quantity,
order.get_total_quantity(),
order.amount_total,
request.website._render("website_sale.total", {'website_sale_order': order})]
@website.route(['/shop/set_cart_json/'], type='json', auth="public")
def set_cart_json(self, path=None, product_id=None, order_line_id=None, set_number=0, json=None):
@ -518,6 +521,8 @@ class Ecommerce(http.Controller):
tx = context.get('website_sale_transaction')
if tx and tx.state != 'draft':
return request.redirect('/shop/payment/confirmation/%s' % order.id)
self.get_pricelist()
orm_partner = registry.get('res.partner')
orm_user = registry.get('res.users')

View File

@ -39,11 +39,17 @@
title: "Create a new product",
content: "Select 'New Product' to create it and manage its properties to boost your sales.",
triggers: function () {
$(document).one('shown.bs.modal', function () {
$('.modal button.btn-primary').click(function () {
self.movetoStep('product-page');
var $doc = $(document);
function stopProductTour () {
self.stop();
}
$doc.on('hide.bs.modal', stopProductTour);
$doc.one('shown.bs.modal', function () {
$('.modal button.btn-primary').one('click', function () {
$doc.off('hide.bs.modal', stopProductTour);
self.moveToStep('product-page');
});
self.movetoStep('enter-name');
self.moveToNextStep();
});
},
},
@ -52,7 +58,7 @@
element: '.modal input[type=text]',
placement: 'right',
title: "Choose name",
content: "Enter a name for your new product.",
content: "Enter a name for your new product then click 'Continue'.",
},
{
@ -71,22 +77,89 @@
content: "Edit the sale price of this product by clicking on the amount. The price is the sale price used in all sale order when selling this product.",
template: self.popover({ next: "OK" }),
},
{
stepId: 'update-image',
element: '#wrap img.img:first',
placement: 'top',
title: "Update image",
content: "Click here to set an image describing your product.",
triggers: function () {
function registerClick () {
$('button.image-edit-button').one('click', function () {
$('#wrap img.img:first').off('hover', registerClick);
self.moveToNextStep();
});
}
$('#wrap img.img:first').on('hover', registerClick);
},
},
{
stepId: 'upload-image',
element: 'button.filepicker',
placement: 'left',
title: "Upload image",
content: "Click on 'Upload an image from your computer' to pick an image describing your product.",
template: self.popover({ next: "OK" }),
triggers: function () {
$(document).on('hide.bs.modal', function () {
self.moveToStep('add-block');
});
}
},
{
stepId: 'save-image',
element: 'button.save',
placement: 'right',
title: "Save the image",
content: "Click 'Save Changes' to add the image to the product decsription.",
},
{
stepId: 'add-block',
element: 'button[data-action=snippet]',
placement: 'bottom',
reflex: true,
title: "Describe the product for your audience",
content: "Insert blocks like text-image, or gallery to fully describe the product and make your visitors want to buy this product.",
triggers: function () {
$('button[data-action=snippet]').one('click', function () {
self.moveToNextStep();
});
},
},
{
stepId: 'drag-big-picture',
element: '#website-top-navbar [data-snippet-id=big-picture].ui-draggable',
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the 'Big Picture' block and drop it in your page.",
triggers: function () {
self.onSnippetDraggedAdvance('big-picture');
},
},
{
stepId: 'save-changes',
element: 'button[data-action=save]',
placement: 'right',
reflex: true,
title: "Save your modifications",
content: "Once you click on save, your product is updated.",
},
{
stepId: 'publish-product',
element: 'button.js_publish_btn',
placement: 'right',
reflex: true,
title: "Publish your product",
content: "Click to publish your product so your customers can see it.",
},
];
return this._super();
},
resume: function () {
return this.isCurrentStep('product-page') && !this.tour.ended();
return (this.isCurrentStep('product-page') || this.isCurrentStep('publish-product')) && this._super();
},
trigger: function (url) {
return (this.resume() && this.testUrl(/^\/shop\/product\/[0-9]+\/\?enable_editor=1/)) || this._super();
return (this.resume() && this.testUrl(/^\/shop\/product\/[0-9]+\//)) || this._super();
},
});

View File

@ -292,7 +292,6 @@
<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-field="product.product_variant_ids[0].lst_price"
@ -886,7 +885,7 @@
</div>
</div>
<div class="js_payment mb64" t-if="not payment_acquirer_id and payments">
<div class="js_payment mb64" t-if="not payment_acquirer_id and payments" id="payment_method">
<p>Payment method:</p>
<div>
<t t-foreach="payments or []" t-as="payment">

View File

@ -6,7 +6,7 @@
<xpath expr="//t[@t-set='head']" position="inside">
<script type="text/javascript" src="/website_sale_delivery/static/src/js/website_sale_delivery.js"></script>
</xpath>
<xpath expr="//div[@id='js_payment']" position="before">
<xpath expr="//div[@id='payment_method']" position="before">
<t t-if="len(deliveries)">
<p>Choose Your Delivery method:</p>