Skip to content

RFC: Cache arbitrary and m2m models #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,58 @@ or `github <http://github.com/django-cache-machine/django-cache-machine>`_::

pip install -e git://github.com/django-cache-machine/django-cache-machine.git#egg=django-cache-machine

then add ``caching`` to your ``INSTALLED_APPS``.


Configuration
-------------

To use Cache Machine with your own models, subclass from
``caching.base.CachingMixin`` and use or subclass
``caching.base.CachingManager`` for your manager instance.

To use Cache Machine with arbitrary models specify the import string with the
``CACHE_MACHINE_MODELS`` setting. This can also be used to register models
automatically generated from``ManyToManyField``s by specifying the ``through``
attribute on the ManyToManyField directly.

Example:
~~~~~~~~

``yourapp/models.py``:

from django.db import models

class Thing(models.Model):
others = models.ManyToManyField('self')


``anotherapp/models.py``:

from django.db import models

class OtherThing(models.Model):
# ...

objects = models.Manager()


``settings.py``:

CACHE_MACHINE_MODELS = {
# Cache the automatically generated intermediate model for the ManyToManyField
'yourapp.models.Thing.others.through': {},

# Cache a model from another app
'anotherapp.models.OtherThing': {
# Options dictionary
}
}

Right now the only available option is ``manager_name``, which you can use to
cache only certain managers. This option defaults to ``objects``, so it can be
safely omitted in almost all cases.


Running Tests
-------------
Expand Down
2 changes: 2 additions & 0 deletions caching/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

VERSION = ('0', '9', '1')
__version__ = '.'.join(VERSION)

default_app_config = 'caching.apps.CacheMachineConfig'
102 changes: 102 additions & 0 deletions caching/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from importlib import import_module

from django.apps import AppConfig, apps
from django.db.models.manager import Manager

from .base import CachingManager, CachingMixin
from .config import settings


class CacheMachineConfig(AppConfig):
name = 'caching'
verbose_name = 'Cache Machine'

def ready(self):
for model_path, model_options in settings.CACHE_MACHINE_MODELS.items():
model_parts = model_path.split('.')

# We use negative indexing so we can support contrib apps out of
# the box.
#
# Example:
#
# 'django.contrib.contenttypes.models.ContentType'
#
# app_name: contenttypes
# model_name: ContentType
#
app_name = model_parts[-3]
model_name = model_parts[-1]

# Support auto-generated through models for ManyToManyFields
#
# Example:
#
# `models.py`:
#
# class OtherThing(models.Model):
# ...
#
#
# class Thing(models.Model):
# ...
# other_things = models.ManyToManyField(OtherThing)
#
#
# `settings.py`:
#
# 'testapp.models.TestThing.other_things.through'
#
# app_name: testapp
# model_name: Thing_other_things
#
if model_name == 'through':
_app, _, _model, _field = model_parts[-5:-1]
_Model = apps.get_model(_app, _model)

field = getattr(_Model, _field)

app_name = field.through.__module__.split('.')[-2]
model_name = field.through.__name__
Model = apps.get_model(app_name, model_name)

# Use CachingManager for automatically generated through models
#
# The related_manager_cls function in
# django/db/models/fields/related_descriptors.py
# uses model._default_manager.__class__, so we set that to an
# *instance* of CachingManager here
#
field.through._default_manager = CachingManager()
else:
# Grab the model
Model = getattr(import_module('{}.models'.format(app_name)), model_name)

# Add CachingMixin to the model's bases
if CachingMixin not in Model.__bases__:
Model.__bases__ = (CachingMixin,) + Model.__bases__

# Support arbitrarily-named managers
manager_name = model_options.get('manager_name', 'objects')

# model_manager is the *instance* of the manager
model_manager = getattr(Model, manager_name)

# ModelManager is the manager *class*
ModelManager = model_manager.__class__

# We have to specially handle boring managers; luckily, this is much
# more straightforward
if ModelManager == Manager:
model_manager.__class__ = CachingManager

elif not any(issubclass(Base, CachingManager) for Base in ModelManager.__bases__):
# Dynamically create a new type of manager with a CachingManager
# mixin and swap the model's manager's *type* out for it
new_bases = (CachingManager,) + ModelManager.__bases__

new_manager_type_name = 'Caching{}'.format(ModelManager.__name__)

NewManagerType = type(new_manager_type_name, new_bases, {})

model_manager.__class__ = NewManagerType
1 change: 1 addition & 0 deletions caching/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
CACHE_INVALIDATE_ON_CREATE = getattr(settings, 'CACHE_INVALIDATE_ON_CREATE', None)
CACHE_MACHINE_NO_INVALIDATION = getattr(settings, 'CACHE_MACHINE_NO_INVALIDATION', False)
CACHE_MACHINE_USE_REDIS = getattr(settings, 'CACHE_MACHINE_USE_REDIS', False)
CACHE_MACHINE_MODELS = getattr(settings, 'CACHE_MACHINE_MODELS', {})

_invalidate_on_create_values = (None, WHOLE_MODEL)
if CACHE_INVALIDATE_ON_CREATE not in _invalidate_on_create_values:
Expand Down