@@ -124,38 +124,25 @@ def __set__(self, instance: models.Model, value: str) -> None:
124124
125125
126126class EncryptableModelMixin (models .Model ):
127- """A mixin for models that use ``EncryptedProperty`` to handle sensitive data .
127+ """Base mixin for models that store encrypted data via ``EncryptedProperty``.
128128
129- Any model that stores encrypted fields should inherit from this mixin.
130- It provides:
129+ Plugin models that hold sensitive per-user data (API keys, observatory
130+ credentials) should inherit from this mixin alongside ``models.Model``.
131+ It provides a standardized ``user`` ForeignKey that ties the encrypted
132+ data to its owner. The helper functions ``get_encrypted_field()`` and
133+ ``set_encrypted_field()`` in ``session_utils`` use this user reference
134+ to look up the user's DEK (via their ``Profile.encrypted_dek``) and
135+ build the Fernet cipher needed by the ``EncryptedProperty`` descriptors.
131136
132- - A standardized ``user`` OneToOneField so that utility functions can
133- always find the user associated with an encryptable model instance.
134- - A ``clear_encrypted_fields()`` method to null out all encrypted fields
135- (used when a user's DEK must be regenerated).
137+ Usage::
138+
139+ class MyAppModel(EncryptableModelMixin, models.Model):
140+ _api_key_encrypted = models.BinaryField(null=True)
141+ api_key = EncryptedProperty('_api_key_encrypted')
136142
137143 Subclasses should not redefine the ``user`` field.
138144 """
139145 user = models .OneToOneField (settings .AUTH_USER_MODEL , on_delete = models .CASCADE )
140146
141- def clear_encrypted_fields (self ) -> None :
142- """Clear all fields managed by an ``EncryptedProperty`` descriptor.
143-
144- Sets each encrypted BinaryField to None and saves the model. This is a
145- destructive operation — the encrypted data is permanently lost.
146- """
147- model_save_needed = False
148- for attr_name in dir (self .__class__ ):
149- attr = getattr (self .__class__ , attr_name )
150- if isinstance (attr , EncryptedProperty ):
151- # Set the underlying BinaryField directly to None, bypassing
152- # the descriptor (which would require a cipher).
153- setattr (self , attr .db_field_name , None )
154- model_save_needed = True
155- logger .info (f"Cleared encrypted property '{ attr_name } ' for { self .__class__ .__name__ } "
156- f"instance { getattr (self , 'pk' , 'UnknownPK' )} ." )
157- if model_save_needed :
158- self .save ()
159-
160147 class Meta :
161148 abstract = True
0 commit comments