Skip to content

Commit

Permalink
Fixed django#15888 -- Made tablename argument of createcachetable opt…
Browse files Browse the repository at this point in the history
…ional

Thanks Aymeric Augustin for the report and the documentation and
Tim Graham for the review.
  • Loading branch information
claudep committed Oct 14, 2013
1 parent b600bb7 commit 1e8eadc
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 58 deletions.
7 changes: 1 addition & 6 deletions django/contrib/gis/db/backends/spatialite/creation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import os

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.db.backends.sqlite3.creation import DatabaseCreation

Expand Down Expand Up @@ -55,10 +53,7 @@ def create_test_db(self, verbosity=1, autoclobber=False):
interactive=False,
database=self.connection.alias)

for cache_alias in settings.CACHES:
cache = get_cache(cache_alias)
if isinstance(cache, BaseDatabaseCache):
call_command('createcachetable', cache._table, database=self.connection.alias)
call_command('createcachetable', database=self.connection.alias)

# Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database.
Expand Down
40 changes: 30 additions & 10 deletions django/core/management/commands/createcachetable.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,50 @@
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.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.utils import DatabaseError
from django.utils.encoding import force_text


class Command(LabelCommand):
help = "Creates the table needed to use the SQL cache backend."
args = "<tablename>"
label = 'tablename'
class Command(BaseCommand):
help = "Creates the tables needed to use the SQL cache backend."

option_list = LabelCommand.option_list + (
option_list = BaseCommand.option_list + (
make_option('--database', action='store', dest='database',
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.'),
)

requires_model_validation = False

def handle_label(self, tablename, **options):
def handle(self, *tablenames, **options):
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, {})
if not router.allow_migrate(db, cache.cache_model_class):
if not router.allow_migrate(database, cache.cache_model_class):
return
connection = connections[database]

if tablename in connection.introspection.table_names():
if self.verbosity > 0:
self.stdout.write("Cache table '%s' already exists." % tablename)
return
connection = connections[db]

fields = (
# "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),
Expand Down Expand Up @@ -63,3 +81,5 @@ def handle_label(self, tablename, **options):
(tablename, force_text(e)))
for statement in index_output:
curs.execute(statement)
if self.verbosity > 1:
self.stdout.write("Cache table '%s' created." % tablename)
8 changes: 1 addition & 7 deletions django/db/backends/creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,13 +356,7 @@ def create_test_db(self, verbosity=1, autoclobber=False):
interactive=False,
database=self.connection.alias)

from django.core.cache import get_cache
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)
call_command('createcachetable', database=self.connection.alias)

# Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database.
Expand Down
11 changes: 9 additions & 2 deletions docs/ref/django-admin.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,19 @@ createcachetable

.. django-admin:: createcachetable

Creates a cache table named ``tablename`` for use with the database cache
backend. See :doc:`/topics/cache` for more information.
Creates the cache tables for use with the database cache backend. See
:doc:`/topics/cache` for more information.

The :djadminopt:`--database` option can be used to specify the database
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
-------

Expand Down
5 changes: 5 additions & 0 deletions docs/releases/1.7.txt
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ Management Commands
``use_natural_primary_keys`` arguments for ``serializers.serialize()``, allow
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
^^^^^^

Expand Down
54 changes: 36 additions & 18 deletions docs/topics/cache.txt
Original file line number Diff line number Diff line change
Expand Up @@ -159,22 +159,18 @@ particularly temporary.
Database caching
----------------

To use a database table as your cache backend, first create a cache table in
your database by running this command::
Django can store its cached data in your database. This works best if you've
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.
(This name can be whatever you want, as long as it's a valid table name that's
not already being used in your database.) This command creates a single table
in your database that is in the proper format that Django's database-cache
system expects.
* Set :setting:`BACKEND <CACHES-BACKEND>` to
``django.core.cache.backends.db.DatabaseCache``
* Set :setting:`LOCATION <CACHES-LOCATION>` to ``tablename``, the name of
the database table. This name can be whatever you want, as long as it's
a valid table name that's not already being used in your database.

Once you've created that database table, set your
: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``::
In this example, the cache table's name is ``my_cache_table``::

CACHES = {
'default': {
Expand All @@ -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
settings file. You can't use a different database backend for your cache table.
Before using the database cache, you must create the cache table with this
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
to set up routing instructions for your database cache table. For the
Expand Down
63 changes: 48 additions & 15 deletions tests/cache/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from django.core.cache import get_cache
from django.core.cache.backends.base import (CacheKeyWarning,
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.http import (HttpResponse, HttpRequest, StreamingHttpResponse,
QueryDict)
Expand Down Expand Up @@ -829,6 +829,14 @@ def custom_key_func(key, key_prefix, version):
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):

available_apps = ['cache']
Expand All @@ -837,15 +845,14 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
def setUp(self):
# Spaces are used in the table name to ensure quoting/escaping is working
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.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.custom_key_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION=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):
from django.db import connection
cursor = connection.cursor()
cursor.execute('DROP TABLE %s' % connection.ops.quote_name(self._table_name))
connection.commit()
Expand All @@ -858,14 +865,29 @@ def test_zero_cull(self):
self.perform_cull_test(50, 18)

def test_second_call_doesnt_crash(self):
with six.assertRaisesRegex(self, management.CommandError,
"Cache table 'test cache table' could not be created"):
management.call_command(
'createcachetable',
self._table_name,
verbosity=0,
interactive=False
)
stdout = six.StringIO()
management.call_command(
'createcachetable',
stdout=stdout
)
self.assertEqual(stdout.getvalue(),
"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):
# Ensure the database transaction is committed (#19896)
Expand Down Expand Up @@ -896,6 +918,14 @@ def allow_migrate(self, db, model):
return db == 'other'


@override_settings(
CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
},
},
)
class CreateCacheTableForDBCacheTests(TestCase):
multi_db = True

Expand All @@ -905,13 +935,16 @@ def test_createcachetable_observes_database_router(self):
router.routers = [DBCacheRouter()]
# cache table should not be created on 'default'
with self.assertNumQueries(0, using='default'):
management.call_command('createcachetable', 'cache_table',
management.call_command('createcachetable',
database='default',
verbosity=0, interactive=False)
# cache table should be created on 'other'
# one query is used to create the table and another one the index
with self.assertNumQueries(2, using='other'):
management.call_command('createcachetable', 'cache_table',
# Queries:
# 1: check table doesn't already exist
# 2: create the table
# 3: create the index
with self.assertNumQueries(3, using='other'):
management.call_command('createcachetable',
database='other',
verbosity=0, interactive=False)
finally:
Expand Down

0 comments on commit 1e8eadc

Please sign in to comment.