diff --git a/README.rst b/README.rst index e78b4f8..46ca322 100644 --- a/README.rst +++ b/README.rst @@ -31,6 +31,58 @@ or `github `_:: 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 ------------- diff --git a/caching/__init__.py b/caching/__init__.py index 88bdfc2..ddfcbfa 100644 --- a/caching/__init__.py +++ b/caching/__init__.py @@ -2,3 +2,5 @@ VERSION = ('0', '9', '1') __version__ = '.'.join(VERSION) + +default_app_config = 'caching.apps.CacheMachineConfig' diff --git a/caching/apps.py b/caching/apps.py new file mode 100644 index 0000000..34f47d6 --- /dev/null +++ b/caching/apps.py @@ -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 diff --git a/caching/config.py b/caching/config.py index 99e3925..86c8642 100644 --- a/caching/config.py +++ b/caching/config.py @@ -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: