Skip to content

Instantly share code, notes, and snippets.

@KrzysztofMadejski
Last active October 16, 2017 23:03
Show Gist options
  • Select an option

  • Save KrzysztofMadejski/4b3a9b996c711eb87a09dcccc9429abd to your computer and use it in GitHub Desktop.

Select an option

Save KrzysztofMadejski/4b3a9b996c711eb87a09dcccc9429abd to your computer and use it in GitHub Desktop.
Overrided controllers
from pylons import config
import ckan.lib.base as base
import ckan.lib.helpers as h
import ckan.lib.app_globals as app_globals
import ckan.model as model
import ckan.logic as logic
import ckan.new_authz
c = base.c
request = base.request
_ = base._
def get_sysadmins():
q = model.Session.query(model.User).filter(model.User.sysadmin==True)
return q.all()
class AdminController(base.BaseController):
def __before__(self, action, **params):
super(AdminController, self).__before__(action, **params)
context = {'model': model,
'user': c.user, 'auth_user_obj': c.userobj}
try:
logic.check_access('sysadmin', context, {})
except logic.NotAuthorized:
base.abort(401, _('Need to be system administrator to administer'))
c.revision_change_state_allowed = True
def _get_config_form_items(self):
# Styles for use in the form.select() macro.
styles = [{'text': 'Default', 'value': '/base/css/main.css'},
{'text': 'Red', 'value': '/base/css/red.css'},
{'text': 'Green', 'value': '/base/css/green.css'},
{'text': 'Maroon', 'value': '/base/css/maroon.css'},
{'text': 'Fuchsia', 'value': '/base/css/fuchsia.css'}]
items = [
{'name': 'ckan.site_title', 'control': 'input', 'label': _('Site Title'), 'placeholder': ''},
# {'name': 'ckan.site_description', 'control': 'input', 'label': _('Site Tag Line'), 'placeholder': ''},
{'name': 'ckan.site_logo', 'control': 'input', 'label': _('Site Tag Logo'), 'placeholder': ''},
{'name': 'ckanext.danepubliczne.maintenance_flash', 'control': 'input', 'label': _('Maintenance alert'),
'placeholder': _('Fill if there is planned maintenance break')},
]
for locale in h.get_available_locales():
lang = locale.language
items += [
{'name': 'ckan.site_about-' + lang, 'control': 'markdown', 'label': _('About') + ' ' + lang.upper(),
'placeholder': _('About page text')}]
for locale in h.get_available_locales():
lang = locale.language
items += [{'name': 'ckan.site_intro_text-' + lang, 'control': 'markdown',
'label': _('Intro Text') + ' ' + lang.upper(), 'placeholder': _('Text on home page')}]
return items
def reset_config(self):
if 'cancel' in request.params:
h.redirect_to(controller='admin', action='config')
if request.method == 'POST':
# remove sys info items
for item in self._get_config_form_items():
name = item['name']
app_globals.delete_global(name)
# reset to values in config
app_globals.reset()
h.redirect_to(controller='admin', action='config')
return base.render('admin/confirm_reset.html')
def config(self):
items = self._get_config_form_items()
data = request.POST
if 'save' in data:
# update config from form
for item in items:
name = item['name']
if name in data:
app_globals.set_global(name, data[name])
app_globals.reset()
h.redirect_to(controller='admin', action='config')
data = {}
for item in items:
name = item['name']
data[name] = config.get(name)
vars = {'data': data, 'errors': {}, 'form_items': items}
return base.render('admin/config.html',
extra_vars = vars)
def index(self):
#now pass the list of sysadmins
c.sysadmins = [a.name for a in get_sysadmins()]
return base.render('admin/index.html')
def trash(self):
c.deleted_revisions = model.Session.query(
model.Revision).filter_by(state=model.State.DELETED)
c.deleted_packages = model.Session.query(
model.Package).filter_by(state=model.State.DELETED)
if not request.params or (len(request.params) == 1 and '__no_cache__'
in request.params):
return base.render('admin/trash.html')
else:
# NB: we repeat retrieval of of revisions
# this is obviously inefficient (but probably not *that* bad)
# but has to be done to avoid (odd) sqlalchemy errors (when doing
# purge packages) of form: "this object already exists in the
# session"
msgs = []
if ('purge-packages' in request.params) or ('purge-revisions' in
request.params):
if 'purge-packages' in request.params:
revs_to_purge = []
for pkg in c.deleted_packages:
revisions = [x[0] for x in pkg.all_related_revisions]
# ensure no accidental purging of other(non-deleted)
# packages initially just avoided purging revisions
# where non-deleted packages were affected
# however this lead to confusing outcomes e.g.
# we succesfully deleted revision in which package
# was deleted (so package now active again) but no
# other revisions
problem = False
for r in revisions:
affected_pkgs = set(r.packages).\
difference(set(c.deleted_packages))
if affected_pkgs:
msg = _('Cannot purge package %s as '
'associated revision %s includes '
'non-deleted packages %s')
msg = msg % (pkg.id, r.id, [pkg.id for r
in affected_pkgs])
msgs.append(msg)
problem = True
break
if not problem:
revs_to_purge += [r.id for r in revisions]
model.Session.remove()
else:
revs_to_purge = [rev.id for rev in c.deleted_revisions]
revs_to_purge = list(set(revs_to_purge))
for id in revs_to_purge:
revision = model.Session.query(model.Revision).get(id)
try:
# TODO deleting the head revision corrupts the edit
# page Ensure that whatever 'head' pointer is used
# gets moved down to the next revision
model.repo.purge_revision(revision, leave_record=False)
except Exception, inst:
msg = _('Problem purging revision %s: %s') % (id, inst)
msgs.append(msg)
h.flash_success(_('Purge complete'))
else:
msgs.append(_('Action not implemented.'))
for msg in msgs:
h.flash_error(msg)
h.redirect_to(controller='admin', action='trash')
import re
import os
import logging
import genshi
import cgi
import datetime
from urllib import urlencode
from pylons.i18n import get_lang
import ckan.lib.base as base
import ckan.lib.helpers as h
import ckan.lib.maintain as maintain
import ckan.lib.navl.dictization_functions as dict_fns
import ckan.logic as logic
import ckan.lib.search as search
import ckan.model as model
import ckan.new_authz as new_authz
import ckan.lib.plugins
import ckan.plugins as plugins
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(base.BaseController):
group_type = 'group'
## hooks for subclasses
def _group_form(self, group_type=None):
return lookup_group_plugin(group_type).group_form()
def _form_to_db_schema(self, group_type=None):
return lookup_group_plugin(group_type).form_to_db_schema()
def _db_to_form_schema(self, group_type=None):
'''This is an interface to manipulate data from the database
into a format suitable for the form (optional)'''
return lookup_group_plugin(group_type).db_to_form_schema()
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):
return lookup_group_plugin(group_type).new_template()
def _index_template(self, group_type):
return lookup_group_plugin(group_type).index_template()
def _about_template(self, group_type):
return lookup_group_plugin(group_type).about_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()
def _edit_template(self, group_type):
return lookup_group_plugin(group_type).edit_template()
def _activity_template(self, group_type):
return lookup_group_plugin(group_type).activity_template()
def _admins_template(self, group_type):
return lookup_group_plugin(group_type).admins_template()
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'''
if self.group_type == 'organization':
string = re.sub('^group', 'organization', string)
return string
def _action(self, action_name):
''' select the correct group/org action '''
return get_action(self._replace_group_org(action_name))
def _check_access(self, action_name, *args, **kw):
''' select the correct group/org check_access '''
return check_access(self._replace_group_org(action_name), *args, **kw)
def _render_template(self, template_name):
''' render the correct group/org template '''
return render(self._replace_group_org(template_name))
def _redirect_to(self, *args, **kw):
''' wrapper to ensue the correct controller is used '''
if self.group_type == 'organization' and 'controller' in kw:
kw['controller'] = 'organization'
return h.redirect_to(*args, **kw)
def _url_for(self, *args, **kw):
''' wrapper to ensue the correct controller is used '''
if self.group_type == 'organization' and 'controller' in kw:
kw['controller'] = 'organization'
return h.url_for(*args, **kw)
def _guess_group_type(self, expecting_name=False):
"""
Guess the type of group from the URL handling the case
where there is a prefix on the URL (such as /data/organization)
"""
parts = [x for x in request.path.split('/') if x]
idx = -1
if expecting_name:
idx = -2
gt = parts[idx]
if gt == 'group':
gt = None
return gt
def index(self):
group_type = self._guess_group_type()
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'with_private': False}
q = c.q = request.params.get('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
else:
data_dict['sort'] = 'title asc' # Sort by title by default
try:
self._check_access('site_read', context)
except NotAuthorized:
abort(401, _('Not authorized to see this page'))
# pass user info to context as needed to view private datasets of
# orgs correctly
if c.userobj:
context['user_id'] = c.userobj.id
context['user_is_admin'] = c.userobj.sysadmin
results = self._action('group_list')(context, data_dict)
c.page = h.Page(
collection=results,
page = self._get_page_number(request.params),
url=h.pager_url,
items_per_page=21
)
return render(self._index_template(group_type))
def read(self, id, limit=20):
group_type = self._get_group_type(id.split('@')[0])
if group_type != self.group_type:
abort(404, _('Incorrect group type'))
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'schema': self._db_to_form_schema(group_type=group_type),
'for_view': True}
data_dict = {'id': id}
# unicode format (decoded from utf8)
q = c.q = request.params.get('q', '')
try:
# Do not query for the group datasets when dictizing, as they will
# be ignored and get requested on the controller anyway
data_dict['include_datasets'] = False
c.group_dict = self._action('group_show')(context, data_dict)
c.group = context['group']
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % id)
self._read(id, limit)
return render(self._read_template(c.group_dict['type']))
ef _read(self, id, limit):
''' This is common code used by both read and bulk_process'''
group_type = self._get_group_type(id.split('@')[0])
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'schema': self._db_to_form_schema(group_type=group_type),
'for_view': True, 'extras_as_string': True}
q = c.q = request.params.get('q', '')
# Search within group
if c.group_dict.get('is_organization'):
q += ' owner_org:"%s"' % c.group_dict.get('id')
else:
q += ' groups:"%s"' % c.group_dict.get('name')
description = c.group_dict.get('description')
if description:
if group_type == 'group':
c.description_formatted = h.render_markdown(description.get(h.lang()))
else:
c.description_formatted = h.render_markdown(description)
context['return_query'] = True
# 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 = new_authz.get_group_or_org_admin_ids(c.group.id)
page = self._get_page_number(request.params)
# most search operations should reset the page counter:
params_nopage = [(k, v) for k, v in request.params.items()
if k != 'page']
default_sort_by = 'metadata_modified desc'
sort_by = request.params.get('sort', default_sort_by)
def search_url(params):
if group_type == 'organization':
if c.action == 'bulk_process':
url = self._url_for(controller='organization',
action='bulk_process',
id=id)
else:
url = self._url_for(controller='organization',
action='read',
id=id)
else:
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)
def drill_down_url(**by):
return h.add_url_param(alternative_url=None,
controller='group', action='read',
extras=dict(id=c.group_dict.get('name')),
new_params=by)
c.drill_down_url = drill_down_url
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')))
c.remove_field = remove_field
def pager_url(q=None, page=None):
params = list(params_nopage)
params.append(('page', page))
return search_url(params)
try:
c.fields = []
search_extras = {}
for (param, value) in request.params.items():
if not param in ['q', 'page', 'sort'] \
and len(value) and not param.startswith('_'):
if not param.startswith('ext_'):
c.fields.append((param, value))
q += ' %s: "%s"' % (param, value)
else:
search_extras[param] = value
fq = 'capacity:"public"'
user_member_of_orgs = [org['id'] for org
in h.organizations_available('read')]
if (c.group and c.group.id in user_member_of_orgs):
fq = ''
context['ignore_capacity_check'] = True
facets = OrderedDict()
default_facet_titles = {'organization': _('Organizations'),
'groups': _('Groups'),
'tags': _('Tags'),
'res_format': _('Formats'),
'license_id': _('Licenses')}
for facet in g.facets:
if facet in default_facet_titles:
facets[facet] = default_facet_titles[facet]
else:
facets[facet] = facet
# Facet titles
for plugin in plugins.PluginImplementations(plugins.IFacets):
if self.group_type == 'organization':
facets = plugin.organization_facets(
facets, self.group_type, None)
else:
facets = plugin.group_facets(
facets, self.group_type, None)
if 'capacity' in facets and (self.group_type != 'organization' or
not user_member_of_orgs):
del facets['capacity']
c.facet_titles = facets
data_dict = {
'q': q,
'fq': fq,
'facet.field': facets.keys(),
'rows': limit,
'sort': sort_by,
'start': (page - 1) * limit,
'extras': search_extras
}
context_ = dict((k, v) for (k, v) in context.items() if k != 'schema')
query = get_action('package_search')(context_, data_dict)
c.page = h.Page(
collection=query['results'],
page=page,
url=pager_url,
item_count=query['count'],
items_per_page=limit
)
c.group_dict['package_count'] = query['count']
c.facets = query['facets']
maintain.deprecate_context_item('facets',
'Use `c.search_facets` instead.')
c.search_facets = query['search_facets']
c.search_facets_limits = {}
for facet in c.search_facets.keys():
limit = int(request.params.get('_%s_limit' % facet,
g.facets_default_number))
c.search_facets_limits[facet] = limit
c.page.items = query['results']
c.sort_by_selected = sort_by
except search.SearchError, se:
log.error('Group search error: %r', se.args)
c.query_error = True
c.facets = {}
c.page = h.Page(collection=[])
self._setup_template_variables(context, {'id': id},
group_type=group_type)
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 is None:
abort(404, _('Organization not found'))
if group_type != 'organization':
# FIXME: better error
raise Exception('Must be an organization')
# check we are org admin
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'schema': self._db_to_form_schema(group_type=group_type),
'for_view': True, 'extras_as_string': True}
data_dict = {'id': id}
try:
# Do not query for the group datasets when dictizing, as they will
# be ignored and get requested on the controller anyway
data_dict['include_datasets'] = False
c.group_dict = self._action('group_show')(context, data_dict)
c.group = context['group']
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % id)
#use different form names so that ie7 can be detected
form_names = set(["bulk_action.public", "bulk_action.delete",
"bulk_action.private"])
actions_in_form = set(request.params.keys())
actions = form_names.intersection(actions_in_form)
# If no action then just show the datasets
if not actions:
# unicode format (decoded from utf8)
limit = 500
self._read(id, limit)
c.packages = c.page.items
return render(self._bulk_process_template(group_type))
#ie7 puts all buttons in form params but puts submitted one twice
for key, value in dict(request.params.dict_of_lists()).items():
if len(value) == 2:
action = key.split('.')[-1]
break
else:
#normal good browser form submission
action = actions.pop().split('.')[-1]
# 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_'):
datasets.append(param[8:])
action_functions = {
'private': 'bulk_update_private',
'public': 'bulk_update_public',
'delete': 'bulk_update_delete',
}
data_dict = {'datasets': datasets, 'org_id': c.group_dict['id']}
try:
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))
def new(self, data=None, errors=None, error_summary=None):
group_type = self._guess_group_type(True)
if data:
data['type'] = group_type
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'save': 'save' in request.params,
'parent': request.params.get('parent', None)}
try:
self._check_access('group_create', context)
except NotAuthorized:
abort(401, _('Unauthorized to create a group'))
if context['save'] and not data:
return self._save_new(context, group_type)
data = data or {}
if not data.get('image_url', '').startswith('http'):
data.pop('image_url', None)
errors = errors or {}
error_summary = error_summary or {}
vars = {'data': data, 'errors': errors,
'error_summary': error_summary, 'action': 'new'}
self._setup_template_variables(context, data, group_type=group_type)
c.form = render(self._group_form(group_type=group_type),
extra_vars=vars)
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])
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'save': 'save' in request.params,
'for_edit': True,
'parent': request.params.get('parent', None)
}
data_dict = {'id': id, 'include_datasets': False}
if context['save'] and not data:
return self._save_edit(id, context)
try:
data_dict['include_datasets'] = False
old_data = self._action('group_show')(context, data_dict)
c.grouptitle = old_data.get('title')
c.groupname = old_data.get('name')
data = data or old_data
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % '')
group = context.get("group")
c.group = group
c.group_dict = self._action('group_show')(context, data_dict)
try:
self._check_access('group_update', context)
except NotAuthorized, e:
abort(401, _('User %r not authorized to edit %s') % (c.user, id))
errors = errors or {}
vars = {'data': data, 'errors': errors,
'error_summary': error_summary, 'action': 'edit'}
self._setup_template_variables(context, data, group_type=group_type)
c.form = render(self._group_form(group_type), extra_vars=vars)
return render(self._edit_template(c.group.type))
def _get_group_type(self, id):
"""
Given the id of a group it determines the type of a group given
a valid id/name for the group.
"""
group = model.Group.get(id)
if not group:
return None
return group.type
def _save_new(self, context, group_type=None):
try:
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', '')
data_dict['users'] = [{'name': c.user, 'capacity': 'admin'}]
group = self._action('group_create')(context, data_dict)
# Redirect to the appropriate _read route for the type of group
h.redirect_to(group['type'] + '_read', id=group['name'])
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % '')
except NotFound, e:
abort(404, _('Group not found'))
except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.new(data_dict, errors, error_summary)
def _force_reindex(self, grp):
''' When the group name has changed, we need to force a reindex
of the datasets within the group, otherwise they will stop
appearing on the read page for the group (as they're connected via
the group name)'''
group = model.Group.get(grp['name'])
for dataset in group.packages():
search.rebuild(dataset.name)
def _save_edit(self, id, context):
try:
data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.params))))
context['message'] = data_dict.get('log_message', '')
data_dict['id'] = id
context['allow_partial_update'] = True
group = self._action('group_update')(context, data_dict)
if id != group['name']:
self._force_reindex(group)
h.redirect_to('%s_read' % group['type'], id=group['name'])
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % id)
except NotFound, e:
abort(404, _('Group not found'))
except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.edit(id, data_dict, errors, error_summary)
def authz(self, id):
group = model.Group.get(id)
if group is None:
abort(404, _('Group not found'))
c.groupname = group.name
c.grouptitle = group.display_name
try:
context = \
{'model': model, 'user': c.user or c.author, 'group': group}
self._check_access('group_edit_permissions', context)
c.authz_editable = True
c.group = context['group']
except NotAuthorized:
c.authz_editable = False
if not c.authz_editable:
abort(401,
_('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)
return render('group/authz.html')
def delete(self, id):
if 'cancel' in request.params:
self._redirect_to(controller='group', action='edit', id=id)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
try:
self._check_access('group_delete', context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s') % '')
try:
if request.method == 'POST':
self._action('group_delete')(context, {'id': id})
if self.group_type == 'organization':
h.flash_notice(_('Organization has been deleted.'))
else:
h.flash_notice(_('Group has been deleted.'))
self._redirect_to(controller='group', action='index')
c.group_dict = self._action('group_show')(context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s') % '')
except NotFound:
abort(404, _('Group not found'))
return self._render_template('group/confirm_delete.html')
def members(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
try:
c.members = self._action('member_list')(
context, {'id': id, 'object_type': 'user'}
)
data_dict = {'id': id}
data_dict['include_datasets'] = False
c.group_dict = self._action('group_show')(context, data_dict)
except NotAuthorized:
abort(401, _('Unauthorized to show group %s') % id)
except NotFound:
abort(404, _('Group not found'))
return self._render_template('group/members.html')
def member_new(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
# self._check_access('group_delete', context, {'id': id})
try:
data_dict = {'id': id}
data_dict['include_datasets'] = False
c.group_dict = self._action('group_show')(context, data_dict)
group_type = 'organization' if c.group_dict['is_organization'] else 'group'
c.roles = self._action('member_roles_list')(
context, {'group_type': group_type}
)
if request.method == 'POST':
data_dict = clean_dict(df.unflatten(
tuplize_dict(parse_params(request.params))))
data_dict['id'] = id
data_dict['role'] = 'editor' # fixed
email = data_dict.get('email').lower()
if email:
# check if user exists
user = context['session'].query(model.User).filter_by(email=email).first()
if user:
data_dict['username'] = user.name
else:
user_data_dict = {
'email': email,
'group_id': data_dict['id'],
'role': data_dict['role']
}
del data_dict['email']
user_dict = self._action('user_invite')(context,
user_data_dict)
data_dict['username'] = user_dict['name']
c.group_dict = self._action('group_member_create')(context, data_dict)
self._redirect_to(controller='group', action='members', id=id)
else:
user = request.params.get('user')
if user:
c.user_dict = get_action('user_show')(context, {'id': user})
c.user_role = new_authz.users_role_for_group_or_org(id, user) or 'member'
else:
c.user_role = 'member'
except NotAuthorized:
abort(401, _('Unauthorized to add member to group %s') % '')
except NotFound:
abort(404, _('Group not found'))
except ValidationError, e:
h.flash_error(e.error_summary)
return self._render_template('group/member_new.html')
def member_delete(self, id):
if 'cancel' in request.params:
self._redirect_to(controller='group', action='members', id=id)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
try:
self._check_access('group_member_delete', context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s members') % '')
try:
user_id = request.params.get('user')
if request.method == 'POST':
self._action('group_member_delete')(context, {'id': id, 'user_id': user_id})
h.flash_notice(_('Group member has been deleted.'))
self._redirect_to(controller='group', action='members', id=id)
c.user_dict = self._action('user_show')(context, {'id': user_id})
c.user_id = user_id
c.group_id = id
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s') % '')
except NotFound:
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:
params = {'id': request.params.getone('group_name'),
'diff': request.params.getone('selected1'),
'oldid': request.params.getone('selected2'),
}
except KeyError, e:
if 'group_name' in dict(request.params):
id = request.params.getone('group_name')
c.error = \
_('Select two revisions before doing the comparison.')
else:
params['diff_entity'] = 'group'
h.redirect_to(controller='revision', action='diff', **params)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'schema': self._db_to_form_schema()}
data_dict = {'id': id}
try:
c.group_dict = self._action('group_show')(context, data_dict)
c.group_revisions = self._action('group_revision_list')(context,
data_dict)
#TODO: remove
# Still necessary for the authz check in group/layout.html
c.group = context['group']
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('User %r not authorized to edit %r') % (c.user, id))
format = request.params.get('format', '')
if format == 'atom':
# Generate and return Atom 1.0 document.
from webhelpers.feedgenerator import Atom1Feed
feed = Atom1Feed(
title=_(u'CKAN Group Revision History'),
link=self._url_for(controller='group', action='read',
id=c.group_dict['name']),
description=_(u'Recent changes to CKAN Group: ') +
c.group_dict['display_name'],
language=unicode(get_lang()),
)
for revision_dict in c.group_revisions:
revision_date = h.date_str_to_datetime(
revision_dict['timestamp'])
try:
dayHorizon = int(request.params.get('days'))
except:
dayHorizon = 30
dayAge = (datetime.datetime.now() - revision_date).days
if dayAge >= dayHorizon:
break
if revision_dict['message']:
item_title = u'%s' % revision_dict['message'].\
split('\n')[0]
else:
item_title = u'%s' % revision_dict['id']
item_link = h.url_for(controller='revision', action='read',
id=revision_dict['id'])
item_description = _('Log message: ')
item_description += '%s' % (revision_dict['message'] or '')
item_author_name = revision_dict['author']
item_pubdate = revision_date
feed.add_item(
title=item_title,
link=item_link,
description=item_description,
author_name=item_author_name,
pubdate=item_pubdate,
)
feed.content_type = 'application/atom+xml'
return feed.writeString('utf-8')
return render(self._history_template(c.group_dict['type']))
def activity(self, id, offset=0):
'''Render this group's public activity stream page.'''
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True}
try:
c.group_dict = self._get_group_dict(id)
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401,
_('Unauthorized to read group {group_id}').format(
group_id=id))
# 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 = self._action('group_activity_list_html')(
context, {'id': c.group_dict['id'], 'offset': offset})
return render(self._activity_template(c.group_dict['type']))
def follow(self, id):
'''Start following this group.'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author}
data_dict = {'id': id}
try:
get_action('follow_group')(context, data_dict)
group_dict = get_action('group_show')(context, data_dict)
h.flash_success(_("You are now following {0}").format(
group_dict['title']))
except ValidationError as e:
error_message = (e.message or e.error_summary
or e.error_dict)
h.flash_error(error_message)
except NotAuthorized as e:
h.flash_error(e.message)
h.redirect_to(controller='group', action='read', id=id)
def unfollow(self, id):
'''Stop following this group.'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author}
data_dict = {'id': id}
try:
get_action('unfollow_group')(context, data_dict)
group_dict = get_action('group_show')(context, data_dict)
h.flash_success(_("You are no longer following {0}").format(
group_dict['title']))
except ValidationError as e:
error_message = (e.message or e.error_summary
or e.error_dict)
h.flash_error(error_message)
except (NotFound, NotAuthorized) as e:
error_message = e.message
h.flash_error(error_message)
h.redirect_to(controller='group', action='read', id=id)
def followers(self, id):
context = {'model': model, 'session': model.Session,
'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})
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 = new_authz.get_group_or_org_admin_ids(id)
return render(self._admins_template(c.group_dict['type']))
def about(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
c.group_dict = self._get_group_dict(id)
group_type = c.group_dict['type']
self._setup_template_variables(context, {'id': id},
group_type=group_type)
return render(self._about_template(group_type))
def _get_group_dict(self, id):
''' returns the result of group_show action or aborts if there is a
problem '''
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'for_view': True}
try:
return self._action('group_show')(context, {'id': id, 'include_datasets': False})
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % id)
from pylons import config, cache
import sqlalchemy.exc
import ckan.logic as logic
import ckan.lib.maintain as maintain
import ckan.lib.search as search
import ckan.lib.base as base
import ckan.model as model
import ckan.lib.helpers as h
from ckan.common import _, g, c
CACHE_PARAMETERS = ['__cache', '__no_cache__']
# horrible hack
dirty_cached_group_stuff = None
class HomeController(base.BaseController):
repo = model.repo
def sitemap(self):
return base.render('home/sitemap.html')
def __before__(self, action, **env):
try:
base.BaseController.__before__(self, action, **env)
context = {'model': model, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
logic.check_access('site_read', context)
except logic.NotAuthorized:
base.abort(401, _('Not authorized to see this page'))
except (sqlalchemy.exc.ProgrammingError,
sqlalchemy.exc.OperationalError), e:
# postgres and sqlite errors for missing tables
msg = str(e)
if ('relation' in msg and 'does not exist' in msg) or \
('no such table' in msg):
# table missing, major database problem
base.abort(503, _('This site is currently off-line. Database '
'is not initialised.'))
# TODO: send an email to the admin person (#1285)
else:
raise
def index(self):
try:
# package search
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
data_dict = {
'q': '*:*',
'facet.field': g.facets,
'rows': 4,
'start': 0,
'sort': 'views_recent desc',
'fq': 'capacity:"public"'
}
query = logic.get_action('package_search')(
context, data_dict)
c.search_facets = query['search_facets']
c.package_count = query['count']
c.datasets = query['results']
c.facets = query['facets']
maintain.deprecate_context_item(
'facets',
'Use `c.search_facets` instead.')
c.search_facets = query['search_facets']
c.facet_titles = {
'organization': _('Organizations'),
'groups': _('Groups'),
'tags': _('Tags'),
'res_format': _('Formats'),
'license': _('Licenses'),
}
data_dict = {'sort': 'package_count desc', 'all_fields': 1}
# only give the terms to group dictize that are returned in the
# facets as full results take a lot longer
if 'groups' in c.search_facets:
data_dict['groups'] = [
item['name'] for item in c.search_facets['groups']['items']
]
c.groups = logic.get_action('group_list')(context, data_dict)
except search.SearchError:
c.package_count = 0
c.groups = []
if c.userobj is not None:
msg = None
url = h.url_for(controller='user', action='edit')
is_google_id = \
c.userobj.name.startswith(
'https://www.google.com/accounts/o8/id')
if not c.userobj.email and (is_google_id and
not c.userobj.fullname):
msg = _(u'Please <a href="{link}">update your profile</a>'
u' and add your email address and your full name. '
u'{site} uses your email address'
u' if you need to reset your password.'.format(
link=url, site=g.site_title))
elif not c.userobj.email:
msg = _('Please <a href="%s">update your profile</a>'
' and add your email address. ') % url + \
_('%s uses your email address'
' if you need to reset your password.') \
% g.site_title
elif is_google_id and not c.userobj.fullname:
msg = _('Please <a href="%s">update your profile</a>'
' and add your full name.') % (url)
if msg:
h.flash_notice(msg, allow_html=True)
# START OF DIRTINESS
def get_group(id):
def _get_group_type(id):
"""
Given the id of a group it determines the type of a group given
a valid id/name for the group.
"""
group = model.Group.get(id)
if not group:
return None
return group.type
def db_to_form_schema(group_type=None):
from ckan.lib.plugins import lookup_group_plugin
return lookup_group_plugin(group_type).db_to_form_schema()
group_type = _get_group_type(id.split('@')[0])
context = {'model': model, 'session': model.Session,
'ignore_auth': True,
'user': c.user or c.author,
'auth_user_obj': c.userobj,
'schema': db_to_form_schema(group_type=group_type),
'limits': {'packages': 2},
'for_view': True}
data_dict = {'id': id, 'include_datasets': True}
try:
group_dict = logic.get_action('group_show')(context, data_dict)
except logic.NotFound:
return None
return {'group_dict': group_dict}
global dirty_cached_group_stuff
if not dirty_cached_group_stuff:
groups_data = []
groups = config.get('ckan.featured_groups', '').split()
for group_name in groups:
group = get_group(group_name)
if group:
groups_data.append(group)
if len(groups_data) == 2:
break
# c.groups is from the solr query above
if len(groups_data) < 2 and len(c.groups) > 0:
group = get_group(c.groups[0]['name'])
if group:
groups_data.append(group)
def index(self):
try:
# package search
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
data_dict = {
'q': '*:*',
'facet': 'false',
'rows': 2,
'start': 0,
'sort': 'metadata_modified desc',
'fq': 'capacity:"public" +type:dataset'
}
query = logic.get_action('package_search')(context, data_dict)
c.package_count = query['count']
c.datasets = query['results']
# groups search
data_dict = {'sort': 'package_count desc', 'all_fields': 1}
# only give the terms to group dictize that are returned in the
# facets as full results take a lot longer
if 'groups' in c.search_facets:
data_dict['groups'] = [
item['name'] for item in c.search_facets['groups']['items']
]
c.groups = logic.get_action('group_list')(context, data_dict)
except search.SearchError:
c.package_count = 0
c.groups = []
try:
# app search
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
data_dict = {
'q': '*:*',
'facet': 'false',
'rows': 3,
'start': 0,
'sort': 'metadata_created desc',
'fq': 'capacity:"public" +type:application +status:verified'
}
query = logic.get_action('package_search')(context, data_dict)
c.apps = query['results']
except:
pass
if c.userobj is not None:
msg = None
url = h.url_for(controller='user', action='edit')
is_google_id = \
c.userobj.name.startswith(
'https://www.google.com/accounts/o8/id')
if not c.userobj.email and (is_google_id and
not c.userobj.fullname):
msg = _(u'Please <a href="{link}">update your profile</a>'
u' and add your email address and your full name. '
u'{site} uses your email address'
u' if you need to reset your password.'.format(
link=url, site=g.site_title))
elif not c.userobj.email:
msg = _('Please <a href="%s">update your profile</a>'
' and add your email address. ') % url + \
_('%s uses your email address'
' if you need to reset your password.') \
% g.site_title
elif is_google_id and not c.userobj.fullname:
msg = _('Please <a href="%s">update your profile</a>'
' and add your full name.') % (url)
if msg:
h.flash_notice(msg, allow_html=True)
# START OF DIRTINESS
def get_group(id):
def _get_group_type(id):
"""
Given the id of a group it determines the type of a group given
a valid id/name for the group.
"""
group = model.Group.get(id)
if not group:
return None
return group.type
def db_to_form_schema(group_type=None):
from ckan.lib.plugins import lookup_group_plugin
return lookup_group_plugin(group_type).db_to_form_schema()
group_type = _get_group_type(id.split('@')[0])
context = {'model': model, 'session': model.Session,
'ignore_auth': True,
'user': c.user or c.author,
'auth_user_obj': c.userobj,
'schema': db_to_form_schema(group_type=group_type),
'limits': {'packages': 2},
'for_view': True}
data_dict = {'id': id, 'include_datasets': True}
try:
group_dict = logic.get_action('group_show')(context, data_dict)
except logic.NotFound:
return None
return {'group_dict': group_dict}
global dirty_cached_group_stuff
if not dirty_cached_group_stuff:
groups_data = []
groups = config.get('ckan.featured_groups', '').split()
for group_name in groups:
group = get_group(group_name)
if group:
groups_data.append(group)
if len(groups_data) == 2:
break
# c.groups is from the solr query above
if len(groups_data) < 2 and len(c.groups) > 0:
group = get_group(c.groups[0]['name'])
if group:
groups_data.append(group)
if len(groups_data) < 2 and len(c.groups) > 1:
group = get_group(c.groups[1]['name'])
if group:
groups_data.append(group)
# We get all the packages or at least too many so
# limit it to just 2
for group in groups_data:
group['group_dict']['packages'] = \
group['group_dict']['packages'][:2]
#now add blanks so we have two
while len(groups_data) < 2:
groups_data.append({'group_dict': {}})
# cache for later use
dirty_cached_group_stuff = groups_data
c.group_package_stuff = dirty_cached_group_stuff
# END OF DIRTINESS
return base.render('home/index.html', cache_force=True)
def license(self):
return base.render('home/license.html')
def about(self):
return base.render('home/about.html')
def cache(self, id):
'''Manual way to clear the caches'''
if id == 'clear':
wui_caches = ['stats']
for cache_name in wui_caches:
cache_ = cache.get_cache(cache_name, type='dbm')
cache_.clear()
return 'Cleared caches: %s' % ', '.join(wui_caches)
def cors_options(self, url=None):
# just return 200 OK and empty data
return ''
import re
import os
import logging
import genshi
import cgi
import datetime
from urllib import urlencode
from pylons.i18n import get_lang
import ckan.lib.base as base
import ckan.lib.helpers as h
import ckan.lib.maintain as maintain
import ckan.lib.navl.dictization_functions as dict_fns
import ckan.logic as logic
import ckan.lib.search as search
import ckan.model as model
import ckan.new_authz as new_authz
import ckan.lib.plugins
import ckan.plugins as plugins
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(base.BaseController):
group_type = 'group'
default_sort_by = 'metadata_modified desc'
## hooks for subclasses
def _group_form(self, group_type=None):
return lookup_group_plugin(group_type).group_form()
def _form_to_db_schema(self, group_type=None):
return lookup_group_plugin(group_type).form_to_db_schema()
def _db_to_form_schema(self, group_type=None):
'''This is an interface to manipulate data from the database
into a format suitable for the form (optional)'''
return lookup_group_plugin(group_type).db_to_form_schema()
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):
return lookup_group_plugin(group_type).new_template()
def _index_template(self, group_type):
return lookup_group_plugin(group_type).index_template()
def _about_template(self, group_type):
return lookup_group_plugin(group_type).about_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()
def _edit_template(self, group_type):
return lookup_group_plugin(group_type).edit_template()
def _activity_template(self, group_type):
return lookup_group_plugin(group_type).activity_template()
def _admins_template(self, group_type):
return lookup_group_plugin(group_type).admins_template()
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'''
if self.group_type == 'organization':
string = re.sub('^group', 'organization', string)
return string
def _action(self, action_name):
''' select the correct group/org action '''
return get_action(self._replace_group_org(action_name))
def _check_access(self, action_name, *args, **kw):
''' select the correct group/org check_access '''
return check_access(self._replace_group_org(action_name), *args, **kw)
def _render_template(self, template_name):
''' render the correct group/org template '''
return render(self._replace_group_org(template_name))
def _redirect_to(self, *args, **kw):
''' wrapper to ensue the correct controller is used '''
if self.group_type == 'organization' and 'controller' in kw:
kw['controller'] = 'organization'
return h.redirect_to(*args, **kw)
def _url_for(self, *args, **kw):
''' wrapper to ensue the correct controller is used '''
if self.group_type == 'organization' and 'controller' in kw:
kw['controller'] = 'organization'
return h.url_for(*args, **kw)
def _guess_group_type(self, expecting_name=False):
"""
Guess the type of group from the URL handling the case
where there is a prefix on the URL (such as /data/organization)
"""
parts = [x for x in request.path.split('/') if x]
idx = -1
if expecting_name:
idx = -2
gt = parts[idx]
if gt == 'group':
gt = None
return gt
def index(self):
group_type = self._guess_group_type()
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'with_private': False}
q = c.q = request.params.get('q', '')
org_type = c.type = request.params.get('type', '')
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
else:
data_dict['sort'] = 'title asc' # Sort by title by default
try:
self._check_access('site_read', context)
except NotAuthorized:
abort(401, _('Not authorized to see this page'))
# pass user info to context as needed to view private datasets of
# orgs correctly
if c.userobj:
context['user_id'] = c.userobj.id
context['user_is_admin'] = c.userobj.sysadmin
if org_type:
data_dict['extra_conditions'] = [
['institution_type', '==', org_type]
]
results = self._action('group_list')(context, data_dict)
c.page = h.Page(
collection=results,
page = self._get_page_number(request.params),
url=h.pager_url,
items_per_page=21
)
return render(self._index_template(group_type))
def read(self, id, limit=20):
group_type = self._get_group_type(id.split('@')[0])
if group_type != self.group_type:
abort(404, _('Incorrect group type'))
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'schema': self._db_to_form_schema(group_type=group_type),
'for_view': True}
data_dict = {'id': id}
# unicode format (decoded from utf8)
q = c.q = request.params.get('q', '')
try:
# Do not query for the group datasets when dictizing, as they will
# be ignored and get requested on the controller anyway
data_dict['include_datasets'] = False
c.group_dict = self._action('group_show')(context, data_dict)
c.group = context['group']
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % id)
self._read(id, limit)
return render(self._read_template(c.group_dict['type']))
def _read(self, id, limit):
''' This is common code used by both read and bulk_process'''
group_type = self._get_group_type(id.split('@')[0])
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'schema': self._db_to_form_schema(group_type=group_type),
'for_view': True, 'extras_as_string': True}
q = c.q = request.params.get('q', '')
# Search within group
if c.group_dict.get('is_organization'):
q += ' owner_org:"%s"' % c.group_dict.get('id')
else:
q += ' groups:"%s"' % c.group_dict.get('name')
c.description_formatted = h.render_markdown(c.group_dict.get('description'))
context['return_query'] = True
# 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 = new_authz.get_group_or_org_admin_ids(c.group.id)
page = self._get_page_number(request.params)
# most search operations should reset the page counter:
params_nopage = [(k, v) for k, v in request.params.items()
if k != 'page']
sort_by = request.params.get('sort', None)
def search_url(params):
if group_type == 'organization':
if c.action == 'bulk_process':
url = self._url_for(controller='organization',
action='bulk_process',
id=id)
else:
url = self._url_for(controller='organization',
action='read',
id=id)
else:
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)
def drill_down_url(**by):
return h.add_url_param(alternative_url=None,
controller='group', action='read',
extras=dict(id=c.group_dict.get('name')),
new_params=by)
c.drill_down_url = drill_down_url
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')))
c.remove_field = remove_field
def pager_url(q=None, page=None):
params = list(params_nopage)
params.append(('page', page))
return search_url(params)
try:
c.fields = []
search_extras = {}
for (param, value) in request.params.items():
if not param in ['q', 'page', 'sort'] \
and len(value) and not param.startswith('_'):
if not param.startswith('ext_'):
c.fields.append((param, value))
q += ' %s: "%s"' % (param, value)
else:
search_extras[param] = value
fq = 'capacity:"public"'
user_member_of_orgs = [org['id'] for org
in h.organizations_available('read')]
if (c.group and c.group.id in user_member_of_orgs):
fq = ''
context['ignore_capacity_check'] = True
facets = OrderedDict()
default_facet_titles = {'organization': _('Organizations'),
'groups': _('Groups'),
'tags': _('Tags'),
'res_format': _('Formats'),
'license_id': _('Licenses')}
for facet in g.facets:
if facet in default_facet_titles:
facets[facet] = default_facet_titles[facet]
else:
facets[facet] = facet
# Facet titles
for plugin in plugins.PluginImplementations(plugins.IFacets):
if self.group_type == 'organization':
facets = plugin.organization_facets(
facets, self.group_type, None)
else:
facets = plugin.group_facets(
facets, self.group_type, None)
if 'capacity' in facets and (self.group_type != 'organization' or
not user_member_of_orgs):
del facets['capacity']
c.facet_titles = facets
data_dict = {
'q': q,
'fq': fq,
'facet.field': facets.keys(),
'rows': limit,
'sort': sort_by,
'start': (page - 1) * limit,
'extras': search_extras
}
context_ = dict((k, v) for (k, v) in context.items() if k != 'schema')
query = get_action('package_search')(context_, data_dict)
c.page = h.Page(
collection=query['results'],
page=page,
url=pager_url,
item_count=query['count'],
items_per_page=limit
)
c.group_dict['package_count'] = query['count']
c.facets = query['facets']
maintain.deprecate_context_item('facets',
'Use `c.search_facets` instead.')
c.search_facets = query['search_facets']
c.search_facets_limits = {}
for facet in c.facets.keys():
limit = int(request.params.get('_%s_limit' % facet,
g.facets_default_number))
c.search_facets_limits[facet] = limit
c.page.items = query['results']
c.sort_by_selected = sort_by
except search.SearchError, se:
log.error('Group search error: %r', se.args)
c.query_error = True
c.facets = {}
c.page = h.Page(collection=[])
self._setup_template_variables(context, {'id':id},
group_type=group_type)
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 is None:
abort(404, _('Organization not found'))
if group_type != 'organization':
# FIXME: better error
raise Exception('Must be an organization')
# check we are org admin
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'schema': self._db_to_form_schema(group_type=group_type),
'for_view': True, 'extras_as_string': True}
data_dict = {'id': id}
try:
# Do not query for the group datasets when dictizing, as they will
# be ignored and get requested on the controller anyway
data_dict['include_datasets'] = False
c.group_dict = self._action('group_show')(context, data_dict)
c.group = context['group']
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % id)
#use different form names so that ie7 can be detected
form_names = set(["bulk_action.public", "bulk_action.delete",
"bulk_action.private"])
actions_in_form = set(request.params.keys())
actions = form_names.intersection(actions_in_form)
# If no action then just show the datasets
if not actions:
# unicode format (decoded from utf8)
limit = 500
self._read(id, limit)
c.packages = c.page.items
return render(self._bulk_process_template(group_type))
#ie7 puts all buttons in form params but puts submitted one twice
for key, value in dict(request.params.dict_of_lists()).items():
if len(value) == 2:
action = key.split('.')[-1]
break
else:
#normal good browser form submission
action = actions.pop().split('.')[-1]
# 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_'):
datasets.append(param[8:])
action_functions = {
'private': 'bulk_update_private',
'public': 'bulk_update_public',
'delete': 'bulk_update_delete',
}
data_dict = {'datasets': datasets, 'org_id': c.group_dict['id']}
try:
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))
def new(self, data=None, errors=None, error_summary=None):
group_type = self._guess_group_type(True)
if data:
data['type'] = group_type
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'save': 'save' in request.params,
'parent': request.params.get('parent', None)}
try:
self._check_access('group_create', context)
except NotAuthorized:
abort(401, _('Unauthorized to create a group'))
if context['save'] and not data:
return self._save_new(context, group_type)
data = data or {}
if not data.get('image_url', '').startswith('http'):
data.pop('image_url', None)
errors = errors or {}
error_summary = error_summary or {}
vars = {'data': data, 'errors': errors,
'error_summary': error_summary, 'action': 'new'}
self._setup_template_variables(context, data, group_type=group_type)
c.form = render(self._group_form(group_type=group_type),
extra_vars=vars)
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])
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'save': 'save' in request.params,
'for_edit': True,
'parent': request.params.get('parent', None)
}
data_dict = {'id': id, 'include_datasets': False}
if context['save'] and not data:
return self._save_edit(id, context)
try:
data_dict['include_datasets'] = False
old_data = self._action('group_show')(context, data_dict)
c.grouptitle = old_data.get('title')
c.groupname = old_data.get('name')
data = data or old_data
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % '')
group = context.get("group")
c.group = group
c.group_dict = self._action('group_show')(context, data_dict)
try:
self._check_access('group_update', context)
except NotAuthorized, e:
abort(401, _('User %r not authorized to edit %s') % (c.user, id))
errors = errors or {}
vars = {'data': data, 'errors': errors,
'error_summary': error_summary, 'action': 'edit'}
self._setup_template_variables(context, data, group_type=group_type)
c.form = render(self._group_form(group_type), extra_vars=vars)
return render(self._edit_template(c.group.type))
def _get_group_type(self, id):
"""
Given the id of a group it determines the type of a group given
a valid id/name for the group.
"""
group = model.Group.get(id)
if not group:
return None
return group.type
def _save_new(self, context, group_type=None):
try:
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', '')
data_dict['users'] = [{'name': c.user, 'capacity': 'admin'}]
group = self._action('group_create')(context, data_dict)
# Redirect to the appropriate _read route for the type of group
h.redirect_to(group['type'] + '_read', id=group['name'])
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % '')
except NotFound, e:
abort(404, _('Group not found'))
except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.new(data_dict, errors, error_summary)
def _force_reindex(self, grp):
''' When the group name has changed, we need to force a reindex
of the datasets within the group, otherwise they will stop
appearing on the read page for the group (as they're connected via
the group name)'''
group = model.Group.get(grp['name'])
for dataset in group.packages():
search.rebuild(dataset.name)
def _save_edit(self, id, context):
try:
data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.params))))
context['message'] = data_dict.get('log_message', '')
data_dict['id'] = id
context['allow_partial_update'] = True
group = self._action('group_update')(context, data_dict)
if id != group['name']:
self._force_reindex(group)
h.redirect_to('%s_read' % group['type'], id=group['name'])
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % id)
except NotFound, e:
abort(404, _('Group not found'))
except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.edit(id, data_dict, errors, error_summary)
def authz(self, id):
group = model.Group.get(id)
if group is None:
abort(404, _('Group not found'))
c.groupname = group.name
c.grouptitle = group.display_name
try:
context = \
{'model': model, 'user': c.user or c.author, 'group': group}
self._check_access('group_edit_permissions', context)
c.authz_editable = True
c.group = context['group']
except NotAuthorized:
c.authz_editable = False
if not c.authz_editable:
abort(401,
_('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)
return render('group/authz.html')
def delete(self, id):
if 'cancel' in request.params:
self._redirect_to(controller='group', action='edit', id=id)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
try:
self._check_access('group_delete', context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s') % '')
try:
if request.method == 'POST':
self._action('group_delete')(context, {'id': id})
if self.group_type == 'organization':
h.flash_notice(_('Organization has been deleted.'))
else:
h.flash_notice(_('Group has been deleted.'))
self._redirect_to(controller='group', action='index')
c.group_dict = self._action('group_show')(context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s') % '')
except NotFound:
abort(404, _('Group not found'))
return self._render_template('group/confirm_delete.html')
def members(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
try:
c.members = self._action('member_list')(
context, {'id': id, 'object_type': 'user'}
)
data_dict = {'id': id}
data_dict['include_datasets'] = False
c.group_dict = self._action('group_show')(context, data_dict)
except NotAuthorized:
abort(401, _('Unauthorized to show group %s') % id)
except NotFound:
abort(404, _('Group not found'))
return self._render_template('group/members.html')
def member_new(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
#self._check_access('group_delete', context, {'id': id})
try:
data_dict = {'id': id}
data_dict['include_datasets'] = False
c.group_dict = self._action('group_show')(context, data_dict)
group_type = 'organization' if c.group_dict['is_organization'] else 'group'
c.roles = self._action('member_roles_list')(
context, {'group_type': group_type}
)
if request.method == 'POST':
data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.params))))
data_dict['id'] = id
email = data_dict.get('email')
if email:
user_data_dict = {
'email': email,
'group_id': data_dict['id'],
'role': data_dict['role']
}
del data_dict['email']
user_dict = self._action('user_invite')(context,
user_data_dict)
data_dict['username'] = user_dict['name']
c.group_dict = self._action('group_member_create')(context, data_dict)
self._redirect_to(controller='group', action='members', id=id)
else:
user = request.params.get('user')
if user:
c.user_dict = get_action('user_show')(context, {'id': user})
c.user_role = new_authz.users_role_for_group_or_org(id, user) or 'member'
else:
c.user_role = 'member'
except NotAuthorized:
abort(401, _('Unauthorized to add member to group %s') % '')
except NotFound:
abort(404, _('Group not found'))
except ValidationError, e:
h.flash_error(e.error_summary)
return self._render_template('group/member_new.html')
def member_delete(self, id):
if 'cancel' in request.params:
self._redirect_to(controller='group', action='members', id=id)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
try:
self._check_access('group_member_delete', context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s members') % '')
try:
user_id = request.params.get('user')
if request.method == 'POST':
self._action('group_member_delete')(context, {'id': id, 'user_id': user_id})
h.flash_notice(_('Group member has been deleted.'))
self._redirect_to(controller='group', action='members', id=id)
c.user_dict = self._action('user_show')(context, {'id': user_id})
c.user_id = user_id
c.group_id = id
except NotAuthorized:
abort(401, _('Unauthorized to delete group %s') % '')
except NotFound:
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:
params = {'id': request.params.getone('group_name'),
'diff': request.params.getone('selected1'),
'oldid': request.params.getone('selected2'),
}
except KeyError, e:
if 'group_name' in dict(request.params):
id = request.params.getone('group_name')
c.error = \
_('Select two revisions before doing the comparison.')
else:
params['diff_entity'] = 'group'
h.redirect_to(controller='revision', action='diff', **params)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'schema': self._db_to_form_schema()}
data_dict = {'id': id}
try:
c.group_dict = self._action('group_show')(context, data_dict)
c.group_revisions = self._action('group_revision_list')(context,
data_dict)
#TODO: remove
# Still necessary for the authz check in group/layout.html
c.group = context['group']
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('User %r not authorized to edit %r') % (c.user, id))
format = request.params.get('format', '')
if format == 'atom':
# Generate and return Atom 1.0 document.
from webhelpers.feedgenerator import Atom1Feed
feed = Atom1Feed(
title=_(u'CKAN Group Revision History'),
link=self._url_for(controller='group', action='read',
id=c.group_dict['name']),
description=_(u'Recent changes to CKAN Group: ') +
c.group_dict['display_name'],
language=unicode(get_lang()),
)
for revision_dict in c.group_revisions:
revision_date = h.date_str_to_datetime(
revision_dict['timestamp'])
try:
dayHorizon = int(request.params.get('days'))
except:
dayHorizon = 30
dayAge = (datetime.datetime.now() - revision_date).days
if dayAge >= dayHorizon:
break
if revision_dict['message']:
item_title = u'%s' % revision_dict['message'].\
split('\n')[0]
else:
item_title = u'%s' % revision_dict['id']
item_link = h.url_for(controller='revision', action='read',
id=revision_dict['id'])
item_description = _('Log message: ')
item_description += '%s' % (revision_dict['message'] or '')
item_author_name = revision_dict['author']
item_pubdate = revision_date
feed.add_item(
title=item_title,
link=item_link,
description=item_description,
author_name=item_author_name,
pubdate=item_pubdate,
)
feed.content_type = 'application/atom+xml'
return feed.writeString('utf-8')
return render(self._history_template(c.group_dict['type']))
def activity(self, id, offset=0):
'''Render this group's public activity stream page.'''
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True}
try:
c.group_dict = self._get_group_dict(id)
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401,
_('Unauthorized to read group {group_id}').format(
group_id=id))
# 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 = self._action('group_activity_list_html')(
context, {'id': c.group_dict['id'], 'offset': offset})
return render(self._activity_template(c.group_dict['type']))
def follow(self, id):
'''Start following this group.'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author}
data_dict = {'id': id}
try:
get_action('follow_group')(context, data_dict)
group_dict = get_action('group_show')(context, data_dict)
h.flash_success(_("You are now following {0}").format(
group_dict['title']))
except ValidationError as e:
error_message = (e.message or e.error_summary
or e.error_dict)
h.flash_error(error_message)
except NotAuthorized as e:
h.flash_error(e.message)
h.redirect_to(controller='group', action='read', id=id)
def unfollow(self, id):
'''Stop following this group.'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author}
data_dict = {'id': id}
try:
get_action('unfollow_group')(context, data_dict)
group_dict = get_action('group_show')(context, data_dict)
h.flash_success(_("You are no longer following {0}").format(
group_dict['title']))
except ValidationError as e:
error_message = (e.message or e.error_summary
or e.error_dict)
h.flash_error(error_message)
except (NotFound, NotAuthorized) as e:
error_message = e.message
h.flash_error(error_message)
h.redirect_to(controller='group', action='read', id=id)
def followers(self, id):
context = {'model': model, 'session': model.Session,
'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})
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 = new_authz.get_group_or_org_admin_ids(id)
return render(self._admins_template(c.group_dict['type']))
def about(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
c.group_dict = self._get_group_dict(id)
group_type = c.group_dict['type']
self._setup_template_variables(context, {'id': id},
group_type=group_type)
return render(self._about_template(group_type))
def _get_group_dict(self, id):
''' returns the result of group_show action or aborts if there is a
problem '''
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'for_view': True}
try:
return self._action('group_show')(context, {'id': id, 'include_datasets': False})
except NotFound:
abort(404, _('Group not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read group %s') % id)
import logging
from urllib import urlencode
import datetime
import mimetypes
import cgi
from pylons import config
from genshi.template import MarkupTemplate
from genshi.template.text import NewTextTemplate
from paste.deploy.converters import asbool
import paste.fileapp
import ckan.logic as logic
import ckan.lib.base as base
import ckan.lib.maintain as maintain
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.lib.plugins
import ckan.lib.uploader as uploader
import ckan.plugins as p
import ckan.lib.render
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
def get_action(action):
if action == 'package_update':
return dp.logic_action_update_package_update
elif action == 'package_create':
return dp.logic_action_create_package_create
else:
return logic.get_action(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', 'ignore') if isinstance(v, basestring) else str(v))
for k, v in params]
def url_with_params(url, params):
params = _encode_params(params)
return url + u'?' + urlencode(params)
def search_url(params, package_type=None):
if not package_type or package_type == 'dataset':
url = h.url_for(controller='package', action='search')
else:
url = h.url_for('{0}_search'.format(package_type))
return url_with_params(url, params)
class PackageController(base.BaseController):
def _package_form(self, package_type=None):
return lookup_package_plugin(package_type).package_form()
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 _edit_template(self, package_type):
return lookup_package_plugin(package_type).edit_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()
def _resource_form(self, package_type):
# backwards compatibility with plugins not inheriting from
# DefaultDatasetPlugin and not implmenting resource_form
plugin = lookup_package_plugin(package_type)
if hasattr(plugin, 'resource_form'):
result = plugin.resource_form()
if result is not None:
return result
return lookup_package_plugin().resource_form()
def _resource_template(self, package_type):
# backwards compatibility with plugins not inheriting from
# DefaultDatasetPlugin and not implmenting resource_template
plugin = lookup_package_plugin(package_type)
if hasattr(plugin, 'resource_template'):
result = plugin.resource_template()
if result is not None:
return result
return lookup_package_plugin().resource_template()
def _guess_package_type(self, expecting_name=False):
"""
Guess the type of package from the URL handling the case
where there is a prefix on the URL (such as /data/package)
"""
# Special case: if the rot URL '/' has been redirected to the package
# controller (e.g. by an IRoutes extension) then there's nothing to do
# here.
if request.path == '/':
return 'dataset'
parts = [x for x in request.path.split('/') if x]
idx = -1
if expecting_name:
idx = -2
pt = parts[idx]
if pt == 'package':
pt = 'dataset'
return pt
def search(self):
from ckan.lib.search import SearchError
package_type = self._guess_package_type()
try:
context = {'model': model, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
check_access('site_read', context)
except NotAuthorized:
abort(401, _('Not authorized to see this page'))
# unicode format (decoded from utf8)
q = c.q = request.params.get('q', u'')
c.query_error = False
page = self._get_page_number(request.params)
limit = g.datasets_per_page
# 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(alternative_url=None, **by):
return h.add_url_param(alternative_url=alternative_url,
controller='package', action='search',
new_params=by)
c.drill_down_url = drill_down_url
def remove_field(key, value=None, replace=None):
return h.remove_url_param(key, value=value, replace=replace,
controller='package', action='search')
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, package_type)
c.sort_by = _sort_by
if not sort_by:
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))
return search_url(params, package_type)
c.search_url_params = urlencode(_encode_params(params_nopage))
try:
c.fields = []
# c.fields_grouped will contain a dict of params containing
# a list of values eg {'tags':['tag1', 'tag2']}
c.fields_grouped = {}
search_extras = {}
fq = ''
for (param, value) in request.params.items():
if param not in ['q', 'page', 'sort'] \
and len(value) and not param.startswith('_'):
if not param.startswith('ext_'):
c.fields.append((param, value))
fq += ' %s:"%s"' % (param, value)
if param not in c.fields_grouped:
c.fields_grouped[param] = [value]
else:
c.fields_grouped[param].append(value)
else:
search_extras[param] = value
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
if package_type and package_type != 'dataset':
# Only show datasets of this particular type
fq += ' +dataset_type:{type}'.format(type=package_type)
else:
# Unless changed via config options, don't show non standard
# dataset types on the default search page
if not asbool(config.get('ckan.search.show_all_types', 'False')):
fq += ' +dataset_type:dataset'
facets = OrderedDict()
default_facet_titles = {
'organization': _('Organizations'),
'groups': _('Groups'),
'tags': _('Tags'),
'res_format': _('Formats'),
'license_id': _('Licenses'),
}
for facet in g.facets:
if facet in default_facet_titles:
facets[facet] = default_facet_titles[facet]
else:
facets[facet] = facet
# Facet titles
for plugin in p.PluginImplementations(p.IFacets):
facets = plugin.dataset_facets(facets, package_type)
c.facet_titles = facets
data_dict = {
'q': q,
'fq': fq.strip(),
'facet.field': facets.keys(),
'rows': limit,
'start': (page - 1) * limit,
'sort': sort_by,
'extras': search_extras
}
query = get_action('package_search')(context, data_dict)
c.sort_by_selected = query['sort']
c.page = h.Page(
collection=query['results'],
page=page,
url=pager_url,
item_count=query['count'],
items_per_page=limit
)
c.facets = query['facets']
c.search_facets = query['search_facets']
c.page.items = query['results']
except SearchError, se:
log.error('Dataset search error: %r', se.args)
c.query_error = True
c.facets = {}
c.search_facets = {}
c.page = h.Page(collection=[])
c.search_facets_limits = {}
for facet in c.search_facets.keys():
def search(self):
from ckan.lib.search import SearchError
package_type = self._guess_package_type()
try:
context = {'model': model, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
check_access('site_read', context)
except NotAuthorized:
abort(401, _('Not authorized to see this page'))
# unicode format (decoded from utf8)
q = c.q = request.params.get('q', u'')
c.query_error = False
page = self._get_page_number(request.params)
limit = g.datasets_per_page
# 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(alternative_url=None, **by):
return h.add_url_param(alternative_url=alternative_url,
controller='package', action='search',
new_params=by)
c.drill_down_url = drill_down_url
def remove_field(key, value=None, replace=None):
return h.remove_url_param(key, value=value, replace=replace,
controller='package', action='search')
c.remove_field = remove_field
if package_type == 'dataset':
default_sort_by = 'metadata_modified desc' if g.tracking_enabled else None
else:
default_sort_by = 'metadata_created desc'
sort_by = request.params.get('sort', default_sort_by)
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, package_type)
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))
return search_url(params, package_type)
c.search_url_params = urlencode(_encode_params(params_nopage))
api_search_url_params = None
try:
c.fields = []
# c.fields_grouped will contain a dict of params containing
# a list of values eg {'tags':['tag1', 'tag2']}
c.fields_grouped = {}
search_extras = {}
fq = ''
fq_list = [] # filter statements that will be sent in separate "fq" params (because we want to be able to exclude them selectively from facets)
for (param, value) in request.params.items():
if param not in ['q', 'page', 'sort'] \
and len(value) and not param.startswith('_'):
if not param.startswith('ext_'):
c.fields.append((param, value))
fq_list.append('{!tag=%s}%s:"%s"' % (param, param, value))
if param not in c.fields_grouped:
c.fields_grouped[param] = [value]
else:
c.fields_grouped[param].append(value)
else:
search_extras[param] = value
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
if package_type and package_type != 'dataset':
# Only show datasets of this particular type
fq += ' +dataset_type:{type}'.format(type=package_type)
if (package_type == 'application') and not (new_authz.is_sysadmin(c.user)):
fq += ' +status:verified'
else:
# Unless changed via config options, don't show non standard
# dataset types on the default search page
if not asbool(config.get('ckan.search.show_all_types', 'False')):
fq += ' +dataset_type:dataset'
facets = OrderedDict()
default_facet_titles = {
'organization': _('Organizations'),
'groups': _('Groups'),
'tags': _('Tags'),
'res_format': _('Formats'),
'license_id': _('Licenses'),
}
for facet in g.facets:
if facet in default_facet_titles:
facets[facet] = default_facet_titles[facet]
else:
facets[facet] = facet
# Facet titles
for plugin in p.PluginImplementations(p.IFacets):
facets = plugin.dataset_facets(facets, package_type)
c.facet_titles = facets
facets_keys = []
for f in facets:
facets_keys.append("{!ex=" + f + "}" + f)
data_dict = {
'q': q,
'fq': fq.strip(),
'fq_list': fq_list,
'facet.field': facets_keys,
'rows': limit,
'start': (page - 1) * limit,
'sort': sort_by,
'extras': search_extras
}
##### EXTENDING ORIGINAL <<<<<<<<<<
# API Params
api_url_params = {
'q': q,
'fq': fq.strip(),
'rows': limit,
'start': (page - 1) * limit,
}
if sort_by:
api_url_params['sort'] = sort_by
if facets.keys():
api_url_params['facet.field'] = json.dumps(facets.keys())
# if search_extras:
# api_url_params['extras'] = ','.join(search_extras)
api_search_url_params = urlencode(_encode_params(api_url_params.items()))
##### EXTENDING ORIGINAL >>>>>>>>>>>>>>
query = get_action('package_search')(context, data_dict)
c.sort_by_selected = query['sort']
c.page = h.Page(
collection=query['results'],
page=page,
url=pager_url,
item_count=query['count'],
items_per_page=limit
)
c.facets = query['facets']
c.search_facets = query['search_facets']
c.page.items = query['results']
except SearchError, se:
log.error('Dataset search error: %r', se.args)
c.query_error = True
c.facets = {}
c.search_facets = {}
c.page = h.Page(collection=[])
c.search_facets_limits = {}
for facet in c.search_facets.keys():
try:
limit = int(request.params.get('_%s_limit' % facet,
g.facets_default_number))
except ValueError:
abort(400, _('Parameter "{parameter_name}" is not '
'an integer').format(
parameter_name='_%s_limit' % facet
))
c.search_facets_limits[facet] = limit
maintain.deprecate_context_item(
'facets',
'Use `c.search_facets` instead.')
self._setup_template_variables(context, {},
package_type=package_type)
return render(self._search_template(package_type),
extra_vars={'dataset_type': package_type, 'api_search_url_params': api_search_url_params})
def _content_type_from_extension(self, ext):
ct, ext = accept.parse_extension(ext)
if not ct:
return None, None
return ct, ext
def download(self):
#from django.http import HttpResponse
#try:
from ckan.common import response
import unicodecsv as csv
response.headers["Content-Disposition"] = "attachment; filename=resources.csv"
context = {'model': model, 'session': model.Session, 'user': c.user or c.author, 'auth_user_obj': c.userobj}
data_dict = {
'q': '*:*',
'facet': 'false',
'start': 0,
'rows': 10000000,
'sort': 'metadata_created desc',
'fq': 'capacity:"public" +type:dataset'
}
query = logic.get_action('package_search')(context, data_dict)
datasets = query['results']
writer = csv.writer(response)
writer.writerow([
_('Dataset ID').encode('utf-8', 'ignore'),
_('Name').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Description').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('URL').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Format').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Type').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('5 Stars of Openness').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Creation Date').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Last Modified').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Dataset name').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Dataset title').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Dataset notes').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Dataset category').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Dataset creation date').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Dataset modification date').encode('utf-8', 'ignore'),
_('Organization name').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
_('Organization title').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore')
])
for dataset in datasets:
org = dataset.get('organization')
for resource in dataset.get('resources'):
writer.writerow([
resource.get('id'),
resource.get('name').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
resource.get('description').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
resource.get('url'),
resource.get('format'),
resource.get('resource_type'),
resource.get('openness_score'),
resource.get('created'),
resource.get('last_modified'),
dataset.get('name').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
dataset.get('title').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
dataset.get('notes').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
dataset.get('category').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
dataset.get('metadata_created'),
dataset.get('metadata_modified'),
org.get('name').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
org.get('title').replace("\n", "").replace("\r", "").replace("\t", "").encode('utf-8', 'ignore'),
])
return response
@jsonp.jsonpify
def jupload_resource(self, id):
if not request.method == 'POST':
abort(400, _('Only POST is supported'))
data = clean_dict(df.unflatten(tuplize_dict(parse_params(
request.POST))))
package_name = data.get('name')
file = data.get('file')
if package_name == None or file == None:
abort(400, _('Missing dataset name or file.'))
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
try:
package_dict = get_action('package_show')(context, {'id': package_name})
except NotAuthorized:
abort(401, _('Unauthorized to update dataset'))
except NotFound:
abort(404,
_('The dataset {id} could not be found.').format(id=package_name))
resource_dict = {
'package_id': package_name,
'upload': file,
'name': file.filename
}
try:
resource = get_action('resource_create')(context, resource_dict)
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.new_resource(id, data, errors, error_summary)
except NotAuthorized:
abort(401, _('Unauthorized to create a resource'))
except NotFound:
abort(404,
_('The dataset {id} could not be found.').format(id=id))
return {"files": [
{
"name": file.filename,
"url": resource['url'],
}
]}
def _content_type_from_accept(self):
"""
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.
"""
ct, ext = accept.parse_header(request.headers.get('Accept', ''))
return ct, ext
def resources(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
check_access('package_update', context, data_dict)
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized, e:
abort(401, _('User %r not authorized to edit %s') % (c.user, id))
# check if package exists
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
c.pkg = context['package']
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % id)
package_type = c.pkg_dict['type'] or 'dataset'
self._setup_template_variables(context, {'id': id},
package_type=package_type)
return render('package/resources.html',
extra_vars={'dataset_type': package_type})
def read(self, id, format='html'):
if not format == 'html':
ctype, extension = \
self._content_type_from_extension(format)
if not ctype:
# An unknown format, we'll carry on in case it is a
# revision specifier and re-constitute the original id
id = "%s.%s" % (id, format)
ctype, format = "text/html; charset=utf-8", "html"
else:
ctype, format = self._content_type_from_accept()
response.headers['Content-Type'] = ctype
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
data_dict = {'id': id}
# interpret @<revision_id> or @<date> suffix
split = id.split('@')
if len(split) == 2:
data_dict['id'], revision_ref = split
if model.is_id(revision_ref):
context['revision_id'] = revision_ref
else:
try:
date = h.date_str_to_datetime(revision_ref)
context['revision_date'] = date
except TypeError, e:
abort(400, _('Invalid revision format: %r') % e.args)
except ValueError, e:
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)
c.pkg = context['package']
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % id)
if (c.pkg_dict.get('type') == 'application') and (c.pkg_dict.get('status') == 'unverified') and not new_authz.is_sysadmin(c.user):
abort(401, _('Unauthorized to read package %s') % id)
# used by disqus plugin
c.current_package_id = c.pkg.id
c.related_count = c.pkg.related_count
# can the resources be previewed?
# DROP IT!
# TODO ckan-dev do we really need to call all these functions (sometimes quite memory demanding) to show labels "Preview' or "More information"
# If it is needed it needs to be cached!
# I had memory crashes when dataset contained multiple resources
for resource in c.pkg_dict['resources']:
# # Backwards compatibility with preview interface
# resource['can_be_previewed'] = self._resource_preview(
# {'resource': resource, 'package': c.pkg_dict})
resource_views = get_action('resource_view_list')(
context, {'id': resource['id']})
resource['has_views'] = len(resource_views) > 0
package_type = c.pkg_dict['type'] or 'dataset'
self._setup_template_variables(context, {'id': id},
package_type=package_type)
template = self._read_template(package_type)
template = template[:template.index('.') + 1] + format
feedback_form = FeedbackController.get_form_items()
feedback_form += [
{'name': 'source_type', 'control': 'hidden'},
{'name': 'source_id', 'control': 'hidden'},
]
if (c.pkg_dict.get('type') == 'application') and c.pkg_dict.get('dataset_name'):
try:
data_dict = {
'q': '*:*',
'fq': '+type:dataset +name:("' + '" OR "'.join(c.pkg_dict.get('dataset_name')) + '")',
'facet': 'false',
'sort': 'metadata_modified desc',
}
query = logic.get_action('package_search')(context, data_dict)
c.datasets = query['results']
except:
pass
if (c.pkg_dict.get('type') == 'dataset'):
try:
data_dict = {
'q': '*:*',
'facet': 'false',
'rows': 3,
'start': 0,
'sort': 'metadata_created desc',
'fq': 'capacity:"public" +type:application +status:verified +dataset_name:' + c.pkg_dict.get('name')
}
query = logic.get_action('package_search')(context, data_dict)
c.apps = query['results']
except:
pass
try:
return render(template,
extra_vars={'dataset_type': package_type, 'feedback_form_items': feedback_form})
except ckan.lib.render.TemplateNotFound:
msg = _("Viewing {package_type} datasets in {format} format is "
"not supported (template file {file} not found).".format(
package_type=package_type, format=format, file=template))
abort(404, msg)
assert False, "We should never get here"
def history(self, id):
if 'diff' in request.params or 'selected1' in request.params:
try:
params = {'id': request.params.getone('pkg_name'),
'diff': request.params.getone('selected1'),
'oldid': request.params.getone('selected2'),
}
except KeyError, e:
if 'pkg_name' in dict(request.params):
id = request.params.getone('pkg_name')
c.error = \
_('Select two revisions before doing the comparison.')
else:
params['diff_entity'] = 'package'
h.redirect_to(controller='revision', action='diff', **params)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
c.pkg_revisions = get_action('package_revision_list')(context,
data_dict)
# TODO: remove
# Still necessary for the authz check in group/layout.html
c.pkg = context['package']
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % '')
except NotFound:
abort(404, _('Dataset not found'))
format = request.params.get('format', '')
if format == 'atom':
# Generate and return Atom 1.0 document.
from webhelpers.feedgenerator import Atom1Feed
feed = Atom1Feed(
title=_(u'CKAN Dataset Revision History'),
link=h.url_for(controller='revision', action='read',
id=c.pkg_dict['name']),
description=_(u'Recent changes to CKAN Dataset: ') +
(c.pkg_dict['title'] or ''),
language=unicode(i18n.get_lang()),
)
for revision_dict in c.pkg_revisions:
revision_date = h.date_str_to_datetime(
revision_dict['timestamp'])
try:
dayHorizon = int(request.params.get('days'))
except:
dayHorizon = 30
dayAge = (datetime.datetime.now() - revision_date).days
if dayAge >= dayHorizon:
break
if revision_dict['message']:
item_title = u'%s' % revision_dict['message'].\
split('\n')[0]
else:
item_title = u'%s' % revision_dict['id']
item_link = h.url_for(controller='revision', action='read',
id=revision_dict['id'])
item_description = _('Log message: ')
item_description += '%s' % (revision_dict['message'] or '')
item_author_name = revision_dict['author']
item_pubdate = revision_date
feed.add_item(
title=item_title,
link=item_link,
description=item_description,
author_name=item_author_name,
pubdate=item_pubdate,
)
response.headers['Content-Type'] = 'application/atom+xml'
return feed.writeString('utf-8')
package_type = c.pkg_dict['type'] or 'dataset'
c.related_count = c.pkg.related_count
return render(
self._history_template(c.pkg_dict.get('type', package_type)),
extra_vars={'dataset_type': package_type})
def new(self, data=None, errors=None, error_summary=None):
if data and 'type' in data:
package_type = data['type']
else:
package_type = self._guess_package_type(True)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj,
'save': 'save' in request.params}
if (package_type == 'application') and (request.method == 'POST') and (request.params.get('from_users') == '1'):
context['ignore_auth'] = True
# Package needs to have a organization group in the call to
# check_access and also to save it
try:
check_access('package_create', context)
except NotAuthorized:
abort(401, _('Unauthorized to create a package'))
if context['save'] and not data:
return self._save_new(context, package_type=package_type)
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
if data and not data.get('tag_string'):
data['tag_string'] = ', '.join(
h.dict_list_reduce(data.get('tags', {}), 'name'))
errors = errors or {}
error_summary = error_summary or {}
# in the phased add dataset we need to know that
# we have already completed stage 1
stage = ['active']
if data.get('state', '').startswith('draft'):
stage = ['active', 'complete']
# if we are creating from a group then this allows the group to be
# set automatically
data['group_id'] = request.params.get('group') or \
request.params.get('groups__0__id')
form_snippet = self._package_form(package_type=package_type)
form_vars = {'data': data, 'errors': errors,
'error_summary': error_summary,
'action': 'new', 'stage': stage,
'dataset_type': package_type,
}
c.errors_json = h.json.dumps(errors)
self._setup_template_variables(context, {},
package_type=package_type)
new_template = self._new_template(package_type)
c.form = ckan.lib.render.deprecated_lazy_render(
new_template,
form_snippet,
lambda: render(form_snippet, extra_vars=form_vars),
'use of c.form is deprecated. please see '
'ckan/templates/package/base_form_page.html for an example '
'of the new way to include the form snippet'
)
#return "done"
return render(new_template,
extra_vars={'form_vars': form_vars,
'form_snippet': form_snippet,
'dataset_type': package_type})
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(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']
context = {'model': model, 'session': model.Session,
'api_version': 3, 'for_edit': True,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
data['package_id'] = id
try:
if resource_id:
data['id'] = resource_id
get_action('resource_update')(context, data)
else:
get_action('resource_create')(context, data)
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.resource_edit(id, resource_id, data,
errors, error_summary)
except NotAuthorized:
abort(401, _('Unauthorized to edit this resource'))
redirect(h.url_for(controller='package', action='resource_read',
id=id, resource_id=resource_id))
context = {'model': model, 'session': model.Session,
'api_version': 3, 'for_edit': True,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
pkg_dict = get_action('package_show')(context, {'id': id})
if pkg_dict['state'].startswith('draft'):
# dataset has not yet been fully created
resource_dict = get_action('resource_show')(context, {'id': resource_id})
fields = ['url', 'resource_type', 'format', 'name', 'description', 'id']
data = {}
for field in fields:
data[field] = resource_dict[field]
return self.new_resource(id, data=data)
# resource is fully created
try:
resource_dict = get_action('resource_show')(context, {'id': resource_id})
except NotFound:
abort(404, _('Resource not found'))
c.pkg_dict = pkg_dict
c.resource = resource_dict
# set the form action
c.form_action = h.url_for(controller='package',
action='resource_edit',
resource_id=resource_id,
id=id)
if not data:
data = resource_dict
package_type = pkg_dict['type'] or 'dataset'
errors = errors or {}
error_summary = error_summary or {}
vars = {'data': data, 'errors': errors,
'error_summary': error_summary, 'action': 'edit',
'resource_form_snippet': self._resource_form(package_type),
'dataset_type':package_type}
return render('package/resource_edit.html', extra_vars=vars)
def new_resource(self, id, data=None, errors=None, error_summary=None):
context = {'model': model, 'session': model.Session, 'user': c.user or c.author, 'auth_user_obj': c.userobj}
pkg_dict = get_action('package_show')(context, {'id': id})
if pkg_dict.get('type') == 'application':
redirect('/application/' + id)
elif pkg_dict.get('type') == 'article':
redirect('/article/' + id)
else:
return super(PackageController, self).new_resource(id, data, errors, error_summary)
def edit(self, id, data=None, errors=None, error_summary=None):
package_type = self._get_package_type(id)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj,
'save': 'save' in request.params}
if context['save'] and not data:
return self._save_edit(id, context, package_type=package_type)
try:
c.pkg_dict = get_action('package_show')(context, {'id': id})
context['for_edit'] = True
old_data = get_action('package_show')(context, {'id': id})
# old data is from the database and data is passed from the
# user if there is a validation error. Use users data if there.
if data:
old_data.update(data)
data = old_data
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % '')
except NotFound:
abort(404, _('Dataset not found'))
# are we doing a multiphase add?
if data.get('state', '').startswith('draft'):
c.form_action = h.url_for(controller='package', action='new')
c.form_style = 'new'
return self.new(data=data, errors=errors,
error_summary=error_summary)
c.pkg = context.get("package")
c.resources_json = h.json.dumps(data.get('resources', []))
try:
check_access('package_update', context)
except NotAuthorized, e:
abort(401, _('User %r not authorized to edit %s') % (c.user, id))
# convert tags if not supplied in data
if data and not data.get('tag_string'):
data['tag_string'] = ', '.join(h.dict_list_reduce(
c.pkg_dict.get('tags', {}), 'name'))
errors = errors or {}
form_snippet = self._package_form(package_type=package_type)
form_vars = {'data': data, 'errors': errors,
'error_summary': error_summary, 'action': 'edit',
'dataset_type': package_type,
}
c.errors_json = h.json.dumps(errors)
self._setup_template_variables(context, {'id': id},
package_type=package_type)
c.related_count = c.pkg.related_count
# we have already completed stage 1
form_vars['stage'] = ['active']
if data.get('state', '').startswith('draft'):
form_vars['stage'] = ['active', 'complete']
edit_template = self._edit_template(package_type)
c.form = ckan.lib.render.deprecated_lazy_render(
edit_template,
form_snippet,
lambda: render(form_snippet, extra_vars=form_vars),
'use of c.form is deprecated. please see '
'ckan/templates/package/edit.html for an example '
'of the new way to include the form snippet'
)
return render(edit_template,
extra_vars={'form_vars': form_vars,
'form_snippet': form_snippet,
'dataset_type': package_type})
def read_ajax(self, id, revision=None):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj,
'revision_id': revision}
try:
data = get_action('package_show')(context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % '')
except NotFound:
abort(404, _('Dataset not found'))
data.pop('tags')
data = flatten_to_string_key(data)
response.headers['Content-Type'] = 'application/json;charset=utf-8'
return h.json.dumps(data)
def history_ajax(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
pkg_revisions = get_action('package_revision_list')(
context, data_dict)
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % '')
except NotFound:
abort(404, _('Dataset not found'))
data = []
approved = False
for num, revision in enumerate(pkg_revisions):
if not approved and revision['approved_timestamp']:
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 h.json.dumps(data)
def _get_package_type(self, id):
"""
Given the id of a package this method will return the type of the
package, or 'dataset' if no type is currently set
"""
pkg = model.Package.get(id)
if pkg:
return pkg.type or 'dataset'
return None
def _tag_string_to_list(self, tag_string):
''' This is used to change tags from a sting to a list of dicts '''
out = []
for tag in tag_string.split(','):
tag = tag.strip()
if tag:
out.append({'name': tag,
'state': 'active'})
return out
def _save_new(self, context, package_type=None):
# The staged add dataset used the new functionality when the dataset is
# partially created so we need to know if we actually are updating or
# this is a real new.
is_an_update = False
ckan_phase = request.params.get('_ckan_phase')
from ckan.lib.search import SearchIndexError
try:
data_dict = clean_dict(dict_fns.unflatten(
tuplize_dict(parse_params(request.POST))))
if ckan_phase:
# prevent clearing of groups etc
context['allow_partial_update'] = True
# sort the tags
if 'tag_string' in data_dict:
data_dict['tags'] = self._tag_string_to_list(
data_dict['tag_string'])
if data_dict.get('pkg_name'):
is_an_update = True
# This is actually an update not a save
data_dict['id'] = data_dict['pkg_name']
del data_dict['pkg_name']
if request.params['save'] == 'finish':
data_dict['state'] = 'active'
# this is actually an edit not a save
pkg_dict = get_action('package_update')(context, data_dict)
# redirect to view
self._form_save_redirect(pkg_dict['name'], 'new', package_type=package_type)
# don't change the dataset state
data_dict['state'] = 'draft'
# this is actually an edit not a save
pkg_dict = get_action('package_update')(context, data_dict)
if request.params['save'] == 'go-metadata':
# redirect to add metadata
url = h.url_for(controller='package',
action='new_metadata',
id=pkg_dict['name'])
else:
# redirect to add dataset resources
url = h.url_for(controller='package',
action='new_resource',
id=pkg_dict['name'])
redirect(url)
# Make sure we don't index this dataset
if request.params['save'] not in ['go-resource', 'go-metadata', 'finish']:
data_dict['state'] = 'draft'
# allow the state to be changed
context['allow_state_change'] = True
data_dict['type'] = package_type
context['message'] = data_dict.get('log_message', '')
try:
if (package_type == 'application') and data_dict['from_users']:
context['ignore_auth'] = True
data_dict['name'] = 'from_users_' + ''.join(random.SystemRandom().choice(string.ascii_lowercase + string.digits) for _ in range(16))
data_dict['status'] = 'unverified';
data_dict['private'] = 'False';
except:
pass
pkg_dict = get_action('package_create')(context, data_dict)
if ckan_phase and not request.params['save'] == 'finish':
# redirect to add dataset resources
url = h.url_for(controller='package',
action='new_resource',
id=pkg_dict['name'])
redirect(url)
if (package_type == 'application') and data_dict['from_users']:
h.flash_notice(_('Application has been submitted'))
redirect('/application')
self._form_save_redirect(pkg_dict['name'], 'new', package_type=package_type)
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % '')
except NotFound, e:
abort(404, _('Dataset not found'))
except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except SearchIndexError, e:
try:
exc_str = unicode(repr(e.args))
except Exception: # We don't like bare excepts
exc_str = unicode(str(e))
abort(500, _(u'Unable to add package to search index.') + exc_str)
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
if is_an_update:
# we need to get the state of the dataset to show the stage we
# are on.
pkg_dict = get_action('package_show')(context, data_dict)
data_dict['state'] = pkg_dict['state']
return self.edit(data_dict['id'], data_dict,
errors, error_summary)
data_dict['state'] = 'none'
return self.new(data_dict, errors, error_summary)
def _save_edit(self, name_or_id, context, package_type=None):
from ckan.lib.search import SearchIndexError
log.debug('Package save request name: %s POST: %r',
name_or_id, request.POST)
try:
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
context['allow_partial_update'] = True
if 'tag_string' in data_dict:
data_dict['tags'] = self._tag_string_to_list(
data_dict['tag_string'])
del data_dict['_ckan_phase']
del data_dict['save']
context['message'] = data_dict.get('log_message', '')
data_dict['id'] = name_or_id
pkg = get_action('package_update')(context, data_dict)
c.pkg = context['package']
c.pkg_dict = pkg
self._form_save_redirect(pkg['name'], 'edit', package_type=package_type)
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % id)
except NotFound, e:
abort(404, _('Dataset not found'))
except dict_fns.DataError:
abort(400, _(u'Integrity Error'))
except SearchIndexError, e:
try:
exc_str = unicode(repr(e.args))
except Exception: # We don't like bare excepts
exc_str = unicode(str(e))
abort(500, _(u'Unable to update search index.') + exc_str)
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.edit(name_or_id, data_dict, errors, error_summary)
def _form_save_redirect(self, pkgname, action, package_type=None):
'''This redirects the user to the CKAN package/read page,
unless there is request parameter giving an alternate location,
perhaps an external website.
@param pkgname - Name of the package just edited
@param action - What the action of the edit was
'''
assert action in ('new', 'edit')
url = request.params.get('return_to') or \
config.get('package_%s_return_url' % action)
if url:
url = url.replace('<NAME>', pkgname)
else:
if package_type is None or package_type == 'dataset':
url = h.url_for(controller='package', action='read', id=pkgname)
else:
url = h.url_for('{0}_read'.format(package_type), id=pkgname)
redirect(url)
def delete(self, id):
if 'cancel' in request.params:
h.redirect_to(controller='package', action='edit', id=id)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
try:
check_access('package_delete', context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete package %s') % '')
try:
if request.method == 'POST':
get_action('package_delete')(context, {'id': id})
h.flash_notice(_('Dataset has been deleted.'))
h.redirect_to(controller='package', action='search')
c.pkg_dict = get_action('package_show')(context, {'id': id})
dataset_type = c.pkg_dict['type'] or 'dataset'
except NotAuthorized:
abort(401, _('Unauthorized to delete package %s') % '')
except NotFound:
abort(404, _('Dataset not found'))
return render('package/confirm_delete.html',
extra_vars={'dataset_type': dataset_type})
def resource_delete(self, id, resource_id):
if 'cancel' in request.params:
h.redirect_to(controller='package', action='resource_edit', resource_id=resource_id, id=id)
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
try:
check_access('package_delete', context, {'id': id})
except NotAuthorized:
abort(401, _('Unauthorized to delete package %s') % '')
try:
if request.method == 'POST':
get_action('resource_delete')(context, {'id': resource_id})
h.flash_notice(_('Resource has been deleted.'))
h.redirect_to(controller='package', action='read', id=id)
c.resource_dict = get_action('resource_show')(context, {'id': resource_id})
c.pkg_id = id
except NotAuthorized:
abort(401, _('Unauthorized to delete resource %s') % '')
except NotFound:
abort(404, _('Resource not found'))
return render('package/confirm_delete_resource.html',
{'dataset_type': self._get_package_type(id)})
def resource_read(self, id, resource_id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
try:
c.package = get_action('package_show')(context, {'id': id})
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read dataset %s') % id)
for resource in c.package.get('resources', []):
if resource['id'] == resource_id:
c.resource = resource
break
if not c.resource:
abort(404, _('Resource not found'))
# required for nav menu
c.pkg = context['package']
c.pkg_dict = c.package
dataset_type = c.pkg.type or 'dataset'
# get package license info
license_id = c.package.get('license_id')
try:
c.package['isopen'] = model.Package.\
get_license_register()[license_id].isopen()
except KeyError:
c.package['isopen'] = False
# TODO: find a nicer way of doing this
c.datastore_api = '%s/api/action' % config.get('ckan.site_url', '').rstrip('/')
c.related_count = c.pkg.related_count
c.resource['can_be_previewed'] = self._resource_preview(
{'resource': c.resource, 'package': c.package})
resource_views = get_action('resource_view_list')(
context, {'id': resource_id})
# filter out recline views if not in dataproxy
if not c.resource.get('datastore_active', False):
resource_views = [view for view in resource_views if not view['view_type'] == 'recline_view']
c.resource['has_views'] = len(resource_views) > 0
current_resource_view = None
view_id = request.GET.get('view_id')
if c.resource['can_be_previewed'] and not view_id:
current_resource_view = None
elif c.resource['has_views']:
if view_id:
current_resource_view = [rv for rv in resource_views
if rv['id'] == view_id]
if len(current_resource_view) == 1:
current_resource_view = current_resource_view[0]
else:
abort(404, _('Resource view not found'))
else:
current_resource_view = resource_views[0]
vars = {'resource_views': resource_views,
'current_resource_view': current_resource_view,
'dataset_type': dataset_type}
template = self._resource_template(dataset_type)
return render(template, extra_vars=vars)
@maintain.deprecated('Resource preview is deprecated. Please use the new '
'resource views')
def _resource_preview(self, data_dict):
'''Deprecated in 2.3, we don't use it functions so get rid of it'''
return False
def _resource_tag_string_to_list(self, tag_string):
''' This is used to change tags from a sting to a list of dicts '''
out = []
for tag in tag_string.split(','):
tag = tag.strip()
if tag:
out.append({'name': tag,
'vocabulary_id': 'resource_tags',
'state': 'active'})
return out
def resource_download(self, id, resource_id, filename=None):
"""
Provides a direct download by either redirecting the user to the url stored
or downloading an uploaded file directly.
"""
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
try:
rsc = get_action('resource_show')(context, {'id': resource_id})
pkg = get_action('package_show')(context, {'id': id})
except NotFound:
abort(404, _('Resource not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read resource %s') % id)
if rsc.get('url_type') == 'upload':
upload = uploader.ResourceUpload(rsc)
filepath = upload.get_path(rsc['id'])
fileapp = paste.fileapp.FileApp(filepath)
try:
status, headers, app_iter = request.call_application(fileapp)
except OSError:
abort(404, _('Resource data not found'))
response.headers.update(dict(headers))
content_type, content_enc = mimetypes.guess_type(rsc.get('url',''))
if content_type:
response.headers['Content-Type'] = content_type
response.status = status
return app_iter
elif not 'url' in rsc:
abort(404, _('No download is available'))
redirect(rsc['url'])
def follow(self, id):
'''Start following this dataset.'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
get_action('follow_dataset')(context, data_dict)
package_dict = get_action('package_show')(context, data_dict)
h.flash_success(_("You are now following {0}").format(
package_dict['title']))
except ValidationError as e:
error_message = (e.message or e.error_summary
or e.error_dict)
h.flash_error(error_message)
except NotAuthorized as e:
h.flash_error(e.message)
h.redirect_to(controller='package', action='read', id=id)
def unfollow(self, id):
'''Stop following this dataset.'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
get_action('unfollow_dataset')(context, data_dict)
package_dict = get_action('package_show')(context, data_dict)
h.flash_success(_("You are no longer following {0}").format(
package_dict['title']))
except ValidationError as e:
error_message = (e.message or e.error_summary
or e.error_dict)
h.flash_error(error_message)
except (NotFound, NotAuthorized) as e:
error_message = e.message
h.flash_error(error_message)
h.redirect_to(controller='package', action='read', id=id)
def followers(self, id=None):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
c.pkg = context['package']
c.followers = get_action('dataset_follower_list')(context,
{'id': c.pkg_dict['id']})
c.related_count = c.pkg.related_count
dataset_type = c.pkg.type or 'dataset'
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read package %s') % id)
return render('package/followers.html',
{'dataset_type': dataset_type})
def groups(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj, 'use_cache': False}
data_dict = {'id': id}
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
dataset_type = c.pkg_dict['type'] or 'dataset'
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read dataset %s') % id)
if request.method == 'POST':
new_group = request.POST.get('group_added')
if new_group:
data_dict = {"id": new_group,
"object": id,
"object_type": 'package',
"capacity": 'public'}
try:
get_action('member_create')(context, data_dict)
except NotFound:
abort(404, _('Group not found'))
removed_group = None
for param in request.POST:
if param.startswith('group_remove'):
removed_group = param.split('.')[-1]
break
if removed_group:
data_dict = {"id": removed_group,
"object": id,
"object_type": 'package'}
try:
get_action('member_delete')(context, data_dict)
except NotFound:
abort(404, _('Group not found'))
redirect(h.url_for(controller='package',
action='groups', id=id))
context['is_member'] = True
users_groups = get_action('group_list_authz')(context, data_dict)
pkg_group_ids = set(group['id'] for group
in c.pkg_dict.get('groups', []))
user_group_ids = set(group['id'] for group
in users_groups)
c.group_dropdown = [[group['id'], group['display_name']]
for group in users_groups if
group['id'] not in pkg_group_ids]
for group in c.pkg_dict.get('groups', []):
group['user_member'] = (group['id'] in user_group_ids)
return render('package/group_list.html',
{'dataset_type': dataset_type})
def activity(self, id):
'''Render this package's public activity stream page.'''
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
c.pkg = context['package']
c.package_activity_stream = get_action(
'package_activity_list_html')(context,
{'id': c.pkg_dict['id']})
c.related_count = c.pkg.related_count
dataset_type = c.pkg_dict['type'] or 'dataset'
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read dataset %s') % id)
return render('package/activity.html',
{'dataset_type': dataset_type})
def resource_embedded_dataviewer(self, id, resource_id,
width=500, height=500):
"""
Embedded page for a read-only resource dataview. Allows
for width and height to be specified as part of the
querystring (as well as accepting them via routes).
"""
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj}
try:
c.resource = get_action('resource_show')(context,
{'id': resource_id})
c.package = get_action('package_show')(context, {'id': id})
c.resource_json = h.json.dumps(c.resource)
# double check that the resource belongs to the specified package
if not c.resource['id'] in [r['id']
for r in c.package['resources']]:
raise NotFound
dataset_type = c.package['type'] or 'dataset'
except NotFound:
abort(404, _('Resource not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read resource %s') % id)
# Construct the recline state
state_version = int(request.params.get('state_version', '1'))
recline_state = self._parse_recline_state(request.params)
if recline_state is None:
abort(400, ('"state" parameter must be a valid recline '
'state (version %d)' % state_version))
c.recline_state = h.json.dumps(recline_state)
c.width = max(int(request.params.get('width', width)), 100)
c.height = max(int(request.params.get('height', height)), 100)
c.embedded = True
return render('package/resource_embedded_dataviewer.html',
extra_vars={'dataset_type': dataset_type})
def _parse_recline_state(self, params):
state_version = int(request.params.get('state_version', '1'))
if state_version != 1:
return None
recline_state = {}
for k, v in request.params.items():
try:
v = h.json.loads(v)
except ValueError:
pass
recline_state[k] = v
recline_state.pop('width', None)
recline_state.pop('height', None)
recline_state['readOnly'] = True
# previous versions of recline setup used elasticsearch_url attribute
# for data api url - see http://trac.ckan.org/ticket/2639
# fix by relocating this to url attribute which is the default location
if 'dataset' in recline_state and 'elasticsearch_url' in recline_state['dataset']:
recline_state['dataset']['url'] = recline_state['dataset']['elasticsearch_url']
# Ensure only the currentView is available
# default to grid view if none specified
if not recline_state.get('currentView', None):
recline_state['currentView'] = 'grid'
for k in recline_state.keys():
if k.startswith('view-') and \
not k.endswith(recline_state['currentView']):
recline_state.pop(k)
return recline_state
def resource_views(self, id, resource_id):
package_type = self._get_package_type(id.split('@')[0])
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
check_access('package_update', context, data_dict)
except NotAuthorized:
abort(401, _('User %r not authorized to edit %s') % (c.user, id))
# check if package exists
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
c.pkg = context['package']
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read dataset %s') % id)
try:
c.resource = get_action('resource_show')(context,
{'id': resource_id})
c.views = get_action('resource_view_list')(context,
{'id': resource_id})
except NotFound:
abort(404, _('Resource not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read resource %s') % id)
self._setup_template_variables(context, {'id': id},
package_type=package_type)
return render('package/resource_views.html')
def edit_view(self, id, resource_id, view_id=None):
package_type = self._get_package_type(id.split('@')[0])
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj}
# update resource should tell us early if the user has privilages.
try:
check_access('resource_update', context, {'id': resource_id})
except NotAuthorized, e:
abort(401, _('User %r not authorized to edit %s') % (c.user, id))
# get resource and package data
try:
c.pkg_dict = get_action('package_show')(context, {'id': id})
c.pkg = context['package']
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read dataset %s') % id)
try:
c.resource = get_action('resource_show')(context,
{'id': resource_id})
except NotFound:
abort(404, _('Resource not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read resource %s') % id)
data = {}
errors = {}
error_summary = {}
view_type = None
to_preview = False
if request.method == 'POST':
request.POST.pop('save', None)
to_preview = request.POST.pop('preview', False)
if to_preview:
context['preview'] = True
to_delete = request.POST.pop('delete', None)
data = clean_dict(dict_fns.unflatten(tuplize_dict(parse_params(
request.params, ignore_keys=CACHE_PARAMETERS))))
data['resource_id'] = resource_id
try:
if to_delete:
data['id'] = view_id
get_action('resource_view_delete')(context, data)
elif view_id:
data['id'] = view_id
data = get_action('resource_view_update')(context, data)
else:
data = get_action('resource_view_create')(context, data)
except ValidationError, e:
## Could break preview if validation error
to_preview = False
errors = e.error_dict
error_summary = e.error_summary
except NotAuthorized:
## This should never happen unless the user maliciously changed
## the resource_id in the url.
abort(401, _('Unauthorized to edit resource'))
else:
if not to_preview:
redirect(h.url_for(controller='package',
action='resource_views',
id=id, resource_id=resource_id))
## view_id exists only when updating
if view_id:
try:
old_data = get_action('resource_view_show')(context,
{'id': view_id})
data = data or old_data
view_type = old_data.get('view_type')
## might as well preview when loading good existing view
if not errors:
to_preview = True
except NotFound:
abort(404, _('View not found'))
except NotAuthorized:
abort(401, _('Unauthorized to view View %s') % view_id)
view_type = view_type or request.GET.get('view_type')
data['view_type'] = view_type
view_plugin = datapreview.get_view_plugin(view_type)
if not view_plugin:
abort(404, _('View Type Not found'))
self._setup_template_variables(context, {'id': id},
package_type=package_type)
data_dict = {'package': c.pkg_dict, 'resource': c.resource,
'resource_view': data}
view_template = view_plugin.view_template(context, data_dict)
form_template = view_plugin.form_template(context, data_dict)
vars = {'form_template': form_template,
'view_template': view_template,
'data': data,
'errors': errors,
'error_summary': error_summary,
'to_preview': to_preview,
'datastore_available': p.plugin_loaded('datastore')}
vars.update(
view_plugin.setup_template_variables(context, data_dict) or {})
vars.update(data_dict)
if view_id:
return render('package/edit_view.html', extra_vars=vars)
return render('package/new_view.html', extra_vars=vars)
def resource_view(self, id, resource_id, view_id=None):
'''
Embedded page for a resource view.
Depending on the type, different views are loaded. This could be an
img tag where the image is loaded directly or an iframe that embeds a
webpage or a recline preview.
'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author,
'auth_user_obj': c.userobj}
try:
package = get_action('package_show')(context, {'id': id})
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read dataset %s') % id)
try:
resource = get_action('resource_show')(
context, {'id': resource_id})
except NotFound:
abort(404, _('Resource not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read resource %s') % resource_id)
view = None
if request.params.get('resource_view', ''):
try:
view = json.loads(request.params.get('resource_view', ''))
except ValueError:
abort(409, _('Bad resource view data'))
elif view_id:
try:
view = get_action('resource_view_show')(
context, {'id': view_id})
except NotFound:
abort(404, _('Resource view not found'))
except NotAuthorized:
abort(401,
_('Unauthorized to read resource view %s') % view_id)
if not view or not isinstance(view, dict):
abort(404, _('Resource view not supplied'))
return h.rendered_resource_view(view, resource, package, embed=True)
def resource_datapreview(self, id, resource_id):
'''
Embedded page for a resource data-preview.
Depending on the type, different previews are loaded. This could be an
img tag where the image is loaded directly or an iframe that embeds a
webpage, or a recline preview.
'''
context = {
'model': model,
'session': model.Session,
'user': c.user or c.author,
'auth_user_obj': c.userobj
}
try:
c.resource = get_action('resource_show')(context,
{'id': resource_id})
c.package = get_action('package_show')(context, {'id': id})
data_dict = {'resource': c.resource, 'package': c.package}
preview_plugin = datapreview.get_preview_plugin(data_dict)
if preview_plugin is None:
abort(409, _('No preview has been defined.'))
preview_plugin.setup_template_variables(context, data_dict)
c.resource_json = json.dumps(c.resource)
dataset_type = c.package['type'] or 'dataset'
except NotFound:
abort(404, _('Resource not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read resource %s') % id)
else:
return render(preview_plugin.preview_template(context, data_dict),
extra_vars={'dataset_type': dataset_type})
import logging
from urllib import quote
from pylons import config
import ckan.lib.base as base
import ckan.model as model
import ckan.lib.helpers as h
import ckan.new_authz as new_authz
import ckan.logic as logic
import ckan.logic.schema as schema
import ckan.lib.captcha as captcha
import ckan.lib.mailer as mailer
import ckan.lib.navl.dictization_functions as dictization_functions
import ckan.plugins as p
from ckan.common import _, c, g, request, response
log = logging.getLogger(__name__)
abort = base.abort
render = base.render
check_access = logic.check_access
get_action = logic.get_action
NotFound = logic.NotFound
NotAuthorized = logic.NotAuthorized
ValidationError = logic.ValidationError
DataError = dictization_functions.DataError
unflatten = dictization_functions.unflatten
class UserController(base.BaseController):
def __before__(self, action, **env):
base.BaseController.__before__(self, action, **env)
try:
context = {'model': model, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
check_access('site_read', context)
except NotAuthorized:
if c.action not in ('login', 'request_reset', 'perform_reset',):
abort(401, _('Not authorized to see this page'))
## hooks for subclasses
new_user_form = 'user/new_user_form.html'
edit_user_form = 'user/edit_user_form.html'
def _unique_email_user_schema(self, schema):
schema.update({
'email': schema['email'] + [email_unique_validator]
})
return schema
def _new_form_to_db_schema(self):
return self._unique_email_user_schema(schema.user_new_form_schema())
def _db_to_new_form_schema(self):
'''This is an interface to manipulate data from the database
into a format suitable for the form (optional)'''
def _edit_form_to_db_schema(self):
return self._unique_email_user_schema(schema.user_edit_form_schema())
def _db_to_edit_form_schema(self):
schema = self._unique_email_user_schema(logic.schema.default_user_schema())
from_json = convert_from_json('about')
ignore_missing = tk.get_validator('ignore_missing')
schema.update({
'official_position': [from_json, ignore_missing],
'official_phone': [from_json, ignore_missing],
'about': []
})
return schema
def _setup_template_variables(self, context, data_dict):
super(UserController, self)._setup_template_variables(context, data_dict)
about = c.user_dict['about']
if about:
of = json.loads(about)
c.user_dict.update(of)
## end hooks
def _get_repoze_handler(self, handler_name):
'''Returns the URL that repoze.who will respond to and perform a
login or logout.'''
return getattr(request.environ['repoze.who.plugins']['friendlyform'],
handler_name)
def index(self):
LIMIT = 20
page = self._get_page_number(request.params)
c.q = request.params.get('q', '')
c.order_by = request.params.get('order_by', 'name')
context = {'return_query': True, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
data_dict = {'q': c.q,
'order_by': c.order_by}
try:
check_access('user_list', context, data_dict)
except NotAuthorized:
abort(401, _('Not authorized to see this page'))
users_list = get_action('user_list')(context, data_dict)
c.page = h.Page(
collection=users_list,
page=page,
url=h.pager_url,
item_count=users_list.count(),
items_per_page=LIMIT
)
return render('user/list.html')
def read(self, id=None):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj,
'for_view': True}
data_dict = {'id': id,
'user_obj': c.userobj,
'include_datasets': True,
'include_num_followers': True}
context['with_related'] = True
self._setup_template_variables(context, data_dict)
# The legacy templates have the user's activity stream on the user
# profile page, new templates do not.
if h.asbool(config.get('ckan.legacy_templates', False)):
c.user_activity_stream = get_action('user_activity_list_html')(
context, {'id': c.user_dict['id']})
return render('user/dashboard_account.html', extra_vars={'userd': c.user_dict})
def me(self, locale=None):
if not c.user:
h.redirect_to(locale=locale, controller='user', action='login',
id=None)
user_ref = c.userobj.get_reference_preferred_for_uri()
h.redirect_to(locale=locale, controller='user', action='dashboard',
id=user_ref)
def register(self, data=None, errors=None, error_summary=None):
context = {'model': model, 'session': model.Session, 'user': c.user,
'auth_user_obj': c.userobj}
try:
check_access('user_create', context)
except NotAuthorized:
abort(401, _('Unauthorized to register as a user.'))
return self.new(data, errors, error_summary)
def new(self, data=None, errors=None, error_summary=None):
'''GET to display a form for registering a new user.
or POST the form data to actually do the user registration.
'''
context = {'model': model, 'session': model.Session,
'user': c.user or c.author,
'auth_user_obj': c.userobj,
'schema': self._new_form_to_db_schema(),
'save': 'save' in request.params}
try:
check_access('user_create', context)
except NotAuthorized:
abort(401, _('Unauthorized to create a user'))
if context['save'] and not data:
return self._save_new(context)
if c.user and not data:
# #1799 Don't offer the registration form if already logged in
return render('user/logout_first.html')
data = data or {}
errors = errors or {}
error_summary = error_summary or {}
vars = {'data': data, 'errors': errors, 'error_summary': error_summary}
c.is_sysadmin = new_authz.is_sysadmin(c.user)
c.form = render(self.new_user_form, extra_vars=vars)
return render('user/new.html')
def delete(self, id):
'''Delete user with id passed as parameter'''
context = {'model': model,
'session': model.Session,
'user': c.user,
'auth_user_obj': c.userobj}
data_dict = {'id': id}
try:
get_action('user_delete')(context, data_dict)
user_index = h.url_for(controller='user', action='index')
h.redirect_to(user_index)
except NotAuthorized:
msg = _('Unauthorized to delete user with id "{user_id}".')
abort(401, msg.format(user_id=id))
def generate_apikey(self, id):
'''Cycle the API key of a user'''
context = {'model': model,
'session': model.Session,
'user': c.user,
'auth_user_obj': c.userobj,
}
if id is None:
if c.userobj:
id = c.userobj.id
else:
abort(400, _('No user specified'))
data_dict = {'id': id}
try:
result = get_action('user_generate_apikey')(context, data_dict)
except NotAuthorized:
abort(401, _('Unauthorized to edit user %s') % '')
except NotFound:
abort(404, _('User not found'))
h.flash_success(_('Profile updated'))
h.redirect_to(controller='user', action='read', id=result['name'])
def _save_new(self, context):
try:
data_dict = logic.clean_dict(df.unflatten(
logic.tuplize_dict(logic.parse_params(request.params))))
context['message'] = data_dict.get('log_message', '')
captcha.check_recaptcha(request)
# Extra: Create username from email
email = data_dict.get('email', '').lower()
email_user = email.split('@')[0]
name = re.sub('[^a-z0-9_\-]', '_', email_user)
# Append num so it becames unique (search inside deleted as well)
session = context['session']
user_names = model.User.search(name, session.query(model.User)).all()
user_names = map(lambda u: u.name, user_names)
while name in user_names:
m = re.match('^(.*?)(\d+)$', name)
if m:
name = m.group(1) + str(int(m.group(2)) + 1)
else:
name = name + '2'
data_dict['name'] = name
user = get_action('user_create')(context, data_dict)
except NotAuthorized:
abort(401, _('Unauthorized to create user %s') % '')
except NotFound, e:
abort(404, _('User not found'))
except df.DataError:
abort(400, _(u'Integrity Error'))
except captcha.CaptchaError:
error_msg = _(u'Bad Captcha. Please try again.')
h.flash_error(error_msg)
return self.new(data_dict)
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.new(data_dict, errors, error_summary)
if not c.user:
# log the user in programatically
rememberer = request.environ['repoze.who.plugins']['friendlyform']
identity = {'repoze.who.userid': data_dict['name']}
response.headerlist += rememberer.remember(request.environ,
identity)
h.redirect_to(controller='user', action='me', __ckan_no_root=True)
else:
# #1799 User has managed to register whilst logged in - warn user
# they are not re-logged in as new user.
h.flash_success(_('User "%s" is now registered but you are still '
'logged in as "%s" from before') %
(data_dict['name'], c.user))
return render('user/logout_first.html')
def edit(self, id=None, data=None, errors=None, error_summary=None):
context = {'save': 'save' in request.params,
'schema': self._edit_form_to_db_schema(),
'model': model, 'session': model.Session,
'user': c.user, 'auth_user_obj': c.userobj
}
if id is None:
if c.userobj:
id = c.userobj.id
else:
abort(400, _('No user specified'))
data_dict = {'id': id}
try:
check_access('user_update', context, data_dict)
except NotAuthorized:
abort(401, _('Unauthorized to edit a user.'))
# Custom handling if user in organization
action_ctx = context.copy()
action_ctx['user'] = id
c.in_organization = bool(logic.get_action('organization_list_for_user')(action_ctx, {'permission': 'create_dataset'}))
to_json = convert_to_json('about')
not_empty = tk.get_validator('not_empty')
if c.in_organization:
context['schema'].update({
'fullname': [not_empty, unicode],
'official_position': [not_empty, to_json],
'official_phone': [not_empty, to_json]
})
# End of custom handling
if (context['save']) and not data:
return self._save_edit(id, context)
try:
if not data:
data = get_action('user_show')(context, data_dict)
schema = self._db_to_edit_form_schema()
if schema:
data, errors = df.validate(data, schema, context)
c.display_name = data.get('display_name')
c.user_name = data.get('name')
except NotAuthorized:
abort(401, _('Unauthorized to edit user %s') % '')
except NotFound:
abort(404, _('User not found'))
user_obj = context.get('user_obj')
if not (new_authz.is_sysadmin(c.user)
or c.user == user_obj.name):
abort(401, _('User %s not authorized to edit %s') %
(str(c.user), id))
errors = errors or {}
vars = {'data': data, 'errors': errors, 'error_summary': error_summary}
self._setup_template_variables({'model': model,
'session': model.Session,
'user': c.user or c.author},
data_dict)
c.is_myself = True
c.show_email_notifications = h.asbool(
config.get('ckan.activity_streams_email_notifications'))
c.form = render(self.edit_user_form, extra_vars=vars)
return render('user/edit.html')
def _save_edit(self, id, context):
try:
data_dict = logic.clean_dict(df.unflatten(
logic.tuplize_dict(logic.parse_params(request.params))))
context['message'] = data_dict.get('log_message', '')
data_dict['id'] = id
# MOAN: Do I really have to do this here?
if 'activity_streams_email_notifications' not in data_dict:
data_dict['activity_streams_email_notifications'] = False
user = get_action('user_update')(context, data_dict)
h.flash_success(_('Profile updated'))
h.redirect_to('user_datasets', id=user['name'])
except NotAuthorized:
abort(401, _('Unauthorized to edit user %s') % id)
except NotFound, e:
abort(404, _('User not found'))
except df.DataError:
abort(400, _(u'Integrity Error'))
except ValidationError, e:
errors = e.error_dict
error_summary = e.error_summary
return self.edit(id, data_dict, errors, error_summary)
def login(self, error=None):
# Do any plugin login stuff
for item in p.PluginImplementations(p.IAuthenticator):
item.login()
if 'error' in request.params:
h.flash_error(request.params['error'])
if not c.user:
came_from = request.params.get('came_from')
if not came_from:
came_from = h.url_for(controller='user', action='logged_in',
__ckan_no_root=True)
c.login_handler = h.url_for(
self._get_repoze_handler('login_handler_path'),
came_from=came_from)
if error:
vars = {'error_summary': {'': error}}
else:
vars = {}
return render('user/login.html', extra_vars=vars)
else:
return render('user/logout_first.html')
def logged_in(self):
# redirect if needed
came_from = request.params.get('came_from', '')
if c.user:
context = None
data_dict = {'id': c.user}
user_dict = get_action('user_show')(context, data_dict)
user_ref = c.userobj.get_reference_preferred_for_uri()
if h.url_is_local(came_from) and came_from != '/':
return h.redirect_to(str(came_from))
h.redirect_to(locale=None, controller='user', action='dashboard_datasets',
id=user_ref)
else:
err = _('Login failed. Wrong email or password.')
if h.asbool(config.get('ckan.legacy_templates', 'false')):
h.flash_error(err)
h.redirect_to(controller='user',
action='login', came_from=came_from)
else:
return self.login(error=err)
def logout(self):
# Do any plugin logout stuff
for item in p.PluginImplementations(p.IAuthenticator):
item.logout()
url = h.url_for(controller='user', action='logged_out_page',
__ckan_no_root=True)
h.redirect_to(self._get_repoze_handler('logout_handler_path') +
'?came_from=' + url)
def logged_out(self):
# came_from = request.params.get('came_from', '')
# if h.url_is_local(came_from):
# return h.redirect_to(str(came_from))
h.redirect_to('/')
def logged_out_page(self):
return render('user/logout.html')
def request_reset(self):
context = {'model': model, 'session': model.Session, 'user': c.user,
'auth_user_obj': c.userobj}
try:
check_access('request_reset', context)
except NotAuthorized:
abort(401, _('Unauthorized to request reset password.'))
error = None
if request.method == 'POST':
email = request.params.get('email').lower()
users = model.Session.query(model.User).filter_by(email=email, state='active').all()
if not users:
error = _('Email not registered: %s') % email
else:
try:
mailer.send_reset_link(users[0])
h.flash_success(_('Please check your inbox for '
'a reset code.'))
h.redirect_to('/')
except mailer.MailerException, e:
h.flash_error(_('Could not send reset link: %s') %
unicode(e))
return render('user/request_reset.html', extra_vars={'error': error})
def perform_reset(self, username):
# FIXME 403 error for invalid key is a non helpful page
context = {'model': model, 'session': model.Session,
'user': username,
'keep_email': True}
try:
check_access('user_reset', context)
except NotAuthorized:
abort(401, _('Unauthorized to reset password.'))
try:
data_dict = {'id': username}
user_dict = get_action('user_show')(context, data_dict)
user_obj = context['user_obj']
except NotFound, e:
abort(404, _('User not found'))
c.reset_key = request.params.get('key')
if not mailer.verify_reset_link(user_obj, c.reset_key):
h.flash_error(_('Invalid reset key. Please try again.'))
abort(403)
if request.method == 'POST':
try:
context['reset_password'] = True
new_password = self._get_form_password()
user_dict['password'] = new_password
user_dict['reset_key'] = c.reset_key
user_dict['state'] = model.State.ACTIVE
user = get_action('user_update')(context, user_dict)
mailer.create_reset_key(user_obj)
h.flash_success(_("Your password has been reset."))
h.redirect_to('/')
except NotAuthorized:
h.flash_error(_('Unauthorized to edit user %s') % username)
except NotFound, e:
h.flash_error(_('User not found'))
except DataError:
h.flash_error(_(u'Integrity Error'))
except ValidationError, e:
h.flash_error(u'%r' % e.error_dict)
except ValueError, ve:
h.flash_error(unicode(ve))
c.user_dict = user_dict
return render('user/perform_reset.html')
def _get_form_password(self):
password1 = request.params.getone('password1')
password2 = request.params.getone('password2')
if (password1 is not None and password1 != ''):
if not len(password1) >= 4:
raise ValueError(_('Your password must be 4 '
'characters or longer.'))
elif not password1 == password2:
raise ValueError(_('The passwords you entered'
' do not match.'))
return password1
raise ValueError(_('You must provide a password'))
def followers(self, id=None):
context = {'for_view': True, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
data_dict = {'id': id, 'user_obj': c.userobj}
self._setup_template_variables(context, data_dict)
f = get_action('user_follower_list')
try:
c.followers = f(context, {'id': c.user_dict['id']})
except NotAuthorized:
abort(401, _('Unauthorized to view followers %s') % '')
return render('user/followers.html')
def activity(self, id, offset=0):
'''Render this user's public activity stream page.'''
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj,
'for_view': True}
data_dict = {'id': id, 'user_obj': c.userobj,
'include_num_followers': True}
try:
check_access('user_show', context, data_dict)
except NotAuthorized:
abort(401, _('Not authorized to see this page'))
self._setup_template_variables(context, data_dict)
c.user_activity_stream = get_action('user_activity_list_html')(
context, {'id': c.user_dict['id'], 'offset': offset})
return render('user/activity_stream.html')
def _get_dashboard_context(self, filter_type=None, filter_id=None, q=None):
'''Return a dict needed by the dashboard view to determine context.'''
def display_name(followee):
'''Return a display name for a user, group or dataset dict.'''
display_name = followee.get('display_name')
fullname = followee.get('fullname')
title = followee.get('title')
name = followee.get('name')
return display_name or fullname or title or name
if (filter_type and filter_id):
context = {
'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj,
'for_view': True
}
data_dict = {'id': filter_id, 'include_num_followers': True}
followee = None
action_functions = {
'dataset': 'package_show',
'user': 'user_show',
'group': 'group_show',
'organization': 'organization_show',
}
action_function = logic.get_action(
action_functions.get(filter_type))
# Is this a valid type?
if action_function is None:
abort(404, _('Follow item not found'))
try:
followee = action_function(context, data_dict)
except NotFound:
abort(404, _('{0} not found').format(filter_type))
except NotAuthorized:
abort(401, _('Unauthorized to read {0} {1}').format(
filter_type, id))
if followee is not None:
return {
'filter_type': filter_type,
'q': q,
'context': display_name(followee),
'selected_id': followee.get('id'),
'dict': followee,
}
return {
'filter_type': filter_type,
'q': q,
'context': _('Everything'),
'selected_id': False,
'dict': None,
}
def dashboard(self, id=None, offset=0):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'auth_user_obj': c.userobj,
'for_view': True}
data_dict = {'id': id, 'user_obj': c.userobj, 'offset': offset}
self._setup_template_variables(context, data_dict)
q = request.params.get('q', u'')
filter_type = request.params.get('type', u'')
filter_id = request.params.get('name', u'')
c.followee_list = get_action('followee_list')(
context, {'id': c.userobj.id, 'q': q})
c.dashboard_activity_stream_context = self._get_dashboard_context(
filter_type, filter_id, q)
c.dashboard_activity_stream = h.dashboard_activity_stream(
c.userobj.id, filter_type, filter_id, offset
)
# Mark the user's new activities as old whenever they view their
# dashboard page.
get_action('dashboard_mark_activities_old')(context, {})
return render('user/dashboard.html')
def dashboard_datasets(self):
context = {'for_view': True, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
data_dict = {'user_obj': c.userobj, 'include_datasets': True}
self._setup_template_variables(context, data_dict)
return render('user/dashboard_datasets.html')
def dashboard_organizations(self):
context = {'for_view': True, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
data_dict = {'user_obj': c.userobj}
self._setup_template_variables(context, data_dict)
return render('user/dashboard_organizations.html')
def dashboard_groups(self):
context = {'for_view': True, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
data_dict = {'user_obj': c.userobj}
self._setup_template_variables(context, data_dict)
return render('user/dashboard_groups.html')
def follow(self, id):
'''Start following this user.'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author,
'auth_user_obj': c.userobj}
data_dict = {'id': id, 'include_num_followers': True}
try:
get_action('follow_user')(context, data_dict)
user_dict = get_action('user_show')(context, data_dict)
h.flash_success(_("You are now following {0}").format(
user_dict['display_name']))
except ValidationError as e:
error_message = (e.message or e.error_summary
or e.error_dict)
h.flash_error(error_message)
except NotAuthorized as e:
h.flash_error(e.message)
h.redirect_to(controller='user', action='read', id=id)
def unfollow(self, id):
'''Stop following this user.'''
context = {'model': model,
'session': model.Session,
'user': c.user or c.author,
'auth_user_obj': c.userobj}
data_dict = {'id': id, 'include_num_followers': True}
try:
get_action('unfollow_user')(context, data_dict)
user_dict = get_action('user_show')(context, data_dict)
h.flash_success(_("You are no longer following {0}").format(
user_dict['display_name']))
except (NotFound, NotAuthorized) as e:
error_message = e.message
h.flash_error(error_message)
except ValidationError as e:
error_message = (e.error_summary or e.message
or e.error_dict)
h.flash_error(error_message)
h.redirect_to(controller='user', action='read', id=id)
def dashboard_search_history(self):
context = {'for_view': True, 'user': c.user or c.author,
'auth_user_obj': c.userobj}
data_dict = {'user_obj': c.userobj}
self._setup_template_variables(context, data_dict)
search_history = get_action('search_history_list')(context, {})
c.search_history = self._search_history_for_display(search_history)
return render('user/dashboard_search_history.html')
def _search_history_for_display(self, search_history):
for item in search_history:
facets = item['params']['facet.field']
q = item['params']['q']
fq = item['params']['fq']
facets_set = {}
for m in re.finditer('\\+?(?P<facet>\\w+)\:(?P<value>(?P<quote>[\'"])(.*?)(?P=quote)|\\w+)', fq[0]):
facet = m.group('facet')
value = m.group('value').strip("\"'")
if facet in facets:
facets_set[facet] = value
url_params = facets_set.copy()
if q:
url_params.update({'q': q})
item['display'] = {
'q': q,
'url': h.url_for('search', **url_params),
'facets': facets_set
}
return search_history
def convert_to_json(field):
def f(key, data, errors, context):
j = data.get((field,), {})
if j:
j = json.loads(j)
j[key[0]] = data.pop(key)
data[(field,)] = json.dumps(j)
return f
def convert_from_json(field):
def f(key, data, errors, context):
j = data.get((field,), {})
if j:
j = json.loads(j)
if key[0] in j:
data[key] = j[key[0]]
return f
def email_unique_validator(key, data, errors, context):
'''Validates a new email
Append an error message to ``errors[key]`` if a user with email ``data[key]``
already exists. Otherwise, do nothing.
:raises ckan.lib.navl.dictization_functions.Invalid: if ``data[key]`` is
not a string
:rtype: None
'''
model = context['model']
new_email = data[key].lower()
data[key] = new_email
# if not isinstance(new_email, basestring):
# raise df.Invalid(_('User names must be strings'))
session = context['session']
user = session.query(model.User).filter_by(email=new_email, state='active').first()
if user:
# A user with new_email already exists in the database.
user_obj_from_context = context.get('user_obj')
if user_obj_from_context and user_obj_from_context.id == user.id:
# If there's a user_obj in context with the same id as the user
# found in the db, then we must be doing a user_update and not
# updating the user name, so don't return an error.
return
else:
# Otherwise return an error: there's already another user with that
# name, so you can create a new user with that name or update an
# existing user's name to that name.
errors[key].append(_('That email is not available.'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment