diff --git a/ceuk-marking/settings.py b/ceuk-marking/settings.py index 8599ef5..9a3bad4 100644 --- a/ceuk-marking/settings.py +++ b/ceuk-marking/settings.py @@ -87,6 +87,7 @@ "django.contrib.staticfiles", "compressor", "django_bootstrap5", + "django_filters", "django_json_widget", "crowdsourcer", ] diff --git a/crowdsourcer/filters.py b/crowdsourcer/filters.py new file mode 100644 index 0000000..f43cc45 --- /dev/null +++ b/crowdsourcer/filters.py @@ -0,0 +1,34 @@ +from django.contrib.auth.models import User + +import django_filters + +from crowdsourcer.models import ResponseType, Section + + +def filter_not_empty(queryset, name, value): + lookup = "__".join([name, "isnull"]) + return queryset.filter(**{lookup: not value}) + + +class VolunteerFilter(django_filters.FilterSet): + has_assignments = django_filters.BooleanFilter( + field_name="num_assignments", method=filter_not_empty, label="Has assignments" + ) + marker__response_type = django_filters.ChoiceFilter( + label="Stage", choices=ResponseType.choices() + ) + assigned_section = django_filters.ChoiceFilter( + field_name="assigned_section", + label="Assigned Section", + lookup_expr="icontains", + choices=Section.objects.values_list("title", "title"), + ) + # have to specify it like this otherwise bootstrap doesn't recognise it as a bound field + username = django_filters.CharFilter(field_name="username", lookup_expr="icontains") + + class Meta: + model = User + fields = { + "marker__response_type": ["exact"], + "is_active": ["exact"], + } diff --git a/crowdsourcer/models.py b/crowdsourcer/models.py index 26600f5..e3e711c 100644 --- a/crowdsourcer/models.py +++ b/crowdsourcer/models.py @@ -321,6 +321,11 @@ class ResponseType(models.Model): priority = models.IntegerField() active = models.BooleanField(default=False) + @classmethod + def choices(cls): + choices = cls.objects.values_list("pk", "type") + return choices + def __str__(self): return self.type diff --git a/crowdsourcer/templates/crowdsourcer/icons/edit.svg b/crowdsourcer/templates/crowdsourcer/icons/edit.svg new file mode 100644 index 0000000..d4766d0 --- /dev/null +++ b/crowdsourcer/templates/crowdsourcer/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crowdsourcer/templates/crowdsourcer/volunteers/list.html b/crowdsourcer/templates/crowdsourcer/volunteers/list.html index 0a002f0..8fc4248 100644 --- a/crowdsourcer/templates/crowdsourcer/volunteers/list.html +++ b/crowdsourcer/templates/crowdsourcer/volunteers/list.html @@ -1,23 +1,57 @@ {% extends 'crowdsourcer/base.html' %} {% load crowdsourcer_tags %} +{% load django_bootstrap5 %} {% block content %} {% if show_login %}
Name/Email | Stage | +Section | Assignments | Active |
---|---|---|---|---|
- {{ volunteer.email|default:volunteer.username }} + {{ volunteer.email|default:volunteer.username }} | {{ volunteer.marker.response_type|default:"First Mark" }} | - {{ volunteer.num_assignments }} - edit + {{ volunteer.assigned_section }} + | ++ {{ volunteer.num_assignments }} + + {% include 'crowdsourcer/icons/edit.svg' %} + Edit + | {{ volunteer.is_active }} diff --git a/crowdsourcer/views/volunteers.py b/crowdsourcer/views/volunteers.py index 2bd6279..eead60b 100644 --- a/crowdsourcer/views/volunteers.py +++ b/crowdsourcer/views/volunteers.py @@ -2,12 +2,16 @@ from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.models import User +from django.contrib.postgres.aggregates import StringAgg from django.db.models import Count, OuterRef, Subquery from django.http import JsonResponse from django.shortcuts import get_object_or_404 from django.urls import reverse from django.views.generic import FormView, ListView +from django_filters.views import FilterView + +from crowdsourcer.filters import VolunteerFilter from crowdsourcer.forms import ( CreateMarkerForm, MarkerFormset, @@ -35,12 +39,21 @@ def test_func(self): return self.request.user.has_perm("crowdsourcer.can_manage_users") -class VolunteersView(VolunteerAccessMixin, ListView): +class VolunteersView(VolunteerAccessMixin, FilterView): template_name = "crowdsourcer/volunteers/list.html" context_object_name = "volunteers" + filterset_class = VolunteerFilter + + def get_filterset(self, filterset_class): + fs = super().get_filterset(filterset_class) + + fs.filters["assigned_section"].field.choices = Section.objects.filter( + marking_session=self.request.current_session + ).values_list("title", "title") + return fs def get_queryset(self): - return ( + qs = ( User.objects.filter(marker__marking_session=self.request.current_session) .select_related("marker") .annotate( @@ -54,9 +67,28 @@ def get_queryset(self): .values("num_assignments") ) ) + .annotate( + assigned_section=( + Subquery( + Assigned.objects.filter( + marking_session=self.request.current_session, + user=OuterRef("pk"), + ) + .values_list("user") + .annotate( + joined_title=StringAgg( + "section__title", distinct=True, delimiter=", " + ) + ) + .values("joined_title") + ) + ) + ) .order_by("username") ) + return qs + class VolunteerAddView(VolunteerAccessMixin, FormView): template_name = "crowdsourcer/volunteers/create.html" diff --git a/poetry.lock b/poetry.lock index e63bc61..ec1c250 100644 --- a/poetry.lock +++ b/poetry.lock @@ -214,6 +214,17 @@ develop = ["coverage[toml] (>=5.0a4)", "furo (>=2021.8.17b43,<2021.9.0)", "pytes docs = ["furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)"] +[[package]] +name = "django-filter" +version = "24.3" +description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." +category = "main" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +Django = ">=4.2" + [[package]] name = "django-json-widget" version = "2.0.1" @@ -628,7 +639,7 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "3824dcfa6bbbb179d83a756736ce4e052450a0fac801c73b0d14aab93dd0f1f9" +content-hash = "a01cf0de08117b09cce48ea4e7e9c3f37014a81922b3f46306638613fd82fef2" [metadata.files] appdirs = [ @@ -840,6 +851,10 @@ django-environ = [ {file = "django-environ-0.9.0.tar.gz", hash = "sha256:bff5381533056328c9ac02f71790bd5bf1cea81b1beeb648f28b81c9e83e0a21"}, {file = "django_environ-0.9.0-py2.py3-none-any.whl", hash = "sha256:f21a5ef8cc603da1870bbf9a09b7e5577ab5f6da451b843dbcc721a7bca6b3d9"}, ] +django-filter = [ + {file = "django_filter-24.3-py3-none-any.whl", hash = "sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64"}, + {file = "django_filter-24.3.tar.gz", hash = "sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3"}, +] django-json-widget = [ {file = "django-json-widget-2.0.1.tar.gz", hash = "sha256:adb4cab17fe5a04139037d7d84725369530ef35b912c3790d3a7b13f99351358"}, ] diff --git a/pyproject.toml b/pyproject.toml index 47e8e00..41fba23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ django-bootstrap5 = "^22.2" django-simple-history = "^3.2.0" mysoc-dataset = "^0.3.0" django-json-widget = "^2.0.1" +django-filter = "^24.3" [tool.poetry.dev-dependencies] black = "^24.4.2" |