Compare commits

..

2 commits

5 changed files with 147 additions and 33 deletions

View file

@ -64,3 +64,9 @@ article.message {
color: var(--pico-color-red-500); color: var(--pico-color-red-500);
} }
} }
.form-error::before {
margin-right: .5em;
font-family: remixicon;
content: "\eca0";
}

View file

@ -1,8 +1,4 @@
{% if form.non_field_errors %} {% for error in form.non_field_errors %}<article class="message error">{{ error }}</article>{% endfor %}
<ul class="form-errors">
{% for error in form.non_field_errors %}<li>{{ error }}</li>{% endfor %}
</ul>
{% endif %}
<form method="post" {% if action %}action="{% url action %}"{% endif %}> <form method="post" {% if action %}action="{% url action %}"{% endif %}>
{% csrf_token %} {% csrf_token %}
<fieldset> <fieldset>
@ -10,12 +6,10 @@
<label> <label>
{{ field.label }} {{ field.label }}
{{ field }} {{ field }}
{% if field.errors %}
<small id="{{ field.errors.field_id }}_error" class="form-error">{{ field.errors|join:", " }}</small>
{% endif %}
</label> </label>
{% if field.errors %}
<ul class="form-errors">
{% for error in field.errors %}<li>{{ error }}</li>{% endfor %}
</ul>
{% endif %}
{% endfor %} {% endfor %}
</fieldset> </fieldset>
<input type="submit" {% if submit %}value="{{ submit }}"{% endif %}> <input type="submit" {% if submit %}value="{{ submit }}"{% endif %}>

View file

@ -0,0 +1,53 @@
# Generated by Django 5.2.3 on 2025-06-14 08:13
import django.db.models.functions.text
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("game", "0012_alter_musikgame_playlist"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterUniqueTogether(
name="group",
unique_together=set(),
),
migrations.AlterUniqueTogether(
name="musicgameorder",
unique_together=set(),
),
migrations.AlterUniqueTogether(
name="musicvideo",
unique_together=set(),
),
migrations.AddConstraint(
model_name="group",
constraint=models.UniqueConstraint(
django.db.models.functions.text.Lower("name"),
models.F("owner"),
name="unique_group_name",
),
),
migrations.AddConstraint(
model_name="musicgameorder",
constraint=models.UniqueConstraint(
fields=("game", "player", "music_video"), name="unique_music_in_game"
),
),
migrations.AddConstraint(
model_name="musicgameorder",
constraint=models.UniqueConstraint(
fields=("game", "order"), name="unique_order"
),
),
migrations.AddConstraint(
model_name="musicvideo",
constraint=models.UniqueConstraint(
fields=("yt_id", "owner", "group"), name="unique_music_in_group"
),
),
]

View file

@ -1,5 +1,6 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models.functions import Lower
from django.urls import reverse from django.urls import reverse
@ -19,7 +20,9 @@ class Group(models.Model):
return reverse("group_detail", kwargs={"pk": self.pk}) return reverse("group_detail", kwargs={"pk": self.pk})
class Meta: class Meta:
unique_together = ["name", "owner"] constraints = [
models.UniqueConstraint(Lower("name"), "owner", name="unique_group_name")
]
class MusicVideo(models.Model): class MusicVideo(models.Model):
@ -31,7 +34,11 @@ class MusicVideo(models.Model):
blacklisted = models.BooleanField(default=False) blacklisted = models.BooleanField(default=False)
class Meta: class Meta:
unique_together = ["yt_id", "owner", "group"] constraints = [
models.UniqueConstraint(
fields=("yt_id", "owner", "group"), name="unique_music_in_group"
)
]
class MusikGame(models.Model): class MusikGame(models.Model):
@ -52,5 +59,10 @@ class MusicGameOrder(models.Model):
order = models.PositiveIntegerField() order = models.PositiveIntegerField()
class Meta: class Meta:
unique_together = [["game", "player", "music_video"], ["game", "order"]] constraints = [
models.UniqueConstraint(
fields=("game", "player", "music_video"), name="unique_music_in_game"
),
models.UniqueConstraint(fields=("game", "order"), name="unique_order"),
]
ordering = ["order"] ordering = ["order"]

View file

@ -4,12 +4,13 @@ import google.oauth2.credentials
import google_auth_oauthlib import google_auth_oauthlib
import googleapiclient.discovery import googleapiclient.discovery
from django.conf import settings from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db import IntegrityError
from django.db.models import Count, Q from django.db.models import Count, Q
from django.http import JsonResponse from django.shortcuts import redirect
from django.shortcuts import get_object_or_404, redirect
from django.views import View from django.views import View
from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
@ -37,13 +38,22 @@ class GroupMixin:
fields = ["name"] fields = ["name"]
class GroupCreateView(LoginRequiredMixin, GroupMixin, CreateView): class GroupIntegrityMixin:
def form_valid(self, form):
try:
return super().form_valid(form)
except IntegrityError:
form.add_error("name", "Ce nom de groupe existe déjà.")
return super().form_invalid(form)
class GroupCreateView(LoginRequiredMixin, GroupMixin, GroupIntegrityMixin, CreateView):
def form_valid(self, form): def form_valid(self, form):
form.instance.owner = self.request.user form.instance.owner = self.request.user
return super().form_valid(form) return super().form_valid(form)
class GroupUpdateView(OwnerFilterMixin, GroupMixin, UpdateView): class GroupUpdateView(OwnerFilterMixin, GroupMixin, GroupIntegrityMixin, UpdateView):
pass pass
@ -79,14 +89,26 @@ class GroupAddMusicView(MemberFilterMixin, SingleObjectMixin, View):
group = self.get_object() group = self.get_object()
yt_id = request.POST.get("yt_id") yt_id = request.POST.get("yt_id")
if not yt_id: if not yt_id:
return JsonResponse({"error": "You must provide a YouTube ID."}, status=400) messages.add_message(request, messages.ERROR, "Aucun identifiant donné")
return redirect(group)
yt_id = utils.parse_musik(yt_id) yt_id = utils.parse_musik(yt_id)
title = utils.get_yt_title(yt_id) title = utils.get_yt_title(yt_id)
if not title: if not title:
return JsonResponse({"error": "Invalid YouTube ID."}, status=400) messages.add_message(
group.musicvideo_set.create(yt_id=yt_id, title=title, owner=request.user) request, messages.ERROR, f"Vidéo Youtube invalide : {yt_id}"
group.save() )
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}"
)
return redirect(group) return redirect(group)
@ -97,6 +119,15 @@ class GroupAddMemberView(OwnerFilterMixin, SingleObjectMixin, View):
group = self.get_object() group = self.get_object()
username = request.POST.get("username") username = request.POST.get("username")
user = User.objects.get(username=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) group.members.add(user)
return redirect(group) return redirect(group)
@ -112,17 +143,25 @@ class GroupRemoveMusicView(OwnerFilterMixin, SingleObjectMixin, View):
return redirect(group) return redirect(group)
class GroupRemoveMemberView(View): class GroupRemoveMemberView(OwnerFilterMixin, SingleObjectMixin, View):
def get(self, request, pk, user_pk): model = models.Group
relation = get_object_or_404(
models.Group.members.through, def get(self, request, pk, user_pk):
group_id=pk, group = self.get_object()
user_id=user_pk, user = User.objects.get(pk=user_pk)
group__owner=request.user,
) relation = models.Group.members.through.objects.filter(
group=group, user=user
).first()
if not relation:
messages.add_message(
request,
messages.ERROR,
f"L'utilisateur {user} n'est pas membre du groupe.",
)
else:
relation.delete()
group = relation.group
relation.delete()
return redirect(group) return redirect(group)
@ -137,6 +176,11 @@ class GroupRemoveGameView(SingleObjectMixin, SuccessMessageMixin, View):
game = self.get_object() game = self.get_object()
group = game.group group = game.group
game.delete() game.delete()
messages.add_message(
request,
messages.SUCCESS,
f"Le jeu du {game.date.strftime('%x')} a été supprimé avec succès.",
)
return redirect(group) return redirect(group)
@ -250,9 +294,11 @@ class YoutubeLoginView(LoginRequiredMixin, View):
class YoutubeCallbackView(LoginRequiredMixin, View): class YoutubeCallbackView(LoginRequiredMixin, View):
def get(self, request): def get(self, request):
if request.GET.get("error"): if err := request.GET.get("error"):
messages.add_message(
request, messages.ERROR, f"Échec de la connexion à Youtube : {err}"
)
return redirect("/") return redirect("/")
print(request.GET)
state = self.request.session.get("state") state = self.request.session.get("state")
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
@ -278,6 +324,8 @@ class YoutubeCallbackView(LoginRequiredMixin, View):
} }
}, },
) )
messages.add_message(request, messages.SUCCESS, "Connexion à Youtube réussie.")
return redirect("/") return redirect("/")
@ -287,4 +335,5 @@ class GroupClearBlacklistView(OwnerFilterMixin, SingleObjectMixin, View):
def get(self, request, pk): def get(self, request, pk):
group = self.get_object() group = self.get_object()
group.musicvideo_set.filter(blacklisted=True).update(blacklisted=False) group.musicvideo_set.filter(blacklisted=True).update(blacklisted=False)
messages.add_message(request, messages.SUCCESS, "La blacklist a été vidée.")
return redirect(group) return redirect(group)