Open simkimsia opened 1 year ago
Not directly, but you can create a dependent datetime field, that updates from changes on comp
, something like this (untested):
class SomeModel(ComputedFieldsModel):
fieldA = ...
@computed(..., depends=[('self', ['fieldA'])])
def comp(self):
# do something with self.fieldA
return ...
@computed(models.DateTimeField(), depends=[('self', ['comp'])])
def comp_last_changed(self):
return timezone.now()
Edit: I am currently not sure, if the evaluation will be skipped if comp
did not change, but some other computed field on that model (so this needs def. testing).
Edit2: Nope this wont work in that case, as the mro evaluation does not stop on non-changes: https://github.com/netzkolchose/django-computedfields/blob/e73407564b5978da5b50e8a603ccb7e3aefa1a7c/computedfields/resolver.py#L544-L548
I think this could be patched in, so far strict updating was no issue, thus the code will eval the function if in doubt, which make the approach above not working correctly.
🤔
forgot to add that my comp
is a boolean, so when it flips, i need to track the last_changed
or in the original comp
method i can get the old value?
EDIT1:
i just saw I think this could be patched in, so far strict updating was no issue, thus the code will eval the function if in doubt, which make the approach above not working correctly.
hmm i guess there's no easy way to do this?
Right, thats not as easy as it sounds - currently the update resolver give no guarantees not to update a field in question. It resorts to "if in doubt - update anyway", since keeping values in sync is more important than anything else.
There is still a custom solution possible here (I admit its not nice) - the only field during cf updates, that knows whether comp
changed is the comp
function itself, thus you can expand on that like this (untested):
class SomeModel(ComputedFieldsModel):
fieldA = ...
comp_last_changed = models.DateTimeField()
@computed(..., depends=[('self', ['fieldA'])])
def comp(self):
# grab old value
old_value = self.comp
# do something with self.fieldA
new_value = ...
# update conditionally on value change
if old_value != new_value:
self.comp_last_changed = timezone.now()
if self.pk:
self.save(update_fields=['comp_last_changed'])
return new_value
The self.pk
condition with an additional save
call is unlucky, and in fact creates a double save for the same instance. Still it should get the trick done. The additional save work could be avoided, if the cf function would expose its internal call state, whether it was called from normal save
or the resolver, and act upon those, sadly thats not possible atm. Maybe thats a better way to approach this issue?
Edit: I think something like this should be possible to avoid the double save:
class SomeModel(ComputedFieldsModel):
fieldA = ...
comp_last_changed = models.DateTimeField()
@computed(..., depends=[('self', ['fieldA'])], may_update=['comp_last_changed'])
def comp(self):
# grab old value
old_value = self.comp
# do something with self.fieldA
new_value = ...
# update conditionally on value change
if old_value != new_value:
self.comp_last_changed = timezone.now()
return new_value
Note the additional kwarg may_update
on the decorator, with that I can extend the update_fields
clause in the resolver to also include that field during bulk_update
(its not even a big change).
Thinking again about the issue the ideas above are not ideal for these reasons:
comp_last_changed
is part of a depends
entry of some other cf (it will turn update execution upside-down)Imho a better and more versatile approach is to get signals from the update resolver done, see #58. So far I did not push that PR forward, as mass updates as normally done by the resolver create some issues about the right signal abstraction, like what types of signals we gonna need and in which transaction context the signal handlers should run. And for strict post signals (emitted after the resolver finished all transactions) it creates a lot of state to be held in memory.
Putting aside these issues for a moment, with the right signals something like this should be possible:
@receiver(post_change_computedfields, sender=SomeModel, field='comp')
def my_handler(sender, field, queryset, **kwargs):
# queryset now contains only instances, where comp changed
# do whatever you like based on those changes
...
let say
so i want a
comp_last_changed
which is a datetimehow do I do this?