Immutable Django model fields
I wanted to make a field on a Django model read-only after it was assigned
an initial value. Maybe I was having a slow day, or my Google-fu was
especially lacking, but I couldn't find a documented way to do this. There's
the editable Field option, but I wanted to ensure I couldn't
mistakenly overwrite the initial value anywhere — not just in the admin
interface or form processing. It turned out to be pretty easy.
The solution was to override the Model's __setattr__ method and intercept writes
for the Field in question. Here's an example Model:
def random_hash():
digest = hashlib.sha512()
digest.update("%s%s%s" % (settings.SECRET_KEY, time.time(), random.random()))
l = long(digest.hexdigest(), 16)
return struct.pack('d', l).encode('base64').replace('\n', '').strip('=')
class Order(models.Model):
confirmation_id = models.CharField(max_length=128, default=random_hash)
def __setattr__(self, name, value):
if name == 'confirmation_id':
if getattr(self, 'confirmation_id', None):
return
super(Order, self).__setattr__(name, value)
def __unicode__(self):
return u"Order %s, confirmation_id: %s" % (self.id, self.confirmation_id)
Not elegant, but it works:
>>> o = Order()
>>> o.save()
>>> o
<Order: Order 4, confirmation_id: vzb0jXVz6l8>
>>> o.confirmation_id = 'something else'
>>> o
<Order: Order 4, confirmation_id: vzb0jXVz6l8>
>>>
A more general solution would be a Model subclass with a list of the immutable fields. You can't put that in the model's Meta class (see this ticket), so here's what I use:
class ImmutableModel(models.Model):
immutable_fields = []
mutable_fields = []
class Meta:
abstract = True
def __setattr__(self, name, value):
if name == 'immutable_fields' or (not self.immutable_fields or name in self.immutable_fields) or (self.mutable_fields and name not in self.mutable_fields):
try:
current_value = getattr(self, name, None)
except:
current_value = None
if current_value is not None and current_value is not '':
return
super(ImmutableModel, self).__setattr__(name, value)
Make your model a subclass of that and set the immutable_fields attribute to a list of fields that should be immutable once set. Conversely, you can define the fields that should be mutable; all others will be immutable.
Good enough to protect you from yourself unless you're really trying.
Comments (3)
if getattr(self, name, None) is not None:
because some returned values could evaluate to a boolean false (i.e. fields with a value of 0), and thus bypass the immutability
Thanks Ryan.
Comments have been turned off for this article, but you can always contact us about it.