diff --git a/hypha/apply/users/admin_views.py b/hypha/apply/users/admin_views.py index b8ae44a43d612fd97450a0ccc087c0f5a4604168..b1e8d9c6e24bd6ab6b9ce28f35b81bb63bb75555 100644 --- a/hypha/apply/users/admin_views.py +++ b/hypha/apply/users/admin_views.py @@ -1,10 +1,13 @@ +import django_filters from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from django.core.paginator import Paginator from django.db.models import Q from django.shortcuts import render from django.utils.translation import gettext as _ from django.views.decorators.vary import vary_on_headers from wagtail.admin.auth import any_permission_required +from wagtail.admin.filters import WagtailFilterSet from wagtail.admin.forms.search import SearchForm from wagtail.core.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME @@ -18,6 +21,33 @@ change_user_perm = "{0}.change_{1}".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_ delete_user_perm = "{0}.delete_{1}".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()) +class UserFilterSet(WagtailFilterSet): + STATUS_CHOICES = ( + ('inactive', 'INACTIVE'), + ('active', 'ACTIVE'), + ) + roles = django_filters.ModelChoiceFilter(queryset=Group.objects.all(), label='Roles', method='filter_by_roles') + status = django_filters.ChoiceFilter(choices=STATUS_CHOICES, label='Status', method='filter_by_status') + + class Meta: + model = User + fields = [ + 'roles', + 'status' + ] + + def filter_by_roles(self, queryset, name, value): + queryset = queryset.filter(groups__name=value) + return queryset + + def filter_by_status(self, queryset, name, value): + if value == 'active': + return queryset.filter(is_active=True) + elif value == 'inactive': + return queryset.filter(is_active=False) + return queryset + + @any_permission_required(add_user_perm, change_user_perm, delete_user_perm) @vary_on_headers('X-Requested-With') def index(request): @@ -30,7 +60,7 @@ def index(request): model_fields = [f.name for f in User._meta.get_fields()] - if 'q' in request.GET: + if request.GET.get('q', None): form = SearchForm(request.GET, placeholder=_("Search users")) if form.is_valid(): q = form.cleaned_data['q'] @@ -61,6 +91,9 @@ def index(request): if not is_searching: users = User.objects.all().order_by('-is_active', 'full_name') + filters = UserFilterSet(request.GET, queryset=users, request=request) + users = filters.qs + if 'ordering' in request.GET: ordering = request.GET['ordering'] @@ -71,21 +104,30 @@ def index(request): else: ordering = 'name' + user_count = users.count() paginator = Paginator(users, per_page=20) users = paginator.get_page(request.GET.get('p')) if request.headers.get('x-requested-with') == 'XMLHttpRequest': return render(request, "wagtailusers/users/results.html", { 'users': users, + 'user_count': user_count, 'is_searching': is_searching, 'query_string': q, + 'filters': filters, 'ordering': ordering, + 'app_label': User._meta.app_label, + 'model_name': User._meta.model_name, }) else: return render(request, "wagtailusers/users/index.html", { 'search_form': form, 'users': users, + 'user_count': user_count, 'is_searching': is_searching, 'ordering': ordering, 'query_string': q, + 'filters': filters, + 'app_label': User._meta.app_label, + 'model_name': User._meta.model_name, }) diff --git a/hypha/apply/users/templates/wagtailusers/users/index.html b/hypha/apply/users/templates/wagtailusers/users/index.html new file mode 100644 index 0000000000000000000000000000000000000000..fd28fb0f853ace65d13e46235be626bcfb50a80b --- /dev/null +++ b/hypha/apply/users/templates/wagtailusers/users/index.html @@ -0,0 +1,44 @@ +{% extends "wagtailadmin/base.html" %} +{% load i18n static wagtailadmin_tags %} +{% block titletag %}{% trans "Users" %}{% endblock %} +{% block extra_js %} + {{ block.super }} + <script> + window.headerSearch = { + {% if group %} + url: "{% url 'wagtailusers_groups:users' group.id %}", + {% else %} + url: "{% url 'wagtailusers_users:index' %}", + {% endif %} + termInput: "#id_q", + targetOutput: "#user-results" + } + </script> + <script> + window.wagtailConfig.BULK_ACTION_ITEM_TYPE = 'USER'; + </script> + <script defer src="{% versioned_static 'wagtailadmin/js/bulk-actions.js' %}"></script> +{% endblock %} + +{% block extra_css %} + {{ block.super }} + <link rel="stylesheet" href="{% static 'css/apply/wagtail_users_list.css' %}" type="text/css" /> + + {{ filters.form.media.css }} +{% endblock %} + +{% block content %} + {% trans "Users" as users_str %} + {% trans "Add a user" as add_a_user_str %} + + {% url "wagtailusers_users:add" as add_link %} + {% include "wagtailadmin/shared/header.html" with subtitle=group.name title=users_str action_url=add_link action_text=add_a_user_str icon="user" search_url="wagtailusers_users:index" %} + + <div class="nice-padding"> + <div id="user-results" class="users"> + {% include "wagtailusers/users/results.html" %} + </div> + {% trans "Select all users in listing" as select_all_text %} + {% include 'wagtailadmin/bulk_actions/footer.html' with select_all_obj_text=select_all_text app_label=app_label model_name=model_name objects=users %} + </div> +{% endblock %} diff --git a/hypha/apply/users/templates/wagtailusers/users/list.html b/hypha/apply/users/templates/wagtailusers/users/list.html index 449f1d102477bab09fd77e776ef9186bf37a7dd8..1ba5b89f7ce5284077d112e25fd76b4cf87cca4d 100644 --- a/hypha/apply/users/templates/wagtailusers/users/list.html +++ b/hypha/apply/users/templates/wagtailusers/users/list.html @@ -1,7 +1,8 @@ -{% load i18n wagtailusers_tags wagtailadmin_tags %} +{% load i18n l10n wagtailusers_tags wagtailadmin_tags %} <table class="listing"> <thead> <tr> + {% include 'wagtailadmin/bulk_actions/select_all_checkbox_cell.html' %} <th class="name"> {% trans "Name" %} {% if ordering == "name" %} @@ -32,8 +33,9 @@ <tbody> {% for user in users %} <tr> - <td class="title" valign="top"> - <h2> + {% include "wagtailadmin/bulk_actions/listing_checkbox_cell.html" with obj_type="user" obj=user aria_labelledby_prefix="user_" aria_labelledby=user.pk|unlocalize aria_labelledby_suffix="_title" %} + <td id="user_{{ user.pk|unlocalize }}_title" class="title" valign="top"> + <h2 class="title-wrapper"> <span class="avatar small"><img src="{% avatar_url user size=25 %}" /></span> <a href="{% url 'wagtailusers_users:edit' user.pk %}">{{ user.get_full_name|default:user.get_username }}</a> </h2> diff --git a/hypha/apply/users/templates/wagtailusers/users/results.html b/hypha/apply/users/templates/wagtailusers/users/results.html new file mode 100644 index 0000000000000000000000000000000000000000..745afe7c11cf18048e497ad56d305e2e7758ac34 --- /dev/null +++ b/hypha/apply/users/templates/wagtailusers/users/results.html @@ -0,0 +1,47 @@ +{% load i18n wagtailadmin_tags %} +<div class="users-list users-list--has-filters"> + <div class="users-list__results"> + {% if users %} + <h2 role="alert"> + {% blocktrans trimmed count count=user_count %} + There is {{ user_count }} user + {% plural %} + There are {{ user_count }} users + {% endblocktrans %} + </h2> + + {% if is_searching %} + {% search_other %} + {% endif %} + {% include "wagtailusers/users/list.html" %} + + {# call pagination_nav with no linkurl, to generate general-purpose links like <div href="?p=2"/> #} + {% include "wagtailadmin/shared/pagination_nav.html" with items=users %} + {% else %} + <h2 role="alert">{% blocktrans trimmed %}Sorry, no users match "<em>{{ query_string }}</em>"{% endblocktrans %}</h2> + {% if is_searching %} + {% search_other %} + {% else %} + {% url 'wagtailusers_users:add' as wagtailusers_add_url %} + {% if group %} + {% with group.name as group_name %} + <p>{% blocktrans trimmed %}The {{ group_name }} group has no users configured. Why not <a href="{{ wagtailusers_add_url }}">add some</a>?{% endblocktrans %}</p> + {% endwith %} + {% else %} + <p>{% blocktrans trimmed %}There are no users configured. Why not <a href="{{ wagtailusers_add_url }}">add some</a>?{% endblocktrans %}</p> + {% endif %} + {% endif %} + {% endif %} + </div> + <div class="users-list__filters"> + <h2>{% trans 'Filter' %}</h2> + <form method="get"> + {% for filter in filters.form %} + {{ filter.label_tag }} + {{ filter }} + {{ filter.errors }} + {% endfor %} + <button class="button button-longrunning" type="submit">{% icon name="spinner" %}{% trans 'Apply filters' %}</button> + </form> + </div> +</div> diff --git a/hypha/static_src/src/sass/apply/wagtail_users_list.scss b/hypha/static_src/src/sass/apply/wagtail_users_list.scss new file mode 100644 index 0000000000000000000000000000000000000000..0770fa211fe5b8ce7d78f281a461688ee3929cfa --- /dev/null +++ b/hypha/static_src/src/sass/apply/wagtail_users_list.scss @@ -0,0 +1,27 @@ +// Abstracts +@import 'abstracts/functions'; +@import 'abstracts/mixins'; +@import 'abstracts/variables'; + +.users-list { + display: grid; + grid-template-columns: auto; + grid-column-gap: 50px; + + &--has-filters { + grid-template-columns: auto 250px; + } + + &__results { + grid-column-start: col-start 1 col-end 2; + } + + &__filters { + grid-column-start: col-start -2 col-end -1; + + button[type='submit'] { + display: block; + margin-top: 20px; + } + } +}