Mots-clé : template

Home assistant : comment obtenir la VRAIE liste de nos prises (switch) : le guide complet

Quand nous voulons automatiser nos appareils dans Home Assistant, la première étape est souvent d’obtenir une liste fiable de nos entités, comme nos prises connectées (les « switch »). Une méthode qui semble logique serait d’interroger l’API, mais nous avons découvert que cela peut prêter à confusion. Voyons ensemble pourquoi et quelle est la méthode la plus simple et la plus fiable pour y parvenir.


L’explication : pourquoi certains identifiants semblent étranges ?

Nous avons parfois remarqué une différence entre les identifiants de nos prises et ceux listés par certains outils. Cela vient de la manière dont Home Assistant gère les noms et les identifiants (Entity ID) des appareils, surtout après leur première connexion via une intégration comme Zigbee2MQTT.

1. Découverte initiale de l’appareil

Quand nous ajoutons une nouvelle prise, Home Assistant lui crée automatiquement une entité avec un ID par défaut, souvent basé sur son adresse technique (comme l’adresse IEEE Zigbee). C’est pourquoi nous pouvons voir des entrées peu parlantes comme switch.0x70b3d52b601a7af7.

2. Renommage par nos soins

Ensuite, nous allons logiquement dans l’interface de Home Assistant pour trouver ce nouvel appareil et le renommer afin qu’il soit plus facile à identifier. Par exemple :

  • Nom convivial : « Cellier Prise Machine Laver »
  • ID de l’entité : Nous le changeons pour switch.cellier_prise_machine_laver.

3. Le « fantôme » de l’ancien ID

Voici le point crucial : Home Assistant garde souvent une trace de l’ancien ID (switch.0x70b3d52b601a7af7) mais le désactive. L’entité que nous utilisons au quotidien est bien la nouvelle (switch.cellier_prise_machine_laver). Le problème est que les requêtes brutes sur l’API peuvent lister ces anciens ID « fantômes » qui sont désactivés et cachés dans l’interface, mais toujours présents dans la base de données.

Les entités que nous voyons correctement, comme switch.veranda_prise_armoire ou switch.cuisine_prise_table, sont simplement des appareils pour lesquels nous avons déjà fait ce renommage depuis longtemps.


La Solution : obtenir la liste correcte et active en 30 secondes

La meilleure méthode n’est donc pas de scripter une requête API, mais d’utiliser directement l’interface de Home Assistant. C’est la source de vérité la plus fiable pour connaître l’état actuel de nos entités.

Voici comment nous pouvons obtenir la liste correcte et complète :

  1. Allons dans notre interface Home Assistant.
  2. Naviguons vers Paramètres > Appareils et services, puis cliquons sur l’onglet Entités.
  3. Dans la barre de recherche située en haut de la liste, tapons simplement switch. pour n’afficher que les commutateurs.
  4. (Optionnel) Pour être encore plus précis, nous pouvons cliquer sur l’icône de filtre et sélectionner l’intégration qui gère nos prises (par exemple, Zigbee2MQTT, ZHA, Tuya, etc.).

Nous obtiendrons alors la liste exacte de toutes nos entités switch actuellement actives, avec les bons ID que nous avons définis.

Capture d'écran de l'interface des entités Home Assistant filtrée par switch.

C’est la colonne « ID de l’entité » de cette vue filtrée que nous devons utiliser pour toutes nos futures configurations et automatisations.

Home-assistant : une carte de configuration personnalisée pour les prises de courant

Cherchons-nous à mieux comprendre et gérer notre consommation électrique ? Grâce à la puissance de Home Assistant et à une carte de configuration personnalisée (config-template-card), il est possible de créer un tableau de bord dynamique et détaillé de nos prises électriques.

Dans cet article, nous allons décortiquer ensemble un exemple de template pour montrer comment nous pouvons regrouper, analyser et afficher les données de nos prises connectées de manière claire et intuitive.

Avant de commencer, assurons-nous d’avoir :

  • Home Assistant installé et fonctionnel.
  • Des prises connectées (Zigbee, Wi-Fi, etc.) intégrées à Home Assistant et qui remontent la consommation d’énergie (capteurs power et energy).
  • La carte config-template-card installée depuis HACS (Home Assistant Community Store).
  • Une connaissance de base du langage de templating Jinja2, utilisé par Home Assistant, est un plus !

Comprendre le Template : Notre tableau de bord décortiqué

Avant de nous plonger dans le code, il est essentiel de comprendre comment ce template fonctionne. Il ne s’agit pas d’une simple suite d’instructions, mais d’un petit programme dynamique qui génère une interface riche et interactive. Il utilise le langage de templating Jinja2, intégré à Home Assistant, pour transformer les données brutes de nos capteurs en informations claires et utiles.

Voici une analyse détaillée, section par section, de tout ce que nous pouvons faire et personnaliser.


La Structure de Base : custom:config-template-card

Le cœur de notre installation est cette carte personnalisée. Elle agit comme un « moteur » qui va lire toutes nos entités et régénérer le contenu d’une autre carte (ici, une carte markdown qui interprète du HTML) à chaque changement d’état.

  • entities : C’est la liste de surveillance. Nous devons y placer toutes les entités (interrupteurs, capteurs de puissance, etc.) que nous souhaitons utiliser dans notre template. Dès que l’état de l’une d’elles change, toute la carte se met à jour en temps réel.
  • card : Définit la carte qui sera affichée. Nous utilisons type: markdown car elle nous offre une liberté totale sur la mise en forme grâce à l’interprétation du HTML.

Section 1 : Définitions & Variables Centrales

Au début du code, nous définissons toutes les variables qui serviront de base à notre tableau de bord. C’est la « centrale de configuration » de notre template.

  • {%- set days = ... -%} : Récupère la valeur d’un sélecteur (input_select). Cela nous permet de changer à la volée la période d’analyse de l’historique sans toucher au code.
  • {%- set outlets_data = [...] -%} : La variable la plus importante. C’est une liste où l’on associe l’identifiant technique de chaque prise à un nom convivial (ex: 'Salon TV'). Pour ajouter ou modifier une prise, il suffit de le faire dans cette liste !
  • {%- set kwh_cost = ... -%} : Ici, nous définissons le coût de notre kilowattheure pour obtenir des estimations financières précises.
  • {%- set outlets_total = 12 -%} : Le nombre total de prises listées. Pensez à mettre ce chiffre à jour si vous ajoutez ou supprimez des appareils de la liste outlets_data.
  • {%- set unavailable_states = [...] -%} : Une sécurité pour éviter les erreurs de calcul si une prise devient indisponible (unavailable) ou si son état est inconnu (unknown).

Section 2 : Calculs des Totaux

Avant d’afficher quoi que ce soit, le template effectue une première boucle pour calculer toutes les valeurs globales. Cela évite de répéter les mêmes calculs plusieurs fois.

  • {%- set total_power = namespace(sum=0) -%} : Nous utilisons un namespace. C’est une astuce Jinja2 pour créer une variable qui peut être modifiée à l’intérieur d’une boucle. Ici, nous créons des « compteurs » pour la puissance totale, l’énergie totale et le nombre de prises allumées.
  • {%- for ... -%} ... {%- endfor -%} : La boucle parcourt chaque prise de notre liste outlets_data et additionne les puissances et énergies pour alimenter nos compteurs.

Section 3 : Tableau détaillé des prises

C’est ici que la magie opère pour afficher la liste de tous nos appareils. Le code génère dynamiquement un tableau HTML pour un affichage propre et aligné.

  • <thead> <tr> <th>...</th> </tr> </thead> : Ces balises définissent la section d’en-tête de notre tableau.
  • {%- for outlet_info in outlets_data -%} : Une seconde boucle parcourt nos prises, mais cette fois pour générer une ligne de tableau (<tr>) pour chaque appareil.
  • Icônes dynamiques :
    • switch_icon : Affiche un point vert 🟢 si la prise est allumée, rouge 🔴 si elle est éteinte, et blanc ⚪ si l’état est inconnu.
    • power_icon : Donne un indice visuel de la consommation : un feu 🔥 pour une forte consommation, un éclair ⚡ pour une consommation modérée, et une ampoule 💡 pour une faible consommation.
  • {{ power | round(1) }} : Le filtre round(1) permet d’arrondir les chiffres à une décimale pour un affichage plus lisible.

Section 4 : Tableau de Bord Global & Analyse Financière

Cette section reprend les totaux calculés précédemment pour les afficher dans un tableau de synthèse clair, incluant des conversions et estimations.

  • Pourcentages et Conversions : Le code calcule le pourcentage de prises actives et convertit la puissance totale de Watts (W) en kilowatts (kW).
  • Estimations de Coût : En utilisant notre variable kwh_cost, le template estime le coût financier basé sur la consommation enregistrée (total_energy) et sur la puissance instantanée (total_power).

Section 5 : Analyse Intelligente de la Consommation

Pour aller plus loin qu’un simple affichage, cette section analyse la consommation en temps réel pour identifier les appareils les plus gourmands.

  • Catégorisation : Le code utilise une condition if/elif/else pour classer chaque appareil dans l’une des trois listes : « Gros », « Modérés » ou « Faibles » consommateurs.
  • Affichage Conditionnel : La section entière, ainsi que chaque catégorie, ne s’affiche que si elle contient au moins un appareil. Notre tableau de bord reste ainsi propre et ne montre que les informations pertinentes.

Section 6 : Recommandations Dynamiques

La touche finale : un tableau de bord qui nous donne des conseils !

  • {%- if total_power.sum > 300 -%} : Cette section n’apparaît que si notre consommation totale dépasse un seuil que nous définissons (ici, 300W).
  • Message d’alerte : Si le seuil est dépassé, un message formaté en liste à puces (<ul>) apparaît pour nous alerter et nous donner des pistes d’action. C’est l’exemple parfait d’une interface qui s’adapte au contexte.

Nous avons maintenant toutes les clés pour comprendre, adapter et faire évoluer ce template selon nos besoins !

type: custom:config-template-card
entities:
  - input_select.garden_history_days
  - switch.0x70b3d52b601433db
  - sensor.0x70b3d52b601433db_power
  - sensor.0x70b3d52b601433db_energy
  - sensor.0x70b3d52b601433db_linkquality
  - switch.0x70b3d52b601a7af7
  - sensor.0x70b3d52b601a7af7_power
  - sensor.0x70b3d52b601a7af7_energy
  - sensor.0x70b3d52b601a7af7_linkquality
  - switch.0x70b3d52b6017f0cd
  - sensor.0x70b3d52b6017f0cd_power
  - sensor.0x70b3d52b6017f0cd_energy
  - sensor.0x70b3d52b6017f0cd_linkquality
  - switch.0x70b3d52b601a79fc
  - sensor.0x70b3d52b601a79fc_power
  - sensor.0x70b3d52b601a79fc_energy
  - sensor.0x70b3d52b601a79fc_linkquality
  - switch.0x70b3d52b601a7a26
  - sensor.0x70b3d52b601a7a26_power
  - sensor.0x70b3d52b601a7a26_energy
  - sensor.0x70b3d52b601a7a26_linkquality
  - switch.cuisine_prise_table
  - sensor.cuisine_prise_table_power
  - sensor.cuisine_prise_table_energy
  - sensor.cuisine_prise_table_linkquality
  - switch.cuisine_prise_the
  - sensor.cuisine_prise_the_power
  - sensor.cuisine_prise_the_energy
  - sensor.cuisine_prise_the_linkquality
  - switch.cuisine_prise_cafe
  - sensor.cuisine_prise_cafe_power
  - sensor.cuisine_prise_cafe_energy
  - sensor.cuisine_prise_cafe_linkquality
  - switch.veranda_prise_piscine
  - sensor.veranda_prise_piscine_power
  - sensor.veranda_prise_piscine_energy
  - sensor.veranda_prise_piscine_linkquality
  - switch.veranda_prise_armoire
  - sensor.veranda_prise_armoire_power
  - sensor.veranda_prise_armoire_energy
  - sensor.veranda_prise_armoire_linkquality
  - switch.jardin_prise_ikea
  - sensor.jardin_prise_ikea_power
  - sensor.jardin_prise_ikea_energy
  - sensor.jardin_prise_ikea_linkquality
  - switch.0x70b3d52b601440c9
  - sensor.0x70b3d52b601440c9_power
  - sensor.0x70b3d52b601440c9_energy
card:
  type: markdown
  title: 🔌 Prises Électriques
  content: >
    {%- set days = states('input_select.garden_history_days') | int(3) -%}
    {%- set now_time = now() -%}
    {%- set start_date = (now_time - timedelta(days=days)).strftime('%Y-%m-%dT%H:%M:%S.001Z') -%}
    {%- set end_date = (now_time - timedelta(minutes=10)).strftime('%Y-%m-%dT%H:%M:%S.001Z') -%}
    {%- set date_params = '&start_date=' ~ start_date ~ '&end_date=' ~ end_date -%}
    {%- set outlets_data = [
      ['0x70b3d52b601433db', 'Salon TV'],
      ['0x70b3d52b601a7af7', 'M. à laver'],  
      ['0x70b3d52b6017f0cd', 'Grille-Pain'],
      ['0x70b3d52b601a79fc', 'Congélateur'],
      ['0x70b3d52b601a7a26', 'Frigo'],
      ['cuisine_prise_table', 'Cuisine Table'],
      ['cuisine_prise_the', 'Cuisine Thé'],
      ['cuisine_prise_cafe', 'Cuisine Café'],
      ['veranda_prise_piscine', 'Piscine'],
      ['veranda_prise_armoire', 'Veranda bas'],
      ['jardin_prise_ikea', 'Jardin IKEA'],
      ['0x70b3d52b601440c9', 'Prise ?']
    ] -%}
    {%- set kwh_cost = 0.15 -%}
    {%- set outlets_total = 12 -%}
    {%- set unavailable_states = ['unavailable', 'unknown', 'none'] -%}

    <div>
      <div> 📅 <strong>Historique : {{ days }} jour{{ 's' if days > 1 else '' }}</strong> </div>
      <table>
        <thead>
          <tr>
            <th>Prise</th>
            <th>Temps Réel</th>
            <th>Total</th>
            <th>LQI</th>
          </tr>
        </thead>
        <tbody>
          {%- for outlet_info in outlets_data %}
            {%- set outlet_id = outlet_info[0] -%}
            {%- set outlet_name = outlet_info[1] -%}
            {%- set power_state = states('sensor.' ~ outlet_id ~ '_power') -%}
            {%- set energy_state = states('sensor.' ~ outlet_id ~ '_energy') -%}
            {%- set lqi_state = states('sensor.' ~ outlet_id ~ '_linkquality') -%}
            {%- set switch_state = states('switch.' ~ outlet_id) -%}
            {%- set power = power_state | float(0) if power_state not in unavailable_states else 0 -%}
            {%- set energy = energy_state | float(0) if energy_state not in unavailable_states else 0 -%}
            {%- set lqi = lqi_state | int(0) if lqi_state not in unavailable_states else 0 -%}
            {%- set switch_icon = '🟢' if switch_state == 'on' else '🔴' if switch_state == 'off' else '⚪' -%}
            {%- set power_icon = '🔥' if power > 100 else '⚡' if power > 50 else '💡' if power > 0 else '⚪' -%}
            {%- set history_url = '/history?entity_id=sensor.' ~ outlet_id ~ '_power,sensor.' ~ outlet_id ~ '_energy' ~ date_params -%}
            <tr>
              <td>{{ switch_icon }} {{ outlet_name }}</td>
              <td>{{ power_icon }} <a href="{{ history_url }}"> {{ power }}W</a></td>
              <td><a href="{{ history_url }}">{{ energy | round(2) }} kWh</a></td>
              <td>📊 <a href="{{ history_url }}">{{ lqi }}</a></td>
            </tr>
          {%- endfor %}
        </tbody>
      </table>

      {%- set total_power = namespace(sum=0) -%}
      {%- set total_energy = namespace(sum=0) -%}
      {%- set outlets_on = namespace(count=0) -%}
      {%- for outlet_info in outlets_data -%}
        {%- set outlet_id = outlet_info[0] -%}
        {%- set switch_state = states('switch.' ~ outlet_id) -%}
        {%- set power_state = states('sensor.' ~ outlet_id ~ '_power') -%}
        {%- set energy_state = states('sensor.' ~ outlet_id ~ '_energy') -%}
        {%- set is_on = switch_state == 'on' -%}
        {%- set power = power_state | float(0) if power_state not in unavailable_states else 0 -%}
        {%- set energy = energy_state | float(0) if energy_state not in unavailable_states else 0 -%}
        {%- if is_on -%}
          {%- set outlets_on.count = outlets_on.count + 1 -%}
        {%- endif -%}
        {%- set total_power.sum = total_power.sum + power -%}
        {%- set total_energy.sum = total_energy.sum + energy -%}
      {%- endfor %}

      <h3>⚡ Tableau de Bord Électrique</h3>
      <table>
        <thead>
          <tr>
            <th>📊 Métriques</th>
            <th>Valeur</th>
            <th>Détail</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>🔌 Prises Actives</td>
            <td>{{ outlets_on.count }} / {{ outlets_total }}</td>
            <td>{{ ((outlets_on.count / outlets_total) * 100) | round(1) }}%</td>
          </tr>
          <tr>
            <td>⚡ Puissance Totale</td>
            <td>{{ total_power.sum | round(1) }}W</td>
            <td>{{ (total_power.sum / 1000) | round(2) }} kW</td>
          </tr>
          <tr>
            <td>📈 Consommation Totale</td>
            <td>{{ total_energy.sum | round(1) }} kWh</td>
            <td>≈ {{ (total_energy.sum * kwh_cost) | round(2) }}€</td>
          </tr>
          <tr>
            <td>⏱️  Coût Journalier Estimé</td>
            <td>{{ ((total_power.sum * 24 / 1000) * kwh_cost) | round(2) }}€</td>
            <td>{{ (total_power.sum * 24 / 1000) | round(2) }} kWh/j</td>
          </tr>
        </tbody>
      </table>
      {%- set high_consumers = [] -%}
      {%- set medium_consumers = [] -%}
      {%- set low_consumers = [] -%}
      {%- for outlet_info in outlets_data -%}
        {%- set outlet_id = outlet_info[0] -%}
        {%- set outlet_name = outlet_info[1] -%}
        {%- set power = states('sensor.' ~ outlet_id ~ '_power') | float(0) -%}
        {%- if power > 50 -%}
          {%- set high_consumers = high_consumers + [outlet_name ~ ' (' ~ power ~ 'W)'] -%}
        {%- elif power > 5 -%}
          {%- set medium_consumers = medium_consumers + [outlet_name ~ ' (' ~ power ~ 'W)'] -%}
        {%- elif power > 0 -%}
          {%- set low_consumers = low_consumers + [outlet_name ~ ' (' ~ power ~ 'W)'] -%}
        {%- endif -%}
      {%- endfor -%}

      {%- if high_consumers | length > 0 or medium_consumers | length > 0 -%}
        <h3>🔥 Analyse de Consommation</h3>
        {%- if high_consumers | length > 0 -%}
          <div><strong>🔴 Gros Consommateurs (>50W) :</strong><br>
            {%- for consumer in high_consumers %}
              <span>• {{ consumer }}</span>
            {%- endfor %}
          </div>
        {%- endif -%}
        {%- if medium_consumers | length > 0 -%}
          <div><strong>🟡 Consommateurs Modérés (5-50W) :</strong><br>
            {%- for consumer in medium_consumers %}
              <span>• {{ consumer }}</span>
            {%- endfor %}
          </div>
        {%- endif -%}
        {%- if low_consumers | length > 0 -%}
          <div><strong>🟢 Faible Consommation (<5W) :</strong><br>
            {%- for consumer in low_consumers %}
              <span>• {{ consumer }}</span>
            {%- endfor %}
          </div>
        {%- endif -%}
      {%- endif -%}

      {%- if total_power.sum > 300 -%}
        <h3>💡 Recommandations</h3>
        <div>
          <ul>
            <li>⚠️ Consommation élevée détectée ({{ total_power.sum | round(1) }}W)</li>
            <li>🔍 Vérifiez les appareils en veille non nécessaires</li>
            <li>⏰ Programmez les gros consommateurs aux heures creuses</li>
            <li>📊 Surveillez l'évolution sur {{ days }} jours</li>
          </ul>
        </div>
      {%- endif -%}
    </div>

Commentaires fermés sur Home-assistant : une carte de configuration personnalisée pour les prises de courant Publié dans Mots-clé , ,

Django : comment vérifier si on est en mode debug dans le template

Vous trouverez l’information dans beaucoup de sites, et cela semble très simple : dans votre template, il suffit de faire :

{% if not debug %}Je suis en debug !{% endif %}

En réalité cela ne suffit pas.

Il faut dans votre fichier settings.py, configurer correctement les adresses IP’s qui précisent qu’on est / ou pas / en mode « développement » :

INTERNAL_IPS = ['127.0.0.1', ]

(Notez que ce code peut être largement optimisé et dépendant de l’environnement, par exemple j’ai fait un settings.py qui prend cela en compte)

Django : faire des pages d’erreur sur mesure (404, 500, etc.)

Lorsque vous voulez faire des pages d’erreur sur mesure, c’est très simple… une fois que vous savez !
En effet, la documentation n’est pas très claire…

Voici un résumé de mon expérience, pour faire des pages d’erreur sur mesure (404, 500, etc.).
Il faut d’abord savoir que les erreurs sont des fonctions appelées (= vous ne pouvez pas le faire via les vues génériques).
Ensuite, la documentation donne un exemple, mais ce n’est pas assez.
Grâce aux raccourcis de Django, vous avez la fonction render() à laquelle vous pouvez passer un template et un contexte (= donc des variables).
Je me suis servi de cela pour créer un dictionnaire qui contient les erreurs, et les passer dans dans le contexte avec la clé title et content.


Voici le code des vues qui affiche une erreur « proprement » :

from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
VIEW_ERRORS = {
    404: {'title': _("404 - Page not found"),
          'content': _("A 404 Not found error indicates..."), },
    500: {'title': _("Internal error"),
          'content': _("A 500 Internal error means..."), },
    403: {'title': _("Permission denied"),
          'content': _("A 403 Forbidden error means ..."), },
    400: {'title': _("Bad request"),
          'content': _("A 400 Bad request error means ..."), }, }
def error_view_handler(request, exception, status):
    return render(request, template_name='errors.html', status=status,
                  context={'error': exception, 'status': status,
                           'title': VIEW_ERRORS[status]['title'],
                           'content': VIEW_ERRORS[status]['content']})
def error_404_view_handler(request, exception=None):
    return error_view_handler(request, exception, 404)
def error_500_view_handler(request, exception=None):
    return error_view_handler(request, exception, 500)
def error_403_view_handler(request, exception=None):
    return error_view_handler(request, exception, 403)
def error_400_view_handler(request, exception=None):
    return error_view_handler(request, exception, 400)


Une fois les vues faites, il faut aller dans la déclaration principale de vos vues. Donc le fichier urls.py qui est la racine de votre projet. Si vous mettez le code ailleurs, il sera ignoré.

Dedans, déclarez vos fonctions qui gèrent les erreurs :

handler404 = 'app.views.errors.error_404_view_handler'
handler500 = 'app.views.errors.error_500_view_handler'
handler403 = 'app.views.errors.error_403_view_handler'
handler400 = 'app.views.errors.error_400_view_handler'

Et enfin, dans vos templates, créez un fichier errors.html dans lequel vous pouvez construire votre page HTML qui gère l’erreur « proprement », avec, en plus dans le contexte, les variables {{ title }} et {{ content }} qui affichent respectivement le titre et le détail de l’erreur.

Maintenant, comment, en mode « développement », tester ces pages ? Dans la pratique vous ne pouvez pas, car en mode développement, une erreur affiche les informations de déboguage, et pas vos pages !
La solution : faire une URL qui « simule » l’erreur. Exemple avec 404 : vous ajouter dans vos urls : path('404/', error_404_view_handler), et puis d’afficher votre URL adéquate, soit http://localhost:8000/404/ et vous verrez l’erreur !

Django : comment passer une constante à tous vos templates ?

Django, nativement, a la possibilité de passer des dictionnaires clé-valeurs à tous les templates, et dans les templates on accède aux valeurs via la clé.

Exemple concret : je voulais passer le nom de mon site à tous les templates, mais sous forme de constante.

Dans settings.py j’ai défini ma constante : WEBSITE_NAME = 'mywebsite'

Il suffit de créer une fonction qui renvoie un dictionnaire avec une clé nommée « correctement », par exemple :

def context_processor_website_name(request):
    return {'website_name': WEBSITE_NAME}

Et ensuite, dans les context_processors, juste ajouter le nom de la fonction par rapport au package où elle est.
J’ai mis cette fonction dans settings.py.
Je ne sais pas si c’est la meilleure place mais l’idée c’est que comme c’est en rapport avec la configuration de mon site, c’est le meilleur endroit…

Donc pour préciser où est la fonction c’est 'myproject.settings.context_processor_website_name' :

On retrouve donc le code final comme ceci :

TEMPLATES = [{
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [os.path.join(BASE_DIR, 'templates')],
    'APP_DIRS': True,
    'OPTIONS': {
        'context_processors': [
            'django.template.context_processors.debug',
            # ... and:
            'myproject.settings.context_processor_website_name'
    ],
    },
}, ]

Et voilà, à partir de maintenant, avec exactement 3 lignes de code, j’ai accès à ma variable website_name, que je peux utiliser ainsi :

<title>{% block title %}{% if title %}{{ title|safe }} - {% endif %}{{ website_name }}{% endblock %}</title>

Petite parenthèse : grâce à ce code, si la page qu’on affiche a un titre, elle aura pour titre : [titre] - [nom du site] et si elle n’a aucun titre, elle affichera : [nom du site]

symfony 2 : isset et null avec Twig

Une des questions qu’on cherche le plus souvent avec Twig, c’est comment vérifier si une variable existe ou pas ?

La réponse se fait en trois étapes :

  1. Vérifier si une variable existe (= si elle a été initialisée) :
    En php, c’est isset().
    En Twig, c’est defined.
    Exemple de code Twig :

    {% if app.user is defined %}
    Code html
    {% endif %}
  2. Vérifier si une variable n’est pas null (= elle est définie, mais contient la valeur null) :
    En php, c’est !is_null().
    En Twig, c’est is not null.
    Exemple de code Twig :

    {% if app.user is not null%}
    Code html
    {% endif %}
  3. Vérifier si une variable existe et qu’elle n’est pas null (= elle est définie, et contient autre chose que la valeur null) :
    En php, c’est isset() && (!is_null()).
    En Twig, c’est is defined and is not null.
    Exemple de code Twig :

    {% if app.user is defined and app.user is not null%}
    Code html
    {% endif %}

En espérant que cela aide du monde !

Symfony 2: générer une url dynamique dans twig dans du code javascript

J’ai eu à faire face à un problème que vous allez très certainement rencontrer si vous faites Symfony2. Dans les fichiers de template, vous allez sûrement mettre du Javascript. Exemple :

<script type="text/javascript">
<!--
function verif_formulaire(){
    window.location = '/test/mon/url/';
}
-->
</script>

Maintenant, si on essaie de faire ça en Twig, c’est simple. Je ne m’attarderai que sur l’URL :

    window.location = '{{ path('my_path') }}';

Supposons que votre path nécessite un paramètre, par exemple, dans mon cas, le code postal :

    window.location = '{{ path('my_path', {'cp': "13480" }) }}';

Facile. Mais supposons que votre code en JavaScript veuille le générer dynamiquement :

/* récupération de la valeur quelque part : */
var monCP = document.getElementById('cp').value;
window.location = ="{{ path('hqf_pizzas_searchpage', {'cp': monCP }) }}";

Eh bien ça ne fonctionnera pas sur Symfony2. Ne cherchez pas c’est comme ça. Vous aurez une erreur. L’erreur, pour reprendre mon code, était :

Variable "monCP" does not exist in HQFPizzasBundle:Default:index.html.twig at line 11

Voici la solution que j’ai trouvée : je reprends ma configuration et il vous suffira de l’adapter à vos besoins. Dans mon fichier de routing src/HQF/Bundle/PizzasBundle/Resources/config/routing.yml, j’ai ce path qui nécessite le paramètre cp :

hqf_pizzas_searchpage:
    pattern:  /search/cp/{cp}
    defaults: { _controller: ... }

L’objectif est de ressortir le path avec un '%s' dedans, de manière à pouvoir avoir une URL qui ressemble à :

/search/cp/%s

Ainsi, il suffira juste après d’utiliser la fonction Twig ‘format‘ et d’y passer la variable JavaScript, par exemple monCP.
Ainsi cet ordre twig:

{{ path('hqf_pizzas_searchpage', {'cp': "%s" }) | format('monCP') }}

génèrera ceci :

/search/cp/monCP

L’objectif final est de sortir du vrai code JavaScript qui ressemble à :

window.location="/search/cp/"+monCP+"";

Donc si veut y arriver, le mélange code Twig + JavaScript, avec les guillemets, devrait être :

window.location = "{{ path('hqf_pizzas_searchpage', {'cp': "%s" })  | format('"+monCP+"') }}"

Seulement, problème : il escape tout ! Le code généré sera ainsi :

window.location ="/pizzas/search/cp/%25s";

Solution : pour que twig n’échappe pas le texte, il faut créer son propre filtre Twig !

Voici les étapes à suivre :

J’ai crée mon fichier src/HQF/Bundle/PizzasBundle/Twig/UrlDecodeExtension.php dans lequel j’ai mis ce code :

<?php

namespace HQF\Bundle\PizzasBundle\Twig;

class UrlDecodeExtension extends \Twig_Extension
{
    public function getFilters()
    {
        return array(
            'url_decode' => new \Twig_Filter_Method($this, 'urlDecode'),
        );
    }

    public function urlDecode( $url )
    {
        return urldecode( $url );
    }

    public function getName()
    {
        return 'url_decode_extension';
    }
}

Ensuite, je l’ai enregistré dans les services.
C’est dans le fichier src/HQF/Bundle/PizzasBundle/Resources/config/services.yml les lignes :

services:
    cme.twig.url_decode_extension:
        class: HQF\Bundle\PizzasBundle\Twig\UrlDecodeExtension
        tags:
            - { name: twig.extension }

A partir de là le filtre url_decode fonctionne. Il suffit de faire le code qui suit :

/* récupération de la valeur quelque part */
var monCP = document.getElementById('cp').value;
window.location ="{{ path('hqf_pizzas_searchpage', {'cp': "%s" }) | url_decode | format('"+monCP+"') | raw }}";

Afin de générer cela en JavaScript :

/* récupération de la valeur quelque part */
var monCP = document.getElementById('cp').value;
window.location ="/pizzas/search/cp/"+monCP+"";

Ce qui est du code JavaScript parfaitement exécutable.

Voici les explications pas à pas :

window.location ="{{ path('hqf_pizzas_searchpage', {'cp': "%s" }) }}";

Génère cela :

window.location ="/pizzas/search/cp/%25s";

C’est échappé, et il ne le faut pas ! Donc ajouter le filtre url_decode :

window.location ="{{ path('hqf_pizzas_searchpage', {'cp': "%s" }) | url_decode }}";

Là le résultat sera celui attendu :

window.location ="/pizzas/search/cp/%s";

Ensuite on y ajoute la fonction format afin d’y ajouter la variable JavaScript :

window.location ="{{ path('hqf_pizzas_searchpage', {'cp': "%s" }) | url_decode | format('"+monCP+"') }}";

Mais là encore le résultat sera échappé :

window.location ="/pizzas/search/cp/&quot;+monCP+&quot;";

Donc il faut lui dire de ressortir le résultat final au format raw :

window.location ="{{ path('hqf_pizzas_searchpage', {'cp': "%s" }) | url_decode | format('"+monCP+"') | raw }}";

Et le résultat de sortie sera (enfin !) parfait :

window.location ="/pizzas/search/cp/"+monCP+"";

Cet article est un mélange de l’explication de création des filtres Twig, ici, et de la question qui ressemble très fortement à la mienne sur stackoverflow, ici.

En espérant que cela serve à quelqu’un, un jour 😉

Smarty : dump de variable : écrire une fonction plugin

Vous avez sûrement déjà été confronté au fait de vouloir afficher le contenu d’une variable Smarty.
J’ai décrit la version courte ici.

Maintenant, il peut arriver que l’affichage ne corresponde pas à ce que vous vouliez notamment parce que le print_r() et autre var_dump() affichent des retour chariot.

J’ai donc crée ma fonction plugin, qui fait cela :


  /**
   * Affiche le contenu d'une variable.
   * Utilisation : {dump var=$variable_smarty}
   *
   * @param array $params Tableau de parametres
   * @param object $smarty objet Smarty
   * @return string Le dump
   */
  public function smarty_dump($params,$smarty)
  {
    // Récupération des paramètres
    if (!isset($params['var'])) {
      throw new Exception(
        "dump : paramètre 'var' obligatoire");
    }
    return
      '<pre>'.
      str_replace(
        "\n", "<br />",
        str_replace(
          "\r", "",
          var_export($params['var'],true)
        )
      ).
      '</pre>';
  }

Ensuite, je déclare le plugin à Smarty :

$smarty->registerPlugin('function','dump',
  array($this,'smarty_dump'));

Et dans mon template, je l’appelle ainsi :


<table>
{foreach from=$tab_devis item=a}
  <tr>
    <td>
      {dump var=$a}
    </td>
  </tr>
{/foreach}
</table>

Et l’affichage devient parfait : il est à l’intérieur d’une balise <pre></pre>, avec retour à la ligne <br /> comme il faut.

En espérant que cela serve à des utilisateurs