Compare commits

..

No commits in common. "main" and "v2025.1.0" have entirely different histories.

9 changed files with 59 additions and 130 deletions

View File

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

View File

@ -62,9 +62,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

@ -7,8 +7,8 @@ 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-11-02 19:23+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,7 +16,7 @@ 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 pretalx_musicrate/signals.py:59
msgid "pretalx-musicrate" msgid "pretalx-musicrate"
@ -194,35 +194,31 @@ msgstr "pretalx-musicrate-Einstellungen"
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,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

@ -11,8 +11,4 @@
<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

@ -11,7 +11,6 @@ from .views import (
PresenterView, PresenterView,
QRCodeView, QRCodeView,
RatingView, RatingView,
ScoreExportView,
) )
urlpatterns = [ urlpatterns = [
@ -34,7 +33,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"),
] ]
), ),

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
@ -16,7 +16,7 @@ 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.views.mixins 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,8 +372,8 @@ 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
@ -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