Fixed #15888 -- Made tablename argument of createcachetable optional

Thanks Aymeric Augustin for the report and the documentation and
Tim Graham for the review.
This commit is contained in:
Claude Paroz 2013-01-05 23:43:01 +01:00
parent b600bb7e08
commit 1e8eadc94e
7 changed files with 130 additions and 58 deletions

View File

@ -1,8 +1,6 @@
import os import os
from django.conf import settings from django.conf import settings
from django.core.cache import get_cache
from django.core.cache.backends.db import BaseDatabaseCache
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db.backends.sqlite3.creation import DatabaseCreation from django.db.backends.sqlite3.creation import DatabaseCreation
@ -55,10 +53,7 @@ class SpatiaLiteCreation(DatabaseCreation):
interactive=False, interactive=False,
database=self.connection.alias) database=self.connection.alias)
for cache_alias in settings.CACHES: call_command('createcachetable', database=self.connection.alias)
cache = get_cache(cache_alias)
if isinstance(cache, BaseDatabaseCache):
call_command('createcachetable', cache._table, database=self.connection.alias)
# Get a cursor (even though we don't need one yet). This has # Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database. # the side effect of initializing the test database.

View File

@ -1,32 +1,50 @@
from optparse import make_option from optparse import make_option
from django.conf import settings
from django.core.cache import get_cache
from django.core.cache.backends.db import BaseDatabaseCache from django.core.cache.backends.db import BaseDatabaseCache
from django.core.management.base import LabelCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
from django.db.utils import DatabaseError from django.db.utils import DatabaseError
from django.utils.encoding import force_text from django.utils.encoding import force_text
class Command(LabelCommand): class Command(BaseCommand):
help = "Creates the table needed to use the SQL cache backend." help = "Creates the tables needed to use the SQL cache backend."
args = "<tablename>"
label = 'tablename'
option_list = LabelCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--database', action='store', dest='database', make_option('--database', action='store', dest='database',
default=DEFAULT_DB_ALIAS, help='Nominates a database onto ' default=DEFAULT_DB_ALIAS, help='Nominates a database onto '
'which the cache table will be installed. ' 'which the cache tables will be installed. '
'Defaults to the "default" database.'), 'Defaults to the "default" database.'),
) )
requires_model_validation = False requires_model_validation = False
def handle_label(self, tablename, **options): def handle(self, *tablenames, **options):
db = options.get('database') db = options.get('database')
self.verbosity = int(options.get('verbosity'))
if len(tablenames):
# Legacy behavior, tablename specified as argument
for tablename in tablenames:
self.create_table(db, tablename)
else:
for cache_alias in settings.CACHES:
cache = get_cache(cache_alias)
if isinstance(cache, BaseDatabaseCache):
self.create_table(db, cache._table)
def create_table(self, database, tablename):
cache = BaseDatabaseCache(tablename, {}) cache = BaseDatabaseCache(tablename, {})
if not router.allow_migrate(db, cache.cache_model_class): if not router.allow_migrate(database, cache.cache_model_class):
return return
connection = connections[db] connection = connections[database]
if tablename in connection.introspection.table_names():
if self.verbosity > 0:
self.stdout.write("Cache table '%s' already exists." % tablename)
return
fields = ( fields = (
# "key" is a reserved word in MySQL, so use "cache_key" instead. # "key" is a reserved word in MySQL, so use "cache_key" instead.
models.CharField(name='cache_key', max_length=255, unique=True, primary_key=True), models.CharField(name='cache_key', max_length=255, unique=True, primary_key=True),
@ -63,3 +81,5 @@ class Command(LabelCommand):
(tablename, force_text(e))) (tablename, force_text(e)))
for statement in index_output: for statement in index_output:
curs.execute(statement) curs.execute(statement)
if self.verbosity > 1:
self.stdout.write("Cache table '%s' created." % tablename)

View File

@ -356,13 +356,7 @@ class BaseDatabaseCreation(object):
interactive=False, interactive=False,
database=self.connection.alias) database=self.connection.alias)
from django.core.cache import get_cache call_command('createcachetable', database=self.connection.alias)
from django.core.cache.backends.db import BaseDatabaseCache
for cache_alias in settings.CACHES:
cache = get_cache(cache_alias)
if isinstance(cache, BaseDatabaseCache):
call_command('createcachetable', cache._table,
database=self.connection.alias)
# Get a cursor (even though we don't need one yet). This has # Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database. # the side effect of initializing the test database.

View File

@ -131,12 +131,19 @@ createcachetable
.. django-admin:: createcachetable .. django-admin:: createcachetable
Creates a cache table named ``tablename`` for use with the database cache Creates the cache tables for use with the database cache backend. See
backend. See :doc:`/topics/cache` for more information. :doc:`/topics/cache` for more information.
The :djadminopt:`--database` option can be used to specify the database The :djadminopt:`--database` option can be used to specify the database
onto which the cachetable will be installed. onto which the cachetable will be installed.
.. versionchanged:: 1.7
It is no longer necessary to provide the cache table name or the
:djadminopt:`--database` option. Django takes this information from your
settings file. If you have configured multiple caches or multiple databases,
all cache tables are created.
dbshell dbshell
------- -------

View File

@ -305,6 +305,11 @@ Management Commands
``use_natural_primary_keys`` arguments for ``serializers.serialize()``, allow ``use_natural_primary_keys`` arguments for ``serializers.serialize()``, allow
the use of natural primary keys when serializing. the use of natural primary keys when serializing.
* It is no longer necessary to provide the cache table name or the
:djadminopt:`--database` option for the :djadmin:`createcachetable` command.
Django takes this information from your settings file. If you have configured
multiple caches or multiple databases, all cache tables are created.
Models Models
^^^^^^ ^^^^^^

View File

@ -159,22 +159,18 @@ particularly temporary.
Database caching Database caching
---------------- ----------------
To use a database table as your cache backend, first create a cache table in Django can store its cached data in your database. This works best if you've
your database by running this command:: got a fast, well-indexed database server.
$ python manage.py createcachetable [cache_table_name] To use a database table as your cache backend:
...where ``[cache_table_name]`` is the name of the database table to create. * Set :setting:`BACKEND <CACHES-BACKEND>` to
(This name can be whatever you want, as long as it's a valid table name that's ``django.core.cache.backends.db.DatabaseCache``
not already being used in your database.) This command creates a single table * Set :setting:`LOCATION <CACHES-LOCATION>` to ``tablename``, the name of
in your database that is in the proper format that Django's database-cache the database table. This name can be whatever you want, as long as it's
system expects. a valid table name that's not already being used in your database.
Once you've created that database table, set your In this example, the cache table's name is ``my_cache_table``::
:setting:`BACKEND <CACHES-BACKEND>` setting to
``"django.core.cache.backends.db.DatabaseCache"``, and
:setting:`LOCATION <CACHES-LOCATION>` to ``tablename`` -- the name of the
database table. In this example, the cache table's name is ``my_cache_table``::
CACHES = { CACHES = {
'default': { 'default': {
@ -183,14 +179,36 @@ database table. In this example, the cache table's name is ``my_cache_table``::
} }
} }
Creating the cache table
~~~~~~~~~~~~~~~~~~~~~~~~
The database caching backend uses the same database as specified in your Before using the database cache, you must create the cache table with this
settings file. You can't use a different database backend for your cache table. command::
Database caching works best if you've got a fast, well-indexed database server. python manage.py createcachetable
Database caching and multiple databases This creates a table in your database that is in the proper format that
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Django's database-cache system expects. The name of the table is taken from
:setting:`LOCATION <CACHES-LOCATION>`.
If you are using multiple database caches, :djadmin:`createcachetable` creates
one table for each cache.
If you are using multiple databases, :djadmin:`createcachetable` observes the
``allow_migrate()`` method of your database routers (see below).
Like :djadmin:`migrate`, :djadmin:`createcachetable` won't touch an existing
table. It will only create missing tables.
.. versionchanged:: 1.7
Before Django 1.7, :djadmin:`createcachetable` created one table at a time.
You had to pass the name of the table you wanted to create, and if you were
using multiple databases, you had to use the :djadminopt:`--database`
option. For backwards compatibility, this is still possible.
Multiple databases
~~~~~~~~~~~~~~~~~~
If you use database caching with multiple databases, you'll also need If you use database caching with multiple databases, you'll also need
to set up routing instructions for your database cache table. For the to set up routing instructions for your database cache table. For the

63
tests/cache/tests.py vendored
View File

@ -20,7 +20,7 @@ from django.core import management
from django.core.cache import get_cache from django.core.cache import get_cache
from django.core.cache.backends.base import (CacheKeyWarning, from django.core.cache.backends.base import (CacheKeyWarning,
InvalidCacheBackendError) InvalidCacheBackendError)
from django.db import router, transaction from django.db import connection, router, transaction
from django.core.cache.utils import make_template_fragment_key from django.core.cache.utils import make_template_fragment_key
from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse, from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse,
QueryDict) QueryDict)
@ -829,6 +829,14 @@ def custom_key_func(key, key_prefix, version):
return 'CUSTOM-' + '-'.join([key_prefix, str(version), key]) return 'CUSTOM-' + '-'.join([key_prefix, str(version), key])
@override_settings(
CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'test cache table',
},
},
)
class DBCacheTests(BaseCacheTests, TransactionTestCase): class DBCacheTests(BaseCacheTests, TransactionTestCase):
available_apps = ['cache'] available_apps = ['cache']
@ -837,7 +845,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
def setUp(self): def setUp(self):
# Spaces are used in the table name to ensure quoting/escaping is working # Spaces are used in the table name to ensure quoting/escaping is working
self._table_name = 'test cache table' self._table_name = 'test cache table'
management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False) management.call_command('createcachetable', verbosity=0, interactive=False)
self.cache = get_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30}) self.cache = get_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30})
self.prefix_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_PREFIX='cacheprefix') self.prefix_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_PREFIX='cacheprefix')
self.v2_cache = get_cache(self.backend_name, LOCATION=self._table_name, VERSION=2) self.v2_cache = get_cache(self.backend_name, LOCATION=self._table_name, VERSION=2)
@ -845,7 +853,6 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION='cache.tests.custom_key_func') self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION='cache.tests.custom_key_func')
def tearDown(self): def tearDown(self):
from django.db import connection
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute('DROP TABLE %s' % connection.ops.quote_name(self._table_name)) cursor.execute('DROP TABLE %s' % connection.ops.quote_name(self._table_name))
connection.commit() connection.commit()
@ -858,14 +865,29 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
self.perform_cull_test(50, 18) self.perform_cull_test(50, 18)
def test_second_call_doesnt_crash(self): def test_second_call_doesnt_crash(self):
with six.assertRaisesRegex(self, management.CommandError, stdout = six.StringIO()
"Cache table 'test cache table' could not be created"): management.call_command(
management.call_command( 'createcachetable',
'createcachetable', stdout=stdout
self._table_name, )
verbosity=0, self.assertEqual(stdout.getvalue(),
interactive=False "Cache table '%s' already exists.\n" % self._table_name)
)
def test_createcachetable_with_table_argument(self):
"""
Delete and recreate cache table with legacy behavior (explicitly
specifying the table name).
"""
self.tearDown()
stdout = six.StringIO()
management.call_command(
'createcachetable',
self._table_name,
verbosity=2,
stdout=stdout
)
self.assertEqual(stdout.getvalue(),
"Cache table '%s' created.\n" % self._table_name)
def test_clear_commits_transaction(self): def test_clear_commits_transaction(self):
# Ensure the database transaction is committed (#19896) # Ensure the database transaction is committed (#19896)
@ -896,6 +918,14 @@ class DBCacheRouter(object):
return db == 'other' return db == 'other'
@override_settings(
CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
},
},
)
class CreateCacheTableForDBCacheTests(TestCase): class CreateCacheTableForDBCacheTests(TestCase):
multi_db = True multi_db = True
@ -905,13 +935,16 @@ class CreateCacheTableForDBCacheTests(TestCase):
router.routers = [DBCacheRouter()] router.routers = [DBCacheRouter()]
# cache table should not be created on 'default' # cache table should not be created on 'default'
with self.assertNumQueries(0, using='default'): with self.assertNumQueries(0, using='default'):
management.call_command('createcachetable', 'cache_table', management.call_command('createcachetable',
database='default', database='default',
verbosity=0, interactive=False) verbosity=0, interactive=False)
# cache table should be created on 'other' # cache table should be created on 'other'
# one query is used to create the table and another one the index # Queries:
with self.assertNumQueries(2, using='other'): # 1: check table doesn't already exist
management.call_command('createcachetable', 'cache_table', # 2: create the table
# 3: create the index
with self.assertNumQueries(3, using='other'):
management.call_command('createcachetable',
database='other', database='other',
verbosity=0, interactive=False) verbosity=0, interactive=False)
finally: finally: