Personnalisation des pages d’erreur sur un Wagtail multisite

Ash_Crow Vie du site , Python , Logiciels , Webdesign English

Parmi les choses à faire pour compléter la création d’un site web, la personnalisation des pages d’erreur, sans être très haut dans la liste, n’est pas à négliger. La page 404 (page non trouvée), notamment, est un endroit où sont fréquemment cachées des private jokes et des easter eggs, et les listes des meilleures pages 404 sont devenues des marronniers sur les blogs/sites de webdesign[1]. C'est également le cas, dans une moindre mesure, des pages 403 (accès refusé) et 500 (erreur interne du serveur)

Je me suis récemment lancé dans la personnalisation de plusieurs pages d’erreur de ce site, et cela me semblait un bon moment pour faire le point sur la création de pages d’erreur personnalisées dans ce contexte.

Codes HTTP

Pour commencer, petit rappel sur les codes HTTP : lorsque l’on tente d’accéder à une page web, on reçoit en retour, outre le contenu de la page proprement dite, un code d’état à 3 chiffres, le premier indiquant le type (1xx = information, 2xx = succès, 3xx = redirection, 4xx = erreur côté utilisateur, 5xx = erreur côté serveur) Il en existe de nombreux, dont certains plus ou moins farfelus, mais seuls une poignée des messages 4xx et 5xx ont généralement le droit à un traitement spécifique.

Pages d’erreur dans Django / Wagtail

Wagtail hérite de la gestion des erreurs de Django, qui outre un système assez poussé en mode développement, fournit des vues facilitant la personnalisation de 4 messages d’erreur :

  • 404 (page non trouvée)
  • 500 (erreur interne du serveur)
  • 403 (accès refusé)
  • 400 (requête incorrecte)

J’ai pas mal hésité sur le look à donner à ces pages, et j’ai fini par me décider pour les uniformiser avec un look un peu glitchy/cyberpunk basé sur un design trouvé sur Codepen, en adaptant l’image de fond à chaque erreur. Cela m’a permis d’utiliser un seul fichier SCSS commun pour les 4 pages.

Page d’erreur 500

La plus simple à gérer a été la page 500, parce que je voulais une page en HTML+CSS uniquement, sans faire appel aux fonctions de template de Django. Il suffit de créer un fichier titré 500.html à la racine du dossier templates de l’application principale, et Django est capable de le trouver tout seul.

Page d’erreur 500

Pages d’erreur 403 et 404

Pour les pages 403 et 404, je voulais une page plus intégrée dans l'interface du site, et notamment qui diffère suivant le site utilisé, puisque mon instance de Wagtail gère des sites avec deux habillages différents.

Une discussion sur StackOverflow[2] pointe vers une solution qui n’est malheureusement pas ou plus fonctionnelle et a apparemment été retirée de la doc de Wagtail. Elle m’a cependant mis sur la voie de la bonne solution.

La première chose à faire est d’indiquer à Django les vues qui doivent gérer les erreurs 403 et 404 :

# config/urls.py

# [...]
handler403 = "portfolio.views.custom_403_view"
handler404 = "portfolio.views.custom_404_view"

(J’ai décidé de placer ces vues dans l’app portfolio, étant donné que c’est celle qui a le thème différent des autres. Fondamentalement, cela aurait marché dans n’importe quelle app.)

Il faut ensuite créer les vues en question. Comme elles font essentiellement la même chose à part le code d’erreur, on va définir la logique dans une méthode que les deux vues appelleront :

# portfolio/views.py


def custom_error_view(request, exception, error_code=404):
    """
    Returns a different error view depending on the site.
    """
    portfolio_site = Site.objects.filter(site_name="Portfolio pro").first()

    if portfolio_site:
        portfolio_hostname = portfolio_site.hostname
        # We can't use request.site, which is not served on an error view.
        if portfolio_hostname in request.environ.get("HTTP_HOST", ""):
            return render(
                request,
                f"portfolio/{error_code}.html",
                context={"exception": exception},
                status=error_code,
            )

    # Render the standard error page by default
    return render(
        request,
        f"{error_code}.html",
        context={"exception": exception},
        status=error_code,
    )


def custom_403_view(request, exception=None):
    """
    Returns a different 403 view depending on the site.
    """
    return custom_error_view(request, exception, error_code=403)


def custom_404_view(request, exception=None):
    """
    Returns a different 404 view depending on the site.
    """
    return custom_error_view(request, exception, error_code=404)

Par rapport à la solution proposée sur StackOverflow, on voit quelques changements:

  • je vérifie que le site portfolio existe bien, ce qui est notamment utile pour les tests unitaires.
  • je fais la comparaison avec request.environ.get("HTTP_HOST", "") et non avec request.site.hostname, cette dernière variable n’étant pas disponible dans une page d’erreur, ce qui faisait que le site renvoyait une erreur 500 (sans moyen de voir le problème de fond puisque rien de tout cela n’est accessible quand DEBUG est activé), y compris sur des pages qui auraient dû marcher (par exemple l’accueil lorsqu’aucune langue n’est sélectionnée.)
Capture d’écran montrant un "404" en gros caractères jaunes, suivi d’un texte indiquand que la page n’a pas été trouvée. Une photo d’une longue-vue, assombrie et entrelacée de lignes sombres, est présente en fond d’écran. On voit l’habillage du site principal, noir avec des accents jaunes
Même chose que l’image précédente, mais cette fois on voit l’habillage du portfolio, noir et blanc.

Comparaison des deux pages 404 (déplacez la glissière pour afficher l’une ou l’autre image)

Erreurs 400 et 502/503/504

Pour l’erreur 400, j’ai fait la même chose que pour l’erreur 500, mais, comme le plus souvent, lorsqu’elle survient, cela n’arrive même pas à Django, j’ai fait en sorte que Nginx puisse également la servir. Dans le même esprit, j’ai fait une page qui couvre les erreurs 502 (Bad Gateway), 503 (Service Unavailable) et 504 (Gateway Time-out), susceptibles de survenir si Nginx tourne mais que Django ou Gunicorn ne répondent pas. Il s’agit des lignes suivantes de la configuration Nginx :

error_page 400 /templates/400.html;
error_page 502 503 504 /templates/50x.html;
location /templates {
        root <projectpath>/config;
}

location /errortesting {
        proxy_pass http://unix:/incorrect/path.sock;
} # This page should render the error page even if the site is properly loaded

Le dernier bloc permet de tester que la page d’erreur est bien rendue. Pour la 400, un simple caractère '%' à la fin de l’URL suffit à la provoquer.

Page de connexion

Au passage, j’en ai profité pour personnaliser la page de connexion de Wagtail. Cela se fait en créant une page dashboard/templates/wagtailadmin/login.html et en redéfinissant les blocs pertinents :

{% extends "wagtailadmin/login.html" %}
{% load i18n wagtailadmin_tags %}

{% block branding_login %}
Attention
{% endblock %}

{% block login_form %}
    <p class="restricted-access">{% translate "Restricted access" %}</p>
    {{ block.super }}
{% endblock %}

{% block branding_logo %}
    <marquee aria-hidden="true">
        This place is a <em>message</em>... and part of a system of messages... <em>pay attention to it!</em>
        [...]
        The danger is unleashed only if you substantially disturb this place physically. <em>This place is best shunned and left uninhabited.</em>
    </marquee>
{% endblock %}

{% block css %}
    <link rel="stylesheet" href="{% versioned_static 'wagtailadmin/css/core.css' %}">
    <link rel="stylesheet" href="{% versioned_static 'css/style_login.css' %}">
    {% hook_output 'insert_global_admin_css' %}
    {% hook_output 'insert_editor_css' %}
{% endblock %}

(Et, oui, j'ai mis les messages d’avertissement de longue durée sur les déchets nucléaires dans une balise <marquee> et non, je n’ai pas honte :D – par contre j'ai tenté de reproduire le fonctionnement de <marquee> avec du CSS non considéré comme obsolète par le W3C et je n'ai pas réussi...)

Fly, you fools

Image d’en-tête:

Un python vert de la Ménagerie du Jardin des Plantes (travail personnel, CC-BY-SA 4.0)

Mots-clefs :

0 commentaire publié.

Commentaires

Les commentaires sont fermés.