0002-manager-add-child-roles-in-role-members-view-59664.patch
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 "{{ user }}" from role "{{ role }}"?{% 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 "{{ record }}" from role "{{ role }}"?{% 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 |
- |