Le principe
Je voulais un modèle de données qui stocke les jours d’une semaine que l’utilisateur désire, et qui les stocke sous forme de chaîne simple, avec des numéros qui correspondent aux jours de la semaine.
J’ai appelé ce modèle DaysField
.
Les valeurs sont stockées par rapport aux jours choisis. Par exemple, 1,3
signifie que l’utilisateur a choisi lundi et mercredi parmi les jours. 6,7
correspondent à samedi et dimanche. Pour finir, tous les jours de la semaine cochés serait donc : 1,2,3,4,5,6,7
Toujours descendre d’un modèle classique
J’ai retenu ces principes et cela semble suffire pour fonctionner : on descend du modèle qu’on veut écrire en base de données. Ici, je voulais écrire une chaîne de caractères dans la base ⇒ on descend de CharField()
, en modifiant la description au passage :
class DaysField(models.CharField):
description = _("Comma-separated integers between 1 and 7")
Conversion de « sources » différentes en données Python
Pour résumer : les modèles ont deux « arrivées » de données :
- quand on les données viennent de la base =
from_db_value()
;
- quand on les données viennent d’un formulaire (ou autre, mais en gros quand ça vient pas de la base de données mais du code Python lui-même) =
to_python()
.
Il faut donc gérer ces deux cas, qui, dans les deux cas, doivent renvoyer quelque chose au format Python qui nous intéresse.
Ici, c’est un tableau d’entiers compris entre 1 et 7 qui correspond aux jours de la semaine.
J’ai centralisé la gestion des deux « arrivées » (base et Python) dans une seule fonction : value_to_array()
:
@staticmethod
def value_to_array(value):
if value is None:
return None
try:
if isinstance(value, list):
return [int(a) for a in value]
elif isinstance(value, str):
return [int(a) for a in value.split(',')]
except (TypeError, ValueError):
raise ValidationError(_("Unexpected value"))
raise ValidationError(_("Unexpected value"))
@staticmethod
def from_db_value(value, expression, connection):
return DaysField.value_to_array(value)
def to_python(self, value):
return DaysField.value_to_array(value)
Peut-être existe-t-il des cas où la provenance du code Python doit être gérée différemment de la provenance de la base de données… ici ce n’est pas le cas – et si vous avez des exemples de gestion différentes pour des entrées de base et de code, laissez-moi un commentaire, car je n’en vois pas…
Conversion de données Python ⇒ données pour la base
C’est la fonction get_prep_value()
.
Dans le cas DaysField
, c’est très simple :
def get_prep_value(self, value):
return ','.join([str(a) for a in value]) \
if value is not None else None
On prend toutes les valeurs du tableau, on les concatène avec la virgule ,
comme séparateur. C’est plus long de l’expliquer que de le coder !