Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #1200 from dstufft/pre-syncdb-signal

Fixed #11398 - Added a pre_syncdb signal
  • Loading branch information...
commit 8133ee6cb4c3e9fee613ee772197dc77cdc78cd1 2 parents 11b0653 + 3de1288
@dstufft dstufft authored
View
2  django/core/management/commands/flush.py
@@ -20,7 +20,7 @@ class Command(NoArgsCommand):
default=DEFAULT_DB_ALIAS, help='Nominates a database to flush. '
'Defaults to the "default" database.'),
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 '
'syncdb was executed. This means that all data will be removed '
View
6 django/core/management/commands/syncdb.py
@@ -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 @@ def model_installed(model):
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")
View
14 django/core/management/sql.py
@@ -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():
View
1  django/db/models/signals.py
@@ -12,6 +12,7 @@
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)
View
47 docs/ref/signals.txt
@@ -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
-----------
View
0  tests/syncdb_signals/__init__.py
No changes.
View
11 tests/syncdb_signals/models.py
@@ -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
View
79 tests/syncdb_signals/tests.py
@@ -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')

0 comments on commit 8133ee6

Please sign in to comment.
Something went wrong with that request. Please try again.