Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,51 @@ The following apps are optional but will enhance the user experience:

* The 'login su' form will render using `django-form-admin`_
* The user selection widget will render using `django-ajax-selects`_
* The user selection widget will render using `django-autocomplete-light`_

`django-autocomplete-light`_ and `django-ajax-selects`_ are mutually exclusive.

Note that `django-ajax-selects`_ requires the following settings:

.. code-block:: python

AJAX_LOOKUP_CHANNELS = {'django_su': dict(model='auth.user', search_field='username')}

`django-autocomplete-light`_ requires the following configuration:

.. code-block:: python

# settings.py
# this setting will be used to find the named url for the autocomplete view
SU_DAL_VIEW_NAME = 'user-autocomplete'

# views.py
# we need an autocomplete view
from dal import autocomplete
from django.contrib.auth.models import User

class UserAutoComplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
user = self.request.user
qs = User.objects.none()
if user.is_authenticated and user.is_staff:
qs = User.objects.filter(username__icontains=self.q)

return qs.order_by('pk')

# urls.py
from .views import UserAutoComplete
from django.conf.urls import url
from django.conf import settings

urlpatterns = [
#...
url(r'^user-autocomplete/$',
UserAutoComplete.as_view(),
name=settings.SU_DAL_VIEW_NAME
)
#....
]

Configuration (optional)
------------------------
Expand Down Expand Up @@ -174,4 +212,5 @@ django-su is packaged using seed_.

.. _django-form-admin: http://pypi.python.org/pypi/django-form-admin
.. _django-ajax-selects: http://pypi.python.org/pypi/django-ajax-selects
.. _django-autocomplete-light: https://pypi.org/project/django-autocomplete-light/
.. _seed: https://github.com/adamcharnock/seed/
21 changes: 21 additions & 0 deletions django_su/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,24 @@ def __str__(self):
except ImportError:
pass
return super(UserSuForm, self).__str__()


class UserSuDalForm(UserSuForm):
def __init__(self, *args, **kwargs):
super(UserSuForm, self).__init__(*args, **kwargs)

if {"dal", "dal_select2"}.issubset(settings.INSTALLED_APPS) and getattr(
settings, "SU_DAL_VIEW_NAME", None
):
try:
from dal import autocomplete
except ImportError:
return
old_field = self.fields["user"]

self.fields["user"] = forms.ModelChoiceField(
required=old_field.required,
label=old_field.label,
queryset=old_field.queryset,
widget=autocomplete.ModelSelect2(url=settings.SU_DAL_VIEW_NAME),
)
30 changes: 30 additions & 0 deletions django_su/tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from dal import autocomplete
from django.contrib.auth import get_user_model
from django.forms import widgets
from django.test import TestCase, override_settings

from django_su.forms import UserSuDalForm


User = get_user_model()


class TestSuForms(TestCase):
def setUp(self):
super(TestSuForms, self).setUp()

user = User.objects.create(username="user")
user.set_password("pass")
user.save()
self.user = user

@override_settings(SU_DAL_VIEW_NAME="sampleview")
def test_dal_form_use_autocomplete_widget(self):
"""Ensure form uses autocomplete widget when SU_DAL_VIEW_NAME is present"""
form = UserSuDalForm(data={"user": self.user})
self.assertIsInstance(form.fields["user"].widget, autocomplete.ModelSelect2)

def test_dal_form_use_default_widget_if_dal_view_not_set(self):
"""Ensure form uses default widget when SU_DAL_VIEW_NAME is not present"""
form = UserSuDalForm(data={"user": self.user})
self.assertIsInstance(form.fields["user"].widget, widgets.Select)
15 changes: 14 additions & 1 deletion django_su/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from datetime import date, timezone
from unittest import mock

from django.conf import settings
from django.contrib import auth
from django.contrib.auth import get_user_model
from django.contrib.sessions.backends import cached_db
from django.test import Client, TestCase
from django.test import Client, TestCase, override_settings
from django.urls import reverse
from django.utils.datetime_safe import datetime

Expand Down Expand Up @@ -197,6 +198,18 @@ def test_post_invalid(self):
str(self.client.session[auth.SESSION_KEY]), str(self.authorized_user.id)
)

@override_settings(SU_DAL_VIEW_NAME="sampleview")
def test_uses_autocomplete_form(self):
"""Ensure the view uses the correct form instance when SU_DAL_VIEW_NAME is present"""
self.client.login(username="authorized", password="pass")

with mock.patch("django_su.views.UserSuDalForm") as dal_form:
dal_form.return_value.is_valid.return_value = False
response = self.client.get(reverse("su_login"))

dal_form.assert_called_once()
self.assertEqual(response.status_code, 200)


class LogoutViewTestCase(SuViewsBaseTestCase):
def test_valid_get(self):
Expand Down
10 changes: 8 additions & 2 deletions django_su/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_http_methods

from .forms import UserSuForm
from .forms import UserSuDalForm, UserSuForm
from .utils import custom_login_action, su_login_callback


Expand Down Expand Up @@ -61,7 +61,13 @@ def login_as_user(request, user_id):
@csrf_protect
@require_http_methods(["POST", "GET"])
@user_passes_test(su_login_callback)
def su_login(request, form_class=UserSuForm, template_name="su/login.html"):
def su_login(request, form_class=None, template_name="su/login.html"):
if form_class is None:
if getattr(settings, "SU_DAL_VIEW_NAME", None):
form_class = UserSuDalForm
else:
form_class = UserSuForm

form = form_class(request.POST or None)
if form.is_valid():
return login_as_user(request, form.get_user().pk)
Expand Down
2 changes: 2 additions & 0 deletions example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
# 'guardian',
"formadmin", # pip install django-form-admin
"ajax_select", # pip install django-ajax-selects
"dal", # pip install django-autocomplete-light
"dal_select2", # pip install django-autocomplete-light
]

INSTALLED_APPS = PROJECT_APPS + INSTALLED_APPS
Expand Down
1 change: 1 addition & 0 deletions requirements.test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
coveralls
django-form-admin
django-ajax-selects
django-autocomplete-light