def read(self, id, format='html'): if not format == 'html': ctype, extension, loader = \ 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, loader = "text/html; charset=utf-8", "html", \ MarkupTemplate else: ctype, format, loader = self._content_type_from_accept() response.headers['Content-Type'] = ctype package_type = self._get_package_type(id.split('@')[0]) context = {'model': model, 'session': model.Session, 'user': c.user or c.author, '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)
def resource_update(context, data_dict): model = context['model'] user = context.get('user') resource = logic_auth.get_resource_object(context, data_dict) # check authentication against package query = model.Session.query(model.Package)\ .join(model.ResourceGroup)\ .join(model.Resource)\ .filter(model.ResourceGroup.id == resource.resource_group_id) pkg = query.first() if not pkg: raise logic.NotFound( _('No package found for this resource, cannot check auth.') ) pkg_dict = {'id': pkg.id} authorized = new_authz.is_authorized('package_update', context, pkg_dict).get('success') if not authorized: return {'success': False, 'msg': _('User %s not authorized to edit resource %s') % (str(user), resource.id)} else: return {'success': True}
def package_update(context, data_dict): user = context.get('user') package = logic_auth.get_package_object(context, data_dict) if package.owner_org: # if there is an owner org then we must have update_dataset # premission for that organization check1 = new_authz.has_user_permission_for_group_or_org( package.owner_org, user, 'update_dataset' ) else: # If dataset is not owned then we can edit if config permissions allow if not new_authz.auth_is_anon_user(context): check1 = new_authz.check_config_permission( 'create_dataset_if_not_in_organization') else: check1 = new_authz.check_config_permission('anon_create_dataset') if not check1: return {'success': False, 'msg': _('User %s not authorized to edit package %s') % (str(user), package.id)} else: check2 = _check_group_auth(context, data_dict) if not check2: return {'success': False, 'msg': _('User %s not authorized to edit these groups') % (str(user))} return {'success': True}
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 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', '') if not context['moderated']: context['pending'] = False data_dict['id'] = name_or_id #log.fatal("-----------> data dict: % s" % data_dict) pkg = get_action('package_update')(context, data_dict) if request.params.get('save', '') == 'Approve': get_action('make_latest_pending_package_active')( 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'))
def user_update(context, data_dict): user = context['user'] # FIXME: We shouldn't have to do a try ... except here, validation should # have ensured that the data_dict contains a valid user id before we get to # authorization. try: user_obj = logic_auth.get_user_object(context, data_dict) except logic.NotFound: return {'success': False, 'msg': _('User not found')} # If the user has a valid reset_key in the db, and that same reset key # has been posted in the data_dict, we allow the user to update # her account without using her password or API key. if user_obj.reset_key and 'reset_key' in data_dict: if user_obj.reset_key == data_dict['reset_key']: return {'success': True} if not user: return {'success': False, 'msg': _('Have to be logged in to edit user')} if user == user_obj.name: # Allow users to update their own user accounts. return {'success': True} else: # Don't allow users to update other users' accounts. return {'success': False, 'msg': _('User %s not authorized to edit user %s') % (user, user_obj.id)}
def authz(self, id): group = model.Group.get(id) if group is None: abort(404, _('Group not found')) group_type = group.type if group_type not in self.group_types: abort(404, _('Incorrect group type')) c.groupname = group.name c.grouptitle = group.display_name try: context = \ {'model': model, 'user': c.user, '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(403, _('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', extra_vars={'group_type': group_type})
def member_delete(self, id): group_type = self._ensure_controller_matches_group_type(id) if 'cancel' in request.params: self._redirect_to_this_controller(action='members', id=id) context = {'model': model, 'session': model.Session, 'user': c.user} try: self._check_access('group_member_delete', context, {'id': id}) except NotAuthorized: abort(403, _('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_this_controller(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(403, _('Unauthorized to delete group %s members') % '') except NotFound: abort(404, _('Group not found')) return self._render_template('group/confirm_delete_member.html', group_type)
def list_of_strings(key, data, errors, context): value = data.get(key) if not isinstance(value, list): raise Invalid(_('Not a list')) for x in value: if not isinstance(x, string_types): raise Invalid('%s: %s' % (_('Not a string'), x))
def _ensure_controller_matches_group_type(self, id): group = model.Group.get(id) if group is None: abort(404, _('Group not found')) if group.type not in self.group_types: abort(404, _('Incorrect group type')) return group.type
def package_name_validator(key, data, errors, context): model = context['model'] session = context['session'] package = context.get('package') query = session.query(model.Package.state).filter_by(name=data[key]) if package: package_id = package.id else: package_id = data.get(key[:-1] + ('id',)) if package_id and package_id is not missing: query = query.filter(model.Package.id != package_id) result = query.first() if result and result.state != State.DELETED: errors[key].append(_('That URL is already in use.')) value = data[key] if len(value) < PACKAGE_NAME_MIN_LENGTH: raise Invalid( _('Name "%s" length is less than minimum %s') % (value, PACKAGE_NAME_MIN_LENGTH) ) if len(value) > PACKAGE_NAME_MAX_LENGTH: raise Invalid( _('Name "%s" length is more than maximum %s') % (value, PACKAGE_NAME_MAX_LENGTH) )
def user_name_exists(user_name, context): model = context['model'] session = context['session'] result = session.query(model.User).filter_by(name=user_name).first() if not result: raise Invalid('%s: %s' % (_('Not found'), _('User'))) return result.name
def owner_org_validator(key, data, errors, context): value = data.get(key) if value is missing or value is None: if not authz.check_config_permission('create_unowned_dataset'): raise Invalid(_('An organization must be provided')) data.pop(key, None) raise df.StopOnError model = context['model'] user = context['user'] user = model.User.get(user) if value == '': if not authz.check_config_permission('create_unowned_dataset'): raise Invalid(_('An organization must be provided')) return group = model.Group.get(value) if not group: raise Invalid(_('Organization does not exist')) group_id = group.id if not context.get(u'ignore_auth', False) and not(user.sysadmin or authz.has_user_permission_for_group_or_org( group_id, user.name, 'create_dataset')): raise Invalid(_('You cannot add a dataset to this organization')) data[key] = group_id
def name_validator(value, context): '''Return the given value if it's a valid name, otherwise raise Invalid. If it's a valid name, the given value will be returned unmodified. This function applies general validation rules for names of packages, groups, users, etc. Most schemas also have their own custom name validator function to apply custom validation rules after this function, for example a ``package_name_validator()`` to check that no package with the given name already exists. :raises ckan.lib.navl.dictization_functions.Invalid: if ``value`` is not a valid name ''' if not isinstance(value, string_types): raise Invalid(_('Names must be strings')) # check basic textual rules if value in ['new', 'edit', 'search']: raise Invalid(_('That name cannot be used')) if len(value) < 2: raise Invalid(_('Must be at least %s characters long') % 2) if len(value) > PACKAGE_NAME_MAX_LENGTH: raise Invalid(_('Name must be a maximum of %i characters long') % \ PACKAGE_NAME_MAX_LENGTH) if not name_match.match(value): raise Invalid(_('Must be purely lowercase alphanumeric ' '(ascii) characters and these symbols: -_')) return value
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 get_terms_of_use_icon(terms_of_use): term_to_image_mapping = { 'NonCommercialAllowed-CommercialAllowed-ReferenceNotRequired': { # noqa 'title': _('Open data'), 'icon': 'terms_open', }, 'NonCommercialAllowed-CommercialAllowed-ReferenceRequired': { # noqa 'title': _('Reference required'), 'icon': 'terms_by', }, 'NonCommercialAllowed-CommercialWithPermission-ReferenceNotRequired': { # noqa 'title': _('Commercial use with permission allowed'), 'icon': 'terms_ask', }, 'NonCommercialAllowed-CommercialWithPermission-ReferenceRequired': { # noqa 'title': _('Reference required / Commercial use with permission allowed'), # noqa 'icon': 'terms_by-ask', }, 'ClosedData': { 'title': _('Closed data'), 'icon': 'terms_closed', }, } term_id = simplify_terms_of_use(terms_of_use) return term_to_image_mapping.get(term_id, None)
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 context['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 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} if context['save'] and not data: return self._save_edit(id, context) try: 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))
def logged_in(self): # redirect if needed came_from = request.params.get('came_from', '') if self._sane_came_from(came_from): return h.redirect_to(str(came_from)) if c.user: context = None data_dict = {'id': c.user} user_dict = get_action('user_show')(context, data_dict) h.flash_success( _("%s is now logged in") % user_dict['display_name']) return self.me() else: err = _('Login failed. Bad username or password.') if g.openid_enabled: err += _(' (Or if using OpenID, it hasn\'t been associated ' 'with a user account.)') 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 dataset_facets(self, facets_dict, package_type): facets_dict = OrderedDict() facets_dict['tags'] = _('Tags') facets_dict['organization'] = _('Organizations') facets_dict['groups'] = _('Groups') facets_dict['format'] = _('Formats') return facets_dict
def tag_delete(context, data_dict): '''Delete a tag. You must be a sysadmin to delete tags. :param id: the id or name of the tag :type id: string :param vocabulary_id: the id or name of the vocabulary that the tag belongs to (optional, default: None) :type vocabulary_id: string ''' model = context['model'] if not data_dict.has_key('id') or not data_dict['id']: raise ValidationError({'id': _('id not in data')}) tag_id_or_name = _get_or_bust(data_dict, 'id') vocab_id_or_name = data_dict.get('vocabulary_id') tag_obj = model.tag.Tag.get(tag_id_or_name, vocab_id_or_name) if tag_obj is None: raise NotFound(_('Could not find tag "%s"') % tag_id_or_name) _check_access('tag_delete', context, data_dict) tag_obj.delete() model.repo.commit()
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(403, _('Unauthorized to edit a user.')) if (context['save']) and not data: return self._save_edit(id, context) try: old_data = get_action('user_show')(context, data_dict) schema = self._db_to_edit_form_schema() if schema: old_data, errors = \ dictization_functions.validate(old_data, schema, context) c.display_name = old_data.get('display_name') c.user_name = old_data.get('name') data = data or old_data except NotAuthorized: abort(403, _('Unauthorized to edit user %s') % '') except NotFound: abort(404, _('User not found')) user_obj = context.get('user_obj') if not (authz.is_sysadmin(c.user) or c.user == user_obj.name): abort(403, _('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}, data_dict) c.is_myself = True c.show_email_notifications = asbool( config.get('ckan.activity_streams_email_notifications')) c.form = render(self.edit_user_form, extra_vars=vars) return render('user/edit.html')
def request_reset(self): context = {'model': model, 'session': model.Session, 'user': c.user, 'auth_user_obj': c.userobj} data_dict = {'id': request.params.get('user')} try: check_access('request_reset', context) except NotAuthorized: abort(401, _('Unauthorized to request reset password.')) if request.method == 'POST': id = request.params.get('user') context = {'model': model, 'user': c.user} data_dict = {'id': id} user_obj = None try: user_dict = get_action('user_show')(context, data_dict) user_obj = context['user_obj'] except NotFound: # Show success regardless of outcome to prevent scanning h.flash_success(_('Please check your inbox for ' 'a reset code.')) if user_obj: try: mailer.send_reset_link(user_obj) 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))
def search(self, ver=None, register=None): log.debug("search %s params: %r" % (register, request.params)) if register == "revision": since_time = None if "since_id" in request.params: id = request.params["since_id"] if not id: return self._finish_bad_request(_(u"No revision specified")) rev = model.Session.query(model.Revision).get(id) if rev is None: return self._finish_not_found(_(u"There is no revision with id: %s") % id) since_time = rev.timestamp elif "since_time" in request.params: since_time_str = request.params["since_time"] try: since_time = h.date_str_to_datetime(since_time_str) except ValueError, inst: return self._finish_bad_request("ValueError: %s" % inst) else: return self._finish_bad_request( _("Missing search term ('since_id=UUID' or " + " 'since_time=TIMESTAMP')") ) revs = model.Session.query(model.Revision).filter(model.Revision.timestamp > since_time) return self._finish_ok([rev.id for rev in revs])
def related_update(context, data_dict): model = context['model'] user = context['user'] if not user: return {'success': False, 'msg': _('Only the owner can update a related item')} related = logic_auth.get_related_object(context, data_dict) userobj = model.User.get(user) if related.datasets: package = related.datasets[0] pkg_dict = {'id': package.id} authorized = _auth_update.package_update(context, pkg_dict).get('success') if authorized: return {'success': True} if not userobj or userobj.id != related.owner_id: return {'success': False, 'msg': _('Only the owner can update a related item')} # Only sysadmins can change the featured field. if ('featured' in data_dict and data_dict['featured'] != related.featured): return {'success': False, 'msg': _('You must be a sysadmin to change a related item\'s ' 'featured field.')} return {'success': True}
def perform_reset(self, id): # FIXME 403 error for invalid key is a non helpful page # FIXME We should reset the reset key when it is used to prevent # reuse of the url context = {'model': model, 'session': model.Session, 'user': c.user, 'keep_sensitive_data': True} data_dict = {'id': id} try: check_access('user_reset', context) except NotAuthorized: abort(401, _('Unauthorized to reset password.')) try: user_dict = get_action('user_show')(context, data_dict) # Be a little paranoid, and get rid of sensitive data that's # not needed. user_dict.pop('apikey', None) user_dict.pop('reset_key', None) user_obj = context['user_obj'] except NotFound, e: abort(404, _('User not found'))
def action(self, logic_function, ver=None): try: function = get_action(logic_function) except KeyError: log.info('Can\'t find logic function: %s', logic_function) return self._finish_bad_request( _('Action name not known: %s') % logic_function) context = {'model': model, 'session': model.Session, 'user': c.user, 'api_version': ver, 'return_type': 'LazyJSONObject', 'auth_user_obj': c.userobj} model.Session()._context = context return_dict = {'help': h.url_for(controller='api', action='action', logic_function='help_show', ver=ver, name=logic_function, qualified=True, ) } try: side_effect_free = getattr(function, 'side_effect_free', False) request_data = self._get_request_data(try_url_params= side_effect_free) except ValueError, inst: log.info('Bad Action API request data: %s', inst) return self._finish_bad_request( _('JSON Error: %s') % inst)
def search(self, ver=None, register=None): log.debug('search %s params: %r', register, request.params) if register == 'revision': since_time = None if 'since_id' in request.params: id = request.params['since_id'] if not id: return self._finish_bad_request( _(u'No revision specified')) rev = model.Session.query(model.Revision).get(id) if rev is None: return self._finish_not_found( _(u'There is no revision with id: %s') % id) since_time = rev.timestamp elif 'since_time' in request.params: since_time_str = request.params['since_time'] try: since_time = h.date_str_to_datetime(since_time_str) except ValueError, inst: return self._finish_bad_request('ValueError: %s' % inst) else: return self._finish_bad_request( _("Missing search term ('since_id=UUID' or " + " 'since_time=TIMESTAMP')")) revs = model.Session.query(model.Revision) \ .filter(model.Revision.timestamp > since_time) \ .order_by(model.Revision.timestamp) \ .limit(50) # reasonable enough for a page return self._finish_ok([rev.id for rev in revs])
def resource_data(self, id, resource_id): if toolkit.request.method == "POST": try: toolkit.c.pkg_dict = p.toolkit.get_action("datapusher_submit")(None, {"resource_id": resource_id}) except logic.ValidationError: pass base.redirect( core_helpers.url_for( controller="ckanext.datapusher.plugin:ResourceDataController", action="resource_data", id=id, resource_id=resource_id, ) ) try: toolkit.c.pkg_dict = p.toolkit.get_action("package_show")(None, {"id": id}) toolkit.c.resource = p.toolkit.get_action("resource_show")(None, {"id": resource_id}) except logic.NotFound: base.abort(404, _("Resource not found")) except logic.NotAuthorized: base.abort(401, _("Unauthorized to edit this resource")) try: datapusher_status = p.toolkit.get_action("datapusher_status")(None, {"resource_id": resource_id}) except logic.NotFound: datapusher_status = {} except logic.NotAuthorized: base.abort(401, _("Not authorized to see this page")) return base.render("package/resource_data.html", extra_vars={"status": datapusher_status})
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.resource = get_action('resource_show')(context, {'id': resource_id}) c.package = get_action('package_show')(context, {'id': id}) # required for nav menu c.pkg = context['package'] c.pkg_dict = c.package except NotFound: abort(404, _('Resource not found')) except NotAuthorized: abort(401, _('Unauthorized to read resource %s') % id) # 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}) return render('package/resource_read.html')
def user_name_validator(key, data, errors, context): '''Validate a new user name. Append an error message to ``errors[key]`` if a user named ``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_user_name = data[key] if not isinstance(new_user_name, basestring): raise Invalid(_('User names must be strings')) user = model.User.get(new_user_name) if user is not None: # A user with new_user_name 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 login name is not available.'))
def _finish_not_authz(extra_msg=None): response_data = _(u'Access denied') if extra_msg: response_data = u'%s - %s' % (response_data, extra_msg) return _finish(403, response_data, u'json')
def before_request(): try: context = dict(model=model, user=g.user, auth_user_obj=g.userobj) logic.check_access(u'sysadmin', context) except logic.NotAuthorized: base.abort(403, _(u'Need to be system administrator to administer'))
def download(context, resource, url_timeout=30, max_content_length='default', method='GET'): '''Given a resource, tries to download it. Params: resource - dict of the resource Exceptions from tidy_url may be propagated: LinkInvalidError if the URL is invalid If there is an error performing the download, raises: DownloadException - connection problems etc. DownloadError - HTTP status code is an error or 0 length If download is not suitable (e.g. too large), raises: ChooseNotToDownload If the basic GET fails then it will try it with common API parameters (SPARQL, WMS etc) to get a better response. Returns a dict of results of a successful download: mimetype, size, hash, headers, saved_file, url_redirected_to ''' from ckanext.archiver import default_settings as settings from pylons import config if max_content_length == 'default': max_content_length = settings.MAX_CONTENT_LENGTH url = resource['url'] url = tidy_url(url) if (resource.get('url_type') == 'upload' and not url.startswith('http')): url = context['site_url'].rstrip('/') + url hosted_externally = not url.startswith(config['ckan.site_url']) if resource.get('url_type') == 'upload' and hosted_externally: # ckanext-cloudstorage for example does that # enable ckanext-archiver.archive_cloud for qa to work on cloud resources # till https://github.com/ckan/ckanext-qa/issues/48 is resolved # Warning: this will result in double storage of all files below archival filesize limit if not config.get('ckanext-archiver.archive_cloud', False): raise ChooseNotToDownload( 'Skipping resource hosted externally to download resource: %s' % url, url) headers = _set_user_agent_string({}) # start the download - just get the headers # May raise DownloadException method_func = {'GET': requests.get, 'POST': requests.post}[method] res = requests_wrapper( log, method_func, url, timeout=url_timeout, stream=True, headers=headers, verify=verify_https(), ) url_redirected_to = res.url if url != res.url else None if context.get('previous') and ('etag' in res.headers): if context.get('previous').etag == res.headers['etag']: log.info("ETAG matches, not downloading content") raise NotChanged("etag suggests content has not changed") if not res.ok: # i.e. 404 or something raise DownloadError( 'Server reported status error: %s %s' % (res.status_code, res.reason), url_redirected_to) log.info('GET started successfully. Content headers: %r', res.headers) # record headers mimetype = _clean_content_type(res.headers.get('content-type', '').lower()) # make sure resource content-length does not exceed our maximum content_length = res.headers.get('content-length') if content_length: try: content_length = int(content_length) except ValueError: # if there are multiple Content-Length headers, requests # will return all the values, comma separated if ',' in content_length: try: content_length = int(content_length.split(',')[0]) except ValueError: pass if isinstance(content_length, int) and \ int(content_length) >= max_content_length: # record fact that resource is too large to archive log.warning( 'Resource too large to download: %s > max (%s). ' 'Resource: %s %r', content_length, max_content_length, resource['id'], url) raise ChooseNotToDownload( _('Content-length %s exceeds maximum ' 'allowed value %s') % (content_length, max_content_length), url_redirected_to) # content_length in the headers is useful but can be unreliable, so when we # download, we will monitor it doesn't go over the max. # continue the download - stream the response body def get_content(): return res.content log.info('Downloading the body') content = requests_wrapper(log, get_content) # APIs can return status 200, but contain an error message in the body if response_is_an_api_error(content): raise DownloadError( _('Server content contained an API error message: %s') % content[:250], url_redirected_to) content_length = len(content) if content_length > max_content_length: raise ChooseNotToDownload( _("Content-length %s exceeds maximum allowed value %s") % (content_length, max_content_length), url_redirected_to) log.info('Saving resource') try: length, hash, saved_file_path = _save_resource(resource, res, max_content_length) except ChooseNotToDownload, e: raise ChooseNotToDownload(str(e), url_redirected_to)
log.info('Saving resource') try: length, hash, saved_file_path = _save_resource(resource, res, max_content_length) except ChooseNotToDownload, e: raise ChooseNotToDownload(str(e), url_redirected_to) log.info('Resource saved. Length: %s File: %s', length, saved_file_path) # zero length (or just one byte) indicates a problem if length < 2: # record fact that resource is zero length log.warning( 'Resource found was length %i - not archiving. Resource: %s %r', length, resource['id'], url) raise DownloadError( _("Content-length after streaming was %i") % length, url_redirected_to) log.info( 'Resource downloaded: id=%s url=%r cache_filename=%s length=%s hash=%s', resource['id'], url, saved_file_path, length, hash) return { 'mimetype': mimetype, 'size': length, 'hash': hash, 'headers': dict(res.headers), 'saved_file': saved_file_path, 'url_redirected_to': url_redirected_to, 'request_type': method }
def action(logic_function, ver=API_DEFAULT_VERSION): u'''Main endpoint for the action API (v3) Creates a dict with the incoming request data and calls the appropiate logic function. Returns a JSON response with the following keys: * ``help``: A URL to the docstring for the specified action * ``success``: A boolean indicating if the request was successful or an exception was raised * ``result``: The output of the action, generally an Object or an Array ''' # Check if action exists try: function = get_action(logic_function) except KeyError: msg = u'Action name not known: {0}'.format(logic_function) log.info(msg) return _finish_bad_request(msg) context = { u'model': model, u'session': model.Session, u'user': g.user, u'api_version': ver, u'auth_user_obj': g.userobj } model.Session()._context = context return_dict = { u'help': url_for( u'api.action', logic_function=u'help_show', ver=ver, name=logic_function, _external=True, ) } # Get the request data try: side_effect_free = getattr(function, u'side_effect_free', False) request_data = _get_request_data(try_url_params=side_effect_free) except ValueError as inst: log.info(u'Bad Action API request data: %s', inst) return _finish_bad_request(_(u'JSON Error: %s') % inst) if not isinstance(request_data, dict): # this occurs if request_data is blank log.info(u'Bad Action API request data - not dict: %r', request_data) return _finish_bad_request( _(u'Bad request data: %s') % u'Request data JSON decoded to %r but ' u'it needs to be a dictionary.' % request_data) if u'callback' in request_data: del request_data[u'callback'] g.user = None g.userobj = None context[u'user'] = None context[u'auth_user_obj'] = None # Call the action function, catch any exception try: result = function(context, request_data) return_dict[u'success'] = True return_dict[u'result'] = result except DataError as e: log.info(u'Format incorrect (Action API): %s - %s', e.error, request_data) return_dict[u'error'] = { u'__type': u'Integrity Error', u'message': e.error, u'data': request_data } return_dict[u'success'] = False return _finish(400, return_dict, content_type=u'json') except NotAuthorized as e: return_dict[u'error'] = { u'__type': u'Authorization Error', u'message': _(u'Access denied') } return_dict[u'success'] = False if text_type(e): return_dict[u'error'][u'message'] += u': %s' % e return _finish(403, return_dict, content_type=u'json') except NotFound as e: return_dict[u'error'] = { u'__type': u'Not Found Error', u'message': _(u'Not found') } if text_type(e): return_dict[u'error'][u'message'] += u': %s' % e return_dict[u'success'] = False return _finish(404, return_dict, content_type=u'json') except ValidationError as e: error_dict = e.error_dict error_dict[u'__type'] = u'Validation Error' return_dict[u'error'] = error_dict return_dict[u'success'] = False # CS nasty_string ignore log.info(u'Validation error (Action API): %r', str(e.error_dict)) return _finish(409, return_dict, content_type=u'json') except SearchQueryError as e: return_dict[u'error'] = { u'__type': u'Search Query Error', u'message': u'Search Query is invalid: %r' % e.args } return_dict[u'success'] = False return _finish(400, return_dict, content_type=u'json') except SearchError as e: return_dict[u'error'] = { u'__type': u'Search Error', u'message': u'Search error: %r' % e.args } return_dict[u'success'] = False return _finish(409, return_dict, content_type=u'json') except SearchIndexError as e: return_dict[u'error'] = { u'__type': u'Search Index Error', u'message': u'Unable to add package to search index: %s' % str(e) } return_dict[u'success'] = False return _finish(500, return_dict, content_type=u'json') return _finish_ok(return_dict)
def _finish_bad_request(extra_msg=None): response_data = _(u'Bad request') if extra_msg: response_data = u'%s - %s' % (response_data, extra_msg) return _finish(400, response_data, u'json')
def _finish_not_found(extra_msg=None): response_data = _(u'Not found') if extra_msg: response_data = u'%s - %s' % (response_data, extra_msg) return _finish(404, response_data, u'json')
def natural_number_validator(value, context): value = int_validator(value, context) if value < 0: raise Invalid(_('Must be a natural number')) return value
def role_exists(role, context): if role not in authz.ROLE_PERMISSIONS: raise Invalid(_('role does not exist.')) return role
def is_positive_integer(value, context): value = int_validator(value, context) if value < 1: raise Invalid(_('Must be a postive integer')) return value
def user_about_validator(value,context): if 'http://' in value or 'https://' in value: raise Invalid(_('Edit not allowed as it looks like spam. Please avoid links in your description.')) return value
def extra_key_not_in_root_schema(key, data, errors, context): for schema_key in context.get('schema_keys', []): if schema_key == data[key]: raise Invalid(_('There is a schema field with the same name'))
def package_version_validator(value, context): if len(value) > PACKAGE_VERSION_MAX_LENGTH: raise Invalid(_('Version must be a maximum of %i characters long') % \ PACKAGE_VERSION_MAX_LENGTH) return value
def vocabulary_id_not_changed(value, context): vocabulary = context.get('vocabulary') if vocabulary and value != vocabulary.id: raise Invalid(_('Cannot change value of key from %s to %s. ' 'This key is read-only') % (vocabulary.id, value)) return value
def querytool_public_read(self, name): ''' :return: base template ''' querytool = _get_action('querytool_public_read', {'name': name}) if not querytool: abort(404, _('Application not found.')) # only sysadmins or organization members can access private querytool if querytool['private'] is True: context = _get_context() try: check_access('querytool_show', context, {'name': name}) except NotAuthorized: abort(403, _('Not authorized to see this page')) # Check if the data for this querytool still exists if querytool['dataset_name']: try: _get_action('package_show', {'id': querytool['dataset_name']}) except NotFound: abort( 404, _('The data used for creating this ' 'Application has been removed ' 'by the administrator.')) if not querytool['visualizations']: abort(404, _('Application not fully set.')) params = toolkit.request.params querytools = [] items = [] items.append({u'order': 0, u'name': querytool['name']}) if querytool['related_querytools']: items.extend(json.loads(querytool['related_querytools'])) items.sort(key=itemgetter('order')) for item in items: q_item = _get_action('querytool_public_read', {'name': item['name']}) if q_item: q_item['visualizations'] = json.loads(q_item['visualizations']) q_item['visualizations'].sort(key=itemgetter('order')) q_name = q_item['name'] new_filters = json.loads(q_item['filters']) for k, v in params.items(): # Update query filters if k.startswith('{}_data_filter_name_'.format(q_name)): id = k.split('_')[-1] for filter in new_filters: # Apply changes only on public filters # to protect changing private # filters by changing the url query params if filter['visibility'] == 'public': if v == filter.get('name'): filter['value'] = \ params.get('{}_data_filter_value_{}' .format(q_name, id)) # Replace & with %26 to fix the error for graphs # not being generated for values with & in them filter['value'] = filter['value'].replace( '&', '%26') # Update charts y_axis value if k.startswith('{}_y_axis_column'.format(q_name)): q_item['y_axis_column'] = v # Update visualizations filters if k.startswith('{}_viz_filter_name'.format(q_name)): id = k.split('_')[-1] for visualization in q_item['visualizations']: if visualization['order'] == int(id): visualization['filter_name'] = \ params.get('{}_viz_filter_name_{}'. format(q_name, id)) visualization['filter_value'] = \ params.get('{}_viz_filter_value_{}'. format(q_name, id)) for image in q_item['visualizations']: if image['type'] == 'image': is_upload = image[ 'url'] and not image['url'].startswith('http') if is_upload: image['url'] = '{0}/uploads/vs/{1}'.format( toolkit.request.host_url, image['url']) related_sql_string = helpers.create_query_str( q_item.get('chart_resource'), new_filters) q_item['public_filters'] = new_filters q_item['public_filters'].sort(key=itemgetter('order')) q_item['sql_string'] = related_sql_string # Add slug to filters for filter in new_filters: filter['slug'] = helpers.slugify(filter.get('alias', '')) # Need this hack for chart filter q_item['public_main_filters'] = json.dumps(new_filters) querytools.append(q_item) return render('querytool/public/read.html', extra_vars={'querytools': querytools})
def tag_not_uppercase(value, context): tagname_uppercase = re.compile('[A-Z]') if tagname_uppercase.search(value): raise Invalid(_('Tag "%s" must not be uppercase' % (value))) return value
def querytool_edit(self, querytool=None, data=None, errors=None, error_summary=None): ''' Create/edit query tool :return: query create/edit template page ''' if querytool: querytool = querytool[1:] data_dict = {'name': querytool} context = _get_context() try: check_access('querytool_update', context, data_dict) except NotAuthorized: abort(403, _('Not authorized to see this page')) _querytool = _get_action('querytool_get', data_dict) if _querytool is None and len(querytool) > 0: abort(404, _('Application not found.')) if _querytool is None: _querytool = {} # Check if the data for this querytool still exists if 'dataset_name' in _querytool.keys(): try: _get_action('package_show', {'id': _querytool['dataset_name']}) except NotFound: abort( 404, _('The data used for creating this ' 'Application has been removed ' 'by the administrator.')) if toolkit.request.method == 'POST' and not data: data = dict(toolkit.request.POST) filters = [] y_axis_columns = [] related_querytools = [] for k, v in data.items(): if k.startswith('data_filter_name_'): filter = {} id = k.split('_')[-1] filter['order'] = int(id) filter['name'] = data['data_filter_name_{}'.format(id)] filter['value'] = data['data_filter_value_{}'.format(id)] # Replace & with %26 to fix the error for graphs # not being generated for values with & in them filter['value'] = filter['value'].replace('&', '%26') filter['alias'] = data['data_filter_alias_{}'.format(id)] filter['visibility'] = \ data['data_filter_visibility_{}'.format(id)] filter['filter_color'] = \ data['data_filter_color_{}'.format(id)] filters.append(filter) elif k.startswith('y_axis_name_'): id = k.split('_')[-1] alias = data.get('y_axis_alias_%s' % id, '') y_axis_columns.append({'name': v, 'alias': alias}) elif k.startswith('related_querytool_'): related_querytool = {} id = k.split('_')[-1] related_querytool['order'] = int(id) related_querytool['name'] = \ data['related_querytool_{}'.format(id)] related_querytools.append(related_querytool) if any(filters): _querytool['filters'] = json.dumps(filters) sql_string = helpers.create_query_str(data['chart_resource'], filters) else: _querytool['filters'] = '' sql_string = '' if 'private' not in data.keys(): _querytool['private'] = True if any(related_querytools): _querytool['related_querytools'] = json.\ dumps(related_querytools) else: _querytool['related_querytools'] = '' _querytool.update(data) _querytool['querytool'] = querytool _querytool['sql_string'] = sql_string _querytool['y_axis_columns'] = (json.dumps(y_axis_columns) if y_axis_columns else '') _querytool['owner_org'] = data['owner_org'] try: junk = _get_action('querytool_update', _querytool) h.flash_success(_('Data Successfully updated.')) except ValidationError, e: errors = e.error_dict error_summary = e.error_summary return self.querytool_edit('/' + querytool, _querytool, errors, error_summary) if 'save_data' in data.keys(): # redirect to querytools group url = h.url_for('querytool_list_by_group', group=_querytool['group']) else: # redirect to manage visualisations url = h.url_for('querytool_edit_visualizations', querytool='/' + _querytool['name']) h.redirect_to(url)
def resource_id_exists(value, context): model = context['model'] session = context['session'] if not session.query(model.Resource).get(value): raise Invalid('%s: %s' % (_('Not found'), _('Resource'))) return value
def resource_update(context, data_dict): '''Update a resource. To update a resource you must be authorized to update the dataset that the resource belongs to. For further parameters see :py:func:`~ckan.logic.action.create.resource_create`. :param id: the id of the resource to update :type id: string :returns: the updated resource :rtype: string ''' model = context['model'] user = context['user'] id = _get_or_bust(data_dict, "id") if not data_dict.get('url'): data_dict['url'] = '' resource = model.Resource.get(id) context["resource"] = resource if not resource: log.debug('Could not find resource %s', id) raise NotFound(_('Resource was not found.')) _check_access('resource_update', context, data_dict) del context["resource"] package_id = resource.package.id pkg_dict = _get_action('package_show')(dict(context, return_type='dict'), { 'id': package_id }) for n, p in enumerate(pkg_dict['resources']): if p['id'] == id: break else: log.error('Could not find resource %s after all', id) raise NotFound(_('Resource was not found.')) # Persist the datastore_active extra if already present and not provided if ('datastore_active' in resource.extras and 'datastore_active' not in data_dict): data_dict['datastore_active'] = resource.extras['datastore_active'] for plugin in plugins.PluginImplementations(plugins.IResourceController): plugin.before_update(context, pkg_dict['resources'][n], data_dict) upload = uploader.get_resource_uploader(data_dict) if 'mimetype' not in data_dict: if hasattr(upload, 'mimetype'): data_dict['mimetype'] = upload.mimetype if 'size' not in data_dict and 'url_type' in data_dict: if hasattr(upload, 'filesize'): data_dict['size'] = upload.filesize pkg_dict['resources'][n] = data_dict try: context['defer_commit'] = True context['use_cache'] = False updated_pkg_dict = _get_action('package_update')(context, pkg_dict) context.pop('defer_commit') except ValidationError, e: errors = e.error_dict['resources'][n] raise ValidationError(errors)
def edit_visualizations(self, querytool=None, data=None, errors=None, error_summary=None): ''' Create or edit visualizations for the querytool :return: query edit template page ''' if querytool: querytool = querytool[1:] data_dict = {'name': querytool} context = _get_context() try: check_access('querytool_update', context, data_dict) except NotAuthorized: abort(403, _('Not authorized to see this page')) _querytool = _get_action('querytool_get', data_dict) if _querytool is None and len(querytool) > 0: abort(404, _('Application not found.')) # Check if the data for this querytool still exists if _querytool['dataset_name']: try: _get_action('package_show', {'id': _querytool['dataset_name']}) except NotFound: abort( 404, _('The data used for creating this ' 'Application has been removed by ' 'the administrator.')) _visualization_items = \ _get_action('querytool_get_visualizations', data_dict) if _visualization_items is None: _visualization_items = {'name': querytool} if toolkit.request.method == 'POST' and not data: data = dict(toolkit.request.POST) visualizations = [] text_boxes = [] images = [] maps = [] tables = [] for k, v in data.items(): ''' TODO: save visualizations with key value e.g {'charts' :[] # 'images': []} for easier itteration ''' if k.startswith('chart_field_graph_'): visualization = {} id = k.split('_')[-1] visualization['type'] = 'chart' visualization['order'] = int(id) visualization['graph'] = \ data.get('chart_field_graph_{}'.format(id)) visualization['x_axis'] = \ data.get('chart_field_axis_x_{}'.format(id)) visualization['y_axis'] = \ data.get('chart_field_axis_y_{}'.format(id)) visualization['color'] = \ data.get('chart_field_color_{}'.format(id)) visualization['title'] = \ data.get('chart_field_title_{}'.format(id)) visualization['x_text_rotate'] = \ data.get('chart_field_x_text_rotate_{}'.format(id)) visualization['tooltip_name'] = \ data.get('chart_field_tooltip_name_{}'.format(id)) visualization['data_format'] = \ data.get('chart_field_data_format_{}'.format(id)) visualization['y_tick_format'] = \ data.get('chart_field_y_ticks_format_{}'.format(id)) visualization['padding_bottom'] = \ data.get('chart_field_padding_bottom_{}'.format(id)) visualization['padding_top'] = \ data.get('chart_field_padding_top_{}'.format(id)) visualization['tick_count'] = \ data.get('chart_field_tick_count_{}'.format(id)) visualization['y_label'] = \ data.get('chart_field_y_label_{}'.format(id)) visualization['size'] = \ data.get('chart_field_size_{}'.format(id)) visualization['chart_padding_left'] = \ data.get('chart_field_chart_padding_left_{}'.format(id)) visualization['chart_padding_bottom'] = \ data.get('chart_field_chart_padding_bottom_{}'.format(id)) visualization['static_reference_columns'] = \ toolkit.request.POST.getall( 'chart_field_static_reference_columns_%s' % id) visualization['static_reference_label'] = \ data.get('chart_field_static_reference_label_%s' % id) visualization['dynamic_reference_type'] = \ data.get('chart_field_dynamic_reference_type_%s' % id) visualization['dynamic_reference_factor'] = \ data.get('chart_field_dynamic_reference_factor_%s' % id) visualization['dynamic_reference_label'] = \ data.get('chart_field_dynamic_reference_label_%s' % id) visualization['sort'] = \ data.get('chart_field_sort_{}'.format(id)) if 'chart_field_x_text_multiline_{}'.format(id) in data: visualization['x_text_multiline'] = 'true' else: visualization['x_text_multiline'] = 'false' visualization['x_tick_culling_max'] = \ data.get('chart_field_x_tick_culling_max_{}'.format(id)) if 'chart_field_legend_{}'.format(id) in data: visualization['show_legend'] = 'true' else: visualization['show_legend'] = 'false' if 'chart_field_labels_{}'.format(id) in data: visualization['show_labels'] = 'true' else: visualization['show_labels'] = 'false' if 'chart_field_y_label_hide_{}'.format(id) in data: visualization['y_label_hide'] = 'true' else: visualization['y_label_hide'] = 'false' if 'chart_field_show_labels_as_percentages_{}'.format( id) in data: visualization['show_labels_as_percentages'] = 'true' else: visualization['show_labels_as_percentages'] = 'false' if 'chart_field_y_from_zero_{}'.format(id) in data: visualization['y_from_zero'] = 'true' else: visualization['y_from_zero'] = 'false' if data['chart_field_filter_name_{}'.format(id)]: visualization['filter_name'] = \ data['chart_field_filter_name_{}'.format(id)] visualization['filter_value'] = \ data['chart_field_filter_value_{}'.format(id)] visualization['filter_alias'] = \ data['chart_field_filter_alias_{}'.format(id)] visualization['filter_visibility'] = \ data['chart_field_filter_visibility_{}'.format(id)] else: visualization['filter_name'] = '' visualization['filter_value'] = '' visualization['filter_alias'] = '' visualization['filter_visibility'] = '' if 'chart_field_category_name_{}'.format(id) in data: visualization['category_name'] = \ data['chart_field_category_name_{}'.format(id)] else: visualization['category_name'] = '' visualizations.append(visualization) if k.startswith('text_box_description_'): text_box = {} id = k.split('_')[-1] text_box['type'] = 'text_box' text_box['order'] = int(id) text_box['description'] = \ data['text_box_description_{}'.format(id)] text_box['size'] = \ data['text_box_size_{}'.format(id)] text_boxes.append(text_box) if k.startswith('image_field_size_'): image = {} id = k.split('_')[-1] image['type'] = 'image' image['order'] = int(id) image_url = data['media_image_url_{}'.format(id)] if h.uploads_enabled(): image_upload = data['media_image_upload_{}'.format(id)] if isinstance(image_upload, cgi.FieldStorage): upload = uploader.get_uploader('vs', image_url) upload.update_data_dict( data, 'media_image_url_{}'.format(id), 'media_image_upload_{}'.format(id), 'False') upload.upload(uploader) image_url = upload.filename image['url'] = image_url image['size'] = \ data['image_field_size_{}'.format(id)] images.append(image) if k.startswith('map_resource_'): map_item = {} id = k.split('_')[-1] map_item['type'] = 'map' map_item['order'] = int(id) map_item['map_resource'] = \ data['map_resource_{}'.format(id)] map_item['map_title_field'] = \ data['map_title_field_{}'.format(id)] map_item['map_key_field'] = \ data['map_key_field_{}'.format(id)] map_item['data_key_field'] = \ data['map_data_key_field_{}'.format(id)] map_item['map_color_scheme'] = \ data['map_color_scheme_{}'.format(id)] map_item['size'] = \ data['map_size_{}'.format(id)] if data['map_field_filter_name_{}'.format(id)]: map_item['filter_name'] = \ data['map_field_filter_name_{}'.format(id)] map_item['filter_value'] = \ data['map_field_filter_value_{}'.format(id)] map_item['filter_alias'] = \ data['map_field_filter_alias_{}'.format(id)] map_item['filter_visibility'] = \ data['map_field_filter_visibility_{}'.format(id)] else: map_item['filter_name'] = '' map_item['filter_value'] = '' map_item['filter_alias'] = '' map_item['filter_visibility'] = '' maps.append(map_item) if k.startswith('table_size_'): table_item = {} id = k.split('_')[-1] table_item['type'] = 'table' table_item['order'] = int(id) table_item['y_axis'] = \ data['choose_y_axis_column'] table_item['size'] = \ data['table_size_{}'.format(id)] table_item['main_value'] = \ data['table_main_value_{}'.format(id)] table_item['title'] = \ data['table_field_title_{}'.format(id)] table_item['data_format'] = \ data['table_data_format_{}'.format(id)] if data['table_field_filter_name_{}'.format(id)]: table_item['filter_name'] = \ data['table_field_filter_name_{}'.format(id)] table_item['filter_value'] = \ data['table_field_filter_value_{}'.format(id)] table_item['filter_alias'] = \ data['table_field_filter_alias_{}'.format(id)] table_item['filter_visibility'] = \ data['table_field_filter_visibility_{}'.format(id)] else: table_item['filter_name'] = '' table_item['filter_value'] = '' table_item['filter_alias'] = '' table_item['filter_visibility'] = '' if data['table_category_name_{}'.format(id)]: table_item['category_name'] = \ data['table_category_name_{}'.format(id)] else: table_item['category_name'] = '' tables.append(table_item) vis = visualizations + text_boxes + images + maps + tables _visualization_items['visualizations'] = json.dumps(vis) if 'choose_y_axis_column' in data: _visualization_items['y_axis_column'] = \ data['choose_y_axis_column'] else: _visualization_items['y_axis_column'] = '' try: junk = _get_action('querytool_visualizations_update', _visualization_items) h.flash_success(_('Visualizations Successfully updated.')) except ValidationError, e: errors = e.error_dict error_summary = e.error_summary return self.querytool_edit('/' + querytool, data, errors, error_summary) if 'save-edit-data' in data.keys(): # redirect to edit data url = h.url_for('querytool_edit', querytool='/' + _querytool['name']) else: # redirect to querytools group url = h.url_for('querytool_list_by_group', group=_querytool['group']) h.redirect_to(url)
def package_owner_org_update(context, data_dict): '''Update the owning organization of a dataset :param id: the name or id of the dataset to update :type id: string :param organization_id: the name or id of the owning organization :type id: string ''' model = context['model'] user = context['user'] name_or_id = data_dict.get('id') organization_id = data_dict.get('organization_id') _check_access('package_owner_org_update', context, data_dict) pkg = model.Package.get(name_or_id) if pkg is None: raise NotFound(_('Package was not found.')) if organization_id: org = model.Group.get(organization_id) if org is None or not org.is_organization: raise NotFound(_('Organization was not found.')) # FIXME check we are in that org pkg.owner_org = org.id else: org = None pkg.owner_org = None if context.get('add_revision', True): rev = model.repo.new_revision() rev.author = user if 'message' in context: rev.message = context['message'] else: rev.message = _(u'REST API: Update object %s') % pkg.get("name") members = model.Session.query(model.Member) \ .filter(model.Member.table_id == pkg.id) \ .filter(model.Member.capacity == 'organization') need_update = True for member_obj in members: if org and member_obj.group_id == org.id: need_update = False else: member_obj.state = 'deleted' member_obj.save() # add the organization to member table if org and need_update: member_obj = model.Member(table_id=pkg.id, table_name='package', group=org, capacity='organization', group_id=org.id, state='active') model.Session.add(member_obj) if not context.get('defer_commit'): model.Session.commit()
def _group_or_org_update(context, data_dict, is_org=False): model = context['model'] user = context['user'] session = context['session'] id = _get_or_bust(data_dict, 'id') group = model.Group.get(id) context["group"] = group if group is None: raise NotFound('Group was not found.') data_dict['type'] = group.type # get the schema group_plugin = lib_plugins.lookup_group_plugin(group.type) try: schema = group_plugin.form_to_db_schema_options({ 'type': 'update', 'api': 'api_version' in context, 'context': context }) except AttributeError: schema = group_plugin.form_to_db_schema() upload = uploader.get_uploader('group', group.image_url) upload.update_data_dict(data_dict, 'image_url', 'image_upload', 'clear_upload') if is_org: _check_access('organization_update', context, data_dict) else: _check_access('group_update', context, data_dict) if 'api_version' not in context: # old plugins do not support passing the schema so we need # to ensure they still work try: group_plugin.check_data_dict(data_dict, schema) except TypeError: group_plugin.check_data_dict(data_dict) data, errors = lib_plugins.plugin_validate( group_plugin, context, data_dict, schema, 'organization_update' if is_org else 'group_update') log.debug('group_update validate_errs=%r user=%s group=%s data_dict=%r', errors, context.get('user'), context.get('group').name if context.get('group') else '', data_dict) if errors: session.rollback() raise ValidationError(errors) rev = model.repo.new_revision() rev.author = user if 'message' in context: rev.message = context['message'] else: rev.message = _(u'REST API: Update object %s') % data.get("name") group = model_save.group_dict_save(data, context, prevent_packages_update=is_org) if is_org: plugin_type = plugins.IOrganizationController else: plugin_type = plugins.IGroupController for item in plugins.PluginImplementations(plugin_type): item.edit(group) if is_org: activity_type = 'changed organization' else: activity_type = 'changed group' activity_dict = { 'user_id': model.User.by_name(user.decode('utf8')).id, 'object_id': group.id, 'activity_type': activity_type, } # Handle 'deleted' groups. # When the user marks a group as deleted this comes through here as # a 'changed' group activity. We detect this and change it to a 'deleted' # activity. if group.state == u'deleted': if session.query(ckan.model.Activity).filter_by( object_id=group.id, activity_type='deleted').all(): # A 'deleted group' activity for this group has already been # emitted. # FIXME: What if the group was deleted and then activated again? activity_dict = None else: # We will emit a 'deleted group' activity. activity_dict['activity_type'] = 'deleted group' if activity_dict is not None: activity_dict['data'] = { 'group': dictization.table_dictize(group, context) } activity_create_context = { 'model': model, 'user': user, 'defer_commit': True, 'ignore_auth': True, 'session': session } _get_action('activity_create')(activity_create_context, activity_dict) # TODO: Also create an activity detail recording what exactly changed # in the group. upload.upload(uploader.get_max_image_size()) if not context.get('defer_commit'): model.repo.commit() return model_dictize.group_dictize(group, context)
def bulk_member_new(self, id): try: req_dict = clean_dict( dict_fns.unflatten(tuplize_dict(parse_params(request.params)))) role = req_dict.get('role') emails = req_dict.get('emails', '').strip() new_members = [] invited_members = [] if emails and role: for email in emails.split(','): context = self._get_context() email = email.strip() try: if email: # Check if email is used user_dict = self._get_user_obj(email) if user_dict: added = self._add_existing_user_as_member( context, id, role, user_dict) if added: new_members.append(user_dict.display_name) else: user_data_dict = { 'email': email, 'group_id': id, 'role': role, 'id': id # This is something staging/prod need } user_dict = self._action('user_invite')( context, user_data_dict) invited_members.append(email) # h.flash_success(email + ' has been invited as ' + role) log.info('{} was invited as a new user'.format( email)) except tk.Invalid as e: h.flash_error( _('Invalid email address or unknown username provided: ' ) + email) if new_members: new_members_msg = _(' were added to the organization.' ) if len(new_members) != 1 else _( ' was added to the organization.') h.flash_success(', '.join(new_members) + new_members_msg) if invited_members: invited_members_msg = _( ' were invited to join the organization. An account was created for them.' ) if len(invited_members) != 1 else _( ' was invited to join the organization. An account was created for her/him.' ) h.flash_success(', '.join(invited_members) + invited_members_msg) org_obj = model.Group.get(id) self.notify_admin_users(org_obj, new_members, invited_members, role) self._send_analytics_info(org_obj, new_members, invited_members) else: h.flash_error(_('''No user or role was specified''')) self._redirect_to_this_controller(action='members', id=id) 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)
def package_update(context, data_dict): '''Update a dataset (package). You must be authorized to edit the dataset and the groups that it belongs to. It is recommended to call :py:func:`ckan.logic.action.get.package_show`, make the desired changes to the result, and then call ``package_update()`` with it. Plugins may change the parameters of this function depending on the value of the dataset's ``type`` attribute, see the :py:class:`~ckan.plugins.interfaces.IDatasetForm` plugin interface. For further parameters see :py:func:`~ckan.logic.action.create.package_create`. :param id: the name or id of the dataset to update :type id: string :returns: the updated dataset (if ``'return_package_dict'`` is ``True`` in the context, which is the default. Otherwise returns just the dataset id) :rtype: dictionary ''' model = context['model'] user = context['user'] name_or_id = data_dict.get("id") or data_dict['name'] pkg = model.Package.get(name_or_id) if pkg is None: raise NotFound(_('Package was not found.')) context["package"] = pkg data_dict["id"] = pkg.id data_dict['type'] = pkg.type _check_access('package_update', context, data_dict) # get the schema package_plugin = lib_plugins.lookup_package_plugin(pkg.type) if 'schema' in context: schema = context['schema'] else: schema = package_plugin.update_package_schema() if 'api_version' not in context: # check_data_dict() is deprecated. If the package_plugin has a # check_data_dict() we'll call it, if it doesn't have the method we'll # do nothing. check_data_dict = getattr(package_plugin, 'check_data_dict', None) if check_data_dict: try: package_plugin.check_data_dict(data_dict, schema) except TypeError: # Old plugins do not support passing the schema so we need # to ensure they still work. package_plugin.check_data_dict(data_dict) data, errors = lib_plugins.plugin_validate(package_plugin, context, data_dict, schema, 'package_update') log.debug('package_update validate_errs=%r user=%s package=%s data=%r', errors, context.get('user'), context.get('package').name if context.get('package') else '', data) if errors: model.Session.rollback() raise ValidationError(errors) rev = model.repo.new_revision() rev.author = user if 'message' in context: rev.message = context['message'] else: rev.message = _(u'REST API: Update object %s') % data.get("name") #avoid revisioning by updating directly model.Session.query(model.Package).filter_by(id=pkg.id).update( {"metadata_modified": datetime.datetime.utcnow()}) model.Session.refresh(pkg) pkg = model_save.package_dict_save(data, context) context_org_update = context.copy() context_org_update['ignore_auth'] = True context_org_update['defer_commit'] = True context_org_update['add_revision'] = False _get_action('package_owner_org_update')(context_org_update, { 'id': pkg.id, 'organization_id': pkg.owner_org }) # Needed to let extensions know the new resources ids model.Session.flush() if data.get('resources'): for index, resource in enumerate(data['resources']): resource['id'] = pkg.resources[index].id for item in plugins.PluginImplementations(plugins.IPackageController): item.edit(pkg) item.after_update(context, data) if not context.get('defer_commit'): model.repo.commit() log.debug('Updated object %s' % pkg.name) return_id_only = context.get('return_id_only', False) # Make sure that a user provided schema is not used on package_show context.pop('schema', None) # we could update the dataset so we should still be able to read it. context['ignore_auth'] = True output = data_dict['id'] if return_id_only \ else _get_action('package_show')(context, {'id': data_dict['id']}) return output
def history(self, id): group_type = self._ensure_controller_matches_group_type(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: 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, '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, NotAuthorized): abort(404, _('Group 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 Group Revision History'), link=h.url_for( group_type + '_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(group_type), extra_vars={'group_type': group_type})
def members(self, id): ''' Modified core method from 'group' controller. Added search & sort functionality. :param id: id of the organization for which the member list is requested :type id: string :return: the rendered template :rtype: unicode ''' context = self._get_context() q, sort = self._find_filter_params() reverse = True if sort == u'title desc' else False org_meta = org_meta_dao.OrgMetaDao(id, c.user or c.author, c.userobj) org_meta.fetch_all() try: member_list = self._action('member_list')(context, { 'id': id, 'object_type': 'user', 'q': q, 'user_info': True }) member_list.sort(key=lambda y: y[4].lower(), reverse=reverse) member_groups = {} for m in member_list: role = m[3] if not member_groups.get(role): member_groups[role] = [] member_groups[role].append(m) member_groups = collections.OrderedDict( sorted(member_groups.items())) data_dict = {'id': id} data_dict['include_datasets'] = False current_user = self._current_user_info(member_list) is_sysadmin = c.userobj and c.userobj.sysadmin c_params = { 'sort': sort, 'members': [a[0:4] for a in member_list], 'member_groups': member_groups, 'org_meta': org_meta, 'current_user': current_user, 'allow_view_right_side': is_sysadmin or bool(current_user.get('role')), 'allow_approve': is_sysadmin or current_user.get('role') == 'admin', 'request_list': self._get_member_requests_for_org(id) } self._set_c_params(c_params) except NotAuthorized: base.abort(401, _('Unauthorized to view member list %s') % '') except NotFound: base.abort(404, _('Group not found')) except Exception, ex: log.error(str(ex)) base.abort(404, _('Server error'))
def _read(self, id, limit, group_type): ''' This is common code used by both read and bulk_process''' context = {'model': model, 'session': model.Session, 'user': c.user, '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'): fq = 'owner_org:"%s"' % c.group_dict.get('id') else: fq = 'groups:"%s"' % c.group_dict.get('name') c.description_formatted = \ h.render_markdown(c.group_dict.get('description')) context['return_query'] = True page = h.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): controller = lookup_group_controller(group_type) action = 'bulk_process' if c.action == 'bulk_process' else 'read' url = h.url_for(controller=controller, action=action, id=id) params = [(k, v.encode('utf-8') if isinstance(v, string_types) 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): controller = lookup_group_controller(group_type) return h.remove_url_param(key, value=value, replace=replace, controller=controller, 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 = [] c.fields_grouped = {} search_extras = {} 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)) q += ' %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 facets = OrderedDict() default_facet_titles = {'organization': _('Organizations'), 'groups': _('Groups'), 'tags': _('Tags'), 'res_format': _('Formats'), 'license_id': _('Licenses')} for facet in h.facets(): if facet in default_facet_titles: facets[facet] = default_facet_titles[facet] else: facets[facet] = facet # Facet titles self._update_facet_titles(facets, group_type) c.facet_titles = facets data_dict = { 'q': q, 'fq': fq, 'include_private': True, '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.search_facets = query['search_facets'] c.search_facets_limits = {} for facet in c.search_facets.keys(): limit = int(request.params.get('_%s_limit' % facet, config.get('search.facets.default', 10))) c.search_facets_limits[facet] = limit c.page.items = query['results'] c.sort_by_selected = sort_by except search.SearchError as se: log.error('Group search error: %r', se.args) c.query_error = True c.page = h.Page(collection=[]) self._setup_template_variables(context, {'id': id}, group_type=group_type)
def member_new(self, id): ''' Modified core method from 'group' controller. Added the 'user_invite' functionality. Expects POST request. If there's an 'email' parameter in the request and the email is not associated with any user account, a new user account with that email will be created and added as a member with the specific 'role' to the organization. Otherwise a 'username' is expected as a request parameter :param id: id of the organization to which a member should be added. :type id: string :return: the rendered template :rtype: unicode ''' context = self._get_context() # self._check_access('group_delete', context, {'id': id}) try: if request.method == 'POST': data_dict = clean_dict( dict_fns.unflatten( tuplize_dict(parse_params(request.params)))) data_dict['id'] = id invited = False if data_dict.get('email', '').strip() != '' or \ data_dict.get('username', '').strip() != '': email = data_dict.get('email') flash_message = None if email: # Check if email is used user_dict = model.User.by_email(email.strip()) if user_dict: # Is user deleted? if user_dict[0].state == 'deleted': h.flash_error( _('This user no longer has an account on HDX' )) self._redirect_to_this_controller( action='members', id=id) # Add user data_dict['username'] = user_dict[0].name flash_message = 'That email is already associated with user ' + data_dict[ 'username'] + ', who has been added as ' + data_dict[ 'role'] else: user_data_dict = { 'email': email, 'group_id': id, 'role': data_dict['role'], 'id': id # This is something staging/prod need } del data_dict['email'] user_dict = self._action('user_invite')( context, user_data_dict) invited = True data_dict['username'] = user_dict['name'] h.flash_success(email + ' has been invited as ' + data_dict['role']) else: flash_message = data_dict[ 'username'] + ' has been added as ' + data_dict[ 'role'] c.group_dict = self._action('group_member_create')( context, data_dict) user_obj = model.User.get(data_dict['username']) display_name = user_obj.display_name or user_obj.name org_obj = model.Group.get(id) self.notify_admin_users( org_obj, None if invited else [display_name], [email] if invited else None, data_dict['role']) h.flash_success(flash_message) org_obj = model.Group.get(id) analytics.ChangeMemberAnalyticsSender( org_obj.id, org_obj.name).send_to_queue() else: h.flash_error( _('''You need to either fill the username or the email of the person you wish to invite''')) self._redirect_to_this_controller(action='members', id=id) if not request.params['username']: abort(404, _('User not found')) 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)
def index(self): group_type = self._guess_group_type() page = h.get_page_number(request.params) or 1 items_per_page = 21 context = {'model': model, 'session': model.Session, 'user': c.user, 'for_view': True, 'with_private': False} q = c.q = request.params.get('q', '') sort_by = c.sort_by_selected = request.params.get('sort') try: self._check_access('site_read', context) self._check_access('group_list', context) except NotAuthorized: abort(403, _('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 try: data_dict_global_results = { 'all_fields': False, 'q': q, 'sort': sort_by, 'type': group_type or 'group', } global_results = self._action('group_list')( context, data_dict_global_results) except ValidationError as e: if e.error_dict and e.error_dict.get('message'): msg = e.error_dict['message'] else: msg = str(e) h.flash_error(msg) c.page = h.Page([], 0) return render(self._index_template(group_type), extra_vars={'group_type': group_type}) data_dict_page_results = { 'all_fields': True, 'q': q, 'sort': sort_by, 'type': group_type or 'group', 'limit': items_per_page, 'offset': items_per_page * (page - 1), 'include_extras': True } page_results = self._action('group_list')(context, data_dict_page_results) c.page = h.Page( collection=global_results, page=page, url=h.pager_url, items_per_page=items_per_page, ) c.page.items = page_results return render(self._index_template(group_type), extra_vars={'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._ensure_controller_matches_group_type( id.split('@')[0]) # check we are org admin context = {'model': model, 'session': model.Session, 'user': c.user, 'schema': self._db_to_form_schema(group_type=group_type), 'for_view': True, 'extras_as_string': True} data_dict = {'id': id, 'type': group_type} try: self._check_access('bulk_update_public', context, {'org_id': id}) # 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(403, _('User %r not authorized to edit %s') % (c.user, id)) if not c.group_dict['is_organization']: # FIXME: better error raise Exception('Must be an organization') # 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, group_type) c.packages = c.page.items return render(self._bulk_process_template(group_type), extra_vars={'group_type': 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(403, _('Not authorized to perform bulk update')) h.redirect_to(group_type + '_bulk_process', id=id)