AFAIK Django does not provide a generic way to see if data was changed by update_or_create()
The boolean created
tells me that a row was created. But how can I know if data was changed (SQL UPDATE which changed data)
Example:
obj, created = MyModel.update_or_create(pk=12345,
defaults=dict(name='Doctor Zhivago'))
There are three cases:
obj
was created. No problem, I have the boolean variable created
obj
was not created, it was updated. For example the previous name was "Machuca".obj
was not created and not updated. For example the previous name was already "Doctor Zhivago".I can't distinguish between case2 and case3 at the moment.
Feeling a bit inspired by @bakkal, I've written a mixin class that you can apply to a custom Manager class, then assign that custom Manager to your MyModel
class. This changes the format of the returned tuple to (obj, created, updated)
. In the loop over the values in defaults.items()
I am checking if any of the new values are different from the old values.
class UpdateOrCreateMixin(object):
def update_or_create_v2(self, defaults=None, **kwargs):
"""
Look up an object with the given kwargs, updating one with defaults
if it exists, otherwise create a new one.
Return a tuple (object, created, updated), where created and updated are
booleans specifying whether an object was created.
"""
defaults = defaults or {}
lookup, params = self._extract_model_params(defaults, **kwargs)
self._for_write = True
with transaction.atomic(using=self.db):
try:
obj = self.select_for_update().get(**lookup)
except self.model.DoesNotExist:
obj, created = self._create_object_from_params(lookup, params)
if created:
return obj, created, False
updated = False
for k, v in defaults.items():
oldattr = getattr(obj, k)
if oldattr != (v() if callable(v) else v):
updated = True
setattr(obj, k, v() if callable(v) else v)
obj.save(using=self.db)
return obj, False, updated
class CustomManager(UpdateOrCreateMixin, models.Manager):
pass
class MyModel(models.Model)
field = models.CharField(max_length=32)
objects = CustomManager()
obj, created, updated = MyModel.objects.update_or_create_v2(pk=12345,
defaults=dict(name='Doctor Zhivago'))
Given how that interface is implemented, there's no way to tell if the object has been updated or not if it's created. Because it always calls save()
in that case.
Here's how update_or_create()
is currently implemented:
with transaction.atomic(using=self.db):
try:
obj = self.select_for_update().get(**lookup)
except self.model.DoesNotExist:
obj, created = self._create_object_from_params(lookup, params)
if created:
return obj, created
# [below part always runs the same if obj was not created]
for k, v in defaults.items():
setattr(obj, k, v() if callable(v) else v)
obj.save(using=self.db)
return obj, False
Ref. https://github.com/django/django/blob/master/django/db/models/query.py#L459
As you can see when the object is not created, the way it's implemented can't tell if it was updated or not.
However you could manually verify if your lookup/update values are different from those on the existing object in the DB before caling save()
, and then set an updated
flag.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With