Add game app with group and music video models, views, and templates
This commit is contained in:
parent
d7545ab43c
commit
ba746c9cae
20 changed files with 315 additions and 48 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -166,4 +166,3 @@ cython_debug/
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
|
|
@ -7,3 +7,11 @@ header .container {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form a[role="button"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.ri-vip-crown-fill {
|
||||||
|
color: var(--pico-color-amber-200);
|
||||||
|
}
|
||||||
|
|
|
@ -4,25 +4,41 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}Musik{% endblock %}</title>
|
<title>
|
||||||
|
{% block title %}Musik{% endblock %}
|
||||||
|
</title>
|
||||||
<link rel="stylesheet" href="{% static "css/main.css" %}">
|
<link rel="stylesheet" href="{% static "css/main.css" %}">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.purple.min.css">
|
<link rel="stylesheet"
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.min.css">
|
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.purple.min.css">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.min.css">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a href="{% url 'index' %}">
|
<a href="{% url 'index' %}">
|
||||||
<img class="logo" src="{% static "logo.svg" %}" height="64" width="64" alt="Musik">
|
<img class="logo"
|
||||||
|
src="{% static "logo.svg" %}"
|
||||||
|
height="64"
|
||||||
|
width="64"
|
||||||
|
alt="Musik">
|
||||||
</a>
|
</a>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<li>{{ user.username }}</li>
|
<li>{{ user.username }}</li>
|
||||||
<li><form action="{% url 'logout' %}" method="post">{% csrf_token %}<input type="submit" value="Se déconnecter" class="logout"></form></li>
|
<li>
|
||||||
|
<form action="{% url 'logout' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="Se déconnecter" class="logout">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url 'login' %}" role="button">Se connecter <i class="ri-login-box-fill"></i></a></li>
|
<li>
|
||||||
|
<a href="{% url 'login' %}" role="button">Se connecter <i class="ri-login-box-fill"></i></a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
<ul class="form-errors">
|
<ul class="form-errors">
|
||||||
{% for error in form.non_field_errors %}
|
{% for error in form.non_field_errors %}<li>{{ error }}</li>{% endfor %}
|
||||||
<li>{{ error }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<form method="post" {% if action %}action="{% url action %}"{% endif %}>
|
||||||
<form method="post" action="{% url "login" %}">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
|
@ -16,9 +13,7 @@
|
||||||
</label>
|
</label>
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<ul class="form-errors">
|
<ul class="form-errors">
|
||||||
{% for error in field.errors %}
|
{% for error in field.errors %}<li>{{ error }}</li>{% endfor %}
|
||||||
<li>{{ error }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Musik</h1>
|
<h1>Musik</h1>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
{% include "game/home.html" %}
|
||||||
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load form %}
|
{% load form %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Connexion</h1>
|
<h1>Connexion</h1>
|
||||||
{% form form submit="Se connecter" %}
|
{% form form submit="Se connecter" %}
|
||||||
|
|
0
game/__init__.py
Normal file
0
game/__init__.py
Normal file
6
game/apps.py
Normal file
6
game/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class GameConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "game"
|
72
game/migrations/0001_initial.py
Normal file
72
game/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
# Generated by Django 5.2.3 on 2025-06-13 14:12
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Group",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField()),
|
||||||
|
(
|
||||||
|
"members",
|
||||||
|
models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="owned_group_set",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="MusicVideo",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("yt_id", models.CharField(max_length=16)),
|
||||||
|
("blacklisted", models.BooleanField(default=False)),
|
||||||
|
(
|
||||||
|
"group",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="game.group"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 5.2.3 on 2025-06-13 15:04
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("game", "0001_initial"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="group",
|
||||||
|
name="name",
|
||||||
|
field=models.CharField(verbose_name="Nom du groupe"),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="group",
|
||||||
|
unique_together={("name", "owner")},
|
||||||
|
),
|
||||||
|
]
|
0
game/migrations/__init__.py
Normal file
0
game/migrations/__init__.py
Normal file
24
game/models.py
Normal file
24
game/models.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
|
class Group(models.Model):
|
||||||
|
name = models.CharField(verbose_name="Nom du groupe")
|
||||||
|
owner = models.ForeignKey(
|
||||||
|
User, on_delete=models.CASCADE, related_name="owned_group_set"
|
||||||
|
)
|
||||||
|
members = models.ManyToManyField(User, blank=True)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("group_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ["name", "owner"]
|
||||||
|
|
||||||
|
|
||||||
|
class MusicVideo(models.Model):
|
||||||
|
yt_id = models.CharField(max_length=16)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||||
|
blacklisted = models.BooleanField(default=False)
|
24
game/templates/game/group_confirm_delete.html
Normal file
24
game/templates/game/group_confirm_delete.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load form %}
|
||||||
|
{% block content %}
|
||||||
|
<h1></h1>
|
||||||
|
<dialog open>
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<p>
|
||||||
|
<i class="ri-group-2-fill"></i> {{ group.name }}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
Confirmer la supression du groupe <strong>{{ group.name }}</strong> ?
|
||||||
|
</p>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit">
|
||||||
|
<i class="ri-delete-bin-fill"></i> Confirmer
|
||||||
|
</button>
|
||||||
|
<a href="{{ group.get_absolute_url }}" role="button" class="secondary"><i class="ri-close-line"></i> Annuler</a>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
</dialog>
|
||||||
|
{% endblock content %}
|
16
game/templates/game/group_detail.html
Normal file
16
game/templates/game/group_detail.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>
|
||||||
|
<i class="ri-group-2-fill"></i> {{ group.name }}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
<a href="{% url "group_update" pk=group.pk %}"><i class="ri-edit-line"></i> Modifier le groupe</a>
|
||||||
|
</p>
|
||||||
|
<h2>Membres</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
{{ group.owner }} <i class="ri-vip-crown-fill"></i>
|
||||||
|
</li>
|
||||||
|
{% for member in group.members.all %}<li>{{ member }}</li>{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock content %}
|
15
game/templates/game/group_form.html
Normal file
15
game/templates/game/group_form.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load form %}
|
||||||
|
{% block content %}
|
||||||
|
{% if group %}
|
||||||
|
<h1>
|
||||||
|
<i class="ri-group-2-fill"></i> {{ group.name }}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
<a href="{% url "group_delete" group.pk %}">Supprimer le groupe</a>
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<h1>Créer un groupe</h1>
|
||||||
|
{% endif %}
|
||||||
|
{% form form %}
|
||||||
|
{% endblock content %}
|
19
game/templates/game/home.html
Normal file
19
game/templates/game/home.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<p>Bienvenue {{ user.username }} !</p>
|
||||||
|
<h2>Mes groupes</h2>
|
||||||
|
<p>
|
||||||
|
<a href="{% url "group_create" %}" role="button"><i class="ri-add-box-fill"></i> Créer un groupe</a>
|
||||||
|
</p>
|
||||||
|
{% if user.owned_group_set.exists or user.group_set.exists %}
|
||||||
|
<ul>
|
||||||
|
{% for group in user.owned_group_set.all %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ group.get_absolute_url }}">{{ group.name }}</a> <i class="ri-vip-crown-fill"></i>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for group in user.group_set.all %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ group.get_absolute_url }}">{{ group.name }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
14
game/urls.py
Normal file
14
game/urls.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("group/create/", views.GroupCreateView.as_view(), name="group_create"),
|
||||||
|
path(
|
||||||
|
"group/<int:pk>/update/", views.GroupUpdateView.as_view(), name="group_update"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"group/<int:pk>/delete/", views.GroupDeleteView.as_view(), name="group_delete"
|
||||||
|
),
|
||||||
|
path("group/<int:pk>/", views.GroupDetailView.as_view(), name="group_detail"),
|
||||||
|
]
|
33
game/views.py
Normal file
33
game/views.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.views.generic.detail import DetailView
|
||||||
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerFilterMixin(LoginRequiredMixin):
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(owner=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
|
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(OwnerFilterMixin, GroupMixin, DetailView):
|
||||||
|
pass
|
|
@ -32,6 +32,7 @@ ALLOWED_HOSTS = []
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"base",
|
"base",
|
||||||
|
"game",
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
|
|
|
@ -20,5 +20,6 @@ from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include("base.urls")),
|
path("", include("base.urls")),
|
||||||
|
path("game/", include("game.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue