Last active
October 16, 2017 23:03
-
-
Save KrzysztofMadejski/4b3a9b996c711eb87a09dcccc9429abd to your computer and use it in GitHub Desktop.
Overrided controllers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 '' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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