Compare commits
No commits in common. "main" and "v0.2.0" have entirely different histories.
40 changed files with 233 additions and 905 deletions
|
@ -17,7 +17,7 @@ form a[role="button"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
i.owner, .gold {
|
i.owner {
|
||||||
color: var(--pico-color-amber-200);
|
color: var(--pico-color-amber-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ i.owner, .gold {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.group, a.running {
|
a.group {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,11 @@ article.message {
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero {
|
#hero {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
background: radial-gradient(
|
background: radial-gradient(
|
||||||
circle 50vh at calc(100vw - 4rem) 50%,
|
circle 50vh at calc(100vw - 4rem) 50%,
|
||||||
var(--pico-primary-background),
|
var(--pico-primary-background),
|
||||||
|
@ -85,41 +90,19 @@ article.message {
|
||||||
color-mix(in hsl, var(--pico-primary-background) 30%, var(--pico-background-color)) 60%,
|
color-mix(in hsl, var(--pico-primary-background) 30%, var(--pico-background-color)) 60%,
|
||||||
color-mix(in hsl, var(--pico-primary-background) 10%, var(--pico-background-color)) 80%,
|
color-mix(in hsl, var(--pico-primary-background) 10%, var(--pico-background-color)) 80%,
|
||||||
var(--pico-background-color));
|
var(--pico-background-color));
|
||||||
position: absolute;
|
display: grid;
|
||||||
top: 0;
|
grid-template-rows: 1fr min-content;
|
||||||
bottom: 0;
|
align-items: center;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 4rem;
|
padding: 4rem;
|
||||||
|
|
||||||
main {
|
.big-logo {
|
||||||
display: contents;
|
font-size: 8rem;
|
||||||
}
|
}
|
||||||
section {
|
|
||||||
max-width: 20rem;
|
h1 {
|
||||||
|
font-size: 4rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.full-page {
|
|
||||||
height: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 4rem;
|
|
||||||
|
|
||||||
&.r {
|
|
||||||
-ms-grid-column-align: end;
|
|
||||||
}
|
|
||||||
.big-logo {
|
|
||||||
font-size: 8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
|
@ -134,9 +117,6 @@ h6,
|
||||||
i.i {
|
i.i {
|
||||||
margin-right: .5em;
|
margin-right: .5em;
|
||||||
}
|
}
|
||||||
i.hl, .me {
|
|
||||||
color: var(--pico-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -150,75 +130,3 @@ td.c, th.c {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table select {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.podium {
|
|
||||||
> :first-child {
|
|
||||||
font-weight: 900;
|
|
||||||
font-size: 1.25em;
|
|
||||||
&::marker {
|
|
||||||
color: var(--pico-color-amber-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> :nth-child(2) {
|
|
||||||
font-weight: 800;
|
|
||||||
font-size: 1.1em;
|
|
||||||
&::marker {
|
|
||||||
color: var(--pico-color-grey-300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> :nth-child(3) {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.1em;
|
|
||||||
&::marker {
|
|
||||||
color: var(--pico-color-sand-300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.score {
|
|
||||||
margin-left: .5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.score {
|
|
||||||
font-weight: 900;
|
|
||||||
color: var(--pico-color-zinc-500);
|
|
||||||
}
|
|
||||||
.correct {
|
|
||||||
.score, i {
|
|
||||||
color: var(--pico-color-lime-200);}
|
|
||||||
}
|
|
||||||
.wrong {
|
|
||||||
.score, i {
|
|
||||||
color: var(--pico-color-red-500);}
|
|
||||||
}
|
|
||||||
|
|
||||||
table.results, table.musics {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.sc i {
|
|
||||||
margin-right: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width < 576px) {
|
|
||||||
[role="group"] {
|
|
||||||
display: grid;
|
|
||||||
& > :first-child {
|
|
||||||
border-radius: var(--pico-border-radius) var(--pico-border-radius) 0 0 !important;
|
|
||||||
}
|
|
||||||
& > :not(:first-child) {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
margin-top: calc(var(--pico-border-width) * -1);
|
|
||||||
}
|
|
||||||
& > :last-child {
|
|
||||||
border-radius: 0 0 var(--pico-border-radius) var(--pico-border-radius) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-name {
|
|
||||||
color: var(--pico-primary);
|
|
||||||
}
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -1,25 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="256" height="256" version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="Gradient" x1="0" x2="0" y1="0" y2="1">
|
|
||||||
<stop offset="0%" style="stop-color:#AA40BF;stop-opacity:1" />
|
|
||||||
<stop offset="100%" style="stop-color:#AA40BF;stop-opacity:1" />
|
|
||||||
</linearGradient>
|
|
||||||
<filter id="alpha-to-white">
|
|
||||||
<feColorMatrix in="SourceGraphic" type="matrix"
|
|
||||||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
<g id="child-svg"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" height="16px" width="16px"><path d="M18.7134 8.12811L18.4668 8.69379C18.2864 9.10792 17.7136 9.10792 17.5331 8.69379L17.2866 8.12811C16.8471 7.11947 16.0555 6.31641 15.0677 5.87708L14.308 5.53922C13.8973 5.35653 13.8973 4.75881 14.308 4.57612L15.0252 4.25714C16.0384 3.80651 16.8442 2.97373 17.2761 1.93083L17.5293 1.31953C17.7058 0.893489 18.2942 0.893489 18.4706 1.31953L18.7238 1.93083C19.1558 2.97373 19.9616 3.80651 20.9748 4.25714L21.6919 4.57612C22.1027 4.75881 22.1027 5.35653 21.6919 5.53922L20.9323 5.87708C19.9445 6.31641 19.1529 7.11947 18.7134 8.12811ZM7 3H12V6H9V17C9 19.2091 7.20914 21 5 21C2.79086 21 1 19.2091 1 17C1 14.7909 2.79086 13 5 13C5.72857 13 6.41165 13.1948 7 13.5351V3ZM18 13.5351V11H20V17C20 19.2091 18.2091 21 16 21C13.7909 21 12 19.2091 12 17C12 14.7909 13.7909 13 16 13C16.7286 13 17.4117 13.1948 18 13.5351Z" /></svg></g>
|
|
||||||
</defs>
|
|
||||||
<rect
|
|
||||||
width="256"
|
|
||||||
height="256"
|
|
||||||
fill="url(#Gradient)"
|
|
||||||
ry="128"
|
|
||||||
x="0"
|
|
||||||
y="0" />
|
|
||||||
<use xlink:href="#child-svg" filter="url(#alpha-to-white)"
|
|
||||||
transform="matrix(8,0,0,8,64,64)" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.7 KiB |
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "MyWebSite",
|
|
||||||
"short_name": "MySite",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/web-app-manifest-192x192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "maskable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/web-app-manifest-512x512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "maskable"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"theme_color": "#aa40bf",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
"display": "standalone"
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 47 KiB |
|
@ -1,24 +1,6 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load form %}
|
{% load form %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Créer mon compte</h1>
|
<h1>Créer un compte</h1>
|
||||||
{% for error in form.non_field_errors %}<article class="message error">{{ error }}</article>{% endfor %}
|
{% form form submit="Créer mon compte" %}
|
||||||
<form method="post" {% if action %}action="{% url action %}"{% endif %}>
|
{% endblock content %}
|
||||||
{% csrf_token %}
|
|
||||||
<fieldset>
|
|
||||||
{% for field in form %}
|
|
||||||
<label for="{{ field.id_for_label }}">
|
|
||||||
{{ field.label }}
|
|
||||||
{{ field }}
|
|
||||||
{% if field.errors %}
|
|
||||||
<small id="{{ field.errors.field_id }}_error" class="form-error">{{ field.errors|join:", " }}</small>
|
|
||||||
{% endif %}
|
|
||||||
</label>
|
|
||||||
{% endfor %}
|
|
||||||
</fieldset>
|
|
||||||
<button type="submit">Créer mon compte</button>
|
|
||||||
<fieldset>
|
|
||||||
<a href="{% url "login" %}" role="button" class="secondary">J'ai déjà un compte</a>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
Musik
|
Musik
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
</title>
|
</title>
|
||||||
{% include "favicon.html" %}
|
<link rel="icon" href="{% static "logo.svg" %}">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Exo:ital,wght@0,100..900;1,100..900&display=swap"
|
<link href="https://fonts.googleapis.com/css2?family=Exo:ital,wght@0,100..900;1,100..900&display=swap"
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
<meta name="theme-color" content="#aa40bf">
|
|
||||||
<link rel="icon" type="image/png" href="{% static "favicon/favicon-96x96.png" %}" sizes="96x96">
|
|
||||||
<link rel="icon" type="image/svg+xml" href="{% static "favicon/favicon.svg" %}">
|
|
||||||
<link rel="shortcut icon" href="{% static "favicon/favicon.ico" %}">
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static "favicon/apple-touch-icon.png" %}">
|
|
||||||
<meta name="apple-mobile-web-app-title" content="Musik">
|
|
||||||
<link rel="manifest" href="{% static "favicon/site.webmanifest" %}">
|
|
|
@ -1 +1 @@
|
||||||
Musik {{ VERSION }} – © <a href="https://code.edgarpierre.fr/edpibu/musik">Edgar P. Burkhart</a> – <a href="{% url "legal" %}">Mentions légales et confidentialité</a>
|
Musik {{ VERSION }} – © <a href="https://code.edgarpierre.fr/edpibu/musik">Edgar P. Burkhart</a> – <a href="{% url "legal" %}">Mentions légales</a>
|
||||||
|
|
|
@ -1,25 +1,11 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<div id="hero">
|
<div id="hero">
|
||||||
<main>
|
<main>
|
||||||
<div class="full-page">
|
<i class="ri-music-ai-fill big-logo"></i>
|
||||||
<div>
|
<h1>Musik</h1>
|
||||||
<i class="ri-music-ai-fill big-logo"></i>
|
<p>
|
||||||
<h1>Musik</h1>
|
<a href="{% url "home" %}" role="button"><i class="ri-play-fill"></i> Jouer</a>
|
||||||
<p>
|
</p>
|
||||||
<a href="{% url "home" %}" role="button"><i class="ri-play-fill"></i> Jouer</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="full-page r">
|
|
||||||
<section>
|
|
||||||
<h2>
|
|
||||||
<span class="brand-name"><i class="ri-music-ai-fill"></i> Musik</span> Le jeu où ta playlist devient ton arme secrète !
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
Invite ta bande, ajoute tes sons fétiches, et c’est parti ! Une playlist Youtube apparaît, mélangeant les coups de cœur de tout le monde. Le jeu ? Écoute, devine qui a choisi quoi, et découvre les secrets musicaux de tes potes. Entre pièges, révélations et fous rires, Musik c’est le jeu parfait pour tester vos oreilles… et vos amitiés. Prêt à jouer le DJ incognito ?
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
{% include "footer.html" %}
|
{% include "footer.html" %}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
Youtube est une marque de Google LLC.
|
Youtube est une marque de Google LLC.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Les données saisies dans Musik (groupes créés, listes de musiques) sont conservées jusqu'à demande de suppression. La suppression du compte entraîne la suppression de l'ensemble des données qui y sont liées.
|
La suppression des données stockée par le service Musik pour son utilisation peut être demandée par email à <a href="mailto:contact@edgarpierre.fr">Edgar P. Burkhart</a>.
|
||||||
La suppression du compte peut être demandée dans les paramètres du compte. La suppression des données est immédiate et définitive.
|
|
||||||
</p>
|
</p>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<form method="post" {% if action %}action="{% url action %}"{% endif %}>
|
<form method="post" {% if action %}action="{% url action %}"{% endif %}>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% if field.id_for_label %}
|
<fieldset>
|
||||||
<label for="{{ field.id_for_label }}">
|
{% if field.id_for_label %}
|
||||||
{{ field.label }}
|
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||||
{% else %}
|
{% else %}
|
||||||
<legend>{{ field.label }}</legend>
|
<legend>{{ field.label }}</legend>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -15,16 +15,16 @@
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<small id="{{ field.errors.field_id }}_error" class="form-error">{{ field.errors|join:", " }}</small>
|
<small id="{{ field.errors.field_id }}_error" class="form-error">{{ field.errors|join:", " }}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</label>
|
</fieldset>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<button type="submit">Me connecter</button>
|
<button type="submit">Me connecter</button>
|
||||||
<fieldset>
|
<p>
|
||||||
<a href="{% url "signup" %}" role="button" class="secondary">Créer mon compte</a>
|
<a href="{% url "signup" %}" role="button" class="secondary">Créer mon compte</a>
|
||||||
</fieldset>
|
</p>
|
||||||
<fieldset>
|
<p>
|
||||||
<a href="{% url "password_reset" %}"
|
<a href="{% url "password_reset" %}"
|
||||||
role="button"
|
role="button"
|
||||||
class="outline secondary">J'ai oublié mon mot de passe</a>
|
class="outline secondary">J'ai oublié mon mot de passe</a>
|
||||||
</fieldset>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -32,12 +32,10 @@ services:
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
image: rabbitmq
|
image: rabbitmq
|
||||||
container_name: musik_rabbitmq
|
container_name: musik_rabbitmq
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17
|
image: postgres:17
|
||||||
container_name: musik_postgres
|
container_name: musik_postgres
|
||||||
restart: unless-stopped
|
|
||||||
env_file: stack.env
|
env_file: stack.env
|
||||||
volumes:
|
volumes:
|
||||||
- /docker/musik/postgres:/var/lib/postgresql/data
|
- /docker/musik/postgres:/var/lib/postgresql/data
|
||||||
|
|
|
@ -39,23 +39,3 @@ class MusikGameForm(forms.ModelForm):
|
||||||
kwargs["initial"].setdefault("players", players)
|
kwargs["initial"].setdefault("players", players)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields["players"].queryset = players
|
self.fields["players"].queryset = players
|
||||||
|
|
||||||
|
|
||||||
class AnswerForm(forms.Form):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
game = kwargs.pop("game")
|
|
||||||
user = kwargs.pop("user")
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
for music in game.musicgameorder_set.all():
|
|
||||||
self.fields[f"answer-{music.order}"] = forms.ChoiceField(
|
|
||||||
choices=[("", "")]
|
|
||||||
+ list(game.players.all().values_list("id", "username")),
|
|
||||||
required=False,
|
|
||||||
label=music.order,
|
|
||||||
initial=ma.answer.id
|
|
||||||
if (ma := music.musicgameanswer_set.filter(player=user).first())
|
|
||||||
and ma.answer
|
|
||||||
else "",
|
|
||||||
disabled=game.over,
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
# Generated by Django 5.2.3 on 2025-06-15 10:36
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("game", "0017_youtubecredentials_title"),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="MusicGameAnswer",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"answer",
|
|
||||||
models.ForeignKey(
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name="+",
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"game",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="game.musicgameorder",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"player",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"ordering": ["game"],
|
|
||||||
"constraints": [
|
|
||||||
models.UniqueConstraint(
|
|
||||||
fields=("game", "player"), name="unique_answer"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,21 +0,0 @@
|
||||||
# Generated by Django 5.2.3 on 2025-06-15 10:44
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("game", "0018_musicgameanswer"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name="musikgame",
|
|
||||||
options={"ordering": ["-over", "-date"]},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="musikgame",
|
|
||||||
name="over",
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,55 +0,0 @@
|
||||||
# Generated by Django 5.2.3 on 2025-06-15 11:23
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("game", "0019_alter_musikgame_options_musikgame_over"),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name="musikgame",
|
|
||||||
options={"ordering": ["over", "-date"]},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="MusicGameResults",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("score", models.PositiveIntegerField(default=0)),
|
|
||||||
(
|
|
||||||
"game",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE, to="game.musikgame"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"player",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"ordering": ["-score"],
|
|
||||||
"constraints": [
|
|
||||||
models.UniqueConstraint(
|
|
||||||
fields=("game", "player"), name="unique_result"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 5.2.3 on 2025-06-15 11:33
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("game", "0020_alter_musikgame_options_musicgameresults"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="musicgameresults",
|
|
||||||
name="score",
|
|
||||||
field=models.IntegerField(default=0),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 5.2.3 on 2025-06-15 12:49
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("game", "0021_alter_musicgameresults_score"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="musicgameorder",
|
|
||||||
name="value",
|
|
||||||
field=models.PositiveIntegerField(default=0),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Generated by Django 5.2.3 on 2025-06-15 14:19
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("game", "0022_musicgameorder_value"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name="musicvideo",
|
|
||||||
options={"ordering": ["blacklisted", "-date_added"]},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,6 +1,5 @@
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F
|
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
from django.db.models.signals import post_delete, post_save
|
from django.db.models.signals import post_delete, post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
@ -66,12 +65,6 @@ class MusicVideo(models.Model):
|
||||||
fields=("yt_id", "owner", "group"), name="unique_music_in_group"
|
fields=("yt_id", "owner", "group"), name="unique_music_in_group"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
ordering = ["blacklisted", "-date_added"]
|
|
||||||
|
|
||||||
|
|
||||||
class GameManager(models.Manager):
|
|
||||||
def playing(self):
|
|
||||||
return self.filter(over=False)
|
|
||||||
|
|
||||||
|
|
||||||
class MusikGame(models.Model):
|
class MusikGame(models.Model):
|
||||||
|
@ -81,16 +74,10 @@ class MusikGame(models.Model):
|
||||||
players = models.ManyToManyField(User, verbose_name="Joueurs")
|
players = models.ManyToManyField(User, verbose_name="Joueurs")
|
||||||
playlist = models.CharField(blank=True, verbose_name="Playlist YouTube")
|
playlist = models.CharField(blank=True, verbose_name="Playlist YouTube")
|
||||||
playlist_loading = models.BooleanField(default=False)
|
playlist_loading = models.BooleanField(default=False)
|
||||||
over = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
objects = GameManager()
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("game_detail", kwargs={"pk": self.pk})
|
return reverse("game_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ["over", "-date"]
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=MusikGame)
|
@receiver(post_save, sender=MusikGame)
|
||||||
def generateYoutubePlaylist(sender, instance, created, **kwargs):
|
def generateYoutubePlaylist(sender, instance, created, **kwargs):
|
||||||
|
@ -99,9 +86,6 @@ def generateYoutubePlaylist(sender, instance, created, **kwargs):
|
||||||
|
|
||||||
if creds := instance.group.owner.youtubecredentials:
|
if creds := instance.group.owner.youtubecredentials:
|
||||||
tasks.generate_playlist.delay_on_commit(creds.credentials, instance.pk)
|
tasks.generate_playlist.delay_on_commit(creds.credentials, instance.pk)
|
||||||
else:
|
|
||||||
instance.playlist_loading = False
|
|
||||||
instance.save()
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=MusikGame)
|
@receiver(post_delete, sender=MusikGame)
|
||||||
|
@ -118,14 +102,6 @@ class MusicGameOrder(models.Model):
|
||||||
player = models.ForeignKey(User, on_delete=models.CASCADE)
|
player = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
music_video = models.ForeignKey(MusicVideo, on_delete=models.CASCADE)
|
music_video = models.ForeignKey(MusicVideo, on_delete=models.CASCADE)
|
||||||
order = models.PositiveIntegerField()
|
order = models.PositiveIntegerField()
|
||||||
value = models.PositiveIntegerField(default=0)
|
|
||||||
|
|
||||||
def update_value(self):
|
|
||||||
x = self.musicgameanswer_set.filter(game__player=F("answer")).count()
|
|
||||||
n = self.game.players.count()
|
|
||||||
n = max(3, n)
|
|
||||||
self.value = 1000 * 2 ** (-(x - 2) / (n - 2))
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
|
@ -135,44 +111,3 @@ class MusicGameOrder(models.Model):
|
||||||
models.UniqueConstraint(fields=("game", "order"), name="unique_order"),
|
models.UniqueConstraint(fields=("game", "order"), name="unique_order"),
|
||||||
]
|
]
|
||||||
ordering = ["order"]
|
ordering = ["order"]
|
||||||
|
|
||||||
|
|
||||||
class AnswerManager(models.Manager):
|
|
||||||
def score(self, game, player):
|
|
||||||
qs = self.filter(game__game=game, player=player)
|
|
||||||
return (
|
|
||||||
qs.exclude(game__player=player)
|
|
||||||
.filter(game__player=F("answer"))
|
|
||||||
.aggregate(score=models.Sum("game__value", default=0))
|
|
||||||
.get("score")
|
|
||||||
- 500
|
|
||||||
* qs.filter(game__player=player).exclude(game__player=F("answer")).count()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MusicGameAnswer(models.Model):
|
|
||||||
game = models.ForeignKey(MusicGameOrder, on_delete=models.CASCADE)
|
|
||||||
player = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
answer = models.ForeignKey(
|
|
||||||
User, on_delete=models.SET_NULL, null=True, related_name="+"
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = AnswerManager()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(fields=("game", "player"), name="unique_answer"),
|
|
||||||
]
|
|
||||||
ordering = ["game"]
|
|
||||||
|
|
||||||
|
|
||||||
class MusicGameResults(models.Model):
|
|
||||||
game = models.ForeignKey(MusikGame, on_delete=models.CASCADE)
|
|
||||||
player = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
score = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(fields=("game", "player"), name="unique_result")
|
|
||||||
]
|
|
||||||
ordering = ["-score"]
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ def generate_playlist(creds, game_pk):
|
||||||
"description": "Playlist générée par Musik",
|
"description": "Playlist générée par Musik",
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"privacyStatus": "unlisted",
|
"privacyStatus": "private",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,115 @@
|
||||||
{{ group.name }}
|
{{ group.name }}
|
||||||
</h1>
|
</h1>
|
||||||
{% include "game/include/group_buttons.html" %}
|
{% include "game/include/group_buttons.html" %}
|
||||||
{% include "game/include/group_games.html" %}
|
{% if group.musikgame_set.exists %}
|
||||||
|
<h2>
|
||||||
|
<i class="ri-play-circle-fill"></i> Parties
|
||||||
|
</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
{% if group.owner == user %}<th></th>{% endif %}
|
||||||
|
<th>Date</th>
|
||||||
|
<th>
|
||||||
|
<i class="ri-youtube-fill"></i> Playlists
|
||||||
|
</th>
|
||||||
|
<th>Joueurs</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for game in group.musikgame_set.all %}
|
||||||
|
<tr>
|
||||||
|
{% if group.owner == user %}
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="game" value="{{ game.pk }}">
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
<td>
|
||||||
|
<a href="{% url "game_detail" pk=game.pk %}">{{ game.date }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if game.playlist %}
|
||||||
|
<a href="{% yt_playlist game %}"
|
||||||
|
{% if game.playlist_loading %}aria-busy="true"{% endif %}><i class="ri-youtube-fill"></i> Playlist</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ game.players.all|join:", " }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% if group.owner == user %}
|
||||||
|
<button type="submit"
|
||||||
|
class="secondary"
|
||||||
|
formaction="{% url "group_remove_game" pk=group.pk %}">
|
||||||
|
<i class="ri-delete-bin-fill"></i> Supprimer les parties sélectionnées
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% include "game/include/group_members.html" %}
|
{% include "game/include/group_members.html" %}
|
||||||
{% include "game/include/group_musics.html" %}
|
<h2>
|
||||||
|
<i class="ri-music-2-fill"></i> Mes musiques <span class="music-count">{{ musics.count }}</span>
|
||||||
|
</h2>
|
||||||
|
<details>
|
||||||
|
<summary role="button">
|
||||||
|
<i class="ri-music-2-fill"></i> Liste des musiques
|
||||||
|
</summary>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Musique</th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>
|
||||||
|
<i class="ri-history-fill"></i>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for music in musics %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="musics" value="{{ music.pk }}">
|
||||||
|
</td>
|
||||||
|
<th>{{ music.title }}</th>
|
||||||
|
<td>
|
||||||
|
<a href="https://youtu.be/{{ music.yt_id }}">{{ music.yt_id }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" disabled {% if music.blacklisted %}checked{% endif %}>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">Aucune musique.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% if musics %}
|
||||||
|
<fieldset role="group">
|
||||||
|
<button type="submit"
|
||||||
|
formaction="{% url "group_remove_music" pk=group.pk %}"
|
||||||
|
class="secondary">
|
||||||
|
<i class="ri-delete-bin-fill"></i> Supprimer
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
class="secondary"
|
||||||
|
formaction="{% url "group_unblacklist_music" pk=group.pk %}">
|
||||||
|
<i class="ri-history-fill"></i> Retirer de la blacklist
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
<form method="post" action="{% url "group_add_music" pk=group.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<fieldset role="group">
|
||||||
|
<input type="string" name="yt_id" id="yt_id" placeholder="Musique" required>
|
||||||
|
<button type="submit">Ajouter</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
{% load game %}
|
|
||||||
{% if musikgame.over %}
|
|
||||||
<h2>
|
|
||||||
<i class="ri-list-ordered"></i> Résultats
|
|
||||||
</h2>
|
|
||||||
<details open>
|
|
||||||
<summary role="button">
|
|
||||||
<i class="ri-list-ordered-2"></i> Résultats
|
|
||||||
</summary>
|
|
||||||
<div class="overflow-auto">
|
|
||||||
<table class="striped results">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<i class="ri-list-ordered-2"></i>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<i class="ri-music-2-fill"></i> Musique
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<i class="ri-user-line"></i> Joueur
|
|
||||||
</th>
|
|
||||||
{% for player in musikgame.musicgameresults_set.all %}
|
|
||||||
<th {% if user == player.player %}class="me"{% endif %}>{{ player.player.username }}</th>
|
|
||||||
<th class="sc">
|
|
||||||
{% if forloop.first %}<i class="ri-medal-fill gold"></i>{% endif %}
|
|
||||||
<span class="score">{{ player.score }}</span>
|
|
||||||
</th>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for music in musikgame.musicgameorder_set.all %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ music.order }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://youtu.be/{{ music.music_video.yt_id }}">{{ music.music_video.title }}</a>
|
|
||||||
</td>
|
|
||||||
<td>{{ music.player }}</td>
|
|
||||||
{% for player in musikgame.musicgameresults_set.all %}
|
|
||||||
{% answer player music %}
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
{% endif %}
|
|
|
@ -1,62 +0,0 @@
|
||||||
{% load form youtube %}
|
|
||||||
{% if group.musikgame_set.exists %}
|
|
||||||
{% for game in group.musikgame_set.playing %}
|
|
||||||
<a class="running" href="{{ game.get_absolute_url }}">
|
|
||||||
<article><i class="ri-play-circle-fill i"></i>{{ game.date }}</article>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
<h2>
|
|
||||||
<i class="ri-play-circle-fill"></i> Parties
|
|
||||||
</h2>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
{% if group.owner == user %}<th></th>{% endif %}
|
|
||||||
<th>
|
|
||||||
<i class="ri-play-circle-fill"></i>
|
|
||||||
</th>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>
|
|
||||||
<i class="ri-youtube-fill"></i> Playlists
|
|
||||||
</th>
|
|
||||||
<th>Joueurs</th>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for game in group.musikgame_set.all %}
|
|
||||||
<tr>
|
|
||||||
{% if group.owner == user %}
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" name="game" value="{{ game.pk }}">
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
<td>
|
|
||||||
{% if game.over %}
|
|
||||||
<i class="ri-stop-circle-fill"></i>
|
|
||||||
{% else %}
|
|
||||||
<i class="ri-play-circle-fill hl"></i>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="{% url "game_detail" pk=game.pk %}">{{ game.date }}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if game.playlist %}
|
|
||||||
<a href="{% yt_playlist game %}"
|
|
||||||
{% if game.playlist_loading %}aria-busy="true"{% endif %}><i class="ri-youtube-fill"></i> Playlist</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ game.players.all|join:", " }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% if group.owner == user %}
|
|
||||||
<button type="submit"
|
|
||||||
class="secondary"
|
|
||||||
formaction="{% url "group_remove_game" pk=group.pk %}">
|
|
||||||
<i class="ri-delete-bin-fill"></i> Supprimer les parties sélectionnées
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
|
@ -12,7 +12,7 @@
|
||||||
<i class="ri-vip-crown-fill"></i>
|
<i class="ri-vip-crown-fill"></i>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<i class="ri-mv-fill"></i>
|
<i class="ri-mv-line"></i>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
<h2>
|
|
||||||
<i class="ri-music-2-fill"></i> Mes musiques <span class="music-count">{{ musics.count }}</span>
|
|
||||||
</h2>
|
|
||||||
<details>
|
|
||||||
<summary role="button">
|
|
||||||
<i class="ri-music-2-fill"></i> Liste des musiques
|
|
||||||
</summary>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="overflow-auto">
|
|
||||||
<table class="musics">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th>Musique</th>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>
|
|
||||||
<i class="ri-history-fill"></i>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for music in musics %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" name="musics" value="{{ music.pk }}">
|
|
||||||
</td>
|
|
||||||
<th>{{ music.title }}</th>
|
|
||||||
<td>
|
|
||||||
<a href="https://youtu.be/{{ music.yt_id }}">{{ music.yt_id }}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" disabled {% if music.blacklisted %}checked{% endif %}>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="4">Aucune musique.</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% if musics %}
|
|
||||||
<fieldset role="group">
|
|
||||||
<button type="submit"
|
|
||||||
formaction="{% url "group_remove_music" pk=group.pk %}"
|
|
||||||
class="secondary">
|
|
||||||
<i class="ri-delete-bin-fill"></i> Supprimer
|
|
||||||
</button>
|
|
||||||
<button type="submit"
|
|
||||||
class="secondary"
|
|
||||||
formaction="{% url "group_unblacklist_music" pk=group.pk %}">
|
|
||||||
<i class="ri-history-fill"></i> Retirer de la blacklist
|
|
||||||
</button>
|
|
||||||
</fieldset>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
</details>
|
|
||||||
<form method="post" action="{% url "group_add_music" pk=group.pk %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<fieldset>
|
|
||||||
<textarea name="yt_id" id="yt_id" placeholder="Musiques" required></textarea>
|
|
||||||
<button type="submit">Ajouter</button>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
|
@ -1,26 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<h2>Mes réponses</h2>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<i class="ri-mv-fill" alt="Musique"></i>
|
|
||||||
</th>
|
|
||||||
<th>Réponse</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for field in form %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ field.label }}</td>
|
|
||||||
<td>{{ field }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% if not musikgame.over %}<button type="submit">Sauvegarder mes réponses</button>{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
|
@ -2,52 +2,20 @@
|
||||||
{% load youtube %}
|
{% load youtube %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>
|
<h1>
|
||||||
{% if musikgame.over %}
|
<i class="ri-play-circle-fill"></i> {{ musikgame.date }}
|
||||||
<i class="ri-stop-circle-fill"></i>
|
|
||||||
{% else %}
|
|
||||||
<i class="ri-play-circle-fill hl"></i>
|
|
||||||
{% endif %}
|
|
||||||
{{ musikgame.date }}
|
|
||||||
</h1>
|
</h1>
|
||||||
<form method="post">
|
{% if musikgame.playlist or musikgame.playlist_loading %}
|
||||||
{% csrf_token %}
|
<p>
|
||||||
<fieldset role="group">
|
<a target="_blank"
|
||||||
{% if musikgame.playlist or musikgame.playlist_loading %}
|
href="{% yt_playlist musikgame %}"
|
||||||
<a target="_blank"
|
role="button"
|
||||||
href="{% yt_playlist musikgame %}"
|
{% if musikgame.playlist_loading %}aria-busy="true"{% endif %}><i class="ri-youtube-fill"></i> Playlist</a>
|
||||||
role="button"
|
</p>
|
||||||
{% if musikgame.playlist_loading %}aria-busy="true"{% endif %}><i class="ri-youtube-fill"></i> Playlist</a>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
{% if musikgame.over %}
|
|
||||||
<a href="{% url "game_answer" musikgame.pk %}"
|
|
||||||
role="button"
|
|
||||||
class="secondary"><i class="ri-play-list-2-fill"></i> Mes réponses</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url "game_answer" musikgame.pk %}" role="button"><i class="ri-play-list-2-fill"></i> Répondre</a>
|
|
||||||
{% endif %}
|
|
||||||
</fieldset>
|
|
||||||
{% if is_leader and not musikgame.over %}
|
|
||||||
<fieldset>
|
|
||||||
<button type="submit" formaction="{% url "game_end" musikgame.pk %}">
|
|
||||||
<i class="ri-stop-circle-fill"></i> Finir la partie
|
|
||||||
</button>
|
|
||||||
</fieldset>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
<h2>
|
<h2>
|
||||||
<i class="ri-group-2-fill"></i> Joueurs
|
<i class="ri-group-2-fill"></i> Joueurs
|
||||||
</h2>
|
</h2>
|
||||||
{% if musikgame.over %}
|
<p>{{ musikgame.players.all|join:", " }}</p>
|
||||||
<ol class="podium">
|
|
||||||
{% for player in musikgame.musicgameresults_set.all %}
|
|
||||||
<li {% if user == player.player %}class="me"{% endif %}>
|
|
||||||
{{ player.player.username }} <span class="score">{{ player.score }}</span>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
|
||||||
{% else %}
|
|
||||||
<p>{{ musikgame.players.all|join:", " }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<h2>
|
<h2>
|
||||||
<i class="ri-music-2-fill"></i> Musiques
|
<i class="ri-music-2-fill"></i> Musiques
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -63,5 +31,38 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
</details>
|
</details>
|
||||||
{% include "game/include/game_results.html" %}
|
<h2>
|
||||||
|
<i class="ri-list-ordered"></i> Résultats
|
||||||
|
</h2>
|
||||||
|
<details>
|
||||||
|
<summary role="button">
|
||||||
|
<i class="ri-list-ordered-2"></i> Résultats
|
||||||
|
</summary>
|
||||||
|
<table class="striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<i class="ri-list-ordered-2"></i>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i class="ri-music-2-fill"></i> Musique
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i class="ri-user-line"></i> Joueur
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for music in musikgame.musicgameorder_set.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ music.order }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://youtu.be/{{ music.music_video.yt_id }}">{{ music.music_video.title }}</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ music.player }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -5,12 +5,10 @@
|
||||||
<i class="ri-group-2-fill"></i> {{ group.name }}
|
<i class="ri-group-2-fill"></i> {{ group.name }}
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
{% if group.owner.youtubecredentials.credentials %}
|
{% if not user.youtubecredentials.credentials %}
|
||||||
<i class="ri-youtube-fill"></i> Une playlist sera générée automatiquement sur le compte Youtube de <strong>{{ group.owner }}</strong> (<strong>{{ group.owner.youtubecredentials.title }}</strong>).
|
<a href="{% url "youtube_login" %}" role="button"><i class="ri-youtube-fill"></i> Me connecter au compte Youtube</a>
|
||||||
{% elif user == group.owner %}
|
|
||||||
<a href="{% url "youtube_login" %}" role="button"><i class="ri-youtube-fill"></i> Connecter mon compte Youtube</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<small>Aucune playlist Youtube ne sera générée car <strong>{{ group.owner }}</strong> n'a pas lié son compte Youtube.</small>
|
<i class="ri-youtube-fill"></i> Une playlist sera générée automatiquement sur le compte Youtube <strong>{{ user.youtubecredentials.title }}</strong>.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% form form %}
|
{% form form %}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
{% if empty %}
|
|
||||||
<td class="empty"></td>
|
|
||||||
<td class="empty sc">
|
|
||||||
<i class="ri-checkbox-blank-circle-fill"></i><span class="score">{{ score }}</span>
|
|
||||||
</td>
|
|
||||||
{% elif correct %}
|
|
||||||
<td class="correct">{{ answer }}</td>
|
|
||||||
<td class="correct sc">
|
|
||||||
<i class="ri-checkbox-circle-fill"></i><span class="score">{{ score }}</span>
|
|
||||||
</td>
|
|
||||||
{% else %}
|
|
||||||
<td class="wrong">{{ answer }}</td>
|
|
||||||
<td class="wrong sc">
|
|
||||||
<i class="ri-close-circle-fill"></i><span class="score">{{ score }}</span>
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
|
@ -1,30 +0,0 @@
|
||||||
from django import template
|
|
||||||
|
|
||||||
from .. import models
|
|
||||||
|
|
||||||
register = template.Library()
|
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag("tags/game/answer.html")
|
|
||||||
def answer(player, music):
|
|
||||||
res = {
|
|
||||||
"answer": "",
|
|
||||||
"correct": False,
|
|
||||||
"score": 0,
|
|
||||||
"empty": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
answer = models.MusicGameAnswer.objects.filter(
|
|
||||||
player=player.player, game=music
|
|
||||||
).first()
|
|
||||||
if answer:
|
|
||||||
res["answer"] = answer.answer
|
|
||||||
res["correct"] = answer.answer == music.player
|
|
||||||
if music.player == player.player:
|
|
||||||
res["score"] = 0 if res["correct"] else "−500"
|
|
||||||
else:
|
|
||||||
res["score"] = music.value if res["correct"] else 0
|
|
||||||
else:
|
|
||||||
res["empty"] = True
|
|
||||||
|
|
||||||
return res
|
|
|
@ -63,10 +63,4 @@ urlpatterns = [
|
||||||
views.GroupClearBlacklistView.as_view(),
|
views.GroupClearBlacklistView.as_view(),
|
||||||
name="group_clear_blacklist",
|
name="group_clear_blacklist",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"group/game/<int:pk>/answer/",
|
|
||||||
views.GameAnswerView.as_view(),
|
|
||||||
name="game_answer",
|
|
||||||
),
|
|
||||||
path("group/game/<int:pk>/end/", views.GameEndView.as_view(), name="game_end"),
|
|
||||||
]
|
]
|
||||||
|
|
146
game/views.py
146
game/views.py
|
@ -104,31 +104,28 @@ class GroupAddMusicView(MemberFilterMixin, SingleObjectMixin, View):
|
||||||
|
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
group = self.get_object()
|
group = self.get_object()
|
||||||
ids = request.POST.get("yt_id")
|
yt_id = request.POST.get("yt_id")
|
||||||
for yt_id in ids.split():
|
if not yt_id:
|
||||||
if not yt_id:
|
messages.add_message(request, messages.ERROR, "Aucun identifiant donné")
|
||||||
messages.add_message(request, messages.ERROR, "Aucun identifiant donné")
|
return redirect(group)
|
||||||
return redirect(group)
|
yt_id = utils.parse_musik(yt_id)
|
||||||
yt_id = utils.parse_musik(yt_id)
|
|
||||||
|
|
||||||
title = utils.get_yt_title(yt_id)
|
title = utils.get_yt_title(yt_id)
|
||||||
if not title:
|
if not title:
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
request, messages.ERROR, f"Vidéo Youtube invalide : {yt_id}"
|
request, messages.ERROR, f"Vidéo Youtube invalide : {yt_id}"
|
||||||
)
|
)
|
||||||
else:
|
return redirect(group)
|
||||||
try:
|
try:
|
||||||
group.musicvideo_set.create(
|
group.musicvideo_set.create(yt_id=yt_id, title=title, owner=request.user)
|
||||||
yt_id=yt_id, title=title, owner=request.user
|
except IntegrityError:
|
||||||
)
|
messages.add_message(
|
||||||
except IntegrityError:
|
request, messages.ERROR, f"Vidéo Youtube déjà ajoutée : {yt_id}"
|
||||||
messages.add_message(
|
)
|
||||||
request, messages.ERROR, f"Vidéo Youtube déjà ajoutée : {yt_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
request, messages.SUCCESS, f"Vidéo Youtube ajoutée : {yt_id}"
|
request, messages.SUCCESS, f"Vidéo Youtube ajoutée : {yt_id}"
|
||||||
)
|
)
|
||||||
return redirect(group)
|
return redirect(group)
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,24 +136,19 @@ class GroupAddMemberView(MemberFilterMixin, SingleObjectMixin, View):
|
||||||
group = self.get_object()
|
group = self.get_object()
|
||||||
if not group.is_leader(request.user):
|
if not group.is_leader(request.user):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
usernames = request.POST.get("username")
|
username = request.POST.get("username")
|
||||||
for username in usernames.split():
|
user = User.objects.get(username=username)
|
||||||
user = User.objects.filter(username=username).first()
|
if user == group.owner:
|
||||||
if not user:
|
messages.add_message(
|
||||||
messages.add_message(
|
request, messages.WARNING, f"{user} est le propriétaire du groupe."
|
||||||
request, messages.ERROR, f"{username} n'existe pas."
|
)
|
||||||
)
|
return redirect(group)
|
||||||
elif user == group.owner:
|
if user in group.members.all():
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
request, messages.WARNING, f"{user} est le propriétaire du groupe."
|
request, messages.WARNING, f"{user} est déjà membre du groupe."
|
||||||
)
|
)
|
||||||
elif user in group.members.all():
|
|
||||||
messages.add_message(
|
|
||||||
request, messages.WARNING, f"{user} est déjà membre du groupe."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
group.members.add(user)
|
|
||||||
|
|
||||||
|
group.members.add(user)
|
||||||
return redirect(group)
|
return redirect(group)
|
||||||
|
|
||||||
|
|
||||||
|
@ -327,12 +319,15 @@ class GameCreateView(LoginRequiredMixin, CreateView):
|
||||||
pm_list = list(zip(players, musics))
|
pm_list = list(zip(players, musics))
|
||||||
random.shuffle(pm_list)
|
random.shuffle(pm_list)
|
||||||
for (player, music), order in zip(pm_list, range(1, len(pm_list) + 1)):
|
for (player, music), order in zip(pm_list, range(1, len(pm_list) + 1)):
|
||||||
|
music.blacklisted = True
|
||||||
|
music.save()
|
||||||
models.MusicGameOrder.objects.create(
|
models.MusicGameOrder.objects.create(
|
||||||
game=form.instance, player=player, music_video=music, order=order
|
game=form.instance, player=player, music_video=music, order=order
|
||||||
)
|
)
|
||||||
|
|
||||||
form.instance.playlist_loading = True
|
if models.YoutubeCredentials.objects.filter(user=self.request.user).exists():
|
||||||
form.instance.save()
|
form.instance.playlist_loading = True
|
||||||
|
form.instance.save()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@ -349,12 +344,6 @@ class GameDetailView(LoginRequiredMixin, DetailView):
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
data = super().get_context_data(**kwargs)
|
|
||||||
data["is_leader"] = data["musikgame"].group.is_leader(self.request.user)
|
|
||||||
data["is_owner"] = data["musikgame"].group.is_owner(self.request.user)
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class YoutubeCallbackView(LoginRequiredMixin, View):
|
class YoutubeCallbackView(LoginRequiredMixin, View):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
@ -432,64 +421,3 @@ class GroupClearBlacklistView(MemberFilterMixin, SingleObjectMixin, View):
|
||||||
group.musicvideo_set.filter(blacklisted=True).update(blacklisted=False)
|
group.musicvideo_set.filter(blacklisted=True).update(blacklisted=False)
|
||||||
messages.add_message(request, messages.SUCCESS, "La blacklist a été effacée.")
|
messages.add_message(request, messages.SUCCESS, "La blacklist a été effacée.")
|
||||||
return redirect(group)
|
return redirect(group)
|
||||||
|
|
||||||
|
|
||||||
class GameAnswerView(LoginRequiredMixin, DetailView):
|
|
||||||
model = models.MusikGame
|
|
||||||
template_name = "game/musikgame_answer.html"
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return super().get_queryset().filter(players=self.request.user)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
return super().get_context_data(**kwargs) | {
|
|
||||||
"form": forms.AnswerForm(game=self.object, user=self.request.user)
|
|
||||||
}
|
|
||||||
|
|
||||||
def post(self, request, pk):
|
|
||||||
game = self.get_object()
|
|
||||||
if game.over:
|
|
||||||
raise PermissionDenied()
|
|
||||||
for music in game.musicgameorder_set.all():
|
|
||||||
answer = request.POST.get(f"answer-{music.order}")
|
|
||||||
if answer:
|
|
||||||
models.MusicGameAnswer.objects.update_or_create(
|
|
||||||
game=music,
|
|
||||||
player=request.user,
|
|
||||||
defaults={"answer": game.players.get(pk=answer)},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
models.MusicGameAnswer.objects.update_or_create(
|
|
||||||
game=music,
|
|
||||||
player=request.user,
|
|
||||||
defaults={"answer": None},
|
|
||||||
)
|
|
||||||
return redirect("game_answer", pk)
|
|
||||||
|
|
||||||
|
|
||||||
class GameEndView(LoginRequiredMixin, SingleObjectMixin, View):
|
|
||||||
model = models.MusikGame
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return super().get_queryset().filter(over=False)
|
|
||||||
|
|
||||||
def post(self, request, pk):
|
|
||||||
game = self.get_object()
|
|
||||||
if not game.group.is_leader(request.user):
|
|
||||||
raise PermissionDenied()
|
|
||||||
game.over = True
|
|
||||||
models.MusicVideo.objects.filter(musicgameorder__game=game).update(
|
|
||||||
blacklisted=True
|
|
||||||
)
|
|
||||||
|
|
||||||
for go in game.musicgameorder_set.all():
|
|
||||||
go.update_value()
|
|
||||||
|
|
||||||
for player in game.players.all():
|
|
||||||
score = player.musicgameanswer_set.score(game, player)
|
|
||||||
models.MusicGameResults.objects.create(
|
|
||||||
game=game, player=player, score=score
|
|
||||||
)
|
|
||||||
|
|
||||||
game.save()
|
|
||||||
return redirect("game_detail", pk)
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
VERSION = "0.4.4"
|
VERSION = "0.2.0"
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "musik"
|
name = "musik"
|
||||||
version = "0.4.4"
|
version = "0.2.0"
|
||||||
description = "Le jeu de Musik."
|
description = "Le jeu de Musik."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|
2
uv.lock
generated
2
uv.lock
generated
|
@ -423,7 +423,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "musik"
|
name = "musik"
|
||||||
version = "0.4.4"
|
version = "0.2.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "celery" },
|
{ name = "celery" },
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue