diff --git a/ckan/__init__.py b/ckan/__init__.py
index 9bdf93a52a7..b4333195546 100644
--- a/ckan/__init__.py
+++ b/ckan/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '1.6.1a'
+__version__ = '1.6.1b'
__description__ = 'Comprehensive Knowledge Archive Network (CKAN) Software'
__long_description__ = \
'''CKAN software provides a hub for datasets. The flagship site running CKAN
diff --git a/ckan/config/plugins.py b/ckan/config/plugins.py
deleted file mode 100644
index 645f3db7be5..00000000000
--- a/ckan/config/plugins.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import logging
-from pkg_resources import iter_entry_points
-
-log = logging.getLogger(__name__)
-
-# Entry point group.
-GROUP_NAME = "ckan.plugins"
-
-class PluginException(Exception): pass
-
-def load_all(config):
- plugins = config.get('ckan.plugins', '')
- log.debug("Loading plugins: %s" % plugins)
- for plugin in plugins.split():
- for entry_point in iter_entry_points(group=GROUP_NAME, name=plugin):
- load(plugin, entry_point, config)
- break
- else:
- raise PluginException("Plugin not found: %s" % plugin)
-
-
-def load(name, entry_point, config):
- log.debug("Plugin: %s", entry_point.dist)
- entry_obj = entry_point.load()(config)
- registry = config.get('ckan.plugin_registry', {})
- registry[entry_point] = entry_obj
- config['ckan.plugin_registry'] = registry
- return entry_obj
-
-
-def find_methods(method_name):
- """ For a given method name, find all plugins where that method exists and iterate over them. """
- from pylons import config
- for k, v in config.get('ckan.plugin_registry', {}).items():
- if hasattr(v, method_name):
- yield getattr(v, method_name)
- else:
- pass
- #log.debug("%s has no method %s" % (k.name, method_name))
-
-
-
-##### Pylons monkey-patch
-
-from pylons.wsgiapp import PylonsApp
-import pkg_resources
-
-log.info("Monkey-patching Pylons to allow loading of controllers via entry point mechanism")
-
-find_controller_generic = PylonsApp.find_controller
-
-# This is from pylons 1.0 source, will monkey-patch into 0.9.7
-def find_controller(self, controller):
- if controller in self.controller_classes:
- return self.controller_classes[controller]
-
- # Check to see if its a dotted name
- if '.' in controller or ':' in controller:
- mycontroller = pkg_resources.EntryPoint.parse('x=%s' % controller).load(False)
- self.controller_classes[controller] = mycontroller
- return mycontroller
-
- return find_controller_generic(self, controller)
-
-PylonsApp.find_controller = find_controller
diff --git a/ckan/config/solr/schema-1.3.xml b/ckan/config/solr/schema-1.3.xml
index bc22441ad7f..21cf3d7b75e 100644
--- a/ckan/config/solr/schema-1.3.xml
+++ b/ckan/config/solr/schema-1.3.xml
@@ -138,6 +138,11 @@
+
+
+
+
diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py
index b7f8efe2413..7c8332f47dd 100644
--- a/ckan/controllers/group.py
+++ b/ckan/controllers/group.py
@@ -36,9 +36,26 @@ def _db_to_form_schema(self, group_type=None):
def _setup_template_variables(self, context, data_dict, group_type=None):
return lookup_group_plugin(group_type).setup_template_variables(context,data_dict)
+ def _new_template(self,group_type):
+ from ckan.lib.helpers import default_group_type
+ return lookup_group_plugin(group_type).new_template()
+
+ def _index_template(self,group_type):
+ from ckan.lib.helpers import default_group_type
+ return lookup_group_plugin(group_type).index_template()
+
+ def _read_template(self, group_type):
+ return lookup_group_plugin(group_type).read_template()
+
+ def _history_template(self, group_type):
+ return lookup_group_plugin(group_type).history_template()
+
## end hooks
def index(self):
+ group_type = request.path.strip('/').split('/')[0]
+ if group_type == 'group':
+ group_type = None
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
@@ -58,7 +75,7 @@ def index(self):
url=h.pager_url,
items_per_page=20
)
- return render('group/index.html')
+ return render( self._index_template(group_type) )
def read(self, id):
@@ -170,7 +187,7 @@ def pager_url(q=None, page=None):
ckan.logic.action.get.group_activity_list_html(context,
{'id': c.group_dict['id']})
- return render('group/read.html')
+ return render( self._read_template(c.group_dict['type']) )
def new(self, data=None, errors=None, error_summary=None):
group_type = request.path.strip('/').split('/')[0]
@@ -198,7 +215,7 @@ def new(self, data=None, errors=None, error_summary=None):
self._setup_template_variables(context,data)
c.form = render(self._group_form(group_type=group_type), extra_vars=vars)
- return render('group/new.html')
+ return render(self._new_template(group_type))
def edit(self, id, data=None, errors=None, error_summary=None):
group_type = self._get_group_type(id.split('@')[0])
@@ -383,7 +400,7 @@ def history(self, id):
)
feed.content_type = 'application/atom+xml'
return feed.writeString('utf-8')
- return render('group/history.html')
+ return render( self._history_template(c.group_dict['type']) )
def _render_edit_form(self, fs):
# errors arrive in c.error and fs.errors
diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py
index ec055e6d64f..338814e51d9 100644
--- a/ckan/controllers/package.py
+++ b/ckan/controllers/package.py
@@ -44,7 +44,7 @@ def search_url(params):
class PackageController(BaseController):
- def _package_form(self, package_type=None):
+ def _package_form(self, package_type=None):
return lookup_package_plugin(package_type).package_form()
def _form_to_db_schema(self, package_type=None):
@@ -63,10 +63,31 @@ def _check_data_dict(self, data_dict, package_type=None):
def _setup_template_variables(self, context, data_dict, package_type=None):
return lookup_package_plugin(package_type).setup_template_variables(context, data_dict)
+ def _new_template(self, package_type):
+ return lookup_package_plugin(package_type).new_template()
+
+ def _comments_template(self, package_type):
+ return lookup_package_plugin(package_type).comments_template()
+
+ def _search_template(self, package_type):
+ return lookup_package_plugin(package_type).search_template()
+
+ def _read_template(self, package_type):
+ return lookup_package_plugin(package_type).read_template()
+
+ def _history_template(self, package_type):
+ return lookup_package_plugin(package_type).history_template()
+
+
authorizer = ckan.authz.Authorizer()
def search(self):
from ckan.lib.search import SearchError
+
+ package_type = request.path.strip('/').split('/')[0]
+ if package_type == 'group':
+ package_type = None
+
try:
context = {'model':model,'user': c.user or c.author}
check_access('site_read',context)
@@ -83,21 +104,45 @@ def search(self):
# most search operations should reset the page counter:
params_nopage = [(k, v) for k,v in request.params.items() if k != 'page']
-
+
def drill_down_url(**by):
params = list(params_nopage)
params.extend(by.items())
return search_url(set(params))
-
- c.drill_down_url = drill_down_url
-
+
+ c.drill_down_url = drill_down_url
+
def remove_field(key, value):
params = list(params_nopage)
params.remove((key, value))
return search_url(params)
c.remove_field = remove_field
-
+
+ sort_by = request.params.get('sort', None)
+ params_nosort = [(k, v) for k,v in params_nopage if k != 'sort']
+ def _sort_by(fields):
+ """
+ Sort by the given list of fields.
+
+ Each entry in the list is a 2-tuple: (fieldname, sort_order)
+
+ eg - [('metadata_modified', 'desc'), ('name', 'asc')]
+
+ If fields is empty, then the default ordering is used.
+ """
+ params = params_nosort[:]
+
+ if fields:
+ sort_string = ', '.join( '%s %s' % f for f in fields )
+ params.append(('sort', sort_string))
+ return search_url(params)
+ c.sort_by = _sort_by
+ if sort_by is None:
+ c.sort_by_fields = []
+ else:
+ c.sort_by_fields = [ field.split()[0] for field in sort_by.split(',') ]
+
def pager_url(q=None, page=None):
params = list(params_nopage)
params.append(('page', page))
@@ -108,7 +153,7 @@ def pager_url(q=None, page=None):
search_extras = {}
fq = ''
for (param, value) in request.params.items():
- if not param in ['q', 'page'] \
+ if param not in ['q', 'page', 'sort'] \
and len(value) and not param.startswith('_'):
if not param.startswith('ext_'):
c.fields.append((param, value))
@@ -125,6 +170,7 @@ def pager_url(q=None, page=None):
'facet.field':g.facets,
'rows':limit,
'start':(page-1)*limit,
+ 'sort': sort_by,
'extras':search_extras
}
@@ -144,17 +190,51 @@ def pager_url(q=None, page=None):
c.query_error = True
c.facets = {}
c.page = h.Page(collection=[])
-
- return render('package/search.html')
+
+ return render( self._search_template(package_type) )
+
+ def _content_type_for_format(self, fmt):
+ """
+ Given a requested format this method determines the content-type
+ to set and the genshi template loader to use in order to render
+ it accurately. TextTemplate must be used for non-xml templates
+ whilst all that are some sort of XML should use MarkupTemplate.
+ """
+ from genshi.template import MarkupTemplate, TextTemplate
+ types = {
+ "html": ("text/html; charset=utf-8", MarkupTemplate),
+ "rdf" : ("application/rdf+xml; charset=utf-8", MarkupTemplate),
+ }
+ if fmt in types:
+ return types[fmt][0], fmt, types[fmt][1]
+ return None, "html", (types["html"][1])
def read(self, id):
+ # Check if the request was for a different format than html, we have to do
+ # it this way because if we instead rely on _content_type_for_format failing
+ # for revisions (with . in the name) then we will have lost the ID by virtue
+ # of the routing splitting it up.
+ format = 'html'
+ if '.' in id:
+ pos = id.index('.')
+ format = id[pos+1:]
+ id = id[:pos]
+
+ ctype,extension,loader = self._content_type_for_format(format)
+ if not ctype:
+ # Reconstitute the ID if we don't know what content type to use
+ ctype = "text/html; charset=utf-8"
+ id = "%s.%s" % (id, format)
+ response.headers['Content-Type'] = ctype
+
package_type = self._get_package_type(id.split('@')[0])
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'extras_as_string': True,
'for_view': True}
data_dict = {'id': id}
+
# interpret @ or @ suffix
split = id.split('@')
if len(split) == 2:
@@ -171,7 +251,7 @@ def read(self, id):
abort(400, _('Invalid revision format: %r') % e.args)
elif len(split) > 2:
abort(400, _('Invalid revision format: %r') % 'Too many "@" symbols')
-
+
#check if package exists
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
@@ -181,7 +261,7 @@ def read(self, id):
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % id)
-
+
#set a cookie so we know whether to display the welcome message
c.hide_welcome_message = bool(request.cookies.get('hide_welcome_message', False))
response.set_cookie('hide_welcome_message', '1', max_age=3600) #(make cross-site?)
@@ -199,13 +279,14 @@ def read(self, id):
if config.get('rdf_packages'):
accept_header = request.headers.get('Accept', '*/*')
for content_type, exts in negotiate(autoneg_cfg, accept_header):
- if "html" not in exts:
+ if "html" not in exts:
rdf_url = '%s%s.%s' % (config['rdf_packages'], c.pkg.id, exts[0])
redirect(rdf_url, code=303)
break
PackageSaver().render_package(c.pkg_dict, context)
- return render('package/read.html')
+
+ return render( self._read_template( package_type ) )
def comments(self, id):
package_type = self._get_package_type(id)
@@ -297,14 +378,14 @@ def history(self, id):
)
feed.content_type = 'application/atom+xml'
return feed.writeString('utf-8')
- return render('package/history.html')
+ return render( self._history_template(c.pkg_dict['type']))
def new(self, data=None, errors=None, error_summary=None):
-
+
package_type = request.path.strip('/').split('/')[0]
if package_type == 'group':
package_type = None
-
+
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'extras_as_string': True,
'save': 'save' in request.params,}
@@ -321,7 +402,7 @@ def new(self, data=None, errors=None, error_summary=None):
data = data or clean_dict(unflatten(tuplize_dict(parse_params(
request.params, ignore_keys=[CACHE_PARAMETER]))))
- c.pkg_json = json.dumps(data)
+ c.pkg_json = json.dumps(data)
errors = errors or {}
error_summary = error_summary or {}
@@ -336,7 +417,7 @@ def new(self, data=None, errors=None, error_summary=None):
c.form = render(self.package_form, extra_vars=vars)
else:
c.form = render(self._package_form(package_type=package_type), extra_vars=vars)
- return render('package/new.html')
+ return render( self._new_template(''))
def edit(self, id, data=None, errors=None, error_summary=None):
@@ -438,22 +519,22 @@ def history_ajax(self, id):
current_approved, approved = True, True
else:
current_approved = False
-
+
data.append({'revision_id': revision['id'],
'message': revision['message'],
'timestamp': revision['timestamp'],
'author': revision['author'],
'approved': bool(revision['approved_timestamp']),
'current_approved': current_approved})
-
+
response.headers['Content-Type'] = 'application/json;charset=utf-8'
return json.dumps(data)
def _get_package_type(self, id):
"""
- Given the id of a package it determines the plugin to load
+ Given the id of a package it determines the plugin to load
based on the package's type name (type). The plugin found
- will be returned, or None if there is no plugin associated with
+ will be returned, or None if there is no plugin associated with
the type.
Uses a minimal context to do so. The main use of this method
@@ -538,8 +619,8 @@ def _form_save_redirect(self, pkgname, action):
url = url.replace('', pkgname)
else:
url = h.url_for(controller='package', action='read', id=pkgname)
- redirect(url)
-
+ redirect(url)
+
def _adjust_license_id_options(self, pkg, fs):
options = fs.license_id.render_opts['options']
is_included = False
@@ -574,7 +655,7 @@ def authz(self, id):
def autocomplete(self):
# DEPRECATED in favour of /api/2/util/dataset/autocomplete
q = unicode(request.params.get('q', ''))
- if not len(q):
+ if not len(q):
return ''
context = {'model': model, 'session': model.Session,
diff --git a/ckan/lib/plugins.py b/ckan/lib/plugins.py
index 089c1b3fd31..18c9594cf94 100644
--- a/ckan/lib/plugins.py
+++ b/ckan/lib/plugins.py
@@ -127,7 +127,7 @@ def register_group_plugins(map):
# Our version of routes doesn't allow the environ to be
# passed into the match call and so we have to set it on the
# map instead. This looks like a threading problem waiting
- # to happen but it is executed sequentially from instead the
+ # to happen but it is executed sequentially from inside the
# routing setup
map.connect('%s_index' % group_type, '/%s' % group_type,
@@ -168,6 +168,41 @@ class DefaultDatasetForm(object):
Note - this isn't a plugin implementation. This is deliberate, as we
don't want this being registered.
"""
+ def new_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the new page
+ """
+ return 'package/new.html'
+
+ def comments_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the comments page
+ """
+ return 'package/comments.html'
+
+ def search_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the search page (if present)
+ """
+ return 'package/search.html'
+
+ def read_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the read page
+ """
+ return 'package/read.html'
+
+ def history_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the history page
+ """
+ return 'package/history.html'
+
def package_form(self):
return 'package/new_package_form.html'
@@ -264,6 +299,34 @@ class DefaultGroupForm(object):
Note - this isn't a plugin implementation. This is deliberate, as we
don't want this being registered.
"""
+ def new_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the 'new' page
+ """
+ return 'group/new.html'
+
+ def index_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the index page
+ """
+ return 'group/index.html'
+
+ def read_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the read page
+ """
+ return 'group/read.html'
+
+ def history_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the read page
+ """
+ return 'group/history.html'
+
def group_form(self):
return 'group/new_group_form.html'
diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py
index e5dd055fade..a36547206af 100644
--- a/ckan/logic/action/create.py
+++ b/ckan/logic/action/create.py
@@ -174,15 +174,19 @@ def member_create(context, data_dict=None):
"""
model = context['model']
user = context['user']
+ group = context['group']
+
+ rev = model.repo.new_revision()
+ rev.author = user
+ if 'message' in context:
+ rev.message = context['message']
+ else:
+ rev.message = _(u'REST API: Create member object %s') % data_dict.get("name", "")
- group_id = data_dict['group']
obj_id = data_dict['object']
obj_type = data_dict['object_type']
capacity = data_dict['capacity']
- if 'group' not in context:
- context['group'] = group_id
-
# User must be able to update the group to add a member to it
check_access('group_update', context, data_dict)
@@ -190,15 +194,14 @@ def member_create(context, data_dict=None):
member = model.Session.query(model.Member).\
filter(model.Member.table_name == obj_type).\
filter(model.Member.table_id == obj_id).\
- filter(model.Member.group_id == group_id).\
- filter(model.Member.state == "active").\
- filter(model.Member.capacity == capacity).first()
+ filter(model.Member.group_id == group.id).\
+ filter(model.Member.state == "active").first()
if member:
member.capacity = capacity
else:
member = model.Member(table_name = obj_type,
table_id = obj_id,
- group_id = group_id,
+ group_id = group.id,
capacity=capacity)
model.Session.add(member)
diff --git a/ckan/logic/action/delete.py b/ckan/logic/action/delete.py
index b2d75458f47..a8368ec6195 100644
--- a/ckan/logic/action/delete.py
+++ b/ckan/logic/action/delete.py
@@ -82,21 +82,19 @@ def member_delete(context, data_dict=None):
"""
model = context['model']
user = context['user']
+ group = context['group']
group_id = data_dict['group']
obj_id = data_dict['object']
obj_type = data_dict['object_type']
- if 'group' not in context:
- context['group'] = group_id
-
# User must be able to update the group to remove a member from it
check_access('group_update', context, data_dict)
member = model.Session.query(model.Member).\
filter(model.Member.table_name == obj_type).\
filter(model.Member.table_id == obj_id).\
- filter(model.Member.group_id == group_id).\
+ filter(model.Member.group_id == group.id).\
filter(model.Member.state == "active").first()
if member:
member.delete()
diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py
index f5bae616a84..bf28588a769 100644
--- a/ckan/logic/action/get.py
+++ b/ckan/logic/action/get.py
@@ -117,18 +117,17 @@ def member_list(context, data_dict=None):
"""
model = context['model']
user = context['user']
+ group = context['group']
group_id = data_dict['group']
obj_type = data_dict.get('object_type', None)
capacity = data_dict.get('capacity', None)
# User must be able to update the group to remove a member from it
- if 'group' not in context:
- context['group'] = group_id
check_access('group_show', context, data_dict)
q = model.Session.query(model.Member).\
- filter(model.Member.group_id == group_id).\
+ filter(model.Member.group_id == group.id).\
filter(model.Member.state == "active")
if obj_type:
diff --git a/ckan/plugins/core.py b/ckan/plugins/core.py
index 8cc05d844e4..5260164f2a0 100644
--- a/ckan/plugins/core.py
+++ b/ckan/plugins/core.py
@@ -63,14 +63,15 @@ def _get_service(plugin):
if isinstance(plugin, basestring):
try:
+ name = plugin
(plugin,) = iter_entry_points(
group=PLUGINS_ENTRY_POINT_GROUP,
- name=plugin
+ name=name
)
except ValueError:
raise PluginNotFoundException(plugin)
- return plugin.load()()
+ return plugin.load()(name=name)
elif isinstance(plugin, _pca_Plugin):
return plugin
diff --git a/ckan/plugins/interfaces.py b/ckan/plugins/interfaces.py
index aa697d9aa33..a04d8fdf473 100644
--- a/ckan/plugins/interfaces.py
+++ b/ckan/plugins/interfaces.py
@@ -9,7 +9,7 @@
'IMapper', 'ISession',
'IMiddleware',
'IAuthFunctions',
- 'IDomainObjectModification', 'IGroupController',
+ 'IDomainObjectModification', 'IGroupController',
'IPackageController', 'IPluginObserver',
'IConfigurable', 'IConfigurer', 'IAuthorizer',
'IActions', 'IResourceUrlChange', 'IDatasetForm',
@@ -78,7 +78,7 @@ def before_map(self, map):
def after_map(self, map):
"""
- Called after routes map is set up. ``after_map`` can be used to add fall-back handlers.
+ Called after routes map is set up. ``after_map`` can be used to add fall-back handlers.
:param map: Routes map object
:returns: Modified version of the map object
@@ -119,12 +119,12 @@ def after_insert(self, mapper, connection, instance):
"""
Receive an object instance after that instance is INSERTed.
"""
-
+
def after_update(self, mapper, connection, instance):
"""
Receive an object instance after that instance is UPDATEed.
"""
-
+
def after_delete(self, mapper, connection, instance):
"""
Receive an object instance after that instance is DELETEed.
@@ -183,10 +183,10 @@ def notify(self, resource):
class IGroupController(Interface):
"""
- Hook into the Group controller. These will
+ Hook into the Group controller. These will
usually be called just before committing or returning the
- respective object, i.e. all validation, synchronization
- and authorization setup are complete.
+ respective object, i.e. all validation, synchronization
+ and authorization setup are complete.
"""
def read(self, entity):
@@ -200,7 +200,7 @@ def edit(self, entity):
def authz_add_role(self, object_role):
pass
-
+
def authz_remove_role(self, object_role):
pass
@@ -231,7 +231,7 @@ def edit(self, entity):
def authz_add_role(self, object_role):
pass
-
+
def authz_remove_role(self, object_role):
pass
@@ -284,7 +284,7 @@ def before_view(self, pkg_dict):
passed will be the one that gets sent to the template.
'''
return pkg_dict
-
+
class IPluginObserver(Interface):
"""
@@ -315,26 +315,26 @@ def after_unload(self, service):
This method is passed the instantiated service object.
"""
-class IConfigurable(Interface):
+class IConfigurable(Interface):
"""
Pass configuration to plugins and extensions
"""
-
+
def configure(self, config):
"""
Called by load_environment
"""
-class IConfigurer(Interface):
+class IConfigurer(Interface):
"""
Configure CKAN (pylons) environment via the ``pylons.config`` object
"""
-
+
def update_config(self, config):
"""
Called by load_environment at earliest point when config is
available to plugins. The config should be updated in place.
-
+
:param config: ``pylons.config`` object
"""
@@ -364,14 +364,14 @@ def is_authorized(self, username, action, domain_obj):
Should return True or False. A value of False will allow
other Authorizers to run; True will shortcircuit and return.
"""
-
+
class IActions(Interface):
"""
Allow adding of actions to the logic layer.
"""
def get_actions(self):
"""
- Should return a dict, the keys being the name of the logic
+ Should return a dict, the keys being the name of the logic
function and the values being the functions themselves.
"""
@@ -442,6 +442,37 @@ def package_types(self):
##### Hooks for customising the PackageController's behaviour #####
##### TODO: flesh out the docstrings a little more. #####
+ def new_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the new page
+ """
+
+ def comments_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the comments page
+ """
+
+ def search_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the search page (if present)
+ """
+
+ def read_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the read page
+ """
+
+ def history_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the history page
+ """
+
+
def package_form(self):
"""
Returns a string representing the location of the template to be
@@ -532,7 +563,34 @@ def group_types(self):
##### Hooks for customising the PackageController's behaviour #####
##### TODO: flesh out the docstrings a little more. #####
-
+ def new_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the 'new' page. Uses the default_group_type configuration
+ option to determine which plugin to use the template from.
+ """
+
+ def index_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the index page. Uses the default_group_type configuration
+ option to determine which plugin to use the template from.
+ """
+
+ def read_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the read page
+ """
+
+ def history_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the history page
+ """
+
+
+
def package_form(self):
"""
Returns a string representing the location of the template to be
diff --git a/ckan/templates/package/read.rdf b/ckan/templates/package/read.rdf
new file mode 100644
index 00000000000..172569eba83
--- /dev/null
+++ b/ckan/templates/package/read.rdf
@@ -0,0 +1,50 @@
+
+
+
+
+ ${c.pkg_dict['name']}
+
+ ${c.pkg_dict['name']}
+ ${c.pkg_dict['title']}
+ ${c.pkg_dict['notes']}
+
+
+
+
+
+
+
+
+
+ ${ c.pkg_dict['author'] }
+
+
+
+
+
+ ${ c.pkg_dict['maintainer'] }
+
+
+
+
+ ${ tag_dict["name"] }
+
+
+
+
+
+ ${extra_dict.get('key','')}
+ ${extra_dict.get('value','')}
+
+
+
+
+
\ No newline at end of file
diff --git a/ckan/tests/functional/test_package.py b/ckan/tests/functional/test_package.py
index 6a1da9f71d2..218350b78cb 100644
--- a/ckan/tests/functional/test_package.py
+++ b/ckan/tests/functional/test_package.py
@@ -305,6 +305,14 @@ def test_read(self):
assert 'book' in res, res
assert 'This dataset satisfies the Open Definition' in res, res
+ def test_read_war_rdf(self):
+ name = u'warandpeace'
+ offset = url_for(controller='package', action='read', id=name + ".rdf")
+ res = self.app.get(offset)
+ ##TODO Ross To Fix
+ #assert 'A Wonderful Story' in res, res
+
+
def test_read_war(self):
name = u'warandpeace'
c.hide_welcome_message = True
@@ -372,7 +380,7 @@ def test_read_plugin_hook(self):
plugins.unload(plugin)
def test_resource_list(self):
- # TODO restore this test. It doesn't make much sense with the
+ # TODO restore this test. It doesn't make much sense with the
# present resource list design.
name = 'annakarenina'
cache_url = 'http://thedatahub.org/test_cache_url.csv'
@@ -1031,7 +1039,7 @@ def test_edit_pkg_with_relationships(self):
model.repo.new_revision()
pkg.add_relationship(u'depends_on', anna)
model.repo.commit_and_remove()
-
+
# check relationship before the test
rels = model.Package.by_name(self.editpkg_name).get_relationships()
assert_equal(str(rels), '[<*PackageRelationship editpkgtest depends_on annakarenina>]')
@@ -1046,7 +1054,7 @@ def test_edit_pkg_with_relationships(self):
# check relationship still exists
rels = model.Package.by_name(self.editpkg_name).get_relationships()
assert_equal(str(rels), '[<*PackageRelationship editpkgtest depends_on annakarenina>]')
-
+
finally:
self._reset_data()
@@ -1315,7 +1323,7 @@ def test_change_locale(self):
assert 'Datensatz' in res.body, res.body
finally:
self.clear_language_setting()
-
+
class TestSearch(TestPackageForm):
pkg_names = []
diff --git a/ckan/tests/logic/test_action.py b/ckan/tests/logic/test_action.py
index d6b15590af9..d1727552b6a 100644
--- a/ckan/tests/logic/test_action.py
+++ b/ckan/tests/logic/test_action.py
@@ -1296,7 +1296,7 @@ def test_28_group_package_show(self):
assert group_names == set(['annakarenina', 'warandpeace']), group_names
def test_29_group_package_show_pending(self):
- context = {'model': model, 'session': model.Session, 'user': self.sysadmin_user.name}
+ context = {'model': model, 'session': model.Session, 'user': self.sysadmin_user.name, 'api_version': 2}
group = {
'name': 'test_group_pending_package',
'packages': [{'id': model.Package.get('annakarenina').id}]
@@ -1768,22 +1768,29 @@ def before_view(self, data_dict):
return data_dict
+MockPackageSearchPlugin().disable()
+
class TestSearchPluginInterface(WsgiAppCase):
@classmethod
def setup_class(cls):
+ MockPackageSearchPlugin().activate()
+ MockPackageSearchPlugin().enable()
setup_test_search_index()
CreateTestData.create()
+ MockPackageSearchPlugin().disable()
@classmethod
def teardown_class(cls):
model.repo.rebuild_db()
- def test_search_plugin_interface_search(self):
- plugin = MockPackageSearchPlugin()
- plugins.load(plugin)
+ def setup(self):
+ MockPackageSearchPlugin().enable()
+ def teardown(self):
+ MockPackageSearchPlugin().disable()
+ def test_search_plugin_interface_search(self):
avoid = 'Tolstoy'
search_params = '%s=1' % json.dumps({
'q': '*:*',
@@ -1797,11 +1804,8 @@ def test_search_plugin_interface_search(self):
assert not avoid.lower() in result['title'].lower()
assert results_dict['count'] == 1
- plugins.unload(plugin)
def test_search_plugin_interface_abort(self):
- plugin = MockPackageSearchPlugin()
- plugins.load(plugin)
search_params = '%s=1' % json.dumps({
'q': '*:*',
@@ -1814,11 +1818,9 @@ def test_search_plugin_interface_abort(self):
res_dict = json.loads(res.body)['result']
assert res_dict['count'] == 0
assert len(res_dict['results']) == 0
- plugins.unload(plugin)
def test_before_index(self):
- plugin = MockPackageSearchPlugin()
- plugins.load(plugin)
+
# no datasets get aaaaaaaa
search_params = '%s=1' % json.dumps({
'q': 'aaaaaaaa',
@@ -1829,7 +1831,6 @@ def test_before_index(self):
res_dict = json.loads(res.body)['result']
assert res_dict['count'] == 0
assert len(res_dict['results']) == 0
- plugins.unload(plugin)
# all datasets should get abcabcabc
search_params = '%s=1' % json.dumps({
@@ -1838,12 +1839,10 @@ def test_before_index(self):
res = self.app.post('/api/action/package_search', params=search_params)
res_dict = json.loads(res.body)['result']
- assert res_dict['count'] == 2
+ assert res_dict['count'] == 2, res_dict['count']
assert len(res_dict['results']) == 2
def test_before_view(self):
- plugin = MockPackageSearchPlugin()
- plugins.load(plugin)
res = self.app.get('/dataset/annakarenina')
assert 'string_not_found_in_rest_of_template' in res.body
@@ -1851,5 +1850,4 @@ def test_before_view(self):
res = self.app.get('/dataset?q=')
assert res.body.count('string_not_found_in_rest_of_template') == 2
- plugins.unload(plugin)
diff --git a/ckan/tests/logic/test_member.py b/ckan/tests/logic/test_member.py
index dcb6e8cba73..3c7aaa5d3a5 100644
--- a/ckan/tests/logic/test_member.py
+++ b/ckan/tests/logic/test_member.py
@@ -9,21 +9,26 @@ def setup_class(cls):
cls.username = 'testsysadmin'
cls.groupname = 'david'
- model.Session.remove()
- CreateTestData.create()
- model.Session.remove()
model.repo.new_revision()
+ CreateTestData.create()
+ cls.pkgs = [
+ model.Package.by_name('warandpeace'),
+ model.Package.by_name('annakarenina'),
+ ]
@classmethod
def teardown_class(cls):
model.repo.rebuild_db()
def _build_context( self, obj, obj_type, capacity='member'):
+ grp = model.Group.by_name(self.groupname)
ctx = { 'model': model,
'session': model.Session,
- 'user':self.username}
+ 'user':self.username,
+ 'group': grp,
+ }
dd = {
- 'group': self.groupname,
+ 'group': grp,
'object': obj,
'object_type': obj_type,
'capacity': capacity }
@@ -34,17 +39,15 @@ def _add_member( self, obj, obj_type, capacity):
return get_action('member_create')(ctx,dd)
def test_member_add(self):
- res = self._add_member( 'warandpeace', 'package', 'member')
+ res = self._add_member( self.pkgs[0].id, 'package', 'member')
assert 'capacity' in res and res['capacity'] == u'member'
- assert 'group_id' in res and res['group_id'] == u'david'
def test_member_list(self):
- _ = self._add_member( 'warandpeace', 'package', 'member')
- _ = self._add_member( 'annakarenina', 'package', 'member')
+ _ = self._add_member( self.pkgs[0].id, 'package', 'member')
+ _ = self._add_member( self.pkgs[1].id, 'package', 'member')
ctx, dd = self._build_context('','package')
res = get_action('member_list')(ctx,dd)
- assert res[0][0] == 'warandpeace', res
- assert res[1][0] == 'annakarenina', res
+ assert len(res) == 2, res
ctx, dd = self._build_context('','user', 'admin')
res = get_action('member_list')(ctx,dd)
diff --git a/ckanext/publisher_form/forms.py b/ckanext/publisher_form/forms.py
index 919127537d1..672209f6b4a 100644
--- a/ckanext/publisher_form/forms.py
+++ b/ckanext/publisher_form/forms.py
@@ -55,6 +55,36 @@ def update_config(self, config):
# Override /group/* as the default groups urls
config['ckan.default.group_type'] = 'publisher'
+ def new_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the new page
+ """
+ return 'publisher_new.html'
+
+ def index_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the index page
+ """
+ return 'publisher_index.html'
+
+
+ def read_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the read page
+ """
+ return 'publisher_read.html'
+
+ def history_template(self):
+ """
+ Returns a string representing the location of the template to be
+ rendered for the read page
+ """
+ return 'publisher_history.html'
+
+
def group_form(self):
"""
Returns a string representing the location of the template to be
diff --git a/ckanext/publisher_form/templates/publisher_index.html b/ckanext/publisher_form/templates/publisher_index.html
new file mode 100644
index 00000000000..321e45197b2
--- /dev/null
+++ b/ckanext/publisher_form/templates/publisher_index.html
@@ -0,0 +1,24 @@
+
+
+ Publishers of Datasets
+ Publishers of Datasets
+
+
+
+
What Are Publishers?
+ Whilst tags are great at collecting datasets together, there are occasions when you want to restrict users from editing a collection. A publisher can be set-up to specify which users have permission to add or remove datasets from it.
+