Amélioration de la généricité

On créé une classe singleton pour les variables de configuration

See #328
This commit is contained in:
Jean-Marie Favreau 2025-04-02 20:03:18 +02:00
parent d2966348fd
commit 15cb6d815a
10 changed files with 609 additions and 374 deletions

View File

@ -28,6 +28,8 @@ On peut aussi peupler les positions de référence qui serviront aux recherches
* ```make create-reference-locations``` * ```make create-reference-locations```
Une fois l'installation réussie, la configuration du site est possible en allant modifier l'objet "Configuration du site" dans l'administration de django.
## Utilisation d'un proxy socket ## Utilisation d'un proxy socket
On peut activer à la main (pour l'instant) un proxy type socket pour l'import d'événements. On peut activer à la main (pour l'instant) un proxy type socket pour l'import d'événements.

View File

@ -4,6 +4,8 @@ from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
from django_better_admin_arrayfield.models.fields import DynamicArrayField from django_better_admin_arrayfield.models.fields import DynamicArrayField
from solo.admin import SingletonModelAdmin
from .models import ( from .models import (
BatchImportation, BatchImportation,
Category, Category,
@ -17,8 +19,11 @@ from .models import (
StaticContent, StaticContent,
Tag, Tag,
UserProfile, UserProfile,
SiteConfiguration,
) )
admin.site.register(SiteConfiguration, SingletonModelAdmin)
admin.site.register(Category) admin.site.register(Category)
admin.site.register(Tag) admin.site.register(Tag)
admin.site.register(StaticContent) admin.site.register(StaticContent)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,102 @@
# Generated by Django 4.2.19 on 2025-04-02 19:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("agenda_culturel", "0160_alter_recurrentimport_processor"),
]
operations = [
migrations.CreateModel(
name="SiteConfiguration",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"site_name",
models.CharField(
default="Pommes de lune",
max_length=255,
verbose_name="Site name",
),
),
(
"site_description",
models.CharField(
default="Agenda participatif des sorties culturelles à Clermont-Ferrand et aux environs",
max_length=255,
verbose_name="Site description",
),
),
(
"html_keywords",
models.CharField(
default="Clermont-Ferrand, Puy-de-Dôme, agenda culturel, agenda participatif, sortir à clermont, sorties, concerts, théâtre, danse, animations, ateliers, lectures",
max_length=1024,
verbose_name="Keywords in html header",
),
),
(
"html_description",
models.CharField(
default="Où sortir à Clermont-Ferrand? Retrouve tous les bons plans sur l'agenda participatif des événements culturels à Clermont-Ferrand et dans le Puy-de-Dôme",
max_length=1024,
verbose_name="Description in html header",
),
),
(
"google_site_verification",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Google site verification value",
),
),
(
"ms_site_verification",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Microsoft (bing) site verification value",
),
),
(
"favicon",
models.ImageField(
blank=True,
max_length=1024,
null=True,
upload_to="",
verbose_name="Illustration",
),
),
(
"favicon_dev",
models.ImageField(
blank=True,
max_length=1024,
null=True,
upload_to="",
verbose_name="Illustration (development version)",
),
),
],
options={
"verbose_name": "Site Configuration",
},
),
]

View File

@ -46,7 +46,9 @@ from django.db.models.signals import post_save
from django.db.models.functions import Now from django.db.models.functions import Now
from django.db.models.functions import ExtractDay from django.db.models.functions import ExtractDay
from django.db.models.expressions import RawSQL from django.db.models.expressions import RawSQL
from django.templatetags.static import static
from django.conf import settings
from solo.models import SingletonModel
from .calendar import CalendarDay from .calendar import CalendarDay
from .import_tasks.extractor import Extractor from .import_tasks.extractor import Extractor
@ -62,6 +64,87 @@ logger = logging.getLogger(__name__)
to_be_translated = [_("mean"), _("median"), _("maximum"), _("minimum"), _("stdev")] to_be_translated = [_("mean"), _("median"), _("maximum"), _("minimum"), _("stdev")]
class SiteConfiguration(SingletonModel):
site_name = models.CharField(
verbose_name=_("Site name"), max_length=255, default="Pommes de lune"
)
site_description = models.CharField(
verbose_name=_("Site description"),
max_length=255,
default="Agenda participatif des sorties culturelles à Clermont-Ferrand et aux environs",
)
html_keywords = models.CharField(
verbose_name=_("Keywords in html header"),
max_length=1024,
default="Clermont-Ferrand, Puy-de-Dôme, agenda culturel, agenda participatif, sortir à clermont, sorties, concerts, théâtre, danse, animations, ateliers, lectures",
)
html_description = models.CharField(
verbose_name=_("Description in html header"),
max_length=1024,
default="Où sortir à Clermont-Ferrand? Retrouve tous les bons plans sur l'agenda participatif des événements culturels à Clermont-Ferrand et dans le Puy-de-Dôme",
)
google_site_verification = models.CharField(
verbose_name=_("Google site verification value"),
max_length=255,
default=None,
blank=True,
null=True,
)
ms_site_verification = models.CharField(
verbose_name=_("Microsoft (bing) site verification value"),
max_length=255,
default=None,
blank=True,
null=True,
)
favicon = models.ImageField(
verbose_name=_("Illustration"),
max_length=1024,
blank=True,
null=True,
)
favicon_dev = models.ImageField(
verbose_name=_("Illustration (development version)"),
max_length=1024,
blank=True,
null=True,
)
def guess_mimetype(url):
if url.endswith("png"):
return "image/png"
if url.endswith("jpg"):
return "image/jpg"
if url.endswith("svg"):
return "image/svg+xml"
if url.endswith("ico"):
return "image/x-icon"
return "image/jpg"
def get_favicon_url(self):
if settings.DEBUG:
if self.favicon_dev:
return self.favicon_dev.url
else:
return static("images/pdl-64-dev.png")
else:
if self.favicon:
return self.favicon.url
else:
return static("images/pdl-64.png")
def get_favicon_mimetype(self):
return SiteConfiguration.guess_mimetype(self.get_favicon_url())
def __str__(self):
return str(_("Site Configuration"))
class Meta:
verbose_name = _("Site Configuration")
class UserProfile(models.Model): class UserProfile(models.Model):
user = models.OneToOneField( user = models.OneToOneField(
User, User,
@ -2453,7 +2536,10 @@ class Event(models.Model):
def export_to_ics(events, request): def export_to_ics(events, request):
cal = icalCal() cal = icalCal()
# Some properties are required to be compliant # Some properties are required to be compliant
cal.add("prodid", "-//Pommes de lune//pommesdelune.fr//") cal.add(
"prodid",
"-//" + SiteConfiguration.get_solo().site_name + "//pommesdelune.fr//",
)
cal.add("version", "2.0") cal.add("version", "2.0")
for event in events: for event in events:

View File

@ -66,8 +66,11 @@ INSTALLED_APPS = [
"template_profiler_panel", "template_profiler_panel",
"django_cleanup.apps.CleanupConfig", "django_cleanup.apps.CleanupConfig",
"django_unused_media", "django_unused_media",
"solo.apps.SoloAppConfig",
] ]
SOLO_CACHE_TIMEOUT = 60 * 15 # 15 minutes
HONEYPOT_FIELD_NAME = "alias_name" HONEYPOT_FIELD_NAME = "alias_name"
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -7,40 +7,39 @@
{% load duplicated_extra %} {% load duplicated_extra %}
{% load rimports_extra %} {% load rimports_extra %}
{% load static %} {% load static %}
{% load solo_tags %}
{% get_solo 'agenda_culturel.SiteConfiguration' as site_config %}
<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>Pommes de lune <title>{{ site_config.site_name }}
{% block title %}{% endblock %} {% block title %}{% endblock %}
</title> </title>
<meta name="google-site-verification" {% if site_config.google_site_verification %}
content="pvRD0rc_xIE-1IYmbao0kj5ngGo1IWxJqKwoxrQwxuA" /> <meta name="google-site-verification"
<meta name="msvalidate.01" content="60E9E7DB667A3C4D6697261420DF3D1B" /> content="{{ site_config.google_site_verification }}" />
<meta name="keywords" {% endif %}
content="Clermont-Ferrand, Puy-de-Dôme, agenda culturel, agenda participatif, sortir à clermont, sorties, concerts, théâtre, danse, animations, ateliers, lectures"> {% if site_config.ms_site_verification %}
<meta name="msvalidate.01" content="{{ site_config.ms_site_verification }}" />
{% endif %}
<meta name="keywords" content="{{ site_config.keywords }}">
<meta name="description" <meta name="description"
content="{% block description %}Où sortir à Clermont-Ferrand? Retrouve tous les bons plans sur l'agenda participatif des événements culturels à Clermont-Ferrand et dans le Puy-de-Dôme{% endblock %}" /> content="{% block description %}{{ site_config.description }}{% endblock %}" />
<script src="{% static 'js/modal.js' %}"></script> <script src="{% static 'js/modal.js' %}"></script>
{% load static %} {% load static %}
<meta property="og:title" <meta property="og:title"
content="Pommes de lune — {% block og_title %}{% endblock %}" /> content="{{ site_config.site_name }} — {% block og_title %}{% endblock %}" />
<meta property="og:description" <meta property="og:description"
content="{% block og_description %}Où sortir à Clermont-Ferrand? Retrouve tous les bons plans sur l'agenda participatif des événements culturels à Clermont-Ferrand et dans le Puy-de-Dôme{% endblock %}" /> content="{% block og_description %}{{ site_config.description }}{% endblock %}" />
<meta property="og:image" <meta property="og:image"
content="{% block og_image %}https://{{ request.get_host }}{% get_media_prefix %}screenshot.png{% endblock %}" /> content="{% block og_image %}https://{{ request.get_host }}{% get_media_prefix %}screenshot.png{% endblock %}" />
<meta property="og:locale" content="fr_FR" /> <meta property="og:locale" content="fr_FR" />
<meta property="og:url" content="{{ request.build_absolute_uri }}" /> <meta property="og:url" content="{{ request.build_absolute_uri }}" />
<link rel="stylesheet" <link rel="stylesheet"
href="{% static 'air-datepicker/air-datepicker.css' %}"> href="{% static 'air-datepicker/air-datepicker.css' %}">
{% if debug %} <link rel="icon"
<link rel="icon" type="{{ site_config.get_favicon_mimetype }}"
type="image/svg+xml" href="{{ site_config.get_favicon_url }}">
href="{% static 'images/pdl-64-dev.png' %}">
{% else %}
<link rel="icon"
type="image/svg+xml"
href="{% static 'images/pdl-64.png' %}">
{% endif %}
{% load compress %} {% load compress %}
{% compress css %} {% compress css %}
<link type="text/x-scss" <link type="text/x-scss"
@ -108,15 +107,16 @@
<ul class="menu-droite"> <ul class="menu-droite">
<li> <li>
<a href="{% url 'home' %}" aria-label="Retour accueil"> <a href="{% url 'home' %}" aria-label="Retour accueil">
<img src="{% static 'images/pdl-64.png' %}" alt="logo pommes de lune" /> <img src="{{ site_config.get_favicon_url }}"
alt="logo {{ site_config.site_name }}" />
</a> </a>
</li> </li>
<li> <li>
<div> <div>
{% if user.is_authenticated %}{{ user.username }} @{% endif %} {% if user.is_authenticated %}{{ user.username }} @{% endif %}
<a href="{% url 'home' %}" aria-label="Retour accueil">Pommes de lune</a> <a href="{% url 'home' %}" aria-label="Retour accueil">{{ site_config.site_name }}</a>
</div> </div>
<div class="soustitre">Agenda participatif des sorties culturelles à Clermont-Ferrand et aux environs</div> <div class="soustitre">{{ site_config.site_description }}</div>
</li> </li>
</ul> </ul>
</nav> </nav>

View File

@ -4,6 +4,7 @@
{% block title %} {% block title %}
{% block og_title %}Statistiques{% endblock %} {% block og_title %}Statistiques{% endblock %}
{% endblock %} {% endblock %}
{% load solo_tags %}
{% load cat_extra %} {% load cat_extra %}
{% block entete_header %} {% block entete_header %}
{% css_categories %} {% css_categories %}
@ -15,11 +16,14 @@
<link rel="stylesheet" href="{% static 'cal-heatmap/cal-heatmap.css' %}"> <link rel="stylesheet" href="{% static 'cal-heatmap/cal-heatmap.css' %}">
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% get_solo 'agenda_culturel.SiteConfiguration' as site_config %}
<article> <article>
<header> <header>
<h1>Statistiques</h1> <h1>Statistiques</h1>
</header> </header>
<p>On retrouve sur cette page une synthèse des événements publiés sur l'agenda culturel pommes de lune.</p> <p>
On retrouve sur cette page une synthèse des événements publiés sur l'agenda culturel {{ site_config.site_name }}.
</p>
<h2>Par date de l'événement</h2> <h2>Par date de l'événement</h2>
<p>Pour chaque date, on retrouve le nombre d'événements qui débutent à cette date (sauf événements récurrents).</p> <p>Pour chaque date, on retrouve le nombre d'événements qui débutent à cette date (sauf événements récurrents).</p>
<div class="large-table"> <div class="large-table">

View File

@ -101,6 +101,7 @@ from .models import (
Tag, Tag,
remove_accents, remove_accents,
UserProfile, UserProfile,
SiteConfiguration,
) )
from .utils import PlaceGuesser from .utils import PlaceGuesser
@ -1216,7 +1217,7 @@ def export_ical(request, cat=None, tag=None, organisation_pk=None, place_pk=None
extra += " - " + emoji.replace_emoji(tag, replace="") extra += " - " + emoji.replace_emoji(tag, replace="")
response["Content-Disposition"] = "attachment; filename={0}{1}{2}".format( response["Content-Disposition"] = "attachment; filename={0}{1}{2}".format(
"Pommes de lune", extra, ".ics" SiteConfiguration.get_solo().site_name, extra, ".ics"
) )
return response return response

View File

@ -50,3 +50,4 @@ requests==2.32.3
django-cleanup==9.0.0 django-cleanup==9.0.0
django-unused-media==0.2.2 django-unused-media==0.2.2
django-resized==1.0.3 django-resized==1.0.3
django-solo==2.4.0