How can I create an encrypted django field that converts data when it's retrieved from the database?
I think the issue is that to_python is also called when you assign a value to your custom field (as part of validation may be, based on this link). So the problem is to distinguish between to_python calls in the following situations:
- When a value from the database is assigned to the field by Django (That's when you want to decrypt the value)
- When you manually assign a value to the custom field, e.g. record.field = value
One hack you could use is to add prefix or suffix to the value string and check for that instead of doing isinstance check.
I was going to write an example, but I found this one (even better :)).
Check BaseEncryptedField: https://github.com/django-extensions/django-extensions/blob/2.2.9/django_extensions/db/fields/encrypted.py (link to an older version because the field was removed in 3.0.0; see Issue #1359 for reason of deprecation)
Source: Django Custom Field: Only run to_python() on values from DB?
You should be overriding to_python
, like the snippet did.
If you take a look at the CharField
class you can see that it doesn't have a value_to_string
method:
django/db/models/fields/__init__.py
The docs say that the to_python
method needs to deal with three things:
- An instance of the correct type
- A string (e.g., from a deserializer).
- Whatever the database returns for the column type you're using.
You are currently only dealing with the third case.
One way to handle this is to create a special class for a decrypted string:
class DecryptedString(str):
pass
Then you can detect this class and handle it in to_python()
:
def to_python(self, value):
if isinstance(value, DecryptedString):
return value
decrypted = self.encrypter.decrypt(encrypted)
return DecryptedString(decrypted)
This prevents you from decrypting more than once.
You forgot to set the metaclass:
class EncryptedCharField(models.CharField):
__metaclass__ = models.SubfieldBase
The custom fields documentation explains why this is necessary.