Removed some unused commented out code
Removed some unused commented out code

file:a/.gitignore -> file:b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,10 @@
 develop-eggs
 .installed.cfg
 
+# Private info
+credentials.json
+token.dat
+
 # Installer logs
 pip-log.txt
 

file:a/README.rst -> file:b/README.rst
--- a/README.rst
+++ b/README.rst
@@ -31,11 +31,10 @@
 2. Ensure you development.ini (or similar) contains the info about your Google Analytics account and configuration::
 
       googleanalytics.id = UA-1010101-1
-      googleanalytics.username = googleaccount@gmail.com
-      googleanalytics.password = googlepassword
+      googleanalytics.account = Account name (i.e. data.gov.uk, see top level item at https://www.google.com/analytics)
       ga-report.period = monthly
 
-   Note that your password 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)::
 
@@ -46,12 +45,52 @@
     ckan.plugins = ga-report
 
 
+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:
+
+1. Visit the `Google APIs Console <https://code.google.com/apis/console>`_
+
+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.
+
+4. Go to the `API Access pane <https://code.google.com/apis/console/#:access>`_
+
+5. Click Create an OAuth 2.0 client ID....
+
+6. Fill out the Branding Information fields and click Next.
+
+7. In Client ID Settings, set Application type to Installed application.
+
+8. Click Create client ID
+
+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
+following command, which will store your oauth token in a file called token.dat once you have finished
+giving permission in the browser::
+
+    $ paster getauthtoken --config=../ckan/development.ini
+
+
 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)::
+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 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
+
+* **all**         - data for all time (since 2010)
+
+* **latest**      - (default) just the 'latest' data
+
+* **YYYY-MM-DD**  - just data for all time periods going back to (and including) this date
+
 
 
 Software Licence

--- a/ckanext/ga_report/command.py
+++ b/ckanext/ga_report/command.py
@@ -1,7 +1,10 @@
 import logging
+import datetime
 
 from ckan.lib.cli import CkanCommand
-# No other CKAN imports allowed until _load_config is run, or logging is disabled
+# No other CKAN imports allowed until _load_config is run,
+# or logging is disabled
+
 
 class InitDB(CkanCommand):
     """Initialise the extension's database tables
@@ -23,13 +26,44 @@
         ga_model.init_tables()
         log.info("DB tables are setup")
 
+
+class GetAuthToken(CkanCommand):
+    """ Get's the Google auth token
+
+    Usage: paster getauthtoken <credentials_file>
+
+    Where <credentials_file> is the file name containing the details
+    for the service (obtained from https://code.google.com/apis/console).
+    By default this is set to credentials.json
+    """
+    summary = __doc__.split('\n')[0]
+    usage = __doc__
+    max_args = 0
+    min_args = 0
+
+    def command(self):
+        """
+        In this case we don't want a valid service, but rather just to
+        force the user through the auth flow. We allow this to complete to
+        act as a form of verification instead of just getting the token and
+        assuming it is correct.
+        """
+        from ga_auth import init_service
+        init_service('token.dat',
+                      self.args[0] if self.args
+                                   else 'credentials.json')
+
+
 class LoadAnalytics(CkanCommand):
     """Get data from Google Analytics API and save it
     in the ga_model
 
-    Usage: paster loadanalytics <time-period>
+    Usage: paster loadanalytics <tokenfile> <time-period>
 
-    Where <time-period> is:
+    Where <tokenfile> is the name of the auth token file from
+    the getauthtoken step.
+
+    And where <time-period> is:
         all         - data for all time
         latest      - (default) just the 'latest' data
         YYYY-MM-DD  - just data for all time periods going
@@ -37,16 +71,26 @@
     """
     summary = __doc__.split('\n')[0]
     usage = __doc__
-    max_args = 1
-    min_args = 0
+    max_args = 2
+    min_args = 1
 
     def command(self):
         self._load_config()
 
         from download_analytics import DownloadAnalytics
-        downloader = DownloadAnalytics()
-        
-        time_period = self.args[0] if self.args else 'latest'
+        from ga_auth import (init_service, get_profile_id)
+
+        try:
+            svc = init_service(self.args[0], None)
+        except TypeError:
+            print ('Have you correctly run the getauthtoken task and '
+                   'specified the correct file here')
+            return
+
+        downloader = DownloadAnalytics(svc, profile_id=get_profile_id(svc))
+
+        time_period = self.args[1] if self.args and len(self.args) > 1 \
+            else 'latest'
         if time_period == 'all':
             downloader.all_()
         elif time_period == 'latest':
@@ -55,4 +99,3 @@
             since_date = datetime.datetime.strptime(time_period, '%Y-%m-%d')
             downloader.since_date(since_date)
 
-

--- a/ckanext/ga_report/download_analytics.py
+++ b/ckanext/ga_report/download_analytics.py
@@ -1,10 +1,12 @@
+import os
 import logging
 import datetime
 
 from pylons import config
 
 import ga_model
-from ga_client import GA
+
+#from ga_client import GA
 
 log = logging.getLogger('ckanext.ga-report')
 
@@ -12,12 +14,16 @@
 
 class DownloadAnalytics(object):
     '''Downloads and stores analytics info'''
-    def __init__(self):
+
+    def __init__(self, service=None, profile_id=None):
         self.period = config['ga-report.period']
-    
+        self.service = service
+        self.profile_id = profile_id
+
+
     def all_(self):
-        pass
-    
+        self.since_date(datetime.datetime(2010, 1, 1))
+
     def latest(self):
         if self.period == 'monthly':
             # from first of this month to today
@@ -49,8 +55,8 @@
                     break
                 elif first_of_the_month < first_of_this_month:
                     in_the_next_month = first_of_the_month + datetime.timedelta(40)
-                    last_of_the_month == datetime.datetime(in_the_next_month.year,
-                                                           in_the_next_month.month, a)\
+                    last_of_the_month = datetime.datetime(in_the_next_month.year,
+                                                           in_the_next_month.month, 1)\
                                                            - datetime.timedelta(1)
                     periods.append((now.strftime(FORMAT_MONTH), 0,
                                     first_of_the_month, last_of_the_month))
@@ -71,7 +77,7 @@
             return period_name + ' (up to %ith)' % period_complete_day
         else:
             return period_name
-        
+
 
     def download_and_store(self, periods):
         for period_name, period_complete_day, start_date, end_date in periods:
@@ -84,31 +90,37 @@
                      self.get_full_period_name(period_name, period_complete_day))
             self.store(period_name, period_complete_day, data)
 
-    @classmethod
-    def download(cls, start_date, end_date):
+
+    def download(self, start_date, end_date):
         '''Get data from GA for a given time period'''
         start_date = start_date.strftime('%Y-%m-%d')
         end_date = end_date.strftime('%Y-%m-%d')
-        # url
-        #query = 'ga:pagePath=~^%s,ga:pagePath=~^%s' % \
-        #        (PACKAGE_URL, self.resource_url_tag)
-        query = 'ga:pagePath=~^/dataset/'
+        query = 'ga:pagePath=~/dataset/[a-z0-9-]+$'
         metrics = 'ga:uniquePageviews'
         sort = '-ga:uniquePageviews'
-        for entry in GA.ga_query(query_filter=query,
-                                 from_date=start_date,
+
+        # Supported query params at
+        # https://developers.google.com/analytics/devguides/reporting/core/v3/reference
+        results = self.service.data().ga().get(
+                                 ids='ga:' + self.profile_id,
+                                 filters=query,
+                                 start_date=start_date,
                                  metrics=metrics,
                                  sort=sort,
-                                 to_date=end_date):
-            print entry
-            import pdb; pdb.set_trace()
-            for dim in entry.dimension:
-                if dim.name == "ga:pagePath":
-                    package = dim.value
-                    count = entry.get_metric(
-                        'ga:uniquePageviews').value or 0
-                    packages[package] = int(count)
-        return packages
+                                 dimensions="ga:pagePath",
+                                 max_results=10000,
+                                 end_date=end_date).execute()
+
+        if os.getenv('DEBUG'):
+            import pprint
+            pprint.pprint(results)
+            print 'Total results: %s' % results.get('totalResults')
+
+        packages = []
+        for entry in results.get('rows'):
+            (loc,size,) = entry
+            packages.append( ('http:/' + loc,size, '',) ) # Temporary hack
+        return dict(url=packages)
 
     def store(self, period_name, period_complete_day, data):
         if 'url' in data:

--- /dev/null
+++ b/ckanext/ga_report/ga_auth.py
@@ -1,1 +1,70 @@
+import os
+import httplib2
+from apiclient.discovery import build
+from oauth2client.client import flow_from_clientsecrets
+from oauth2client.file import Storage
+from oauth2client.tools import run
 
+from pylons import config
+
+
+def _prepare_credentials(token_filename, credentials_filename):
+    """
+    Either returns the user's oauth credentials or uses the credentials
+    file to generate a token (by forcing the user to login in the browser)
+    """
+    storage = Storage(token_filename)
+    credentials = storage.get()
+
+    if credentials is None or credentials.invalid:
+        flow = flow_from_clientsecrets(credentials_filename,
+                scope='https://www.googleapis.com/auth/analytics.readonly',
+                message="Can't find the credentials file")
+        credentials = run(flow, storage)
+
+    return credentials
+
+
+def init_service(token_file, credentials_file):
+    """
+    Given a file containing the user's oauth token (and another with
+    credentials in case we need to generate the token) will return a
+    service object representing the analytics API.
+    """
+    http = httplib2.Http()
+
+    credentials = _prepare_credentials(token_file, credentials_file)
+    http = credentials.authorize(http)  # authorize the http object
+
+    return build('analytics', 'v3', http=http)
+
+
+def get_profile_id(service):
+    """
+    Get the profile ID for this user and the service specified by the
+    'googleanalytics.id' configuration option. This function iterates
+    over all of the accounts available to the user who invoked the
+    service to find one where the account name matches (in case the
+    user has several).
+    """
+    accounts = service.management().accounts().list().execute()
+
+    if not accounts.get('items'):
+        return None
+
+    accountName = config.get('googleanalytics.account')
+    webPropertyId = config.get('googleanalytics.id')
+    for acc in accounts.get('items'):
+        if acc.get('name') == accountName:
+            accountId = acc.get('id')
+
+    webproperties = service.management().webproperties().list(accountId=accountId).execute()
+
+    profiles = service.management().profiles().list(
+        accountId=accountId, webPropertyId=webPropertyId).execute()
+
+    if profiles.get('items'):
+        return profiles.get('items')[0].get('id')
+
+    return None
+

--- a/ckanext/ga_report/ga_model.py
+++ b/ckanext/ga_report/ga_model.py
@@ -3,30 +3,45 @@
 
 from sqlalchemy import Table, Column, MetaData
 from sqlalchemy import types
-from sqlalchemy.sql import select, text
+from sqlalchemy.sql import select
+from sqlalchemy.orm import mapper
 from sqlalchemy import func
 
 import ckan.model as model
-from ckan.model.types import JsonType
 from ckan.lib.base import *
 
 def make_uuid():
     return unicode(uuid.uuid4())
 
+
+
+class GA_Url(object):
+
+    def __init__(self, **kwargs):
+        for k,v in kwargs.items():
+            setattr(self, k, v)
+
+
+metadata = MetaData()
+url_table = Table('ga_url', metadata,
+                      Column('id', types.UnicodeText, primary_key=True,
+                             default=make_uuid),
+                      Column('period_name', types.UnicodeText),
+                      Column('period_complete_day', types.Integer),
+                      Column('metric', types.UnicodeText),
+                      Column('value', types.UnicodeText),
+                      Column('url', types.UnicodeText),
+                      Column('department_id', types.UnicodeText),
+                )
+mapper(GA_Url, url_table)
+
+
 def init_tables():
-    metadata = MetaData()
-    package_stats = Table('ga_url', metadata,
-                          Column('id', types.UnicodeText, primary_key=True, default=make_uuid),
-                          Column('period_name', types.UnicodeText),
-                          Column('period_complete_day', types.Integer),
-                          Column('visits', types.Integer),
-                          Column('group_id', types.String(60)),
-                          Column('next_page', JsonType),
-                          )
     metadata.create_all(model.meta.engine)
 
 
 cached_tables = {}
+
 
 def get_table(name):
     if name not in cached_tables:
@@ -46,6 +61,7 @@
     url = re.sub('https?://(www\.)?data.gov.uk', '', url)
     return url
 
+
 def _get_department_id_of_url(url):
     # e.g. /dataset/fuel_prices
     # e.g. /dataset/fuel_prices/resource/e63380d4
@@ -56,36 +72,34 @@
         if dataset:
             publisher_groups = dataset.get_groups('publisher')
             if publisher_groups:
-                return publisher_groups[0].id
+                return publisher_groups[0].name
+
 
 def update_url_stats(period_name, period_complete_day, url_data):
-    table = get_table('ga_url')
-    connection = model.Session.connection()
     for url, views, next_page in url_data:
         url = _normalize_url(url)
         department_id = _get_department_id_of_url(url)
+
         # see if the row for this url & month is in the table already
-        s = select([func.count(id_col)],
-                   table.c.period_name == period_name,
-                   table.c.url == url)
-        count = connection.execute(s).fetchone()
-        if count and count[0]:
-            # update the row
-            connection.execute(table.update()\
-                .where(table.c.period_name == period_name,
-                       table.c.url == url)\
-                .values(period_complete_day=period_complete_day,
-                        views=views,
-                        department_id=department_id,
-                        next_page=next_page))
+        item = model.Session.query(GA_Url).\
+            filter(GA_Url.period_name==period_name).\
+            filter(GA_Url.url==url).\
+            filter(GA_Url.metric == 'Total views').first()
+        if item:
+            item.period_name = period_complete_day = period_complete_day
+            item.value = views
+            item.department_id = department_id
+            model.Session.add(item)
         else:
             # create the row
-            values = {'period_name': period_name,
+            values = {'id': make_uuid(),
+                      'period_name': period_name,
                       'period_complete_day': period_complete_day,
                       'url': url,
-                      'views': views,
-                      'department_id': department_id,
-                      'next_page': next_page}
-            connection.execute(stats.insert()\
-                               .values(**values))
+                      'value': views,
+                      'metric': 'Total views',
+                      'department_id': department_id
+                     }
+            model.Session.add(GA_Url(**values))
+        model.Session.commit()
 

--- /dev/null
+++ b/ckanext/ga_report/tests/test_api.py
@@ -1,1 +1,38 @@
+import os
+import datetime
+from nose.tools import assert_equal
+from ckanext.ga_report.download_analytics import DownloadAnalytics
+from ckanext.ga_report.ga_auth import (init_service, get_profile_id)
+from ckanext.ga_report.ga_model import init_tables
 
+class TestAPI:
+
+    @classmethod
+    def setup_class(cls):
+        if not os.path.exists("token.dat") or not os.path.exists("credentials.json"):
+            print '*' * 60
+            print "Tests may not run without first having run the auth process"
+            print '*' * 60
+        init_tables()
+
+    @classmethod
+    def teardown_class(cls):
+        pass
+
+    def test_latest(self):
+        svc = init_service("token.dat", "credentials.json")
+        try:
+            downloader = DownloadAnalytics(svc, profile_id=get_profile_id(svc))
+            downloader.latest()
+        except Exception as e:
+            assert False, e
+
+
+    def test_since(self):
+        svc = init_service("token.dat", "credentials.json")
+        downloader = DownloadAnalytics(svc, profile_id=get_profile_id(svc))
+        try:
+            downloader.since_date(datetime.datetime.now() - datetime.timedelta(days=-30))
+        except Exception as e:
+            assert False, e
+

--- /dev/null
+++ b/ckanext/ga_report/tests/test_auth.py
@@ -1,1 +1,41 @@
+import os
+from nose.tools import assert_equal
+from ckanext.ga_report.ga_auth import (init_service, get_profile_id)
 
+class TestAuth:
+
+    @classmethod
+    def setup_class(cls):
+        if not os.path.exists("token.dat") or not os.path.exists("credentials.json"):
+            print '*' * 60
+            print "Tests may not run without first having run the auth process"
+            print '*' * 60
+
+    @classmethod
+    def teardown_class(cls):
+        pass
+
+    def test_init(self):
+        try:
+            res = init_service(None, None)
+            assert False, "Init service worked without credentials or tokens"
+        except TypeError:
+            pass
+
+    def test_init_with_token(self):
+        res = init_service("token.dat", None)
+        assert res is not None, "Init service worked without credentials"
+
+    def test_init_with_token_and_credentials(self):
+        res = init_service("token.dat", "credentials.json")
+        assert res is not None, "Unable to create service with valid details"
+
+    def test_init_with_redentials(self):
+        #res = init_service("", "credentials.json")
+        # Triggers the auth flow via the browser
+        pass
+
+    def test_get_profile(self):
+        svc = init_service("token.dat", "credentials.json")
+        profile = get_profile_id(svc)
+        assert profile is not None, "Unable to find a profile given configured UA id and user details"

--- /dev/null
+++ b/credentials.json.template
@@ -1,1 +1,11 @@
+{
+  "installed": {
+    "client_id": "",
+    "client_secret": "",
+    "redirect_uris": [""],
+    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+    "token_uri": "https://accounts.google.com/o/oauth2/token"
+  }
+}
 
+

file:a/setup.py -> file:b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -20,7 +20,8 @@
 	include_package_data=True,
 	zip_safe=False,
 	install_requires=[
-		'gdata'
+		'gdata',
+		'google-api-python-client'
 	],
 	entry_points=\
 	"""