0001-manager-use-multiple-selection-widget-to-manage-role.patch
src/authentic2/manager/forms.py | ||
---|---|---|
30 | 30 |
from django.utils.text import slugify |
31 | 31 |
from django.utils.translation import pgettext, ugettext |
32 | 32 |
from django.utils.translation import ugettext_lazy as _ |
33 |
from django_select2.forms import HeavySelect2Widget |
|
33 |
from django_select2.forms import HeavySelect2MultipleWidget
|
|
34 | 34 | |
35 | 35 |
from authentic2.a2_rbac.models import OrganizationalUnit, Permission, Role |
36 | 36 |
from authentic2.a2_rbac.utils import generate_slug, get_default_ou |
... | ... | |
859 | 859 |
self.add_error('import_file', error) |
860 | 860 | |
861 | 861 | |
862 |
class HeavySelect2WidgetNoCache(HeavySelect2Widget):
|
|
862 |
class HeavySelect2MultipleWidgetNoCache(HeavySelect2MultipleWidget):
|
|
863 | 863 |
class Media: |
864 | 864 |
js = ('authentic2/manager/js/select2_locale.js',) |
865 | 865 | |
... | ... | |
867 | 867 |
pass |
868 | 868 | |
869 | 869 | |
870 |
class UserOrRoleField(forms.Field): |
|
871 |
widget = HeavySelect2MultipleWidgetNoCache( |
|
872 |
data_url='/', |
|
873 |
attrs={'data-placeholder': _('Select a user or a role'), 'data-minimum-input-length': 3}, |
|
874 |
) |
|
875 | ||
876 | ||
870 | 877 |
class ChooseUserOrRoleForm(FormWithRequest, forms.Form): |
871 |
user_or_role = forms.CharField(label=_('Add to role'))
|
|
878 |
user_or_roles = UserOrRoleField(label=_('Add to role'))
|
|
872 | 879 |
action = forms.CharField(initial='add', widget=forms.HiddenInput) |
873 | 880 | |
874 | 881 |
def __init__(self, *args, **kwargs): |
875 | 882 |
self.role = kwargs.pop('role', None) |
876 | 883 |
super().__init__(*args, **kwargs) |
877 |
self.fields['user_or_role'].widget = HeavySelect2WidgetNoCache( |
|
878 |
data_url=reverse('user-or-role-select2-json', kwargs={'pk': self.role.pk}), |
|
879 |
attrs={'data-placeholder': _('Select a user or a role'), 'data-minimum-input-length': 3}, |
|
884 |
self.fields['user_or_roles'].widget.data_url = reverse( |
|
885 |
'user-or-role-select2-json', kwargs={'pk': self.role.pk} |
|
880 | 886 |
) |
881 | 887 | |
882 | 888 |
def clean(self): |
883 | 889 |
super().clean() |
884 |
try: |
|
885 |
object_type, pk = self.cleaned_data.get('user_or_role', '').split('-') |
|
886 |
pk = int(pk) |
|
887 |
except (ValueError, TypeError): |
|
888 |
return |
|
889 | ||
890 |
if object_type == 'user': |
|
890 |
self.cleaned_data['users'] = set() |
|
891 |
self.cleaned_data['roles'] = set() |
|
892 |
for user_or_role in self.cleaned_data.get('user_or_roles', []): |
|
891 | 893 |
try: |
892 |
self.cleaned_data['user'] = self.get_user_queryset(self.request.user, self.role).get(pk=pk) |
|
893 |
except User.DoesNotExist: |
|
894 |
return |
|
895 |
elif object_type == 'role': |
|
896 |
try: |
|
897 |
self.cleaned_data['role'] = self.get_role_queryset(self.request.user, self.role).get(pk=pk) |
|
898 |
except Role.DoesNotExist: |
|
894 |
object_type, pk = user_or_role.split('-') |
|
895 |
pk = int(pk) |
|
896 |
except (ValueError, TypeError): |
|
899 | 897 |
return |
900 | 898 | |
899 |
if object_type == 'user': |
|
900 |
try: |
|
901 |
self.cleaned_data['users'].add( |
|
902 |
self.get_user_queryset(self.request.user, self.role).get(pk=pk) |
|
903 |
) |
|
904 |
except User.DoesNotExist: |
|
905 |
return |
|
906 |
elif object_type == 'role': |
|
907 |
try: |
|
908 |
self.cleaned_data['roles'].add( |
|
909 |
self.get_role_queryset(self.request.user, self.role).get(pk=pk) |
|
910 |
) |
|
911 |
except Role.DoesNotExist: |
|
912 |
return |
|
913 | ||
901 | 914 |
@staticmethod |
902 | 915 |
def get_role_queryset(user, role): |
903 | 916 |
qs = Role.objects.exclude(pk=role.pk) |
src/authentic2/manager/role_views.py | ||
---|---|---|
223 | 223 |
action = form.cleaned_data['action'] |
224 | 224 |
if not self.can_manage_members: |
225 | 225 |
messages.warning(self.request, _('You are not authorized')) |
226 |
elif 'user' in form.cleaned_data:
|
|
227 |
if action == 'add':
|
|
228 |
self.add_user(form.cleaned_data['user'])
|
|
229 |
elif action == 'remove':
|
|
230 |
self.remove_user(form.cleaned_data['user'])
|
|
231 |
elif 'role' in form.cleaned_data:
|
|
232 |
if action == 'add':
|
|
233 |
self.add_role(form.cleaned_data['role'])
|
|
234 |
elif action == 'remove':
|
|
235 |
self.remove_role(form.cleaned_data['role'])
|
|
226 |
if action == 'add':
|
|
227 |
user_action = self.add_user
|
|
228 |
role_action = self.add_role
|
|
229 |
elif action == 'remove': |
|
230 |
user_action = self.remove_user
|
|
231 |
role_action = self.remove_role
|
|
232 |
for user in form.cleaned_data.get('users', []):
|
|
233 |
user_action(user)
|
|
234 |
for role in form.cleaned_data.get('roles', []):
|
|
235 |
role_action(role)
|
|
236 | 236 |
return super().form_valid(form) |
237 | 237 | |
238 | 238 |
def add_user(self, user): |
src/authentic2/manager/templates/authentic2/manager/role_members_table.html | ||
---|---|---|
6 | 6 |
<th></th> |
7 | 7 |
{% endblock %} |
8 | 8 |
{% block table.tbody.last.column %} |
9 |
<td class="remove-icon-column">{% if table.context.view.can_manage_members and row.record.direct %}<a class="js-remove-object" data-confirm="{% blocktrans with record=row.record role=table.context.object %}Do you really want to remove "{{ record }}" from role "{{ role }}"?{% endblocktrans %}" href="#" data-pk-arg="user_or_role"><span class="icon-remove-sign"></span></a>{% endif %}</td> |
|
9 |
<td class="remove-icon-column">{% if table.context.view.can_manage_members and row.record.direct %}<a class="js-remove-object" data-confirm="{% blocktrans with record=row.record role=table.context.object %}Do you really want to remove "{{ record }}" from role "{{ role }}"?{% endblocktrans %}" href="#" data-pk-arg="user_or_roles"><span class="icon-remove-sign"></span></a>{% endif %}</td>
|
|
10 | 10 |
{% endblock %} |
tests/test_manager.py | ||
---|---|---|
937 | 937 |
# user can add members |
938 | 938 |
response = app.get('/manage/roles/%s/' % simple_role.pk) |
939 | 939 |
form = response.forms['add-member'] |
940 |
form['user_or_role'].force_value('user-%s' % admin.pk) |
|
940 |
form['user_or_roles'].force_value('user-%s' % admin.pk)
|
|
941 | 941 |
response = form.submit().follow() |
942 | 942 |
assert simple_role in admin.roles.all() |
943 | 943 | |
... | ... | |
945 | 945 |
q = response.pyquery.remove_namespaces() |
946 | 946 |
assert q('table tbody tr td .icon-remove-sign') |
947 | 947 |
token = str(response.context['csrf_token']) |
948 |
params = {'action': 'remove', 'user_or_role': 'user-%s' % admin.pk, 'csrfmiddlewaretoken': token} |
|
948 |
params = {'action': 'remove', 'user_or_roles': 'user-%s' % admin.pk, 'csrfmiddlewaretoken': token}
|
|
949 | 949 |
app.post('/manage/roles/%s/' % simple_role.pk, params=params) |
950 | 950 |
assert simple_role not in admin.roles.all() |
951 | 951 | |
... | ... | |
981 | 981 |
# user can add role as a member through role members form |
982 | 982 |
response = app.get('/manage/roles/%s/' % simple_role.pk) |
983 | 983 |
form = response.forms['add-member'] |
984 |
form['user_or_role'].force_value('role-%s' % role.pk) |
|
984 |
form['user_or_roles'].force_value('role-%s' % role.pk)
|
|
985 | 985 |
response = form.submit().follow() |
986 | 986 |
assert role in simple_role.children() |
987 | 987 | |
... | ... | |
989 | 989 |
q = response.pyquery.remove_namespaces() |
990 | 990 |
assert q('table tbody tr td .icon-remove-sign') |
991 | 991 |
token = str(response.context['csrf_token']) |
992 |
params = {'action': 'remove', 'user_or_role': 'role-%s' % role.pk, 'csrfmiddlewaretoken': token} |
|
992 |
params = {'action': 'remove', 'user_or_roles': 'role-%s' % role.pk, 'csrfmiddlewaretoken': token}
|
|
993 | 993 |
app.post('/manage/roles/%s/' % simple_role.pk, params=params) |
994 | 994 |
assert role not in simple_role.children() |
995 | 995 |
tests/test_role_manager.py | ||
---|---|---|
563 | 563 |
select2_json = request_select2(app, resp, term='Jôhn') |
564 | 564 |
assert len(select2_json['results']) == 1 |
565 | 565 |
form = resp.forms['add-member'] |
566 |
form['user_or_role'].force_value(select2_json['results'][0]['id']) |
|
566 |
form['user_or_roles'].force_value(select2_json['results'][0]['id'])
|
|
567 | 567 |
resp = form.submit().follow() |
568 | 568 |
assert 'Jôhn Dôe' in resp.text |
569 | 569 | |
... | ... | |
573 | 573 |
data_pks = [row.attrib['data-pk'] for row in resp.pyquery('table tbody tr')] |
574 | 574 |
assert data_pks == ['user-%s' % simple_user.pk] |
575 | 575 |
data_pk_args = [row.attrib['data-pk-arg'] for row in resp.pyquery('table tbody tr td a.js-remove-object')] |
576 |
assert data_pk_args == ['user_or_role'] |
|
576 |
assert data_pk_args == ['user_or_roles']
|
|
577 | 577 | |
578 | 578 |
select2_json = request_select2(app, resp, term='role_ou1') |
579 | 579 |
assert len(select2_json['results']) == 1 |
580 | 580 |
form = resp.forms['add-member'] |
581 |
form['user_or_role'].force_value(select2_json['results'][0]['id']) |
|
581 |
form['user_or_roles'].force_value(select2_json['results'][0]['id'])
|
|
582 | 582 |
resp = form.submit().follow() |
583 | 583 |
assert 'role_ou1' in resp.text |
584 | 584 | |
... | ... | |
588 | 588 |
data_pks = [row.attrib['data-pk'] for row in resp.pyquery('table tbody tr')] |
589 | 589 |
assert data_pks == ['role-%s' % role_ou1.pk, 'user-%s' % simple_user.pk] |
590 | 590 |
data_pk_args = [row.attrib['data-pk-arg'] for row in resp.pyquery('table tbody tr td a.js-remove-object')] |
591 |
assert data_pk_args == ['user_or_role', 'user_or_role']
|
|
591 |
assert data_pk_args == ['user_or_roles', 'user_or_roles']
|
|
592 | 592 | |
593 | 593 |
# simulate click on Jôhn Dôe delete icon |
594 | 594 |
token = str(resp.context['csrf_token']) |
595 |
params = {'action': 'remove', 'user_or_role': 'user-%s' % simple_user.pk, 'csrfmiddlewaretoken': token} |
|
595 |
params = {'action': 'remove', 'user_or_roles': 'user-%s' % simple_user.pk, 'csrfmiddlewaretoken': token}
|
|
596 | 596 |
resp = app.post('/manage/roles/%s/' % simple_role.pk, params=params).follow() |
597 | 597 |
assert 'Jôhn Dôe' not in resp.text |
598 | 598 | |
599 | 599 |
# simulate click on role_ou1 delete icon |
600 | 600 |
token = str(resp.context['csrf_token']) |
601 |
params = {'action': 'remove', 'user_or_role': 'role-%s' % role_ou1.pk, 'csrfmiddlewaretoken': token} |
|
601 |
params = {'action': 'remove', 'user_or_roles': 'role-%s' % role_ou1.pk, 'csrfmiddlewaretoken': token}
|
|
602 | 602 |
resp = app.post('/manage/roles/%s/' % simple_role.pk, params=params).follow() |
603 | 603 |
assert 'role_ou1' not in resp.text |
604 | 604 | |
605 | 605 |
# invalid choices are ignored |
606 | 606 |
for invalid_choice in ('', 'wrong-wrong', 'user-', 'user-xxx', 'role', 'user-99999'): |
607 | 607 |
form = resp.forms['add-member'] |
608 |
form['user_or_role'].force_value(invalid_choice) |
|
608 |
form['user_or_roles'].force_value(invalid_choice)
|
|
609 | 609 |
resp = form.submit().maybe_follow() |
610 |
- |