Files
odoo_source/addons/website/models/website_visitor.py
T
fja-odoo e33172e832 [IMP] website_sale, *: keep track of products viewed
*= 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) <jke@openerp.com>
2019-09-16 09:38:53 +00:00

203 lines
11 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
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 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 = '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', 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 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('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.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, '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['visitor_page_count']
visitor.page_count = visitor_info['page_count']
@api.depends('last_connections_ids')
def _compute_time_statistics(self):
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_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_from_request(self, with_previous_visitors=False):
if not request:
return None
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):
# 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 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 _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 = {
'lang_id': request.lang.id,
'country_id': country_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
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})