Compare commits

...

10 Commits

Author SHA1 Message Date
Luca d319559272 fix(computescores): unexpected keyword argument
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2025-02-23 17:57:25 +01:00
Luca 1b264054fc chore: bump version
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2025-02-23 17:46:19 +01:00
Luca c5b39e1890 feat(computescores): add option to include frozen jurors 2025-02-23 17:45:55 +01:00
Luca 97a1dbf33a chore: bump version
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2025-02-23 14:41:17 +01:00
Luca 27e0b1e2c5 feat: add flag to Juror to disable them 2025-02-23 14:40:52 +01:00
Luca ec3acac3fc fix: assignee assignment
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2025-01-30 18:47:07 +01:00
Luca d5e3a35056 chore: bump version
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-12-09 22:33:19 +01:00
Luca 9accf9714e feat: add score export 2024-12-09 22:32:50 +01:00
Luca 2d7efeaf09 chore: bump version
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2024-12-08 15:49:16 +01:00
Luca 50d189d8ed feat: 'fix' last submission when submission types change 2024-12-08 15:48:24 +01:00
9 changed files with 92 additions and 35 deletions

View File

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

View File

@ -62,12 +62,9 @@ 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.none().union( self.fields["user"].queryset = User.objects.filter(
*( teams__in=submission.event.teams, teams__can_change_submissions=True
t.members.all() ).distinct()
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-11-02 19:23+0100\n" "POT-Creation-Date: 2024-12-09 22:22+0100\n"
"PO-Revision-Date: 2024-02-14 22:30+0100\n" "PO-Revision-Date: 2024-12-09 22:23+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.1\n" "X-Generator: Poedit 3.4.2\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,31 +194,35 @@ msgstr "pretalx-musicrate-Einstellungen"
msgid "Export ratings" msgid "Export ratings"
msgstr "Bewertungen exportieren" msgstr "Bewertungen exportieren"
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:29 #: 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)" msgid "(not specified)"
msgstr "(nicht angegeben)" msgstr "(nicht angegeben)"
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:42 #: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:47
msgid "Previous" msgid "Previous"
msgstr "Zurück" msgstr "Zurück"
#: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:50 #: pretalx_musicrate/templates/pretalx_musicrate/submission_base.html:55
msgid "Next" msgid "Next"
msgstr "Weiter" msgstr "Weiter"
#: pretalx_musicrate/views.py:48 #: pretalx_musicrate/views.py:66
msgid "Invalid token" msgid "Invalid token"
msgstr "Ungültiges Token" msgstr "Ungültiges Token"
#: pretalx_musicrate/views.py:123 #: pretalx_musicrate/views.py:133
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:282 pretalx_musicrate/views.py:469 #: pretalx_musicrate/views.py:289 pretalx_musicrate/views.py:482
msgid "Saved!" msgid "Saved!"
msgstr "Gespeichert!" msgstr "Gespeichert!"
#: pretalx_musicrate/views.py:386 #: pretalx_musicrate/views.py:398
#, 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,6 +20,9 @@ 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):
@ -31,9 +34,15 @@ class Command(BaseCommand):
with scope(event=event): with scope(event=event):
submissions = {} submissions = {}
for juror in event.jurors.prefetch_related("ratings__submission").order_by( jurors = 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

@ -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),
),
]

View File

@ -78,6 +78,7 @@ 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,4 +11,8 @@
<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,6 +11,7 @@ from .views import (
PresenterView, PresenterView,
QRCodeView, QRCodeView,
RatingView, RatingView,
ScoreExportView,
) )
urlpatterns = [ urlpatterns = [
@ -33,6 +34,7 @@ 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, FilteredRelation, Q, Value, When from django.db.models import Case, F, 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
@ -39,15 +39,14 @@ def get_last_submission(settings, submissions, submission=None):
if submission is not None and submission.state != SubmissionStates.SUBMITTED: if submission is not None and submission.state != SubmissionStates.SUBMITTED:
submission = None submission = None
return ( submissions = submissions.filter(
submission submission_type__in=settings.submission_types.all(),
or submissions.filter( state=SubmissionStates.SUBMITTED,
submission_type__in=settings.submission_types.all(),
state=SubmissionStates.SUBMITTED,
)
.order_by("created")
.first()
) )
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):
@ -55,7 +54,7 @@ class JoinView(TemplateView):
def validate_token(self, token): def validate_token(self, token):
try: try:
self.juror = Juror.objects.get(token=token) self.juror = Juror.objects.get(token=token, frozen=False)
return True return True
except Juror.DoesNotExist: except Juror.DoesNotExist:
self.juror = None self.juror = None
@ -257,7 +256,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"] Juror, token=self.request.resolver_match.kwargs["token"], frozen=False
) )
@context @context
@ -385,8 +384,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.count() num_ratings = self.submission.ratings.filter(juror__frozen=False).count()
num_jurors = self.request.event.jurors.count() num_jurors = self.request.event.jurors.filter(frozen=False).count()
return JsonResponse( return JsonResponse(
{ {
"mayAdvance": num_ratings "mayAdvance": num_ratings
@ -418,13 +417,12 @@ 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.order_by("token") jurors = request.event.jurors.filter(frozen=False).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")
.filter( .filter(
submission_type__in=request.event.pretalx_musicrate_settings.submission_types.all(), submission_type__in=request.event.pretalx_musicrate_settings.submission_types.all()
state=SubmissionStates.SUBMITTED,
) )
.only("title", "submission_type__name") .only("title", "submission_type__name")
.order_by("created") .order_by("created")
@ -515,3 +513,27 @@ 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