Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/ckan/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Deinok committed May 25, 2016
2 parents 61d7349 + dc3bb9e commit b97e102
Show file tree
Hide file tree
Showing 30 changed files with 496 additions and 62 deletions.
4 changes: 2 additions & 2 deletions README.rst
Expand Up @@ -12,8 +12,8 @@ CKAN: The Open Source Data Portal Software
:target: https://stackoverflow.com/questions/tagged/ckan
:alt: Support on StackOverflow

.. image:: https://secure.travis-ci.org/ckan/ckan.png?branch=master
:target: http://travis-ci.org/ckan/ckan
.. image:: https://circleci.com/gh/ckan/ckan.svg?style=shield
:target: https://circleci.com/gh/ckan/ckan
:alt: Build Status

.. image:: https://coveralls.io/repos/github/ckan/ckan/badge.svg?branch=master
Expand Down
20 changes: 11 additions & 9 deletions ckan/controllers/feed.py
Expand Up @@ -188,24 +188,26 @@ def _group_or_organization(self, obj_dict, is_org):
action=group_type,
id=obj_dict['name'])

guid = _create_atom_id(u'/feeds/group/%s.atom' %
obj_dict['name'])
alternate_url = self._alternate_url(params, groups=obj_dict['name'])
desc = u'Recently created or updated datasets on %s by group: "%s"' %\
(g.site_title, obj_dict['title'])
title = u'%s - Group: "%s"' %\
(g.site_title, obj_dict['title'])

if is_org:
guid = _create_atom_id(u'/feeds/organization/%s.atom' %
obj_dict['name'])
alternate_url = self._alternate_url(params,
organization=obj_dict['name'])
desc = u'Recently created or updated datasets on %s '\
desc = u'Recently created or updated datasets on %s '\
'by organization: "%s"' % (g.site_title, obj_dict['title'])
title = u'%s - Organization: "%s"' %\
(g.site_title, obj_dict['title'])

else: # is group
guid = _create_atom_id(u'/feeds/group/%s.atom' %
obj_dict['name'])
alternate_url = self._alternate_url(params,
groups=obj_dict['name'])
desc = u'Recently created or updated datasets on %s '\
'by group: "%s"' % (g.site_title, obj_dict['title'])
title = u'%s - Group: "%s"' %\
(g.site_title, obj_dict['title'])

return self.output_feed(results,
feed_title=title,
feed_description=desc,
Expand Down
13 changes: 6 additions & 7 deletions ckan/controllers/group.py
Expand Up @@ -159,6 +159,7 @@ def index(self):
sort_by = c.sort_by_selected = request.params.get('sort')
try:
self._check_access('site_read', context)
self._check_access('group_list', context)
except NotAuthorized:
abort(403, _('Not authorized to see this page'))

Expand Down Expand Up @@ -380,10 +381,6 @@ def bulk_process(self, id):
group_type = self._ensure_controller_matches_group_type(
id.split('@')[0])

if group_type != 'organization':
# FIXME: better error
raise Exception('Must be an organization')

# check we are org admin

context = {'model': model, 'session': model.Session,
Expand All @@ -401,6 +398,10 @@ def bulk_process(self, id):
except (NotFound, NotAuthorized):
abort(404, _('Group not found'))

if not c.group_dict['is_organization']:
# FIXME: better error
raise Exception('Must be an organization')

#use different form names so that ie7 can be detected
form_names = set(["bulk_action.public", "bulk_action.delete",
"bulk_action.private"])
Expand Down Expand Up @@ -443,9 +444,7 @@ def bulk_process(self, id):
get_action(action_functions[action])(context, data_dict)
except NotAuthorized:
abort(403, _('Not authorized to perform bulk update'))
base.redirect(h.url_for(controller='organization',
action='bulk_process',
id=id))
self._redirect_to_this_controller(action='bulk_process', id=id)

def new(self, data=None, errors=None, error_summary=None):
if data and 'type' in data:
Expand Down
25 changes: 21 additions & 4 deletions ckan/controllers/user.py
Expand Up @@ -35,6 +35,15 @@
unflatten = dictization_functions.unflatten


def set_repoze_user(user_id):
'''Set the repoze.who cookie to match a given user_id'''
if 'repoze.who.plugins' in request.environ:
rememberer = request.environ['repoze.who.plugins']['friendlyform']
identity = {'repoze.who.userid': user_id}
response.headerlist += rememberer.remember(request.environ,
identity)


class UserController(base.BaseController):
def __before__(self, action, **env):
base.BaseController.__before__(self, action, **env)
Expand Down Expand Up @@ -245,10 +254,7 @@ def _save_new(self, context):
return self.new(data_dict, errors, error_summary)
if not c.user:
# log the user in programatically
rememberer = request.environ['repoze.who.plugins']['friendlyform']
identity = {'repoze.who.userid': data_dict['name']}
response.headerlist += rememberer.remember(request.environ,
identity)
set_repoze_user(data_dict['name'])
h.redirect_to(controller='user', action='me', __ckan_no_root=True)
else:
# #1799 User has managed to register whilst logged in - warn user
Expand Down Expand Up @@ -321,6 +327,12 @@ def edit(self, id=None, data=None, errors=None, error_summary=None):

def _save_edit(self, id, context):
try:
if id in (c.userobj.id, c.userobj.name):
current_user = True
else:
current_user = False
old_username = c.userobj.name

data_dict = logic.clean_dict(unflatten(
logic.tuplize_dict(logic.parse_params(request.params))))
context['message'] = data_dict.get('log_message', '')
Expand All @@ -343,6 +355,11 @@ def _save_edit(self, id, context):

user = get_action('user_update')(context, data_dict)
h.flash_success(_('Profile updated'))

if current_user and data_dict['name'] != old_username:
# Changing currently logged in user's name.
# Update repoze.who cookie to match
set_repoze_user(data_dict['name'])
h.redirect_to(controller='user', action='read', id=user['name'])
except NotAuthorized:
abort(403, _('Unauthorized to edit user %s') % id)
Expand Down
4 changes: 2 additions & 2 deletions ckan/lib/helpers.py
Expand Up @@ -54,8 +54,8 @@ def __init__(self, *args, **kwargs):

def __getitem__(self, key):
try:
value = super(HelperAttributeDict, self).__getitem__(self, key)
except AttributeError:
value = super(HelperAttributeDict, self).__getitem__(key)
except KeyError:
raise ckan.exceptions.HelperError(
'Helper \'{key}\' has not been defined.'.format(
key=key
Expand Down
5 changes: 4 additions & 1 deletion ckan/lib/jinja_extensions.py
Expand Up @@ -62,7 +62,10 @@ class CkanInternationalizationExtension(ext.InternationalizationExtension):

def parse(self, parser):
node = ext.InternationalizationExtension.parse(self, parser)
args = getattr(node.nodes[0], 'args', None)
if isinstance(node, list):
args = getattr(node[1].nodes[0], 'args', None)
else:
args = getattr(node.nodes[0], 'args', None)
if args:
for arg in args:
if isinstance(arg, nodes.Const):
Expand Down
2 changes: 1 addition & 1 deletion ckan/lib/munge.py
Expand Up @@ -139,7 +139,7 @@ def munge_filename(filename):
# clean up
filename = substitute_ascii_equivalents(filename)
filename = filename.lower().strip()
filename = re.sub(r'[^a-zA-Z0-9. -]', '', filename).replace(' ', '-')
filename = re.sub(r'[^a-zA-Z0-9_. -]', '', filename).replace(' ', '-')
# resize if needed but keep extension
name, ext = os.path.splitext(filename)
# limit overly long extensions
Expand Down
23 changes: 17 additions & 6 deletions ckan/lib/plugins.py
Expand Up @@ -178,7 +178,7 @@ def register_group_plugins(map):
'/%s/{action}/{id}' % group_type,
controller=group_controller,
requirements=dict(action='|'.join(
['edit', 'authz', 'history', 'member_new',
['edit', 'authz', 'delete', 'history', 'member_new',
'member_delete', 'followers', 'follow',
'unfollow', 'admins', 'activity'])))
map.connect('%s_edit' % group_type, '/%s/edit/{id}' % group_type,
Expand All @@ -193,6 +193,13 @@ def register_group_plugins(map):
'/%s/activity/{id}/{offset}' % group_type,
controller=group_controller,
action='activity', ckan_icon='time'),
map.connect('%s_about' % group_type, '/%s/about/{id}' % group_type,
controller=group_controller,
action='about', ckan_icon='info-sign')
map.connect('%s_bulk_process' % group_type,
'/%s/bulk_process/{id}' % group_type,
controller=group_controller,
action='bulk_process', ckan_icon='sitemap')

if group_type in _group_plugins:
raise ValueError("An existing IGroupForm is "
Expand All @@ -201,12 +208,16 @@ def register_group_plugins(map):
_group_plugins[group_type] = plugin
_group_controllers[group_type] = group_controller

controller_obj = None
# If using one of the default controllers, tell it that it is allowed
# to handle other group_types.
# Import them here to avoid circular imports.
if group_controller == 'group':
# Tell the default group controller that it is allowed to
# handle other group_types.
# Import it here to avoid circular imports.
from ckan.controllers.group import GroupController
GroupController.add_group_type(group_type)
from ckan.controllers.group import GroupController as controller_obj
elif group_controller == 'organization':
from ckan.controllers.organization import OrganizationController as controller_obj
if controller_obj is not None:
controller_obj.add_group_type(group_type)

# Setup the fallback behaviour if one hasn't been defined.
if _default_group_plugin is None:
Expand Down
5 changes: 5 additions & 0 deletions ckan/pastertemplates/template/bin/travis-build.bash_tmpl
Expand Up @@ -22,6 +22,11 @@ echo "Creating the PostgreSQL user and database..."
sudo -u postgres psql -c "CREATE USER ckan_default WITH PASSWORD 'pass';"
sudo -u postgres psql -c 'CREATE DATABASE ckan_test WITH OWNER ckan_default;'

echo "SOLR config..."
# Solr is multicore for tests on ckan master, but it's easier to run tests on
# Travis single-core. See https://github.com/ckan/ckan/issues/2972
sed -i -e 's/solr_url.*/solr_url = http:\/\/127.0.0.1:8983\/solr/' ckan/test-core.ini

echo "Initialising the database..."
cd ckan
paster db init -c test-core.ini
Expand Down
2 changes: 1 addition & 1 deletion ckan/templates/group/index.html
Expand Up @@ -34,7 +34,7 @@ <h1 class="hide-heading">{{ _('Groups') }}</h1>
{% endif %}
{% endblock %}
{% block page_pagination %}
{{ c.page.pager() }}
{{ c.page.pager(q=c.q or '', sort=c.sort_by_selected or '') }}
{% endblock %}
{% endblock %}

Expand Down
5 changes: 5 additions & 0 deletions ckan/templates/tests/broken_helper_as_attribute.html
@@ -0,0 +1,5 @@
{% extends 'page.html' %}

{% block page %}
{{ h.not_here() }}
{% endblock %}
5 changes: 5 additions & 0 deletions ckan/templates/tests/broken_helper_as_item.html
@@ -0,0 +1,5 @@
{% extends 'page.html' %}

{% block page %}
{{ h['not_here']() }}
{% endblock %}
5 changes: 5 additions & 0 deletions ckan/templates/tests/helper_as_attribute.html
@@ -0,0 +1,5 @@
{% extends 'page.html' %}

{% block page %}
My lang is: {{ h.lang() }}
{% endblock %}
5 changes: 5 additions & 0 deletions ckan/templates/tests/helper_as_item.html
@@ -0,0 +1,5 @@
{% extends 'page.html' %}

{% block page %}
My lang is: {{ h['lang']() }}
{% endblock %}
31 changes: 24 additions & 7 deletions ckan/tests/controllers/test_group.py
Expand Up @@ -23,17 +23,34 @@ def test_bulk_process_throws_404_for_nonexistent_org(self):
action='bulk_process', id='does-not-exist')
app.get(url=bulk_process_url, status=404)

def test_page_thru_list_of_orgs(self):
orgs = [factories.Organization() for i in range(35)]
def test_page_thru_list_of_orgs_preserves_sort_order(self):
orgs = [factories.Organization() for _ in range(35)]
app = self._get_test_app()
org_url = url_for(controller='organization', action='index')
org_url = url_for(controller='organization',
action='index',
sort='name desc')
response = app.get(url=org_url)
assert orgs[0]['name'] in response
assert orgs[-1]['name'] not in response
assert orgs[-1]['name'] in response
assert orgs[0]['name'] not in response

response2 = response.click('2')
assert orgs[0]['name'] not in response2
assert orgs[-1]['name'] in response2
assert orgs[-1]['name'] not in response2
assert orgs[0]['name'] in response2

def test_page_thru_list_of_groups_preserves_sort_order(self):
groups = [factories.Group() for _ in range(35)]
app = self._get_test_app()
group_url = url_for(controller='group',
action='index',
sort='title desc')

response = app.get(url=group_url)
assert groups[-1]['title'] in response
assert groups[0]['title'] not in response

response2 = response.click(r'^2$')
assert groups[-1]['title'] not in response2
assert groups[0]['title'] in response2


def _get_group_new_page(app):
Expand Down
17 changes: 17 additions & 0 deletions ckan/tests/controllers/test_organization.py
@@ -1,6 +1,7 @@
from bs4 import BeautifulSoup
from nose.tools import assert_equal, assert_true
from routes import url_for
from mock import patch

from ckan.tests import factories, helpers
from ckan.tests.helpers import webtest_submit, submit_and_follow, assert_in
Expand Down Expand Up @@ -61,6 +62,22 @@ def test_all_fields_saved(self):
assert_equal(group['description'], 'Sciencey datasets')


class TestOrganizationList(helpers.FunctionalTestBase):
def setup(self):
super(TestOrganizationList, self).setup()
self.app = helpers._get_test_app()
self.user = factories.User()
self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')}
self.organization_list_url = url_for(controller='organization',
action='index')

@patch('ckan.logic.auth.get.organization_list', return_value={'success': False})
def test_error_message_shown_when_no_organization_list_permission(self, mock_check_access):
response = self.app.get(url=self.organization_list_url,
extra_environ=self.user_env,
status=403)


class TestOrganizationRead(helpers.FunctionalTestBase):
def setup(self):
super(TestOrganizationRead, self).setup()
Expand Down
14 changes: 14 additions & 0 deletions ckan/tests/controllers/test_package.py
Expand Up @@ -1569,3 +1569,17 @@ def test_package_follower_list(self):
followers_response = app.get(followers_url, extra_environ=env,
status=200)
assert_true(user_one['display_name'] in followers_response)


class TestDatasetRead(helpers.FunctionalTestBase):

def test_dataset_read(self):
app = self._get_test_app()

dataset = factories.Dataset()

url = url_for(controller='package',
action='read',
id=dataset['id'])
response = app.get(url)
assert_in(dataset['title'], response)

0 comments on commit b97e102

Please sign in to comment.