Ajout du mode maintenance avec modèle, vues, URL, contexte, et intégration des templates. Ajout de nouvelles fonctionnalités côté client, comme le basculement de thème et les interactions de navigation mobile.

This commit is contained in:
mrtoine 2025-12-17 12:48:05 +01:00
parent acd9f42cea
commit 536f4e303f
12 changed files with 227 additions and 27 deletions

View file

@ -1,5 +1,5 @@
from django.contrib import admin
from .models import SiteSettings, Visit
from .models import SiteSettings, Visit, Maintenance
@admin.register(SiteSettings)
class SiteSettingsAdmin(admin.ModelAdmin):
@ -28,6 +28,10 @@ class SiteSettingsAdmin(admin.ModelAdmin):
}),
)
@admin.register(Maintenance)
class MaintenanceAdmin(admin.ModelAdmin):
list_display = ("name","start_date", "end_date")
@admin.register(Visit)
class VisitAdmin(admin.ModelAdmin):

View file

@ -1,5 +1,14 @@
from .models import SiteSettings
from django.utils.timesince import timesince
from .models import SiteSettings, Maintenance
def site_settings(request):
# On récupère le premier objet, ou None s'il n'existe pas encore
return {'settings': SiteSettings.objects.first()}
def site_maintenance(request):
last = Maintenance.objects.last()
start = last.start_date if last else None
end = last.end_date if last else None
delay = timesince(start, end) if start and end else None
return {'maintenance': Maintenance.objects.last(), 'delay': delay}

View file

@ -0,0 +1,23 @@
# Generated by Django 6.0 on 2025-12-17 10:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_sitesettings_receive_emails_active'),
]
operations = [
migrations.CreateModel(
name='Maintenance',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_active', models.BooleanField(default=False)),
('message', models.TextField(blank=True)),
('start_date', models.DateTimeField(blank=True, null=True)),
('end_date', models.DateTimeField(blank=True, null=True)),
],
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 6.0 on 2025-12-17 10:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_maintenance'),
]
operations = [
migrations.AddField(
model_name='maintenance',
name='name',
field=models.CharField(blank=True, max_length=200),
),
]

View file

@ -35,6 +35,12 @@ class SiteSettings(models.Model):
verbose_name = "Réglages du site"
verbose_name_plural = "Réglages du site"
class Maintenance(models.Model):
is_active = models.BooleanField(default=False)
name = models.CharField(max_length=200, blank=True)
message = models.TextField(blank=True)
start_date = models.DateTimeField(blank=True, null=True)
end_date = models.DateTimeField(blank=True, null=True)
class Visit(models.Model):
"""Enregistrement simplifié des visites (agrégées par jour et visiteur).

8
core/urls.py Normal file
View file

@ -0,0 +1,8 @@
from django.urls import path
from . import views
urlpatterns = [
path('update_database', views.update_database, name='update_database'),
path('clear_cache', views.clear_cache, name='clear_cache'),
path('regen_static_files', views.regen_static_files, name='regen_static_files')
]

View file

@ -1,3 +1,20 @@
from django.shortcuts import render
from django.core.management import call_command
from django.core.cache import cache
import subprocess
# Create your views here.
def update_database(request):
call_command('makemigrations')
call_command('migrate')
message = "La base de données à bien été mise à jour !"
return render(request, 'home.html', {'message': message})
def clear_cache(request):
cache.clear()
message = "Le cache à bien été effacé !"
return render(request, 'home.html', {'message': message})
def regen_static_files(request):
call_command('collectstatic', '--noinput')
message = "Les fichiers statics ont bien été générés !"
return render(request, 'home.html', {'message': message})

View file

@ -84,6 +84,7 @@ TEMPLATES = [
'devart.context_processor.app_version',
'core.context_processor.site_settings',
'core.context_processor.site_maintenance',
'courses.context_processors.course_list',
'blog.context_processor.posts_list',
],

View file

@ -22,12 +22,13 @@ from django.http import HttpResponse
from devart.sitemap import CourseSitemap, StaticViewSitemap
from django.contrib.sitemaps.views import sitemap
# La vue pour le robots.txt
def robots_txt(request):
lines = [
"User-agent: *",
"Disallow: /admin/",
"Disallow: /users/",
"Disallow: /maintenance/",
"Disallow: /core/",
"Allow: /",
"Sitemap: https://partirdezero.com/sitemap.xml", # On indique déjà où sera le plan
]
@ -39,6 +40,7 @@ sitemaps_dict = {
}
urlpatterns = [
path('core/', include('core.urls')),
path('admin/', admin.site.urls),
path('', include('home.urls')),
path('courses/', include('courses.urls')),

View file

@ -13,3 +13,85 @@ function show(id) {
let buttonChange = document.getElementById(id);
buttonChange.onclick = function() { hide(id); };
}
// Fonction pour supprimer le message d'alerte après 5 secondes
document.addEventListener('DOMContentLoaded', function() {
let messages = document.querySelector('.flash_messages')
if (messages) {
setTimeout(function() {
messages.style.opacity = 0;
setTimeout(function() {
messages.style.display = 'none';
}, 1000);
}, 5000);
}
// Theme toggle setup
var toggle = document.getElementById('themeToggle');
var themeLink = document.getElementById('theme-css');
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
if (theme === 'light') {
if (themeLink) themeLink.href = themeLink.href.replace('colors_dark.css', 'colors_light.css');
} else {
if (themeLink) themeLink.href = themeLink.href.replace('colors_light.css', 'colors_dark.css');
}
var icon = toggle && toggle.querySelector('i');
if (icon) {
icon.classList.remove('fa-sun','fa-moon');
icon.classList.add(theme === 'light' ? 'fa-moon' : 'fa-sun');
}
if (toggle) {
toggle.setAttribute('aria-pressed', theme === 'dark' ? 'true' : 'false');
toggle.setAttribute('aria-label', theme === 'light' ? 'Activer le thème sombre' : 'Activer le thème clair');
toggle.title = toggle.getAttribute('aria-label');
}
}
try {
var current = document.documentElement.getAttribute('data-theme') || 'dark';
applyTheme(current);
if (toggle) {
toggle.addEventListener('click', function() {
var next = (document.documentElement.getAttribute('data-theme') === 'light') ? 'dark' : 'light';
localStorage.setItem('pdz-theme', next);
applyTheme(next);
});
}
} catch(e) {}
// Mobile nav toggle
try {
var navToggle = document.getElementById('navToggle');
var primaryNav = document.getElementById('primaryNav');
if (navToggle && primaryNav) {
function setExpanded(expanded) {
navToggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
navToggle.setAttribute('aria-label', expanded ? 'Fermer le menu' : 'Ouvrir le menu');
var icon = navToggle.querySelector('i');
if (icon) {
icon.classList.remove('fa-bars','fa-xmark');
icon.classList.add(expanded ? 'fa-xmark' : 'fa-bars');
}
primaryNav.classList.toggle('is-open', expanded);
}
navToggle.addEventListener('click', function() {
var expanded = navToggle.getAttribute('aria-expanded') === 'true';
setExpanded(!expanded);
});
// Close menu when a link is clicked (on small screens)
primaryNav.addEventListener('click', function(e) {
var target = e.target;
if (target.tagName === 'A' || target.closest('a')) {
setExpanded(false);
}
});
// Close on Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') setExpanded(false);
});
}
} catch(e) {}
});

View file

@ -84,6 +84,9 @@
<script defer>hljs.highlightAll();</script>
</head>
<body>
{% if maintenance.is_active == True %}
{% include "maintenance.html" %}
{% else %}
{% now "n" as month %}
{% if month == '12' %}
<!-- Overlay neige discret, non interactif -->
@ -108,5 +111,6 @@
{% block footer %}
{% include "partials/_footer.html" %}
{% endblock %}
{% endif %}
</body>
</html>

View file

@ -0,0 +1,26 @@
{% load comment_format %}
<section>
<h1>Maintenance : {{ maintenance.name }}</h1>
{{ maintenance.message|comment_markdown }}
<div class="text-right">Durée estimée : {{ delay }}</div>
<div class="text-right">Début de la maintenance : {{ maintenance.start_date }}</div>
<div class="text-right">Fin de la maintenance estimé : {{ maintenance.end_date }}</div>
{% if message %}
<h2>{{ message }}</h2>
{% endif %}
{% if user.is_superuser %}
<div style="border-bottom: 2px solid white"></div>
<div>
<ul>
<li><a href="{% url 'update_database' %}">Mettre à jour la base de données</a></li>
<li><a href="">Nettoyer la base de données</a></li>
<li><a href="{% url 'clear_cache' %}">Effacer le cache</a></li>
<li><a href="{% url 'regen_static_files' %}">Régénérer les fichiers static</a></li>
<div style="border-bottom: 2px solid white; margin: 5px;"></div>
<li><a href="{% url 'admin:index' %}" class="btn btn-warning" target="_blank">Panel Admin</a> <a href="" class="btn btn-danger" target="_blank">Redemarrer le serveur Django</a></li>
</ul>
</div>
{% endif %}
</section>