[MERGE] saas-3

bzr revid: al@openerp.com-20140209140315-ukqlf90ngy6ycu2k
This commit is contained in:
Antony Lesuisse 2014-02-09 15:03:15 +01:00
commit 87172d90d2
64 changed files with 1082 additions and 619 deletions

View File

@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010-2012 OpenERP SA (<http://openerp.com>).
# Copyright (C) 2010-2014 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -30,7 +30,9 @@ Allow users to sign up through OAuth2 Provider.
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['auth_oauth', 'auth_signup'],
'data': [],
'data': [
'views/auth_oauth_signup.xml',
],
'js': [],
'css': [],
'qweb': [],

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:et:si:ts=2:sts=2:sw=2 -->
<openerp>
<data>
<template id="auth_oauth_signup.signup" inherit_id="auth_signup.signup" name="OAuth Signup buttons">
<xpath expr="//button[@type='submit']" position="before">
<div class="pull-right">
<div t-foreach="providers or []" t-as="p">
<a t-att-href="p['auth_link']" class="btn btn-link">
<i t-att-class="p['css_class']"/>
<t t-esc="p['body']"/>
</a>
</div>
</div>
</xpath>
</template>
</data>
</openerp>

View File

@ -30,13 +30,16 @@ from openerp.tools import exception_to_unicode
_logger = logging.getLogger(__name__)
class Home(openerp.addons.web.controllers.main.Home):
class AuthSignup(openerp.addons.web.controllers.main.Home):
@http.route()
def web_login(self, *args, **kw):
mode = request.params.get('mode')
qcontext = request.params.copy()
super_response = super(AuthSignup, self).web_login(*args, **kw)
response = webmain.render_bootstrap_template(request.session.db, 'auth_signup.signup', qcontext, lazy=True)
if isinstance(super_response, LazyResponse):
response.params['values'].update(super_response.params['values'])
token = qcontext.get('token', None)
token_infos = None
if token:
@ -60,10 +63,9 @@ class Home(openerp.addons.web.controllers.main.Home):
qcontext.update(config)
if 'error' in qcontext or mode not in ('reset', 'signup') or (not token and not config[mode]):
response = super(Home, self).web_login(*args, **kw)
if isinstance(response, LazyResponse):
response.params['values'].update(config)
return response
if isinstance(super_response, LazyResponse):
super_response.params['values'].update(config)
return super_response
if request.httprequest.method == 'GET':
if token_infos:
@ -86,7 +88,7 @@ class Home(openerp.addons.web.controllers.main.Home):
request.cr.commit()
except SignupError, e:
qcontext['error'] = exception_to_unicode(e)
return super(Home, self).web_login(*args, **kw)
return super(AuthSignup, self).web_login(*args, **kw)
return response

View File

@ -5,8 +5,8 @@
<data>
<template id="auth_signup.login" inherit_id="web.login" name="Sign up - Reset Password">
<xpath expr="//button[@type='submit']" position="before">
<a t-if="signup" t-attf-href="?mode=signup{{ '&amp;debug' if debug else '' }}" class="btn btn-link pull-right">Sign up</a>
<a t-if="reset" t-attf-href="?mode=reset{{ '&amp;debug' if debug else '' }}" class="btn btn-link pull-right">Reset Password</a>
<a t-if="signup" t-attf-href="?{{ keep_query('*', mode='signup') }}" class="btn btn-link pull-right">Sign up</a>
<a t-if="reset" t-attf-href="?{{ keep_query('*', mode='reset') }}" class="btn btn-link pull-right">Reset Password</a>
</xpath>
</template>
@ -29,7 +29,7 @@
<t t-set="reset_without_token" t-value="mode == 'reset' and not token"/>
<form class="oe_signup_form" role="form" t-attf-action="/web/login{{ '?debug' if debug else '' }}" method="post">
<form class="oe_signup_form" role="form" method="post">
<t t-call="web.database_select"/>
<div class="form-group field-name" t-if="not reset_without_token">
@ -68,7 +68,7 @@
<input type="hidden" name="mode" t-att-value="mode"/>
<input type="hidden" name="token" t-att-value="token"/>
<div class="clearfix oe_login_buttons">
<a href="/web/login" class="btn btn-link pull-right">Back to Login</a>
<a t-attf-href="?{{ keep_query('*', mode='login') }}" class="btn btn-link pull-right">Back to Login</a>
<button type="submit" class="btn btn-primary pull-left">
<t t-if="mode == 'signup'">Sign up</t>
<t t-if="mode == 'reset'">Reset password</t>

View File

@ -123,8 +123,9 @@ class crm_case_section(osv.osv):
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
} for i in range(self._period_number - 1, -1, -1)]
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
for group in group_obj:
group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT)
group_begin_date = datetime.strptime(group['__domain'][0][2], pattern)
month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
return section_result

View File

@ -28,7 +28,8 @@
<div>Use template
<!--FIX: To avoid css issue of many2one field in footer temporary used oe_form (BUG:1152464)-->
<field name="template_id" nolabel="1" class='oe_inline'
on_change="onchange_template_id(template_id, composition_mode, model, res_id, context)" domain="[('model_id.model','=',model)]"/>
on_change="onchange_template_id(template_id, composition_mode, model, res_id, context)" domain="[('model_id.model','=',model)]"
context="{'default_model': model, 'default_body_html': body, 'default_subject': subject}"/>
</div>
<button icon="/email_template/static/src/img/email_template_save.png"
type="object" name="save_as_template" string="Save as new template" class="oe_link"

View File

@ -387,6 +387,7 @@ class gamification_challenge(osv.Model):
end_date = challenge.end_date
for line in challenge.line_ids:
# FIXME: allow to restrict to a subset of users
for user in challenge.user_ids:
goal_obj = self.pool.get('gamification.goal')
@ -400,8 +401,9 @@ class gamification_challenge(osv.Model):
# resume canceled goals
domain.append(('state', '=', 'canceled'))
canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
goal_obj.update(cr, uid, canceled_goal_ids, context=context)
if canceled_goal_ids:
goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
goal_obj.update(cr, uid, canceled_goal_ids, context=context)
# skip to next user
continue
@ -516,13 +518,6 @@ class gamification_challenge(osv.Model):
domain.append(('user_id', '=', user_id))
sorting = goal_obj._order
limit = 1
# initialise in case search returns no results
line_data.update({
'id': 0,
'current': 0,
'completeness': 0,
'state': 'draft',
})
else:
line_data.update({
'own_goal_id': False,
@ -559,7 +554,8 @@ class gamification_challenge(osv.Model):
'completeness': goal.completeness,
'state': goal.state,
})
res_lines.append(line_data)
if goal_ids:
res_lines.append(line_data)
return res_lines
##### Reporting #####

View File

@ -163,7 +163,8 @@ class gamification_goal(osv.Model):
string="Challenge",
type='many2one',
relation='gamification.challenge',
store=True),
store=True, readonly=True,
help="Challenge that generated the goal, assign challenge to users to generate goals with a value in this field."),
'start_date': fields.date('Start Date'),
'end_date': fields.date('End Date'), # no start and end = always active
'target_goal': fields.float('To Reach',

View File

@ -42,6 +42,7 @@ class res_users_gamification_group(osv.Model):
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
return write_res
def create(self, cr, uid, vals, context=None):
@ -56,6 +57,7 @@ class res_users_gamification_group(osv.Model):
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
return write_res
# def get_goals_todo_info(self, cr, uid, context=None):
@ -83,13 +85,15 @@ class res_users_gamification_group(osv.Model):
challenge_ids = challenge_obj.search(cr, uid, [('user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
# serialize goals info to be able to use it in javascript
all_goals_info.append({
'id': challenge.id,
'name': challenge.name,
'visibility_mode': challenge.visibility_mode,
'currency': user.company_id.currency_id.id,
'lines': challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context),
})
lines = challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context)
if lines:
all_goals_info.append({
'id': challenge.id,
'name': challenge.name,
'visibility_mode': challenge.visibility_mode,
'currency': user.company_id.currency_id.id,
'lines': lines,
})
return all_goals_info
@ -129,4 +133,5 @@ class res_groups_gamification_group(osv.Model):
challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', ids)], context=context)
if challenge_ids:
challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
challenge_obj.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
return write_res

View File

@ -55,7 +55,7 @@
<group string="Reference">
<field name="definition_id" on_change="on_change_definition_id(definition_id)" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="challenge_id" attrs="{'readonly':[('state','!=','draft')]}"/>
<field name="challenge_id" />
</group>
<group string="Schedule">
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
@ -280,8 +280,8 @@
<!-- menus in settings - technical feature required -->
<menuitem id="gamification_menu" name="Gamification Tools" parent="base.menu_administration" groups="base.group_no_one" />
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="0"/>
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="10"/>
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="0"/>
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="10"/>
<menuitem id="gamification_definition_menu" parent="gamification_menu" action="goal_definition_list_action" sequence="20"/>
<menuitem id="gamification_badge_menu" parent="gamification_menu" action="badge_list_action" sequence="30"/>

View File

@ -37,4 +37,5 @@ Badge received are displayed on the user profile.
'views/gamification.xml',
],
'js': ['static/src/js/gamification.js'],
'auto_install': True,
}

View File

@ -50,7 +50,7 @@
<field name="view_type">form</field>
<field name="name">Goals History</field>
<field name="view_mode">tree,kanban</field>
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_type': True}</field>
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_definition': True}</field>
<field name="domain">[('challenge_id.category', '=', 'hr')]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">

View File

@ -179,6 +179,7 @@ class Website(openerp.addons.web.controllers.main.Home):
result.append({
'name': v.inherit_option_id.name,
'id': v.id,
'xml_id': v.xml_id,
'inherit_id': v.inherit_id.id,
'header': True,
'active': False
@ -187,6 +188,7 @@ class Website(openerp.addons.web.controllers.main.Home):
result.append({
'name': v.name,
'id': v.id,
'xml_id': v.xml_id,
'inherit_id': v.inherit_id.id,
'header': False,
'active': (v.inherit_id.id == v.inherit_option_id.id) or (not optional and v.inherit_id.id)
@ -239,13 +241,20 @@ class Website(openerp.addons.web.controllers.main.Home):
@http.route('/website/attach', type='http', auth='user', methods=['POST'], website=True)
def attach(self, func, upload):
req = request.httprequest
url = message = None
try:
image_data = upload.read()
image = Image.open(cStringIO.StringIO(image_data))
w, h = image.size
if w*h > 42e6: # Nokia Lumia 1020 photo resolution
raise ValueError(
u"Image size excessive, uploaded images must be smaller "
u"than 42 million pixel")
attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
'name': upload.filename,
'datas': upload.read().encode('base64'),
'datas': image_data.encode('base64'),
'datas_fname': upload.filename,
'res_model': 'ir.ui.view',
}, request.context)
@ -259,7 +268,7 @@ class Website(openerp.addons.web.controllers.main.Home):
})
except Exception, e:
logger.exception("Failed to upload image to attachment")
message = str(e)
message = unicode(e)
return """<script type='text/javascript'>
window.parent['%s'](%s, %s);

View File

@ -59,7 +59,7 @@
</record>
<record id="action_website_tutorial" model="ir.actions.act_url">
<field name="name">Website With Tutorial</field>
<field name="url">/?tutorial.banner=true</field>
<field name="url">/#tutorial.banner=true</field>
<field name="target">self</field>
</record>
<record id="action_website_homepage" model="ir.actions.act_url">

View File

@ -167,5 +167,5 @@ class PageConverter(werkzeug.routing.PathConverter):
for view in views:
xid = xids[view['id']]
if xid and (not query or query in xid):
if xid and (not query or query.lower() in xid.lower()):
yield xid

View File

@ -1,8 +1,12 @@
# -*- coding: utf-8 -*-
import copy
import simplejson
import werkzeug
from lxml import etree, html
from openerp.addons.website.models import website
from openerp.http import request
from openerp.osv import osv, fields
class view(osv.osv):
@ -117,6 +121,42 @@ class view(osv.osv):
return arch
def render(self, cr, uid, id_or_xml_id, values=None, engine='ir.qweb', context=None):
if getattr(request, 'website_enabled', False):
engine='website.qweb'
if isinstance(id_or_xml_id, list):
id_or_xml_id = id_or_xml_id[0]
if isinstance(id_or_xml_id, (int, long)):
id_or_xml_id = self.get_view_xmlid(cr, uid, id_or_xml_id)
if not context:
context = {}
qcontext = context.copy()
qcontext.update(
website=request.website,
url_for=website.url_for,
slug=website.slug,
res_company=request.website.company_id,
user_id=self.pool.get("res.users").browse(cr, uid, uid),
)
# add some values
if values:
qcontext.update(values)
# in edit mode ir.ui.view will tag nodes
context['inherit_branding'] = qcontext['editable']
view_obj = request.website.get_template(id_or_xml_id)
if 'main_object' not in qcontext:
qcontext['main_object'] = view_obj
values = qcontext
return super(view, self).render(cr, uid, id_or_xml_id, values=values, engine=engine, context=context)
def save(self, cr, uid, res_id, value, xpath=None, context=None):
""" Update a view section. The view section may embed fields to write

View File

@ -29,6 +29,14 @@ class website_config_settings(osv.osv_memory):
values[fname] = v[0] if v and self._columns[fname]._type == 'many2one' else v
return {'value' : values}
# FIXME in trunk for god sake. Change the fields above to fields.char instead of fields.related,
# and create the function set_website who will set the value on the website_id
# create does not forward the values to the related many2one. Write does.
def create(self, cr, uid, vals, context=None):
config_id = super(website_config_settings, self).create(cr, uid, vals, context=context)
self.write(cr, uid, config_id, vals, context=context)
return config_id
_defaults = {
'website_id': lambda self,cr,uid,c: self.pool.get('website').search(cr, uid, [], context=c)[0],
}

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import fnmatch
import inspect
import itertools
import logging
@ -7,7 +6,6 @@ import math
import re
import urlparse
import simplejson
import werkzeug
import werkzeug.exceptions
import werkzeug.wrappers
@ -24,24 +22,19 @@ from openerp.addons.web.http import request, LazyResponse
logger = logging.getLogger(__name__)
def keep_query(*args, **kw):
if not args and not kw:
args = ('*',)
params = kw.copy()
query_params = frozenset(werkzeug.url_decode(request.httprequest.query_string).keys())
for keep_param in args:
for param in fnmatch.filter(query_params, keep_param):
if param not in params and param in request.params:
params[param] = request.params[param]
return werkzeug.urls.url_encode(params)
def url_for(path_or_uri, lang=None):
if isinstance(path_or_uri, unicode):
path_or_uri = path_or_uri.encode('utf-8')
current_path = request.httprequest.path
if isinstance(current_path, unicode):
current_path = current_path.encode('utf-8')
location = path_or_uri.strip()
force_lang = lang is not None
url = urlparse.urlparse(location)
if request and not url.netloc and not url.scheme and (url.path or force_lang):
location = urlparse.urljoin(request.httprequest.path, location)
location = urlparse.urljoin(current_path, location)
lang = lang or request.context.get('lang')
langs = [lg[0] for lg in request.website.get_languages()]
@ -59,7 +52,7 @@ def url_for(path_or_uri, lang=None):
ps.insert(1, lang)
location = '/'.join(ps)
return location
return location.decode('utf-8')
def is_multilang_url(path, langs=None):
if not langs:
@ -78,7 +71,11 @@ def is_multilang_url(path, langs=None):
def slugify(s, max_length=None):
if slugify_lib:
return slugify_lib.slugify(s, max_length)
# There are 2 different libraries only python-slugify is supported
try:
return slugify_lib.slugify(s, max_length=max_length)
except TypeError:
pass
spaceless = re.sub(r'\s+', '-', s)
specialless = re.sub(r'[^-_A-Za-z0-9]', '', spaceless)
return specialless[:max_length]
@ -215,7 +212,6 @@ class website(osv.osv):
request.redirect = lambda url: werkzeug.utils.redirect(url_for(url))
request.context.update(
is_master_lang=is_master_lang,
editable=is_website_publisher,
translatable=not is_master_lang,
)
@ -228,37 +224,8 @@ class website(osv.osv):
return self.pool["ir.ui.view"].browse(cr, uid, view_id, context=context)
def _render(self, cr, uid, ids, template, values=None, context=None):
user = self.pool.get("res.users")
if not context:
context = {}
# Take a context
qweb_values = context.copy()
# add some values
if values:
qweb_values.update(values)
# fill some defaults
qweb_values.update(
request=request,
json=simplejson,
website=request.website,
url_for=url_for,
keep_query=keep_query,
slug=slug,
res_company=request.website.company_id,
user_id=user.browse(cr, uid, uid),
quote_plus=werkzeug.url_quote_plus,
)
qweb_values.setdefault('editable', False)
# in edit mode ir.ui.view will tag nodes
context['inherit_branding'] = qweb_values['editable']
view = self.get_template(cr, uid, ids, template)
if 'main_object' not in qweb_values:
qweb_values['main_object'] = view
return view.render(qweb_values, engine='website.qweb', context=context)
# TODO: remove this. (just kept for backward api compatibility for saas-3)
return self.pool['ir.ui.view'].render(cr, uid, template, values=values, context=context)
def render(self, cr, uid, ids, template, values=None, status_code=None, context=None):
def callback(template, values, context):

View File

@ -1,6 +1,7 @@
@import "compass/css3"
@import "compass/css3/user-interface"
@import "compass/css3/transition"
@import "compass/support"
//smartphones, iPhone, portrait 480x320 phones
$smart_phone: 320px
@ -23,3 +24,46 @@ $desktop: 1025px
@content
::selection
@content
// Lifted from compass-animation
// LICENSED UNDER THE MIT LICENSE (MIT)
//
// Copyright (c) 2012 Eric Meyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@mixin set-experimental-support($moz: false, $webkit: false, $ms: false, $o: false, $khtml: false)
$experimental-support-for-mozilla: $moz
$experimental-support-for-webkit: $webkit
$experimental-support-for-microsoft: $ms
$experimental-support-for-opera: $o
$experimental-support-for-khtml: $khtml
@mixin with-only-support-for($moz: false, $webkit: false, $ms: false, $o: false, $khtml: false)
// Capture the current state
$original-moz: $experimental-support-for-mozilla
$original-webkit: $experimental-support-for-webkit
$original-o: $experimental-support-for-opera
$original-ms: $experimental-support-for-microsoft
$original-khtml: $experimental-support-for-khtml
@include set-experimental-support($moz, $webkit, $ms, $o, $khtml)
@content
@include set-experimental-support($original-moz, $original-webkit, $original-ms, $original-o, $original-khtml)
@mixin keyframes($name)
@-moz-keyframes #{$name}
+with-only-support-for($moz: true)
@content
@-webkit-keyframes #{$name}
+with-only-support-for($webkit: true)
@content
@-o-keyframes #{$name}
+with-only-support-for($o: true)
@content
@keyframes #{$name}
+with-only-support-for
@content

View File

@ -195,13 +195,19 @@ ul.oe_menu_editor .disclose {
text-align: left;
}
.modal.nosave .modal-footer button.save {
display: none;
}
.modal.nosave .modal-footer button.wait {
.modal.nosave .wait {
display: inline-block !important;
visibility: visible !important;
}
.modal.nosave .modal-body .filepicker, .modal.nosave .modal-body .image-preview {
display: none;
}
.modal.nosave .modal-body .wait {
width: 100%;
}
.modal.nosave .modal-footer .save {
display: none;
}
.modal .font-icons-icons {
font-size: 2em;

View File

@ -162,12 +162,17 @@ ul.oe_menu_editor
.modal-footer
text-align: left
.modal.nosave .modal-footer
button.save
display: none
button.wait
.modal.nosave
.wait
display: inline-block !important
visibility: visible !important
.modal-body
.filepicker, .image-preview
display: none
.wait
width: 100%
.modal-footer .save
display: none
// fontawesome modal
.modal

View File

@ -276,12 +276,16 @@ ul.nav-stacked > li > a {
}
/* ---- SNIPPETS --- */
.oe_img_bg {
background-size: 100%;
}
[data-snippet-id], .colmd, .hr, .blockquote {
overflow: hidden;
}
@media (max-width: 400px) {
[data-snippet-id] {
[data-snippet-id]:not([data-snippet-id="carousel"]) {
height: auto !important;
}
}
@ -290,7 +294,7 @@ ul.nav-stacked > li > a {
}
.carousel-inner .item {
height: 100%;
background-size: 100%;
background-size: cover;
}
.carousel .carousel-caption {
@ -360,7 +364,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
.parallax {
position: relative;
background-size: 100%;
background-size: cover;
display: table;
width: 100%;
min-height: 100px;
@ -440,10 +444,6 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
}
/* Misc */
.oe_img_bg {
background-size: 100%;
}
.texttop {
vertical-align: top;
}

View File

@ -224,19 +224,24 @@ ul.nav-stacked > li > a
a
color: white
/* ---- SNIPPETS --- */
.oe_img_bg
background-size: 100%
[data-snippet-id],.colmd,.hr,.blockquote
overflow: hidden
@media (max-width: 400px)
[data-snippet-id]
[data-snippet-id]:not([data-snippet-id="carousel"])
height: auto !important
.carousel-inner
height: 100%
.item
height: 100%
background-size: 100%
background-size: cover
.carousel
.carousel-caption
left: auto
@ -296,7 +301,7 @@ div.carousel[data-snippet-id="slider"]
.parallax
position: relative
background-size: 100%
background-size: cover
display: table
width: 100%
min-height: 100px
@ -358,9 +363,6 @@ div.carousel[data-snippet-id="slider"]
/* Misc */
.oe_img_bg
background-size: 100%
.texttop
vertical-align: top

View File

@ -10,6 +10,9 @@
if (!is_smartphone) {
website.ready().then(website.init_editor);
} else {
// remove padding of fake editor bar
document.body.style.padding = 0;
}
$(document).on('click', 'a.js_link2post', function (ev) {
@ -411,6 +414,7 @@
openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': view_name }).then(
function(result) {
_.each(result, function (item) {
if (item.xml_id === "website.debugger" && !window.location.search.match(/[&?]debug(&|$)/)) return;
if (item.header) {
menu.append('<li class="dropdown-header">' + item.name + '</li>');
} else {
@ -437,6 +441,12 @@
});
},
start: function() {
// remove placeholder editor bar
var fakebar = document.getElementById('website-top-navbar-placeholder');
if (fakebar) {
fakebar.parentNode.removeChild(fakebar);
}
var self = this;
this.saving_mutex = new openerp.Mutex();
@ -1073,7 +1083,7 @@
} else {
// Create the page, get the URL back
done = $.get(_.str.sprintf(
'/pagenew/%s?noredirect=1', encodeURI(data.id)))
'/website/add/%s?noredirect=1', encodeURI(data.id)))
.then(function (response) {
self.make_link(response, false, data.id);
});
@ -1265,7 +1275,7 @@
}),
start: function () {
this.$('.modal-footer [disabled]').text("Uploading…");
this.$('button.wait').text("Uploading…");
var $options = this.$('.image-style').children();
this.image_styles = $options.map(function () { return this.value; }).get();
@ -1298,13 +1308,16 @@
* Sets the provided image url as the dialog's value-to-save and
* refreshes the preview element to use it.
*/
set_image: function (url) {
set_image: function (url, error) {
this.$('input.url').val(
error ? '' : url);
this.$('input.url').val(url);
this.preview_image();
},
file_selection: function () {
this.$el.addClass('nosave');
this.$('form').removeClass('has-error').find('.help-block').empty();
this.$('button.filepicker').removeClass('btn-danger btn-success');
var self = this;
@ -1319,25 +1332,38 @@
},
file_selected: function(url, error) {
var $button = this.$('button.filepicker');
if (error) {
if (!error) {
$button.addClass('btn-success');
} else {
url = null;
this.$('form').addClass('has-error')
.find('.help-block').text(error);
$button.addClass('btn-danger');
return;
}
$button.addClass('btn-success');
this.set_image(url);
this.set_image(url, error);
},
preview_image: function () {
this.$el.removeClass('nosave');
var loaded = function () {
this.$el.removeClass('nosave');
}.bind(this);
var image = this.$('input.url').val();
if (!image) { return; }
if (!image) { loaded(); return; }
this.$('img.image-preview')
var $img = this.$('img.image-preview')
.attr('src', image)
.removeClass(this.image_styles.join(' '))
.addClass(this.$('select.image-style').val());
if ($img.prop('complete')) {
loaded();
} else {
$img.load(loaded)
}
},
browse_existing: function (e) {
e.preventDefault();
this.$('form').removeClass('has-error').find('.help-block').empty();
this.$('button.filepicker').removeClass('btn-danger btn-success');
new website.editor.ExistingImageDialog(this).appendTo(document.body);
},
});

View File

@ -2,6 +2,7 @@
"use strict";
var website = openerp.website;
var _t = openerp._t;
website.is_editable = true;
website.is_editable_button = true;
@ -16,7 +17,8 @@
'click a[data-action=new_page]': function (ev) {
ev.preventDefault();
website.prompt({
window_title: "New Page",
id: "editor_new_page",
window_title: _t("New Page"),
input: "Page Title",
}).then(function (val) {
if (val) {

View File

@ -105,7 +105,7 @@
var offset = 0;
var padding = parseInt($(document.body).css("padding-top"));
if (speed < 1) {
var inner_offset = self.$target.outerHeight() - this.height / this.width * document.body.clientWidth;
var inner_offset = - self.$target.outerHeight() + this.height / this.width * document.body.clientWidth;
var outer_offset = self.$target.offset().top - (document.body.clientHeight - self.$target.outerHeight()) - padding;
offset = - outer_offset * speed + inner_offset;
} else {

View File

@ -9,7 +9,7 @@
var self = this;
$("[data-oe-model]").on('click', function (event) {
var $this = $(event.srcElement);
var tag = $this[0].tagName.toLowerCase();
var tag = $this[0] && $this[0].tagName.toLowerCase();
if (!(tag === 'a' || tag === "button") && !$this.parents("a, button").length) {
self.$('[data-action="edit"]').parent().effect('bounce', {distance: 18, times: 5}, 250);
}
@ -416,6 +416,25 @@
}
setTimeout(function () {
$("#oe_snippets").trigger('snippet-dropped', $target);
// reset snippet for rte
$target.removeData("snippet-editor");
if ($target.data("overlay")) {
$target.data("overlay").remove();
$target.removeData("overlay");
}
self.create_overlay($target);
$target.find("[data-snippet-id]").each(function () {
var $snippet = $(this);
$snippet.removeData("snippet-editor");
if ($snippet.data("overlay")) {
$snippet.data("overlay").remove();
$snippet.removeData("overlay");
}
self.create_overlay($snippet);
});
// end
self.make_active($target);
},0);
} else {

View File

@ -1,40 +0,0 @@
var tests = {};
var droptest = function () {
var errors = [];
for (var snippet_id in tests) {
if (!tests[snippet_id]['activated'] || !tests[snippet_id]['dropped']){
console.log("Can't dropped or activated snippet: " + snippet_id);
}
}
if (errors.length) {
console.log(tests);
throw new Error("Can't dropped or activated at least one snippet");
}
$("#oe_snippets").off('snippet-activated snippet-dropped');
};
var droptesttime = setTimeout(droptest,0);
$("#oe_snippets").off('snippet-activated snippet-dropped')
.on('snippet-activated', function (event, dom) {
tests[$(dom).data('src-snippet-id')]['activated'] = true;
clearTimeout(droptesttime);
droptesttime = setTimeout(droptest,0);
})
.on('snippet-dropped', function (event, dom, src_snipped_id) {
tests[$(dom).data('src-snippet-id')]['dropped'] = true;
clearTimeout(droptesttime);
droptesttime = setTimeout(droptest,0);
});
var $thumbnails = $('#oe_snippets div.oe_snippet[data-snippet-id] .oe_snippet_thumbnail');
$thumbnails.each(function () {
var $thumbnail = $(this);
tests[$thumbnail.parent().data('snippet-id')] = {};
var position = $thumbnail.position();
$thumbnail.trigger( $.Event( "mousedown", { which: 1, pageX: position.left, pageY: position.top } ) );
$thumbnail.trigger( $.Event( "mousemove", { which: 1, pageX: position.left+100, pageY: position.top+100 } ) );
$first_drop = $(".oe_drop_zone").first();
position = $first_drop.position();
$first_drop.trigger( $.Event( "mouseup", { which: 1, pageX: position.left+20, pageY: position.top+20 } ) );
clearTimeout(droptesttime);
droptesttime = setTimeout(droptest,0);
});

View File

@ -2,114 +2,113 @@
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.EditorBar.include({
start: function () {
this.registerTour(new website.BannerTour(this));
this.registerTour(new website.Tour.Banner(this));
return this._super();
},
});
website.BannerTour = website.Tour.extend({
website.Tour.Banner = website.Tour.extend({
id: 'banner',
name: "Build a page",
path: '/page/website.homepage',
init: function (editor) {
init: function () {
var self = this;
self.steps = [
{
title: "Welcome to your website!",
content: "This tutorial will guide you to build your home page. We will start by adding a banner.",
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
backdrop: true,
title: _t("Welcome to your website!"),
content: _t("This tutorial will guide you to build your home page. We will start by adding a banner."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
waitNot: '.popover.tour',
element: 'button[data-action=edit]',
placement: 'bottom',
title: "Edit this page",
content: "Every page of your website can be modified through the <i>Edit</i> button.",
template: self.popover({ fixed: true }),
title: _t("Edit this page"),
content: _t("Every page of your website can be modified through the <i>Edit</i> button."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Insert building blocks",
content: "To add content in a page, you can insert building blocks.",
template: self.popover({ fixed: true }),
title: _t("Insert building blocks"),
content: _t("To add content in a page, you can insert building blocks."),
popover: { fixed: true },
},
{
snippet: 'carousel',
placement: 'bottom',
title: "Drag & Drop a Banner",
content: "Drag the Banner block and drop it in your page.",
template: self.popover({ fixed: true }),
title: _t("Drag & Drop a Banner"),
content: _t("Drag the Banner block and drop it in your page."),
popover: { fixed: true },
},
{
waitFor: '.oe_overlay_options .oe_options:visible',
element: '#wrap [data-snippet-id=carousel]:first .carousel-caption',
sampleText: 'My Title',
placement: 'top',
title: "Customize banner's text",
content: "Click in the text and start editing it.",
title: _("Customize banner's text"),
content: _("Click in the text and start editing it."),
popover: { next: _t("Continue") },
},
{
waitNot: '#wrap [data-snippet-id=carousel]:first .carousel-caption:contains("Your Banner Title")',
element: '.oe_overlay_options .oe_options',
placement: 'left',
title: "Customize the banner",
content: "Customize any block through this menu. Try to change the background of the banner.",
template: self.popover({ next: "Continue" }),
title: _t("Customize the banner"),
content: _t("Customize any block through this menu. Try to change the background of the banner."),
popover: { next: _t("Continue") },
},
{
waitNot: '.popover.tour',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Add Another Block",
content: "Let's add another building block to your page.",
template: self.popover({ fixed: true }),
title: _t("Add Another Block"),
content: _t("Let's add another building block to your page."),
popover: { fixed: true },
},
{
snippet: 'three-columns',
placement: 'bottom',
title: "Drag & Drop a Block",
content: "Drag the <em>'3 Columns'</em> block and drop it below the banner.",
template: self.popover({ fixed: true }),
title: _t("Drag & Drop a Block"),
content: _t("Drag the <em>'3 Columns'</em> block and drop it below the banner."),
popover: { fixed: true },
},
{
waitFor: '.oe_overlay_options .oe_options:visible',
element: 'button[data-action=save]',
placement: 'right',
title: "Save your modifications",
content: "Publish your page by clicking on the <em>'Save'</em> button.",
template: self.popover({ fixed: true }),
title: _t("Save your modifications"),
content: _t("Publish your page by clicking on the <em>'Save'</em> button."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
title: "Good Job!",
content: "Well done, you created your homepage.",
template: self.popover({ next: "Continue" }),
title: _("Good Job!"),
content: _("Well done, you created your homepage."),
popover: { next: _t("Continue") },
},
{
waitNot: '.popover.tour',
element: 'a[data-action=show-mobile-preview]',
placement: 'bottom',
title: "Test Your Mobile Version",
content: "Let's check how your homepage looks like on mobile devices.",
template: self.popover({ fixed: true }),
title: _t("Test Your Mobile Version"),
content: _t("Let's check how your homepage looks like on mobile devices."),
popover: { fixed: true },
},
{
element: 'button[data-dismiss=modal]',
placement: 'right',
title: "Close Mobile Preview",
content: "Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog.",
title: _t("Close Mobile Preview"),
content: _t("Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog."),
},
{
title: "Congratulation",
element: 'a[id=content-menu-button]',
placement: 'bottom',
content: "This tour is finished. You can continue discovering features with the <em>'Content'</em> menu.",
template: self.popover({ next: "Close Tutorial" }),
content: _t("This tour is finished. You can continue discovering features with the <em>'Content'</em> menu."),
popover: { fixed: true, next: _t("Close Tutorial") },
},
];
return this._super();

View File

@ -2,15 +2,14 @@
'use strict';
var website = openerp.website;
if (typeof QWeb2 !== "undefined")
website.add_template_file('/website/static/src/xml/website.tour.xml');
if (website.EditorBar)
website.EditorBar.include({
tours: [],
start: function () {
// $('.tour-backdrop').click(function (e) {
// e.stopImmediatePropagation();
// e.preventDefault();
// });
var self = this;
var menu = $('#help-menu');
_.each(this.tours, function (tour) {
@ -21,31 +20,11 @@ website.EditorBar.include({
});
menu.append($menuItem);
});
this.waitRTEReady = false;
this.on('rte:called', this, function () {self.waitRTEReady = true; });
this.on('rte:ready', this, function () {self.waitRTEReady = false;});
var res = this._super();
website.Tour.waitReady.call(this, this.testRunning);
return res;
return this._super();
},
registerTour: function (tour) {
website.Tour.add(tour);
this.tours.push(tour);
},
testRunning: function () {
if (this.waitRTEReady) {
this.on('rte:ready', this, function () {
website.Tour.each(function () {
this.running();
});
});
} else {
website.Tour.each(function () {
this.running();
});
}
}
});
@ -82,23 +61,12 @@ $.ajaxSetup({
}
});
website.Tour = openerp.Class.extend({
steps: [],
defaultDelay: 50, //ms
defaultOverLaps: 5000, //ms
localStorage: window.localStorage,
init: function (url) {
this.tour = new Tour({
name: this.id,
storage: this.tourStorage,
keyboard: false,
template: this.popover(),
onHide: function () {
window.scrollTo(0, 0);
}
});
this.registerSteps();
},
init: function () {},
run: function (automatic) {
this.reset();
@ -109,15 +77,16 @@ website.Tour = openerp.Class.extend({
website.Tour.busy = true;
if (automatic) {
this.localStorage.setItem("tour-"+this.id+"-test-automatic", true);
}
this.localStorage.setItem("tour-"+this.id+"-test-automatic", automatic);
this.automatic = automatic;
// redirect to begin of the tour
if (this.path) {
var path = this.path.split('?');
window.location.href = path[0] + "?tutorial."+this.id+"=true" + path.slice(1, path.length).join("?");
return;
// redirect to begin of the tour in function of the language
if (!this.testUrl(this.path+"(#.*)?$")) {
var path = this.path.split('#');
window.location.href = "/"+this.getLang()+path[0] + "#tutorial."+this.id+"=true&" + path.slice(1, path.length).join("#");
return;
}
}
var self = this;
@ -125,12 +94,13 @@ website.Tour = openerp.Class.extend({
website.Tour.waitReady.call(this, function () {self._running();});
},
running: function () {
if (+this.localStorage.getItem("tour-"+this.id+"-test") >= this.steps.length) {
var self = this;
if (+this.localStorage.getItem("tour-"+this.id+"-test") >= this.steps.length-1) {
this.endTour();
return;
}
if (website.Tour.is_busy() || !this.testUrl()) return;
if (website.Tour.is_busy()) return;
// launch tour with url
this.checkRunningUrl();
@ -138,22 +108,32 @@ website.Tour = openerp.Class.extend({
// mark tour as busy (only one test running)
if (this.localStorage.getItem("tour-"+this.id+"-test") != null) {
website.Tour.busy = true;
this.automatic = !!this.localStorage.getItem("tour-"+this.id+"-test-automatic");
}
if (!this.testPathUrl()) {
if (this.automatic) {
this.timer = setTimeout(function () {
self.reset();
throw new Error("Wrong url for running " + self.id
+ '\ntestPath: ' + self.testPath
+ '\nhref: ' + window.location.href
+ "\nreferrer: " + document.referrer
);
},this.defaultOverLaps);
}
return;
}
var self = this;
website.Tour.waitReady.call(this, function () {self._running();});
},
_running: function () {
var stepId = this.localStorage.getItem("tour-"+this.id+"-test");
var automatic = !!this.localStorage.getItem("tour-"+this.id+"-test-automatic");
if (stepId != null) {
if (!this.check(this.step(stepId))) {
var step = this.next(stepId);
stepId = step ? step.stepId : stepId;
}
this.nextStep(stepId, automatic ? this.autoNextStep : null, automatic ? 5000 : null);
this.registerTour();
this.nextStep(stepId, this.automatic ? this.autoNextStep : null, this.automatic ? this.defaultOverLaps : null);
}
},
@ -174,30 +154,51 @@ website.Tour = openerp.Class.extend({
$('.popover.tour').remove();
},
testUrl: function () {
return !this.testPath || this.testPath.test(window.location.href);
getLang: function () {
return $("html").attr("lang").replace(/-/, '_');
},
testUrl: function (url) {
return new RegExp("(/"+this.getLang()+")?"+url, "i").test(window.location.href);
},
testPathUrl: function () {
if (!this.testPath || this.testUrl(this.testPath)) return true;
},
checkRunningUrl: function () {
if (window.location.search.indexOf("tutorial."+this.id+"=true") > -1) {
if (window.location.hash.indexOf("tutorial."+this.id+"=true") > -1) {
this.localStorage.setItem("tour-"+this.id+"-test", 0);
window.location.href = window.location.href.replace(/tutorial.+=true&?/, '');
window.location.hash = window.location.hash.replace(/tutorial.+=true&?/, '');
}
},
registerTour: function () {
this.tour = new Tour({
name: this.id,
storage: this.tourStorage,
keyboard: false,
template: this.popover(),
onHide: function () {
window.scrollTo(0, 0);
}
});
this.registerSteps();
},
registerSteps: function () {
for (var index=0, len=this.steps.length; index<len; index++) {
var step = this.steps[index];
step.stepId = step.stepId || ""+index;
if (!step.waitNot && index > 0 && $(this.steps[index-1].template).has("button[data-role='next']").size()) {
step.waitNot = '.popover.tour';
if (!step.waitNot && index > 0 && this.steps[index-1] &&
this.steps[index-1].popover && this.steps[index-1].popover.next) {
step.waitNot = '.popover.tour:visible';
}
if (!step.waitFor && index > 0 && this.steps[index-1].snippet) {
step.waitFor = '.oe_overlay_options .oe_options:visible';
}
step._title = step.title;
step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title });
step._title = step._title || step.title;
step.title = this.popoverTitle({ title: step._title });
step.template = step.template || this.popover( step.popover );
if (!step.element) step.orphan = true;
if (step.snippet) {
step.element = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail';
@ -205,9 +206,10 @@ website.Tour = openerp.Class.extend({
}
if ($(this.steps[index-1].template).has("button[data-role='next']").size()) {
if (this.steps[index-1] &&
this.steps[index-1].popover && this.steps[index-1].popover.next) {
var step = {
stepId: index,
stepId: ""+index,
waitNot: '.popover.tour:visible'
};
this.steps.push(step);
@ -216,8 +218,21 @@ website.Tour = openerp.Class.extend({
this.tour.addSteps(this.steps);
},
popoverTitle: function (options) {
try {
return openerp.qweb.render('website.tour_popover_title', options);
} catch (e) {
if (!this.automatic) throw e;
return options.title;
}
},
popover: function (options) {
return openerp.qweb.render('website.tour_popover', options);
try {
return openerp.qweb.render('website.tour_popover', options);
} catch (e) {
if (!this.automatic) throw e;
return "";
}
},
timer: null,
@ -265,7 +280,12 @@ website.Tour = openerp.Class.extend({
self.timer = setTimeout(checkNext, self.defaultDelay);
} else {
self.reset();
throw new Error("Time overlaps to arrive to step " + step.stepId + ": '" + step._title + "'");
throw new Error("Time overlaps to arrive to step " + step.stepId + ": '" + step._title + "'"
+ '\nelement: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden")))
+ '\nwaitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size())
+ '\nwaitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size())
+ '\n\n' + $("body").html()
);
}
}
checkNext();
@ -288,7 +308,7 @@ website.Tour = openerp.Class.extend({
$(".popover.tour").remove();
// go to step in bootstrap tour
this.tour.goto(index);
if (step.callback) step.callback();
if (step.onload) step.onload();
next = steps.shift();
break;
}
@ -315,7 +335,11 @@ website.Tour = openerp.Class.extend({
}
},
endTour: function () {
console.log('{ "event": "success" }');
if (parseInt(this.localStorage.getItem("tour-"+this.id+"-test"),10) >= this.steps.length-1) {
console.log('{ "event": "success" }');
} else {
console.log('{ "event": "canceled" }');
}
this.reset();
},
autoNextStep: function () {
@ -351,7 +375,10 @@ website.Tour = openerp.Class.extend({
} else if (step.sampleText) {
$element.trigger($.Event("keydown", { srcElement: $element }));
if ($element.is("select") || $element.is("input") ) {
if ($element.is("input") ) {
$element.val(step.sampleText);
} if ($element.is("select")) {
$element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true);
$element.val(step.sampleText);
} else {
$element.html(step.sampleText);
@ -397,7 +424,10 @@ website.Tour.busy = false;
website.Tour.add = function (tour) {
website.Tour.waitReady(function () {
tour = tour.id ? tour : new tour();
website.Tour.tours[tour.id] = tour;
if (!website.Tour.tours[tour.id]) {
website.Tour.tours[tour.id] = tour;
tour.running();
}
});
};
website.Tour.get = function (id) {
@ -413,7 +443,7 @@ website.Tour.each = function (callback) {
website.Tour.waitReady = function (callback) {
var self = this;
$(document).ready(function () {
if ($.ajaxBusy == null || $.ajaxBusy) {
if ($.ajaxBusy) {
$(document).ajaxStop(function() {
setTimeout(function () {
callback.call(self);
@ -428,18 +458,20 @@ website.Tour.waitReady = function (callback) {
});
};
website.Tour.run_test = function (id) {
website.Tour.get(id).run(true);
website.Tour.waitReady(function () {
if (!website.Tour.is_busy()) {
website.Tour.tours[id].run(true);
}
});
};
website.Tour.is_busy = function () {
for (var k in this.localStorage) {
if (!k.indexOf("tour-")) {
return true;
return k;
}
}
return website.Tour.busy;
};
}());

View File

@ -0,0 +1,106 @@
(function () {
'use strict';
var website = openerp.website;
website.Tour.LoginEdit = website.Tour.extend({
id: 'login_edit',
name: "Try to log as admin and check editor",
path: '/',
init: function () {
var self = this;
self.steps = [
{
title: "click login",
element: '#top_menu a[href*="/web/login"]',
},
{
title: "insert login",
element: '.oe_login_form input[name="login"]',
sampleText: "admin",
},
{
title: "insert password",
waitFor: '.oe_login_form input[name="login"][value!=""]',
element: '.oe_login_form input[name="password"]',
sampleText: "admin",
},
{
title: "select 2 Standard tickets",
waitFor: '.oe_login_form input[name="password"][value!=""]',
element: '.oe_login_form button',
},
{
title: "go back to website from backend",
element: 'a[data-action-model="ir.actions.act_url"]:contains("Website")',
},
{
title: 'try to edit',
waitNot: '#wrap .carousel',
element: 'button[data-action=edit]:visible',
},
{
title: 'check edit mode',
waitFor: 'button[data-action=save]:visible',
},
{
title: 'check branding',
waitFor: '#wrap[data-oe-model="ir.ui.view"]',
},
{
title: 'check rte',
waitFor: '#oe_rte_toolbar',
},
{
title: 'check insert block button',
element: '[data-action="snippet"]:visible',
},
{
title: 'add snippets',
snippet: 'carousel',
},
{
title: 'try to save',
waitFor: '.oe_overlay_options .oe_options:visible',
element: 'button[data-action=save]:visible',
},
{
title: 'check saved',
waitFor: '#wrap div.carousel',
element: 'button[data-action=edit]:visible',
},
{
title: 'try to re-edit',
waitFor: 'button[data-action=save]:visible',
element: '#wrap .carousel',
},
{
title: 'remove snippet',
element: '.oe_snippet_remove',
},
{
title: 'try to re-save',
waitNot: '#wrap .carousel',
element: 'button[data-action=save]:visible',
},
{
title: "click admin",
waitFor: 'button[data-action=edit]:visible',
element: 'a:contains("Administrator")',
},
{
title: "click logout",
element: '#top_menu a[href*="/logout"]',
},
{
title: "check logout",
waitFor: '#top_menu a[href*="/web/login"]',
},
];
return this._super();
},
});
// for test without editor bar
website.Tour.add(website.Tour.LoginEdit);
}());

View File

@ -0,0 +1,34 @@
(function () {
function LoadScript(src) {
xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if(xmlHttp.readyState == 4) {
if (xmlHttp.status == 200 || xmlHttp.status == 304) {
new Function(xmlHttp.responseText)();
} else {
throw new Error("Can't load JavaScript.\nhref: " + window.location.href + "\nsrc: " + src);
}
}
};
xmlHttp.open("GET", src, false);
xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xmlHttp.send(null);
}
if (typeof jQuery === "undefined")
LoadScript("/web/static/lib/jquery/jquery.js");
if (typeof _ === "undefined")
LoadScript("/web/static/lib/underscore/underscore.js");
if (typeof openerp === "undefined") {
LoadScript("/web/static/lib/qweb/qweb2.js");
LoadScript("/web/static/src/js/openerpframework.js");
}
if (typeof openerp === "undefined" || !openerp.website || !openerp.website.add_template_file)
LoadScript("/website/static/src/js/website.js");
if (typeof Tour === "undefined")
LoadScript("/website/static/lib/bootstrap-tour/bootstrap-tour.js");
if (typeof openerp === "undefined" || !openerp.website.Tour)
LoadScript("/website/static/src/js/website.tour.js");
})();

View File

@ -101,6 +101,7 @@
<button type="button" class="btn btn-primary btn-lg filepicker">
Upload an image from your computer
</button>
<button type="button" class="btn btn-lg hidden wait" disabled="disabled"/>
<p class="text-muted mt16">— or —</p>
</div>
<div class="well">
@ -110,11 +111,15 @@
placeholder="http://openerp.com"/>
</div>
<input type="hidden" name="func"/>
<div class="help-block"/>
</form>
<div class="col-sm-4 image-preview-container">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAC0lEQVQIHWP4zwAAAgEBAMVfG14AAAAASUVORK5CYII%3D"
class="pull-right img-rounded image-preview"
width="100%"/>
<img src="/web/static/src/img/throbber-large.gif"
class="pull-right img-rounded wait hidden"
width="100%"/>
<select class="form-control image-style">
<option value="">No styling</option>
<option value="img-rounded">Rounded corners</option>

View File

@ -102,7 +102,7 @@
<h3 class="modal-title"><t t-esc="window_title"/></h3>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form">
<form class="form-horizontal" role="form" t-att-id="id">
<div class="form-group mb0">
<label for="page-name" class="col-sm-3 control-label">
<t t-esc="field_name"/>:

View File

@ -41,7 +41,7 @@ class WebsiteUiSuite(unittest.TestSuite):
# timeout in seconds
def __init__(self, testfile, options, timeout=60.0):
self._testfile = testfile
self._timeout = timeout
self._timeout = timeout + 5.0
self._options = options
self._test = None
self._ignore_filters = [
@ -58,7 +58,6 @@ class WebsiteUiSuite(unittest.TestSuite):
return iter([self])
def run(self, result):
return
# clean slate
if sql_db._Pool is not None:
sql_db._Pool.close_all(sql_db.dsn(tools.config['db_name']))
@ -79,18 +78,21 @@ class WebsiteUiSuite(unittest.TestSuite):
del result._exc_info_to_string
def _run(self, result):
self._test = WebsiteUiTest(self._testfile)
self._test = WebsiteUiTest("%s (as %s)" %
(self._testfile, self._options.get('user') or "Anonymous" if 'user' in self._options else "admin" ))
start_time = time.time()
last_check_time = time.time()
self._options['timeout'] = self._timeout
self._options['port'] = tools.config.get('xmlrpc_port', 80)
self._options['db'] = tools.config.get('db_name', '')
self._options['user'] = 'admin'
self._options['password'] = tools.config.get('admin_passwd', 'admin')
if 'user' not in self._options:
self._options['user'] = 'admin'
self._options['password'] = 'admin'
phantom = subprocess.Popen([
'phantomjs',
#'--debug=true',
self._testfile,
json.dumps(self._options)
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@ -148,6 +150,7 @@ def full_path(pyfile, filename):
def load_tests(loader, base, _):
base.addTest(WebsiteUiSuite(full_path(__file__, 'dummy_test.js'), {}, 5.0))
base.addTest(WebsiteUiSuite(full_path(__file__, 'login_test.js'), {'path': '/', 'user': None}, 60.0))
base.addTest(WebsiteUiSuite(full_path(__file__, 'simple_dom_test.js'), {'redirect': '/page/website.homepage'}, 60.0))
base.addTest(WebsiteUiSuite(full_path(__file__, 'homepage_test.js'), {'redirect': '/page/website.homepage'}, 60.0))
return base

View File

@ -1,17 +1,3 @@
var testRunner = require('./ui_test_runner.js');
var waitFor = testRunner.waitFor;
testRunner.run(function homepageTest (page, timeout) {
page.evaluate(function () { localStorage.clear(); });
waitFor(function clientReady () {
return page.evaluate(function () {
return window.$ && window.openerp && window.openerp.website
&& window.openerp.website.Tour;
});
}, function executeTest () {
page.evaluate(function () {
window.openerp.website.Tour.run_test('banner');
});
}, timeout);
});
testRunner.run_test('banner');

View File

@ -0,0 +1,7 @@
var testRunner = require('./ui_test_runner.js');
testRunner.run_test('login_edit', {
"inject": [
"./../../../website/static/src/js/website.tour.test.js",
"./../../../website/static/src/js/website.tour.test.admin.js"]
});

View File

@ -19,15 +19,15 @@ function waitFor (ready, callback, timeout, timeoutMessageCallback) {
}, 100);
}
function run (test) {
function run (test, onload, inject) {
var options = JSON.parse(phantom.args);
var timeout = options.timeout ? Math.round(parseFloat(options.timeout)*1000) : 60000;
var timeout = options.timeout ? Math.round(parseFloat(options.timeout)*1000-5000) : 60000;
var scheme = options.scheme ? options.scheme+'://' : 'http://';
var host = options.host ? options.host : 'localhost';
var port = options.port ? ':'+options.port : '';
var path = options.path ? options.path : '/login';
var path = options.path ? options.path : '/web/login';
var queryParams = [];
if (options.db) queryParams.push('db='+options.db);
@ -56,24 +56,52 @@ function run (test) {
};
page.onConsoleMessage = function(message) {
console.log(message);
try {
var result = JSON.parse(message);
if (result.event === 'success') {
phantom.exit(0);
} else if (result.event === 'console') {
return;
} else {
phantom.exit(1);
}
} catch (exception) {
phantom.exit(1);
}
};
page.onCallback = function(data) {
if (data.event && data.event === 'start') {
test(page, timeout);
if (test) test(page, timeout, options);
}
};
page.onLoadFinished = function(status) {
if (status === "success") {
if (inject) {
if (!inject instanceof Array) {
inject = [inject];
}
for (var k in inject) {
if(!page.injectJs(inject[k])) {
console.log("Can't inject "+inject[k]);
phantom.exit(1);
}
}
}
if (onload) onload(page, timeout, options);
}
};
var maxRetries = 10;
var retryDelay = 1000; // ms
var tries = 0;
var tries = 0;
page.open(url, function openPage (status) {
if (status !== 'success') {
tries++;
if (tries < maxRetries) {
setTimeout(function () {
page.open(url, openPage);
}, retryDelay);
setTimeout(function () {
page.open(url, openPage);
}, retryDelay);
} else {
console.log('{ "event": "error", "message": "'+url+' failed to load '+tries+' times ('+status+')"}');
phantom.exit(1);
@ -85,9 +113,45 @@ function run (test) {
}
});
setTimeout(function () {
page.evaluate(function (timeout) {
var message = ("Timeout after " +(timeout/1000)+ " s"
+ "\nhref: " + window.location.href
+ "\nreferrer: " + document.referrer
+ "\n\n" + document.body.innerHTML).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*");
console.log(JSON.stringify({ "event": "error", "message": message}));
phantom.exit(1);
},timeout);
}, timeout);
}
function run_test (testname, options) {
options = options || {};
run(
function start (page, timeout) {
page.evaluate(function () { localStorage.clear(); });
},
function onload (page, timeout) {
waitFor(function clientReady () {
return page.evaluate(function () {
return window.$
&& window.openerp
&& window.openerp.website
&& window.openerp.website.Tour;
});
}, function executeTest () {
page.evaluate(function (testname) {
window.openerp.website.Tour.run_test(testname);
}, testname);
}, timeout);
},
options.inject || null
);
}
module.exports = {
waitFor: waitFor,
run: run
run: run,
run_test: run_test
}

View File

@ -898,7 +898,7 @@
</div>
<div data-snippet-style-id='background' data-selector="section, section[data-snippet-id='parallax'], div[data-snippet-id='carousel'], div[data-snippet-id='slider']">
<li class="dropdown-submenu">
<li class="dropdown-submenu" data-required="true">
<a tabindex="-1" href="#">Background</a>
<ul class="dropdown-menu">
<li class="dropdown-submenu">

View File

@ -221,6 +221,34 @@
</template>
<template id="editor_head" inherit_id="website.layout" name="Editor" groups="base.group_website_publisher">
<xpath expr="//body" position="attributes">
<attribute name="style">padding-top: 51px;</attribute>
</xpath>
<xpath expr="//body/*[1]" position="before">
<div id="website-top-navbar-placeholder" class="navbar navbar-inverse navbar-fixed-top hidden-xs">
<div class="navbar-header">
<form class="navbar-form navbar-left">
<button type="button" class="btn btn-primary">Edit</button>
</form>
</div>
<div class="collapse navbar-collapse navbar-edit-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#" onclick="return false;"><i class="fa fa-mobile" title="Mobile preview"/></a></li>
<li class="divider-vertical"/>
<li><a href="#" onclick="return false;"><span title="Promote page on the web">Promote</span></a></li>
<li class="dropdown">
<a href="#" onclick="return false;">Content <span class="caret"/></a>
</li>
<li class="dropdown">
<a href="#" onclick="return false;">Customize <span class="caret"/></a>
</li>
<li class="dropdown">
<a href="#" onclick="return false;">Help <span class="caret"/></a>
</li>
</ul>
</div>
</div>
</xpath>
<xpath expr='//t[@name="layout_head"]' position="before">
<link rel='stylesheet' href='/website/static/src/css/snippets.css'/>
<link rel='stylesheet' href='/website/static/src/css/editor.css'/>
@ -256,6 +284,13 @@
</xpath>
</template>
<template id="debugger" inherit_option_id="website.layout" name="Debugger &amp; Tests">
<xpath expr='//t[@name="layout_head"]' position="after">
<script type="text/javascript" src="/website/static/src/js/website.tour.test.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.tour.test.admin.js"></script>
</xpath>
</template>
<template id="login_layout" inherit_id="web.login_layout" name="Website Login Layout">
<xpath expr="t" position="replace">
<t t-call="website.layout">

View File

@ -20,7 +20,7 @@
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Blogs</field>
<field name="target">self</field>
<field name="url" eval="'/blog/'+str(ref('website_blog.blog_blog_1'))+'/?tutorial.blog=true'"/>
<field name="url" eval="'/blog/'+str(ref('website_blog.blog_blog_1'))+'/#tutorial.blog=true'"/>
</record>
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_open_website"/>

View File

@ -2,114 +2,115 @@
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.EditorBar.include({
start: function () {
this.registerTour(new website.BlogTour(this));
this.registerTour(new website.Tour.Blog(this));
return this._super();
},
});
website.BlogTour = website.Tour.extend({
website.Tour.Blog = website.Tour.extend({
id: 'blog',
name: "Create a blog post",
testPath: /\/(blog|blogpost)\/[0-9]+\//,
init: function (editor) {
testPath: '/(blog|blogpost)',
init: function () {
var self = this;
self.steps = [
{
title: "New Blog Post",
content: "Let's go through the first steps to write beautiful blog posts.",
template: self.popover({ next: "Start Tutorial", end: "Skip" }),
title: _t("New Blog Post"),
content: _t("Let's go through the first steps to write beautiful blog posts."),
popover: { next: _t("Start Tutorial"), end: _t("Skip") },
},
{
element: '#content-menu-button',
placement: 'left',
title: "Add Content",
content: "Create new pages, blogs, menu items and products through the <em>'Content'</em> menu.",
template: self.popover({ fixed: true }),
title: _t("Add Content"),
content: _t("Create new pages, blogs, menu items and products through the <em>'Content'</em> menu."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_blog_post]',
placement: 'left',
title: "New Blog Post",
content: "Select this menu item to create a new blog post.",
template: self.popover({ fixed: true }),
title: _t("New Blog Post"),
content: _t("Select this menu item to create a new blog post."),
popover: { fixed: true },
},
{
element: '.modal button.btn-primary',
placement: 'bottom',
title: "Create Blog Post",
content: "Click <em>Continue</em> to create the blog post.",
element: '.modal:has(#editor_new_blog) button.btn-primary',
placement: 'right',
title: _t("Create Blog Post"),
content: _t("Click <em>Continue</em> to create the blog post."),
},
{
waitNot: '.modal',
title: "Blog Post Created",
content: "This is your new blog post. Let's edit it.",
template: self.popover({ next: "Continue" }),
waitFor: 'body:has(button[data-action=save]:visible):has(.js_blog)',
title: _t("Blog Post Created"),
content: _t("This is your new blog post. Let's edit it."),
popover: { next: _t("Continue") },
},
{
element: 'h1[data-oe-expression="blog_post.name"]',
placement: 'bottom',
sampleText: 'New Blog',
title: "Set a Title",
content: "Click on this area and set a catchy title for your blog post.",
title: _t("Set a Title"),
content: _t("Click on this area and set a catchy title for your blog post."),
},
{
waitNot: '#wrap h1[data-oe-model="blog.post"]:contains("Blog Post Title")',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Layout Your Blog Post",
content: "Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content.",
template: self.popover({ fixed: true }),
title: _t("Layout Your Blog Post"),
content: _t("Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content."),
popover: { fixed: true },
},
{
snippet: 'image-text',
placement: 'bottom',
title: "Drag & Drop a Block",
content: "Drag the <em>'Image-Text'</em> block and drop it in your page.",
template: self.popover({ fixed: true }),
title: _t("Drag & Drop a Block"),
content: _t("Drag the <em>'Image-Text'</em> block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Add Another Block",
content: "Let's add another block to your post.",
template: self.popover({ fixed: true }),
title: _t("Add Another Block"),
content: _t("Let's add another block to your post."),
popover: { fixed: true },
},
{
snippet: 'text-block',
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the <em>'Text Block'</em> block and drop it below the image block.",
template: self.popover({ fixed: true }),
title: _t("Drag & Drop a block"),
content: _t("Drag the <em>'Text Block'</em> block and drop it below the image block."),
popover: { fixed: true },
},
{
element: '.oe_active .oe_snippet_remove',
placement: 'top',
title: "Delete the Title",
content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the garbage can image to delete the title.",
title: _t("Delete the Title"),
content: _t("From this toolbar you can move, duplicate or delete the selected zone. Click on the garbage can image to delete the title."),
},
{
waitNot: '.oe_active .oe_snippet_remove:visible',
element: 'button[data-action=save]',
placement: 'right',
title: "Save Your Blog",
content: "Click the <em>Save</em> button to record changes on the page.",
template: self.popover({ fixed: true }),
title: _t("Save Your Blog"),
content: _t("Click the <em>Save</em> button to record changes on the page."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
element: 'button.btn-danger.js_publish_btn',
placement: 'top',
title: "Publish Your Post",
content: "Your blog post is not yet published. You can update this draft version and publish it once you are ready.",
title: _t("Publish Your Post"),
content: _t("Your blog post is not yet published. You can update this draft version and publish it once you are ready."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
title: "Thanks!",
content: "This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu.",
template: self.popover({ end: "Close Tutorial" }),
content: _t("This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu."),
popover: { next: _t("Close Tutorial") },
},
];
return this._super();

View File

@ -2,6 +2,7 @@
"use strict";
var website = openerp.website;
var _t = openerp._t;
website.add_template_file('/website_blog/static/src/xml/website_blog.xml');
website.is_editable = true;
@ -16,7 +17,8 @@
'click a[data-action=new_blog_post]': function (ev) {
ev.preventDefault();
website.prompt({
window_title: "New Blog Post",
id: "editor_new_blog",
window_title: _t("New Blog Post"),
select: "Select Blog",
init: function (field) {
return website.session.model('blog.blog')

View File

@ -1,17 +1,3 @@
var testRunner = require('../../../website/tests/ui_suite/ui_test_runner.js');
var waitFor = testRunner.waitFor;
testRunner.run(function blogTest (page, timeout) {
page.evaluate(function () { localStorage.clear(); });
waitFor(function clientReady () {
return page.evaluate(function () {
return window.$ && window.openerp && window.openerp.website
&& window.openerp.website.Tour;
});
}, function executeTest () {
page.evaluate(function () {
window.openerp.website.Tour.run_test('blog');
});
}, timeout);
});
testRunner.run_test('blog');

View File

@ -203,7 +203,9 @@ class website_event(http.Controller):
def add_event(self, event_name="New Event", **kwargs):
return self._add_event(event_name, request.context, **kwargs)
def _add_event(self, event_name="New Event", context={}, **kwargs):
def _add_event(self, event_name=None, context={}, **kwargs):
if not event_name:
event_name = _("New Event")
Event = request.registry.get('event.event')
date_begin = datetime.today() + timedelta(days=(14))
vals = {

View File

@ -2,6 +2,7 @@
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.EditorBar.include({
start: function () {
@ -13,98 +14,98 @@
website.EventTour = website.Tour.extend({
id: 'event',
name: "Create an event",
testPath: /\/event\/[0-9]+\/register/,
testPath: '/event(/[0-9]+/register)?',
init: function (editor) {
var self = this;
self.steps = [
{
title: "Create an Event",
content: "Let's go through the first steps to publish a new event.",
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
title: _t("Create an Event"),
content: _t("Let's go through the first steps to publish a new event."),
popover: { next: _("Start Tutorial"), end: _("Skip It") },
},
{
element: '#content-menu-button',
placement: 'left',
title: "Add Content",
content: "The <em>Content</em> menu allows you to create new pages, events, menus, etc.",
template: self.popover({ fixed: true }),
title: _t("Add Content"),
content: _t("The <em>Content</em> menu allows you to create new pages, events, menus, etc."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_event]',
placement: 'left',
title: "New Event",
content: "Click here to create a new event.",
template: self.popover({ fixed: true }),
title: _t("New Event"),
content: _t("Click here to create a new event."),
popover: { fixed: true },
},
{
element: '.modal:contains("New Event") input[type=text]',
element: '.modal #editor_new_event input[type=text]',
sampleText: 'Advanced Technical Training',
placement: 'right',
title: "Create an Event Name",
content: "Create a name for your new event and click <em>'Continue'</em>. e.g: Technical Training",
title: _t("Create an Event Name"),
content: _t("Create a name for your new event and click <em>'Continue'</em>. e.g: Technical Training"),
},
{
waitNot: '.modal input[type=text]:not([value!=""])',
element: '.modal button.btn-primary',
placement: 'right',
title: "Create Event",
content: "Click <em>Continue</em> to create the event.",
title: _t("Create Event"),
content: _t("Click <em>Continue</em> to create the event."),
},
{
waitFor: '#website-top-navbar button[data-action="save"]:visible',
title: "New Event Created",
content: "This is your new event page. We will edit the event presentation page.",
template: self.popover({ next: "Continue" }),
waitFor: 'body:has(button[data-action=save]:visible):has(.js_event)',
title: _t("New Event Created"),
content: _t("This is your new event page. We will edit the event presentation page."),
popover: { next: _t("Continue") },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Layout your event",
content: "Insert blocks to layout the body of your event.",
template: self.popover({ fixed: true }),
title: _t("Layout your event"),
content: _t("Insert blocks to layout the body of your event."),
popover: { fixed: true },
},
{
snippet: 'image-text',
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the 'Image-Text' block and drop it in your page.",
template: self.popover({ fixed: true }),
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Image-Text' block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Layout your event",
content: "Insert another block to your event.",
template: self.popover({ fixed: true }),
title: _t("Layout your event"),
content: _t("Insert another block to your event."),
popover: { fixed: true },
},
{
snippet: 'text-block',
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the 'Text Block' in your event page.",
template: self.popover({ fixed: true }),
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Text Block' in your event page."),
popover: { fixed: true },
},
{
element: 'button[data-action=save]',
placement: 'right',
title: "Save your modifications",
content: "Once you click on save, your event is updated.",
template: self.popover({ fixed: true }),
title: _t("Save your modifications"),
content: _t("Once you click on save, your event is updated."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=edit]:visible',
element: 'button.btn-danger.js_publish_btn',
placement: 'top',
title: "Publish your event",
content: "Click to publish your event.",
title: _t("Publish your event"),
content: _t("Click to publish your event."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
element: '.js_publish_management button[data-toggle="dropdown"]',
placement: 'left',
title: "Customize your event",
content: "Click here to customize your event further.",
title: _t("Customize your event"),
content: _t("Click here to customize your event further."),
},
{
element: '.js_publish_management ul>li>a:last:visible',

View File

@ -2,6 +2,7 @@
"use strict";
var website = openerp.website;
var _t = openerp._t;
website.add_template_file('/website_event/static/src/xml/website_event.xml');
website.is_editable = true;
@ -16,7 +17,8 @@
'click a[data-action=new_event]': function (ev) {
ev.preventDefault();
website.prompt({
window_title: "New Event",
id: "editor_new_event",
window_title: _t("New Event"),
input: "Event Name",
}).then(function (event_name) {
website.form('/event/add_event', 'POST', {

View File

@ -1,17 +1,3 @@
var testRunner = require('../../../website/tests/ui_suite/ui_test_runner.js');
var waitFor = testRunner.waitFor;
testRunner.run(function eventTest (page, timeout) {
page.evaluate(function () { localStorage.clear(); });
waitFor(function clientReady () {
return page.evaluate(function () {
return window.$ && window.openerp && window.openerp.website
&& window.openerp.website.Tour;
});
}, function executeTest () {
page.evaluate(function () {
window.openerp.website.Tour.run_test('event');
});
}, timeout);
});
testRunner.run_test('event');

View File

@ -0,0 +1,80 @@
(function () {
'use strict';
var website = openerp.website;
website.Tour.EventSaleTest = website.Tour.extend({
id: 'event_buy_tickets',
name: "Try to buy tickets for event",
path: '/event',
testPath: '/(event|shop)',
init: function () {
var self = this;
self.steps = [
{
title: "select event",
element: 'a[href*="/event"]:contains("Open Days in Los Angeles")',
},
{
title: "go to register page",
waitNot: 'a[href*="/event"]:contains("Functional Webinar")',
onload: function () {
// use onload if website_event_track is installed
if (!$('form:contains("Ticket Type")').size()) {
window.location.href = $('a[href*="/event"][href*="/register"]').attr("href");
}
},
},
{
title: "select 2 Standard tickets",
element: 'select[name="ticket-1"]',
sampleText: '2',
},
{
title: "select 3 VIP tickets",
waitFor: 'select[name="ticket-1"] option:contains(2):selected',
element: 'select[name="ticket-2"]',
sampleText: '3',
},
{
title: "Order Now",
waitFor: 'select[name="ticket-2"] option:contains(3):selected',
element: 'button.btn-primary:contains("Order Now")',
},
{
title: "Complete checkout",
waitFor: '#top_menu .my_cart_quantity:contains(5)',
element: 'form[action="/shop/confirm_order/"] button',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_event_sale_test_shoptest@websiteeventsaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
},
{
title: "select payment",
element: '#payment_method label:has(img[title="transfer"]) input',
},
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="transfer"])',
element: '.oe_sale_acquirer_button button[name="submit"]:visible',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
];
return this._super();
},
});
// for test without editor bar
website.Tour.add(website.Tour.EventSaleTest);
}());

View File

@ -0,0 +1 @@
import test_ui

View File

@ -0,0 +1,10 @@
import openerp.addons.website.tests.test_ui as test_ui
def load_tests(loader, base, _):
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'),
{'redirect': '/page/website.homepage'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'),
{'redirect': '/page/website.homepage', 'user': 'demo', 'password': 'demo'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'),
{'path': '/', 'user': None}))
return base

View File

@ -0,0 +1,7 @@
var testRunner = require('../../../website/tests/ui_suite/ui_test_runner.js');
testRunner.run_test('event_buy_tickets', {
"inject": [
"./../../../website/static/src/js/website.tour.test.js",
"./../../../website_event_sale/static/src/js/website.tour.event_sale.js"]
});

View File

@ -2,6 +2,13 @@
<openerp>
<data>
<template id="debugger" inherit_id="website.debugger" name="Event Debugger">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/website_event_sale/static/src/js/website.tour.event_sale.js"></script>
</xpath>
</template>
<template id="index" inherit_id="website_event.index" name="Event's Ticket">
<xpath expr="//li[@t-foreach='event_ids']/div/h4" position="before">
<t t-if="event.state in ['draft', 'confirm'] and event.event_ticket_ids">

View File

@ -6,6 +6,7 @@ import werkzeug
from openerp import SUPERUSER_ID
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.tools.translate import _
PPG = 20 # Products Per Page
PPR = 4 # Products Per Row
@ -294,7 +295,9 @@ class Ecommerce(http.Controller):
return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
@http.route(['/shop/add_product/'], type='http', auth="user", methods=['POST'], website=True, multilang=True)
def add_product(self, name="New Product", category=0, **post):
def add_product(self, name=None, category=0, **post):
if not name:
name = _("New Product")
Product = request.registry.get('product.product')
product_id = Product.create(request.cr, request.uid, {
'name': name, 'public_categ_id': category

View File

@ -16,7 +16,7 @@
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Shop</field>
<field name="target">self</field>
<field name="url">/shop?tutorial.shop=true</field>
<field name="url">/shop#tutorial.shop=true</field>
</record>
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_open_website"/>

View File

@ -0,0 +1,95 @@
(function () {
'use strict';
var website = openerp.website;
website.Tour.ShopTest = website.Tour.extend({
id: 'shop_buy_product',
name: "Try to buy products",
path: '/shop',
testPath: '/shop',
init: function () {
var self = this;
self.steps = [
{
title: "select ipod",
element: '.oe_product_cart a:contains("iPod")',
},
{
title: "select ipod 32Go",
element: 'input[name="product_id"]:not([checked])',
},
{
title: "click on add to cart",
waitFor: 'input[name="product_id"]:eq(1)[checked]',
element: 'form[action="/shop/add_cart/"] button',
},
{
title: "add suggested",
element: 'form[action="/shop/add_cart/"] button.btn-link:contains("Add to Cart")',
},
{
title: "add one more iPod",
waitFor: '.my_cart_quantity:contains(2)',
element: '#mycart_products tr:contains("iPod: 32 Gb") a.js_add_cart_json:eq(1)',
},
{
title: "remove Headphones",
waitFor: '#mycart_products tr:contains("iPod: 32 Gb") input.js_quantity[value=2]',
element: '#mycart_products tr:contains("Apple In-Ear Headphones") a.js_add_cart_json:first',
},
{
title: "set one iPod",
waitNot: '#mycart_products tr:contains("Apple In-Ear Headphones")',
element: '#mycart_products input.js_quantity',
sampleText: '1',
},
{
title: "go to checkout",
waitFor: '#mycart_products input.js_quantity[value=1]',
element: 'a[href="/shop/checkout/"]',
},
{
title: "test with input error",
element: 'form[action="/shop/confirm_order/"] button',
onload: function (tour) {
$("input[name='phone']").val("");
},
},
{
title: "test without input error",
waitFor: 'form[action="/shop/confirm_order/"] .has-error',
element: 'form[action="/shop/confirm_order/"] button',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_sale_test_shoptest@websitesaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
},
{
title: "select payment",
element: '#payment_method label:has(img[title="transfer"]) input',
},
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="transfer"])',
element: '.oe_sale_acquirer_button button[name="submit"]:visible',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
];
return this._super();
},
});
// for test without editor bar
website.Tour.add(website.Tour.ShopTest);
}());

View File

@ -2,238 +2,143 @@
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.EditorBar.include({
start: function () {
this.registerTour(new website.EditorShopTour(this));
var res = this._super();
return res;
this.registerTour(new website.Tour.Shop(this));
return this._super();
},
});
website.EditorShopTour = website.Tour.extend({
website.Tour.Shop = website.Tour.extend({
id: 'shop',
name: "Create a product",
testPath: /\/shop\/.*/,
init: function (editor) {
testPath: '/shop',
init: function () {
var self = this;
self.steps = [
{
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: self.popover({ next: "Start Tutorial", end: "Skip It" }),
title: _t("Welcome to your shop"),
content: _t("You successfully installed the e-commerce. This guide will help you to create your product and promote your sales."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
element: '#content-menu-button',
placement: 'left',
title: "Create your first product",
content: "Click here to add a new product.",
template: self.popover({ fixed: true }),
title: _t("Create your first product"),
content: _t("Click here to add a new product."),
popover: { fixed: true },
},
{
element: 'a[data-action=new_product]',
placement: 'left',
title: "Create a new product",
content: "Select 'New Product' to create it and manage its properties to boost your sales.",
template: self.popover({ fixed: true }),
title: _t("Create a new product"),
content: _t("Select 'New Product' to create it and manage its properties to boost your sales."),
popover: { fixed: true },
},
{
element: '.modal:contains("New Product") input[type=text]',
element: '.modal #editor_new_product input[type=text]',
sampleText: 'New Product',
placement: 'right',
title: "Choose name",
content: "Enter a name for your new product then click 'Continue'.",
title: _t("Choose name"),
content: _t("Enter a name for your new product then click 'Continue'."),
},
{
waitNot: '.modal input[type=text]:not([value!=""])',
element: '.modal button.btn-primary',
placement: 'right',
title: "Create Product",
content: "Click <em>Continue</em> to create the product.",
title: _t("Create Product"),
content: _t("Click <em>Continue</em> to create the product."),
},
{
waitFor: '#website-top-navbar button[data-action="save"]:visible',
title: "New product created",
content: "This page contains all the information related to the new product.",
template: self.popover({ next: "Continue" }),
waitFor: 'body:has(button[data-action=save]:visible):has(.js_sale)',
title: _t("New product created"),
content: _t("This page contains all the information related to the new product."),
popover: { next: _t("Continue") },
},
{
element: '.product_price .oe_currency_value',
sampleText: '20.50',
placement: 'left',
title: "Change the price",
content: "Edit the price of this product by clicking on the amount.",
title: _t("Change the price"),
content: _t("Edit the price of this product by clicking on the amount."),
},
{
waitNot: '.product_price .oe_currency_value:containsExact(1.00)',
element: '#wrap img.img:first',
placement: 'top',
title: "Update image",
content: "Click here to set an image describing your product.",
title: _t("Update image"),
content: _t("Click here to set an image describing your product."),
},
{
element: 'button.hover-edition-button:visible',
placement: 'top',
title: "Update image",
content: "Click here to set an image describing your product.",
title: _t("Update image"),
content: _t("Click here to set an image describing your product."),
},
{
wait: 500,
element: '.well a.pull-right',
placement: 'bottom',
title: "Select an Image",
content: "Let's select an existing image.",
template: self.popover({ fixed: true }),
title: _t("Select an Image"),
content: _t("Let's select an existing image."),
popover: { fixed: true },
},
{
element: 'img[alt=imac]',
placement: 'bottom',
title: "Select an Image",
content: "Let's select an imac image.",
template: self.popover({ fixed: true }),
title: _t("Select an Image"),
content: _t("Let's select an imac image."),
popover: { fixed: true },
},
{
waitNot: 'img[alt=imac]',
element: '.modal-content button.save',
placement: 'bottom',
title: "Select this Image",
content: "Click to add the image to the product decsription.",
template: self.popover({ fixed: true }),
title: _t("Select this Image"),
content: _t("Click to add the image to the product decsription."),
popover: { fixed: true },
},
{
waitNot: '.modal-content:visible',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: "Describe the Product",
content: "Insert blocks like text-image, or gallery to fully describe the product.",
template: self.popover({ fixed: true }),
title: _t("Describe the Product"),
content: _t("Insert blocks like text-image, or gallery to fully describe the product."),
popover: { fixed: true },
},
{
snippet: 'big-picture',
placement: 'bottom',
title: "Drag & Drop a block",
content: "Drag the 'Big Picture' block and drop it in your page.",
template: self.popover({ fixed: true }),
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Big Picture' block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=save]',
placement: 'right',
title: "Save your modifications",
content: "Once you click on save, your product is updated.",
template: self.popover({ fixed: true }),
title: _t("Save your modifications"),
content: _t("Once you click on save, your product is updated."),
popover: { fixed: true },
},
{
waitFor: '#website-top-navbar button[data-action="edit"]:visible',
element: '.js_publish_management button.js_publish_btn.btn-danger',
placement: 'top',
title: "Publish your product",
content: "Click to publish your product so your customers can see it.",
title: _t("Publish your product"),
content: _t("Click to publish your product so your customers can see it."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
title: "Congratulations",
content: "Congratulations! You just created and published your first product.",
template: self.popover({ next: "Close Tutorial" }),
title: _t("Congratulations"),
content: _t("Congratulations! You just created and published your first product."),
popover: { next: _t("Close Tutorial") },
},
];
return this._super();
}
});
website.EditorShopTest = website.Tour.extend({
id: 'shop_buy_product',
name: "Try to buy products",
path: '/shop',
testPath: /\/shop/,
init: function (editor) {
var self = this;
self.steps = [
{
title: 'begin-test',
template: self.popover({ next: "Start Test"}),
backdrop: true,
},
{
title: "select ipod",
element: '.oe_product_cart a:contains("iPod")',
},
{
title: "select ipod 32Go",
element: 'input[name="product_id"]:not([checked])',
},
{
title: "click on add to cart",
waitFor: 'input[name="product_id"]:eq(1)[checked]',
element: 'form[action="/shop/add_cart/"] button',
},
{
title: "add suggested",
element: 'form[action="/shop/add_cart/"] button.btn-link:contains("Add to Cart")',
},
{
title: "add one more iPod",
waitFor: '.my_cart_quantity:contains(2)',
element: '#mycart_products tr:contains("iPod: 32 Gb") a.js_add_cart_json:eq(1)',
},
{
title: "remove Headphones",
waitFor: '#mycart_products tr:contains("iPod: 32 Gb") input.js_quantity[value=2]',
element: '#mycart_products tr:contains("Apple In-Ear Headphones") a.js_add_cart_json:first',
},
{
title: "set one iPod",
waitNot: '#mycart_products tr:contains("Apple In-Ear Headphones")',
element: '#mycart_products input.js_quantity',
sampleText: '1',
},
{
title: "go to checkout",
waitFor: '#mycart_products input.js_quantity[value=1]',
element: 'a[href="/shop/checkout/"]',
},
{
title: "test with input error",
element: 'form[action="/shop/confirm_order/"] button',
callback: function (tour) {
$("input[name='phone']").val("");
},
},
{
title: "test without input error",
waitFor: 'form[action="/shop/confirm_order/"] .has-error',
element: 'form[action="/shop/confirm_order/"] button',
callback: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_sale-test-shoptest@website_sale-test-shoptest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
},
{
title: "select acquirer",
element: 'input[name="acquirer"]',
},
{
title: "confirm",
element: 'button:contains("Pay Now")',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
];
return this._super();
},
});
// for test without editor bar
$(document).ready(function () {
website.Tour.add(website.EditorShopTest);
});
}());

View File

@ -2,6 +2,7 @@
"use strict";
var website = openerp.website;
var _t = openerp._t;
website.add_template_file('/website_sale/static/src/xml/website_sale.xml');
website.is_editable = true;
@ -16,7 +17,8 @@
'click a[data-action=new_product]': function (ev) {
ev.preventDefault();
website.prompt({
window_title: "New Product",
id: "editor_new_product",
window_title: _t("New Product"),
input: "Product Name",
}).then(function (name) {
website.form('/shop/add_product', 'POST', {

View File

@ -26,11 +26,12 @@ $(document).ready(function () {
if (isNaN(value)) value = 0;
openerp.jsonRpc("/shop/set_cart_json/", 'call', {'order_line_id': $input.data('id'), 'set_number': value})
.then(function (data) {
if (!data) {
location.reload();
return;
}
set_my_cart_quantity(data[1]);
$input.val(data[0]);
if (!data[0]) {
location.reload();
}
});
});

View File

@ -1,8 +1,12 @@
import openerp.addons.website.tests.test_ui as test_ui
def load_tests(loader, base, _):
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-add_product-test.js'),
{'redirect': '/page/website.homepage'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'),
{ 'action': 'website.action_website_homepage' }))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test-2.js'),
{ 'action': 'website.action_website_homepage' }))
{'redirect': '/page/website.homepage'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'),
{'redirect': '/page/website.homepage', 'user': 'demo', 'password': 'demo'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'),
{'path': '/', 'user': None}))
return base

View File

@ -0,0 +1,3 @@
var testRunner = require('../../../website/tests/ui_suite/ui_test_runner.js');
testRunner.run_test('shop');

View File

@ -1,17 +0,0 @@
var testRunner = require('../../../website/tests/ui_suite/ui_test_runner.js');
var waitFor = testRunner.waitFor;
testRunner.run(function websiteSaleTest (page, timeout) {
page.evaluate(function () { localStorage.clear(); });
waitFor(function clientReady () {
return page.evaluate(function () {
return window.$ && window.openerp && window.openerp.website
&& window.openerp.website.Tour;
});
}, function executeTest () {
page.evaluate(function () {
window.openerp.website.Tour.run_test('shop');
});
}, timeout);
});

View File

@ -1,17 +1,7 @@
var testRunner = require('../../../website/tests/ui_suite/ui_test_runner.js');
var waitFor = testRunner.waitFor;
testRunner.run(function websiteSaleTest (page, timeout) {
page.evaluate(function () { localStorage.clear(); });
waitFor(function clientReady () {
return page.evaluate(function () {
return window.$ && window.openerp && window.openerp.website
&& window.openerp.website.Tour;
});
}, function executeTest () {
page.evaluate(function () {
window.openerp.website.Tour.run_test('shop_buy_product');
});
}, timeout);
});
testRunner.run_test('shop_buy_product', {
"inject": [
"./../../../website/static/src/js/website.tour.test.js",
"./../../../website_sale/static/src/js/website.tour.sale.js"]
});

View File

@ -4,6 +4,12 @@
<!-- Layout add nav and footer -->
<template id="debugger" inherit_id="website.debugger" name="Event Debugger">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/website_sale/static/src/js/website.tour.sale.js"></script>
</xpath>
</template>
<template id="editor_head" inherit_id="website.editor_head" name="Shop Editor" groups="base.group_sale_manager">
<xpath expr="//script[@id='website_tour_js']" position="after">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.editor.js"></script>
@ -532,7 +538,7 @@
<a t-if="website_sale_order and website_sale_order.website_order_line" href="/shop/checkout/" class="btn btn-primary pull-right mb32">Process Checkout <span class="fa fa-long-arrow-right"/></a>
<div class="oe_structure"/>
</div>
<div class="col-lg-3 col-lg-offset-1 col-sm-3 text-muted" id="right_column">
<div class="col-lg-3 col-lg-offset-1 col-sm-3 col-md-3 text-muted" id="right_column">
<h4>Policies</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 30-days money-back guarantee</li>
@ -830,7 +836,7 @@
</div>
<button type="submit" class="btn btn-default btn-primary pull-right mb32">Confirm <span class="fa fa-long-arrow-right"/></button>
</div>
<div class="col-lg-offset-1 col-lg-3 text-muted">
<div class="col-lg-offset-1 col-lg-3 col-md-3 text-muted">
<h3 class="page-header mt16">Your Order <small><a href="/shop/mycart"><span class="fa fa-arrow-right"/> change</a></small></h3>
<div class="row">
<div class="col-sm-6 text-right">Subtotal:</div>