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);
}
}
.form-error::before {
margin-right: .5em;
font-family: remixicon;
content: "\eca0";
}

View file

@ -1,8 +1,4 @@
{% if form.non_field_errors %}
<ul class="form-errors">
{% for error in form.non_field_errors %}<li>{{ error }}</li>{% endfor %}
</ul>
{% endif %}
{% 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>
@ -10,12 +6,10 @@
<label>
{{ field.label }}
{{ field }}
{% if field.errors %}
<small id="{{ field.errors.field_id }}_error" class="form-error">{{ field.errors|join:", " }}</small>
{% endif %}
</label>
{% if field.errors %}
<ul class="form-errors">
{% for error in field.errors %}<li>{{ error }}</li>{% endfor %}
</ul>
{% endif %}
{% endfor %}
</fieldset>
<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.db import models
from django.db.models.functions import Lower
from django.urls import reverse
@ -19,7 +20,9 @@ class Group(models.Model):
return reverse("group_detail", kwargs={"pk": self.pk})
class Meta:
unique_together = ["name", "owner"]
constraints = [
models.UniqueConstraint(Lower("name"), "owner", name="unique_group_name")
]
class MusicVideo(models.Model):
@ -31,7 +34,11 @@ class MusicVideo(models.Model):
blacklisted = models.BooleanField(default=False)
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):
@ -52,5 +59,10 @@ class MusicGameOrder(models.Model):
order = models.PositiveIntegerField()
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"]

View file

@ -4,12 +4,13 @@ import google.oauth2.credentials
import google_auth_oauthlib
import googleapiclient.discovery
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.db import IntegrityError
from django.db.models import Count, Q
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import redirect
from django.views import View
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.generic.edit import CreateView, DeleteView, UpdateView
@ -37,13 +38,22 @@ class GroupMixin:
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):
form.instance.owner = self.request.user
return super().form_valid(form)
class GroupUpdateView(OwnerFilterMixin, GroupMixin, UpdateView):
class GroupUpdateView(OwnerFilterMixin, GroupMixin, GroupIntegrityMixin, UpdateView):
pass
@ -79,14 +89,26 @@ class GroupAddMusicView(MemberFilterMixin, SingleObjectMixin, View):
group = self.get_object()
yt_id = request.POST.get("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)
title = utils.get_yt_title(yt_id)
if not title:
return JsonResponse({"error": "Invalid YouTube ID."}, status=400)
group.musicvideo_set.create(yt_id=yt_id, title=title, owner=request.user)
group.save()
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}"
)
return redirect(group)
@ -97,6 +119,15 @@ class GroupAddMemberView(OwnerFilterMixin, SingleObjectMixin, View):
group = self.get_object()
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)
@ -112,17 +143,25 @@ class GroupRemoveMusicView(OwnerFilterMixin, SingleObjectMixin, View):
return redirect(group)
class GroupRemoveMemberView(View):
def get(self, request, pk, user_pk):
relation = get_object_or_404(
models.Group.members.through,
group_id=pk,
user_id=user_pk,
group__owner=request.user,
)
class GroupRemoveMemberView(OwnerFilterMixin, SingleObjectMixin, View):
model = models.Group
def get(self, request, pk, user_pk):
group = self.get_object()
user = User.objects.get(pk=user_pk)
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)
@ -137,6 +176,11 @@ class GroupRemoveGameView(SingleObjectMixin, SuccessMessageMixin, View):
game = self.get_object()
group = game.group
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)
@ -250,9 +294,11 @@ class YoutubeLoginView(LoginRequiredMixin, View):
class YoutubeCallbackView(LoginRequiredMixin, View):
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("/")
print(request.GET)
state = self.request.session.get("state")
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("/")
@ -287,4 +335,5 @@ class GroupClearBlacklistView(OwnerFilterMixin, SingleObjectMixin, View):
def get(self, request, pk):
group = self.get_object()
group.musicvideo_set.filter(blacklisted=True).update(blacklisted=False)
messages.add_message(request, messages.SUCCESS, "La blacklist a été vidée.")
return redirect(group)