[ADD] Versioning Module.

This commit is contained in:
Goffin Simon
2015-01-07 18:17:13 +01:00
parent e5ac65de59
commit 421a76dabb
78 changed files with 2823 additions and 52 deletions
+5
View File
@@ -81,6 +81,11 @@
</div>
</div>
</group>
<group>
<label for="view_analytics_config_settings" string="Google Analytics"/>
<div name="google_analytics">
</div>
</group>
<group>
<label for="id" string="Google Calendar"/>
<div name="google_calendar">
+1
View File
@@ -14,6 +14,7 @@ OpenERP Website CMS
'installable': True,
'data': [
'data/data.xml',
'data/demo.xml',
'security/ir.model.access.csv',
'security/ir_ui_view.xml',
'views/website_templates.xml',
+9 -9
View File
@@ -163,7 +163,8 @@ class Website(openerp.addons.web.controllers.main.Home):
def pagenew(self, path, noredirect=False, add_menu=None):
xml_id = request.registry['website'].new_page(request.cr, request.uid, path, context=request.context)
if add_menu:
request.registry['website.menu'].create(request.cr, request.uid, {
request.registry['website.menu'].create(
request.cr, request.uid, {
'name': path,
'url': "/page/" + xml_id,
'parent_id': request.website.menu_id.id,
@@ -202,10 +203,10 @@ class Website(openerp.addons.web.controllers.main.Home):
return request.redirect(redirect)
@http.route('/website/customize_template_get', type='json', auth='user', website=True)
def customize_template_get(self, xml_id, full=False, bundles=False):
""" Lists the templates customizing ``xml_id``. By default, only
returns optional templates (which can be toggled on and off), if
``full=True`` returns all templates customizing ``xml_id``
def customize_template_get(self, key, full=False, bundles=False, **kw):
""" Get inherit view's informations of the template ``xml_id``. By default, only
returns ``customize_show`` templates (which can be active or not), if
``full=True`` returns inherit view's informations of the template ``xml_id``.
``bundles=True`` returns also the asset bundles
"""
imd = request.registry['ir.model.data']
@@ -215,9 +216,8 @@ class Website(openerp.addons.web.controllers.main.Home):
user = request.registry['res.users']\
.browse(request.cr, request.uid, request.uid, request.context)
user_groups = set(user.groups_id)
views = request.registry["ir.ui.view"]\
._views_get(request.cr, request.uid, xml_id, bundles=bundles, context=dict(request.context or {}, active_test=False))
._views_get(request.cr, request.uid, key, bundles=bundles, context=dict(request.context or {}, active_test=False))
done = set()
result = []
for v in views:
@@ -228,7 +228,7 @@ class Website(openerp.addons.web.controllers.main.Home):
result.append({
'name': v.inherit_id.name,
'id': v.id,
'xml_id': v.xml_id,
'key': v.key,
'inherit_id': v.inherit_id.id,
'header': True,
'active': False
@@ -237,7 +237,7 @@ class Website(openerp.addons.web.controllers.main.Home):
result.append({
'name': v.name,
'id': v.id,
'xml_id': v.xml_id,
'key': v.key,
'inherit_id': v.inherit_id.id,
'header': False,
'active': v.active,
+1 -1
View File
@@ -135,7 +135,7 @@ response = request.render("website.template_partner_comment", values)
<record id="website2" model="website">
<field name="name">0.0.0.0</field>
<field name="social_twitter">https://twitter.com/Odoo</field>
<field name="domain">0.0.0.0</field>
<field name="social_facebook">https://www.facebook.com/Odoo</field>
<field name="social_googleplus">https://plus.google.com/+Odooapps</field>
<field name="social_linkedin">https://www.linkedin.com/company/odoo</field>
+6 -2
View File
@@ -38,9 +38,13 @@ class ir_http(orm.AbstractModel):
)
def _auth_method_public(self):
# TODO: select user_id from matching website
if not request.session.uid:
request.uid = self.pool['ir.model.data'].xmlid_to_res_id(request.cr, openerp.SUPERUSER_ID, 'base.public_user')
domain_name = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0]
website_id = self.pool['website']._get_current_website_id(request.cr, openerp.SUPERUSER_ID, domain_name, context=request.context)
if website_id:
request.uid = self.pool['website'].browse(request.cr, openerp.SUPERUSER_ID, website_id, request.context).user_id.id
else:
dummy, request.uid = self.pool['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'base', 'public_user')
else:
request.uid = request.session.uid
-5
View File
@@ -23,11 +23,6 @@ class view(osv.osv):
'website_id': fields.many2one('website', ondelete='cascade', string="Website"),
}
_sql_constraints = [
('key_website_id_uniq', 'unique(key, website_id)',
'Key must be unique per website.'),
]
_defaults = {
'page': False,
'customize_show': False,
+6 -6
View File
@@ -279,16 +279,16 @@ class website(osv.osv):
@openerp.tools.ormcache(skiparg=4)
def _get_current_website_id(self, cr, uid, domain_name, context=None):
website_id = 1
if request:
ids = self.search(cr, uid, [('domain', '=', domain_name)], context=context)
if ids:
website_id = ids[0]
return website_id
ids = self.search(cr, uid, [('name', '=', domain_name)], context=context)
if ids:
return ids[0]
else:
return self.search(cr, uid, [], context=context)[0]
def get_current_website(self, cr, uid, context=None):
domain_name = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0]
website_id = self._get_current_website_id(cr, uid, domain_name, context=context)
request.context['website_id'] = website_id
return self.browse(cr, uid, website_id, context=context)
def is_publisher(self, cr, uid, ids, context=None):
+1 -1
View File
@@ -135,7 +135,7 @@
loadTemplates: function () {
var self = this;
var args = {
xml_id: $(document.documentElement).data('view-xmlid'),
key: $(document.documentElement).data('view-xmlid'),
full: true,
bundles: !!$('script[src*=".assets_common"]').length
};
+11 -3
View File
@@ -901,7 +901,7 @@ define(['summernote/summernote'], function () {
rte_changed: function () {
this.$buttons.save.prop('disabled', false);
},
save: function () {
_save: function () {
var self = this;
var saved = {}; // list of allready saved views and data
@@ -969,9 +969,17 @@ define(['summernote/summernote'], function () {
});
});
},
save: function () {
return this._save().then(function () {
website.reload();
});
},
/**
* Saves an RTE content, which always corresponds to a view section (?).
*/
save_without_reload: function () {
return this._save();
},
saveElement: function ($el) {
var markup = $el.prop('outerHTML');
return openerp.jsonRpc('/web/dataset/call', 'call', {
@@ -1020,10 +1028,10 @@ define(['summernote/summernote'], function () {
if(this.loaded) {
return;
}
openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': this.view_name }).then(
openerp.jsonRpc('/website/customize_template_get', 'call', { 'key': this.view_name }).then(
function(result) {
_.each(result, function (item) {
if (item.xml_id === "website.debugger" && !window.location.search.match(/[&?]debug(&|$)/)) return;
if (item.key === "website.debugger" && !window.location.search.match(/[&?]debug(&|$)/)) return;
if (item.header) {
self.$menu.append('<li class="dropdown-header">' + item.name + '</li>');
} else {
@@ -102,7 +102,7 @@
var self = this;
this.$button = $(openerp.qweb.render('website.snippets_button'))
.prependTo(this.parent.$("#website-top-edit ul"))
.prependTo(this.parent.$(".js_editor_placeholder"))
.find("button");
this.$button.click(_.bind(this.show_blocks, this));
+20 -24
View File
@@ -59,7 +59,7 @@
t-att-data-website-id="website.id if editable and website else None"
t-att-data-editable="'1' if editable else None"
t-att-data-translatable="'1' if translatable else None"
t-att-data-view-xmlid="xmlid if editable else None"
t-att-data-view-xmlid="xmlid"
t-att-data-main-object="repr(main_object) if editable else None"
t-att-data-oe-company-name="res_company.name">
<head>
@@ -88,10 +88,21 @@
</t>
</t>
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="website.assets_frontend" t-js="false"/>
<t t-call-assets="web.assets_common"/>
<t t-call-assets="website.assets_frontend"/>
<t t-raw="head or ''" name='layout_head'/>
<t t-if="website and website.google_analytics_key">
<script id="tracking_code">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', _.str.trim('<t t-esc="website.google_analytics_key"/>'), 'auto');
ga('send','pageview');
</script>
</t>
</head>
<body>
<div id="wrapwrap">
@@ -133,28 +144,16 @@
<t t-raw="0"/>
</main>
<footer>
<div id="footer_container">
<div id="footer">
</div>
</footer>
</div>
<t t-call-assets="web.assets_common" t-css="false"/>
<t t-call-assets="website.assets_frontend" t-css="false"/>
<script t-if="website and website.google_analytics_key">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', _.str.trim('<t t-esc="website.google_analytics_key"/>'), 'auto');
ga('send','pageview');
</script>
</body>
</html>
</template>
<template id="layout_footer_copyright" inherit_id="website.layout" name="Footer Copyright">
<xpath expr="//footer" position="inside">
<xpath expr="//div[contains(@id, 'footer')]" position="inside">
<div class="container mt16 mb8">
<div class="pull-right" t-ignore="true" t-if="not editable">
Create a <a href="http://www.odoo.com/page/website-builder">free website</a> with
@@ -176,11 +175,8 @@
</template>
<template id="editor_head" inherit_id="website.layout" name="Editor" groups="base.group_website_publisher,base.group_website_designer">
<xpath expr='//t[@t-call-assets="website.assets_frontend"][@t-js]' position="after">
<t t-call-assets="website.assets_editor" t-js="false"/>
</xpath>
<xpath expr='//t[@t-call-assets="website.assets_frontend"][@t-css]' position="after">
<t t-call-assets="website.assets_editor" t-css="false"/>
<xpath expr='//t[@t-call-assets="website.assets_frontend"]' position="after">
<t t-call-assets="website.assets_editor"/>
</xpath>
</template>
@@ -303,7 +299,7 @@
</template>
<template id="footer_custom" inherit_id="website.layout" name="Footer">
<xpath expr="//div[@id='footer_container']" position="replace">
<xpath expr="//div[@id='footer']" position="replace">
<div class="oe_structure" id="footer">
<section>
<div class="container">
@@ -335,7 +331,7 @@
<template id="footer_default" inherit_id="website.footer_custom" customize_show="True" name="Automatic Footer">
<xpath expr="//div[@id='footer']" position="replace">
<div class="container hidden-print">
<div id="footer" class="container hidden-print">
<div class="row">
<div class="col-md-3">
<h4>Our products &amp; Services</h4>
+1
View File
@@ -3,6 +3,7 @@
<data noupdate="1">
<record id="default_website" model="website">
<field name="name">localhost</field>
<field name="domain">localhost</field>
<field name="company_id" ref="base.main_company"/>
<field name="language_ids" eval="[(6, 0, [ ref('base.lang_en')])]"/>
<field name="default_lang_id" ref="base.lang_en"/>
+3
View File
@@ -0,0 +1,3 @@
import models
import controllers
import tests
+26
View File
@@ -0,0 +1,26 @@
{
'name': 'Website Versioning',
'category': 'Website',
'summary': 'Allow to save all the versions of your website and allow to perform AB testing.',
'version': '1.0',
'description': """
OpenERP Website CMS
===================
""",
'author': 'OpenERP SA',
'depends': ['website','marketing','google_account'],
'installable': True,
'data': [
'security/ir.model.access.csv',
'views/website_version_templates.xml',
'views/marketing_view.xml',
'views/website_version_views.xml',
'views/res_config.xml',
'data/demo.xml',
'data/data.xml',
],
'demo': [],
'qweb': ['static/src/xml/*.xml'],
'application': True,
}
@@ -0,0 +1 @@
import main
+188
View File
@@ -0,0 +1,188 @@
from openerp import http
from openerp.http import request
import datetime
from openerp.addons.website.controllers.main import Website
class Versioning_Controller(Website):
@http.route(['/website_version/change_version'], type = 'json', auth = "user", website = True)
def change_version(self, version_id):
request.session['version_id'] = int(version_id)
return version_id
@http.route(['/website_version/create_version'], type = 'json', auth = "user", website = True)
def create_version(self, name, version_id):
if name == "":
name = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
website_id = request.website.id
new_version = request.env['website_version.version'].create({'name':name, 'website_id':website_id})
if version_id:
request.env['ir.ui.view'].copy_version(version_id, new_version.id)
request.session['version_id'] = new_version.id
return new_version.id
@http.route(['/website_version/delete_version'], type = 'json', auth = "user", website = True)
def delete_version(self, version_id):
version_id = int(version_id)
x = request.env['website_version.version'].browse(version_id)
name = x.name
x.unlink()
current_id = request.context.get('version_id')
if version_id== current_id:
request.session['version_id'] = 0
return name
@http.route(['/website_version/check_version'], type = 'json', auth = "user", website = True)
def check_version(self, version_id):
#To check if the version is in a running or paused experiment
Exp = request.env['website_version.experiment']
return bool(Exp.search(['|',('state','=','running'),('state','=','paused'),('experiment_version_ids.version_id', '=', int(version_id))]))
@http.route(['/website_version/all_versions'], type = 'json', auth = "public", website = True)
def all_versions(self, view_id):
#To get all versions in the menu
v = request.env['ir.ui.view'].browse(int(view_id))
ver = request.env['website_version.version']
website_id = request.website.id
result = ver.search_read([('website_id','=',website_id),'|',('view_ids.key','=',v.key),('view_ids.key','=','website.footer_default')],['id','name'])
version_id = request.context.get('version_id')
check = False
for x in result:
if x['id'] == version_id:
x['bold'] = 1
check = True
else:
x['bold'] = 0
#To always show in the menu the current version
if not check and version_id:
result.append({'id':version_id, 'name':ver.browse(version_id).name, 'bold':1})
return result
@http.route(['/website_version/has_experiments'], type = 'json', auth = "public", website = True)
def has_experiments(self, view_id):
v = request.env['ir.ui.view'].browse(int(view_id))
website_id = request.context.get('website_id')
return bool(request.env["website_version.experiment_version"].search([('version_id.view_ids.key', '=', v.key),('experiment_id.website_id.id','=',website_id)]))
@http.route(['/website_version/publish_version'], type = 'json', auth = "public", website = True)
def publish_version(self, version_id, save_master, copy_master_name):
request.session['version_id'] = 0
return request.env['website_version.version'].publish_version(int(version_id), save_master, copy_master_name)
@http.route(['/website_version/diff_version'], type = 'json', auth = "public", website = True)
def diff_version(self, version_id):
mod_version = request.env['website_version.version']
version = mod_version.browse(int(version_id))
name_list = []
for view in version.view_ids:
name_list.append(view.name)
return name_list
@http.route(['/website_version/get_analytics'], type = 'json', auth = "public", website = True)
def get_analytics(self, view_id):
#To get the ExpId and the VarId of the view if it is in a running experiment
result = {'ExpId':False, 'VarId': 0}
obj_v = request.env['ir.ui.view']
view = obj_v.browse(int(view_id))
#search all the running experiments with the key of view
exp_ids = request.env['website_version.experiment'].search([('experiment_version_ids.version_id.view_ids.key', '=', view.key),('state','=','running'),('experiment_version_ids.version_id.website_id', '=',request.context['website_id'])])
if exp_ids:
#No overlap between running experiments then we can take the first one
x = exp_ids[0]
result['ExpId'] = x.google_id
if view.version_id:
exp_ver_ids = request.env['website_version.experiment_version'].search([('experiment_id','=',exp_ids[0].id),('version_id','=',view.version_id.id)])
if exp_ver_ids:
y = exp_ver_ids[0]
result['VarId'] = y.google_index
return result
@http.route(['/website_version/google_access'], type='json', auth="user")
def google_authorize(self, **kw):
#Check if client_id and client_secret are set to get the authorization from Google
gs_obj = request.env['google.service']
gm_obj = request.env['google.management']
client_id = gs_obj.get_client_id('management', context=kw.get('local_context'))
client_secret = gs_obj.get_client_secret('management', context=kw.get('local_context'))
if not client_id or not client_secret:
dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid, 'website_version', 'action_config_settings_google_management')
return {
"status": "need_config_from_admin",
"url": '',
"action": action
}
url = gm_obj.authorize_google_uri(from_url=kw.get('fromurl'), context=kw.get('local_context'))
return {
"status": "need_auth",
"url": url
}
@http.route(['/website_version/set_google_access'], type = 'json', auth = "public", website = True)
def set_google_access(self, ga_key, view_id, client_id, client_secret):
#To set ga_key, view_id, client_id, client_secret
website_id = request.context.get('website_id')
web = request.env['website'].browse(website_id)
web.write({'google_analytics_key':ga_key, 'google_analytics_view_id':view_id})
if client_id and client_secret:
icp = request.env['ir.config_parameter']
icp.set_param('google_management_client_id', client_id or '', groups=['base.group_system'])
icp.set_param('google_management_client_secret', client_secret or '', groups=['base.group_system'])
@http.route(['/website_version/all_versions_all_goals'], type = 'json', auth = "public", website = True)
def all_versions_all_goals(self, view_id):
#To get all versions and all goals to create an experiment
view = request.env['ir.ui.view']
version = request.env['website_version.version']
goal = request.env['website_version.goals']
icp = request.env['ir.config_parameter']
v = view.browse(int(view_id))
website_id = request.website.id
r1 = version.search_read([('website_id','=',website_id),'|',('view_ids.key','=',v.key),('view_ids.key','=','website.footer_default')],['id','name'])
r2 = goal.search_read([],['id','name'])
#Check if all the parameters are set to communicate with Google analytics
if icp.get_param('google_%s_token' % 'management'):
r3 = 2
if request.website.google_analytics_key and request.website.google_analytics_view_id:
r3 = 1
else:
r3 = 0
return {'tab_version':r1, 'tab_goal':r2, 'check_conf': r3}
def check_view(self, version_ids):
#Check if version_ids don't overlap with running experiments
version_keys = set([v['key'] for v in request.env['ir.ui.view'].search_read([('version_id', 'in', version_ids)], ['key'])])
exp_mod = request.env['website_version.experiment']
exps = exp_mod.search([('state','=','running'),('website_id','=',request.context.get('website_id'))])
for exp in exps:
for exp_ver in exp.experiment_version_ids:
for view in exp_ver.version_id.view_ids:
if view.key in version_keys:
return (False,exp.name)
return (True,"")
@http.route(['/website_version/launch_experiment'], type = 'json', auth = "public", website = True)
def launch_experiment(self, name, version_ids, objectives):
tab = []
check = self.check_view(version_ids)
if check[0]:
for x in version_ids:
tab.append([0, False, {'frequency': '50', 'version_id': int(x)}])
vals = {'name':name, 'google_id': False, 'state': 'running', 'website_id':request.context.get('website_id'), 'experiment_version_ids':tab, 'objectives': int(objectives)}
exp_obj = request.env['website_version.experiment']
exp_obj.create(vals)
return check
@http.route('/website/customize_template_get', type='json', auth='user', website=True)
def customize_template_get(self, key, full=False, bundles=False, **kw):
result = Website.customize_template_get(self, key, full=full, bundles=bundles, **kw)
check = []
res = []
for data in result:
if data['name'] not in check:
check.append(data['name'])
res.append(data)
return res
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="goal_pageviews" model="website_version.goals">
<field name="name">Page Views</field>
<field name="google_ref">ga:pageviews</field>
</record>
<record id="goal_bounces" model="website_version.goals">
<field name="name">Decrease Bounces</field>
<field name="google_ref">ga:bounces</field>
</record>
<record id="goal_sessionDuration" model="website_version.goals">
<field name="name">Session Duration</field>
<field name="google_ref">ga:sessionDuration</field>
</record>
</data>
</openerp>
+158
View File
@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="default_1" model="website_version.version">
<field name="name">Default_1</field>
<field name="website_id" ref="website.default_website"/>
</record>
<record id="default_2" model="website_version.version">
<field name="name">Default_2</field>
<field name="website_id" ref="website.website2"/>
</record>
<record id="version_localhost" model="website_version.version">
<field name="name">version localhost</field>
<field name="website_id" ref="website.default_website"/>
</record>
<record id="version_0_0_0_0" model="website_version.version">
<field name="name">version 0.0.0.0</field>
<field name="website_id" ref="website.website2"/>
</record>
<record id="website2_homepage_other" model="ir.ui.view">
<field name="name">Homepage</field>
<field name="type">qweb</field>
<field name="key">website.homepage</field>
<field name="version_id" ref="version_0_0_0_0"/>
<field name="website_id" ref="website.website2"/>
<field name="arch" type="xml">
<t name="Homepage" priority="29" t-name="website.homepage">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty">
<div class="carousel slide mb32" id="myCarousel0" style="height: 320px;">
<ol class="carousel-indicators hidden">
<li class="active" data-slide-to="0" data-target="#myCarousel0"/>
</ol>
<div class="carousel-inner">
<div class="item image_text oe_img_bg active" style="background-image: url(http://0.0.0.0:8069/website/static/src/img/banner/mountains.jpg);">
<div class="container">
<div class="row content">
<div class="carousel-content col-md-6 col-sm-12">
<h2>version 0.0.0.0</h2>
<h3>Click to customize this text</h3>
<p>
<a class="btn btn-success btn-large" href="/page/website.contactus">Contact us</a>
</p>
</div>
<span class="carousel-img col-md-6 hidden-sm hidden-xs"> </span>
</div>
</div>
</div>
</div>
<div class="carousel-control left hidden" data-slide="prev" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-left"/>
</div>
<div class="carousel-control right hidden" data-slide="next" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-right"/>
</div>
</div>
</div>
</t>
</t>
</field>
</record>
<record id="homepage_localhost" model="ir.ui.view">
<field name="name">Homepage</field>
<field name="type">qweb</field>
<field name="key">website.homepage</field>
<field name="version_id" ref="version_localhost"/>
<field name="website_id" ref="website.default_website"/>
<field name="arch" type="xml">
<t name="Homepage" priority="29" t-name="website.homepage">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty">
<div class="carousel slide mb32" id="myCarousel0" style="height: 320px;">
<ol class="carousel-indicators hidden">
<li class="active" data-slide-to="0" data-target="#myCarousel0"/>
</ol>
<div class="carousel-inner">
<div class="item image_text oe_img_bg active" style="background-image: url(http://localhost:8069/website/static/src/img/banner/greenfields.jpg);">
<div class="container">
<div class="row content">
<div class="carousel-content col-md-6 col-sm-12">
<h2>version localhost</h2>
<h3>Click to customize this text</h3>
<p>
<a class="btn btn-success btn-large" href="/page/website.contactus">Contact us</a>
</p>
</div>
<span class="carousel-img col-md-6 hidden-sm hidden-xs"> </span>
</div>
</div>
</div>
</div>
<div class="carousel-control left hidden" data-slide="prev" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-left"/>
</div>
<div class="carousel-control right hidden" data-slide="next" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-right"/>
</div>
</div>
</div>
</t>
</t>
</field>
</record>
<record id="homepage_default_2" model="ir.ui.view">
<field name="name">Homepage</field>
<field name="type">qweb</field>
<field name="key">website.homepage</field>
<field name="version_id" ref="default_2"/>
<field name="website_id" ref="website.website2"/>
<!-- <field name="application">Always applied</field> -->
<field name="arch" type="xml">
<t name="Homepage" priority="29" t-name="website.homepage">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty">
<div class="carousel slide mb32" id="myCarousel0" style="height: 320px;">
<ol class="carousel-indicators hidden">
<li class="active" data-slide-to="0" data-target="#myCarousel0"/>
</ol>
<div class="carousel-inner">
<div class="item image_text oe_img_bg active" style="background-image: url(http://localhost:8069/website/static/src/img/parallax/parallax_bg.jpg);">
<div class="container">
<div class="row content">
<div class="carousel-content col-md-6 col-sm-12">
<h2>Master</h2>
<h3>Click to customize this text</h3>
<p>
<a class="btn btn-success btn-large" href="/page/website.contactus">Contact us</a>
</p>
</div>
<span class="carousel-img col-md-6 hidden-sm hidden-xs">
<img alt="Banner Odoo Image" class="carousel-image img-responsive" src="/website/static/src/img/banner/banner_picture.png"/>
</span>
</div>
</div>
</div>
</div>
<div class="carousel-control left hidden" data-slide="prev" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-left"/>
</div>
<div class="carousel-control right hidden" data-slide="next" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-right"/>
</div>
</div>
</div>
</t>
</t>
</field>
</record>
</data>
</openerp>
@@ -0,0 +1,7 @@
import ir_ui_view
import version
import website
import experiment
import ir_qweb
import res_config
import ir_http
+310
View File
@@ -0,0 +1,310 @@
# -*- coding: utf-8 -*-
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.exceptions import Warning
from openerp import SUPERUSER_ID
from datetime import datetime, timedelta
from openerp.osv import osv, fields
import simplejson
from openerp.tools.translate import _
class Experiment_version(osv.Model):
_name = "website_version.experiment_version"
#To get the index of the version of an experiment(Google analytics needs this information)
def _get_index(self, cr, uid, ids, name, arg, context=None):
result = {}
for exp_ver in self.browse(cr, uid, ids, context=context):
exp = exp_ver.experiment_id
index = 1
for x in exp.experiment_version_ids:
if x.id == exp_ver.id:
break
else:
index+=1
result[exp_ver.id] = index
return result
_columns = {
'version_id': fields.many2one('website_version.version',string="version_id",required=True ,ondelete='cascade'),
'experiment_id': fields.many2one('website_version.experiment',string="Experiment_id",required=True,ondelete='cascade'),
'frequency': fields.selection([('10','Less'),('50','Medium'),('80','More')], 'Frequency'),
'google_index': fields.function(_get_index,type='integer', string='Google_index'),
}
_defaults = {
'frequency': '50',
}
#This model allow to define the Google analytics goals in a experiment
class Goals(osv.Model):
_name = "website_version.goals"
_columns = {
'name': fields.char(string="Name", size=256, required=True),
'google_ref': fields.char(string="Reference Google", size=256, required=True),
}
EXPERIMENT_STATES = [('running','Running'),('paused','Paused'),('ended','Ended')]
class Experiment(osv.Model):
_name = "website_version.experiment"
_inherit = ['mail.thread']
def _get_version_number(self, cr, uid, ids, name, arg, context=None):
result = {}
for exp in self.browse(cr, uid, ids, context=context):
result[exp.id] = 0
for exp_ver in exp.experiment_version_ids:
result[exp.id] += 1
#For master
result[exp.id] += 1
return result
_columns = {
'name': fields.char(string="Title", size=256, required=True),
'experiment_version_ids': fields.one2many('website_version.experiment_version', 'experiment_id',string="experiment_version_ids"),
'website_id': fields.many2one('website',string="Website", required=True),
'state': fields.selection(EXPERIMENT_STATES, 'Status', required=True, copy=False, track_visibility='onchange'),
'objectives': fields.many2one('website_version.goals',string="Objective", required=True),
'color': fields.integer('Color Index'),
'version_number' : fields.function(_get_version_number,type='integer'),
'sequence': fields.integer('Sequence', required=True, help="Test."),
'google_id': fields.char(string="Google_id", size=256),
}
_defaults = {
'state': 'running',
'sequence': 1,
}
def create(self, cr, uid, vals, context=None):
exp={}
exp['name'] = vals['name']
exp['objectiveMetric'] = self.pool['website_version.goals'].browse(cr, uid, [vals['objectives']],context)[0].google_ref
exp['status'] = vals['state']
exp['variations'] =[{'name':'master','url': 'http://localhost/master'}]
version_list = vals.get('experiment_version_ids')
if version_list:
for version in version_list:
name = self.pool['website_version.version'].browse(cr, uid, [version[2]['version_id']],context)[0].name
#We must give a URL for each version in the experiment
exp['variations'].append({'name':name, 'url': 'http://localhost/'+name})
else:
raise Warning(_("You must select at least one version in your experiment."))
google_id = self.pool['google.management'].create_an_experiment(cr, uid, exp, vals['website_id'], context=context)
if not google_id:
raise Warning(_("Please verify you give the authorizations to use google analytics api ..."))
vals['google_id'] = google_id
return super(Experiment, self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, context=None):
state = vals.get('state')
for exp in self.browse(cr, uid, ids, context=context):
if state and exp.state == 'ended':
raise Warning(_("You cannot modify an ended experiment."))
elif state == 'ended':
#temp is the data to send to Googe
temp={}
temp['name'] = exp.name
temp['status'] = state
temp['variations'] = [{'name':'master','url': 'http://localhost/master'}]
for exp_v in exp.experiment_version_ids:
temp['variations'].append({'name':exp_v.version_id.name, 'url': 'http://localhost/'+exp_v.version_id.name})
#to check the constraints before to write on the google analytics account
self.pool['google.management'].update_an_experiment(cr, uid, temp, exp.google_id, exp.website_id.id, context=context)
return super(Experiment, self).write(cr, uid, ids, vals, context=context)
def unlink(self, cr, uid, ids, context=None):
for exp in self.browse(cr, uid, ids, context=context):
self.pool['google.management'].delete_an_experiment(cr, uid, exp.google_id, exp.website_id.id, context=context)
return super(Experiment, self).unlink(cr, uid, ids, context=context)
def update_goals(self,cr,uid,ids,context=None):
gm_obj = self.pool['google.management']
goals_obj = self.pool['website_version.goals']
website_id = context.get('website_id')
if not website_id:
raise Warning("You must specify the website.")
x = gm_obj.get_goal_info(cr, uid, website_id, context=context)
for y in x[1]['items']:
if not goals_obj.search(cr, uid, [('name','=',y['name'])],context=context):
vals ={'name':y['name'], 'google_ref':'ga:goal'+y['id']+'Completions'}
goals_obj.create(cr, uid, vals, context=None)
def _check_view(self, cr, uid, ids, context=None):
#No overlap for running experiments
exp_ids = self.search(cr,uid,[('state','=','running')],context=context)
exps = self.browse(cr,uid,exp_ids,context=context)
check_a = set()
check_b = set()
for exp in exps:
for exp_ver in exp.experiment_version_ids:
for view in exp_ver.version_id.view_ids:
x = (view.key,view.website_id.id)
#the versions in the same experiment can have common keys
y = (view.key,view.website_id.id,exp_ver.experiment_id.id)
if x in check_a and not y in check_b:
return False
else:
check_a.add(x)
check_b.add(y)
return True
def _check_website(self, cr, uid, ids, context=None):
for exp in self.browse(cr,uid,ids,context=context):
for exp_ver in exp.experiment_version_ids:
if not exp_ver.version_id.website_id.id == exp.website_id.id:
return False
return True
_constraints = [
(_check_view, _('This experiment contains a view which is already used in another running experience'), ['state']),
(_check_website, _('This experiment must have versions which are in the same website'), ['website_id', 'experiment_version_ids']),
]
_order = 'sequence'
_group_by_full = {
'state': lambda *args, **kwargs : ([s[0] for s in EXPERIMENT_STATES], dict()),
}
class google_management(osv.AbstractModel):
STR_SERVICE = 'management'
_name = 'google.%s' % STR_SERVICE
def generate_data(self, cr, uid, experiment, isCreating=False, context=None):
data = {
'name': experiment['name'],
'status': experiment['status'],
'variations': experiment['variations']
}
return data
def create_an_experiment(self, cr, uid, data, website_id, context=None):
gs_pool = self.pool['google.service']
website = self.pool['website'].browse(cr, uid, website_id, context=context)[0]
webPropertyId = website.google_analytics_key
if not webPropertyId:
raise Warning(_("You must give a Google Analytics Key."))
accountId = webPropertyId.split('-')[1]
profileId = website.google_analytics_view_id
if not profileId:
raise Warning(_("You must give a Google view ID."))
url = '/analytics/v3/management/accounts/%s/webproperties/%s/profiles/%s/experiments?access_token=%s' % (accountId, webPropertyId, profileId, self.get_token(cr, uid, context))
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
data_json = simplejson.dumps(data)
try:
x = gs_pool._do_request(cr, uid, url, data_json, headers, type='POST', context=context)
except:
x = False
return x[1]['id']
def update_an_experiment(self, cr, uid, data, google_id, website_id, context=None):
gs_pool = self.pool['google.service']
website = self.pool['website'].browse(cr, uid, website_id, context=context)[0]
webPropertyId = website.google_analytics_key
accountId = webPropertyId.split('-')[1]
profileId = website.google_analytics_view_id
url = '/analytics/v3/management/accounts/%s/webproperties/%s/profiles/%s/experiments/%s?access_token=%s' % (accountId, webPropertyId, profileId, google_id, self.get_token(cr, uid, context))
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
data_json = simplejson.dumps(data)
return gs_pool._do_request(cr, uid, url, data_json, headers, type='PUT', context=context)
def get_experiment_info(self, cr, uid, google_id, website_id, context=None):
gs_pool = self.pool['google.service']
website = self.pool['website'].browse(cr, uid, website_id, context=context)[0]
webPropertyId = website.google_analytics_key
accountId = webPropertyId.split('-')[1]
profileId = website.google_analytics_view_id
params = {
'access_token': self.get_token(cr, uid, context),
}
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
url = '/analytics/v3/management/accounts/%s/webproperties/%s/profiles/%s/experiments/%s' % (accountId, webPropertyId, profileId, google_id)
return gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
def get_goal_info(self, cr, uid, website_id, context=None):
gs_pool = self.pool['google.service']
website = self.pool['website'].browse(cr, uid, website_id, context=context)[0]
webPropertyId = website.google_analytics_key
accountId = webPropertyId.split('-')[1]
profileId = website.google_analytics_view_id
params = {
'access_token': self.get_token(cr, uid, context),
}
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
url = '/analytics/v3/management/accounts/%s/webproperties/%s/profiles/%s/goals' % (accountId, webPropertyId, profileId)
return gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context)
def delete_an_experiment(self, cr, uid, google_id, website_id, context=None):
gs_pool = self.pool['google.service']
website = self.pool['website'].browse(cr, uid, website_id, context=context)[0]
webPropertyId = website.google_analytics_key
accountId = webPropertyId.split('-')[1]
profileId = website.google_analytics_view_id
params = {
'access_token': self.get_token(cr, uid, context)
}
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
url = '/analytics/v3/management/accounts/%s/webproperties/%s/profiles/%s/experiments/%s' %(accountId, webPropertyId, profileId, google_id)
return gs_pool._do_request(cr, uid, url, params, headers, type='DELETE', context=context)
def get_token(self, cr, uid, context=None):
icp = self.pool['ir.config_parameter']
validity = icp.get_param(cr, SUPERUSER_ID, 'google_%s_token_validity' % self.STR_SERVICE)
token = icp.get_param(cr, SUPERUSER_ID, 'google_%s_token' % self.STR_SERVICE)
if not (validity and token):
raise Warning(_("You must configure your account."))
if datetime.strptime(validity.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) < (datetime.now() + timedelta(minutes=3)):
token = self.do_refresh_token(cr, uid, context=context)
return token
def do_refresh_token(self, cr, uid, context=None):
gs_pool = self.pool['google.service']
icp = self.pool['ir.config_parameter']
rtoken = icp.get_param(cr, SUPERUSER_ID, 'google_%s_rtoken' % self.STR_SERVICE)
all_token = gs_pool._refresh_google_token_json(cr, uid, rtoken, self.STR_SERVICE, context=context)
icp.set_param(cr, SUPERUSER_ID, 'google_%s_token_validity' % self.STR_SERVICE, datetime.now() + timedelta(seconds=all_token.get('expires_in')))
icp.set_param(cr, SUPERUSER_ID, 'google_%s_token' % self.STR_SERVICE, all_token.get('access_token'))
return all_token.get('access_token')
# Should be called at configuration
def get_management_scope(self):
return 'https://www.googleapis.com/auth/analytics https://www.googleapis.com/auth/analytics.edit'
def authorize_google_uri(self, cr, uid, from_url='http://www.odoo.com', context=None):
url = self.pool['google.service']._get_authorize_uri(cr, uid, from_url, self.STR_SERVICE, scope=self.get_management_scope(), context=context)
return url
# convert code from authorize into token
def set_all_tokens(self, cr, uid, authorization_code, context=None):
gs_pool = self.pool['google.service']
all_token = gs_pool._get_google_token_json(cr, uid, authorization_code, self.STR_SERVICE, context=context)
vals = {}
vals['google_%s_rtoken' % self.STR_SERVICE] = all_token.get('refresh_token')
vals['google_%s_token_validity' % self.STR_SERVICE] = datetime.now() + timedelta(seconds=all_token.get('expires_in'))
vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token')
icp = self.pool['ir.config_parameter']
icp.set_param(cr, SUPERUSER_ID, 'google_%s_rtoken' % self.STR_SERVICE, all_token.get('refresh_token'))
icp.set_param(cr, SUPERUSER_ID, 'google_%s_token_validity' % self.STR_SERVICE, datetime.now() + timedelta(seconds=all_token.get('expires_in')))
icp.set_param(cr, SUPERUSER_ID, 'google_%s_token' % self.STR_SERVICE, all_token.get('access_token'))
+16
View File
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from openerp.http import request
from openerp.osv import orm
import json
class ir_http(orm.AbstractModel):
_inherit = 'ir.http'
def _dispatch(self):
x = super(ir_http, self)._dispatch()
if request.context.get('website_version_experiment'):
data=json.dumps(request.context['website_version_experiment'], ensure_ascii=False)
x.set_cookie('website_version_experiment', data)
return x
+59
View File
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""
Website-context rendering needs to add some metadata to rendered fields,
as well as render a few fields differently.
Also, adds methods to convert values back to openerp models.
"""
from openerp.addons.base.ir.ir_qweb import QWebContext
from openerp.osv import osv, orm, fields
class QWeb(orm.AbstractModel):
""" QWeb object for rendering stuff in the website context
"""
_inherit = 'website.qweb'
def render(self, cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None):
if qwebcontext is None:
qwebcontext = {}
if not isinstance(qwebcontext, QWebContext):
qwebcontext = QWebContext(cr, uid, qwebcontext, loader=loader, context=context)
if context is None:
context = {}
website_id=context.get('website_id')
if website_id:
if 'experiment_id' in context:
#Is there a version which have the view.key == id_or_xml_id and which is in a running experiment?
exp_ver_id = self.pool["website_version.experiment_version"].search(cr, uid, [('version_id.view_ids.key', '=', id_or_xml_id),('experiment_id.state','=','running'),('experiment_id.website_id.id','=',website_id)], context=context)
if exp_ver_id:
#If yes take the first because there is no overlap between running experiments.
exp_version = self.pool["website_version.experiment_version"].browse(cr, uid, [exp_ver_id[0]], context=context)
exp = exp_version.experiment_id
#We set the google_id as key in the dictionnary to avoid problem when reinitializating the db, exp.google_id is unique
version_id = context.get('website_version_experiment').get(str(exp.google_id))
if version_id:
context['version_id'] = int(version_id)
if 'version_id' in context:
version_id=context.get('version_id')
if version_id:
id_or_xml_id=self.pool["ir.ui.view"].search(cr, uid, [('key', '=', id_or_xml_id), '|', ('version_id', '=', False), ('version_id', '=', version_id), '|',('website_id','=',website_id),('website_id','=',False)], order='website_id, version_id', limit=1, context=context)[0]
else:
id_or_xml_id=self.pool["ir.ui.view"].search(cr, uid, [('key', '=', id_or_xml_id), ('version_id', '=', False), '|',('website_id','=',website_id),('website_id','=',False)], order='website_id', limit=1, context=context)[0]
else:
id_or_xml_id=self.pool["ir.ui.view"].search(cr, uid, [('key', '=', id_or_xml_id), '|', ('website_id','=',website_id),('website_id','=',False),('version_id', '=', False)], order='website_id', limit=1, context=context)[0]
qwebcontext['__template__'] = id_or_xml_id
stack = qwebcontext.get('__stack__', [])
if stack:
qwebcontext['__caller__'] = stack[-1]
stack.append(id_or_xml_id)
qwebcontext['__stack__'] = stack
qwebcontext['xmlid'] = str(stack[0]) # Temporary fix
return self.render_node(self.get_template(id_or_xml_id, qwebcontext), qwebcontext)
+173
View File
@@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv,fields
from openerp.http import request
from lxml import etree
from openerp import tools
from openerp.osv import osv, fields
class view(osv.Model):
_inherit = "ir.ui.view"
_columns = {
'version_id' : fields.many2one('website_version.version',ondelete='cascade', string="version_id"),
}
def write(self, cr, uid, ids, vals, toggle=False, context=None):
if context is None:
context = {}
try:
iter(ids)
except:
ids=[ids]
version_id=context.get('version_id')
#toggle is true when changing active field
if version_id and not context.get('mykey') and not toggle:
ctx = dict(context, write_on_view=True)
snap = self.pool['website_version.version']
version=snap.browse(cr, uid, [version_id], context=ctx)[0]
website_id=version.website_id.id
version_view_ids = []
for current in self.browse(cr, uid, ids, context=context):
#check if current is in version
if current.version_id.id == version_id:
version_view_ids.append(current.id)
else:
new_id = self.search(cr, uid, [('website_id', '=', website_id),('version_id', '=', version_id), ('key', '=', current.key)], context=context)
if new_id:
version_view_ids.append(new_id[0])
else:
copy_id=self.copy(cr,uid, current.id,{'version_id':version_id, 'website_id':website_id},context=ctx)
version_view_ids.append(copy_id)
super(view, self).write(cr, uid, version_view_ids, vals, context=ctx)
else:
ctx = dict(context, write_on_view=True)
super(view, self).write(cr, uid, ids, vals, context=context)
#To make a version of a version
def copy_version(self,cr, uid, version_id,new_version_id, context=None):
if context is None:
context = {}
ctx = dict(context, mykey=True)
snap = self.pool['website_version.version']
version=snap.browse(cr, uid, [version_id],ctx)[0]
for view in version.view_ids:
copy_id=self.copy(cr,uid,view.id,{'version_id':new_version_id},context=ctx)
def publish(self, cr, uid, ids, context=None):
del_l = []
views = self.browse(cr, uid, ids, context)
for view in views:
#To delete and replace views which are in the website( in fact with website_id)
master_id = self.pool['ir.ui.view'].search(cr, uid, [('key','=',view.key),('version_id', '=', False),('website_id', '=', view.website_id.id)],context=context)
del_l += master_id
if del_l:
self.pool['ir.ui.view'].unlink(cr, uid, del_l, context=context)
for view in views:
view.copy({'version_id': None})
#To publish a view in backend
def action_publish(self,cr,uid,ids,context=None):
if context is None:
context = {}
ctx = dict(context, mykey=True)
view_id = context.get('active_id')
self.publish(cr,uid, [view_id], context=None)
def get_view_id(self, cr, uid, xml_id, context=None):
if context and 'website_id' in context and not isinstance(xml_id, (int, long)):
domain = [('key', '=', xml_id), '|', ('website_id', '=', context['website_id']), ('website_id', '=', False)]
[xml_id] = self.search(cr, uid, domain, order='website_id', limit=1, context=context)
else:
xml_id = super(view, self).get_view_id(cr, uid, xml_id, context=context)
return xml_id
_read_template_cache = dict(accepted_keys=('lang', 'inherit_branding', 'editable', 'translatable', 'website_id','version_id'))
@tools.ormcache_context(**_read_template_cache)
def _read_template(self, cr, uid, view_id, context=None):
arch = self.read_combined(cr, uid, view_id, fields=['arch'], context=context)['arch']
arch_tree = etree.fromstring(arch)
if 'lang' in context:
arch_tree = self.translate_qweb(cr, uid, view_id, arch_tree, context['lang'], context)
self.distribute_branding(arch_tree)
root = etree.Element('templates')
root.append(arch_tree)
arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
return arch
@tools.ormcache(size=0)
def read_template(self, cr, uid, xml_id, context=None):
if isinstance(xml_id, (int, long)):
view_id = xml_id
else:
if '.' not in xml_id:
raise ValueError('Invalid template id: %r' % (xml_id,))
view_id = self.get_view_id(cr, uid, xml_id, context=context)
return self._read_template(cr, uid, view_id, context=context)
def clear_cache(self):
self._read_template.clear_cache(self)
#self.get_view_id.clear_cache(self)
#To take the right inheriting views
def get_inheriting_views_arch(self, cr, uid, view_id, model, context=None):
arch = super(view, self).get_inheriting_views_arch(cr, uid, view_id, model, context=context)
v = self.browse(cr, uid, [view_id],context)[0]
if not (context and context.get('website_id') and v.type == 'qweb'):
return arch
#right_ids to collect the right ids according to the key
right_ids = {}
priority = {}
#To create a dico called view_arch where v(key) is the id and a the arch(value)
view_arch = dict([(v, a) for a, v in arch])
keys = self.read(cr, uid, view_arch.keys(), ['key','version_id','website_id'], context)
for k in keys:
#The view to take depends of the context
if context.get('version_id'):
#priority:1 take the view which is in the same version
if k['version_id'] and k['version_id'][0] == context.get('version_id'):
right_ids[k['key']] = k['id']
priority[k['key']] = 3
#priority:2 take the view which is just in the same website
elif k['version_id'] == False and k['website_id'] and k['website_id'][0] == context.get('website_id'):
if not priority.get(k['key']) or priority.get(k['key']) < 3:
right_ids[k['key']] = k['id']
priority[k['key']] = 2
#priority:3 take the original view
elif k['version_id'] == False and k['website_id'] == False:
if not priority.get(k['key']) or priority.get(k['key']) < 2:
right_ids[k['key']] = k['id']
priority[k['key']] = 1
else:
#priority:1 take the view which is just in the same website
if k['version_id'] == False and k['website_id'] and k['website_id'][0] == context.get('website_id'):
right_ids[k['key']] = k['id']
priority[k['key']] = 2
#priority:2 take the original view
elif k['version_id'] == False and k['website_id'] == False:
if not priority.get(k['key']) or priority.get(k['key']) < 2:
right_ids[k['key']] = k['id']
priority[k['key']] = 1
return [x for x in arch if x[1] in right_ids.values()]
#To active or desactive the right views according to the key
def toggle(self, cr, uid, ids, context=None):
""" Switches between enabled and disabled statuses
"""
for view in self.browse(cr, uid, ids, context=dict(context or {}, active_test=False)):
all_id = self.search(cr, uid, [('key','=',view.key)], context=dict(context or {}, active_test=False))
for v in self.browse(cr, uid, all_id, context=dict(context or {}, active_test=False)):
v.write({'active': not v.active}, toggle=True)
@@ -0,0 +1,24 @@
from openerp.osv import fields, osv
class analytics_config_settings(osv.TransientModel):
_inherit = 'base.config.settings'
_columns = {
'ga_sync': fields.boolean("Show tutorial to know how to get my 'Client ID' and my 'Client Secret'"),
'ga_client_id': fields.char('Client ID'),
'ga_client_secret': fields.char('Client Secret'),
'google_management_authorization': fields.char('Google authorization')
}
def set_analytics(self,cr,uid,ids,context=None) :
params = self.pool['ir.config_parameter']
myself = self.browse(cr,uid,ids[0],context=context)
params.set_param(cr, uid, 'google_management_client_id', myself.ga_client_id or '', groups=['base.group_system'], context=None)
params.set_param(cr, uid, 'google_management_client_secret', myself.ga_client_secret or '', groups=['base.group_system'], context=None)
def get_analytics(self,cr,uid,ids,context=None):
params = self.pool.get('ir.config_parameter')
ga_client_id = params.get_param(cr, uid, 'google_management_client_id',default='',context=context)
ga_client_secret = params.get_param(cr, uid, 'google_management_client_secret',default='',context=context)
return dict(ga_client_id=ga_client_id,ga_client_secret=ga_client_secret)
+71
View File
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv, fields
from openerp.exceptions import Warning
from openerp.http import request
from openerp.tools.translate import _
class version(osv.Model):
_name = "website_version.version"
_columns = {
'name' : fields.char(string="Title", required=True),
'view_ids': fields.one2many('ir.ui.view', 'version_id',string="view_ids",copy=True),
'website_id': fields.many2one('website',ondelete='cascade', string="Website"),
'create_date': fields.datetime('Create Date'),
}
_sql_constraints = [
('name_uniq', 'unique(name, website_id)', _('You cannot have multiple versions with the same name in the same domain!')),
]
def unlink(self, cr, uid, ids, context=None):
Exp = self.pool['website_version.experiment']
result = Exp.search(cr, uid, ['|',('state','=','running'),('state','=','paused'),('experiment_version_ids.version_id', 'in', ids)],context=context)
if result:
raise Warning(_("You cannot delete a version which is in a running or paused experiment."))
#To avoid problem when we delete versions in Backend
if request:
request.session['version_id'] = 0
return super(version, self).unlink(cr, uid, ids, context=context)
def action_publish(self, cr, uid, ids, context=None):
if context is None:
context = {}
version_id = context.get('active_id')
if version_id:
view_ids = self.pool['ir.ui.view'].search(cr, uid, [('version_id','=',version_id)], context=context)
if view_ids:
self.pool['ir.ui.view'].publish(cr,uid, view_ids, context=context)
def publish_version(self, cr, uid, version_id, save_master, copy_master_name, context = None):
#Info: there were some cache problems with browse, this is why the code is so long
version = self.browse(cr, uid, [int(version_id)],context)[0]
del_l = []
copy_l = []
for view in version.view_ids:
master_id = self.pool['ir.ui.view'].search(cr, uid, [('key','=',view.key),('version_id', '=', False),('website_id', '=', view.website_id.id)],context=context)
if master_id:
#Delete all the website views having a key which is in the version published
del_l += master_id
copy_l+= master_id
else:
#Views that have no website_id, must be copied because they can be shared with another website
master_id = self.pool['ir.ui.view'].search(cr, uid, [('key','=',view.key),('version_id', '=', False),('website_id', '=', False)],context=context)
copy_l+= master_id
if copy_l:
if save_master:
#To check if the name of the version to copy master already exists
check_id = self.search(cr, uid, [('name','=', copy_master_name),('website_id', '=', version.website_id.id)],context=context)
#If it already exists, we delete the old to make the new
if check_id:
self.unlink(cr, uid, check_id, context=context)
copy_version_id = self.create(cr, uid, {'name' : copy_master_name, 'website_id' : version.website_id.id}, context=context)
for view in request.registry['ir.ui.view'].browse(cr, uid, copy_l, context=context):
view.copy({'version_id': copy_version_id, 'website_id' : version.website_id.id})
#Here, instead of deleting all the views we can just change the version_id BUT I've got some cache problems
self.pool['ir.ui.view'].unlink(cr, uid, del_l, context=context)
#All the views in the version published are copied without version_id
for view in self.browse(cr, uid, [int(version_id)],context).view_ids:
view.copy({'version_id': None}, context=context)
return version.name
+64
View File
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv,fields
from openerp.http import request
import random
import json
class NewWebsite(osv.Model):
_inherit = "website"
_columns = {
'tuto_sync': fields.boolean("How to get GA key and View ID"),
'google_analytics_view_id': fields.char('View ID'),
'google_management_authorization': fields.char('Google authorization')
}
def get_current_version(self,cr,uid,context=None):
snap = request.registry['website_version.version']
version_id = request.context.get('version_id')
if not version_id:
request.context['version_id'] = 0
return (0, '')
return snap.name_get(cr, uid, [version_id], context=context)[0];
def get_current_website(self, cr, uid, context=None):
website = super(NewWebsite,self).get_current_website(cr, uid, context=context)
#We just set the cookie for the first visit
if 'website_version_experiment' in request.httprequest.cookies:
EXP = json.loads(request.httprequest.cookies.get('website_version_experiment'))
else:
EXP = request.context.get('website_version_experiment', {})
exp_ids = self.pool["website_version.experiment"].search(cr, uid, [('state','=','running'),('website_id.id','=',website.id)], context=context)
exps = self.pool["website_version.experiment"].browse(cr, uid, exp_ids, context=context)
for exp in exps:
if not str(exp.google_id) in EXP:
result=[]
pond_sum=0
for exp_snap in exp.experiment_version_ids:
result.append([int(exp_snap.frequency)+pond_sum, exp_snap.version_id.id])
pond_sum+=int(exp_snap.frequency)
if pond_sum:
#by default master has a frequency of 50
pond_sum = pond_sum + 50
#by default on master
#We set the google_id as key in the cookie to avoid problem when reinitializating the db
EXP[str(exp.google_id)] = str(0)
x = random.randint(0,pond_sum-1)
for res in result:
if x<res[0]:
EXP[str(exp.google_id)] = str(res[1])
break
request.context['website_version_experiment'] = EXP
request.context['website_id'] = website.id
if 'version_id' in request.session:
request.context['version_id'] = request.session.get('version_id')
elif self.pool['res.users'].has_group(cr, uid, 'base.group_website_publisher'):
request.context['version_id'] = 0
else:
request.context['experiment_id'] = 1
return website
@@ -0,0 +1,6 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_website_version_version,access_website_version_version,model_website_version_version,,1,0,0,0
access_website_version_version_admin,access_website_version_version_admin,model_website_version_version,base.group_website_designer,1,1,1,1
access_website_version_experiment,access_website_version_experiment,model_website_version_experiment,,1,1,1,1
access_website_version_experiment_version,access_website_version_experiment_version,model_website_version_experiment_version,,1,1,1,1
access_website_version_goals,access_website_version_goals,model_website_version_goals,,1,1,1,1
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
2 access_website_version_version access_website_version_version model_website_version_version 1 0 0 0
3 access_website_version_version_admin access_website_version_version_admin model_website_version_version base.group_website_designer 1 1 1 1
4 access_website_version_experiment access_website_version_experiment model_website_version_experiment 1 1 1 1
5 access_website_version_experiment_version access_website_version_experiment_version model_website_version_experiment_version 1 1 1 1
6 access_website_version_goals access_website_version_goals model_website_version_goals 1 1 1 1
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,48 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Website Versioning</h2>
<h3 class="oe_slogan">Save all the versions of your website with Website Versioning</h3>
<div class="oe_span6">
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="presentation.png">
</div>
</div>
<div class="oe_span6">
<p class="oe_mt32">
Never loss any data on your website when you are editing pages. With Website Versioning you can save all the modification you realised and retrieve all of them through the versions.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row">
<div class="oe_span12">
<h2 class="oe_slogan">Features</h2>
<p class="oe_mt32">
Quickly create different versions of your website, organize all versions with date or name and update all your website in just one click.
</p>
</div>
<div class="oe_span4">
<h3>Save a modification as a draft version</h3>
<p>
People used to modify their website instinctively. Sometimes it is great but sometimes it is also bad. With Website Versioning, delete the bad and save the great through versions.
</p>
</div>
<div class="oe_span4">
<h3>Publish a draft version you like</h3>
<p>
When you decide that a version seems to be the good one to illustrate your website, you can simply publish this version in one click.
</p>
</div>
<div class="oe_span4">
<h3>Travel in the past</h3>
<p>
With Website Versioning, you can easily retrieve your old versions thanks to their name. Just choose the right one to begin the edition of a new great version.
</p>
</div>
</div>
</section>
<section class="oe_container oe_separator">
</section>
Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

@@ -0,0 +1,7 @@
@charset "utf-8";
img.analytics_img_tuto {
margin-left : 50px;
margin-bottom : 10px;
border: 2px solid grey;
}
@@ -0,0 +1,16 @@
.dropdown-menu .o_version > a:after {
margin-right: 10px;
}
.navbar-inverse .btn-link.o_version:hover {
color: #000;
}
.btn.o_version {
text-align: left;
}
.o_required_field{
background-color: #d2d2ff;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,82 @@
(function() {
'use strict';
var hash = "#advanced-view-editor";
var _t = openerp._t;
var website=openerp.website;
website.action= {};
website.EditorBar.include({
start: function() {
var self = this;
var m_names = new Array("jan", "feb", "mar",
"apr", "may", "jun", "jul", "aug", "sep",
"oct", "nov", "dec");
this.$el.on('click', '#save_as_new_version', function() {
var d = new Date();
var curr_date = d.getDate();
var curr_month = d.getMonth();
var curr_year = d.getFullYear();
website.prompt({
id: "editor_new_version",
window_title: _t("New version"),
input: "Version name" ,
default :(curr_date + " " + m_names[curr_month] + " " + curr_year),
}).then(function (name) {
var context = website.get_context();
openerp.jsonRpc( '/website_version/create_version', 'call', { 'name': name, 'version_id': 0 }).then(function (result) {
$('html').data('version_id', result);
var wizard = $(openerp.qweb.render("website_version.message",{message:_.str.sprintf("You are actually working on %s version.", name)}));
wizard.appendTo($('body')).modal({"keyboard" :true});
wizard.on('click','.o_confirm', function(){
self.save();
});
wizard.on('hidden.bs.modal', function () {$(this).remove();});
}).fail(function(){
var wizard = $(openerp.qweb.render("website_version.message",{message:_t("This name already exists.")}));
wizard.appendTo($('body')).modal({"keyboard" :true});
wizard.on('hidden.bs.modal', function () {$(this).remove();});
});
});
});
this.$el.on('click', '#save_and_publish', function() {
var version_id = $('html').data('version_id');
if(version_id)
{
self.save();
}
else
{
var wizard = $(openerp.qweb.render("website_version.delete_message",{message:_t("Are you sure you want to publish your modifications.")}));
wizard.appendTo($('body')).modal({"keyboard" :true});
wizard.on('click','.o_confirm', function(){
self.save();
});
wizard.on('hidden.bs.modal', function () {$(this).remove();});
}
});
$('.option_choice').click(function() {
self.$el.find(".o_second_choice").remove();
var name = $('#version-menu-button').data('version_name');
if(name){
self.$el.find(".o_first_choice").before(openerp.qweb.render("all_options", {version:'Save on '+name}));
}
else{
self.$el.find(".o_first_choice").before(openerp.qweb.render("all_options", {version:'Save and Publish'}));
}
});
return this._super();
}
});
})();
@@ -0,0 +1,287 @@
(function() {
'use strict';
var _t = openerp._t;
var website=openerp.website;
var QWeb = openerp.qweb;
website.add_template_file('/website_version/static/src/xml/version_templates.xml');
website.EditorVersion = openerp.Widget.extend({
start: function() {
var self = this;
$('html').data('version_id', this.$el.find("#version-menu-button").data("version_id"));
var _get_context = website.get_context;
website.get_context = function (dict) {
return _.extend({ 'version_id': $('html').data('version_id') }, _get_context(dict));
};
self.$el.on('click', 'a[data-action]', function(ev) {
ev.preventDefault();
self[$(this).data('action')](ev);
});
this.$el.find('#version-menu-button').click(function() {
var view_id = $('html').attr('data-view-xmlid');
openerp.jsonRpc( '/website_version/all_versions', 'call', { 'view_id': view_id }).then(function (result) {
self.$el.find(".o_version_choice").remove();
self.$el.find(".first_divider").before(QWeb.render("all_versions", {versions:result}));
});
openerp.jsonRpc( '/website_version/has_experiments', 'call', { 'view_id': view_id }).then(function (result) {
self.$el.find(".o_experiment").remove();
if(result){
self.$el.find(".create_experiment").after(QWeb.render("experiment_menu"));
}
});
});
return this._super();
},
duplicate_version: function(event) {
var version_id = $('html').data('version_id');
if(!version_id){
version_id = 0;
}
var m_names = new Array("jan", "feb", "mar",
"apr", "may", "jun", "jul", "aug", "sep",
"oct", "nov", "dec");
var d = new Date();
var curr_date = d.getDate();
var curr_month = d.getMonth();
var curr_year = d.getFullYear();
website.prompt({
id: "editor_new_version",
window_title: _t("New version"),
input: "Version name" ,
default :(curr_date + " " + m_names[curr_month] + " " + curr_year),
}).then(function (name) {
openerp.jsonRpc( '/website_version/create_version', 'call', { 'name': name, 'version_id': version_id}).then(function (result) {
var wizard = $(openerp.qweb.render("website_version.message",{message:_.str.sprintf("You are actually working on %s version.", name)}));
wizard.appendTo($('body')).modal({"keyboard" :true});
wizard.on('click','.o_confirm', function(){
window.location.href = '\?enable_editor';
});
wizard.on('hidden.bs.modal', function () {$(this).remove();});
}).fail(function(){
var wizard = $(openerp.qweb.render("website_version.message",{message:_t("This name already exists.")}));
wizard.appendTo($('body')).modal({"keyboard" :true});
wizard.on('hidden.bs.modal', function () {$(this).remove();});
});
});
},
change_version: function(event) {
var version_id = $(event.target).parent().data("version_id");
if(! version_id){
version_id = 0;//By default master
}
openerp.jsonRpc( '/website_version/change_version', 'call', { 'version_id':version_id }).then(function (result) {
location.reload();
});
},
delete_version: function(event) {
var version_id = $(event.currentTarget).parent().data("version_id");
var name = $(event.currentTarget).parent().children(':last-child').text();
openerp.jsonRpc( '/website_version/check_version', 'call', { 'version_id':version_id }).then(function (result) {
if (result){
var wizard = $(openerp.qweb.render("website_version.message",{message:_.str.sprintf("You cannot delete the %s version because it is in a running or paused experiment", name)}));
wizard.appendTo($('body')).modal({"keyboard" :true});
wizard.on('hidden.bs.modal', function () {$(this).remove();});
}
else{
var wizardA = $(openerp.qweb.render("website_version.delete_message",{message:_.str.sprintf("Are you sure you want to delete the %s version ?", name)}));
wizardA.appendTo($('body')).modal({"keyboard" :true});
wizardA.on('click','.o_confirm', function(){
openerp.jsonRpc( '/website_version/delete_version', 'call', { 'version_id':version_id }).then(function (result) {
var wizardB = $(openerp.qweb.render("website_version.message",{message:_.str.sprintf("The %s version has been deleted.", result)}));
wizardB.appendTo($('body')).modal({"keyboard" :true});
wizardB.on('click','.o_confirm', function(){
location.reload();
wizardB.on('hidden.bs.modal', function () {$(this).remove();});
});
});
});
wizardA.on('hidden.bs.modal', function () {$(this).remove();});
}
});
},
publish_version: function(event) {
var version_id = $('html').data('version_id');
var name = $(event.currentTarget).parent().parent().parent().children().children(':first-child').text();
openerp.jsonRpc( '/website_version/diff_version', 'call', { 'version_id':version_id}).then(function (result) {
var wizardA = $(openerp.qweb.render("website_version.publish_message",{message:_.str.sprintf("Are you sure you want to publish the %s version ?", name), list:result}));
wizardA.appendTo($('body')).modal({"keyboard" :true});
wizardA.on('click','.o_confirm', function(){
wizardA.find('.o_message').remove();
var check = wizardA.find('.o_check').is(':checked');
var copy_master_name = wizardA.find('.o_name').val();
if(check){
if(copy_master_name.length == 0){
wizardA.find(".o_name").after("<p class='o_message' style='color : red'> *"+_t("This field is required")+"</p>");
}
else{
openerp.jsonRpc( '/website_version/publish_version', 'call', { 'version_id':version_id, 'save_master':true, 'copy_master_name':copy_master_name}).then(function (result) {
var wizardB = $(openerp.qweb.render("website_version.dialogue",{message:_.str.sprintf("The %s version has been published", result), dialogue:_.str.sprintf("The master has been saved on a new version called %s.",copy_master_name)}));
wizardB.appendTo($('body')).modal({"keyboard" :true});
wizardB.on('click','.o_confirm', function(){
location.reload();
});
wizardB.on('hidden.bs.modal', function () {$(this).remove();});
});
}
}
else{
openerp.jsonRpc( '/website_version/publish_version', 'call', { 'version_id':version_id, 'save_master':false, 'copy_master_name':""}).then(function (result) {
var wizardC = $(openerp.qweb.render("website_version.message",{message:_.str.sprintf("The %s version has been published.", result)}));
wizardC.appendTo($('body')).modal({"keyboard" :true});
wizardC.on('click','.o_confirm', function(){
location.reload();
});
wizardC.on('hidden.bs.modal', function () {$(this).remove();});
});
}
});
wizardA.on('click','input[name="optionsRadios"]', function(){
wizardA.find('.o_message').remove();
wizardA.find('.o_name').toggle( wizardA.find('.o_check').is(':checked') );
});
wizardA.on('hidden.bs.modal', function () {$(this).remove();});
});
},
diff_version: function(event) {
var version_id = $('html').data('version_id');
var name = $('#version-menu-button').data('version_name');
openerp.jsonRpc( '/website_version/diff_version', 'call', { 'version_id':version_id}).then(function (result) {
var wizard = $(openerp.qweb.render("website_version.diff",{list:result, version_name:name}));
wizard.appendTo($('body')).modal({"keyboard" :true});
wizard.on('click','.o_confirm', function(){});
wizard.on('hidden.bs.modal', function () {$(this).remove();});
});
},
google_analytics: function(event){
window.location.href = 'https://www.google.com/analytics/web';
},
create_experiment: function() {
var self = this;
var view_id = $('html').attr('data-view-xmlid');
openerp.jsonRpc( '/website_version/all_versions_all_goals', 'call', { 'view_id': view_id }).then(function (result) {
var wizardA = $(openerp.qweb.render("website_version.create_experiment",{versions:result.tab_version, goals:result.tab_goal, config:result.check_conf}));
wizardA.appendTo($('body')).modal({"keyboard" :true});
wizardA.on('click','.o_launch', function(){
wizardA.find('.o_message').remove();
var name = wizardA.find('.o_name').val();
var tab = wizardA.find('.o_version');
var result = [];
var i;
for (var i = 0; i < tab.length; i++){
if ($(tab[i]).is(':checked')) {
result.push(parseInt($(tab[i]).attr('data-version_id')));
}
}
var objectives = wizardA.find('.box').val();
var check = true;
if (name.length == 0){
wizardA.find(".o_name").after("<p class='o_message' style='color : red'> *"+_t("This field is required")+"</p>");
check = false;
}
if (result.length == 0 && check){
wizardA.find(".o_versions").after("<p class='o_message' style='color : red'> *"+_t("You must select at least one version which is not Master.")+"</p>");
check = false;
}
if(check){
openerp.jsonRpc( '/website_version/launch_experiment', 'call', { 'name':name, 'version_ids':result, 'objectives':objectives }).then(function (result) {
if (result[0]){
var wizardB = $(openerp.qweb.render("website_version.dialogue",{message:_.str.sprintf("Your %s experiment is created.", name), dialogue:_t(" Now you can manage this experiment by clicking on Manage A/B tests.")}));
wizardB.appendTo($('body')).modal({"keyboard" :true});
wizardB.on('click','.o_confirm', function(){
location.reload();
});
wizardB.on('hidden.bs.modal', function () {$(this).remove();});
}
else{
wizardA.find(".o_versions").after("<p class='o_message' style='color : red'> *"+_.str.sprintf("Your %s experiment cannot be launched because this experiment contains a view which is already used in the running %s experiment.", name, result[1])+"</p>");
}
});
}
});
wizardA.on('click','.o_configure', function(){
var website_id = $('html').attr('data-website-id');
window.location.href ='/web#id='+website_id+'&view_type=form&model=website&action=website_version.action_website_view';
});
wizardA.on('click','.o_validate_0', function(){
var website_id = $('html').attr('data-website-id');
var ga_key = wizardA.find('.o_ga_key').val();
var view_id = wizardA.find('.o_view_id').val();
var client_id = wizardA.find('.o_client_id').val();
var client_secret = wizardA.find('.o_client_secret').val();
if(ga_key.length == 0 || view_id.length == 0 || client_id.length == 0 || client_secret.length == 0){
wizardA.find(".o_configure_ab").after("<p class='o_message' style='color : red'> *"+_t("You must fill all the fields.")+"</p>");
}
else{
openerp.jsonRpc( '/website_version/set_google_access', 'call', {'ga_key':ga_key, 'view_id':view_id, 'client_id':client_id, 'client_secret':client_secret}).then(function (result) {
var context = website.get_context();
openerp.jsonRpc( '/website_version/google_access', 'call', {
fromurl: window.location.href,
local_context: context
}).done(function(o) {
if (o.status === "need_auth") {
var wizardC = $(openerp.qweb.render("website_version.message",{message:_t("You will be redirected to Google to authorize access to your Analytics Account!")}));
wizardC.appendTo($('body')).modal({"keyboard" :true});
wizardC.on('click','.o_confirm', function(){
window.location.href = o.url;
});
wizardC.on('hidden.bs.modal', function () {$(this).remove();});
}
else if (o.status === "need_config_from_admin"){
if (confirm(_t("The Google Management API key needs to be configured before you can use it, do you want to do it now?"))) {
window.location.href = o.action;
}
}
}).always(function(o) { $('button.GoogleAccess').prop('disabled', false); });
});
}
});
wizardA.on('click','.o_validate_2', function(){
var website_id = $('html').attr('data-website-id');
var ga_key = wizardA.find('.o_ga_key').val();
var view_id = wizardA.find('.o_view_id').val();
if(ga_key.length == 0 || view_id.length == 0){
wizardA.find(".o_configure_ab").after("<p class='o_message' style='color : red'> *"+_t("You must fill all the fields.")+"</p>");
}
else{
openerp.jsonRpc( '/website_version/set_google_access', 'call', {'ga_key':ga_key, 'view_id':view_id, 'client_id':0, 'client_secret':0});
}
});
wizardA.on('hidden.bs.modal', function () {$(this).remove();});
});
},
manage_experiment: function() {
window.location.href = '/web#return_label=Website&action=website_version.action_experiment';
},
statistics: function() {
window.open('https://www.google.com/analytics/web/?authuser=0#report/siteopt-experiments/','_blank');
}
});
$(document).ready(function() {
var version = new website.EditorVersion();
version.setElement($("#version-menu"));
version.start();
});
})();
@@ -0,0 +1,212 @@
(function () {
'use strict';
var _t = openerp._t;
openerp.Tour.register({
id: 'versioning',
name: "Tutorial versioning",
path: '/',
mode: 'tutorial',
steps: [
//1.
{
title: _t("Welcome to the tutorial"),
content: _t("This tutorial will guide you to build a version of your home page."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
title: _t("Click on Version"),
content: _t("You can create a version for every page of your website."),
popover: { fixed: true },
element: 'a[id="version-menu-button"]:contains("Version"):first',
},
{
title: _t("Click on New version"),
popover: { fixed: true },
element: 'a[data-action="duplicate_version"]:contains("New Version"):first',
},
{
title: _t("Give a version name"),
content: _t("Give a clever name to retrieve it easily."),
popover: { fixed: true },
waitFor: '.modal button[type="button"]:contains("Continue")',
element: '.modal input[type="text"]',
sampleText: 'Test',
},
{
title: _t("Validate the version name"),
popover: { fixed: true },
element: '.modal button[type="button"]:contains("Continue")',
},
{
title: _t("Confirm"),
popover: { fixed: true },
waitNot: '.modal button[type="button"]:contains("Continue")',
element: '.modal button[type="button"]:contains("Ok")',
},
{
title: _t("You are on your new version"),
content: _t("All the modifications you will do, will be saved in this version."),
waitNot: '.modal button[type="button"]:contains("Ok")',
},
{
title: _t("Click on Edit"),
content: _t("Every page of your website can be modified through the <i>Edit</i> button."),
waitFor: 'button[data-action="edit"]:contains("Edit")',
element: 'button[data-action="edit"]:contains("Edit")',
},
{
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Insert building blocks"),
content: _t("Click here to insert blocks of content in the page."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:first',
placement: 'bottom',
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: '#wrapwrap .carousel:first div.carousel-content',
placement: 'top',
title: _t("Customize banner's text"),
content: _t("Click in the text and start editing it."),
sampleText: 'Here, a customized text',
popover: { next: _t("Continue") },
},
{
waitNot: '#wrap .carousel:first div.carousel-content:has(h2:'+
'containsExact('+_t('Your Banner Title')+')):has(h3:'+
'containsExact('+_t('Click to customize this text')+'))',
element: '.oe_snippet_parent:visible',
placement: 'bottom',
title: _t("Get banner properties"),
content: _t("Select the parent container to get the global options of the banner."),
popover: { fixed: true },
},
{
element: '.oe_overlay_options .oe_options:visible',
placement: 'left',
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: _t("Add Another Block"),
content: _t("Let's add another building block to your page."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(6)',
placement: 'bottom',
title: _t("Drag & Drop This Block"),
content: _t("Drag the <em>'Features'</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: _t("Save your modifications"),
content: _t("Publish your page by clicking on the <em>'Save'</em> button."),
popover: { fixed: true },
},
{
waitFor: 'button[data-action=save]:not(:visible)',
title: _t("Good Job!"),
content: _t("Well done, you created a version of your homepage."),
popover: { next: _t("Continue") },
},
//2.
{
title: _t("Publish the version"),
content: _t("Now we will publish your version in production."),
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
title: _t("Click on Version"),
element: 'a[id="version-menu-button"]:contains("Version"):first',
popover: { fixed: true },
},
{
title: _t("Click on Publish Version"),
element: 'a[data-action="publish_version"]:contains("Publish Version"):first',
popover: { fixed: true },
},
{
title: _t("Click on Publish button"),
//waitFor: '.modal button[type="button"]:contains("Publish")',
element: '.modal button[type="button"]:contains("Publish")',
placement: 'left',
},
{
title: _t("Confirm"),
waitFor: '.modal button[type="button"]:contains("Ok")',
element: '.modal button[type="button"]:contains("Ok")',
popover: { fixed: true },
},
//3.
{
title: _t("Delete the version"),
content: _t("Now we will delete the version you have just published."),
waitNot: '.modal button[type="button"]:contains("Ok")',
popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
},
{
title: _t("Click on Version"),
element: 'a[id="version-menu-button"]:contains("Version"):first',
popover: { fixed: true },
},
{
title: _t("Delete Version Test"),
element: 'li > a[data-action="delete_version"]:last',
popover: { fixed: true },
},
{
title: _t("Click on delete version button"),
waitFor: '.modal button[type="button"]:contains("Delete Version")',
element: '.modal button[type="button"]:contains("Delete Version")',
popover: { fixed: true },
},
{
title: _t("Confirm"),
waitFor: '.modal button[type="button"]:contains("Ok")',
element: '.modal button[type="button"]:contains("Ok")',
popover: { fixed: true },
},
{
title: _t("Finish"),
content: _t("Felicitation, now you are able to edit and manage your versions."),
waitNot: '.modal button[type="button"]:contains("Ok")',
},
]
});
}());
@@ -0,0 +1,45 @@
(function() {
"use strict";
var instance = openerp;
openerp.website_version = instance.website_version || {};
var module = instance.website_version;
var QWeb = instance.web.qweb;
var _t = openerp._t;
instance.website_version.rtoken = instance.web.form.AbstractField.extend({
template: 'website_version.GoogleAccess',
start: function() {
var self = this;
this.$el.on('click', 'button.GoogleAccess', function() {
self.allow_google();
});
},
allow_google: function() {
var self = this;
$('button.GoogleAccess').prop('disabled', true);
var context = instance.web.pyeval.eval('context');
self.rpc('/website_version/google_access', {
fromurl: window.location.href,
local_context: context
}).done(function(o) {
if (o.status === "need_auth") {
alert(_t("You will be redirected to Google to authorize access to your Analytics Account!"));
instance.web.redirect(o.url);
}
else if (o.status === "need_config_from_admin"){
if (confirm(_t("The Google Management API key needs to be configured before you can use it, do you want to do it now?"))) {
self.do_action(o.action);
}
}
}).always(function(o) { $('button.GoogleAccess').prop('disabled', false); });
}
});
instance.web.form.widgets.add('rtoken', 'openerp.website_version.rtoken');
})();
@@ -0,0 +1,314 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="all_versions">
<t t-foreach="versions" t-as="item">
<li class="o_version_choice" t-att-data-version_id="item.id">
<a href="#" data-action="delete_version" style="position: absolute; right: 0px;" class="pull-right"><i class="fa fa-trash-o fa-fw"></i></a>
<t t-if="item.bold == 0 ">
<a href="#" data-action="change_version" t-att-data-version_id="item.id"><t t-esc="item.name"></t> </a>
</t>
<t t-if="item.bold == 1 ">
<a href="#" data-action="change_version" t-att-data-version_id="item.id"> <b><t t-esc="item.name"></t></b> </a>
</t>
</li>
</t>
</t>
<t t-extend="website.editorbar">
<t t-jquery=".navbar-header form button" t-operation="replace">
<div class="btn-group">
<button type="button" data-action="save" class="btn btn-primary btn-xs">Save</button>
<button type="button" class="btn btn-primary btn-xs dropdown-toggle option_choice" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li class="o_first_choice"><a href="#" id="save_as_new_version" data-action="save_as_new_version"
class="btn btn-link btn-xs o_version">Save as New Version</a>
</li>
</ul>
</div>
</t>
</t>
<t t-name="all_options">
<li class="o_second_choice">
<a href="#" id="save_and_publish" data-action="save_and_publish"
class="btn btn-link btn-xs o_version"><t t-esc="version"></t></a>
</li>
</t>
<t t-name="experiment_menu">
<li class="o_experiment"><a href="#" data-action="manage_experiment">Manage A/B tests</a></li>
<li class="o_experiment"><a href="#" data-action="statistics">Statistics</a></li>
</t>
<t t-name="website_version.GoogleAccess">
<div id="CheckGoogleAccess" class="odoo_google_token" >
<button class="GoogleAccess"> Authorize google </button>
</div>
</t>
<t t-name="website_version.create_experiment">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Launch A/B tests</h4>
</div>
<div class="modal-body">
<t t-if="versions.length != 0 and config == 1">
<form id="productForm" class="form-horizontal" role="form">
<div class="form-group form-group">
<label class="col-sm-3 control-label">Experiment Name</label>
<div class="col-sm-8">
<input type="text" class="form-control form-field-label o_name o_required_field"/>
</div>
</div>
<div class="form-group form-group">
<label class="col-sm-3 control-label">Objective: </label>
<div class="col-sm-8 ">
<select class="form-control col-sm-2 box">
<t t-foreach="goals" t-as="item">
<option t-att-value="item.id"><t t-esc="item.name"></t></option>
</t>
</select>
</div>
</div>
<div class="form-group form-group">
<div class="col-sm-3"></div>
<div class="checkbox col-sm-8">
<label>
<input type="checkbox" checked="checked" disabled="disabled"/> Master
</label>
</div>
</div>
<div class="o_versions">
<t t-foreach="versions" t-as="item">
<div class="form-group form-group-lg">
<div class="col-sm-3"></div>
<div class="checkbox col-sm-8">
<label>
<input type="checkbox" class="o_version" t-att-data-version_id="item.id"/> <t t-esc="item.name"></t>
</label>
</div>
</div>
</t>
</div>
</form>
</t>
<t t-if="versions.length == 0 and config == 1">
<div>
<p>You must have at least one version for this page to create a new experiment.</p>
</div>
</t>
<t t-if= "config == 0">
<form id="productForm" class="form-horizontal" role="form">
<div class="form-group form-group">
<label class="col-sm-3 control-label">GA Key</label>
<div class="col-sm-8">
<input type="text" class="form-control form-field-label o_ga_key o_required_field"/>
</div>
</div>
<div class="form-group form-group">
<label class="col-sm-3 control-label">View ID</label>
<div class="col-sm-8">
<input type="text" class="form-control form-field-label o_view_id o_required_field"/>
</div>
</div>
<div class="form-group form-group">
<label class="col-sm-3 control-label">Client ID</label>
<div class="col-sm-8">
<input type="text" class="form-control form-field-label o_client_id o_required_field"/>
</div>
</div>
<div class="form-group form-group">
<label class="col-sm-3 control-label">Client Secret</label>
<div class="col-sm-8">
<input type="text" class="form-control form-field-label o_client_secret o_required_field"/>
</div>
</div>
</form>
<div class="o_configure_ab">
<p>You must configure your account.</p>
<p>To configure your account, you must have a google analytics account.</p>
</div>
</t>
<t t-if= "config == 2">
<form id="productForm" class="form-horizontal" role="form">
<div class="form-group form-group">
<label class="col-sm-3 control-label">GA Key</label>
<div class="col-sm-8">
<input type="text" class="form-control form-field-label o_ga_key o_required_field"/>
</div>
</div>
<div class="form-group form-group">
<label class="col-sm-3 control-label">View ID</label>
<div class="col-sm-8">
<input type="text" class="form-control form-field-label o_view_id o_required_field"/>
</div>
</div>
</form>
<div class="o_configure_ab">
<p>You must configure your account.</p>
<p>To configure your account, you must have a google analytics account.</p>
</div>
</t>
</div>
<div class="modal-footer">
<t t-if="versions.length != 0 and config == 1">
<div class="btn-group">
<button type="button" class="btn btn-primary o_launch">Launch</button>
</div>
</t>
<t t-if= "config == 0 or config == 2">
<div class="btn-group">
<t t-if= "config == 0">
<button type="button" class="btn btn-primary o_validate_0">Validate</button>
</t>
<t t-if= "config == 2">
<button type="button" class="btn btn-primary o_validate_2" data-dismiss="modal">Validate</button>
</t>
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="#" class="btn btn-link btn-xs o_configure">Configure</a></li>
</ul>
</div>
</t>
<a href="#" class="btn btn-link btn-xs" data-dismiss="modal">Cancel</a>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</t>
<t t-name="website_version.message">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"><t t-esc="message"></t></h4>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary o_confirm" data-dismiss="modal">Ok</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</t>
<t t-name="website_version.dialogue">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"><t t-esc="message"></t></h4>
</div>
<div class="modal-body">
<p><t t-esc="dialogue"></t></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary o_confirm" data-dismiss="modal">Ok</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</t>
<t t-name="website_version.diff">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Diff between Master and <t t-esc="version_name"></t></h4>
</div>
<div class="modal-body">
<t t-if="list.length != 0">
<ul>
<t t-foreach="list" t-as="item">
<li><t t-esc="item"></t> </li>
</t>
</ul>
</t>
<t t-if="list.length == 0">
<p>There is no difference with Master</p>
</t>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary o_confirm" data-dismiss="modal">Ok</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</t>
<t t-name="website_version.delete_message">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"><t t-esc="message"></t></h4>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary o_confirm" data-dismiss="modal">Delete Version</button>
<a href="#" class="btn btn-link btn-xs cancel" data-dismiss="modal">Cancel</a>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</t>
<t t-name="website_version.publish_message">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"><t t-esc="message"></t></h4>
</div>
<div class="modal-body">
<p>When you publish a version, you erase some views in master. If you want to save the views that will be erased, you can save these views in a new version. Here is the list of master views which will be erased:</p>
<t t-if="list.length != 0">
<ul>
<t t-foreach="list" t-as="item">
<li><t t-esc="item"></t> </li>
</t>
</ul>
</t>
<t t-if="list.length == 0">
<p>There is no difference with Master</p>
</t>
<form id="productForm" class="form-horizontal" role="form">
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="optionsRadios1" value="option1" checked="checked"/>
Don't save Master
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="optionsRadios2" value="option2" class="o_check"/>
Save Master
<input type="text" class="form-control o_name o_required_field" style="display: none;"/>
</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary o_confirm">Publish</button>
<a href="#" class="btn btn-link btn-xs" data-dismiss="modal">Cancel</a>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</t>
</templates>
+7
View File
@@ -0,0 +1,7 @@
import test_ui
from . import test_website_version_all
checks = [
test_website_version_all,
]
+8
View File
@@ -0,0 +1,8 @@
import openerp.tests
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase):
def test_01_versioning(self):
self.phantom_js("/", "openerp.Tour.run('versioning', 'test')", "openerp.Tour.tours.versioning", login='admin')
@@ -0,0 +1,17 @@
from . import test_website_version_base
from openerp.osv.orm import except_orm
from openerp.http import request
class TestWebsiteVersionAll(test_website_version_base.TestWebsiteVersionBase):
def test_copy_version(self):
""" Testing version_copy"""
cr, uid, view_0_0_0_0_id, version_id, website_id = self.cr, self.uid, self.view_0_0_0_0_id, self.version_id, self.website_id
copy_version_id = self.version.create(cr, uid,{'name':'copy_version_0_0_0_0', 'website_id':website_id}, context=None)
self.ir_ui_view.copy_version(cr, uid, version_id,copy_version_id,context=None)
copy_version = self.version.browse(cr, uid, [copy_version_id], context=None)[0]
view_copy_version=copy_version.view_ids[0]
view_0_0_0_0 = self.ir_ui_view.browse(cr, uid, [view_0_0_0_0_id], context={'version_id':version_id})[0]
self.assertEqual(view_copy_version.arch, view_0_0_0_0.arch, 'website_version: copy_version: website_version must have in snpashot_copy the same views then in version_0_0_0_0')
@@ -0,0 +1,60 @@
from openerp.tests import common
class TestWebsiteVersionBase(common.TransactionCase):
def setUp(self):
super(TestWebsiteVersionBase, self).setUp()
cr, uid = self.cr, self.uid
# Usefull models
self.ir_ui_view = self.registry('ir.ui.view')
self.ir_qweb = self.registry('website.qweb')
self.version = self.registry('website_version.version')
self.website = self.registry('website')
#Usefull objects
master_view_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'website', 'website2_homepage')
self.master_view_id = master_view_ref and master_view_ref[1] or False
self.arch_master=self.ir_ui_view.browse(cr, uid, [self.master_view_id], context=None)[0].arch
version_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'website_version', 'version_0_0_0_0')
self.version_id = version_ref and version_ref[1] or False
website_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'website', 'website2')
self.website_id = website_ref and website_ref[1] or False
view_0_0_0_0_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'website_version', 'website2_homepage_other')
self.view_0_0_0_0_id = view_0_0_0_0_ref and view_0_0_0_0_ref[1] or False
self.arch_0_0_0_0 = self.ir_ui_view.browse(cr, uid, [self.view_0_0_0_0_id], context=None)[0].arch
self.vals = {'arch':
"""<t name="Homepage" priority="29" t-name="website.homepage">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty">
<div class="carousel slide mb32" id="myCarousel0" style="height: 320px;">
<ol class="carousel-indicators hidden">
<li class="active" data-slide-to="0" data-target="#myCarousel0"/>
</ol>
<div class="carousel-inner">
<div class="item image_text oe_img_bg active" style="background-image: url(http://0.0.0.0:8069/website/static/src/img/banner/mountains.jpg);">
<div class="container">
<div class="row content">
<div class="carousel-content col-md-6 col-sm-12">
<h2>version 0.0.0.0</h2>
<h3>Click to customize this text</h3>
<p>
<a class="btn btn-success btn-large" href="/page/website.contactus">Contact us</a>
</p>
</div>
<span class="carousel-img col-md-6 hidden-sm hidden-xs"> </span>
</div>
</div>
</div>
</div>
<div class="carousel-control left hidden" data-slide="prev" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-left"/>
</div>
<div class="carousel-control right hidden" data-slide="next" data-target="#myCarousel0" href="#myCarousel0" style="width: 10%">
<i class="fa fa-chevron-right"/>
</div>
</div>
</div>
</t>
</t>"""
}
@@ -0,0 +1,234 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="action_experiment" model="ir.actions.act_window">
<field name="name">Experiments</field>
<field name="res_model">website_version.experiment</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context">{}</field>
</record>
<record model="ir.ui.view" id="view_experiment_kanban">
<field name="name">Experiment Kanban</field>
<field name="model">website_version.experiment</field>
<field name="arch" type="xml">
<kanban default_group_by="state" string="Experiments" class="oe_background_grey">
<field name="sequence" widget="handle" />
<field name="name"/>
<field name="website_id"/>
<field name="experiment_version_ids"/>
<field name="color"/>
<field name="message_follower_ids"/>
<templates>
<field name="state"/>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_global_click_edit oe_semantic_html_override oe_kanban_card">
<div class="oe_dropdown_kanban">
<!-- dropdown menu -->
<span class="oe_dropdown_toggle">
<span class="oe_e">í</span>
<ul class="oe_dropdown_menu">
<li><a type="delete">Delete</a></li>
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>
</span>
</div>
<!-- kanban note -->
<div class="oe_kanban_content">
<!-- title -->
<field name="name"/> on <field name="website_id"/>
</div>
<div class="oe_clear"></div>
<div> <field name="version_number"/> versions </div>
<div> Creation :<field name="create_date" /> </div>
<div class="oe_right">
<t t-foreach="record.message_follower_ids.raw_value" t-as="follower">
<img t-att-src="kanban_image('res.partner', 'image_small', follower)" width="24" height="24" class="oe_kanban_avatar" t-att-data-member_id="follower"/>
</t>
</div>
<div class="oe_clear"></div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="action_analytic" model="ir.actions.act_url">
<field name="name">Google analytics</field>
<field name="url">https://www.google.com/analytics/web/?authuser=0#report/siteopt-experiments/</field>
<field name="target">self</field>
</record>
<record model="ir.ui.view" id="experiment_form_view">
<field name="name">experiment.form</field>
<field name="model">website_version.experiment</field>
<field name="arch" type="xml">
<form string="Experiment Form">
<header>
<field name="state" widget="statusbar" statusbar_visible="running,paused,ended" clickable="True"/>
</header>
<sheet string="Exp">
<button icon="fa-line-chart" name="website_version.action_analytic" String="Statistics" action="action"
type="action" class="oe_stat_button pull-right" />
<div class="oe_title">
<field name="google_id" invisible="1"/>
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" attrs="{'readonly' : [('google_id', '!=', False)] }"/>
</h1>
</div>
<group col="4">
<field name="website_id" attrs="{'readonly' : [('google_id', '!=', False)] }"/>
<field name="google_id" readonly="1" groups="base.group_no_one"/>
<label for="objectives"/>
<div>
<field name="objectives" class="oe_inline" widget="selection" attrs="{'readonly' : [('google_id', '!=', False)] }"/>
<button name="update_goals" string="(Sync)" type="object" context="{'website_id': website_id}" class="oe_link oe_edit_only"/>
</div>
</group>
<notebook colspan="4">
<page string="Versions in experiment" colspan="4" >
<field name="experiment_version_ids" colspan="4" context="{'website_id': website_id,
'google_id': google_id}" attrs="{'readonly' : [('google_id', '!=', False)] }">
<tree string="PPP" editable="bottom">
<field name="version_id" string="Versions" domain="[('website_id','=',context.get('website_id'))]" colspan="4"/>
<field name="frequency" colspan="4"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="experiment_tree_view">
<field name="name">experiment.tree</field>
<field name="model">website_version.experiment</field>
<field name="arch" type="xml">
<tree string="Experiment tree">
<field name="name" />
<field name="website_id"/>
<field name="state"/>
<field name="sequence" widget="handle"/>
</tree>
</field>
</record>
<record id="experiment_search" model="ir.ui.view">
<field name="name">experiment.search</field>
<field name="model">website_version.experiment</field>
<field name="arch" type="xml">
<search string="Search Experiment">
<field name="website_id"/>
<field name="state"/>
<group string="Group By">
<filter string="Website" domain="[]" name="my_exps" context="{'group_by':'website_id'}"/>
<filter string="State" domain="[]" context="{'group_by':'state'}"/>
</group>
</search>
</field>
</record>
<!-- <record id="action_experiment_search" model="ir.actions.act_window">
<field name="name">Experiment search</field>
<field name="res_model">website_version.experiment</field>
<field name="view_mode">list</field>
<field name="context">{'search_default_my_exps':1}</field>
</record> -->
<record model="ir.ui.view" id="experiment_version_form_view">
<field name="name">experiment_version.form</field>
<field name="model">website_version.experiment_version</field>
<field name="arch" type="xml">
<form string="Experiment_page Form">
<notebook colspan="4">
<page string="Features" colspan="4" >
<field name="version_id" domain="[('website_id','=',context.get('website_id'))]" colspan="4"/>
<field name="frequency"/>
</page>
</notebook>
</form>
</field>
</record>
<record id="action_version" model="ir.actions.act_window">
<field name="name">Versions</field>
<field name="res_model">website_version.version</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<record model="ir.ui.view" id="version_form_view">
<field name="name">version.form</field>
<field name="model">website_version.version</field>
<field name="arch" type="xml">
<form string="version Form">
<sheet string="Snap">
<group>
<button icon="fa-pencil" name="action_publish" string="Publish" type="object" class="oe_stat_button pull-right"
context="{'active_id': active_id}"/>
</group>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
</div>
<group col="4">
<field name="website_id"/>
</group>
<group colspan="4">
<field name="view_ids" widget="handle" colspan="4"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="version_tree_view">
<field name="name">version.tree</field>
<field name="model">website_version.version</field>
<field name="arch" type="xml">
<tree string="version tree">
<field name="name" />
<field name="website_id" />
<field name="create_date" />
</tree>
</field>
</record>
<record id="version_search" model="ir.ui.view">
<field name="name">version.search</field>
<field name="model">website_version.version</field>
<field name="arch" type="xml">
<search string="Search version">
<field name="website_id"/>
<group string="Group By">
<filter string="Website" domain="[]" context="{'group_by':'website_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_website_view" model="ir.actions.act_window">
<field name="name">Website Domain</field>
<field name="res_model">website</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem name="Website" id="website_menu" sequence="10" parent="base.marketing_menu" />
<menuitem action="action_website_view" id="menu_website_view" parent="website_menu"/>
<menuitem action="action_version" id="menu_action_version" parent="website_menu" groups="base.group_no_one"/>
<menuitem action="action_experiment" id="menu_action_experiment" parent="website_menu"/>
</data>
</openerp>
+112
View File
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="view_website_config_settings_extend" model="ir.ui.view">
<field name="name">Website settings</field>
<field name="model">website.config.settings</field>
<field name="inherit_id" ref="website.view_website_config_settings"/>
<field name="arch" type="xml">
<field name="website_id" position="replace">
<field name="website_id" invisible="False" on_change="on_change_website_id(website_id)"/>
</field>
</field>
</record>
<record id="view_analytics_config_settings" model="ir.ui.view">
<field name="name">Analytics_settings</field>
<field name="model">base.config.settings</field>
<field name="inherit_id" ref="base_setup.view_general_configuration"/>
<field name="arch" type="xml">
<div name="google_analytics" position="replace">
<div>
<div>
<label for="ga_sync"/>
<field name="ga_sync" class="oe_inline"/>
<div>
<div attrs="{'invisible':[('ga_sync','=',False)]}">
<br/><h2>To setup the signin process with Google, first you have to perform the following steps</h2>
<ul>
<li> Connect on your google account and go to <a href='https://console.developers.google.com' target='_blank'>https://console.developers.google.com</a> </li>
<li>
Click on <b>"Create Project"</b> and enter a project name.
<br/><br/><img src='/website_version/static/src/img/GC/GC_create_project.png'
class='analytics_img_tuto'/>
<br/><br/><img src='/website_version/static/src/img/GC/GC_new_project.png'
class='analytics_img_tuto'/>
</li>
<li> In the menu on left side, select the sub menu APIs (from menu APIs and auth) and activate <b>"Analytics API"</b> by clicking on button "OFF".
<br/> When it's done, check that the button of <b>"Calendar API"</b> is well in green and with text "ON"
<br/><br/><img src='/website_version/static/src/img/GC/GC_API.png'
class='analytics_img_tuto'/>
<br/><br/><img src='/website_version/static/src/img/GC/GC_off_api.png'
class='analytics_img_tuto'/>
<br/><br/><img src='/website_version/static/src/img/GC/GC_on_api.png'
class='analytics_img_tuto'/>
</li>
<li>
In the menu on left side, select the sub menu <b>'Credentials'</b> (from menu APIs and auth) and click on button <b>'Create New Client ID'</b>
<br/><br/><img src='/website_version/static/src/img/GC/GC_credentials.png'
class='analytics_img_tuto'/>
<br/><br/><img src='/website_version/static/src/img/GC/GC_create_new_client_id.png'
class='analytics_img_tuto'/>
</li>
<li> Fill in the Name of application and check that the platform is well on <b>'Web Application'</b>.<br/>
<br/> You should now configure the allowed pages on which you will be redirected. To do it, you need to complete the field <b>"Redirect RI"</b>
and set as value (your own domain followed by <i>"/google_account/authentication"</i>) :
<br/> You can now click on <b>'Create Client ID'</b>
<br/><br/><img src='/website_version/static/src/img/GC/GC_client_id.png'
class='analytics_img_tuto'/>
</li>
<li>Once done, you will have the both informations (<b>Client ID</b> and <b>Client Secret</b>) that you need to insert in the 2 fields below !
<br/><br/><img src='/website_version/static/src/img/GC/GC_copy.png'
class='analytics_img_tuto'/>
</li>
<li>Click on <b>Apply</b>
<br/><br/><img src='/website_version/static/src/img/GC/GC_Apply.png'
class='analytics_img_tuto'/>
</li>
<a href="#" class="oe_link">Return at Top</a>
</ul>
</div>
</div>
</div>
<group>
<div class="analytics_settings">
<group>
<div>
<label for="ga_client_id" string="Google Client ID" />
<field name="ga_client_id" class="oe_inline"/>
</div>
</group>
<group>
<div>
<label for="ga_client_secret" string="Google Client Secret"/>
<field name="ga_client_secret" class="oe_inline"/>
</div>
</group>
<group>
<div>
<field name="google_management_authorization" widget="rtoken"/>
</div>
</group>
</div>
</group>
</div>
</div>
</field>
</record>
<record id="action_config_settings_google_management" model="ir.actions.act_window">
<field name="name">API Management Configuration</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">base.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
</record>
</data>
</openerp>
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Front-end/Back-end integration -->
<template id="user_navbar_version" inherit_id="website.layout" groups="base.group_user">
<xpath expr="//li[contains(@id, 'customize-menu')]" position="before">
<li class="dropdown" id="version-menu" groups="base.group_website_designer">
<a id="version-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#" t-att-data-version_id="website.get_current_version(context=context)[0]" t-att-data-version_name="website.get_current_version(context=context)[1]">
Version
<span><t t-esc="website.get_current_version(context=context)[1]"></t></span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu version_menu" role="menu">
<li class="dropdown-header" style="text-transform: uppercase;">All versions</li>
<t t-if="website.get_current_version(context=context)[0] == 0 ">
<li><a href="#" data-action="change_version"><b>Master</b></a></li>
</t>
<t t-if="website.get_current_version(context=context)[0] != 0 ">
<li><a href="#" data-action="change_version">Master</a></li>
</t>
<li class="first_divider divider"> </li>
<t t-if="website.get_current_version(context=context)[0] == 0 ">
<li><a href="#" data-action="duplicate_version">New Version</a></li>
</t>
<t t-if="website.get_current_version(context=context)[0] != 0 ">
<li><a href="#" data-action="duplicate_version">Duplicate Version</a></li>
<li><a href="#" data-action="publish_version">Publish Version</a></li>
<li><a href="#" data-action="diff_version" groups="base.group_no_one">Diff with Master</a></li>
</t>
<li class="divider o_publish_version"> </li>
<li class="create_experiment"><a href="#" data-action="create_experiment">Launch A/B tests</a></li>
</ul>
</li>
</xpath>
</template>
<template id="assets_frontend_menu_version" name="website_menu_version assets" inherit_id="website.assets_editor">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_version/static/src/js/menu_version.js"></script>
<script type="text/javascript" src="/website_version/static/src/js/edit_version.js"></script>
<script type="text/javascript" src="/website_version/static/src/js/website_version.tour.versioning.js"></script>
<link rel="stylesheet" href="/website_version/static/src/css/menu_version.css"/>
</xpath>
</template>
<template id="assets_frontend_AB" name="website_AB assets" inherit_id="website.layout">
<xpath expr="//script[contains(@id, 'tracking_code')]" position="replace">
<script id='tracking_code'>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', _.str.trim('<t t-esc="website.google_analytics_key"/>'), {'cookieDomain': 'none'});
ga('send','pageview');
edit = $('html').attr('data-editable');
if (edit != "1"){
var view_id = $('html').attr('data-view-xmlid');
openerp.jsonRpc( '/website_version/get_analytics', 'call', { 'view_id':view_id }).then(function (result) {
var exp = result['ExpId'];
var ver = result['VarId'];
ga('set', 'expId', exp);
ga('set', 'expVar', ver);
ga('send', 'pageview');
});
}
</script>
</xpath>
</template>
<template id="assets_backend" name="stock assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/website_version/static/src/css/google_analytics.css"/>
<script type="text/javascript" src="/website_version/static/src/js/widget.js"></script>
</xpath>
</template>
</data>
</openerp>
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_website_form_extend">
<field name="name">website.form</field>
<field name="model">website</field>
<field name="inherit_id" ref="website.view_website_form"/>
<field name="priority">1</field>
<field name="arch" type="xml">
<!-- find field name, and insert field name before it -->
<field name="name" position="replace">
</field>
<field name="google_analytics_key" position="before">
<field name="tuto_sync"/>
<div colspan="4">
<div attrs="{'invisible':[('tuto_sync','=',False)]}">
<br/><h2>How to get the Google Analytics Key and the View ID</h2>
<ul>
<li> Go to <a href='https://www.google.com/analytics/' target='_blank'>https://www.google.com/analytics/</a>
and connect you.
<br/><br/><img src='/website_version/static/src/img/GA/GA_Login.png' class='analytics_img_tuto'/>
</li>
<li>
Click on <b>"Admin"</b>.
<br/><br/><img src='/website_version/static/src/img/GA/GA_admin.png' class='analytics_img_tuto'/>
</li>
<li>
Click on <b>"Tracking Code"</b>.
<br/><br/><img src='/website_version/static/src/img/GA/GA_tracking_code.png' class='analytics_img_tuto'/>
</li>
<li>
Copy the <b>"Tracking ID"</b> and paste it in the field Google Analytics key.
<br/><br/><img src='/website_version/static/src/img/GA/GA_tracking_id.png' class='analytics_img_tuto'/>
</li>
<li>
Click on <b>"Admin"</b> and click on <b>"View Settings"</b>.
<br/><br/><img src='/website_version/static/src/img/GA/GA_view_settings.png' class='analytics_img_tuto'/>
</li>
<li>
Copy the <b>"View ID"</b> and paste it in the field View ID.
<br/><br/><img src='/website_version/static/src/img/GA/GA_view_id.png' class='analytics_img_tuto'/>
</li>
<li>
Click on Save.
<br/><br/><img src='/website_version/static/src/img/GA/GA_Save.png' class='analytics_img_tuto' />
</li>
<li>
Click on Authorize google.
<br/><br/><img src='/website_version/static/src/img/GA/GA_authorize.png' class='analytics_img_tuto' />
</li>
<a href="#" class="oe_link">Return at Top</a>
</ul>
</div>
</div>
</field>
<field name="google_analytics_key" position="after">
<field name="google_analytics_view_id" placeholder="7654321"/>
<field name="google_management_authorization" widget="rtoken"/>
</field>
<field name="company_id" position="after">
<field name="name" class="oe_inline"/>
<field name="company_id"/>
<field name="user_id" context="{'active_test': False}"/>
</field>
<separator string="Domain" position="replace">
<separator string="Statistics"/>
</separator>
</field>
</record>
<record id="action_website_view" model="ir.actions.act_window">
<field name="name">Websites</field>
<field name="res_model">website</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem action="action_website_view" id="menu_website_view" parent="base.menu_res_company_global"/>
<record model="ir.ui.view" id="view_view_form_extend">
<field name="model">ir.ui.view</field>
<field name="inherit_id" ref="base.view_view_form"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="version_id"/>
</field>
<group position="before">
<button icon="fa-pencil" name="action_publish" string="Publish" type="object" class="oe_stat_button pull-right"
context="{'active_id': active_id}" attrs="{'invisible': ['|','|',('type', '!=', 'qweb'),('website_id', '=',False), ('version_id', '=', False)]}" />
</group>
</field>
</record>
</data>
</openerp>
+2
View File
@@ -193,6 +193,8 @@
<rng:optional><rng:attribute name="forcecreate"/></rng:optional>
<rng:optional><rng:attribute name="context"/></rng:optional>
<rng:optional><rng:attribute name="priority"/></rng:optional>
<rng:optional><rng:attribute name="key"/></rng:optional>
<rng:optional><rng:attribute name="website_id"/></rng:optional>
<rng:choice>
<rng:group>
<rng:optional>
+4
View File
@@ -751,6 +751,10 @@ form: module.record_id""" % (xml_id,)
record.append(Field(el.get('priority', "16"), name='priority'))
if 'inherit_id' in el.attrib:
record.append(Field(name='inherit_id', ref=el.get('inherit_id')))
if 'website_id' in el.attrib:
record.append(Field(name='website_id', ref=el.get('website_id')))
if 'key' in el.attrib:
record.append(Field(el.get('key'), name='key'))
if el.get('active') in ("True", "False"):
view_id = self.id_get(cr, tpl_id, raise_if_not_found=False)
if mode != "update" or not view_id: