diff --git a/apps/common/forms.py b/apps/common/forms.py index 27dd37e334..4615a1ee9f 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -122,3 +122,19 @@ class FilterForm(forms.Form): self.fields[list_filter['name']] = forms.ModelChoiceField( queryset=list_filter['queryset'], label=label[0].upper() + label[1:], required=False) + + +class ChoiceForm(forms.Form): + """ + Form to be used in side by side templates used to add or remove + items from a many to many field + """ + def __init__(self, *args, **kwargs): + choices = kwargs.pop('choices', []) + label = kwargs.pop('label', _(u'Selection')) + super(ChoiceForm, self).__init__(*args, **kwargs) + self.fields['selection'].choices = choices + self.fields['selection'].label = label + self.fields['selection'].widget.attrs.update({'size': 14, 'class': 'choice_form'}) + + selection = forms.MultipleChoiceField() diff --git a/apps/common/utils.py b/apps/common/utils.py index 403ab64d92..6f5a933ae3 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -8,6 +8,8 @@ from django.utils.http import urlencode as django_urlencode from django.utils.datastructures import MultiValueDict from django.conf import settings from django.utils.translation import ugettext_lazy as _ +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import User def urlquote(link=None, get=None): @@ -311,3 +313,21 @@ def parse_range(astr): x = part.split(u'-') result.update(range(int(x[0]), int(x[-1]) + 1)) return sorted(result) + + +def generate_choices_w_labels(choices, display_object_type=True): + results = [] + for choice in choices: + ct_label = ContentType.objects.get_for_model(choice).name + label = unicode(choice) + if isinstance(choice, User): + label = choice.get_full_name() if choice.get_full_name() else choice + + if display_object_type: + verbose_name = unicode(getattr(choice._meta, u'verbose_name', ct_label)) + results.append((u'%s,%s' % (ct_label, choice.pk), u'%s: %s' % (verbose_name, label))) + else: + results.append((u'%s,%s' % (ct_label, choice.pk), u'%s' % (label))) + + #Sort results by the label not the key value + return sorted(results, key=lambda x: x[1]) diff --git a/apps/permissions/forms.py b/apps/permissions/forms.py index f988339e17..818c7edf19 100644 --- a/apps/permissions/forms.py +++ b/apps/permissions/forms.py @@ -14,15 +14,3 @@ class RoleForm(forms.ModelForm): class RoleForm_view(DetailForm): class Meta: model = Role - - -class ChoiceForm(forms.Form): - def __init__(self, *args, **kwargs): - choices = kwargs.pop('choices', []) - label = kwargs.pop('label', _(u'Selection')) - super(ChoiceForm, self).__init__(*args, **kwargs) - self.fields['selection'].choices = choices - self.fields['selection'].label = label - self.fields['selection'].widget.attrs.update({'size': 14}) - - selection = forms.MultipleChoiceField() diff --git a/apps/permissions/views.py b/apps/permissions/views.py index 8f331b98f1..d479f03b9f 100644 --- a/apps/permissions/views.py +++ b/apps/permissions/views.py @@ -10,8 +10,11 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.models import User, Group +from common.forms import ChoiceForm +from common.utils import generate_choices_w_labels + from permissions.models import Role, Permission, PermissionHolder, RoleMember -from permissions.forms import RoleForm, RoleForm_view, ChoiceForm +from permissions.forms import RoleForm, RoleForm_view from permissions import PERMISSION_ROLE_VIEW, PERMISSION_ROLE_EDIT, \ PERMISSION_ROLE_CREATE, PERMISSION_ROLE_DELETE, PERMISSION_PERMISSION_GRANT, \ PERMISSION_PERMISSION_REVOKE @@ -166,21 +169,6 @@ def permission_grant_revoke(request, permission_id, app_label, module_name, pk, }, context_instance=RequestContext(request)) -def generate_choices_w_labels(choices): - results = [] - for choice in choices: - ct_label = ContentType.objects.get_for_model(choice).name - verbose_name = unicode(getattr(choice._meta, u'verbose_name', ct_label)) - label = unicode(choice) - if isinstance(choice, User): - label = choice.get_full_name() if choice.get_full_name() else choice - - results.append((u'%s,%s' % (ct_label, choice.pk), u'%s: %s' % (verbose_name, label))) - - #Sort results by the label not the key value - return sorted(results, key=lambda x: x[1]) - - def get_role_members(role): user_ct = ContentType.objects.get(model='user') group_ct = ContentType.objects.get(model='group') diff --git a/apps/user_management/__init__.py b/apps/user_management/__init__.py index 1b9f2dcf2e..b95089df2c 100644 --- a/apps/user_management/__init__.py +++ b/apps/user_management/__init__.py @@ -40,11 +40,12 @@ group_edit = {'text': _(u'edit'), 'view': 'group_edit', 'args': 'object.id', 'fa group_add = {'text': _(u'create new group'), 'view': 'group_add', 'famfam': 'group_add', 'permissions': {'namespace': 'user_management', 'permissions': [PERMISSION_GROUP_CREATE]}} group_delete = {u'text': _('delete'), 'view': 'group_delete', 'args': 'object.id', 'famfam': 'group_delete', 'permissions': {'namespace': 'user_management', 'permissions': [PERMISSION_GROUP_DELETE]}} group_multiple_delete = {u'text': _('delete'), 'view': 'group_multiple_delete', 'famfam': 'group_delete', 'permissions': {'namespace': 'user_management', 'permissions': [PERMISSION_GROUP_DELETE]}} +group_members = {'text': _(u'members'), 'view': 'group_members', 'args': 'object.id', 'famfam': 'group_link', 'permissions': {'namespace': 'user_management', 'permissions': [PERMISSION_GROUP_EDIT]}} register_links(User, [user_edit, user_set_password, user_delete]) register_links(['user_multiple_set_password', 'user_set_password', 'user_multiple_delete', 'user_delete', 'user_edit', 'user_list','user_add'], [user_add, user_list], menu_name=u'sidebar') register_multi_item_links(['user_list'], [user_multiple_set_password, user_multiple_delete]) -register_links(Group, [group_edit, group_delete]) -register_links(['group_multiple_delete', 'group_delete', 'group_edit', 'group_list','group_add'], [group_add, group_list], menu_name=u'sidebar') +register_links(Group, [group_edit, group_members, group_delete]) +register_links(['group_multiple_delete', 'group_delete', 'group_edit', 'group_list','group_add', 'group_members'], [group_add, group_list], menu_name=u'sidebar') register_multi_item_links(['group_list'], [group_multiple_delete]) diff --git a/apps/user_management/urls.py b/apps/user_management/urls.py index 6c6e856e58..163b5c264c 100644 --- a/apps/user_management/urls.py +++ b/apps/user_management/urls.py @@ -14,4 +14,5 @@ urlpatterns = patterns('user_management.views', url(r'^group/(?P\d+)/edit/$', 'group_edit', (), 'group_edit'), url(r'^group/(?P\d+)/delete/$', 'group_delete', (), 'group_delete'), url(r'^group/multiple/delete/$', 'group_multiple_delete', (), 'group_multiple_delete'), + url(r'^group/(?P\d+)/members/$', 'group_members', (), 'group_members'), ) diff --git a/apps/user_management/views.py b/apps/user_management/views.py index f11603ae39..b63c90666e 100644 --- a/apps/user_management/views.py +++ b/apps/user_management/views.py @@ -6,8 +6,11 @@ from django.contrib import messages from django.views.generic.list_detail import object_list from django.core.urlresolvers import reverse from django.contrib.auth.models import User, Group +from django.contrib.contenttypes.models import ContentType from permissions.api import check_permissions +from common.forms import ChoiceForm +from common.utils import generate_choices_w_labels from user_management import PERMISSION_USER_VIEW, \ PERMISSION_USER_EDIT, PERMISSION_USER_CREATE, \ @@ -22,7 +25,7 @@ def user_list(request): return object_list( request, - queryset=User.objects.exclude(is_superuser=True).exclude(is_staff=True), + queryset=User.objects.exclude(is_superuser=True).exclude(is_staff=True).order_by('username'), template_name='generic_list.html', extra_context={ 'title': _(u'users'), @@ -67,6 +70,7 @@ def user_edit(request, user_id): 'title': _(u'edit user: %s') % user, 'form': form, 'object': user, + 'object_name': _(u'user'), }, context_instance=RequestContext(request)) @@ -243,6 +247,7 @@ def group_edit(request, group_id): 'title': _(u'edit group: %s') % group, 'form': form, 'object': group, + 'object_name': _(u'group'), }, context_instance=RequestContext(request)) @@ -315,3 +320,74 @@ def group_multiple_delete(request): request, group_id_list=request.GET.get('id_list', []) ) + +def get_group_members(group): + return group.user_set.all() + + +def get_non_group_members(group): + return User.objects.exclude(groups=group).exclude(is_staff=True).exclude(is_superuser=True) + + +def group_members(request, group_id): + check_permissions(request.user, 'user_management', [PERMISSION_GROUP_EDIT]) + group = get_object_or_404(Group, pk=group_id) + + if request.method == 'POST': + if 'unselected-users-submit' in request.POST.keys(): + unselected_users_form = ChoiceForm(request.POST, + prefix='unselected-users', + choices=generate_choices_w_labels(get_non_group_members(group), display_object_type=False)) + if unselected_users_form.is_valid(): + for selection in unselected_users_form.cleaned_data['selection']: + model, pk = selection.split(u',') + ct = ContentType.objects.get(model=model) + obj = ct.get_object_for_this_type(pk=pk) + group.user_set.add(obj) + messages.success(request, _(u'%(obj)s added successfully to the group: %(group)s.') % { + 'obj': generate_choices_w_labels([obj])[0][1], 'group': group}) + elif 'selected-users-submit' in request.POST.keys(): + selected_users_form = ChoiceForm(request.POST, + prefix='selected-users', + choices=generate_choices_w_labels(get_group_members(group), display_object_type=False)) + if selected_users_form.is_valid(): + for selection in selected_users_form.cleaned_data['selection']: + model, pk = selection.split(u',') + ct = ContentType.objects.get(model=model) + obj = ct.get_object_for_this_type(pk=pk) + try: + group.user_set.remove(obj) + messages.success(request, _(u'%(obj)s removed successfully from the group: %(group)s.') % { + 'obj': generate_choices_w_labels([obj])[0][1], 'group': group}) + except member.DoesNotExist: + messages.error(request, _(u'Unable to remove %(obj)s from the group: %(group)s.') % { + 'obj': generate_choices_w_labels([obj])[0][1], 'group': group}) + unselected_users_form = ChoiceForm(prefix='unselected-users', + choices=generate_choices_w_labels(get_non_group_members(group), display_object_type=False)) + selected_users_form = ChoiceForm(prefix='selected-users', + choices=generate_choices_w_labels(get_group_members(group), display_object_type=False)) + + context = { + 'object': group, + 'object_name': _(u'group'), + 'form_list': [ + { + 'form': unselected_users_form, + 'title': _(u'non members of group: %s') % group, + 'grid': 6, + 'grid_clear': False, + 'submit_label': _(u'Add'), + }, + { + 'form': selected_users_form, + 'title': _(u'members of group: %s') % group, + 'grid': 6, + 'grid_clear': True, + 'submit_label': _(u'Remove'), + }, + + ], + } + + return render_to_response('generic_form.html', context, + context_instance=RequestContext(request)) diff --git a/site_media/css/override.css b/site_media/css/override.css index e0982e24cd..4d8797950e 100644 --- a/site_media/css/override.css +++ b/site_media/css/override.css @@ -17,3 +17,7 @@ .undecorated_list { list-style-type: none; } + +.choice_form { + min-width: 200px; +}