Skip to content

Commit

Permalink
Merge branch '1102-dataset-group-add'
Browse files Browse the repository at this point in the history
  • Loading branch information
amercader committed Dec 9, 2013
2 parents 10ea251 + b377ec2 commit 4e89ef8
Show file tree
Hide file tree
Showing 19 changed files with 164 additions and 71 deletions.
3 changes: 3 additions & 0 deletions ckan/config/routing.py
Expand Up @@ -229,6 +229,7 @@ def make_map():
'history_ajax',
'follow',
'activity',
'groups',
'unfollow',
'delete',
'api_data',
Expand All @@ -240,6 +241,8 @@ def make_map():
m.connect('dataset_activity', '/dataset/activity/{id}',
action='activity', ckan_icon='time')
m.connect('/dataset/activity/{id}/{offset}', action='activity')
m.connect('dataset_groups', '/dataset/groups/{id}',
action='groups', ckan_icon='group')
m.connect('/dataset/{id}.{format}', action='read')
m.connect('dataset_resources', '/dataset/resources/{id}',
action='resources', ckan_icon='reorder')
Expand Down
2 changes: 1 addition & 1 deletion ckan/controllers/group.py
Expand Up @@ -643,7 +643,7 @@ def member_new(self, id):
else:
c.user_role = 'member'
c.group_dict = self._action('group_show')(context, {'id': id})
c.roles = self._action('member_roles_list')(context, {})
c.roles = self._action('member_roles_list')(context, {'group_type': 'group'})
except NotAuthorized:
abort(401, _('Unauthorized to add member to group %s') % '')
except NotFound:
Expand Down
56 changes: 56 additions & 0 deletions ckan/controllers/package.py
Expand Up @@ -1302,6 +1302,62 @@ def followers(self, id=None):

return render('package/followers.html')

def groups(self, id):
context = {'model': model, 'session': model.Session,
'user': c.user or c.author, 'for_view': True,
'auth_user_obj': c.userobj, 'use_cache': False}
data_dict = {'id': id}
try:
c.pkg_dict = get_action('package_show')(context, data_dict)
except NotFound:
abort(404, _('Dataset not found'))
except NotAuthorized:
abort(401, _('Unauthorized to read dataset %s') % id)

if request.method == 'POST':
new_group = request.POST.get('group_added')
if new_group:
data_dict = {"id": new_group,
"object": id,
"object_type": 'package',
"capacity": 'public'}
try:
get_action('member_create')(context, data_dict)
except NotFound:
abort(404, _('Group not found'))

removed_group = request.POST.get('group_removed')
if removed_group:
data_dict = {"id": removed_group,
"object": id,
"object_type": 'package'}

try:
get_action('member_delete')(context, data_dict)
except NotFound:
abort(404, _('Group not found'))
redirect(h.url_for(controller='package',
action='groups', id=id))



context['is_member'] = True
users_groups = get_action('group_list_authz')(context, data_dict)

pkg_group_ids = set(group['id'] for group
in c.pkg_dict.get('groups', []))
user_group_ids = set(group['id'] for group
in users_groups)

c.group_dropdown = [[group['id'], group['display_name']]
for group in users_groups if
group['id'] not in pkg_group_ids]

for group in c.pkg_dict.get('groups', []):
group['user_member'] = (group['id'] in user_group_ids)

return render('package/group_list.html')

def activity(self, id):
'''Render this package's public activity stream page.'''

Expand Down
38 changes: 23 additions & 15 deletions ckan/lib/dictization/model_dictize.py
Expand Up @@ -15,18 +15,18 @@
## package save

def group_list_dictize(obj_list, context,
sort_key=lambda x:x['display_name'], reverse=False):
sort_key=lambda x:x['display_name'], reverse=False,
with_package_counts=True):

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', 'owner_org'],
'facet.limit': -1, 'rows': 1}

query.run(q)
if with_package_counts:
query = search.PackageSearchQuery()
q = {'q': '+capacity:public' if not with_private else '*:*',
'fl': 'groups', 'facet.field': ['groups', 'owner_org'],
'facet.limit': -1, 'rows': 1}
query.run(q)

result_list = []

Expand All @@ -40,7 +40,8 @@ def group_list_dictize(obj_list, context,
if active and obj.state not in ('active', 'pending'):
continue

group_dict['display_name'] = obj.display_name
group_dict['display_name'] = (group_dict.get('title') or
group_dict.get('name'))

image_url = group_dict.get('image_url')
group_dict['image_display_url'] = image_url
Expand All @@ -49,13 +50,16 @@ def group_list_dictize(obj_list, context,
#of potential vulnerability of dodgy api input
image_url = munge.munge_filename(image_url)
group_dict['image_display_url'] = h.url_for_static(
'uploads/group/%s' % group_dict.get('image_url')
'uploads/group/%s' % group_dict.get('image_url'),
qualified=True
)

if obj.is_organization:
group_dict['packages'] = query.facets['owner_org'].get(obj.id, 0)
else:
group_dict['packages'] = query.facets['groups'].get(obj.name, 0)
if with_package_counts:
facets = query.facets
if obj.is_organization:
group_dict['packages'] = facets['owner_org'].get(obj.id, 0)
else:
group_dict['packages'] = facets['groups'].get(obj.name, 0)

if context.get('for_view'):
if group_dict['is_organization']:
Expand Down Expand Up @@ -274,7 +278,11 @@ def package_dictize(pkg, context):
.where(member_rev.c.state == 'active') \
.where(group.c.is_organization == False)
result = _execute_with_revision(q, member_rev, context)
result_dict["groups"] = d.obj_list_dictize(result, context)
context['with_capacity'] = False
## no package counts as cannot fetch from search index at the same
## time as indexing to it.
result_dict["groups"] = group_list_dictize(result, context,
with_package_counts=False)
#owning organization
group_rev = model.group_revision_table
q = select([group_rev]
Expand Down
4 changes: 1 addition & 3 deletions ckan/logic/action/create.py
Expand Up @@ -469,9 +469,7 @@ def member_create(context, data_dict=None):
if not obj:
raise NotFound('%s was not found.' % obj_type.title())


# User must be able to update the group to add a member to it
_check_access('group_update', context, data_dict)
_check_access('member_create', context, data_dict)

# Look up existing, in case it exists
member = model.Session.query(model.Member).\
Expand Down
3 changes: 1 addition & 2 deletions ckan/logic/action/delete.py
Expand Up @@ -226,8 +226,7 @@ def member_delete(context, data_dict=None):
if not obj:
raise NotFound('%s was not found.' % obj_type.title())

# User must be able to update the group to remove a member from it
_check_access('group_update', context, data_dict)
_check_access('member_create', context, data_dict)

member = model.Session.query(model.Member).\
filter(model.Member.table_name == obj_type).\
Expand Down
14 changes: 12 additions & 2 deletions ckan/logic/action/get.py
Expand Up @@ -335,6 +335,7 @@ def translated_capacity(capacity):
def _group_or_org_list(context, data_dict, is_org=False):

model = context['model']
user = context['user']
api = context.get('api_version')
groups = data_dict.get('groups')
ref_group_by = 'id' if api == 2 else 'name'
Expand Down Expand Up @@ -464,7 +465,7 @@ def group_list_authz(context, data_dict):
_check_access('group_list_authz',context, data_dict)

sysadmin = new_authz.is_sysadmin(user)
roles = ckan.new_authz.get_roles_with_permission('edit_group')
roles = ckan.new_authz.get_roles_with_permission('manage_group')
if not roles:
return []
user_id = new_authz.get_user_id_for_username(user, allow_none=True)
Expand Down Expand Up @@ -2925,11 +2926,20 @@ def _unpick_search(sort, allowed_fields=None, total=None):
def member_roles_list(context, data_dict):
'''Return the possible roles for members of groups and organizations.
:param group_type: the group type, either "group" or "organization"
(optional, default "organization")
:type id: string
:returns: a list of dictionaries each with two keys: "text" (the display
name of the role, e.g. "Admin") and "value" (the internal name of the
role, e.g. "admin")
:rtype: list of dictionaries
'''
group_type = data_dict.get('group_type', 'organization')
roles_list = new_authz.roles_list()
if group_type == 'group':
roles_list = [role for role in roles_list
if role['value'] != 'editor']

_check_access('member_roles_list', context, data_dict)
return new_authz.roles_list()
return roles_list
21 changes: 21 additions & 0 deletions ckan/logic/auth/create.py
@@ -1,5 +1,6 @@
import ckan.logic as logic
import ckan.new_authz as new_authz
import ckan.logic.auth as logic_auth

from ckan.common import _

Expand Down Expand Up @@ -207,3 +208,23 @@ def organization_member_create(context, data_dict):

def group_member_create(context, data_dict):
return _group_or_org_member_create(context, data_dict)

def member_create(context, data_dict):
group = logic_auth.get_group_object(context, data_dict)
user = context['user']

# User must be able to update the group to add a member to it
permission = 'update'
# However if the user is member of group then they can add/remove datasets
if not group.is_organization and data_dict.get('object_type') == 'package':
permission = 'manage_group'

authorized = new_authz.has_user_permission_for_group_or_org(group.id,
user,
permission)
if not authorized:
return {'success': False,
'msg': _('User %s not authorized to edit group %s') %
(str(user), group.id)}
else:
return {'success': True}
2 changes: 2 additions & 0 deletions ckan/logic/schema.py
Expand Up @@ -223,6 +223,8 @@ def default_show_package_schema():

schema['groups'].update({
'description': [ignore_missing],
'display_name': [ignore_missing],
'image_display_url': [ignore_missing],
})

# Remove validators for several keys from the schema so validation doesn't
Expand Down
4 changes: 2 additions & 2 deletions ckan/new_authz.py
Expand Up @@ -192,8 +192,8 @@ def is_authorized(action, context, data_dict=None):
# these are the permissions that roles have
ROLE_PERMISSIONS = OrderedDict([
('admin', ['admin']),
('editor', ['read', 'delete_dataset', 'create_dataset', 'update_dataset']),
('member', ['read']),
('editor', ['read', 'delete_dataset', 'create_dataset', 'update_dataset', 'manage_group']),
('member', ['read', 'manage_group']),
])


Expand Down
9 changes: 3 additions & 6 deletions ckan/templates/group/member_new.html
Expand Up @@ -85,12 +85,9 @@ <h2 class="module-heading">
</h2>
<div class="module-content">
{% trans %}
<p><strong>Admin:</strong> Can add/edit and delete datasets, as well as
manage group members.</p>
<p><strong>Editor:</strong> Can add and edit datasets, but not manage
group members.</p>
<p><strong>Member:</strong> Can view the group's private
datasets, but not add new datasets.</p>
<p><strong>Admin:</strong> Can edit group information, as well as
manage organization members.</p>
<p><strong>Member:</strong> Can add/remove datasets from groups</p>
{% endtrans %}
</div>
</div>
Expand Down
4 changes: 1 addition & 3 deletions ckan/templates/group/read.html
@@ -1,8 +1,6 @@
{% extends "group/read_base.html" %}

{% block page_primary_action %}
{% link_for _('Add Dataset'), controller='package', action='new', group=c.group_dict.id, class_='btn btn-primary', icon='plus-sign-alt' %}
{% endblock %}
{% block subtitle %}{{ c.group_dict.display_name }}{% endblock %}

{% block primary_content_inner %}
{% block groups_search_form %}
Expand Down
8 changes: 6 additions & 2 deletions ckan/templates/group/snippets/group_item.html
Expand Up @@ -11,7 +11,8 @@
{% endfor %}
</ul>
#}
{% set url = h.url_for(group.type ~ '_read', action='read', id=group.name) %}
{% set type = group.type or 'group' %}
{% set url = h.url_for(type ~ '_read', action='read', id=group.name) %}
<li class="media-item">
{% block image %}
<img src="{{ group.image_display_url or h.url_for_static('/base/images/placeholder-group.png') }}" alt="{{ group.name }}" class="media-image">
Expand All @@ -29,13 +30,16 @@ <h3 class="media-heading">{{ group.display_name }}</h3>
{% block datasets %}
{% if group.packages %}
<strong class="count">{{ ungettext('{num} Dataset', '{num} Datasets', group.packages).format(num=group.packages) }}</strong>
{% else %}
{% elif group.packages == 0 %}
<span class="count">{{ _('0 Datasets') }}</span>
{% endif %}
{% endblock %}
<a href="{{ url }}" title="{{ _('View {name}').format(name=group.display_name) }}" class="media-view">
<span>{{ _('View {name}').format(name=group.display_name) }}</span>
</a>
{% if group.user_member %}
<button name="group_removed" value="{{ group.id }}" type="submit" class="btn btn-danger btn-small media-edit" title="{{ _('Remove dataset from this group') }}">{{ _('Remove') }}</button>
{% endif %}
</li>
{% if position is divisibleby 3 %}
<li class="clearfix js-hide"></li>
Expand Down
26 changes: 26 additions & 0 deletions ckan/templates/package/group_list.html
@@ -0,0 +1,26 @@
{% extends "package/read_base.html" %}
{% import 'macros/form.html' as form %}

{% block primary_content_inner %}
<h2 class="hide-heading">{{ _('Groups') }}</h2>

{% if c.group_dropdown %}
<form method="post" class="form-horizontal">
<select id="field-add_group" name="group_added" data-module="autocomplete">
{% for option in c.group_dropdown %}
<option value="{{ option[0] }}"> {{ option[1] }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary" title="{{ _('Associate this group with this dataset') }}">{{ _('Add to group') }}</button>
</form>
{% endif %}

{% if c.pkg_dict.groups %}
<form method="post">
{% snippet 'group/snippets/group_list.html', groups=c.pkg_dict.groups %}
</form>
{% else %}
<p class="empty">{{ _('There are no groups associated with this dataset') }}</p>
{% endif %}

{% endblock %}
18 changes: 1 addition & 17 deletions ckan/templates/package/read_base.html
Expand Up @@ -22,6 +22,7 @@

{% block content_primary_nav %}
{{ h.build_nav_icon('dataset_read', _('Dataset'), id=pkg.name) }}
{{ h.build_nav_icon('dataset_groups', _('Groups'), id=pkg.name) }}
{{ h.build_nav_icon('dataset_activity', _('Activity Stream'), id=pkg.name) }}
{{ h.build_nav_icon('related_list', _('Related'), id=pkg.name) }}
{% endblock %}
Expand Down Expand Up @@ -59,23 +60,6 @@
{% endif %}
{% endblock %}

{% block package_groups %}
{% if pkg.groups %}
<div class="module module-narrow">
<h2 class="module-heading"><i class="icon-folder-open"></i> {{ _('Groups') }}</h2>
<ul class="nav nav-simple">
{% for group in pkg.groups %}
<li class="nav-item">
<a href="{{ h.url_for(controller='group', action='read', id=group.name) }}">
{{ group.title or group.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endblock %}

{% block package_social %}
{% snippet "snippets/social.html" %}
{% endblock %}
Expand Down

0 comments on commit 4e89ef8

Please sign in to comment.