Compare commits

...

13 commits
v0.4.1 ... main

Author SHA1 Message Date
8c2e62e8de
Fix formatting of favicon links in HTML template 2025-06-17 00:03:44 +02:00
e4168b474d
Bump version to 0.4.4 in settings, pyproject.toml, and uv.lock
All checks were successful
Build and push Docker image / build (push) Successful in 1m41s
2025-06-17 00:00:05 +02:00
b20eee8cfb
Add favicon assets and update HTML references for improved branding 2025-06-16 23:59:02 +02:00
a462fabde4
Bump version to 0.4.3 in settings, pyproject.toml, and uv.lock
All checks were successful
Build and push Docker image / build (push) Successful in 1m57s
2025-06-16 22:04:51 +02:00
b9711cbe9c
Refactor hero section layout and update footer privacy notice for clarity 2025-06-16 22:04:18 +02:00
bd8529cd01
Bump version to 0.4.2 in settings and pyproject.toml
All checks were successful
Build and push Docker image / build (push) Successful in 1m33s
2025-06-16 16:17:18 +02:00
84c432c325
Refactor value calculation in MusicGameOrder to improve scoring logic
Fix #13
Close #12
2025-06-16 16:16:11 +02:00
c639307cfb
Add restart policy for RabbitMQ and Postgres services in Docker Compose
Fix #11
2025-06-16 15:44:43 +02:00
951128147c
Fix playlist loading logic in MusikGame creation and update related template messages
Fix #10
2025-06-16 15:44:17 +02:00
0b8ce65a0a
Fix playlist display logic in musikgame_detail template
Fix #9
2025-06-16 15:33:23 +02:00
22bb6931e8
Remove individual blacklisting of music in GameCreateView and update all related music to blacklisted in GameEndView
Fix #8
2025-06-16 15:29:40 +02:00
cb3518a5e5
Change playlist privacy status from private to unlisted in generate_playlist task
Fix #7
2025-06-16 15:23:19 +02:00
e6d757c069
Update button text in musikgame_answer template for clarity
Fix #6
2025-06-16 15:19:06 +02:00
23 changed files with 155 additions and 61 deletions

View file

@ -77,11 +77,6 @@ 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),
@ -90,19 +85,41 @@ 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));
display: grid;
grid-template-rows: 1fr min-content;
align-items: center;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
overflow-y: auto;
padding: 4rem;
.big-logo {
font-size: 8rem;
main {
display: contents;
}
h1 {
font-size: 4rem;
section {
max-width: 20rem;
}
}
.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,
@ -201,3 +218,7 @@ table.results, table.musics {
}
}
}
.brand-name {
color: var(--pico-primary);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,25 @@
<?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>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,21 @@
{
"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.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View file

@ -11,7 +11,7 @@
Musik
{% endblock title %}
</title>
<link rel="icon" href="{% static "logo.svg" %}">
{% include "favicon.html" %}
<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"

View file

@ -0,0 +1,8 @@
{% 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" %}">

View file

@ -1 +1 @@
Musik {{ VERSION }} © <a href="https://code.edgarpierre.fr/edpibu/musik">Edgar P. Burkhart</a> <a href="{% url "legal" %}">Mentions légales</a>
Musik {{ VERSION }} © <a href="https://code.edgarpierre.fr/edpibu/musik">Edgar P. Burkhart</a> <a href="{% url "legal" %}">Mentions légales et confidentialité</a>

View file

@ -1,11 +1,25 @@
{% load static %}
<div id="hero">
<main>
<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 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 cest 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 cest 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" %}

View file

@ -16,6 +16,7 @@
Youtube est une marque de Google LLC.
</p>
<p>
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>.
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 %}

View file

@ -32,10 +32,12 @@ services:
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

View file

@ -99,6 +99,9 @@ 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,13 +121,10 @@ class MusicGameOrder(models.Model):
value = models.PositiveIntegerField(default=0)
def update_value(self):
n_right = self.musicgameanswer_set.filter(game__player=F("answer")).count()
if n_right == 0:
self.value = 1000
else:
self.value = 1000 / (
1 + ((n_right - 1) / (self.game.players.count() - 1)) ** 0.5
)
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:

View file

@ -18,7 +18,7 @@ def generate_playlist(creds, game_pk):
"description": "Playlist générée par Musik",
},
"status": {
"privacyStatus": "private",
"privacyStatus": "unlisted",
},
},
)

View file

@ -21,6 +21,6 @@
{% endfor %}
</tbody>
</table>
{% if not musikgame.over %}<button type="submit">Valider mes réponses</button>{% endif %}
{% if not musikgame.over %}<button type="submit">Sauvegarder mes réponses</button>{% endif %}
</form>
{% endblock content %}

View file

@ -9,31 +9,31 @@
{% endif %}
{{ musikgame.date }}
</h1>
{% if musikgame.playlist or musikgame.playlist_loading %}
<form method="post">
{% csrf_token %}
<fieldset role="group">
<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>
{% 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>
{% 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>
<i class="ri-group-2-fill"></i> Joueurs
</h2>

View file

@ -5,10 +5,12 @@
<i class="ri-group-2-fill"></i> {{ group.name }}
</h1>
<p>
{% if not user.youtubecredentials.credentials %}
<a href="{% url "youtube_login" %}" role="button"><i class="ri-youtube-fill"></i> Me connecter au compte Youtube</a>
{% 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 %}
<i class="ri-youtube-fill"></i> Une playlist sera générée automatiquement sur le compte Youtube <strong>{{ user.youtubecredentials.title }}</strong>.
<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 %}

View file

@ -327,15 +327,12 @@ 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
)
if models.YoutubeCredentials.objects.filter(user=self.request.user).exists():
form.instance.playlist_loading = True
form.instance.save()
form.instance.playlist_loading = True
form.instance.save()
return res
@ -481,6 +478,9 @@ class GameEndView(LoginRequiredMixin, SingleObjectMixin, View):
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()

View file

@ -13,7 +13,7 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
import os
from pathlib import Path
VERSION = "0.4.1"
VERSION = "0.4.4"
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

View file

@ -1,6 +1,6 @@
[project]
name = "musik"
version = "0.4.1"
version = "0.4.4"
description = "Le jeu de Musik."
readme = "README.md"
requires-python = ">=3.12"

2
uv.lock generated
View file

@ -423,7 +423,7 @@ wheels = [
[[package]]
name = "musik"
version = "0.4.1"
version = "0.4.4"
source = { virtual = "." }
dependencies = [
{ name = "celery" },