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 django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
|
||||||
from i18nfield.forms import I18nModelForm
|
from i18nfield.forms import I18nModelForm
|
||||||
|
|
||||||
from .models import MusicrateSettings
|
from .models import MusicrateSettings, Rating
|
||||||
|
|
||||||
|
|
||||||
class MusicrateSettingsForm(I18nModelForm):
|
class MusicrateSettingsForm(I18nModelForm):
|
||||||
|
@ -42,3 +42,13 @@ class MusicrateSettingsForm(I18nModelForm):
|
||||||
"genre_question": SafeModelChoiceField,
|
"genre_question": SafeModelChoiceField,
|
||||||
"origin_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.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from pretalx.common.mixins.models import PretalxModel
|
||||||
|
|
||||||
|
|
||||||
def generate_token():
|
def generate_token():
|
||||||
|
@ -50,3 +51,49 @@ class MusicrateSettings(models.Model):
|
||||||
),
|
),
|
||||||
verbose_name=_("Advance Threshold"),
|
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) =>
|
const mayAdvance = (callback) =>
|
||||||
fetch("may-advance?")
|
fetch("may-advance?")
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(mayAdvance => {
|
.then(({mayAdvance, statusText}) => {
|
||||||
if (mayAdvance === true) {
|
callback({mayAdvance, statusText});
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
mayAdvance(() => {
|
mayAdvance(({mayAdvance, statusText}) => {
|
||||||
location = document.getElementById("next").href;
|
document.getElementById("status").innerText = statusText;
|
||||||
|
if (mayAdvance === true) {
|
||||||
|
location = document.getElementById("next").href;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}, 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 %}
|
{% block content %}
|
||||||
<h1>{% translate "Join collective rating" %}</h1>
|
<h1>{% translate "Join collective rating" %}</h1>
|
||||||
<hr>
|
<hr>
|
||||||
<form method="post">
|
{% if juror %}
|
||||||
{% csrf_token %}
|
<p>{% translate "You have already joined the collective rating, but there are no submissions yet." %}</p>
|
||||||
<button class="btn btn-success btn-lg btn-block" type="submit"{% if not token_valid %} disabled{% endif %}>{% translate "Join" %}</button>
|
{% else %}
|
||||||
</form>
|
<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 %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
{% extends "pretalx_musicrate/submission_base.html" %}
|
{% extends "pretalx_musicrate/submission_base.html" %}
|
||||||
{% load compress %}
|
{% load compress %}
|
||||||
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block submission_content %}
|
{% block submission_header %}
|
||||||
{% if next %}
|
{% if next %}
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
<script src="{% static "pretalx_musicrate/may-advance.js" %}"></script>
|
<script defer src="{% static "pretalx_musicrate/may-advance.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% 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" %}
|
{% extends "cfp/event/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
{% load qrcode %}
|
{% load qrcode %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -6,4 +7,8 @@
|
||||||
{% qrcode contents %}
|
{% qrcode contents %}
|
||||||
{{ contents | urlize }}
|
{{ contents | urlize }}
|
||||||
</div>
|
</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 %}
|
{% 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,
|
MusicrateSettingsView,
|
||||||
PresenterView,
|
PresenterView,
|
||||||
QRCodeView,
|
QRCodeView,
|
||||||
|
RatingView,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -26,6 +27,7 @@ urlpatterns = [
|
||||||
name="may_advance",
|
name="may_advance",
|
||||||
),
|
),
|
||||||
path("<slug:token>/", JoinView.as_view(), name="join"),
|
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.contrib import messages
|
||||||
from django.http import JsonResponse
|
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.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
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 import FormView, TemplateView, View
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from django_context_decorator import context
|
from django_context_decorator import context
|
||||||
from pretalx.common.mixins.views import EventPermissionRequired
|
from pretalx.common.mixins.views import EventPermissionRequired
|
||||||
|
|
||||||
from .forms import MusicrateSettingsForm
|
from .forms import MusicrateSettingsForm, RatingForm
|
||||||
|
from .models import Juror, Rating
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
|
self.juror = Juror.objects.get(token=token)
|
||||||
|
return True
|
||||||
|
except Juror.DoesNotExist:
|
||||||
|
self.juror = None
|
||||||
if compare_digest(
|
if compare_digest(
|
||||||
token.encode("utf-8"),
|
token.encode("utf-8"),
|
||||||
self.request.event.pretalx_musicrate_settings.join_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):
|
def get_context_data(self, token_valid=False, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
context["juror"] = self.juror
|
||||||
context["token_valid"] = token_valid
|
context["token_valid"] = token_valid
|
||||||
return context
|
return context
|
||||||
|
|
||||||
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:
|
||||||
|
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)
|
return super().get(request, *args, token_valid=token_valid, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, token, **kwargs):
|
def post(self, request, *args, token, **kwargs):
|
||||||
token_valid = self.validate_token(token)
|
token_valid = self.validate_token(token)
|
||||||
if token_valid:
|
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(
|
return self.render_to_response(
|
||||||
self.get_context_data(token_valid=token_valid, **kwargs)
|
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
|
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):
|
class PresenterView(EventPermissionRequired, SubmissionMixin, TemplateView):
|
||||||
permission_required = "orga.view_submissions"
|
permission_required = "orga.view_submissions"
|
||||||
template_name = "pretalx_musicrate/present.html"
|
template_name = "pretalx_musicrate/present.html"
|
||||||
|
@ -185,11 +293,37 @@ 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).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)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class MayAdvanceView(EventPermissionRequired, View):
|
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):
|
||||||
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