musik/game/views.py

417 lines
14 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()
)
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 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)