Projet

Général

Profil

0002-manager-add-child-roles-in-role-members-view-59664.patch

Valentin Deniaud, 21 décembre 2021 11:53

Télécharger (29,7 ko)

Voir les différences:

Subject: [PATCH 2/2] manager: add child roles in role members view (#59664)

 src/authentic2/manager/forms.py               |  58 ++++++
 src/authentic2/manager/role_views.py          | 187 ++++++++++++++----
 src/authentic2/manager/tables.py              |   2 +-
 .../authentic2/manager/role_members.html      |   2 +-
 .../manager/role_members_table.html           |   2 +-
 src/authentic2/manager/urls.py                |   5 +
 tests/test_a2_rbac.py                         |   6 +-
 tests/test_manager.py                         |  34 +++-
 tests/test_role_manager.py                    | 128 +++++++++++-
 tests/utils.py                                |  22 ++-
 10 files changed, 382 insertions(+), 64 deletions(-)
src/authentic2/manager/forms.py
26 26
from django.contrib.auth import get_user_model
27 27
from django.contrib.contenttypes.models import ContentType
28 28
from django.core.exceptions import ValidationError
29
from django.urls import reverse
29 30
from django.utils.text import slugify
30 31
from django.utils.translation import pgettext, ugettext
31 32
from django.utils.translation import ugettext_lazy as _
33
from django_select2.forms import HeavySelect2Widget
32 34

  
33 35
from authentic2.a2_rbac.models import OrganizationalUnit, Permission, Role
34 36
from authentic2.a2_rbac.utils import generate_slug, get_default_ou
......
855 857
    def add_line_error(self, error, line):
856 858
        error = _('%(error)s (line %(number)d)') % {'error': error, 'number': line + 1}
857 859
        self.add_error('import_file', error)
860

  
861

  
862
class HeavySelect2WidgetNoCache(HeavySelect2Widget):
863
    def set_to_cache(self):
864
        pass
865

  
866

  
867
class ChooseUserOrRoleForm(FormWithRequest, forms.Form):
868
    user_or_role = forms.CharField(label=_('Add to role'))
869
    action = forms.CharField(initial='add', widget=forms.HiddenInput)
870

  
871
    def __init__(self, *args, **kwargs):
872
        self.role = kwargs.pop('role', None)
873
        super().__init__(*args, **kwargs)
874
        self.fields['user_or_role'].widget = HeavySelect2WidgetNoCache(
875
            data_url=reverse('user-or-role-select2-json', kwargs={'pk': self.role.pk})
876
        )
877

  
878
    def clean(self):
879
        super().clean()
880
        try:
881
            object_type, pk = self.cleaned_data.get('user_or_role', '').split('-')
882
        except (ValueError, TypeError):
883
            return
884

  
885
        try:
886
            pk = int(pk)
887
        except ValueError:
888
            return
889

  
890
        if object_type == 'user':
891
            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:
899
                return
900

  
901
    @staticmethod
902
    def get_role_queryset(user, role):
903
        qs = Role.objects.exclude(pk=role.pk)
904

  
905
        perm = '%s.search_%s' % (Role._meta.app_label, Role._meta.model_name)
906
        return user.filter_by_perm(perm, qs)
907

  
908
    @staticmethod
909
    def get_user_queryset(user, role):
910
        qs = User.objects.all()
911
        if app_settings.ROLE_MEMBERS_FROM_OU and role.ou:
912
            qs = qs.filter(ou=role.ou)
913

  
914
        perm = '%s.search_%s' % (User._meta.app_label, User._meta.model_name)
915
        return user.filter_by_perm(perm, qs)
src/authentic2/manager/role_views.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import json
18
from functools import reduce
18 19

  
19 20
from django.contrib import messages
20 21
from django.contrib.auth import get_user_model
21 22
from django.contrib.contenttypes.models import ContentType
23
from django.core import signing
22 24
from django.core.exceptions import PermissionDenied, ValidationError
25
from django.core.paginator import EmptyPage, Paginator
23 26
from django.db import transaction
24
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Prefetch, Q
27
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Prefetch, Q, Value
25 28
from django.db.models.functions import Cast
29
from django.http import Http404, JsonResponse
26 30
from django.shortcuts import get_object_or_404
27 31
from django.urls import reverse
28 32
from django.utils.functional import cached_property
29 33
from django.utils.translation import ugettext_lazy as _
30
from django.views.generic import FormView, TemplateView
34
from django.views.generic import DetailView, FormView, TemplateView
31 35
from django.views.generic.detail import SingleObjectMixin
32 36

  
33 37
from authentic2 import data_transfer, hooks
......
37 41
from authentic2.forms.profile import modelform_factory
38 42
from authentic2.utils.misc import redirect
39 43

  
40
from . import app_settings, forms, resources, tables, views
44
from . import forms, resources, tables, views
41 45
from .journal_views import BaseJournalView
42
from .utils import has_show_username
46
from .utils import get_ou_count, has_show_username, label_from_user
47

  
48
User = get_user_model()
43 49

  
44 50

  
45 51
class RolesMixin:
......
168 174

  
169 175
class RoleMembersView(views.HideOUColumnMixin, RoleViewMixin, views.BaseSubTableView):
170 176
    template_name = 'authentic2/manager/role_members.html'
171
    form_class = forms.ChooseUserForm
177
    form_class = forms.ChooseUserOrRoleForm
172 178
    success_url = '.'
173 179
    search_form_class = forms.RoleMembersSearchForm
174 180
    permissions = ['a2_rbac.view_role']
......
192 198
        self._can_manage_members = value
193 199

  
194 200
    def dispatch(self, request, *args, **kwargs):
195
        self.children = views.filter_view(self.request, self.get_object().children(include_self=False))
201
        self.children = views.filter_view(
202
            self.request, self.get_object().children(include_self=False, annotate=True)
203
        )
196 204
        return super().dispatch(request, *args, **kwargs)
197 205

  
198 206
    def get_table_data(self):
199 207
        if self.view_all_members:
200 208
            return super().get_table_data()
201 209
        members = views.filter_view(self.request, self.object.members.all())
210
        members = members.annotate(direct=Value(True, output_field=BooleanField()))
202 211
        members = self.filter_by_search(members)
203 212
        return list(self.children) + list(members)
204 213

  
......
211 220
        return self.search_form.is_valid() and self.search_form.cleaned_data.get('all_members')
212 221

  
213 222
    def form_valid(self, form):
214
        user = form.cleaned_data['user']
215 223
        action = form.cleaned_data['action']
216
        if self.can_manage_members:
224
        if not self.can_manage_members:
225
            messages.warning(self.request, _('You are not authorized'))
226
        elif 'user' in form.cleaned_data:
217 227
            if action == 'add':
218
                if self.object.members.filter(pk=user.pk).exists():
219
                    messages.warning(self.request, _('User already in this role.'))
220
                else:
221
                    self.object.members.add(user)
222
                    hooks.call_hooks(
223
                        'event',
224
                        name='manager-add-role-member',
225
                        user=self.request.user,
226
                        role=self.object,
227
                        member=user,
228
                    )
229
                    self.request.journal.record(
230
                        'manager.role.membership.grant', role=self.object, member=user
231
                    )
228
                self.add_user(form.cleaned_data['user'])
232 229
            elif action == 'remove':
233
                if not self.object.members.filter(pk=user.pk).exists():
234
                    messages.warning(self.request, _('User was not in this role.'))
235
                else:
236
                    self.object.members.remove(user)
237
                    hooks.call_hooks(
238
                        'event',
239
                        name='manager-remove-role-member',
240
                        user=self.request.user,
241
                        role=self.object,
242
                        member=user,
243
                    )
244
                    self.request.journal.record(
245
                        'manager.role.membership.removal', role=self.object, member=user
246
                    )
247
        else:
248
            messages.warning(self.request, _('You are not authorized'))
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'])
249 236
        return super().form_valid(form)
250 237

  
238
    def add_user(self, user):
239
        if self.object.members.filter(pk=user.pk).exists():
240
            messages.warning(self.request, _('User already in this role.'))
241
        else:
242
            self.object.members.add(user)
243
            hooks.call_hooks(
244
                'event', name='manager-add-role-member', user=self.request.user, role=self.object, member=user
245
            )
246
            self.request.journal.record('manager.role.membership.grant', role=self.object, member=user)
247

  
248
    def remove_user(self, user):
249
        if not self.object.members.filter(pk=user.pk).exists():
250
            messages.warning(self.request, _('User was not in this role.'))
251
        else:
252
            self.object.members.remove(user)
253
            hooks.call_hooks(
254
                'event',
255
                name='manager-remove-role-member',
256
                user=self.request.user,
257
                role=self.object,
258
                member=user,
259
            )
260
            self.request.journal.record('manager.role.membership.removal', role=self.object, member=user)
261

  
262
    def add_role(self, role):
263
        self.object.add_child(role)
264
        hooks.call_hooks(
265
            'event', name='manager-add-child-role', user=self.request.user, parent=self.object, child=role
266
        )
267
        self.request.journal.record('manager.role.inheritance.addition', parent=self.object, child=role)
268

  
269
    def remove_role(self, role):
270
        self.object.remove_child(role)
271
        hooks.call_hooks(
272
            'event', name='manager-remove-child-role', user=self.request.user, parent=self.object, child=role
273
        )
274
        self.request.journal.record('manager.role.inheritance.removal', parent=self.object, child=role)
275

  
251 276
    def get_form_kwargs(self):
252 277
        kwargs = super().get_form_kwargs()
253
        # if role's members can only be from the same OU we filter user based on the role's OU
254
        if app_settings.ROLE_MEMBERS_FROM_OU:
255
            kwargs['ou'] = self.object.ou
278
        kwargs['role'] = self.object
256 279
        return kwargs
257 280

  
258 281
    def get_search_form_kwargs(self):
......
783 806

  
784 807

  
785 808
roles_journal = RolesJournal.as_view()
809

  
810

  
811
class UserOrRoleSelect2View(DetailView):
812
    form_class = forms.ChooseUserOrRoleForm
813
    model = Role
814

  
815
    def get(self, request, *args, **kwargs):
816
        if not self.request.user.is_authenticated or not hasattr(self.request.user, 'filter_by_perm'):
817
            raise Http404('Invalid user')
818

  
819
        role = self.get_object()
820

  
821
        field_id = self.kwargs.get('field_id', self.request.GET.get('field_id', None))
822
        try:
823
            signing.loads(field_id)
824
        except (signing.SignatureExpired, signing.BadSignature):
825
            raise Http404('Invalid or expired signature.')
826

  
827
        search_term = request.GET.get('term', '')
828
        try:
829
            page_number = int(request.GET.get('page', 1))
830
        except ValueError:
831
            page_number = 1
832

  
833
        role_qs = self.form_class.get_role_queryset(self.request.user, role)
834
        children = role.children(annotate=True)
835
        children = children.annotate(is_direct=Cast('direct', output_field=BooleanField()))
836
        role_qs = role_qs.exclude(pk__in=children.filter(is_direct=True))
837
        role_qs = self.filter_queryset(role_qs, search_term, ['name', 'service__name', 'ou__name'])
838

  
839
        role_paginator = Paginator(role_qs, 10)
840
        try:
841
            role_page = role_paginator.page(page_number)
842
        except EmptyPage:
843
            role_page = []
844
        else:
845
            has_next = role_page.has_next()
846

  
847
        user_page = []
848
        if len(role_page) < 10:
849
            user_qs = self.form_class.get_user_queryset(self.request.user, role)
850
            user_qs = user_qs.exclude(roles=role)
851
            user_qs = self.filter_queryset(
852
                user_qs, search_term, ['username', 'first_name', 'last_name', 'email']
853
            )
854

  
855
            page_number = page_number - role_paginator.num_pages + 1
856
            user_paginator = Paginator(user_qs, 10)
857
            try:
858
                user_page = user_paginator.page(page_number)
859
            except EmptyPage:
860
                has_next = False
861
            else:
862
                has_next = user_page.has_next()
863

  
864
        return JsonResponse(
865
            {
866
                "results": [self.get_choice(obj) for obj in list(role_page) + list(user_page)],
867
                "more": has_next,
868
            }
869
        )
870

  
871
    @staticmethod
872
    def filter_queryset(qs, search_term, search_fields):
873
        lookups = Q()
874
        for term in [term for term in search_term.split() if not term == '']:
875
            lookups &= reduce(Q.__or__, (Q(**{'%s__icontains' % field: term}) for field in search_fields))
876
        return qs.filter(lookups)
877

  
878
    def get_choice(self, obj):
879
        if isinstance(obj, Role):
880
            text = str(obj)
881
            if obj.ou and get_ou_count() > 1:
882
                text = f'{obj.ou} - {obj}'
883
            key = 'role-%s'
884
        elif isinstance(obj, User):
885
            text = label_from_user(obj)
886
            key = 'user-%s'
887
        return {'id': key % obj.pk, 'text': text}
888

  
889

  
890
user_or_role_select2 = UserOrRoleSelect2View.as_view()
src/authentic2/manager/tables.py
93 93
    )
94 94

  
95 95
    class Meta(UserTable.Meta):
96
        pass
96
        row_attrs = {"data-pk": lambda record: 'user-%s' % record.pk}
97 97

  
98 98

  
99 99
class UserOrRoleColumn(UserLinkColumn):
src/authentic2/manager/templates/authentic2/manager/role_members.html
70 70
 {% endif %}
71 71

  
72 72
 {% if view.can_manage_members %}
73
   <form method="post" class="manager-m2m-add-form" id="add-user">
73
   <form method="post" class="manager-m2m-add-form" id="add-member">
74 74
           {% csrf_token %}
75 75
           {{ form }}
76 76
           <button>{% trans "Add" %}</button>
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 user=row.record role=table.context.object %}Do you really want to remove user &quot;{{ user }}&quot; from role &quot;{{ role }}&quot;?{% endblocktrans %}" href="#" data-pk-arg="user"><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 &quot;{{ record }}&quot; from role &quot;{{ role }}&quot;?{% endblocktrans %}" href="#" data-pk-arg="user_or_role"><span class="icon-remove-sign"></span></a>{% endif %}</td>
10 10
  {% endblock %}
src/authentic2/manager/urls.py
157 157
        url(r'^roles/(?P<pk>\d+)/edit/$', role_views.edit, name='a2-manager-role-edit'),
158 158
        url(r'^roles/(?P<pk>\d+)/permissions/$', role_views.permissions, name='a2-manager-role-permissions'),
159 159
        url(r'^roles/(?P<pk>\d+)/journal/$', role_views.journal, name='a2-manager-role-journal'),
160
        url(
161
            r'^roles/(?P<pk>\d+)/user-or-role-select2.json$',
162
            role_views.user_or_role_select2,
163
            name='user-or-role-select2-json',
164
        ),
160 165
        # Authentic2 organizational units
161 166
        url(r'^organizational-units/$', ou_views.listing, name='a2-manager-ous'),
162 167
        url(r'^organizational-units/add/$', ou_views.add, name='a2-manager-ou-add'),
tests/test_a2_rbac.py
402 402
    response = response.click('role_ou1')
403 403
    select2_json = request_select2(app, response)
404 404
    assert select2_json['more'] is False
405
    assert {result['id'] for result in select2_json['results']} == {simple_user.id, user_ou1.id, admin.id}
405
    user_ids = {int(x['id'].split('-')[1]) for x in select2_json['results'] if x['id'].startswith('user')}
406
    assert user_ids == {simple_user.id, user_ou1.id, admin.id}
406 407

  
407 408
    # with A2_RBAC_ROLE_ADMIN_RESTRICT_TO_OU_USERS after a reload of the admin
408 409
    # page, we should only see user from the same OU as the role
......
412 413
    response = response.click('role_ou1')
413 414
    select2_json = request_select2(app, response)
414 415
    assert select2_json['more'] is False
415
    assert {result['id'] for result in select2_json['results']} == {user_ou1.id}
416
    user_ids = {int(x['id'].split('-')[1]) for x in select2_json['results'] if x['id'].startswith('user')}
417
    assert user_ids == {user_ou1.id}
416 418

  
417 419

  
418 420
def test_no_managed_ct(transactional_db, settings):
tests/test_manager.py
207 207
    url = reverse('a2-manager-role-members', kwargs={'pk': r.pk})
208 208

  
209 209
    response = login(app, superuser, url)
210
    assert not response.context['form'].fields['user'].queryset.query.where
211
    select2_json = request_select2(app, response)
212
    assert len(select2_json['results']) == 2
210
    select2_json = request_select2(app, response, fetch_all=True)
211
    assert len([x for x in select2_json['results'] if x['id'].startswith('user')]) == 2
213 212

  
214 213
    settings.A2_MANAGER_ROLE_MEMBERS_FROM_OU = True
215 214
    response = app.get(url)
216
    assert response.context['form'].fields['user'].queryset.query.where
217
    select2_json = request_select2(app, response)
218
    assert len(select2_json['results']) == 1
219
    assert select2_json['results'][0]['id'] == simple_user.pk
215
    select2_json = request_select2(app, response, fetch_all=True)
216
    user_choices = [x for x in select2_json['results'] if x['id'].startswith('user')]
217
    assert len(user_choices) == 1
218
    assert user_choices[0]['id'] == 'user-%s' % simple_user.pk
220 219

  
221 220

  
222 221
def test_manager_create_user(superuser_or_admin, app, settings):
......
937 936

  
938 937
    # user can add members
939 938
    response = app.get('/manage/roles/%s/' % simple_role.pk)
940
    form = response.forms['add-user']
941
    form['user'].force_value(admin.pk)
939
    form = response.forms['add-member']
940
    form['user_or_role'].force_value('user-%s' % admin.pk)
942 941
    response = form.submit().follow()
943 942
    assert simple_role in admin.roles.all()
944 943

  
......
946 945
    q = response.pyquery.remove_namespaces()
947 946
    assert q('table tbody tr td .icon-remove-sign')
948 947
    token = str(response.context['csrf_token'])
949
    params = {'action': 'remove', 'user': admin.pk, 'csrfmiddlewaretoken': token}
948
    params = {'action': 'remove', 'user_or_role': 'user-%s' % admin.pk, 'csrfmiddlewaretoken': token}
950 949
    app.post('/manage/roles/%s/' % simple_role.pk, params=params)
951 950
    assert simple_role not in admin.roles.all()
952 951

  
......
979 978
    response = app.post('/manage/roles/%s/parents/' % role.pk, params=params)
980 979
    assert simple_role not in role.parents()
981 980

  
981
    # user can add role as a member through role members form
982
    response = app.get('/manage/roles/%s/' % simple_role.pk)
983
    form = response.forms['add-member']
984
    form['user_or_role'].force_value('role-%s' % role.pk)
985
    response = form.submit().follow()
986
    assert role in simple_role.children()
987

  
988
    # user can delete role members
989
    q = response.pyquery.remove_namespaces()
990
    assert q('table tbody tr td .icon-remove-sign')
991
    token = str(response.context['csrf_token'])
992
    params = {'action': 'remove', 'user_or_role': 'role-%s' % role.pk, 'csrfmiddlewaretoken': token}
993
    app.post('/manage/roles/%s/' % simple_role.pk, params=params)
994
    assert role not in simple_role.children()
995

  
982 996
    # try to add arbitrary role
983 997
    admin_role = Role.objects.get(slug='_a2-manager')
984 998
    response = app.get('/manage/roles/%s/parents/' % role.pk)
tests/test_role_manager.py
24 24
from authentic2.a2_rbac.utils import get_default_ou
25 25
from authentic2.custom_user.models import User
26 26

  
27
from .utils import login, text_content
27
from .utils import login, request_select2, text_content
28 28

  
29 29

  
30 30
def test_manager_role_export(app, admin, ou1, role_ou1, ou2, role_ou2):
......
475 475

  
476 476
    resp = resp.click('Add a role as a member')
477 477
    assert 'Role a' in resp.text
478

  
479
    # add child role to child
480
    grandchild = Role.objects.create(name='grandchild')
481
    role.add_child(grandchild)
482

  
483
    resp = app.get(url)
484
    rows = [text_content(el) for el in resp.pyquery('tr td.name')]
485
    assert rows == ['Members of role Role a', 'Members of role grandchild', 'Jôhn Dôe']
486

  
487
    # remove icon is not shown for indirect child
488
    assert len(resp.pyquery('tr td a.js-remove-object')) == 2
489

  
490

  
491
def test_role_members_user_role_mixed_field_choices(
492
    app, superuser, settings, simple_role, simple_user, role_ou1
493
):
494
    url = reverse('a2-manager-role-members', kwargs={'pk': simple_role.pk})
495
    resp = login(app, superuser, url)
496

  
497
    select2_json = request_select2(app, resp)
498
    assert len(select2_json['results']) == 10
499
    assert select2_json['more'] is True
500

  
501
    select2_json = request_select2(app, resp, fetch_all=True)
502
    assert len(select2_json['results']) == 17
503
    choices = [x['text'] for x in select2_json['results']]
504
    assert choices == [
505
        'Default organizational unit - Managers of role "simple role"',
506
        'Default organizational unit - Roles - Default organizational unit',
507
        'Default organizational unit - Services - Default organizational unit',
508
        'Default organizational unit - Users - Default organizational unit',
509
        'OU1 - role_ou1',
510
        'OU1 - Roles - OU1',
511
        'OU1 - Services - OU1',
512
        'OU1 - Users - OU1',
513
        'Manager',
514
        'Manager of organizational units',
515
        'Manager of roles',
516
        'Manager of services',
517
        'Manager of users',
518
        'Managers of "Default organizational unit"',
519
        'Managers of "OU1"',
520
        'Jôhn Dôe - user@example.net - user',
521
        'super user - superuser@example.net - superuser',
522
    ]
523

  
524
    select2_json = request_select2(app, resp, term='user')
525
    choices = [x['text'] for x in select2_json['results']]
526
    assert choices == [
527
        'Default organizational unit - Users - Default organizational unit',
528
        'OU1 - Users - OU1',
529
        'Manager of users',
530
        'Jôhn Dôe - user@example.net - user',
531
        'super user - superuser@example.net - superuser',
532
    ]
533
    assert select2_json['more'] is False
534

  
535
    select2_json = request_select2(app, resp, term='Manager')
536
    assert len(select2_json['results']) == 8
537
    select2_json = request_select2(app, resp, term='Manager of')
538
    assert len(select2_json['results']) == 7
539
    select2_json = request_select2(app, resp, term='Manager of serv')
540
    assert len(select2_json['results']) == 1
541

  
542
    for i in range(25):
543
        Role.objects.create(name=f'test_role_{i}')
544
    select2_json = request_select2(app, resp, term='test_role_', fetch_all=True)
545
    assert len(select2_json['results']) == 25
546

  
547
    for i in range(25):
548
        User.objects.create(username=f'test_user_{i}')
549
    select2_json = request_select2(app, resp, term='test_user_', fetch_all=True)
550
    assert len(select2_json['results']) == 25
551

  
552

  
553
def test_role_members_user_role_add_remove(app, superuser, settings, simple_role, simple_user, role_ou1):
554
    url = reverse('a2-manager-role-members', kwargs={'pk': simple_role.pk})
555
    resp = login(app, superuser, url)
556

  
557
    select2_json = request_select2(app, resp, term='Jôhn')
558
    assert len(select2_json['results']) == 1
559
    form = resp.forms['add-member']
560
    form['user_or_role'].force_value(select2_json['results'][0]['id'])
561
    resp = form.submit().follow()
562
    assert 'Jôhn Dôe' in resp.text
563

  
564
    select2_json = request_select2(app, resp, term='Jôhn')
565
    assert len(select2_json['results']) == 0
566

  
567
    data_pks = [row.attrib['data-pk'] for row in resp.pyquery('table tbody tr')]
568
    assert data_pks == ['user-%s' % simple_user.pk]
569
    data_pk_args = [row.attrib['data-pk-arg'] for row in resp.pyquery('table tbody tr td a.js-remove-object')]
570
    assert data_pk_args == ['user_or_role']
571

  
572
    select2_json = request_select2(app, resp, term='role_ou1')
573
    assert len(select2_json['results']) == 1
574
    form = resp.forms['add-member']
575
    form['user_or_role'].force_value(select2_json['results'][0]['id'])
576
    resp = form.submit().follow()
577
    assert 'role_ou1' in resp.text
578

  
579
    select2_json = request_select2(app, resp, term='role_ou1')
580
    assert len(select2_json['results']) == 0
581

  
582
    data_pks = [row.attrib['data-pk'] for row in resp.pyquery('table tbody tr')]
583
    assert data_pks == ['role-%s' % role_ou1.pk, 'user-%s' % simple_user.pk]
584
    data_pk_args = [row.attrib['data-pk-arg'] for row in resp.pyquery('table tbody tr td a.js-remove-object')]
585
    assert data_pk_args == ['user_or_role', 'user_or_role']
586

  
587
    # simulate click on Jôhn Dôe delete icon
588
    token = str(resp.context['csrf_token'])
589
    params = {'action': 'remove', 'user_or_role': 'user-%s' % simple_user.pk, 'csrfmiddlewaretoken': token}
590
    resp = app.post('/manage/roles/%s/' % simple_role.pk, params=params).follow()
591
    assert 'Jôhn Dôe' not in resp.text
592

  
593
    # simulate click on role_ou1 delete icon
594
    token = str(resp.context['csrf_token'])
595
    params = {'action': 'remove', 'user_or_role': 'role-%s' % role_ou1.pk, 'csrfmiddlewaretoken': token}
596
    resp = app.post('/manage/roles/%s/' % simple_role.pk, params=params).follow()
597
    assert 'role_ou1' not in resp.text
598

  
599
    # invalid choices are ignored
600
    for invalid_choice in ('', 'wrong-wrong', 'user-', 'user-xxx', 'role', 'user-99999'):
601
        form = resp.forms['add-member']
602
        form['user_or_role'].force_value(invalid_choice)
603
        resp = form.submit().maybe_follow()
tests/utils.py
228 228
        return s.getsockname()[1]
229 229

  
230 230

  
231
def request_select2(app, response, term='', get_kwargs=None):
231
def request_select2(app, response, term='', fetch_all=False, page=1, get_kwargs=None):
232 232
    select2_url = response.pyquery('select')[0].attrib['data-ajax--url']
233 233
    select2_field_id = response.pyquery('select')[0].attrib['data-field_id']
234
    select2_response = app.get(
235
        select2_url, params={'field_id': select2_field_id, 'term': term}, **(get_kwargs or {})
236
    )
237
    if select2_response['content-type'] == 'application/json':
238
        return select2_response.json
239
    else:
234

  
235
    params = {'field_id': select2_field_id, 'term': term}
236
    if page:
237
        params['page'] = page
238

  
239
    select2_response = app.get(select2_url, params=params, **(get_kwargs or {}))
240
    if select2_response['content-type'] != 'application/json':
240 241
        return select2_response
241 242

  
243
    select2_json = select2_response.json
244
    results = select2_json['results']
245
    if fetch_all and select2_json['more']:
246
        results.extend(request_select2(app, response, term, fetch_all, page + 1, get_kwargs)['results'])
247

  
248
    return select2_json
249

  
242 250

  
243 251
@contextmanager
244 252
def run_on_commit_hooks():
245
-