From 6f736d662a366f44f4d8366cee076559556181a8 Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Wed, 20 Nov 2024 17:56:35 +0000 Subject: [PATCH 1/3] add django-filter to dependencies used for filtering the volunteer page --- poetry.lock | 17 ++++++++++++++++- pyproject.toml | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) 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" From a81bd5a6bd9b32420b39c4fa78f090c4c15c5d29 Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Wed, 20 Nov 2024 17:57:09 +0000 Subject: [PATCH 2/3] add some filters to the volunteers list --- ceuk-marking/settings.py | 1 + crowdsourcer/filters.py | 34 ++++++++++++++ crowdsourcer/models.py | 5 ++ .../crowdsourcer/volunteers/list.html | 47 +++++++++++++++++-- crowdsourcer/views/volunteers.py | 36 +++++++++++++- 5 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 crowdsourcer/filters.py 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/volunteers/list.html b/crowdsourcer/templates/crowdsourcer/volunteers/list.html index 0a002f0..62fb554 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 %}

Sign in

Sign in {% else %} -
- Add volunteer - Bulk assignment - Stage deactivate +
+

Volunteers

+
-

Volunteers

+ +
+
+
+ {% bootstrap_field filter.form.marker__response_type %} +
+
+ {% bootstrap_field filter.form.is_active %} +
+
+ {% bootstrap_field filter.form.has_assignments %} +
+
+ {% bootstrap_field filter.form.assigned_section %} +
+
+ {% bootstrap_field filter.form.username %} +
+
+ +
+
+
+ + @@ -31,6 +65,9 @@

Volunteers

+
Name/Email StageSection Assignments Active
{{ volunteer.marker.response_type|default:"First Mark" }} + {{ volunteer.assigned_section }} + {{ volunteer.num_assignments }} edit 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" From 914a869365bada7f445f68a16e754767a7561e8c Mon Sep 17 00:00:00 2001 From: Zarino Zappia Date: Thu, 21 Nov 2024 13:36:49 +0000 Subject: [PATCH 3/3] Tidier "Edit" action for assignments on volunteer list --- crowdsourcer/templates/crowdsourcer/icons/edit.svg | 1 + crowdsourcer/templates/crowdsourcer/volunteers/list.html | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 crowdsourcer/templates/crowdsourcer/icons/edit.svg 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 62fb554..8fc4248 100644 --- a/crowdsourcer/templates/crowdsourcer/volunteers/list.html +++ b/crowdsourcer/templates/crowdsourcer/volunteers/list.html @@ -60,7 +60,7 @@

Volunteers

{% for volunteer in volunteers %}
- {{ volunteer.email|default:volunteer.username }} + {{ volunteer.email|default:volunteer.username }} {{ volunteer.marker.response_type|default:"First Mark" }} @@ -69,8 +69,11 @@

Volunteers

{{ volunteer.assigned_section }}
- {{ volunteer.num_assignments }} - edit + {{ volunteer.num_assignments }} + + {% include 'crowdsourcer/icons/edit.svg' %} + Edit + {{ volunteer.is_active }}