+class DenormalizedField(object):
+ def __init__(self, manager, **kwargs):
+ self.manager = manager
+ self.filter = kwargs
+
+ def setup_class(self, cls, name):
+ dict_name = '_%s_cache_' % name
+
+ def getter(inst):
+ val = inst.__dict__.get(dict_name, None)
+
+ if val is None:
+ val = getattr(inst, self.manager).filter(**self.filter).count()
+ inst.__dict__[dict_name] = val
+ inst.__class__.objects.cache_obj(inst)
+
+ return val
+
+ def reset_cache(inst):
+ inst.__dict__.pop(dict_name, None)
+ inst.__class__.objects.cache_obj(inst)
+
+ cls.add_to_class(name, property(getter))
+ cls.add_to_class("reset_%s_cache" % name, reset_cache)
+
+
+class BaseMetaClass(models.Model.__metaclass__):
+ to_denormalize = []
+
+ def __new__(cls, *args, **kwargs):
+ new_cls = super(BaseMetaClass, cls).__new__(cls, *args, **kwargs)
+
+ BaseMetaClass.to_denormalize.extend(
+ [(new_cls, name, field) for name, field in new_cls.__dict__.items() if isinstance(field, DenormalizedField)]
+ )
+
+ return new_cls
+
+ @classmethod
+ def setup_denormalizes(cls):
+ for new_cls, name, field in BaseMetaClass.to_denormalize:
+ field.setup_class(new_cls, name)
+
+