Compare commits
No commits in common. "main" and "web" have entirely different histories.
59 changed files with 403 additions and 1601 deletions
|
@ -17,7 +17,7 @@ form a[role="button"] {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
i.owner, .gold {
|
||||
i.owner {
|
||||
color: var(--pico-color-amber-200);
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ i.owner, .gold {
|
|||
display: none;
|
||||
}
|
||||
|
||||
a.group, a.running {
|
||||
a.group {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,11 @@ article.message {
|
|||
}
|
||||
|
||||
#hero {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: radial-gradient(
|
||||
circle 50vh at calc(100vw - 4rem) 50%,
|
||||
var(--pico-primary-background),
|
||||
|
@ -85,41 +90,18 @@ article.message {
|
|||
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%,
|
||||
var(--pico-background-color));
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
padding: 4rem;
|
||||
|
||||
main {
|
||||
display: contents;
|
||||
.big-logo {
|
||||
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,
|
||||
h2,
|
||||
|
@ -134,91 +116,3 @@ h6,
|
|||
i.i {
|
||||
margin-right: .5em;
|
||||
}
|
||||
i.hl, .me {
|
||||
color: var(--pico-primary);
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
font-weight: 350;
|
||||
}
|
||||
|
||||
td.c, th.c {
|
||||
text-align: center;
|
||||
|
||||
input {
|
||||
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,25 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<dialog open>
|
||||
<article>
|
||||
<header>
|
||||
<p>
|
||||
<i class="ri-user-fill"></i> {{ user.username }}
|
||||
</p>
|
||||
</header>
|
||||
<p>
|
||||
Confirmer la supression du compte <strong>{{ user.username }}</strong> ?
|
||||
</p>
|
||||
<p>
|
||||
<small>Toute suppression est immédiate et définitive. La suppression du compte entraîne la suppression des groupes dont celui-ci est propriétaire.</small>
|
||||
</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit">
|
||||
<i class="ri-delete-bin-fill"></i> Confirmer
|
||||
</button>
|
||||
<a href="{% url "account_settings" %}" role="button" class="secondary"><i class="ri-close-line"></i> Annuler</a>
|
||||
</form>
|
||||
</article>
|
||||
</dialog>
|
||||
{% endblock content %}
|
|
@ -1,24 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<h1>Créer mon compte</h1>
|
||||
{% for error in form.non_field_errors %}<article class="message error">{{ error }}</article>{% endfor %}
|
||||
<form method="post" {% if action %}action="{% url action %}"{% endif %}>
|
||||
{% 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 %}
|
||||
<h1>Créer un compte</h1>
|
||||
{% form form %}
|
||||
{% endblock content %}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
<i class="ri-user-settings-fill"></i> Mon compte
|
||||
</h1>
|
||||
{% for error in form.non_field_errors %}<article class="message error">{{ error }}</article>{% endfor %}
|
||||
<form method="post" {% if action %}action="{% url action %}"{% endif %}>
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<button type="submit" formaction="{% url "logout" %}" class="secondary">
|
||||
<i class="ri-logout-box-r-fill"></i> Me déconnecter
|
||||
</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
{% if not user.youtubecredentials.credentials %}
|
||||
<a href="{% url "youtube_login" %}" role="button"><i class="ri-youtube-fill"></i> Me connecter au compte Youtube</a>
|
||||
{% else %}
|
||||
<button type="submit"
|
||||
formaction="{% url "youtube_logout" %}"
|
||||
class="secondary">
|
||||
<i class="ri-youtube-fill"></i> Déconnecter le compte Youtube <strong>{{ user.youtubecredentials.title }}</strong>
|
||||
</button>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<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 %}
|
||||
<button type="submit">Mettre à jour mon compte</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<a href="{% url "password_change" %}" role="button" class="secondary">Changer mon mot de passe</a>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<a href="{% url "account_delete" %}" role="button" class="contrast">Supprimer mon compte</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endblock content %}
|
|
@ -11,7 +11,7 @@
|
|||
Musik
|
||||
{% endblock 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.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Exo:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
|
@ -47,8 +47,16 @@
|
|||
<nav>
|
||||
<ul>
|
||||
{% if user.is_authenticated %}
|
||||
<li>{{ user.username }}</li>
|
||||
<li>
|
||||
<a href="{% url "account_settings" %}"><i class="ri-user-fill" alt="Compte"></i> {{ user.username }}</a>
|
||||
<form action="{% url 'logout' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Se déconnecter" class="logout">
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<a href="{% url 'login' %}" role="button">Se connecter <i class="ri-login-box-fill"></i></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@ -60,9 +68,6 @@
|
|||
{% block content %}
|
||||
{% endblock content %}
|
||||
</main>
|
||||
<footer class="container">
|
||||
{% include "footer.html" %}
|
||||
</footer>
|
||||
{% endblock body %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{% for error in form.non_field_errors %}<article class="message error">{{ error }}</article>{% endfor %}
|
||||
<form method="post" {% if action %}action="{% url action %}"{% endif %}>
|
||||
{% csrf_token %}
|
||||
<fieldset role="group">
|
||||
{% for field in form %}{{ field }}{% endfor %}
|
||||
<input type="submit" {% if submit %}value="{{ submit }}"{% endif %}>
|
||||
{% if field.errors %}
|
||||
<small id="{{ field.errors.field_id }}_error" class="form-error">{{ field.errors|join:", " }}</small>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
</form>
|
|
@ -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 +0,0 @@
|
|||
Musik {{ VERSION }} – © <a href="https://code.edgarpierre.fr/edpibu/musik">Edgar P. Burkhart</a> – <a href="{% url "legal" %}">Mentions légales et confidentialité</a>
|
|
@ -1,27 +1,10 @@
|
|||
{% load static %}
|
||||
<div id="hero">
|
||||
<main>
|
||||
<div class="full-page">
|
||||
<div>
|
||||
<i class="ri-music-ai-fill big-logo"></i>
|
||||
<h1>Musik</h1>
|
||||
<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>
|
||||
<footer>
|
||||
{% include "footer.html" %}
|
||||
</footer>
|
||||
<div>
|
||||
<i class="ri-music-ai-fill big-logo"></i>
|
||||
<h1>Musik</h1>
|
||||
<p>
|
||||
<a href="{% url "login" %}" role="button"><i class="ri-play-fill"></i> Jouer</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% include "game/home.html" %}
|
||||
{% endblock content %}
|
||||
{% block body %}
|
||||
{% include "hero.html" %}
|
||||
{% if user.is_authenticated %}
|
||||
{{ block.super }}
|
||||
{% else %}
|
||||
{% include "hero.html" %}
|
||||
{% endif %}
|
||||
{% endblock body %}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h1>Mentions légales</h1>
|
||||
<h2>Éditeur</h2>
|
||||
<p>
|
||||
Ce site est réalisé et hébergé par <a href="mailto:contact@edgarpierre.fr">Edgar P. Burkhart</a>.
|
||||
</p>
|
||||
<h2>Données Personnelles</h2>
|
||||
<p>
|
||||
Dans le cadre de la création d'un compte, un nom d'utilisateur, une adresse email et un mot de passe sont requis.
|
||||
Ces données sont stockées dans le seul but de permettre la connexion et la réinitialisation du mot de passe de l'utilisateur (dans le cas de l'adresse mail).
|
||||
</p>
|
||||
<p>
|
||||
La connexion à un compte Youtube est utilisée pour permettre la génération automatique de playlists, et sa suppression le cas échéant.
|
||||
Elle n'est pas requise pour l'utilisation de Musik.
|
||||
Youtube est une marque de Google LLC.
|
||||
</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 du compte peut être demandée dans les paramètres du compte. La suppression des données est immédiate et définitive.
|
||||
</p>
|
||||
{% endblock content %}
|
|
@ -1,30 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<h1>Connexion</h1>
|
||||
{% for error in form.non_field_errors %}<article class="message error">{{ error }}</article>{% endfor %}
|
||||
<form method="post" {% if action %}action="{% url action %}"{% endif %}>
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if field.id_for_label %}
|
||||
<label for="{{ field.id_for_label }}">
|
||||
{{ field.label }}
|
||||
{% else %}
|
||||
<legend>{{ field.label }}</legend>
|
||||
{% endif %}
|
||||
{{ field }}
|
||||
{% if field.errors %}
|
||||
<small id="{{ field.errors.field_id }}_error" class="form-error">{{ field.errors|join:", " }}</small>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
<button type="submit">Me connecter</button>
|
||||
<fieldset>
|
||||
<a href="{% url "signup" %}" role="button" class="secondary">Créer mon compte</a>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<a href="{% url "password_reset" %}"
|
||||
role="button"
|
||||
class="outline secondary">J'ai oublié mon mot de passe</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
<p>
|
||||
<a href="{% url "signup" %}">Créer un compte</a>
|
||||
</p>
|
||||
{% form form submit="Se connecter" %}
|
||||
{% endblock content %}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
<i class="ri-lock-password-line"></i> Changer mon mot de passe
|
||||
</h1>
|
||||
{% form form submit="Changer mon mot de passe" %}
|
||||
{% endblock content %}
|
|
@ -1,8 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
<i class="ri-lock-password-line"></i> Réinitialiser mon mot de passe
|
||||
</h1>
|
||||
{% form form submit="Réinitialiser mon mot de passe" %}
|
||||
{% endblock content %}
|
|
@ -1,8 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
<i class="ri-lock-password-line"></i> Réinitialiser mon mot de passe
|
||||
</h1>
|
||||
{% form form submit="Réinitialiser mon mot de passe" %}
|
||||
{% endblock content %}
|
|
@ -4,13 +4,8 @@ register = template.Library()
|
|||
|
||||
|
||||
@register.inclusion_tag("base/form.html")
|
||||
def form(f, **kwargs):
|
||||
def form(form, **kwargs):
|
||||
return kwargs | {
|
||||
"form": f,
|
||||
"errors": f.errors,
|
||||
"form": form,
|
||||
"errors": form.errors,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag("base/inline_form.html")
|
||||
def inline_form(f, **kwargs):
|
||||
return form(f, **kwargs)
|
||||
|
|
25
base/urls.py
25
base/urls.py
|
@ -1,30 +1,9 @@
|
|||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import path
|
||||
from django.views.generic import TemplateView
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.HomePageView.as_view(), name="index"),
|
||||
path("accounts/signup/", views.SignupView.as_view(), name="signup"),
|
||||
path("accounts/login/", auth_views.LoginView.as_view(), name="login"),
|
||||
path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
|
||||
path(
|
||||
"accounts/password_change/",
|
||||
views.PasswordChangeView.as_view(),
|
||||
name="password_change",
|
||||
),
|
||||
path(
|
||||
"accounts/password_reset/",
|
||||
views.PasswordResetView.as_view(),
|
||||
name="password_reset",
|
||||
),
|
||||
path(
|
||||
"accounts/reset/<uidb64>/<token>/",
|
||||
views.PasswordResetConfirmView.as_view(),
|
||||
name="password_reset_confirm",
|
||||
),
|
||||
path("accounts/settings/", views.AccountView.as_view(), name="account_settings"),
|
||||
path("accounts/delete/", views.AccountDeleteView.as_view(), name="account_delete"),
|
||||
path("legal/", TemplateView.as_view(template_name="privacy.html"), name="legal"),
|
||||
path("accounts/", include("django.contrib.auth.urls")),
|
||||
]
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
from django.contrib.auth import views as auth_views
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
from django.views.generic.edit import CreateView
|
||||
|
||||
from . import forms
|
||||
|
||||
|
@ -12,53 +9,9 @@ from . import forms
|
|||
class HomePageView(TemplateView):
|
||||
template_name = "index.html"
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.user.is_authenticated:
|
||||
return redirect("home")
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class SignupView(SuccessMessageMixin, CreateView):
|
||||
model = User
|
||||
form_class = forms.UserSignupForm
|
||||
success_url = reverse_lazy("login")
|
||||
success_url = "/"
|
||||
success_message = "Le compte %(username)s a été créé avec succès."
|
||||
|
||||
|
||||
class PasswordChangeView(SuccessMessageMixin, auth_views.PasswordChangeView):
|
||||
success_message = "Le mot de passe a été changé avec succès."
|
||||
success_url = reverse_lazy("index")
|
||||
|
||||
|
||||
class PasswordResetView(SuccessMessageMixin, auth_views.PasswordResetView):
|
||||
success_message = "Un courriel a été envoyé avec les instructions pour réinitialiser votre mot de passe."
|
||||
success_url = reverse_lazy("login")
|
||||
|
||||
|
||||
class PasswordResetConfirmView(
|
||||
SuccessMessageMixin, auth_views.PasswordResetConfirmView
|
||||
):
|
||||
success_message = "Le mot de passe a été réinitialisé avec succès."
|
||||
success_url = reverse_lazy("login")
|
||||
|
||||
|
||||
class AccountView(UpdateView):
|
||||
model = User
|
||||
fields = ["username", "email"]
|
||||
success_url = reverse_lazy("index")
|
||||
template_name = "auth/user_settings.html"
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
return queryset.get(pk=self.request.user.pk)
|
||||
|
||||
|
||||
class AccountDeleteView(DeleteView):
|
||||
model = User
|
||||
success_url = reverse_lazy("index")
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
return queryset.get(pk=self.request.user.pk)
|
||||
|
|
18
compose.yaml
18
compose.yaml
|
@ -6,16 +6,13 @@ services:
|
|||
ports:
|
||||
- 35001:8000
|
||||
volumes:
|
||||
- /docker/musik/config:/config:ro
|
||||
- /data/srv/musik/static:/app/static
|
||||
- /docker/musik/config:/config
|
||||
- /docker/musik/static:/app/static
|
||||
environment:
|
||||
CELERY_BROKER_URL: amqp://rabbitmq:5672//
|
||||
POSTGRES_HOST: postgres
|
||||
env_file: stack.env
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
- celery
|
||||
- postgres
|
||||
|
||||
celery:
|
||||
image: code.edgarpierre.fr/edpibu/musik
|
||||
|
@ -24,20 +21,9 @@ services:
|
|||
command: uv run celery -A musik worker
|
||||
environment:
|
||||
CELERY_BROKER_URL: amqp://rabbitmq:5672//
|
||||
POSTGRES_HOST: postgres
|
||||
env_file: stack.env
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq
|
||||
container_name: musik_rabbitmq
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:17
|
||||
container_name: musik_postgres
|
||||
restart: unless-stopped
|
||||
env_file: stack.env
|
||||
volumes:
|
||||
- /docker/musik/postgres:/var/lib/postgresql/data
|
||||
|
|
|
@ -3,16 +3,6 @@ from django import forms
|
|||
from . import models
|
||||
|
||||
|
||||
class GroupForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = models.Group
|
||||
fields = ["name"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["name"].widget.attrs["placeholder"] = self.fields["name"].label
|
||||
|
||||
|
||||
class GroupAddMembersForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -39,23 +29,3 @@ class MusikGameForm(forms.ModelForm):
|
|||
kwargs["initial"].setdefault("players", players)
|
||||
super().__init__(*args, **kwargs)
|
||||
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,35 +0,0 @@
|
|||
# Generated by Django 5.2.3 on 2025-06-14 18:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("game", "0014_musikgame_playlist_loading"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="GroupLeader",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("is_leader", models.BooleanField(default=False)),
|
||||
(
|
||||
"member",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="game.group_members",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,22 +0,0 @@
|
|||
# Generated by Django 5.2.3 on 2025-06-14 18:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("game", "0015_groupleader"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="groupleader",
|
||||
name="member",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="lead",
|
||||
to="game.group_members",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,17 +0,0 @@
|
|||
# Generated by Django 5.2.3 on 2025-06-15 08:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("game", "0016_alter_groupleader_member"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="youtubecredentials",
|
||||
name="title",
|
||||
field=models.CharField(blank=True),
|
||||
),
|
||||
]
|
|
@ -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.db import models
|
||||
from django.db.models import F
|
||||
from django.db.models.functions import Lower
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
from django.dispatch import receiver
|
||||
|
@ -12,7 +11,6 @@ from . import tasks
|
|||
class YoutubeCredentials(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
credentials = models.JSONField()
|
||||
title = models.CharField(blank=True)
|
||||
|
||||
|
||||
class Group(models.Model):
|
||||
|
@ -25,33 +23,12 @@ class Group(models.Model):
|
|||
def get_absolute_url(self):
|
||||
return reverse("group_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def is_owner(self, user):
|
||||
return user == self.owner
|
||||
|
||||
def is_leader(self, user):
|
||||
return (
|
||||
self.is_owner(user)
|
||||
or self.members.through.objects.filter(
|
||||
lead__is_leader=True, user=user
|
||||
).exists()
|
||||
)
|
||||
|
||||
def is_member(self, user):
|
||||
return self.is_owner(user) or self.members.filter(user=user).exists()
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(Lower("name"), "owner", name="unique_group_name")
|
||||
]
|
||||
|
||||
|
||||
class GroupLeader(models.Model):
|
||||
member = models.OneToOneField(
|
||||
Group.members.through, on_delete=models.CASCADE, related_name="lead"
|
||||
)
|
||||
is_leader = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class MusicVideo(models.Model):
|
||||
yt_id = models.CharField(max_length=16)
|
||||
title = models.CharField(blank=True)
|
||||
|
@ -66,12 +43,6 @@ class MusicVideo(models.Model):
|
|||
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):
|
||||
|
@ -81,16 +52,10 @@ class MusikGame(models.Model):
|
|||
players = models.ManyToManyField(User, verbose_name="Joueurs")
|
||||
playlist = models.CharField(blank=True, verbose_name="Playlist YouTube")
|
||||
playlist_loading = models.BooleanField(default=False)
|
||||
over = models.BooleanField(default=False)
|
||||
|
||||
objects = GameManager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("game_detail", kwargs={"pk": self.pk})
|
||||
|
||||
class Meta:
|
||||
ordering = ["over", "-date"]
|
||||
|
||||
|
||||
@receiver(post_save, sender=MusikGame)
|
||||
def generateYoutubePlaylist(sender, instance, created, **kwargs):
|
||||
|
@ -99,9 +64,6 @@ def generateYoutubePlaylist(sender, instance, created, **kwargs):
|
|||
|
||||
if creds := instance.group.owner.youtubecredentials:
|
||||
tasks.generate_playlist.delay_on_commit(creds.credentials, instance.pk)
|
||||
else:
|
||||
instance.playlist_loading = False
|
||||
instance.save()
|
||||
|
||||
|
||||
@receiver(post_delete, sender=MusikGame)
|
||||
|
@ -118,14 +80,6 @@ class MusicGameOrder(models.Model):
|
|||
player = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
music_video = models.ForeignKey(MusicVideo, on_delete=models.CASCADE)
|
||||
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:
|
||||
constraints = [
|
||||
|
@ -135,44 +89,3 @@ class MusicGameOrder(models.Model):
|
|||
models.UniqueConstraint(fields=("game", "order"), name="unique_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",
|
||||
},
|
||||
"status": {
|
||||
"privacyStatus": "unlisted",
|
||||
"privacyStatus": "private",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<dialog open>
|
||||
<article>
|
||||
|
|
|
@ -9,8 +9,196 @@
|
|||
{% endif %}
|
||||
{{ group.name }}
|
||||
</h1>
|
||||
{% include "game/include/group_buttons.html" %}
|
||||
{% include "game/include/group_games.html" %}
|
||||
{% include "game/include/group_members.html" %}
|
||||
{% include "game/include/group_musics.html" %}
|
||||
{% if group.owner == user %}
|
||||
<p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<a href="{% url "start_game" pk=group.pk %}" role="button"><i class="ri-play-fill"></i> Jouer</a>
|
||||
</p>
|
||||
<div role="group">
|
||||
<a href="{% url "group_update" pk=group.pk %}"
|
||||
class="secondary"
|
||||
role="button"><i class="ri-edit-line"></i> Renommer</a>
|
||||
<button type="submit"
|
||||
class="secondary"
|
||||
formaction="{% url "group_clear_blacklist" pk=group.pk %}">
|
||||
<i class="ri-history-fill"></i> Effacer la blacklist
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% 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 %}
|
||||
<h2>
|
||||
<i class="ri-group-2-fill"></i> Membres
|
||||
</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% if group.owner == user %}<th></th>{% endif %}
|
||||
<th>Membre</th>
|
||||
<th>
|
||||
<i class="ri-vip-crown-fill"></i>
|
||||
</th>
|
||||
<th>
|
||||
<i class="ri-mv-line"></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{% if group.owner == user %}<td></td>{% endif %}
|
||||
<td>{{ group.owner }}</td>
|
||||
<td>
|
||||
<i class="ri-vip-crown-fill owner"></i>
|
||||
</td>
|
||||
<td>{{ owner_count }}</td>
|
||||
</tr>
|
||||
{% for member in members.all %}
|
||||
<tr>
|
||||
{% if group.owner == user %}
|
||||
<td>
|
||||
<input type="checkbox" name="member" value="{{ member.pk }}">
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{{ member }}</td>
|
||||
<td></td>
|
||||
<td>{{ member.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if group.owner == user %}
|
||||
<button type="submit"
|
||||
class="secondary"
|
||||
formaction="{% url "group_remove_member" pk=group.pk %}">
|
||||
<i class="ri-delete-bin-fill"></i> Supprimer les membres sélectionnés
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% if group.owner == user %}
|
||||
<form method="post" action="{% url "group_add_member" pk=group.pk %}">
|
||||
{% csrf_token %}
|
||||
<fieldset role="group">
|
||||
<input type="string"
|
||||
name="username"
|
||||
id="username"
|
||||
placeholder="Membre"
|
||||
required>
|
||||
<button type="submit">Ajouter</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
<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 %}
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
<h1>
|
||||
<i class="ri-group-2-fill"></i> {{ group.name }}
|
||||
</h1>
|
||||
<p>
|
||||
<a href="{% url "group_delete" group.pk %}">Supprimer le groupe</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<h1>Créer un groupe</h1>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
<i class="ri-music-ai-fill"></i> Musik
|
||||
</h1>
|
||||
<p>
|
||||
Bienvenue <strong>{{ user.username }}</strong> !
|
||||
</p>
|
||||
<h2>
|
||||
<i class="ri-group-2-fill"></i> Mes groupes
|
||||
</h2>
|
||||
{% if user.owned_group_set.exists or user.group_set.exists %}
|
||||
{% for group in user.owned_group_set.all %}
|
||||
<a class="group" href="{{ group.get_absolute_url }}">
|
||||
<article>
|
||||
<i class="ri-vip-crown-fill owner i"></i> {{ group.name }}
|
||||
</article>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% for group in user.group_set.all %}
|
||||
<a class="group" href="{{ group.get_absolute_url }}">
|
||||
<article>
|
||||
<i class="ri-group-2-fill i"></i> {{ group.name }} <span class="group-owner">{{ group.owner }}</span>
|
||||
</article>
|
||||
</a>
|
||||
{% endfor %}
|
||||
<p>Bienvenue {{ user.username }} !</p>
|
||||
<h2>
|
||||
<i class="ri-group-2-fill"></i> Mes groupes
|
||||
</h2>
|
||||
<p>
|
||||
<a href="{% url "group_create" %}" role="button"><i class="ri-add-box-fill"></i> Créer un groupe</a>
|
||||
{% if not user.youtubecredentials.credentials %}
|
||||
<a href="{% url "youtube_login" %}" role="button"><i class="ri-youtube-fill"></i> Me connecter au compte Youtube</a>
|
||||
{% endif %}
|
||||
{% inline_form group_form action="group_create" submit="Créer" %}
|
||||
{% endblock content %}
|
||||
</p>
|
||||
{% if user.owned_group_set.exists or user.group_set.exists %}
|
||||
{% for group in user.owned_group_set.all %}
|
||||
<a class="group" href="{{ group.get_absolute_url }}">
|
||||
<article>
|
||||
<i class="ri-vip-crown-fill owner i"></i> {{ group.name }}
|
||||
</article>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% for group in user.group_set.all %}
|
||||
<a class="group" href="{{ group.get_absolute_url }}">
|
||||
<article>
|
||||
<i class="ri-group-2-fill i"></i> {{ group.name }} <span class="group-owner">{{ group.owner }}</span>
|
||||
</article>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
|
|
@ -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,28 +0,0 @@
|
|||
{% if is_leader %}
|
||||
<p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<a href="{% url "start_game" pk=group.pk %}" role="button"><i class="ri-play-fill"></i> Jouer</a>
|
||||
</p>
|
||||
<div role="group">
|
||||
{% if is_owner %}
|
||||
<a href="{% url "group_update" pk=group.pk %}"
|
||||
class="secondary"
|
||||
role="button"><i class="ri-edit-line"></i> Renommer</a>
|
||||
{% endif %}
|
||||
<button type="submit"
|
||||
class="secondary"
|
||||
formaction="{% url "group_clear_blacklist" pk=group.pk %}">
|
||||
<i class="ri-history-fill"></i> Effacer la blacklist
|
||||
</button>
|
||||
{% if is_owner %}
|
||||
<a href="{% url "group_delete" group.pk %}"
|
||||
role="button"
|
||||
class="secondary">
|
||||
<i class="ri-delete-bin-fill"></i> Supprimer</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
{% 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 %}
|
|
@ -1,81 +0,0 @@
|
|||
<h2>
|
||||
<i class="ri-group-2-fill"></i> Membres
|
||||
</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% if is_leader %}<th></th>{% endif %}
|
||||
<th>Membre</th>
|
||||
<th class="c">
|
||||
<i class="ri-vip-crown-fill"></i>
|
||||
</th>
|
||||
<th>
|
||||
<i class="ri-mv-fill"></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{% if is_leader %}<td></td>{% endif %}
|
||||
<td>{{ group.owner }}</td>
|
||||
<td class="c">
|
||||
<i class="ri-vip-crown-fill owner"></i>
|
||||
</td>
|
||||
<td>{{ owner_count }}</td>
|
||||
</tr>
|
||||
{% for member in members.all %}
|
||||
<tr>
|
||||
{% if is_leader %}
|
||||
<td>
|
||||
<input type="checkbox" name="member" value="{{ member.pk }}">
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{{ member.user }}</td>
|
||||
<td class="c">
|
||||
{% if is_owner %}
|
||||
<input type="checkbox"
|
||||
name="leader"
|
||||
value="{{ member.pk }}"
|
||||
role="switch"
|
||||
{% if member.lead.is_leader %}checked{% endif %}>
|
||||
{% elif member.lead.is_leader %}
|
||||
<i class="ri-vip-crown-fill pico-color-zinc-500"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ member.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if is_leader %}
|
||||
<div role="group">
|
||||
{% if is_owner %}
|
||||
<button type="submit"
|
||||
class="secondary"
|
||||
formaction="{% url "group_set_leader" pk=group.pk %}">
|
||||
<i class="ri-vip-crown-fill"></i> Mettre à jour meneurs
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="submit"
|
||||
class="secondary"
|
||||
formaction="{% url "group_remove_member" pk=group.pk %}">
|
||||
<i class="ri-delete-bin-fill"></i> Supprimer
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% if is_leader %}
|
||||
<form method="post" action="{% url "group_add_member" pk=group.pk %}">
|
||||
{% csrf_token %}
|
||||
<fieldset role="group">
|
||||
<input type="string"
|
||||
name="username"
|
||||
id="username"
|
||||
placeholder="Membre"
|
||||
required>
|
||||
<button type="submit">Ajouter</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
|
@ -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 %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% if musikgame.over %}
|
||||
<i class="ri-stop-circle-fill"></i>
|
||||
{% else %}
|
||||
<i class="ri-play-circle-fill hl"></i>
|
||||
{% endif %}
|
||||
{{ musikgame.date }}
|
||||
<i class="ri-play-circle-fill"></i> {{ musikgame.date }}
|
||||
</h1>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<fieldset role="group">
|
||||
{% if musikgame.playlist or musikgame.playlist_loading %}
|
||||
<a target="_blank"
|
||||
href="{% yt_playlist musikgame %}"
|
||||
role="button"
|
||||
{% if musikgame.playlist_loading %}aria-busy="true"{% endif %}><i class="ri-youtube-fill"></i> Playlist</a>
|
||||
{% 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>
|
||||
{% if musikgame.playlist or musikgame.playlist_loading %}
|
||||
<p>
|
||||
<a target="_blank"
|
||||
href="{% yt_playlist musikgame %}"
|
||||
role="button"
|
||||
{% if musikgame.playlist_loading %}aria-busy="true"{% endif %}><i class="ri-youtube-fill"></i> Playlist</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<h2>
|
||||
<i class="ri-group-2-fill"></i> Joueurs
|
||||
</h2>
|
||||
{% if musikgame.over %}
|
||||
<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 %}
|
||||
<p>{{ musikgame.players.all|join:", " }}</p>
|
||||
<h2>
|
||||
<i class="ri-music-2-fill"></i> Musiques
|
||||
</h2>
|
||||
|
@ -63,5 +31,38 @@
|
|||
{% endfor %}
|
||||
</ol>
|
||||
</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 %}
|
||||
|
|
|
@ -4,14 +4,5 @@
|
|||
<h1>
|
||||
<i class="ri-group-2-fill"></i> {{ group.name }}
|
||||
</h1>
|
||||
<p>
|
||||
{% if group.owner.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>).
|
||||
{% elif user == group.owner %}
|
||||
<a href="{% url "youtube_login" %}" role="button"><i class="ri-youtube-fill"></i> Connecter mon compte Youtube</a>
|
||||
{% else %}
|
||||
<small>Aucune playlist Youtube ne sera générée car <strong>{{ group.owner }}</strong> n'a pas lié son compte Youtube.</small>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% form form %}
|
||||
{% endblock content %}
|
||||
|
|
|
@ -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
|
13
game/urls.py
13
game/urls.py
|
@ -3,7 +3,6 @@ from django.urls import path
|
|||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("home", views.HomeView.as_view(), name="home"),
|
||||
path("group/create/", views.GroupCreateView.as_view(), name="group_create"),
|
||||
path(
|
||||
"group/<int:pk>/update/", views.GroupUpdateView.as_view(), name="group_update"
|
||||
|
@ -42,17 +41,11 @@ urlpatterns = [
|
|||
views.GroupRemoveMemberView.as_view(),
|
||||
name="group_remove_member",
|
||||
),
|
||||
path(
|
||||
"group/<int:pk>/set_leader/",
|
||||
views.GroupSetLead.as_view(),
|
||||
name="group_set_leader",
|
||||
),
|
||||
path(
|
||||
"group/<int:pk>/start_game/", views.GameCreateView.as_view(), name="start_game"
|
||||
),
|
||||
path("group/game/<int:pk>/", views.GameDetailView.as_view(), name="game_detail"),
|
||||
path("youtube_login/", views.YoutubeLoginView.as_view(), name="youtube_login"),
|
||||
path("youtube_logout/", views.YoutubeLogoutView.as_view(), name="youtube_logout"),
|
||||
path(
|
||||
"youtube_callback/",
|
||||
views.YoutubeCallbackView.as_view(),
|
||||
|
@ -63,10 +56,4 @@ urlpatterns = [
|
|||
views.GroupClearBlacklistView.as_view(),
|
||||
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"),
|
||||
]
|
||||
|
|
263
game/views.py
263
game/views.py
|
@ -1,33 +1,21 @@
|
|||
import random
|
||||
|
||||
import google_auth_oauthlib
|
||||
import googleapiclient
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Count, Q
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.views import View
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
|
||||
from . import forms, models, utils
|
||||
|
||||
|
||||
class HomeView(LoginRequiredMixin, TemplateView):
|
||||
template_name = "game/home.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
data["group_form"] = forms.GroupForm()
|
||||
return data
|
||||
|
||||
|
||||
class OwnerFilterMixin(LoginRequiredMixin):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(owner=self.request.user)
|
||||
|
@ -82,19 +70,14 @@ class GroupDetailView(MemberFilterMixin, GroupMixin, DetailView):
|
|||
.musicvideo_set.filter(owner=data["group"].owner, blacklisted=False)
|
||||
.count()
|
||||
)
|
||||
data["members"] = models.Group.members.through.objects.filter(
|
||||
group=data["group"]
|
||||
).annotate(
|
||||
data["members"] = data["group"].members.annotate(
|
||||
count=Count(
|
||||
"user__musicvideo",
|
||||
"musicvideo",
|
||||
filter=Q(
|
||||
user__musicvideo__group=data["group"],
|
||||
user__musicvideo__blacklisted=False,
|
||||
musicvideo__group=data["group"], musicvideo__blacklisted=False
|
||||
),
|
||||
)
|
||||
)
|
||||
data["is_leader"] = data["group"].is_leader(self.request.user)
|
||||
data["is_owner"] = data["group"].is_owner(self.request.user)
|
||||
|
||||
return data
|
||||
|
||||
|
@ -104,59 +87,49 @@ class GroupAddMusicView(MemberFilterMixin, SingleObjectMixin, View):
|
|||
|
||||
def post(self, request, pk):
|
||||
group = self.get_object()
|
||||
ids = request.POST.get("yt_id")
|
||||
for yt_id in ids.split():
|
||||
if not yt_id:
|
||||
messages.add_message(request, messages.ERROR, "Aucun identifiant donné")
|
||||
return redirect(group)
|
||||
yt_id = utils.parse_musik(yt_id)
|
||||
yt_id = request.POST.get("yt_id")
|
||||
if not yt_id:
|
||||
messages.add_message(request, messages.ERROR, "Aucun identifiant donné")
|
||||
return redirect(group)
|
||||
yt_id = utils.parse_musik(yt_id)
|
||||
|
||||
title = utils.get_yt_title(yt_id)
|
||||
if not title:
|
||||
messages.add_message(
|
||||
request, messages.ERROR, f"Vidéo Youtube invalide : {yt_id}"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
group.musicvideo_set.create(
|
||||
yt_id=yt_id, title=title, owner=request.user
|
||||
)
|
||||
except IntegrityError:
|
||||
messages.add_message(
|
||||
request, messages.ERROR, f"Vidéo Youtube déjà ajoutée : {yt_id}"
|
||||
)
|
||||
title = utils.get_yt_title(yt_id)
|
||||
if not title:
|
||||
messages.add_message(
|
||||
request, messages.ERROR, f"Vidéo Youtube invalide : {yt_id}"
|
||||
)
|
||||
return redirect(group)
|
||||
try:
|
||||
group.musicvideo_set.create(yt_id=yt_id, title=title, owner=request.user)
|
||||
except IntegrityError:
|
||||
messages.add_message(
|
||||
request, messages.ERROR, f"Vidéo Youtube déjà ajoutée : {yt_id}"
|
||||
)
|
||||
|
||||
messages.add_message(
|
||||
request, messages.SUCCESS, f"Vidéo Youtube ajoutée : {yt_id}"
|
||||
)
|
||||
messages.add_message(
|
||||
request, messages.SUCCESS, f"Vidéo Youtube ajoutée : {yt_id}"
|
||||
)
|
||||
return redirect(group)
|
||||
|
||||
|
||||
class GroupAddMemberView(MemberFilterMixin, SingleObjectMixin, View):
|
||||
class GroupAddMemberView(OwnerFilterMixin, SingleObjectMixin, View):
|
||||
model = models.Group
|
||||
|
||||
def post(self, request, pk):
|
||||
group = self.get_object()
|
||||
if not group.is_leader(request.user):
|
||||
raise PermissionDenied()
|
||||
usernames = request.POST.get("username")
|
||||
for username in usernames.split():
|
||||
user = User.objects.filter(username=username).first()
|
||||
if not user:
|
||||
messages.add_message(
|
||||
request, messages.ERROR, f"{username} n'existe pas."
|
||||
)
|
||||
elif user == group.owner:
|
||||
messages.add_message(
|
||||
request, messages.WARNING, f"{user} est le propriétaire 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)
|
||||
username = request.POST.get("username")
|
||||
user = User.objects.get(username=username)
|
||||
if user == group.owner:
|
||||
messages.add_message(
|
||||
request, messages.WARNING, f"{user} est le propriétaire du groupe."
|
||||
)
|
||||
return redirect(group)
|
||||
if user in group.members.all():
|
||||
messages.add_message(
|
||||
request, messages.WARNING, f"{user} est déjà membre du groupe."
|
||||
)
|
||||
|
||||
group.members.add(user)
|
||||
return redirect(group)
|
||||
|
||||
|
||||
|
@ -207,16 +180,14 @@ class GroupUnblacklistMusicView(MemberFilterMixin, SingleObjectMixin, View):
|
|||
return redirect(group)
|
||||
|
||||
|
||||
class GroupRemoveMemberView(MemberFilterMixin, SingleObjectMixin, View):
|
||||
class GroupRemoveMemberView(OwnerFilterMixin, SingleObjectMixin, View):
|
||||
model = models.Group
|
||||
|
||||
def post(self, request, pk):
|
||||
group = self.get_object()
|
||||
if not group.is_leader(request.user):
|
||||
raise PermissionDenied()
|
||||
|
||||
relations = models.Group.members.through.objects.filter(
|
||||
group=group, pk__in=request.POST.getlist("member")
|
||||
group=group, user__id__in=request.POST.getlist("member")
|
||||
)
|
||||
if relations.count() == 0:
|
||||
messages.add_message(request, messages.INFO, "Aucun membre supprimé.")
|
||||
|
@ -239,25 +210,6 @@ class GroupRemoveMemberView(MemberFilterMixin, SingleObjectMixin, View):
|
|||
return redirect(group)
|
||||
|
||||
|
||||
class GroupSetLead(OwnerFilterMixin, SingleObjectMixin, View):
|
||||
model = models.Group
|
||||
|
||||
def post(self, request, pk):
|
||||
group = self.get_object()
|
||||
|
||||
members = models.Group.members.through.objects.filter(group=group)
|
||||
for member in members.filter(pk__in=request.POST.getlist("leader")):
|
||||
models.GroupLeader.objects.update_or_create(
|
||||
member=member, defaults={"is_leader": True}
|
||||
)
|
||||
for member in members.exclude(pk__in=request.POST.getlist("leader")):
|
||||
models.GroupLeader.objects.update_or_create(
|
||||
member=member, defaults={"is_leader": False}
|
||||
)
|
||||
|
||||
return redirect(group)
|
||||
|
||||
|
||||
class GroupRemoveGameView(OwnerFilterMixin, SingleObjectMixin, View):
|
||||
model = models.Group
|
||||
|
||||
|
@ -298,15 +250,15 @@ class GameCreateView(LoginRequiredMixin, CreateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
data["group"] = get_object_or_404(models.Group, pk=self.kwargs["pk"])
|
||||
if not data["group"].is_leader(self.request.user):
|
||||
raise PermissionDenied()
|
||||
data["group"] = get_object_or_404(
|
||||
models.Group, owner=self.request.user, pk=self.kwargs["pk"]
|
||||
)
|
||||
return data
|
||||
|
||||
def form_valid(self, form):
|
||||
group = get_object_or_404(models.Group, pk=self.kwargs["pk"])
|
||||
if not group.is_leader(self.request.user):
|
||||
return super().form_invalid(form)
|
||||
group = get_object_or_404(
|
||||
models.Group, owner=self.request.user, pk=self.kwargs["pk"]
|
||||
)
|
||||
form.instance.group = group
|
||||
res = super().form_valid(form)
|
||||
players = []
|
||||
|
@ -327,12 +279,15 @@ class GameCreateView(LoginRequiredMixin, CreateView):
|
|||
pm_list = list(zip(players, musics))
|
||||
random.shuffle(pm_list)
|
||||
for (player, music), order in zip(pm_list, range(1, len(pm_list) + 1)):
|
||||
music.blacklisted = True
|
||||
music.save()
|
||||
models.MusicGameOrder.objects.create(
|
||||
game=form.instance, player=player, music_video=music, order=order
|
||||
)
|
||||
|
||||
form.instance.playlist_loading = True
|
||||
form.instance.save()
|
||||
if self.request.user.youtubecredentials:
|
||||
form.instance.playlist_loading = True
|
||||
form.instance.save()
|
||||
return res
|
||||
|
||||
|
||||
|
@ -349,11 +304,21 @@ class GameDetailView(LoginRequiredMixin, DetailView):
|
|||
.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 YoutubeLoginView(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
||||
settings.YOUTUBE_OAUTH_SECRETS,
|
||||
["https://www.googleapis.com/auth/youtube.force-ssl"],
|
||||
)
|
||||
flow.redirect_uri = "https://localhost/youtube_callback/"
|
||||
auth_url, state = flow.authorization_url(
|
||||
access_type="offline",
|
||||
include_granted_scopes="true",
|
||||
prompt="consent",
|
||||
)
|
||||
self.request.session["state"] = state
|
||||
return redirect(auth_url)
|
||||
|
||||
|
||||
class YoutubeCallbackView(LoginRequiredMixin, View):
|
||||
|
@ -370,18 +335,11 @@ class YoutubeCallbackView(LoginRequiredMixin, View):
|
|||
["https://www.googleapis.com/auth/youtube.force-ssl"],
|
||||
state=state,
|
||||
)
|
||||
flow.redirect_uri = request.build_absolute_uri("/youtube_callback/")
|
||||
flow.redirect_uri = "https://localhost/youtube_callback/"
|
||||
|
||||
flow.fetch_token(code=request.GET.get("code"))
|
||||
|
||||
credentials = flow.credentials
|
||||
|
||||
yt_api = googleapiclient.discovery.build(
|
||||
"youtube", "v3", credentials=credentials
|
||||
)
|
||||
channel_request = yt_api.channels().list(part="snippet", mine=True)
|
||||
res = channel_request.execute()
|
||||
|
||||
models.YoutubeCredentials.objects.update_or_create(
|
||||
user=request.user,
|
||||
defaults={
|
||||
|
@ -392,104 +350,19 @@ class YoutubeCallbackView(LoginRequiredMixin, View):
|
|||
"client_id": credentials.client_id,
|
||||
"client_secret": credentials.client_secret,
|
||||
"granted_scopes": credentials.granted_scopes,
|
||||
},
|
||||
"title": res["items"][0]["snippet"]["title"],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
messages.add_message(request, messages.SUCCESS, "Connexion à Youtube réussie.")
|
||||
return redirect("/")
|
||||
|
||||
|
||||
class YoutubeLoginView(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
||||
settings.YOUTUBE_OAUTH_SECRETS,
|
||||
["https://www.googleapis.com/auth/youtube.force-ssl"],
|
||||
)
|
||||
flow.redirect_uri = request.build_absolute_uri("/youtube_callback/")
|
||||
auth_url, state = flow.authorization_url(
|
||||
access_type="offline",
|
||||
include_granted_scopes="true",
|
||||
prompt="consent",
|
||||
)
|
||||
self.request.session["state"] = state
|
||||
return redirect(auth_url)
|
||||
|
||||
|
||||
class YoutubeLogoutView(LoginRequiredMixin, View):
|
||||
def post(self, request):
|
||||
request.user.youtubecredentials.delete()
|
||||
return redirect("account_settings")
|
||||
|
||||
|
||||
class GroupClearBlacklistView(MemberFilterMixin, SingleObjectMixin, View):
|
||||
class GroupClearBlacklistView(OwnerFilterMixin, SingleObjectMixin, View):
|
||||
model = models.Group
|
||||
|
||||
def post(self, request, pk):
|
||||
group = self.get_object()
|
||||
if not group.is_leader(request.user):
|
||||
raise PermissionDenied()
|
||||
group.musicvideo_set.filter(blacklisted=True).update(blacklisted=False)
|
||||
messages.add_message(request, messages.SUCCESS, "La blacklist a été effacée.")
|
||||
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)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
def version(request):
|
||||
return {"VERSION": settings.VERSION}
|
|
@ -13,8 +13,6 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
|
|||
import os
|
||||
from pathlib import Path
|
||||
|
||||
VERSION = "0.4.4"
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
@ -23,19 +21,13 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.getenv(
|
||||
"MUSIK_SECRET_KEY",
|
||||
"django-insecure-&z*xu$^w8btr(%1!y#+0a98)l_q*+*6z54611pi678mdpsar_=",
|
||||
)
|
||||
SECRET_KEY = "django-insecure-&z*xu$^w8btr(%1!y#+0a98)l_q*+*6z54611pi678mdpsar_="
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = "DEBUG" in os.environ
|
||||
DEBUG = True
|
||||
|
||||
HOST = os.getenv("MUSIK_HOST", "localhost")
|
||||
ALLOWED_HOSTS = [HOST]
|
||||
CSRF_TRUSTED_ORIGINS = [f"https://{HOST}"]
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
ALLOWED_HOSTS = []
|
||||
CSRF_TRUSTED_ORIGINS = ["https://localhost"]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
@ -73,7 +65,6 @@ TEMPLATES = [
|
|||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"musik.context_processors.version",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -87,14 +78,12 @@ WSGI_APPLICATION = "musik.wsgi.application"
|
|||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"HOST": os.getenv("POSTGRES_HOST", "localhost"),
|
||||
"NAME": os.getenv("POSTGRES_DB", os.getenv("POSTGRES_USER", "musik")),
|
||||
"USER": os.getenv("POSTGRES_USER", "musik"),
|
||||
"PASSWORD": os.getenv("POSTGRES_PASSWORD"),
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "db.sqlite3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||
|
||||
|
@ -144,12 +133,3 @@ YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY", "")
|
|||
YOUTUBE_OAUTH_SECRETS = os.getenv("YOUTUBE_OAUTH_SECRETS", "")
|
||||
|
||||
CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", None)
|
||||
|
||||
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||
EMAIL_HOST = os.getenv("EMAIL_HOST")
|
||||
EMAIL_PORT = os.getenv("EMAIL_PORT", 587)
|
||||
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
|
||||
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
|
||||
EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", False)
|
||||
EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", not EMAIL_USE_SSL)
|
||||
DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", EMAIL_HOST_USER)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "musik"
|
||||
version = "0.4.4"
|
||||
version = "0.1.0"
|
||||
description = "Le jeu de Musik."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
@ -12,7 +12,6 @@ dependencies = [
|
|||
"google-auth-httplib2>=0.2.0",
|
||||
"google-auth-oauthlib>=1.2.2",
|
||||
"gunicorn>=23.0.0",
|
||||
"psycopg[binary]>=3.2.9",
|
||||
"requests>=2.32.4",
|
||||
]
|
||||
|
||||
|
|
66
uv.lock
generated
66
uv.lock
generated
|
@ -66,11 +66,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.6.15"
|
||||
version = "2025.4.26"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -423,7 +423,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "musik"
|
||||
version = "0.4.4"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "celery" },
|
||||
|
@ -433,7 +433,6 @@ dependencies = [
|
|||
{ name = "google-auth-httplib2" },
|
||||
{ name = "google-auth-oauthlib" },
|
||||
{ name = "gunicorn" },
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
|
@ -452,7 +451,6 @@ requires-dist = [
|
|||
{ name = "google-auth-httplib2", specifier = ">=0.2.0" },
|
||||
{ name = "google-auth-oauthlib", specifier = ">=1.2.2" },
|
||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.9" },
|
||||
{ name = "requests", specifier = ">=2.32.4" },
|
||||
]
|
||||
|
||||
|
@ -561,53 +559,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
binary = [
|
||||
{ name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-binary"
|
||||
version = "3.2.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/29/6f/ec9957e37a606cd7564412e03f41f1b3c3637a5be018d0849914cb06e674/psycopg_binary-3.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be7d650a434921a6b1ebe3fff324dbc2364393eb29d7672e638ce3e21076974e", size = 4022205, upload-time = "2025-05-13T16:07:48.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/ba/497b8bea72b20a862ac95a94386967b745a472d9ddc88bc3f32d5d5f0d43/psycopg_binary-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76b4722a529390683c0304501f238b365a46b1e5fb6b7249dbc0ad6fea51a0", size = 4083795, upload-time = "2025-05-13T16:07:50.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/07/af9503e8e8bdad3911fd88e10e6a29240f9feaa99f57d6fac4a18b16f5a0/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a551e4683f1c307cfc3d9a05fec62c00a7264f320c9962a67a543e3ce0d8ff", size = 4655043, upload-time = "2025-05-13T16:07:54.857Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/ed/aff8c9850df1648cc6a5cc7a381f11ee78d98a6b807edd4a5ae276ad60ad/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61d0a6ceed8f08c75a395bc28cb648a81cf8dee75ba4650093ad1a24a51c8724", size = 4477972, upload-time = "2025-05-13T16:07:57.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/bd/8e9d1b77ec1a632818fe2f457c3a65af83c68710c4c162d6866947d08cc5/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad280bbd409bf598683dda82232f5215cfc5f2b1bf0854e409b4d0c44a113b1d", size = 4737516, upload-time = "2025-05-13T16:08:01.616Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/ec/222238f774cd5a0881f3f3b18fb86daceae89cc410f91ef6a9fb4556f236/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76eddaf7fef1d0994e3d536ad48aa75034663d3a07f6f7e3e601105ae73aeff6", size = 4436160, upload-time = "2025-05-13T16:08:04.278Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/78/af5af2a1b296eeca54ea7592cd19284739a844974c9747e516707e7b3b39/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52e239cd66c4158e412318fbe028cd94b0ef21b0707f56dcb4bdc250ee58fd40", size = 3753518, upload-time = "2025-05-13T16:08:07.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/ac/8a3ed39ea069402e9e6e6a2f79d81a71879708b31cc3454283314994b1ae/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08bf9d5eabba160dd4f6ad247cf12f229cc19d2458511cab2eb9647f42fa6795", size = 3313598, upload-time = "2025-05-13T16:08:09.999Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/43/26549af068347c808fbfe5f07d2fa8cef747cfff7c695136172991d2378b/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1b2cf018168cad87580e67bdde38ff5e51511112f1ce6ce9a8336871f465c19a", size = 3407289, upload-time = "2025-05-13T16:08:12.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/55/ea8d227c77df8e8aec880ded398316735add8fda5eb4ff5cc96fac11e964/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:14f64d1ac6942ff089fc7e926440f7a5ced062e2ed0949d7d2d680dc5c00e2d4", size = 3472493, upload-time = "2025-05-13T16:08:15.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/02/6ff2a5bc53c3cd653d281666728e29121149179c73fddefb1e437024c192/psycopg_binary-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:7a838852e5afb6b4126f93eb409516a8c02a49b788f4df8b6469a40c2157fa21", size = 2927400, upload-time = "2025-05-13T16:08:18.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.1"
|
||||
|
@ -784,15 +735,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue