feat: implement rating
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
1c5f068ea3
commit
9edc043451
|
@ -2,7 +2,7 @@ from django import forms
|
|||
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
|
||||
from i18nfield.forms import I18nModelForm
|
||||
|
||||
from .models import MusicrateSettings
|
||||
from .models import MusicrateSettings, Rating
|
||||
|
||||
|
||||
class MusicrateSettingsForm(I18nModelForm):
|
||||
|
@ -42,3 +42,13 @@ class MusicrateSettingsForm(I18nModelForm):
|
|||
"genre_question": SafeModelChoiceField,
|
||||
"origin_question": SafeModelChoiceField,
|
||||
}
|
||||
|
||||
|
||||
class RatingForm(forms.ModelForm):
|
||||
rating = forms.ChoiceField(
|
||||
choices=Rating.RATING_CHOICES, required=False, widget=forms.RadioSelect
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Rating
|
||||
fields = ("rating",)
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# Generated by Django 4.2.8 on 2023-12-15 18:27
|
||||
|
||||
import django.db.models.deletion
|
||||
import pretalx.common.mixins.models
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretalx_musicrate.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("submission", "0074_created_updated_everywhere"),
|
||||
("event", "0035_created_updated_everywhere"),
|
||||
("pretalx_musicrate", "0004_musicratesettings_advance_threshold"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Juror",
|
||||
fields=[
|
||||
("created", models.DateTimeField(auto_now_add=True, null=True)),
|
||||
("updated", models.DateTimeField(auto_now=True, null=True)),
|
||||
(
|
||||
"token",
|
||||
models.CharField(
|
||||
default=pretalx_musicrate.models.generate_token,
|
||||
max_length=43,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"event",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="jurors",
|
||||
to="event.event",
|
||||
),
|
||||
),
|
||||
(
|
||||
"last_submission",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="jurors",
|
||||
to="submission.submission",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=(
|
||||
pretalx.common.mixins.models.LogMixin,
|
||||
pretalx.common.mixins.models.FileCleanupMixin,
|
||||
models.Model,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="musicratesettings",
|
||||
name="last_submission",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="pretalx_musicrate_settings",
|
||||
to="submission.submission",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Rating",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("rating", models.CharField(max_length=2)),
|
||||
(
|
||||
"juror",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="ratings",
|
||||
to="pretalx_musicrate.juror",
|
||||
),
|
||||
),
|
||||
(
|
||||
"submission",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="ratings",
|
||||
to="submission.submission",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="rating",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("submission", "juror"), name="unique_rating"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -3,6 +3,7 @@ from secrets import token_urlsafe
|
|||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pretalx.common.mixins.models import PretalxModel
|
||||
|
||||
|
||||
def generate_token():
|
||||
|
@ -50,3 +51,49 @@ class MusicrateSettings(models.Model):
|
|||
),
|
||||
verbose_name=_("Advance Threshold"),
|
||||
)
|
||||
last_submission = models.ForeignKey(
|
||||
"submission.Submission",
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="pretalx_musicrate_settings",
|
||||
null=True,
|
||||
)
|
||||
|
||||
|
||||
class Juror(PretalxModel):
|
||||
event = models.ForeignKey(
|
||||
"event.Event", on_delete=models.CASCADE, related_name="jurors"
|
||||
)
|
||||
token = models.CharField(max_length=43, default=generate_token, primary_key=True)
|
||||
last_submission = models.ForeignKey(
|
||||
"submission.Submission",
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="jurors",
|
||||
null=True,
|
||||
)
|
||||
|
||||
|
||||
class Rating(models.Model):
|
||||
RATING_CHOICES = [
|
||||
("10", "sehr gut"),
|
||||
("9", "ziemlich gut"),
|
||||
("8", "gut"),
|
||||
("7", "eher gut"),
|
||||
("6", "ok"),
|
||||
("5", "naja"),
|
||||
("4", "eher schlecht"),
|
||||
("3", "schlecht"),
|
||||
("2", "ziemlich schlecht"),
|
||||
("1", "sehr schlecht"),
|
||||
]
|
||||
rating = models.CharField(max_length=2, choices=RATING_CHOICES, blank=True)
|
||||
submission = models.ForeignKey(
|
||||
"submission.Submission", on_delete=models.CASCADE, related_name="ratings"
|
||||
)
|
||||
juror = models.ForeignKey(Juror, on_delete=models.CASCADE, related_name="ratings")
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=("submission", "juror"), name="unique_rating"
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
const mayAdvance = (callback) =>
|
||||
fetch("may-advance?")
|
||||
.then(response => response.json())
|
||||
.then(mayAdvance => {
|
||||
if (mayAdvance === true) {
|
||||
callback();
|
||||
}
|
||||
.then(({mayAdvance, statusText}) => {
|
||||
callback({mayAdvance, statusText});
|
||||
});
|
||||
const timeout = setTimeout(() => {
|
||||
setInterval(() => {
|
||||
mayAdvance(() => {
|
||||
location = document.getElementById("next").href;
|
||||
mayAdvance(({mayAdvance, statusText}) => {
|
||||
document.getElementById("status").innerText = statusText;
|
||||
if (mayAdvance === true) {
|
||||
location = document.getElementById("next").href;
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
}, 3000);
|
||||
mayAdvance(() => clearTimeout(timeout));
|
||||
mayAdvance(({mayAdvance, statusText}) => {
|
||||
document.getElementById("status").innerText = statusText;
|
||||
if (mayAdvance === true) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 10 10.000002"
|
||||
height="10.000002mm"
|
||||
width="10mm"
|
||||
xml:space="preserve"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata8"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs6"><clipPath
|
||||
id="clipPath18"
|
||||
clipPathUnits="userSpaceOnUse"><path
|
||||
id="path20"
|
||||
d="M 0,1114.8 V 0 h 1741.01 v 1114.8 z" /></clipPath></defs><path
|
||||
style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.193771"
|
||||
d="M 5.0054876,0 C 2.2542107,0 0,2.2541389 0,5.0054876 0,7.7567643 2.2542107,10.000002 5.0054876,10.000002 7.7567876,10.000002 10,7.7567643 10,5.0054876 10,2.2541389 7.7567876,0 5.0054876,0 Z M 3.4712178,1.5024796 H 4.6667669 L 8.1693968,5.0054876 4.6773643,8.4971423 H 3.4602426 L 6.9628727,5.0054876 Z"
|
||||
id="path22" /></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 10 10.000002"
|
||||
height="10.000002mm"
|
||||
width="10mm"
|
||||
xml:space="preserve"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata8"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs6"><clipPath
|
||||
id="clipPath18"
|
||||
clipPathUnits="userSpaceOnUse"><path
|
||||
id="path20"
|
||||
d="M 0,1114.8 V 0 h 1741.01 v 1114.8 z" /></clipPath></defs><path
|
||||
style="color:#000000;fill:none;stroke:#231f20;stroke-opacity:1;stroke-width:0.1;stroke-dasharray:none"
|
||||
d="M 3.5917969,1.552734 H 4.6464844 L 8.0996094,5.0058594 4.65625,8.4472656 H 3.5820313 L 7.0332031,5.0058594 Z"
|
||||
id="path1699" /><path
|
||||
style="color:#000000;fill:none;stroke:#231f20;stroke-opacity:1;stroke-width:0.1;stroke-dasharray:none"
|
||||
d="m 5.0058594,0.05078125 c 2.7239873,0 4.9433594,2.23090925 4.9433594,4.95507815 0,2.7240961 -2.2192401,4.9433594 -4.9433594,4.9433594 -2.7240961,0 -4.95507815,-2.2193965 -4.95507815,-4.9433594 0,-2.7240356 2.23111415,-4.95507815 4.95507815,-4.95507815 z"
|
||||
id="path1695" /></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,55 @@
|
|||
.rating-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.rating-container > .legend {
|
||||
display: flex;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.rating-container > .legend > span {
|
||||
flex: 1 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rating-container > .legend > :first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.rating-container > .legend > :last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rating-container > .rating {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rating-container > .rating > input[type=radio] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rating-container > .rating > input[type=radio]:checked ~ .rating-point {
|
||||
background-image: url(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%;
|
||||
height: 3em;
|
||||
margin: 0 0.1em;
|
||||
max-height: var(--max-size);
|
||||
max-width: var(--max-size);
|
||||
width: 3em;
|
||||
}
|
||||
|
||||
.rating-container > .rating > .rating-point:first-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.rating-container > .rating > .rating-point:last-of-type {
|
||||
margin-left: 0;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
const options = document.querySelectorAll('input[name=rating]');
|
||||
const submitRating = document.getElementById('submit_rating');
|
||||
|
||||
let empty = true;
|
||||
options.forEach(option => {
|
||||
empty &&= !option.checked;
|
||||
});
|
||||
|
||||
if (empty) {
|
||||
submitRating.innerText = 'Enthalten';
|
||||
|
||||
options.forEach(option => {
|
||||
option.addEventListener('change', () => {
|
||||
submitRating.innerText = 'Speichern';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('reset_rating').addEventListener('click', () => {
|
||||
options.forEach(option => {
|
||||
if (option.attributes.getNamedItem('checked') !== null) {
|
||||
option.attributes.removeNamedItem('checked');
|
||||
}
|
||||
});
|
||||
|
||||
submitRating.innerText = 'Enthalten';
|
||||
});
|
|
@ -4,8 +4,12 @@
|
|||
{% block content %}
|
||||
<h1>{% translate "Join collective rating" %}</h1>
|
||||
<hr>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-success btn-lg btn-block" type="submit"{% if not token_valid %} disabled{% endif %}>{% translate "Join" %}</button>
|
||||
</form>
|
||||
{% if juror %}
|
||||
<p>{% translate "You have already joined the collective rating, but there are no submissions yet." %}</p>
|
||||
{% else %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-success btn-lg btn-block" type="submit"{% if not token_valid %} disabled{% endif %}>{% translate "Join" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
{% extends "pretalx_musicrate/submission_base.html" %}
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block submission_content %}
|
||||
{% block submission_header %}
|
||||
{% if next %}
|
||||
{% compress js %}
|
||||
<script src="{% static "pretalx_musicrate/may-advance.js" %}"></script>
|
||||
<script defer src="{% static "pretalx_musicrate/may-advance.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block submission_content %}
|
||||
<a class="btn btn-primary btn-lg btn-block mb-3" href="{% url "plugins:pretalx_musicrate:qrcode" event=request.event.slug %}">{% translate "Show Join QR Code" %}</a>
|
||||
<p class="text-center" id="status"></p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "cfp/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load qrcode %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -6,4 +7,8 @@
|
|||
{% qrcode contents %}
|
||||
{{ contents | urlize }}
|
||||
</div>
|
||||
{% if last_submission %}
|
||||
<hr>
|
||||
<a class="btn btn-success btn-lg btn-block" href="{% url "plugins:pretalx_musicrate:present" event=request.event.slug code=last_submission.code %}">{% translate "Start collective rating" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
{% extends "pretalx_musicrate/submission_base.html" %}
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block submission_header %}
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" href="{% static "pretalx_musicrate/rating.css" %}">
|
||||
{% endcompress %}
|
||||
{% compress js %}
|
||||
<script defer src="{% static "pretalx_musicrate/rating.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
{% block submission_content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="rating-container">
|
||||
<div class="rating" style="--num-choices: {{ form.rating | length }}">
|
||||
{% for radio in form.rating %}
|
||||
{{ radio.tag }}
|
||||
<label class="rating-point" for="{{ radio.id_for_label }}"></label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="legend">
|
||||
<span>sehr schlecht</span>
|
||||
<span>sehr neutral</span>
|
||||
<span>sehr gut</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="submit-group">
|
||||
<span>
|
||||
<button class="btn btn-danger btn-lg" id="reset_rating" type="reset">Zurücksetzen</button>
|
||||
</span>
|
||||
<span>
|
||||
<button class="btn btn-success btn-lg" id="submit_rating" type="submit">Speichern</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -6,6 +6,7 @@ from .views import (
|
|||
MusicrateSettingsView,
|
||||
PresenterView,
|
||||
QRCodeView,
|
||||
RatingView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
|
@ -26,6 +27,7 @@ urlpatterns = [
|
|||
name="may_advance",
|
||||
),
|
||||
path("<slug:token>/", JoinView.as_view(), name="join"),
|
||||
path("<slug:token>/<code>/", RatingView.as_view(), name="rating"),
|
||||
]
|
||||
),
|
||||
),
|
||||
|
|
|
@ -2,22 +2,28 @@ from hmac import compare_digest
|
|||
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
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 .forms import MusicrateSettingsForm
|
||||
from .forms import MusicrateSettingsForm, RatingForm
|
||||
from .models import Juror, Rating
|
||||
|
||||
|
||||
class JoinView(TemplateView):
|
||||
template_name = "pretalx_musicrate/join.html"
|
||||
|
||||
def validate_token(self, token):
|
||||
try:
|
||||
self.juror = Juror.objects.get(token=token)
|
||||
return True
|
||||
except Juror.DoesNotExist:
|
||||
self.juror = None
|
||||
if compare_digest(
|
||||
token.encode("utf-8"),
|
||||
self.request.event.pretalx_musicrate_settings.join_token.encode("utf-8"),
|
||||
|
@ -28,17 +34,55 @@ class JoinView(TemplateView):
|
|||
|
||||
def get_context_data(self, token_valid=False, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["juror"] = self.juror
|
||||
context["token_valid"] = token_valid
|
||||
return context
|
||||
|
||||
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()
|
||||
)
|
||||
if submission is not None:
|
||||
return redirect(
|
||||
"plugins:pretalx_musicrate:rating",
|
||||
event=self.request.event.slug,
|
||||
token=self.juror.token,
|
||||
code=submission.code,
|
||||
)
|
||||
return super().get(request, *args, token_valid=token_valid, **kwargs)
|
||||
|
||||
def post(self, request, *args, token, **kwargs):
|
||||
token_valid = self.validate_token(token)
|
||||
if token_valid:
|
||||
return redirect(request.path)
|
||||
if self.juror is None:
|
||||
self.juror = Juror.objects.create(
|
||||
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()
|
||||
)
|
||||
if submission is not None:
|
||||
return redirect(
|
||||
"plugins:pretalx_musicrate:rating",
|
||||
event=self.request.event.slug,
|
||||
token=self.juror.token,
|
||||
code=submission.code,
|
||||
)
|
||||
return self.render_to_response(
|
||||
self.get_context_data(token_valid=token_valid, **kwargs)
|
||||
)
|
||||
|
@ -80,6 +124,14 @@ 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()
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
|
@ -174,6 +226,62 @@ class SubmissionMixin(SingleObjectMixin):
|
|||
)
|
||||
|
||||
|
||||
class RatingView(FormView, SubmissionMixin):
|
||||
template_name = "pretalx_musicrate/rating.html"
|
||||
form_class = RatingForm
|
||||
|
||||
@cached_property
|
||||
def juror(self):
|
||||
return get_object_or_404(
|
||||
Juror, token=self.request.resolver_match.kwargs["token"]
|
||||
)
|
||||
|
||||
@context
|
||||
@cached_property
|
||||
def can_continue(self):
|
||||
return self.submission.ratings.filter(juror=self.juror).exists()
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
if form_class is None:
|
||||
form_class = self.get_form_class()
|
||||
try:
|
||||
instance = Rating.objects.get(submission=self.submission, juror=self.juror)
|
||||
except Rating.DoesNotExist:
|
||||
instance = None
|
||||
return form_class(instance=instance, **self.get_form_kwargs())
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.path
|
||||
|
||||
def form_valid(self, form):
|
||||
obj = form.save(commit=False)
|
||||
obj.submission = self.submission
|
||||
obj.juror = self.juror
|
||||
obj.save()
|
||||
try:
|
||||
self.juror.last_submission = self.submission
|
||||
self.juror.save()
|
||||
except Exception:
|
||||
pass
|
||||
messages.success(self.request, _("Saved!"))
|
||||
return super().form_valid(form)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if (
|
||||
(settings := self.request.event.pretalx_musicrate_settings).last_submission
|
||||
is not None
|
||||
and self.object.created > settings.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,
|
||||
)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class PresenterView(EventPermissionRequired, SubmissionMixin, TemplateView):
|
||||
permission_required = "orga.view_submissions"
|
||||
template_name = "pretalx_musicrate/present.html"
|
||||
|
@ -185,11 +293,37 @@ 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
|
||||
):
|
||||
try:
|
||||
settings.last_submission = self.object
|
||||
settings.save()
|
||||
except Exception:
|
||||
pass
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class MayAdvanceView(EventPermissionRequired, View):
|
||||
class MayAdvanceView(EventPermissionRequired, SubmissionMixin, View):
|
||||
permission_required = "orga.view_submissions"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return JsonResponse(True, safe=False)
|
||||
num_ratings = self.submission.ratings.count()
|
||||
num_jurors = self.request.event.jurors.count()
|
||||
return JsonResponse(
|
||||
{
|
||||
"mayAdvance": num_ratings
|
||||
>= int(
|
||||
num_jurors
|
||||
* self.request.event.pretalx_musicrate_settings.advance_threshold
|
||||
),
|
||||
"statusText": ngettext(
|
||||
"%(num_ratings)d of %(num_jurors)d has rated this submission",
|
||||
"%(num_ratings)d of %(num_jurors)d have rated this submission",
|
||||
num_ratings,
|
||||
)
|
||||
% {"num_jurors": num_jurors, "num_ratings": num_ratings},
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue