Author: russellm
Date: 2010-01-25 06:23:30 -0600 (Mon, 25 Jan 2010)
New Revision: 12290

Modified:
   django/trunk/django/core/management/commands/loaddata.py
   django/trunk/django/core/management/commands/syncdb.py
   django/trunk/django/db/utils.py
   django/trunk/django/test/simple.py
   django/trunk/docs/ref/django-admin.txt
   django/trunk/docs/topics/db/multi-db.txt
   django/trunk/tests/modeltests/fixtures/models.py
   django/trunk/tests/regressiontests/multiple_database/tests.py
Log:
Fixed #12672 -- Added the ability to configure which applications are available 
on which database.

Modified: django/trunk/django/core/management/commands/loaddata.py
===================================================================
--- django/trunk/django/core/management/commands/loaddata.py    2010-01-25 
12:05:38 UTC (rev 12289)
+++ django/trunk/django/core/management/commands/loaddata.py    2010-01-25 
12:23:30 UTC (rev 12290)
@@ -8,16 +8,11 @@
 from django.core import serializers
 from django.core.management.base import BaseCommand
 from django.core.management.color import no_style
-from django.db import connections, transaction, DEFAULT_DB_ALIAS
+from django.db import connections, router, transaction, DEFAULT_DB_ALIAS
 from django.db.models import get_apps
 from django.utils.itercompat import product
 
 try:
-    set
-except NameError:
-    from sets import Set as set   # Python 2.3 fallback
-
-try:
     import bz2
     has_bz2 = True
 except ImportError:
@@ -31,13 +26,10 @@
         make_option('--database', action='store', dest='database',
             default=DEFAULT_DB_ALIAS, help='Nominates a specific database to 
load '
                 'fixtures into. Defaults to the "default" database.'),
-        make_option('-e', '--exclude', dest='exclude',action='append', 
default=[],
-            help='App to exclude (use multiple --exclude to exclude multiple 
apps).'),
     )
 
     def handle(self, *fixture_labels, **options):
         using = options.get('database', DEFAULT_DB_ALIAS)
-        excluded_apps = options.get('exclude', [])
 
         connection = connections[using]
         self.style = no_style()
@@ -171,7 +163,7 @@
                             try:
                                 objects = serializers.deserialize(format, 
fixture, using=using)
                                 for obj in objects:
-                                    if obj.object._meta.app_label not in 
excluded_apps:
+                                    if router.allow_syncdb(using, 
obj.object.__class__):
                                         objects_in_fixture += 1
                                         models.add(obj.object.__class__)
                                         obj.save(using=using)

Modified: django/trunk/django/core/management/commands/syncdb.py
===================================================================
--- django/trunk/django/core/management/commands/syncdb.py      2010-01-25 
12:05:38 UTC (rev 12289)
+++ django/trunk/django/core/management/commands/syncdb.py      2010-01-25 
12:23:30 UTC (rev 12290)
@@ -5,7 +5,7 @@
 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.db import connections, transaction, models, DEFAULT_DB_ALIAS
+from django.db import connections, router, transaction, models, 
DEFAULT_DB_ALIAS
 from django.utils.importlib import import_module
 
 
@@ -16,8 +16,6 @@
         make_option('--database', action='store', dest='database',
             default=DEFAULT_DB_ALIAS, help='Nominates a database to 
synchronize. '
                 'Defaults to the "default" database.'),
-        make_option('-e', '--exclude', dest='exclude',action='append', 
default=[],
-            help='App to exclude (use multiple --exclude to exclude multiple 
apps).'),
     )
     help = "Create the database tables for all apps in INSTALLED_APPS whose 
tables haven't already been created."
 
@@ -26,7 +24,6 @@
         verbosity = int(options.get('verbosity', 1))
         interactive = options.get('interactive')
         show_traceback = options.get('traceback', False)
-        exclude = options.get('exclude', [])
 
         self.style = no_style()
 
@@ -59,13 +56,16 @@
         created_models = set()
         pending_references = {}
 
-        excluded_apps = set(models.get_app(app_label) for app_label in exclude)
-        included_apps = set(app for app in models.get_apps() if app not in 
excluded_apps)
+        # Build the manifest of apps and models that are to be synchronized
+        manifest = dict(
+            (app.__name__.split('.')[-2],
+                [m for m in models.get_models(app, include_auto_created=True)
+                if router.allow_syncdb(db, m)])
+            for app in models.get_apps()
+        )
 
         # Create the tables for each model
-        for app in included_apps:
-            app_name = app.__name__.split('.')[-2]
-            model_list = models.get_models(app, include_auto_created=True)
+        for app_name, model_list in manifest.items():
             for model in model_list:
                 # Create the model's database table, if it doesn't already 
exist.
                 if verbosity >= 2:
@@ -101,9 +101,8 @@
 
         # Install custom SQL for the app (but only if this
         # is a model we've just created)
-        for app in included_apps:
-            app_name = app.__name__.split('.')[-2]
-            for model in models.get_models(app):
+        for app_name, model_list in manifest.items():
+            for model in model_list:
                 if model in created_models:
                     custom_sql = custom_sql_for_model(model, self.style, 
connection)
                     if custom_sql:
@@ -126,9 +125,8 @@
                             print "No custom SQL for %s.%s model" % (app_name, 
model._meta.object_name)
 
         # Install SQL indicies for all newly created models
-        for app in included_apps:
-            app_name = app.__name__.split('.')[-2]
-            for model in models.get_models(app):
+        for app_name, model_list in manifest.items():
+            for model in model_list:
                 if model in created_models:
                     index_sql = 
connection.creation.sql_indexes_for_model(model, self.style)
                     if index_sql:
@@ -145,4 +143,4 @@
                             transaction.commit_unless_managed(using=db)
 
         from django.core.management import call_command
-        call_command('loaddata', 'initial_data', verbosity=verbosity, 
exclude=exclude, database=db)
+        call_command('loaddata', 'initial_data', verbosity=verbosity, 
database=db)

Modified: django/trunk/django/db/utils.py
===================================================================
--- django/trunk/django/db/utils.py     2010-01-25 12:05:38 UTC (rev 12289)
+++ django/trunk/django/db/utils.py     2010-01-25 12:23:30 UTC (rev 12290)
@@ -121,3 +121,10 @@
             if allow is not None:
                 return allow
         return obj1._state.db == obj2._state.db
+
+    def allow_syncdb(self, db, model):
+        for router in self.routers:
+            allow = router.allow_syncdb(db, model)
+            if allow is not None:
+                return allow
+        return True

Modified: django/trunk/django/test/simple.py
===================================================================
--- django/trunk/django/test/simple.py  2010-01-25 12:05:38 UTC (rev 12289)
+++ django/trunk/django/test/simple.py  2010-01-25 12:23:30 UTC (rev 12290)
@@ -284,17 +284,11 @@
         Returns the number of tests that failed.
         """
         self.setup_test_environment()
-
         suite = self.build_suite(test_labels, extra_tests)
-
         old_config = self.setup_databases()
-
         result = self.run_suite(suite)
-
         self.teardown_databases(old_config)
-
         self.teardown_test_environment()
-
         return self.suite_result(result)
 
 def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, 
extra_tests=None):

Modified: django/trunk/docs/ref/django-admin.txt
===================================================================
--- django/trunk/docs/ref/django-admin.txt      2010-01-25 12:05:38 UTC (rev 
12289)
+++ django/trunk/docs/ref/django-admin.txt      2010-01-25 12:23:30 UTC (rev 
12290)
@@ -423,25 +423,6 @@
 have specified that you want to load data onto the ``master``
 database.
 
-Excluding applications from loading
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 1.2
-
-The :djadminopt:`--exclude` option may be provided to prevent specific
-applications from being loaded.
-
-For example, if you wanted to exclude models from ``django.contrib.auth``
-from being loaded into your database, you would call::
-
-    django-admin.py loaddata mydata.json --exclude auth
-
-This will look for for a JSON fixture called ``mydata`` in all the
-usual locations - including the ``fixtures`` directory of the
-``django.contrib.auth`` application. However, any fixture object that
-identifies itself as belonging to the ``auth`` application (e.g.,
-instance of ``auth.User``) would be ignored by loaddata.
-
 makemessages
 ------------
 

Modified: django/trunk/docs/topics/db/multi-db.txt
===================================================================
--- django/trunk/docs/topics/db/multi-db.txt    2010-01-25 12:05:38 UTC (rev 
12289)
+++ django/trunk/docs/topics/db/multi-db.txt    2010-01-25 12:23:30 UTC (rev 
12290)
@@ -66,14 +66,10 @@
     $ ./manage.py syncdb --database=users
 
 If you don't want every application to be synchronized onto a
-particular database. you can specify the :djadminopt:`--exclude`
-argument to :djadmin:`syncdb`. The :djadminopt:`--exclude` option lets
-you prevent a specific application or applications from being
-synchronized. For example, if you don't want the ``sales`` application
-to be in the ``users`` database, you could run::
+particular database, you can define a :ref:`database
+router<topics-db-multi-db-routing>` that implements a policy
+constraining the availability of particular models.
 
-    $ ./manage.py syncdb --database=users --exclude=sales
-
 Alternatively, if you want fine-grained control of synchronization,
 you can pipe all or part of the output of :djadmin:`sqlall` for a
 particular application directly into your database prompt, like this::
@@ -103,7 +99,7 @@
 Database routers
 ----------------
 
-A database Router is a class that provides three methods:
+A database Router is a class that provides four methods:
 
 .. method:: db_for_read(model, **hints)
 
@@ -137,6 +133,14 @@
     used by foreign key and many to many operations to determine if a
     relation should be allowed between two objects.
 
+.. method:: allow_syncdb(db, model)
+
+    Determine if the ``model`` should be synchronized onto the
+    database with alias ``db``. Return True if the model should be
+    synchronized, False if it should not be synchronized, or None if
+    the router has no opinion. This method can be used to determine
+    the availability of a model on a given database.
+
 .. _topics-db-multi-db-hints:
 
 Hints
@@ -221,6 +225,13 @@
                 return True
             return None
 
+        def allow_syncdb(self, db, model):
+            "Make sure the auth app only appears on the 'credentials' db"
+            if db == 'credentials':
+                return model._meta.app_label == 'auth'
+            elif model._meta.app_label == 'auth':
+                return False
+            return None
 
      class MasterSlaveRouter(object):
         """A router that sets up a simple master/slave configuration"""
@@ -240,11 +251,26 @@
                 return True
             return None
 
+        def allow_syncdb(self, db, model):
+            "Explicitly put all models on all databases."
+            return True
+
 Then, in your settings file, add the following (substituting ``path.to.`` with
 the actual python path to the module where you define the routers)::
 
     DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter']
 
+The order in which routers are processed is significant. Routers will
+be queried in the order the are listed in the
+:setting:`DATABASE_ROUTERS` setting . In this example, the
+``AuthRouter`` is processed before the ``MasterSlaveRouter``, and as a
+result, decisions concerning the models in ``auth`` are processed
+before any other decision is made. If the :setting:`DATABASE_ROUTERS`
+setting listed the two routers in the other order,
+``MasterSlaveRouter.allow_syncdb()`` would be processed first. The
+catch-all nature of the MasterSlaveRouter implementation would mean
+that all models would be available on all databases.
+
 With this setup installed, lets run some Django code::
 
     >>> # This retrieval will be performed on the 'credentials' database
@@ -270,6 +296,7 @@
     >>> # ... but if we re-retrieve the object, it will come back on a slave
     >>> mh = Book.objects.get(title='Mostly Harmless')
 
+
 Manually selecting a database
 =============================
 

Modified: django/trunk/tests/modeltests/fixtures/models.py
===================================================================
--- django/trunk/tests/modeltests/fixtures/models.py    2010-01-25 12:05:38 UTC 
(rev 12289)
+++ django/trunk/tests/modeltests/fixtures/models.py    2010-01-25 12:23:30 UTC 
(rev 12290)
@@ -289,14 +289,6 @@
 
 >>> management.call_command('flush', verbosity=0, interactive=False)
 
-# Try to load fixture 1, but this time, exclude the 'fixtures' app.
->>> management.call_command('loaddata', 'fixture1', verbosity=0, 
exclude='fixtures')
->>> Article.objects.all()
-[<Article: Python program becomes self aware>]
-
->>> Category.objects.all()
-[]
-
 # Load back in fixture 1, we need the articles from it
 >>> management.call_command('loaddata', 'fixture1', verbosity=0)
 

Modified: django/trunk/tests/regressiontests/multiple_database/tests.py
===================================================================
--- django/trunk/tests/regressiontests/multiple_database/tests.py       
2010-01-25 12:05:38 UTC (rev 12289)
+++ django/trunk/tests/regressiontests/multiple_database/tests.py       
2010-01-25 12:23:30 UTC (rev 12290)
@@ -655,6 +655,25 @@
     def allow_relation(self, obj1, obj2, **hints):
         return obj1._state.db in ('default', 'other') and obj2._state.db in 
('default', 'other')
 
+    def allow_syncdb(self, db, model):
+        return True
+
+class AuthRouter(object):
+    # Another test router. This one doesn't do anything interesting
+    # other than validate syncdb behavior
+    def db_for_read(self, model, **hints):
+        return None
+    def db_for_write(self, model, **hints):
+        return None
+    def allow_relation(self, obj1, obj2, **hints):
+        return None
+    def allow_syncdb(self, db, model):
+        if db == 'other':
+            return model._meta.app_label == 'auth'
+        elif model._meta.app_label == 'auth':
+            return False
+        return None
+
 class RouterTestCase(TestCase):
     multi_db = True
 
@@ -677,6 +696,35 @@
         self.assertEquals(Book.objects.db_manager('default').db, 'default')
         self.assertEquals(Book.objects.db_manager('default').all().db, 
'default')
 
+    def test_syncdb_selection(self):
+        "Synchronization behaviour is predicatable"
+
+        self.assertTrue(router.allow_syncdb('default', User))
+        self.assertTrue(router.allow_syncdb('default', Book))
+
+        self.assertTrue(router.allow_syncdb('other', User))
+        self.assertTrue(router.allow_syncdb('other', Book))
+
+        # Add the auth router to the chain.
+        # TestRouter is a universal synchronizer, so it should have no effect.
+        router.routers = [TestRouter(), AuthRouter()]
+
+        self.assertTrue(router.allow_syncdb('default', User))
+        self.assertTrue(router.allow_syncdb('default', Book))
+
+        self.assertTrue(router.allow_syncdb('other', User))
+        self.assertTrue(router.allow_syncdb('other', Book))
+
+        # Now check what happens if the router order is the other way around
+        router.routers = [AuthRouter(), TestRouter()]
+
+        self.assertFalse(router.allow_syncdb('default', User))
+        self.assertTrue(router.allow_syncdb('default', Book))
+
+        self.assertTrue(router.allow_syncdb('other', User))
+        self.assertFalse(router.allow_syncdb('other', Book))
+
+
     def test_database_routing(self):
         marty = Person.objects.using('default').create(name="Marty Alchin")
         pro = Book.objects.using('default').create(title="Pro Django",
@@ -1046,6 +1094,7 @@
         self.assertEquals(alice.get_profile().flavor, 'chocolate')
         self.assertEquals(bob.get_profile().flavor, 'crunchy frog')
 
+
 class FixtureTestCase(TestCase):
     multi_db = True
     fixtures = ['multidb-common', 'multidb']

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to