472 lines
16 KiB
Python
472 lines
16 KiB
Python
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)
|
|
|
|
|
|
class MemberFilterMixin(LoginRequiredMixin):
|
|
def get_queryset(self):
|
|
return (
|
|
super()
|
|
.get_queryset()
|
|
.filter(Q(members=self.request.user) | Q(owner=self.request.user))
|
|
.distinct()
|
|
)
|
|
|
|
|
|
class GroupMixin:
|
|
model = models.Group
|
|
fields = ["name"]
|
|
|
|
|
|
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, GroupIntegrityMixin, UpdateView):
|
|
pass
|
|
|
|
|
|
class GroupDeleteView(OwnerFilterMixin, GroupMixin, SuccessMessageMixin, DeleteView):
|
|
success_url = "/"
|
|
success_message = "Le groupe a été supprimé avec succès."
|
|
|
|
|
|
class GroupDetailView(MemberFilterMixin, GroupMixin, DetailView):
|
|
def get_context_data(self, **kwargs):
|
|
data = super().get_context_data(**kwargs)
|
|
|
|
data["musics"] = data["group"].musicvideo_set.filter(owner=self.request.user)
|
|
data["owner_count"] = (
|
|
data["group"]
|
|
.musicvideo_set.filter(owner=data["group"].owner, blacklisted=False)
|
|
.count()
|
|
)
|
|
data["members"] = models.Group.members.through.objects.filter(
|
|
group=data["group"]
|
|
).annotate(
|
|
count=Count(
|
|
"user__musicvideo",
|
|
filter=Q(
|
|
user__musicvideo__group=data["group"],
|
|
user__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
|
|
|
|
|
|
class GroupAddMusicView(MemberFilterMixin, SingleObjectMixin, View):
|
|
model = models.Group
|
|
|
|
def post(self, request, pk):
|
|
group = self.get_object()
|
|
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}"
|
|
)
|
|
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)
|
|
|
|
|
|
class GroupAddMemberView(MemberFilterMixin, SingleObjectMixin, View):
|
|
model = models.Group
|
|
|
|
def post(self, request, pk):
|
|
group = self.get_object()
|
|
if not group.is_leader(request.user):
|
|
raise PermissionDenied()
|
|
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)
|
|
|
|
|
|
class GroupRemoveMusicView(MemberFilterMixin, SingleObjectMixin, View):
|
|
model = models.Group
|
|
|
|
def post(self, request, pk):
|
|
group = self.get_object()
|
|
musics = group.musicvideo_set.filter(
|
|
owner=request.user, pk__in=request.POST.getlist("musics")
|
|
)
|
|
|
|
if musics.count() == 0:
|
|
messages.add_message(request, messages.INFO, "Aucune musique supprimée.")
|
|
return redirect(group)
|
|
if musics.count() != len(request.POST.getlist("musics")):
|
|
messages.add_message(
|
|
request,
|
|
messages.WARNING,
|
|
"Certaines musiques n'ont pas pu être supprimées.",
|
|
)
|
|
musics.delete()
|
|
else:
|
|
musics.delete()
|
|
messages.add_message(
|
|
request,
|
|
messages.SUCCESS,
|
|
"Les musiques sélectionnées ont été supprimées.",
|
|
)
|
|
return redirect(group)
|
|
|
|
|
|
class GroupUnblacklistMusicView(MemberFilterMixin, SingleObjectMixin, View):
|
|
model = models.Group
|
|
|
|
def post(self, request, pk):
|
|
group = self.get_object()
|
|
musics = group.musicvideo_set.filter(
|
|
owner=request.user, pk__in=request.POST.getlist("musics")
|
|
)
|
|
|
|
musics.update(blacklisted=False)
|
|
messages.add_message(
|
|
request,
|
|
messages.SUCCESS,
|
|
"Les musiques sélectionnées ont été enlevées de la blacklist.",
|
|
)
|
|
return redirect(group)
|
|
|
|
|
|
class GroupRemoveMemberView(MemberFilterMixin, 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")
|
|
)
|
|
if relations.count() == 0:
|
|
messages.add_message(request, messages.INFO, "Aucun membre supprimé.")
|
|
return redirect(group)
|
|
if relations.count() != len(request.POST.getlist("member")):
|
|
messages.add_message(
|
|
request,
|
|
messages.WARNING,
|
|
"Certains membres n'ont pas pu être supprimés.",
|
|
)
|
|
relations.delete()
|
|
else:
|
|
relations.delete()
|
|
messages.add_message(
|
|
request,
|
|
messages.SUCCESS,
|
|
"Les membres sélectionnés ont été supprimés.",
|
|
)
|
|
|
|
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
|
|
|
|
def post(self, request, pk):
|
|
group = self.get_object()
|
|
|
|
games = group.musikgame_set.filter(pk__in=request.POST.getlist("game"))
|
|
|
|
if games.count() == 0:
|
|
messages.add_message(request, messages.INFO, "Aucune partie supprimé.")
|
|
return redirect(group)
|
|
if games.count() != len(request.POST.getlist("game")):
|
|
messages.add_message(
|
|
request,
|
|
messages.WARNING,
|
|
"Certaines parties n'ont pas pu être supprimées.",
|
|
)
|
|
games.delete()
|
|
else:
|
|
games.delete()
|
|
messages.add_message(
|
|
request,
|
|
messages.SUCCESS,
|
|
"Les parties sélectionnées ont été supprimées.",
|
|
)
|
|
|
|
return redirect(group)
|
|
|
|
|
|
class GameCreateView(LoginRequiredMixin, CreateView):
|
|
model = models.MusikGame
|
|
form_class = forms.MusikGameForm
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["group"] = self.kwargs["pk"]
|
|
return kwargs
|
|
|
|
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()
|
|
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)
|
|
form.instance.group = group
|
|
res = super().form_valid(form)
|
|
players = []
|
|
musics = []
|
|
n = form.instance.n
|
|
for player in form.instance.players.all():
|
|
music_set = player.musicvideo_set.filter(group=group, blacklisted=False)
|
|
if music_set.count() < n:
|
|
form.instance.delete()
|
|
form.add_error("n", f"{player} n'a pas assez de musiques.")
|
|
return super().form_invalid(form)
|
|
players += n * [player]
|
|
musics += random.sample(
|
|
list(music_set.all()),
|
|
n,
|
|
)
|
|
|
|
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()
|
|
return res
|
|
|
|
|
|
class GameDetailView(LoginRequiredMixin, DetailView):
|
|
model = models.MusikGame
|
|
|
|
def get_queryset(self):
|
|
return (
|
|
super()
|
|
.get_queryset()
|
|
.filter(
|
|
Q(group__members=self.request.user) | Q(group__owner=self.request.user)
|
|
)
|
|
.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):
|
|
if err := request.GET.get("error"):
|
|
messages.add_message(
|
|
request, messages.ERROR, f"Échec de la connexion à Youtube : {err}"
|
|
)
|
|
return redirect("/")
|
|
|
|
state = self.request.session.get("state")
|
|
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
|
settings.YOUTUBE_OAUTH_SECRETS,
|
|
["https://www.googleapis.com/auth/youtube.force-ssl"],
|
|
state=state,
|
|
)
|
|
flow.redirect_uri = request.build_absolute_uri("/youtube_callback/")
|
|
|
|
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={
|
|
"credentials": {
|
|
"token": credentials.token,
|
|
"refresh_token": credentials.refresh_token,
|
|
"token_uri": credentials.token_uri,
|
|
"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("/")
|
|
|
|
|
|
class YoutubeLoginView(LoginRequiredMixin, View):
|
|
def get(self, request):
|
|
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
|
settings.YOUTUBE_OAUTH_SECRETS,
|
|
["https://www.googleapis.com/auth/youtube.force-ssl"],
|
|
)
|
|
flow.redirect_uri = request.build_absolute_uri("/youtube_callback/")
|
|
auth_url, state = flow.authorization_url(
|
|
access_type="offline",
|
|
include_granted_scopes="true",
|
|
prompt="consent",
|
|
)
|
|
self.request.session["state"] = state
|
|
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):
|
|
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(over=False, 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()
|
|
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 post(self, request, pk):
|
|
game = self.get_object()
|
|
if not game.group.is_leader(request.user):
|
|
raise PermissionDenied()
|
|
game.over = True
|
|
game.save()
|
|
return redirect("game_detail", pk)
|