Merge pull request #1200 from dstufft/pre-syncdb-signal
Fixed #11398 - Added a pre_syncdb signal
This commit is contained in:
commit
8133ee6cb4
|
@ -20,7 +20,7 @@ class Command(NoArgsCommand):
|
||||||
default=DEFAULT_DB_ALIAS, help='Nominates a database to flush. '
|
default=DEFAULT_DB_ALIAS, help='Nominates a database to flush. '
|
||||||
'Defaults to the "default" database.'),
|
'Defaults to the "default" database.'),
|
||||||
make_option('--no-initial-data', action='store_false', dest='load_initial_data', default=True,
|
make_option('--no-initial-data', action='store_false', dest='load_initial_data', default=True,
|
||||||
help='Tells Django not to load any initial data after database synchronization.'),
|
help='Tells Django not to load any initial data after database synchronization.'),
|
||||||
)
|
)
|
||||||
help = ('Returns the database to the state it was in immediately after '
|
help = ('Returns the database to the state it was in immediately after '
|
||||||
'syncdb was executed. This means that all data will be removed '
|
'syncdb was executed. This means that all data will be removed '
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
|
import itertools
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import NoArgsCommand
|
from django.core.management.base import NoArgsCommand
|
||||||
from django.core.management.color import no_style
|
from django.core.management.color import no_style
|
||||||
from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal
|
from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal, emit_pre_sync_signal
|
||||||
from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
|
from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
@ -80,6 +81,9 @@ class Command(NoArgsCommand):
|
||||||
for app_name, model_list in all_models
|
for app_name, model_list in all_models
|
||||||
)
|
)
|
||||||
|
|
||||||
|
create_models = set([x for x in itertools.chain(*manifest.values())])
|
||||||
|
emit_pre_sync_signal(create_models, verbosity, interactive, db)
|
||||||
|
|
||||||
# Create the tables for each model
|
# Create the tables for each model
|
||||||
if verbosity >= 1:
|
if verbosity >= 1:
|
||||||
self.stdout.write("Creating tables ...\n")
|
self.stdout.write("Creating tables ...\n")
|
||||||
|
|
|
@ -137,6 +137,7 @@ def sql_indexes(app, style, connection):
|
||||||
output.extend(connection.creation.sql_indexes_for_model(model, style))
|
output.extend(connection.creation.sql_indexes_for_model(model, style))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def sql_destroy_indexes(app, style, connection):
|
def sql_destroy_indexes(app, style, connection):
|
||||||
"Returns a list of the DROP INDEX SQL statements for all models in the given app."
|
"Returns a list of the DROP INDEX SQL statements for all models in the given app."
|
||||||
output = []
|
output = []
|
||||||
|
@ -191,6 +192,19 @@ def custom_sql_for_model(model, style, connection):
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def emit_pre_sync_signal(create_models, verbosity, interactive, db):
|
||||||
|
# Emit the pre_sync signal for every application.
|
||||||
|
for app in models.get_apps():
|
||||||
|
app_name = app.__name__.split('.')[-2]
|
||||||
|
if verbosity >= 2:
|
||||||
|
print("Running pre-sync handlers for application %s" % app_name)
|
||||||
|
models.signals.pre_syncdb.send(sender=app, app=app,
|
||||||
|
create_models=create_models,
|
||||||
|
verbosity=verbosity,
|
||||||
|
interactive=interactive,
|
||||||
|
db=db)
|
||||||
|
|
||||||
|
|
||||||
def emit_post_sync_signal(created_models, verbosity, interactive, db):
|
def emit_post_sync_signal(created_models, verbosity, interactive, db):
|
||||||
# Emit the post_sync signal for every application.
|
# Emit the post_sync signal for every application.
|
||||||
for app in models.get_apps():
|
for app in models.get_apps():
|
||||||
|
|
|
@ -12,6 +12,7 @@ post_save = Signal(providing_args=["instance", "raw", "created", "using", "updat
|
||||||
pre_delete = Signal(providing_args=["instance", "using"], use_caching=True)
|
pre_delete = Signal(providing_args=["instance", "using"], use_caching=True)
|
||||||
post_delete = Signal(providing_args=["instance", "using"], use_caching=True)
|
post_delete = Signal(providing_args=["instance", "using"], use_caching=True)
|
||||||
|
|
||||||
|
pre_syncdb = Signal(providing_args=["app", "create_models", "verbosity", "interactive", "db"])
|
||||||
post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive", "db"], use_caching=True)
|
post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive", "db"], use_caching=True)
|
||||||
|
|
||||||
m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True)
|
m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True)
|
||||||
|
|
|
@ -360,6 +360,53 @@ Management signals
|
||||||
|
|
||||||
Signals sent by :doc:`django-admin </ref/django-admin>`.
|
Signals sent by :doc:`django-admin </ref/django-admin>`.
|
||||||
|
|
||||||
|
pre_syncdb
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. data:: django.db.models.signals.pre_syncdb
|
||||||
|
:module:
|
||||||
|
|
||||||
|
Sent by the :djadmin:`syncdb` command before it starts to install an
|
||||||
|
application.
|
||||||
|
|
||||||
|
Any handlers that listen to this signal need to be written in a particular
|
||||||
|
place: a ``management`` module in one of your :setting:`INSTALLED_APPS`. If
|
||||||
|
handlers are registered anywhere else they may not be loaded by
|
||||||
|
:djadmin:`syncdb`.
|
||||||
|
|
||||||
|
Arguments sent with this signal:
|
||||||
|
|
||||||
|
``sender``
|
||||||
|
The ``models`` module that was just installed. That is, if
|
||||||
|
:djadmin:`syncdb` just installed an app called ``"foo.bar.myapp"``,
|
||||||
|
``sender`` will be the ``foo.bar.myapp.models`` module.
|
||||||
|
|
||||||
|
``app``
|
||||||
|
Same as ``sender``.
|
||||||
|
|
||||||
|
``create_models``
|
||||||
|
A list of the model classes from any app which :djadmin:`syncdb` plans to
|
||||||
|
create.
|
||||||
|
|
||||||
|
|
||||||
|
``verbosity``
|
||||||
|
Indicates how much information manage.py is printing on screen. See
|
||||||
|
the :djadminopt:`--verbosity` flag for details.
|
||||||
|
|
||||||
|
Functions which listen for :data:`pre_syncdb` should adjust what they
|
||||||
|
output to the screen based on the value of this argument.
|
||||||
|
|
||||||
|
``interactive``
|
||||||
|
If ``interactive`` is ``True``, it's safe to prompt the user to input
|
||||||
|
things on the command line. If ``interactive`` is ``False``, functions
|
||||||
|
which listen for this signal should not try to prompt for anything.
|
||||||
|
|
||||||
|
For example, the :mod:`django.contrib.auth` app only prompts to create a
|
||||||
|
superuser when ``interactive`` is ``True``.
|
||||||
|
|
||||||
|
``db``
|
||||||
|
The alias of database on which a command will operate.
|
||||||
|
|
||||||
post_syncdb
|
post_syncdb
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
# class Author(models.Model):
|
||||||
|
# name = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
# class Meta:
|
||||||
|
# ordering = ['name']
|
||||||
|
|
||||||
|
# def __unicode__(self):
|
||||||
|
# return self.name
|
|
@ -0,0 +1,79 @@
|
||||||
|
from django.db import connections
|
||||||
|
from django.db.models import signals
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.core import management
|
||||||
|
from django.utils import six
|
||||||
|
|
||||||
|
from shared_models import models
|
||||||
|
|
||||||
|
|
||||||
|
PRE_SYNCDB_ARGS = ['app', 'create_models', 'verbosity', 'interactive', 'db']
|
||||||
|
SYNCDB_DATABASE = 'default'
|
||||||
|
SYNCDB_VERBOSITY = 1
|
||||||
|
SYNCDB_INTERACTIVE = False
|
||||||
|
|
||||||
|
|
||||||
|
class PreSyncdbReceiver(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.call_counter = 0
|
||||||
|
self.call_args = None
|
||||||
|
|
||||||
|
def __call__(self, signal, sender, **kwargs):
|
||||||
|
self.call_counter = self.call_counter + 1
|
||||||
|
self.call_args = kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class OneTimeReceiver(object):
|
||||||
|
"""
|
||||||
|
Special receiver for handle the fact that test runner calls syncdb for
|
||||||
|
several databases and several times for some of them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.call_counter = 0
|
||||||
|
self.call_args = None
|
||||||
|
self.tables = None # list of tables at the time of the call
|
||||||
|
|
||||||
|
def __call__(self, signal, sender, **kwargs):
|
||||||
|
# Although test runner calls syncdb for several databases,
|
||||||
|
# testing for only one of them is quite sufficient.
|
||||||
|
if kwargs['db'] == SYNCDB_DATABASE:
|
||||||
|
self.call_counter = self.call_counter + 1
|
||||||
|
self.call_args = kwargs
|
||||||
|
connection = connections[SYNCDB_DATABASE]
|
||||||
|
self.tables = connection.introspection.table_names()
|
||||||
|
# we need to test only one call of syncdb
|
||||||
|
signals.pre_syncdb.disconnect(pre_syncdb_receiver, sender=models)
|
||||||
|
|
||||||
|
|
||||||
|
# We connect receiver here and not in unit test code because we need to
|
||||||
|
# connect receiver before test runner creates database. That is, sequence of
|
||||||
|
# actions would be:
|
||||||
|
#
|
||||||
|
# 1. Test runner imports this module.
|
||||||
|
# 2. We connect receiver.
|
||||||
|
# 3. Test runner calls syncdb for create default database.
|
||||||
|
# 4. Test runner execute our unit test code.
|
||||||
|
pre_syncdb_receiver = OneTimeReceiver()
|
||||||
|
signals.pre_syncdb.connect(pre_syncdb_receiver, sender=models)
|
||||||
|
|
||||||
|
|
||||||
|
class SyncdbSignalTests(TestCase):
|
||||||
|
def test_pre_syncdb_call_time(self):
|
||||||
|
self.assertEqual(pre_syncdb_receiver.call_counter, 1)
|
||||||
|
self.assertFalse(pre_syncdb_receiver.tables)
|
||||||
|
|
||||||
|
def test_pre_syncdb_args(self):
|
||||||
|
r = PreSyncdbReceiver()
|
||||||
|
signals.pre_syncdb.connect(r, sender=models)
|
||||||
|
management.call_command('syncdb', database=SYNCDB_DATABASE,
|
||||||
|
verbosity=SYNCDB_VERBOSITY, interactive=SYNCDB_INTERACTIVE,
|
||||||
|
load_initial_data=False, stdout=six.StringIO())
|
||||||
|
|
||||||
|
args = r.call_args
|
||||||
|
self.assertEqual(r.call_counter, 1)
|
||||||
|
self.assertEqual(set(args), set(PRE_SYNCDB_ARGS))
|
||||||
|
self.assertEqual(args['app'], models)
|
||||||
|
self.assertEqual(args['verbosity'], SYNCDB_VERBOSITY)
|
||||||
|
self.assertEqual(args['interactive'], SYNCDB_INTERACTIVE)
|
||||||
|
self.assertEqual(args['db'], 'default')
|
Loading…
Reference in New Issue