From c7a08b3d3ca8dc48b845cdfa667a871fbb10cfdb Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 19 Oct 2025 17:59:46 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20mod=C3=A8le=20Chant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- beesgospel/admin.py | 10 +++++++- beesgospel/forms.py | 8 ++++++- beesgospel/migrations/0006_chant.py | 25 +++++++++++++++++++ beesgospel/models.py | 20 ++++++++++++++++ beesgospel/static/css/main.css | 27 +++++++++++++++++++-- beesgospel/static/js/main.js | 14 +++++++++++ beesgospel/views.py | 37 +++++++++++++++++++++++++++-- common/settings.py | 9 +++++++ common/urls.py | 4 ++++ templates/base.html | 12 ++++++++++ templates/membres/chant_edit.html | 10 ++++++++ templates/membres/index.html | 26 ++++++++++++++++---- templates/membres/liste.html | 2 +- templates/membres/liste_chants.html | 27 +++++++++++++++++++++ 14 files changed, 220 insertions(+), 11 deletions(-) create mode 100644 beesgospel/migrations/0006_chant.py create mode 100644 beesgospel/static/js/main.js create mode 100644 templates/membres/chant_edit.html create mode 100644 templates/membres/liste_chants.html diff --git a/beesgospel/admin.py b/beesgospel/admin.py index afcc0ed..b21d25d 100644 --- a/beesgospel/admin.py +++ b/beesgospel/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin -from beesgospel.models import Agenda, Document, Membre, User +from beesgospel.models import Agenda, Chant, Document, Membre, User @admin.register(Agenda) @@ -25,6 +25,14 @@ class MembreAdmin(admin.ModelAdmin): ordering = ["nom"] +@admin.register(Chant) +class ChantAdmin(admin.ModelAdmin): + list_display = ["numero", "titre", "particularite", "statut"] + list_filter = ["statut"] + search_fields = ["titre"] + ordering = ["numero"] + + @admin.register(User) class UserAdmin(UserAdmin): list_display = ["email", "is_active", "is_staff", "last_login"] diff --git a/beesgospel/forms.py b/beesgospel/forms.py index d09bfb6..a0c5d67 100644 --- a/beesgospel/forms.py +++ b/beesgospel/forms.py @@ -4,7 +4,7 @@ from django import forms from django.contrib.auth import forms as auth_forms from django.db import transaction -from .models import Membre, User +from .models import Chant, Membre, User class BootstrapMixin: @@ -53,3 +53,9 @@ class MembreEditForm(BootstrapMixin, forms.ModelForm): self.instance.user.email = self.cleaned_data["courriel"] self.instance.user.save() return super().save(**kwargs) + + +class ChantEditForm(BootstrapMixin, forms.ModelForm): + class Meta: + model = Chant + fields = ["numero", "titre", "particularite"] diff --git a/beesgospel/migrations/0006_chant.py b/beesgospel/migrations/0006_chant.py new file mode 100644 index 0000000..b8af783 --- /dev/null +++ b/beesgospel/migrations/0006_chant.py @@ -0,0 +1,25 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('beesgospel', '0005_agenda_infos_interne'), + ] + + operations = [ + migrations.CreateModel( + name='Chant', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('numero', models.PositiveSmallIntegerField(verbose_name='Numéro')), + ('titre', models.CharField(max_length=100, verbose_name='Titre')), + ('particularite', models.CharField(blank=True, max_length=100, verbose_name='Particularité')), + ('statut', models.CharField(choices=[('actif', 'Actif'), ('prep', 'En préparation'), ('inactif', 'Inactif')], default='actif', max_length=10, verbose_name='Statut')), + ], + ), + migrations.AddConstraint( + model_name='chant', + constraint=models.UniqueConstraint(models.F('numero'), name='numero_unique'), + ), + ] diff --git a/beesgospel/models.py b/beesgospel/models.py index 50eb484..3457239 100644 --- a/beesgospel/models.py +++ b/beesgospel/models.py @@ -98,3 +98,23 @@ class Document(models.Model): return self.url elif self.fichier: return self.fichier.url + + +class Chant(models.Model): + class StatutChoices(models.TextChoices): + ACTIF = "actif", "Actif" + PREP = "prep", "En préparation" + INACTIF = "inactif", "Inactif" + + numero = models.PositiveSmallIntegerField("Numéro") + titre = models.CharField("Titre", max_length=100) + particularite = models.CharField("Particularité", max_length=100, blank=True) + statut = models.CharField("Statut", max_length=10, choices=StatutChoices, default="actif") + + class Meta: + constraints = [ + models.UniqueConstraint("numero", name="numero_unique"), + ] + + def __str__(self): + return f"{self.numero}. {self.titre}" diff --git a/beesgospel/static/css/main.css b/beesgospel/static/css/main.css index b42b394..77bcfe4 100644 --- a/beesgospel/static/css/main.css +++ b/beesgospel/static/css/main.css @@ -44,6 +44,15 @@ nav { @media only screen and (max-width : 500px) { img#abeille { width: 75px; } } +.errorlist { list-style: none; padding-left: 0 !important; } +.errorlist li { + padding: .75rem 1.25rem; + margin-bottom: 1rem; + border-radius: .25rem; + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} .alert-danger { background-color: #FFD79C; } .main-text { max-width: 100vw; } .red-bottom { border-bottom: 1px solid red; } @@ -86,8 +95,8 @@ nav { } .portrait { max-width: 13rem; } -tr.editable .edit-button { display: none; } -tr.editable:hover .edit-button { display: inline-block; } +tr.editable .edit-button, tr.editable .delete-button { display: none; } +tr.editable:hover .edit-button, tr.editable:hover .delete-button { display: inline-block; } .agenda_line { margin-right: 7em !important; } .agenda_container > div:first-of-type { margin-top: 7em !important; } @@ -104,3 +113,17 @@ tr.editable:hover .edit-button { display: inline-block; } color: red; font-style: italic; } +.card-membres { + background-color: #b56a4d; + height: 6em; +} +.card-membres:hover { + background-color: #999; +} +.card-membres a { + color: white; + text-decoration: none; + height: 100%; +} +table.table-chants th.numero { width: 3em; } +table.table-chants th.boutons { width: 3em; } diff --git a/beesgospel/static/js/main.js b/beesgospel/static/js/main.js new file mode 100644 index 0000000..cbd6606 --- /dev/null +++ b/beesgospel/static/js/main.js @@ -0,0 +1,14 @@ +'use strict'; + +function attachHandlerSelector(section, selector, event, func) { + section.querySelectorAll(selector).forEach(item => { + item.addEventListener(event, func); + }); +} + +window.addEventListener('DOMContentLoaded', () => { + attachHandlerSelector(document, 'button.delete-button', 'click', (ev) => { + let resp = confirm("Voulez-vous vraiment supprimer cette ligne ?"); + if (!resp) { ev.preventDefault(); ev.stopImmediatePropagation(); } + }); +}) diff --git a/beesgospel/views.py b/beesgospel/views.py index d9c993f..53b26a2 100644 --- a/beesgospel/views.py +++ b/beesgospel/views.py @@ -1,12 +1,13 @@ from datetime import date, timedelta +from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.urls import reverse_lazy from django.views.generic import ( CreateView, DeleteView, ListView, TemplateView, UpdateView ) -from .forms import MembreEditForm -from .models import Agenda, Document, Membre +from .forms import ChantEditForm, MembreEditForm +from .models import Agenda, Chant, Document, Membre class HomeView(TemplateView): @@ -103,5 +104,37 @@ class MembreDeleteView(PermissionRequiredMixin, DeleteView): success_url = reverse_lazy("liste-membres") def form_valid(self, form): + msg = f"{self.object} a bien été effacé·e de la liste" self.object.user.delete() + messages.success(self.request, msg) return super().form_valid(form) + + +class ListeChantsView(LoginRequiredMixin, ListView): + model = Chant + template_name = "membres/liste_chants.html" + + def get_queryset(self): + return super().get_queryset().order_by("numero") + + +class ChantAddView(PermissionRequiredMixin, CreateView): + model = Chant + form_class = ChantEditForm + permission_required = "beesgospel.add_chant" + template_name = "membres/chant_edit.html" + success_url = reverse_lazy("liste-chants") + + +class ChantEditView(PermissionRequiredMixin, UpdateView): + model = Chant + form_class = ChantEditForm + permission_required = "beesgospel.change_chant" + template_name = "membres/chant_edit.html" + success_url = reverse_lazy("liste-chants") + + +class ChantDeleteView(PermissionRequiredMixin, DeleteView): + model = Chant + permission_required = "beesgospel.delete_chant" + success_url = reverse_lazy("liste-chants") diff --git a/common/settings.py b/common/settings.py index cb69fb6..48fbd3a 100644 --- a/common/settings.py +++ b/common/settings.py @@ -4,6 +4,7 @@ Django settings for beesgospel project. from pathlib import Path +from django.contrib.messages import constants from django.urls import reverse_lazy # Build paths inside the project like this: BASE_DIR / "subdir". @@ -113,6 +114,14 @@ STATIC_ROOT = BASE_DIR / "static" MEDIA_URL = "media/" MEDIA_ROOT = BASE_DIR / "media" +MESSAGE_TAGS = { + constants.DEBUG: "alert-info", + constants.INFO: "alert-info", + constants.SUCCESS: "alert-success", + constants.WARNING: "alert-warning", + constants.ERROR: "alert-danger", +} + LOGOUT_REDIRECT_URL = reverse_lazy("presentation") DEFAULT_FROM_EMAIL = "webmaster@2xlibre.net" diff --git a/common/urls.py b/common/urls.py index 2ace8e4..c6a9fbf 100644 --- a/common/urls.py +++ b/common/urls.py @@ -22,5 +22,9 @@ urlpatterns = [ path("membres//edit/", views.MembreEditView.as_view(), name="membre-edit"), path("membres//delete/", views.MembreDeleteView.as_view(), name="membre-delete"), path("membres/liste/", views.ListeMembresView.as_view(), name="liste-membres"), + path("membres/chants/", views.ListeChantsView.as_view(), name="liste-chants"), + path("membres/chants/nouveau/", views.ChantAddView.as_view(), name="chant-add"), + path("membres/chants//edit/", views.ChantEditView.as_view(), name="chant-edit"), + path("membres/chants//delete/", views.ChantDeleteView.as_view(), name="chant-delete"), path("membres/documents/", views.MediaView.as_view(prive=True), name="docs-membres"), ] diff --git a/templates/base.html b/templates/base.html index 5f499df..d17b716 100644 --- a/templates/base.html +++ b/templates/base.html @@ -8,6 +8,7 @@ + {% block header %} @@ -39,6 +40,17 @@ {% endblock header %}
+ {% if messages %} +
    + {% for message in messages %} + + {% endfor %} +
+ {% endif %} + {% block content %}{% endblock %}
diff --git a/templates/membres/chant_edit.html b/templates/membres/chant_edit.html new file mode 100644 index 0000000..bb439d2 --- /dev/null +++ b/templates/membres/chant_edit.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +

Édition/ajout de chant

+ +
{% csrf_token %} + {{ form.as_div }} +
+
+{% endblock content %} diff --git a/templates/membres/index.html b/templates/membres/index.html index 1d53109..1df7725 100644 --- a/templates/membres/index.html +++ b/templates/membres/index.html @@ -5,11 +5,29 @@
{% csrf_token %}

Espace membres

-
- - +
+ + + + {% if perms.beesgospel.change_agenda %} - + {% endif %}
{% endblock %} diff --git a/templates/membres/liste.html b/templates/membres/liste.html index 1c6d1b2..3a9ac77 100644 --- a/templates/membres/liste.html +++ b/templates/membres/liste.html @@ -18,7 +18,7 @@
{% csrf_token %} - +
{% endif %} diff --git a/templates/membres/liste_chants.html b/templates/membres/liste_chants.html new file mode 100644 index 0000000..0d2c1ef --- /dev/null +++ b/templates/membres/liste_chants.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block content %} +

Liste des chants

+ + + {% for chant in object_list %} + + + + + + + {% endfor %} +
Titre
{{ chant.numero }}{{ chant.titre }}{{ chant.particularite }}{% if perms.beesgospel.change_chant %} + + + +
{% csrf_token %} + +
+ {% endif %} +
+ {% if perms.beesgospel.add_chant %} + + {% endif %} +{% endblock %}