import random import google.oauth2.credentials import google_auth_oauthlib import googleapiclient.discovery from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.db.models import Count, Q from django.http import JsonResponse 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, 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 GroupCreateView(LoginRequiredMixin, GroupMixin, CreateView): def form_valid(self, form): form.instance.owner = self.request.user return super().form_valid(form) class GroupUpdateView(OwnerFilterMixin, GroupMixin, UpdateView): pass class GroupDeleteView(OwnerFilterMixin, GroupMixin, DeleteView): success_url = "/" 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: return JsonResponse({"error": "You must provide a YouTube ID."}, status=400) yt_id = utils.parse_musik(yt_id) title = utils.get_yt_title(yt_id) if not title: return JsonResponse({"error": "Invalid YouTube ID."}, status=400) group.musicvideo_set.create(yt_id=yt_id, title=title, owner=request.user) group.save() 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) 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(View): def get(self, request, pk, user_pk): relation = get_object_or_404( models.Group.members.through, group_id=pk, user_id=user_pk, group__owner=request.user, ) group = relation.group relation.delete() return redirect(group) class GroupRemoveGameView(SingleObjectMixin, View): model = models.MusikGame 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() 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 request.GET.get("error"): return redirect("/") print(request.GET) 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, } }, ) 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) return redirect(group)