109

I have a function which currently calls Models.object.get(), which returns either 0 or 1 model objects:

  • if it returns 0, I create a new model instance in the except DoesNotExist clause of the function.
  • Otherwise, I would like to update the fields in the pre-existing instance, without creating a new one.

I was originally attempting to call .update() on the instance which was found, but .update() seems to be only callable on a QuerySets. How do I get around changing a dozen fields, without calling .filter() and comparing the lengths to know if I have to create or update a pre-existing instance?

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
zhuyxn
  • 6,671
  • 9
  • 38
  • 44

8 Answers8

117

With the advent of Django 1.7, there is now a new update_or_create QuerySet method, which should do exactly what you want. Just be careful of potential race conditions if uniqueness is not enforced at the database level.

Example from the documentation:

obj, created = Person.objects.update_or_create(
    first_name='John', last_name='Lennon',
    defaults={'first_name': 'Bob'},
)

The update_or_create method tries to fetch an object from database based on the given kwargs. If a match is found, it updates the fields passed in the defaults dictionary.


Pre-Django 1.7:

Change the model field values as appropriate, then call .save() to persist the changes:

try:
    obj = Model.objects.get(field=value)
    obj.field = new_value
    obj.save()
except Model.DoesNotExist:
    obj = Model.objects.create(field=new_value)
# do something else with obj if need be
kae_screechae
  • 169
  • 12
Platinum Azure
  • 45,269
  • 12
  • 110
  • 134
  • 5
    But suppose I have 20 fields, is there an easier way to update them? With .update() I suppose I can just pass in **kwargs? – zhuyxn Sep 12 '12 at 05:39
  • Try looking here: http://stackoverflow.com/questions/1576664/how-to-update-multiple-fields-of-a-django-model-instance – Platinum Azure Sep 12 '12 at 05:44
  • 9
    This approach has atomicity problems -- if two app instances both change the same instance in the DB,one could clobber the other. – Nils Oct 10 '13 at 06:22
  • 1
    Also use this only if you work with the object. If you just want to update, it would be much better to use filter with the corresponding pk attribute. This has the advantage that only one query is issued. If you use get for an update, two queries are used. – Chris Feb 07 '14 at 11:44
  • 1
    @zhuyxn `for attr, val in kwargs.iteritems(): setattr(obj, attr, val)` – DylanYoung Sep 22 '16 at 16:33
  • `.update_or_create()` also uses `.save()` under the hood, so it has the same effect at the end of the day if the obj exists, which you might want to avoid – slajma Jul 22 '20 at 20:20
65

if you want only to update model if exist (without create it):

Model.objects.filter(id = 223).update(field1 = 2)

mysql query:

UPDATE `model` SET `field1` = 2 WHERE `model`.`id` = 223
Eyal Ch
  • 9,552
  • 5
  • 44
  • 54
47

As of Django 1.5, there is an update_fields property on model save. eg:

obj.save(update_fields=['field1', 'field2', ...])

https://docs.djangoproject.com/en/dev/ref/models/instances/

I prefer this approach because it doesn't create an atomicity problem if you have multiple web app instances changing different parts of a model instance.

Nils
  • 5,612
  • 4
  • 34
  • 37
  • 1
    updated_fields is a list not dictionary then how we will pass the updated value for the field? – brainLoop Sep 26 '18 at 05:34
  • 4
    @brainLoop, you wouldn't pass the values separately. You would set them on the object itself. obj.field1=value before calling save on the obj. – codescribblr Nov 21 '19 at 14:20
  • 1
    update_fields argument prevents race conditions by forcing an update to the model for the specified fields. This way we avoid loading the model into memory and then saving which means that even if another application instance exists, it can't do anything in between. – dimyG Jun 02 '20 at 09:23
29

I don't know how good or bad this is, but you can try something like this:

try:
    obj = Model.objects.get(id=some_id)
except Model.DoesNotExist:
    obj = Model.objects.create()
obj.__dict__.update(your_fields_dict) 
obj.save()
Rohan
  • 52,392
  • 12
  • 90
  • 87
11

Here's a mixin that you can mix into any model class which gives each instance an update method:

class UpdateMixin(object):
    def update(self, **kwargs):
        if self._state.adding:
            raise self.DoesNotExist
        for field, value in kwargs.items():
            setattr(self, field, value)
        self.save(update_fields=kwargs.keys())

The self._state.adding check checks to see if the model is saved to the database, and if not, raises an error.

(Note: This update method is for when you want to update a model and you know the instance is already saved to the database, directly answering the original question. The built-in update_or_create method featured in Platinum Azure's answer already covers the other use-case.)

You would use it like this (after mixing this into your user model):

user = request.user
user.update(favorite_food="ramen")

Besides having a nicer API, another advantage to this approach is that it calls the pre_save and post_save hooks, while still avoiding atomicity issues if another process is updating the same model.

Julien
  • 5,243
  • 4
  • 34
  • 35
5

As @Nils mentionned, you can use the update_fields keyword argument of the save() method to manually specify the fields to update.

obj_instance = Model.objects.get(field=value)
obj_instance.field = new_value
obj_instance.field2 = new_value2

obj_instance.save(update_fields=['field', 'field2'])

The update_fields value should be a list of the fields to update as strings.

See https://docs.djangoproject.com/en/2.1/ref/models/instances/#specifying-which-fields-to-save

Rémi Héneault
  • 383
  • 3
  • 12
1

update:

1 - individual instance : get instance and update manually get() retrieve individual object

post = Post.objects.get(id=1)
post.title = "update title"
post.save()

2 - Set of instances : use update() method that works only with queryset that what would be returned by filter() method

Post.objects.filter(author='ahmed').update(title='updated title for ahmed')
omar ahmed
  • 635
  • 5
  • 19
0

I am using the following code in such cases:

obj, created = Model.objects.get_or_create(id=some_id)

if not created:
   resp= "It was created"
else:
   resp= "OK"
   obj.save()
urcm
  • 2,274
  • 1
  • 19
  • 45