Refactor music video model and views: rename user to owner, add title field, and implement music management in group detail view
This commit is contained in:
parent
8ed39c78b8
commit
4e28311b1c
11 changed files with 272 additions and 8 deletions
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.2.3 on 2025-06-13 16:21
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("game", "0002_alter_group_name_alter_group_unique_together"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="musicvideo",
|
||||
old_name="user",
|
||||
new_name="owner",
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="musicvideo",
|
||||
unique_together={("yt_id", "owner", "group")},
|
||||
),
|
||||
]
|
17
game/migrations/0004_musicvideo_title.py
Normal file
17
game/migrations/0004_musicvideo_title.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 5.2.3 on 2025-06-13 16:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("game", "0003_rename_user_musicvideo_owner_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="musicvideo",
|
||||
name="title",
|
||||
field=models.CharField(blank=True),
|
||||
),
|
||||
]
|
|
@ -19,6 +19,10 @@ class Group(models.Model):
|
|||
|
||||
class MusicVideo(models.Model):
|
||||
yt_id = models.CharField(max_length=16)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
title = models.CharField(blank=True)
|
||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
blacklisted = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["yt_id", "owner", "group"]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% load form %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
<i class="ri-group-2-fill"></i> {{ group.name }}
|
||||
|
@ -12,8 +13,50 @@
|
|||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
{{ group.owner }} <i class="ri-vip-crown-fill"></i>
|
||||
{{ group.owner }} ({{ owner_count }}) <i class="ri-vip-crown-fill"></i>
|
||||
</li>
|
||||
{% for member in group.members.all %}<li>{{ member }}</li>{% endfor %}
|
||||
{% for member in members.all %}<li>{{ member }} ({{ member.count }})</li>{% endfor %}
|
||||
</ul>
|
||||
{% endblock content %}
|
||||
<h2>Mes musiques ({{ musics.count }})</h2>
|
||||
<table class="striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Musique</th>
|
||||
<th>ID</th>
|
||||
<th>
|
||||
<i class="ri-history-fill"></i>
|
||||
</th>
|
||||
<th>
|
||||
<i class="ri-delete-bin-fill"></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for music in musics %}
|
||||
<tr>
|
||||
<th>{{ music.title }}</th>
|
||||
<td>
|
||||
<a href="https://youtu.be/{{ music.yt_id }}">{{ music.yt_id }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" disabled {% if music.blacklisted %}checked{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url "group_remove_music" pk=music.pk %}"><i class="ri-close-fill" alt="Supprimer"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="2">Aucune musique.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<form method="post" action="{% url "group_add_music" pk=group.pk %}">
|
||||
{% csrf_token %}
|
||||
<fieldset role="group">
|
||||
<input type="string" name="yt_id" id="yt_id" placeholder="Musique" required>
|
||||
<button type="submit">Ajouter</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
|
10
game/urls.py
10
game/urls.py
|
@ -16,4 +16,14 @@ urlpatterns = [
|
|||
name="group_edit_members",
|
||||
),
|
||||
path("group/<int:pk>/", views.GroupDetailView.as_view(), name="group_detail"),
|
||||
path(
|
||||
"group/<int:pk>/add_music/",
|
||||
views.GroupAddMusicView.as_view(),
|
||||
name="group_add_music",
|
||||
),
|
||||
path(
|
||||
"group/remove_music/<int:pk>/",
|
||||
views.GroupRemoveMusicView.as_view(),
|
||||
name="group_remove_music",
|
||||
),
|
||||
]
|
||||
|
|
35
game/utils.py
Normal file
35
game/utils.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def parse_musik(raw):
|
||||
if "youtube.com" in raw:
|
||||
return parse_qs(urlparse(raw).query).get("v", [None])[0]
|
||||
elif "youtu.be" in raw:
|
||||
return urlparse(raw).path[1:]
|
||||
|
||||
return raw
|
||||
|
||||
|
||||
def get_yt_title(yt_id):
|
||||
if not yt_id:
|
||||
return None
|
||||
req = requests.get(
|
||||
"https://www.googleapis.com/youtube/v3/videos",
|
||||
params={
|
||||
"part": "snippet",
|
||||
"id": yt_id,
|
||||
"key": settings.YOUTUBE_API_KEY,
|
||||
},
|
||||
)
|
||||
|
||||
if req.status_code != 200:
|
||||
raise Exception(f"Error fetching YouTube video: {req.status_code} {req.text}")
|
||||
|
||||
data = req.json()
|
||||
if not data.get("items"):
|
||||
return None
|
||||
|
||||
return data["items"][0]["snippet"]["title"]
|
|
@ -1,8 +1,12 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.db.models import Count, Q
|
||||
from django.http import JsonResponse
|
||||
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
|
||||
from . import forms, models, utils
|
||||
|
||||
|
||||
class OwnerFilterMixin(LoginRequiredMixin):
|
||||
|
@ -30,9 +34,48 @@ class GroupDeleteView(OwnerFilterMixin, GroupMixin, DeleteView):
|
|||
|
||||
|
||||
class GroupDetailView(OwnerFilterMixin, GroupMixin, DetailView):
|
||||
pass
|
||||
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).count()
|
||||
)
|
||||
data["members"] = data["group"].members.annotate(
|
||||
count=Count("musicvideo", filter=Q(group=data["group"]))
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class GroupAddMembersView(OwnerFilterMixin, GroupMixin, UpdateView):
|
||||
fields = None
|
||||
form_class = forms.GroupAddMembersForm
|
||||
|
||||
|
||||
class GroupAddMusicView(OwnerFilterMixin, 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 GroupRemoveMusicView(OwnerFilterMixin, SingleObjectMixin, View):
|
||||
model = models.MusicVideo
|
||||
|
||||
def get(self, request, pk):
|
||||
music = self.get_object()
|
||||
group = music.group
|
||||
music.delete()
|
||||
return redirect(group)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue