From fa12443fb33ba92ca157c9be0adab318a94250d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Sat, 28 Mar 2020 14:52:41 +0100
Subject: [PATCH] manager: make select2 fields use direct widget references
 (#41017)

Instead of relying on cache.
---
 src/authentic2/manager/views.py   |  9 +++++++--
 src/authentic2/manager/widgets.py | 26 ++++++++++++++++++++------
 tests/test_manager.py             |  6 ++----
 3 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/src/authentic2/manager/views.py b/src/authentic2/manager/views.py
index 579b20f9..0dbb3212 100644
--- a/src/authentic2/manager/views.py
+++ b/src/authentic2/manager/views.py
@@ -46,7 +46,7 @@ from authentic2.utils import redirect, batch_queryset
 from authentic2.decorators import json as json_view
 from authentic2 import hooks
 
-from . import app_settings, utils, forms
+from . import app_settings, utils, forms, widgets
 
 
 # https://github.com/MongoEngine/django-mongoengine/blob/master/django_mongoengine/views/edit.py
@@ -679,13 +679,18 @@ class Select2View(AutoResponseView):
     '''Overrided default django-select2 view to enforce security checks on Select2 AJAX requests.'''
 
     def get_widget_or_404(self):
-        widget = super(Select2View, self).get_widget_or_404()
+        field_id = self.kwargs.get('field_id', self.request.GET.get('field_id', None))
+        if not field_id or not hasattr(widgets, field_id):
+            raise Http404('Missing or unknown "field_id" provided.')
+        widget = getattr(widgets, field_id)()
+        widget.queryset = widget.get_queryset()
         widget.view = self
         if hasattr(widget, 'security_check'):
             if not widget.security_check(self.request, *self.args, **self.kwargs):
                 raise PermissionDenied
         return widget
 
+
 select2 = Select2View.as_view()
 
 
diff --git a/src/authentic2/manager/widgets.py b/src/authentic2/manager/widgets.py
index e3431334..b54cbdbb 100644
--- a/src/authentic2/manager/widgets.py
+++ b/src/authentic2/manager/widgets.py
@@ -80,7 +80,21 @@ class RoleLabelMixin(object):
         return label
 
 
-class ChooseUserWidget(SecurityCheckMixin, ModelSelect2Widget):
+class SimpleModelSelect2Widget(ModelSelect2Widget):
+    def build_attrs(self, *args, **kwargs):
+        attrs = super(SimpleModelSelect2Widget, self).build_attrs(*args, **kwargs)
+        attrs['data-field_id'] = self.__class__.__name__
+        return attrs
+
+
+class SimpleModelSelect2MultipleWidget(ModelSelect2MultipleWidget):
+    def build_attrs(self, *args, **kwargs):
+        attrs = super(SimpleModelSelect2MultipleWidget, self).build_attrs(*args, **kwargs)
+        attrs['data-field_id'] = self.__class__.__name__
+        return attrs
+
+
+class ChooseUserWidget(SecurityCheckMixin, SimpleModelSelect2Widget):
     model = get_user_model()
     search_fields = [
         'username__icontains', 'first_name__icontains',
@@ -91,7 +105,7 @@ class ChooseUserWidget(SecurityCheckMixin, ModelSelect2Widget):
         return utils.label_from_user(user)
 
 
-class ChooseUsersWidget(SecurityCheckMixin, ModelSelect2MultipleWidget):
+class ChooseUsersWidget(SecurityCheckMixin, SimpleModelSelect2MultipleWidget):
     model = get_user_model()
     search_fields = [
         'username__icontains', 'first_name__icontains',
@@ -102,7 +116,7 @@ class ChooseUsersWidget(SecurityCheckMixin, ModelSelect2MultipleWidget):
         return utils.label_from_user(user)
 
 
-class ChooseRoleWidget(RoleLabelMixin, SecurityCheckMixin, ModelSelect2Widget):
+class ChooseRoleWidget(RoleLabelMixin, SecurityCheckMixin, SimpleModelSelect2Widget):
     queryset = get_role_model().objects.exclude(slug__startswith='_')
     split_term_operator = operator.__and__
     search_fields = [
@@ -112,7 +126,7 @@ class ChooseRoleWidget(RoleLabelMixin, SecurityCheckMixin, ModelSelect2Widget):
     ]
 
 
-class ChooseRolesWidget(RoleLabelMixin, SecurityCheckMixin, ModelSelect2MultipleWidget):
+class ChooseRolesWidget(RoleLabelMixin, SecurityCheckMixin, SimpleModelSelect2MultipleWidget):
     queryset = get_role_model().objects.exclude(slug__startswith='_')
     split_term_operator = operator.__and__
     search_fields = [
@@ -122,7 +136,7 @@ class ChooseRolesWidget(RoleLabelMixin, SecurityCheckMixin, ModelSelect2Multiple
     ]
 
 
-class ChooseRolesForChangeWidget(RoleLabelMixin, SecurityCheckMixin, ModelSelect2MultipleWidget):
+class ChooseRolesForChangeWidget(RoleLabelMixin, SecurityCheckMixin, SimpleModelSelect2MultipleWidget):
     operations = ['change']
     queryset = get_role_model().objects.all()
     split_term_operator = operator.__and__
@@ -133,7 +147,7 @@ class ChooseRolesForChangeWidget(RoleLabelMixin, SecurityCheckMixin, ModelSelect
     ]
 
 
-class ChooseUserRoleWidget(RoleLabelMixin, SecurityCheckMixin, ModelSelect2Widget):
+class ChooseUserRoleWidget(RoleLabelMixin, SecurityCheckMixin, SimpleModelSelect2Widget):
     operations = ['change']
     model = get_role_model()
     search_fields = [
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 1a89c1b2..be097f81 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -842,7 +842,6 @@ def test_manager_edit_user_next_form_error(superuser_or_admin, app, ou1, simple_
 
 
 def test_roles_widget(admin, app, db):
-    from django.core import signing
     from authentic2.manager.forms import ChooseRoleForm
 
     login(app, admin, '/manage/')
@@ -855,7 +854,7 @@ def test_roles_widget(admin, app, db):
 
     form = ChooseRoleForm()
     assert form.as_p()
-    field_id = signing.dumps(id(form.fields['role'].widget))
+    field_id = form.fields['role'].widget.__class__.__name__
     url = reverse('django_select2-json')
     response = app.get(url, params={'field_id': field_id, 'term': 'Admin'})
     assert len(response.json['results']) == 3
@@ -868,7 +867,6 @@ def test_roles_widget(admin, app, db):
 
 
 def test_roles_for_change_widget(admin, app, db):
-    from django.core import signing
     from authentic2.manager.forms import RolesForChangeForm
 
     login(app, admin, '/manage/')
@@ -877,7 +875,7 @@ def test_roles_for_change_widget(admin, app, db):
 
     form = RolesForChangeForm()
     assert form.as_p()
-    field_id = signing.dumps(id(form.fields['roles'].widget))
+    field_id = form.fields['roles'].widget.__class__.__name__
     url = reverse('django_select2-json')
     response = app.get(url, params={'field_id': field_id, 'term': 'admin'})
     assert len(response.json['results']) == 1
-- 
2.26.0

