Introduction des descriptions d'étiquettes

Améliorations du rendu
This commit is contained in:
Jean-Marie Favreau 2024-10-30 22:48:53 +01:00
parent d267642268
commit e34edc2e7c
18 changed files with 332 additions and 64 deletions

View File

@ -3,6 +3,7 @@ from django import forms
from .models import (
Event,
Category,
Tag,
StaticContent,
DuplicatedEvents,
BatchImportation,
@ -17,6 +18,7 @@ from django_better_admin_arrayfield.models.fields import DynamicArrayField
admin.site.register(Category)
admin.site.register(Tag)
admin.site.register(StaticContent)
admin.site.register(DuplicatedEvents)
admin.site.register(BatchImportation)

View File

@ -0,0 +1,22 @@
# Generated by Django 4.2.9 on 2024-10-30 17:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0092_alter_categorisationrule_weight'),
]
operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Tag name', max_length=512, verbose_name='Name')),
('description', models.TextField(blank=True, help_text='Description of the tag', null=True, verbose_name='Description')),
('principal', models.BooleanField(default=True, help_text='This tag is highlighted as a main tag for visitors, particularly in the filter.', verbose_name='Principal')),
],
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 4.2.9 on 2024-10-30 19:02
from django.db import migrations
from django.contrib.auth.models import Group, Permission
def update_groups_permissions(apps, schema_editor):
# first add a missing role
user_roles = ["Tag editor"]
for name in user_roles:
Group.objects.create(name=name)
all_perms = Permission.objects.all()
# set permissions for moderators
editor_perms = [i for i in all_perms if i.content_type.app_label == 'agenda_culturel' and i.content_type.model == 'tag']
Group.objects.get(name="Tag editor").permissions.add(*editor_perms)
def update_groups_delete(apps, schema_editor):
user_roles = ["Tag editor"]
for name in user_roles:
Group.objects.filter(name=name).delete()
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0093_tag'),
]
operations = [
migrations.RunPython(update_groups_permissions, reverse_code=update_groups_delete),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.9 on 2024-10-30 19:11
from django.db import migrations
import django_ckeditor_5.fields
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0094_auto_20241030_2002'),
]
operations = [
migrations.AlterField(
model_name='tag',
name='description',
field=django_ckeditor_5.fields.CKEditor5Field(blank=True, help_text='Description of the tag', null=True, verbose_name='Description'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.9 on 2024-10-30 20:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0095_alter_tag_description'),
]
operations = [
migrations.AlterField(
model_name='tag',
name='name',
field=models.CharField(help_text='Tag name', max_length=512, unique=True, verbose_name='Name'),
),
]

View File

@ -172,6 +172,30 @@ class Category(models.Model):
verbose_name_plural = _("Categories")
class Tag(models.Model):
name = models.CharField(
verbose_name=_("Name"), help_text=_("Tag name"), max_length=512,
unique=True
)
description = CKEditor5Field(
verbose_name=_("Description"),
help_text=_("Description of the tag"),
blank=True,
null=True,
)
principal = models.BooleanField(
verbose_name=_("Principal"),
help_text=_("This tag is highlighted as a main tag for visitors, particularly in the filter."),
default=True,
)
def get_absolute_url(self):
return reverse("view_tag", kwargs={"t": self.name})
class DuplicatedEvents(models.Model):
fixed = models.BooleanField(

View File

@ -222,6 +222,10 @@ svg {
vertical-align: -0.125em;
}
.small-ephemeride {
font-size: 80%;
clear: both;
}
.ephemeris {
float: left;
font-size: 110%;
@ -1235,7 +1239,7 @@ article {
}
.a-venir, .place {
.a-venir, .place, .tag, .tag-descriptions {
article#filters {
margin: 2em 0;
}
@ -1248,7 +1252,7 @@ article {
.resume {
column-count: 4;
}
.single-event {
.single-event, .tag-description {
display: grid;
grid-template-columns: 30% auto 14em;
grid-column-gap: 1em;

View File

@ -103,23 +103,27 @@
<footer class="container-fluid">
<div class="grid">
<div>
<a href="{% url 'mentions_legales' %}">{% picto_from_name "align-left" %} Mentions légales</a>
<a href="{% url 'mentions_legales' %}">{% picto_from_name "align-left" %} mentions légales</a>
</div>
<div>
<a href="{% url 'view_places' %}">{% picto_from_name "map-pin" %} Lieux</a>
<a href="{% url 'view_places' %}">{% picto_from_name "map-pin" %} lieux</a>
</div>
<div>
<a href="{% url 'about' %}">{% picto_from_name "users" %} À propos</a>
<a href="{% url 'view_all_tags' %}">{% picto_from_name "tag" %} étiquettes</a>
</div>
<div>
<a href="{% url 'about' %}">{% picto_from_name "users" %} à propos</a>
</div>
<div>
<a href="{% url 'contact' %}">{% picto_from_name "mail" %} Contact</a>
<a href="{% url 'contact' %}">{% picto_from_name "mail" %} contact</a>
</div>
<div>
{% if user.is_authenticated %}
<a href="{% url 'moderation' %}">{% picto_from_name "settings" %} Administrer</a>
<a href="{% url 'moderation' %}">{% picto_from_name "settings" %} administrer</a>
<p>Vous êtes connecté(e) en tant que {{ user }}</p>
{% else %}
<a href="{% url 'login' %}?next={% url 'moderation' %}">{% picto_from_name "log-in" %} Administrer</a>
<a href="{% url 'login' %}?next={% url 'moderation' %}">{% picto_from_name "log-in" %} administrer</a>
{% endif %}
</div>
</div>

View File

@ -84,7 +84,7 @@
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
{% for event in object_list %}
{% include "agenda_culturel/single-event/event-elegant-inc.html" with event=event no_location=1 %}
{% include "agenda_culturel/single-event/event-elegant-inc.html" with event=event day=0 no_location=1 %}
{% endfor %}
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}

View File

@ -15,7 +15,6 @@
{% if perms.agenda_culturel.change_duplicatedevents %}
<li><a {% if current == "duplicates" %}class="selected" {% endif %}href="{% url 'duplicates' %}">Gestion des doublons</a>{% show_badge_duplicated "left" %}</li>
{% endif %}
<li><a {% if current == "tags" %}class="selected" {% endif %}href="{% url 'view_all_tags' %}">Consulter les étiquettes</a></li>
</ul>
</nav>
{% if perms.agenda_culturel.change_place %}

View File

@ -6,20 +6,22 @@
<article id="event-{{ event.pk}}" class="single-event">
<header class="head">
{% if event|can_show_start_time:day %}
{% if event.start_time %}
<article class='ephemeris-hour'>
<span class="large">{{ event.start_time }}</span>
</article>
{% if day != 0 %}
{% if event|can_show_start_time:day %}
{% if event.start_time %}
<article class='ephemeris-hour'>
<span class="large">{{ event.start_time }}</span>
</article>
{% endif %}
{% endif %}
{% if event|can_show_end_time:day %}
{% if event.end_time %}
<article class='ephemeris-hour'>
jusqu'à <span class="large">{{ event.end_time }}</span>
</article>
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% if event|can_show_end_time:day %}
{% if event.end_time %}
<article class='ephemeris-hour'>
jusqu'à <span class="large">{{ event.end_time }}</span>
</article>
{% endif %}
{% endif %}
{{ event.category | small_cat_recurrent:event.has_recurrences }}
@ -37,7 +39,13 @@
{% endif %}
</hgroup>
{% endif %}
{% if day == 0 %}
<div class="small-ephemeride">
{% include "agenda_culturel/ephemeris-inc.html" with event=event filter=filter %}
</div>
{% endif %}
{% if event|need_complete_display:True %}<p>
{% picto_from_name "calendar" %}
@ -45,11 +53,6 @@
{% include "agenda_culturel/date-times-inc.html" with event=event %}
</em></p>
{% endif %}
<div class="buttons">
{% if perms.agenda_culturel.change_event %}
{% include "agenda_culturel/edit-buttons-inc.html" with event=event %}
{% endif %}
</div>
<div class="infos">
<p>
{% for tag in event.tags %}
@ -65,7 +68,12 @@
{{ r.to_text }}{% if not forloop.first %}, {% endif %}{% endfor %}, depuis le {{ event.recurrences.dtstart.date }}
</p>
{% endif %}
</div>
</div>
<div class="buttons" style="clear: both">
{% if perms.agenda_culturel.change_event %}
{% include "agenda_culturel/edit-buttons-inc.html" with event=event %}
{% endif %}
</div>
</header>

View File

@ -4,16 +4,41 @@
{% load cat_extra %}
{% block fluid %}{% endblock %}
{% block entete_header %}
{% css_categories %}
{% endblock %}
{% block body-class %}tag{% endblock %}
{% block content %}
<h1>Les événements <em>{{ tag }}</em></h1>
<article>
<header>
<div class="slide-buttons">
{% if object %}
{% if perms.agenda_culturel.change_tag %}
<a href="{% url 'edit_tag' object.pk %}" role="button">Modifier</a>
{% endif %}
{% if perms.agenda_culturel.delete_tag %}
<a href="{% url 'delete_tag' object.pk %}" role="button">Supprimer les informations</a>
{% endif %}
{% else %}
{% if perms.agenda_culturel.add_tag %}
<a href="{% url 'add_tag' %}?name={{ tag }}" role="button">Renseigner l'étiquette</a>
{% endif %}
{% endif %}
</div>
<h1>Les événements <em>{{ tag }}</em></h1></header>
{% if object %}
<p>{{ object.description|safe }}</p>
{% endif %}
</article>
{% for event in events %}
{% include "agenda_culturel/single-event/event-in-list-inc.html" with event=event %}
{% include "agenda_culturel/single-event/event-elegant-inc.html" with event=event day=0 %}
{% endfor %}
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "agenda_culturel/page.html" %}
{% block title %}{% block og_title %}Supprimer les informations de l'étiquette {{ object.name }}{% endblock %}{% endblock %}
{% block fluid %}{% endblock %}
{% block configurer-bouton %}{% endblock %}
{% block content %}
<h1>Supprimer les informations de l'étiquette {{ object.name }}</h1>
<form method="post">{% csrf_token %}
<p>Êtes-vous sûr·e de vouloir supprimer les informations de l'étiquette «&nbsp;{{ object.name }} ({{ object.pk }})&nbsp;» correspondant à la source <a href="{{ object.source }}">{{ object.source }}</a>&nbsp;?</p>
{{ form }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Confirmer">
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends "agenda_culturel/page-admin.html" %}
{% load static %}
{% block entete_header %}
<script src="{% url 'jsi18n' %}"></script>
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
<script src="/static/admin/js/jquery.init.js"></script>
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
{% endblock %}
{% block title %}{% block og_title %}Renseignement d'une étiquette{% endblock %}{% endblock %}
{% block fluid %}{% endblock %}
{% block content %}
<h1>Renseignement d'une étiquette</h1>
<article>
<form method="post">{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<input type="submit" value="Envoyer">
</form>
</article>
{% endblock %}

View File

@ -1,33 +1,53 @@
{% extends "agenda_culturel/page.html" %}
{% block title %}{% block og_title %}Toutes les étiquettes{% endblock %}{% endblock %}
{% block title %}{% block og_title %}Les étiquettes des événements{% endblock %}{% endblock %}
{% load cat_extra %}
{% load utils_extra %}
{% load tag_extra %}
{% load cache %}
{% block fluid %}{% endblock %}
{% block sidemenu-bouton %}
<li><a href="#contenu-principal" aria-label="Aller au contenu">{% picto_from_name "chevron-up" %}</a></li>
<li><a href="#sidebar" aria-label="Aller au menu latéral">{% picto_from_name "chevron-down" %}</a></li>
{% endblock %}
{% block content %}
<div class="grid two-columns">
<article>
<header>
<h1>Toutes les étiquettes</h1>
</header>
{% if tags %}
<p>Liste de toutes les étiquettes utilisées par les événements référencés&nbsp;:</p>
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout alltags LANGUAGE_CODE %}
<article class="tag-descriptions">
<header>
<h1>Les étiquettes des événements</h1>
</header>
{% if tags %}
{% for tag in tags %}
<article class="tag-description">
<header><a href="{% url 'view_tag' tag.name %}" role="button" class="small-cat">{{ tag.name }}</a></header>
<div>
{{ tag.description|safe }}
</div>
</article>
{% endfor %}
{% else %}
<p><em>Il n'y a aucune étiquette disposant d'une description.</em></p>
{% endif %}
<footer>
{% if other_tags %}
<p>Liste des étiquettes sans description&nbsp;:</p>
<div>
{% for tag in tags %}
<a href="{% url 'view_tag' tag %}" role="button" class="small-cat">{{ tag }}</a>
{% for tag in other_tags %}
{% if tag|tag_not_in_db:tags %}
<a href="{% url 'view_tag' tag %}" role="button" class="small-cat">{{ tag }}</a>
{% endif %}
{% endfor %}
</div>
{% else %}
<p><em>Il n'y a pour l'instant aucun événement renseigné par une étiquette.</em></p>
<p><em>Il n'y a aucune étiquette sans description.</em></p>
{% endif %}
</article>
</footer>
</article>
{% include "agenda_culturel/side-nav.html" with current="tags" %}
</div>
{% endcache %}
{% endwith %}
{% endblock %}

View File

@ -37,4 +37,8 @@ def tag_button_strike(tag, link=False):
@register.filter
def tag_button_link(tag):
return t_button(tag, '/?tags=' + tag, False)
return t_button(tag, '/?tags=' + tag, False)
@register.filter
def tag_not_in_db(tag, tags):
return len([t for t in tags if t.name == tag]) == 0

View File

@ -20,6 +20,9 @@ urlpatterns = [
path("ce-mois-ci", month_view, name="ce_mois_ci"),
path("tag/<t>/", view_tag, name="view_tag"),
path("tags/", tag_list, name="view_all_tags"),
path("tag/<int:pk>/edit", TagUpdateView.as_view(), name="edit_tag"),
path("tag/<int:pk>/delete", TagDeleteView.as_view(), name="delete_tag"),
path("tags/add", TagCreateView.as_view(), name="add_tag"),
path("moderation/", moderation, name="moderation"),
path(
"event/<int:year>/<int:month>/<int:day>/<int:pk>-<extra>",

View File

@ -41,6 +41,7 @@ from .forms import (
from .models import (
Event,
Category,
Tag,
StaticContent,
ContactMessage,
BatchImportation,
@ -499,19 +500,6 @@ def upcoming_events(request, year=None, month=None, day=None, neighsize=1):
return render(request, "agenda_culturel/page-upcoming.html", context)
def view_tag(request, t):
events = Event.objects.filter(tags__contains=[t]).order_by(
"start_day", "start_time"
)
context = {"tag": t, "events": events}
return render(request, "agenda_culturel/tag.html", context)
def tag_list(request):
tags = Event.get_all_tags()
context = {"tags": sorted(tags, key=lambda x: remove_accents(x).lower())}
return render(request, "agenda_culturel/tags.html", context)
class StaticContentCreateView(LoginRequiredMixin, CreateView):
model = StaticContent
@ -2073,3 +2061,50 @@ class PlaceFromEventCreateView(PlaceCreateView):
def get_success_url(self):
return self.event.get_absolute_url()
#########################
## Tags
#########################
class TagUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
model = Tag
permission_required = "agenda_culturel.change_tag"
fields = ["name", "description", "principal"]
success_message = _("The tag has been successfully updated.")
class TagCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
model = Tag
permission_required = "agenda_culturel.add_tag"
fields = ["name", "description", "principal"]
success_message = _("The tag has been successfully created.")
def get_initial(self, *args, **kwargs):
initial = super().get_initial(**kwargs)
if "name" in self.request.GET:
initial["name"] = self.request.GET.get("name")
return initial
class TagDeleteView(PermissionRequiredMixin, DeleteView):
model = Tag
permission_required = "agenda_culturel.delete_tag"
success_url = reverse_lazy("view_all_tags")
def view_tag(request, t):
events = Event.objects.filter(tags__contains=[t]).order_by(
"start_day", "start_time"
)
tag = Tag.objects.filter(name=t).first()
context = {"tag": t, "events": events, "object": tag}
return render(request, "agenda_culturel/tag.html", context)
def tag_list(request):
tags = Event.get_all_tags()
objects = Tag.objects.order_by("name").all()
context = {"other_tags": sorted(tags, key=lambda x: remove_accents(x).lower()),
"tags": objects}
return render(request, "agenda_culturel/tags.html", context)