To transition an existing unencrypted database field to an encrypted field using django-cryptography, you must follow a specific sequence of schema and data migrations to ensure no data loss. The process involves renaming the old field, adding a new encrypted field with the original name, copying the data, and finally removing the old field.
Migration Workflow
- Rename the existing field (Schema): Use
migrations.RenameField to add a prefix (e.g., old_) to the current field name. - Add the new encrypted field (Schema): Add a new field using the original name, but wrap the field type with
django_cryptography.fields.encrypt(). - Copy the data (Data): Use
migrations.RunPython to iterate through the model instances and copy values from the old_ field to the new encrypted field. It is highly recommended to provide both forwards and reverse functions to allow for rollbacks. - Remove the old field (Schema): Use
migrations.RemoveField to delete the prefixed field once the data migration is verified.
Example Implementation
Given a model originally defined as:
class EncryptedCharModel(models.Model):
field = models.CharField(max_length=15)
Follow these steps in your Django migrations:
# 1. Rename existing field
migrations.RenameField(
model_name='encryptedcharmodel',
old_name='field',
new_name='old_field',
)
# 2. Add new encrypted field
migrations.AddField(
model_name='encryptedcharmodel',
name='field',
field=django_cryptography.fields.encrypt(
models.CharField(default=None, max_length=15)
),
preserve_default=False,
)
# 3. Copy data (RunPython)
def forwards_encrypted_char(apps, schema_editor):
EncryptedCharModel = apps.get_model("fields", "EncryptedCharModel")
for row in EncryptedCharModel.objects.all():
row.field = row.old_field
row.save(update_fields=["field"])
def reverse_encrypted_char(apps, schema_editor):
EncryptedCharModel = apps.get_model("fields", "EncryptedCharModel")
for row in EncryptedCharModel.objects.all():
row.old_field = row.field
row.save(update_fields=["old_field"])
migrations.RunPython(forwards_encrypted_char, reverse_encrypted_char)
# 4. Remove old field
migrations.RemoveField(
model_name='encryptedcharmodel',
name='old_field',
)