Kaydet (Commit) 1e8eadc9 authored tarafından Claude Paroz's avatar Claude Paroz

Fixed #15888 -- Made tablename argument of createcachetable optional

Thanks Aymeric Augustin for the report and the documentation and
Tim Graham for the review.
üst b600bb7e
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
......@@ -55,10 +53,7 @@ class SpatiaLiteCreation(DatabaseCreation):
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.
......
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),
......@@ -63,3 +81,5 @@ class Command(LabelCommand):
(tablename, force_text(e)))
for statement in index_output:
curs.execute(statement)
if self.verbosity > 1:
self.stdout.write("Cache table '%s' created." % tablename)
......@@ -356,13 +356,7 @@ class BaseDatabaseCreation(object):
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.
......
......@@ -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
-------
......
......@@ -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
^^^^^^
......
......@@ -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': {
......@@ -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
......
......@@ -20,7 +20,7 @@ from django.core import management
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)
......@@ -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']
......@@ -837,7 +845,7 @@ 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)
......@@ -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')
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()
......@@ -858,14 +865,29 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
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)
......@@ -896,6 +918,14 @@ class DBCacheRouter(object):
return db == 'other'
@override_settings(
CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
},
},
)
class CreateCacheTableForDBCacheTests(TestCase):
multi_db = True
......@@ -905,13 +935,16 @@ class CreateCacheTableForDBCacheTests(TestCase):
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:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment