diff --git a/.gitignore b/.gitignore index c6f515c..1af379d 100644 --- a/.gitignore +++ b/.gitignore @@ -166,4 +166,3 @@ cython_debug/ # 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. #.idea/ - diff --git a/base/static/css/main.css b/base/static/css/main.css index 0b47044..e8527d1 100644 --- a/base/static/css/main.css +++ b/base/static/css/main.css @@ -7,3 +7,11 @@ header .container { align-items: center; justify-content: space-between; } + +form a[role="button"] { + width: 100%; +} + +i.ri-vip-crown-fill { + color: var(--pico-color-amber-200); +} diff --git a/base/templates/base.html b/base/templates/base.html index 5d38174..49f52bf 100644 --- a/base/templates/base.html +++ b/base/templates/base.html @@ -1,36 +1,52 @@ {% load static %} - - - - {% block title %}Musik{% endblock %} - - - - - - -
-
- - - - -
-
-
- {% block content %} - {% endblock content %} -
- + + + + + {% block title %}Musik{% endblock %} + + + + + + + +
+
+ + + + +
+
+
+ {% block content %} + {% endblock content %} +
+ diff --git a/base/templates/base/form.html b/base/templates/base/form.html index 57d5486..df40bc7 100644 --- a/base/templates/base/form.html +++ b/base/templates/base/form.html @@ -1,12 +1,9 @@ {% if form.non_field_errors %} - + {% endif %} - -
+ {% csrf_token %}
{% for field in form %} @@ -16,9 +13,7 @@ {% if field.errors %} {% endif %} {% endfor %} diff --git a/base/templates/index.html b/base/templates/index.html index 64383d8..42c83ec 100644 --- a/base/templates/index.html +++ b/base/templates/index.html @@ -1,5 +1,7 @@ {% extends "base.html" %} - {% block content %} -

Musik

+

Musik

+ {% if user.is_authenticated %} + {% include "game/home.html" %} + {% endif %} {% endblock content %} diff --git a/base/templates/registration/login.html b/base/templates/registration/login.html index f699914..a41b5aa 100644 --- a/base/templates/registration/login.html +++ b/base/templates/registration/login.html @@ -1,7 +1,6 @@ {% extends "base.html" %} {% load form %} - {% block content %} -

Connexion

-{% form form submit="Se connecter" %} +

Connexion

+ {% form form submit="Se connecter" %} {% endblock content %} diff --git a/game/__init__.py b/game/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game/apps.py b/game/apps.py new file mode 100644 index 0000000..3ee5205 --- /dev/null +++ b/game/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GameConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "game" diff --git a/game/migrations/0001_initial.py b/game/migrations/0001_initial.py new file mode 100644 index 0000000..5cf8035 --- /dev/null +++ b/game/migrations/0001_initial.py @@ -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, + ), + ), + ], + ), + ] diff --git a/game/migrations/0002_alter_group_name_alter_group_unique_together.py b/game/migrations/0002_alter_group_name_alter_group_unique_together.py new file mode 100644 index 0000000..abcb37b --- /dev/null +++ b/game/migrations/0002_alter_group_name_alter_group_unique_together.py @@ -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")}, + ), + ] diff --git a/game/migrations/__init__.py b/game/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game/models.py b/game/models.py new file mode 100644 index 0000000..ea80626 --- /dev/null +++ b/game/models.py @@ -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) diff --git a/game/templates/game/group_confirm_delete.html b/game/templates/game/group_confirm_delete.html new file mode 100644 index 0000000..696227f --- /dev/null +++ b/game/templates/game/group_confirm_delete.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% load form %} +{% block content %} +

+ +
+
+

+ {{ group.name }} +

+
+

+ Confirmer la supression du groupe {{ group.name }} ? +

+ + {% csrf_token %} + + Annuler + +
+
+{% endblock content %} diff --git a/game/templates/game/group_detail.html b/game/templates/game/group_detail.html new file mode 100644 index 0000000..8654554 --- /dev/null +++ b/game/templates/game/group_detail.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% block content %} +

+ {{ group.name }} +

+

+ Modifier le groupe +

+

Membres

+ +{% endblock content %} diff --git a/game/templates/game/group_form.html b/game/templates/game/group_form.html new file mode 100644 index 0000000..67f1f05 --- /dev/null +++ b/game/templates/game/group_form.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% load form %} +{% block content %} + {% if group %} +

+ {{ group.name }} +

+

+ Supprimer le groupe +

+ {% else %} +

Créer un groupe

+ {% endif %} + {% form form %} +{% endblock content %} diff --git a/game/templates/game/home.html b/game/templates/game/home.html new file mode 100644 index 0000000..d2c045b --- /dev/null +++ b/game/templates/game/home.html @@ -0,0 +1,19 @@ +

Bienvenue {{ user.username }} !

+

Mes groupes

+

+ Créer un groupe +

+{% if user.owned_group_set.exists or user.group_set.exists %} + +{% endif %} diff --git a/game/urls.py b/game/urls.py new file mode 100644 index 0000000..28bc1d6 --- /dev/null +++ b/game/urls.py @@ -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//update/", views.GroupUpdateView.as_view(), name="group_update" + ), + path( + "group//delete/", views.GroupDeleteView.as_view(), name="group_delete" + ), + path("group//", views.GroupDetailView.as_view(), name="group_detail"), +] diff --git a/game/views.py b/game/views.py new file mode 100644 index 0000000..05fdfe7 --- /dev/null +++ b/game/views.py @@ -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 diff --git a/musik/settings.py b/musik/settings.py index 9c3c277..10439ce 100644 --- a/musik/settings.py +++ b/musik/settings.py @@ -32,6 +32,7 @@ ALLOWED_HOSTS = [] INSTALLED_APPS = [ "base", + "game", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", diff --git a/musik/urls.py b/musik/urls.py index b10d442..946e05d 100644 --- a/musik/urls.py +++ b/musik/urls.py @@ -20,5 +20,6 @@ from django.urls import include, path urlpatterns = [ path("", include("base.urls")), + path("game/", include("game.urls")), path("admin/", admin.site.urls), ]