mattwilliamson
11/4/2011 - 8:13 PM

Metaclass Django Model Instance Caching

Metaclass Django Model Instance Caching

import logging
import uuid
import re
from decimal import Decimal

from django.conf import settings
from django.db import models
from django.db.models import signals
from django.dispatch import receiver
from django.core.cache import cache

def post_save_cache(sender, instance, signal, *args, **kwargs):
    """This is called when any BaseModel subclass instance is saved. Then we cache the instance."""
    logging.debug('Saving %s instance to cache.' % instance.__class__.__name__)
    
    # Store full instance under SID-based Key
    cache.set(instance.get_cache_key_sid(instance.sid), instance, settings.DEFAULT_INSTANCE_CACHE_TIME)
    
    # Store SID under PK-based key
    cache.set(instance.get_cache_key_pk(instance.pk), instance, settings.DEFAULT_INSTANCE_CACHE_TIME)

class InstanceCachable(models.Model.__metaclass__):
    """Meta class for Models. 
    Used to setup post/pre-save signals for caching all models that inherit from BaseModel"""
    def __init__(cls, name, bases, dct):
        super(InstanceCachable, cls).__init__(name, bases, dct)
        signals.post_save.connect(post_save_cache, sender=cls)

class BaseModel(models.Model):
    """Contains some basic fields for most models to contain"""
    __metaclass__ = InstanceCachable
    
    class Meta:
        abstract = True
        ordering = ('-date_created',)
        
    sid_prefix = ''
    
    date_created    = models.DateTimeField(blank=True, null=True, auto_now_add=True)
    date_updated    = models.DateTimeField(blank=True, null=True, auto_now=True)
    sid             = models.CharField(max_length=34, db_index=True, unique=True, blank=True, null=True)
    friendly_name   = models.CharField(max_length=64, blank=True, default='')
    
    def __unicode__(self):
        return self.friendly_name or self.sid
    
    def generate_sid(self):
        self.sid = str(getattr(self, 'sid_prefix', self.__class__.__name__))[:2] + uuid.uuid4().hex
    
    def save(self, *args, **kwargs):
        if self.sid in ('', None):
            self.generate_sid()
            
        super(BaseModel, self).save(*args, **kwargs)
        
    @classmethod
    def get_by_sid(cls, sid):
        return cls.object.get(sid=sid)
        
    @classmethod
    def get_cache_key_sid(cls, sid):
        """Returns a string used as a caching key. SID-based."""
        return 'Instance.sid.%s.%s' % (cls.__name__, sid)
        
    @classmethod
    def get_cache_key_pk(cls, pk):
        """Returns a string used as a caching key. Primary key-based."""
        return 'Instance.pk.%s.%s' % (cls.__name__, pk)
        
    @classmethod
    def get_by_sid(cls, sid):
        logging.debug('Fetching %s from cache with sid "%s"' % (cls.__name__, sid))
        key = cls.get_cache_key_sid(sid)
        instance = cache.get()
        
        # If not in the cache, fetch from db and put into cache
        if not instance:
            logging.debug('Not in cache. Fetching from DB.')
            instance = cls.objects.get(sid=sid)
            post_save_cache(cls, instance, signals.post_save)
            
        return instance
        
    @classmethod
    def get_by_pk(cls, pk):
        logging.debug('Fetching %s from cache with primary key "%s"' % (cls.__name__, pk))
        key = cls.get_cache_key_pk(pk)
        instance = cache.get()
        
        # If not in the cache, fetch from db and put into cache
        if not instance:
            logging.debug('Not in cache. Fetching from DB.')
            instance = cls.objects.get(pk=pk)
            post_save_cache(cls, instance, signals.post_save)
            
        return instance