Skip to content

Commit

Permalink
[controller/package] A very rough sketch of pluggable package control…
Browse files Browse the repository at this point in the history
…ler behaviour.
  • Loading branch information
icmurray authored and rossjones committed Dec 7, 2011
1 parent 2a1c013 commit 5113646
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 14 deletions.
13 changes: 12 additions & 1 deletion ckan/config/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"""
from pylons import config
from routes import Mapper
from ckan.plugins import PluginImplementations, IRoutes
from ckan.controllers.package import set_fallback_controller as set_fallback_package_controller,\
add_package_controller,\
set_default_as_fallback_controller_if_required as set_default_as_fallback_package_controller_if_required
from ckan.plugins import PluginImplementations, IRoutes, IPluggablePackageController

routing_plugins = PluginImplementations(IRoutes)

Expand Down Expand Up @@ -177,6 +180,14 @@ def make_map():
## /END API
###########

for plugin in PluginImplementations(IPluggablePackageController):
if plugin.is_fallback():
set_fallback_package_controller(plugin)
for package_type in plugin.package_types():
add_package_controller(package_type, plugin)

set_default_as_fallback_package_controller_if_required()

map.redirect("/packages", "/dataset")
map.redirect("/packages/{url:.*}", "/dataset/{url}")
map.redirect("/package", "/dataset")
Expand Down
89 changes: 78 additions & 11 deletions ckan/controllers/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from ckan.logic import tuplize_dict, clean_dict, parse_params, flatten_to_string_key
from ckan.lib.dictization import table_dictize
from ckan.lib.i18n import get_lang
from ckan.plugins import SingletonPlugin, implements
from ckan.plugins.interfaces import IPluggablePackageController
import ckan.forms
import ckan.authz
import ckan.rating
Expand All @@ -47,19 +49,63 @@ def search_url(params):
("text", "x-graphviz", ["dot"]),
]

class PackageController(BaseController):

## hooks for subclasses
package_form = 'package/new_package_form.html'

def _form_to_db_schema(self):
_pluggable_package_controllers = dict()
_default_pluggable_package_controller = None

def set_fallback_controller(plugin_instance):
"""
Sets the fallback controller
"""
global _default_pluggable_package_controller
if _default_pluggable_package_controller is not None:
raise ValueError, "A fallback package controller has alread been registered"
_default_pluggable_package_controller = plugin_instance

def set_default_as_fallback_controller_if_required():
"""
Set the fallback controller to be an instance of DefaultPluggablePackageController
"""
if _default_pluggable_package_controller is None:
set_fallback_controller(DefaultPluggablePackageController())

def _lookup_plugin(package_type):
"""
Returns the plugin controller associoated with the given package type.
"""
if package_type is None:
return _default_pluggable_package_controller
return _pluggable_package_controllers.get(package_type,
_default_pluggable_package_controller)

def add_package_controller(package_type, plugin_instance):
"""
Register the given plugin_instance to the given package_type.
"""
if package_type in _pluggable_package_controllers:
raise ValueError, 'A PluggablePackageController is already ' \
'associated with this package_type: "%s"' % package_type

class DefaultPluggablePackageController(object):
"""
Provides a default implementation of the package controller.
Note - this isn't a plugin implementation. This is deliberate, as
we don't want this being registered.
"""

##### Define the hooks that control the behaviour #####

def package_form(self):
return 'package/new_package_form.html'

def form_to_db_schema(self):
return package_form_schema()

def _db_to_form_schema(self):
def db_to_form_schema(self):
'''This is an interface to manipulate data from the database
into a format suitable for the form (optional)'''

def _check_data_dict(self, data_dict):
def check_data_dict(self, data_dict):
'''Check if the return data is correct, mostly for checking out if
spammers are submitting only part of the form'''

Expand All @@ -79,7 +125,7 @@ def _check_data_dict(self, data_dict):
log.info('incorrect form fields posted')
raise DataError(data_dict)

def _setup_template_variables(self, context, data_dict):
def setup_template_variables(self, context, data_dict):
c.groups_authz = get_action('group_list_authz')(context, data_dict)
data_dict.update({'available_only':True})
c.groups_available = get_action('group_list_authz')(context, data_dict)
Expand All @@ -100,6 +146,27 @@ def _setup_template_variables(self, context, data_dict):

## end hooks

class PackageController(BaseController):

def _package_form(self, package_type=None):
return _lookup_plugin(package_type).package_form()

def _form_to_db_schema(self, package_type=None):
return _lookup_plugin(package_type).form_to_db_schema()

def _db_to_form_schema(self, package_type=None):
'''This is an interface to manipulate data from the database
into a format suitable for the form (optional)'''
return _lookup_plugin(package_type).db_to_form_schema()

def _check_data_dict(self, data_dict, package_type=None):
'''Check if the return data is correct, mostly for checking out if
spammers are submitting only part of the form'''
return _lookup_plugin(package_type).check_data_dict(data_dict)

def _setup_template_variables(self, context, data_dict, package_type=None):
return _lookup_plugin(package_type).setup_template_variables(context, data_dict)

authorizer = ckan.authz.Authorizer()

def search(self):
Expand Down Expand Up @@ -339,7 +406,7 @@ def new(self, data=None, errors=None, error_summary=None):
vars = {'data': data, 'errors': errors, 'error_summary': error_summary}

self._setup_template_variables(context, {'id': id})
c.form = render(self.package_form, extra_vars=vars)
c.form = render(self._package_form(), extra_vars=vars)

return render('package/new.html')

Expand Down Expand Up @@ -378,7 +445,7 @@ def edit(self, id, data=None, errors=None, error_summary=None):

self._setup_template_variables(context, {'id': id})

c.form = render(self.package_form, extra_vars=vars)
c.form = render(self._package_form(), extra_vars=vars)
return render('package/edit.html')

def read_ajax(self, id, revision=None):
Expand Down
79 changes: 77 additions & 2 deletions ckan/plugins/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
'IDomainObjectModification', 'IGroupController',
'IPackageController', 'IPluginObserver',
'IConfigurable', 'IConfigurer', 'IAuthorizer',
'IActions', 'IResourceUrlChange'
'IActions', 'IResourceUrlChange', 'IPluggablePackageController',
]

from inspect import isclass
from pyutilib.component.core import Interface as _pca_Interface


class Interface(_pca_Interface):

@classmethod
Expand Down Expand Up @@ -330,3 +329,79 @@ def get_auth_functions(self):
implementation overrides
"""

class IPluggablePackageController(Interface):
"""
Allows customisation of the package controller as a plugin.
Different package types can be associated with different
IPluggablePackageControllers, allowing multiple package controllers
on a CKAN instance.
The PackageController uses hooks to customise behaviour. A implementation
of IPluggablePackageController must implement these hooks.
Implementations might want to consider subclassing
ckan.controllers.package.DefaultPluggablePackageController
"""

##### These methods control when the plugin is delegated to #####

def is_fallback(self):
"""
Returns true iff this provides the fallback behaviour, when no other
plugin matches a package's type.
There must be exactly one fallback controller defined, any attempt to
register more than one or to not have any registered, will throw
an exception at startup.
"""

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 will be delegated to.
There must only be one plugin registered to each package type. Any
attempts to register more than one plugin to a given package type will
raise an exception at startup.
"""

##### End of control methods

##### Hooks for customising the PackageController's behaviour #####

def package_form(self):
"""
Returns a string representing the location of the template to be
rendered. e.g. "package/new_package_form.html".
"""

def form_to_db_schema(self):
"""
Returns the schema for mapping package data from a form to a format
suitable for the database.
"""

def db_to_form_schema(self):
"""
Returns the schema for mapping package data from the database into a
format suitable for the form (optional)
"""

def check_data_dict(self, data_dict):
"""
Check if the return data is correct.
raise a DataError if not.
"""

def setup_template_variables(self, context, data_dict):
"""
Add variables to c just prior to the template being rendered.
"""

##### End of hooks #####

0 comments on commit 5113646

Please sign in to comment.