I have a need to track changes on Django model instances. I'm aware of solutions like django-reversion but they are overkill for my cause.
I had the idea to create a parameterized class decorator to fit this purpose. The arguments are the field names and a callback function. Here is the code I have at this time:
def audit_fields(fields, callback_fx):
def __init__(self, *args, **kwargs):
self.__old_init(*args, **kwargs)
self.__old_state = self.__get_state_helper()
def save(self, *args, **kwargs):
new_state = self.__get_state_helper()
for k,v in new_state.items():
if (self.__old_state[k] != v):
callback_fx(self, k, self.__old_state[k], v)
val = self.__old_save(*args, **kwargs)
self.__old_state = self.__get_state_helper()
return val
def __get_state_helper(self):
# make a list of field/values.
state_dict = dict()
for k,v in [(field.name, field.value_to_string(self)) for field in self._meta.fields if field.name in fields]:
state_dict[k] = v
return state_dict
def fx(clazz):
# Stash originals
clazz.__old_init = clazz.__init__
clazz.__old_save = clazz.save
# Override (and add helper)
clazz.__init__ = __init__
clazz.__get_state_helper = __get_state_helper
clazz.save = save
return clazz
return fx
And use it as follows (only relevant part):
@audit_fields(["status"], fx)
class Order(models.Model):
BASKET = "BASKET"
OPEN = "OPEN"
PAID = "PAID"
SHIPPED = "SHIPPED"
CANCELED = "CANCELED"
ORDER_STATES = ( (BASKET, 'BASKET'),
(OPEN, 'OPEN'),
(PAID, 'PAID'),
(SHIPPED, 'SHIPPED'),
(CANCELED, 'CANCELED') )
status = models.CharField(max_length=16, choices=ORDER_STATES, default=BASKET)
And test on the Django shell with:
from store.models import Order
o=Order()
o.status=Order.OPEN
o.save()
The error I receive then is:
TypeError: int() argument must be a string or a number, not 'Order'
The full stacktrace is here: https://gist.github.com/4020212
Thanks in advance and let me know if you would need more info!
EDIT: Question answered by randomhuman, code edited and usable as shown!
You do not need to explicitly pass a reference to self on this line:
val = self.__old_save(self, *args, **kwargs)
It is a method being called on an object reference. Passing it explicitly in this way is causing it to be seen as one of the other parameters of the save method, one which is expected to be a string or a number.
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