diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py
index 2915b1b997c..f3f8508c6f4 100644
--- a/ckan/controllers/group.py
+++ b/ckan/controllers/group.py
@@ -4,32 +4,38 @@
import datetime
from urllib import urlencode
-from ckan.lib.base import BaseController, c, model, request, render, g
-from ckan.lib.base import ValidationException, abort, gettext
+from pylons.i18n import get_lang
+
import ckan.lib.base as base
-from pylons.i18n import get_lang, _
import ckan.lib.helpers as h
import ckan.lib.maintain as maintain
-from ckan.lib.navl.dictization_functions import DataError, unflatten, validate
-from ckan.logic import NotFound, NotAuthorized, ValidationError
-from ckan.logic import check_access, get_action
-from ckan.logic import tuplize_dict, clean_dict, parse_params
-import ckan.logic.action.get
+import ckan.lib.navl.dictization_functions as dict_fns
+import ckan.logic as logic
import ckan.lib.search as search
-import ckan.new_authz
-
-from ckan.lib.plugins import lookup_group_plugin
+import ckan.model as model
+import ckan.new_authz as new_authz
+import ckan.lib.plugins
import ckan.plugins as plugins
-
-try:
- from collections import OrderedDict # 2.7
-except ImportError:
- from sqlalchemy.util import OrderedDict
+from ckan.common import OrderedDict, c, g, request, _
log = logging.getLogger(__name__)
+render = base.render
+abort = base.abort
+
+NotFound = logic.NotFound
+NotAuthorized = logic.NotAuthorized
+ValidationError = logic.ValidationError
+check_access = logic.check_access
+get_action = logic.get_action
+tuplize_dict = logic.tuplize_dict
+clean_dict = logic.clean_dict
+parse_params = logic.parse_params
+
+lookup_group_plugin = ckan.lib.plugins.lookup_group_plugin
+
-class GroupController(BaseController):
+class GroupController(base.BaseController):
group_type = 'group'
@@ -48,7 +54,7 @@ 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)
+ setup_template_variables(context, data_dict)
def _new_template(self, group_type):
return lookup_group_plugin(group_type).new_template()
@@ -77,7 +83,6 @@ def _admins_template(self, group_type):
def _bulk_process_template(self, group_type):
return lookup_group_plugin(group_type).bulk_process_template()
-
## end hooks
def _replace_group_org(self, string):
''' substitute organization for group if this is an org'''
@@ -134,8 +139,7 @@ def index(self):
'with_private': False}
q = c.q = request.params.get('q', '')
- data_dict = {'all_fields': True,
- 'q': q,}
+ data_dict = {'all_fields': True, 'q': q}
sort_by = c.sort_by_selected = request.params.get('sort')
if sort_by:
data_dict['sort'] = sort_by
@@ -199,7 +203,7 @@ def _read(self, id, limit):
try:
description_formatted = ckan.misc.MarkdownFormat().to_html(
- c.group_dict.get('description', ''))
+ c.group_dict.get('description', ''))
c.description_formatted = genshi.HTML(description_formatted)
except Exception, e:
error_msg = "%s" %\
@@ -210,7 +214,7 @@ def _read(self, id, limit):
# c.group_admins is used by CKAN's legacy (Genshi) templates only,
# if we drop support for those then we can delete this line.
- c.group_admins = ckan.new_authz.get_group_or_org_admin_ids(c.group.id)
+ c.group_admins = new_authz.get_group_or_org_admin_ids(c.group.id)
try:
page = int(request.params.get('page', 1))
@@ -234,8 +238,7 @@ def search_url(params):
action='read',
id=id)
else:
- url = self._url_for(controller='group', action='read',
- id=id)
+ url = self._url_for(controller='group', action='read', id=id)
params = [(k, v.encode('utf-8') if isinstance(v, basestring)
else str(v)) for k, v in params]
return url + u'?' + urlencode(params)
@@ -250,8 +253,8 @@ def drill_down_url(**by):
def remove_field(key, value=None, replace=None):
return h.remove_url_param(key, value=value, replace=replace,
- controller='group', action='read',
- extras=dict(id=c.group_dict.get('name')))
+ controller='group', action='read',
+ extras=dict(id=c.group_dict.get('name')))
c.remove_field = remove_field
@@ -283,9 +286,9 @@ def pager_url(q=None, page=None):
facets = OrderedDict()
default_facet_titles = {'groups': _('Groups'),
- 'tags': _('Tags'),
- 'res_format': _('Formats'),
- 'license': _('Licence'), }
+ 'tags': _('Tags'),
+ 'res_format': _('Formats'),
+ 'license': _('Licence')}
for facet in g.facets:
if facet in default_facet_titles:
@@ -302,7 +305,8 @@ def pager_url(q=None, page=None):
facets = plugin.group_facets(
facets, self.group_type, None)
- if 'capacity' in facets and (self.group_type != 'organization' or not user_member_of_orgs):
+ if 'capacity' in facets and (self.group_type != 'organization' or
+ not user_member_of_orgs):
del facets['capacity']
c.facet_titles = facets
@@ -328,9 +332,8 @@ def pager_url(q=None, page=None):
)
c.facets = query['facets']
- maintain.deprecate_context_item(
- 'facets',
- 'Use `c.search_facets` instead.')
+ maintain.deprecate_context_item('facets',
+ 'Use `c.search_facets` instead.')
c.search_facets = query['search_facets']
c.search_facets_limits = {}
@@ -347,15 +350,13 @@ def pager_url(q=None, page=None):
c.facets = {}
c.page = h.Page(collection=[])
-
-
def bulk_process(self, id):
''' Allow bulk processing of datasets for an organization. Make
private/public or delete. For organization admins.'''
group_type = self._get_group_type(id.split('@')[0])
- if group_type != 'organization':
+ if group_type != 'organization':
# FIXME: better error
raise Exception('Must be an organization')
@@ -385,7 +386,8 @@ def bulk_process(self, id):
c.packages = c.page.items
return render(self._bulk_process_template(group_type))
- # process the action first find the datasets to perform the action on. they are prefixed by dataset_ in the form data
+ # process the action first find the datasets to perform the action on.
+ # they are prefixed by dataset_ in the form data
datasets = []
for param in request.params:
if param.startswith('dataset_'):
@@ -403,8 +405,9 @@ def bulk_process(self, id):
get_action(action_functions[action])(context, data_dict)
except NotAuthorized:
abort(401, _('Not authorized to perform bulk update'))
- base.redirect(h.url_for(controller='organization', action='bulk_process',
- id=id))
+ base.redirect(h.url_for(controller='organization',
+ action='bulk_process',
+ id=id))
def new(self, data=None, errors=None, error_summary=None):
group_type = self._guess_group_type(True)
@@ -486,7 +489,7 @@ def _get_group_type(self, id):
def _save_new(self, context, group_type=None):
try:
- data_dict = clean_dict(unflatten(
+ data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.params))))
data_dict['type'] = group_type or 'group'
context['message'] = data_dict.get('log_message', '')
@@ -499,7 +502,7 @@ def _save_new(self, context, group_type=None):
abort(401, _('Unauthorized to read group %s') % '')
except NotFound, e:
abort(404, _('Group not found'))
- except DataError:
+ except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except ValidationError, e:
errors = e.error_dict
@@ -517,7 +520,7 @@ def _force_reindex(self, grp):
def _save_edit(self, id, context):
try:
- data_dict = clean_dict(unflatten(
+ data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.params))))
context['message'] = data_dict.get('log_message', '')
data_dict['id'] = id
@@ -532,7 +535,7 @@ def _save_edit(self, id, context):
abort(401, _('Unauthorized to read group %s') % id)
except NotFound, e:
abort(404, _('Group not found'))
- except DataError:
+ except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except ValidationError, e:
errors = e.error_dict
@@ -556,8 +559,8 @@ def authz(self, id):
c.authz_editable = False
if not c.authz_editable:
abort(401,
- gettext('User %r not authorized to edit %s authorizations') %
- (c.user, id))
+ _('User %r not authorized to edit %s authorizations') %
+ (c.user, id))
roles = self._handle_update_of_authz(group)
self._prepare_authz_info_for_render(roles)
@@ -595,8 +598,9 @@ def members(self, id):
'user': c.user or c.author}
try:
- c.members = self._action('member_list')(context, {'id': id,
- 'object_type': 'user'})
+ c.members = self._action('member_list')(
+ context, {'id': id, 'object_type': 'user'}
+ )
c.group_dict = self._action('group_show')(context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s') % '')
@@ -611,7 +615,7 @@ def member_new(self, id):
#self._check_access('group_delete', context, {'id': id})
try:
if request.method == 'POST':
- data_dict = clean_dict(unflatten(
+ data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.params))))
data_dict['id'] = id
c.group_dict = self._action('group_member_create')(context, data_dict)
@@ -620,7 +624,7 @@ def member_new(self, id):
user = request.params.get('user')
if user:
c.user_dict = get_action('user_show')(context, {'id': user})
- c.user_role = ckan.new_authz.users_role_for_group_or_org(id, user) or 'member'
+ c.user_role = new_authz.users_role_for_group_or_org(id, user) or 'member'
else:
c.user_role = 'member'
c.group_dict = self._action('group_show')(context, {'id': id})
@@ -658,7 +662,6 @@ def member_delete(self, id):
abort(404, _('Group not found'))
return self._render_template('group/confirm_delete_member.html')
-
def history(self, id):
if 'diff' in request.params or 'selected1' in request.params:
try:
@@ -682,7 +685,7 @@ def history(self, id):
try:
c.group_dict = self._action('group_show')(context, data_dict)
c.group_revisions = self._action('group_revision_list')(context,
- data_dict)
+ data_dict)
#TODO: remove
# Still necessary for the authz check in group/layout.html
c.group = context['group']
@@ -698,7 +701,7 @@ def history(self, id):
feed = Atom1Feed(
title=_(u'CKAN Group Revision History'),
link=self._url_for(controller='group', action='read',
- id=c.group_dict['name']),
+ id=c.group_dict['name']),
description=_(u'Recent changes to CKAN Group: ') +
c.group_dict['display_name'],
language=unicode(get_lang()),
@@ -755,7 +758,7 @@ def activity(self, id, offset=0):
# Add the group's activity stream (already rendered to HTML) to the
# template context for the group/read.html template to retrieve later.
c.group_activity_stream = get_action('group_activity_list_html')(
- context, {'id': c.group_dict['id'], 'offset': offset})
+ context, {'id': c.group_dict['id'], 'offset': offset})
#return render('group/activity_stream.html')
return render(self._activity_template(c.group_dict['type']))
@@ -773,7 +776,7 @@ def follow(self, id):
group_dict['title']))
except ValidationError as e:
error_message = (e.extra_msg or e.message or e.error_summary
- or e.error_dict)
+ or e.error_dict)
h.flash_error(error_message)
except NotAuthorized as e:
h.flash_error(e.extra_msg)
@@ -792,7 +795,7 @@ def unfollow(self, id):
group_dict['title']))
except ValidationError as e:
error_message = (e.extra_msg or e.message or e.error_summary
- or e.error_dict)
+ or e.error_dict)
h.flash_error(error_message)
except (NotFound, NotAuthorized) as e:
error_message = e.extra_msg or e.message
@@ -804,15 +807,14 @@ def followers(self, id):
'user': c.user or c.author}
c.group_dict = self._get_group_dict(id)
try:
- c.followers = get_action('group_follower_list')(context,
- {'id': id})
+ c.followers = get_action('group_follower_list')(context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to view followers %s') % '')
return render('group/followers.html')
def admins(self, id):
c.group_dict = self._get_group_dict(id)
- c.admins = ckan.new_authz.get_group_or_org_admin_ids(id)
+ c.admins = new_authz.get_group_or_org_admin_ids(id)
return render(self._admins_template(c.group_dict['type']))
def about(self, id):
@@ -845,7 +847,7 @@ def _update(self, fs, group_name, group_id):
validation = fs.validate()
if not validation:
c.form = self._render_edit_form(fs)
- raise ValidationException(fs)
+ raise base.ValidationException(fs)
try:
fs.sync()
@@ -859,7 +861,7 @@ def _update_authz(self, fs):
validation = fs.validate()
if not validation:
c.form = self._render_edit_form(fs)
- raise ValidationException(fs)
+ raise base.ValidationException(fs)
try:
fs.sync()
except Exception, inst:
diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py
index cb7b0c19b77..00cc0b09a37 100644
--- a/ckan/controllers/package.py
+++ b/ckan/controllers/package.py
@@ -3,46 +3,43 @@
import datetime
from pylons import config
-from pylons.i18n import _
from genshi.template import MarkupTemplate
from genshi.template.text import NewTextTemplate
from paste.deploy.converters import asbool
-from ckan.logic import get_action, check_access
-from ckan.lib.base import (request,
- render,
- BaseController,
- model,
- abort, g, c)
-from ckan.lib.base import response, redirect, gettext
+import ckan.logic as logic
+import ckan.lib.base as base
import ckan.lib.maintain as maintain
-from ckan.lib.package_saver import PackageSaver, ValidationException
-from ckan.lib.navl.dictization_functions import DataError, unflatten, validate
-from ckan.logic import NotFound, NotAuthorized, ValidationError
-from ckan.logic import (tuplize_dict,
- clean_dict,
- parse_params,
- flatten_to_string_key)
-from ckan.lib.i18n import get_lang
-import ckan.rating
-import ckan.misc
+import ckan.lib.package_saver as package_saver
+import ckan.lib.i18n as i18n
+import ckan.lib.navl.dictization_functions as dict_fns
import ckan.lib.accept as accept
import ckan.lib.helpers as h
+import ckan.model as model
import ckan.lib.datapreview as datapreview
-import ckan.plugins as plugins
-from home import CACHE_PARAMETERS
-
-from ckan.lib.plugins import lookup_package_plugin
+import ckan.lib.plugins
import ckan.plugins as p
-
-try:
- from collections import OrderedDict # 2.7
-except ImportError:
- from sqlalchemy.util import OrderedDict
+from ckan.common import OrderedDict, _, json, request, c, g, response
+from home import CACHE_PARAMETERS
log = logging.getLogger(__name__)
+render = base.render
+abort = base.abort
+redirect = base.redirect
+
+NotFound = logic.NotFound
+NotAuthorized = logic.NotAuthorized
+ValidationError = logic.ValidationError
+check_access = logic.check_access
+get_action = logic.get_action
+tuplize_dict = logic.tuplize_dict
+clean_dict = logic.clean_dict
+parse_params = logic.parse_params
+flatten_to_string_key = logic.flatten_to_string_key
+
+lookup_package_plugin = ckan.lib.plugins.lookup_package_plugin
def _encode_params(params):
return [(k, v.encode('utf-8') if isinstance(v, basestring) else str(v))
@@ -62,24 +59,11 @@ def search_url(params, package_type=None):
return url_with_params(url, params)
-class PackageController(BaseController):
+class PackageController(base.BaseController):
def _package_form(self, package_type=None):
return lookup_package_plugin(package_type).package_form()
- def _form_to_db_schema(self, package_type=None):
- return lookup_package_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_package_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_package_plugin(package_type).check_data_dict(data_dict)
-
def _setup_template_variables(self, context, data_dict, package_type=None):
return lookup_package_plugin(package_type).\
setup_template_variables(context, data_dict)
@@ -245,7 +229,7 @@ def pager_url(q=None, page=None):
facets[facet] = facet
# Facet titles
- for plugin in plugins.PluginImplementations(plugins.IFacets):
+ for plugin in p.PluginImplementations(p.IFacets):
facets = plugin.dataset_facets(facets, package_type)
c.facet_titles = facets
@@ -363,7 +347,7 @@ def read(self, id, format='html'):
self._setup_template_variables(context, {'id': id},
package_type=package_type)
- PackageSaver().render_package(c.pkg_dict, context)
+ package_saver.PackageSaver().render_package(c.pkg_dict, context)
template = self._read_template(package_type)
template = template[:template.index('.') + 1] + format
@@ -388,7 +372,7 @@ def comments(self, id):
c.current_package_id = c.pkg.id
#render the package
- PackageSaver().render_package(c.pkg_dict)
+ package_saver.PackageSaver().render_package(c.pkg_dict)
return render(self._comments_template(package_type))
def history(self, id):
@@ -435,7 +419,7 @@ def history(self, id):
id=c.pkg_dict['name']),
description=_(u'Recent changes to CKAN Dataset: ') +
(c.pkg_dict['title'] or ''),
- language=unicode(get_lang()),
+ language=unicode(i18n.get_lang()),
)
for revision_dict in c.pkg_revisions:
revision_date = h.date_str_to_datetime(
@@ -489,7 +473,7 @@ def new(self, data=None, errors=None, error_summary=None):
if context['save'] and not data:
return self._save_new(context, package_type=package_type)
- data = data or clean_dict(unflatten(tuplize_dict(parse_params(
+ data = data or clean_dict(dict_fns.unflatten(tuplize_dict(parse_params(
request.params, ignore_keys=CACHE_PARAMETERS))))
c.resources_json = h.json.dumps(data.get('resources', []))
# convert tags if not supplied in data
@@ -533,7 +517,7 @@ def new(self, data=None, errors=None, error_summary=None):
def resource_edit(self, id, resource_id, data=None, errors=None,
error_summary=None):
if request.method == 'POST' and not data:
- data = data or clean_dict(unflatten(tuplize_dict(parse_params(
+ data = data or clean_dict(dict_fns.unflatten(tuplize_dict(parse_params(
request.POST))))
# we don't want to include save as it is part of the form
del data['save']
@@ -598,7 +582,7 @@ def new_resource(self, id, data=None, errors=None, error_summary=None):
forms. '''
if request.method == 'POST' and not data:
save_action = request.params.get('save')
- data = data or clean_dict(unflatten(tuplize_dict(parse_params(
+ data = data or clean_dict(dict_fns.unflatten(tuplize_dict(parse_params(
request.POST))))
# we don't want to include save as it is part of the form
del data['save']
@@ -688,7 +672,7 @@ def new_metadata(self, id, data=None, errors=None, error_summary=None):
if request.method == 'POST' and not data:
save_action = request.params.get('save')
- data = data or clean_dict(unflatten(tuplize_dict(parse_params(
+ data = data or clean_dict(dict_fns.unflatten(tuplize_dict(parse_params(
request.POST))))
# we don't want to include save as it is part of the form
del data['save']
@@ -809,22 +793,14 @@ def read_ajax(self, id, revision=None):
package_type = self._get_package_type(id)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
- 'schema': self._form_to_db_schema(
- package_type=package_type),
'revision_id': revision}
try:
data = get_action('package_show')(context, {'id': id})
- schema = self._db_to_form_schema(package_type=package_type)
- if schema:
- data, errors = validate(data, schema)
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % '')
except NotFound:
abort(404, _('Dataset not found'))
- ## hack as db_to_form schema should have this
- data['tag_string'] = ', '.join([tag['name'] for tag
- in data.get('tags', [])])
data.pop('tags')
data = flatten_to_string_key(data)
response.headers['Content-Type'] = 'application/json;charset=utf-8'
@@ -889,12 +865,9 @@ def _save_new(self, context, package_type=None):
# this is a real new.
is_an_update = False
ckan_phase = request.params.get('_ckan_phase')
- if ckan_phase:
- # phased add dataset so use api schema for validation
- context['api_version'] = 3
from ckan.lib.search import SearchIndexError
try:
- data_dict = clean_dict(unflatten(
+ data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.POST))))
if ckan_phase:
# prevent clearing of groups etc
@@ -943,7 +916,7 @@ def _save_new(self, context, package_type=None):
abort(401, _('Unauthorized to read package %s') % '')
except NotFound, e:
abort(404, _('Dataset not found'))
- except DataError:
+ except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except SearchIndexError, e:
try:
@@ -969,7 +942,7 @@ def _save_edit(self, name_or_id, context, package_type=None):
log.debug('Package save request name: %s POST: %r',
name_or_id, request.POST)
try:
- data_dict = clean_dict(unflatten(
+ data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.POST))))
if '_ckan_phase' in data_dict:
# we allow partial updates to not destroy existing resources
@@ -994,7 +967,7 @@ def _save_edit(self, name_or_id, context, package_type=None):
abort(401, _('Unauthorized to read package %s') % id)
except NotFound, e:
abort(404, _('Dataset not found'))
- except DataError:
+ except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except SearchIndexError, e:
try:
@@ -1039,7 +1012,7 @@ def _adjust_license_id_options(self, pkg, fs):
def authz(self, id):
pkg = model.Package.get(id)
if pkg is None:
- abort(404, gettext('Dataset not found'))
+ abort(404, _('Dataset not found'))
# needed to add in the tab bar to the top of the auth page
c.pkg = pkg
c.pkgname = pkg.name
@@ -1053,7 +1026,7 @@ def authz(self, id):
except NotAuthorized:
c.authz_editable = False
if not c.authz_editable:
- abort(401, gettext('User %r not authorized to edit %s '
+ abort(401, _('User %r not authorized to edit %s '
'authorizations') % (c.user, id))
roles = self._handle_update_of_authz(pkg)
@@ -1166,7 +1139,7 @@ def _update_authz(self, fs):
validation = fs.validate()
if not validation:
c.form = self._render_edit_form(fs, request.params)
- raise ValidationException(fs)
+ raise package_saver.ValidationException(fs)
try:
fs.sync()
except Exception, inst:
@@ -1414,7 +1387,7 @@ def resource_datapreview(self, id, resource_id):
plugin = plugins_that_can_preview[0]
plugin.setup_template_variables(context, data_dict)
- c.resource_json = h.json.dumps(c.resource)
+ c.resource_json = json.dumps(c.resource)
except NotFound:
abort(404, _('Resource not found'))
diff --git a/ckan/controllers/tag.py b/ckan/controllers/tag.py
index bc45347db75..a736c17e8d0 100644
--- a/ckan/controllers/tag.py
+++ b/ckan/controllers/tag.py
@@ -1,5 +1,5 @@
from pylons.i18n import _
-from pylons import request, c
+from pylons import request, c, config
import ckan.logic as logic
import ckan.model as model
@@ -65,4 +65,7 @@ def read(self, id):
except logic.NotFound:
base.abort(404, _('Tag not found'))
- return base.render('tag/read.html')
+ if h.asbool(config.get('ckan.legacy_templates', False)):
+ return base.render('tag/read.html')
+ else:
+ h.redirect_to(controller='package', action='search', tags=c.tag.get('name'))
diff --git a/ckan/lib/activity_streams_session_extension.py b/ckan/lib/activity_streams_session_extension.py
index 08a6e37756c..e1945657462 100644
--- a/ckan/lib/activity_streams_session_extension.py
+++ b/ckan/lib/activity_streams_session_extension.py
@@ -1,7 +1,11 @@
+from pylons import config
from sqlalchemy.orm.session import SessionExtension
+from paste.deploy.converters import asbool
import logging
+
logger = logging.getLogger(__name__)
+
def activity_stream_item(obj, activity_type, revision, user_id):
method = getattr(obj, "activity_stream_item", None)
if callable(method):
@@ -11,6 +15,7 @@ def activity_stream_item(obj, activity_type, revision, user_id):
"activity_stream_item() method, it must not be a package.")
return None
+
def activity_stream_detail(obj, activity_id, activity_type):
method = getattr(obj, "activity_stream_detail",
None)
@@ -21,6 +26,7 @@ def activity_stream_detail(obj, activity_id, activity_type):
"activity_stream_detail() method.")
return None
+
class DatasetActivitySessionExtension(SessionExtension):
"""Session extension that emits activity stream activities for packages
and related objects.
@@ -36,6 +42,8 @@ class DatasetActivitySessionExtension(SessionExtension):
"""
def before_commit(self, session):
+ if not asbool(config.get('ckan.activity_streams_enabled', 'true')):
+ return
session.flush()
diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py
index d6ed92f4089..48dab3d58ec 100644
--- a/ckan/lib/helpers.py
+++ b/ckan/lib/helpers.py
@@ -323,38 +323,57 @@ def _create_link_text(text, **kwargs):
)
-def nav_link(text, controller, **kwargs):
+def nav_link(text, *args, **kwargs):
'''
params
class_: pass extra class(s) to add to the tag
icon: name of ckan icon to use within the link
condition: if False then no link is returned
'''
- if kwargs.pop('condition', True):
+ if len(args) > 1:
+ raise Exception('Too many unnamed parameters supplied')
+ if args:
kwargs['controller'] = controller
- link = _link_to(text, **kwargs)
+ log.warning('h.nav_link() please supply controller as a named '
+ 'parameter not a positional one')
+ named_route = kwargs.pop('named_route', '')
+ if kwargs.pop('condition', True):
+ if named_route:
+ link = _link_to(text, named_route, **kwargs)
+ else:
+ link = _link_to(text, **kwargs)
else:
link = ''
return link
-def nav_named_link(text, name, **kwargs):
- '''Create a link for a named route.'''
- return _link_to(text, name, **kwargs)
+@maintain.deprecated('h.nav_named_link is deprecated please '
+ 'use h.nav_link\nNOTE: you will need to pass the '
+ 'route_name as a named parameter')
+def nav_named_link(text, named_route, **kwargs):
+ '''Create a link for a named route.
+ Deprecated in ckan 2.0 '''
+ return nav_link(text, named_route=named_route, **kwargs)
+@maintain.deprecated('h.subnav_link is deprecated please '
+ 'use h.nav_link\nNOTE: if action is passed as the second '
+ 'parameter make sure it is passed as a named parameter '
+ 'eg. `action=\'my_action\'')
def subnav_link(text, action, **kwargs):
- '''Create a link for a named route.'''
+ '''Create a link for a named route.
+ Deprecated in ckan 2.0 '''
kwargs['action'] = action
- return _link_to(text, **kwargs)
+ return nav_link(text, **kwargs)
@maintain.deprecated('h.subnav_named_route is deprecated please '
- 'use h.nav_named_link')
-def subnav_named_route(text, routename, **kwargs):
+ 'use h.nav_link\nNOTE: you will need to pass the '
+ 'route_name as a named parameter')
+def subnav_named_route(text, named_route, **kwargs):
'''Generate a subnav element based on a named route
Deprecated in ckan 2.0 '''
- return nav_named_link(text, routename, **kwargs)
+ return nav_link(text, named_route=named_route, **kwargs)
def build_nav_main(*args):
@@ -433,7 +452,7 @@ def _make_menu_item(menu_item, title, **kw):
if need not in kw:
raise Exception('menu item `%s` need parameter `%s`'
% (menu_item, need))
- link = nav_named_link(title, menu_item, suppress_active_class=True, **item)
+ link = _link_to(title, menu_item, suppress_active_class=True, **item)
if active:
return literal('') + link + literal('')
return literal('') + link + literal('')
diff --git a/ckan/lib/plugins.py b/ckan/lib/plugins.py
index f71dd75f04d..5f643c0e26d 100644
--- a/ckan/lib/plugins.py
+++ b/ckan/lib/plugins.py
@@ -158,133 +158,30 @@ def register_group_plugins(map):
class DefaultDatasetForm(object):
- """
- Provides a default implementation of the pluggable package
- controller behaviour.
+ '''The default implementation of IDatasetForm.
- This class has 2 purposes:
+ See ckan.plugins.interfaces.IDatasetForm.
- - it provides a base class for IDatasetForm implementations to use
- if only a subset of the 5 method hooks need to be customised.
+ This class has two purposes:
- - it provides the fallback behaviour if no plugin is setup to
- provide the fallback behaviour.
+ 1. It provides a base class for IDatasetForm implementations to inherit
+ from.
+
+ 2. It is used as the default fallback plugin, if no IDatasetForm plugin
+ registers itself as the fallback.
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 edit_template(self):
- """
- Returns a string representing the location of the template to be
- rendered for the edit page
- """
- return 'package/edit.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'
-
- def form_to_db_schema_options(self, options):
- ''' This allows us to select different schemas for different
- purpose eg via the web interface or via the api or creation vs
- updating. It is optional and if not available form_to_db_schema
- should be used.
- If a context is provided, and it contains a schema, it will be
- returned.
- '''
- schema = options.get('context', {}).get('schema', None)
- if schema:
- return schema
- if options.get('api'):
- if options.get('type') == 'create':
- return self.form_to_db_schema_api_create()
- else:
- assert options.get('type') == 'update'
- return self.form_to_db_schema_api_update()
- else:
- return self.form_to_db_schema()
-
- def form_to_db_schema(self):
- return logic.schema.form_to_db_package_schema()
-
- def form_to_db_schema_api_create(self):
- return logic.schema.default_create_package_schema()
-
- def form_to_db_schema_api_update(self):
- return logic.schema.default_update_package_schema()
-
- def db_to_form_schema(self):
- '''This is an interface to manipulate data from the database
- into a format suitable for the form (optional)'''
- return logic.schema.db_to_form_package_schema()
+ '''
+ def create_package_schema(self):
+ return ckan.logic.schema.default_create_package_schema()
- def db_to_form_schema_options(self, options):
- '''This allows the selectino of different schemas for different
- purposes. It is optional and if not available, ``db_to_form_schema``
- should be used.
- If a context is provided, and it contains a schema, it will be
- returned.
- '''
- schema = options.get('context', {}).get('schema', None)
- if schema:
- return schema
- return self.db_to_form_schema()
+ def update_package_schema(self):
+ return ckan.logic.schema.default_update_package_schema()
- def check_data_dict(self, data_dict, schema=None):
- '''Check if the return data is correct, mostly for checking out
- if spammers are submitting only part of the form'''
-
- # Resources might not exist yet (eg. Add Dataset)
- surplus_keys_schema = ['__extras', '__junk', 'state', 'groups',
- 'extras_validation', 'save', 'return_to',
- 'resources', 'type', 'owner_org', 'private',
- 'log_message', 'tag_string', 'tags',
- 'url', 'version', 'extras']
-
- if not schema:
- schema = self.form_to_db_schema()
- schema_keys = schema.keys()
- keys_in_schema = set(schema_keys) - set(surplus_keys_schema)
-
- missing_keys = keys_in_schema - set(data_dict.keys())
- if missing_keys:
- log.info('incorrect form fields posted, missing %s' % missing_keys)
- raise dictization_functions.DataError(data_dict)
+ def show_package_schema(self):
+ return ckan.logic.schema.default_show_package_schema()
def setup_template_variables(self, context, data_dict):
authz_fn = logic.get_action('group_list_authz')
@@ -311,6 +208,27 @@ def setup_template_variables(self, context, data_dict):
except logic.NotAuthorized:
c.auth_for_change_state = False
+ def new_template(self):
+ return 'package/new.html'
+
+ def read_template(self):
+ return 'package/read.html'
+
+ def edit_template(self):
+ return 'package/edit.html'
+
+ def comments_template(self):
+ return 'package/comments.html'
+
+ def search_template(self):
+ return 'package/search.html'
+
+ def history_template(self):
+ return 'package/history.html'
+
+ def package_form(self):
+ return 'package/new_package_form.html'
+
class DefaultGroupForm(object):
"""
diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py
index c1ece99aa19..df259500790 100644
--- a/ckan/logic/action/create.py
+++ b/ckan/logic/action/create.py
@@ -1,5 +1,7 @@
import logging
+from pylons import config
from pylons.i18n import _
+from paste.deploy.converters import asbool
import ckan.new_authz as new_authz
import ckan.lib.plugins as lib_plugins
@@ -107,22 +109,22 @@ def package_create(context, data_dict):
package_type = data_dict.get('type')
package_plugin = lib_plugins.lookup_package_plugin(package_type)
- try:
- schema = package_plugin.form_to_db_schema_options({'type':'create',
- 'api':'api_version' in context,
- 'context': context})
- except AttributeError:
- schema = package_plugin.form_to_db_schema()
+ schema = package_plugin.create_package_schema()
_check_access('package_create', context, data_dict)
if 'api_version' not in context:
- # old plugins do not support passing the schema so we need
- # to ensure they still work
- try:
- package_plugin.check_data_dict(data_dict, schema)
- except TypeError:
- package_plugin.check_data_dict(data_dict)
+ # check_data_dict() is deprecated. If the package_plugin has a
+ # check_data_dict() we'll call it, if it doesn't have the method we'll
+ # do nothing.
+ check_data_dict = getattr(package_plugin, 'check_datadict', None)
+ if check_data_dict:
+ try:
+ check_data_dict(data_dict, schema)
+ except TypeError:
+ # Old plugins do not support passing the schema so we need
+ # to ensure they still work
+ package_plugin.check_data_dict(data_dict)
data, errors = _validate(data_dict, schema, context)
log.debug('package_create validate_errs=%r user=%s package=%s data=%r',
@@ -178,19 +180,6 @@ def package_create(context, data_dict):
return output
-def package_create_validate(context, data_dict):
- model = context['model']
- schema = lib_plugins.lookup_package_plugin().form_to_db_schema()
-
- _check_access('package_create',context,data_dict)
-
- data, errors = _validate(data_dict, schema, context)
- if errors:
- model.Session.rollback()
- raise ValidationError(errors)
- else:
- return data
-
def resource_create(context, data_dict):
'''Appends a new resource to a datasets list of resources.
@@ -435,13 +424,14 @@ def member_create(context, data_dict=None):
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").first()
+ 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,
+ state = 'active',
capacity=capacity)
model.Session.add(member)
@@ -901,6 +891,9 @@ def activity_create(context, activity_dict, ignore_auth=False):
:rtype: dictionary
'''
+ if not asbool(config.get('ckan.activity_streams_enabled', 'true')):
+ return
+
model = context['model']
# Any revision_id that the caller attempts to pass in the activity_dict is
diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py
index 19c18aad479..45417d8f8a6 100644
--- a/ckan/logic/action/get.py
+++ b/ckan/logic/action/get.py
@@ -11,6 +11,7 @@
import ckan.lib.dictization
import ckan.logic as logic
import ckan.logic.action
+import ckan.logic.schema
import ckan.lib.dictization.model_dictize as model_dictize
import ckan.lib.navl.dictization_functions
import ckan.model.misc as misc
@@ -334,9 +335,9 @@ def _group_or_org_list(context, data_dict, is_org=False):
if q:
q = u'%{0}%'.format(q)
query = query.filter(_or_(
- model.GroupRevision.name.like(q),
- model.GroupRevision.title.like(q),
- model.GroupRevision.description.like(q),
+ model.GroupRevision.name.ilike(q),
+ model.GroupRevision.title.ilike(q),
+ model.GroupRevision.description.ilike(q),
))
@@ -737,13 +738,7 @@ def package_show(context, data_dict):
item.read(pkg)
package_plugin = lib_plugins.lookup_package_plugin(package_dict['type'])
- try:
- schema = package_plugin.db_to_form_schema_options({
- 'type':'show',
- 'api': 'api_version' in context,
- 'context': context })
- except AttributeError:
- schema = package_plugin.db_to_form_schema()
+ schema = package_plugin.show_package_schema()
if schema and context.get('validate', True):
package_dict, errors = _validate(package_dict, schema, context=context)
diff --git a/ckan/logic/action/update.py b/ckan/logic/action/update.py
index 757d314a346..d6b969bc923 100644
--- a/ckan/logic/action/update.py
+++ b/ckan/logic/action/update.py
@@ -235,20 +235,20 @@ def package_update(context, data_dict):
# get the schema
package_plugin = lib_plugins.lookup_package_plugin(pkg.type)
- try:
- schema = package_plugin.form_to_db_schema_options({'type':'update',
- 'api':'api_version' in context,
- 'context': context})
- except AttributeError:
- schema = package_plugin.form_to_db_schema()
+ schema = package_plugin.update_package_schema()
if 'api_version' not in context:
- # old plugins do not support passing the schema so we need
- # to ensure they still work
- try:
- package_plugin.check_data_dict(data_dict, schema)
- except TypeError:
- package_plugin.check_data_dict(data_dict)
+ # check_data_dict() is deprecated. If the package_plugin has a
+ # check_data_dict() we'll call it, if it doesn't have the method we'll
+ # do nothing.
+ check_data_dict = getattr(package_plugin, 'check_data_dict', None)
+ if check_data_dict:
+ try:
+ package_plugin.check_data_dict(data_dict, schema)
+ except TypeError:
+ # Old plugins do not support passing the schema so we need
+ # to ensure they still work.
+ package_plugin.check_data_dict(data_dict)
data, errors = _validate(data_dict, schema, context)
log.debug('package_update validate_errs=%r user=%s package=%s data=%r',
@@ -295,37 +295,6 @@ def package_update(context, data_dict):
return output
-def package_update_validate(context, data_dict):
- model = context['model']
- user = context['user']
-
- id = _get_or_bust(data_dict, "id")
-
- pkg = model.Package.get(id)
- context["package"] = pkg
-
- if pkg is None:
- raise NotFound(_('Package was not found.'))
- data_dict["id"] = pkg.id
-
- # get the schema
- package_plugin = lib_plugins.lookup_package_plugin(pkg.type)
- try:
- schema = package_plugin.form_to_db_schema_options({'type':'update',
- 'api':'api_version' in context,
- 'context': context})
- except AttributeError:
- schema = package_plugin.form_to_db_schema()
-
- _check_access('package_update', context, data_dict)
-
- data, errors = _validate(data_dict, schema, context)
- if errors:
- model.Session.rollback()
- raise ValidationError(errors)
- return data
-
-
def _update_package_relationship(relationship, comment, context):
model = context['model']
api = context.get('api_version')
diff --git a/ckan/logic/schema.py b/ckan/logic/schema.py
index c79af5bea3a..07b22b6f9cb 100644
--- a/ckan/logic/schema.py
+++ b/ckan/logic/schema.py
@@ -50,6 +50,7 @@
convert_group_name_or_id_to_id,)
from formencode.validators import OneOf
import ckan.model
+import ckan.lib.maintain as maintain
def default_resource_schema():
@@ -112,10 +113,10 @@ def default_create_tag_schema():
schema['id'] = [empty]
return schema
-def default_package_schema():
+def default_create_package_schema():
schema = {
- 'id': [ignore_missing, unicode, package_id_exists],
+ 'id': [empty],
'revision_id': [ignore],
'name': [not_empty, unicode, name_validator, package_name_validator],
'title': [if_empty_same_as("name"), unicode],
@@ -130,12 +131,17 @@ def default_package_schema():
'state': [ignore_not_package_admin, ignore_missing],
'type': [ignore_missing, unicode],
'owner_org': [owner_org_validator, unicode],
+ 'log_message': [ignore_missing, unicode, no_http],
'private': [ignore_missing, boolean_validator],
'__extras': [ignore],
'__junk': [empty],
'resources': default_resource_schema(),
'tags': default_tags_schema(),
+ 'tag_string': [ignore_missing, tag_string_convert],
'extras': default_extras_schema(),
+ 'extras_validation': [duplicate_extras_key, ignore],
+ 'save': [ignore],
+ 'return_to': [ignore],
'relationships_as_object': default_relationship_schema(),
'relationships_as_subject': default_relationship_schema(),
'groups': {
@@ -147,56 +153,31 @@ def default_package_schema():
}
return schema
-def default_create_package_schema():
-
- schema = default_package_schema()
- schema["id"] = [empty]
-
- return schema
-
def default_update_package_schema():
+ schema = default_create_package_schema()
- schema = default_package_schema()
- schema["id"] = [ignore_missing, package_id_not_changed]
- schema["name"] = [ignore_missing, name_validator, package_name_validator, unicode]
- schema["title"] = [ignore_missing, unicode]
-
- schema['private'] = [ignore_missing, boolean_validator]
- schema['owner_org'] = [ignore_missing, owner_org_validator, unicode]
- return schema
+ # Users can (optionally) supply the package id when updating a package, but
+ # only to identify the package to be updated, they cannot change the id.
+ schema['id'] = [ignore_missing, package_id_not_changed]
-def package_form_schema():
- # This function is deprecated and was replaced by
- # form_to_db_package_schema(), it remains here for backwards compatibility.
- return form_to_db_package_schema()
+ # Supplying the package name when updating a package is optional (you can
+ # supply the id to identify the package instead).
+ schema['name'] = [ignore_missing, name_validator, package_name_validator,
+ unicode]
-def form_to_db_package_schema():
+ # Supplying the package title when updating a package is optional, if it's
+ # not supplied the title will not be changed.
+ schema['title'] = [ignore_missing, unicode]
- schema = default_package_schema()
- ##new
- schema['log_message'] = [ignore_missing, unicode, no_http]
- schema['groups'] = {
- 'id': [ignore_missing, unicode],
- '__extras': [ignore],
- }
- schema['tag_string'] = [ignore_missing, tag_string_convert]
- schema['extras_validation'] = [duplicate_extras_key, ignore]
- schema['save'] = [ignore]
- schema['return_to'] = [ignore]
- schema['type'] = [ignore_missing, unicode]
- schema['private'] = [ignore_missing, boolean_validator]
schema['owner_org'] = [ignore_missing, owner_org_validator, unicode]
- ##changes
- schema.pop("id")
- schema.pop('relationships_as_object')
- schema.pop('revision_id')
- schema.pop('relationships_as_subject')
-
return schema
-def db_to_form_package_schema():
- schema = default_package_schema()
+def default_show_package_schema():
+ schema = default_create_package_schema()
+
+ # Don't strip ids from package dicts when validating them.
+ schema['id'] = []
schema.update({
'tags': {'__extras': [ckan.lib.navl.validators.keep_extras]}})
@@ -246,7 +227,7 @@ def db_to_form_package_schema():
schema['url'] = []
schema['version'] = []
- # Add several keys that are missing from default_package_schema(), so
+ # Add several keys that are missing from default_create_package_schema(), so
# validation doesn't strip the keys from the package dicts.
schema['metadata_created'] = []
schema['metadata_modified'] = []
diff --git a/ckan/new_authz.py b/ckan/new_authz.py
index 197d6ab35cf..94166741e5d 100644
--- a/ckan/new_authz.py
+++ b/ckan/new_authz.py
@@ -1,17 +1,12 @@
import sys
from logging import getLogger
-try:
- from collections import OrderedDict # 2.7
-except ImportError:
- from sqlalchemy.util import OrderedDict
-
-from pylons import config, c
-from pylons.i18n import _
+from pylons import config
from paste.deploy.converters import asbool
import ckan.plugins as p
import ckan.model as model
+from ckan.common import OrderedDict, _, c
log = getLogger(__name__)
@@ -49,6 +44,7 @@ def get_group_or_org_admin_ids(group_id):
q = model.Session.query(model.Member) \
.filter(model.Member.group_id == group_id) \
.filter(model.Member.table_name == 'user') \
+ .filter(model.Member.state == 'active') \
.filter(model.Member.capacity == 'admin')
return [a.table_id for a in q.all()]
@@ -135,6 +131,7 @@ def has_user_permission_for_group_or_org(group_id, user_name, permission):
q = model.Session.query(model.Member) \
.filter(model.Member.group_id == group_id) \
.filter(model.Member.table_name == 'user') \
+ .filter(model.Member.state == 'active') \
.filter(model.Member.table_id == user_id)
# see if any role has the required permission
# admin permission allows anything for the group
@@ -158,6 +155,7 @@ def users_role_for_group_or_org(group_id, user_name):
q = model.Session.query(model.Member) \
.filter(model.Member.group_id == group_id) \
.filter(model.Member.table_name == 'user') \
+ .filter(model.Member.state == 'active') \
.filter(model.Member.table_id == user_id)
# return the first role we find
for row in q.all():
@@ -176,6 +174,7 @@ def has_user_permission_for_some_org(user_name, permission):
# get any groups the user has with the needed role
q = model.Session.query(model.Member) \
.filter(model.Member.table_name == 'user') \
+ .filter(model.Member.state == 'active') \
.filter(model.Member.capacity.in_(roles)) \
.filter(model.Member.table_id == user_id)
group_ids = []
diff --git a/ckan/plugins/interfaces.py b/ckan/plugins/interfaces.py
index ea0a1881c78..1dd22b2f88c 100644
--- a/ckan/plugins/interfaces.py
+++ b/ckan/plugins/interfaces.py
@@ -525,123 +525,211 @@ def get_helpers(self):
class IDatasetForm(Interface):
- """
- Allows customisation of the package controller as a plugin.
+ '''Customize CKAN's dataset (package) schemas and forms.
- The behaviour of the plugin is determined by 5 method hooks:
+ By implementing this interface plugins can customise CKAN's dataset schema,
+ for example to add new custom fields to datasets.
- - package_form(self)
- - form_to_db_schema(self)
- - db_to_form_schema(self)
- - check_data_dict(self, data_dict, schema=None)
- - setup_template_variables(self, context, data_dict)
+ Multiple IDatasetForm plugins can be used at once, each plugin associating
+ itself with different package types using the ``package_types()`` and
+ ``is_fallback()`` methods below, and then providing different schemas and
+ templates for different types of dataset. When a package controller action
+ is invoked, the ``type`` field of the package will determine which
+ IDatasetForm plugin (if any) gets delegated to.
- Furthermore, there can be many implementations of this plugin registered
- at once. With each instance associating itself with 0 or more package
- type strings. When a package controller action is invoked, the package
- type determines which of the registered plugins to delegate to. Each
- implementation must implement two methods which are used to determine the
- package-type -> plugin mapping:
+ When implementing IDatasetForm, you can inherit from
+ ``ckan.plugins.toolkit.DefaultDatasetForm``, which provides default
+ implementations for each of the methods defined in this interface.
- - is_fallback(self)
- - package_types(self)
+ See ``ckanext/example_idatasetform`` for an example plugin.
- Implementations might want to consider mixing in
- ckan.lib.plugins.DefaultDatasetForm which provides
- default behaviours for the 5 method hooks.
+ '''
+ def package_types(self):
+ '''Return an iterable of package types that this plugin handles.
- """
+ If a request involving a package of one of the returned types is made,
+ then this plugin instance will be delegated to.
- ##### These methods control when the plugin is delegated to #####
+ There cannot be two IDatasetForm plugins that return the same package
+ type, if this happens then CKAN will raise an exception at startup.
+
+ :rtype: iterable of strings
+
+ '''
def is_fallback(self):
- """
- Returns true iff this provides the fallback behaviour, when no other
- plugin instance matches a package's type.
+ '''Return ``True`` if this plugin is the fallback plugin.
- There must be exactly one fallback controller defined, any attempt to
- register more than one will throw an exception at startup. If there's
- no fallback registered at startup the
- ckan.lib.plugins.DefaultDatasetForm is used as the fallback.
- """
+ When no IDatasetForm plugin's ``package_types()`` match the ``type`` of
+ the package being processed, the fallback plugin is delegated to
+ instead.
- def package_types(self):
- """
- Returns an iterable of package type strings.
+ There cannot be more than one IDatasetForm plugin whose
+ ``is_fallback()`` method returns ``True``, if this happens CKAN will
+ raise an exception at startup.
- If a request involving a package of one of those types is made, then
- this plugin instance will be delegated to.
+ If no IDatasetForm plugin's ``is_fallback()`` method returns ``True``,
+ CKAN will use ``DefaultDatasetForm`` as the fallback.
- 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.
- """
+ :rtype: boolean
- ##### End of control methods
+ '''
+
+ def create_package_schema(self):
+ '''Return the schema for validating new dataset dicts.
+
+ CKAN will use the returned schema to validate and convert data coming
+ from users (via the dataset form or API) when creating new datasets,
+ before entering that data into the database.
+
+ If it inherits from ``ckan.plugins.toolkit.DefaultDatasetForm``, a
+ plugin can call ``DefaultDatasetForm``'s ``create_package_schema()``
+ method to get the default schema and then modify and return it.
+
+ CKAN's ``convert_to_tags()`` or ``convert_to_extras()`` functions can
+ be used to convert custom fields into dataset tags or extras for
+ storing in the database.
+
+ See ``ckanext/example_idatasetform`` for examples.
+
+ :returns: a dictionary mapping dataset dict keys to lists of validator
+ and converter functions to be applied to those keys
+ :rtype: dictionary
+
+ '''
+
+ def update_package_schema(self):
+ '''Return the schema for validating updated dataset dicts.
+
+ CKAN will use the returned schema to validate and convert data coming
+ from users (via the dataset form or API) when updating datasets, before
+ entering that data into the database.
+
+ If it inherits from ``ckan.plugins.toolkit.DefaultDatasetForm``, a
+ plugin can call ``DefaultDatasetForm``'s ``update_package_schema()``
+ method to get the default schema and then modify and return it.
+
+ CKAN's ``convert_to_tags()`` or ``convert_to_extras()`` functions can
+ be used to convert custom fields into dataset tags or extras for
+ storing in the database.
+
+ See ``ckanext/example_idatasetform`` for examples.
+
+ :returns: a dictionary mapping dataset dict keys to lists of validator
+ and converter functions to be applied to those keys
+ :rtype: dictionary
+
+ '''
+
+ def show_package_schema(self):
+ '''
+ Return a schema to validate datasets before they're shown to the user.
+
+ CKAN will use the returned schema to validate and convert data coming
+ from the database before it is returned to the user via the API or
+ passed to a template for rendering.
+
+ If it inherits from ``ckan.plugins.toolkit.DefaultDatasetForm``, a
+ plugin can call ``DefaultDatasetForm``'s ``show_package_schema()``
+ method to get the default schema and then modify and return it.
+
+ If you have used ``convert_to_tags()`` or ``convert_to_extras()`` in
+ your ``create_package_schema()`` and ``update_package_schema()`` then
+ you should use ``convert_from_tags()`` or ``convert_from_extras()`` in
+ your ``show_package_schema()`` to convert the tags or extras in the
+ database back into your custom dataset fields.
+
+ See ``ckanext/example_idatasetform`` for examples.
+
+ :returns: a dictionary mapping dataset dict keys to lists of validator
+ and converter functions to be applied to those keys
+ :rtype: dictionary
+
+ '''
+
+ def setup_template_variables(self, context, data_dict):
+ '''Add variables to the template context for use in templates.
+
+ This function is called before a dataset template is rendered. If you
+ have custom dataset templates that require some additional variables,
+ you can add them to the template context ``ckan.plugins.toolkit.c``
+ here and they will be available in your templates. See
+ ``ckanext/example_idatasetform`` for an example.
+
+ '''
- ##### 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
- """
+ '''Return the path to the template for the new dataset page.
+
+ The path should be relative to the plugin's templates dir, e.g.
+ ``'package/new.html'``.
+
+ :rtype: string
+
+ '''
+
+ def read_template(self):
+ '''Return the path to the template for the dataset read page.
+
+ The path should be relative to the plugin's templates dir, e.g.
+ ``'package/read.html'``.
+
+ :rtype: string
+
+ '''
+
+ def edit_template(self):
+ '''Return the path to the template for the dataset edit page.
+
+ The path should be relative to the plugin's templates dir, e.g.
+ ``'package/edit.html'``.
+
+ :rtype: string
+
+ '''
def comments_template(self):
- """
- Returns a string representing the location of the template to be
- rendered for the comments page
- """
+ '''Return the path to the template for the dataset comments page.
+
+ The path should be relative to the plugin's templates dir, e.g.
+ ``'package/comments.html'``.
+
+ :rtype: string
+
+ '''
def search_template(self):
- """
- Returns a string representing the location of the template to be
- rendered for the search page (if present)
- """
+ '''Return the path to the template for use in the dataset search page.
- def read_template(self):
- """
- Returns a string representing the location of the template to be
- rendered for the read page
- """
+ This template is used to render each dataset that is listed in the
+ search results on the dataset search page.
+
+ The path should be relative to the plugin's templates dir, e.g.
+ ``'package/search.html'``.
+
+ :rtype: string
+
+ '''
def history_template(self):
- """
- Returns a string representing the location of the template to be
- rendered for the history page
- """
+ '''Return the path to the template for the dataset history page.
- def package_form(self):
- """
- Returns a string representing the location of the template to be
- rendered. e.g. "package/new_package_form.html".
- """
+ The path should be relative to the plugin's templates dir, e.g.
+ ``'package/history.html'``.
- def form_to_db_schema(self):
- """
- Returns the schema for mapping package data from a form to a format
- suitable for the database.
- """
+ :rtype: string
- 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, schema=None):
- """
- Check if the return data is correct.
+ def package_form(self):
+ '''Return the path to the template for the dataset form.
- raise a DataError if not.
- """
+ The path should be relative to the plugin's templates dir, e.g.
+ ``'package/form.html'``.
- def setup_template_variables(self, context, data_dict):
- """
- Add variables to c just prior to the template being rendered.
- """
+ :rtype: string
- ##### End of hooks #####
+ '''
class IGroupForm(Interface):
diff --git a/ckan/plugins/toolkit.py b/ckan/plugins/toolkit.py
index 7131d8a5442..e011184fe56 100644
--- a/ckan/plugins/toolkit.py
+++ b/ckan/plugins/toolkit.py
@@ -51,6 +51,7 @@ class _Toolkit(object):
'UnknownValidator', # validator not found exception
'ValidationError', # model update validation error
'CkanCommand', # class for providing cli interfaces
+ 'DefaultDatasetForm', # base class for IDatasetForm plugins
## Fully defined in this file ##
'add_template_directory',
@@ -72,6 +73,7 @@ def _initialize(self):
import ckan.lib.base as base
import ckan.logic as logic
import ckan.lib.cli as cli
+ import ckan.lib.plugins as lib_plugins
# Allow class access to these modules
self.__class__.ckan = ckan
@@ -101,6 +103,7 @@ def _initialize(self):
t['UnknownValidator'] = logic.UnknownValidator
t['CkanCommand'] = cli.CkanCommand
+ t['DefaultDatasetForm'] = lib_plugins.DefaultDatasetForm
# class functions
t['render_snippet'] = self._render_snippet
diff --git a/ckan/public/base/css/main.css b/ckan/public/base/css/main.css
index c182477d335..a3f654b889c 100644
--- a/ckan/public/base/css/main.css
+++ b/ckan/public/base/css/main.css
@@ -4874,6 +4874,7 @@ a.tag:hover {
margin: 20px 0;
}
.module-heading {
+ *zoom: 1;
margin: 0;
padding: 7px 25px;
font-size: 14px;
@@ -4882,6 +4883,15 @@ a.tag:hover {
border-top: 1px solid #dddddd;
border-bottom: 1px solid #dddddd;
}
+.module-heading:before,
+.module-heading:after {
+ display: table;
+ content: "";
+ line-height: 0;
+}
+.module-heading:after {
+ clear: both;
+}
.module-heading .action {
float: right;
color: #888888;
@@ -5755,24 +5765,23 @@ textarea {
background: #c6898b;
margin: -3px 0 0;
color: #ffffff;
- width: 216px;
+ width: 208px;
}
.control-medium .error-block {
- width: 326px;
+ width: 318px;
}
.control-full .error-block {
width: auto;
}
.control-group.error .input-prepend .error-block,
.control-custom.error .error-block {
- margin-left: -1px;
width: auto;
}
.control-custom.error .error-block {
- width: 409px;
+ width: 401px;
}
.control-select.error .error-block {
- width: 204px;
+ width: 196px;
}
.stages {
margin: 0;
@@ -7833,6 +7842,9 @@ textarea {
.context-info.editing .module-content {
margin-top: 0;
}
+.modal {
+ top: 50%;
+}
.hero {
background: url("../../../base/images/background-tile.png");
padding: 20px 0;
@@ -8185,10 +8197,15 @@ textarea {
.footer-links {
margin-left: 0;
}
-.footer-links li {
- display: inline-block;
- width: 44%;
- margin-right: 5%;
+.footer-links ul {
+ float: left;
+ margin-left: 20px;
+ width: 220px;
+}
+.footer-links ul:first-child {
+ margin-left: 0;
+}
+.footer-links ul li {
margin-bottom: 5px;
}
.attribution small {
diff --git a/ckan/public/base/less/footer.less b/ckan/public/base/less/footer.less
index b91ee55ff75..34311bd64db 100644
--- a/ckan/public/base/less/footer.less
+++ b/ckan/public/base/less/footer.less
@@ -22,10 +22,16 @@
margin-left: 0;
}
-.footer-links li {
- display: inline-block;
- width: 44%;
- margin-right: 5%;
+.footer-links ul {
+ float: left;
+ .makeColumn(3);
+}
+
+.footer-links ul:first-child {
+ margin-left: 0;
+}
+
+.footer-links ul li {
margin-bottom: 5px;
}
diff --git a/ckan/public/base/less/forms.less b/ckan/public/base/less/forms.less
index 2fc4ff285f7..a7449a9980a 100644
--- a/ckan/public/base/less/forms.less
+++ b/ckan/public/base/less/forms.less
@@ -376,11 +376,11 @@ textarea {
background: @errorBorder;
margin: -@inputBorderRadius 0 0;
color: @inputBackground;
- width: 216px;
+ width: 208px;
}
.control-medium .error-block {
- width: 326px;
+ width: 318px;
}
.control-full .error-block {
@@ -389,16 +389,15 @@ textarea {
.control-group.error .input-prepend .error-block,
.control-custom.error .error-block {
- margin-left: -1px;
width: auto;
}
.control-custom.error .error-block {
- width: 409px;
+ width: 401px;
}
.control-select.error .error-block {
- width: 204px;
+ width: 196px;
}
// Stages
diff --git a/ckan/public/base/less/layout.less b/ckan/public/base/less/layout.less
index ad12f81dc9b..a35f83f93a5 100644
--- a/ckan/public/base/less/layout.less
+++ b/ckan/public/base/less/layout.less
@@ -163,3 +163,7 @@
}
}
}
+
+.modal {
+ top: 50%;
+}
diff --git a/ckan/templates/footer.html b/ckan/templates/footer.html
index 3c5d429f6f3..fca1141eb46 100644
--- a/ckan/templates/footer.html
+++ b/ckan/templates/footer.html
@@ -1,15 +1,19 @@