13

Is there a way, hopefully without breaking admin, to disable editing existing model instances on the ORM level?

I'm not talking about removing 'Save' and 'Save and continue' buttons from templates - there should be no operations that can change the values of a committed instance of a model.

Preferably, the 'Save As' option should work instead.

qdot
  • 6,195
  • 5
  • 44
  • 95

3 Answers3

15

Overwrite the save function for your model like so:

class MyModel(models.Model):

    def save(self, *args, **kwargs):

        if self.pk is None:
            super(MyModel, self).save(*args, **kwargs)

This function only call the superclass save function (which actually saves the change) if there is no pk, e.g. the model instance is new.

Kevin Audleman
  • 334
  • 2
  • 4
  • 3
    What I do not like about this approach is that after clicking on save there is still message "The ... was changed successfully.", altho nothing was changed. Is there way to change this message to "Changing is disabled" ? Or even better, to disable clicking on that row ? – WebOrCode Dec 04 '13 at 13:04
  • 7
    Is there a way to prevent `MyModel.objects.filter(pk=my_model.pk).update(name="bob")` – Aleck Landgraf Aug 04 '14 at 21:59
8

You could override your model class's save() (do nothing if self.pk) and delete (always do nothing)

But really, the database level is the safest place for that. For example, in PostgreSQL you could write two simple rules:

CREATE RULE noupd_myapp_mymodel AS ON UPDATE TO myapp_mymodel
   DO NOTHING;
CREATE RULE nodel_myapp_mymodel AS ON DELETE TO myapp_mymodel
   DO NOTHING;

Either way, the admin wouldn't know anything about this, so everything still looks editable. See my answer to Whole model as read-only for an attempt at making a model read-only in the admin. For your purposes, keep the add permission as-is, and only declare all fields read-only when not adding.

EDIT: One reason why overriding delete() in your model class is not safe, is the fact that "bulk delete" (Queryset.delete(), e.g. admin checkboxes action) will not call the individual instances' delete() method, it will go straight to SQL: https://docs.djangoproject.com/en/dev/topics/db/queries/#deleting-objects

Community
  • 1
  • 1
Danny W. Adair
  • 12,498
  • 4
  • 43
  • 49
  • Thank you for this answer. I am trying to figure out how to pass these rules on models.py. The most relevant part of the documentation I found was the [Executing custom SQL directly](https://docs.djangoproject.com/en/dev/topics/db/sql/#executing-custom-sql-directly). Could you please give a simple example to illustrate this approach? – raratiru Mar 27 '14 at 22:49
  • 1
    The above SQL statemtents are only executed once, any way you like. Once the rule is in Postgres it will be adhered to. So you could use psql to do this. If you use South for model migrations, I would recommend a custom migration that db.execute() the rules. See for example the answer http://stackoverflow.com/a/5466251/640759 – Danny W. Adair Mar 28 '14 at 00:23
0

For those who need to prevent MyModel.objects.filter(pk=123).update(name="bob"):

class NoUpdateQuerySet(models.QuerySet):
    def update(self, *args, **kwargs):
        pass

class MyModel(models.Model):
    objects = NoUpdateQuerySet.as_manager()
    ...

Django docs - link

Sergey Geron
  • 9,098
  • 2
  • 22
  • 29