Ticket #16039: 16039.patch

File 16039.patch, 13.4 KB (added by Aymeric Augustin, 11 years ago)
  • django/contrib/auth/management/__init__.py

    commit 587072824e819884989dc659068dde0e59452514
    Author: Aymeric Augustin <aymeric.augustin@m4x.org>
    Date:   Thu Nov 22 20:09:40 2012 +0100
    
        Fixed #16039 -- Made post_syncdb handlers multi-db aware.
        
        Also reverted 8fb7a9002669fb7ba7bec7df90b465b92e1ed3c2. Refs #17055.
    
    diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
    index b5fd29a..ce5d57f 100644
    a b import unicodedata  
    1010from django.contrib.auth import models as auth_app, get_user_model
    1111from django.core import exceptions
    1212from django.core.management.base import CommandError
     13from django.db import DEFAULT_DB_ALIAS, router
    1314from django.db.models import get_models, signals
    1415from django.utils import six
    1516from django.utils.six.moves import input
    def _check_permission_clashing(custom, builtin, ctype):  
    5758                (codename, ctype.app_label, ctype.model_class().__name__))
    5859        pool.add(codename)
    5960
    60 def create_permissions(app, created_models, verbosity, **kwargs):
     61def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs):
     62    if not router.allow_syncdb(db, auth_app.Permission):
     63        return
     64
    6165    from django.contrib.contenttypes.models import ContentType
    6266
    6367    app_models = get_models(app)
    def create_permissions(app, created_models, verbosity, **kwargs):  
    6872    # The codenames and ctypes that should exist.
    6973    ctypes = set()
    7074    for klass in app_models:
    71         ctype = ContentType.objects.get_for_model(klass)
     75        # Force looking up the content types in the current database
     76        # before creating foreign keys to them.
     77        ctype = ContentType.objects.db_manager(db).get_for_model(klass)
    7278        ctypes.add(ctype)
    7379        for perm in _get_all_permissions(klass._meta, ctype):
    7480            searched_perms.append((ctype, perm))
    def create_permissions(app, created_models, verbosity, **kwargs):  
    7682    # Find all the Permissions that have a context_type for a model we're
    7783    # looking for.  We don't need to check for codenames since we already have
    7884    # a list of the ones we're going to create.
    79     all_perms = set(auth_app.Permission.objects.filter(
     85    all_perms = set(auth_app.Permission.objects.using(db).filter(
    8086        content_type__in=ctypes,
    8187    ).values_list(
    8288        "content_type", "codename"
    8389    ))
    8490
    85     objs = [
     91    perms = [
    8692        auth_app.Permission(codename=codename, name=name, content_type=ctype)
    8793        for ctype, (codename, name) in searched_perms
    8894        if (ctype.pk, codename) not in all_perms
    8995    ]
    90     auth_app.Permission.objects.bulk_create(objs)
     96    auth_app.Permission.objects.using(db).bulk_create(perms)
    9197    if verbosity >= 2:
    92         for obj in objs:
    93             print("Adding permission '%s'" % obj)
     98        for perm in perms:
     99            print("Adding permission '%s'" % perm)
    94100
    95101
    96102def create_superuser(app, created_models, verbosity, db, **kwargs):
  • django/contrib/contenttypes/management.py

    diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py
    index 9f287d4..85f76ba 100644
    a b  
    11from django.contrib.contenttypes.models import ContentType
     2from django.db import DEFAULT_DB_ALIAS, router
    23from django.db.models import get_apps, get_models, signals
    34from django.utils.encoding import smart_text
    45from django.utils import six
    56from django.utils.six.moves import input
    67
    7 def update_contenttypes(app, created_models, verbosity=2, **kwargs):
     8def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, **kwargs):
    89    """
    910    Creates content types for models in the given app, removing any model
    1011    entries that no longer have a matching model class.
    1112    """
     13    if not router.allow_syncdb(db, ContentType):
     14        return
     15
    1216    ContentType.objects.clear_cache()
    1317    app_models = get_models(app)
    1418    if not app_models:
    def update_contenttypes(app, created_models, verbosity=2, **kwargs):  
    1923        (model._meta.object_name.lower(), model)
    2024        for model in app_models
    2125    )
     26
    2227    # Get all the content types
    2328    content_types = dict(
    2429        (ct.model, ct)
    25         for ct in ContentType.objects.filter(app_label=app_label)
     30        for ct in ContentType.objects.using(db).filter(app_label=app_label)
    2631    )
    2732    to_remove = [
    2833        ct
    def update_contenttypes(app, created_models, verbosity=2, **kwargs):  
    3035        if model_name not in app_models
    3136    ]
    3237
    33     cts = ContentType.objects.bulk_create([
     38    cts = [
    3439        ContentType(
    3540            name=smart_text(model._meta.verbose_name_raw),
    3641            app_label=app_label,
    def update_contenttypes(app, created_models, verbosity=2, **kwargs):  
    3843        )
    3944        for (model_name, model) in six.iteritems(app_models)
    4045        if model_name not in content_types
    41     ])
     46    ]
     47    ContentType.objects.using(db).bulk_create(cts)
    4248    if verbosity >= 2:
    4349        for ct in cts:
    4450            print("Adding content type '%s | %s'" % (ct.app_label, ct.model))
  • docs/releases/1.5.txt

    diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
    index b73bb04..e26b927 100644
    a b with the :meth:`~django.forms.Form.is_valid()` method and not with the  
    551551presence or absence of the :attr:`~django.forms.Form.cleaned_data` attribute
    552552on the form.
    553553
     554Behavior of :djadmin:`syncdb` with multiple databases
     555~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     556
     557:djadmin:`syncdb` now queries the database routers to determine if content
     558types (when :mod:`~django.contrib.contenttypes` is enabled) and permissions
     559(when :mod:`~django.contrib.auth` is enabled) should be created in the target
     560database. Previously, it created them in the default database, even when
     561another database was specified with the :djadminopt:`--database` option.
     562
     563If you use :djadmin:`syncdb` on multiple databases, you should ensure that
     564your routers allow synchronizing content types and permissions to only one of
     565them. See the docs on the :ref:`behavior of contrib apps with multiple
     566databases <contrib_app_multiple_databases>` for more information.
     567
    554568Miscellaneous
    555569~~~~~~~~~~~~~
    556570
  • docs/topics/db/multi-db.txt

    diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt
    index d2ff864..9c0fa23 100644
    a b However, if you're using SQLite or MySQL with MyISAM tables, there is  
    630630no enforced referential integrity; as a result, you may be able to
    631631'fake' cross database foreign keys. However, this configuration is not
    632632officially supported by Django.
     633
     634.. _contrib_app_multiple_databases:
     635
     636Behavior of contrib apps
     637------------------------
     638
     639Several contrib apps include models, and some apps depend on others. Since
     640cross-database relationships are impossible, this creates some restrictions on
     641how you can split these models across databases:
     642
     643- each one of ``contenttypes.ContentType``, ``sessions.Session`` and
     644  ``sites.Site`` can be stored in any database, given a suitable router.
     645- ``auth`` models — ``User``, ``Group`` and ``Permission`` — are linked
     646  together and linked to ``ContentType``, so they must be stored in the same
     647  database as ``ContentType``.
     648- ``admin`` and ``comments`` depend on ``auth``, so their models must be in
     649  the same database as ``auth``.
     650- ``flatpages`` and ``redirects`` depend on ``sites``, so their models must be
     651  in the same database as ``sites``.
     652
     653In addition, some objects are automatically created just after
     654:djadmin:`syncdb` creates a table to hold them in a database:
     655
     656- a default ``Site``,
     657- a ``ContentType`` for each model (including those not stored to that
     658  database),
     659- three ``Permission`` for each model (same comment).
     660
     661.. versionchanged:: 1.5
     662    Previously, ``ContentType`` and ``Permission`` instances were created only
     663    in the default database.
     664
     665For common setups with multiple databases, it isn't useful to have these
     666objects in more than one database. Common setups include master / slave and
     667connecting to external databases. Therefore, it's recommended:
     668
     669- either to run :djadmin:`syncdb` only for the default database;
     670- or to write :ref:`database router<topics-db-multi-db-routing>` that allows
     671  synchronizing these three models only to one database.
     672
     673.. warning::
     674
     675    If you're synchronizing content types to more that one database, be aware
     676    that their primary keys may not match across databases. This may result in
     677    data corruption or data loss.
  • tests/regressiontests/multiple_database/tests.py

    diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py
    index 79fe6be..a1d9e93 100644
    a b from django.utils.six import StringIO  
    1616from .models import Book, Person, Pet, Review, UserProfile
    1717
    1818
    19 def copy_content_types_from_default_to_other():
    20     # On post_syncdb, content types are created in the 'default' database.
    21     # However, tests of generic foreign keys require them in 'other' too.
    22     # The problem is masked on backends that defer constraints checks: at the
    23     # end of each test, there's a rollback, and constraints are never checked.
    24     # It only appears on MySQL + InnoDB.
    25     for ct in ContentType.objects.using('default').all():
    26         ct.save(using='other')
    27 
    28 
    2919class QueryTestCase(TestCase):
    3020    multi_db = True
    3121
    class QueryTestCase(TestCase):  
    705695
    706696    def test_generic_key_separation(self):
    707697        "Generic fields are constrained to a single database"
    708         copy_content_types_from_default_to_other()
    709 
    710698        # Create a book and author on the default database
    711699        pro = Book.objects.create(title="Pro Django",
    712700                                  published=datetime.date(2008, 12, 16))
    class QueryTestCase(TestCase):  
    734722
    735723    def test_generic_key_reverse_operations(self):
    736724        "Generic reverse manipulations are all constrained to a single DB"
    737         copy_content_types_from_default_to_other()
    738 
    739725        dive = Book.objects.using('other').create(title="Dive into Python",
    740726                                                  published=datetime.date(2009, 5, 4))
    741727
    class QueryTestCase(TestCase):  
    780766
    781767    def test_generic_key_cross_database_protection(self):
    782768        "Operations that involve sharing generic key objects across databases raise an error"
    783         copy_content_types_from_default_to_other()
    784 
    785769        # Create a book and author on the default database
    786770        pro = Book.objects.create(title="Pro Django",
    787771                                  published=datetime.date(2008, 12, 16))
    class QueryTestCase(TestCase):  
    833817
    834818    def test_generic_key_deletion(self):
    835819        "Cascaded deletions of Generic Key relations issue queries on the right database"
    836         copy_content_types_from_default_to_other()
    837 
    838820        dive = Book.objects.using('other').create(title="Dive into Python",
    839821                                                  published=datetime.date(2009, 5, 4))
    840822        review = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
    class RouterTestCase(TestCase):  
    14021384
    14031385    def test_generic_key_cross_database_protection(self):
    14041386        "Generic Key operations can span databases if they share a source"
    1405         copy_content_types_from_default_to_other()
    1406 
    14071387        # Create a book and author on the default database
    14081388        pro = Book.objects.using('default'
    14091389                ).create(title="Pro Django", published=datetime.date(2008, 12, 16))
    class RouterTestCase(TestCase):  
    15151495
    15161496    def test_generic_key_managers(self):
    15171497        "Generic key relations are represented by managers, and can be controlled like managers"
    1518         copy_content_types_from_default_to_other()
    1519 
    15201498        pro = Book.objects.using('other').create(title="Pro Django",
    15211499                                                 published=datetime.date(2008, 12, 16))
    15221500
    class RouterModelArgumentTestCase(TestCase):  
    19221900        pet = Pet.objects.create(owner=person, name='Wart')
    19231901        # test related FK collection
    19241902        person.delete()
     1903
     1904
     1905class SyncOnlyDefaultDatabaseRouter(object):
     1906    def allow_syncdb(self, db, model):
     1907        return db == DEFAULT_DB_ALIAS
     1908
     1909
     1910class SyncDBTestCase(TestCase):
     1911    multi_db = True
     1912
     1913    def test_syncdb_to_other_database(self):
     1914        """Regression test for #16039: syncdb with --database option."""
     1915        count = ContentType.objects.count()
     1916        self.assertGreater(count, 0)
     1917
     1918        ContentType.objects.using('other').delete()
     1919        management.call_command('syncdb', verbosity=0, interactive=False,
     1920            load_initial_data=False, database='other')
     1921
     1922        self.assertEqual(ContentType.objects.using("other").count(), count)
     1923
     1924    def test_syncdb_to_other_database_with_router(self):
     1925        """Regression test for #16039: syncdb with --database option."""
     1926        ContentType.objects.using('other').delete()
     1927        try:
     1928            old_routers = router.routers
     1929            router.routers = [SyncOnlyDefaultDatabaseRouter()]
     1930            management.call_command('syncdb', verbosity=0, interactive=False,
     1931                load_initial_data=False, database='other')
     1932        finally:
     1933            router.routers = old_routers
     1934
     1935        self.assertEqual(ContentType.objects.using("other").count(), 0)
Back to Top