Minor template tweaks
Minor template tweaks

file:a/README.rst -> file:b/README.rst
ckanext-ga-report ckanext-ga-report
================= =================
   
**Status:** Development **Status:** Development
   
**CKAN Version:** 1.7.1+ **CKAN Version:** 1.7.1+
   
   
Overview Overview
-------- --------
   
For creating detailed reports of CKAN analytics, including totals per group. For creating detailed reports of CKAN analytics, including totals per group.
   
Whereas ckanext-googleanalytics focusses on providing page view stats a recent period and for all time (aimed at end users), ckanext-ga-report is more interested in building regular periodic reports (more for site managers to monitor). Whereas ckanext-googleanalytics focusses on providing page view stats a recent period and for all time (aimed at end users), ckanext-ga-report is more interested in building regular periodic reports (more for site managers to monitor).
   
Contents of this extension: Contents of this extension:
   
* Use the CLI tool to download Google Analytics data for each time period into this extension's database tables * Use the CLI tool to download Google Analytics data for each time period into this extension's database tables
   
* Users can view the data as web page reports * Users can view the data as web page reports
   
   
Installation Installation
------------ ------------
   
1. Activate you CKAN python environment and install this extension's software:: 1. Activate you CKAN python environment and install this extension's software::
   
$ pyenv/bin/activate $ pyenv/bin/activate
$ pip install -e git+https://github.com/okfn/ckanext-ga-report.git#egg=ckanext-ga-report $ pip install -e git+https://github.com/datagovuk/ckanext-ga-report.git#egg=ckanext-ga-report
   
2. Ensure you development.ini (or similar) contains the info about your Google Analytics account and configuration:: 2. Ensure you development.ini (or similar) contains the info about your Google Analytics account and configuration::
   
googleanalytics.id = UA-1010101-1 googleanalytics.id = UA-1010101-1
googleanalytics.account = Account name (i.e. data.gov.uk, see top level item at https://www.google.com/analytics) googleanalytics.account = Account name (i.e. data.gov.uk, see top level item at https://www.google.com/analytics)
ga-report.period = monthly ga-report.period = monthly
   
Note that your credentials will be readable by system administrators on your server. Rather than use sensitive account details, it is suggested you give access to the GA account to a new Google account that you create just for this purpose. Note that your credentials will be readable by system administrators on your server. Rather than use sensitive account details, it is suggested you give access to the GA account to a new Google account that you create just for this purpose.
   
3. Set up this extension's database tables using a paster command. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file):: 3. Set up this extension's database tables using a paster command. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file)::
   
$ paster initdb --config=../ckan/development.ini $ paster initdb --config=../ckan/development.ini
   
4. Enable the extension in your CKAN config file by adding it to ``ckan.plugins``:: 4. Enable the extension in your CKAN config file by adding it to ``ckan.plugins``::
   
ckan.plugins = ga-report ckan.plugins = ga-report
   
   
Authorization Authorization
-------------- --------------
   
Before you can access the data, you need to set up the OAUTH details which you can do by following the `instructions <https://developers.google.com/analytics/resources/tutorials/hello-analytics-api>`_ the outcome of which will be a file called credentials.json which should look like credentials.json.template with the relevant fields completed. These steps are below for convenience: Before you can access the data, you need to set up the OAUTH details which you can do by following the `instructions <https://developers.google.com/analytics/resources/tutorials/hello-analytics-api>`_ the outcome of which will be a file called credentials.json which should look like credentials.json.template with the relevant fields completed. These steps are below for convenience:
   
1. Visit the `Google APIs Console <https://code.google.com/apis/console>`_ 1. Visit the `Google APIs Console <https://code.google.com/apis/console>`_
   
2. Sign-in and create a project or use an existing project. 2. Sign-in and create a project or use an existing project.
   
3. In the `Services pane <https://code.google.com/apis/console#:services>`_ , activate Analytics API for your project. If prompted, read and accept the terms of service. 3. In the `Services pane <https://code.google.com/apis/console#:services>`_ , activate Analytics API for your project. If prompted, read and accept the terms of service.
   
4. Go to the `API Access pane <https://code.google.com/apis/console/#:access>`_ 4. Go to the `API Access pane <https://code.google.com/apis/console/#:access>`_
   
5. Click Create an OAuth 2.0 client ID.... 5. Click Create an OAuth 2.0 client ID....
   
6. Fill out the Branding Information fields and click Next. 6. Fill out the Branding Information fields and click Next.
   
7. In Client ID Settings, set Application type to Installed application. 7. In Client ID Settings, set Application type to Installed application.
   
8. Click Create client ID 8. Click Create client ID
   
9. The details you need below are Client ID, Client secret, and Redirect URIs 9. The details you need below are Client ID, Client secret, and Redirect URIs
   
   
Once you have set up your credentials.json file you can generate an oauth token file by using the Once you have set up your credentials.json file you can generate an oauth token file by using the
following command, which will store your oauth token in a file called token.dat once you have finished following command, which will store your oauth token in a file called token.dat once you have finished
giving permission in the browser:: giving permission in the browser::
   
$ paster getauthtoken --config=../ckan/development.ini $ paster getauthtoken --config=../ckan/development.ini
   
   
Tutorial Tutorial
-------- --------
   
Download some GA data and store it in CKAN's db. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file) and specifying the name of your auth file (token.dat by default) from the previous step:: Download some GA data and store it in CKAN's db. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file) and specifying the name of your auth file (token.dat by default) from the previous step::
   
$ paster loadanalytics token.dat latest --config=../ckan/development.ini $ paster loadanalytics token.dat latest --config=../ckan/development.ini
   
The value after the token file is how much data you want to retrieve, this can be The value after the token file is how much data you want to retrieve, this can be
   
* **all** - data for all time (since 2010) * **all** - data for all time (since 2010)
   
* **latest** - (default) just the 'latest' data * **latest** - (default) just the 'latest' data
   
* **YYYY-MM-DD** - just data for all time periods going back to (and including) this date * **YYYY-MM-DD** - just data for all time periods going back to (and including) this date
   
   
   
Software Licence Software Licence
================ ================
   
This software is developed by Cabinet Office. It is Crown Copyright and opened up under the Open Government Licence (OGL) (which is compatible with Creative Commons Attibution License). This software is developed by Cabinet Office. It is Crown Copyright and opened up under the Open Government Licence (OGL) (which is compatible with Creative Commons Attibution License).
   
OGL terms: http://www.nationalarchives.gov.uk/doc/open-government-licence/ OGL terms: http://www.nationalarchives.gov.uk/doc/open-government-licence/
   
import logging import logging
from ckan.lib.base import BaseController, c, render import operator
import report_model from ckan.lib.base import BaseController, c, render, request, response
   
  import sqlalchemy
  from sqlalchemy import func, cast, Integer
  import ckan.model as model
  from ga_model import GA_Url, GA_Stat
   
log = logging.getLogger('ckanext.ga-report') log = logging.getLogger('ckanext.ga-report')
   
   
  def _get_month_name(str):
  import calendar
  from time import strptime
  d = strptime('2012-10', '%Y-%m')
  return '%s %s' % (calendar.month_name[d.tm_mon], d.tm_year)
   
   
  def _month_details(cls):
  months = []
  vals = model.Session.query(cls.period_name).distinct().all()
  for m in vals:
  months.append( (m[0], _get_month_name(m)))
  return sorted(months, key=operator.itemgetter(0), reverse=True)
   
   
class GaReport(BaseController): class GaReport(BaseController):
   
  def csv(self, month):
  import csv
   
  entries = model.Session.query(GA_Stat).\
  filter(GA_Stat.period_name==month).\
  order_by('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"
   
  writer = csv.writer(response)
  writer.writerow(["Period", "Statistic", "Key", "Value"])
   
  for entry in entries:
  writer.writerow([entry.period_name.encode('utf-8'),
  entry.stat_name.encode('utf-8'),
  entry.key.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
  # month names from the values.
  c.months = _month_details(GA_Stat)
   
  # 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 = ''.join([m[1] for m in c.months if m[0]==c.month])
   
  entries = model.Session.query(GA_Stat).\
  filter(GA_Stat.stat_name=='Totals').\
  filter(GA_Stat.period_name==c.month).all()
  c.global_totals = [(s.key, s.value) for s in entries ]
   
  keys = {
  'Browser versions': 'browsers',
  'Operating Systems versions': 'os',
  'Social sources': 'social_networks',
  'Languages': 'languages',
  'Country': 'country'
  }
   
  for k, v in keys.iteritems():
  entries = model.Session.query(GA_Stat).\
  filter(GA_Stat.stat_name==k).\
  filter(GA_Stat.period_name==c.month).\
  order_by('ga_stat.value::int desc').all()
  setattr(c, v, [(s.key, s.value) for s in entries ])
   
   
return render('ga_report/site/index.html') return render('ga_report/site/index.html')
   
   
class GaPublisherReport(BaseController): class GaPublisherReport(BaseController):
  """
  Displays the pageview and visit count for specific publishers based on
  the datasets associated with the publisher.
  """
   
def index(self, id): def index(self):
  # Get the month details by fetching distinct values and determining the
  # month names from the values.
  c.months = _month_details(GA_Url)
   
  # 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 = ''.join([m[1] for m in c.months if m[0]==c.month])
   
  connection = model.Session.connection()
  q = """
  select department_id, sum(pageviews::int) views, sum(visitors::int) visits
  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 read(self, id):
  c.publisher = model.Group.get(id)
  c.top_packages = [] # package, dataset_views in c.top_packages
   
  # Get the month details by fetching distinct values and determining the
  # month names from the values.
  c.months = _month_details(GA_Url)
   
  # 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 = ''.join([m[1] for m in c.months if m[0]==c.month])
   
  entry = model.Session.query(GA_Url).\
  filter(GA_Url.url=='/publisher/%s' % c.publisher.name).\
  filter(GA_Url.period_name==c.month).first()
  c.publisher_page_views = entry.pageviews if entry else 0
   
  entries = model.Session.query(GA_Url).\
  filter(GA_Url.department_id==c.publisher.name).\
  filter(GA_Url.period_name==c.month).\
  order_by('ga_url.pageviews::int desc')[:20]
  for entry in entries:
  if entry.url.startswith('/dataset/'):
  p = model.Package.get(entry.url[len('/dataset/'):])
  c.top_packages.append((p,entry.pageviews,entry.visitors))
   
  return render('ga_report/publisher/read.html')
   
import logging import logging
import ckan.lib.helpers as h import ckan.lib.helpers as h
import ckan.plugins as p import ckan.plugins as p
from ckan.plugins import implements, toolkit from ckan.plugins import implements, toolkit
#import gasnippet #import gasnippet
#import commands #import commands
#import dbutil #import dbutil
   
log = logging.getLogger('ckanext.ga-report') log = logging.getLogger('ckanext.ga-report')
   
class GAReportPlugin(p.SingletonPlugin): class GAReportPlugin(p.SingletonPlugin):
implements(p.IConfigurer, inherit=True) implements(p.IConfigurer, inherit=True)
implements(p.IRoutes, inherit=True) implements(p.IRoutes, inherit=True)
   
def update_config(self, config): def update_config(self, config):
toolkit.add_template_directory(config, 'templates') toolkit.add_template_directory(config, 'templates')
toolkit.add_public_directory(config, 'public') toolkit.add_public_directory(config, 'public')
   
def after_map(self, map): def after_map(self, map):
map.connect( map.connect(
'/data/analytics/usage', '/data/analytics',
controller='ckanext.ga_report.controller:GaReport', controller='ckanext.ga_report.controller:GaReport',
  action='index'
  )
  map.connect(
  '/data/analytics_{month}.csv',
  controller='ckanext.ga_report.controller:GaReport',
  action='csv'
  )
  map.connect(
  '/data/analytics/publisher/',
  controller='ckanext.ga_report.controller:GaPublisherReport',
action='index' action='index'
) )
map.connect( map.connect(
'/data/analytics/publisher/{id}', '/data/analytics/publisher/{id}',
controller='ckanext.ga_report.controller:GaPublisherReport', controller='ckanext.ga_report.controller:GaPublisherReport',
action='index' action='read'
) )
return map return map
   
   
HAI <html xmlns:py="http://genshi.edgewall.org/"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip="">
   
  <py:def function="page_title">Publisher Analytics for ${g.site_title}</py:def>
   
  <py:match path="primarysidebar">
  <li class="widget-container boxed widget_text">
  <h4>Publishers</h4>
  </li>
  </py:match>
   
  <div py:match="content">
  <h1>Publisher Analytics</h1>
  <h2>The top 20 publishers</h2>
   
  <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaPublisherReport',action='index')}" method="get">
  <div class="controls">
  <select name="month">
  <py:for each="val,desc in c.months">
  <option value='${val}' py:attrs="{'selected': 'selected' if c.month == val else None}">${desc}</option>
  </py:for>
  </select>
  <input class="btn button" type='submit' value="Update"/>
  </div>
  </form>
   
  <table class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Publisher</th>
  <th>Dataset Views</th>
  <th>Visits</th>
  </tr>
  <py:for each="publisher, views, visits in c.top_publishers">
  <tr>
  <td>${h.link_to(publisher.title, h.url_for(controller='ckanext.ga_report.controller:GaPublisherReport', action='read', id=publisher.name))}
  </td>
  <td>${views}</td>
  <td>${visits}</td>
  </tr>
  </py:for>
  </table>
   
   
  </div>
   
  <xi:include href="../../layout.html" />
   
  <py:def function="optional_footer">
  <script type='text/javascript'>
  $('.nav-tabs li a').click(function (e) {
  e.preventDefault();
  $(this).tab('show');
  })
  </script>
  </py:def>
   
  </html>
   
   
   
   
  <html xmlns:py="http://genshi.edgewall.org/"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip="">
 
  <py:def function="page_title">Analytics for ${g.site_title}</py:def>
 
  <py:match path="primarysidebar">
  <li class="widget-container boxed widget_text">
  <h4>${c.publisher.title}</h4>
  <p>
  The table shows the top 20 most viewed datasets belonging to ${c.publisher.title}.
  </p>
  <p>
  As well as showing the number of views within ${c.month_desc}, it will also show the
  number of visitors that viewed each dataset.
  </p>
  <p>
  <p>The dataset list page for <a href="${h.url_for(controller='ckanext.dgu.controllers.publisher:PublisherController', action='read', id=c.publisher.name)}">${c.publisher.title}</a> was viewed ${c.publisher_page_views} times during ${c.month_desc}</p>
  </p>
  </li>
  </py:match>
 
  <div py:match="content">
  <h1>Analytics for ${c.publisher.title}</h1>
 
  <h2>Top 20 most viewed datasets</h2>
  <p><em>Note: this data does not include API calls</em></p>
 
  <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaPublisherReport',action='read',id=c.publisher.name)}" method="get">
  <div class="controls">
  <select name="month">
  <py:for each="val,desc in c.months">
  <option value='${val}' py:attrs="{'selected': 'selected' if c.month == val else None}">${desc}</option>
  </py:for>
  </select>
  <input class="btn button" type='submit' value="Update"/>
  </div>
  </form>
 
  <table class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Dataset</th>
  <th>Views</th>
  <th>Visits</th>
  </tr>
  <py:for each="package, views, visits in c.top_packages">
  <tr>
  <td>${h.link_to(package.title or package.name, h.url_for(controller='package', action='read', id=package.name))}
  </td>
  <td>${views}</td>
  <td>${visits}</td>
  </tr>
  </py:for>
  </table>
 
 
  </div>
 
  <xi:include href="../../layout.html" />
  </html>
 
 
 
 
HAI Site <html xmlns:py="http://genshi.edgewall.org/"
  xmlns:i18n="http://genshi.edgewall.org/i18n"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  py:strip="">
   
  <py:def function="page_title">Site analytics</py:def>
   
  <py:match path="primarysidebar">
  <li class="widget-container boxed widget_text">
  <h4>Statistics</h4>
  </li>
  <p>It is possible to<a href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv',month=c.month)}">export the data</a> as CSV, which contains all of the information for ${c.month_desc}</p>
   
  </py:match>
   
  <div py:match="content">
  <h1>Site statistics</h1>
   
  <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
  <div class="controls">
  <select name="month">
  <py:for each="val,desc in c.months">
  <option value='${val}' py:attrs="{'selected': 'selected' if c.month == val else None}">${desc}</option>
  </py:for>
  </select>
  <input class="btn button" type='submit' value="Update"/>
  </div>
  </form>
   
  <div class="tabbable">
  <ul class="nav nav-tabs">
  <li class="active"><a href="#totals" data-toggle="tab">Totals</a></li>
  <li><a href="#browsers" data-toggle="tab">Browsers</a></li>
  <li><a href="#os" data-toggle="tab">Operating Systems</a></li>
  <li><a href="#social_networks" data-toggle="tab">Social Networks</a></li>
  <li><a href="#languages" data-toggle="tab">Languages</a></li>
  <li><a href="#country" data-toggle="tab">Country</a></li>
  </ul>
  <div class="tab-content">
  <div class="tab-pane active" id="totals">
  <table class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Name</th>
  <th>Value</th>
  </tr>
  <py:for each="name, value in c.global_totals">
  <tr>
  <td>${name}</td>
  <td>${value}</td>
  </tr>
  </py:for>
  </table>
  </div>
  <div class="tab-pane" id="browsers">
  <table class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Name</th>
  <th>Value</th>
  </tr>
  <py:for each="name, value in c.browsers">
  <tr>
  <td>${name}</td>
  <td>${value}</td>
  </tr>
  </py:for>
  </table>
  </div>
  <div class="tab-pane" id="os">
  <table class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Name</th>
  <th>Value</th>
  </tr>
  <py:for each="name, value in c.os">
  <tr>
  <td>${name}</td>
  <td>${value}</td>
  </tr>
  </py:for>
  </table>
  </div>
  <div class="tab-pane" id="social_networks">
  <table class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Name</th>
  <th>Value</th>
  </tr>
  <py:for each="name, value in c.social_networks">
  <tr>
  <td>${name}</td>
  <td>${value}</td>
  </tr>
  </py:for>
  </table>
  </div>
  <div class="tab-pane" id="languages">
  <table class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Name</th>
  <th>Value</th>
  </tr>
  <py:for each="name, value in c.languages">
  <tr>
  <td>${name}</td>
  <td>${value}</td>
  </tr>
  </py:for>
  </table>
  </div>
  <div class="tab-pane" id="country">
  <table class="table table-condensed table-bordered table-striped">
  <tr>
  <th>Name</th>
  <th>Value</th>
  </tr>
  <py:for each="name, value in c.country">
  <tr>
  <td>${name}</td>
  <td>${value}</td>
  </tr>
  </py:for>
  </table>
  </div>
   
   
  </div>
  </div>
   
   
   
  </div>
   
  <xi:include href="../../layout.html" />
   
  <py:def function="optional_footer">
  <script type='text/javascript'>
  $('.nav-tabs li a').click(function (e) {
  e.preventDefault();
  $(this).tab('show');
  })
  </script>
  </py:def>
  </html>
   
   
   
   
file:a/setup.py -> file:b/setup.py
from setuptools import setup, find_packages from setuptools import setup, find_packages
import sys, os import sys, os
   
version = '0.1' version = '0.1'
   
setup( setup(
name='ckanext-ga-report', name='ckanext-ga-report',
version=version, version=version,
description="GA reporting for CKAN", description="GA reporting for CKAN",
long_description="""\ long_description="""\
""", """,
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='', keywords='',
author='David Read', author='David Read',
author_email='david.read@hackneyworkshop.com', author_email='david.read@hackneyworkshop.com',
url='', url='',
license='', license='',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
namespace_packages=['ckanext', 'ckanext.ga_report'], namespace_packages=['ckanext', 'ckanext.ga_report'],
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
install_requires=[ install_requires=[
'gdata', 'gdata',
'google-api-python-client' 'google-api-python-client'
], ],
entry_points=\ entry_points=\
""" """
[ckan.plugins] [ckan.plugins]
# Add plugins here, eg # Add plugins here, eg
ga-report=ckanext.ga_report.plugin:GAReportPlugin ga-report=ckanext.ga_report.plugin:GAReportPlugin
   
[paste.paster_command] [paste.paster_command]
loadanalytics = ckanext.ga_report.command:LoadAnalytics loadanalytics = ckanext.ga_report.command:LoadAnalytics
initdb = ckanext.ga_report.command:InitDB initdb = ckanext.ga_report.command:InitDB
  getauthtoken = ckanext.ga_report.command:GetAuthToken
""", """,
) )