From 1e8eadc94e3b27fe90ce9356b48e8543a1ff590e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 5 Jan 2013 23:43:01 +0100 Subject: [PATCH] Fixed #15888 -- Made tablename argument of createcachetable optional Thanks Aymeric Augustin for the report and the documentation and Tim Graham for the review. --- .../gis/db/backends/spatialite/creation.py | 7 +-- .../management/commands/createcachetable.py | 40 +++++++++--- django/db/backends/creation.py | 8 +-- docs/ref/django-admin.txt | 11 +++- docs/releases/1.7.txt | 5 ++ docs/topics/cache.txt | 54 ++++++++++------ tests/cache/tests.py | 63 ++++++++++++++----- 7 files changed, 130 insertions(+), 58 deletions(-) diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py index 2838ecd8e1..fb3606f7e9 100644 --- a/django/contrib/gis/db/backends/spatialite/creation.py +++ b/django/contrib/gis/db/backends/spatialite/creation.py @@ -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 @@ -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. diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index 27668f272d..253f5b5201 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -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 = "" - 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[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 = ( # "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) diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 9c700c96b2..e4c870c01b 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -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. diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 39cbbc8dd5..9626606c01 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -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 ------- diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 52f9eb0d43..7a49f13327 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -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 ^^^^^^ diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 513584dbbf..4e37eeb3f4 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -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 ` to + ``django.core.cache.backends.db.DatabaseCache`` + * Set :setting:`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 ` setting to -``"django.core.cache.backends.db.DatabaseCache"``, and -:setting:`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 `. + +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 diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 6912e8f755..c04decbad8 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -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: