-
Darken
-
Background
+
+
+
-
-
Green
-
Background
+
+
+
-
-
-
-
Custom Picture...
-
Background
+
+
+
+
diff --git a/addons/website_blog/static/src/js/website.tour.blog.js b/addons/website_blog/static/src/js/website.tour.blog.js
index 89e1eabc568..d43ac9dfd0c 100644
--- a/addons/website_blog/static/src/js/website.tour.blog.js
+++ b/addons/website_blog/static/src/js/website.tour.blog.js
@@ -3,8 +3,6 @@
var website = openerp.website;
- var render = website.tour.render;
-
website.EditorBar.include({
start: function () {
this.registerTour(new website.BlogTour(this));
@@ -15,7 +13,6 @@
website.BlogTour = website.Tour.extend({
id: 'blog-tutorial',
name: "Create a blog post",
- startPath: '/blog/cat/1/',
init: function (editor) {
var self = this;
self.steps = [
@@ -25,7 +22,7 @@
backdrop: true,
title: "Blog",
content: "We will show how to create a new blog post.",
- template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }),
+ template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
},
{
stepId: 'content-menu',
@@ -34,7 +31,6 @@
reflex: true,
title: "Edit the content",
content: "Click here to add content to your site.",
- template: render('website.tour_popover'),
},
{
stepId: 'new-post-entry',
@@ -42,8 +38,7 @@
placement: 'left',
title: "New blog post",
content: "Click here to create a blog post.",
- template: render('website.tour_popover'),
- onShow: function () {
+ triggers: function () {
$(document).one('shown.bs.modal', function () {
$('.modal button.btn-primary').click(function () {
self.movetoStep('post-page');
@@ -58,7 +53,22 @@
placement: 'right',
title: "Choose the post category",
content: "Select the 'News' category and click 'Continue'.",
- template: render('website.tour_popover'),
+ triggers: function () {
+ $('.modal select').change(function () {
+ var $this = $(this);
+ var selected = $this.find("[value="+$this.val()+"]").text();
+ if (selected.toLowerCase() === 'news') {
+ self.movetoStep('continue-category');
+ }
+ });
+ },
+ },
+ {
+ stepId: 'continue-category',
+ element: '.modal button.btn-primary',
+ placement: 'right',
+ title: "Choose the post category",
+ content: "Click 'Continue' to create the post.",
},
{
stepId: 'post-page',
@@ -66,7 +76,7 @@
backdrop: true,
title: "New blog post created",
content: "You just created a new blog post. We are now going to edit it.",
- template: render('website.tour_popover', { next: "OK" }),
+ template: self.popover({ next: "OK" }),
},
{
stepId: 'post-title',
@@ -74,16 +84,15 @@
placement: 'top',
title: "Pick a title",
content: "Choose a catchy title for your blog post.",
- template: render('website.tour_popover', { next: "OK" }),
+ template: self.popover({ next: "OK" }),
},
{
- stepId: 'add-block',
+ stepId: 'add-image-block',
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.",
- template: render('website.tour_popover'),
- onShow: function () {
+ triggers: function () {
$('button[data-action=snippet]').click(function () {
self.movetoStep('drag-image-text');
});
@@ -95,30 +104,76 @@
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the 'Image Text' block and drop it in your page.",
- template: render('website.tour_popover'),
- onShow: function () {
- var $body = $(document.body);
- function beginDrag () {
- $('.popover.tour').remove();
- function goToNextStep () {
- $('#snippets').toggle();
- self.stop();
- $body.off('mouseup', goToNextStep);
- }
- $body.off('mousedown', beginDrag);
- $body.on('mouseup', goToNextStep);
- }
- $body.on('mousedown', beginDrag);
+ triggers: function () {
+ self.onSnippetDraggedMoveTo('add-text-block');
},
},
+ {
+ stepId: 'add-text-block',
+ element: 'button[data-action=snippet]',
+ placement: 'bottom',
+ title: "Add another block",
+ content: "Let's add another blog to your post.",
+ triggers: function () {
+ $('button[data-action=snippet]').click(function () {
+ self.movetoStep('drag-text-block');
+ });
+ }
+ },
+ {
+ stepId: 'drag-text-block',
+ 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.",
+ triggers: function () {
+ self.onSnippetDraggedMoveTo('activate-text-block-title');
+ },
+ },
+ {
+ 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.",
+ triggers: function () {
+ $('#wrap [data-snippet-id=text-block] .text-center[data-snippet-id=colmd]').click(function () {
+ self.movetoStep('remove-text-block-title');
+ });
+ },
+ },
+ {
+ stepId: 'remove-text-block-title',
+ 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.",
+ },
+ {
+ 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.",
+ },
];
return this._super();
},
- continueTour: function () {
+ resume: function () {
return this.isCurrentStep('post-page') && !this.tour.ended();
},
- isTriggerUrl: function () {
- return (this.continueTour() && this.testUrl(/^\/blog\/[0-9]+\/\?enable_editor=1/)) || this._super();
+ trigger: function () {
+ return (this.resume() && this.testUrl(/^\/blog\/[0-9]+\/\?enable_editor=1/)) || this._super();
},
});
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index 24ef7f3cea9..a2ac7314c9d 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -36,7 +36,7 @@ from openerp import tools
import urllib
class website_event(http.Controller):
- @website.route(['/event/', '/event/page/
/'], type='http', auth="public", multilang=True)
+ @website.route(['/event/', '/event/page/'], type='http', auth="public", multilang=True)
def events(self, page=1, **searches):
cr, uid, context = request.cr, request.uid, request.context
event_obj = request.registry['event.event']
@@ -163,20 +163,30 @@ class website_event(http.Controller):
return request.website.render("website_event.index", values)
- @website.route(['/event/'], type='http', auth="public", multilang=True)
- def event(self, event_id=None, **post):
- event_obj = request.registry['event.event']
- event = event_obj.browse(request.cr, request.uid, event_id, dict(request.context, show_address_only=1))
+ @website.route(['/event//page/'], type='http', auth="public", multilang=True)
+ def event_page(self, event, page, **post):
values = {
- 'event_id': event,
- 'main_object': event,
+ 'event': event,
+ }
+ return request.website.render(page, values)
+
+ @website.route(['/event/'], type='http', auth="public", multilang=True)
+ 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))
+
+ @website.route(['/event/register/'], type='http', auth="public", multilang=True)
+ def event_register(self, event=None, **post):
+ values = {
+ 'event': event,
'range': range,
- 'float': float,
}
return request.website.render("website_event.event_description_full", values)
- @website.route(['/event//add_cart'], type='http', auth="public", multilang=True)
+ @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'
user_obj = request.registry['res.users']
order_line_obj = request.registry.get('sale.order.line')
ticket_obj = request.registry.get('event.event.ticket')
diff --git a/addons/website_event/data/event_data.xml b/addons/website_event/data/event_data.xml
index d2e717ebb3e..92bed149032 100644
--- a/addons/website_event/data/event_data.xml
+++ b/addons/website_event/data/event_data.xml
@@ -18,20 +18,5 @@
open
-
-
-
-
diff --git a/addons/website_event/data/event_demo.xml b/addons/website_event/data/event_demo.xml
index 4da5b931d32..a08ca75e952 100644
--- a/addons/website_event/data/event_demo.xml
+++ b/addons/website_event/data/event_demo.xml
@@ -5,6 +5,7 @@
True
+
True
openerp
diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event.py
index b405c73b291..7220f416d80 100644
--- a/addons/website_event/models/event.py
+++ b/addons/website_event/models/event.py
@@ -22,6 +22,8 @@
from openerp.osv import osv, fields
from openerp import SUPERUSER_ID
+from openerp.tools.translate import _
+
# defined for access rules
class product(osv.osv):
@@ -35,6 +37,51 @@ class event(osv.osv):
_name = 'event.event'
_inherit = ['event.event','website.seo.metadata']
+ def _get_new_menu_pages(self, cr, uid, event, context=None):
+ context = context or {}
+ todo = [
+ (_('Introduction'), 'website_event.template_intro'),
+ (_('Location'), 'website_event.template_location')
+ ]
+ web = self.pool.get('website')
+ result = []
+ for name,path in todo:
+ name2 = name+' '+event.name
+ newpath = web.new_page(cr, uid, name2, path, ispage=False, context=context)
+ url = "/event/"+str(event.id)+"/page/" + newpath
+ result.append((name, url))
+ return result
+
+ def _set_show_menu(self, cr, uid, ids, name, value, arg, context=None):
+ menuobj = self.pool.get('website.menu')
+ eventobj = self.pool.get('event.event')
+ for event in self.browse(cr, uid, [ids], context=context):
+ if event.menu_id and not value:
+ menuobj.unlink(cr, uid, [event.menu_id.id], context=context)
+ elif value and not event.menu_id:
+ root = menuobj.create(cr, uid, {
+ 'name': event.name
+ }, context=context)
+ tocreate = self._get_new_menu_pages(cr, uid, event, context)
+ tocreate.append((_('Register'), '/event/register/'+str(event.id)))
+ sequence = 0
+ for name,url in tocreate:
+ menuobj.create(cr, uid, {
+ 'name': name,
+ 'url': url,
+ 'parent_id': root,
+ 'sequence': sequence
+ }, context=context)
+ sequence += 1
+ eventobj.write(cr, uid, [event.id], {'menu_id': root}, context=context)
+ return True
+
+ def _get_show_menu(self, cr, uid, ids, field_name, arg, context=None):
+ res = dict.fromkeys(ids, '')
+ for event in self.browse(cr, uid, ids, context=context):
+ res[event.id] = bool(event.menu_id)
+ return res
+
def _website_url(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, '')
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
@@ -44,7 +91,7 @@ class event(osv.osv):
_columns = {
'twitter_hashtag': fields.char('Twitter Hashtag'),
- 'website_published': fields.boolean('Available in the website'),
+ 'website_published': fields.boolean('Visible in Website'),
# TDE TODO FIXME: when website_mail/mail_thread.py inheritance work -> this field won't be necessary
'website_message_ids': fields.one2many(
'mail.message', 'res_id',
@@ -55,9 +102,11 @@ class event(osv.osv):
help="Website communication history",
),
'website_url': fields.function(_website_url, string="Website url"),
+ 'show_menu': fields.function(_get_show_menu, fnct_inv=_set_show_menu, type='boolean', string='Dedicated Menu'),
+ 'menu_id': fields.many2one('website.menu', 'Event Menu'),
}
_defaults = {
- 'website_published': False,
+ 'show_menu': False,
}
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
diff --git a/addons/website_event/static/src/js/website.tour.event.js b/addons/website_event/static/src/js/website.tour.event.js
index 8bbd470d1e6..a724b024813 100644
--- a/addons/website_event/static/src/js/website.tour.event.js
+++ b/addons/website_event/static/src/js/website.tour.event.js
@@ -3,8 +3,6 @@
var website = openerp.website;
- var render = website.tour.render;
-
website.EditorBar.include({
start: function () {
this.registerTour(new website.EventTour(this));
@@ -15,7 +13,6 @@
website.EventTour = website.Tour.extend({
id: 'event-tutorial',
name: "Create an event",
- startPath: '/event',
init: function (editor) {
var self = this;
self.steps = [
@@ -25,7 +22,7 @@
backdrop: true,
title: "Event",
content: "We will show how to create a new event.",
- template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }),
+ template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
},
{
stepId: 'content-menu',
@@ -34,7 +31,6 @@
reflex: true,
title: "Edit the content",
content: "Click here to add content to your site.",
- template: render('website.tour_popover'),
},
{
stepId: 'new-post-entry',
@@ -42,8 +38,7 @@
placement: 'left',
title: "New event",
content: "Click here to create an event.",
- template: render('website.tour_popover'),
- onShow: function () {
+ triggers: function () {
$(document).one('shown.bs.modal', function () {
$('.modal button.btn-primary').click(function () {
self.movetoStep('event-page');
@@ -58,7 +53,6 @@
placement: 'right',
title: "Choose the event name",
content: "Choose a name for the new event and click 'Continue'.",
- template: render('website.tour_popover'),
},
{
stepId: 'event-page',
@@ -66,7 +60,7 @@
backdrop: true,
title: "New event created",
content: "You just created a new event. We are now going to edit it.",
- template: render('website.tour_popover', { next: "OK" }),
+ template: self.popover({ next: "OK" }),
},
{
stepId: 'add-block',
@@ -75,16 +69,15 @@
reflex: true,
title: "Layout your event",
content: "Insert blocks like text-image to layout the body of your event.",
- template: render('website.tour_popover'),
},
];
return this._super();
},
- continueTour: function () {
+ resume: function () {
return this.isCurrentStep('event-page') && !this.tour.ended();
},
- isTriggerUrl: function () {
- return (this.continueTour() && this.testUrl(/^\/event\/[0-9]+\/\?enable_editor=1/)) || this._super();
+ trigger: function () {
+ return (this.resume() && this.testUrl(/^\/event\/[0-9]+\/\?enable_editor=1/)) || this._super();
},
});
diff --git a/addons/website_event/views/website_event.xml b/addons/website_event/views/website_event.xml
index fbb75e49736..4f13491f59a 100644
--- a/addons/website_event/views/website_event.xml
+++ b/addons/website_event/views/website_event.xml
@@ -72,7 +72,7 @@
@@ -165,48 +165,78 @@
-
+
- Events
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- to
-
+ to
+
-
+
- ,
-
+ ,
+
-
-
-
+
-
+
+
diff --git a/addons/website_event_track/__init__.py b/addons/website_event_track/__init__.py
new file mode 100644
index 00000000000..708d6ce4669
--- /dev/null
+++ b/addons/website_event_track/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA (
).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import controllers
+import models
diff --git a/addons/website_event_track/__openerp__.py b/addons/website_event_track/__openerp__.py
new file mode 100644
index 00000000000..546fb7f562c
--- /dev/null
+++ b/addons/website_event_track/__openerp__.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+{
+ 'name': 'Tracks and Agenda of Events',
+ 'category': 'Website',
+ 'summary': 'Organize Your Events',
+ 'version': '1.0',
+ 'description': """
+Online Events
+=============
+
+ """,
+ 'author': 'OpenERP SA',
+ 'depends': ['website_event', 'website_blog'],
+ 'data': [
+ 'data/event_view.xml',
+ 'data/event_data.xml',
+ 'views/website_event.xml',
+ 'security/ir.model.access.csv',
+ 'security/event.xml',
+ ],
+ 'qweb': ['static/src/xml/*.xml'],
+ 'demo': [
+ 'data/event_demo.xml'
+ ],
+ 'installable': True,
+}
diff --git a/addons/website_event_track/controllers/__init__.py b/addons/website_event_track/controllers/__init__.py
new file mode 100644
index 00000000000..1b1d9d596fa
--- /dev/null
+++ b/addons/website_event_track/controllers/__init__.py
@@ -0,0 +1,2 @@
+import event
+
diff --git a/addons/website_event_track/controllers/event.py b/addons/website_event_track/controllers/event.py
new file mode 100644
index 00000000000..c40625783c5
--- /dev/null
+++ b/addons/website_event_track/controllers/event.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+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/'], type='http', auth="public", multilang=True)
+ def event_track_view(self, 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/'], type='http', auth="public", multilang=True)
+ def event_tracks(self, event, tag=None, **post):
+ # TODO: filter on tracks: tags, search keywords
+ values = {
+ 'event': event,
+ 'tracks': event.track_ids,
+ 'tags': event.track_tag_ids,
+ 'searches': {}
+ }
+ print 'ICI'
+ return request.website.render("website_event_track.tracks", values)
+
+ @website.route(['/event/detail/'], type='http', auth="public", multilang=True)
+ def event_detail(self, event, **post):
+ values = { 'event': event }
+ return request.website.render("website_event_track.event_home", values)
+
+ @website.route(['/event/track_proposal/'], type='http', auth="public", multilang=True)
+ def event_detail(self, event=None, **post):
+ values = { 'event': event }
+ return request.website.render("website_event_track.event_track_proposal", values)
+
diff --git a/addons/website_event_track/data/event_data.xml b/addons/website_event_track/data/event_data.xml
new file mode 100644
index 00000000000..fe681518b55
--- /dev/null
+++ b/addons/website_event_track/data/event_data.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Proposals
+
+
+ Confirmed
+
+
+ Published
+
+
+ Announced
+
+
+ Completed
+
+
+
+
diff --git a/addons/website_event_track/data/event_demo.xml b/addons/website_event_track/data/event_demo.xml
new file mode 100644
index 00000000000..34b2da55e29
--- /dev/null
+++ b/addons/website_event_track/data/event_demo.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+ Webinar
+
+
+ Exhibition
+
+
+ Conference
+
+
+
+ Technical
+
+
+ Business
+
+
+ Lightning Talks
+
+
+ Round Table
+
+
+
+ Room 1
+
+
+ Room 2
+
+
+ Room 3
+
+
+ Room 4
+
+
+
+
+ True
+ True
+ True
+
+
+ True
+
+
+
+
diff --git a/addons/website_event_track/data/event_view.xml b/addons/website_event_track/data/event_view.xml
new file mode 100644
index 00000000000..a2b930e4a8d
--- /dev/null
+++ b/addons/website_event_track/data/event_view.xml
@@ -0,0 +1,290 @@
+
+
+
+
+
+ event.track
+ Event Tracks
+ form
+ kanban,tree,form,calendar,graph
+ {'search_default_event_id': active_id, 'default_event_id': active_id, 'group_by': 'stage_id'}
+
+
+ Click to add a track.
+
+ Tracks define the agenda of your event. These can be
+ a talk, a round table, a meeting, etc.
+
+
+
+
+
+
+
+ Event Locations
+ event.track.location
+
+
+
+
+
+ Event Location
+ event.track.location
+
+
+
+
+
+
+
+ Event Locations
+ event.track.location
+
+
+
+
+
+ Event Stages
+ event.track.stage
+
+
+
+
+
+ Event Stage
+ event.track.stage
+
+
+
+
+
+
+
+
+ Event Stages
+ event.track.stage
+
+
+
+
+
+ Track Tags
+ event.track.tag
+
+
+
+
+
+ Tracks Tag
+ event.track.tag
+
+
+
+
+
+
+
+ Track Tags
+ event.track.tag
+
+
+
+
+
+
+ Event Tags
+ event.tag
+
+
+
+
+
+ Event Tag
+ event.tag
+
+
+
+
+
+
+
+ Event Tags
+ event.tag
+
+
+
+
+
+
+ Event Tracks
+
+ event.event
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ event.track.kanban
+ event.track
+
+
+
+
+
+
+
+
+
+ í
+
+
+
+
+
+
hours
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+ event.track.calendar
+ event.track
+
+
+
+
+
+
+
+
+
+ Event Tracks
+ event.track
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ event.track.form
+ event.track
+
+
+
+
+
+
+ Tracks Graph
+ event.track
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_event_track/models/__init__.py b/addons/website_event_track/models/__init__.py
new file mode 100644
index 00000000000..e7c003730cf
--- /dev/null
+++ b/addons/website_event_track/models/__init__.py
@@ -0,0 +1 @@
+import event
diff --git a/addons/website_event_track/models/event.py b/addons/website_event_track/models/event.py
new file mode 100644
index 00000000000..bd071d63e61
--- /dev/null
+++ b/addons/website_event_track/models/event.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+
+class event_track_tag(osv.osv):
+ _name = "event.track.tag"
+ _columns = {
+ 'name': fields.char('Event Track Tag')
+ }
+
+class event_tag(osv.osv):
+ _name = "event.tag"
+ _columns = {
+ 'name': fields.char('Event Tag')
+ }
+
+#
+# Tracks: conferences
+#
+
+class event_track_stage(osv.osv):
+ _name = "event.track.stage"
+ _order = 'sequence'
+ _columns = {
+ 'name': fields.char('Track Stage'),
+ 'sequence': fields.integer('Sequence')
+ }
+ _defaults = {
+ 'sequence': 0
+ }
+
+
+class event_track_location(osv.osv):
+ _name = "event.track.location"
+ _columns = {
+ 'name': fields.char('Track Rooms')
+ }
+
+class event_track(osv.osv):
+ _name = "event.track"
+ _order = 'date'
+ _inherit = ['mail.thread', 'ir.needaction_mixin']
+ _columns = {
+ 'name': fields.char('Track Title', required=True),
+ 'user_id': fields.many2one('res.users', 'Responsible'),
+ 'speaker_ids': fields.many2many('res.partner', string='Speakers'),
+ 'tag_ids': fields.many2many('event.track.tag', string='Tags'),
+ 'stage_id': fields.many2one('event.track.stage'),
+ 'description': fields.html('Track Description'),
+ 'date': fields.datetime('Track Date'),
+ 'duration': fields.float('Duration (Hours)'),
+ 'location_id': fields.many2one('event.track.location', 'Location'),
+ 'show_attachments': fields.boolean('Show Documents'),
+ 'event_id': fields.many2one('event.event', 'Event', required=True),
+ 'color': fields.integer('Color Index'),
+ 'priority': fields.selection([('3','Low'),('2','Medium (*)'),('1','High (**)'),('0','Highest (***)')], 'Priority', required=True),
+ }
+ def set_priority(self, cr, uid, ids, priority, context={}):
+ return self.write(cr, uid, ids, {'priority' : priority})
+
+ def _default_stage_id(self, cr, uid, context={}):
+ stage_obj = self.pool.get('event.track.stage')
+ ids = stage_obj.search(cr, uid, [], context=context)
+ return ids and ids[0] or False
+
+ _defaults = {
+ 'user_id': lambda self, cr, uid, ctx: uid,
+ 'show_attachments': lambda self, cr, uid, ctx: True,
+ 'duration': lambda *args: 0,
+ 'stage_id': _default_stage_id,
+ 'priority': '2'
+ }
+ def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
+ stage_obj = self.pool.get('event.track.stage')
+ result = stage_obj.name_search(cr, uid, '', context=context)
+ return result, {}
+
+ _group_by_full = {
+ 'stage_id': _read_group_stage_ids,
+ }
+
+#
+# Events
+#
+
+class event_event(osv.osv):
+ _inherit = "event.event"
+ _columns = {
+ 'tag_ids': fields.many2many('event.tag', string='Tags'),
+ 'track_ids': fields.one2many('event.track', 'event_id', 'Tracks'),
+ 'blog_id': fields.many2one('blog.category', 'Event Blog'),
+ 'show_track_proposal': fields.boolean('Talks Proposals'),
+ 'show_tracks': fields.boolean('Multiple Tracks'),
+ 'show_blog': fields.boolean('News'),
+ 'track_tag_ids': fields.many2many('event.track.tag', string='Accepted Tracks'),
+ }
+ _defaults = {
+ 'show_track_proposal': False,
+ 'show_tracks': False,
+ 'show_blog': False,
+ }
+ def _get_new_menu_pages(self, cr, uid, event, context=None):
+ 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)))
+ 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)))
+ return result
+
diff --git a/addons/website_event_track/security/event.xml b/addons/website_event_track/security/event.xml
new file mode 100644
index 00000000000..1aa62809cd4
--- /dev/null
+++ b/addons/website_event_track/security/event.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ event tracks: Public
+
+ [('website_published', '=', True)]
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_event_track/security/ir.model.access.csv b/addons/website_event_track/security/ir.model.access.csv
new file mode 100644
index 00000000000..11e63faffb3
--- /dev/null
+++ b/addons/website_event_track/security/ir.model.access.csv
@@ -0,0 +1,4 @@
+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
diff --git a/addons/website_event_track/views/website_event.xml b/addons/website_event_track/views/website_event.xml
new file mode 100644
index 00000000000..82e8c4538eb
--- /dev/null
+++ b/addons/website_event_track/views/website_event.xml
@@ -0,0 +1,225 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /event/publish
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Not Implmented
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We will accept a broad range of
+ presentations, from reports on academic and
+ commercial projects to tutorials and case
+ studies. As long as the presentation is
+ interesting and potentially useful to the
+ audience, it will be considered for
+ inclusion in the programme.
+
+
+
+
+
+ Fill this form to propose your talk.
+
+
+
+
+
+
+
+
+
+
Talks Types
+
+
+
+ -
+ Regular Talks. These are standard talks with slides,
+ alocated in slots of 30 minutes.
+
-
+ Lightning Talks. These are 10 minutes talks on many
+ different topics. Most topics are accepted in lightning talks.
+
+
+
+
+
+
+
+
+
Submission Agreement
+
+
+
+ We require speakers to accept an agreement in which they commit to:
+
+
+ -
+ Timely release of presentation material (slides),
+ for publishing on our website.
+
-
+ Allow video and audio recording of their
+ presentation, for publishing on our website.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_membership/controllers/main.py b/addons/website_membership/controllers/main.py
index 74b719c9e57..e2b201bb855 100644
--- a/addons/website_membership/controllers/main.py
+++ b/addons/website_membership/controllers/main.py
@@ -27,9 +27,9 @@ class WebsiteMembership(http.Controller):
post_country_id = int(post.get('country_id', '0'))
# base domain for groupby / searches
- base_line_domain = [(1, '=', 1)]
+ base_line_domain = []
if membership_id:
- base_line_domain += [('membership_id', '=', membership_id)]
+ base_line_domain.append(('membership_id', '=', membership_id))
membership = product_obj.browse(cr, uid, membership_id, context=context)
else:
membership = None
@@ -41,7 +41,7 @@ class WebsiteMembership(http.Controller):
countries = partner_obj.read_group(
cr, uid, [('member_lines', 'in', membership_line_ids)], ["id", "country_id"],
groupby="country_id", orderby="country_id", context=request.context)
- countries_total = sum([country_dict['country_id_count'] for country_dict in countries])
+ countries_total = sum(country_dict['country_id_count'] for country_dict in countries)
countries.insert(0, {
'country_id_count': countries_total,
'country_id': (0, _("All Countries"))
@@ -50,16 +50,15 @@ class WebsiteMembership(http.Controller):
# displayed membership lines
line_domain = list(base_line_domain)
if post_country_id:
- line_domain += [('partner.country_id', '=', post_country_id)]
+ line_domain.append(('partner.country_id', '=', post_country_id))
membership_line_ids = membership_line_obj.search(cr, uid, line_domain, context=context)
membership_lines = membership_line_obj.browse(cr, uid, membership_line_ids, context=context)
partner_ids = [m.partner and m.partner.id for m in membership_lines]
- google_map_partner_ids = ",".join([str(pid) for pid in partner_ids])
+ google_map_partner_ids = ",".join(map(str, partner_ids))
# format domain for group_by and memberships
- membership_domain = [('membership', '=', True)]
- membership_ids = product_obj.search(cr, uid, membership_domain, context=context)
+ membership_ids = product_obj.search(cr, uid, [('membership', '=', True)], context=context)
memberships = product_obj.browse(cr, uid, membership_ids, context=context)
# request pager for lines
@@ -77,16 +76,14 @@ class WebsiteMembership(http.Controller):
}
return request.website.render("website_membership.index", values)
- @website.route(['/members//'], type='http', auth="public", multilang=True)
- def partners_ref(self, partner_id=0, **post):
- partner_obj = request.registry['res.partner']
- partner_ids = partner_obj.search(request.cr, request.uid, [('id', '=', partner_id)], context=request.context)
- if not partner_ids:
- return self.members(post)
+ @website.route(['/members//'], type='http', auth="public", multilang=True)
+ def partners_ref(self, partner, **post):
+ if not partner.exists():
+ return self.members(**post)
values = {
- 'partner_id': partner_obj.browse(
- request.cr, request.uid, partner_ids[0],
+ 'partner_id': request.registry['res.partner'].browse(
+ request.cr, request.uid, partner.id,
context=dict(request.context, show_address=True)),
}
return request.website.render("website_membership.partner", values)
diff --git a/addons/website_payment/__init__.py b/addons/website_payment/__init__.py
new file mode 100644
index 00000000000..edccdf73182
--- /dev/null
+++ b/addons/website_payment/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import controllers
diff --git a/addons/website_payment/__openerp__.py b/addons/website_payment/__openerp__.py
new file mode 100644
index 00000000000..2a01d2f1aec
--- /dev/null
+++ b/addons/website_payment/__openerp__.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+{
+ 'name': 'Payment: Website Integration (Test Module)',
+ 'category': 'Website',
+ 'summary': 'Payment: Website Integration (Test Module)',
+ 'version': '1.0',
+ 'description': """Module installing all sub-payment modules and adding some
+ controllers and menu entries in order to test them.""",
+ 'author': 'OpenERP SA',
+ 'depends': [
+ 'website',
+ 'payment_acquirer',
+ 'payment_acquirer_ogone',
+ 'payment_acquirer_paypal',
+ 'payment_acquirer_transfer',
+ ],
+ 'data': [
+ 'views/website_payment_templates.xml',
+ ],
+ 'installable': True,
+}
diff --git a/addons/website_payment/controllers/__init__.py b/addons/website_payment/controllers/__init__.py
new file mode 100644
index 00000000000..8ee9bae18d9
--- /dev/null
+++ b/addons/website_payment/controllers/__init__.py
@@ -0,0 +1 @@
+import main
diff --git a/addons/website_payment/controllers/main.py b/addons/website_payment/controllers/main.py
new file mode 100644
index 00000000000..4a971975d97
--- /dev/null
+++ b/addons/website_payment/controllers/main.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.web import http
+from openerp.addons.web.http import request
+from openerp.addons.website.models import website
+
+
+class WebsitePayment(http.Controller):
+
+ @website.route([
+ '/payment/paypal/test',
+ ], type='http', auth="public")
+ def paypal_test(self, **post):
+ """ TODO
+ """
+ cr, uid, context = request.cr, request.uid, request.context
+ acquirer_obj = request.registry['payment.acquirer']
+ payment_obj = request.registry['payment.transaction']
+ currency_obj = request.registry['res.currency']
+
+ paypal_id = acquirer_obj.search(cr, uid, [('name', '=', 'paypal')], limit=1, context=context)[0]
+ currency_id = currency_obj.search(cr, uid, [('name', '=', 'EUR')], limit=1, context=context)[0]
+
+ nbr_tx = payment_obj.search(cr, uid, [], count=True, context=context)
+ tx_id = payment_obj.create(cr, uid, {
+ 'reference': 'test_ref_%s' % (nbr_tx),
+ 'amount': 1.95,
+ 'currency_id': currency_id,
+ 'acquirer_id': paypal_id,
+ 'partner_name': 'Norbert Buyer',
+ 'partner_email': 'norbert.buyer@example.com',
+ 'partner_lang': 'fr_FR',
+ }, context=context)
+
+ paypal_form = acquirer_obj.render(cr, uid, paypal_id, None, None, None, tx_id=tx_id, context=context)
+ paypal = acquirer_obj.browse(cr, uid, paypal_id, context=context)
+
+ values = {
+ 'acquirer': paypal,
+ 'acquirer_form': paypal_form,
+ }
+ return request.website.render("website_payment.index_paypal", values)
+
+ @website.route([
+ '/payment/ogone/test',
+ ], type='http', auth="public")
+ def ogone_test(self, **post):
+ """ TODO
+ """
+ cr, uid, context = request.cr, request.uid, request.context
+ acquirer_obj = request.registry['payment.acquirer']
+ payment_obj = request.registry['payment.transaction']
+ currency_obj = request.registry['res.currency']
+
+ ogone_id = acquirer_obj.search(cr, uid, [('name', '=', 'ogone')], limit=1, context=context)[0]
+ currency_id = currency_obj.search(cr, uid, [('name', '=', 'EUR')], limit=1, context=context)[0]
+
+ nbr_tx = payment_obj.search(cr, uid, [], count=True, context=context)
+ tx_id = payment_obj.create(cr, uid, {
+ 'reference': 'test_ref_%s' % (nbr_tx),
+ 'amount': 1.95,
+ 'currency_id': currency_id,
+ 'acquirer_id': ogone_id,
+ 'partner_name': 'Norbert Buyer',
+ 'partner_email': 'norbert.buyer@example.com',
+ 'partner_lang': 'fr_FR',
+ }, context=context)
+
+ ogone_form = acquirer_obj.render(cr, uid, ogone_id, None, None, None, tx_id=tx_id, context=context)
+ ogone = acquirer_obj.browse(cr, uid, ogone_id, context=context)
+
+ values = {
+ 'acquirer': ogone,
+ 'acquirer_form': ogone_form,
+ }
+ return request.website.render("website_payment.index_ogone", values)
diff --git a/addons/website_payment/doc/changelog.rst b/addons/website_payment/doc/changelog.rst
new file mode 100644
index 00000000000..8b6131b5806
--- /dev/null
+++ b/addons/website_payment/doc/changelog.rst
@@ -0,0 +1,9 @@
+.. _changelog:
+
+Changelog
+=========
+
+`trunk (saas-3)`
+----------------
+
+ - created ``website_blog`` menu, build on defunct document_page module.
diff --git a/addons/website_payment/doc/index.rst b/addons/website_payment/doc/index.rst
new file mode 100644
index 00000000000..953748fd48d
--- /dev/null
+++ b/addons/website_payment/doc/index.rst
@@ -0,0 +1,10 @@
+Blog Module documentation topics
+''''''''''''''''''''''''''''''''
+
+Changelog
+'''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ changelog.rst
diff --git a/addons/website_payment/static/description/icon.png b/addons/website_payment/static/description/icon.png
new file mode 100644
index 00000000000..51e33ac6ddf
Binary files /dev/null and b/addons/website_payment/static/description/icon.png differ
diff --git a/addons/website_payment/static/lib/jquery.payment/jquery.payment.js b/addons/website_payment/static/lib/jquery.payment/jquery.payment.js
new file mode 100644
index 00000000000..c81c6e00d81
--- /dev/null
+++ b/addons/website_payment/static/lib/jquery.payment/jquery.payment.js
@@ -0,0 +1,497 @@
+// Generated by CoffeeScript 1.4.0
+(function() {
+ var $, cardFromNumber, cardFromType, cards, defaultFormat, formatBackCardNumber, formatBackExpiry, formatCardNumber, formatExpiry, formatForwardExpiry, formatForwardSlash, hasTextSelected, luhnCheck, reFormatCardNumber, restrictCVC, restrictCardNumber, restrictExpiry, restrictNumeric, setCardType,
+ __slice = [].slice,
+ __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+ _this = this;
+
+ $ = jQuery;
+
+ $.payment = {};
+
+ $.payment.fn = {};
+
+ $.fn.payment = function() {
+ var args, method;
+ method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+ return $.payment.fn[method].apply(this, args);
+ };
+
+ defaultFormat = /(\d{1,4})/g;
+
+ cards = [
+ {
+ type: 'maestro',
+ pattern: /^(5018|5020|5038|6304|6759|676[1-3])/,
+ format: defaultFormat,
+ length: [12, 13, 14, 15, 16, 17, 18, 19],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'dinersclub',
+ pattern: /^(36|38|30[0-5])/,
+ format: defaultFormat,
+ length: [14],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'laser',
+ pattern: /^(6706|6771|6709)/,
+ format: defaultFormat,
+ length: [16, 17, 18, 19],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'jcb',
+ pattern: /^35/,
+ format: defaultFormat,
+ length: [16],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'unionpay',
+ pattern: /^62/,
+ format: defaultFormat,
+ length: [16, 17, 18, 19],
+ cvcLength: [3],
+ luhn: false
+ }, {
+ type: 'discover',
+ pattern: /^(6011|65|64[4-9]|622)/,
+ format: defaultFormat,
+ length: [16],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'mastercard',
+ pattern: /^5[1-5]/,
+ format: defaultFormat,
+ length: [16],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'amex',
+ pattern: /^3[47]/,
+ format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/,
+ length: [15],
+ cvcLength: [3, 4],
+ luhn: true
+ }, {
+ type: 'visa',
+ pattern: /^4/,
+ format: defaultFormat,
+ length: [13, 14, 15, 16],
+ cvcLength: [3],
+ luhn: true
+ }
+ ];
+
+ cardFromNumber = function(num) {
+ var card, _i, _len;
+ num = (num + '').replace(/\D/g, '');
+ for (_i = 0, _len = cards.length; _i < _len; _i++) {
+ card = cards[_i];
+ if (card.pattern.test(num)) {
+ return card;
+ }
+ }
+ };
+
+ cardFromType = function(type) {
+ var card, _i, _len;
+ for (_i = 0, _len = cards.length; _i < _len; _i++) {
+ card = cards[_i];
+ if (card.type === type) {
+ return card;
+ }
+ }
+ };
+
+ luhnCheck = function(num) {
+ var digit, digits, odd, sum, _i, _len;
+ odd = true;
+ sum = 0;
+ digits = (num + '').split('').reverse();
+ for (_i = 0, _len = digits.length; _i < _len; _i++) {
+ digit = digits[_i];
+ digit = parseInt(digit, 10);
+ if ((odd = !odd)) {
+ digit *= 2;
+ }
+ if (digit > 9) {
+ digit -= 9;
+ }
+ sum += digit;
+ }
+ return sum % 10 === 0;
+ };
+
+ hasTextSelected = function($target) {
+ var _ref;
+ if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== $target.prop('selectionEnd')) {
+ return true;
+ }
+ if (typeof document !== "undefined" && document !== null ? (_ref = document.selection) != null ? typeof _ref.createRange === "function" ? _ref.createRange().text : void 0 : void 0 : void 0) {
+ return true;
+ }
+ return false;
+ };
+
+ reFormatCardNumber = function(e) {
+ var _this = this;
+ return setTimeout(function() {
+ var $target, value;
+ $target = $(e.currentTarget);
+ value = $target.val();
+ value = $.payment.formatCardNumber(value);
+ return $target.val(value);
+ });
+ };
+
+ formatCardNumber = function(e) {
+ var $target, card, digit, length, re, upperLength, value;
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ $target = $(e.currentTarget);
+ value = $target.val();
+ card = cardFromNumber(value + digit);
+ length = (value.replace(/\D/g, '') + digit).length;
+ upperLength = 16;
+ if (card) {
+ upperLength = card.length[card.length.length - 1];
+ }
+ if (length >= upperLength) {
+ return;
+ }
+ if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
+ return;
+ }
+ if (card && card.type === 'amex') {
+ re = /^(\d{4}|\d{4}\s\d{6})$/;
+ } else {
+ re = /(?:^|\s)(\d{4})$/;
+ }
+ if (re.test(value)) {
+ e.preventDefault();
+ return $target.val(value + ' ' + digit);
+ } else if (re.test(value + digit)) {
+ e.preventDefault();
+ return $target.val(value + digit + ' ');
+ }
+ };
+
+ formatBackCardNumber = function(e) {
+ var $target, value;
+ $target = $(e.currentTarget);
+ value = $target.val();
+ if (e.meta) {
+ return;
+ }
+ if (e.which !== 8) {
+ return;
+ }
+ if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
+ return;
+ }
+ if (/\d\s$/.test(value)) {
+ e.preventDefault();
+ return $target.val(value.replace(/\d\s$/, ''));
+ } else if (/\s\d?$/.test(value)) {
+ e.preventDefault();
+ return $target.val(value.replace(/\s\d?$/, ''));
+ }
+ };
+
+ formatExpiry = function(e) {
+ var $target, digit, val;
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ $target = $(e.currentTarget);
+ val = $target.val() + digit;
+ if (/^\d$/.test(val) && (val !== '0' && val !== '1')) {
+ e.preventDefault();
+ return $target.val("0" + val + " / ");
+ } else if (/^\d\d$/.test(val)) {
+ e.preventDefault();
+ return $target.val("" + val + " / ");
+ }
+ };
+
+ formatForwardExpiry = function(e) {
+ var $target, digit, val;
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ $target = $(e.currentTarget);
+ val = $target.val();
+ if (/^\d\d$/.test(val)) {
+ return $target.val("" + val + " / ");
+ }
+ };
+
+ formatForwardSlash = function(e) {
+ var $target, slash, val;
+ slash = String.fromCharCode(e.which);
+ if (slash !== '/') {
+ return;
+ }
+ $target = $(e.currentTarget);
+ val = $target.val();
+ if (/^\d$/.test(val) && val !== '0') {
+ return $target.val("0" + val + " / ");
+ }
+ };
+
+ formatBackExpiry = function(e) {
+ var $target, value;
+ if (e.meta) {
+ return;
+ }
+ $target = $(e.currentTarget);
+ value = $target.val();
+ if (e.which !== 8) {
+ return;
+ }
+ if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
+ return;
+ }
+ if (/\d(\s|\/)+$/.test(value)) {
+ e.preventDefault();
+ return $target.val(value.replace(/\d(\s|\/)*$/, ''));
+ } else if (/\s\/\s?\d?$/.test(value)) {
+ e.preventDefault();
+ return $target.val(value.replace(/\s\/\s?\d?$/, ''));
+ }
+ };
+
+ restrictNumeric = function(e) {
+ var input;
+ if (e.metaKey || e.ctrlKey) {
+ return true;
+ }
+ if (e.which === 32) {
+ return false;
+ }
+ if (e.which === 0) {
+ return true;
+ }
+ if (e.which < 33) {
+ return true;
+ }
+ input = String.fromCharCode(e.which);
+ return !!/[\d\s]/.test(input);
+ };
+
+ restrictCardNumber = function(e) {
+ var $target, card, digit, value;
+ $target = $(e.currentTarget);
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ if (hasTextSelected($target)) {
+ return;
+ }
+ value = ($target.val() + digit).replace(/\D/g, '');
+ card = cardFromNumber(value);
+ if (card) {
+ return value.length <= card.length[card.length.length - 1];
+ } else {
+ return value.length <= 16;
+ }
+ };
+
+ restrictExpiry = function(e) {
+ var $target, digit, value;
+ $target = $(e.currentTarget);
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ if (hasTextSelected($target)) {
+ return;
+ }
+ value = $target.val() + digit;
+ value = value.replace(/\D/g, '');
+ if (value.length > 6) {
+ return false;
+ }
+ };
+
+ restrictCVC = function(e) {
+ var $target, digit, val;
+ $target = $(e.currentTarget);
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ val = $target.val() + digit;
+ return val.length <= 4;
+ };
+
+ setCardType = function(e) {
+ var $target, allTypes, card, cardType, val;
+ $target = $(e.currentTarget);
+ val = $target.val();
+ cardType = $.payment.cardType(val) || 'unknown';
+ if (!$target.hasClass(cardType)) {
+ allTypes = (function() {
+ var _i, _len, _results;
+ _results = [];
+ for (_i = 0, _len = cards.length; _i < _len; _i++) {
+ card = cards[_i];
+ _results.push(card.type);
+ }
+ return _results;
+ })();
+ $target.removeClass('unknown');
+ $target.removeClass(allTypes.join(' '));
+ $target.addClass(cardType);
+ $target.toggleClass('identified', cardType !== 'unknown');
+ return $target.trigger('payment.cardType', cardType);
+ }
+ };
+
+ $.payment.fn.formatCardCVC = function() {
+ this.payment('restrictNumeric');
+ this.on('keypress', restrictCVC);
+ return this;
+ };
+
+ $.payment.fn.formatCardExpiry = function() {
+ this.payment('restrictNumeric');
+ this.on('keypress', restrictExpiry);
+ this.on('keypress', formatExpiry);
+ this.on('keypress', formatForwardSlash);
+ this.on('keypress', formatForwardExpiry);
+ this.on('keydown', formatBackExpiry);
+ return this;
+ };
+
+ $.payment.fn.formatCardNumber = function() {
+ this.payment('restrictNumeric');
+ this.on('keypress', restrictCardNumber);
+ this.on('keypress', formatCardNumber);
+ this.on('keydown', formatBackCardNumber);
+ this.on('keyup', setCardType);
+ this.on('paste', reFormatCardNumber);
+ return this;
+ };
+
+ $.payment.fn.restrictNumeric = function() {
+ this.on('keypress', restrictNumeric);
+ return this;
+ };
+
+ $.payment.fn.cardExpiryVal = function() {
+ return $.payment.cardExpiryVal($(this).val());
+ };
+
+ $.payment.cardExpiryVal = function(value) {
+ var month, prefix, year, _ref;
+ value = value.replace(/\s/g, '');
+ _ref = value.split('/', 2), month = _ref[0], year = _ref[1];
+ if ((year != null ? year.length : void 0) === 2 && /^\d+$/.test(year)) {
+ prefix = (new Date).getFullYear();
+ prefix = prefix.toString().slice(0, 2);
+ year = prefix + year;
+ }
+ month = parseInt(month, 10);
+ year = parseInt(year, 10);
+ return {
+ month: month,
+ year: year
+ };
+ };
+
+ $.payment.validateCardNumber = function(num) {
+ var card, _ref;
+ num = (num + '').replace(/\s+|-/g, '');
+ if (!/^\d+$/.test(num)) {
+ return false;
+ }
+ card = cardFromNumber(num);
+ if (!card) {
+ return false;
+ }
+ return (_ref = num.length, __indexOf.call(card.length, _ref) >= 0) && (card.luhn === false || luhnCheck(num));
+ };
+
+ $.payment.validateCardExpiry = function(month, year) {
+ var currentTime, expiry, prefix, _ref;
+ if (typeof month === 'object' && 'month' in month) {
+ _ref = month, month = _ref.month, year = _ref.year;
+ }
+ if (!(month && year)) {
+ return false;
+ }
+ month = $.trim(month);
+ year = $.trim(year);
+ if (!/^\d+$/.test(month)) {
+ return false;
+ }
+ if (!/^\d+$/.test(year)) {
+ return false;
+ }
+ if (!(parseInt(month, 10) <= 12)) {
+ return false;
+ }
+ if (year.length === 2) {
+ prefix = (new Date).getFullYear();
+ prefix = prefix.toString().slice(0, 2);
+ year = prefix + year;
+ }
+ expiry = new Date(year, month);
+ currentTime = new Date;
+ expiry.setMonth(expiry.getMonth() - 1);
+ expiry.setMonth(expiry.getMonth() + 1, 1);
+ return expiry > currentTime;
+ };
+
+ $.payment.validateCardCVC = function(cvc, type) {
+ var _ref, _ref1;
+ cvc = $.trim(cvc);
+ if (!/^\d+$/.test(cvc)) {
+ return false;
+ }
+ if (type) {
+ return _ref = cvc.length, __indexOf.call((_ref1 = cardFromType(type)) != null ? _ref1.cvcLength : void 0, _ref) >= 0;
+ } else {
+ return cvc.length >= 3 && cvc.length <= 4;
+ }
+ };
+
+ $.payment.cardType = function(num) {
+ var _ref;
+ if (!num) {
+ return null;
+ }
+ return ((_ref = cardFromNumber(num)) != null ? _ref.type : void 0) || null;
+ };
+
+ $.payment.formatCardNumber = function(num) {
+ var card, groups, upperLength, _ref;
+ card = cardFromNumber(num);
+ if (!card) {
+ return num;
+ }
+ upperLength = card.length[card.length.length - 1];
+ num = num.replace(/\D/g, '');
+ num = num.slice(0, +upperLength + 1 || 9e9);
+ if (card.format.global) {
+ return (_ref = num.match(card.format)) != null ? _ref.join(' ') : void 0;
+ } else {
+ groups = card.format.exec(num);
+ if (groups != null) {
+ groups.shift();
+ }
+ return groups != null ? groups.join(' ') : void 0;
+ }
+ };
+
+}).call(this);
diff --git a/addons/website_payment/static/src/css/Makefile b/addons/website_payment/static/src/css/Makefile
new file mode 100644
index 00000000000..7f7f16d3616
--- /dev/null
+++ b/addons/website_payment/static/src/css/Makefile
@@ -0,0 +1,2 @@
+sass:
+ sass --trace -t expanded website_payment.sass:website_payment.css
diff --git a/addons/website_payment/static/src/css/website_payment.css b/addons/website_payment/static/src/css/website_payment.css
new file mode 100644
index 00000000000..196d6cf8956
--- /dev/null
+++ b/addons/website_payment/static/src/css/website_payment.css
@@ -0,0 +1,53 @@
+@charset "utf-8";
+input#cc_number {
+ background-image: url("/website_payment/static/src/img/grey-dots.png");
+ background-repeat: no-repeat;
+ background-position: 14px 14px;
+}
+
+div.card_placeholder {
+ background-image: url("/website_payment/static/src/img/placeholder.png");
+ background-repeat: no-repeat;
+ width: 32px;
+ height: 20px;
+ position: absolute;
+ top: 34px;
+ right: 20px;
+ -webkit-transition: 0.4s cubic-bezier(0.455, 0.03, 0.515, 0.955);
+ transition: 0.4s cubic-bezier(0.455, 0.03, 0.515, 0.955);
+ pointer-events: none;
+}
+
+div.amex {
+ background-image: url("/website_payment/static/src/img/amex.png");
+ background-repeat: no-repeat;
+}
+
+div.diners {
+ background-image: url("/website_payment/static/src/img/diners.png");
+ background-repeat: no-repeat;
+}
+
+div.discover {
+ background-image: url("/website_payment/static/src/img/discover.png");
+ background-repeat: no-repeat;
+}
+
+div.jcb {
+ background-image: url("/website_payment/static/src/img/jcb.png");
+ background-repeat: no-repeat;
+}
+
+div.mastercard {
+ background-image: url("/website_payment/static/src/img/mastercard.png");
+ background-repeat: no-repeat;
+}
+
+div.visa {
+ background-image: url("/website_payment/static/src/img/visa.png");
+ background-repeat: no-repeat;
+}
+
+input#cc_number:active, input#cc_number:focus {
+ background-image: none;
+}
diff --git a/addons/website_payment/static/src/css/website_payment.sass b/addons/website_payment/static/src/css/website_payment.sass
new file mode 100644
index 00000000000..b5d4d03a9bd
--- /dev/null
+++ b/addons/website_payment/static/src/css/website_payment.sass
@@ -0,0 +1,45 @@
+@charset "utf-8"
+
+input#cc_number
+ background-image: url('/website_payment/static/src/img/grey-dots.png')
+ background-repeat: no-repeat
+ background-position: 14px 14px
+
+div.card_placeholder
+ background-image: url('/website_payment/static/src/img/placeholder.png')
+ background-repeat: no-repeat
+ width: 32px
+ height: 20px
+ position: absolute
+ top: 34px
+ right: 20px
+ -webkit-transition: .4s cubic-bezier(0.455,0.03,0.515,0.955)
+ transition: .4s cubic-bezier(0.455,0.03,0.515,0.955)
+ pointer-events: none
+
+div.amex
+ background-image: url('/website_payment/static/src/img/amex.png')
+ background-repeat: no-repeat
+
+div.diners
+ background-image: url('/website_payment/static/src/img/diners.png')
+ background-repeat: no-repeat
+
+div.discover
+ background-image: url('/website_payment/static/src/img/discover.png')
+ background-repeat: no-repeat
+
+div.jcb
+ background-image: url('/website_payment/static/src/img/jcb.png')
+ background-repeat: no-repeat
+
+div.mastercard
+ background-image: url('/website_payment/static/src/img/mastercard.png')
+ background-repeat: no-repeat
+
+div.visa
+ background-image: url('/website_payment/static/src/img/visa.png')
+ background-repeat: no-repeat
+
+input#cc_number:active, input#cc_number:focus
+ background-image: none
diff --git a/addons/website_payment/static/src/img/amex.png b/addons/website_payment/static/src/img/amex.png
new file mode 100644
index 00000000000..7de472cdd9b
Binary files /dev/null and b/addons/website_payment/static/src/img/amex.png differ
diff --git a/addons/website_payment/static/src/img/diners.png b/addons/website_payment/static/src/img/diners.png
new file mode 100644
index 00000000000..93b909d1b33
Binary files /dev/null and b/addons/website_payment/static/src/img/diners.png differ
diff --git a/addons/website_payment/static/src/img/discover.png b/addons/website_payment/static/src/img/discover.png
new file mode 100644
index 00000000000..692bd76d403
Binary files /dev/null and b/addons/website_payment/static/src/img/discover.png differ
diff --git a/addons/website_payment/static/src/img/grey-dots.png b/addons/website_payment/static/src/img/grey-dots.png
new file mode 100644
index 00000000000..bf5391ef906
Binary files /dev/null and b/addons/website_payment/static/src/img/grey-dots.png differ
diff --git a/addons/website_payment/static/src/img/jcb.png b/addons/website_payment/static/src/img/jcb.png
new file mode 100644
index 00000000000..4160175eb5f
Binary files /dev/null and b/addons/website_payment/static/src/img/jcb.png differ
diff --git a/addons/website_payment/static/src/img/mastercard.png b/addons/website_payment/static/src/img/mastercard.png
new file mode 100644
index 00000000000..b51a920d156
Binary files /dev/null and b/addons/website_payment/static/src/img/mastercard.png differ
diff --git a/addons/website_payment/static/src/img/placeholder.png b/addons/website_payment/static/src/img/placeholder.png
new file mode 100644
index 00000000000..665f42efba8
Binary files /dev/null and b/addons/website_payment/static/src/img/placeholder.png differ
diff --git a/addons/website_payment/static/src/img/visa.png b/addons/website_payment/static/src/img/visa.png
new file mode 100644
index 00000000000..0358fbf00b6
Binary files /dev/null and b/addons/website_payment/static/src/img/visa.png differ
diff --git a/addons/website_payment/static/src/js/payment_acquirer.js b/addons/website_payment/static/src/js/payment_acquirer.js
new file mode 100644
index 00000000000..8955af0b7bc
--- /dev/null
+++ b/addons/website_payment/static/src/js/payment_acquirer.js
@@ -0,0 +1,58 @@
+$(document).ready(function () {
+
+ $('input#cc_number').payment('formatCardNumber');
+ $('input#cc_cvc').payment('formatCardCVC');
+ $('input#cc_expiry').payment('formatCardExpiry')
+
+ $('input#cc_number').on('focusout', function (e) {
+ var valid_value = $.payment.validateCardNumber(this.value);
+ var card_type = $.payment.cardType(this.value);
+ console.log('Validating card', this.value, 'is a', card_type, 'and valid:', valid_value);
+ if (card_type) {
+ $(this).parent('.form-group').children('.card_placeholder').removeClass().addClass('card_placeholder ' + card_type);
+ }
+ else {
+ $(this).parent('.form-group').children('.card_placeholder').removeClass().addClass('card_placeholder');
+ }
+ if (valid_value) {
+ $(this).parent('.form-group').addClass('has-success');
+ $(this).parent('.form-group').removeClass('has-error');
+ }
+ else {
+ $(this).parent('.form-group').addClass('has-error');
+ $(this).parent('.form-group').removeClass('has-success');
+ }
+ });
+
+ $('input#cc_cvc').on('focusout', function (e) {
+ var cc_nbr = $(this).parents('.oe_cc').find('#cc_number').val();
+ var card_type = $.payment.cardType(cc_nbr);
+ var valid_value = $.payment.validateCardCVC(this.value, card_type);
+ console.log('Validating CVC', this.value, 'for card', cc_nbr, 'of type', card_type, 'and is valid:', valid_value);
+ if (valid_value) {
+ $(this).parent('.form-group').addClass('has-success');
+ $(this).parent('.form-group').removeClass('has-error');
+ }
+ else {
+ $(this).parent('.form-group').addClass('has-error');
+ $(this).parent('.form-group').removeClass('has-success');
+ }
+ });
+
+ $('input#cc_expiry').on('focusout', function (e) {
+ var expiry_value = $.payment.cardExpiryVal(this.value);
+ var month = expiry_value.month || '';
+ var year = expiry_value.year || '';
+ var valid_value = $.payment.validateCardExpiry(month, year);
+ console.log('Validating expiry', this.value, 'month', month, 'year', year, 'and is valid:', valid_value);
+ if (valid_value) {
+ $(this).parent('.form-group').addClass('has-success');
+ $(this).parent('.form-group').removeClass('has-error');
+ }
+ else {
+ $(this).parent('.form-group').addClass('has-error');
+ $(this).parent('.form-group').removeClass('has-success');
+ }
+ });
+
+});
diff --git a/addons/website_payment/views/website_payment_templates.xml b/addons/website_payment/views/website_payment_templates.xml
new file mode 100644
index 00000000000..bf6b151326f
--- /dev/null
+++ b/addons/website_payment/views/website_payment_templates.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Paypal payment: form based
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Transfer payment
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_sale/__openerp__.py b/addons/website_sale/__openerp__.py
index 634078fe149..f3e370d989d 100644
--- a/addons/website_sale/__openerp__.py
+++ b/addons/website_sale/__openerp__.py
@@ -9,7 +9,7 @@ OpenERP E-Commerce
""",
'author': 'OpenERP SA',
- 'depends': ['website', 'sale', 'product'],
+ 'depends': ['website', 'sale', 'product', 'payment_acquirer'],
'data': [
'website_sale_data.xml',
'views/website_sale.xml',
diff --git a/addons/website_sale/controllers/__init__.py b/addons/website_sale/controllers/__init__.py
index e11f9ba81bb..ef613dca3d5 100644
--- a/addons/website_sale/controllers/__init__.py
+++ b/addons/website_sale/controllers/__init__.py
@@ -1,3 +1,2 @@
+import website
import main
-
-# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 2159304157c..e4190389b98 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -1,63 +1,13 @@
# -*- coding: utf-8 -*-
import random
-import uuid
import simplejson
-
-import werkzeug.exceptions
+import urllib
from openerp import SUPERUSER_ID
-from openerp.osv import osv
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
-def get_order(order_id=None):
- order_obj = request.registry.get('sale.order')
- # check if order allready exists and have access
- if order_id:
- try:
- order = order_obj.browse(request.cr, request.uid, order_id, context=request.context)
- order.pricelist_id
- if order:
- return order
- except:
- return False
-
- fields = [k for k, v in order_obj._columns.items()]
- order_value = order_obj.default_get(request.cr, SUPERUSER_ID, fields, context=request.context)
- if request.httprequest.session.get('ecommerce_pricelist'):
- order_value['pricelist_id'] = request.httprequest.session['ecommerce_pricelist']
- order_value['partner_id'] = request.registry.get('res.users').browse(request.cr, SUPERUSER_ID, request.uid, context=request.context).partner_id.id
- order_value.update(order_obj.onchange_partner_id(request.cr, SUPERUSER_ID, [], order_value['partner_id'], context=request.context)['value'])
-
- # add website_session_id key for access rules
- if not request.httprequest.session.get('website_session_id'):
- request.httprequest.session['website_session_id'] = str(uuid.uuid4())
-
- order_value["website_session_id"] = request.httprequest.session['website_session_id']
- order_id = order_obj.create(request.cr, SUPERUSER_ID, order_value, context=request.context)
- order = order_obj.browse(request.cr, SUPERUSER_ID, order_id, context=request.context)
- request.httprequest.session['ecommerce_order_id'] = order.id
-
- return order_obj.browse(request.cr, request.uid, order_id,
- context=dict(request.context, pricelist=order.pricelist_id.id))
-
-def get_current_order():
- if request.httprequest.session.get('ecommerce_order_id'):
- order = get_order(request.httprequest.session.get('ecommerce_order_id'))
- if not order:
- request.httprequest.session['ecommerce_order_id'] = False
- return order
- else:
- return False
-
-class Website(osv.osv):
- _inherit = "website"
- def preprocess_request(self, cr, uid, ids, request, context=None):
- request.context.update({
- 'website_sale_order': get_current_order(),
- })
- return super(Website, self).preprocess_request(cr, uid, ids, request, context=None)
class CheckoutInfo:
mandatory_billing_fields = ["name", "phone", "email", "street", "city", "country_id", "zip"]
@@ -89,6 +39,7 @@ class CheckoutInfo:
def from_post(self, post):
return dict((field_name, post[field_name]) for field_name in self.all_fields() if post[field_name])
+
class Ecommerce(http.Controller):
_order = 'website_sequence desc, website_published desc'
@@ -96,7 +47,7 @@ class Ecommerce(http.Controller):
def get_attribute_ids(self):
attributes_obj = request.registry.get('product.attribute')
- attributes_ids = attributes_obj.search(request.cr, request.uid, [(1, "=", 1)], context=request.context)
+ attributes_ids = attributes_obj.search(request.cr, request.uid, [], context=request.context)
return attributes_obj.browse(request.cr, request.uid, attributes_ids, context=request.context)
def get_categories(self):
@@ -289,27 +240,33 @@ class Ecommerce(http.Controller):
filter[index.index(cat_id)].append( cat[2] )
post.pop(key)
- return request.redirect("/shop/?filter=%s%s%s%s" % (
+ return request.redirect("/shop/?filter=%s%s%s" % (
simplejson.dumps(filter),
- post.get("search") and "&search=%s" % post.get("search") or "",
- post.get("category") and "&category=%s" % post.get("category") or ""
+ post.get("search") and ("&search=%s" % post.get("search")) or "",
+ post.get("category") and ("&category=%s" % post.get("category")) or ""
))
def attributes_to_ids(self, attributes):
obj = request.registry.get('product.attribute.product')
- domain = [(1, '=', 1)]
+ domain = []
for key_val in attributes:
- domain += [("attribute_id", "=", key_val[0])]
+ domain.append(("attribute_id", "=", key_val[0]))
if isinstance(key_val[1], list):
- domain += [("value", ">=", key_val[1][0]), ("value", "<=", key_val[1][1])]
+ domain.append(("value", ">=", key_val[1][0]))
+ domain.append(("value", "<=", key_val[1][1]))
else:
- domain += [("value_id", "in", key_val[1:])]
+ domain.append(("value_id", "in", key_val[1:]))
att_ids = obj.search(request.cr, request.uid, domain, context=request.context)
att = obj.read(request.cr, request.uid, att_ids, ["product_id"], context=request.context)
return [r["product_id"][0] for r in att]
- @website.route(['/shop/', '/shop/page//'], type='http', auth="public", multilang=True)
- def category(self, category=0, filter="", page=0, **post):
+ @website.route([
+ '/shop/',
+ '/shop/page//',
+ '/shop/category//',
+ '/shop/category//page//'
+ ], type='http', auth="public", multilang=True)
+ def category(self, category=0, page=0, filter='', search='', **post):
# TDE-NOTE: shouldn't we do somethign about product_template without variants ???
# TDE-NOTE: is there a reason to call a method category when the route is
# basically a shop without category_id speceified ?
@@ -322,27 +279,26 @@ class Ecommerce(http.Controller):
# remove product_product_consultant from ecommerce editable mode, this product never be publish
ref = request.registry.get('ir.model.data').get_object_reference(request.cr, SUPERUSER_ID, 'product', 'product_product_consultant')
- domain += [("id", "!=", ref[1])]
+ domain.append(("id", "!=", ref[1]))
- if post.get("search"):
+ if search:
domain += ['|', '|', '|',
- ('name', 'ilike', "%%%s%%" % post.get("search")),
- ('description', 'ilike', "%%%s%%" % post.get("search")),
- ('website_description', 'ilike', "%%%s%%" % post.get("search")),
- ('product_variant_ids.public_categ_id.name', 'ilike', "%%%s%%" % post.get("search"))]
+ ('name', 'ilike', "%%%s%%" % search),
+ ('description', 'ilike', "%%%s%%" % search),
+ ('website_description', 'ilike', "%%%s%%" % search),
+ ('product_variant_ids.public_categ_id.name', 'ilike', "%%%s%%" % search)]
if category:
- cat_id = int(category)
- domain = [('product_variant_ids.public_categ_id.id', 'child_of', cat_id)] + domain
+ domain.append(('product_variant_ids.public_categ_id.id', 'child_of', category))
if filter:
filter = simplejson.loads(filter)
if filter:
ids = self.attributes_to_ids(filter)
- domain = [('id', 'in', ids or [0] )] + domain
+ domain.append(('id', 'in', ids or [0]))
step = 20
- product_count = len(product_obj.search(request.cr, request.uid, domain, context=request.context))
+ product_count = product_obj.search_count(request.cr, request.uid, domain, context=request.context)
pager = request.website.pager(url="/shop/", total=product_count, page=page, step=step, scope=7, url_args=post)
request.context['pricelist'] = self.get_pricelist()
@@ -353,7 +309,7 @@ class Ecommerce(http.Controller):
styles = []
if not request.context['is_public_user']:
style_obj = request.registry.get('website.product.style')
- style_ids = style_obj.search(request.cr, request.uid, [(1, '=', 1)], context=request.context)
+ style_ids = style_obj.search(request.cr, request.uid, [], context=request.context)
styles = style_obj.browse(request.cr, request.uid, style_ids, context=request.context)
values = {
@@ -361,9 +317,9 @@ class Ecommerce(http.Controller):
'product_ids': product_ids,
'product_ids_for_holes': fill_hole,
'search': {
- 'search': post.get('search') or '',
+ 'search': search,
'category': category,
- 'filter': filter or '',
+ 'filter': filter,
},
'pager': pager,
'styles': styles,
@@ -433,7 +389,7 @@ class Ecommerce(http.Controller):
request.httprequest.session['ecommerce_pricelist'] = pricelist_id
- order = get_current_order()
+ order = request.registry['website'].get_current_order(request.cr, request.uid, context=request.context)
if order:
values = {'pricelist_id': pricelist_id}
values.update(order.onchange_pricelist_id(pricelist_id, None)['value'])
@@ -445,9 +401,9 @@ class Ecommerce(http.Controller):
order_line_obj = request.registry.get('sale.order.line')
order_obj = request.registry.get('sale.order')
- order = get_current_order()
+ order = request.registry.get('website').get_current_order(request.cr, request.uid, context=request.context)
if not order:
- order = get_order()
+ order = request.registry.get('website')._get_order(request.cr, request.uid, context=request.context)
request.context = dict(request.context, pricelist=self.get_pricelist())
@@ -497,9 +453,15 @@ class Ecommerce(http.Controller):
@website.route(['/shop/mycart/'], type='http', auth="public", multilang=True)
def mycart(self, **post):
- order = get_current_order()
+ cr, uid, context = request.cr, request.uid, request.context
prod_obj = request.registry.get('product.product')
+ # must have a draft sale order with lines at this point, otherwise reset
+ order = context.get('website_sale_order')
+ if order and order.state != 'draft':
+ request.registry['website'].sale_reset_order(cr, uid, context=context)
+ return request.redirect('/shop/')
+
if 'promo' in post:
self.change_pricelist(post.get('promo'))
@@ -511,7 +473,7 @@ class Ecommerce(http.Controller):
product_ids.append(line.product_id.id)
suggested_ids = list(set(suggested_ids) - set(product_ids))
if suggested_ids:
- suggested_ids = prod_obj.search(request.cr, request.uid, [('id', 'in', suggested_ids)], context=request.context)
+ suggested_ids = prod_obj.search(cr, uid, [('id', 'in', suggested_ids)], context=context)
# select 3 random products
suggested_products = []
@@ -522,7 +484,7 @@ class Ecommerce(http.Controller):
values = {
'int': int,
'get_categories': self.get_categories,
- 'suggested_products': prod_obj.browse(request.cr, request.uid, suggested_products, request.context),
+ 'suggested_products': prod_obj.browse(cr, uid, suggested_products, context),
}
return request.website.render("website_sale.mycart", values)
@@ -534,7 +496,7 @@ class Ecommerce(http.Controller):
@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 = get_current_order()
+ 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()]
@@ -547,18 +509,23 @@ class Ecommerce(http.Controller):
def checkout(self, **post):
cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
- order = get_current_order()
-
+ # must have a draft sale order with lines at this point, otherwise reset
+ order = context.get('website_sale_order')
if not order or order.state != 'draft' or not order.order_line:
- return self.mycart(**post)
+ request.registry['website'].sale_reset_order(cr, uid, context=context)
+ return request.redirect('/shop/')
+ # if transaction pending / done: redirect to confirmation
+ tx = context.get('website_sale_transaction')
+ if tx and tx.state != 'draft':
+ return request.redirect('/shop/payment/confirmation/%s' % order.id)
orm_partner = registry.get('res.partner')
orm_user = registry.get('res.users')
orm_country = registry.get('res.country')
- country_ids = orm_country.search(cr, SUPERUSER_ID, [(1, "=", 1)], context=context)
+ country_ids = orm_country.search(cr, SUPERUSER_ID, [], context=context)
countries = orm_country.browse(cr, SUPERUSER_ID, country_ids, context)
state_orm = registry.get('res.country.state')
- states_ids = state_orm.search(cr, SUPERUSER_ID, [(1, "=", 1)], context=context)
+ states_ids = state_orm.search(cr, SUPERUSER_ID, [], context=context)
states = state_orm.browse(cr, SUPERUSER_ID, states_ids, context)
info = CheckoutInfo()
@@ -598,18 +565,23 @@ class Ecommerce(http.Controller):
def confirm_order(self, **post):
cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
- order = get_current_order()
-
+ # must have a draft sale order with lines at this point, otherwise redirect to shop
+ order = request.registry['website'].get_current_order(request.cr, request.uid, context=request.context)
if not order or order.state != 'draft' or not order.order_line:
- return self.mycart(**post)
+ request.registry['website'].sale_reset_order(cr, uid, context=context)
+ return request.redirect('/shop/')
+ # if transaction pending / done: redirect to confirmation
+ tx = context.get('website_sale_transaction')
+ if tx and tx.state != 'draft':
+ return request.redirect('/shop/payment/confirmation/%s' % order.id)
orm_parter = registry.get('res.partner')
orm_user = registry.get('res.users')
orm_country = registry.get('res.country')
- country_ids = orm_country.search(cr, SUPERUSER_ID, [(1, "=", 1)], context=context)
+ country_ids = orm_country.search(cr, SUPERUSER_ID, [], context=context)
countries = orm_country.browse(cr, SUPERUSER_ID, country_ids, context)
orm_state = registry.get('res.country.state')
- states_ids = orm_state.search(cr, SUPERUSER_ID, [(1, "=", 1)], context=context)
+ states_ids = orm_state.search(cr, SUPERUSER_ID, [], context=context)
states = orm_state.browse(cr, SUPERUSER_ID, states_ids, context)
info = CheckoutInfo()
@@ -641,7 +613,7 @@ class Ecommerce(http.Controller):
company_id = (company_ids and company_ids[0]) or orm_parter.create(cr, SUPERUSER_ID, {'name': company_name, 'is_company': True}, context)
billing_info = dict(checkout)
- billing_info['parent_id'] = company_id;
+ billing_info['parent_id'] = company_id
if not context['is_public_user']:
partner_id = orm_user.browse(cr, uid, uid, context=context).partner_id.id
@@ -657,6 +629,7 @@ class Ecommerce(http.Controller):
'street': post['shipping_street'],
'city': post['shipping_city'],
'name': post['shipping_name'],
+ 'email': post['email'],
'type': 'delivery',
'parent_id': partner_id,
'country_id': post['shipping_country_id'],
@@ -685,31 +658,183 @@ class Ecommerce(http.Controller):
@website.route(['/shop/payment/'], type='http', auth="public", multilang=True)
def payment(self, **post):
- order = get_current_order()
+ """ Payment step. This page proposes several payment means based on available
+ payment.acquirer. State at this point :
- if not order or not order.order_line:
- return self.mycart(**post)
+ - a draft sale order with lines; otherwise, clean context / session and
+ back to the shop
+ - no transaction in context / session, or only a draft one, if the customer
+ did go to a payment.acquirer website but closed the tab without
+ paying / canceling
+ """
+ cr, uid, context = request.cr, request.uid, request.context
+ payment_obj = request.registry.get('payment.acquirer')
+
+ # if no sale order at this stage: back to checkout beginning
+ order = context.get('website_sale_order')
+ if not order or not order.state == 'draft' or not order.order_line:
+ request.registry['website'].sale_reset_order(cr, uid, context=context)
+ return request.redirect("/shop/")
+ # alread a transaction: forward to confirmation
+ tx = context.get('website_sale_transaction')
+ if tx and not tx.state == 'draft':
+ print 'embetatn'
+ # return request.redirect('/shop/confirmation/%s' % order.id)
+
+ partner_id = False
+ shipping_partner_id = False
+ if order:
+ if order.partner_id.id:
+ partner_id = order.partner_id.id
+ shipping_partner_id = order.partner_id.id
+ if order.partner_shipping_id.id:
+ shipping_partner_id = order.partner_shipping_id.id
values = {
- 'partner': False,
- 'order': order,
+ 'partner': partner_id,
+ 'order': order
}
- values.update( request.registry.get('sale.order')._get_website_data(request.cr, request.uid, order, request.context) )
+ values.update(request.registry.get('sale.order')._get_website_data(cr, uid, order, context))
- payment_obj = request.registry.get('portal.payment.acquirer')
- payment_ids = payment_obj.search(request.cr, SUPERUSER_ID, [('visible', '=', True)], context=request.context)
- values['payments'] = payment_obj.browse(request.cr, SUPERUSER_ID, payment_ids, request.context)
- for payment in values['payments']:
- content = payment_obj.render(request.cr, SUPERUSER_ID, payment.id, order, order.name, order.pricelist_id.currency_id, order.amount_total)
- payment._content = content
+ # fetch all registered payment means
+ if tx:
+ payment_ids = [tx.acquirer_id.id]
+ else:
+ payment_ids = payment_obj.search(cr, SUPERUSER_ID, [('portal_published', '=', True)], context=context)
+ values['payments'] = payment_obj.browse(cr, uid, payment_ids, context=context)
+ for pay in values['payments']:
+ pay._content = payment_obj.render(
+ cr, uid, pay.id,
+ order.name,
+ order.amount_total,
+ order.pricelist_id.currency_id,
+ partner_id=shipping_partner_id,
+ tx_custom_values={
+ 'return_url': '/shop/payment/validate',
+ },
+ context=context)
return request.website.render("website_sale.payment", values)
- @website.route(['/shop/payment_validate/'], type='http', auth="public", multilang=True)
- def payment_validate(self, **post):
- request.httprequest.session['ecommerce_order_id'] = False
- request.httprequest.session['ecommerce_pricelist'] = False
- return request.redirect("/shop/")
+ @website.route(['/shop/payment/transaction/'],
+ type='http', methods=['POST'], auth="public")
+ def payment_transaction(self, acquirer_id, **post):
+ """ Hook method that creates a payment.transaction and redirect to the
+ acquirer, using post values to re-create the post action.
+
+ :param int acquirer_id: id of a payment.acquirer record. If not set the
+ user is redirected to the checkout page
+ :param dict post: should coutain all post data for the acquirer
+ """
+ # @TDEFIXME: don't know why we received those data, but should not be send to the acquirer
+ post.pop('submit.x', None)
+ post.pop('submit.y', None)
+ cr, uid, context = request.cr, request.uid, request.context
+ payment_obj = request.registry.get('payment.acquirer')
+ transaction_obj = request.registry.get('payment.transaction')
+ order = request.registry['website'].get_current_order(request.cr, request.uid, context=request.context)
+
+ if not order or not order.order_line or acquirer_id is None:
+ return request.redirect("/shop/checkout/")
+
+ # find an already existing transaction
+ tx = context.get('website_sale_transaction')
+ if not tx:
+ tx_id = transaction_obj.create(cr, uid, {
+ 'acquirer_id': acquirer_id,
+ 'type': 'form',
+ 'amount': order.amount_total,
+ 'currency_id': order.pricelist_id.currency_id.id,
+ 'partner_id': order.partner_id.id,
+ 'reference': order.name,
+ 'sale_order_id': order.id,
+ }, context=context)
+ request.httprequest.session['website_sale_transaction_id'] = tx_id
+ elif tx and tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
+ tx.write({
+ 'acquirer_id': acquirer_id,
+ })
+
+ acquirer_form_post_url = payment_obj.get_form_action_url(cr, uid, acquirer_id, context=context)
+ acquirer_total_url = '%s?%s' % (acquirer_form_post_url, urllib.urlencode(post))
+ return request.redirect(acquirer_total_url)
+
+ @website.route('/shop/payment/get_status/', type='json', auth="public", multilang=True)
+ def payment_get_status(self, order, **post):
+ cr, uid, context = request.cr, request.uid, request.context
+ if not order:
+ return {
+ 'state': 'error',
+ }
+
+ tx_ids = request.registry['payment.transaction'].search(
+ cr, uid, [
+ '|', ('sale_order_id', '=', order.id), ('reference', '=', order.name)
+ ], context=context)
+ if not tx_ids:
+ return {
+ 'state': 'error'
+ }
+ tx = request.registry['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
+ return {
+ 'state': tx.state,
+ }
+
+ @website.route('/shop/payment/validate/', type='http', auth="public", multilang=True)
+ def payment_validate(self, transaction_id=None, sale_order_id=None, **post):
+ """ Method that should be called by the server when receiving an update
+ for a transaction. State at this point :
+
+ - UDPATE ME
+ """
+ cr, uid, context = request.cr, request.uid, request.context
+ email_act = None
+ sale_order_obj = request.registry['sale.order']
+
+ if transaction_id is None:
+ tx = context.get('website_sale_transaction')
+ else:
+ tx = request.registry['payment.transaction'].browse(cr, uid, transaction_id, context=context)
+
+ if sale_order_id is None:
+ order = context.get('website_sale_order')
+ else:
+ order = request.registry['sale.order'].browse(cr, uid, sale_order_id, context=context)
+
+ if tx.state == 'done':
+ # confirm the quotation
+ sale_order_obj.action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
+ # send by email
+ email_act = sale_order_obj.action_quotation_send(cr, SUPERUSER_ID, [order.id], context=request.context)
+ elif tx.state == 'pending':
+ # send by email
+ email_act = sale_order_obj.action_quotation_send(cr, SUPERUSER_ID, [order.id], context=request.context)
+ elif tx.state == 'cancel':
+ # cancel the quotation
+ sale_order_obj.action_cancel(cr, SUPERUSER_ID, [order.id], context=request.context)
+
+ if email_act:
+ create_ctx = email_act.get('context', context)
+ compose_id = request.registry['mail.compose.message'].create(cr, uid, {}, context=create_ctx)
+ request.registry['mail.compose.message'].send_mail(cr, uid, [compose_id], context=create_ctx)
+
+ # clean context and session, then redirect to the confirmation page
+ request.registry['website'].sale_reset_order(cr, uid, context=context)
+
+ return request.redirect('/shop/confirmation/%s' % order.id)
+
+ @website.route(['/shop/confirmation/'], type='http', auth="public", multilang=True)
+ def payment_confirmation(self, order, **post):
+ """ End of checkout process controller. Confirmation is basically seing
+ the status of a sale.order. State at this point :
+
+ - should not have any context / session info: clean them
+ - take a sale.order id, because we request a sale.order and are not
+ session dependant anymore
+ """
+ cr, uid, context = request.cr, request.uid, request.context
+
+ return request.website.render("website_sale.confirmation", {'order': order})
@website.route(['/shop/change_sequence/'], type='json', auth="public")
def change_sequence(self, id, top):
diff --git a/addons/website_sale/controllers/website.py b/addons/website_sale/controllers/website.py
new file mode 100644
index 00000000000..949b2263617
--- /dev/null
+++ b/addons/website_sale/controllers/website.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+import uuid
+
+from openerp import SUPERUSER_ID
+from openerp.osv import osv
+from openerp.addons.web.http import request
+
+
+class Website(osv.Model):
+ _inherit = "website"
+
+ def _get_order(self, cr, uid, order_id=None, context=None):
+ order_obj = request.registry.get('sale.order')
+ # check if order allready exists and have access
+ if order_id:
+ if not order_id in order_obj.exists(cr, uid, [order_id], context=context):
+ return False
+ try:
+ order = order_obj.browse(cr, uid, order_id, context=context)
+ if order:
+ return order
+ except:
+ return False
+
+ fields = [k for k, v in order_obj._columns.items()]
+ order_value = order_obj.default_get(cr, SUPERUSER_ID, fields, context=context)
+ if request.httprequest.session.get('ecommerce_pricelist'):
+ order_value['pricelist_id'] = request.httprequest.session['ecommerce_pricelist']
+ order_value['partner_id'] = request.registry.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
+ order_value.update(order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], order_value['partner_id'], context=context)['value'])
+
+ # add website_session_id key for access rules
+ if not request.httprequest.session.get('website_session_id'):
+ request.httprequest.session['website_session_id'] = str(uuid.uuid4())
+
+ order_value["website_session_id"] = request.httprequest.session['website_session_id']
+ order_id = order_obj.create(cr, SUPERUSER_ID, order_value, context=context)
+ order = order_obj.browse(cr, SUPERUSER_ID, order_id, context=context)
+ request.httprequest.session['ecommerce_order_id'] = order.id
+
+ return order_obj.browse(cr, uid, order_id,
+ context=dict(request.context, pricelist=order.pricelist_id.id))
+
+ def get_current_order(self, cr, uid, context=None):
+ if request.httprequest.session.get('ecommerce_order_id'):
+ order = self._get_order(cr, uid, order_id=request.httprequest.session['ecommerce_order_id'], context=context)
+ if not order:
+ request.httprequest.session['ecommerce_order_id'] = False
+ return order
+ return False
+
+ def _get_transaction(self, cr, uid, tx_id=None, context=None):
+ transaction_obj = request.registry['payment.transaction']
+ if tx_id:
+ tx_ids = transaction_obj.search(cr, uid, [('id', '=', tx_id), ('state', 'not in', ['cancel'])], context=context)
+ if tx_ids:
+ return transaction_obj.browse(cr, uid, tx_ids[0], context=context)
+ return False
+
+ def get_current_transaction(self, cr, uid, context=None):
+ if request.httprequest.session.get('website_sale_transaction_id'):
+ tx = self._get_transaction(cr, uid, tx_id=request.httprequest.session['website_sale_transaction_id'], context=context)
+ if not tx:
+ request.httprequest.session['website_sale_transaction_id'] = False
+ return tx
+ return False
+
+ def sale_reset_order(self, cr, uid, context=None):
+ request.httprequest.session.update({
+ 'ecommerce_order_id': False,
+ 'ecommerce_pricelist': False,
+ 'website_sale_transaction_id': False,
+ })
+ request.context.update({
+ 'website_sale_order': False,
+ 'website_sale_transaction': False,
+ })
+
+ def preprocess_request(self, cr, uid, ids, request, context=None):
+ request.context.update({
+ 'website_sale_order': self.get_current_order(cr, uid, context=context),
+ 'website_sale_transaction': self.get_current_transaction(cr, uid, context=context)
+ })
+ return super(Website, self).preprocess_request(cr, uid, ids, request, context=None)
diff --git a/addons/website_sale/models/website.py b/addons/website_sale/models/website.py
index 83ede423d37..3193b41a4fd 100644
--- a/addons/website_sale/models/website.py
+++ b/addons/website_sale/models/website.py
@@ -2,6 +2,7 @@
from openerp.osv import orm, fields
from openerp.addons.web import http
+
class Website(orm.Model):
_inherit = 'website'
@@ -16,3 +17,12 @@ class Website(orm.Model):
'pricelist_id': fields.function(
_get_pricelist, type='many2one', obj='product.pricelist')
}
+
+
+class PaymentTransaction(orm.Model):
+ _inherit = 'payment.transaction'
+
+ _columns = {
+ # link with the sale order
+ 'sale_order_id': fields.many2one('sale.order', 'Sale Order'),
+ }
diff --git a/addons/website_sale/static/src/js/website.tour.shop.js b/addons/website_sale/static/src/js/website.tour.shop.js
index 6b67836ac84..8f22205eb8c 100644
--- a/addons/website_sale/static/src/js/website.tour.shop.js
+++ b/addons/website_sale/static/src/js/website.tour.shop.js
@@ -3,8 +3,6 @@
var website = openerp.website;
- var render = website.tour.render;
-
website.EditorBar.include({
start: function () {
this.registerTour(new website.EditorShopTour(this));
@@ -15,7 +13,6 @@
website.EditorShopTour = website.Tour.extend({
id: 'shop-tutorial',
name: "Create a product",
- startPath: '/shop',
init: function (editor) {
var self = this;
self.steps = [
@@ -25,7 +22,7 @@
backdrop: true,
title: "Welcome to your shop",
content: "You successfully installed the e-commerce. This guide will help you to create your product and promote your sales.",
- template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }),
+ template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
},
{
stepId: 'content-menu',
@@ -34,7 +31,6 @@
reflex: true,
title: "Create your first product",
content: "Click here to add a new product.",
- template: render('website.tour_popover'),
},
{
stepId: 'edit-entry',
@@ -42,8 +38,7 @@
placement: 'left',
title: "Create a new product",
content: "Select 'New Product' to create it and manage its properties to boost your sales.",
- template: render('website.tour_popover'),
- onShow: function () {
+ triggers: function () {
$(document).one('shown.bs.modal', function () {
$('.modal button.btn-primary').click(function () {
self.movetoStep('product-page');
@@ -58,7 +53,6 @@
placement: 'right',
title: "Choose name",
content: "Enter a name for your new product.",
- template: render('website.tour_popover'),
},
{
@@ -67,7 +61,7 @@
backdrop: true,
title: "New product created",
content: "This page contains all the information related to the new product.",
- template: render('website.tour_popover', { next: "OK" }),
+ template: self.popover({ next: "OK" }),
},
{
stepId: 'edit-price',
@@ -75,7 +69,7 @@
placement: 'left',
title: "Change the public price",
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: render('website.tour_popover', { next: "OK" }),
+ template: self.popover({ next: "OK" }),
},
{
stepId: 'add-block',
@@ -84,16 +78,15 @@
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.",
- template: render('website.tour_popover'),
},
];
return this._super();
},
- continueTour: function () {
+ resume: function () {
return this.isCurrentStep('product-page') && !this.tour.ended();
},
- isTriggerUrl: function (url) {
- return (this.continueTour() && this.testUrl(/^\/shop\/product\/[0-9]+\/\?enable_editor=1/)) || this._super();
+ trigger: function (url) {
+ return (this.resume() && this.testUrl(/^\/shop\/product\/[0-9]+\/\?enable_editor=1/)) || this._super();
},
});
diff --git a/addons/website_sale/static/src/js/website_sale.editor.js b/addons/website_sale/static/src/js/website_sale.editor.js
index 366b4fadce5..87be89b59c6 100644
--- a/addons/website_sale/static/src/js/website_sale.editor.js
+++ b/addons/website_sale/static/src/js/website_sale.editor.js
@@ -19,4 +19,60 @@
}
}),
});
+
+
+ $(document).on('click', '.js_publish_management .js_go_to_top,.js_publish_management .js_go_to_bottom', function (event) {
+ var $a = $(event.currentTarget);
+ var $data = $a.parents(".js_publish_management:first");
+ openerp.jsonRpc('/shop/change_sequence/', 'call', {'id': $data.data('id'), 'top': $a.hasClass('js_go_to_top')});
+ });
+
+ $(document).on('click', '.js_options ul[name="style"] a', function (event) {
+ var $a = $(event.currentTarget);
+ var $li = $a.parent();
+ var $data = $a.parents(".js_options:first");
+ var $product = $a.parents(".oe_product:first");
+
+ $li.parent().removeClass("active");
+ openerp.jsonRpc('/shop/change_styles/', 'call', {'id': $data.data('id'), 'style_id': $a.data("id")})
+ .then(function (result) {
+ $product.toggleClass($a.data("class"));
+ $li.toggleClass("active", result);
+ });
+ });
+
+ $(document).on('mouseenter', '#products_grid .js_options ul[name="size"] table', function (event) {
+ $(event.currentTarget).addClass("oe_hover");
+ });
+ $(document).on('mouseleave', '#products_grid .js_options ul[name="size"] table', function (event) {
+ $(event.currentTarget).removeClass("oe_hover");
+ });
+ $(document).on('mouseover', '#products_grid .js_options ul[name="size"] td', function (event) {
+ var $td = $(event.currentTarget);
+ var $table = $td.parents("table:first");
+ var x = $td.index()+1;
+ var y = $td.parent().index()+1;
+
+ var tr = [];
+ for (var yi=0; yi
-
-
-
-
+
+
+
@@ -25,7 +24,7 @@
-
+
@@ -69,7 +68,7 @@
@@ -118,7 +117,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Shop - Confirmed
+
+
+
+
+ - Review Order
+ - Shipping & Billing
+ - Payment
+ - Confirmation
+
+
Order Confirmed
+
+
+
Thank you for your order.
+
+
+
+
+
+
+
Bill To:
+
+
+
Ship To:
+
+
+
Amount:
+
+
diff --git a/addons/website_sale/website_sale_data.xml b/addons/website_sale/website_sale_data.xml
index b8849a6234f..223d7fe03ba 100644
--- a/addons/website_sale/website_sale_data.xml
+++ b/addons/website_sale/website_sale_data.xml
@@ -1,12 +1,5 @@
-
-
-
-
-
-
-