Compare commits
57 Commits
Author | SHA1 | Date |
---|---|---|
|
d319559272 | |
|
1b264054fc | |
|
c5b39e1890 | |
|
97a1dbf33a | |
|
27e0b1e2c5 | |
|
ec3acac3fc | |
|
d5e3a35056 | |
|
9accf9714e | |
|
2d7efeaf09 | |
|
50d189d8ed | |
|
f8f95db5c3 | |
|
dcce8e5407 | |
|
660650c8da | |
|
18af2a75e6 | |
|
01f5434065 | |
|
e7df05566f | |
|
cabf4538d2 | |
|
81f1d44700 | |
|
1c8047a006 | |
|
6823e11ff0 | |
|
a693370a7d | |
|
5b508bf61b | |
|
d4a1a4738f | |
|
91acd4e855 | |
|
83409ef2c8 | |
|
353213b1c8 | |
|
a67cc96935 | |
|
fdf9110875 | |
|
c7d67d97f6 | |
|
8b1b807ef3 | |
|
e3a82cb705 | |
|
03b5a6ebf4 | |
|
5df8a8b29b | |
|
ca3ca52ab2 | |
|
d709f020f4 | |
|
f0caef1cca | |
|
f3b5473cd3 | |
|
122c120eae | |
|
23821c523d | |
|
1a3382614a | |
|
b99264647d | |
|
48216a8862 | |
|
d2ca7db7aa | |
|
57317b51d2 | |
|
3091fb0783 | |
|
14df5d5b68 | |
|
a0ccd2ff8e | |
|
049062d5bb | |
|
2bb22a0e78 | |
|
31c3c820fd | |
|
b6d35d152c | |
|
41f5fa840b | |
|
fac4f8ba56 | |
|
ad86b5dfd3 | |
|
98d9607a70 | |
|
14df6ecec4 | |
|
dc8baee749 |
|
@ -17,7 +17,7 @@ steps:
|
|||
export PATH="$$DRONE_WORKSPACE/.venv/bin:$$PATH"
|
||||
- echo $$PATH
|
||||
- which pip
|
||||
- pip install black build flake8 isort twine
|
||||
- pip install black build Django flake8 isort twine
|
||||
|
||||
- name: check style
|
||||
image: python:3.11-alpine
|
||||
|
@ -31,6 +31,10 @@ steps:
|
|||
image: python:3.11-alpine
|
||||
commands:
|
||||
- *path
|
||||
- apk --update-cache add gettext
|
||||
- cd pretalx_musicrate
|
||||
- django-admin compilemessages
|
||||
- cd ..
|
||||
- python -m build
|
||||
|
||||
- name: publish
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
root = true
|
||||
|
||||
[*.{html,js}]
|
||||
indent_size = 4
|
||||
indent_style = space
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright 2023 Luca
|
||||
Copyright 2023-2024 Luca Schmid
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -40,7 +40,7 @@ black .
|
|||
|
||||
## License
|
||||
|
||||
Copyright 2023 Luca Schmid
|
||||
Copyright 2023-2024 Luca Schmid
|
||||
|
||||
Released under the terms of the Apache License 2.0
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "0.2.0"
|
||||
__version__ = "2025.6.1"
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
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 MusicrateSettings, Rating
|
||||
from .models import Assignee, MusicrateSettings, Rating
|
||||
|
||||
|
||||
class MusicrateSettingsForm(I18nModelForm):
|
||||
|
@ -34,12 +37,6 @@ class MusicrateSettingsForm(I18nModelForm):
|
|||
"link_questions",
|
||||
"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 = {
|
||||
"submission_types": SafeModelMultipleChoiceField,
|
||||
"genre_question": SafeModelChoiceField,
|
||||
|
@ -56,3 +53,31 @@ class RatingForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = Rating
|
||||
fields = ("rating",)
|
||||
|
||||
|
||||
class AssigneeForm(forms.ModelForm):
|
||||
def __init__(self, *args, submission=None, **kwargs):
|
||||
try:
|
||||
self.instance = Assignee.objects.get(submission=submission)
|
||||
except Assignee.DoesNotExist:
|
||||
self.instance = Assignee(submission=submission)
|
||||
super().__init__(*args, instance=self.instance, **kwargs)
|
||||
self.fields["user"].queryset = User.objects.filter(
|
||||
teams__in=submission.event.teams, teams__can_change_submissions=True
|
||||
).distinct()
|
||||
|
||||
class Meta:
|
||||
model = Assignee
|
||||
fields = ("user",)
|
||||
widgets = {"user": forms.Select(attrs={"class": "select2"})}
|
||||
|
||||
|
||||
class EnhancedSubmissionFilterForm(SubmissionFilterForm):
|
||||
require_all_tags = forms.BooleanField(required=False, label=_("require all"))
|
||||
|
||||
def filter_queryset(self, qs):
|
||||
qs = super().filter_queryset(qs)
|
||||
if self.cleaned_data.get("require_all_tags"):
|
||||
for tag in self.cleaned_data.get("tags"):
|
||||
qs = qs.filter(tags__in=[tag])
|
||||
return qs
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
# pretalx plugin for rating music.
|
||||
# Copyright (C) 2023
|
||||
# Copyright (C) 2023-2024
|
||||
# This file is distributed under the same license as the pretalx_musicrate package.
|
||||
# Luca Schmid <Luca@hackerspace-bamberg.de>, 2023.
|
||||
# Luca Schmid <Luca@hackerspace-bamberg.de>, 2023-2024.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: pretalx-musicrate 0.0.1\n"
|
||||
"Project-Id-Version: pretalx-musicrate 0.9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-12-16 03:23+0100\n"
|
||||
"PO-Revision-Date: 2023-12-16 03:24+0100\n"
|
||||
"POT-Creation-Date: 2024-12-09 22:22+0100\n"
|
||||
"PO-Revision-Date: 2024-12-09 22:23+0100\n"
|
||||
"Last-Translator: Luca <Luca@hackerspace-bamberg.de>\n"
|
||||
"Language-Team: Luca <Luca@hackerspace-bamberg.de>\n"
|
||||
"Language: de_DE\n"
|
||||
|
@ -16,9 +16,9 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.4.1\n"
|
||||
"X-Generator: Poedit 3.4.2\n"
|
||||
|
||||
#: pretalx_musicrate/apps.py:12
|
||||
#: pretalx_musicrate/apps.py:12 pretalx_musicrate/signals.py:59
|
||||
msgid "pretalx-musicrate"
|
||||
msgstr "pretalx-musicrate"
|
||||
|
||||
|
@ -26,6 +26,10 @@ msgstr "pretalx-musicrate"
|
|||
msgid "pretalx plugin for rating music"
|
||||
msgstr "pretalx-Plugin zur Bewertung von Musik"
|
||||
|
||||
#: pretalx_musicrate/forms.py:79
|
||||
msgid "require all"
|
||||
msgstr "alle voraussetzen"
|
||||
|
||||
#: pretalx_musicrate/models.py:23
|
||||
msgid ""
|
||||
"You can limit pretalx-musicrate to some session types. Leave this field "
|
||||
|
@ -62,10 +66,103 @@ msgstr "Weiter-Schwellenwert"
|
|||
msgid "Link Questions"
|
||||
msgstr "Fragen zu Links"
|
||||
|
||||
#: pretalx_musicrate/signals.py:16
|
||||
#: pretalx_musicrate/signals.py:21
|
||||
msgid "The name of the submission's assignee"
|
||||
msgstr "Der Name der für die Einreichung zuständigen Person"
|
||||
|
||||
#: pretalx_musicrate/signals.py:35
|
||||
msgid "Sessions, but better"
|
||||
msgstr "Vorträge, aber besser"
|
||||
|
||||
#: pretalx_musicrate/signals.py:45
|
||||
msgid "Collective Rating"
|
||||
msgstr "Anhörtag"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/assignee.html:5
|
||||
#, python-format
|
||||
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"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:43
|
||||
msgid "proposal"
|
||||
msgid_plural "proposals"
|
||||
msgstr[0] "Einreichung"
|
||||
msgstr[1] "Einreichungen"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:72
|
||||
msgid "Search"
|
||||
msgstr "Suche"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:77
|
||||
#, python-format
|
||||
msgid "List filtered by answers to question \"%(question)s\"."
|
||||
msgstr "Liste gefiltert nach Antworten zur Frage \"%(question)s\"."
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:82
|
||||
msgid "Remove filter"
|
||||
msgstr "Filter entfernen"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:93
|
||||
msgid "Rating"
|
||||
msgstr "Bewertung"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:94
|
||||
msgid "Sort by rating (0-10)"
|
||||
msgstr "Nach Bewertung sortieren (0-10)"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:95
|
||||
msgid "Sort by rating (10-0)"
|
||||
msgstr "Nach Bewertung sortieren (10-0)"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:98
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:99
|
||||
msgid "Sort by title (a-z)"
|
||||
msgstr "Nach Titel sortieren (a-z)"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:100
|
||||
msgid "Sort by title (z-a)"
|
||||
msgstr "Nach Titel sortieren (z-a)"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:104
|
||||
msgid "Type"
|
||||
msgstr "Typ"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:108
|
||||
msgid "State"
|
||||
msgstr "Status"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:109
|
||||
msgid "Sort by state (a-z)"
|
||||
msgstr "Nach Status sortieren (a-z)"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:110
|
||||
msgid "Sort by state (z-a)"
|
||||
msgstr "Nach Status sortieren (z-a)"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:113
|
||||
msgid "Assignee"
|
||||
msgstr "Zuständige*r"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:114
|
||||
msgid "Sort by assignee (a-z)"
|
||||
msgstr "Nach Zuständiger*m sortieren (a-z)"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:115
|
||||
msgid "Sort by assignee (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:162
|
||||
msgid "edit"
|
||||
msgstr "bearbeiten"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/enhanced_list.html:167
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/join.html:5
|
||||
msgid "Join collective rating"
|
||||
msgstr "Beim Anhörtag mitmachen"
|
||||
|
@ -89,35 +186,43 @@ msgstr "Mitmach-QR-Code anzeigen"
|
|||
msgid "Go to collective rating"
|
||||
msgstr "Zum Anhörtag"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/settings.html:8
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/settings.html:7
|
||||
msgid "pretalx-musicrate settings"
|
||||
msgstr "pretalx-musicrate-Einstellungen"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:29
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/settings.html:12
|
||||
msgid "Export ratings"
|
||||
msgstr "Bewertungen exportieren"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/settings.html:16
|
||||
msgid "Export scores"
|
||||
msgstr "Auswertung exportieren"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:34
|
||||
msgid "(not specified)"
|
||||
msgstr "(nicht angegeben)"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:42
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:47
|
||||
msgid "Previous"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:50
|
||||
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:55
|
||||
msgid "Next"
|
||||
msgstr "Weiter"
|
||||
|
||||
#: pretalx_musicrate/views.py:39
|
||||
#: pretalx_musicrate/views.py:66
|
||||
msgid "Invalid token"
|
||||
msgstr "Ungültiges Token"
|
||||
|
||||
#: pretalx_musicrate/views.py:114
|
||||
#: pretalx_musicrate/views.py:133
|
||||
msgid "The pretalx-musicrate settings were updated."
|
||||
msgstr "Die pretalx-musicrate-Einstellungen wurden gespeichert."
|
||||
|
||||
#: pretalx_musicrate/views.py:273
|
||||
#: pretalx_musicrate/views.py:289 pretalx_musicrate/views.py:482
|
||||
msgid "Saved!"
|
||||
msgstr "Gespeichert!"
|
||||
|
||||
#: pretalx_musicrate/views.py:375
|
||||
#: pretalx_musicrate/views.py:398
|
||||
#, python-format
|
||||
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"
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import math
|
||||
import statistics
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django_scopes import scope
|
||||
from pretalx.event.models import Event
|
||||
from pretalx.submission.models import Submission
|
||||
|
||||
from ...models import Rating, Score
|
||||
|
||||
RATINGS_MIN = 30
|
||||
SCALE = list(map(float, map(itemgetter(0), Rating.RATING_CHOICES)))
|
||||
|
||||
MEAN = statistics.mean(SCALE)
|
||||
STD = math.sqrt(statistics.mean([(i - MEAN) ** 2 for i in SCALE]))
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Compute submission scores from ratings"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"-a", "--all", action="store_true", help="include frozen jurors"
|
||||
)
|
||||
parser.add_argument("event")
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
try:
|
||||
event = Event.objects.get(slug=kwargs["event"])
|
||||
except Event.DoesNotExist:
|
||||
raise CommandError(f"no event found with slug '{kwargs['event']}'")
|
||||
|
||||
with scope(event=event):
|
||||
submissions = {}
|
||||
|
||||
jurors = event.jurors.prefetch_related("ratings__submission").order_by(
|
||||
"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(
|
||||
juror.ratings.exclude(rating="").order_by("submission__created")
|
||||
)
|
||||
values = list(map(int, map(attrgetter("rating"), ratings)))
|
||||
ratings = dict(
|
||||
zip(
|
||||
map(attrgetter("code"), map(attrgetter("submission"), ratings)),
|
||||
values,
|
||||
)
|
||||
)
|
||||
|
||||
if len(values) < RATINGS_MIN:
|
||||
mean = MEAN
|
||||
std = STD
|
||||
else:
|
||||
mean = sum(values) / len(values)
|
||||
std = math.sqrt(
|
||||
sum([(i - mean) ** 2 for i in values]) / len(values)
|
||||
)
|
||||
|
||||
for code in ratings:
|
||||
if code not in submissions:
|
||||
submissions[code] = []
|
||||
submissions[code].append((ratings[code] - mean) / std * STD + MEAN)
|
||||
|
||||
for submission in Submission.objects.select_related("score").filter(
|
||||
code__in=submissions.keys()
|
||||
):
|
||||
try:
|
||||
score = submission.score
|
||||
except Submission.score.RelatedObjectDoesNotExist:
|
||||
score = Score(submission=submission)
|
||||
|
||||
ratings = submissions[submission.code]
|
||||
score.value = sum(ratings) / len(ratings)
|
||||
score.save()
|
|
@ -8,8 +8,8 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("submission", "0074_created_updated_everywhere"),
|
||||
("event", "0035_created_updated_everywhere"),
|
||||
("submission", "0072_alter_reviewscore_label"),
|
||||
("event", "0033_chinese_locale_codes"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("submission", "0074_created_updated_everywhere"),
|
||||
("submission", "0072_alter_reviewscore_label"),
|
||||
("pretalx_musicrate", "0001_initial"),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Generated by Django 4.2.8 on 2023-12-15 18:27
|
||||
|
||||
import django.db.models.deletion
|
||||
import pretalx.common.mixins.models
|
||||
import pretalx.common.models.mixins
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretalx_musicrate.models
|
||||
|
@ -9,8 +9,8 @@ import pretalx_musicrate.models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("submission", "0074_created_updated_everywhere"),
|
||||
("event", "0035_created_updated_everywhere"),
|
||||
("submission", "0072_alter_reviewscore_label"),
|
||||
("event", "0033_chinese_locale_codes"),
|
||||
("pretalx_musicrate", "0004_musicratesettings_advance_threshold"),
|
||||
]
|
||||
|
||||
|
@ -51,8 +51,8 @@ class Migration(migrations.Migration):
|
|||
"abstract": False,
|
||||
},
|
||||
bases=(
|
||||
pretalx.common.mixins.models.LogMixin,
|
||||
pretalx.common.mixins.models.FileCleanupMixin,
|
||||
pretalx.common.models.mixins.LogMixin,
|
||||
pretalx.common.models.mixins.FileCleanupMixin,
|
||||
models.Model,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("submission", "0074_created_updated_everywhere"),
|
||||
("submission", "0072_alter_reviewscore_label"),
|
||||
(
|
||||
"pretalx_musicrate",
|
||||
"0005_juror_musicratesettings_last_submission_rating_and_more",
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# Generated by Django 4.2.8 on 2024-02-12 21:11
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("submission", "0072_alter_reviewscore_label"),
|
||||
("pretalx_musicrate", "0007_remove_juror_created_remove_juror_updated"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Assignee",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
(
|
||||
"submission",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="submission.submission",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="assigned_submissions",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 4.2.8 on 2024-02-14 00:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("submission", "0072_alter_reviewscore_label"),
|
||||
("pretalx_musicrate", "0008_assignee"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Score",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("value", models.FloatField()),
|
||||
(
|
||||
"submission",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="submission.submission",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# 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),
|
||||
),
|
||||
]
|
|
@ -78,6 +78,7 @@ class Juror(models.Model):
|
|||
related_name="jurors",
|
||||
null=True,
|
||||
)
|
||||
frozen = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class Rating(models.Model):
|
||||
|
@ -105,3 +106,15 @@ class Rating(models.Model):
|
|||
fields=("submission", "juror"), name="unique_rating"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class Assignee(models.Model):
|
||||
submission = models.OneToOneField("submission.Submission", on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(
|
||||
"person.User", on_delete=models.CASCADE, related_name="assigned_submissions"
|
||||
)
|
||||
|
||||
|
||||
class Score(models.Model):
|
||||
submission = models.OneToOneField("submission.Submission", on_delete=models.CASCADE)
|
||||
value = models.FloatField()
|
||||
|
|
|
@ -1,23 +1,52 @@
|
|||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pretalx.mail.placeholders import SimpleFunctionalMailTextPlaceholder
|
||||
from pretalx.mail.signals import register_mail_placeholders
|
||||
from pretalx.orga.signals import nav_event, nav_event_settings
|
||||
|
||||
|
||||
@receiver(register_mail_placeholders)
|
||||
def pretalx_musicrate_placeholders(sender, **kwargs):
|
||||
return [
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
"assignee",
|
||||
["submission"],
|
||||
lambda submission: (
|
||||
submission.assignee.user.name
|
||||
if hasattr(submission, "assignee")
|
||||
else "Günther"
|
||||
),
|
||||
"Günther",
|
||||
_("The name of the submission's assignee"),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@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.url_name
|
||||
"active": request.resolver_match.view_name
|
||||
== "plugins:pretalx_musicrate:enhanced_list",
|
||||
"icon": "sticky-note-o",
|
||||
"label": _("Sessions, but better"),
|
||||
"url": reverse(
|
||||
"plugins:pretalx_musicrate:enhanced_list",
|
||||
kwargs={"event": request.event.slug},
|
||||
),
|
||||
},
|
||||
{
|
||||
"active": request.resolver_match.view_name
|
||||
== "plugins:pretalx_musicrate:qrcode",
|
||||
"icon": "star",
|
||||
"label": _("Collective Rating"),
|
||||
"url": reverse(
|
||||
"plugins:pretalx_musicrate:qrcode", kwargs={"event": request.event.slug}
|
||||
),
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
@ -27,12 +56,12 @@ def pretalx_musicrate_settings(sender, request, **kwargs):
|
|||
return []
|
||||
return [
|
||||
{
|
||||
"label": "pretalx-musicrate",
|
||||
"label": _("pretalx-musicrate"),
|
||||
"url": reverse(
|
||||
"plugins:pretalx_musicrate:settings",
|
||||
"plugins:pretalx_musicrate:settings.musicrate",
|
||||
kwargs={"event": request.event.slug},
|
||||
),
|
||||
"active": request.resolver_match.url_name
|
||||
== "plugins:pretalx_musicrate:settings",
|
||||
"active": request.resolver_match.view_name
|
||||
== "plugins:pretalx_musicrate:settings.musicrate",
|
||||
}
|
||||
]
|
||||
|
|
|
@ -33,12 +33,12 @@
|
|||
}
|
||||
|
||||
.rating-container > .rating > input[type=radio]:checked ~ .rating-point {
|
||||
background-image: url(point-fill.svg);
|
||||
background-image: url(static('pretalx_musicrate/point-fill.svg'));
|
||||
}
|
||||
|
||||
.rating-container > .rating > .rating-point {
|
||||
--max-size: calc((100vw - 2em - 2 * 0.1em * var(--num-choices)) / var(--num-choices));
|
||||
background: url(point-stroke.svg) no-repeat center/100%;
|
||||
background: url(static('pretalx_musicrate/point-stroke.svg')) no-repeat center/100%;
|
||||
height: 3em;
|
||||
margin: 0 0.1em;
|
||||
max-height: var(--max-size);
|
|
@ -0,0 +1,14 @@
|
|||
onReady(() => {
|
||||
const updateRequireAllVisibility = () => {
|
||||
if (document.querySelector("#id_tags").value) {
|
||||
document.querySelector("#requireAll").classList.remove("d-none")
|
||||
} else {
|
||||
document.querySelector("#requireAll").classList.add("d-none")
|
||||
}
|
||||
}
|
||||
|
||||
document
|
||||
.querySelector("#id_tags")
|
||||
.addEventListener("change", updateRequireAllVisibility)
|
||||
updateRequireAllVisibility()
|
||||
})
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "orga/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% blocktranslate with title=submission.title %}Assignee for {{ quotation_open }}{{ title }}{{ quotation_close }}{% endblocktranslate %}</h2>
|
||||
{% include "orga/includes/base_form.html" %}
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
{% extends "orga/base.html" %}
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
{% load rules %}
|
||||
{% load static %}
|
||||
|
||||
{% block scripts %}
|
||||
{% compress js %}
|
||||
<script defer src="{% static "orga/js/submission_filter.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% compress js %}
|
||||
<script defer 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 %}
|
||||
{% 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 %}
|
||||
<h2>
|
||||
{{ page_obj.paginator.count }}
|
||||
{% blocktranslate trimmed count count=page_obj.paginator.count %}
|
||||
proposal
|
||||
{% plural %}
|
||||
proposals
|
||||
{% endblocktranslate %}
|
||||
</h2>
|
||||
|
||||
<div class="submit-group search-submit-group">
|
||||
<form class="search-form">
|
||||
{{ filter_form.q.as_field_group }}
|
||||
{% if show_submission_types and filter_form.submission_type %}{{ filter_form.submission_type.as_field_group }}{% endif %}
|
||||
<div class="d-flex flex-column form-group">
|
||||
{{ filter_form.state.as_field_group }}
|
||||
<div id="pending" class="ml-1 d-none">{{ filter_form.pending_state__isnull.as_field_group }}</div>
|
||||
</div>
|
||||
{% if filter_form.track %}{{ filter_form.track.as_field_group }}{% endif %}
|
||||
{% if filter_form.tags %}
|
||||
<div class="d-flex flex-column form-group">
|
||||
{{ filter_form.tags.as_field_group }}
|
||||
<div id="requireAll" class="ml-1 d-none">{{ filter_form.require_all_tags.as_field_group }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if filter_form.content_locale %}{{ filter_form.content_locale.as_field_group }}{% 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>
|
||||
</form>
|
||||
{% if filter_form.is_valid and filter_form.cleaned_data.question %}
|
||||
<p class="text-muted ml-2">
|
||||
<span class="fa fa-filter"></span>
|
||||
{% blocktranslate trimmed with question=filter_form.cleaned_data.question.question %}
|
||||
List filtered by answers to question "{{ question }}".
|
||||
{% endblocktranslate %}
|
||||
<a href="{% querystring question="" answer="" answer__options="" %}" class="text-muted">
|
||||
<span class="fa fa-times"></span>
|
||||
{% translate "Remove filter" %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover table-flip">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% 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="{% querystring sort="-score__value" %}"><i class="fa fa-caret-up" title="{% translate "Sort by rating (10-0)" %}"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% translate "Title" %}
|
||||
<a href="?{% querystring 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>
|
||||
</th>
|
||||
{% if show_submission_types %}
|
||||
<th>
|
||||
{% translate "Type" %}
|
||||
</th>
|
||||
{% endif %}
|
||||
<th>
|
||||
{% translate "State" %}
|
||||
<a href="?{% querystring 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>
|
||||
</th>
|
||||
<th>
|
||||
{% translate "Assignee" %}
|
||||
<a href="?{% querystring 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>
|
||||
</th>
|
||||
{% if can_change_submission %}
|
||||
<th></th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for submission in submissions %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if submission.score %}
|
||||
{{ submission.score.value }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ submission.orga_urls.base }}">
|
||||
{% if can_view_speakers %}{{ submission.title }}{% else %}{{ submission.anonymised.title|default:submission.title }}{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
{% if show_submission_types %}
|
||||
<td>
|
||||
{{ submission.submission_type.name }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% include "orga/submission/state_dropdown.html" with submission=submission %}
|
||||
</td>
|
||||
<td>
|
||||
{% if submission.assignee %}
|
||||
{{ submission.assignee.user.name }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{% if can_change_submission %}
|
||||
<a class="btn btn-sm btn-link"
|
||||
href="{% url "plugins:pretalx_musicrate:assignee" event=request.event.slug code=submission.code %}"
|
||||
title="{% translate "edit" %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if can_change_submission %}
|
||||
<td class="action-column">
|
||||
<a href="{{ submission.orga_urls.edit }}"
|
||||
title="{% translate "edit" %}"
|
||||
class="btn btn-sm btn-info">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
<a href="{{ submission.orga_urls.delete }}?from=list"
|
||||
title="{% translate "Delete" %}"
|
||||
class="btn btn-sm btn-danger">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% include "orga/includes/pagination.html" %}
|
||||
{% endblock %}
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
{% block submission_header %}
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" href="{% static "pretalx_musicrate/rating.css" %}">
|
||||
<link rel="stylesheet" type="text/x-scss" href="{% static "pretalx_musicrate/rating.scss" %}">
|
||||
{% endcompress %}
|
||||
{% compress js %}
|
||||
<script defer src="{% static "pretalx_musicrate/rating.js" %}"></script>
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
{% extends "orga/base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% translate "pretalx-musicrate settings" %}</h2>
|
||||
<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' %}
|
||||
{% include "orga/includes/submit_row.html" %}
|
||||
</form>
|
||||
{% include "orga/includes/base_form.html" %}
|
||||
<hr>
|
||||
<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>
|
||||
{% translate "Export ratings" %}
|
||||
</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 %}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
{% extends "cfp/event/base.html" %}
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ submission.title }} ::{% endblock %}
|
||||
|
||||
{% block cfp_header %}
|
||||
{% block stylesheets %}
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "common/css/_forms.css" %}" />
|
||||
<style>
|
||||
.musicrate-pagination {
|
||||
display: flex;
|
||||
|
@ -21,6 +23,9 @@
|
|||
}
|
||||
</style>
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
{% block cfp_header %}
|
||||
{% block submission_header %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@ -29,7 +34,7 @@
|
|||
{% translate "(not specified)" as not_specified %}
|
||||
<h1>{{ submission.title }}</h1>
|
||||
<p>
|
||||
{{ genre|default:not_specified }} · {{ origin|default:not_specified }}
|
||||
{{ submission.submission_type.name }} · {{ genre|default:not_specified }} · {{ origin|default:not_specified }}
|
||||
{% if submission.internal_notes %}
|
||||
<br>
|
||||
{{ submission.internal_notes }}
|
||||
|
|
|
@ -1,22 +1,49 @@
|
|||
from django.urls import include, path
|
||||
from django.urls import include, path, re_path
|
||||
from pretalx.event.models.event import SLUG_REGEX
|
||||
|
||||
from .views import (
|
||||
AssigneeView,
|
||||
EnhancedSubmissionList,
|
||||
ExportView,
|
||||
JoinView,
|
||||
MayAdvanceView,
|
||||
MusicrateSettingsView,
|
||||
PresenterView,
|
||||
QRCodeView,
|
||||
RatingView,
|
||||
ScoreExportView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"orga/event/<slug:event>/settings/p/pretalx_musicrate/",
|
||||
MusicrateSettingsView.as_view(),
|
||||
name="settings",
|
||||
re_path(
|
||||
rf"^orga/event/(?P<event>{SLUG_REGEX})/",
|
||||
include(
|
||||
[
|
||||
path(
|
||||
"settings/p/pretalx_musicrate/",
|
||||
MusicrateSettingsView.as_view(),
|
||||
name="settings.musicrate",
|
||||
),
|
||||
path(
|
||||
"p/pretalx_musicrate/",
|
||||
include(
|
||||
[
|
||||
path("export/", ExportView.as_view(), name="export"),
|
||||
path(
|
||||
"list/",
|
||||
EnhancedSubmissionList.as_view(),
|
||||
name="enhanced_list",
|
||||
),
|
||||
path("scores/", ScoreExportView.as_view(), name="scores"),
|
||||
path("<code>/", AssigneeView.as_view(), name="assignee"),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
path(
|
||||
"<slug:event>/p/pretalx_musicrate/",
|
||||
re_path(
|
||||
rf"^(?P<event>{SLUG_REGEX})/p/pretalx_musicrate/",
|
||||
include(
|
||||
[
|
||||
path("", QRCodeView.as_view(), name="qrcode"),
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import csv
|
||||
import re
|
||||
from hmac import compare_digest
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.db.models import Case, F, FilteredRelation, Q, Value, When
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -12,9 +14,16 @@ from django.utils.translation import gettext_lazy as _, ngettext
|
|||
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.common.views.mixins import EventPermissionRequired
|
||||
from pretalx.orga.views.submission import BaseSubmissionList, SubmissionList
|
||||
from pretalx.submission.models import Submission, SubmissionStates
|
||||
|
||||
from .forms import MusicrateSettingsForm, RatingForm
|
||||
from .forms import (
|
||||
AssigneeForm,
|
||||
EnhancedSubmissionFilterForm,
|
||||
MusicrateSettingsForm,
|
||||
RatingForm,
|
||||
)
|
||||
from .models import Juror, Rating
|
||||
|
||||
youtube_re = re.compile(
|
||||
|
@ -22,12 +31,30 @@ 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):
|
||||
template_name = "pretalx_musicrate/join.html"
|
||||
|
||||
def validate_token(self, token):
|
||||
try:
|
||||
self.juror = Juror.objects.get(token=token)
|
||||
self.juror = Juror.objects.get(token=token, frozen=False)
|
||||
return True
|
||||
except Juror.DoesNotExist:
|
||||
self.juror = None
|
||||
|
@ -48,14 +75,10 @@ class JoinView(TemplateView):
|
|||
def get(self, request, *args, token, **kwargs):
|
||||
token_valid = self.validate_token(token)
|
||||
if self.juror is not None:
|
||||
submission = (
|
||||
self.juror.last_submission
|
||||
or self.request.event.pretalx_musicrate_settings.last_submission
|
||||
or self.request.event.submissions.filter(
|
||||
submission_type__in=self.request.event.pretalx_musicrate_settings.submission_types.all()
|
||||
)
|
||||
.order_by("created")
|
||||
.first()
|
||||
submission = get_last_submission(
|
||||
self.request.event.pretalx_musicrate_settings,
|
||||
self.request.event.submissions,
|
||||
self.juror.last_submission,
|
||||
)
|
||||
if submission is not None:
|
||||
return redirect(
|
||||
|
@ -74,14 +97,10 @@ class JoinView(TemplateView):
|
|||
event=self.request.event,
|
||||
last_submission=self.request.event.pretalx_musicrate_settings.last_submission,
|
||||
)
|
||||
submission = (
|
||||
self.juror.last_submission
|
||||
or self.request.event.pretalx_musicrate_settings.last_submission
|
||||
or self.request.event.submissions.filter(
|
||||
submission_type__in=self.request.event.pretalx_musicrate_settings.submission_types.all()
|
||||
)
|
||||
.order_by("created")
|
||||
.first()
|
||||
submission = get_last_submission(
|
||||
self.request.event.pretalx_musicrate_settings,
|
||||
self.request.event.submissions,
|
||||
self.juror.last_submission,
|
||||
)
|
||||
if submission is not None:
|
||||
return redirect(
|
||||
|
@ -131,13 +150,9 @@ class QRCodeView(EventPermissionRequired, TemplateView):
|
|||
},
|
||||
)
|
||||
)
|
||||
context["last_submission"] = (
|
||||
self.request.event.pretalx_musicrate_settings.last_submission
|
||||
or self.request.event.submissions.filter(
|
||||
submission_type__in=self.request.event.pretalx_musicrate_settings.submission_types.all()
|
||||
)
|
||||
.order_by("created")
|
||||
.first()
|
||||
context["last_submission"] = get_last_submission(
|
||||
self.request.event.pretalx_musicrate_settings,
|
||||
self.request.event.submissions,
|
||||
)
|
||||
return context
|
||||
|
||||
|
@ -148,7 +163,8 @@ class SubmissionMixin(SingleObjectMixin):
|
|||
|
||||
def get_queryset(self):
|
||||
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
|
||||
|
@ -240,7 +256,7 @@ class RatingView(FormView, SubmissionMixin):
|
|||
@cached_property
|
||||
def juror(self):
|
||||
return get_object_or_404(
|
||||
Juror, token=self.request.resolver_match.kwargs["token"]
|
||||
Juror, token=self.request.resolver_match.kwargs["token"], frozen=False
|
||||
)
|
||||
|
||||
@context
|
||||
|
@ -275,17 +291,22 @@ class RatingView(FormView, SubmissionMixin):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
|
||||
last_submission = get_last_submission(
|
||||
self.request.event.pretalx_musicrate_settings,
|
||||
self.request.event.submissions,
|
||||
)
|
||||
if (
|
||||
(settings := self.request.event.pretalx_musicrate_settings).last_submission
|
||||
is not None
|
||||
and self.object.created > settings.last_submission.created
|
||||
last_submission is not None
|
||||
and self.object.created > last_submission.created
|
||||
):
|
||||
return redirect(
|
||||
self.request.resolver_match.view_name,
|
||||
event=self.request.event.slug,
|
||||
token=self.juror.token,
|
||||
code=settings.last_submission.code,
|
||||
code=last_submission.code,
|
||||
)
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
@ -321,13 +342,15 @@ class PresenterView(EventPermissionRequired, SubmissionMixin, TemplateView):
|
|||
links.append(
|
||||
(
|
||||
"youtube",
|
||||
url.removeprefix("http")
|
||||
.removeprefix("s")
|
||||
.removeprefix("://")
|
||||
.removeprefix("youtu.be/")[:11]
|
||||
if m[1] == "youtu.be/"
|
||||
else "".join(
|
||||
parse_qs(urlparse(url).query).get("v", "dQw4w9WgXcQ")
|
||||
(
|
||||
url.removeprefix("http")
|
||||
.removeprefix("s")
|
||||
.removeprefix("://")
|
||||
.removeprefix("youtu.be/")[:11]
|
||||
if m[1] == "youtu.be/"
|
||||
else "".join(
|
||||
parse_qs(urlparse(url).query).get("v", "dQw4w9WgXcQ")
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
@ -342,16 +365,16 @@ class PresenterView(EventPermissionRequired, SubmissionMixin, TemplateView):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if (
|
||||
(settings := self.request.event.pretalx_musicrate_settings).last_submission
|
||||
is None
|
||||
or self.object.created > settings.last_submission.created
|
||||
):
|
||||
|
||||
settings = self.request.event.pretalx_musicrate_settings
|
||||
last_submission = get_last_submission(settings, self.request.event.submissions)
|
||||
if last_submission is None or self.object.created > last_submission.created:
|
||||
try:
|
||||
settings.last_submission = self.object
|
||||
settings.save()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
response = super().get(request, *args, **kwargs)
|
||||
response._csp_update = {"frame-src": "https://www.youtube-nocookie.com"}
|
||||
return response
|
||||
|
@ -361,12 +384,12 @@ class MayAdvanceView(EventPermissionRequired, SubmissionMixin, View):
|
|||
permission_required = "orga.view_submissions"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
num_ratings = self.submission.ratings.count()
|
||||
num_jurors = self.request.event.jurors.count()
|
||||
num_ratings = self.submission.ratings.filter(juror__frozen=False).count()
|
||||
num_jurors = self.request.event.jurors.filter(frozen=False).count()
|
||||
return JsonResponse(
|
||||
{
|
||||
"mayAdvance": num_ratings
|
||||
>= int(
|
||||
>= round(
|
||||
num_jurors
|
||||
* self.request.event.pretalx_musicrate_settings.advance_threshold
|
||||
)
|
||||
|
@ -379,3 +402,138 @@ class MayAdvanceView(EventPermissionRequired, SubmissionMixin, View):
|
|||
% {"num_jurors": num_jurors, "num_ratings": num_ratings},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ExportView(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"'
|
||||
},
|
||||
)
|
||||
writer = csv.writer(response)
|
||||
genre_question = request.event.pretalx_musicrate_settings.genre_question
|
||||
origin_question = request.event.pretalx_musicrate_settings.origin_question
|
||||
jurors = request.event.jurors.filter(frozen=False).order_by("token")
|
||||
for submission in (
|
||||
request.event.submissions.prefetch_related("answers")
|
||||
.select_related("submission_type")
|
||||
.filter(
|
||||
submission_type__in=request.event.pretalx_musicrate_settings.submission_types.all()
|
||||
)
|
||||
.only("title", "submission_type__name")
|
||||
.order_by("created")
|
||||
):
|
||||
submission_info = [submission.title, submission.submission_type.name]
|
||||
if genre_question is not None:
|
||||
submission_info.append(
|
||||
submission.answers.filter(question=genre_question)
|
||||
.values_list("answer", flat=True)
|
||||
.first()
|
||||
or ""
|
||||
)
|
||||
if origin_question is not None:
|
||||
submission_info.append(
|
||||
submission.answers.filter(question=origin_question)
|
||||
.values_list("answer", flat=True)
|
||||
.first()
|
||||
or ""
|
||||
)
|
||||
writer.writerow(
|
||||
[
|
||||
*submission_info,
|
||||
*jurors.annotate(
|
||||
filtered_ratings=FilteredRelation(
|
||||
"ratings", condition=Q(ratings__submission=submission)
|
||||
),
|
||||
rating=Case(
|
||||
When(filtered_ratings__rating=None, then=Value("")),
|
||||
When(filtered_ratings__rating="", then=Value("E")),
|
||||
default="filtered_ratings__rating",
|
||||
),
|
||||
).values_list("rating", flat=True),
|
||||
]
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
class AssigneeView(EventPermissionRequired, FormView, SingleObjectMixin):
|
||||
form_class = AssigneeForm
|
||||
model = Submission
|
||||
permission_required = "orga.change_submissions"
|
||||
slug_field = "code"
|
||||
slug_url_kwarg = "code"
|
||||
template_name = "pretalx_musicrate/assignee.html"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["submission"] = self.object
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.path
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
messages.success(self.request, _("Saved!"))
|
||||
return super().form_valid(form)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return super().get(*args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return super().post(*args, **kwargs)
|
||||
|
||||
|
||||
class EnhancedSubmissionList(SubmissionList):
|
||||
sortable_fields = ("code", "score__value", "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,
|
||||
search_fields=self.get_default_filters(),
|
||||
)
|
||||
|
||||
def _get_base_queryset(self, for_review=False):
|
||||
qs = (
|
||||
super(BaseSubmissionList, self)
|
||||
.get_queryset(for_review=for_review)
|
||||
.prefetch_related("assignee")
|
||||
.order_by("-id")
|
||||
)
|
||||
if not self.filter_form.is_valid():
|
||||
return 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
|
||||
|
|
|
@ -12,13 +12,7 @@ maintainers = [
|
|||
{name = "Luca", email = "Luca@hackerspace-bamberg.de"},
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"Django",
|
||||
"django-context-decorator",
|
||||
"django-i18nfield",
|
||||
"pretalx",
|
||||
"qrcode",
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://git.luj0ga.de/kontakt/pretalx-musicrate"
|
||||
|
|
Loading…
Reference in New Issue