From e33172e83210a5e0044a6fb92e315b84deefc440 Mon Sep 17 00:00:00 2001 From: fja-odoo Date: Wed, 21 Aug 2019 16:40:10 +0000 Subject: [PATCH] [IMP] website_sale, *: keep track of products viewed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *= website, website_livechat, website_rating ///// Tracking Product ///// Now when a user browse products in eCommerce, we keep track of the products he looked at. We use the website_visitor to store the products viewed. A cookie is added with a TTl of 30 min it will prevent the RPC for that time. We track the page only if the product view is tracked. The recently viewed products are displayed as a snippet but also with the customize option in product pages of website_sale. Products that are in cart will not be returned as recently viewed. It is possible to add a recently viewed product to the cart directly from the carousel, it will not redirect to the cart. If we are on the cart page, the product is displayed in the cart. The Visitor page in website now references products viewed ///// Tracking Page ///// Feature to track a view was remove in: https://github.com/odoo/enterprise/pull/4834 That feature is now reintroduced and will use website_track instead of leads to be stored. The track field is now on the view instead of the page. url field is added to website.track, it will store the url for pages and views The Visitor page in website now references urls viewed Add some tests task-1984575 closes odoo/odoo#35810 Signed-off-by: Jérémy Kersten (jke) --- addons/website/data/website_data.xml | 6 +- addons/website/models/ir_http.py | 18 +- addons/website/models/ir_ui_view.py | 1 + addons/website/models/website_page.py | 1 - addons/website/models/website_visitor.py | 204 +++++++++++------- addons/website/security/ir.model.access.csv | 8 +- .../s_products_recently_viewed.png | Bin 0 -> 833 bytes .../website/static/src/js/set_view_track.js | 89 ++++++++ addons/website/static/src/xml/track_page.xml | 15 ++ addons/website/tests/test_website_visitor.py | 48 +++-- addons/website/tools.py | 1 + addons/website/views/snippets.xml | 1 + addons/website/views/website_templates.xml | 1 + addons/website/views/website_views.xml | 8 +- .../website/views/website_visitor_views.xml | 126 +++++++++-- addons/website_crm/models/crm_lead.py | 2 +- .../website_livechat/models/mail_channel.py | 10 +- .../website_rating/views/rating_templates.xml | 22 ++ addons/website_sale/__manifest__.py | 1 + addons/website_sale/controllers/main.py | 61 ++++++ addons/website_sale/models/__init__.py | 1 + addons/website_sale/models/website_visitor.py | 45 ++++ .../static/src/js/website_sale.editor.js | 46 ++++ .../static/src/js/website_sale.js | 59 ++--- .../src/js/website_sale_recently_viewed.js | 185 ++++++++++++++++ .../static/src/js/website_sale_utils.js | 31 +++ .../static/src/scss/website_sale.scss | 31 +++ .../src/xml/website_sale_recently_viewed.xml | 48 +++++ addons/website_sale/tests/__init__.py | 1 + .../tests/test_website_sale_visitor.py | 42 ++++ addons/website_sale/views/snippets.xml | 24 +++ addons/website_sale/views/templates.xml | 15 +- .../views/website_sale_visitor_views.xml | 125 +++++++++++ .../static/src/js/website_sale_wishlist.js | 21 +- odoo/import_xml.rng | 1 + odoo/tools/convert.py | 2 + 36 files changed, 1117 insertions(+), 183 deletions(-) create mode 100644 addons/website/static/src/img/snippets_thumbs/s_products_recently_viewed.png create mode 100644 addons/website/static/src/js/set_view_track.js create mode 100644 addons/website/static/src/xml/track_page.xml create mode 100644 addons/website_sale/models/website_visitor.py create mode 100644 addons/website_sale/static/src/js/website_sale_recently_viewed.js create mode 100644 addons/website_sale/static/src/xml/website_sale_recently_viewed.xml create mode 100644 addons/website_sale/tests/test_website_sale_visitor.py create mode 100644 addons/website_sale/views/website_sale_visitor_views.xml diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index e0df9ef806e..ce4091a6352 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -120,19 +120,19 @@ True / - True + True /contactus True - True + True True /aboutus - True + True diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index ecacdc62bac..da99aa35287 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -12,12 +12,13 @@ import werkzeug.routing import werkzeug.utils import odoo -from odoo import api, models, registry +from odoo import api, models, registry, fields from odoo import SUPERUSER_ID from odoo.http import request from odoo.tools import config, ormcache from odoo.tools.safe_eval import safe_eval from odoo.osv.expression import FALSE_DOMAIN, OR +from odoo.osv import osv from odoo.addons.base.models.qweb import QWebException from odoo.addons.http_routing.models.ir_http import ModelConverter, _guess_mimetype @@ -87,17 +88,20 @@ class Http(models.AbstractModel): super(Http, cls)._auth_method_public() @classmethod - def _extract_website_page(cls, response): - if getattr(response, 'status_code', 0) != 200: + def _register_website_track(cls, response): + if getattr(response, 'status_code', 0) != 200 or not hasattr(response, 'qcontext'): return False - - main_object = getattr(response, 'qcontext', {}).get('main_object') - return main_object if getattr(main_object, '_name', False) == 'website.page' else False + main_object = response.qcontext.get('main_object') + website_page = getattr(main_object, '_name', False) == 'website.page' and main_object + template = response.qcontext.get('response_template') + view = template and request.env['website'].get_template(template) + if view and view.track: + request.env['website.visitor']._handle_webpage_dispatch(response, website_page) @classmethod def _dispatch(cls): response = super(Http, cls)._dispatch() - request.env['website.visitor']._handle_webpage_dispatch(response, cls._extract_website_page(response)) + cls._register_website_track(response) return response @classmethod diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index a3aca36ced5..5d7a992dca1 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -23,6 +23,7 @@ class View(models.Model): website_id = fields.Many2one('website', ondelete='cascade', string="Website") page_ids = fields.One2many('website.page', 'view_id') first_page_id = fields.Many2one('website.page', string='Website Page', help='First page linked to this view', compute='_compute_first_page_id') + track = fields.Boolean(string='Track', default=False, help="Allow to specify for one page of the website to be trackable or not") def _compute_first_page_id(self): for view in self: diff --git a/addons/website/models/website_page.py b/addons/website/models/website_page.py index 105aa49d89c..5473efeffe6 100644 --- a/addons/website/models/website_page.py +++ b/addons/website/models/website_page.py @@ -20,7 +20,6 @@ class Page(models.Model): menu_ids = fields.One2many('website.menu', 'page_id', 'Related Menus') is_homepage = fields.Boolean(compute='_compute_homepage', inverse='_set_homepage', string='Homepage') is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible') - is_tracked = fields.Boolean(string='Is Tracked', default=False, help="A tracked page will be included in visitors browsing history.") # Page options header_overlay = fields.Boolean() diff --git a/addons/website/models/website_visitor.py b/addons/website/models/website_visitor.py index 7f7e50073b4..d2d9ca0bf7d 100644 --- a/addons/website/models/website_visitor.py +++ b/addons/website/models/website_visitor.py @@ -4,139 +4,199 @@ from datetime import datetime, timedelta from hashlib import sha256 import hmac +import uuid from odoo import fields, models, api, _ from odoo.tools.misc import _consteq, _format_time_ago from odoo.http import request +from odoo.osv import expression -class WebsitVisitorPage(models.Model): - _name = 'website.visitor.page' +class WebsiteTrack(models.Model): + _name = 'website.track' _description = 'Visited Pages' _order = 'visit_datetime DESC' _log_access = False visitor_id = fields.Many2one('website.visitor', ondelete="cascade", index=True, required=True, readonly=True) page_id = fields.Many2one('website.page', index=True, ondelete='cascade', readonly=True) + url = fields.Text('Url', index=True) visit_datetime = fields.Datetime('Visit Date', default=fields.Datetime.now, required=True, readonly=True) +class VisitorLastConnection(models.Model): + _name = 'website.visitor.lastconnection' + _description = 'temporary table for Visitor' + + visitor_id = fields.Many2one('website.visitor', ondelete="cascade", index=True, required=True, readonly=True) + connection_datetime = fields.Datetime(default=fields.Datetime.now, required=True, readonly=True) + + class WebsiteVisitor(models.Model): _name = 'website.visitor' _description = 'Website Visitor' - _order = 'last_connection_datetime DESC' + _order = 'create_date DESC' name = fields.Char('Name', default=_('Website Visitor')) + access_token = fields.Char(required=True, default=lambda x: uuid.uuid4().hex, index=True, copy=False, groups='base.group_website_publisher') active = fields.Boolean('Active', default=True) website_id = fields.Many2one('website', "Website", readonly=True) user_partner_id = fields.Many2one('res.partner', string="Linked Partner", help="Partner of the last logged in user.") create_date = fields.Datetime('First connection date', readonly=True) - last_connection_datetime = fields.Datetime('Last Connection', help="Last page view date", readonly=True) + last_connection_datetime = fields.Datetime('Last Connection', compute="_compute_time_statistics", help="Last page view date", search='_search_last_connection', readonly=True) + last_connections_ids = fields.One2many('website.visitor.lastconnection', 'visitor_id', readonly=True) country_id = fields.Many2one('res.country', 'Country', readonly=True) country_flag = fields.Binary(related="country_id.image", string="Country Flag") lang_id = fields.Many2one('res.lang', string='Language', help="Language from the website when visitor has been created") - visit_count = fields.Integer('Number of visits', default=1, readonly=True, help="A new visit is considered if last connection was more than 8 hours ago.") - visitor_page_ids = fields.One2many('website.visitor.page', 'visitor_id', string='Visited Pages History', readonly=True) - visitor_page_count = fields.Integer('Page Views', compute="_compute_page_statistics") - page_ids = fields.Many2many('website.page', string="Visited Pages", compute="_compute_page_statistics", store=True) - page_count = fields.Integer('# Visited Pages', compute="_compute_page_statistics") + visit_count = fields.Integer('Number of visits', default=1, readonly=True, help="A new visit is considered if last connection was more than 24 hours ago.") + website_track_ids = fields.One2many('website.track', 'visitor_id', string='Visited Pages History', readonly=True) + visitor_page_count = fields.Integer('Page Views', compute="_compute_page_statistics", help="Total number of visits on tracked pages") + page_ids = fields.Many2many('website.page', string="Visited Pages", compute="_compute_page_statistics") + page_count = fields.Integer('# Visited Pages', compute="_compute_page_statistics", help="Total number of tracked page visited") time_since_last_action = fields.Char('Last action', compute="_compute_time_statistics", help='Time since last page view. E.g.: 2 minutes ago') is_connected = fields.Boolean('Is connected ?', compute='_compute_time_statistics', help='A visitor is considered as connected if his last page view was within the last 5 minutes.') - @api.depends('visitor_page_ids') + @api.depends('name') + def name_get(self): + ret_list = [] + for record in self: + name = '%s #%d' % (record.name, record.id) + ret_list.append((record.id, name)) + return ret_list + + @api.model + def _search_last_connection(self, operator, value): + assert operator in expression.TERM_OPERATORS + self.env['website.visitor.lastconnection'].flush(['visitor_id']) + query = """ + SELECT v.visitor_id + FROM website_visitor_lastconnection v + WHERE v.connection_datetime %s %%s + GROUP BY v.visitor_id, v.connection_datetime + ORDER BY v.connection_datetime + """ % (operator,) + return [('id', 'inselect', (query, [value]))] + + @api.depends('website_track_ids') def _compute_page_statistics(self): - results = self.env['website.visitor.page'].read_group( - [('visitor_id', 'in', self.ids)], ['visitor_id', 'page_id'], ['visitor_id', 'page_id'], lazy=False) + results = self.env['website.track'].read_group( + [('visitor_id', 'in', self.ids), ('url', '!=', False)], ['visitor_id', 'page_id', 'url'], ['visitor_id', 'page_id', 'url'], lazy=False) mapped_data = {} for result in results: - visitor_info = mapped_data.get(result['visitor_id'][0], {'page_count': 0, 'page_ids': set()}) - visitor_info['page_count'] += result['__count'] - visitor_info['page_ids'].add(result['page_id'][0]) + visitor_info = mapped_data.get(result['visitor_id'][0], {'page_count': 0, 'visitor_page_count': 0, 'page_ids': set()}) + visitor_info['visitor_page_count'] += result['__count'] + visitor_info['page_count'] += 1 + if result['page_id']: + visitor_info['page_ids'].add(result['page_id'][0]) mapped_data[result['visitor_id'][0]] = visitor_info for visitor in self: visitor_info = mapped_data.get(visitor.id, {'page_ids': [], 'page_count': 0}) - visitor.page_ids = [(6, 0, visitor_info['page_ids'])] - visitor.visitor_page_count = visitor_info['page_count'] - visitor.page_count = len(visitor_info['page_ids']) + visitor.visitor_page_count = visitor_info['visitor_page_count'] + visitor.page_count = visitor_info['page_count'] - @api.depends('last_connection_datetime') + @api.depends('last_connections_ids') def _compute_time_statistics(self): - results = self.env['website.visitor'].search_read([('id', 'in', self.ids)], ['id', 'last_connection_datetime']) - mapped_data = {result['id']: result['last_connection_datetime'] for result in results} - + results = self.env['website.visitor.lastconnection'].read_group([('visitor_id', 'in', self.ids)], ['visitor_id', 'connection_datetime:max'], ['visitor_id']) + mapped_data = {result['visitor_id'][0]: result['connection_datetime'] for result in results} for visitor in self: - last_connection_date = mapped_data[visitor.id] - visitor.time_since_last_action = _format_time_ago(self.env, (datetime.now() - last_connection_date)) - visitor.is_connected = (datetime.now() - last_connection_date) < timedelta(minutes=5) + last_connection_datetime = mapped_data.get(visitor.id, False) + if last_connection_datetime: + visitor.last_connection_datetime = last_connection_datetime + visitor.time_since_last_action = _format_time_ago(self.env, (datetime.now() - last_connection_datetime)) + visitor.is_connected = (datetime.now() - last_connection_datetime) < timedelta(minutes=5) - def _get_visitor_sign(self): - return {visitor.id: "%d-%s" % (visitor.id, self._get_visitor_hash(visitor.id)) for visitor in self} - - @api.model - def _get_visitor_hash(self, visitor_id): - db_secret = request.env['ir.config_parameter'].sudo().get_param('database.secret') - return hmac.new(str(visitor_id).encode('utf-8'), db_secret.encode('utf-8'), sha256).hexdigest() - - def _get_visitor_from_request(self): + def _get_visitor_from_request(self, with_previous_visitors=False): if not request: return None - visitor = self.env['website.visitor'] - cookie_content = request.httprequest.cookies.get('visitor_id') - if cookie_content and '-' in cookie_content: - visitor_id, visitor_hash = cookie_content.split('-', 1) - if _consteq(visitor_hash, self._get_visitor_hash(visitor_id)): - return visitor.sudo().with_context(active_test=False).search([('id', '=', visitor_id)]) # search to avoid having to call exists() - return visitor + if with_previous_visitors and not request.env.user._is_public(): + partner_id = request.env.user.partner_id + # Retrieve all the previous partner's visitors to have full history of his last products viewed. + return request.env['website.visitor'].sudo().with_context(active_test=False).search([('user_partner_id', '=', partner_id.id)]) + else: + visitor = self.env['website.visitor'] + access_token = request.httprequest.cookies.get('visitor_id') + if access_token: + visitor = visitor.sudo().with_context(active_test=False).search([('access_token', '=', access_token)]) + return visitor def _handle_webpage_dispatch(self, response, website_page): - if website_page: - # get visitor only if page tracked. Done here to avoid having to do it multiple times in case of override. - visitor_sudo = self._get_visitor_from_request() if website_page.is_tracked else False - self._handle_website_page_visit(response, website_page, visitor_sudo) + # get visitor. Done here to avoid having to do it multiple times in case of override. + visitor_sudo = self._get_visitor_from_request() + self._handle_website_page_visit(response, website_page, visitor_sudo) def _handle_website_page_visit(self, response, website_page, visitor_sudo): """ Called on dispatch. This will create a website.visitor if the http request object - is a tracked website page. Only on tracked page to avoid having too much operations done on every page - or other http requests. - Note: The side effect is that the last_connection_datetime is updated ONLY on tracked pages.""" - if website_page.is_tracked: - if not visitor_sudo: - # If visitor does not exist - visitor_sudo = self._create_visitor(website_page.id) - sign = visitor_sudo._get_visitor_sign().get(visitor_sudo.id) - expiration_date = datetime.now() + timedelta(days=100*365) # never expire - response.set_cookie('visitor_id', sign, expires=expiration_date.timestamp()) - else: - # Add page even if already in visitor_page_ids as checks on relations are done in many2many write method - vals = { - 'last_connection_datetime': datetime.now(), - 'visitor_page_ids': [(0, 0, {'page_id': website_page.id, 'visit_datetime': datetime.now()})], - } - if visitor_sudo.last_connection_datetime < (datetime.now() - timedelta(hours=8)): - vals['visit_count'] = visitor_sudo.visit_count + 1 - if not visitor_sudo.active: - vals['active'] = True - visitor_sudo.write(vals) + is a tracked website page or a tracked view. Only on tracked elements to avoid having + too much operations done on every page or other http requests. + Note: The side effect is that the last_connection_datetime is updated ONLY on tracked elements.""" + url = request.httprequest.url + website_track_values = { + 'url': url, + 'visit_datetime': datetime.now(), + } + if website_page: + website_track_values['page_id'] = website_page.id + domain = [('page_id', '=', website_page.id)] + else: + domain = [('url', '=', url)] + if not visitor_sudo: + visitor_sudo = self._create_visitor(website_track_values) + expiration_date = datetime.now() + timedelta(days=365) + response.set_cookie('visitor_id', visitor_sudo.access_token, expires=expiration_date) + else: + visitor_sudo._add_tracking(domain, website_track_values) - def _create_visitor(self, website_page_id=False): + def _add_tracking(self, domain, website_track_values): + """ Update the visitor when a website_track is added""" + domain = expression.AND([domain, [('visitor_id', '=', self.id)]]) + last_view = self.env['website.track'].sudo().search(domain, limit=1) + if not last_view or last_view.visit_datetime < datetime.now() - timedelta(minutes=30): + website_track_values['visitor_id'] = self.id + self.env['website.track'].create(website_track_values) + self.env['website.visitor.lastconnection'].create({'visitor_id': self.id, + 'connection_datetime': website_track_values['visit_datetime']}) + + def _create_visitor(self, website_track_values=None): + """ Create a visitor and add a track to it if website_track_values is set.""" country_code = request.session.get('geoip', {}).get('country_code', False) country_id = request.env['res.country'].sudo().search([('code', '=', country_code)], limit=1).id if country_code else False vals = { - 'last_connection_datetime': datetime.now(), 'lang_id': request.lang.id, 'country_id': country_id, - 'website_id': request.website.id + 'last_connections_ids': [(0, 0, {'connection_datetime': website_track_values['visit_datetime'] or datetime.now()})], } if not self.env.user._is_public(): vals['user_partner_id'] = self.env.user.partner_id.id - if website_page_id: - vals['visitor_page_ids'] = [(0, 0, {'page_id': website_page_id, 'visit_datetime': datetime.now()})] - # Set signed visitor id in cookie + vals['name'] = self.env.user.partner_id.name + if website_track_values: + vals['website_track_ids'] = [(0, 0, website_track_values)] return self.sudo().create(vals) def _cron_archive_visitors(self): + self.flush(['visit_count']) + yesterday = datetime.now() - timedelta(days=1) + query = """ + UPDATE website_visitor + SET visit_count = visit_count + 1 + WHERE id in ( + SELECT visitor_id + FROM website_visitor_lastconnection + GROUP BY visitor_id + HAVING COUNT(*) > 1) + """ + self.env.cr.execute(query, [yesterday]) + + query = """ + DELETE FROM website_visitor_lastconnection + WHERE (visitor_id, connection_datetime) not in ( + SELECT v.visitor_id, max(connection_datetime) + FROM website_visitor_lastconnection v + GROUP BY v.visitor_id) + """ + self.env.cr.execute(query, []) + one_week_ago = datetime.now() - timedelta(days=7) visitors_to_archive = self.env['website.visitor'].sudo().search([('last_connection_datetime', '<', one_week_ago)]) visitors_to_archive.write({'active': False}) diff --git a/addons/website/security/ir.model.access.csv b/addons/website/security/ir.model.access.csv index 7e80fee2dc2..624735ca4a4 100644 --- a/addons/website/security/ir.model.access.csv +++ b/addons/website/security/ir.model.access.csv @@ -12,7 +12,9 @@ access_website_ir_ui_view,access_website_ir_ui_view,model_ir_ui_view,group_websi access_seo_public,access_seo_public,model_website_seo_metadata,,1,0,0,0 access_seo_manager,access_seo_manager,model_website_seo_metadata,group_website_designer,1,1,1,1 access_seo_designer,access_seo_designer,model_website_seo_metadata,group_website_designer,1,1,1,1 -access_website_visitor_designer,access_website_visitor_designer,model_website_visitor,website.group_website_designer,1,0,0,0 +access_website_visitor_designer,access_website_visitor_designer,model_website_visitor,website.group_website_designer,1,1,1,1 access_website_visitor_system,access_website_visitor_system,model_website_visitor,base.group_system,1,1,1,1 -access_website_visitor_page_designer,access_website_visitor_page_designer,model_website_visitor_page,website.group_website_designer,1,0,0,0 -access_website_visitor_page_system,access_website_visitor_page_system,model_website_visitor_page,base.group_system,1,1,1,1 +access_website_visitor_lastconnection_designer,access_website_visitor_lastconnection_designer,model_website_visitor_lastconnection,website.group_website_designer,1,1,1,1 +access_website_visitor_lastconnection_system,access_website_visitor_lastconnection_system,model_website_visitor_lastconnection,base.group_system,1,1,1,1 +access_website_track_designer,access_website_track_designer,model_website_track,website.group_website_designer,1,1,1,1 +access_website_track_system,access_website_track_system,model_website_track,base.group_system,1,1,1,1 diff --git a/addons/website/static/src/img/snippets_thumbs/s_products_recently_viewed.png b/addons/website/static/src/img/snippets_thumbs/s_products_recently_viewed.png new file mode 100644 index 0000000000000000000000000000000000000000..c55ad28b628734388bbbe359397202c6124defd9 GIT binary patch literal 833 zcmeAS@N?(olHy`uVBq!ia0vp^DL@>+!2~1|vfXz8Db50q$YKTtZeb8+WSBKa0w~B{ z;_2(k{+LyQ!_4xgbG0V}1JeUf7srr_TW@drXIw52VgE4QKGrjT-kvRw98QTGIqcBG zA-C)Hml<)*O%?Ai78hNe{7$J)Yxfo&7Jj80j-qR#wn-j)b1>>?$l9fnTHDsF{_@;$ zMrn!3$-2zzpK6y+`19*l&8+Ev;_8@^dj#G&u`q6EVAV-rR}VV%*i@Y`#8D&NHo_)7EYAMFXD2?-#xyl%U=F?>B~3czu)Hk>CvfIzuNWxZD>02 zW}ixG+|ujPpEnx=(cbD)@pG3x&tI0(*^%NIQ@8WwpPaLEEek!2cI$W+CWzHfcX<1I z{hzd_@5}zWf3B<3Rq@>I?mX$onlFZ*lO6}8%2jM)UZ)d#{qe^Nv)Nylgsrpe)?PhH z!|e84ZuS2Abukl;?ET-R!j(Ps{K?PDZRh$s%v7G(u`F}dD!DJ2D^_Lch&l$tJ1|!hX%g+&+z@@B+HI78zX$Kf6XvCmHtYg*DZKuh}vXL5w5&r+pYKC z{c|VR-*{Pq(dt#ReAGl;A6lLd5;G%3wmh0a6)F|7Y^iJ&QcyIL7W&im{wURz2(-_uFjC{D&2pZdl^CEgW45jLx zEA}f-6F8#6mC6B5P6wPAxFf&`sTH2E7=bd`GLQH}zc=hJtv2!iCVvJ`S3j3^P6 { + if (data[0]['track']) { + this.track = true; + this.$input.attr('checked', 'checked'); + } else { + this.track = false; + } + }); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _isTracked: function (val) { + var viewid = $('html').data('viewid'); + if (!viewid) { + return Promise.reject(); + } else { + return this._rpc({ + model: 'ir.ui.view', + method: 'read', + args: [[viewid], ['track']], + }); + } + }, + /** + * @private + */ + _onTrackChange: function (ev) { + var checkboxValue = this.$input.is(':checked'); + if (checkboxValue !== this.track) { + this.track = checkboxValue; + this._trackPage(checkboxValue); + } + }, + /** + * @private + */ + _trackPage: function (val) { + var viewid = $('html').data('viewid'); + if (!viewid) { + return Promise.reject(); + } else { + return this._rpc({ + model: 'ir.ui.view', + method: 'write', + args: [[viewid], {track: val}], + }); + } + }, +}); + +CustomizeMenu.include({ + _loadCustomizeOptions: function () { + var self = this; + var def = this._super.apply(this, arguments); + return def.then(function () { + if (!self.__trackpageLoaded) { + self.__trackpageLoaded = true; + self.trackPage = new TrackPage(self); + self.trackPage.appendTo(self.$el.children('.dropdown-menu')); + } + }); + }, +}); + +}); diff --git a/addons/website/static/src/xml/track_page.xml b/addons/website/static/src/xml/track_page.xml new file mode 100644 index 00000000000..11311156ca7 --- /dev/null +++ b/addons/website/static/src/xml/track_page.xml @@ -0,0 +1,15 @@ + + + + +