Merge pull request #1200 from dstufft/pre-syncdb-signal
Fixed #11398 - Added a pre_syncdb signal
This commit is contained in:
commit
8133ee6cb4
|
@ -1,11 +1,12 @@
|
|||
from optparse import make_option
|
||||
import itertools
|
||||
import traceback
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import NoArgsCommand
|
||||
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.utils.datastructures import SortedDict
|
||||
from django.utils.importlib import import_module
|
||||
|
@ -80,6 +81,9 @@ class Command(NoArgsCommand):
|
|||
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
|
||||
if verbosity >= 1:
|
||||
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))
|
||||
return output
|
||||
|
||||
|
||||
def sql_destroy_indexes(app, style, connection):
|
||||
"Returns a list of the DROP INDEX SQL statements for all models in the given app."
|
||||
output = []
|
||||
|
@ -191,6 +192,19 @@ def custom_sql_for_model(model, style, connection):
|
|||
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):
|
||||
# Emit the post_sync signal for every application.
|
||||
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)
|
||||
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)
|
||||
|
||||
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>`.
|
||||
|
||||
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
|
||||
-----------
|
||||
|
||||
|
|
|
@ -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