17

What it says on the tin. Is there a way to make a Django model read-only?

By this I mean a Django model in which once records have been created, they can't be edited.

This would be useful for a model that records transaction history.

AP257
  • 89,519
  • 86
  • 202
  • 261

4 Answers4

13

You can override the model's save method and check whether it's an existing entity, in which case you won't save any changes:

def save(self, *args, **kwargs):
    if self.id is None:
        super(ModelName, self).save(*args, **kwargs)

So in this example you only save the changes when the entity has not got an id yet, which is only the case when it's a new entity that hasn't been inserted yet.

Tom van Enckevort
  • 4,170
  • 1
  • 25
  • 40
  • Incorrect. Non-`None` IDs that don't exist in the table will also be new records. – Ignacio Vazquez-Abrams Dec 02 '10 at 11:11
  • @Ignacio: I think you're right. Someone can set the id property before saving the entity, in which case id is not None. – Tom van Enckevort Dec 02 '10 at 11:16
  • You can just use `if not self.id:`. It's python after all :) Or alternatively: `if self.id: raise NotImplementedError("Editing not allowed")` – vdboor Dec 02 '10 at 11:32
  • @lazerscience: yes, that's an option, but I'm trying not to do an extra call to the database, just to check whether the id already exists. – Tom van Enckevort Dec 02 '10 at 11:34
  • @vdboor: but the person using this model might already have set the id in code when creating an instance. In which case it wouldn't get saved, even if the id is unique. – Tom van Enckevort Dec 02 '10 at 11:36
  • Also, this should be a relatively rare case. If you're using this method, I'm assuming that you'd have a fair amount of control on how this model will be used. If someone tries to set the id manually, and it doesn't save, bad luck. – Josh Smeaton Dec 02 '10 at 23:40
  • You'll also want to update your default manager's update method. – DylanYoung Nov 14 '16 at 21:23
4

You can override the save method and not call super if you wanted to. That'd be a fairly easy way of accomplishing this.

# blatantly ripped the save from another answer, since I forgot to save original model
def save(self, *args, **kwargs):
    if self.id is None:
        super(ModelName, self).save(*args, **kwargs)

def delete(self, *args, **kwargs):
    return

You should probably also raise an exception if a delete or update is attempting to occur instead of simply returning. You want to signal the user what is happening - that the behaviour isn't valid.

Josh Smeaton
  • 47,939
  • 24
  • 129
  • 164
2

In addition to other solutions: If your main goal is to avoid write access from the admin, you can modify the used admin class so that nobody has an add/change permission:

class HistoryAdmin(admin.ModelAdmin):

    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False
Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148
  • 7
    Readonly means one can still read them. If you remove change permission, that's not the case anymore. – webjunkie Oct 12 '11 at 10:37
1

If you don't want an attempt to modify a record to fail silently:

def save(self, *args, **kwargs):
    if self.pk:
        (raise an exception)
    super(YourModel, self).save(*args, **kwargs)


def delete(self, *args, **kwargs):
    (raise an exception)
Vinayak Kaniyarakkal
  • 1,110
  • 17
  • 23