Données Unicode

Django natively supports Unicode data everywhere. Providing your database can somehow store the data, you can safely pass around strings to templates, models, and the database.

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.

All of Django’s database backends automatically convert strings into the appropriate encoding for talking to the database. They also automatically convert strings retrieved from the database into strings. You don’t even need to tell Django what encoding your database uses: that is handled transparently.

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

Traitement général des chaînes

Whenever you use strings with Django – e.g., in database lookups, template rendering or anywhere else – you have two choices for encoding those strings. You can use normal strings or bytestrings (starting with a “b”).

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.

In most cases when Django is dealing with strings, it will convert them to strings before doing anything else. So, as a general rule, if you pass in a bytestring, be prepared to receive a string back in the result.

Chaînes traduites

Aside from strings and bytestrings, there’s a third type of string-like object you may encounter when using Django. The framework’s internationalization features introduce the concept of a « lazy translation » – a string that has been marked as translated but whose actual translation result isn’t determined until the object is used in a string. This feature is useful in cases where the translation locale is unknown until the string is used, even though the string might have originally been created when the code was first imported.

Normally, you won’t have to worry about lazy translations. Just be aware that if you examine an object and it claims to be a django.utils.functional.__proxy__ object, it is a lazy translation. Calling str() with the lazy translation as the argument will generate a string in the current locale.

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

Fonctions utilitaires

Because some string operations come up again and again, Django ships with a few useful functions that should make working with string and bytestring objects a bit easier.

Fonctions de conversion

The django.utils.encoding module contains a few functions that are handy for converting back and forth between strings and bytestrings.

  • smart_text(s, encoding='utf-8', strings_only=False, errors='strict') converts its input to a string. The encoding parameter specifies the input encoding. (For example, Django uses this internally when processing form input data, which might not be UTF-8 encoded.) The strings_only parameter, if set to True, will result in Python numbers, booleans and None not being converted to a string (they keep their original types). The errors parameter takes any of the values that are accepted by Python’s str() function for its error handling.
  • force_text(s, encoding='utf-8', strings_only=False, errors='strict') is identical to smart_text() in almost all cases. The difference is when the first argument is a lazy translation instance. While smart_text() preserves lazy translations, force_text() forces those objects to a string (causing the translation to occur). Normally, you’ll want to use smart_text(). However, force_text() is useful in template tags and filters that absolutely must have a string to work with, not just something that can be converted to a string.
  • 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.

Normally, you’ll only need to use force_text(). Call it as early as possible on any input data that might be either a string or a bytestring, and from then on, you can treat the result as always being a string.

Traitement d’URI et d’IRI

Web frameworks have to deal with URLs (which are a type of IRI). One requirement of URLs is that they are encoded using only ASCII characters. However, in an international environment, you might need to construct a URL from an IRI – very loosely speaking, a URI that can contain Unicode characters. Use these functions for quoting and converting an IRI to a URI:

These two groups of functions have slightly different purposes, and it’s important to keep them straight. Normally, you would use quote() on the individual portions of the IRI or URI path so that any reserved characters such as “&” or “%” are correctly encoded. Then, you apply iri_to_uri() to the full IRI and it converts any non-ASCII characters to the correct encoded values.

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 :

>>> from urllib.parse import quote
>>> from django.utils.encoding import iri_to_uri
>>> quote('Paris & Orléans')
'Paris%20%26%20Orl%C3%A9ans'
>>> iri_to_uri('/favorites/François/%s' % quote('Paris & Orléans'))
'/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans'

If you look carefully, you can see that the portion that was generated by quote() in the second example was not double-quoted when passed to iri_to_uri(). This is a very important and useful feature. It means that you can construct your IRI without worrying about whether it contains non-ASCII characters and then, right at the end, call iri_to_uri() on the result.

Similarly, Django provides django.utils.encoding.uri_to_iri() which implements the conversion from URI to IRI as per RFC 3987#section-3.2.

Un exemple pour démontrer cela :

>>> from django.utils.encoding import uri_to_iri
>>> uri_to_iri('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93')
'/♥♥/?utf8=✓'
>>> uri_to_iri('%A9hello%3Fworld')
'%A9hello%3Fworld'

In the first example, the UTF-8 characters are unquoted. In the second, the percent-encodings remain unchanged because they lie outside the valid UTF-8 range or represent a reserved character.

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

Because all strings are returned from the database as str objects, model fields that are character based (CharField, TextField, URLField, etc.) will contain Unicode values when Django retrieves data from the database. This is always the case, even if the data could fit into an ASCII bytestring.

You can pass in bytestrings when creating a model or populating a field, and Django will convert it to strings when it needs to.

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.

If you’re constructing a URL manually (i.e., not using the reverse() function), you’ll need to take care of the encoding yourself. In this case, use the iri_to_uri() and quote() functions that were documented above. For example:

from urllib.parse import quote
from django.utils.encoding import iri_to_uri

def get_absolute_url(self):
    url = '/person/%s/?x=0&y=0' % quote(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

You can pass either strings or UTF-8 bytestrings as arguments to filter() methods and the like in the database API. The following two querysets are identical:

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

Gabarits

You can use either strings or UTF-8 bytestrings when creating templates manually:

from django.template import Template
t1 = Template(b'This is a bytestring template.')
t2 = Template('This is a string 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 :

  • Always return strings from a template tag’s render() method and from template filters.
  • Use force_text() in preference to smart_text() in these places. Tag rendering and filter calls occur as the template is being rendered, so there is no advantage to postponing the conversion of lazy translation objects into strings. It’s easier to work solely with strings at that point.

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"

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