Use nicer vocab names in dataset view template
Use nicer vocab names in dataset view template

file:a/README.rst -> file:b/README.rst
This CKAN Extension demonstrates some common patterns for customising a CKAN instance. This CKAN Extension demonstrates some common patterns for customising a CKAN instance.
   
It comprises: It comprises:
   
* A CKAN Extension "plugin" at ``ckanext/example/plugin.py`` which, when * A CKAN Extension "plugin" at ``ckanext/example/plugin.py`` which, when
loaded, overrides various settings in the core ``ini``-file to provide: loaded, overrides various settings in the core ``ini``-file to provide:
   
* A path to local customisations of the core templates and stylesheets * A path to local customisations of the core templates and stylesheets
* A "stream filter" that replaces arbitrary strings in rendered templates * A "stream filter" that replaces arbitrary strings in rendered templates
* A "route" to override and extend the default behaviour of a core CKAN page * A "route" to override and extend the default behaviour of a core CKAN page
   
* A custom Pylons controller for overriding some core CKAN behaviour * A custom Pylons controller for overriding some core CKAN behaviour
   
* A custom Package edit form * A custom Package edit form
   
  * A custom Group edit form
   
  * A plugin that allows for custom forms to be used for datasets based on
  their "type".
   
* A custom User registration and edition form * A custom User registration and edition form
   
* Some simple template customisations * Some simple template customisations
   
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-example#egg=ckanext-example pip install -e git+https://github.com/okfn/ckanext-example#egg=ckanext-example
   
Then activate it by setting ``ckan.plugins = example`` in your main ``ini``-file. Then activate it by setting ``ckan.plugins = example`` in your main ``ini``-file.
   
   
Orientation Orientation
=========== ===========
   
* Examine the source code, starting with ``ckanext/example/plugin.py`` * Examine the source code, starting with ``ckanext/example/plugin.py``
   
* To understand the nuts and bolts of this file, which is a CKAN * To understand the nuts and bolts of this file, which is a CKAN
*Extension*, read in conjunction with the "Extension *Extension*, read in conjunction with the "Extension
documentation": http://docs.ckan.org/en/latest/plugins.html documentation": http://docs.ckan.org/en/latest/plugins.html
   
* One thing the extension does is set the values of * One thing the extension does is set the values of
``extra_public_paths`` and ``extra_template_paths`` in the CKAN ``extra_public_paths`` and ``extra_template_paths`` in the CKAN
config, which are "documented config, which are "documented
here": http://docs.ckan.org/en/latest/configuration.html#extra-template-paths here": http://docs.ckan.org/en/latest/configuration.html#extra-template-paths
   
* These are set to point at directories within * These are set to point at directories within
``ckanext/example/theme/`` (in this package). Here we: ``ckanext/example/theme/`` (in this package). Here we:
* override the home page HTML ``ckanext/example/theme/templates/home/index.html`` * override the home page HTML ``ckanext/example/theme/templates/home/index.html``
* provide some extra style by serving ``extra.css`` (which is loaded using the ``ckan.template_head_end`` option * provide some extra style by serving ``extra.css`` (which is loaded using the ``ckan.template_head_end`` option
* customise the navigation and header of the main template in the file ``layout.html``. * customise the navigation and header of the main template in the file ``layout.html``.
   
The latter file is a great place to make global theme alterations. The latter file is a great place to make global theme alterations.
It uses the _layout template_ pattern "described in the Genshi It uses the _layout template_ pattern "described in the Genshi
documentation":http://genshi.edgewall.org/wiki/GenshiTutorial#AddingaLayoutTemplate. documentation":http://genshi.edgewall.org/wiki/GenshiTutorial#AddingaLayoutTemplate.
This allows you to use Xpath selectors to override snippets of HTML This allows you to use Xpath selectors to override snippets of HTML
globally. globally.
   
* The custom package edit form at ``package_form.py`` follows a deprecated * The custom package edit form at ``package_form.py`` follows a deprecated
way to make a form (using FormAlchemy). This part of the Example Theme needs way to make a form (using FormAlchemy). This part of the Example Theme needs
updating. In the meantime, follow the instructions at: updating. In the meantime, follow the instructions at:
http://readthedocs.org/docs/ckan/en/latest/forms.html http://readthedocs.org/docs/ckan/en/latest/forms.html
   
  Example Tags With Vocabularies
  ==============================
   
  To add example tag vocabulary data to the database, from the ckanext-example directory run:
   
  ::
   
  paster example create-example-vocabs -c <path to your ckan config file>
   
  This data can be removed with
   
  ::
   
  paster example clean -c <path to your ckan config file>
   
   
  from ckan import model
  from ckan.lib.cli import CkanCommand
  from ckan.logic import get_action, NotFound
  import forms
 
  import logging
  log = logging.getLogger()
 
 
  class ExampleCommand(CkanCommand):
  '''
  CKAN Example Extension
 
  Usage::
 
  paster example create-example-vocabs -c <path to config file>
 
  paster example clean -c <path to config file>
  - Remove all data created by ckanext-example
 
  The commands should be run from the ckanext-example directory.
  '''
  summary = __doc__.split('\n')[0]
  usage = __doc__
 
  def command(self):
  '''
  Parse command line arguments and call appropriate method.
  '''
  if not self.args or self.args[0] in ['--help', '-h', 'help']:
  print ExampleCommand.__doc__
  return
 
  cmd = self.args[0]
  self._load_config()
 
  if cmd == 'create-example-vocabs':
  self.create_example_vocabs()
  if cmd == 'clean':
  self.clean()
  else:
  log.error('Command "%s" not recognized' % (cmd,))
 
  def create_example_vocabs(self):
  '''
  Adds example vocabularies to the database if they don't already exist.
  '''
  user = get_action('get_site_user')({'model': model, 'ignore_auth': True}, {})
  context = {'model': model, 'session': model.Session, 'user': user['name']}
 
  try:
  data = {'id': forms.GENRE_VOCAB}
  get_action('vocabulary_show')(context, data)
  log.info("Example genre vocabulary already exists, skipping.")
  except NotFound:
  log.info("Creating vocab %s" % forms.GENRE_VOCAB)
  data = {'name': forms.GENRE_VOCAB}
  vocab = get_action('vocabulary_create')(context, data)
  log.info("Adding tag %s to vocab %s" % ('jazz', forms.GENRE_VOCAB))
  data = {'name': 'jazz', 'vocabulary_id': vocab['id']}
  get_action('tag_create')(context, data)
  log.info("Adding tag %s to vocab %s" % ('soul', forms.GENRE_VOCAB))
  data = {'name': 'soul', 'vocabulary_id': vocab['id']}
  get_action('tag_create')(context, data)
 
  try:
  data = {'id': forms.COMPOSER_VOCAB}
  get_action('vocabulary_show')(context, data)
  log.info("Example composer vocabulary already exists, skipping.")
  except NotFound:
  log.info("Creating vocab %s" % forms.COMPOSER_VOCAB)
  data = {'name': forms.COMPOSER_VOCAB}
  vocab = get_action('vocabulary_create')(context, data)
  log.info("Adding tag %s to vocab %s" % ('Bob Mintzer', forms.COMPOSER_VOCAB))
  data = {'name': 'Bob Mintzer', 'vocabulary_id': vocab['id']}
  get_action('tag_create')(context, data)
  log.info("Adding tag %s to vocab %s" % ('Steve Lewis', forms.COMPOSER_VOCAB))
  data = {'name': 'Steve Lewis', 'vocabulary_id': vocab['id']}
  get_action('tag_create')(context, data)
 
  def clean(self):
  log.error("Clean command not yet implemented")
 
import logging  
from ckan.lib.base import BaseController, render, c, model, abort, request  
from ckan.lib.base import redirect, _, config, h  
import ckan.logic.action.create as create  
import ckan.logic.action.update as update  
import ckan.logic.action.get as get  
from ckan.logic.converters import date_to_db, date_to_form, convert_to_extras, convert_from_extras  
from ckan.lib.navl.dictization_functions import DataError, flatten_dict, unflatten  
from ckan.logic import NotFound, NotAuthorized, ValidationError  
from ckan.logic import tuplize_dict, clean_dict, parse_params  
from ckan.logic.schema import package_form_schema  
from ckan.plugins import IDatasetForm  
from ckan.plugins import implements, SingletonPlugin  
from ckan.lib.package_saver import PackageSaver  
from ckan.lib.field_types import DateType, DateConvertError  
from ckan.authz import Authorizer  
from ckan.lib.navl.dictization_functions import Invalid  
from ckanext.dgu.forms.package_gov_fields import GeoCoverageType  
from ckan.lib.navl.dictization_functions import validate, missing  
import ckan.logic.validators as val  
import ckan.logic.schema as default_schema  
from ckan.lib.navl.validators import (ignore_missing,  
not_empty,  
empty,  
ignore,  
keep_extras,  
)  
 
log = logging.getLogger(__name__)  
 
geographic_granularity = [('', ''),  
('national', 'national'),  
('regional', 'regional'),  
('local authority', 'local authority'),  
('ward', 'ward'),  
('point', 'point'),  
('other', 'other - please specify')]  
 
update_frequency = [('', ''),  
('never', 'never'),  
('discontinued', 'discontinued'),  
('annual', 'annual'),  
('quarterly', 'quarterly'),  
('monthly', 'monthly'),  
('other', 'other - please specify')]  
 
temporal_granularity = [("",""),  
("year","year"),  
("quarter","quarter"),  
("month","month"),  
("week","week"),  
("day","day"),  
("hour","hour"),  
("point","point"),  
("other","other - please specify")]  
 
 
class ExamplePackageController(SingletonPlugin):  
 
implements(IDatasetForm, inherit=True)  
 
def package_form(self):  
return 'controller/package_plugin.html'  
 
def is_fallback(self):  
"""  
Returns true iff this provides the fallback behaviour, when no other  
plugin instance matches a package's type.  
 
As this is not the fallback controller we should return False. If  
we were wanting to act as the fallback, we'd return True  
"""  
return False  
 
def package_types(self):  
"""  
Returns an iterable of package type strings.  
 
If a request involving a package of one of those types is made, then  
this plugin instance will be delegated to.  
 
There must only be one plugin registered to each package type. Any  
attempts to register more than one plugin instance to a given package  
type will raise an exception at startup.  
"""  
return ["example"]  
 
def _setup_template_variables(self, context, data_dict=None):  
c.licences = [('', '')] + model.Package.get_license_options()  
c.geographic_granularity = geographic_granularity  
c.update_frequency = update_frequency  
c.temporal_granularity = temporal_granularity  
 
c.publishers = self.get_publishers()  
 
c.is_sysadmin = Authorizer().is_sysadmin(c.user)  
c.resource_columns = model.Resource.get_columns()  
 
## This is messy as auths take domain object not data_dict  
pkg = context.get('package') or c.pkg  
if pkg:  
c.auth_for_change_state = Authorizer().am_authorized(  
c, model.Action.CHANGE_STATE, pkg)  
 
def _form_to_db_schema(self):  
 
schema = {  
'title': [not_empty, unicode],  
'name': [not_empty, unicode, val.name_validator, val.package_name_validator],  
'notes': [not_empty, unicode],  
 
'date_released': [date_to_db, convert_to_extras],  
'date_updated': [date_to_db, convert_to_extras],  
'date_update_future': [date_to_db, convert_to_extras],  
'update_frequency': [use_other, unicode, convert_to_extras],  
'update_frequency-other': [],  
'precision': [unicode, convert_to_extras],  
'geographic_granularity': [use_other, unicode, convert_to_extras],  
'geographic_granularity-other': [],  
'geographic_coverage': [ignore_missing, convert_geographic_to_db, convert_to_extras],  
'temporal_granularity': [use_other, unicode, convert_to_extras],  
'temporal_granularity-other': [],  
'temporal_coverage-from': [date_to_db, convert_to_extras],  
'temporal_coverage-to': [date_to_db, convert_to_extras],  
'url': [unicode],  
'taxonomy_url': [unicode, convert_to_extras],  
 
'resources': default_schema.default_resource_schema(),  
 
'published_by': [not_empty, unicode, convert_to_extras],  
'published_via': [ignore_missing, unicode, convert_to_extras],  
'author': [ignore_missing, unicode],  
'author_email': [ignore_missing, unicode],  
'mandate': [ignore_missing, unicode, convert_to_extras],  
'license_id': [ignore_missing, unicode],  
'tag_string': [ignore_missing, val.tag_string_convert],  
'national_statistic': [ignore_missing, convert_to_extras],  
'state': [val.ignore_not_admin, ignore_missing],  
 
'log_message': [unicode, val.no_http],  
 
'__extras': [ignore],  
'__junk': [empty],  
}  
return schema  
 
def _db_to_form_schema(data):  
schema = {  
'date_released': [convert_from_extras, ignore_missing, date_to_form],  
'date_updated': [convert_from_extras, ignore_missing, date_to_form],  
'date_update_future': [convert_from_extras, ignore_missing, date_to_form],  
'update_frequency': [convert_from_extras, ignore_missing, extract_other(update_frequency)],  
'precision': [convert_from_extras, ignore_missing],  
'geographic_granularity': [convert_from_extras, ignore_missing, extract_other(geographic_granularity)],  
'geographic_coverage': [convert_from_extras, ignore_missing, convert_geographic_to_form],  
'temporal_granularity': [convert_from_extras, ignore_missing, extract_other(temporal_granularity)],  
'temporal_coverage-from': [convert_from_extras, ignore_missing, date_to_form],  
'temporal_coverage-to': [convert_from_extras, ignore_missing, date_to_form],  
'taxonomy_url': [convert_from_extras, ignore_missing],  
 
'resources': default_schema.default_resource_schema(),  
'extras': {  
'key': [],  
'value': [],  
'__extras': [keep_extras]  
},  
'tags': {  
'__extras': [keep_extras]  
},  
 
'published_by': [convert_from_extras, ignore_missing],  
'published_via': [convert_from_extras, ignore_missing],  
'mandate': [convert_from_extras, ignore_missing],  
'national_statistic': [convert_from_extras, ignore_missing],  
'__extras': [keep_extras],  
'__junk': [ignore],  
}  
return schema  
 
def _check_data_dict(self, data_dict):  
return  
 
def get_publishers(self):  
return [('pub1', 'pub2')]  
 
 
def use_other(key, data, errors, context):  
 
other_key = key[-1] + '-other'  
other_value = data.get((other_key,), '').strip()  
if other_value:  
data[key] = other_value  
 
def extract_other(option_list):  
 
def other(key, data, errors, context):