Ticket #12672: t12672-r12278.2.diff

File t12672-r12278.2.diff, 14.4 KB (added by russellm, 6 years ago)

RC1 of a patch for configuring multiple database usage

  • django/core/management/commands/loaddata.py

    diff -r d205a9ab4b3b django/core/management/commands/loaddata.py
    a b  
    88from django.core import serializers
    99from django.core.management.base import BaseCommand
    1010from django.core.management.color import no_style
    11 from django.db import connections, transaction, DEFAULT_DB_ALIAS
     11from django.db import connections, router, transaction, DEFAULT_DB_ALIAS
    1212from django.db.models import get_apps
    1313from django.utils.itercompat import product
    1414
    1515try:
    16     set
    17 except NameError:
    18     from sets import Set as set   # Python 2.3 fallback
    19 
    20 try:
    2116    import bz2
    2217    has_bz2 = True
    2318except ImportError:
     
    3126        make_option('--database', action='store', dest='database',
    3227            default=DEFAULT_DB_ALIAS, help='Nominates a specific database to load '
    3328                'fixtures into. Defaults to the "default" database.'),
    34         make_option('-e', '--exclude', dest='exclude',action='append', default=[],
    35             help='App to exclude (use multiple --exclude to exclude multiple apps).'),
    3629    )
    3730
    3831    def handle(self, *fixture_labels, **options):
    3932        using = options.get('database', DEFAULT_DB_ALIAS)
    40         excluded_apps = options.get('exclude', [])
    4133
    4234        connection = connections[using]
    4335        self.style = no_style()
     
    171163                            try:
    172164                                objects = serializers.deserialize(format, fixture, using=using)
    173165                                for obj in objects:
    174                                     if obj.object._meta.app_label not in excluded_apps:
     166                                    if router.allow_syncdb(using, obj.object.__class__):
    175167                                        objects_in_fixture += 1
    176168                                        models.add(obj.object.__class__)
    177169                                        obj.save(using=using)
  • django/core/management/commands/syncdb.py

    diff -r d205a9ab4b3b django/core/management/commands/syncdb.py
    a b  
    55from django.core.management.base import NoArgsCommand
    66from django.core.management.color import no_style
    77from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal
    8 from django.db import connections, transaction, models, DEFAULT_DB_ALIAS
     8from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
    99from django.utils.importlib import import_module
    1010
    1111
     
    1616        make_option('--database', action='store', dest='database',
    1717            default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. '
    1818                'Defaults to the "default" database.'),
    19         make_option('-e', '--exclude', dest='exclude',action='append', default=[],
    20             help='App to exclude (use multiple --exclude to exclude multiple apps).'),
    2119    )
    2220    help = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
    2321
     
    2624        verbosity = int(options.get('verbosity', 1))
    2725        interactive = options.get('interactive')
    2826        show_traceback = options.get('traceback', False)
    29         exclude = options.get('exclude', [])
    3027
    3128        self.style = no_style()
    3229
     
    5956        created_models = set()
    6057        pending_references = {}
    6158
    62         excluded_apps = set(models.get_app(app_label) for app_label in exclude)
    63         included_apps = set(app for app in models.get_apps() if app not in excluded_apps)
     59        # Build the manifest of apps and models that are to be synchronized
     60        manifest = dict(
     61            (app.__name__.split('.')[-2],
     62                [m for m in models.get_models(app, include_auto_created=True)
     63                if router.allow_syncdb(db, m)])
     64            for app in models.get_apps()
     65        )
    6466
    6567        # Create the tables for each model
    66         for app in included_apps:
    67             app_name = app.__name__.split('.')[-2]
    68             model_list = models.get_models(app, include_auto_created=True)
     68        for app_name, model_list in manifest.items():
    6969            for model in model_list:
    7070                # Create the model's database table, if it doesn't already exist.
    7171                if verbosity >= 2:
     
    101101
    102102        # Install custom SQL for the app (but only if this
    103103        # is a model we've just created)
    104         for app in included_apps:
    105             app_name = app.__name__.split('.')[-2]
    106             for model in models.get_models(app):
     104        for app_name, model_list in manifest.items():
     105            for model in model_list:
    107106                if model in created_models:
    108107                    custom_sql = custom_sql_for_model(model, self.style, connection)
    109108                    if custom_sql:
     
    126125                            print "No custom SQL for %s.%s model" % (app_name, model._meta.object_name)
    127126
    128127        # Install SQL indicies for all newly created models
    129         for app in included_apps:
    130             app_name = app.__name__.split('.')[-2]
    131             for model in models.get_models(app):
     128        for app_name, model_list in manifest.items():
     129            for model in model_list:
    132130                if model in created_models:
    133131                    index_sql = connection.creation.sql_indexes_for_model(model, self.style)
    134132                    if index_sql:
     
    145143                            transaction.commit_unless_managed(using=db)
    146144
    147145        from django.core.management import call_command
    148         call_command('loaddata', 'initial_data', verbosity=verbosity, exclude=exclude, database=db)
     146        call_command('loaddata', 'initial_data', verbosity=verbosity, database=db)
  • django/db/utils.py

    diff -r d205a9ab4b3b django/db/utils.py
    a b  
    120120            if allow is not None:
    121121                return allow
    122122        return obj1._state.db == obj2._state.db
     123
     124    def allow_syncdb(self, db, model):
     125        for router in self.routers:
     126            allow = router.allow_syncdb(db, model)
     127            if allow is not None:
     128                return allow
     129        return True
  • docs/ref/django-admin.txt

    diff -r d205a9ab4b3b docs/ref/django-admin.txt
    a b  
    423423have specified that you want to load data onto the ``master``
    424424database.
    425425
    426 Excluding applications from loading
    427 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    428 
    429 .. versionadded:: 1.2
    430 
    431 The :djadminopt:`--exclude` option may be provided to prevent specific
    432 applications from being loaded.
    433 
    434 For example, if you wanted to exclude models from ``django.contrib.auth``
    435 from being loaded into your database, you would call::
    436 
    437     django-admin.py loaddata mydata.json --exclude auth
    438 
    439 This will look for for a JSON fixture called ``mydata`` in all the
    440 usual locations - including the ``fixtures`` directory of the
    441 ``django.contrib.auth`` application. However, any fixture object that
    442 identifies itself as belonging to the ``auth`` application (e.g.,
    443 instance of ``auth.User``) would be ignored by loaddata.
    444 
    445426makemessages
    446427------------
    447428
  • docs/topics/db/multi-db.txt

    diff -r d205a9ab4b3b docs/topics/db/multi-db.txt
    a b  
    6666    $ ./manage.py syncdb --database=users
    6767
    6868If you don't want every application to be synchronized onto a
    69 particular database. you can specify the :djadminopt:`--exclude`
    70 argument to :djadmin:`syncdb`. The :djadminopt:`--exclude` option lets
    71 you prevent a specific application or applications from being
    72 synchronized. For example, if you don't want the ``sales`` application
    73 to be in the ``users`` database, you could run::
    74 
    75     $ ./manage.py syncdb --database=users --exclude=sales
     69particular database, you can define a :ref:`database
     70router<topics-db-multi-db-routing>` that implements a policy
     71constraining the availability of particular models.
    7672
    7773Alternatively, if you want fine-grained control of synchronization,
    7874you can pipe all or part of the output of :djadmin:`sqlall` for a
     
    10399Database routers
    104100----------------
    105101
    106 A database Router is a class that provides three methods:
     102A database Router is a class that provides four methods:
    107103
    108104.. method:: db_for_read(model, **hints)
    109105
     
    137133    used by foreign key and many to many operations to determine if a
    138134    relation should be allowed between two objects.
    139135
     136.. method:: allow_syncdb(db, model)
     137
     138    Determine if the ``model`` should be synchronized onto the
     139    database with alias ``db``. Return True if the model should be
     140    synchronized, False if it should not be synchronized, or None if
     141    the router has no opinion. This method can be used to determine
     142    the availability of a model on a given database.
     143
    140144.. _topics-db-multi-db-hints:
    141145
    142146Hints
     
    221225                return True
    222226            return None
    223227
     228        def allow_syncdb(self, db, model):
     229            "Make sure the auth app only appears on the 'credentials' db"
     230            if db == 'credentials':
     231                return model._meta.app_label == 'auth'
     232            elif model._meta.app_label == 'auth':
     233                return False
     234            return None
    224235
    225236     class MasterSlaveRouter(object):
    226237        """A router that sets up a simple master/slave configuration"""
     
    240251                return True
    241252            return None
    242253
     254        def allow_syncdb(self, db, model):
     255            "Explicitly put all models on all databases."
     256            return True
     257
    243258Then, in your settings file, add the following (substituting ``path.to.`` with
    244259the actual python path to the module where you define the routers)::
    245260
    246261    DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter']
    247262
     263The order in which routers are processed is significant. Routers will
     264be queried in the order the are listed in the
     265:setting:`DATABASE_ROUTERS` setting . In this example, the
     266``AuthRouter`` is processed before the ``MasterSlaveRouter``, and as a
     267result, decisions concerning the models in ``auth`` are processed
     268before any other decision is made. If the :setting:`DATABASE_ROUTERS`
     269setting listed the two routers in the other order,
     270``MasterSlaveRouter.allow_syncdb()`` would be processed first. The
     271catch-all nature of the MasterSlaveRouter implementation would mean
     272that all models would be available on all databases.
     273
    248274With this setup installed, lets run some Django code::
    249275
    250276    >>> # This retrieval will be performed on the 'credentials' database
     
    270296    >>> # ... but if we re-retrieve the object, it will come back on a slave
    271297    >>> mh = Book.objects.get(title='Mostly Harmless')
    272298
     299
    273300Manually selecting a database
    274301=============================
    275302
  • tests/modeltests/fixtures/models.py

    diff -r d205a9ab4b3b tests/modeltests/fixtures/models.py
    a b  
    289289
    290290>>> management.call_command('flush', verbosity=0, interactive=False)
    291291
    292 # Try to load fixture 1, but this time, exclude the 'fixtures' app.
    293 >>> management.call_command('loaddata', 'fixture1', verbosity=0, exclude='fixtures')
    294 >>> Article.objects.all()
    295 [<Article: Python program becomes self aware>]
    296 
    297 >>> Category.objects.all()
    298 []
    299 
    300292# Load back in fixture 1, we need the articles from it
    301293>>> management.call_command('loaddata', 'fixture1', verbosity=0)
    302294
  • tests/regressiontests/multiple_database/tests.py

    diff -r d205a9ab4b3b tests/regressiontests/multiple_database/tests.py
    a b  
    655655    def allow_relation(self, obj1, obj2, **hints):
    656656        return obj1._state.db in ('default', 'other') and obj2._state.db in ('default', 'other')
    657657
     658    def allow_syncdb(self, db, model):
     659        return True
     660
     661class AuthRouter(object):
     662    # Another test router. This one doesn't do anything interesting
     663    # other than validate syncdb behavior
     664    def db_for_read(self, model, **hints):
     665        return None
     666    def db_for_write(self, model, **hints):
     667        return None
     668    def allow_relation(self, obj1, obj2, **hints):
     669        return None
     670    def allow_syncdb(self, db, model):
     671        if db == 'other':
     672            return model._meta.app_label == 'auth'
     673        elif model._meta.app_label == 'auth':
     674            return False
     675        return None
     676
    658677class RouterTestCase(TestCase):
    659678    multi_db = True
    660679
     
    677696        self.assertEquals(Book.objects.db_manager('default').db, 'default')
    678697        self.assertEquals(Book.objects.db_manager('default').all().db, 'default')
    679698
     699    def test_syncdb_selection(self):
     700        "Synchronization behaviour is predicatable"
     701
     702        self.assertTrue(router.allow_syncdb('default', User))
     703        self.assertTrue(router.allow_syncdb('default', Book))
     704
     705        self.assertTrue(router.allow_syncdb('other', User))
     706        self.assertTrue(router.allow_syncdb('other', Book))
     707
     708        # Add the auth router to the chain.
     709        # TestRouter is a universal synchronizer, so it should have no effect.
     710        router.routers = [TestRouter(), AuthRouter()]
     711
     712        self.assertTrue(router.allow_syncdb('default', User))
     713        self.assertTrue(router.allow_syncdb('default', Book))
     714
     715        self.assertTrue(router.allow_syncdb('other', User))
     716        self.assertTrue(router.allow_syncdb('other', Book))
     717
     718        # Now check what happens if the router order is the other way around
     719        router.routers = [AuthRouter(), TestRouter()]
     720
     721        self.assertFalse(router.allow_syncdb('default', User))
     722        self.assertTrue(router.allow_syncdb('default', Book))
     723
     724        self.assertTrue(router.allow_syncdb('other', User))
     725        self.assertFalse(router.allow_syncdb('other', Book))
     726
     727
    680728    def test_database_routing(self):
    681729        marty = Person.objects.using('default').create(name="Marty Alchin")
    682730        pro = Book.objects.using('default').create(title="Pro Django",
     
    10461094        self.assertEquals(alice.get_profile().flavor, 'chocolate')
    10471095        self.assertEquals(bob.get_profile().flavor, 'crunchy frog')
    10481096
     1097
    10491098class FixtureTestCase(TestCase):
    10501099    multi_db = True
    10511100    fixtures = ['multidb-common', 'multidb']
Back to Top