Refactored template loader to use new backend architecture, that populates the cache by using signals.

Adds a BaseCacheBackend class with a simple API (load, save, remove) to be subclassed and specified in the DBTEMPLATES_CACHE_BACKEND settings.
Adds DjangoCacheBackend (uses Django's own caching mechanism) and FileSystemBackend (simple approach to filesystem saving).

git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@58 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : fde60d8ab2e834d898d9f662cf013d5546370eeb
This commit is contained in:
leidel 2008-11-02 00:05:38 +00:00
parent 391b00c655
commit 41ff350628
3 changed files with 170 additions and 67 deletions

97
dbtemplates/cache.py Normal file
View file

@ -0,0 +1,97 @@
import os
from django.conf import settings
from django.core.cache import cache
from django.contrib.sites.models import Site
from django.template import TemplateDoesNotExist
from django.core.exceptions import ImproperlyConfigured
from django.utils.encoding import smart_unicode, force_unicode
class BaseCacheBackend(object):
"""
Base class for custom cache backend of dbtemplates to be used while
subclassing.
Set DBTEMPLATES_CACHE_BACKEND setting to the Python path to that subclass.
"""
def __init__(self):
self.site = Site.objects.get_current()
def load(self, name):
"""
Loads a template from the cache with the given name.
"""
raise NotImplemented
def save(self, name, content):
"""
Saves the given template content with the given name in the cache.
"""
raise NotImplemented
def remove(self, name):
"""
Removes the template with the given name from the cache.
"""
raise NotImplemented
class DjangoCacheBackend(BaseCacheBackend):
"""
A cache backend that uses Django's cache mechanism.
"""
def _cache_key(self, name):
return 'dbtemplates::%s' % name
def load(self, name):
cache_key = self._cache_key(name)
return cache.get(cache_key)
def save(self, name, content):
cache_key = self._cache_key(name)
cache.set(cache_key, content)
def remove(self, name):
cache_key = self._cache_key(name)
cache.delete(cache_key)
class FileSystemBackend(BaseCacheBackend):
"""
A cache backend that uses simple files to hold the template cache.
"""
def __init__(self):
try:
self.cache_dir = getattr(settings, 'DBTEMPLATES_CACHE_DIR', None)
self.cache_dir = os.path.normpath(self.cache_dir)
if not os.path.isdir(self.cache_dir):
raise Exception
except:
raise ImproperlyConfigured('You\'re using the dbtemplates\' file system cache backend without having set the DBTEMPLATES_CACHE_DIR setting to a valid value. Make sure the directory exists and is writeable for the user your Django instance is running with.')
super(FileSystemBackend, self).__init__()
def _filepath(self, name):
return os.path.join(self.cache_dir, name)
def load(self, name):
try:
filepath = self._filepath(name)
return open(filepath).read().decode('utf-8')
except:
raise None
def save(self, name, content, retry=False):
try:
filepath = self._filepath(name)
dirname = os.path.dirname(filepath)
if not os.path.exists(dirname):
os.makedirs(dirname)
cache_file = open(filepath, 'w')
cache_file.write(force_unicode(content).encode('utf-8'))
cache_file.close()
except Exception:
raise
def remove(self, name):
try:
filepath = self._filepath(name)
os.remove(filepath)
except:
pass

View file

@ -1,75 +1,32 @@
import os
from django.conf import settings
from django.db.models import signals
from django.template import TemplateDoesNotExist
from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured
from dbtemplates.models import Template
from dbtemplates.models import Template, backend
try:
site = Site.objects.get_current()
except:
site = None
try:
cache_dir = os.path.normpath(getattr(settings, 'DBTEMPLATES_CACHE_DIR', None))
if not os.path.isdir(cache_dir):
raise
except:
cache_dir = None
def load_template_source(template_name, template_dirs=None):
"""
Tries to load the template from DBTEMPLATES_CACHE_DIR. If it does not exist
loads templates from the database by querying the database field ``name``
with a template path and ``sites`` with the current site,
and tries to save the template as DBTEMPLATES_CACHE_DIR/``name`` for subsequent
requests.
If DBTEMPLATES_CACHE_DIR is not configured falls back to database-only operation.
Tries to load the template from DBTEMPLATES_CACHE_DIR. If it does not
exist loads templates from the database by querying the database field
``name`` with a template path and ``sites`` with the current site,
and tries to save the template as DBTEMPLATES_CACHE_DIR/``name`` for
subsequent requests. If DBTEMPLATES_CACHE_DIR is not configured falls
back to database-only operation.
"""
if site is not None:
if cache_dir is not None:
filepath = os.path.join(cache_dir, template_name)
try:
return (open(filepath).read(), filepath)
except IOError:
try:
t = Template.objects.get(name__exact=template_name, sites__pk=site.id)
try:
f = open(filepath, 'w')
f.write(t.content)
f.close()
except IOError:
try:
head, tail = os.path.split(filepath)
if head and not os.path.isdir(head):
os.makedirs(head)
except Exception:
pass
return (t.content, 'db:%s:%s' % (settings.DATABASE_ENGINE, template_name))
except:
pass
else:
try:
t = Template.objects.get(name__exact=template_name, sites__pk=site.id)
return (t.content, 'db:%s:%s' % (settings.DATABASE_ENGINE, template_name))
except:
pass
display_name = 'db:%s:%s' % (settings.DATABASE_ENGINE, template_name)
if backend:
try:
backend_template = backend.load(template_name)
if backend_template is not None:
return backend_template, template_name
except:
pass
try:
template = Template.objects.get(name__exact=template_name,
sites__pk=settings.SITE_ID)
return (template.content, display_name)
except:
pass
raise TemplateDoesNotExist, template_name
load_template_source.is_usable = True
def remove_cached_template(instance, **kwargs):
"""
Called via django's signals to remove cached templates, if the template in the
database was changed or deleted.
"""
if cache_dir is not None:
try:
filepath = os.path.join(cache_dir, instance.name)
os.remove(filepath)
except OSError:
pass
signals.post_save.connect(remove_cached_template, sender=Template)
signals.pre_delete.connect(remove_cached_template, sender=Template)

View file

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from django.db import models
from django.conf import settings
from django.db.models import signals
from django.contrib.sites.models import Site
from django.utils.translation import gettext_lazy as _
from django.template import TemplateDoesNotExist
from django.template.loader import find_template_source
from django.core.exceptions import ImproperlyConfigured
class Template(models.Model):
"""
@ -14,7 +16,7 @@ class Template(models.Model):
"""
name = models.CharField(_('name'), unique=True, max_length=100, help_text=_("Example: 'flatpages/default.html'"))
content = models.TextField(_('content'), blank=True)
sites = models.ManyToManyField(Site)
sites = models.ManyToManyField(Site, default=[settings.SITE_ID])
creation_date = models.DateTimeField(_('creation date'), default=datetime.now)
last_changed = models.DateTimeField(_('last changed'), default=datetime.now)
@ -29,6 +31,8 @@ class Template(models.Model):
def save(self, *args, **kwargs):
self.last_changed = datetime.now()
# If content is empty look for a template with the given name and
# populate the template instance with its content.
if not self.content:
try:
source, origin = find_template_source(self.name)
@ -38,6 +42,51 @@ class Template(models.Model):
pass
super(Template, self).save(*args, **kwargs)
# Check if django-reversion is installed and register the model with it if yes
try:
models.get_app('reversion')
except ImproperlyConfigured:
pass
else:
import reversion
reversion.register(Template)
def get_cache_backend():
path = getattr(settings, 'DBTEMPLATES_CACHE_BACKEND', False)
if path:
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
try:
mod = __import__(module, {}, {}, [attr])
except ImportError, e:
raise ImproperlyConfigured, 'Error importing dbtemplates cache backend %s: "%s"' % (module, e)
try:
cls = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured, 'Module "%s" does not define a "%s" cache backend' % (module, attr)
return cls()
return False
backend = get_cache_backend()
def add_template_to_cache(instance, **kwargs):
"""
Called via Django's signals to cache the templates, if the template
in the database was added or changed.
"""
backend.save(instance.name, instance.content)
def remove_cached_template(instance, **kwargs):
"""
Called via Django's signals to remove cached templates, if the template
in the database was changed or deleted.
"""
backend.remove(instance.name)
if backend:
signals.post_save.connect(remove_cached_template, sender=Template)
signals.post_save.connect(add_template_to_cache, sender=Template)
signals.pre_delete.connect(remove_cached_template, sender=Template)
__test__ = {'API_TESTS':"""
>>> from django.template import loader, Context