mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		| @@ -6,6 +6,7 @@ import os, sys | ||||
| ACTION_MAPPING = { | ||||
|     'adminindex': management.get_admin_index, | ||||
|     'createsuperuser': management.createsuperuser, | ||||
|     'createcachetable' : management.createcachetable, | ||||
| #     'dbcheck': management.database_check, | ||||
|     'init': management.init, | ||||
|     'inspectdb': management.inspectdb, | ||||
| @@ -23,7 +24,7 @@ ACTION_MAPPING = { | ||||
|     'validate': management.validate, | ||||
| } | ||||
|  | ||||
| NO_SQL_TRANSACTION = ('adminindex', 'dbcheck', 'install', 'sqlindexes') | ||||
| NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'sqlindexes') | ||||
|  | ||||
| def get_usage(): | ||||
|     """ | ||||
| @@ -79,6 +80,11 @@ def main(): | ||||
|         except NotImplementedError: | ||||
|             sys.stderr.write("Error: %r isn't supported for the currently selected database backend.\n" % action) | ||||
|             sys.exit(1) | ||||
|     elif action == 'createcachetable': | ||||
|         try: | ||||
|             ACTION_MAPPING[action](args[1]) | ||||
|         except IndexError: | ||||
|             parser.print_usage_and_exit() | ||||
|     elif action in ('startapp', 'startproject'): | ||||
|         try: | ||||
|             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 | ||||
|                                     on localhost port 11211. | ||||
|  | ||||
|     sql://tablename/                A SQL backend.  If you use this backend, | ||||
|                                     you must have django.contrib.cache in | ||||
|                                     INSTALLED_APPS, and you must have installed | ||||
|                                     the tables for django.contrib.cache. | ||||
|     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/. | ||||
| @@ -55,7 +54,7 @@ arguments are: | ||||
| For example: | ||||
|  | ||||
|     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 | ||||
| arguments. | ||||
| @@ -350,7 +349,84 @@ class _FileCache(_SimpleCache): | ||||
|        | ||||
|     def _key_to_file(self, 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 # | ||||
| ########################################## | ||||
| @@ -362,6 +438,7 @@ _BACKENDS = { | ||||
|     'simple'    : _SimpleCache, | ||||
|     'locmem'    : _LocMemCache, | ||||
|     'file'      : _FileCache, | ||||
|     'db'        : _DBCache, | ||||
| } | ||||
|  | ||||
| def get_cache(backend_uri): | ||||
|   | ||||
| @@ -621,3 +621,35 @@ def runserver(addr, port): | ||||
|     from django.utils import autoreload | ||||
|     autoreload.main(inner_run) | ||||
| 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 | ||||
|                                     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 | ||||
|                                     probably don't want to use this except for | ||||
|                                     testing. Note that this cache backend is | ||||
|                                     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 | ||||
|   | ||||
| @@ -40,6 +40,14 @@ your admin's index page. See `Tutorial 2`_ for more information. | ||||
|  | ||||
| .. _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 | ||||
| --------------- | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user