About •  Books •  Apps •  Jobs •   Feeds •  Twitter
Login

Read only fields in models

Supposing you have a field in your model and you don't want it to be changed, ever.

How would you do that? Well actually you can't from Django, which we'll see in a minute. Here's a script that should work for most people. Basically, set up a signal (or a custom save method) so that when your object is saved it compares the old to new and then figures out if it can be changed.

from django.db import models

class BankAccount(models.Model):
    amount = models.DecimalField(max_digits=20, decimal_places=2)
    number = models.CharField(max_length=10)
    
    read_only = ("number",)
        
def read_only(sender, instance, **kw):
    if instance.id:
        old = sender.objects.get(id=instance.id)
        for field in sender.read_only:
            if (getattr(old, field) != getattr(instance, field)):
                raise ValueError, "The field %s cannot be changed, once set, ever." % field

models.signals.pre_save.connect(read_only, sender=BankAccount)

On a save, we look up the read_only attribute and see if any of those fields have changed and raise an error if they do. In this scenario, you can change the amount of money in a bank account, but the number of the account will not change once set.

Here's a test to check it works:

from django.test import TestCase                
from models import BankAccount
from decimal import Decimal

class tests(TestCase):
    def testReadOnly(self):
        bank = BankAccount()
        bank.number = "1234567890"
        bank.amount = Decimal("100.00")
        bank.save()
        
        id = bank.id
        
        bank.amount = Decimal("200.00")
        bank.save()
        
        bank.number = "9999999999"
        self.assertRaises(ValueError, bank.save)

This is awfully close to model validation. Something that Django doesn't have yet. And it there's two areas it can break down.

        # Warning does not work with update
        BankAccount.objects.all().update(number="999999999")
        # eeek!
        assert BankAccount.objects.get(id=id).number == "999999999"

 

This is discussed here and here. I wonder if it's worth going any further on this until model validation comes along. Perhaps not.

Comments

There are no comments.

Login to add comments