From ca3ca52ab24aa7d913085afda273f0a456a99529 Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 14 Feb 2024 00:54:28 +0100 Subject: [PATCH] feat: implement enhanced submission list --- pretalx_musicrate/forms.py | 6 + pretalx_musicrate/signals.py | 14 +- .../pretalx_musicrate/submission_filter.js | 11 ++ .../pretalx_musicrate/enhanced_list.html | 154 ++++++++++++++++++ pretalx_musicrate/urls.py | 6 + pretalx_musicrate/views.py | 28 +++- 6 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 pretalx_musicrate/static/pretalx_musicrate/submission_filter.js create mode 100644 pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html diff --git a/pretalx_musicrate/forms.py b/pretalx_musicrate/forms.py index c6687fb..83d03c1 100644 --- a/pretalx_musicrate/forms.py +++ b/pretalx_musicrate/forms.py @@ -1,7 +1,9 @@ from django import forms +from django.utils.translation import gettext_lazy as _ from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField from i18nfield.forms import I18nModelForm from pretalx.person.models import User +from pretalx.submission.forms import SubmissionFilterForm from .models import Assignee, MusicrateSettings, Rating @@ -77,3 +79,7 @@ class AssigneeForm(forms.ModelForm): model = Assignee fields = ("user",) widgets = {"user": forms.Select(attrs={"class": "select2"})} + + +class EnhancedSubmissionFilterForm(SubmissionFilterForm): + require_all_tags = forms.BooleanField(required=False, label=_("require all")) diff --git a/pretalx_musicrate/signals.py b/pretalx_musicrate/signals.py index c9b03db..1231531 100644 --- a/pretalx_musicrate/signals.py +++ b/pretalx_musicrate/signals.py @@ -24,10 +24,20 @@ def pretalx_musicrate_placeholders(sender, **kwargs): @receiver(nav_event) -def pretalx_musicrate_qrcode(sender, request, **kwargs): +def pretalx_musicrate_nav_event(sender, request, **kwargs): if not request.user.has_perm("orga.view_submissions", request.event): return [] return [ + { + "active": request.resolver_match.view_name + == "plugins:pretalx_musicrate:enhanced_list", + "icon": "sticky-note-o", + "label": _("Proposals, but better"), + "url": reverse( + "plugins:pretalx_musicrate:enhanced_list", + kwargs={"event": request.event.slug}, + ), + }, { "active": request.resolver_match.view_name == "plugins:pretalx_musicrate:qrcode", @@ -36,7 +46,7 @@ def pretalx_musicrate_qrcode(sender, request, **kwargs): "url": reverse( "plugins:pretalx_musicrate:qrcode", kwargs={"event": request.event.slug} ), - } + }, ] diff --git a/pretalx_musicrate/static/pretalx_musicrate/submission_filter.js b/pretalx_musicrate/static/pretalx_musicrate/submission_filter.js new file mode 100644 index 0000000..18b7d4d --- /dev/null +++ b/pretalx_musicrate/static/pretalx_musicrate/submission_filter.js @@ -0,0 +1,11 @@ +document.addEventListener("DOMContentLoaded", function() { + const updateRequireAllVisibility = () => { + if (document.querySelector("#id_tags").value) { + document.querySelector("#requireAll").classList.remove("d-none") + } else { + document.querySelector("#requireAll").classList.add("d-none") + } + } + $("#id_tags").on("change", updateRequireAllVisibility) + updateRequireAllVisibility() +}) diff --git a/pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html b/pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html new file mode 100644 index 0000000..fe20d1f --- /dev/null +++ b/pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html @@ -0,0 +1,154 @@ +{% extends "orga/base.html" %} +{% load bootstrap4 %} +{% load compress %} +{% load i18n %} +{% load rules %} +{% load static %} +{% load url_replace %} + +{% block scripts %} + {% compress js %} + + {% endcompress %} + {% compress js %} + + {% endcompress %} +{% endblock %} + +{% block content %} + {% has_perm 'orga.change_submission_state' request.user request.event as can_change_submission %} + {% has_perm 'orga.view_speakers' request.user request.event as can_view_speakers %} +

+ {{ page_obj.paginator.count }} + {% blocktranslate trimmed count count=page_obj.paginator.count %} + proposal + {% plural %} + proposals + {% endblocktranslate %} +

+ +
+
+ {% bootstrap_form search_form %} + {% if show_submission_types and filter_form.submission_type %}{% bootstrap_field filter_form.submission_type %}{% endif %} +
+ {% bootstrap_field filter_form.state layout='inline' %} +
{% bootstrap_field filter_form.pending_state__isnull layout='inline' %}
+
+ {% if filter_form.track %}{% bootstrap_field filter_form.track %}{% endif %} + {% if filter_form.tags %} +
+ {% bootstrap_field filter_form.tags layout='inline' %} +
{% bootstrap_field filter_form.require_all_tags layout='inline' %}
+
+ {% endif %} + {% if filter_form.content_locale %}{% bootstrap_field filter_form.content_locale %}{% endif %} + +
+ {% if filter_form.is_valid and filter_form.cleaned_data.question %} +

+ + {% blocktranslate trimmed with question=filter_form.cleaned_data.question.question %} + List filtered by answers to question "{{ question }}". + {% endblocktranslate %} + + + {% translate "Remove filter" %} + +

+ {% endif %} +
+ +
+ + + + + + {% if show_submission_types %} + + {% endif %} + + + {% if can_change_submission %} + + {% endif %} + + + + {% for submission in submissions %} + + + + {% if show_submission_types %} + + {% endif %} + + + {% if can_change_submission %} + + {% endif %} + + {% endfor %} + +
+ {% translate "Rating" %} + + + + {% translate "Title" %} + + + + {% translate "Type" %} + + {% translate "State" %} + + + + {% translate "Assignee" %} + + +
+ {% if submission.rating %} + {{ submission.rating.value }} + {% else %} + – + {% endif %} + + + {% if can_view_speakers %}{{ submission.title }}{% else %}{{ submission.anonymised.title|default:submission.title }}{% endif %} + + + {{ submission.submission_type.name }} + + {% include "orga/submission/state_dropdown.html" with submission=submission %} + + {% if submission.assignee %} + {{ submission.assignee.user.name }} + {% else %} + – + {% endif %} + {% if can_change_submission %} + + + + {% endif %} + + + + + + + +
+
+ + {% include "orga/pagination.html" %} +{% endblock %} diff --git a/pretalx_musicrate/urls.py b/pretalx_musicrate/urls.py index 571d103..afbf7d6 100644 --- a/pretalx_musicrate/urls.py +++ b/pretalx_musicrate/urls.py @@ -2,6 +2,7 @@ from django.urls import include, path from .views import ( AssigneeView, + EnhancedSubmissionList, ExportView, JoinView, MayAdvanceView, @@ -26,6 +27,11 @@ urlpatterns = [ include( [ path("export/", ExportView.as_view(), name="export"), + path( + "list/", + EnhancedSubmissionList.as_view(), + name="enhanced_list", + ), path("/", AssigneeView.as_view(), name="assignee"), ] ), diff --git a/pretalx_musicrate/views.py b/pretalx_musicrate/views.py index e94a88e..cdef06a 100644 --- a/pretalx_musicrate/views.py +++ b/pretalx_musicrate/views.py @@ -15,9 +15,15 @@ from django.views.generic import FormView, TemplateView, View from django.views.generic.detail import SingleObjectMixin from django_context_decorator import context from pretalx.common.mixins.views import EventPermissionRequired +from pretalx.orga.views.submission import SubmissionList from pretalx.submission.models import Submission -from .forms import AssigneeForm, MusicrateSettingsForm, RatingForm +from .forms import ( + AssigneeForm, + EnhancedSubmissionFilterForm, + MusicrateSettingsForm, + RatingForm, +) from .models import Juror, Rating youtube_re = re.compile( @@ -470,3 +476,23 @@ class AssigneeView(EventPermissionRequired, FormView, SingleObjectMixin): def post(self, *args, **kwargs): self.object = self.get_object() return super().post(*args, **kwargs) + + +class EnhancedSubmissionList(SubmissionList): + sortable_fields = ("code", "rating", "title", "state", "assignee") + template_name = "pretalx_musicrate/enhanced_list.html" + + def get_filter_form(self): + return EnhancedSubmissionFilterForm( + data=self.request.GET, + event=self.request.event, + usable_states=self.usable_states, + limit_tracks=self.limit_tracks, + ) + + def filter_queryset(self, qs): + qs = super().filter_queryset(qs.prefetch_related("assignee")) + if self.request.GET.get("require_all_tags", "") == "on": + for tag in self.request.GET.getlist("tags"): + qs = qs.filter(tags__in=[tag]) + return qs