Author: Alex
Date: 2009-12-17 10:12:06 -0600 (Thu, 17 Dec 2009)
New Revision: 11889

Modified:
   django/branches/soc2009/multidb/django/contrib/contenttypes/generic.py
   django/branches/soc2009/multidb/django/contrib/contenttypes/management.py
   django/branches/soc2009/multidb/django/contrib/contenttypes/models.py
   django/branches/soc2009/multidb/docs/ref/contrib/contenttypes.txt
   
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/models.py
   
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/tests.py
Log:
[soc2009/multidb] Updated content types to be multidb aware.  Patch from 
Russell Keith-Magee.

Modified: django/branches/soc2009/multidb/django/contrib/contenttypes/generic.py
===================================================================
--- django/branches/soc2009/multidb/django/contrib/contenttypes/generic.py      
2009-12-17 15:15:26 UTC (rev 11888)
+++ django/branches/soc2009/multidb/django/contrib/contenttypes/generic.py      
2009-12-17 16:12:06 UTC (rev 11889)
@@ -5,7 +5,7 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.db import connection
 from django.db.models import signals
-from django.db import models
+from django.db import models, DEFAULT_DB_ALIAS
 from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
 from django.db.models.loading import get_model
 from django.forms import ModelForm
@@ -45,14 +45,14 @@
             kwargs[self.ct_field] = self.get_content_type(obj=value)
             kwargs[self.fk_field] = value._get_pk_val()
 
-    def get_content_type(self, obj=None, id=None):
+    def get_content_type(self, obj=None, id=None, using=DEFAULT_DB_ALIAS):
         # Convenience function using get_model avoids a circular import when
         # using this model
         ContentType = get_model("contenttypes", "contenttype")
         if obj:
-            return ContentType.objects.get_for_model(obj)
+             return ContentType.objects.get_for_model(obj, using=obj._state.db)
         elif id:
-            return ContentType.objects.get_for_id(id)
+             return ContentType.objects.get_for_id(id, using=using)
         else:
             # This should never happen. I love comments like this, don't you?
             raise Exception("Impossible arguments to GFK.get_content_type!")
@@ -73,7 +73,7 @@
             f = self.model._meta.get_field(self.ct_field)
             ct_id = getattr(instance, f.get_attname(), None)
             if ct_id:
-                ct = self.get_content_type(id=ct_id)
+                ct = self.get_content_type(id=ct_id, using=instance._state.db)
                 try:
                     rel_obj = ct.get_object_for_this_type(pk=getattr(instance, 
self.fk_field))
                 except ObjectDoesNotExist:
@@ -201,11 +201,10 @@
             join_table = qn(self.field.m2m_db_table()),
             source_col_name = qn(self.field.m2m_column_name()),
             target_col_name = qn(self.field.m2m_reverse_name()),
-            content_type = ContentType.objects.get_for_model(instance),
+            content_type = ContentType.objects.get_for_model(instance, 
using=instance._state.db),
             content_type_field_name = self.field.content_type_field_name,
             object_id_field_name = self.field.object_id_field_name
         )
-
         return manager
 
     def __set__(self, instance, value):
@@ -247,7 +246,7 @@
                 '%s__pk' % self.content_type_field_name : self.content_type.id,
                 '%s__exact' % self.object_id_field_name : self.pk_val,
             }
-            return superclass.get_query_set(self).filter(**query)
+            return 
superclass.get_query_set(self).using(self.instance._state.db).filter(**query)
 
         def add(self, *objs):
             for obj in objs:
@@ -255,17 +254,17 @@
                     raise TypeError, "'%s' instance expected" % 
self.model._meta.object_name
                 setattr(obj, self.content_type_field_name, self.content_type)
                 setattr(obj, self.object_id_field_name, self.pk_val)
-                obj.save()
+                obj.save(using=self.instance._state.db)
         add.alters_data = True
 
         def remove(self, *objs):
             for obj in objs:
-                obj.delete()
+                obj.delete(using=self.instance._state.db)
         remove.alters_data = True
 
         def clear(self):
             for obj in self.all():
-                obj.delete()
+                obj.delete(using=self.instance._state.db)
         clear.alters_data = True
 
         def create(self, **kwargs):

Modified: 
django/branches/soc2009/multidb/django/contrib/contenttypes/management.py
===================================================================
--- django/branches/soc2009/multidb/django/contrib/contenttypes/management.py   
2009-12-17 15:15:26 UTC (rev 11888)
+++ django/branches/soc2009/multidb/django/contrib/contenttypes/management.py   
2009-12-17 16:12:06 UTC (rev 11889)
@@ -10,18 +10,19 @@
     ContentType.objects.clear_cache()
     content_types = 
list(ContentType.objects.filter(app_label=app.__name__.split('.')[-2]))
     app_models = get_models(app)
+    db = kwargs['db']
     if not app_models:
         return
     for klass in app_models:
         opts = klass._meta
         try:
-            ct = ContentType.objects.get(app_label=opts.app_label,
-                                         model=opts.object_name.lower())
+            ct = ContentType.objects.using(db).get(app_label=opts.app_label,
+                                                   
model=opts.object_name.lower())
             content_types.remove(ct)
         except ContentType.DoesNotExist:
             ct = ContentType(name=smart_unicode(opts.verbose_name_raw),
                 app_label=opts.app_label, model=opts.object_name.lower())
-            ct.save()
+            ct.save(using=db)
             if verbosity >= 2:
                 print "Adding content type '%s | %s'" % (ct.app_label, 
ct.model)
     # The presence of any remaining content types means the supplied app has an

Modified: django/branches/soc2009/multidb/django/contrib/contenttypes/models.py
===================================================================
--- django/branches/soc2009/multidb/django/contrib/contenttypes/models.py       
2009-12-17 15:15:26 UTC (rev 11888)
+++ django/branches/soc2009/multidb/django/contrib/contenttypes/models.py       
2009-12-17 16:12:06 UTC (rev 11889)
@@ -1,4 +1,4 @@
-from django.db import models
+from django.db import models, DEFAULT_DB_ALIAS
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode
 
@@ -8,14 +8,14 @@
     # This cache is shared by all the get_for_* methods.
     _cache = {}
 
-    def get_by_natural_key(self, app_label, model):
+    def get_by_natural_key(self, app_label, model, using=DEFAULT_DB_ALIAS):
         try:
-            ct = self.__class__._cache[(app_label, model)]
+            ct = self.__class__._cache[using][(app_label, model)]
         except KeyError:
-            ct = self.get(app_label=app_label, model=model)
+            ct = self.using(using).get(app_label=app_label, model=model)
         return ct
 
-    def get_for_model(self, model):
+    def get_for_model(self, model, using=DEFAULT_DB_ALIAS):
         """
         Returns the ContentType object for a given model, creating the
         ContentType if necessary. Lookups are cached so that subsequent lookups
@@ -27,32 +27,33 @@
             opts = model._meta
         key = (opts.app_label, opts.object_name.lower())
         try:
-            ct = self.__class__._cache[key]
+            ct = self.__class__._cache[using][key]
         except KeyError:
             # Load or create the ContentType entry. The smart_unicode() is
             # needed around opts.verbose_name_raw because name_raw might be a
             # django.utils.functional.__proxy__ object.
-            ct, created = self.get_or_create(
+            ct, created = self.using(using).get_or_create(
                 app_label = opts.app_label,
                 model = opts.object_name.lower(),
                 defaults = {'name': smart_unicode(opts.verbose_name_raw)},
             )
-            self._add_to_cache(ct)
+            self._add_to_cache(using, ct)
 
         return ct
 
-    def get_for_id(self, id):
+    def get_for_id(self, id, using=DEFAULT_DB_ALIAS):
         """
         Lookup a ContentType by ID. Uses the same shared cache as get_for_model
         (though ContentTypes are obviously not created on-the-fly by 
get_by_id).
         """
         try:
-            ct = self.__class__._cache[id]
+            ct = self.__class__._cache[using][id]
+
         except KeyError:
             # This could raise a DoesNotExist; that's correct behavior and will
             # make sure that only correct ctypes get stored in the cache dict.
-            ct = self.get(pk=id)
-            self._add_to_cache(ct)
+            ct = self.using(using).get(pk=id)
+            self._add_to_cache(using, ct)
         return ct
 
     def clear_cache(self):
@@ -64,12 +65,12 @@
         """
         self.__class__._cache.clear()
 
-    def _add_to_cache(self, ct):
+    def _add_to_cache(self, using, ct):
         """Insert a ContentType into the cache."""
         model = ct.model_class()
         key = (model._meta.app_label, model._meta.object_name.lower())
-        self.__class__._cache[key] = ct
-        self.__class__._cache[ct.id] = ct
+        self.__class__._cache.setdefault(using, {})[key] = ct
+        self.__class__._cache.setdefault(using, {})[ct.id] = ct
 
 class ContentType(models.Model):
     name = models.CharField(max_length=100)
@@ -99,7 +100,7 @@
         method. The ObjectNotExist exception, if thrown, will not be caught,
         so code that calls this method should catch it.
         """
-        return self.model_class()._default_manager.get(**kwargs)
+        return 
self.model_class()._default_manager.using(self._state.db).get(**kwargs)
 
     def natural_key(self):
         return (self.app_label, self.model)

Modified: django/branches/soc2009/multidb/docs/ref/contrib/contenttypes.txt
===================================================================
--- django/branches/soc2009/multidb/docs/ref/contrib/contenttypes.txt   
2009-12-17 15:15:26 UTC (rev 11888)
+++ django/branches/soc2009/multidb/docs/ref/contrib/contenttypes.txt   
2009-12-17 16:12:06 UTC (rev 11889)
@@ -183,12 +183,16 @@
         probably won't ever need to call this method yourself; Django will call
         it automatically when it's needed.
 
-    .. method:: models.ContentTypeManager.get_for_model(model)
+    .. method:: models.ContentTypeManager.get_for_model(model, 
using=DEFAULT_DB_ALIAS)
 
         Takes either a model class or an instance of a model, and returns the
         :class:`~django.contrib.contenttypes.models.ContentType` instance
         representing that model.
 
+        By default, this will find the content type on the default database.
+        You can load an instance from a different database by providing
+        a ``using`` argument.
+
 The :meth:`~models.ContentTypeManager.get_for_model()` method is especially 
useful when you know you
 need to work with a :class:`ContentType 
<django.contrib.contenttypes.models.ContentType>` but don't want to go to the
 trouble of obtaining the model's metadata to perform a manual lookup::

Modified: 
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/models.py
===================================================================
--- 
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/models.py
   2009-12-17 15:15:26 UTC (rev 11888)
+++ 
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/models.py
   2009-12-17 16:12:06 UTC (rev 11889)
@@ -1,10 +1,25 @@
 from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
 from django.db import models, DEFAULT_DB_ALIAS
 
+class Review(models.Model):
+    source = models.CharField(max_length=100)
+    content_type = models.ForeignKey(ContentType)
+    object_id = models.PositiveIntegerField()
+    content_object = generic.GenericForeignKey()
+
+    def __unicode__(self):
+        return self.source
+
+    class Meta:
+        ordering = ('source',)
+
 class Book(models.Model):
     title = models.CharField(max_length=100)
     published = models.DateField()
     authors = models.ManyToManyField('Author')
+    reviews = generic.GenericRelation(Review)
 
     def __unicode__(self):
         return self.title

Modified: 
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/tests.py
===================================================================
--- 
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/tests.py
    2009-12-17 15:15:26 UTC (rev 11888)
+++ 
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/tests.py
    2009-12-17 16:12:06 UTC (rev 11889)
@@ -5,7 +5,7 @@
 from django.db import connections
 from django.test import TestCase
 
-from models import Book, Author
+from models import Book, Author, Review
 
 try:
     # we only have these models if the user is using multi-db, it's safe the
@@ -258,7 +258,54 @@
         
self.assertEquals(list(Author.objects.using('other').filter(book__title='Dive 
into HTML5').values_list('name', flat=True)),
                           [u'Mark Pilgrim'])
 
+    def test_m2m_cross_database_protection(self):
+        "Operations that involve sharing M2M objects across databases raise an 
error"
+        # Create a book and author on the default database
+        pro = Book.objects.create(title="Pro Django",
+                                  published=datetime.date(2008, 12, 16))
 
+        marty = Author.objects.create(name="Marty Alchin")
+
+        # Create a book and author on the other database
+        dive = Book.objects.using('other').create(title="Dive into Python",
+                                                  
published=datetime.date(2009, 5, 4))
+
+        mark = Author.objects.using('other').create(name="Mark Pilgrim")
+        # Set a foreign key set with an object from a different database
+        try:
+            marty.book_set = [pro, dive]
+            self.fail("Shouldn't be able to assign across databases")
+        except ValueError:
+            pass
+
+        # Add to an m2m with an object from a different database
+        try:
+            marty.book_set.add(dive)
+            self.fail("Shouldn't be able to assign across databases")
+        except ValueError:
+            pass
+
+        # Set a m2m with an object from a different database
+        try:
+            marty.book_set = [pro, dive]
+            self.fail("Shouldn't be able to assign across databases")
+        except ValueError:
+            pass
+
+        # Add to a reverse m2m with an object from a different database
+        try:
+            dive.authors.add(marty)
+            self.fail("Shouldn't be able to assign across databases")
+        except ValueError:
+            pass
+
+        # Set a reverse m2m with an object from a different database
+        try:
+            dive.authors = [mark, marty]
+            self.fail("Shouldn't be able to assign across databases")
+        except ValueError:
+            pass
+
     def test_foreign_key_separation(self):
         "FK fields are constrained to a single database"
         # Create a book and author on the default database
@@ -351,54 +398,6 @@
         
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='Jane
 Brown').values_list('title', flat=True)),
                           [u'Dive into Python'])
 
-    def test_m2m_cross_database_protection(self):
-        "Operations that involve sharing M2M objects across databases raise an 
error"
-        # Create a book and author on the default database
-        pro = Book.objects.create(title="Pro Django",
-                                  published=datetime.date(2008, 12, 16))
-
-        marty = Author.objects.create(name="Marty Alchin")
-
-        # Create a book and author on the other database
-        dive = Book.objects.using('other').create(title="Dive into Python",
-                                                  
published=datetime.date(2009, 5, 4))
-
-        mark = Author.objects.using('other').create(name="Mark Pilgrim")
-        # Set a foreign key set with an object from a different database
-        try:
-            marty.book_set = [pro, dive]
-            self.fail("Shouldn't be able to assign across databases")
-        except ValueError:
-            pass
-
-        # Add to an m2m with an object from a different database
-        try:
-            marty.book_set.add(dive)
-            self.fail("Shouldn't be able to assign across databases")
-        except ValueError:
-            pass
-
-        # Set a m2m with an object from a different database
-        try:
-            marty.book_set = [pro, dive]
-            self.fail("Shouldn't be able to assign across databases")
-        except ValueError:
-            pass
-
-        # Add to a reverse m2m with an object from a different database
-        try:
-            dive.authors.add(marty)
-            self.fail("Shouldn't be able to assign across databases")
-        except ValueError:
-            pass
-
-        # Set a reverse m2m with an object from a different database
-        try:
-            dive.authors = [mark, marty]
-            self.fail("Shouldn't be able to assign across databases")
-        except ValueError:
-            pass
-
     def test_foreign_key_cross_database_protection(self):
         "Operations that involve sharing FK objects across databases raise an 
error"
         # Create a book and author on the default database
@@ -469,7 +468,129 @@
         
self.assertEquals(list(Author.objects.using('other').values_list('name',flat=True)),
                           [u'Jane Brown', u'John Smith', u'Mark Pilgrim'])
 
+    def test_generic_key_separation(self):
+        "Generic fields are constrained to a single database"
+        # Create a book and author on the default database
+        pro = Book.objects.create(title="Pro Django",
+                                  published=datetime.date(2008, 12, 16))
 
+        review1 = Review.objects.create(source="Python Monthly", 
content_object=pro)
+
+        # Create a book and author on the other database
+        dive = Book.objects.using('other').create(title="Dive into Python",
+                                                  
published=datetime.date(2009, 5, 4))
+
+        review2 = Review.objects.using('other').create(source="Python Weekly", 
content_object=dive)
+
+        review1 = Review.objects.using('default').get(source="Python Monthly")
+        self.assertEquals(review1.content_object.title, "Pro Django")
+
+        review2 = Review.objects.using('other').get(source="Python Weekly")
+        self.assertEquals(review2.content_object.title, "Dive into Python")
+
+        # Reget the objects to clear caches
+        dive = Book.objects.using('other').get(title="Dive into Python")
+
+        # Retrive related object by descriptor. Related objects should be 
database-bound
+        self.assertEquals(list(dive.reviews.all().values_list('source', 
flat=True)),
+                          [u'Python Weekly'])
+
+    def test_generic_key_reverse_operations(self):
+        "Generic reverse manipulations are all constrained to a single DB"
+        dive = Book.objects.using('other').create(title="Dive into Python",
+                                                  
published=datetime.date(2009, 5, 4))
+
+        temp = Book.objects.using('other').create(title="Temp",
+                                                  
published=datetime.date(2009, 5, 4))
+
+        review1 = Review.objects.using('other').create(source="Python Weekly", 
content_object=dive)
+        review2 = Review.objects.using('other').create(source="Python 
Monthly", content_object=temp)
+
+        
self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [])
+        
self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [u'Python Weekly'])
+
+        # Add a second review
+        dive.reviews.add(review2)
+        
self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [])
+        
self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [u'Python Monthly', u'Python Weekly'])
+
+        # Remove the second author
+        dive.reviews.remove(review1)
+        
self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [])
+        
self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [u'Python Monthly'])
+
+        # Clear all reviews
+        dive.reviews.clear()
+        
self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [])
+        
self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [])
+
+        # Create an author through the generic interface
+        dive.reviews.create(source='Python Daily')
+        
self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [])
+        
self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',
 flat=True)),
+                          [u'Python Daily'])
+
+    def test_generic_key_cross_database_protection(self):
+        "Operations that involve sharing FK objects across databases raise an 
error"
+        # Create a book and author on the default database
+        pro = Book.objects.create(title="Pro Django",
+                                  published=datetime.date(2008, 12, 16))
+
+        review1 = Review.objects.create(source="Python Monthly", 
content_object=pro)
+
+        # Create a book and author on the other database
+        dive = Book.objects.using('other').create(title="Dive into Python",
+                                                  
published=datetime.date(2009, 5, 4))
+
+        review2 = Review.objects.using('other').create(source="Python Weekly", 
content_object=dive)
+
+        # Set a foreign key with an object from a different database
+        try:
+            review1.content_object = dive
+            self.fail("Shouldn't be able to assign across databases")
+        except ValueError:
+            pass
+
+        # Add to a foreign key set with an object from a different database
+        try:
+            dive.reviews.add(review1)
+            self.fail("Shouldn't be able to assign across databases")
+        except ValueError:
+            pass
+
+        # BUT! if you assign a FK object when the base object hasn't
+        # been saved yet, you implicitly assign the database for the
+        # base object.
+        review3 = Review(source="Python Daily")
+        # initially, no db assigned
+        self.assertEquals(review3._state.db, None)
+
+        # Dive comes from 'other', so review3 is set to use 'other'...
+        review3.content_object = dive
+        self.assertEquals(review3._state.db, 'other')
+        # ... but it isn't saved yet
+        
self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source',
 flat=True)),
+                          [u'Python Monthly'])
+        
self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),
+                          [u'Python Weekly'])
+
+        # When saved, John goes to 'other'
+        review3.save()
+        
self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source',
 flat=True)),
+                          [u'Python Monthly'])
+        
self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),
+                          [u'Python Daily', u'Python Weekly'])
+
+
 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