diff --git a/beesgospel/forms.py b/beesgospel/forms.py index a0c5d67..c6be86c 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 Chant, Membre, User +from .models import Chant, ChantDoc, Membre, User class BootstrapMixin: @@ -59,3 +59,20 @@ class ChantEditForm(BootstrapMixin, forms.ModelForm): class Meta: model = Chant fields = ["numero", "titre", "particularite"] + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.formset = None + if self.instance.pk: + DocFormSet = forms.inlineformset_factory(Chant, ChantDoc, fields=["fichier", "titre"], extra=1) + self.formset = DocFormSet(data=kwargs.get("data"), files=kwargs.get("files"), instance=self.instance) + + def is_valid(self): + return all([super().is_valid()] + [self.formset.is_valid()] if self.formset else []) + + @transaction.atomic() + def save(self, **kwargs): + obj = super().save(**kwargs) + if self.formset: + self.formset.save() + return obj diff --git a/beesgospel/migrations/0007_chantdoc.py b/beesgospel/migrations/0007_chantdoc.py new file mode 100644 index 0000000..8336b84 --- /dev/null +++ b/beesgospel/migrations/0007_chantdoc.py @@ -0,0 +1,21 @@ +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('beesgospel', '0006_chant'), + ] + + operations = [ + migrations.CreateModel( + name='ChantDoc', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('fichier', models.FileField(blank=True, upload_to='chants', verbose_name='Fichier')), + ('titre', models.CharField(max_length=200, verbose_name='Titre')), + ('chant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beesgospel.chant')), + ], + ), + ] diff --git a/beesgospel/models.py b/beesgospel/models.py index 3457239..e9cafe2 100644 --- a/beesgospel/models.py +++ b/beesgospel/models.py @@ -118,3 +118,12 @@ class Chant(models.Model): def __str__(self): return f"{self.numero}. {self.titre}" + + +class ChantDoc(models.Model): + chant = models.ForeignKey(Chant, on_delete=models.CASCADE) + fichier = models.FileField("Fichier", upload_to="chants", blank=True) + titre = models.CharField("Titre", max_length=200) + + def __str__(self): + return f"Document {self.titre} pour le chant {self.chant}" diff --git a/beesgospel/static/ficons/README b/beesgospel/static/ficons/README new file mode 100644 index 0000000..e9cca77 --- /dev/null +++ b/beesgospel/static/ficons/README @@ -0,0 +1,2 @@ +Many thanks to Daniel M. Hendricks, http://daniel.hn +https://github.com/dmhendricks/file-icon-vectors/ diff --git a/beesgospel/static/ficons/docx.svg b/beesgospel/static/ficons/docx.svg new file mode 100644 index 0000000..ac084a0 --- /dev/null +++ b/beesgospel/static/ficons/docx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/beesgospel/static/ficons/image.svg b/beesgospel/static/ficons/image.svg new file mode 100644 index 0000000..8d4cac8 --- /dev/null +++ b/beesgospel/static/ficons/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/beesgospel/static/ficons/master.svg b/beesgospel/static/ficons/master.svg new file mode 100644 index 0000000..2537cbe --- /dev/null +++ b/beesgospel/static/ficons/master.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/beesgospel/static/ficons/mp3.svg b/beesgospel/static/ficons/mp3.svg new file mode 100644 index 0000000..7d5a0a8 --- /dev/null +++ b/beesgospel/static/ficons/mp3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/beesgospel/static/ficons/odt.svg b/beesgospel/static/ficons/odt.svg new file mode 100644 index 0000000..f30088b --- /dev/null +++ b/beesgospel/static/ficons/odt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/beesgospel/static/ficons/pdf.svg b/beesgospel/static/ficons/pdf.svg new file mode 100644 index 0000000..e6472df --- /dev/null +++ b/beesgospel/static/ficons/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/beesgospel/static/ficons/tsv.svg b/beesgospel/static/ficons/tsv.svg new file mode 100644 index 0000000..318ba05 --- /dev/null +++ b/beesgospel/static/ficons/tsv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/beesgospel/static/js/main.js b/beesgospel/static/js/main.js index cbd6606..3ec05b6 100644 --- a/beesgospel/static/js/main.js +++ b/beesgospel/static/js/main.js @@ -11,4 +11,7 @@ window.addEventListener('DOMContentLoaded', () => { let resp = confirm("Voulez-vous vraiment supprimer cette ligne ?"); if (!resp) { ev.preventDefault(); ev.stopImmediatePropagation(); } }); + + const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') + const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) }) diff --git a/beesgospel/templatetags/bees_utils.py b/beesgospel/templatetags/bees_utils.py new file mode 100644 index 0000000..6c58526 --- /dev/null +++ b/beesgospel/templatetags/bees_utils.py @@ -0,0 +1,27 @@ +from pathlib import Path + +from django.template import Library +from django.templatetags.static import static + +IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".tif", ".tiff", ".gif"] + +register = Library() + + +@register.filter +def icon_url(file_name): + ext = Path(file_name).suffix.lower() + icon = "master" # default + if ext in IMAGE_EXTS: + icon = "image" + elif ext == ".pdf": + icon = "pdf" + elif ext in [".mp3", ".wav"]: + icon = "mp3" + elif ext in (".xls", ".xlsx", ".ods", ".csv"): + icon = "tsv" + elif ext == ".odt": + icon = "odt" + elif ext in (".doc", ".docx"): + icon = "docx" + return static(f"ficons/{icon}.svg") diff --git a/beesgospel/views.py b/beesgospel/views.py index 53b26a2..fc7619f 100644 --- a/beesgospel/views.py +++ b/beesgospel/views.py @@ -115,7 +115,7 @@ class ListeChantsView(LoginRequiredMixin, ListView): template_name = "membres/liste_chants.html" def get_queryset(self): - return super().get_queryset().order_by("numero") + return super().get_queryset().prefetch_related("chantdoc_set").order_by("numero") class ChantAddView(PermissionRequiredMixin, CreateView): diff --git a/templates/membres/chant_edit.html b/templates/membres/chant_edit.html index bb439d2..3420565 100644 --- a/templates/membres/chant_edit.html +++ b/templates/membres/chant_edit.html @@ -3,8 +3,19 @@ {% block content %}

Édition/ajout de chant

-
{% csrf_token %} + {% csrf_token %} {{ form.as_div }} + {% if form.formset %} +

Fichiers joints à ce chant

+ {{ form.formset.management_form }} + {% for subform in form.formset %} +
+
+ {{ subform }} +
+
+ {% endfor %} + {% endif %}
{% endblock content %} diff --git a/templates/membres/liste_chants.html b/templates/membres/liste_chants.html index 0d2c1ef..561fa3e 100644 --- a/templates/membres/liste_chants.html +++ b/templates/membres/liste_chants.html @@ -1,23 +1,31 @@ {% extends "base.html" %} +{% load bees_utils %} {% block content %}

Liste des chants

- - +
Titre
+ + {% if perms.beesgospel.change_chant %}{% endif %} {% for chant in object_list %} + - + {% endif %} {% endfor %}
Titre
{{ chant.numero }} {{ chant.titre }}{% for doc in chant.chantdoc_set.all %} + + + {% endfor %} + {{ chant.particularite }}{% if perms.beesgospel.change_chant %} + {% if perms.beesgospel.change_chant %} +
{% csrf_token %}
- {% endif %}