瀏覽代碼

Fixed #11398 - Added a pre_syncdb signal

Donald Stufft 12 年之前
父節點
當前提交
3de1288042

+ 1 - 1
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 '

+ 5 - 1
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 @@ 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")

+ 14 - 0
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():

+ 1 - 0
django/db/models/signals.py

@@ -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)

+ 47 - 0
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
 -----------
 

+ 0 - 0
tests/syncdb_signals/__init__.py


+ 11 - 0
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

+ 79 - 0
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')