import random 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.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 from . import forms, models, 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(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(OwnerFilterMixin, SingleObjectMixin, View): model = models.MusicVideo def get(self, request, pk): music = self.get_object() group = music.group music.delete() return redirect(group) 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() return redirect(group) class GroupRemoveGameView(SingleObjectMixin, SuccessMessageMixin, View): model = models.MusikGame success_message = "Le jeu du %(date)s a été supprimé avec succès." def get_queryset(self): return super().get_queryset().filter(group__owner=self.request.user) def get(self, request, pk): 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) 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"] = models.Group.objects.filter(owner=self.request.user).get( pk=self.kwargs["pk"] ) return data def form_valid(self, form): group = models.Group.objects.get(pk=self.kwargs["pk"]) form.instance.group = group res = super().form_valid(form) players = [] musics = [] for player in form.instance.players.all(): players += 2 * [player] musics += random.sample( list( player.musicvideo_set.filter(group=group, blacklisted=False).all() ), form.instance.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: credentials = google.oauth2.credentials.Credentials(**creds.credentials) yt_api = googleapiclient.discovery.build( "youtube", "v3", credentials=credentials ) pl_request = yt_api.playlists().insert( part="snippet,status", body={ "snippet": { "title": f"Musik – {group.name} – {form.instance.date.strftime('%x')}", "description": "Playlist générée par Musik", }, "status": { "privacyStatus": "private", }, }, ) pl_response = pl_request.execute() pl_id = pl_response.get("id") form.instance.playlist = pl_id form.instance.save() for _, music in pm_list: request = yt_api.playlistItems().insert( part="snippet", body={ "snippet": { "playlistId": pl_id, "resourceId": { "kind": "youtube#video", "videoId": music.yt_id, }, } }, ) request.execute() 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 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)