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)