Données Unicode

Django prend en charge nativement les données Unicode n’importe où. Pour autant que la base de données puisse stocker les données d’une manière ou d’une autre, des chaînes Unicode peuvent être transmises sans crainte aux gabarits, aux modèles et à la base de données.

Ce document présente ce qu’il faut savoir si vous écrivez des applications qui utilisent des données ou des gabarits qui sont codés dans d’autres formats que l’ASCII.

Création de la base de données

Assurez-vous que la base de données que vous utilisez est configurée pour pouvoir stocker des données textuelles arbitraires. Normalement, cela implique de permettre les données codées en UTF-8 ou UTF-16. Si vous utilisez un codage plus limité, par exemple latin1 (iso8859-1), vous ne pourrez pas stocker certains caractères dans la base de données et des informations seront perdues.

  • Les utilisateurs de MySQL doivent se référer au manuel MySQL pour des détails sur la façon de définir ou de modifier le codage de caractères.

  • Pour les utilisateurs de PostgreSQL, référez-vous au manuel PostgreSQL (section 22.3.2 dans PostgreSQL 9) pour plus de détails sur la création de bases de données avec le bon codage.

  • Les utilisateurs d’Oracle doivent se référer au manuel Oracle pour des détails sur la façon de définir (section 2) ou de modifier (section 11) le codage de caractères de la base de données.

  • Il n’y a rien à savoir pour les utilisateurs de SQLite. SQLite utilise toujours UTF-8 pour son codage interne.

Tous les moteurs de base de données de Django convertissent automatiquement les chaînes Unicode dans le codage convenant au dialogue avec la base de données. Ils convertissent aussi automatiquement les chaînes chargées de la base de données dans des chaînes Python Unicode. Vous n’avez même pas besoin de dire à Django le codage utilisé par la base de données : tout est géré de manière transparente.

Pour en savoir plus, lisez la section « L’API de base de données » ci-dessous.

Traitement général des chaînes

Chaque fois que vous utilisez des chaînes de caractères avec Django, par ex. dans des interrogations en base de données, dans l’affichage des gabarits ou partout ailleurs, vous avez deux choix pour le codage de ces chaînes. Vous pouvez utiliser des chaînes Unicode ou des chaînes normales (appelées parfois « chaînes d’octets » ou « bytestrings » en anglais) qui sont codées en UTF-8.

Avec Python 3, la logique est inversée, c’est-à-dire que les chaînes normales sont des chaînes Unicode, et lorsque vous voulez vraiment créer une chaîne d’octets, vous devez préfixer la chaîne avec « b ». Comme nous le faisons dans le code Django à partir de la version 1.5, nous recommandons d’importer unicode_literals depuis la bibliothèque __future__ dans votre code. Puis, lorsque vous avez réellement besoin de créer une chaîne d’octets littérale, préfixez-la avec « b ».

Situation Python 2 traditionnelle :

my_string = "This is a bytestring"
my_unicode = u"This is an Unicode string"

Python 2 avec unicode_literals ou Python 3 :

from __future__ import unicode_literals

my_string = b"This is a bytestring"
my_unicode = "This is an Unicode string"

Voir aussi Compatibilité avec Python 3.

Avertissement

Une chaîne d’octets ne comporte aucune information en elle-même à propos de son codage. Pour cette raison, il faut se baser sur une supposition lors de leur interprétation et Django suppose que toutes les chaînes d’octets sont en UTF-8.

Si vous passez à Django une chaîne qui a été codée dans un autre format, des choses bizarres risquent bien de se produire. En principe, Django produira une exception UnicodeDecodeError à un moment ou à un autre.

Si votre code n’utilise que des données ASCII, vous pouvez sans problème faire transiter des chaînes normales dans la mesure où l’ASCII est un sous-ensemble de UTF-8.

Ne vous méprenez pas en pensant qu’en définissant le réglage DEFAULT_CHARSET à quelque chose d’autre que 'utf-8' vous pourrez utiliser cet autre codage dans vos chaînes d’octets. DEFAULT_CHARSET ne s’applique qu’aux chaînes produites par l’affichage des gabarits (et des courriels). Django suppose toujours que les chaînes d’octets internes sont en UTF-8. La raison en est que le réglage DEFAULT_CHARSET n’est pas vraiment sous votre contrôle (si vous êtes développeur d’application). Il est plutôt sous le contrôle de la personne installant et utilisant votre application, et si cette personne choisit un réglage différent, votre code doit continuer de fonctionner. En conséquence, Django ne peut pas se baser sur ce réglage.

Dans la plupart des cas, quand Django traite des chaînes, il les convertit en chaînes Unicode avant toute chose. Ainsi en règle générale, si vous transmettez une chaîne d’octets, ne soyez pas surpris de recevoir une chaîne Unicode en retour.

Chaînes traduites

À part les chaînes Unicode et les chaînes d’octets, il existe un troisième type d’objet apparenté aux chaînes que l’on peut rencontrer en utilisant Django. Le système d’internationalisation introduit le concept de « traduction différée », une chaîne marquée comme traduite mais dont le résultat traduit n’est déterminé qu’au moment où l’objet est réellement utilisé comme chaîne. Cette fonctionnalité est utile dans les cas où la langue de traduction n’est connue qu’au moment de l’utilisation de la chaîne, alors que l’objet chaîne peut avoir été initialement créé au moment de la première importation du code.

Il n’y a normalement pas besoin de se préoccuper des traductions différées. Il faut juste savoir que si l’on examine un objet et que celui-ci s’identifie comme objet django.utils.functional.__proxy__, il s’agit d’une traduction différée. En appelant unicode() avec la traduction différée en paramètre, une chaîne Unicode sera produite dans la langue actuellement active.

Pour plus de détails sur les objets de traduction différée, référez-vous à la documentation sur l’internationalisation.

Fonctions utilitaires

Comme certaines opérations sur les chaînes reviennent fréquemment, Django fournit quelques fonctions utiles qui devraient faciliter quelque peu la manipulation d’objets Unicode et chaînes d’octets.

Fonctions de conversion

Le module django.utils.encoding contient quelques fonctions bien pratiques pour la conversion entre chaînes Unicode et chaînes d’octets.

  • smart_text(s, encoding='utf-8', strings_only=False, errors='strict') convertit son premier paramètre en chaîne Unicode. Le paramètre encoding définit le codage en entrée. Par exemple, Django utilise cette fonction en interne lorsqu’il traite des données de saisies de formulaires qui pourraient ne pas être codées en UTF-8. Le paramètre strings_only, lorsqu’il vaut True, évite que les contenus Python de type nombre, booléen et None ne soient convertis en chaînes de caractères (ils conservent leur type de base). Le paramètre errors accepte les mêmes valeurs que la fonction Python unicode() pour sa gestion d’erreurs.

    Si vous passez à smart_text() un objet possédant une méthode __unicode__, elle utilisera cette méthode pour effectuer la conversion.

  • force_text(s, encoding='utf-8', strings_only=False, errors='strict') est identique à smart_text() dans presque tous les cas. La différence est quand le premier paramètre est une instance de traduction différée. Alors que smart_text() préserve les traductions différées, force_text() force ces objets en chaînes Unicode (provoquant le processus de traduction). Normalement, c’est plutôt smart_text() qui est souhaité. Cependant, force_text() est utile dans les balises et filtres de gabarits qui doivent absolument obtenir une chaîne à traiter, et pas simplement quelque chose qui peut être converti en une chaîne.

  • smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict') est essentiellement l’inverse de smart_text(). Elle force le premier paramètre à une chaîne d’octets. Le paramètre strings_only se comporte comme pour smart_text() et force_text(). La sémantique est légèrement différente de la fonction Python intégrée str(), mais cette différence est nécessaire à quelques endroits dans le code interne de Django.

Normalement vous n’aurez besoin que de force_text(). Appelez-la aussi tôt que possible pour toute donnée saisie qui peut indifféremment être de l’Unicode ou une chaîne d’octets, et à partir de cet instant, vous pouvez traiter le résultat comme étant toujours de l’Unicode.

Traitement d’URI et d’IRI

Les infrastructures Web doivent gérer des URL (qui sont un type d’IRI). L’une des exigences quant aux URL est qu’elles ne doivent être codées qu’avec des caractères ASCII. Cependant, dans un environnement international, il peut être nécessaire de construire une URL à partir d’une IRI – dit de manière très raccourcie, une URI contenant des caractères Unicode. Le processus d’échappement et de conversion d’une IRI vers une URI peut être un peu délicat, c’est pourquoi Django fournit un peu d’aide.

Ces deux groupes de fonctions ont des objectifs légèrement différents, et il est important de bien les différencier. Normalement, on utilise urlquote() sur des parties individuelles de chemins d’IRI et d’URI afin que tout caractère réservé tels que « & » ou « % » soit codé correctement. Puis, on applique iri_to_uri() à l’IRI complète pour que tout caractère non ASCII soit converti dans les bonnes valeurs codées.

Note

Techniquement, il est incorrect de dire que iri_to_uri() implémente l’algorithme complet de la spécification IRI. Elle n’implémente pas (encore) la partie de codage des noms de domaine internationalisés de l’algorithme.

La fonction iri_to_uri() ne touche pas aux caractères ASCII qui sont normalement autorisés dans les URL. Ainsi, par exemple, le caractère « % » n’est pas recodé lorsqu’il est transmis à iri_to_uri(). Cela signifie que vous pouvez transmettre une URL complète à cette fonction et qu’elle ne corrompra pas la chaîne de requête ou toute autre partie.

Un exemple va clarifier les choses :

>>> urlquote('Paris & Orléans')
'Paris%20%26%20Orl%C3%A9ans'
>>> iri_to_uri('/favorites/François/%s' % urlquote('Paris & Orléans'))
'/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans'

Si vous regardez attentivement, vous pouvez voir que la portion générée par urlquote() dans le deuxième exemple n’a pas été doublement échappée lors de son passage dans iri_to_uri(). Il s’agit d’une fonctionnalité importante et utile. Cela signifie que vous pouvez construire des IRI sans vous soucier de la présence de caractères non ASCII, puis, tout à la fin, appeler iri_to_uri() sur le résultat.

De manière semblable, Django propose django.utils.encoding.uri_to_iri() qui implémente la conversion URI vers IRI selon RFC 3987#section-3.2. Elle décode toutes les séquences codées avec le pour-cent à l’exception de celles ne représentant pas une séquence UTF-8 valide.

Un exemple pour démontrer cela :

>>> uri_to_iri('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93')
'/♥♥/?utf8=✓'
>>> uri_to_iri('%A9helloworld')
'%A9helloworld'

Dans le premier exemple, les caractères UTF-8 et les caractères réservés sont décodés. Dans le second, le codage en pour-cent reste inchangé car le résultat serait hors de la plage UTF-8 valide.

Les fonctions iri_to_uri() et uri_to_iri() sont toutes deux idempotentes, ce qui signifie que l’assertion suivante est toujours vraie :

iri_to_uri(iri_to_uri(some_string)) == iri_to_uri(some_string)
uri_to_iri(uri_to_iri(some_string)) == uri_to_iri(some_string)

Vous pouvez donc sans crainte l’appeler plusieurs fois sur le même contenu URI/IRI sans risquer d’éventuels problèmes de double échappement.

Modèles

Comme toutes les chaînes sont renvoyées de la base de données sous forme de chaînes Unicode, les champs de modèles basés sur des caractères (CharField, TextField, URLField, etc.) contiennent des valeurs Unicode lorsque Django récupère des données à partir de la base de données. C’est toujours le cas, même si les données pourraient convenir pour une chaîne d’octets ASCII.

Vous pouvez passer des chaînes d’octets lors de la création d’un modèle ou de l’attribution d’une valeur à un champ, et Django se charge de les convertir en Unicode au moment où c’est nécessaire.

Choix entre __str__() et __unicode__()

Note

Si vous utilisez Python 3, vous pouvez passer cette section car vous allez toujours créer des méthodes __str__() plutôt que des méthodes __unicode__(). Si vous souhaitez conserver la compatibilité avec Python 2, vous pouvez décorer les classes de modèles avec python_2_unicode_compatible().

Une conséquence de l’utilisation d’Unicode par défaut est que vous devez prendre certaines précautions lors de l’affichage de données à partir des modèles.

En particulier, plutôt que de donner une méthode __str__() à vos modèles, nous recommandons d’implémenter une méthode __unicode__(). Dans cette méthode, vous pouvez sans aucune crainte renvoyer les valeurs de tous vos champs sans vous préoccuper de savoir si elles conviennent pour une chaîne d’octets ou non (dans le fonctionnement Python normal, le résultat de __str__() est toujours une chaîne d’octets, même quand vous essayez de renvoyer un objet Unicode sans le vouloir).

Vous pouvez tout de même créer une méthode __str__() dans vos modèles si vous le voulez, bien sûr, mais vous ne devriez pas le faire sans avoir une bonne raison. La classe de base Model de Django fournit automatiquement une implémentation de __str__() qui appelle __unicode__() et qui code le résultat en UTF-8. Cela signifie que seule une implémentation __unicode__() est normalement nécessaire et que Django se charge lui-même de forcer à une chaîne d’octets lorsque c’est nécessaire.

Précautions dans get_absolute_url()

Les URL ne peuvent contenir que des caractères ASCII. Si vous construisez une URL à partir de morceaux de données pas forcément ASCII, prenez soin de coder le résultat d’une façon qui convienne à une URL. La fonction reverse() se charge automatiquement de cela à votre place.

Si vous construisez une URL manuellement (c’est-à-dire sans utiliser la fonction reverse()), vous devrez vous charger vous-même du codage. Dans ce cas, utilisez les fonctions iri_to_uri() et urlquote() qui ont été documentées ci-dessus. Par exemple :

from django.utils.encoding import iri_to_uri
from django.utils.http import urlquote

def get_absolute_url(self):
    url = '/person/%s/?x=0&y=0' % urlquote(self.location)
    return iri_to_uri(url)

Cette fonction renvoie une URL correctement codée, même si self.location contient quelque chose comme « Jack a visité Paris & Orléans ». En fait, l’appel à iri_to_uri() n’est pas absolument nécessaire dans l’exemple ci-dessus, car tous les caractères non ASCII auront été remplacés durant l’échappement à la première ligne.

L’API de base de données

Vous pouvez transmettre aussi bien des chaînes Unicode que des chaînes d’octets UTF-8 comme paramètres des méthodes filter() et apparentées dans l’API de base de données. Les deux jeux de requête suivants sont identiques :

from __future__ import unicode_literals

qs = People.objects.filter(name__contains='Å')
qs = People.objects.filter(name__contains=b'\xc3\x85') # UTF-8 encoding of Å

Gabarits

Vous pouvez utiliser aussi bien des chaînes Unicode que des chaînes d’octets lors de la création manuelle de gabarits :

from __future__ import unicode_literals
from django.template import Template
t1 = Template(b'This is a bytestring template.')
t2 = Template('This is a Unicode template.')

Mais le cas le plus courant est de lire des gabarits à partir du système de fichiers, et cela ajoute une petite complication : certains systèmes de fichiers ne stockent pas leurs données dans un codage UTF-8. Si vos fichiers de gabarits ne sont pas stockés en UTF-8, définissez le réglage FILE_CHARSET pour qu’il corresponde au codage des fichiers sur disque. Lorsque Django lit un fichier de gabarit, il se charge de convertir les données à partir de ce codage vers Unicode (FILE_CHARSET est défini par défaut à 'utf-8').

Le réglage DEFAULT_CHARSET contrôle le codage des gabarits produits. Il est défini par défaut à UTF-8.

Balises et filtres de gabarit

Quelques astuces à garder en tête lors de l’écriture de vos propres balises et filtres de gabarits :

  • Toujours renvoyer des chaînes Unicode à partir de la méthode render() d’une balise de gabarit et à partir des filtres de gabarit.

  • Préférer l’utilisation de force_text() à smart_text() à ces endroits. La production de balises et l’appel aux filtres se produisent lors de la phase de rendu des gabarits, il n’y a donc pas d’avantage à différer la conversion d’objets de traduction différée en chaînes. Il est plus simple de travailler uniquement avec des chaînes Unicode à ce stade.

Fichiers

Si vous prévoyez de permettre aux utilisateurs d’envoyer des fichiers, vous devez vous assurer que l’environnement utilisé pour faire fonctionner Django est bien configuré pour fonctionner avec des noms de fichiers non ASCII. Si votre environnement n’est pas configuré correctement, vous allez recevoir des exceptions UnicodeEncodeError lors de l’enregistrement de fichiers dont le nom contient des caractères non ASCII.

La prise en charge de noms de fichiers UTF-8 par le système de fichiers est variable et peut dépendre de l’environnement. Contrôlez votre configuration actuelle dans un shell Python interactif en exécutant :

import sys
sys.getfilesystemencoding()

Cela devrait afficher « UTF-8 ».

Sur les plates-formes Unix, c’est la variable d’environnement LANG qui est responsable de définir le codage souhaité. Consultez la documentation de votre système d’exploitation et de votre serveur d’applications pour la syntaxe et l’emplacement appropriés pour définir cette variable.

Dans votre environnement de développement, il pourrait être nécessaire d’ajouter un réglage à votre fichier ~.bashrc du style :

export LANG="en_US.UTF-8"

Messagerie

Le système de messagerie de Django (dans django.core.mail) gère l’Unicode de façon transparente. Vous pouvez utiliser des données Unicode dans le corps et les en-têtes des messages. Cependant, vous devez toujours respecter les exigences des spécifications de la messagerie électronique ; par exemple, les adresses électroniques ne doivent comporter que des caractères ASCII.

L’exemple de code suivant démontre que tout peut contenir des caractères non ASCII à l’exception des adresses électroniques :

from __future__ import unicode_literals
from django.core.mail import EmailMessage

subject = 'My visit to Sør-Trøndelag'
sender = 'Arnbjörg Ráðormsdóttir <arnbjorg@example.com>'
recipients = ['Fred <fred@example.com']
body = '...'
msg = EmailMessage(subject, body, sender, recipients)
msg.attach("Une pièce jointe.pdf", "%PDF-1.4.%...", mimetype="application/pdf")
msg.send()

Envoi de formulaire

L’envoi de formulaire HTML est un domaine délicat. Il n’existe aucune garantie que les données envoyées contiennent des informations sur le codage, ce qui signifie que le système peut avoir à deviner le codage de ces données.

Django adopte une approche « différée » du décodage des données de formulaires. Les données d’un objet HttpRequest ne sont décodées qu’au moment où l’on tente d’y accéder. En fait, la plupart des données ne sont même pas décodées du tout. Le décodage ne s’applique qu’aux seules structures de données HttpRequest.GET et HttpRequest.POST. Ces deux champs renvoient leur contenu sous forme de données Unicode. Tous les autres attributs et méthodes de HttpRequest renvoient les données exactement comme elles ont été envoyées par le client.

Par défaut, le réglage DEFAULT_CHARSET est utilisé comme le codage supposé des données de formulaires. Si vous avez besoin de le modifier pour un formulaire particulier, vous pouvez définir l’attribut encoding de l’instance HttpRequest. Par exemple :

def some_view(request):
    # We know that the data must be encoded as KOI8-R (for some reason).
    request.encoding = 'koi8-r'
    ...

Il est même possible de modifier le codage après avoir accédé à request.GET ou request.POST, et tous les accès suivants utiliseront le nouveau codage.

Il n’est généralement pas nécessaire de se préoccuper de modifier le codage des formulaires, mais c’est une fonctionnalité utile lorsque des applications doivent communiquer avec des systèmes existants dont on ne maîtrise pas le codage.

Django ne décode pas les données des téléversements de fichiers, car les données sont normalement considérées comme des suites d’octets et pas comme des chaînes. Tout décodage automatique à ce niveau pourrait altérer la signification du flux d’octets.

Back to Top