Added a database-backed cache backend, along with a tool in django-admin to
create the necessary table structure. This closes #515; thanks again, Eugene! git-svn-id: http://code.djangoproject.com/svn/django/trunk@692 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
6b4095ad10
commit
0fa1aa8711
|
@ -6,6 +6,7 @@ import os, sys
|
||||||
ACTION_MAPPING = {
|
ACTION_MAPPING = {
|
||||||
'adminindex': management.get_admin_index,
|
'adminindex': management.get_admin_index,
|
||||||
'createsuperuser': management.createsuperuser,
|
'createsuperuser': management.createsuperuser,
|
||||||
|
'createcachetable' : management.createcachetable,
|
||||||
# 'dbcheck': management.database_check,
|
# 'dbcheck': management.database_check,
|
||||||
'init': management.init,
|
'init': management.init,
|
||||||
'inspectdb': management.inspectdb,
|
'inspectdb': management.inspectdb,
|
||||||
|
@ -23,7 +24,7 @@ ACTION_MAPPING = {
|
||||||
'validate': management.validate,
|
'validate': management.validate,
|
||||||
}
|
}
|
||||||
|
|
||||||
NO_SQL_TRANSACTION = ('adminindex', 'dbcheck', 'install', 'sqlindexes')
|
NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'sqlindexes')
|
||||||
|
|
||||||
def get_usage():
|
def get_usage():
|
||||||
"""
|
"""
|
||||||
|
@ -79,6 +80,11 @@ def main():
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
sys.stderr.write("Error: %r isn't supported for the currently selected database backend.\n" % action)
|
sys.stderr.write("Error: %r isn't supported for the currently selected database backend.\n" % action)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
elif action == 'createcachetable':
|
||||||
|
try:
|
||||||
|
ACTION_MAPPING[action](args[1])
|
||||||
|
except IndexError:
|
||||||
|
parser.print_usage_and_exit()
|
||||||
elif action in ('startapp', 'startproject'):
|
elif action in ('startapp', 'startproject'):
|
||||||
try:
|
try:
|
||||||
name = args[1]
|
name = args[1]
|
||||||
|
|
|
@ -15,10 +15,9 @@ The CACHE_BACKEND setting is a quasi-URI; examples are:
|
||||||
memcached://127.0.0.1:11211/ A memcached backend; the server is running
|
memcached://127.0.0.1:11211/ A memcached backend; the server is running
|
||||||
on localhost port 11211.
|
on localhost port 11211.
|
||||||
|
|
||||||
sql://tablename/ A SQL backend. If you use this backend,
|
db://tablename/ A database backend in a table named
|
||||||
you must have django.contrib.cache in
|
"tablename". This table should be created
|
||||||
INSTALLED_APPS, and you must have installed
|
with "django-admin createcachetable".
|
||||||
the tables for django.contrib.cache.
|
|
||||||
|
|
||||||
file:///var/tmp/django_cache/ A file-based cache stored in the directory
|
file:///var/tmp/django_cache/ A file-based cache stored in the directory
|
||||||
/var/tmp/django_cache/.
|
/var/tmp/django_cache/.
|
||||||
|
@ -55,7 +54,7 @@ arguments are:
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
memcached://127.0.0.1:11211/?timeout=60
|
memcached://127.0.0.1:11211/?timeout=60
|
||||||
sql://tablename/?timeout=120&max_entries=500&cull_percentage=4
|
db://tablename/?timeout=120&max_entries=500&cull_percentage=4
|
||||||
|
|
||||||
Invalid arguments are silently ignored, as are invalid values of known
|
Invalid arguments are silently ignored, as are invalid values of known
|
||||||
arguments.
|
arguments.
|
||||||
|
@ -351,6 +350,83 @@ class _FileCache(_SimpleCache):
|
||||||
def _key_to_file(self, key):
|
def _key_to_file(self, key):
|
||||||
return os.path.join(self._dir, urllib.quote_plus(key))
|
return os.path.join(self._dir, urllib.quote_plus(key))
|
||||||
|
|
||||||
|
#############
|
||||||
|
# SQL cache #
|
||||||
|
#############
|
||||||
|
|
||||||
|
import base64
|
||||||
|
from django.core.db import db
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class _DBCache(_Cache):
|
||||||
|
"""SQL cache backend"""
|
||||||
|
|
||||||
|
def __init__(self, table, params):
|
||||||
|
_Cache.__init__(self, params)
|
||||||
|
self._table = table
|
||||||
|
max_entries = params.get('max_entries', 300)
|
||||||
|
try:
|
||||||
|
self._max_entries = int(max_entries)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
self._max_entries = 300
|
||||||
|
cull_frequency = params.get('cull_frequency', 3)
|
||||||
|
try:
|
||||||
|
self._cull_frequency = int(cull_frequency)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
self._cull_frequency = 3
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute("SELECT key, value, expires FROM %s WHERE key = %%s" % self._table, [key])
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return default
|
||||||
|
now = datetime.now()
|
||||||
|
if row[2] < now:
|
||||||
|
cursor.execute("DELETE FROM %s WHERE key = %%s" % self._table, [key])
|
||||||
|
db.commit()
|
||||||
|
return default
|
||||||
|
return pickle.loads(base64.decodestring(row[1]))
|
||||||
|
|
||||||
|
def set(self, key, value, timeout=None):
|
||||||
|
if timeout is None:
|
||||||
|
timeout = self.default_timeout
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
|
||||||
|
num = cursor.fetchone()[0]
|
||||||
|
now = datetime.now().replace(microsecond=0)
|
||||||
|
exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0)
|
||||||
|
if num > self._max_entries:
|
||||||
|
self._cull(cursor, now)
|
||||||
|
encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
|
||||||
|
cursor.execute("SELECT key FROM %s WHERE key = %%s" % self._table, [key])
|
||||||
|
if cursor.fetchone():
|
||||||
|
cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE key = %%s" % self._table, [encoded, str(exp), key])
|
||||||
|
else:
|
||||||
|
cursor.execute("INSERT INTO %s (key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)])
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
def delete(self, key):
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute("DELETE FROM %s WHERE key = %%s" % self._table, [key])
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
def has_key(self, key):
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute("SELECT key FROM %s WHERE key = %%s" % self._table, [key])
|
||||||
|
return cursor.fetchone() is not None
|
||||||
|
|
||||||
|
def _cull(self, cursor, now):
|
||||||
|
if self._cull_frequency == 0:
|
||||||
|
cursor.execute("DELETE FROM %s" % self._table)
|
||||||
|
else:
|
||||||
|
cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table, [str(now)])
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
|
||||||
|
num = cursor.fetchone()[0]
|
||||||
|
if num > self._max_entries:
|
||||||
|
cursor.execute("SELECT key FROM %s ORDER BY key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency])
|
||||||
|
cursor.execute("DELETE FROM %s WHERE key < %%s" % self._table, [cursor.fetchone()[0]])
|
||||||
|
|
||||||
##########################################
|
##########################################
|
||||||
# Read settings and load a cache backend #
|
# Read settings and load a cache backend #
|
||||||
##########################################
|
##########################################
|
||||||
|
@ -362,6 +438,7 @@ _BACKENDS = {
|
||||||
'simple' : _SimpleCache,
|
'simple' : _SimpleCache,
|
||||||
'locmem' : _LocMemCache,
|
'locmem' : _LocMemCache,
|
||||||
'file' : _FileCache,
|
'file' : _FileCache,
|
||||||
|
'db' : _DBCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_cache(backend_uri):
|
def get_cache(backend_uri):
|
||||||
|
|
|
@ -621,3 +621,35 @@ def runserver(addr, port):
|
||||||
from django.utils import autoreload
|
from django.utils import autoreload
|
||||||
autoreload.main(inner_run)
|
autoreload.main(inner_run)
|
||||||
runserver.args = '[optional port number, or ipaddr:port]'
|
runserver.args = '[optional port number, or ipaddr:port]'
|
||||||
|
|
||||||
|
def createcachetable(tablename):
|
||||||
|
"Creates the table needed to use the SQL cache backend"
|
||||||
|
from django.core import db, meta
|
||||||
|
fields = (
|
||||||
|
meta.CharField(name='key', maxlength=255, unique=True, primary_key=True),
|
||||||
|
meta.TextField(name='value'),
|
||||||
|
meta.DateTimeField(name='expires', db_index=True),
|
||||||
|
)
|
||||||
|
table_output = []
|
||||||
|
index_output = []
|
||||||
|
for f in fields:
|
||||||
|
field_output = [f.column, db.DATA_TYPES[f.__class__.__name__] % f.__dict__]
|
||||||
|
field_output.append("%sNULL" % (not f.null and "NOT " or ""))
|
||||||
|
if f.unique:
|
||||||
|
field_output.append("UNIQUE")
|
||||||
|
if f.primary_key:
|
||||||
|
field_output.append("PRIMARY KEY")
|
||||||
|
if f.db_index:
|
||||||
|
unique = f.unique and "UNIQUE " or ""
|
||||||
|
index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % (unique, tablename, f.column, tablename, f.column))
|
||||||
|
table_output.append(" ".join(field_output))
|
||||||
|
full_statement = ["CREATE TABLE %s (" % tablename]
|
||||||
|
for i, line in enumerate(table_output):
|
||||||
|
full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
|
||||||
|
full_statement.append(');')
|
||||||
|
curs = db.db.cursor()
|
||||||
|
curs.execute("\n".join(full_statement))
|
||||||
|
for statement in index_output:
|
||||||
|
curs.execute(statement)
|
||||||
|
db.db.commit()
|
||||||
|
createcachetable.args = "[tablename]"
|
|
@ -29,10 +29,20 @@ Examples:
|
||||||
memcached://127.0.0.1:11211/ A memcached backend; the server is running
|
memcached://127.0.0.1:11211/ A memcached backend; the server is running
|
||||||
on localhost port 11211.
|
on localhost port 11211.
|
||||||
|
|
||||||
|
db://tablename/ A database backend in a table named
|
||||||
|
"tablename". This table should be created
|
||||||
|
with "django-admin createcachetable".
|
||||||
|
|
||||||
|
file:///var/tmp/django_cache/ A file-based cache stored in the directory
|
||||||
|
/var/tmp/django_cache/.
|
||||||
|
|
||||||
simple:/// A simple single-process memory cache; you
|
simple:/// A simple single-process memory cache; you
|
||||||
probably don't want to use this except for
|
probably don't want to use this except for
|
||||||
testing. Note that this cache backend is
|
testing. Note that this cache backend is
|
||||||
NOT threadsafe!
|
NOT threadsafe!
|
||||||
|
|
||||||
|
locmem:/// A more sophisticaed local memory cache;
|
||||||
|
this is multi-process- and thread-safe.
|
||||||
============================== ===========================================
|
============================== ===========================================
|
||||||
|
|
||||||
All caches may take arguments -- they're given in query-string style. Valid
|
All caches may take arguments -- they're given in query-string style. Valid
|
||||||
|
|
|
@ -40,6 +40,14 @@ your admin's index page. See `Tutorial 2`_ for more information.
|
||||||
|
|
||||||
.. _Tutorial 2: http://www.djangoproject.com/documentation/tutorial2/
|
.. _Tutorial 2: http://www.djangoproject.com/documentation/tutorial2/
|
||||||
|
|
||||||
|
createcachetable [tablename]
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Creates a cache table named ``tablename`` for use with the database cache
|
||||||
|
backend. See the `cache documentation`_ for more information.
|
||||||
|
|
||||||
|
.. _cache documentation: http://www.djangoproject.com/documentation/cache/
|
||||||
|
|
||||||
createsuperuser
|
createsuperuser
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue