Django

Code

Changeset 692

Show
Ignore:
Timestamp:
09/25/05 17:03:30 (3 years ago)
Author:
jacob
Message:

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!

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/bin/django-admin.py

    r606 r692  
    77    'adminindex': management.get_admin_index, 
    88    'createsuperuser': management.createsuperuser, 
     9    'createcachetable' : management.createcachetable, 
    910#     'dbcheck': management.database_check, 
    1011    'init': management.init, 
     
    2425} 
    2526 
    26 NO_SQL_TRANSACTION = ('adminindex', 'dbcheck', 'install', 'sqlindexes') 
     27NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'sqlindexes') 
    2728 
    2829def get_usage(): 
     
    8081            sys.stderr.write("Error: %r isn't supported for the currently selected database backend.\n" % action) 
    8182            sys.exit(1) 
     83    elif action == 'createcachetable': 
     84        try: 
     85            ACTION_MAPPING[action](args[1]) 
     86        except IndexError: 
     87            parser.print_usage_and_exit() 
    8288    elif action in ('startapp', 'startproject'): 
    8389        try: 
  • django/trunk/django/core/cache.py

    r686 r692  
    1616                                    on localhost port 11211. 
    1717 
    18     sql://tablename/                A SQL backend.  If you use this backend, 
    19                                     you must have django.contrib.cache in 
    20                                     INSTALLED_APPS, and you must have installed 
    21                                     the tables for django.contrib.cache. 
     18    db://tablename/                 A database backend in a table named  
     19                                    "tablename". This table should be created 
     20                                    with "django-admin createcachetable". 
    2221 
    2322    file:///var/tmp/django_cache/   A file-based cache stored in the directory 
     
    5655 
    5756    memcached://127.0.0.1:11211/?timeout=60 
    58     sql://tablename/?timeout=120&max_entries=500&cull_percentage=4 
     57    db://tablename/?timeout=120&max_entries=500&cull_percentage=4 
    5958 
    6059Invalid arguments are silently ignored, as are invalid values of known 
     
    351350    def _key_to_file(self, key): 
    352351        return os.path.join(self._dir, urllib.quote_plus(key)) 
     352 
     353############# 
     354# SQL cache # 
     355############# 
     356 
     357import base64 
     358from django.core.db import db 
     359from datetime import datetime 
     360 
     361class _DBCache(_Cache): 
     362    """SQL cache backend""" 
    353363     
     364    def __init__(self, table, params): 
     365        _Cache.__init__(self, params) 
     366        self._table = table 
     367        max_entries = params.get('max_entries', 300)  
     368        try:  
     369            self._max_entries = int(max_entries)  
     370        except (ValueError, TypeError):  
     371            self._max_entries = 300  
     372        cull_frequency = params.get('cull_frequency', 3)  
     373        try:  
     374            self._cull_frequency = int(cull_frequency)  
     375        except (ValueError, TypeError):  
     376            self._cull_frequency = 3  
     377         
     378    def get(self, key, default=None): 
     379        cursor = db.cursor() 
     380        cursor.execute("SELECT key, value, expires FROM %s WHERE key = %%s" % self._table, [key]) 
     381        row = cursor.fetchone() 
     382        if row is None: 
     383            return default 
     384        now = datetime.now() 
     385        if row[2] < now: 
     386            cursor.execute("DELETE FROM %s WHERE key = %%s" % self._table, [key]) 
     387            db.commit() 
     388            return default 
     389        return pickle.loads(base64.decodestring(row[1])) 
     390         
     391    def set(self, key, value, timeout=None): 
     392        if timeout is None: 
     393            timeout = self.default_timeout 
     394        cursor = db.cursor() 
     395        cursor.execute("SELECT COUNT(*) FROM %s" % self._table) 
     396        num = cursor.fetchone()[0] 
     397        now = datetime.now().replace(microsecond=0) 
     398        exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0) 
     399        if num > self._max_entries: 
     400            self._cull(cursor, now) 
     401        encoded = base64.encodestring(pickle.dumps(value, 2)).strip() 
     402        cursor.execute("SELECT key FROM %s WHERE key = %%s" % self._table, [key]) 
     403        if cursor.fetchone(): 
     404            cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE key = %%s" % self._table, [encoded, str(exp), key]) 
     405        else: 
     406            cursor.execute("INSERT INTO %s (key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)]) 
     407        db.commit() 
     408         
     409    def delete(self, key): 
     410        cursor = db.cursor() 
     411        cursor.execute("DELETE FROM %s WHERE key = %%s" % self._table, [key]) 
     412        db.commit() 
     413         
     414    def has_key(self, key): 
     415        cursor = db.cursor() 
     416        cursor.execute("SELECT key FROM %s WHERE key = %%s" % self._table, [key]) 
     417        return cursor.fetchone() is not None 
     418         
     419    def _cull(self, cursor, now): 
     420        if self._cull_frequency == 0: 
     421            cursor.execute("DELETE FROM %s" % self._table) 
     422        else: 
     423            cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table, [str(now)]) 
     424            cursor.execute("SELECT COUNT(*) FROM %s" % self._table) 
     425            num = cursor.fetchone()[0] 
     426            if num > self._max_entries: 
     427                cursor.execute("SELECT key FROM %s ORDER BY key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency]) 
     428                cursor.execute("DELETE FROM %s WHERE key < %%s" % self._table, [cursor.fetchone()[0]]) 
     429         
    354430########################################## 
    355431# Read settings and load a cache backend # 
     
    363439    'locmem'    : _LocMemCache, 
    364440    'file'      : _FileCache, 
     441    'db'        : _DBCache, 
    365442} 
    366443 
  • django/trunk/django/core/management.py

    r685 r692  
    622622    autoreload.main(inner_run) 
    623623runserver.args = '[optional port number, or ipaddr:port]' 
     624 
     625def createcachetable(tablename): 
     626    "Creates the table needed to use the SQL cache backend" 
     627    from django.core import db, meta 
     628    fields = ( 
     629        meta.CharField(name='key', maxlength=255, unique=True, primary_key=True), 
     630        meta.TextField(name='value'), 
     631        meta.DateTimeField(name='expires', db_index=True), 
     632    ) 
     633    table_output = [] 
     634    index_output = [] 
     635    for f in fields: 
     636        field_output = [f.column, db.DATA_TYPES[f.__class__.__name__] % f.__dict__] 
     637        field_output.append("%sNULL" % (not f.null and "NOT " or "")) 
     638        if f.unique: 
     639            field_output.append("UNIQUE") 
     640        if f.primary_key: 
     641            field_output.append("PRIMARY KEY") 
     642        if f.db_index: 
     643            unique = f.unique and "UNIQUE " or "" 
     644            index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % (unique, tablename, f.column, tablename, f.column)) 
     645        table_output.append(" ".join(field_output)) 
     646    full_statement = ["CREATE TABLE %s (" % tablename] 
     647    for i, line in enumerate(table_output): 
     648        full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or '')) 
     649    full_statement.append(');') 
     650    curs = db.db.cursor() 
     651    curs.execute("\n".join(full_statement)) 
     652    for statement in index_output: 
     653        curs.execute(statement) 
     654    db.db.commit() 
     655createcachetable.args = "[tablename]" 
  • django/trunk/docs/cache.txt

    r633 r692  
    3030                                    on localhost port 11211. 
    3131 
     32    db://tablename/                 A database backend in a table named  
     33                                    "tablename". This table should be created 
     34                                    with "django-admin createcachetable". 
     35 
     36    file:///var/tmp/django_cache/   A file-based cache stored in the directory 
     37                                    /var/tmp/django_cache/. 
     38 
    3239    simple:///                      A simple single-process memory cache; you 
    3340                                    probably don't want to use this except for 
    3441                                    testing. Note that this cache backend is 
    3542                                    NOT threadsafe! 
     43                                         
     44    locmem:///                      A more sophisticaed local memory cache; 
     45                                    this is multi-process- and thread-safe. 
    3646    ==============================  =========================================== 
    3747 
  • django/trunk/docs/django-admin.txt

    r612 r692  
    4141.. _Tutorial 2: http://www.djangoproject.com/documentation/tutorial2/ 
    4242 
     43createcachetable [tablename] 
     44---------------------------- 
     45 
     46Creates a cache table named ``tablename`` for use with the database cache 
     47backend.  See the `cache documentation`_ for more information. 
     48 
     49.. _cache documentation: http://www.djangoproject.com/documentation/cache/ 
     50 
    4351createsuperuser 
    4452---------------