Compare commits

..

No commits in common. "main" and "v0.15.0" have entirely different histories.

18 changed files with 178 additions and 246 deletions

View File

@ -1,5 +0,0 @@
root = true
[*.{html,js}]
indent_size = 4
indent_style = space

View File

@ -1,4 +1,4 @@
Copyright 2023-2024 Luca Schmid Copyright 2023 Luca
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -40,7 +40,7 @@ black .
## License ## License
Copyright 2023-2024 Luca Schmid Copyright 2023 Luca Schmid
Released under the terms of the Apache License 2.0 Released under the terms of the Apache License 2.0

View File

@ -1 +1 @@
__version__ = "2025.6.1" __version__ = "0.15.0"

View File

@ -37,6 +37,12 @@ class MusicrateSettingsForm(I18nModelForm):
"link_questions", "link_questions",
"advance_threshold", "advance_threshold",
) )
widgets = {
"submission_types": forms.SelectMultiple(attrs={"class": "select2"}),
"genre_question": forms.Select(attrs={"class": "select2"}),
"origin_question": forms.Select(attrs={"class": "select2"}),
"link_questions": forms.SelectMultiple(attrs={"class": "select2"}),
}
field_classes = { field_classes = {
"submission_types": SafeModelMultipleChoiceField, "submission_types": SafeModelMultipleChoiceField,
"genre_question": SafeModelChoiceField, "genre_question": SafeModelChoiceField,
@ -62,9 +68,12 @@ class AssigneeForm(forms.ModelForm):
except Assignee.DoesNotExist: except Assignee.DoesNotExist:
self.instance = Assignee(submission=submission) self.instance = Assignee(submission=submission)
super().__init__(*args, instance=self.instance, **kwargs) super().__init__(*args, instance=self.instance, **kwargs)
self.fields["user"].queryset = User.objects.filter( self.fields["user"].queryset = User.objects.none().union(
teams__in=submission.event.teams, teams__can_change_submissions=True *(
).distinct() t.members.all()
for t in submission.event.teams.filter(can_change_submissions=True)
)
)
class Meta: class Meta:
model = Assignee model = Assignee

View File

@ -1,14 +1,14 @@
# pretalx plugin for rating music. # pretalx plugin for rating music.
# Copyright (C) 2023-2024 # Copyright (C) 2023
# This file is distributed under the same license as the pretalx_musicrate package. # This file is distributed under the same license as the pretalx_musicrate package.
# Luca Schmid <Luca@hackerspace-bamberg.de>, 2023-2024. # Luca Schmid <Luca@hackerspace-bamberg.de>, 2023.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: pretalx-musicrate 0.9.0\n" "Project-Id-Version: pretalx-musicrate 0.9.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-09 22:22+0100\n" "POT-Creation-Date: 2024-02-14 22:24+0100\n"
"PO-Revision-Date: 2024-12-09 22:23+0100\n" "PO-Revision-Date: 2024-02-14 22:30+0100\n"
"Last-Translator: Luca <Luca@hackerspace-bamberg.de>\n" "Last-Translator: Luca <Luca@hackerspace-bamberg.de>\n"
"Language-Team: Luca <Luca@hackerspace-bamberg.de>\n" "Language-Team: Luca <Luca@hackerspace-bamberg.de>\n"
"Language: de_DE\n" "Language: de_DE\n"
@ -16,9 +16,9 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.4.2\n" "X-Generator: Poedit 3.4.1\n"
#: pretalx_musicrate/apps.py:12 pretalx_musicrate/signals.py:59 #: pretalx_musicrate/apps.py:12
msgid "pretalx-musicrate" msgid "pretalx-musicrate"
msgstr "pretalx-musicrate" msgstr "pretalx-musicrate"
@ -26,7 +26,7 @@ msgstr "pretalx-musicrate"
msgid "pretalx plugin for rating music" msgid "pretalx plugin for rating music"
msgstr "pretalx-Plugin zur Bewertung von Musik" msgstr "pretalx-Plugin zur Bewertung von Musik"
#: pretalx_musicrate/forms.py:79 #: pretalx_musicrate/forms.py:85
msgid "require all" msgid "require all"
msgstr "alle voraussetzen" msgstr "alle voraussetzen"
@ -71,95 +71,100 @@ msgid "The name of the submission's assignee"
msgstr "Der Name der für die Einreichung zuständigen Person" msgstr "Der Name der für die Einreichung zuständigen Person"
#: pretalx_musicrate/signals.py:35 #: pretalx_musicrate/signals.py:35
msgid "Sessions, but better" msgid "Proposals, but better"
msgstr "Vorträge, aber besser" msgstr "Einreichungen, aber besser"
#: pretalx_musicrate/signals.py:45 #: pretalx_musicrate/signals.py:45
msgid "Collective Rating" msgid "Collective Rating"
msgstr "Anhörtag" msgstr "Anhörtag"
#: pretalx_musicrate/templates/pretalx_musicrate/assignee.html:5 #: pretalx_musicrate/templates/pretalx_musicrate/assignee.html:6
#, python-format #, python-format
msgid "Assignee for %(quotation_open)s%(title)s%(quotation_close)s" msgid "Assignee for %(quotation_open)s%(title)s%(quotation_close)s"
msgstr "Zuständige*r für %(quotation_open)s%(title)s%(quotation_close)s" msgstr "Zuständige*r für %(quotation_open)s%(title)s%(quotation_close)s"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:43 #: pretalx_musicrate/templates/pretalx_musicrate/assignee.html:16
#: pretalx_musicrate/templates/pretalx_musicrate/settings.html:22
msgid "Save"
msgstr "Speichern"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:23
msgid "proposal" msgid "proposal"
msgid_plural "proposals" msgid_plural "proposals"
msgstr[0] "Einreichung" msgstr[0] "Einreichung"
msgstr[1] "Einreichungen" msgstr[1] "Einreichungen"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:72 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:46
msgid "Search" msgid "Search"
msgstr "Suche" msgstr "Suche"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:77 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:51
#, python-format #, python-format
msgid "List filtered by answers to question \"%(question)s\"." msgid "List filtered by answers to question \"%(question)s\"."
msgstr "Liste gefiltert nach Antworten zur Frage \"%(question)s\"." msgstr "Liste gefiltert nach Antworten zur Frage \"%(question)s\"."
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:82 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:56
msgid "Remove filter" msgid "Remove filter"
msgstr "Filter entfernen" msgstr "Filter entfernen"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:93 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:67
msgid "Rating" msgid "Rating"
msgstr "Bewertung" msgstr "Bewertung"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:94 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:68
msgid "Sort by rating (0-10)" msgid "Sort by rating (0-10)"
msgstr "Nach Bewertung sortieren (0-10)" msgstr "Nach Bewertung sortieren (0-10)"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:95 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:69
msgid "Sort by rating (10-0)" msgid "Sort by rating (10-0)"
msgstr "Nach Bewertung sortieren (10-0)" msgstr "Nach Bewertung sortieren (10-0)"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:98 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:72
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:99 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:73
msgid "Sort by title (a-z)" msgid "Sort by title (a-z)"
msgstr "Nach Titel sortieren (a-z)" msgstr "Nach Titel sortieren (a-z)"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:100 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:74
msgid "Sort by title (z-a)" msgid "Sort by title (z-a)"
msgstr "Nach Titel sortieren (z-a)" msgstr "Nach Titel sortieren (z-a)"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:104 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:78
msgid "Type" msgid "Type"
msgstr "Typ" msgstr "Typ"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:108 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:82
msgid "State" msgid "State"
msgstr "Status" msgstr "Status"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:109 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:83
msgid "Sort by state (a-z)" msgid "Sort by state (a-z)"
msgstr "Nach Status sortieren (a-z)" msgstr "Nach Status sortieren (a-z)"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:110 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:84
msgid "Sort by state (z-a)" msgid "Sort by state (z-a)"
msgstr "Nach Status sortieren (z-a)" msgstr "Nach Status sortieren (z-a)"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:113 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:87
msgid "Assignee" msgid "Assignee"
msgstr "Zuständige*r" msgstr "Zuständige*r"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:114 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:88
msgid "Sort by assignee (a-z)" msgid "Sort by assignee (a-z)"
msgstr "Nach Zuständiger*m sortieren (a-z)" msgstr "Nach Zuständiger*m sortieren (a-z)"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:115 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:89
msgid "Sort by assignee (z-a)" msgid "Sort by assignee (z-a)"
msgstr "Nach Zuständiger*m sortieren (z-a)" msgstr "Nach Zuständiger*m sortieren (z-a)"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:154 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:128
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:162 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:136
msgid "edit" msgid "edit"
msgstr "bearbeiten" msgstr "bearbeiten"
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:167 #: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:141
msgid "Delete" msgid "Delete"
msgstr "Löschen" msgstr "Löschen"
@ -186,43 +191,39 @@ msgstr "Mitmach-QR-Code anzeigen"
msgid "Go to collective rating" msgid "Go to collective rating"
msgstr "Zum Anhörtag" msgstr "Zum Anhörtag"
#: pretalx_musicrate/templates/pretalx_musicrate/settings.html:7 #: pretalx_musicrate/templates/pretalx_musicrate/settings.html:8
msgid "pretalx-musicrate settings" msgid "pretalx-musicrate settings"
msgstr "pretalx-musicrate-Einstellungen" msgstr "pretalx-musicrate-Einstellungen"
#: pretalx_musicrate/templates/pretalx_musicrate/settings.html:12 #: pretalx_musicrate/templates/pretalx_musicrate/settings.html:30
msgid "Export ratings" msgid "Export ratings"
msgstr "Bewertungen exportieren" msgstr "Bewertungen exportieren"
#: pretalx_musicrate/templates/pretalx_musicrate/settings.html:16 #: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:29
msgid "Export scores"
msgstr "Auswertung exportieren"
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:34
msgid "(not specified)" msgid "(not specified)"
msgstr "(nicht angegeben)" msgstr "(nicht angegeben)"
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:47 #: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:42
msgid "Previous" msgid "Previous"
msgstr "Zurück" msgstr "Zurück"
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:55 #: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:50
msgid "Next" msgid "Next"
msgstr "Weiter" msgstr "Weiter"
#: pretalx_musicrate/views.py:66 #: pretalx_musicrate/views.py:48
msgid "Invalid token" msgid "Invalid token"
msgstr "Ungültiges Token" msgstr "Ungültiges Token"
#: pretalx_musicrate/views.py:133 #: pretalx_musicrate/views.py:123
msgid "The pretalx-musicrate settings were updated." msgid "The pretalx-musicrate settings were updated."
msgstr "Die pretalx-musicrate-Einstellungen wurden gespeichert." msgstr "Die pretalx-musicrate-Einstellungen wurden gespeichert."
#: pretalx_musicrate/views.py:289 pretalx_musicrate/views.py:482 #: pretalx_musicrate/views.py:282 pretalx_musicrate/views.py:469
msgid "Saved!" msgid "Saved!"
msgstr "Gespeichert!" msgstr "Gespeichert!"
#: pretalx_musicrate/views.py:398 #: pretalx_musicrate/views.py:386
#, python-format #, python-format
msgid "%(num_ratings)d of %(num_jurors)d has rated this submission" msgid "%(num_ratings)d of %(num_jurors)d has rated this submission"
msgid_plural "%(num_ratings)d of %(num_jurors)d have rated this submission" msgid_plural "%(num_ratings)d of %(num_jurors)d have rated this submission"

View File

@ -20,9 +20,6 @@ class Command(BaseCommand):
help = "Compute submission scores from ratings" help = "Compute submission scores from ratings"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument(
"-a", "--all", action="store_true", help="include frozen jurors"
)
parser.add_argument("event") parser.add_argument("event")
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
@ -34,15 +31,9 @@ class Command(BaseCommand):
with scope(event=event): with scope(event=event):
submissions = {} submissions = {}
jurors = event.jurors.prefetch_related("ratings__submission").order_by( for juror in event.jurors.prefetch_related("ratings__submission").order_by(
"token" "token"
) ):
if not kwargs["all"]:
jurors = jurors.filter(frozen=False)
self.stderr.write(f"computing scores from {jurors.count()} jurors")
for juror in jurors:
ratings = list( ratings = list(
juror.ratings.exclude(rating="").order_by("submission__created") juror.ratings.exclude(rating="").order_by("submission__created")
) )

View File

@ -1,7 +1,7 @@
# Generated by Django 4.2.8 on 2023-12-15 18:27 # Generated by Django 4.2.8 on 2023-12-15 18:27
import django.db.models.deletion import django.db.models.deletion
import pretalx.common.models.mixins import pretalx.common.mixins.models
from django.db import migrations, models from django.db import migrations, models
import pretalx_musicrate.models import pretalx_musicrate.models
@ -51,8 +51,8 @@ class Migration(migrations.Migration):
"abstract": False, "abstract": False,
}, },
bases=( bases=(
pretalx.common.models.mixins.LogMixin, pretalx.common.mixins.models.LogMixin,
pretalx.common.models.mixins.FileCleanupMixin, pretalx.common.mixins.models.FileCleanupMixin,
models.Model, models.Model,
), ),
), ),

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.5 on 2025-02-23 13:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretalx_musicrate", "0009_score"),
]
operations = [
migrations.AddField(
model_name="juror",
name="frozen",
field=models.BooleanField(default=False),
),
]

View File

@ -78,7 +78,6 @@ class Juror(models.Model):
related_name="jurors", related_name="jurors",
null=True, null=True,
) )
frozen = models.BooleanField(default=False)
class Rating(models.Model): class Rating(models.Model):

View File

@ -32,7 +32,7 @@ def pretalx_musicrate_nav_event(sender, request, **kwargs):
"active": request.resolver_match.view_name "active": request.resolver_match.view_name
== "plugins:pretalx_musicrate:enhanced_list", == "plugins:pretalx_musicrate:enhanced_list",
"icon": "sticky-note-o", "icon": "sticky-note-o",
"label": _("Sessions, but better"), "label": _("Proposals, but better"),
"url": reverse( "url": reverse(
"plugins:pretalx_musicrate:enhanced_list", "plugins:pretalx_musicrate:enhanced_list",
kwargs={"event": request.event.slug}, kwargs={"event": request.event.slug},
@ -56,7 +56,7 @@ def pretalx_musicrate_settings(sender, request, **kwargs):
return [] return []
return [ return [
{ {
"label": _("pretalx-musicrate"), "label": "pretalx-musicrate",
"url": reverse( "url": reverse(
"plugins:pretalx_musicrate:settings.musicrate", "plugins:pretalx_musicrate:settings.musicrate",
kwargs={"event": request.event.slug}, kwargs={"event": request.event.slug},

View File

@ -1,14 +1,11 @@
onReady(() => { document.addEventListener("DOMContentLoaded", function() {
const updateRequireAllVisibility = () => { const updateRequireAllVisibility = () => {
if (document.querySelector("#id_tags").value) { if (document.querySelector("#id_tags").value) {
document.querySelector("#requireAll").classList.remove("d-none") document.querySelector("#requireAll").classList.remove("d-none")
} else { } else {
document.querySelector("#requireAll").classList.add("d-none") document.querySelector("#requireAll").classList.add("d-none")
}
} }
}
document $("#id_tags").on("change", updateRequireAllVisibility)
.querySelector("#id_tags") updateRequireAllVisibility()
.addEventListener("change", updateRequireAllVisibility)
updateRequireAllVisibility()
}) })

View File

@ -1,8 +1,22 @@
{% extends "orga/base.html" %} {% extends "orga/base.html" %}
{% load bootstrap4 %}
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<h2>{% blocktranslate with title=submission.title %}Assignee for {{ quotation_open }}{{ title }}{{ quotation_close }}{% endblocktranslate %}</h2> <h2>{% blocktranslate with title=submission.title %}Assignee for {{ quotation_open }}{{ title }}{{ quotation_close }}{% endblocktranslate %}</h2>
{% include "orga/includes/base_form.html" %} <form method="post">
{% csrf_token %}
{% bootstrap_form_errors form %}
{% bootstrap_field form.user layout='event' %}
<div class="submit-group panel">
<span></span>
<span>
<button type="submit" class="btn btn-success btn-lg">
<i class="fa fa-check"></i>
{% translate "Save" %}
</button>
</span>
</div>
</form>
{% endblock %} {% endblock %}

View File

@ -1,37 +1,17 @@
{% extends "orga/base.html" %} {% extends "orga/base.html" %}
{% load bootstrap4 %}
{% load compress %} {% load compress %}
{% load i18n %} {% load i18n %}
{% load rules %} {% load rules %}
{% load static %} {% load static %}
{% load url_replace %}
{% block scripts %} {% block scripts %}
{% compress js %} {% compress js %}
<script defer src="{% static "orga/js/submission_filter.js" %}"></script> <script src="{% static "orga/js/submission_filter.js" %}"></script>
{% endcompress %} {% endcompress %}
{% compress js %} {% compress js %}
<script defer src="{% static "pretalx_musicrate/submission_filter.js" %}"></script> <script src="{% static "pretalx_musicrate/submission_filter.js" %}"></script>
{% endcompress %}
{% endblock %}
{% block stylesheets %}
{% compress css %}
<style>
.search-form {
#requireAll .form-group {
margin-bottom: 0;
}
#requireAll label {
display: inline-block;
color: #6c757d;
}
}
#requireAll .form-group-inline {
flex-direction: row;
align-items: center;
}
</style>
{% endcompress %} {% endcompress %}
{% endblock %} {% endblock %}
@ -49,26 +29,20 @@
<div class="submit-group search-submit-group"> <div class="submit-group search-submit-group">
<form class="search-form"> <form class="search-form">
{{ filter_form.q.as_field_group }} {% bootstrap_field filter_form.q %}
{% if show_submission_types and filter_form.submission_type %}{{ filter_form.submission_type.as_field_group }}{% endif %} {% if show_submission_types and filter_form.submission_type %}{% bootstrap_field filter_form.submission_type %}{% endif %}
<div class="d-flex flex-column form-group"> <div class="d-flex flex-column form-group">
{{ filter_form.state.as_field_group }} {% bootstrap_field filter_form.state layout='inline' %}
<div id="pending" class="ml-1 d-none">{{ filter_form.pending_state__isnull.as_field_group }}</div> <div id="pending" class="d-none">{% bootstrap_field filter_form.pending_state__isnull layout='inline' %}</div>
</div> </div>
{% if filter_form.track %}{{ filter_form.track.as_field_group }}{% endif %} {% if filter_form.track %}{% bootstrap_field filter_form.track %}{% endif %}
{% if filter_form.tags %} {% if filter_form.tags %}
<div class="d-flex flex-column form-group"> <div class="d-flex flex-column form-group">
{{ filter_form.tags.as_field_group }} {% bootstrap_field filter_form.tags layout='inline' %}
<div id="requireAll" class="ml-1 d-none">{{ filter_form.require_all_tags.as_field_group }}</div> <div id="requireAll" class="d-none">{% bootstrap_field filter_form.require_all_tags layout='inline' %}</div>
</div> </div>
{% endif %} {% endif %}
{% if filter_form.content_locale %}{{ filter_form.content_locale.as_field_group }}{% endif %} {% if filter_form.content_locale %}{% bootstrap_field filter_form.content_locale %}{% endif %}
{# These fields are hidden, but included to keep question search intact #}
{% if request.GET.question %} <input type="hidden" name="question" value="{{ request.GET.question }}"> {% endif %}
{% if request.GET.answer__options %} <input type="hidden" name="answer__options" value="{{ request.GET.answer__options }}"> {% endif %}
{% if request.GET.answer %} <input type="hidden" name="answer" value="{{ request.GET.answer }}"> {% endif %}
{% if request.GET.unanswered %} <input type="hidden" name="unanswered" value="{{ request.GET.unanswered }}"> {% endif %}
<button class="btn btn-success" type="submit">{% translate "Search" %}</button> <button class="btn btn-success" type="submit">{% translate "Search" %}</button>
</form> </form>
{% if filter_form.is_valid and filter_form.cleaned_data.question %} {% if filter_form.is_valid and filter_form.cleaned_data.question %}
@ -77,7 +51,7 @@
{% blocktranslate trimmed with question=filter_form.cleaned_data.question.question %} {% blocktranslate trimmed with question=filter_form.cleaned_data.question.question %}
List filtered by answers to question "{{ question }}". List filtered by answers to question "{{ question }}".
{% endblocktranslate %} {% endblocktranslate %}
<a href="{% querystring question="" answer="" answer__options="" %}" class="text-muted"> <a href="?{% url_replace request 'question' '' 'answer' '' 'answer__options' '' %}" class="text-muted">
<span class="fa fa-times"></span> <span class="fa fa-times"></span>
{% translate "Remove filter" %} {% translate "Remove filter" %}
</a> </a>
@ -91,13 +65,13 @@
<tr> <tr>
<th> <th>
{% translate "Rating" %} {% translate "Rating" %}
<a href="{% querystring sort="score__value" %}"><i class="fa fa-caret-down" title="{% translate "Sort by rating (0-10)" %}"></i></a> <a href="?{% url_replace request 'sort' 'score__value' %}"><i class="fa fa-caret-down" title="{% translate "Sort by rating (0-10)" %}"></i></a>
<a href="{% querystring sort="-score__value" %}"><i class="fa fa-caret-up" title="{% translate "Sort by rating (10-0)" %}"></i></a> <a href="?{% url_replace request 'sort' '-score__value' %}"><i class="fa fa-caret-up" title="{% translate "Sort by rating (10-0)" %}"></i></a>
</th> </th>
<th> <th>
{% translate "Title" %} {% translate "Title" %}
<a href="?{% querystring sort="title" %}"><i class="fa fa-caret-down" title="{% translate "Sort by title (a-z)" %}"></i></a> <a href="?{% url_replace request 'sort' 'title' %}"><i class="fa fa-caret-down" title="{% translate "Sort by title (a-z)" %}"></i></a>
<a href="?{% querystring sort="-title" %}"><i class="fa fa-caret-up" title="{% translate "Sort by title (z-a)" %}"></i></a> <a href="?{% url_replace request 'sort' '-title' %}"><i class="fa fa-caret-up" title="{% translate "Sort by title (z-a)" %}"></i></a>
</th> </th>
{% if show_submission_types %} {% if show_submission_types %}
<th> <th>
@ -106,13 +80,13 @@
{% endif %} {% endif %}
<th> <th>
{% translate "State" %} {% translate "State" %}
<a href="?{% querystring sort="state" %}"><i class="fa fa-caret-down" title="{% translate "Sort by state (a-z)" %}"></i></a> <a href="?{% url_replace request 'sort' 'state' %}"><i class="fa fa-caret-down" title="{% translate "Sort by state (a-z)" %}"></i></a>
<a href="?{% querystring sort="-state" %}"><i class="fa fa-caret-up" title="{% translate "Sort by state (z-a)" %}"></i></a> <a href="?{% url_replace request 'sort' '-state' %}"><i class="fa fa-caret-up" title="{% translate "Sort by state (z-a)" %}"></i></a>
</th> </th>
<th> <th>
{% translate "Assignee" %} {% translate "Assignee" %}
<a href="?{% querystring sort="assignee" %}"><i class="fa fa-caret-down" title="{% translate "Sort by assignee (a-z)" %}"></i></a> <a href="?{% url_replace request 'sort' 'assignee' %}"><i class="fa fa-caret-down" title="{% translate "Sort by assignee (a-z)" %}"></i></a>
<a href="?{% querystring sort="-assignee" %}"><i class="fa fa-caret-up" title="{% translate "Sort by assignee (z-a)" %}"></i></a> <a href="?{% url_replace request 'sort' '-assignee' %}"><i class="fa fa-caret-up" title="{% translate "Sort by assignee (z-a)" %}"></i></a>
</th> </th>
{% if can_change_submission %} {% if can_change_submission %}
<th></th> <th></th>

View File

@ -1,18 +1,32 @@
{% extends "orga/base.html" %} {% extends "orga/base.html" %}
{% load bootstrap4 %}
{% load compress %} {% load compress %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<h2>{% translate "pretalx-musicrate settings" %}</h2> <h2>{% translate "pretalx-musicrate settings" %}</h2>
{% include "orga/includes/base_form.html" %} <form method="post">
{% csrf_token %}
{% bootstrap_form_errors form %}
{% bootstrap_field form.submission_types layout='event' %}
{% bootstrap_field form.genre_question layout='event' %}
{% bootstrap_field form.origin_question layout='event' %}
{% bootstrap_field form.link_questions layout='event' %}
{% bootstrap_field form.advance_threshold layout='event' %}
<div class="submit-group panel">
<span></span>
<span>
<button type="submit" class="btn btn-success btn-lg">
<i class="fa fa-check"></i>
{% translate "Save" %}
</button>
</span>
</div>
</form>
<hr> <hr>
<a class="btn btn-success btn-lg btn-block" href="{% url "plugins:pretalx_musicrate:export" event=request.event.slug %}"> <a class="btn btn-success btn-lg btn-block" href="{% url "plugins:pretalx_musicrate:export" event=request.event.slug %}">
<i class="fa fa-download"></i> <i class="fa fa-download"></i>
{% translate "Export ratings" %} {% translate "Export ratings" %}
</a> </a>
<a class="btn btn-success btn-lg btn-block" href="{% url "plugins:pretalx_musicrate:scores" event=request.event.slug %}">
<i class="fa fa-download"></i>
{% translate "Export scores" %}
</a>
{% endblock %} {% endblock %}

View File

@ -1,13 +1,11 @@
{% extends "cfp/event/base.html" %} {% extends "cfp/event/base.html" %}
{% load compress %} {% load compress %}
{% load i18n %} {% load i18n %}
{% load static %}
{% block title %}{{ submission.title }} ::{% endblock %} {% block title %}{{ submission.title }} ::{% endblock %}
{% block stylesheets %} {% block cfp_header %}
{% compress css %} {% compress css %}
<link rel="stylesheet" type="text/css" href="{% static "common/css/_forms.css" %}" />
<style> <style>
.musicrate-pagination { .musicrate-pagination {
display: flex; display: flex;
@ -23,9 +21,6 @@
} }
</style> </style>
{% endcompress %} {% endcompress %}
{% endblock %}
{% block cfp_header %}
{% block submission_header %} {% block submission_header %}
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}

View File

@ -1,5 +1,4 @@
from django.urls import include, path, re_path from django.urls import include, path
from pretalx.event.models.event import SLUG_REGEX
from .views import ( from .views import (
AssigneeView, AssigneeView,
@ -11,12 +10,11 @@ from .views import (
PresenterView, PresenterView,
QRCodeView, QRCodeView,
RatingView, RatingView,
ScoreExportView,
) )
urlpatterns = [ urlpatterns = [
re_path( path(
rf"^orga/event/(?P<event>{SLUG_REGEX})/", "orga/event/<slug:event>/",
include( include(
[ [
path( path(
@ -34,7 +32,6 @@ urlpatterns = [
EnhancedSubmissionList.as_view(), EnhancedSubmissionList.as_view(),
name="enhanced_list", name="enhanced_list",
), ),
path("scores/", ScoreExportView.as_view(), name="scores"),
path("<code>/", AssigneeView.as_view(), name="assignee"), path("<code>/", AssigneeView.as_view(), name="assignee"),
] ]
), ),
@ -42,8 +39,8 @@ urlpatterns = [
] ]
), ),
), ),
re_path( path(
rf"^(?P<event>{SLUG_REGEX})/p/pretalx_musicrate/", "<slug:event>/p/pretalx_musicrate/",
include( include(
[ [
path("", QRCodeView.as_view(), name="qrcode"), path("", QRCodeView.as_view(), name="qrcode"),

View File

@ -4,7 +4,7 @@ from hmac import compare_digest
from urllib.parse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
from django.contrib import messages from django.contrib import messages
from django.db.models import Case, F, FilteredRelation, Q, Value, When from django.db.models import Case, FilteredRelation, Q, Value, When
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
@ -14,9 +14,9 @@ from django.utils.translation import gettext_lazy as _, ngettext
from django.views.generic import FormView, TemplateView, View from django.views.generic import FormView, TemplateView, View
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django_context_decorator import context from django_context_decorator import context
from pretalx.common.views.mixins import EventPermissionRequired from pretalx.common.mixins.views import EventPermissionRequired
from pretalx.orga.views.submission import BaseSubmissionList, SubmissionList from pretalx.orga.views.submission import BaseSubmissionList, SubmissionList
from pretalx.submission.models import Submission, SubmissionStates from pretalx.submission.models import Submission
from .forms import ( from .forms import (
AssigneeForm, AssigneeForm,
@ -31,30 +31,12 @@ youtube_re = re.compile(
) )
def get_last_submission(settings, submissions, submission=None):
if submission is not None and submission.state != SubmissionStates.SUBMITTED:
submission = None
submission = submission or settings.last_submission
if submission is not None and submission.state != SubmissionStates.SUBMITTED:
submission = None
submissions = submissions.filter(
submission_type__in=settings.submission_types.all(),
state=SubmissionStates.SUBMITTED,
)
if submission is not None and not submissions.filter(pk=submission.pk).exists():
submission = None
return submission or submissions.order_by("created").first()
class JoinView(TemplateView): class JoinView(TemplateView):
template_name = "pretalx_musicrate/join.html" template_name = "pretalx_musicrate/join.html"
def validate_token(self, token): def validate_token(self, token):
try: try:
self.juror = Juror.objects.get(token=token, frozen=False) self.juror = Juror.objects.get(token=token)
return True return True
except Juror.DoesNotExist: except Juror.DoesNotExist:
self.juror = None self.juror = None
@ -75,10 +57,14 @@ class JoinView(TemplateView):
def get(self, request, *args, token, **kwargs): def get(self, request, *args, token, **kwargs):
token_valid = self.validate_token(token) token_valid = self.validate_token(token)
if self.juror is not None: if self.juror is not None:
submission = get_last_submission( submission = (
self.request.event.pretalx_musicrate_settings, self.juror.last_submission
self.request.event.submissions, or self.request.event.pretalx_musicrate_settings.last_submission
self.juror.last_submission, or self.request.event.submissions.filter(
submission_type__in=self.request.event.pretalx_musicrate_settings.submission_types.all()
)
.order_by("created")
.first()
) )
if submission is not None: if submission is not None:
return redirect( return redirect(
@ -97,10 +83,14 @@ class JoinView(TemplateView):
event=self.request.event, event=self.request.event,
last_submission=self.request.event.pretalx_musicrate_settings.last_submission, last_submission=self.request.event.pretalx_musicrate_settings.last_submission,
) )
submission = get_last_submission( submission = (
self.request.event.pretalx_musicrate_settings, self.juror.last_submission
self.request.event.submissions, or self.request.event.pretalx_musicrate_settings.last_submission
self.juror.last_submission, or self.request.event.submissions.filter(
submission_type__in=self.request.event.pretalx_musicrate_settings.submission_types.all()
)
.order_by("created")
.first()
) )
if submission is not None: if submission is not None:
return redirect( return redirect(
@ -150,9 +140,13 @@ class QRCodeView(EventPermissionRequired, TemplateView):
}, },
) )
) )
context["last_submission"] = get_last_submission( context["last_submission"] = (
self.request.event.pretalx_musicrate_settings, self.request.event.pretalx_musicrate_settings.last_submission
self.request.event.submissions, or self.request.event.submissions.filter(
submission_type__in=self.request.event.pretalx_musicrate_settings.submission_types.all()
)
.order_by("created")
.first()
) )
return context return context
@ -163,8 +157,7 @@ class SubmissionMixin(SingleObjectMixin):
def get_queryset(self): def get_queryset(self):
return self.request.event.submissions.prefetch_related("answers").filter( return self.request.event.submissions.prefetch_related("answers").filter(
submission_type__in=self.request.event.pretalx_musicrate_settings.submission_types.all(), submission_type__in=self.request.event.pretalx_musicrate_settings.submission_types.all()
state=SubmissionStates.SUBMITTED,
) )
@context @context
@ -256,7 +249,7 @@ class RatingView(FormView, SubmissionMixin):
@cached_property @cached_property
def juror(self): def juror(self):
return get_object_or_404( return get_object_or_404(
Juror, token=self.request.resolver_match.kwargs["token"], frozen=False Juror, token=self.request.resolver_match.kwargs["token"]
) )
@context @context
@ -291,22 +284,17 @@ class RatingView(FormView, SubmissionMixin):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
last_submission = get_last_submission(
self.request.event.pretalx_musicrate_settings,
self.request.event.submissions,
)
if ( if (
last_submission is not None (settings := self.request.event.pretalx_musicrate_settings).last_submission
and self.object.created > last_submission.created is not None
and self.object.created > settings.last_submission.created
): ):
return redirect( return redirect(
self.request.resolver_match.view_name, self.request.resolver_match.view_name,
event=self.request.event.slug, event=self.request.event.slug,
token=self.juror.token, token=self.juror.token,
code=last_submission.code, code=settings.last_submission.code,
) )
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@ -365,16 +353,16 @@ class PresenterView(EventPermissionRequired, SubmissionMixin, TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if (
settings = self.request.event.pretalx_musicrate_settings (settings := self.request.event.pretalx_musicrate_settings).last_submission
last_submission = get_last_submission(settings, self.request.event.submissions) is None
if last_submission is None or self.object.created > last_submission.created: or self.object.created > settings.last_submission.created
):
try: try:
settings.last_submission = self.object settings.last_submission = self.object
settings.save() settings.save()
except Exception: except Exception:
pass pass
response = super().get(request, *args, **kwargs) response = super().get(request, *args, **kwargs)
response._csp_update = {"frame-src": "https://www.youtube-nocookie.com"} response._csp_update = {"frame-src": "https://www.youtube-nocookie.com"}
return response return response
@ -384,12 +372,12 @@ class MayAdvanceView(EventPermissionRequired, SubmissionMixin, View):
permission_required = "orga.view_submissions" permission_required = "orga.view_submissions"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
num_ratings = self.submission.ratings.filter(juror__frozen=False).count() num_ratings = self.submission.ratings.count()
num_jurors = self.request.event.jurors.filter(frozen=False).count() num_jurors = self.request.event.jurors.count()
return JsonResponse( return JsonResponse(
{ {
"mayAdvance": num_ratings "mayAdvance": num_ratings
>= round( >= int(
num_jurors num_jurors
* self.request.event.pretalx_musicrate_settings.advance_threshold * self.request.event.pretalx_musicrate_settings.advance_threshold
) )
@ -417,7 +405,7 @@ class ExportView(EventPermissionRequired, View):
writer = csv.writer(response) writer = csv.writer(response)
genre_question = request.event.pretalx_musicrate_settings.genre_question genre_question = request.event.pretalx_musicrate_settings.genre_question
origin_question = request.event.pretalx_musicrate_settings.origin_question origin_question = request.event.pretalx_musicrate_settings.origin_question
jurors = request.event.jurors.filter(frozen=False).order_by("token") jurors = request.event.jurors.order_by("token")
for submission in ( for submission in (
request.event.submissions.prefetch_related("answers") request.event.submissions.prefetch_related("answers")
.select_related("submission_type") .select_related("submission_type")
@ -513,27 +501,3 @@ class EnhancedSubmissionList(SubmissionList):
if not self.filter_form.is_valid(): if not self.filter_form.is_valid():
return qs return qs
return self.filter_form.filter_queryset(qs) return self.filter_form.filter_queryset(qs)
class ScoreExportView(EventPermissionRequired, View):
permission_required = "orga.view_submissions"
def get(self, request, *args, **kwargs):
response = HttpResponse(
content_type="text/csv",
headers={
"Content-Disposition": f'attachment; filename="{request.event.slug}.csv"'
},
)
csv.writer(response).writerows(
request.event.submissions.select_related("score", "submission_type")
.filter(
submission_type__in=request.event.pretalx_musicrate_settings.submission_types.all()
)
.exclude(state__in=(SubmissionStates.CANCELED, SubmissionStates.WITHDRAWN))
.order_by(F("score__value").desc(nulls_last=True))
.values_list("score__value", "title")
)
return response