From 12fdd7042f6e2af4c21182609631d76afd42d437 Mon Sep 17 00:00:00 2001 From: Bibhas Date: Wed, 4 Apr 2018 01:40:10 +0530 Subject: [PATCH 01/14] Moving anon user tracking from image element to form submit --- hasjob/templates/formlayout.html.jinja2 | 2 + hasjob/templates/layout.html.jinja2 | 8 +- hasjob/templates/macros.html.jinja2 | 34 ++++++ hasjob/templates/sheet.html.jinja2 | 3 +- hasjob/templates/tablayout.html.jinja2 | 2 + hasjob/views/helper.py | 139 +++++++++++------------- hasjob/views/index.py | 1 - 7 files changed, 107 insertions(+), 82 deletions(-) diff --git a/hasjob/templates/formlayout.html.jinja2 b/hasjob/templates/formlayout.html.jinja2 index a81c7c9a7..dee9b718a 100644 --- a/hasjob/templates/formlayout.html.jinja2 +++ b/hasjob/templates/formlayout.html.jinja2 @@ -1,3 +1,4 @@ +{% from "macros.html.jinja2" import anon_user_script %} {%- if current_view.tabs -%} {%- extends "tablayout.html.jinja2" -%} {%- else -%} @@ -27,4 +28,5 @@ {%- if header_campaign %}{{ campaign_script() }}{% endif %} {% assets "js_tinymce" %}{% endassets %} {% block footerscripts %}{% endblock %} + {{ anon_user_script() }} {% endblock %} diff --git a/hasjob/templates/layout.html.jinja2 b/hasjob/templates/layout.html.jinja2 index 68c3b45da..ef338e4d3 100644 --- a/hasjob/templates/layout.html.jinja2 +++ b/hasjob/templates/layout.html.jinja2 @@ -1,6 +1,6 @@ {%- extends "baseframe.html.jinja2" -%} {%- from "baseframe/components.html.jinja2" import hgnav -%} -{%- from "macros.html.jinja2" import campaign_header, campaign_script, filters_setup_script -%} +{%- from "macros.html.jinja2" import campaign_header, campaign_script, filters_setup_script, anon_user_script -%} {%- block doctypehtml -%} @@ -54,7 +54,7 @@ {{ filters_setup_script(job_filters, data_filters) }} {%- else %} {{ filters_setup_script(job_filters) }} - {%- endif %} + {%- endif %} {%- endif %} {%- endblock %} @@ -225,13 +225,11 @@ to find out when new jobs are posted. Hosted by E2E Networks. {%- endif %} - {%- if not g.user and not g.anon_user %} - - {%- endif %}

{% endblock %} {% block layoutscripts %} {%- if header_campaign %}{{ campaign_script() }}{% endif %} {% block footerscripts %}{% endblock %} + {{ anon_user_script() }} {% endblock %} diff --git a/hasjob/templates/macros.html.jinja2 b/hasjob/templates/macros.html.jinja2 index 65ad2e12e..c47d4d0ff 100644 --- a/hasjob/templates/macros.html.jinja2 +++ b/hasjob/templates/macros.html.jinja2 @@ -207,3 +207,37 @@ {%- endwith %} {%- endmacro -%} + +{%- macro anon_user_script() -%} + {%- if not g.user and not g.anon_user %} + + {%- endif %} +{%- endmacro -%} + diff --git a/hasjob/templates/sheet.html.jinja2 b/hasjob/templates/sheet.html.jinja2 index 95f023cd1..4a52ec016 100644 --- a/hasjob/templates/sheet.html.jinja2 +++ b/hasjob/templates/sheet.html.jinja2 @@ -1,5 +1,5 @@ {%- extends "layout.html.jinja2" -%} -{%- from "macros.html.jinja2" import campaign_header, campaign_script -%} +{%- from "macros.html.jinja2" import campaign_header, campaign_script, anon_user_script -%} {% block messages %}{% endblock %} {% block basecontent %} {%- if header_campaign %}
{% endif %} @@ -24,4 +24,5 @@ {% block layoutscripts %} {%- if header_campaign %}{{ campaign_script() }}{% endif %} {% block footerscripts %}{% endblock %} + {{ anon_user_script() }} {% endblock %} diff --git a/hasjob/templates/tablayout.html.jinja2 b/hasjob/templates/tablayout.html.jinja2 index 6f669d089..332ba08c2 100644 --- a/hasjob/templates/tablayout.html.jinja2 +++ b/hasjob/templates/tablayout.html.jinja2 @@ -1,3 +1,4 @@ +{% from "macros.html.jinja2" import anon_user_script %} {%- extends "layout.html.jinja2" -%} {% block pageheaders %} @@ -31,4 +32,5 @@ {% block footerscripts %}{% endblock %} + {{ anon_user_script() }} {% endblock %} diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index ff9802ee7..f37f71710 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -10,7 +10,7 @@ from sqlalchemy import or_ from sqlalchemy.exc import IntegrityError from geoip2.errors import AddressNotFoundError -from flask import Markup, request, g, session +from flask import Markup, request, g, session, Response from flask_rq import job from flask_lastuser import signal_user_looked_up from coaster.sqlalchemy import failsafe_add @@ -28,14 +28,55 @@ gif1x1 = 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw=='.decode('base64') -@app.route('/_sniffle.gif') +@app.route('/_sniffle', methods=['POST']) def sniffle(): - return gif1x1, 200, { - 'Content-Type': 'image/gif', - 'Cache-Control': 'no-cache, no-store, must-revalidate', - 'Pragma': 'no-cache', - 'Expires': '0' - } + """ + Load anon user: + + 1. If there's g.user and session['anon_user'], it loads that anon_user and tags with user=g.user, then removes anon + 2. If there's no g.user and no session['anon_user'] and form submitted token matches session['au'], sets session['anon_user'] = 'test' + 3. If there's no g.user and there is session['au'] != form['token'], loads g.anon_user + """ + # Loading an anon user only if we're not rendering static resources + if g.user: + if 'au' in session and session['au'] is not None and not unicode(session['au']).startswith(u'test'): + anon_user = AnonUser.query.get(session['au']) + if anon_user: + anon_user.user = g.user + session.pop('au', None) + else: + if unicode(session['au']).startswith('test') and unicode(session['au']) == request.form.get('token'): + # This client sent us back our test cookie, so set a real value now + g.anon_user = AnonUser() + db.session.add(g.anon_user) + g.esession = EventSession.new_from_request(request) + g.esession.anon_user = g.anon_user + db.session.add(g.esession) + g.esession.load_from_cache(session['au'], UserEvent) + # We'll update session['au'] below after database commit + else: + anon_user = AnonUser.query.get(session['au']) + if not anon_user: + # XXX: We got a fake value? This shouldn't happen + g.event_data['anon_cookie_test'] = session['au'] + session['au'] = u'test-' + unicode(uuid4()) # Try again + g.esession = EventSessionBase.new_from_request(request) + else: + g.anon_user = anon_user + + db.session.commit() + + if g.anon_user: + session['au'] = g.anon_user.id + session.permanent = True + + # Prepare event session if it's not already present + if g.user or g.anon_user and not g.esession: + g.esession = EventSession.get_session(uuid=session.get('es'), user=g.user, anon_user=g.anon_user) + if g.esession: + session['es'] = g.esession.uuid + + return Response("OK") def index_is_paginated(): @@ -66,18 +107,11 @@ def load_user_data(user): """ All pre-request utilities, run after g.user becomes available. - Part 1: Load anon user: - - 1. If there's g.user and session['anon_user'], it loads that anon_user and tags with user=g.user, then removes anon - 2. If there's no g.user and no session['anon_user'], sets session['anon_user'] = 'test' - 3. If there's no g.user and there is session['anon_user'] = 'test', creates a new anon user, then saves to cookie - 4. If there's no g.user and there is session['anon_user'] != 'test', loads g.anon_user - Part 2: Are we in kiosk mode? Is there a preview campaign? Part 3: Look up user's IP address location as geonameids for use in targeting. """ - g.anon_user = None # Could change below - g.event_data = {} # Views can add data to the current pageview event + g.anon_user = None + g.event_data = {} g.esession = None g.viewcounts = {} g.impressions = session.pop('impressions', {}) # Retrieve from cookie session if present there @@ -86,66 +120,21 @@ def load_user_data(user): g.bgroup = None now = datetime.utcnow() - if request.endpoint not in ('static', 'baseframe.static'): - # Loading an anon user only if we're not rendering static resources - if user: - if 'au' in session and session['au'] is not None and not unicode(session['au']).startswith(u'test'): - anon_user = AnonUser.query.get(session['au']) - if anon_user: - anon_user.user = user - session.pop('au', None) - else: - if not session.get('au'): - session['au'] = u'test-' + unicode(uuid4()) - g.esession = EventSessionBase.new_from_request(request) - g.event_data['anon_cookie_test'] = session['au'] - # elif session['au'] == 'test': # Legacy test cookie, original request now lost - # g.anon_user = AnonUser() - # db.session.add(g.anon_user) - # g.esession = EventSession.new_from_request(request) - # g.esession.anon_user = g.anon_user - # db.session.add(g.esession) - # # We'll update session['au'] below after database commit - # elif unicode(session['au']).startswith('test-'): # Newer redis-backed test cookie - # # This client sent us back our test cookie, so set a real value now - # g.anon_user = AnonUser() - # db.session.add(g.anon_user) - # g.esession = EventSession.new_from_request(request) - # g.esession.anon_user = g.anon_user - # db.session.add(g.esession) - # g.esession.load_from_cache(session['au'], UserEvent) - # # We'll update session['au'] below after database commit - else: - anon_user = None # AnonUser.query.get(session['au']) - if not anon_user: - # XXX: We got a fake value? This shouldn't happen - g.event_data['anon_cookie_test'] = session['au'] - session['au'] = u'test-' + unicode(uuid4()) # Try again - g.esession = EventSessionBase.new_from_request(request) - else: - g.anon_user = anon_user + if 'au' not in session or session['au'] is None: + session['au'] = u'test-' + unicode(uuid4()) + g.esession = EventSessionBase.new_from_request(request) + g.event_data['anon_cookie_test'] = session['au'] + elif not unicode(session['au']).startswith(u'test'): + anon_user = AnonUser.query.get(session['au']) + if anon_user: + g.anon_user = anon_user - # Prepare event session if it's not already present - if g.user or g.anon_user and not g.esession: - g.esession = EventSession.get_session(uuid=session.get('es'), user=g.user, anon_user=g.anon_user) - if g.esession: - session['es'] = g.esession.uuid - - # Don't commit here. It flushes SQLAlchemy's session cache and forces - # fresh database hits. Let after_request commit. (Commented out 30-03-2016) - # db.session.commit() - g.db_commit_needed = True - - if g.anon_user: - session['au'] = g.anon_user.id - session.permanent = True - if 'impressions' in session: - # Run this in the foreground since we need this later in the request for A/B display consistency. - # This is most likely being called from the UI-non-blocking sniffle.gif anyway. - save_impressions(g.esession.id, session.pop('impressions').values(), now) + if g.anon_user and 'impressions' in session: + # Run this in the foreground since we need this later in the request for A/B display consistency. + # This is most likely being called from the UI-non-blocking sniffle.gif anyway. + save_impressions(g.esession.id, session.pop('impressions').values(), now) # We have a user, now look up everything else - if session.get('kiosk'): g.kiosk = True else: @@ -313,7 +302,7 @@ def session_jobpost_ab(): Returns the user's B-group assignment (NA, True, False) for all jobs shown to the user in the current event session (impressions or views) as a dictionary of {id: bgroup} """ - if not g.esession.persistent: + if g.esession and not g.esession.persistent: return {key: value[2] for key, value in session.get('impressions', {}).items()} result = {ji.jobpost_id: ji.bgroup for ji in JobImpression.query.filter_by(event_session=g.esession)} result.update({jvs.jobpost_id: jvs.bgroup for jvs in JobViewSession.query.filter_by(event_session=g.esession)}) diff --git a/hasjob/views/index.py b/hasjob/views/index.py index 892f4cd10..239d65a93 100644 --- a/hasjob/views/index.py +++ b/hasjob/views/index.py @@ -353,7 +353,6 @@ def index(basequery=None, filters={}, md5sum=None, tag=None, domain=None, locati BoardJobPost.board == g.board, JobPost.state.LISTED).options( db.load_only('jobpost_id', 'pinned')).all() } - else: board_jobs = {} From 6dd80bea5ce921fa91fd2ce8388ed0efccfd9cd1 Mon Sep 17 00:00:00 2001 From: Bibhas Date: Thu, 5 Apr 2018 01:58:35 +0530 Subject: [PATCH 02/14] fixes in handling anon user and associating with actual user upon login --- hasjob/views/helper.py | 43 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index f37f71710..d068a268e 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -37,14 +37,7 @@ def sniffle(): 2. If there's no g.user and no session['anon_user'] and form submitted token matches session['au'], sets session['anon_user'] = 'test' 3. If there's no g.user and there is session['au'] != form['token'], loads g.anon_user """ - # Loading an anon user only if we're not rendering static resources - if g.user: - if 'au' in session and session['au'] is not None and not unicode(session['au']).startswith(u'test'): - anon_user = AnonUser.query.get(session['au']) - if anon_user: - anon_user.user = g.user - session.pop('au', None) - else: + if not g.user: if unicode(session['au']).startswith('test') and unicode(session['au']) == request.form.get('token'): # This client sent us back our test cookie, so set a real value now g.anon_user = AnonUser() @@ -55,16 +48,12 @@ def sniffle(): g.esession.load_from_cache(session['au'], UserEvent) # We'll update session['au'] below after database commit else: - anon_user = AnonUser.query.get(session['au']) - if not anon_user: - # XXX: We got a fake value? This shouldn't happen - g.event_data['anon_cookie_test'] = session['au'] - session['au'] = u'test-' + unicode(uuid4()) # Try again - g.esession = EventSessionBase.new_from_request(request) - else: - g.anon_user = anon_user - - db.session.commit() + # form submitted token doesn't match the already set session['au'] + # XXX: We got a fake value? This shouldn't happen + g.event_data['anon_cookie_test'] = session['au'] + session['au'] = u'test-' + unicode(uuid4()) # Try again + g.esession = EventSessionBase.new_from_request(request) + db.session.commit() if g.anon_user: session['au'] = g.anon_user.id @@ -120,14 +109,22 @@ def load_user_data(user): g.bgroup = None now = datetime.utcnow() - if 'au' not in session or session['au'] is None: + if 'au' in session and session['au'] is not None: + if not unicode(session['au']).startswith(u'test'): + # fetch anon user and set anon_user.user + anon_user = AnonUser.query.get(session['au']) + if anon_user and g.user: + # we have anon user id in session['au'], set anon_user.user to current user + anon_user.user = g.user + g.db_commit_needed = True + session.pop('au', None) + elif anon_user and not g.user: + # set g.anon_user + g.anon_user = anon_user + elif not g.user: session['au'] = u'test-' + unicode(uuid4()) g.esession = EventSessionBase.new_from_request(request) g.event_data['anon_cookie_test'] = session['au'] - elif not unicode(session['au']).startswith(u'test'): - anon_user = AnonUser.query.get(session['au']) - if anon_user: - g.anon_user = anon_user if g.anon_user and 'impressions' in session: # Run this in the foreground since we need this later in the request for A/B display consistency. From 8648385e2a983d8734431c1ae1451a4b3b7f00d4 Mon Sep 17 00:00:00 2001 From: Bibhas Date: Thu, 5 Apr 2018 02:04:59 +0530 Subject: [PATCH 03/14] added comment --- hasjob/views/helper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index d068a268e..21fa03e5d 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -96,6 +96,8 @@ def load_user_data(user): """ All pre-request utilities, run after g.user becomes available. + Part 1: If session['au'] exists, either set g.anon_user or set anon_user.user (if g.user exists). + If session['au'] does not exist, set it Part 2: Are we in kiosk mode? Is there a preview campaign? Part 3: Look up user's IP address location as geonameids for use in targeting. """ From 72139e28af27bd9ac6b7cbc0dd25f336b0e9f5ec Mon Sep 17 00:00:00 2001 From: Bibhas Date: Thu, 5 Apr 2018 14:04:47 +0530 Subject: [PATCH 04/14] moved eventsession setting to per request --- hasjob/views/helper.py | 55 +++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index 21fa03e5d..daf1d0703 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -59,12 +59,6 @@ def sniffle(): session['au'] = g.anon_user.id session.permanent = True - # Prepare event session if it's not already present - if g.user or g.anon_user and not g.esession: - g.esession = EventSession.get_session(uuid=session.get('es'), user=g.user, anon_user=g.anon_user) - if g.esession: - session['es'] = g.esession.uuid - return Response("OK") @@ -111,27 +105,34 @@ def load_user_data(user): g.bgroup = None now = datetime.utcnow() - if 'au' in session and session['au'] is not None: - if not unicode(session['au']).startswith(u'test'): - # fetch anon user and set anon_user.user - anon_user = AnonUser.query.get(session['au']) - if anon_user and g.user: - # we have anon user id in session['au'], set anon_user.user to current user - anon_user.user = g.user - g.db_commit_needed = True - session.pop('au', None) - elif anon_user and not g.user: - # set g.anon_user - g.anon_user = anon_user - elif not g.user: - session['au'] = u'test-' + unicode(uuid4()) - g.esession = EventSessionBase.new_from_request(request) - g.event_data['anon_cookie_test'] = session['au'] - - if g.anon_user and 'impressions' in session: - # Run this in the foreground since we need this later in the request for A/B display consistency. - # This is most likely being called from the UI-non-blocking sniffle.gif anyway. - save_impressions(g.esession.id, session.pop('impressions').values(), now) + if request.endpoint not in ('static', 'baseframe.static'): + if 'au' in session and session['au'] is not None: + if not unicode(session['au']).startswith(u'test'): + # fetch anon user and set anon_user.user + anon_user = AnonUser.query.get(session['au']) + if anon_user and g.user: + # we have anon user id in session['au'], set anon_user.user to current user + anon_user.user = g.user + g.db_commit_needed = True + session.pop('au', None) + elif anon_user and not g.user: + # set g.anon_user + g.anon_user = anon_user + elif not g.user: + session['au'] = u'test-' + unicode(uuid4()) + g.esession = EventSessionBase.new_from_request(request) + g.event_data['anon_cookie_test'] = session['au'] + + # Prepare event session if it's not already present + if g.user or g.anon_user and not g.esession: + g.esession = EventSession.get_session(uuid=session.get('es'), user=g.user, anon_user=g.anon_user) + if g.esession: + session['es'] = g.esession.uuid + + if g.anon_user and 'impressions' in session: + # Run this in the foreground since we need this later in the request for A/B display consistency. + # This is most likely being called from the UI-non-blocking sniffle.gif anyway. + save_impressions(g.esession.id, session.pop('impressions').values(), now) # We have a user, now look up everything else if session.get('kiosk'): From 426e753451ff230aae5df1e0f936b68a2c636b2a Mon Sep 17 00:00:00 2001 From: Bibhas Date: Fri, 6 Apr 2018 01:46:51 +0530 Subject: [PATCH 05/14] renamed sniffle and moved to api views --- hasjob/templates/macros.html.jinja2 | 24 +++++----- hasjob/views/api.py | 37 +++++++++++++-- hasjob/views/helper.py | 71 ++++++++--------------------- 3 files changed, 64 insertions(+), 68 deletions(-) diff --git a/hasjob/templates/macros.html.jinja2 b/hasjob/templates/macros.html.jinja2 index c47d4d0ff..9c284d4a0 100644 --- a/hasjob/templates/macros.html.jinja2 +++ b/hasjob/templates/macros.html.jinja2 @@ -212,28 +212,28 @@ {%- if not g.user and not g.anon_user %}