pythondjangomodel

Why is it not possible to update a django model instance like this: instance(**update_dict)?


I can make a new django model object with Model(**dictionary) but if I want to update an instance in the same way, I've found only update_or_create which has its problems and often returns 'duplicate PK' errors. It also doesn't seem particularly pythonic to call a custom helper function on a model object. Alternatively, you can use this pattern:

 Model.objects.get(id=id).update(**dict)

or

model.__dict__.update(**dict)
model.save()

The latter feels like a hack and I read a few times that it's not 'supposed' to be done this way. The first method requires a query call and again feels incorrect since the model instance is likely already instantiated and to update it we need to send a query to the DB again?

I also read that 'you can do this with a form' - but its not always users that are updating models in the DB, there are all kinds of functions we might write which requires models to be updated. I wonder, is there a reason that the below code is not possible/implemented? Is there a reason that model objects are not directly callable and updateable in this way?

model_instance(**dict)
model_instance.save()

Solution

  • Model instances [Django docs] do not have any built-in methods which update the field values by feeding in keyword arguments.

    Queryset update()

    The following query will update the database row immediately, but has the disadvantage that it won't call any pre-save or post-save signals [Django docs] that you may have registered to the model:

    Model.objects.filter(id=model_instance.id).update(**dict_)
    

    And as you mentioned, if you intend to keep using the same instance you would need to update its values from the database:

    model_instance.refresh_from_db()
    

    Why model_instance(**dict_) doesn't work

    Django model instances (objects) are not callable. Objects cannot be called like functions by default. For example, when you create a plain dict dict_ = dict(), you can't update it by calling dict_(**kwargs). An object can be made callable by overriding the __call__ [Python docs] class method.

    A solution – custom update() method on model

    You could create your own model method so you're only making one database call (and preserving signals). For example you could create an abstract base class [Django docs] that all of your models inherit from (rather than from models.Model) e.g.

    class ModelWithUpdate(models.Model):
        class Meta:
            abstract = True
    
        def update(self, commit=False, **kwargs):
            for key, value in kwargs.items():
                setattr(self, key, value)
            if commit:
                self.save()
    

    And then your usage would be:

    model_instance.update(**dict_)
    model_instance.save()
    
    # or a shortcut
    model_instance.update(commit=True, **dict_)