diff --git a/base/static/css/main.css b/base/static/css/main.css index 74e4488..c329a18 100644 --- a/base/static/css/main.css +++ b/base/static/css/main.css @@ -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); -} diff --git a/base/static/favicon/apple-touch-icon.png b/base/static/favicon/apple-touch-icon.png deleted file mode 100644 index abb8a48..0000000 Binary files a/base/static/favicon/apple-touch-icon.png and /dev/null differ diff --git a/base/static/favicon/favicon-96x96.png b/base/static/favicon/favicon-96x96.png deleted file mode 100644 index beab288..0000000 Binary files a/base/static/favicon/favicon-96x96.png and /dev/null differ diff --git a/base/static/favicon/favicon.ico b/base/static/favicon/favicon.ico deleted file mode 100644 index 178d60c..0000000 Binary files a/base/static/favicon/favicon.ico and /dev/null differ diff --git a/base/static/favicon/favicon.svg b/base/static/favicon/favicon.svg deleted file mode 100644 index 63c10e0..0000000 --- a/base/static/favicon/favicon.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/base/static/favicon/site.webmanifest b/base/static/favicon/site.webmanifest deleted file mode 100644 index af6d059..0000000 --- a/base/static/favicon/site.webmanifest +++ /dev/null @@ -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" -} diff --git a/base/static/favicon/web-app-manifest-192x192.png b/base/static/favicon/web-app-manifest-192x192.png deleted file mode 100644 index f7f97da..0000000 Binary files a/base/static/favicon/web-app-manifest-192x192.png and /dev/null differ diff --git a/base/static/favicon/web-app-manifest-512x512.png b/base/static/favicon/web-app-manifest-512x512.png deleted file mode 100644 index 9e4e381..0000000 Binary files a/base/static/favicon/web-app-manifest-512x512.png and /dev/null differ diff --git a/base/templates/auth/user_confirm_delete.html b/base/templates/auth/user_confirm_delete.html deleted file mode 100644 index 8840f15..0000000 --- a/base/templates/auth/user_confirm_delete.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base.html" %} -{% block content %} - -
-
-

- {{ user.username }} -

-
-

- Confirmer la supression du compte {{ user.username }} ? -

-

- Toute suppression est immédiate et définitive. La suppression du compte entraîne la suppression des groupes dont celui-ci est propriétaire. -

-
- {% csrf_token %} - - Annuler -
-
-
-{% endblock content %} diff --git a/base/templates/auth/user_form.html b/base/templates/auth/user_form.html index c895f16..257d1dc 100644 --- a/base/templates/auth/user_form.html +++ b/base/templates/auth/user_form.html @@ -1,24 +1,6 @@ {% extends "base.html" %} {% load form %} {% block content %} -

Créer mon compte

- {% for error in form.non_field_errors %}
{{ error }}
{% endfor %} -
- {% csrf_token %} -
- {% for field in form %} - - {% endfor %} -
- -
- J'ai déjà un compte -
-
-{% endblock content %} +

Créer un compte

+ {% form form %} + {% endblock content %} diff --git a/base/templates/auth/user_settings.html b/base/templates/auth/user_settings.html deleted file mode 100644 index c69e952..0000000 --- a/base/templates/auth/user_settings.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "base.html" %} -{% load form %} -{% block content %} -

- Mon compte -

- {% for error in form.non_field_errors %}
{{ error }}
{% endfor %} -
- {% csrf_token %} -
- -
-
- {% if not user.youtubecredentials.credentials %} - Me connecter au compte Youtube - {% else %} - - {% endif %} -
-
- {% for field in form %} - - {% endfor %} - -
-
- Changer mon mot de passe -
-
- Supprimer mon compte -
-
-{% endblock content %} diff --git a/base/templates/base.html b/base/templates/base.html index 504228f..8e677eb 100644 --- a/base/templates/base.html +++ b/base/templates/base.html @@ -11,7 +11,7 @@ Musik {% endblock title %} - {% include "favicon.html" %} + @@ -60,9 +68,6 @@ {% block content %} {% endblock content %} - {% endblock body %} diff --git a/base/templates/base/inline_form.html b/base/templates/base/inline_form.html deleted file mode 100644 index 75769ae..0000000 --- a/base/templates/base/inline_form.html +++ /dev/null @@ -1,11 +0,0 @@ -{% for error in form.non_field_errors %}
{{ error }}
{% endfor %} -
- {% csrf_token %} -
- {% for field in form %}{{ field }}{% endfor %} - - {% if field.errors %} - {{ field.errors|join:", " }} - {% endif %} -
-
diff --git a/base/templates/favicon.html b/base/templates/favicon.html deleted file mode 100644 index dad76d7..0000000 --- a/base/templates/favicon.html +++ /dev/null @@ -1,8 +0,0 @@ -{% load static %} - - - - - - - diff --git a/base/templates/footer.html b/base/templates/footer.html deleted file mode 100644 index 6c57641..0000000 --- a/base/templates/footer.html +++ /dev/null @@ -1 +0,0 @@ -Musik {{ VERSION }} – © Edgar P. BurkhartMentions légales et confidentialité diff --git a/base/templates/hero.html b/base/templates/hero.html index 813c011..7dcdbe0 100644 --- a/base/templates/hero.html +++ b/base/templates/hero.html @@ -1,27 +1,10 @@ {% load static %}
-
-
-
- -

Musik

-

- Jouer -

-
-
-
-
-

- Musik Le jeu où ta playlist devient ton arme secrète ! -

-

- 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 ? -

-
-
-
- +
+ +

Musik

+

+ Jouer +

+
diff --git a/base/templates/index.html b/base/templates/index.html index e026c32..5235a9f 100644 --- a/base/templates/index.html +++ b/base/templates/index.html @@ -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 %} diff --git a/base/templates/privacy.html b/base/templates/privacy.html deleted file mode 100644 index 1549403..0000000 --- a/base/templates/privacy.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Mentions légales

-

Éditeur

-

- Ce site est réalisé et hébergé par Edgar P. Burkhart. -

-

Données Personnelles

-

- 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). -

-

- 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. -

-

- 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. -

-{% endblock content %} diff --git a/base/templates/registration/login.html b/base/templates/registration/login.html index 4e0394e..1a78811 100644 --- a/base/templates/registration/login.html +++ b/base/templates/registration/login.html @@ -1,30 +1,9 @@ {% extends "base.html" %} +{% load form %} {% block content %}

Connexion

- {% for error in form.non_field_errors %}
{{ error }}
{% endfor %} -
- {% csrf_token %} - {% for field in form %} - {% if field.id_for_label %} - - {% endfor %} - -
- Créer mon compte -
-
- J'ai oublié mon mot de passe -
-
-{% endblock content %} +

+ Créer un compte +

+ {% form form submit="Se connecter" %} + {% endblock content %} diff --git a/base/templates/registration/password_change_form.html b/base/templates/registration/password_change_form.html deleted file mode 100644 index 25304fe..0000000 --- a/base/templates/registration/password_change_form.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} -{% load form %} -{% block content %} -

- Changer mon mot de passe -

- {% form form submit="Changer mon mot de passe" %} - {% endblock content %} diff --git a/base/templates/registration/password_reset_confirm.html b/base/templates/registration/password_reset_confirm.html deleted file mode 100644 index 2f7f317..0000000 --- a/base/templates/registration/password_reset_confirm.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} -{% load form %} -{% block content %} -

- Réinitialiser mon mot de passe -

- {% form form submit="Réinitialiser mon mot de passe" %} - {% endblock content %} diff --git a/base/templates/registration/password_reset_form.html b/base/templates/registration/password_reset_form.html deleted file mode 100644 index 2f7f317..0000000 --- a/base/templates/registration/password_reset_form.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} -{% load form %} -{% block content %} -

- Réinitialiser mon mot de passe -

- {% form form submit="Réinitialiser mon mot de passe" %} - {% endblock content %} diff --git a/base/templatetags/form.py b/base/templatetags/form.py index 66532a8..9ab40ce 100644 --- a/base/templatetags/form.py +++ b/base/templatetags/form.py @@ -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) diff --git a/base/urls.py b/base/urls.py index f240d91..2a6b2d5 100644 --- a/base/urls.py +++ b/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///", - 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")), ] diff --git a/base/views.py b/base/views.py index 7ad30e5..e3618a0 100644 --- a/base/views.py +++ b/base/views.py @@ -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) diff --git a/compose.yaml b/compose.yaml index a6ee8b3..04838ec 100644 --- a/compose.yaml +++ b/compose.yaml @@ -32,12 +32,10 @@ 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 diff --git a/game/forms.py b/game/forms.py index a548455..99a18cf 100644 --- a/game/forms.py +++ b/game/forms.py @@ -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, - ) diff --git a/game/migrations/0015_groupleader.py b/game/migrations/0015_groupleader.py deleted file mode 100644 index a9e5dc2..0000000 --- a/game/migrations/0015_groupleader.py +++ /dev/null @@ -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", - ), - ), - ], - ), - ] diff --git a/game/migrations/0016_alter_groupleader_member.py b/game/migrations/0016_alter_groupleader_member.py deleted file mode 100644 index 1561a34..0000000 --- a/game/migrations/0016_alter_groupleader_member.py +++ /dev/null @@ -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", - ), - ), - ] diff --git a/game/migrations/0017_youtubecredentials_title.py b/game/migrations/0017_youtubecredentials_title.py deleted file mode 100644 index 89870e4..0000000 --- a/game/migrations/0017_youtubecredentials_title.py +++ /dev/null @@ -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), - ), - ] diff --git a/game/migrations/0018_musicgameanswer.py b/game/migrations/0018_musicgameanswer.py deleted file mode 100644 index 1f760be..0000000 --- a/game/migrations/0018_musicgameanswer.py +++ /dev/null @@ -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" - ) - ], - }, - ), - ] diff --git a/game/migrations/0019_alter_musikgame_options_musikgame_over.py b/game/migrations/0019_alter_musikgame_options_musikgame_over.py deleted file mode 100644 index f5e4489..0000000 --- a/game/migrations/0019_alter_musikgame_options_musikgame_over.py +++ /dev/null @@ -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), - ), - ] diff --git a/game/migrations/0020_alter_musikgame_options_musicgameresults.py b/game/migrations/0020_alter_musikgame_options_musicgameresults.py deleted file mode 100644 index 3af5916..0000000 --- a/game/migrations/0020_alter_musikgame_options_musicgameresults.py +++ /dev/null @@ -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" - ) - ], - }, - ), - ] diff --git a/game/migrations/0021_alter_musicgameresults_score.py b/game/migrations/0021_alter_musicgameresults_score.py deleted file mode 100644 index 3166d4d..0000000 --- a/game/migrations/0021_alter_musicgameresults_score.py +++ /dev/null @@ -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), - ), - ] diff --git a/game/migrations/0022_musicgameorder_value.py b/game/migrations/0022_musicgameorder_value.py deleted file mode 100644 index 712353c..0000000 --- a/game/migrations/0022_musicgameorder_value.py +++ /dev/null @@ -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), - ), - ] diff --git a/game/migrations/0023_alter_musicvideo_options.py b/game/migrations/0023_alter_musicvideo_options.py deleted file mode 100644 index ee96adb..0000000 --- a/game/migrations/0023_alter_musicvideo_options.py +++ /dev/null @@ -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"]}, - ), - ] diff --git a/game/models.py b/game/models.py index 153583f..88fe6cc 100644 --- a/game/models.py +++ b/game/models.py @@ -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"] diff --git a/game/tasks.py b/game/tasks.py index 61937d0..22a7dc0 100644 --- a/game/tasks.py +++ b/game/tasks.py @@ -18,7 +18,7 @@ def generate_playlist(creds, game_pk): "description": "Playlist générée par Musik", }, "status": { - "privacyStatus": "unlisted", + "privacyStatus": "private", }, }, ) diff --git a/game/templates/game/group_confirm_delete.html b/game/templates/game/group_confirm_delete.html index 26c5411..f8d7e5e 100644 --- a/game/templates/game/group_confirm_delete.html +++ b/game/templates/game/group_confirm_delete.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load form %} {% block content %}
diff --git a/game/templates/game/group_detail.html b/game/templates/game/group_detail.html index 90e9901..b6f48d8 100644 --- a/game/templates/game/group_detail.html +++ b/game/templates/game/group_detail.html @@ -9,8 +9,200 @@ {% endif %} {{ group.name }} - {% 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 %} +

+

+ {% csrf_token %} +

+ Jouer +

+
+ Renommer + + + Supprimer +
+
+

+ {% endif %} + {% if group.musikgame_set.exists %} +

+ Parties +

+
+ {% csrf_token %} + + + {% if group.owner == user %}{% endif %} + + + + + + {% for game in group.musikgame_set.all %} + + {% if group.owner == user %} + + {% endif %} + + + + + {% endfor %} + +
Date + Playlists + Joueurs
+ + + {{ game.date }} + + {% if game.playlist %} + Playlist + {% endif %} + {{ game.players.all|join:", " }}
+ {% if group.owner == user %} + + {% endif %} +
+ {% endif %} +

+ Membres +

+
+ {% csrf_token %} + + + + {% if group.owner == user %}{% endif %} + + + + + + + + {% if group.owner == user %}{% endif %} + + + + + {% for member in members.all %} + + {% if group.owner == user %} + + {% endif %} + + + + + {% endfor %} + +
Membre + + + +
{{ group.owner }} + + {{ owner_count }}
+ + {{ member }}{{ member.count }}
+ {% if group.owner == user %} + + {% endif %} +
+ {% if group.owner == user %} +
+ {% csrf_token %} +
+ + +
+
+ {% endif %} +

+ Mes musiques {{ musics.count }} +

+
+ + Liste des musiques + +
+ {% csrf_token %} + + + + + + + + + + + {% for music in musics %} + + + + + + + {% empty %} + + + + {% endfor %} + +
MusiqueID + +
+ + {{ music.title }} + {{ music.yt_id }} + + +
Aucune musique.
+ {% if musics %} +
+ + +
+ {% endif %} +
+
+
+ {% csrf_token %} +
+ + +
+
{% endblock content %} diff --git a/game/templates/game/home.html b/game/templates/game/home.html index b1d2399..9aee9fe 100644 --- a/game/templates/game/home.html +++ b/game/templates/game/home.html @@ -1,30 +1,26 @@ -{% extends "base.html" %} -{% load form %} -{% block content %} -

- Musik -

-

- Bienvenue {{ user.username }} ! -

-

- Mes groupes -

- {% if user.owned_group_set.exists or user.group_set.exists %} - {% for group in user.owned_group_set.all %} - -
- {{ group.name }} -
-
- {% endfor %} - {% for group in user.group_set.all %} - -
- {{ group.name }} {{ group.owner }} -
-
- {% endfor %} +

Bienvenue {{ user.username }} !

+

+ Mes groupes +

+

+ Créer un groupe + {% if not user.youtubecredentials.credentials %} + Me connecter au compte Youtube {% endif %} - {% inline_form group_form action="group_create" submit="Créer" %} -{% endblock content %} +

+{% if user.owned_group_set.exists or user.group_set.exists %} + {% for group in user.owned_group_set.all %} + +
+ {{ group.name }} +
+
+ {% endfor %} + {% for group in user.group_set.all %} + +
+ {{ group.name }} {{ group.owner }} +
+
+ {% endfor %} +{% endif %} diff --git a/game/templates/game/include/game_results.html b/game/templates/game/include/game_results.html deleted file mode 100644 index ca2821c..0000000 --- a/game/templates/game/include/game_results.html +++ /dev/null @@ -1,49 +0,0 @@ -{% load game %} -{% if musikgame.over %} -

- Résultats -

-
- - Résultats - -
- - - - - - - {% for player in musikgame.musicgameresults_set.all %} - - - {% endfor %} - - - - {% for music in musikgame.musicgameorder_set.all %} - - - - - {% for player in musikgame.musicgameresults_set.all %} - {% answer player music %} - {% endfor %} - - {% endfor %} - -
- - - Musique - - Joueur - {{ player.player.username }} - {% if forloop.first %}{% endif %} - {{ player.score }} -
{{ music.order }} - {{ music.music_video.title }} - {{ music.player }}
-
-
-{% endif %} diff --git a/game/templates/game/include/group_buttons.html b/game/templates/game/include/group_buttons.html deleted file mode 100644 index 065545f..0000000 --- a/game/templates/game/include/group_buttons.html +++ /dev/null @@ -1,28 +0,0 @@ -{% if is_leader %} -

-

- {% csrf_token %} -

- Jouer -

-
- {% if is_owner %} - Renommer - {% endif %} - - {% if is_owner %} - - Supprimer - {% endif %} -
-
-

-{% endif %} diff --git a/game/templates/game/include/group_games.html b/game/templates/game/include/group_games.html deleted file mode 100644 index 15016fb..0000000 --- a/game/templates/game/include/group_games.html +++ /dev/null @@ -1,62 +0,0 @@ -{% load form youtube %} -{% if group.musikgame_set.exists %} - {% for game in group.musikgame_set.playing %} - -
{{ game.date }}
-
- {% endfor %} -

- Parties -

-
- {% csrf_token %} - - - {% if group.owner == user %}{% endif %} - - - - - - - {% for game in group.musikgame_set.all %} - - {% if group.owner == user %} - - {% endif %} - - - - - - {% endfor %} - -
- - Date - Playlists - Joueurs
- - - {% if game.over %} - - {% else %} - - {% endif %} - - {{ game.date }} - - {% if game.playlist %} - Playlist - {% endif %} - {{ game.players.all|join:", " }}
- {% if group.owner == user %} - - {% endif %} -
-{% endif %} diff --git a/game/templates/game/include/group_members.html b/game/templates/game/include/group_members.html deleted file mode 100644 index daf3059..0000000 --- a/game/templates/game/include/group_members.html +++ /dev/null @@ -1,81 +0,0 @@ -

- Membres -

-
- {% csrf_token %} - - - - {% if is_leader %}{% endif %} - - - - - - - - {% if is_leader %}{% endif %} - - - - - {% for member in members.all %} - - {% if is_leader %} - - {% endif %} - - - - - {% endfor %} - -
Membre - - - -
{{ group.owner }} - - {{ owner_count }}
- - {{ member.user }} - {% if is_owner %} - - {% elif member.lead.is_leader %} - - {% endif %} - {{ member.count }}
- {% if is_leader %} -
- {% if is_owner %} - - {% endif %} - -
- {% endif %} -
-{% if is_leader %} -
- {% csrf_token %} -
- - -
-
-{% endif %} diff --git a/game/templates/game/include/group_musics.html b/game/templates/game/include/group_musics.html deleted file mode 100644 index 56117cf..0000000 --- a/game/templates/game/include/group_musics.html +++ /dev/null @@ -1,66 +0,0 @@ -

- Mes musiques {{ musics.count }} -

-
- - Liste des musiques - -
- {% csrf_token %} -
- - - - - - - - - - - {% for music in musics %} - - - - - - - {% empty %} - - - - {% endfor %} - -
MusiqueID - -
- - {{ music.title }} - {{ music.yt_id }} - - -
Aucune musique.
-
- {% if musics %} -
- - -
- {% endif %} -
-
-
- {% csrf_token %} -
- - -
-
diff --git a/game/templates/game/musikgame_answer.html b/game/templates/game/musikgame_answer.html deleted file mode 100644 index f947802..0000000 --- a/game/templates/game/musikgame_answer.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Mes réponses

-
- {% csrf_token %} - - - - - - - - - {% for field in form %} - - - - - {% endfor %} - -
- - Réponse
{{ field.label }}{{ field }}
- {% if not musikgame.over %}{% endif %} -
-{% endblock content %} diff --git a/game/templates/game/musikgame_detail.html b/game/templates/game/musikgame_detail.html index 66708c0..444c7f1 100644 --- a/game/templates/game/musikgame_detail.html +++ b/game/templates/game/musikgame_detail.html @@ -2,52 +2,20 @@ {% load youtube %} {% block content %}

- {% if musikgame.over %} - - {% else %} - - {% endif %} - {{ musikgame.date }} + {{ musikgame.date }}

-
- {% csrf_token %} -
- {% if musikgame.playlist or musikgame.playlist_loading %} - Playlist - {% endif %} - {% if musikgame.over %} - Mes réponses - {% else %} - Répondre - {% endif %} -
- {% if is_leader and not musikgame.over %} -
- -
- {% endif %} -
+ {% if musikgame.playlist or musikgame.playlist_loading %} +

+ Playlist +

+ {% endif %}

Joueurs

- {% if musikgame.over %} -
    - {% for player in musikgame.musicgameresults_set.all %} -
  1. - {{ player.player.username }} {{ player.score }} -
  2. - {% endfor %} -
- {% else %} -

{{ musikgame.players.all|join:", " }}

- {% endif %} +

{{ musikgame.players.all|join:", " }}

Musiques

@@ -63,5 +31,38 @@ {% endfor %} - {% include "game/include/game_results.html" %} +

+ Résultats +

+
+ + Résultats + + + + + + + + + + + {% for music in musikgame.musicgameorder_set.all %} + + + + + + {% endfor %} + +
+ + + Musique + + Joueur +
{{ music.order }} + {{ music.music_video.title }} + {{ music.player }}
+
{% endblock content %} diff --git a/game/templates/game/musikgame_form.html b/game/templates/game/musikgame_form.html index 655723a..3e6d4ed 100644 --- a/game/templates/game/musikgame_form.html +++ b/game/templates/game/musikgame_form.html @@ -4,14 +4,5 @@

{{ group.name }}

-

- {% if group.owner.youtubecredentials.credentials %} - Une playlist sera générée automatiquement sur le compte Youtube de {{ group.owner }} ({{ group.owner.youtubecredentials.title }}). - {% elif user == group.owner %} - Connecter mon compte Youtube - {% else %} - Aucune playlist Youtube ne sera générée car {{ group.owner }} n'a pas lié son compte Youtube. - {% endif %} -

{% form form %} {% endblock content %} diff --git a/game/templates/tags/game/answer.html b/game/templates/tags/game/answer.html deleted file mode 100644 index bfdeba5..0000000 --- a/game/templates/tags/game/answer.html +++ /dev/null @@ -1,16 +0,0 @@ -{% if empty %} - - - {{ score }} - -{% elif correct %} - {{ answer }} - - {{ score }} - -{% else %} - {{ answer }} - - {{ score }} - -{% endif %} diff --git a/game/templatetags/game.py b/game/templatetags/game.py deleted file mode 100644 index eec1bd0..0000000 --- a/game/templatetags/game.py +++ /dev/null @@ -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 diff --git a/game/urls.py b/game/urls.py index 927dc79..763d277 100644 --- a/game/urls.py +++ b/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//update/", views.GroupUpdateView.as_view(), name="group_update" @@ -42,17 +41,11 @@ urlpatterns = [ views.GroupRemoveMemberView.as_view(), name="group_remove_member", ), - path( - "group//set_leader/", - views.GroupSetLead.as_view(), - name="group_set_leader", - ), path( "group//start_game/", views.GameCreateView.as_view(), name="start_game" ), path("group/game//", 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//answer/", - views.GameAnswerView.as_view(), - name="game_answer", - ), - path("group/game//end/", views.GameEndView.as_view(), name="game_end"), ] diff --git a/game/views.py b/game/views.py index 45685aa..f6632f0 100644 --- a/game/views.py +++ b/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,12 +304,6 @@ 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 YoutubeCallbackView(LoginRequiredMixin, View): def get(self, request): @@ -375,13 +324,6 @@ class YoutubeCallbackView(LoginRequiredMixin, View): 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,10 +334,10 @@ 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("/") @@ -416,80 +358,11 @@ class YoutubeLoginView(LoginRequiredMixin, View): 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) diff --git a/musik/context_processors.py b/musik/context_processors.py deleted file mode 100644 index de709cd..0000000 --- a/musik/context_processors.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.conf import settings - - -def version(request): - return {"VERSION": settings.VERSION} diff --git a/musik/settings.py b/musik/settings.py index 0f097d7..b12b987 100644 --- a/musik/settings.py +++ b/musik/settings.py @@ -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 @@ -73,7 +71,6 @@ TEMPLATES = [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", - "musik.context_processors.version", ], }, }, @@ -144,12 +141,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) diff --git a/pyproject.toml b/pyproject.toml index 0be7cd8..7656e66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/uv.lock b/uv.lock index 21e0387..ce6f258 100644 --- a/uv.lock +++ b/uv.lock @@ -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" },