Add simple_ga app to push backend event info to Google Analytics
This commit is contained in:
parent
aed1dc2b14
commit
0690e3241e
10 changed files with 199 additions and 0 deletions
|
@ -55,6 +55,7 @@ INSTALLED_APPS = [
|
|||
'events',
|
||||
'accounts',
|
||||
'resume',
|
||||
'simple_ga',
|
||||
]
|
||||
|
||||
LOGIN_URL = 'login'
|
||||
|
@ -84,6 +85,7 @@ MIDDLEWARE = [
|
|||
|
||||
'social_django.middleware.SocialAuthExceptionMiddleware',
|
||||
'resume.middleware.ResumeMiddleware',
|
||||
'simple_ga.middleware.GAEventMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'get_together.urls'
|
||||
|
@ -102,6 +104,7 @@ TEMPLATES = [
|
|||
'social_django.context_processors.backends',
|
||||
'social_django.context_processors.login_redirect',
|
||||
'django_settings_export.settings_export',
|
||||
'simple_ga.context_processors.events',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '{{settings.GOOGLE_ANALYTICS_ID}}');
|
||||
|
||||
// GA Events
|
||||
{% for event in ga_events %}
|
||||
{{ event.gtag }}
|
||||
{% endfor %}
|
||||
{% block extra_google_analytics %}{% endblock %}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
|
|
@ -15,6 +15,8 @@ from events import location
|
|||
from accounts.decorators import setup_wanted
|
||||
from django.conf import settings
|
||||
|
||||
import simple_ga as ga
|
||||
|
||||
import datetime
|
||||
import simplejson
|
||||
import geocoder
|
||||
|
@ -45,6 +47,8 @@ def home(request, *args, **kwards):
|
|||
|
||||
near_distance = int(request.GET.get("distance", DEFAULT_NEAR_DISTANCE))
|
||||
context['distance'] = near_distance
|
||||
if "distance" in request.GET and request.GET.get("distance"):
|
||||
ga.add_event(request, 'homepage_search', category='search', label='distance', value=near_distance)
|
||||
|
||||
city=None
|
||||
ll = None
|
||||
|
@ -53,6 +57,7 @@ def home(request, *args, **kwards):
|
|||
city = City.objects.get(id=request.GET.get("city"))
|
||||
context['city'] = city
|
||||
ll = [city.latitude, city.longitude]
|
||||
ga.add_event(request, 'homepage_search', category='search', label='city', value=city.short_name)
|
||||
else :
|
||||
context['city_search'] = False
|
||||
try:
|
||||
|
|
1
simple_ga/__init__.py
Normal file
1
simple_ga/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from simple_ga.api import *
|
36
simple_ga/api.py
Normal file
36
simple_ga/api.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
class GAFailure(Exception):
|
||||
pass
|
||||
|
||||
def add_event(request, action, category=None, label=None, value=None, fail_silently=False):
|
||||
"""
|
||||
Attempt to add a message to the request using the 'messages' app.
|
||||
"""
|
||||
try:
|
||||
events = request._ga_events
|
||||
except AttributeError:
|
||||
if not hasattr(request, 'META'):
|
||||
raise TypeError(
|
||||
"add_message() argument must be an HttpRequest object, not "
|
||||
"'%s'." % request.__class__.__name__
|
||||
)
|
||||
if not fail_silently:
|
||||
raise GAFailure(
|
||||
'You cannot add messages without installing '
|
||||
'django.contrib.messages.middleware.MessageMiddleware'
|
||||
)
|
||||
else:
|
||||
return events.add(action, category, label, value)
|
||||
|
||||
|
||||
def get_events(request):
|
||||
"""
|
||||
Return the message storage on the request if it exists, otherwise return
|
||||
an empty list.
|
||||
"""
|
||||
if not hasattr(request, '_ga_events'):
|
||||
return []
|
||||
next_event = request._ga_events.pop()
|
||||
while next_event is not None:
|
||||
yield next_event
|
||||
next_event = request._ga_events.pop();
|
5
simple_ga/apps.py
Normal file
5
simple_ga/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SimpleGaConfig(AppConfig):
|
||||
name = 'simple_ga'
|
11
simple_ga/context_processors.py
Normal file
11
simple_ga/context_processors.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from simple_ga.api import get_events
|
||||
|
||||
|
||||
def events(request):
|
||||
"""
|
||||
Return a lazy 'messages' context variable as well as
|
||||
'DEFAULT_MESSAGE_LEVELS'.
|
||||
"""
|
||||
return {
|
||||
'ga_events': get_events(request),
|
||||
}
|
130
simple_ga/middleware.py
Normal file
130
simple_ga/middleware.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
import json
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
|
||||
class GAEvent:
|
||||
def __init__(self, action, category=None, label=None, value=None):
|
||||
self.action = action
|
||||
self.category = category
|
||||
self.label = label
|
||||
self.value = value
|
||||
|
||||
def gtag(self):
|
||||
return mark_safe(
|
||||
"gtag('event', '%(action)s', {'event_category' : '%(category)s', 'event_label' : '%(label)s' }, 'event_value' : '%(value)s' });" % {
|
||||
'action': self.action,
|
||||
'category': self.category,
|
||||
'label': self.label,
|
||||
'value': self.value,
|
||||
})
|
||||
|
||||
class EventEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Compactly serialize instances of the ``Message`` class as JSON.
|
||||
"""
|
||||
message_key = '__json_message'
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, GAEvent):
|
||||
event = {
|
||||
'type': 'GAEvent',
|
||||
'action': obj.action,
|
||||
'category': obj.category,
|
||||
'label': obj.label,
|
||||
'value': obj.value,
|
||||
}
|
||||
return event
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
class EventDecoder(json.JSONDecoder):
|
||||
"""
|
||||
Decode JSON that includes serialized ``Message`` instances.
|
||||
"""
|
||||
|
||||
def process_events(self, obj):
|
||||
if isinstance(obj, list) and obj:
|
||||
return [self.process_events(item) for item in obj]
|
||||
if isinstance(obj, dict):
|
||||
if getattr(obj, 'type', None) == 'GAEvent':
|
||||
return GAEvent(**obj)
|
||||
return {key: self.process_events(value)
|
||||
for key, value in obj.items()}
|
||||
return obj
|
||||
|
||||
def decode(self, s, **kwargs):
|
||||
decoded = super().decode(s, **kwargs)
|
||||
return self.process_events(decoded)
|
||||
|
||||
class EventStorage:
|
||||
session_key = '_ga_events'
|
||||
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
self._ga_events = self.load()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._ga_events)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._ga_events)
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._ga_events
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Retrieve a list of resume points from the request's session.
|
||||
"""
|
||||
if self.session_key not in self.request.session:
|
||||
return []
|
||||
return self.deserialize_events(self.request.session.get(self.session_key))
|
||||
|
||||
def store(self):
|
||||
"""
|
||||
Store a list of resume points to the request's session.
|
||||
"""
|
||||
if self._ga_events:
|
||||
self.request.session[self.session_key] = self.serialize_events(self._ga_events)
|
||||
else:
|
||||
self.request.session.pop(self.session_key, None)
|
||||
return []
|
||||
|
||||
def serialize_events(self, events):
|
||||
encoder = EventEncoder(separators=(',', ':'))
|
||||
return encoder.encode(events)
|
||||
|
||||
def deserialize_events(self, data):
|
||||
if data and isinstance(data, str):
|
||||
return json.loads(data, cls=EventDecoder)
|
||||
return data
|
||||
|
||||
def add(self, action, category=None, label=None, value=None):
|
||||
self._ga_events.append(GAEvent(action, category, label, value))
|
||||
|
||||
def pop(self):
|
||||
if len(self._ga_events) > 0:
|
||||
return self._ga_events.pop()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class GAEventMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
Middleware that handles setting resume points in a user flow.
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
request._ga_events = EventStorage(request)
|
||||
|
||||
def process_response(self, request, response):
|
||||
"""
|
||||
Update the storage backend (i.e., save the resume points).
|
||||
|
||||
Raise ValueError if not all resume points could be stored and DEBUG is True.
|
||||
"""
|
||||
# A higher middleware layer may return a request which does not contain
|
||||
# resume storage, so make no assumption that it will be there.
|
||||
if hasattr(request, '_ga_events'):
|
||||
request._ga_events.store()
|
||||
return response
|
0
simple_ga/migrations/__init__.py
Normal file
0
simple_ga/migrations/__init__.py
Normal file
3
simple_ga/tests.py
Normal file
3
simple_ga/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
Loading…
Reference in a new issue