Move API google analytics and AGLS metadata to seperate extensions
Move API google analytics and AGLS metadata to seperate extensions

file:a/README.rst -> file:b/README.rst
This CKAN Extension customises a CKAN instance for the hosting of data.gov.au. This CKAN Extension customises a CKAN instance for the hosting of data.gov.au.
   
It comprises: It comprises:
   
* A CKAN Extension "plugin" at ``ckanext/datagovau/plugin.py`` which, when * A custom Package edit form that defaults to cc-by licence
loaded, overrides various settings in the core ``ini``-file to provide: * Replaces links with http/https protocol independent versions
* A path to local customisations of the core templates to include AGLS/Dublin Core minimum metadata * Provides HTML to users to embed data previews on their own website
* A custom Package edit form that defaults to cc-by licence * A cut down licenses.json file
* A custom n3/rdf output format  
* Replaces links with http/https protocol independent versions  
* Provides HTML to users to embed data previews on their own website  
   
* A cut down licenses.json file This extension is complimented by ckanext-agls for AGLS metadata and ckanext-googleanalytics for Google Analytics tracking of API usage
   
Installation Installation
============ ============
   
To install this package, from your CKAN virtualenv, run the following from your CKAN base folder (e.g. ``pyenv/``):: To install this package, from your CKAN virtualenv, run the following from your CKAN base folder (e.g. ``pyenv/``)::
   
pip install -e git+https://github.com/okfn/ckanext-datagovau#egg=ckanext-datagovau pip install -e git+https://github.com/okfn/ckanext-datagovau#egg=ckanext-datagovau
   
Then activate it by setting ``ckan.plugins = datagovau`` in your main ``ini``-file. Then activate it by setting ``ckan.plugins = datagovau`` in your main ``ini``-file.
   
To add the cut down licenses.json set ``licenses_group_url = http://%(ckan.site_url)/licenses.json`` To add the cut down licenses.json set ``licenses_group_url = http://%(ckan.site_url)/licenses.json``
or copy ``ckanext/datagovau/theme/public/licenses.json`` to the same folder as your CKAN config ini file or copy ``ckanext/datagovau/theme/public/licenses.json`` to the same folder as your CKAN config ini file
and set ``licenses_group_url = file://%(here)s/licenses.json`` and set ``licenses_group_url = file://%(here)s/licenses.json``
   
   
   
import urllib  
import json  
from pprint import pprint  
import logging  
import ckan.logic as logic  
import hashlib  
import threading  
from ckan.common import _, c, request, response  
from pylons import config  
from webob.multidict import UnicodeMultiDict  
from paste.util.multidict import MultiDict  
 
log = logging.getLogger(__name__)  
 
from ckan.controllers.api import ApiController  
 
class DGAApiController(ApiController):  
 
def _post_analytics(self,user,request_obj_type,request_function,request_id):  
if (config.get('googleanalytics.id') != None):  
data = urllib.urlencode({  
"v":1,  
"tid":config.get('googleanalytics.id'),  
"cid":hashlib.md5(user).hexdigest(),  
"t":"event",  
"dh":c.environ['HTTP_HOST'],  
"dp":c.environ['PATH_INFO'],  
"dr":c.environ.get('HTTP_REFERER',''),  
"ec":"CKAN API Request",  
"ea":request_obj_type+request_function,  
"el":request_id,  
})  
log.debug("Sending API Analytics Data: "+data)  
# send analytics asynchronously  
threading.Thread(target=urllib.urlopen,args=("http://www.google-analytics.com/collect", data)).start()  
 
 
def action(self, logic_function, ver=None):  
try:  
function = logic.get_action(logic_function)  
except Exception,e:  
log.debug(e)  
pass  
try:  
side_effect_free = getattr(function, 'side_effect_free', False)  
request_data = self._get_request_data(try_url_params=side_effect_free)  
if isinstance(request_data, dict):  
id = request_data.get('id','')  
if 'q' in request_data.keys():  
id = request_data['q']  
if 'query' in request_data.keys():  
id = request_data['query']  
self._post_analytics(c.user,logic_function,'', id)  
except Exception,e:  
print log.debug(e)  
pass  
 
return ApiController.action(self,logic_function, ver)  
 
def list(self, ver=None, register=None, subregister=None, id=None):  
self._post_analytics(c.user,register+("_"+str(subregister) if subregister else ""),"list",id)  
return ApiController.list(self,ver, register, subregister, id)  
def show(self, ver=None, register=None, subregister=None, id=None, id2=None):  
self._post_analytics(c.user,register+("_"+str(subregister) if subregister else ""),"show",id)  
return ApiController.show(self,ver, register, subregister, id,id2)  
def update(self, ver=None, register=None, subregister=None, id=None, id2=None):  
self._post_analytics(c.user,register+("_"+str(subregister) if subregister else ""),"update",id)  
return ApiController.update(self,ver, register, subregister, id,id2)  
def delete(self, ver=None, register=None, subregister=None, id=None, id2=None):  
self._post_analytics(c.user,register+("_"+str(subregister) if subregister else ""),"delete",id)  
return ApiController.delete(self,ver, register, subregister, id,id2)  
def search(self, ver=None, register=None):  
id = None  
try:  
params = MultiDict(self._get_search_params(request.params))  
if 'q' in params.keys():  
id = params['q']  
if 'query' in params.keys():  
id = params['query']  
except ValueError, e:  
print str(e)  
pass  
self._post_analytics(c.user,register,"search",id)  
 
import logging import logging
   
import ckan.plugins as plugins import ckan.plugins as plugins
import ckan.lib as lib import ckan.lib as lib
import ckan.lib.dictization.model_dictize as model_dictize import ckan.lib.dictization.model_dictize as model_dictize
import ckan.plugins.toolkit as tk import ckan.plugins.toolkit as tk
import ckan.model as model import ckan.model as model
from pylons import config from pylons import config
from routes.mapper import SubMapper, Mapper as _Mapper  
   
from sqlalchemy import orm from sqlalchemy import orm
import ckan.model import ckan.model
   
#parse the activity feed for last active non-system user  
def get_last_active_user(id):  
system_user = lib.helpers.get_action('user_show',{'id': config.get('ckan.site_id', 'ckan_site_user')})  
user_list = [x for x in lib.helpers.get_action('package_activity_list',{'id':id}) if x['user_id'] != system_user['id']]  
user = None  
if len(user_list) > 0:  
user = user_list[0].get('user_id', None)  
if user is None:  
return system_user  
else:  
return lib.helpers.get_action('user_show',{'id':user})  
   
# get user created datasets and those they have edited # get user created datasets and those they have edited
def get_user_datasets(user_dict): def get_user_datasets(user_dict):
created_datasets_list = user_dict['datasets'] created_datasets_list = user_dict['datasets']
active_datasets_list = [x['data']['package'] for x in active_datasets_list = [x['data']['package'] for x in
lib.helpers.get_action('user_activity_list',{'id':user_dict['id']}) if x['data'].get('package')] lib.helpers.get_action('user_activity_list',{'id':user_dict['id']}) if x['data'].get('package')]
raw_list = created_datasets_list + active_datasets_list raw_list = created_datasets_list + active_datasets_list
filtered_dict = {} filtered_dict = {}
for dataset in raw_list: for dataset in raw_list:
if dataset['id'] not in filtered_dict.keys(): if dataset['id'] not in filtered_dict.keys():
filtered_dict[dataset['id']] = dataset filtered_dict[dataset['id']] = dataset
return filtered_dict.values() return filtered_dict.values()
   
class DataGovAuPlugin(plugins.SingletonPlugin, class DataGovAuPlugin(plugins.SingletonPlugin,
tk.DefaultDatasetForm): tk.DefaultDatasetForm):
'''An example IDatasetForm CKAN plugin. '''An example IDatasetForm CKAN plugin.
   
Uses a tag vocabulary to add a custom metadata field to datasets. Uses a tag vocabulary to add a custom metadata field to datasets.
   
''' '''
plugins.implements(plugins.IConfigurer, inherit=False) plugins.implements(plugins.IConfigurer, inherit=False)
plugins.implements(plugins.IDatasetForm, inherit=False)  
plugins.implements(plugins.ITemplateHelpers, inherit=False) plugins.implements(plugins.ITemplateHelpers, inherit=False)
plugins.implements(plugins.IRoutes, inherit=True)  
   
def before_map(self, map):  
   
# Helpers to reduce code clutter  
GET = dict(method=['GET'])  
PUT = dict(method=['PUT'])  
POST = dict(method=['POST'])  
DELETE = dict(method=['DELETE'])  
GET_POST = dict(method=['GET', 'POST'])  
# intercept API calls that we want to capture analytics on  
register_list = [  
'package',  
'dataset',  
'resource',  
'tag',  
'group',  
'related',  
'revision',  
'licenses',  
'rating',  
'user',  
'activity'  
]  
register_list_str = '|'.join(register_list)  
# /api ver 3 or none  
with SubMapper(map, controller='ckanext.datagovau.controller:DGAApiController', path_prefix='/api{ver:/3|}',  
ver='/3') as m:  
m.connect('/action/{logic_function}', action='action',  
conditions=GET_POST)  
   
# /api ver 1, 2, 3 or none  
with SubMapper(map, controller='ckanext.datagovau.controller:DGAApiController', path_prefix='/api{ver:/1|/2|/3|}',  
ver='/1') as m:  
m.connect('/search/{register}', action='search')  
   
# /api/rest ver 1, 2 or none  
with SubMapper(map, controller='ckanext.datagovau.controller:DGAApiController', path_prefix='/api{ver:/1|/2|}',  
ver='/1', requirements=dict(register=register_list_str)  
) as m:  
   
m.connect('/rest/{register}', action='list', conditions=GET)  
m.connect('/rest/{register}', action='create', conditions=POST)  
m.connect('/rest/{register}/{id}', action='show', conditions=GET)  
m.connect('/rest/{register}/{id}', action='update', conditions=PUT)  
m.connect('/rest/{register}/{id}', action='update', conditions=POST)  
m.connect('/rest/{register}/{id}', action='delete', conditions=DELETE)  
   
return map  
   
def update_config(self, config): def update_config(self, config):
# Add this plugin's templates dir to CKAN's extra_template_paths, so # Add this plugin's templates dir to CKAN's extra_template_paths, so
# that CKAN will use this plugin's custom templates. # that CKAN will use this plugin's custom templates.
# here = os.path.dirname(__file__) # here = os.path.dirname(__file__)
# rootdir = os.path.dirname(os.path.dirname(here)) # rootdir = os.path.dirname(os.path.dirname(here))
   
tk.add_template_directory(config, 'templates') tk.add_template_directory(config, 'templates')
tk.add_public_directory(config, 'theme/public') tk.add_public_directory(config, 'theme/public')
tk.add_resource('theme/public', 'ckanext-datagovau') tk.add_resource('theme/public', 'ckanext-datagovau')
# config['licenses_group_url'] = 'http://%(ckan.site_url)/licenses.json' # config['licenses_group_url'] = 'http://%(ckan.site_url)/licenses.json'
   
def get_helpers(self): def get_helpers(self):
return {'get_last_active_user': get_last_active_user, 'get_user_datasets': get_user_datasets} return {'get_user_datasets': get_user_datasets}
   
def is_fallback(self):  
# Return True to register this plugin as the default handler for  
# package types not handled by any other IDatasetForm plugin.  
return True  
   
def package_types(self):  
# This plugin doesn't handle any special package types, it just  
# registers itself as the default (above).  
return []  
   
   
def create_package_schema(self):  
schema = super(DataGovAuPlugin, self).create_package_schema()  
schema = self._modify_package_schema(schema)  
return schema  
   
def update_package_schema(self):  
schema = super(DataGovAuPlugin, self).update_package_schema()  
schema = self._modify_package_schema(schema)  
return schema  
   
def show_package_schema(self):  
schema = super(DataGovAuPlugin, self).show_package_schema()  
   
# Don't show vocab tags mixed in with normal 'free' tags  
# (e.g. on dataset pages, or on the search page)  
schema['tags']['__extras'].append(tk.get_converter('free_tags_only'))  
   
# Add our custom_text field to the dataset schema.  
# ignore_missing == optional  
# ignore_empty == mandatory but not for viewing  
# !!! always convert_from_extras first  
schema.update({  
'agency_program': [tk.get_converter('convert_from_extras'),  
tk.get_validator('ignore_missing')],  
'contact_point': [tk.get_converter('convert_from_extras'),  
tk.get_validator('ignore_empty')],  
'spatial_coverage': [tk.get_converter('convert_from_extras'),  
tk.get_validator('ignore_empty')],  
'granularity': [tk.get_converter('convert_from_extras'),  
tk.get_validator('ignore_empty')],  
'jurisdiction': [tk.get_converter('convert_from_extras'),  
tk.get_validator('ignore_empty')],  
'temporal_coverage': [tk.get_converter('convert_from_extras'),  
tk.get_validator('ignore_empty')],  
'data_state': [tk.get_converter('convert_from_extras'),  
tk.get_validator('ignore_empty')],  
'update_freq': [tk.get_converter('convert_from_extras'),  
tk.get_validator('ignore_empty')]  
})  
return schema  
   
def _modify_package_schema(self, schema):  
# Add our custom_test metadata field to the schema, this one will use  
# convert_to_extras instead of convert_to_tags.  
# ignore_missing == optional  
# not_empty == mandatory, enforced here while modifying  
   
schema.update({  
'agency_program': [tk.get_validator('ignore_missing'),  
tk.get_converter('convert_to_extras')],  
'contact_point': [tk.get_converter('convert_to_extras'),  
tk.get_validator('not_empty')],  
'spatial_coverage': [tk.get_converter('convert_to_extras'),  
tk.get_validator('not_empty')],  
'granularity': [tk.get_converter('convert_to_extras'),  
tk.get_validator('not_empty')],  
'jurisdiction': [tk.get_converter('convert_to_extras'),  
tk.get_validator('not_empty')],  
'temporal_coverage': [tk.get_converter('convert_to_extras'),  
tk.get_validator('not_empty')],  
'data_state': [tk.get_converter('convert_to_extras'),  
tk.get_validator('not_empty')],  
'update_freq': [tk.get_converter('convert_to_extras'),  
tk.get_validator('not_empty')]  
})  
return schema  
   
# These methods just record how many times they're called, for testing  
# purposes.  
# TODO: It might be better to test that custom templates returned by  
# these methods are actually used, not just that the methods get  
# called.  
   
def setup_template_variables(self, context, data_dict):  
return super(DataGovAuPlugin, self).setup_template_variables(  
context, data_dict)  
   
def new_template(self):  
return super(DataGovAuPlugin, self).new_template()  
   
def read_template(self):  
return super(DataGovAuPlugin, self).read_template()  
   
def edit_template(self):  
return super(DataGovAuPlugin, self).edit_template()  
   
def search_template(self):  
return super(DataGovAuPlugin, self).search_template()  
   
def history_template(self):  
return super(DataGovAuPlugin, self).history_template()  
   
def package_form(self):  
return super(DataGovAuPlugin, self).package_form()