From 72d3036ac461dac08e81877107b87feb45d26e54 Mon Sep 17 00:00:00 2001 From: tobes Date: Tue, 22 Jan 2013 09:33:05 +0000 Subject: [PATCH 01/46] [#285] Remove code in base controller that is not used in the ckan codebase --- ckan/lib/base.py | 222 ----------------------------------------------- 1 file changed, 222 deletions(-) diff --git a/ckan/lib/base.py b/ckan/lib/base.py index 4fd2953ed21..3fb56b3bbd7 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -340,119 +340,6 @@ def _set_cors(self): response.headers['Access-Control-Allow-Headers'] = \ "X-CKAN-API-KEY, Authorization, Content-Type" - def _get_user(self, reference): - return model.User.by_name(reference) - - def _get_pkg(self, reference): - return model.Package.get(reference) - - def _get_group(self, reference): - return model.Group.get(reference) - - def _get_tag(self, reference): - return model.Tag.get(reference) - - @classmethod - def _get_request_data(cls, try_url_params=False): - '''Returns a dictionary, extracted from a request. - - If there is no data, None or "" is returned. - ValueError will be raised if the data is not a JSON-formatted dict. - - The data is retrieved as a JSON-encoded dictionary from the request - body. Or, if the `try_url_params` argument is True and the request is - a GET request, then an attempt is made to read the data from the url - parameters of the request. - - try_url_params - If try_url_params is False, then the data_dict is read from the - request body. - - If try_url_params is True and the request is a GET request then the - data is read from the url parameters. The resulting dict will only - be 1 level deep, with the url-param fields being the keys. If a - single key has more than one value specified, then the value will - be a list of strings, otherwise just a string. - - This function is only used by the API, so no strings need to be - translated. - - TODO: If this is only used by the API, then perhaps it should be - moved to the api controller class? - ''' - cls.log.debug('Retrieving request params: %r' % request.params) - cls.log.debug('Retrieving request POST: %r' % request.POST) - cls.log.debug('Retrieving request GET: %r' % request.GET) - request_data = None - if request.POST: - try: - keys = request.POST.keys() - # Parsing breaks if there is a = in the value, so for now - # we will check if the data is actually all in a single key - if keys and request.POST[keys[0]] in [u'1', u'']: - request_data = keys[0] - else: - request_data = urllib.unquote_plus(request.body) - except Exception, inst: - msg = "Could not find the POST data: %r : %s" % \ - (request.POST, inst) - raise ValueError(msg) - - elif try_url_params and request.GET: - return request.GET.mixed() - - else: - try: - if request.method in ['POST', 'PUT']: - request_data = request.body - else: - request_data = None - except Exception, inst: - msg = "Could not extract request body data: %s" % \ - (inst) - raise ValueError(msg) - cls.log.debug('Retrieved request body: %r' % request.body) - if not request_data: - msg = "No request body data" - raise ValueError(msg) - if request_data: - try: - request_data = json.loads(request_data, encoding='utf8') - except ValueError, e: - raise ValueError('Error decoding JSON data. ' - 'Error: %r ' - 'JSON data extracted from the request: %r' % - (e, request_data)) - if not isinstance(request_data, dict): - raise ValueError('Request data JSON decoded to %r but ' - 'it needs to be a dictionary.' % request_data) - # ensure unicode values - for key, val in request_data.items(): - # if val is str then assume it is ascii, since json converts - # utf8 encoded JSON to unicode - request_data[key] = cls._make_unicode(val) - cls.log.debug('Request data extracted: %r' % request_data) - return request_data - - @classmethod - def _make_unicode(cls, entity): - """Cast bare strings and strings in lists or dicts to Unicode - """ - if isinstance(entity, str): - return unicode(entity) - elif isinstance(entity, list): - new_items = [] - for item in entity: - new_items.append(cls._make_unicode(item)) - return new_items - elif isinstance(entity, dict): - new_dict = {} - for key, val in entity.items(): - new_dict[key] = cls._make_unicode(val) - return new_dict - else: - return entity - def _get_user_for_apikey(self): apikey_header_name = config.get(APIKEY_HEADER_NAME_KEY, APIKEY_HEADER_NAME_DEFAULT) @@ -475,115 +362,6 @@ def _get_user_for_apikey(self): user = query.filter_by(apikey=apikey).first() return user - def _get_timing_cache_path(self): - - return path - - def _handle_update_of_authz(self, domain_object): - '''In the event of a post request to a domain object\'s authz form, - work out which of the four possible actions is to be done, - and do it before displaying the page. - - Returns the updated roles for the domain_object. - ''' - from ckan.logic import NotFound, get_action - - context = {'model': model, 'session': model.Session, - 'user': c.user or c.author} - data_dict = {'domain_object': domain_object.id} - - # Work out actions needed, depending on which button was pressed - update_type = 'user' - if 'save' in request.POST: - update_or_add = 'update' - elif 'add' in request.POST: - update_or_add = 'add' - else: - update_type = None - update_or_add = None - - # Work out what role checkboxes are checked or unchecked - checked_roles = [box_id for (box_id, value) in request.params.items() - if (value == u'on')] - unchecked_roles = [box_id for (box_id, value) in request.params.items() - if (value == u'submitted')] - - action = None - if update_or_add is 'update': - # Get user_roles by decoding the checkbox grid - user$role strings - user_roles = {} - for checked_role in checked_roles: - obj_id, role = checked_role.split('$') - if obj_id not in user_roles: - user_roles[obj_id] = [] - user_roles[obj_id].append(role) - # Users without roles need adding to the user_roles too to make - # their roles be deleted - for unchecked_role in unchecked_roles: - obj_id, role = unchecked_role.split('$') - if obj_id not in user_roles: - user_roles[obj_id] = [] - # Convert user_roles to role dictionaries - role_dicts = [] - for user, roles in user_roles.items(): - role_dicts.append({update_type: user, 'roles': roles}) - data_dict['user_roles'] = role_dicts - - action = 'user_role_bulk_update' - success_message = _('Updated') - elif update_or_add is 'add': - # Roles for this new user is a simple list from the checkbox row - data_dict['roles'] = checked_roles - - # User comes from the input box. - new_user = request.params.get('new_user_name') - if new_user: - data_dict[update_type] = new_user - - action = 'user_role_update' - success_message = _('User role(s) added') - else: - h.flash_error(_('Please supply a user name')) - - if action: - try: - roles = get_action(action)(context, data_dict) - except NotFound, e: - h.flash_error(_('Not found') + (': %s' % e if str(e) else '')) - else: - h.flash_success(success_message) - - # Return roles for all users on this domain object - if update_or_add is 'add': - if update_type in data_dict: - del data_dict[update_type] - return get_action('roles_show')(context, data_dict) - - def _prepare_authz_info_for_render(self, user_object_roles): - # ================= - # Display the page - - # Find out all the possible roles. At the moment, any role can be - # associated with any object, so that's easy: - possible_roles = model.Role.get_all() - - # uniquify and sort - users = sorted(list(set([uor['user_id'] - for uor in user_object_roles['roles'] - if uor['user_id']]))) - - # make a dictionary from (user, role) to True, False - users_roles = [(uor['user_id'], uor['role']) - for uor in user_object_roles['roles'] - if uor['user_id']] - user_role_dict = {} - for u in users: - for r in possible_roles: - user_role_dict[(u, r)] = (u, r) in users_roles - - c.roles = possible_roles - c.users = users - c.user_role_dict = user_role_dict # Include the '_' function in the public names __all__ = [__name for __name in locals().keys() if not __name.startswith('_') From 8f91b3ce702dc55d1b68c4542ccea95572b97a5a Mon Sep 17 00:00:00 2001 From: tobes Date: Wed, 23 Jan 2013 08:09:31 +0000 Subject: [PATCH 02/46] [#285] Re add function mis-used in api controller --- ckan/lib/base.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/ckan/lib/base.py b/ckan/lib/base.py index 3fb56b3bbd7..ce06951c2b6 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -363,6 +363,88 @@ def _get_user_for_apikey(self): return user + @classmethod + def _get_request_data(cls, try_url_params=False): + '''Returns a dictionary, extracted from a request. + + If there is no data, None or "" is returned. + ValueError will be raised if the data is not a JSON-formatted dict. + + The data is retrieved as a JSON-encoded dictionary from the request + body. Or, if the `try_url_params` argument is True and the request is + a GET request, then an attempt is made to read the data from the url + parameters of the request. + + try_url_params + If try_url_params is False, then the data_dict is read from the + request body. + + If try_url_params is True and the request is a GET request then the + data is read from the url parameters. The resulting dict will only + be 1 level deep, with the url-param fields being the keys. If a + single key has more than one value specified, then the value will + be a list of strings, otherwise just a string. + + This function is only used by the API, so no strings need to be + translated. + + TODO: If this is only used by the API, then perhaps it should be + moved to the api controller class? + ''' + cls.log.debug('Retrieving request params: %r' % request.params) + cls.log.debug('Retrieving request POST: %r' % request.POST) + cls.log.debug('Retrieving request GET: %r' % request.GET) + request_data = None + if request.POST: + try: + keys = request.POST.keys() + # Parsing breaks if there is a = in the value, so for now + # we will check if the data is actually all in a single key + if keys and request.POST[keys[0]] in [u'1', u'']: + request_data = keys[0] + else: + request_data = urllib.unquote_plus(request.body) + except Exception, inst: + msg = "Could not find the POST data: %r : %s" % \ + (request.POST, inst) + raise ValueError(msg) + + elif try_url_params and request.GET: + return request.GET.mixed() + + else: + try: + if request.method in ['POST', 'PUT']: + request_data = request.body + else: + request_data = None + except Exception, inst: + msg = "Could not extract request body data: %s" % \ + (inst) + raise ValueError(msg) + cls.log.debug('Retrieved request body: %r' % request.body) + if not request_data: + msg = "No request body data" + raise ValueError(msg) + if request_data: + try: + request_data = json.loads(request_data, encoding='utf8') + except ValueError, e: + raise ValueError('Error decoding JSON data. ' + 'Error: %r ' + 'JSON data extracted from the request: %r' % + (e, request_data)) + if not isinstance(request_data, dict): + raise ValueError('Request data JSON decoded to %r but ' + 'it needs to be a dictionary.' % request_data) + # ensure unicode values + for key, val in request_data.items(): + # if val is str then assume it is ascii, since json converts + # utf8 encoded JSON to unicode + request_data[key] = cls._make_unicode(val) + cls.log.debug('Request data extracted: %r' % request_data) + return request_data + # Include the '_' function in the public names __all__ = [__name for __name in locals().keys() if not __name.startswith('_') or __name == '_'] From cdce0fcd538a1ada46fc3f4b9344f1bb32a249e7 Mon Sep 17 00:00:00 2001 From: tobes Date: Wed, 23 Jan 2013 12:07:17 +0000 Subject: [PATCH 03/46] [#285] _make_unicode() still has user --- ckan/lib/base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ckan/lib/base.py b/ckan/lib/base.py index ce06951c2b6..eb38918b02c 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -445,6 +445,24 @@ def _get_request_data(cls, try_url_params=False): cls.log.debug('Request data extracted: %r' % request_data) return request_data + @classmethod + def _make_unicode(cls, entity): + """Cast bare strings and strings in lists or dicts to Unicode + """ + if isinstance(entity, str): + return unicode(entity) + elif isinstance(entity, list): + new_items = [] + for item in entity: + new_items.append(cls._make_unicode(item)) + return new_items + elif isinstance(entity, dict): + new_dict = {} + for key, val in entity.items(): + new_dict[key] = cls._make_unicode(val) + return new_dict + else: + return entity # Include the '_' function in the public names __all__ = [__name for __name in locals().keys() if not __name.startswith('_') or __name == '_'] From 2edc0a875270aad655c0cb66b02f5864c5920582 Mon Sep 17 00:00:00 2001 From: tobes Date: Thu, 24 Jan 2013 12:25:59 +0000 Subject: [PATCH 04/46] [#285] Move function into only function using it --- ckan/lib/base.py | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/ckan/lib/base.py b/ckan/lib/base.py index eb38918b02c..a280fd36245 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -385,12 +385,24 @@ def _get_request_data(cls, try_url_params=False): single key has more than one value specified, then the value will be a list of strings, otherwise just a string. - This function is only used by the API, so no strings need to be - translated. - - TODO: If this is only used by the API, then perhaps it should be - moved to the api controller class? ''' + def make_unicode(entity): + '''Cast bare strings and strings in lists or dicts to Unicode. ''' + if isinstance(entity, str): + return unicode(entity) + elif isinstance(entity, list): + new_items = [] + for item in entity: + new_items.append(make_unicode(item)) + return new_items + elif isinstance(entity, dict): + new_dict = {} + for key, val in entity.items(): + new_dict[key] = make_unicode(val) + return new_dict + else: + return entity + cls.log.debug('Retrieving request params: %r' % request.params) cls.log.debug('Retrieving request POST: %r' % request.POST) cls.log.debug('Retrieving request GET: %r' % request.GET) @@ -441,28 +453,11 @@ def _get_request_data(cls, try_url_params=False): for key, val in request_data.items(): # if val is str then assume it is ascii, since json converts # utf8 encoded JSON to unicode - request_data[key] = cls._make_unicode(val) + request_data[key] = make_unicode(val) cls.log.debug('Request data extracted: %r' % request_data) return request_data - @classmethod - def _make_unicode(cls, entity): - """Cast bare strings and strings in lists or dicts to Unicode - """ - if isinstance(entity, str): - return unicode(entity) - elif isinstance(entity, list): - new_items = [] - for item in entity: - new_items.append(cls._make_unicode(item)) - return new_items - elif isinstance(entity, dict): - new_dict = {} - for key, val in entity.items(): - new_dict[key] = cls._make_unicode(val) - return new_dict - else: - return entity + # Include the '_' function in the public names __all__ = [__name for __name in locals().keys() if not __name.startswith('_') or __name == '_'] From 4e4aea6c95b4ad280d4713d3b90fe3574cfad815 Mon Sep 17 00:00:00 2001 From: tobes Date: Thu, 24 Jan 2013 12:29:45 +0000 Subject: [PATCH 05/46] [#285] Move function into API controller as only user --- ckan/controllers/api.py | 94 ++++++++++++++++++++++++++++++++++++++++ ckan/lib/base.py | 95 ----------------------------------------- 2 files changed, 94 insertions(+), 95 deletions(-) diff --git a/ckan/controllers/api.py b/ckan/controllers/api.py index cf22ba9ad78..c092d6e9533 100644 --- a/ckan/controllers/api.py +++ b/ckan/controllers/api.py @@ -758,3 +758,97 @@ def i18n_js_translations(self, lang): return '{}' f = open(source, 'r') return(f) + + @classmethod + def _get_request_data(cls, try_url_params=False): + '''Returns a dictionary, extracted from a request. + + If there is no data, None or "" is returned. + ValueError will be raised if the data is not a JSON-formatted dict. + + The data is retrieved as a JSON-encoded dictionary from the request + body. Or, if the `try_url_params` argument is True and the request is + a GET request, then an attempt is made to read the data from the url + parameters of the request. + + try_url_params + If try_url_params is False, then the data_dict is read from the + request body. + + If try_url_params is True and the request is a GET request then the + data is read from the url parameters. The resulting dict will only + be 1 level deep, with the url-param fields being the keys. If a + single key has more than one value specified, then the value will + be a list of strings, otherwise just a string. + + ''' + def make_unicode(entity): + '''Cast bare strings and strings in lists or dicts to Unicode. ''' + if isinstance(entity, str): + return unicode(entity) + elif isinstance(entity, list): + new_items = [] + for item in entity: + new_items.append(make_unicode(item)) + return new_items + elif isinstance(entity, dict): + new_dict = {} + for key, val in entity.items(): + new_dict[key] = make_unicode(val) + return new_dict + else: + return entity + + cls.log.debug('Retrieving request params: %r' % request.params) + cls.log.debug('Retrieving request POST: %r' % request.POST) + cls.log.debug('Retrieving request GET: %r' % request.GET) + request_data = None + if request.POST: + try: + keys = request.POST.keys() + # Parsing breaks if there is a = in the value, so for now + # we will check if the data is actually all in a single key + if keys and request.POST[keys[0]] in [u'1', u'']: + request_data = keys[0] + else: + request_data = urllib.unquote_plus(request.body) + except Exception, inst: + msg = "Could not find the POST data: %r : %s" % \ + (request.POST, inst) + raise ValueError(msg) + + elif try_url_params and request.GET: + return request.GET.mixed() + + else: + try: + if request.method in ['POST', 'PUT']: + request_data = request.body + else: + request_data = None + except Exception, inst: + msg = "Could not extract request body data: %s" % \ + (inst) + raise ValueError(msg) + cls.log.debug('Retrieved request body: %r' % request.body) + if not request_data: + msg = "No request body data" + raise ValueError(msg) + if request_data: + try: + request_data = json.loads(request_data, encoding='utf8') + except ValueError, e: + raise ValueError('Error decoding JSON data. ' + 'Error: %r ' + 'JSON data extracted from the request: %r' % + (e, request_data)) + if not isinstance(request_data, dict): + raise ValueError('Request data JSON decoded to %r but ' + 'it needs to be a dictionary.' % request_data) + # ensure unicode values + for key, val in request_data.items(): + # if val is str then assume it is ascii, since json converts + # utf8 encoded JSON to unicode + request_data[key] = make_unicode(val) + cls.log.debug('Request data extracted: %r' % request_data) + return request_data diff --git a/ckan/lib/base.py b/ckan/lib/base.py index a280fd36245..3fb56b3bbd7 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -363,101 +363,6 @@ def _get_user_for_apikey(self): return user - @classmethod - def _get_request_data(cls, try_url_params=False): - '''Returns a dictionary, extracted from a request. - - If there is no data, None or "" is returned. - ValueError will be raised if the data is not a JSON-formatted dict. - - The data is retrieved as a JSON-encoded dictionary from the request - body. Or, if the `try_url_params` argument is True and the request is - a GET request, then an attempt is made to read the data from the url - parameters of the request. - - try_url_params - If try_url_params is False, then the data_dict is read from the - request body. - - If try_url_params is True and the request is a GET request then the - data is read from the url parameters. The resulting dict will only - be 1 level deep, with the url-param fields being the keys. If a - single key has more than one value specified, then the value will - be a list of strings, otherwise just a string. - - ''' - def make_unicode(entity): - '''Cast bare strings and strings in lists or dicts to Unicode. ''' - if isinstance(entity, str): - return unicode(entity) - elif isinstance(entity, list): - new_items = [] - for item in entity: - new_items.append(make_unicode(item)) - return new_items - elif isinstance(entity, dict): - new_dict = {} - for key, val in entity.items(): - new_dict[key] = make_unicode(val) - return new_dict - else: - return entity - - cls.log.debug('Retrieving request params: %r' % request.params) - cls.log.debug('Retrieving request POST: %r' % request.POST) - cls.log.debug('Retrieving request GET: %r' % request.GET) - request_data = None - if request.POST: - try: - keys = request.POST.keys() - # Parsing breaks if there is a = in the value, so for now - # we will check if the data is actually all in a single key - if keys and request.POST[keys[0]] in [u'1', u'']: - request_data = keys[0] - else: - request_data = urllib.unquote_plus(request.body) - except Exception, inst: - msg = "Could not find the POST data: %r : %s" % \ - (request.POST, inst) - raise ValueError(msg) - - elif try_url_params and request.GET: - return request.GET.mixed() - - else: - try: - if request.method in ['POST', 'PUT']: - request_data = request.body - else: - request_data = None - except Exception, inst: - msg = "Could not extract request body data: %s" % \ - (inst) - raise ValueError(msg) - cls.log.debug('Retrieved request body: %r' % request.body) - if not request_data: - msg = "No request body data" - raise ValueError(msg) - if request_data: - try: - request_data = json.loads(request_data, encoding='utf8') - except ValueError, e: - raise ValueError('Error decoding JSON data. ' - 'Error: %r ' - 'JSON data extracted from the request: %r' % - (e, request_data)) - if not isinstance(request_data, dict): - raise ValueError('Request data JSON decoded to %r but ' - 'it needs to be a dictionary.' % request_data) - # ensure unicode values - for key, val in request_data.items(): - # if val is str then assume it is ascii, since json converts - # utf8 encoded JSON to unicode - request_data[key] = make_unicode(val) - cls.log.debug('Request data extracted: %r' % request_data) - return request_data - - # Include the '_' function in the public names __all__ = [__name for __name in locals().keys() if not __name.startswith('_') or __name == '_'] From 7e51ec6bc5fcaa80610804738ec66e416e46ab1d Mon Sep 17 00:00:00 2001 From: tobes Date: Thu, 24 Jan 2013 13:18:15 +0000 Subject: [PATCH 06/46] [#285] Fix missing imports --- ckan/controllers/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ckan/controllers/api.py b/ckan/controllers/api.py index c092d6e9533..cb9653241d6 100644 --- a/ckan/controllers/api.py +++ b/ckan/controllers/api.py @@ -3,6 +3,8 @@ import cgi import datetime import glob +import urllib +import simplejson as json from pylons import c, request, response from pylons.i18n import _, gettext From 1d5d602eb28723c3c261f9a8a0aa14e815a1af8a Mon Sep 17 00:00:00 2001 From: tobes Date: Thu, 24 Jan 2013 18:21:37 +0000 Subject: [PATCH 07/46] [#285] Use json defined in h --- ckan/controllers/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ckan/controllers/api.py b/ckan/controllers/api.py index cb9653241d6..db87ceda02b 100644 --- a/ckan/controllers/api.py +++ b/ckan/controllers/api.py @@ -4,7 +4,6 @@ import datetime import glob import urllib -import simplejson as json from pylons import c, request, response from pylons.i18n import _, gettext @@ -838,7 +837,7 @@ def make_unicode(entity): raise ValueError(msg) if request_data: try: - request_data = json.loads(request_data, encoding='utf8') + request_data = h.json.loads(request_data, encoding='utf8') except ValueError, e: raise ValueError('Error decoding JSON data. ' 'Error: %r ' From cdd77c9d9f114fd2891cdc10300fb930f2eb5c82 Mon Sep 17 00:00:00 2001 From: tobes Date: Fri, 25 Jan 2013 01:42:24 +0000 Subject: [PATCH 08/46] [#252] Check_access helper function does not need data_dict supplying --- ckan/lib/helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index 7fc9bf12214..1a7ceca738b 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -568,6 +568,8 @@ def check_access(action, data_dict=None): context = {'model': model, 'user': c.user or c.author} + if not data_dict: + data_dict = {} try: check_access_logic(action, context, data_dict) From fc7dc1eaded8c3978b8a43f081f6e9fc7ebc82b0 Mon Sep 17 00:00:00 2001 From: tobes Date: Fri, 25 Jan 2013 01:43:23 +0000 Subject: [PATCH 09/46] [#252] Only show add related item button if permission to add --- ckan/templates/snippets/related.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ckan/templates/snippets/related.html b/ckan/templates/snippets/related.html index 4a4395cc2e3..1d501677bfd 100644 --- a/ckan/templates/snippets/related.html +++ b/ckan/templates/snippets/related.html @@ -12,9 +12,11 @@

{{ item.title }}

{% endwith %} {% else %} -

No apps, ideas, news stories or images have been - related to this dataset yet, why not add one now?

-

{% link_for _('Add Item'), controller='related', action='new', id=pkg_name, icon='plus', class_='btn' %}

+

{% trans %}No apps, ideas, news stories or images have been + related to this dataset yet.{% endtrans %}

+ {% if h.check_access('related_create') %} +

{% link_for _('Add Item'), controller='related', action='new', id=pkg_name, icon='plus', class_='btn' %}

+ {% endif %} {% endif %} From 22ec8c182e58b3b27c5f1e19b55209337aac1754 Mon Sep 17 00:00:00 2001 From: tobes Date: Fri, 25 Jan 2013 02:41:38 +0000 Subject: [PATCH 10/46] [#306] Fix error where icon passed to qs in links --- ckan/lib/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index 7fc9bf12214..c0f63d0c143 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -322,11 +322,11 @@ def _create_link_text(text, **kwargs): kwargs ''' if kwargs.pop('inner_span', None): text = literal('') + text + literal('') - icon = kwargs.pop('icon', None) if icon: text = literal(' ' % icon) + text return text + icon = kwargs.pop('icon', None) class_ = _link_class(kwargs) return link_to( _create_link_text(text, **kwargs), From fa213d94e5a7d887813d71dffba086fb0540eb41 Mon Sep 17 00:00:00 2001 From: kindly Date: Fri, 25 Jan 2013 23:05:53 +0000 Subject: [PATCH 11/46] 309 no private datasets in activity streams, revisions, group listing and tag listings --- ckan/controllers/revision.py | 7 +++++-- ckan/lib/activity_streams_session_extension.py | 9 +++++++++ ckan/lib/dictization/model_dictize.py | 17 ++++++++++++++--- ckan/logic/action/get.py | 11 +---------- ckan/model/__init__.py | 3 ++- ckan/tests/functional/api/model/test_package.py | 1 - ckan/tests/functional/api/model/test_tag.py | 3 +++ ckan/tests/logic/test_tag.py | 2 ++ 8 files changed, 36 insertions(+), 17 deletions(-) diff --git a/ckan/controllers/revision.py b/ckan/controllers/revision.py index b3e6a5dfdf9..84683c1efc9 100644 --- a/ckan/controllers/revision.py +++ b/ckan/controllers/revision.py @@ -69,6 +69,8 @@ def list(self): # but in the meantime while that is fixed, # avoid an exception here continue + if package.private: + continue number = len(package.all_revisions) package_revision = None count = 0 @@ -142,10 +144,11 @@ def read(self, id=None): pkgs = model.Session.query(model.PackageRevision).\ filter_by(revision=c.revision) - c.packages = [pkg.continuity for pkg in pkgs] + c.packages = [pkg.continuity for pkg in pkgs if not pkg.private] pkgtags = model.Session.query(model.PackageTagRevision).\ filter_by(revision=c.revision) - c.pkgtags = [pkgtag.continuity for pkgtag in pkgtags] + c.pkgtags = [pkgtag.continuity for pkgtag in pkgtags + if not pkgtag.package.private] grps = model.Session.query(model.GroupRevision).\ filter_by(revision=c.revision) c.groups = [grp.continuity for grp in grps] diff --git a/ckan/lib/activity_streams_session_extension.py b/ckan/lib/activity_streams_session_extension.py index 9fbc1a67e7f..c2d9713fff2 100644 --- a/ckan/lib/activity_streams_session_extension.py +++ b/ckan/lib/activity_streams_session_extension.py @@ -79,6 +79,11 @@ def before_commit(self, session): # object is a package. logger.debug("Looks like this object is a package") logger.debug("activity: %s" % activity) + ## Ignore private datasets for 2.0, + ## fix later to have privicy against streams + if obj.private: + continue + activities[obj.id] = activity activity_detail = activity_stream_detail(obj, activity.id, "new") @@ -113,6 +118,10 @@ def before_commit(self, session): for package in related_packages: if package is None: continue + ## Ignore private datasets for 2.0, + ## fix later to have privicy against streams + if package.private: + continue if package.id in activities: activity = activities[package.id] diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index 40cc566624d..1c7865e9516 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -9,6 +9,7 @@ import ckan.lib.helpers as h import ckan.lib.dictization as d import ckan.new_authz as new_authz +import ckan.lib.search as search ## package save @@ -302,11 +303,15 @@ def _get_members(context, group, member_type): model = context['model'] Entity = getattr(model, member_type[:-1].capitalize()) - return model.Session.query(Entity, model.Member.capacity).\ + q = model.Session.query(Entity, model.Member.capacity).\ join(model.Member, model.Member.table_id == Entity.id).\ filter(model.Member.group_id == group.id).\ filter(model.Member.state == 'active').\ - filter(model.Member.table_name == member_type[:-1]).all() + filter(model.Member.table_name == member_type[:-1]) + if member_type == 'packages': + q = q.filter(Entity.private==False) + return q.all() + def group_dictize(group, context): model = context['model'] @@ -376,8 +381,14 @@ def tag_list_dictize(tag_list, context): def tag_dictize(tag, context): result_dict = d.table_dictize(tag, context) - result_dict["packages"] = d.obj_list_dictize(tag.packages, context) + query = search.PackageSearchQuery() + + q = {'q': '+tags:"%s" +capacity:public' % tag.name, 'fl': 'data_dict', + 'wt': 'json', 'rows': 1000} + result_dict["packages"] = [ + h.json.loads(result['data_dict']) for result in query.run(q)['results'] + ] # Add display_names to tags. At first a tag's display_name is just the # same as its name, but the display_name might get changed later (e.g. # translated into another language by the multilingual extension). diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 5a6525dfa58..331b8be3c72 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -929,16 +929,7 @@ def tag_show(context, data_dict): _check_access('tag_show',context, data_dict) - tag_dict = model_dictize.tag_dictize(tag,context) - - extended_packages = [] - for package in tag_dict['packages']: - pkg = model.Package.get(package['id']) - extended_packages.append(model_dictize.package_dictize(pkg,context)) - - tag_dict['packages'] = extended_packages - - return tag_dict + return model_dictize.tag_dictize(tag,context) def user_show(context, data_dict): '''Return a user account. diff --git a/ckan/model/__init__.py b/ckan/model/__init__.py index 94d30d9c0be..6cc0914f09e 100644 --- a/ckan/model/__init__.py +++ b/ckan/model/__init__.py @@ -460,7 +460,8 @@ def revision_as_dict(revision, include_packages=True, include_groups=True, )) if include_packages: revision_dict['packages'] = [getattr(pkg, ref_package_by) \ - for pkg in revision.packages if pkg] + for pkg in revision.packages + if (pkg and not pkg.private)] if include_groups: revision_dict['groups'] = [getattr(grp, ref_package_by) \ for grp in revision.groups if grp] diff --git a/ckan/tests/functional/api/model/test_package.py b/ckan/tests/functional/api/model/test_package.py index e5a22bf0c7b..84563eb94ae 100644 --- a/ckan/tests/functional/api/model/test_package.py +++ b/ckan/tests/functional/api/model/test_package.py @@ -279,7 +279,6 @@ def test_register_post_indexerror(self): self.post_json(offset, data, status=500, extra_environ=self.admin_extra_environ) model.Session.remove() finally: - plugins.unload('synchronous_search') SolrSettings.init(original_settings) def test_register_post_tag_too_long(self): diff --git a/ckan/tests/functional/api/model/test_tag.py b/ckan/tests/functional/api/model/test_tag.py index 365d24df594..f9f0afdaa2a 100644 --- a/ckan/tests/functional/api/model/test_tag.py +++ b/ckan/tests/functional/api/model/test_tag.py @@ -4,6 +4,7 @@ from ckan import model from ckan.lib.create_test_data import CreateTestData +import ckan.lib.search as search from ckan.tests.functional.api.base import BaseModelApiTestCase from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase @@ -14,6 +15,7 @@ class TagsTestCase(BaseModelApiTestCase): @classmethod def setup_class(cls): + search.clear() CreateTestData.create() cls.testsysadmin = model.User.by_name(u'testsysadmin') cls.comment = u'Comment umlaut: \xfc.' @@ -22,6 +24,7 @@ def setup_class(cls): @classmethod def teardown_class(cls): + search.clear() model.repo.rebuild_db() def test_register_get_ok(self): diff --git a/ckan/tests/logic/test_tag.py b/ckan/tests/logic/test_tag.py index 1f710ab6563..82d0cdd1e1e 100644 --- a/ckan/tests/logic/test_tag.py +++ b/ckan/tests/logic/test_tag.py @@ -1,6 +1,7 @@ import json from pprint import pprint from nose.tools import assert_equal, assert_raises +import ckan.lib.search as search import ckan.model as model from ckan.lib.create_test_data import CreateTestData @@ -10,6 +11,7 @@ class TestAction(WsgiAppCase): @classmethod def setup_class(cls): + search.clear() CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') cls.normal_user = model.User.get('annafan') From c14558a1b4113a07365300469975bc6338ec00cd Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Sat, 26 Jan 2013 19:11:21 +0100 Subject: [PATCH 12/46] Update issue tracker link in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fce4eeea262..0a2562fced5 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ Community * Developer mailing list: `ckan-dev@lists.okfn.org `_ * Developer IRC channel: `#ckan on irc.freenode.net `_ -* Issue tracker: `trac.ckan.org `_ +* `Issue tracker `_ * `CKAN tag on StackOverflow `_ From 90b04f17615b28569ab6e33c3a48994bcbf4cc9d Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Sat, 26 Jan 2013 19:14:42 +0100 Subject: [PATCH 13/46] Update contributing link in README --- README.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 0a2562fced5..17fce2e20d0 100644 --- a/README.rst +++ b/README.rst @@ -25,17 +25,14 @@ Community * Developer IRC channel: `#ckan on irc.freenode.net `_ * `Issue tracker `_ * `CKAN tag on StackOverflow `_ +* `Wiki `_ Contributing to CKAN -------------------- -CKAN is a free software project and code contributions are welcome. -The `For CKAN Developers `_ -section of the documentation explains how to contribute to CKAN or its documentation, -including our **coding standards**. - -The `CKAN Wiki `_ is also open fo contributions. +For contributing to CKAN or its documentation, see +`CONTRIBUTING `_. Copying and License From d294d0dcc9e6c95fdee4ac6aab1068777f6c4fd9 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sun, 27 Jan 2013 00:32:52 +0100 Subject: [PATCH 14/46] [#287] Add is_datastore_supported method to tests and engine_is_pg to model --- ckan/model/__init__.py | 1 + ckan/model/meta.py | 8 +++++++- ckan/tests/__init__.py | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ckan/model/__init__.py b/ckan/model/__init__.py index 94d30d9c0be..f6373130cec 100644 --- a/ckan/model/__init__.py +++ b/ckan/model/__init__.py @@ -13,6 +13,7 @@ from meta import ( Session, engine_is_sqlite, + engine_is_pg, ) from core import ( System, diff --git a/ckan/model/meta.py b/ckan/model/meta.py index 41d67541a1b..13dcf1cb1fc 100644 --- a/ckan/model/meta.py +++ b/ckan/model/meta.py @@ -10,7 +10,7 @@ import extension import ckan.lib.activity_streams_session_extension as activity -__all__ = ['Session', 'engine_is_sqlite'] +__all__ = ['Session', 'engine_is_sqlite', 'engine_is_pg'] class CkanCacheExtension(SessionExtension): @@ -158,3 +158,9 @@ def engine_is_sqlite(): Returns true iff the engine is connected to a sqlite database. """ return engine.url.drivername == 'sqlite' + +def engine_is_pg(): + """ + Returns true iff the engine is connected to a postgresql database. + """ + return engine.url.drivername == 'psycopg2' diff --git a/ckan/tests/__init__.py b/ckan/tests/__init__.py index 0d4be8b4e49..1a0aa934cc1 100644 --- a/ckan/tests/__init__.py +++ b/ckan/tests/__init__.py @@ -330,6 +330,10 @@ def is_migration_supported(): is_supported_db = not model.engine_is_sqlite() return is_supported_db +def is_datastore_supported(): + is_supported_db = model.engine_is_pg() + return is_supported_db + def search_related(test): def skip_test(*args): raise SkipTest("Search not supported") From 23c2bcaf93e9f607b57e5421d6274579a0314c4e Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sun, 27 Jan 2013 00:59:24 +0100 Subject: [PATCH 15/46] [#287] Log warning if the database is not a pg database. Skip datastore tests, if there is no support for them. --- ckanext/datastore/plugin.py | 32 +++++++++++++++----------- ckanext/datastore/tests/test_create.py | 3 +++ ckanext/datastore/tests/test_delete.py | 3 +++ ckanext/datastore/tests/test_search.py | 7 ++++++ ckanext/datastore/tests/test_upsert.py | 7 ++++++ 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/ckanext/datastore/plugin.py b/ckanext/datastore/plugin.py index 98f8cd92af1..412ba37f48c 100644 --- a/ckanext/datastore/plugin.py +++ b/ckanext/datastore/plugin.py @@ -1,11 +1,13 @@ import logging import pylons from sqlalchemy.exc import ProgrammingError + import ckan.plugins as p import ckanext.datastore.logic.action as action import ckanext.datastore.logic.auth as auth import ckanext.datastore.db as db import ckan.logic as logic +import ckan.model as model log = logging.getLogger(__name__) _get_or_bust = logic.get_or_bust @@ -52,21 +54,25 @@ def configure(self, config): else: self.read_url = self.config['ckan.datastore.read_url'] - if not self._is_read_only_database(): - # Make sure that the right permissions are set - # so that no harmful queries can be made - if not ('debug' in config and config['debug']): - self._check_separate_db() - if self.legacy_mode: - log.warn('Legacy mode active. The sql search will not be available.') - else: - self._check_read_permissions() + if model.engine_is_pg(): + if not self._is_read_only_database(): + # Make sure that the right permissions are set + # so that no harmful queries can be made + if not ('debug' in config and config['debug']): + self._check_separate_db() + if self.legacy_mode: + log.warn('Legacy mode active. The sql search will not be available.') + else: + self._check_read_permissions() - self._create_alias_table() + self._create_alias_table() + else: + log.warn("We detected that CKAN is running on a read only database. " + "Permission checks and _table_metadata creation are skipped." + "Make sure that replication is properly set-up.") else: - log.warn("We detected that CKAN is running on a read only database. " - "Permission checks and _table_metadata creation are skipped." - "Make sure that replication is properly set-up.") + log.warn("We detected that you do not use a PostgreSQL database. " + "The DataStore will NOT work and datastore tests will be skipped.") ## Do light wrapping around action function to add datastore_active ## to resource dict. Not using IAction extension as this prevents other plugins diff --git a/ckanext/datastore/tests/test_create.py b/ckanext/datastore/tests/test_create.py index 4fd7da6b48c..d4afaf01fd4 100644 --- a/ckanext/datastore/tests/test_create.py +++ b/ckanext/datastore/tests/test_create.py @@ -1,4 +1,5 @@ import json +import nose import sqlalchemy.orm as orm @@ -18,6 +19,8 @@ class TestDatastoreCreate(tests.WsgiAppCase): @classmethod def setup_class(cls): + if not tests.is_datastore_supported(): + raise nose.SkipTest("Datastore not supported") p.load('datastore') ctd.CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') diff --git a/ckanext/datastore/tests/test_delete.py b/ckanext/datastore/tests/test_delete.py index 41a43431d5e..01333142585 100644 --- a/ckanext/datastore/tests/test_delete.py +++ b/ckanext/datastore/tests/test_delete.py @@ -1,4 +1,5 @@ import json +import nose import sqlalchemy import sqlalchemy.orm as orm @@ -19,6 +20,8 @@ class TestDatastoreDelete(tests.WsgiAppCase): @classmethod def setup_class(cls): + if not tests.is_datastore_supported(): + raise nose.SkipTest("Datastore not supported") p.load('datastore') ctd.CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') diff --git a/ckanext/datastore/tests/test_search.py b/ckanext/datastore/tests/test_search.py index 36fe8556a31..44e6a4f5ac8 100644 --- a/ckanext/datastore/tests/test_search.py +++ b/ckanext/datastore/tests/test_search.py @@ -1,4 +1,5 @@ import json +import nose import pprint import sqlalchemy.orm as orm @@ -18,6 +19,8 @@ class TestDatastoreSearch(tests.WsgiAppCase): @classmethod def setup_class(cls): + if not tests.is_datastore_supported(): + raise nose.SkipTest("Datastore not supported") p.load('datastore') ctd.CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') @@ -331,6 +334,8 @@ def test_search_table_metadata(self): class TestDatastoreFullTextSearch(tests.WsgiAppCase): @classmethod def setup_class(cls): + if not tests.is_datastore_supported(): + raise nose.SkipTest("Datastore not supported") p.load('datastore') ctd.CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') @@ -397,6 +402,8 @@ class TestDatastoreSQL(tests.WsgiAppCase): @classmethod def setup_class(cls): + if not tests.is_datastore_supported(): + raise nose.SkipTest("Datastore not supported") p.load('datastore') ctd.CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') diff --git a/ckanext/datastore/tests/test_upsert.py b/ckanext/datastore/tests/test_upsert.py index fa8016a6d3c..de32c376f4c 100644 --- a/ckanext/datastore/tests/test_upsert.py +++ b/ckanext/datastore/tests/test_upsert.py @@ -1,4 +1,5 @@ import json +import nose import datetime import sqlalchemy.orm as orm @@ -19,6 +20,8 @@ class TestDatastoreUpsert(tests.WsgiAppCase): @classmethod def setup_class(cls): + if not tests.is_datastore_supported(): + raise nose.SkipTest("Datastore not supported") p.load('datastore') ctd.CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') @@ -243,6 +246,8 @@ class TestDatastoreInsert(tests.WsgiAppCase): @classmethod def setup_class(cls): + if not tests.is_datastore_supported(): + raise nose.SkipTest("Datastore not supported") p.load('datastore') ctd.CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') @@ -344,6 +349,8 @@ class TestDatastoreUpdate(tests.WsgiAppCase): @classmethod def setup_class(cls): + if not tests.is_datastore_supported(): + raise nose.SkipTest("Datastore not supported") p.load('datastore') ctd.CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') From 6e33f76b18db06e6f1b643cf7f9645daba059ac9 Mon Sep 17 00:00:00 2001 From: kindly Date: Mon, 28 Jan 2013 10:16:30 +0000 Subject: [PATCH 16/46] make group_list_dictize reasonably fast by going through search index --- ckan/lib/dictization/model_dictize.py | 12 ++++++++++-- ckan/tests/functional/test_group.py | 2 ++ ckan/tests/logic/test_action.py | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index 1c7865e9516..ab6a4f107d0 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -18,6 +18,15 @@ def group_list_dictize(obj_list, context, active = context.get('active', True) with_private = context.get('include_private_packages', False) + + query = search.PackageSearchQuery() + + q = {'q': '+capacity:public' if not with_private else '*:*', + 'fl': 'groups', 'facet.field': ['groups'], + 'facet.limit': -1, 'rows': 1} + + query.run(q) + result_list = [] for obj in obj_list: @@ -32,8 +41,7 @@ def group_list_dictize(obj_list, context, group_dict['display_name'] = obj.display_name - group_dict['packages'] = \ - len(obj.packages(with_private=with_private, context=context)) + group_dict['packages'] = query.facets['groups'].get(obj.name, 0) if context.get('for_view'): if group_dict['is_organization']: diff --git a/ckan/tests/functional/test_group.py b/ckan/tests/functional/test_group.py index cbe1fb52f13..c45090a6a7d 100644 --- a/ckan/tests/functional/test_group.py +++ b/ckan/tests/functional/test_group.py @@ -8,6 +8,7 @@ import ckan.model as model from ckan.lib.create_test_data import CreateTestData from ckan.logic import check_access, NotAuthorized, get_action +import ckan.lib.search as search from pylons import config @@ -51,6 +52,7 @@ class TestGroup(FunctionalTestCase): @classmethod def setup_class(self): + search.clear() model.Session.remove() CreateTestData.create() diff --git a/ckan/tests/logic/test_action.py b/ckan/tests/logic/test_action.py index 6f418edfce3..1e115b24374 100644 --- a/ckan/tests/logic/test_action.py +++ b/ckan/tests/logic/test_action.py @@ -16,6 +16,7 @@ from ckan.logic import get_action, NotAuthorized from ckan.logic.action import get_domain_object from ckan.tests import TestRoles +import ckan.lib.search as search from ckan import plugins from ckan.plugins import SingletonPlugin, implements, IPackageController @@ -28,6 +29,7 @@ class TestAction(WsgiAppCase): @classmethod def setup_class(cls): + search.clear() CreateTestData.create() cls.sysadmin_user = model.User.get('testsysadmin') cls.normal_user = model.User.get('annafan') From 2a74c8dc16900ce364a86ddeed746b3f5ac90661 Mon Sep 17 00:00:00 2001 From: kindly Date: Mon, 28 Jan 2013 23:15:48 +0000 Subject: [PATCH 17/46] [#309] add package count using search index --- ckan/controllers/home.py | 1 + ckan/lib/dictization/model_dictize.py | 6 ++++++ ckan/logic/action/get.py | 1 + ckan/templates/snippets/organization.html | 2 +- ckan/tests/lib/test_dictization.py | 7 ++++++- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ckan/controllers/home.py b/ckan/controllers/home.py index 1fe85d47b56..a766771cd39 100644 --- a/ckan/controllers/home.py +++ b/ckan/controllers/home.py @@ -125,6 +125,7 @@ def db_to_form_schema(group_type=None): 'ignore_auth': True, 'user': c.user or c.author, 'schema': db_to_form_schema(group_type=group_type), + 'limits': {'packages': 2}, 'for_view': True} data_dict = {'id': id} diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index ab6a4f107d0..490b5601fc5 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -318,6 +318,8 @@ def _get_members(context, group, member_type): filter(model.Member.table_name == member_type[:-1]) if member_type == 'packages': q = q.filter(Entity.private==False) + if 'limits' in context and member_type in context['limits']: + return q[:context['limits'][member_type]] return q.all() @@ -336,6 +338,10 @@ def group_dictize(group, context): _get_members(context, group, 'packages'), context) + query = search.PackageSearchQuery() + q = {'q': 'groups:"%s" +capacity:public' % group.name, 'rows': 1} + result_dict['package_count'] = query.run(q)['count'] + result_dict['tags'] = tag_list_dictize( _get_members(context, group, 'tags'), context) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 331b8be3c72..99dac9f194c 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -824,6 +824,7 @@ def _group_or_org_show(context, data_dict, is_org=False): _check_access('organization_show',context, data_dict) else: _check_access('group_show',context, data_dict) + group_dict = model_dictize.group_dictize(group, context) diff --git a/ckan/templates/snippets/organization.html b/ckan/templates/snippets/organization.html index 8988a4c1338..d0fddf914bd 100644 --- a/ckan/templates/snippets/organization.html +++ b/ckan/templates/snippets/organization.html @@ -35,7 +35,7 @@

{{ organization.title or organization.name }}

{{ _('Datasets') }}
-
{{ h.SI_number_span(organization.packages|length) }}
+
{{ h.SI_number_span(organization.package_count) }}
diff --git a/ckan/tests/lib/test_dictization.py b/ckan/tests/lib/test_dictization.py index c78c9365dc0..61828f0a27d 100644 --- a/ckan/tests/lib/test_dictization.py +++ b/ckan/tests/lib/test_dictization.py @@ -1,6 +1,7 @@ from ckan.tests import assert_equal, assert_not_in, assert_in from pprint import pprint, pformat from difflib import unified_diff +import ckan.lib.search as search from ckan.lib.create_test_data import CreateTestData from ckan import model @@ -32,6 +33,7 @@ class TestBasicDictize: def setup_class(cls): # clean the db so we can run these tests on their own model.repo.rebuild_db() + search.clear() CreateTestData.create() cls.package_expected = { @@ -928,6 +930,7 @@ def test_16_group_dictized(self): 'name': u'help', 'display_name': u'help', 'image_url': u'', + 'package_count': 2, 'is_organization': False, 'packages': [{'author': None, 'author_email': None, @@ -969,10 +972,12 @@ def test_16_group_dictized(self): result['packages'] = sorted(result['packages'], key=lambda x: x['name']) assert_equal(sorted(result.keys()), sorted(expected.keys())) + for key in result: - if key == 'is_organization': + if key in ('is_organization', 'package_count'): continue assert_equal(sorted(result[key]), sorted(expected[key])) + assert_equal(result['package_count'], expected['package_count']) def test_17_group_apis_to_dict(self): From 1fa0b1a464a44019fc5c65277f0cd7e95222b178 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 30 Jan 2013 10:12:17 +0000 Subject: [PATCH 18/46] [#257] Set timeout to 10 seconds in recline source --- ckanext/reclinepreview/theme/public/vendor/recline/recline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckanext/reclinepreview/theme/public/vendor/recline/recline.js b/ckanext/reclinepreview/theme/public/vendor/recline/recline.js index 083279fc2a3..912773291b8 100644 --- a/ckanext/reclinepreview/theme/public/vendor/recline/recline.js +++ b/ckanext/reclinepreview/theme/public/vendor/recline/recline.js @@ -423,7 +423,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; my.dataproxy_url = 'http://jsonpdataproxy.appspot.com'; // Timeout for dataproxy (after this time if no response we error) // Needed because use JSONP so do not receive e.g. 500 errors - my.timeout = 5000; + my.timeout = 10000; // ## load // From e024873f596d481137845951af57988b077969fb Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 12:15:40 +0100 Subject: [PATCH 19/46] [#316] Disable follower lists Disable follower lists for normal users Make it so that only sysadmins can see a dataset, user or group's list of followers. Add auth functions to user, dataset and group follower_list functions. user controller, group controller, package controller: /followers pages send 401 if not authorized Remove followers tabs from user, dataset and group pages. Some tests need to be fixed now --- ckan/controllers/group.py | 6 +++++- ckan/controllers/user.py | 5 ++++- ckan/logic/action/get.py | 3 +++ ckan/logic/auth/get.py | 12 ++++++++++++ ckan/templates/group/read_base.html | 3 --- ckan/templates/package/read_base.html | 1 - ckan/templates/user/read_base.html | 1 - 7 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index f9e21baaf38..57395125e2c 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -673,7 +673,11 @@ def followers(self, id): context = {'model': model, 'session': model.Session, 'user': c.user or c.author} c.group_dict = self._get_group_dict(id) - c.followers = get_action('group_follower_list')(context, {'id': 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): diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index d4eea07fcc1..ca5f4899577 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -479,7 +479,10 @@ def followers(self, id=None): data_dict = {'id': id, 'user_obj': c.userobj} self._setup_template_variables(context, data_dict) f = get_action('user_follower_list') - c.followers = f(context, {'id': c.user_dict['id']}) + 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): diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 5a6525dfa58..9d69f537613 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2228,6 +2228,7 @@ def user_follower_list(context, data_dict): :rtype: list of dictionaries ''' + _check_access('user_follower_list', context, data_dict) return _follower_list(context, data_dict, ckan.logic.schema.default_follow_user_schema(), context['model'].UserFollowingUser) @@ -2242,6 +2243,7 @@ def dataset_follower_list(context, data_dict): :rtype: list of dictionaries ''' + _check_access('dataset_follower_list', context, data_dict) return _follower_list(context, data_dict, ckan.logic.schema.default_follow_dataset_schema(), context['model'].UserFollowingDataset) @@ -2256,6 +2258,7 @@ def group_follower_list(context, data_dict): :rtype: list of dictionaries ''' + _check_access('group_follower_list', context, data_dict) return _follower_list(context, data_dict, ckan.logic.schema.default_follow_group_schema(), context['model'].UserFollowingGroup) diff --git a/ckan/logic/auth/get.py b/ckan/logic/auth/get.py index 95a75b0d1af..f79e1b3b54a 100644 --- a/ckan/logic/auth/get.py +++ b/ckan/logic/auth/get.py @@ -217,3 +217,15 @@ def dashboard_new_activities_count(context, data_dict): # This is so a better not authourized message can be sent. return new_authz.is_authorized('dashboard_activity_list', context, data_dict) + + +def user_follower_list(context, data_dict): + return sysadmin(context, data_dict) + + +def dataset_follower_list(context, data_dict): + return sysadmin(context, data_dict) + + +def group_follower_list(context, data_dict): + return sysadmin(context, data_dict) diff --git a/ckan/templates/group/read_base.html b/ckan/templates/group/read_base.html index f476bce7a6b..e41cb7ebe7d 100644 --- a/ckan/templates/group/read_base.html +++ b/ckan/templates/group/read_base.html @@ -26,9 +26,6 @@ {% link_for _('Activity Stream'), controller='group', action='activity', id=c.group_dict.name, icon='time' %} - - {% link_for _('Followers'), controller='group', action='followers', id=c.group_dict.name, icon='group' %} - {% link_for _('Administrators'), controller='group', action='admins', id=c.group_dict.name, icon='cog' %} diff --git a/ckan/templates/package/read_base.html b/ckan/templates/package/read_base.html index 9a3d838ceb8..14c90e7cbf9 100644 --- a/ckan/templates/package/read_base.html +++ b/ckan/templates/package/read_base.html @@ -43,7 +43,6 @@ {% snippet 'snippets/page_header.html', items=[ h.build_nav_icon('dataset_read', _('Dataset'), id=pkg.name), h.build_nav_icon('dataset_activity', _('Activity Stream'), id=pkg.name), - h.build_nav_icon('dataset_followers', _('Followers'), id=pkg.name), h.build_nav_icon('related_list', _('Related'), id=pkg.name), ] %} {% endblock %} diff --git a/ckan/templates/user/read_base.html b/ckan/templates/user/read_base.html index cf287f33170..6baf84d3aa5 100644 --- a/ckan/templates/user/read_base.html +++ b/ckan/templates/user/read_base.html @@ -24,7 +24,6 @@ {% snippet 'snippets/page_header.html', items=[ h.build_nav_icon('user_datasets', _('Datasets'), id=user.name), h.build_nav_icon('user_activity_stream', _('Activity Stream'), id=user.name), - h.build_nav_icon('user_followers', _('Followers'), id=user.name), ] %} {% endblock %}
From 44a1fd16956341331e9eb534911a9d08acc6a758 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 13:02:51 +0100 Subject: [PATCH 20/46] [#316] Update follower api tests Make it pass a sysadmin's API key whenever calling *_follower_list, since only sysadmins are authorised to call that now. --- ckan/tests/functional/api/test_follow.py | 116 ++++++++++++++--------- 1 file changed, 72 insertions(+), 44 deletions(-) diff --git a/ckan/tests/functional/api/test_follow.py b/ckan/tests/functional/api/test_follow.py index 90bc4a23101..d03d20e7714 100644 --- a/ckan/tests/functional/api/test_follow.py +++ b/ckan/tests/functional/api/test_follow.py @@ -25,7 +25,8 @@ def datetime_from_string(s): ''' return datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f') -def follow_user(app, follower_id, apikey, object_id, object_arg): +def follow_user(app, follower_id, apikey, object_id, object_arg, + sysadmin_apikey): '''Test a user starting to follow another user via the API. :param follower_id: id of the user that will be following something. @@ -65,7 +66,7 @@ def follow_user(app, follower_id, apikey, object_id, object_arg): # Check that the follower appears in the object's list of followers. followers = ckan.tests.call_action_api(app, 'user_follower_list', - id=object_id) + id=object_id, apikey=sysadmin_apikey) assert len(followers) == follower_count_before + 1 assert len([follower for follower in followers if follower['id'] == follower_id]) == 1 @@ -85,7 +86,8 @@ def follow_user(app, follower_id, apikey, object_id, object_arg): 'user_followee_count', id=follower_id) assert followee_count_after == followee_count_before + 1 -def follow_dataset(app, follower_id, apikey, dataset_id, dataset_arg): +def follow_dataset(app, follower_id, apikey, dataset_id, dataset_arg, + sysadmin_apikey): '''Test a user starting to follow a dataset via the API. :param follower_id: id of the user. @@ -125,7 +127,7 @@ def follow_dataset(app, follower_id, apikey, dataset_id, dataset_arg): # Check that the follower appears in the dataset's list of followers. followers = ckan.tests.call_action_api(app, 'dataset_follower_list', - id=dataset_id) + id=dataset_id, apikey=sysadmin_apikey) assert len(followers) == follower_count_before + 1 assert len([follower for follower in followers if follower['id'] == follower_id]) == 1 @@ -145,7 +147,7 @@ def follow_dataset(app, follower_id, apikey, dataset_id, dataset_arg): 'dataset_followee_count', id=follower_id) assert followee_count_after == followee_count_before + 1 -def follow_group(app, user_id, apikey, group_id, group_arg): +def follow_group(app, user_id, apikey, group_id, group_arg, sysadmin_apikey): '''Test a user starting to follow a group via the API. :param user_id: id of the user @@ -185,7 +187,7 @@ def follow_group(app, user_id, apikey, group_id, group_arg): # Check that the user appears in the group's list of followers. followers = ckan.tests.call_action_api(app, 'group_follower_list', - id=group_id) + id=group_id, apikey=sysadmin_apikey) assert len(followers) == follower_count_before + 1 assert len([follower for follower in followers if follower['id'] == user_id]) == 1 @@ -316,27 +318,33 @@ def test_01_follow_missing_object_id(self): def test_02_user_follow_user_by_id(self): follow_user(self.app, self.annafan['id'], self.annafan['apikey'], - self.russianfan['id'], self.russianfan['id']) + self.russianfan['id'], self.russianfan['id'], + self.testsysadmin['apikey']) def test_02_user_follow_dataset_by_id(self): follow_dataset(self.app, self.annafan['id'], self.annafan['apikey'], - self.warandpeace['id'], self.warandpeace['id']) + self.warandpeace['id'], self.warandpeace['id'], + self.testsysadmin['apikey']) def test_02_user_follow_group_by_id(self): follow_group(self.app, self.annafan['id'], self.annafan['apikey'], - self.rogers_group['id'], self.rogers_group['id']) + self.rogers_group['id'], self.rogers_group['id'], + self.testsysadmin['apikey']) def test_02_user_follow_user_by_name(self): follow_user(self.app, self.annafan['id'], self.annafan['apikey'], - self.testsysadmin['id'], self.testsysadmin['name']) + self.testsysadmin['id'], self.testsysadmin['name'], + self.testsysadmin['apikey']) def test_02_user_follow_dataset_by_name(self): follow_dataset(self.app, self.joeadmin['id'], self.joeadmin['apikey'], - self.warandpeace['id'], self.warandpeace['name']) + self.warandpeace['id'], self.warandpeace['name'], + self.testsysadmin['apikey']) def test_02_user_follow_group_by_name(self): follow_group(self.app, self.joeadmin['id'], self.joeadmin['apikey'], - self.rogers_group['id'], self.rogers_group['name']) + self.rogers_group['id'], self.rogers_group['name'], + self.testsysadmin['apikey']) def test_03_user_follow_user_already_following(self): for object_id in (self.russianfan['id'], self.russianfan['name'], @@ -400,28 +408,31 @@ def test_04_follower_list_bad_id(self): 'group_follower_list'): for object_id in ('bad id', ' ', 3, 35.7, 'xxx', ''): error = ckan.tests.call_action_api(self.app, action, - status=409, id=object_id) + status=409, id=object_id, + apikey=self.testsysadmin['apikey']) assert error['id'] def test_04_follower_list_missing_id(self): for action in ('user_follower_list', 'dataset_follower_list', 'group_follower_list'): - error = ckan.tests.call_action_api(self.app, action, status=409) + error = ckan.tests.call_action_api(self.app, action, status=409, + apikey=self.testsysadmin['apikey']) assert error['id'] == ['Missing value'] def test_04_user_follower_list_no_followers(self): followers = ckan.tests.call_action_api(self.app, 'user_follower_list', - id=self.annafan['id']) + id=self.annafan['id'], apikey=self.testsysadmin['apikey']) assert followers == [] def test_04_dataset_follower_list_no_followers(self): followers = ckan.tests.call_action_api(self.app, - 'dataset_follower_list', id=self.annakarenina['id']) + 'dataset_follower_list', id=self.annakarenina['id'], + apikey=self.testsysadmin['apikey']) assert followers == [] def test_04_group_follower_list_no_followers(self): followers = ckan.tests.call_action_api(self.app, 'group_follower_list', - id=self.davids_group['id']) + id=self.davids_group['id'], apikey=self.testsysadmin['apikey']) assert followers == [] def test_04_am_following_bad_id(self): @@ -529,26 +540,34 @@ def setup_class(self): self.app = paste.fixture.TestApp(pylons.test.pylonsapp) follow_user(self.app, self.testsysadmin['id'], self.testsysadmin['apikey'], self.joeadmin['id'], - self.joeadmin['id']) + self.joeadmin['id'], self.testsysadmin['apikey']) follow_user(self.app, self.tester['id'], self.tester['apikey'], - self.joeadmin['id'], self.joeadmin['id']) + self.joeadmin['id'], self.joeadmin['id'], + self.testsysadmin['apikey']) follow_user(self.app, self.russianfan['id'], self.russianfan['apikey'], - self.joeadmin['id'], self.joeadmin['id']) + self.joeadmin['id'], self.joeadmin['id'], + self.testsysadmin['apikey']) follow_user(self.app, self.annafan['id'], self.annafan['apikey'], - self.joeadmin['id'], self.joeadmin['id']) + self.joeadmin['id'], self.joeadmin['id'], + self.testsysadmin['apikey']) follow_user(self.app, self.annafan['id'], self.annafan['apikey'], - self.tester['id'], self.tester['id']) + self.tester['id'], self.tester['id'], + self.testsysadmin['apikey']) follow_dataset(self.app, self.testsysadmin['id'], self.testsysadmin['apikey'], self.warandpeace['id'], - self.warandpeace['id']) + self.warandpeace['id'], self.testsysadmin['apikey']) follow_dataset(self.app, self.tester['id'], self.tester['apikey'], - self.warandpeace['id'], self.warandpeace['id']) + self.warandpeace['id'], self.warandpeace['id'], + self.testsysadmin['apikey']) follow_dataset(self.app, self.russianfan['id'], self.russianfan['apikey'], - self.warandpeace['id'], self.warandpeace['id']) + self.warandpeace['id'], self.warandpeace['id'], + self.testsysadmin['apikey']) follow_dataset(self.app, self.annafan['id'], self.annafan['apikey'], - self.warandpeace['id'], self.warandpeace['id']) + self.warandpeace['id'], self.warandpeace['id'], + self.testsysadmin['apikey']) follow_group(self.app, self.annafan['id'], self.annafan['apikey'], - self.davids_group['id'], self.davids_group['id']) + self.davids_group['id'], self.davids_group['id'], + self.testsysadmin['apikey']) @classmethod def teardown_class(self): @@ -653,7 +672,7 @@ def _unfollow_user(self, follower_id, apikey, object_id, object_arg): # Check that the user doesn't appear in the object's list of followers. followers = ckan.tests.call_action_api(self.app, 'user_follower_list', - id=object_id) + id=object_id, apikey=self.testsysadmin['apikey']) assert len([follower for follower in followers if follower['id'] == follower_id]) == 0 @@ -693,7 +712,8 @@ def _unfollow_dataset(self, user_id, apikey, dataset_id, dataset_arg): # Check that the user doesn't appear in the dataset's list of # followers. followers = ckan.tests.call_action_api(self.app, - 'dataset_follower_list', id=dataset_id) + 'dataset_follower_list', id=dataset_id, + apikey=self.testsysadmin['apikey']) assert len([follower for follower in followers if follower['id'] == user_id]) == 0 @@ -733,7 +753,7 @@ def _unfollow_group(self, user_id, apikey, group_id, group_arg): # Check that the user doesn't appear in the group's list of # followers. followers = ckan.tests.call_action_api(self.app, 'group_follower_list', - id=group_id) + id=group_id, apikey=self.testsysadmin['apikey']) assert len([follower for follower in followers if follower['id'] == user_id]) == 0 @@ -800,31 +820,38 @@ def setup_class(self): self.app = paste.fixture.TestApp(pylons.test.pylonsapp) follow_user(self.app, self.joeadmin['id'], self.joeadmin['apikey'], - self.testsysadmin['id'], self.testsysadmin['id']) + self.testsysadmin['id'], self.testsysadmin['id'], + self.testsysadmin['apikey']) follow_user(self.app, self.annafan['id'], self.annafan['apikey'], - self.testsysadmin['id'], self.testsysadmin['id']) + self.testsysadmin['id'], self.testsysadmin['id'], + self.testsysadmin['apikey']) follow_user(self.app, self.russianfan['id'], self.russianfan['apikey'], - self.testsysadmin['id'], self.testsysadmin['id']) + self.testsysadmin['id'], self.testsysadmin['id'], + self.testsysadmin['apikey']) follow_dataset(self.app, self.joeadmin['id'], self.joeadmin['apikey'], - self.annakarenina['id'], self.annakarenina['id']) + self.annakarenina['id'], self.annakarenina['id'], + self.testsysadmin['apikey']) follow_dataset(self.app, self.annafan['id'], self.annafan['apikey'], - self.annakarenina['id'], self.annakarenina['id']) + self.annakarenina['id'], self.annakarenina['id'], + self.testsysadmin['apikey']) follow_dataset(self.app, self.russianfan['id'], self.russianfan['apikey'], - self.annakarenina['id'], self.annakarenina['id']) + self.annakarenina['id'], self.annakarenina['id'], + self.testsysadmin['apikey']) follow_user(self.app, self.tester['id'], self.tester['apikey'], - self.joeadmin['id'], self.joeadmin['id']) + self.joeadmin['id'], self.joeadmin['id'], + self.testsysadmin['apikey']) follow_dataset(self.app, self.testsysadmin['id'], self.testsysadmin['apikey'], self.warandpeace['id'], - self.warandpeace['id']) + self.warandpeace['id'], self.testsysadmin['apikey']) follow_group(self.app, self.testsysadmin['id'], self.testsysadmin['apikey'], self.davids_group['id'], - self.davids_group['id']) + self.davids_group['id'], self.testsysadmin['apikey']) session = ckan.model.Session() session.delete(ckan.model.User.get('joeadmin')) @@ -848,17 +875,17 @@ def test_01_on_delete_cascade_api(self): ''' # It should no longer be possible to get joeadmin's follower list. error = ckan.tests.call_action_api(self.app, 'user_follower_list', - status=409, id='joeadmin') + status=409, id='joeadmin', apikey=self.testsysadmin['apikey']) assert 'id' in error # It should no longer be possible to get warandpeace's follower list. error = ckan.tests.call_action_api(self.app, 'dataset_follower_list', - status=409, id='warandpeace') + status=409, id='warandpeace', apikey=self.testsysadmin['apikey']) assert 'id' in error # It should no longer be possible to get david's follower list. error = ckan.tests.call_action_api(self.app, 'group_follower_list', - status=409, id='david') + status=409, id='david', apikey=self.testsysadmin['apikey']) assert 'id' in error # It should no longer be possible to get joeadmin's follower count. @@ -926,13 +953,14 @@ def test_01_on_delete_cascade_api(self): # Users who joeadmin was following should no longer have him in their # follower list. followers = ckan.tests.call_action_api(self.app, 'user_follower_list', - id=self.testsysadmin['id']) + id=self.testsysadmin['id'], apikey=self.testsysadmin['apikey']) assert 'joeadmin' not in [follower['name'] for follower in followers] # Datasets who joeadmin was following should no longer have him in # their follower list. followers = ckan.tests.call_action_api(self.app, - 'dataset_follower_list', id=self.annakarenina['id']) + 'dataset_follower_list', id=self.annakarenina['id'], + apikey=self.testsysadmin['apikey']) assert 'joeadmin' not in [follower['name'] for follower in followers] def test_02_on_delete_cascade_db(self): From eab929e4842179dc09583cd67f58e9677d2c1b6f Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Wed, 30 Jan 2013 14:03:29 +0000 Subject: [PATCH 21/46] [#257] Set the timeout outside the recline source so that it can be updated without patching the source again --- ckanext/reclinepreview/theme/public/preview_recline.js | 2 ++ ckanext/reclinepreview/theme/public/vendor/recline/recline.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ckanext/reclinepreview/theme/public/preview_recline.js b/ckanext/reclinepreview/theme/public/preview_recline.js index 91c5063e816..bf56d89b66c 100644 --- a/ckanext/reclinepreview/theme/public/preview_recline.js +++ b/ckanext/reclinepreview/theme/public/preview_recline.js @@ -39,6 +39,8 @@ this.ckan.module('reclinepreview', function (jQuery, _) { .html(msg); } + recline.Backend.DataProxy.timeout = 10000; + // 2 situations // a) something was posted to the datastore - need to check for this // b) csv or xls (but not datastore) diff --git a/ckanext/reclinepreview/theme/public/vendor/recline/recline.js b/ckanext/reclinepreview/theme/public/vendor/recline/recline.js index 912773291b8..083279fc2a3 100644 --- a/ckanext/reclinepreview/theme/public/vendor/recline/recline.js +++ b/ckanext/reclinepreview/theme/public/vendor/recline/recline.js @@ -423,7 +423,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; my.dataproxy_url = 'http://jsonpdataproxy.appspot.com'; // Timeout for dataproxy (after this time if no response we error) // Needed because use JSONP so do not receive e.g. 500 errors - my.timeout = 10000; + my.timeout = 5000; // ## load // From f183205ea5474839bcd941984debd0d44c08c04a Mon Sep 17 00:00:00 2001 From: John Martin Date: Wed, 30 Jan 2013 14:23:07 +0000 Subject: [PATCH 22/46] [#319] Added extra state to .select2-container that handles error state better --- ckan/public/base/less/forms.less | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ckan/public/base/less/forms.less b/ckan/public/base/less/forms.less index 445c2677ec2..5902a859f8b 100644 --- a/ckan/public/base/less/forms.less +++ b/ckan/public/base/less/forms.less @@ -686,3 +686,11 @@ textarea { // sets it on the element. width: 520px !important; } + +.control-group.error .select2-container { + input:focus, + select:focus, + textarea:focus { + .box-shadow(none); + } +} From bde08f104909d0d40df2d0dba8efd6f20c00050c Mon Sep 17 00:00:00 2001 From: John Martin Date: Wed, 30 Jan 2013 14:28:12 +0000 Subject: [PATCH 23/46] [#319] Rebuild of main.css --- ckan/public/base/css/main.css | 40 ++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/ckan/public/base/css/main.css b/ckan/public/base/css/main.css index 6d4fc938097..eaa904d3dac 100644 --- a/ckan/public/base/css/main.css +++ b/ckan/public/base/css/main.css @@ -4506,13 +4506,13 @@ ul.icons li .icon-large:before { .simple-list:after { clear: both; } -.simple-list > li { +.simple-list > li { font-size: 12px; line-height: 1.1666666666666667em; padding: 7px 25px; border-bottom: 1px dotted #cccccc; } -.simple-list > li:last-of-type { +.simple-list > li:last-of-type { border-bottom: 0; } .simple-list .ckan-icon { @@ -4669,8 +4669,6 @@ ul.icons li .icon-large:before { padding-right: 15px; } .module-grid { - margin: 0; - list-style: none; margin: 0; list-style: none; *zoom: 1; @@ -4694,7 +4692,6 @@ ul.icons li .icon-large:before { padding-left: 20px; padding-bottom: 25px; float: left; - float: left; margin-left: 20px; width: 460px; padding-top: 10px; @@ -4720,10 +4717,10 @@ ul.icons li .icon-large:before { .ckanext-datapreview { position: relative; } -.ckanext-datapreview > iframe { +.ckanext-datapreview > iframe { min-height: 400px; } -.ckanext-datapreview > img { +.ckanext-datapreview > img { max-height: 500px; max-width: 100%; overflow: hidden; @@ -4887,13 +4884,13 @@ ol.media-grid:after { .nav-simple:after { clear: both; } -.nav-simple > li { +.nav-simple > li { font-size: 12px; line-height: 1.1666666666666667em; padding: 7px 25px; border-bottom: 1px dotted #cccccc; } -.nav-simple > li:last-of-type { +.nav-simple > li:last-of-type { border-bottom: 0; } .nav-simple .ckan-icon { @@ -4915,12 +4912,10 @@ ol.media-grid:after { } .nav-item.active > a { background: url("../../../base/images/background-tag.png") no-repeat -13px center; - position: relative; display: block; font-size: 11px; line-height: 27px; color: #187794; - padding-left: 10px; padding-right: 5px; margin-right: 11px; -webkit-box-sizing: border-box; @@ -5226,8 +5221,6 @@ textarea { background-image: url("../../../base/images/sprite-ckan-icons.png"); background-repeat: no-repeat; background-position: 16px 16px; - width: 17px; - height: 17px; background-position: -51px -16px; position: absolute; display: block; @@ -5654,6 +5647,13 @@ textarea { .control-full .select2-container { width: 520px !important; } +.control-group.error .select2-container input:focus, +.control-group.error .select2-container select:focus, +.control-group.error .select2-container textarea:focus { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} .dataset-item { border-bottom: 1px dotted #cccccc; padding-bottom: 20px; @@ -5821,8 +5821,6 @@ textarea { vertical-align: text-bottom; position: relative; top: 2px; - width: 16px; - height: 16px; background-image: url("../../../base/images/sprite-ckan-icons.png"); background-repeat: no-repeat; background-position: 16px 16px; @@ -6682,7 +6680,6 @@ li .icon-large:before { float: right; } [role=main] .secondary { - float: left; margin-left: 20px; width: 220px; margin-left: 0; @@ -7239,9 +7236,18 @@ header.masthead .debug { display: block; font-size: 30px; font-weight: 700; - line-height: 1.2; + line-height: 36px; margin-left: 0; } +.profile-info .nums dl dd .smallest { + font-size: 13px; +} +.profile-info .nums dl dd .smaller { + font-size: 16px; +} +.profile-info .nums dl dd .small { + font-size: 21px; +} .profile-info.editing .module-content { margin-top: 0; } From 40e4630d24a81b3294d31d93d5b942f02713acff Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 15:47:46 +0100 Subject: [PATCH 24/46] [#316] Update follower frontend tests --- ckan/tests/functional/test_follow.py | 82 +++++----------------------- 1 file changed, 14 insertions(+), 68 deletions(-) diff --git a/ckan/tests/functional/test_follow.py b/ckan/tests/functional/test_follow.py index 268fb823706..acb97deb0ac 100644 --- a/ckan/tests/functional/test_follow.py +++ b/ckan/tests/functional/test_follow.py @@ -68,24 +68,12 @@ def test_dataset_read_not_logged_in(self): assert 'id="dataset_follow_button"' not in result def test_dataset_followers_not_logged_in(self): + '''Not-logged-in users cannot see /dataset/followers/ pages.''' offset = url_for(controller='package', action='followers', id='warandpeace') result = self.app.get(offset) - assert 'href="/dataset/followers/warandpeace"' in result - assert 'Followers (0)' in result - assert 'id="dataset_follow_button"' not in result - assert '
  • ' not in result - - offset = url_for(controller='package', action='followers', - id='annakarenina') - result = self.app.get(offset) - assert 'href="/dataset/followers/annakarenina"' in result - assert 'Followers (3)' in result - assert 'id="dataset_follow_button"' not in result - assert str(result).count('
  • ') == 3 - assert self.joeadmin.display_name in result - assert self.annafan.display_name in result - assert self.russianfan.display_name in result + assert result.status == 302 + assert '/user/login' in result.header_dict['location'] def test_user_read_not_logged_in(self): offset = url_for(controller='user', action='read', @@ -106,20 +94,8 @@ def test_user_followers_not_logged_in(self): offset = url_for(controller='user', action='followers', id='joeadmin') result = self.app.get(offset) - assert 'href="/user/followers/joeadmin"' in result - assert 'Followers (0)' in result - assert '
  • ' not in result - assert 'id="user_follow_button"' not in result - - offset = url_for(controller='user', action='followers', - id='annafan') - result = self.app.get(offset) - assert 'href="/user/followers/annafan"' in result - assert 'Followers (2)' in result - assert 'id="user_follow_button"' not in result - assert str(result).count('
  • ') == 2 - assert self.tester.display_name in result - assert self.russianfan.display_name in result + assert result.status == 302 + assert '/user/login' in result.header_dict['location'] def test_own_user_read_logged_in(self): offset = url_for(controller='user', action='read', @@ -141,28 +117,17 @@ def test_own_user_read_logged_in(self): def test_own_user_followers_logged_in(self): offset = url_for(controller='user', action='followers', id='joeadmin') - extra_environ = {'Authorization': str(self.joeadmin.apikey)} + extra_environ = {'Authorization': str(self.testsysadmin.apikey)} result = self.app.get(offset, extra_environ=extra_environ) assert 'href="/user/followers/joeadmin"' in result - assert 'My Followers (0)' in result - assert 'id="user_follow_button"' not in result + assert 'Followers (0)' in result + assert 'id="user_follow_button"' in result assert '
  • ' not in result - offset = url_for(controller='user', action='followers', - id='annafan') - extra_environ = {'Authorization': str(self.annafan.apikey)} - result = self.app.get(offset, extra_environ=extra_environ) - assert 'href="/user/followers/annafan"' in result - assert 'My Followers (2)' in result - assert 'id="user_follow_button"' not in result - assert str(result).count('
  • ') == 2 - assert self.tester.display_name in result - assert self.russianfan.display_name in result - def test_dataset_read_logged_in(self): offset = url_for(controller='package', action='read', id='warandpeace') - extra_environ = {'Authorization': str(self.tester.apikey)} + extra_environ = {'Authorization': str(self.testsysadmin.apikey)} result = self.app.get(offset, extra_environ=extra_environ) assert 'href="/dataset/followers/warandpeace"' in result assert 'Followers (0)' in result @@ -179,16 +144,16 @@ def test_dataset_read_logged_in(self): def test_dataset_follow_logged_in(self): offset = url_for(controller='package', action='followers', id='warandpeace') - extra_environ = {'Authorization': str(self.tester.apikey)} + extra_environ = {'Authorization': str(self.testsysadmin.apikey)} result = self.app.get(offset, extra_environ=extra_environ) - assert 'href="/dataset/followers/warandpeace"' in result + assert 'id="dataset_follow_button"' in result assert 'Followers (0)' in result assert 'id="dataset_follow_button"' in result assert '
  • ' not in result offset = url_for(controller='package', action='followers', id='annakarenina') - extra_environ = {'Authorization': str(self.tester.apikey)} + extra_environ = {'Authorization': str(self.testsysadmin.apikey)} result = self.app.get(offset, extra_environ=extra_environ) assert 'href="/dataset/followers/annakarenina"' in result assert 'Followers (3)' in result @@ -202,7 +167,7 @@ def test_dataset_follow_logged_in(self): # button. offset = url_for(controller='package', action='followers', id='annakarenina') - extra_environ = {'Authorization': str(self.joeadmin.apikey)} + extra_environ = {'Authorization': str(self.testsysadmin.apikey)} result = self.app.get(offset, extra_environ=extra_environ) assert 'Unfollow' in result @@ -226,28 +191,9 @@ def test_user_read_logged_in(self): def test_user_follow_logged_in(self): offset = url_for(controller='user', action='followers', id='joeadmin') - extra_environ = {'Authorization': str(self.tester.apikey)} + extra_environ = {'Authorization': str(self.testsysadmin.apikey)} result = self.app.get(offset, extra_environ=extra_environ) assert 'href="/user/followers/joeadmin"' in result assert 'Followers (0)' in result assert '
  • ' not in result assert 'id="user_follow_button"' in result - - offset = url_for(controller='user', action='followers', - id='annafan') - extra_environ = {'Authorization': str(self.tester.apikey)} - result = self.app.get(offset, extra_environ=extra_environ) - assert 'href="/user/followers/annafan"' in result - assert 'Followers (2)' in result - assert 'id="user_follow_button"' in result - assert str(result).count('
  • ') == 2 - assert self.tester.display_name in result - assert self.russianfan.display_name in result - - # russianfan is following annafan so he should see an Unfollow - # button. - offset = url_for(controller='user', action='followers', - id='annafan') - extra_environ = {'Authorization': str(self.russianfan.apikey)} - result = self.app.get(offset, extra_environ=extra_environ) - assert 'Unfollow' in result From 825117eca63105ac582b7708f3dde242499c30db Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 16:03:52 +0100 Subject: [PATCH 25/46] [#316] Add tests for new follower list auth --- ckan/tests/functional/api/test_follow.py | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/ckan/tests/functional/api/test_follow.py b/ckan/tests/functional/api/test_follow.py index d03d20e7714..e3cfdd74994 100644 --- a/ckan/tests/functional/api/test_follow.py +++ b/ckan/tests/functional/api/test_follow.py @@ -258,6 +258,45 @@ def setup_class(self): def teardown_class(self): ckan.model.repo.rebuild_db() + def test_00_visitor_cannot_get_user_follower_list(self): + ckan.tests.call_action_api(self.app, 'user_follower_list', + id=self.russianfan['id'], status=403) + + def test_00_user_cannot_get_user_follower_list(self): + ckan.tests.call_action_api(self.app, 'user_follower_list', + id=self.russianfan['id'], status=403, + apikey=self.annafan['apikey']) + + def test_00_sysadmin_can_get_user_follower_list(self): + ckan.tests.call_action_api(self.app, 'user_follower_list', + id=self.russianfan['id'], status=200, + apikey=self.testsysadmin['apikey']) + + def test_00_visitor_cannot_get_dataset_follower_list(self): + ckan.tests.call_action_api(self.app, 'dataset_follower_list', + id='warandpeace', status=403) + + def test_00_user_cannot_get_dataset_follower_list(self): + ckan.tests.call_action_api(self.app, 'dataset_follower_list', + id='warandpeace', status=403, apikey=self.annafan['apikey']) + + def test_00_sysadmin_can_get_dataset_follower_list(self): + ckan.tests.call_action_api(self.app, 'dataset_follower_list', + id='warandpeace', status=200, + apikey=self.testsysadmin['apikey']) + + def test_00_visitor_cannot_get_group_follower_list(self): + ckan.tests.call_action_api(self.app, 'group_follower_list', + id='roger', status=403) + + def test_00_user_cannot_get_group_follower_list(self): + ckan.tests.call_action_api(self.app, 'group_follower_list', + id='roger', status=403, apikey=self.annafan['apikey']) + + def test_00_sysadmin_can_get_group_follower_list(self): + ckan.tests.call_action_api(self.app, 'group_follower_list', + id='roger', status=200, apikey=self.testsysadmin['apikey']) + def test_01_user_follow_user_bad_apikey(self): for apikey in ('bad api key', '', ' ', 'None', '3', '35.7', 'xxx'): error = ckan.tests.call_action_api(self.app, 'follow_user', From 5c7e022a09798d02b1fd30356517a9ef071d0577 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 16:08:13 +0100 Subject: [PATCH 26/46] [#316] Remove "started following" activities --- ckan/logic/action/create.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py index b9f1aca8679..5f551125920 100644 --- a/ckan/logic/action/create.py +++ b/ckan/logic/action/create.py @@ -1028,24 +1028,6 @@ def follow_user(context, data_dict): follower = model_save.follower_dict_save(validated_data_dict, context, model.UserFollowingUser) - activity_dict = { - 'user_id': userobj.id, - 'object_id': validated_data_dict['id'], - 'activity_type': 'follow user', - } - activity_dict['data'] = { - 'user': ckan.lib.dictization.table_dictize( - model.User.get(validated_data_dict['id']), context), - } - activity_create_context = { - 'model': model, - 'user': userobj, - 'defer_commit': True, - 'session': session - } - logic.get_action('activity_create')(activity_create_context, - activity_dict, ignore_auth=True) - if not context.get('defer_commit'): model.repo.commit() From a20d0b3f9aa5cf65d1f9780a98a7faaff90f6ab7 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 16:17:38 +0100 Subject: [PATCH 27/46] [#316] Update activity streams API tests --- ckan/tests/functional/api/test_activity.py | 38 ++++++++++++---------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/ckan/tests/functional/api/test_activity.py b/ckan/tests/functional/api/test_activity.py index 4fa1ed45f72..c0daf7b17ee 100644 --- a/ckan/tests/functional/api/test_activity.py +++ b/ckan/tests/functional/api/test_activity.py @@ -2063,24 +2063,28 @@ def test_follow_user(self): # Find the new activity in the user's activity stream. user_new_activities = (find_new_activities( before['user activity stream'], after['user activity stream'])) - assert len(user_new_activities) == 1, ("There should be 1 new " - " activity in the user's activity stream, but found %i" % + assert len(user_new_activities) == 0, ("There should be 0 new " + " activities in the user's activity stream, but found %i" % len(user_new_activities)) - activity = user_new_activities[0] + + # The rest of this test is commented out because follow_user activities + # are disabled, uncomment it if they're enabled again. + + #activity = user_new_activities[0] # Check that the new activity has the right attributes. - assert activity['object_id'] == self.sysadmin_user['id'], \ - str(activity['object_id']) - assert activity['user_id'] == user['id'], str(activity['user_id']) - assert activity['activity_type'] == 'follow user', \ - str(activity['activity_type']) - if 'id' not in activity: - assert False, "activity object should have an id value" + #assert activity['object_id'] == self.sysadmin_user['id'], \ + # str(activity['object_id']) + #assert activity['user_id'] == user['id'], str(activity['user_id']) + #assert activity['activity_type'] == 'follow user', \ + # str(activity['activity_type']) + #if 'id' not in activity: + # assert False, "activity object should have an id value" # TODO: Test for the _correct_ revision_id value. - if 'revision_id' not in activity: - assert False, "activity object should have a revision_id value" - timestamp = datetime_from_string(activity['timestamp']) - assert timestamp >= before['time'] and timestamp <= \ - after['time'], str(activity['timestamp']) - - assert len(self.activity_details(activity)) == 0 + #if 'revision_id' not in activity: + # assert False, "activity object should have a revision_id value" + #timestamp = datetime_from_string(activity['timestamp']) + #assert timestamp >= before['time'] and timestamp <= \ + # after['time'], str(activity['timestamp']) + # + #assert len(self.activity_details(activity)) == 0 From b722815983378201dba41a4b2adc36c4430aafac Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 16:20:15 +0100 Subject: [PATCH 28/46] [#316] Update activity streams frontend tests --- ckan/tests/functional/test_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/tests/functional/test_activity.py b/ckan/tests/functional/test_activity.py index 7e4f0d89a3d..a8d9e0a9588 100644 --- a/ckan/tests/functional/test_activity.py +++ b/ckan/tests/functional/test_activity.py @@ -127,7 +127,7 @@ def test_user_activity(self): result = self.app.get(offset, status=200) stripped = self.strip_tags(result) assert '%s started following %s' % (user['fullname'], - 'joeadmin') in stripped, stripped + 'joeadmin') not in stripped, stripped # Create a new group. group = { From ef89d80549214276f5c3f7ee08c50ce28597a44e Mon Sep 17 00:00:00 2001 From: John Glover Date: Wed, 30 Jan 2013 15:55:39 +0000 Subject: [PATCH 29/46] Remove theme folder from gitignore --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index daf2c2e2007..b765bfee61f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,6 @@ syntax: glob .DS_Store ckan.egg-info/* sandbox/* -theme/* -theme dist # pylons @@ -28,9 +26,6 @@ fl_notes.txt .noseids *~ -# local symlinks -ckan/public/scripts/ckanjs.js - # custom style ckan/public/base/less/custom.less From 7f4f0def48560951d0d171e2665dfeefaad80e82 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 18:19:07 +0100 Subject: [PATCH 30/46] [#316] Add auth for followee_list APIs Don't let visitors or other users see what a user is following. --- ckan/logic/action/get.py | 3 + ckan/logic/auth/get.py | 29 ++++++++++ ckan/tests/functional/api/test_follow.py | 73 +++++++++++++++++++++++- 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 9d69f537613..8ce5a442a3c 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2383,6 +2383,7 @@ def user_followee_list(context, data_dict): :rtype: list of dictionaries ''' + _check_access('user_followee_list', context, data_dict) schema = context.get('schema') or ( ckan.logic.schema.default_follow_user_schema()) data_dict, errors = _validate(data_dict, schema, context) @@ -2410,6 +2411,7 @@ def dataset_followee_list(context, data_dict): :rtype: list of dictionaries ''' + _check_access('dataset_followee_list', context, data_dict) schema = context.get('schema') or ( ckan.logic.schema.default_follow_user_schema()) data_dict, errors = _validate(data_dict, schema, context) @@ -2438,6 +2440,7 @@ def group_followee_list(context, data_dict): :rtype: list of dictionaries ''' + _check_access('group_followee_list', context, data_dict) schema = context.get('schema', ckan.logic.schema.default_follow_user_schema()) data_dict, errors = _validate(data_dict, schema, context) diff --git a/ckan/logic/auth/get.py b/ckan/logic/auth/get.py index f79e1b3b54a..c6412bc27c9 100644 --- a/ckan/logic/auth/get.py +++ b/ckan/logic/auth/get.py @@ -229,3 +229,32 @@ def dataset_follower_list(context, data_dict): def group_follower_list(context, data_dict): return sysadmin(context, data_dict) + + +def _followee_list(context, data_dict): + model = context['model'] + + # Visitors cannot see what users are following. + authorized_user = model.User.get(context.get('user')) + if not authorized_user: + return {'success': False, 'msg': _('Not authorized')} + + # Any user is authorized to see what she herself is following. + requested_user = model.User.get(data_dict.get('id')) + if authorized_user == requested_user: + return {'success': True} + + # Sysadmins are authorized to see what anyone is following. + return sysadmin(context, data_dict) + + +def user_followee_list(context, data_dict): + return _followee_list(context, data_dict) + + +def dataset_followee_list(context, data_dict): + return _followee_list(context, data_dict) + + +def group_followee_list(context, data_dict): + return _followee_list(context, data_dict) diff --git a/ckan/tests/functional/api/test_follow.py b/ckan/tests/functional/api/test_follow.py index e3cfdd74994..eb4530e3cea 100644 --- a/ckan/tests/functional/api/test_follow.py +++ b/ckan/tests/functional/api/test_follow.py @@ -72,7 +72,7 @@ def follow_user(app, follower_id, apikey, object_id, object_arg, # Check that the object appears in the follower's list of followees. followees = ckan.tests.call_action_api(app, 'user_followee_list', - id=follower_id) + apikey=sysadmin_apikey, id=follower_id) assert len(followees) == followee_count_before + 1 assert len([followee for followee in followees if followee['id'] == object_id]) == 1 @@ -133,7 +133,7 @@ def follow_dataset(app, follower_id, apikey, dataset_id, dataset_arg, # Check that the dataset appears in the follower's list of followees. followees = ckan.tests.call_action_api(app, 'dataset_followee_list', - id=follower_id) + apikey=sysadmin_apikey, id=follower_id) assert len(followees) == followee_count_before + 1 assert len([followee for followee in followees if followee['id'] == dataset_id]) == 1 @@ -194,7 +194,7 @@ def follow_group(app, user_id, apikey, group_id, group_arg, sysadmin_apikey): # Check that the group appears in the user's list of followees. followees = ckan.tests.call_action_api(app, 'group_followee_list', - id=user_id) + apikey=sysadmin_apikey, id=user_id) assert len(followees) == followee_count_before + 1 assert len([followee for followee in followees if followee['id'] == group_id]) == 1 @@ -297,6 +297,73 @@ def test_00_sysadmin_can_get_group_follower_list(self): ckan.tests.call_action_api(self.app, 'group_follower_list', id='roger', status=200, apikey=self.testsysadmin['apikey']) + def test_00_visitor_cannot_get_user_followee_list(self): + '''A visitor cannot see what users a user is following.''' + ckan.tests.call_action_api(self.app, 'user_followee_list', + id=self.russianfan['id'], status=403) + + def test_00_user_cannot_get_user_followee_list(self): + '''A user cannot see what users another user is following.''' + ckan.tests.call_action_api(self.app, 'user_followee_list', + id=self.russianfan['id'], status=403, + apikey=self.annafan['apikey']) + + def test_00_sysadmin_can_get_user_followee_list(self): + '''A sysadmin can see what users another user is following.''' + ckan.tests.call_action_api(self.app, 'user_followee_list', + id=self.russianfan['id'], status=200, + apikey=self.testsysadmin['apikey']) + + def test_00_user_can_get_own_user_followee_list(self): + '''A user can see what users she herself is following.''' + ckan.tests.call_action_api(self.app, 'user_followee_list', + id=self.russianfan['id'], status=200, + apikey=self.russianfan['apikey']) + + def test_00_visitor_cannot_get_dataset_followee_list(self): + '''A visitor cannot see what datasets a user is following.''' + ckan.tests.call_action_api(self.app, 'dataset_followee_list', + id=self.russianfan['id'], status=403) + + def test_00_user_cannot_get_dataset_followee_list(self): + '''A user cannot see what datasets another user is following.''' + ckan.tests.call_action_api(self.app, 'dataset_followee_list', + id='russianfan', status=403, apikey=self.annafan['apikey']) + + def test_00_sysadmin_can_get_dataset_followee_list(self): + '''A sysadmin can see what datasets another user is following.''' + ckan.tests.call_action_api(self.app, 'dataset_followee_list', + id='russianfan', status=200, + apikey=self.testsysadmin['apikey']) + + def test_00_user_can_get_own_dataset_followee_list(self): + '''A user can see what datasets she herself is following.''' + ckan.tests.call_action_api(self.app, 'dataset_followee_list', + id=self.russianfan['id'], status=200, + apikey=self.russianfan['apikey']) + + def test_00_visitor_cannot_get_group_followee_list(self): + '''A visitor cannot see what groups a user is following.''' + ckan.tests.call_action_api(self.app, 'group_followee_list', + id='roger', status=403) + + def test_00_user_cannot_get_group_followee_list(self): + '''A user cannot see what groups another user is following.''' + ckan.tests.call_action_api(self.app, 'group_followee_list', + id='roger', status=403, apikey=self.annafan['apikey']) + + def test_00_sysadmin_can_get_group_followee_list(self): + '''A sysadmin can see what groups another user is following.''' + ckan.tests.call_action_api(self.app, 'group_followee_list', + id=self.annafan['id'], status=200, + apikey=self.testsysadmin['apikey']) + + def test_00_user_can_get_own_group_followee_list(self): + '''A user can see what groups she herself is following.''' + ckan.tests.call_action_api(self.app, 'group_followee_list', + id=self.russianfan['id'], status=200, + apikey=self.russianfan['apikey']) + def test_01_user_follow_user_bad_apikey(self): for apikey in ('bad api key', '', ' ', 'None', '3', '35.7', 'xxx'): error = ckan.tests.call_action_api(self.app, 'follow_user', From 6adc06072ad1535e5671ad3142a16408cae60cb5 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 18:38:42 +0100 Subject: [PATCH 31/46] [#316] Removed 'follow dataset' and 'follow group' activities Forgot to remove these in earlier commit 5c7e022a09798d02b1fd30356517a9ef071d0577 --- ckan/logic/action/create.py | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py index 5f551125920..13495a0d4df 100644 --- a/ckan/logic/action/create.py +++ b/ckan/logic/action/create.py @@ -1081,24 +1081,6 @@ def follow_dataset(context, data_dict): follower = model_save.follower_dict_save(validated_data_dict, context, model.UserFollowingDataset) - activity_dict = { - 'user_id': userobj.id, - 'object_id': validated_data_dict['id'], - 'activity_type': 'follow dataset', - } - activity_dict['data'] = { - 'dataset': ckan.lib.dictization.table_dictize( - model.Package.get(validated_data_dict['id']), context), - } - activity_create_context = { - 'model': model, - 'user': userobj, - 'defer_commit':True, - 'session': session - } - logic.get_action('activity_create')(activity_create_context, - activity_dict, ignore_auth=True) - if not context.get('defer_commit'): model.repo.commit() @@ -1191,24 +1173,6 @@ def follow_group(context, data_dict): follower = model_save.follower_dict_save(validated_data_dict, context, model.UserFollowingGroup) - activity_dict = { - 'user_id': userobj.id, - 'object_id': validated_data_dict['id'], - 'activity_type': 'follow group', - } - activity_dict['data'] = { - 'group': ckan.lib.dictization.table_dictize( - model.Group.get(validated_data_dict['id']), context), - } - activity_create_context = { - 'model': model, - 'user': userobj, - 'defer_commit': True, - 'session': session - } - logic.get_action('activity_create')(activity_create_context, - activity_dict, ignore_auth=True) - if not context.get('defer_commit'): model.repo.commit() From 99920836a6ee3ed4a4d8f4812ae28918d0278ee7 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 18:39:41 +0100 Subject: [PATCH 32/46] [#316] Update user dashboard tests --- ckan/tests/functional/api/test_dashboard.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ckan/tests/functional/api/test_dashboard.py b/ckan/tests/functional/api/test_dashboard.py index adf9953ce9a..f44e163e293 100644 --- a/ckan/tests/functional/api/test_dashboard.py +++ b/ckan/tests/functional/api/test_dashboard.py @@ -172,23 +172,23 @@ def test_03_dashboard_activity_list_own_activities(self): '''Test that a user's own activities appear in her dashboard.''' activities = self.dashboard_activity_list(self.new_user) - # FIXME: There should actually be 6 activities here, but when you + # FIXME: There should actually be 3 activities here, but when you # follow something it's old activities (from before you followed it) # appear in your activity stream. So here we get more activities than # expected. - assert len(activities) == 8 + assert len(activities) == 5, len(activities) assert activities[0]['activity_type'] == 'changed package' - assert activities[1]['activity_type'] == 'follow group' - assert activities[2]['activity_type'] == 'follow user' - assert activities[3]['activity_type'] == 'follow dataset' - assert activities[4]['activity_type'] == 'new package' - assert activities[5]['activity_type'] == 'new user' + #assert activities[1]['activity_type'] == 'follow group' + #assert activities[2]['activity_type'] == 'follow user' + #assert activities[3]['activity_type'] == 'follow dataset' + assert activities[1]['activity_type'] == 'new package' + assert activities[2]['activity_type'] == 'new user' - # FIXME: Shouldn't need the [:6] here, it's because when you follow + # FIXME: Shouldn't need the [:3] here, it's because when you follow # something its old activities (from before you started following it) # appear in your dashboard. - for activity in activities[:6]: + for activity in activities[:3]: assert activity['user_id'] == self.new_user['id'] def test_03_own_activities_not_marked_as_new(self): From 7ea77f068fff68e669b3dafc69d31515ff535c55 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 30 Jan 2013 18:53:11 +0100 Subject: [PATCH 33/46] [#316] Update some more activity streams tests --- ckan/tests/functional/api/test_activity.py | 40 ++++++++++++---------- ckan/tests/functional/test_activity.py | 2 +- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/ckan/tests/functional/api/test_activity.py b/ckan/tests/functional/api/test_activity.py index c0daf7b17ee..51bee6de429 100644 --- a/ckan/tests/functional/api/test_activity.py +++ b/ckan/tests/functional/api/test_activity.py @@ -2017,32 +2017,36 @@ def test_follow_dataset(self): # Find the new activity in the user's activity stream. user_new_activities = (find_new_activities( before['user activity stream'], after['user activity stream'])) - assert len(user_new_activities) == 1, ("There should be 1 new " + assert len(user_new_activities) == 0, ("There should be 0 new " " activity in the user's activity stream, but found %i" % len(user_new_activities)) - activity = user_new_activities[0] + + # The rest of this test is commented out because 'follow dataset' + # activities are disabled, even they are reenabled then uncomment it. + + #activity = user_new_activities[0] # The same new activity should appear in the package's activity stream. - pkg_new_activities = after['package activity stream'] - for activity in user_new_activities: - assert activity in pkg_new_activities + #pkg_new_activities = after['package activity stream'] + #for activity in user_new_activities: + # assert activity in pkg_new_activities # Check that the new activity has the right attributes. - assert activity['object_id'] == self.warandpeace['id'], \ - str(activity['object_id']) - assert activity['user_id'] == user['id'], str(activity['user_id']) - assert activity['activity_type'] == 'follow dataset', \ - str(activity['activity_type']) - if 'id' not in activity: - assert False, "activity object should have an id value" + #assert activity['object_id'] == self.warandpeace['id'], \ + # str(activity['object_id']) + #assert activity['user_id'] == user['id'], str(activity['user_id']) + #assert activity['activity_type'] == 'follow dataset', \ + # str(activity['activity_type']) + #if 'id' not in activity: + # assert False, "activity object should have an id value" # TODO: Test for the _correct_ revision_id value. - if 'revision_id' not in activity: - assert False, "activity object should have a revision_id value" - timestamp = datetime_from_string(activity['timestamp']) - assert timestamp >= before['time'] and timestamp <= \ - after['time'], str(activity['timestamp']) + #if 'revision_id' not in activity: + # assert False, "activity object should have a revision_id value" + #timestamp = datetime_from_string(activity['timestamp']) + #assert timestamp >= before['time'] and timestamp <= \ + # after['time'], str(activity['timestamp']) - assert len(self.activity_details(activity)) == 0 + #assert len(self.activity_details(activity)) == 0 def test_follow_user(self): user = self.normal_user diff --git a/ckan/tests/functional/test_activity.py b/ckan/tests/functional/test_activity.py index a8d9e0a9588..5634171c12d 100644 --- a/ckan/tests/functional/test_activity.py +++ b/ckan/tests/functional/test_activity.py @@ -120,7 +120,7 @@ def test_user_activity(self): result = self.app.get(offset, status=200) stripped = self.strip_tags(result) assert '%s started following %s' % (user['fullname'], - package['title']) in stripped, stripped + package['title']) not in stripped, stripped # Follow another user. follow_user(context, {'id': 'joeadmin'}) From 07e4ec8e8c67eea69a70afe176d8e158a0816698 Mon Sep 17 00:00:00 2001 From: John Martin Date: Fri, 1 Feb 2013 15:26:06 +0000 Subject: [PATCH 34/46] [#328] Simply removes the 'Administrators' tab from the group pages --- ckan/templates/group/read_base.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/ckan/templates/group/read_base.html b/ckan/templates/group/read_base.html index f476bce7a6b..323e12fae90 100644 --- a/ckan/templates/group/read_base.html +++ b/ckan/templates/group/read_base.html @@ -29,9 +29,6 @@ {% link_for _('Followers'), controller='group', action='followers', id=c.group_dict.name, icon='group' %}
  • - - {% link_for _('Administrators'), controller='group', action='admins', id=c.group_dict.name, icon='cog' %} - {% link_for _('About'), controller='group', action='about', id=c.group_dict.name, icon='info-sign' %} From 64c321509656b7e46f6c65142a19694a4ad3c2e3 Mon Sep 17 00:00:00 2001 From: John Martin Date: Fri, 1 Feb 2013 15:39:33 +0000 Subject: [PATCH 35/46] [#322] Adds the organization to the dataset page --- ckan/templates/organization/read_base.html | 2 +- ckan/templates/package/read_base.html | 6 ++++++ ckan/templates/snippets/organization.html | 24 ++++++++++++---------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/ckan/templates/organization/read_base.html b/ckan/templates/organization/read_base.html index d0c142f015c..23dc5c71ccd 100644 --- a/ckan/templates/organization/read_base.html +++ b/ckan/templates/organization/read_base.html @@ -33,7 +33,7 @@ {% endblock %} {% block secondary_content %} - {% snippet 'snippets/organization.html', organization=c.group_dict %} + {% snippet 'snippets/organization.html', organization=c.group_dict, show_nums=true %} {% if h.user_in_org_or_group(c.group_dict.id) %} {{ h.snippet('snippets/facet_list.html', title='Visibility', name='capacity', extras={'id':c.group_dict.id}) }} diff --git a/ckan/templates/package/read_base.html b/ckan/templates/package/read_base.html index 9a3d838ceb8..a67e82cd296 100644 --- a/ckan/templates/package/read_base.html +++ b/ckan/templates/package/read_base.html @@ -56,6 +56,12 @@ {% block secondary_help_content %}{% endblock %} + {% block package_organization %} + {% if pkg.organization %} + {% snippet "snippets/organization.html", organization=pkg.organization, truncate=70, show_nums=false %} + {% endif %} + {% endblock %} + {% block package_groups %} {% for group in pkg.groups %} {% snippet "snippets/group.html", group=group, truncate=70 %} diff --git a/ckan/templates/snippets/organization.html b/ckan/templates/snippets/organization.html index 8988a4c1338..a3b93f77695 100644 --- a/ckan/templates/snippets/organization.html +++ b/ckan/templates/snippets/organization.html @@ -26,18 +26,20 @@

    {{ organization.title or organization.name }}

    {% link_for _('read more'), controller='organization', action='about', id=organization.name %}

    {% else %} -

    {{ _('There is no description for this group') }}

    +

    {{ _('There is no description for this organization') }}

    + {% endif %} + {% if show_nums %} +
    +
    +
    {{ _('Members') }}
    +
    {{ h.SI_number_span(organization.members|length) }}
    +
    +
    +
    {{ _('Datasets') }}
    +
    {{ h.SI_number_span(organization.packages|length) }}
    +
    +
    {% endif %} -
    -
    -
    {{ _('Members') }}
    -
    {{ h.SI_number_span(organization.members|length) }}
    -
    -
    -
    {{ _('Datasets') }}
    -
    {{ h.SI_number_span(organization.packages|length) }}
    -
    -
    {% endwith %} From 01ccbbac5d97d3077cdc471bb39716c1ea03f91f Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Sun, 3 Feb 2013 19:06:57 +0100 Subject: [PATCH 36/46] [#345] Also recognize driver `postgres` in `engine_is_pg` --- ckan/model/meta.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ckan/model/meta.py b/ckan/model/meta.py index 13dcf1cb1fc..b8d6b42cd65 100644 --- a/ckan/model/meta.py +++ b/ckan/model/meta.py @@ -153,14 +153,12 @@ def after_rollback(self, session): # names, you'll need a metadata for each database metadata = MetaData() + def engine_is_sqlite(): - """ - Returns true iff the engine is connected to a sqlite database. - """ + # Returns true iff the engine is connected to a sqlite database. return engine.url.drivername == 'sqlite' + def engine_is_pg(): - """ - Returns true iff the engine is connected to a postgresql database. - """ - return engine.url.drivername == 'psycopg2' + # Returns true iff the engine is connected to a postgresql database. + return engine.url.drivername in ['psycopg2', 'postgres'] From c8f9adaade747ed66fad681a100e1f66c2249bcb Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 4 Feb 2013 17:20:37 +0100 Subject: [PATCH 37/46] [#352] Hide "Create one now?" link if not authorized --- ckan/templates/user/read.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ckan/templates/user/read.html b/ckan/templates/user/read.html index 9a272ecfa3e..c2afb1bca6c 100644 --- a/ckan/templates/user/read.html +++ b/ckan/templates/user/read.html @@ -11,7 +11,9 @@

    {{ _('Datasets') }}

    {% if c.is_myself %}

    {{ _('You haven\'t created any datasets.') }} - {% link_for _('Create one now?'), controller='package', action='new' %}. + {% if h.check_access('package_create') %} + {% link_for _('Create one now?'), controller='package', action='new' %} + {% endif %}

    {% else %}

    @@ -20,4 +22,4 @@

    {{ _('Datasets') }}

    {% endif %} {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} From a915602a6a2c2be580ffddcf7012076a04ff5810 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 4 Feb 2013 17:50:56 +0100 Subject: [PATCH 38/46] [#309] Tidy up a couple of comments No point in putting "TODO" comments in the source code no one will remember to go back and do them and the comment may become outdated/misleading. --- ckan/lib/activity_streams_session_extension.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ckan/lib/activity_streams_session_extension.py b/ckan/lib/activity_streams_session_extension.py index c2d9713fff2..08a6e37756c 100644 --- a/ckan/lib/activity_streams_session_extension.py +++ b/ckan/lib/activity_streams_session_extension.py @@ -79,8 +79,8 @@ def before_commit(self, session): # object is a package. logger.debug("Looks like this object is a package") logger.debug("activity: %s" % activity) - ## Ignore private datasets for 2.0, - ## fix later to have privicy against streams + + # Don't create activities for private datasets. if obj.private: continue @@ -118,8 +118,8 @@ def before_commit(self, session): for package in related_packages: if package is None: continue - ## Ignore private datasets for 2.0, - ## fix later to have privicy against streams + + # Don't create activities for private datasets. if package.private: continue From 680c58fffde62c6834cfb006f94d7eb1ab592845 Mon Sep 17 00:00:00 2001 From: John Glover Date: Mon, 4 Feb 2013 17:58:16 +0100 Subject: [PATCH 39/46] [#285] PEP8 --- ckan/controllers/api.py | 7 +++---- ckan/lib/base.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ckan/controllers/api.py b/ckan/controllers/api.py index db87ceda02b..f13e8eb7e14 100644 --- a/ckan/controllers/api.py +++ b/ckan/controllers/api.py @@ -546,7 +546,7 @@ def search(self, ver=None, register=None): def _get_search_params(cls, request_params): if 'qjson' in request_params: try: - qjson_param = request_params['qjson'].replace('\\\\u','\\u') + qjson_param = request_params['qjson'].replace('\\\\u', '\\u') params = h.json.loads(qjson_param, encoding='utf8') except ValueError, e: raise ValueError(gettext('Malformed qjson value') + ': %r' @@ -669,9 +669,8 @@ def group_exists(val): def dataset_autocomplete(self): q = request.params.get('incomplete', '') - q_lower = q.lower() limit = request.params.get('limit', 10) - tag_names = [] + package_dicts = [] if q: context = {'model': model, 'session': model.Session, 'user': c.user or c.author} @@ -753,7 +752,7 @@ def i18n_js_translations(self, lang): ''' translation strings for front end ''' ckan_path = os.path.join(os.path.dirname(__file__), '..') source = os.path.abspath(os.path.join(ckan_path, 'public', - 'base', 'i18n', '%s.js' % lang)) + 'base', 'i18n', '%s.js' % lang)) response.headers['Content-Type'] = CONTENT_TYPES['json'] if not os.path.exists(source): return '{}' diff --git a/ckan/lib/base.py b/ckan/lib/base.py index 3fb56b3bbd7..33253d6e0d0 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -86,7 +86,7 @@ def render_jinja2(template_name, extra_vars): def render(template_name, extra_vars=None, cache_key=None, cache_type=None, cache_expire=None, method='xhtml', loader_class=MarkupTemplate, - cache_force = None, renderer=None): + cache_force=None, renderer=None): ''' Main template rendering function. ''' def render_template(): @@ -101,12 +101,12 @@ def render_template(): try: template_path, template_type = lib.render.template_info(template_name) except lib.render.TemplateNotFound: - template_type = 'genshi' + template_type = 'genshi' template_path = '' # snippets should not pass the context # but allow for legacy genshi templates - if renderer == 'snippet' and template_type != 'genshi': + if renderer == 'snippet' and template_type != 'genshi': del globs['c'] del globs['tmpl_context'] @@ -115,12 +115,12 @@ def render_template(): context_vars = globs.get('c') if context_vars: context_vars = dir(context_vars) - debug_info = {'template_name' : template_name, - 'template_path' : template_path, - 'template_type' : template_type, - 'vars' : globs, + debug_info = {'template_name': template_name, + 'template_path': template_path, + 'template_type': template_type, + 'vars': globs, 'c_vars': context_vars, - 'renderer' : renderer,} + 'renderer': renderer} if 'CKAN_DEBUG_INFO' not in request.environ: request.environ['CKAN_DEBUG_INFO'] = [] request.environ['CKAN_DEBUG_INFO'].append(debug_info) @@ -221,9 +221,9 @@ def __before__(self, action, **params): if c.userobj: from ckan.logic import get_action new_activities_count = get_action( - 'dashboard_new_activities_count') + 'dashboard_new_activities_count') context = {'model': model, 'session': model.Session, - 'user': c.user or c.author} + 'user': c.user or c.author} c.new_activities = new_activities_count(context, {}) def _identify_user(self): From db2b4627fb08652e476047474129cdb3d2631085 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 5 Feb 2013 19:39:56 +0100 Subject: [PATCH 40/46] [#345] Remove confusing warning if the datastore is running on a read only db --- ckanext/datastore/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ckanext/datastore/plugin.py b/ckanext/datastore/plugin.py index 412ba37f48c..cdb547a2211 100644 --- a/ckanext/datastore/plugin.py +++ b/ckanext/datastore/plugin.py @@ -68,8 +68,7 @@ def configure(self, config): self._create_alias_table() else: log.warn("We detected that CKAN is running on a read only database. " - "Permission checks and _table_metadata creation are skipped." - "Make sure that replication is properly set-up.") + "Permission checks and the creation of _table_metadata are skipped.") else: log.warn("We detected that you do not use a PostgreSQL database. " "The DataStore will NOT work and datastore tests will be skipped.") From 42e2a197768947da883233cb97c6a2db02041892 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 5 Feb 2013 19:41:35 +0100 Subject: [PATCH 41/46] [#345] Fix detection in engine_is_pg --- ckan/model/meta.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ckan/model/meta.py b/ckan/model/meta.py index b8d6b42cd65..ce175d309d7 100644 --- a/ckan/model/meta.py +++ b/ckan/model/meta.py @@ -161,4 +161,6 @@ def engine_is_sqlite(): def engine_is_pg(): # Returns true iff the engine is connected to a postgresql database. - return engine.url.drivername in ['psycopg2', 'postgres'] + # According to http://docs.sqlalchemy.org/en/latest/core/engines.html#postgresql + # all Postgres driver names start with `postgresql` + return engine.url.drivername.startswith('postgresql') From fb42565bb835aa6fec3691bdd97e6f1a69676a61 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 6 Feb 2013 18:08:04 +0100 Subject: [PATCH 42/46] [#252] Delete "Add Related" link from dataset page The "Related" sidebar box on the dataset page has both an "+Add Item" button and an "Add Related" link, seems only one is necessary. --- ckan/templates/snippets/related.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ckan/templates/snippets/related.html b/ckan/templates/snippets/related.html index 1d501677bfd..380a6276709 100644 --- a/ckan/templates/snippets/related.html +++ b/ckan/templates/snippets/related.html @@ -1,5 +1,5 @@
    -

    Related Add Related

    +

    Related

    {% if item %} {% with url = h.url_for(controller='related', action='list', id=pkg_name) %} From 2aa75befac1325184d26e4e367e5be5fd4634a84 Mon Sep 17 00:00:00 2001 From: John Martin Date: Wed, 6 Feb 2013 19:32:14 +0000 Subject: [PATCH 43/46] [#328] Removed followers tab from group read page as it's not in master --- ckan/templates/group/read_base.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/ckan/templates/group/read_base.html b/ckan/templates/group/read_base.html index 323e12fae90..a16615b7777 100644 --- a/ckan/templates/group/read_base.html +++ b/ckan/templates/group/read_base.html @@ -26,9 +26,6 @@ {% link_for _('Activity Stream'), controller='group', action='activity', id=c.group_dict.name, icon='time' %} - - {% link_for _('Followers'), controller='group', action='followers', id=c.group_dict.name, icon='group' %} - {% link_for _('About'), controller='group', action='about', id=c.group_dict.name, icon='info-sign' %} From 6af04e7ed5e5d5f8b5081a1e59e378dbaa470047 Mon Sep 17 00:00:00 2001 From: amercader Date: Thu, 7 Feb 2013 18:39:57 +0000 Subject: [PATCH 44/46] Accessibility standards require attributes. Thanks @tauberer for the patch --- ckan/templates/admin/authz.html | 16 +++++++-------- ckan/templates/ajax_snippets/api_info.html | 8 ++++---- ckan/templates/organization/members.html | 6 +++--- ckan/templates/package/resource_read.html | 6 +++--- .../package/snippets/additional_info.html | 20 +++++++++---------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ckan/templates/admin/authz.html b/ckan/templates/admin/authz.html index cecb18a9f75..5b60f95cc27 100644 --- a/ckan/templates/admin/authz.html +++ b/ckan/templates/admin/authz.html @@ -14,9 +14,9 @@ {% endfor %} - {{ _('User') }} + {{ _('User') }} {% for role in roles %} - {{ role }} + {{ role }} {% endfor %} {% for user in users %} @@ -49,9 +49,9 @@ {% endfor %} - {{ _('User') }} + {{ _('User') }} {% for role in roles %} - {{ role }} + {{ role }} {% endfor %} @@ -77,9 +77,9 @@ {% endfor %} - {{ _('User Group') }} + {{ _('User Group') }} {% for role in roles %} - {{ role }} + {{ role }} {% endfor %} {% for user in users %} @@ -112,9 +112,9 @@ {% endfor %} - User Group + User Group {% for role in roles %} - {{ role }} + {{ role }} {% endfor %} diff --git a/ckan/templates/ajax_snippets/api_info.html b/ckan/templates/ajax_snippets/api_info.html index f339054e86d..20fc44281b8 100644 --- a/ckan/templates/ajax_snippets/api_info.html +++ b/ckan/templates/ajax_snippets/api_info.html @@ -39,19 +39,19 @@

    - Create + Create {{ datastore_root_url }}/datastore_create - Update / Insert + Update / Insert {{ datastore_root_url }}/datastore_upsert - Query + Query {{ datastore_root_url }}/datastore_search - Query (via SQL) + Query (via SQL) {{ datastore_root_url }}/datastore_search_sql diff --git a/ckan/templates/organization/members.html b/ckan/templates/organization/members.html index fd0e34b6496..175af5e7566 100644 --- a/ckan/templates/organization/members.html +++ b/ckan/templates/organization/members.html @@ -11,9 +11,9 @@

    {{ _('Members') }}

    - {{ _('User') }} - {{ _('Role') }} - + {{ _('User') }} + {{ _('Role') }} + diff --git a/ckan/templates/package/resource_read.html b/ckan/templates/package/resource_read.html index 0f6f5c6d849..32dbcfc0a03 100644 --- a/ckan/templates/package/resource_read.html +++ b/ckan/templates/package/resource_read.html @@ -79,13 +79,13 @@

    Additional Information

    - - + + {% for key, value in h.format_resource_items(res.items()) %} - + {% endfor %}
    FieldValueFieldValue
    {{ key }}{{ value }}
    {{ key }}{{ value }}
    diff --git a/ckan/templates/package/snippets/additional_info.html b/ckan/templates/package/snippets/additional_info.html index 82e1eabd7d8..64821f08f59 100644 --- a/ckan/templates/package/snippets/additional_info.html +++ b/ckan/templates/package/snippets/additional_info.html @@ -3,52 +3,52 @@

    {{ _('Additional Info') }}

    - - + + {% if pkg_dict.url %} - + {% endif %} {% if pkg_dict.author_email %} - + {% elif pkg_dict.author %} - + {% endif %} {% if pkg_dict.maintainer_email %} - + {% elif pkg_dict.maintainer %} - + {% endif %} {% if pkg_dict.version %} - + {% endif %} {% if h.check_access('package_update',{'id':pkg_dict.id}) %} - + {% endif %} @@ -56,7 +56,7 @@

    {{ _('Additional Info') }}

    {% for extra in h.sorted_extras(pkg_dict.extras) %} {% set key, value = extra %} - + {% endfor %} From 3d699be62889f3085c6329ba878464b4472fe30f Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Fri, 8 Feb 2013 15:17:47 +0100 Subject: [PATCH 45/46] Update links in source install docs Wiki has moved from wiki.ckan.org to github --- doc/install-from-source.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/install-from-source.rst b/doc/install-from-source.rst index 032d777fa1a..ace9dfc2235 100644 --- a/doc/install-from-source.rst +++ b/doc/install-from-source.rst @@ -7,13 +7,13 @@ This section describes how to install CKAN from source. Although CKAN from source works with Ubuntu 10.04, with other versions of Ubuntu (e.g. 12.04) and with other operating systems (e.g. RedHat, Fedora, CentOS, OS X). If you install CKAN from source on your own operating system, please share your -experiences on our wiki: http://wiki.ckan.org/Install +experiences on our `How to Install CKAN `_ +wiki page. From source is also the right installation method for developers who want to work on CKAN. -If you run into problems, see :doc:`common-error-messages` or contact `the -ckan-dev mailing list `_. +If you run into problems, see :doc:`common-error-messages`. 1. Install the required packages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 57afa6d3cd22bf04883284da26c999afa80f3c91 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Fri, 8 Feb 2013 15:22:33 +0100 Subject: [PATCH 46/46] Update another link in the source install docs --- doc/install-from-source.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/install-from-source.rst b/doc/install-from-source.rst index ace9dfc2235..2475503c1b8 100644 --- a/doc/install-from-source.rst +++ b/doc/install-from-source.rst @@ -25,7 +25,8 @@ required packages with this command:: If you're not using a Debian-based operating system, find the best way to install the following packages on your operating system (see -http://wiki.ckan.org/Install for help): +our `How to Install CKAN `_ +wiki page for help): ===================== =============================================== Package Description
    {{ _('Field') }}{{ _('Value') }}{{ _('Field') }}{{ _('Value') }}
    {{ _('Source') }}{{ _('Source') }} {{ h.link_to(pkg_dict.url, pkg_dict.url, rel='foaf:homepage', target='_blank') }}
    {{ _("Author") }}{{ _("Author") }} {{ h.mail_to(email_address=pkg_dict.author_email, name=pkg_dict.author) }}
    {{ _("Author") }}{{ _("Author") }} {{ pkg_dict.author }}
    {{ _('Maintainer') }}{{ _('Maintainer') }} {{ h.mail_to(email_address=pkg_dict.maintainer_email, name=pkg_dict.maintainer) }}
    {{ _('Maintainer') }}{{ _('Maintainer') }} {{ pkg_dict.maintainer }}
    {{ _("Version") }}{{ _("Version") }} {{ pkg_dict.version }}
    {{ _("State") }}{{ _("State") }} {{ pkg_dict.state }}
    {{ _(key) }}{{ _(key) }} {{ value }}