Compare commits
2 commits
43ec6aafc4
...
0859b36f98
Author | SHA1 | Date | |
---|---|---|---|
0859b36f98 | |||
245a2503e2 |
5 changed files with 147 additions and 33 deletions
|
@ -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";
|
||||||
|
}
|
||||||
|
|
|
@ -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 }}
|
||||||
</label>
|
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<ul class="form-errors">
|
<small id="{{ field.errors.field_id }}_error" class="form-error">{{ field.errors|join:", " }}</small>
|
||||||
{% for error in field.errors %}<li>{{ error }}</li>{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<input type="submit" {% if submit %}value="{{ submit }}"{% endif %}>
|
<input type="submit" {% if submit %}value="{{ submit }}"{% endif %}>
|
||||||
|
|
53
game/migrations/0013_alter_group_unique_together_and_more.py
Normal file
53
game/migrations/0013_alter_group_unique_together_and_more.py
Normal 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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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"]
|
||||||
|
|
|
@ -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(
|
||||||
|
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)
|
group.musicvideo_set.create(yt_id=yt_id, title=title, owner=request.user)
|
||||||
group.save()
|
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,
|
|
||||||
group_id=pk,
|
|
||||||
user_id=user_pk,
|
|
||||||
group__owner=request.user,
|
|
||||||
)
|
|
||||||
|
|
||||||
group = relation.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()
|
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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue