deadlines
This commit is contained in:
+4
-1
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Track, Artist, Album, Background, Vote
|
from .models import Track, Artist, Album, Background, Vote, Profile
|
||||||
|
|
||||||
class TrackAdmin(admin.ModelAdmin):
|
class TrackAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
@@ -11,9 +11,12 @@ class BackgroundAdmin(admin.ModelAdmin):
|
|||||||
pass
|
pass
|
||||||
class VoteAdmin(admin.ModelAdmin):
|
class VoteAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
class ProfileAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
admin.site.register(Track, TrackAdmin)
|
admin.site.register(Track, TrackAdmin)
|
||||||
admin.site.register(Artist, ArtistAdmin)
|
admin.site.register(Artist, ArtistAdmin)
|
||||||
admin.site.register(Album, AlbumAdmin)
|
admin.site.register(Album, AlbumAdmin)
|
||||||
admin.site.register(Background, BackgroundAdmin)
|
admin.site.register(Background, BackgroundAdmin)
|
||||||
admin.site.register(Vote, VoteAdmin)
|
admin.site.register(Vote, VoteAdmin)
|
||||||
|
admin.site.register(Profile, ProfileAdmin)
|
||||||
|
|||||||
@@ -4,8 +4,12 @@ from .track import Track
|
|||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, UTC
|
||||||
from ..spotify import spt
|
from ..spotify import spt
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
NOMINATIONS_PER_DAY = 10
|
NOMINATIONS_PER_DAY = 10
|
||||||
MAX_QUOTA = 22
|
MAX_QUOTA = 22
|
||||||
@@ -43,6 +47,39 @@ class Profile(models.Model):
|
|||||||
print(f"Er zijn {elapsed.seconds}s voorbij, je nieuwe quota is {self.quota}")
|
print(f"Er zijn {elapsed.seconds}s voorbij, je nieuwe quota is {self.quota}")
|
||||||
self.quota_last_updated = timezone.now()
|
self.quota_last_updated = timezone.now()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_nominate(self):
|
||||||
|
"""Is this user allowed to nominate at this point in time?"""
|
||||||
|
end_date = os.getenv('DATE_NOM_END')
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return (True,)
|
||||||
|
if end_date:
|
||||||
|
end_date = datetime.fromisoformat(end_date)
|
||||||
|
if end_date <= datetime.now(tz=UTC):
|
||||||
|
return (False, "over")
|
||||||
|
if self.quota < 1:
|
||||||
|
return (False, "quota")
|
||||||
|
return (True,)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_vote(self):
|
||||||
|
"""Is this user allowed to vote at this point in time?"""
|
||||||
|
end_date = os.getenv('DATE_VOTE_END')
|
||||||
|
if self.user.is_superuser or not end_date:
|
||||||
|
return True
|
||||||
|
end_date = datetime.fromisoformat(end_date)
|
||||||
|
if end_date <= datetime.now(tz=UTC):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vote_end(self):
|
||||||
|
return os.getenv('DATE_VOTE_END')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nominate_end(self):
|
||||||
|
return os.getenv('DATE_NOM_END')
|
||||||
|
|
||||||
def init_spt(self, code):
|
def init_spt(self, code):
|
||||||
"""Initial setting of spotify tokens from user accept code"""
|
"""Initial setting of spotify tokens from user accept code"""
|
||||||
data = spt.get_token_json(
|
data = spt.get_token_json(
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
let timer = null;
|
||||||
|
function startCountdown(dateString) {
|
||||||
|
if (timer) clearInterval(timer);
|
||||||
|
let cd = document.getElementById("countdown")
|
||||||
|
if (cd) {
|
||||||
|
cd.classList.remove("hide");
|
||||||
|
if (dateString == "None") cd.classList.add("hide");
|
||||||
|
}
|
||||||
|
const end = new Date(dateString).getTime();
|
||||||
|
const dayEl = document.getElementById('days');
|
||||||
|
const hoursEl = document.getElementById('hours');
|
||||||
|
const minutesEl = document.getElementById('minutes');
|
||||||
|
const secondsEl = document.getElementById('seconds');
|
||||||
|
const seconds = 1000;
|
||||||
|
const minutes = seconds * 60;
|
||||||
|
const hours = minutes * 60;
|
||||||
|
const days = hours * 24;
|
||||||
|
timer = setInterval(updateTime, seconds);
|
||||||
|
function updateTime() {
|
||||||
|
if (dayEl) {
|
||||||
|
let now = new Date().getTime();
|
||||||
|
const difference = end - now;
|
||||||
|
|
||||||
|
if (difference < 0) {
|
||||||
|
if (timer) clearInterval(timer);
|
||||||
|
cd.classList.add("hide");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dayEl.innerText = Math.floor(difference / days);
|
||||||
|
hoursEl.innerText = Math.floor( (difference % days) / hours );
|
||||||
|
minutesEl.innerText = Math.floor( (difference % hours) / minutes );
|
||||||
|
secondsEl.innerText = Math.floor( (difference % minutes) / seconds );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateTime();
|
||||||
|
}
|
||||||
@@ -7,10 +7,17 @@ Nomineren
|
|||||||
{% block main-content %}
|
{% block main-content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>Vul een spotify link in om een nummer te nomineren.</p>
|
{% if user.profile.can_nominate.0 %}
|
||||||
<input type="text" id="spotify-input" name="spotify_link" placeholder="Spotify-link">
|
<p>Vul een spotify link in om een nummer te nomineren.</p>
|
||||||
<input type="submit" value="Insturen" {% if user.profile.quota < 1%}disabled{%endif%}>
|
<input type="text" id="spotify-input" name="spotify_link" placeholder="Spotify-link">
|
||||||
|
<input type="submit" value="Insturen" {% if user.profile.quota < 1%}disabled{%endif%}>
|
||||||
|
{% elif user.profile.can_nominate.1 == 'quota' %}
|
||||||
|
<p>Je quotum is op. Je zal helaas eventjes moeten wachten met nomineren.</p>
|
||||||
|
{% elif user.profile.can_nominate.1 == 'over' %}
|
||||||
|
<p>Het nomineren is voorbij voor dit jaar! Bedankt voor de inzendingen.</p>
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block status-bar%}
|
{% block status-bar%}
|
||||||
@@ -23,4 +30,16 @@ Nomineren
|
|||||||
</div>
|
</div>
|
||||||
<p class="status-bar-field">{{user.profile.quota_display}} nominaties over.</p>
|
<p class="status-bar-field">{{user.profile.quota_display}} nominaties over.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="status-bar" id="countdown">
|
||||||
|
<p class="status-bar-field"><span id="days"></span> dagen</p>
|
||||||
|
<p class="status-bar-field"><span id="hours"></span> uur</p>
|
||||||
|
<p class="status-bar-field"><span id="minutes"></span> minuten</p>
|
||||||
|
<p class="status-bar-field"><span id="seconds"></span> seconden</p>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.body.addEventListener('htmx:load', function(evt) {
|
||||||
|
//startCountdown("2024-06-25T23:59:59+02:00");
|
||||||
|
startCountdown("{{user.profile.nominate_end}}");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ Overzicht
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block multi-row %}
|
{% block multi-row %}
|
||||||
<menu role="tablist" hx-boost="true" class="multirows">
|
{% comment %} <menu role="tablist" hx-boost="true" class="multirows">
|
||||||
<li role="tab" aria-selected="true">
|
<li role="tab" aria-selected="true">
|
||||||
<a href="{% url 'overview' %}">Tracks</a>
|
<a href="{% url 'overview' %}">Tracks</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="tab">
|
<li role="tab">
|
||||||
<a href="{% url 'playlist-list' %}">Playlists</a>
|
<a href="{% url 'playlist-list' %}">Playlists</a>
|
||||||
</li>
|
</li>
|
||||||
</menu>
|
</menu> {% endcomment %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main-content %}
|
{% block main-content %}
|
||||||
|
|||||||
@@ -5,59 +5,86 @@ Stemmen
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main-content %}
|
{% block main-content %}
|
||||||
{% if track %}
|
{% if user.profile.can_vote %}
|
||||||
<div id="vote-box" hx-target="body">
|
{% if track %}
|
||||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
<div id="vote-box" hx-target="body">
|
||||||
{% include 'MediaPlayer.html' with track=track %}
|
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||||
|
{% include 'MediaPlayer.html' with track=track %}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="vote-stars"
|
id="vote-stars"
|
||||||
hx-vals='{"spotify_id": "{{ track.id }}"}'
|
hx-vals='{"spotify_id": "{{ track.id }}"}'
|
||||||
style="margin-top:10px;"
|
style="margin-top:10px;"
|
||||||
>
|
>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Hoeveel sterren geef jij dit?</legend>
|
<legend>Hoeveel sterren geef jij dit?</legend>
|
||||||
<div class="star-wrapper">
|
<div class="star-wrapper">
|
||||||
<a
|
<a
|
||||||
hx-vals='{"vote": "1"}'
|
hx-vals='{"vote": "1"}'
|
||||||
hx-post="{% url 'vote' %}"
|
hx-post="{% url 'vote' %}"
|
||||||
></a>
|
></a>
|
||||||
<a
|
<a
|
||||||
hx-vals='{"vote": "2"}'
|
hx-vals='{"vote": "2"}'
|
||||||
hx-post="{% url 'vote' %}"
|
hx-post="{% url 'vote' %}"
|
||||||
></a>
|
></a>
|
||||||
<a
|
<a
|
||||||
hx-vals='{"vote": "3"}'
|
hx-vals='{"vote": "3"}'
|
||||||
hx-post="{% url 'vote' %}"
|
hx-post="{% url 'vote' %}"
|
||||||
></a>
|
></a>
|
||||||
<a
|
<a
|
||||||
hx-vals='{"vote": "4"}'
|
hx-vals='{"vote": "4"}'
|
||||||
hx-post="{% url 'vote' %}"
|
hx-post="{% url 'vote' %}"
|
||||||
></a>
|
></a>
|
||||||
<a
|
<a
|
||||||
hx-vals='{"vote": "5"}'
|
hx-vals='{"vote": "5"}'
|
||||||
hx-post="{% url 'vote' %}"
|
hx-post="{% url 'vote' %}"
|
||||||
></a>
|
></a>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{% if undo %}
|
||||||
|
<!--<form class="undo-wrapper" method="post" action="{% url 'undo' %}" hx-vals='{"spotify_id": "{{ undo.track.id }}"}' >-->
|
||||||
|
<div class="undo-wrapper" hx-vals='{"spotify_id": "{{ undo.track.id }}"}' >
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>Je hebt {{undo.track}} een score van {{undo.points}} sterren gegeven!</p>
|
||||||
|
<button class="undo" hx-post="{% url 'undo' %}" >Undo</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p hx-boost="true">
|
||||||
|
Je hebt niets meer om te stemmen. Lekker bezig!
|
||||||
|
<a href="{% url 'nominate' %}">Nomineer zelf iets</a>, als je nog quota hebt.
|
||||||
|
</p>
|
||||||
{% if undo %}
|
{% if undo %}
|
||||||
<!--<form class="undo-wrapper" method="post" action="{% url 'undo' %}" hx-vals='{"spotify_id": "{{ undo.track.id }}"}' >-->
|
<!--<form class="undo-wrapper" method="post" action="{% url 'undo' %}" hx-vals='{"spotify_id": "{{ undo.track.id }}"}' >-->
|
||||||
<div class="undo-wrapper" hx-vals='{"spotify_id": "{{ undo.track.id }}"}' >
|
<div class="undo-wrapper" hx-target="body" hx-vals='{"spotify_id": "{{ undo.track.id }}"}' >
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>Je hebt {{undo.track}} een score van {{undo.points}} sterren gegeven!</p>
|
<p>Je hebt {{undo.track}} een score van {{undo.points}} sterren gegeven!</p>
|
||||||
<button class="undo" hx-post="{% url 'undo' %}" >Undo</button>
|
<button class="undo" hx-post="{% url 'undo' %}" >Undo</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p hx-boost="true">
|
<p> Stemmen is voorbij. Joe.</p>
|
||||||
Je hebt niets meer om te stemmen. Lekker bezig!
|
|
||||||
<a href="{% url 'nominate' %}">Nomineer zelf iets</a>, als je nog quota hebt.
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block clippy %}
|
{% block clippy %}
|
||||||
{% include "Clippy.html" with body=track.banter img="img/draakje/draakje-headphones3.gif" %}
|
{% include "Clippy.html" with body=track.banter img="img/draakje/draakje-headphones3.gif" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block status-bar%}
|
||||||
|
<div class="status-bar" id="countdown">
|
||||||
|
<p class="status-bar-field"><span id="days"></span> dagen</p>
|
||||||
|
<p class="status-bar-field"><span id="hours"></span> uur</p>
|
||||||
|
<p class="status-bar-field"><span id="minutes"></span> minuten</p>
|
||||||
|
<p class="status-bar-field"><span id="seconds"></span> seconden</p>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.body.addEventListener('htmx:load', function(evt) {
|
||||||
|
//startCountdown("2024-08-25T23:59:59+02:00");
|
||||||
|
startCountdown("{{user.profile.vote_end}}");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<script src="{% static 'js/htmx.min.js' %}" defer></script>
|
<script src="{% static 'js/htmx.min.js' %}" defer></script>
|
||||||
<script src="{% static 'js/interact.js' %}" defer></script>
|
<script src="{% static 'js/interact.js' %}" defer></script>
|
||||||
|
<script src="{% static 'js/countdown.js' %}" defer></script>
|
||||||
<link href="{% static '98.css/style.css' %}" rel="stylesheet" type="text/css" />
|
<link href="{% static '98.css/style.css' %}" rel="stylesheet" type="text/css" />
|
||||||
<link href="{% static 'style.css'%}" rel="stylesheet" type="text/css" />
|
<link href="{% static 'style.css'%}" rel="stylesheet" type="text/css" />
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
+10
-2
@@ -6,12 +6,17 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.db.models import Avg
|
from django.db.models import Avg
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
from datetime import UTC, datetime
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from dotenv import load_dotenv
|
||||||
from .models import Track, Artist, Album, Vote, Background, Profile, Playlist
|
from .models import Track, Artist, Album, Vote, Background, Profile, Playlist
|
||||||
from .utils import from_json, get_unvoted
|
from .utils import from_json, get_unvoted
|
||||||
from .spotify import spt
|
from .spotify import spt
|
||||||
from .tasks import get_banter, get_and_dither_image
|
from .tasks import get_banter, get_and_dither_image
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
class AuthView(View):
|
class AuthView(View):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
@@ -26,7 +31,10 @@ class VoteView(LoginRequiredMixin, View):
|
|||||||
get_and_dither_image(track.album.image_url, track.album, "image")
|
get_and_dither_image(track.album.image_url, track.album, "image")
|
||||||
return TemplateResponse(request, "Vote.html", {"track": track, "undo": undo})
|
return TemplateResponse(request, "Vote.html", {"track": track, "undo": undo})
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
(profile,_) = Profile.objects.get_or_create(user=request.user)
|
||||||
try:
|
try:
|
||||||
|
if not profile.can_vote:
|
||||||
|
raise Exception(f"{request.user} tried to vote after closing for {track}")
|
||||||
spotify_id = self.request.POST['spotify_id']
|
spotify_id = self.request.POST['spotify_id']
|
||||||
score = int(self.request.POST['vote'])
|
score = int(self.request.POST['vote'])
|
||||||
score = min(max(1, score), 5)
|
score = min(max(1, score), 5)
|
||||||
@@ -49,10 +57,10 @@ class NominateView(LoginRequiredMixin, View):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
(profile,_) = Profile.objects.get_or_create(user=request.user)
|
(profile,_) = Profile.objects.get_or_create(user=request.user)
|
||||||
profile.update_quota()
|
profile.update_quota()
|
||||||
return TemplateResponse(request, "Nominate.html", {})
|
return TemplateResponse(request, "Nominate.html", {'end_time': os.getenv('DATE_NOM_END')})
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
(profile,_) = Profile.objects.get_or_create(user=request.user)
|
(profile,_) = Profile.objects.get_or_create(user=request.user)
|
||||||
if profile.quota < 1:
|
if not profile.can_nominate[0]:
|
||||||
return TemplateResponse(request, "Nominate.html", {"message_type": "error", "message": "Nee nee nee, dat gaan we dus even niet doen."})
|
return TemplateResponse(request, "Nominate.html", {"message_type": "error", "message": "Nee nee nee, dat gaan we dus even niet doen."})
|
||||||
try:
|
try:
|
||||||
spotify_link = self.request.POST['spotify_link']
|
spotify_link = self.request.POST['spotify_link']
|
||||||
|
|||||||
Reference in New Issue
Block a user