import random import google_auth_oauthlib 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.shortcuts import get_object_or_404, redirect from django.views import View from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.edit import CreateView, DeleteView, UpdateView from . import forms, models, tasks, utils 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"] = data["group"].members.annotate( count=Count( "musicvideo", filter=Q( musicvideo__group=data["group"], musicvideo__blacklisted=False ), ) ) 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(OwnerFilterMixin, SingleObjectMixin, View): model = models.Group def post(self, request, pk): 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) 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(OwnerFilterMixin, SingleObjectMixin, View): model = models.Group def post(self, request, pk): group = self.get_object() relations = models.Group.members.through.objects.filter( group=group, user__id__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 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, owner=self.request.user, pk=self.kwargs["pk"] ) return data def form_valid(self, form): group = get_object_or_404( models.Group, owner=self.request.user, pk=self.kwargs["pk"] ) 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 creds := self.request.user.youtubecredentials: form.instance.playlist_loading = True form.instance.save() tasks.generate_playlist.delay_on_commit(creds.credentials, form.instance.pk) 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() ) 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 = "https://localhost/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 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 = "https://localhost/youtube_callback/" flow.fetch_token(code=request.GET.get("code")) credentials = flow.credentials 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, } }, ) messages.add_message(request, messages.SUCCESS, "Connexion à Youtube réussie.") return redirect("/") class GroupClearBlacklistView(OwnerFilterMixin, SingleObjectMixin, View): model = models.Group def post(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é effacée.") return redirect(group)