Faire un ModelField Django encodé
Django est plein de surprises. Sa class Model est impressionnante de
complexité. Voici un exemple de ce qui est possible de faire:
# utilisation
class MonModel(models.Model):
...
data = EncryptedField(encoder=Base64Enc)
...
mon_instance = MonModel()
mon_instance.data = "coucou"
print(mon_instance.data)
# 'coucou'
print(mon_instance.data_enc)
# 'Y291Y291\n'
Moi, ça me botte. Au final, ça s'apparente à ce que Django fait pour les mots de passe de la base User avec la méthode set_password
,
mais en plus pratique.
On va voir le détail des opérations pour arriver à ce résultat.
Tout d'abord, il faut implémenter un objet qui permet d'encoder et de décoder des messages : un encodeur. Pour l'exemple, on va en faire un que ne fait
strictement rien, et un objet qui crée une représentation base64 du contenu.
from django.db import models
import base64
class BaseEnc(object):
def encode(self, value):
return value
def decode(self, msg):
return msg
class Base64Enc(object):
def encode(self, value):
return base64.encodestring(value)
def decode(self, msg):
return base64.decodestring(msg)
Maintenant, on crée une sous-classe de TextField
et on surcharge quelques méthodes.
class EncodedField(models.TextField):
description = "An encoded field"
def __init__(self, encoder=BaseEnc, *args, **kwargs):
self.encoder = encoder()
super(EncodedField, self).__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
if self.db_column is None:
self.db_column = name
self.field_name = name + '_enc'
super(EncodedField, self).contribute_to_class(cls, self.field_name)
setattr(cls, name, property(self.get_data, self.set_data))
def get_data(self, obj):
if getattr(obj, self.field_name):
return self.encoder.decode(getattr(obj, self.field_name))
return None
def set_data(self, obj, data):
if data:
setattr(obj, self.field_name, self.encoder.encode(data))
else:
setattr(obj, self.field_name, None)
La magie opère dans la méthode contribute_to_class
. La raison d'être de cette méthode est très bien expliquée dans cet article d'Alex Gaynor. Pour ceux d'entre vous qui ne seraient pas anglophones, ou qui auraient la flemme de tout lire voici un raccourci :
Certaines classes de Django possède une méthode add_to_class
. C'est le cas des classes qui héritent de ModelBase
par exemple.
Lorsque vous ajoutez un attribut data = MaValeur
à un modèle MonModel
, add_to_class('data', MaValeur)
est appelée.
Deux cas de figurent se présentent, soit MaValeur
ne possède pas de méthode contribute_to_class
, soit cette méthode existe.
Dans le premier cas, add_to_class
va simplement faire un setattr('data', MaValeur)
. Dans le second, c'est contribute_to_class
qui va être appelée.
Ok, maintenant que c'est clair, on reprend.
Notre méthode contribute_to_class
va faire plusieurs choses:
- changer l'attribut
field_name
de notre attribut en ajoutant"_enc"
au nom de l'attribut - définir un getter/setter attaché au nom de l'attribut telle qu'on l'a définit dans le modèle
Par exemple, pour un attribut appelé data
, les données enregistrées en DB se retrouveront dans l'attribut data_enc
, tandis que l'attribut data
retournera le resultat de instance_de_champ.get_data('data_enc')
. Et voilà, notre valeur est décodée !