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 variablecreated
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.
3 Answers
Answers 1
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'))
Answers 2
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.
Answers 3
I'm currently using mixed approach to handling this type of tasks:
I have last_edited
or last_modified
field on model without auto_now=True
.
Instead of auto_now
I use django-dirtyfields and inside save
method I do something like this:
def save(*args, **kwargs): if self.is_dirty(): self.last_modified = now() super().save(*args, **kwargs)
If you don't like the mess in save
method, you can use pre_save
signal and update last_modified
there based on dirty fields.
Then after calling update_or_create
you can pretty safely rely on last_modified
field in order to check if model was indeed modified.
dirtyfield's api is pretty simple, you can play with it without too much setup pain.
0 comments:
Post a Comment