Removing cruft
[ckanext-ga-report.git] / ckanext / ga_report / controller.py
blob:a/ckanext/ga_report/controller.py -> blob:b/ckanext/ga_report/controller.py
  import re
  import csv
  import sys
  import json
import logging import logging
import operator import operator
from ckan.lib.base import BaseController, c, render, request, response import collections
  from ckan.lib.base import (BaseController, c, g, render, request, response, abort)
   
import sqlalchemy import sqlalchemy
from sqlalchemy import func, cast, Integer from sqlalchemy import func, cast, Integer
import ckan.model as model import ckan.model as model
from ga_model import GA_Url, GA_Stat from ga_model import GA_Url, GA_Stat, GA_ReferralStat, GA_Publisher
   
log = logging.getLogger('ckanext.ga-report') log = logging.getLogger('ckanext.ga-report')
   
  DOWNLOADS_AVAILABLE_FROM = '2012-12'
   
def _get_month_name(strdate): def _get_month_name(strdate):
import calendar import calendar
from time import strptime from time import strptime
d = strptime(strdate, '%Y-%m') d = strptime(strdate, '%Y-%m')
return '%s %s' % (calendar.month_name[d.tm_mon], d.tm_year) return '%s %s' % (calendar.month_name[d.tm_mon], d.tm_year)
   
  def _get_unix_epoch(strdate):
def _month_details(cls): from time import strptime,mktime
  d = strptime(strdate, '%Y-%m')
  return int(mktime(d))
   
  def _month_details(cls, stat_key=None):
  '''
  Returns a list of all the periods for which we have data, unfortunately
  knows too much about the type of the cls being passed as GA_Url has a
  more complex query
   
  This may need extending if we add a period_name to the stats
  '''
months = [] months = []
vals = model.Session.query(cls.period_name).distinct().all() day = None
   
  q = model.Session.query(cls.period_name,cls.period_complete_day)\
  .filter(cls.period_name!='All').distinct(cls.period_name)
  if stat_key:
  q= q.filter(cls.stat_name==stat_key)
   
  vals = q.order_by("period_name desc").all()
   
  if vals and vals[0][1]:
  day = int(vals[0][1])
  ordinal = 'th' if 11 <= day <= 13 \
  else {1:'st',2:'nd',3:'rd'}.get(day % 10, 'th')
  day = "{day}{ordinal}".format(day=day, ordinal=ordinal)
   
for m in vals: for m in vals:
months.append( (m[0], _get_month_name(m[0]))) months.append( (m[0], _get_month_name(m[0])))
return sorted(months, key=operator.itemgetter(0), reverse=True)  
  return months, day
   
   
class GaReport(BaseController): class GaReport(BaseController):
   
def csv(self, month): def csv(self, month):
import csv import csv
   
entries = model.Session.query(GA_Stat).\ q = model.Session.query(GA_Stat).filter(GA_Stat.stat_name!='Downloads')
filter(GA_Stat.period_name==month).\ if month != 'all':
order_by('GA_Stat.stat_name, GA_Stat.key').all() q = q.filter(GA_Stat.period_name==month)
  entries = q.order_by('GA_Stat.period_name, GA_Stat.stat_name, GA_Stat.key').all()
response.headers['Content-disposition'] = 'attachment; filename=dgu_analytics_%s.csv' % (month)  
response.headers['Content-Type'] = "text/csv; charset=utf-8" response.headers['Content-Type'] = "text/csv; charset=utf-8"
  response.headers['Content-Disposition'] = str('attachment; filename=stats_%s.csv' % (month,))
   
writer = csv.writer(response) writer = csv.writer(response)
writer.writerow(["Period", "Statistic", "Key", "Value"]) writer.writerow(["Period", "Statistic", "Key", "Value"])
   
for entry in entries: for entry in entries:
writer.writerow([entry.period_name.encode('utf-8'), writer.writerow([entry.period_name.encode('utf-8'),
entry.stat_name.encode('utf-8'), entry.stat_name.encode('utf-8'),
entry.key.encode('utf-8'), entry.key.encode('utf-8'),
entry.value.encode('utf-8')]) entry.value.encode('utf-8')])
   
   
def index(self): def index(self):
   
# Get the month details by fetching distinct values and determining the # Get the month details by fetching distinct values and determining the
# month names from the values. # month names from the values.
c.months = _month_details(GA_Stat) c.months, c.day = _month_details(GA_Stat)
   
# Work out which month to show, based on query params of the first item # Work out which month to show, based on query params of the first item
c.month = request.params.get('month', c.months[0][0] if c.months else '') c.month_desc = 'all months'
c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) c.month = request.params.get('month', '')
  if c.month:
entries = model.Session.query(GA_Stat).\ c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
filter(GA_Stat.stat_name=='Totals').\  
filter(GA_Stat.period_name==c.month).\ q = model.Session.query(GA_Stat).\
order_by('ga_stat.key').all() filter(GA_Stat.stat_name=='Totals')
c.global_totals = [(s.key, s.value) for s in entries ] if c.month:
  q = q.filter(GA_Stat.period_name==c.month)
  entries = q.order_by('ga_stat.key').all()
   
  def clean_key(key, val):
  if key in ['Average time on site', 'Pages per visit', 'New visits', 'Bounce rate (home page)']:
  val = "%.2f" % round(float(val), 2)
  if key == 'Average time on site':
  mins, secs = divmod(float(val), 60)
  hours, mins = divmod(mins, 60)
  val = '%02d:%02d:%02d (%s seconds) ' % (hours, mins, secs, val)
  if key in ['New visits','Bounce rate (home page)']:
  val = "%s%%" % val
  if key in ['Total page views', 'Total visits']:
  val = int(val)
   
  return key, val
   
  # Query historic values for sparkline rendering
  sparkline_query = model.Session.query(GA_Stat)\
  .filter(GA_Stat.stat_name=='Totals')\
  .order_by(GA_Stat.period_name)
  sparkline_data = {}
  for x in sparkline_query:
  sparkline_data[x.key] = sparkline_data.get(x.key,[])
  key, val = clean_key(x.key,float(x.value))
  tooltip = '%s: %s' % (_get_month_name(x.period_name), val)
  sparkline_data[x.key].append( (tooltip,x.value) )
  # Trim the latest month, as it looks like a huge dropoff
  for key in sparkline_data:
  sparkline_data[key] = sparkline_data[key][:-1]
   
  c.global_totals = []
  if c.month:
  for e in entries:
  key, val = clean_key(e.key, e.value)
  sparkline = sparkline_data[e.key]
  c.global_totals.append((key, val, sparkline))
  else:
  d = collections.defaultdict(list)
  for e in entries:
  d[e.key].append(float(e.value))
  for k, v in d.iteritems():
  if k in ['Total page views', 'Total visits']:
  v = sum(v)
  else:
  v = float(sum(v))/float(len(v))
  sparkline = sparkline_data[k]
  key, val = clean_key(k,v)
   
  c.global_totals.append((key, val, sparkline))
  # Sort the global totals into a more pleasant order
  def sort_func(x):
  key = x[0]
  total_order = ['Total page views','Total visits','Pages per visit']
  if key in total_order:
  return total_order.index(key)
  return 999
  c.global_totals = sorted(c.global_totals, key=sort_func)
   
keys = { keys = {
'Browser versions': 'browsers', 'Browser versions': 'browser_versions',
'Operating Systems versions': 'os', 'Browsers': 'browsers',
  'Operating Systems versions': 'os_versions',
  'Operating Systems': 'os',
'Social sources': 'social_networks', 'Social sources': 'social_networks',
'Languages': 'languages', 'Languages': 'languages',
'Country': 'country' 'Country': 'country'
} }
   
  def shorten_name(name, length=60):
  return (name[:length] + '..') if len(name) > 60 else name
   
  def fill_out_url(url):
  import urlparse
  return urlparse.urljoin(g.site_url, url)
   
  c.social_referrer_totals, c.social_referrers = [], []
  q = model.Session.query(GA_ReferralStat)
  q = q.filter(GA_ReferralStat.period_name==c.month) if c.month else q
  q = q.order_by('ga_referrer.count::int desc')
  for entry in q.all():
  c.social_referrers.append((shorten_name(entry.url), fill_out_url(entry.url),
  entry.source,entry.count))
   
  q = model.Session.query(GA_ReferralStat.url,
  func.sum(GA_ReferralStat.count).label('count'))
  q = q.filter(GA_ReferralStat.period_name==c.month) if c.month else q
  q = q.order_by('count desc').group_by(GA_ReferralStat.url)
  for entry in q.all():
  c.social_referrer_totals.append((shorten_name(entry[0]), fill_out_url(entry[0]),'',
  entry[1]))
   
for k, v in keys.iteritems(): for k, v in keys.iteritems():
entries = model.Session.query(GA_Stat).\ q = model.Session.query(GA_Stat).\
filter(GA_Stat.stat_name==k).\ filter(GA_Stat.stat_name==k).\
filter(GA_Stat.period_name==c.month).\ order_by(GA_Stat.period_name)
order_by('ga_stat.value::int desc').all() # Run the query on all months to gather graph data
setattr(c, v, [(s.key, s.value) for s in entries ]) series = {}
  x_axis = set()
  for stat in q:
  x_val = _get_unix_epoch(stat.period_name)
  series[ stat.key ] = series.get(stat.key,{})
  series[ stat.key ][x_val] = float(stat.value)
  x_axis.add(x_val)
  # Common x-axis for all series. Exclude this month (incomplete data)
  x_axis = sorted(list(x_axis))[:-1]
  # Buffer a rickshaw dataset from the series
  def create_graph(series_name, series_data):
  return {
  'name':series_name,
  'data':[ {'x':x,'y':series_data.get(x,0)} for x in x_axis ]
  }
  rickshaw = [ create_graph(name,data) for name, data in series.items() ]
  rickshaw = sorted(rickshaw,key=lambda x:x['data'][-1]['y'])
  setattr(c, v+'_graph', json.dumps(rickshaw))
   
  # Buffer the tabular data
  if c.month:
  entries = []
  q = q.filter(GA_Stat.period_name==c.month).\
  order_by('ga_stat.value::int desc')
   
  d = collections.defaultdict(int)
  for e in q.all():
  d[e.key] += int(e.value)
  entries = []
  for key, val in d.iteritems():
  entries.append((key,val,))
  entries = sorted(entries, key=operator.itemgetter(1), reverse=True)
   
  # Get the total for each set of values and then set the value as
  # a percentage of the total
  if k == 'Social sources':
  total = sum([x for n,x,graph in c.global_totals if n == 'Total visits'])
  else:
  total = sum([num for _,num in entries])
  setattr(c, v, [(k,_percent(v,total)) for k,v in entries ])
   
return render('ga_report/site/index.html') return render('ga_report/site/index.html')
   
   
class GaPublisherReport(BaseController): class GaDatasetReport(BaseController):
""" """
Displays the pageview and visit count for specific publishers based on Displays the pageview and visit count for datasets
the datasets associated with the publisher. with options to filter by publisher and time period.
""" """
  def publisher_csv(self, month):
def index(self): '''
  Returns a CSV of each publisher with the total number of dataset
  views & visits.
  '''
  c.month = month if not month == 'all' else ''
  response.headers['Content-Type'] = "text/csv; charset=utf-8"
  response.headers['Content-Disposition'] = str('attachment; filename=publishers_%s.csv' % (month,))
   
  writer = csv.writer(response)
  writer.writerow(["Publisher Title", "Publisher Name", "Views", "Visits", "Period Name"])
   
  for publisher,view,visit in _get_top_publishers(None):
  writer.writerow([publisher.title.encode('utf-8'),
  publisher.name.encode('utf-8'),
  view,
  visit,
  month])
   
  def dataset_csv(self, id='all', month='all'):
  '''
  Returns a CSV with the number of views & visits for each dataset.
   
  :param id: A Publisher ID or None if you want for all
  :param month: The time period, or 'all'
  '''
  c.month = month if not month == 'all' else ''
  if id != 'all':
  c.publisher = model.Group.get(id)
  if not c.publisher:
  abort(404, 'A publisher with that name could not be found')
   
  packages = self._get_packages(c.publisher)
  response.headers['Content-Type'] = "text/csv; charset=utf-8"
  response.headers['Content-Disposition'] = \
  str('attachment; filename=datasets_%s_%s.csv' % (c.publisher_name, month,))
   
  writer = csv.writer(response)
  writer.writerow(["Dataset Title", "Dataset Name", "Views", "Visits", "Resource downloads", "Period Name"])
   
  for package,view,visit,downloads in packages:
  writer.writerow([package.title.encode('utf-8'),
  package.name.encode('utf-8'),
  view,
  visit,
  downloads,
  month])
   
  def publishers(self):
  '''A list of publishers and the number of views/visits for each'''
   
# Get the month details by fetching distinct values and determining the # Get the month details by fetching distinct values and determining the
# month names from the values. # month names from the values.
c.months = _month_details(GA_Url) c.months, c.day = _month_details(GA_Url)
   
# Work out which month to show, based on query params of the first item # Work out which month to show, based on query params of the first item
c.month = request.params.get('month', c.months[0][0] if c.months else '') c.month = request.params.get('month', '')
c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) c.month_desc = 'all months'
  if c.month:
connection = model.Session.connection() c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
q = """  
select department_id, sum(pageviews::int) views, sum(visitors::int) visits c.top_publishers = _get_top_publishers()
from ga_url  
where department_id <> ''  
and not url like '/publisher/%%'  
and period_name=%s  
group by department_id order by views desc limit 20;  
"""  
c.top_publishers = []  
res = connection.execute(q, c.month)  
for row in res:  
c.top_publishers.append((model.Group.get(row[0]), row[1], row[2]))  
   
return render('ga_report/publisher/index.html') return render('ga_report/publisher/index.html')
   
  def _get_packages(self, publisher=None, count=-1):
def read(self, id): '''Returns the datasets in order of views'''
c.publisher = model.Group.get(id) have_download_data = True
  month = c.month or 'All'
  if month != 'All':
  have_download_data = month >= DOWNLOADS_AVAILABLE_FROM
   
  q = model.Session.query(GA_Url,model.Package)\
  .filter(model.Package.name==GA_Url.package_id)\
  .filter(GA_Url.url.like('/dataset/%'))
  if publisher:
  q = q.filter(GA_Url.department_id==publisher.name)
  q = q.filter(GA_Url.period_name==month)
  q = q.order_by('ga_url.pageviews::int desc')
  top_packages = []
  if count == -1:
  entries = q.all()
  else:
  entries = q.limit(count)
   
  for entry,package in entries:
  if package:
  # Downloads ....
  if have_download_data:
  dls = model.Session.query(GA_Stat