Ticket #12540: t12540-r12262.2.diff
File t12540-r12262.2.diff, 53.4 KB (added by , 15 years ago) |
---|
-
django/conf/global_settings.py
diff -r e165fea06e06 django/conf/global_settings.py
a b 128 128 SEND_BROKEN_LINK_EMAILS = False 129 129 130 130 # Database connection info. 131 # Legacy format 131 132 DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 132 133 DATABASE_NAME = '' # Or path to database file if using sqlite3. 133 134 DATABASE_USER = '' # Not used with sqlite3. … … 136 137 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 137 138 DATABASE_OPTIONS = {} # Set to empty dictionary for default. 138 139 140 # New format 139 141 DATABASES = { 140 142 } 141 143 144 # Classes used to implement db routing behaviour 145 DATABASE_ROUTERS = [] 146 142 147 # The email backend to use. For possible shortcuts see django.core.mail. 143 148 # The default is to use the SMTP backend. 144 149 # Third-party backends can be specified by providing a Python path -
django/contrib/auth/models.py
diff -r e165fea06e06 django/contrib/auth/models.py
a b 3 3 4 4 from django.contrib import auth 5 5 from django.core.exceptions import ImproperlyConfigured 6 from django.db import models , DEFAULT_DB_ALIAS6 from django.db import models 7 7 from django.db.models.manager import EmptyManager 8 8 from django.contrib.contenttypes.models import ContentType 9 9 from django.utils.encoding import smart_str -
django/contrib/contenttypes/generic.py
diff -r e165fea06e06 django/contrib/contenttypes/generic.py
a b 5 5 from django.core.exceptions import ObjectDoesNotExist 6 6 from django.db import connection 7 7 from django.db.models import signals 8 from django.db import models , DEFAULT_DB_ALIAS8 from django.db import models 9 9 from django.db.models.fields.related import RelatedField, Field, ManyToManyRel 10 10 from django.db.models.loading import get_model 11 11 from django.forms import ModelForm … … 255 255 raise TypeError("'%s' instance expected" % self.model._meta.object_name) 256 256 setattr(obj, self.content_type_field_name, self.content_type) 257 257 setattr(obj, self.object_id_field_name, self.pk_val) 258 obj.save( using=self.instance._state.db)258 obj.save() 259 259 add.alters_data = True 260 260 261 261 def remove(self, *objs): -
django/contrib/contenttypes/models.py
diff -r e165fea06e06 django/contrib/contenttypes/models.py
a b 1 from django.db import models , DEFAULT_DB_ALIAS1 from django.db import models 2 2 from django.utils.translation import ugettext_lazy as _ 3 3 from django.utils.encoding import smart_unicode 4 4 -
django/contrib/gis/db/models/sql/query.py
diff -r e165fea06e06 django/contrib/gis/db/models/sql/query.py
a b 1 from django.db import connections , DEFAULT_DB_ALIAS1 from django.db import connections 2 2 from django.db.models.query import sql 3 3 4 4 from django.contrib.gis.db.models.fields import GeometryField -
django/db/__init__.py
diff -r e165fea06e06 django/db/__init__.py
a b 1 1 from django.conf import settings 2 2 from django.core import signals 3 3 from django.core.exceptions import ImproperlyConfigured 4 from django.db.utils import ConnectionHandler, load_backend4 from django.db.utils import ConnectionHandler, ConnectionRouter, load_backend, DEFAULT_DB_ALIAS 5 5 from django.utils.functional import curry 6 6 7 7 __all__ = ('backend', 'connection', 'connections', 'DatabaseError', 8 8 'IntegrityError', 'DEFAULT_DB_ALIAS') 9 9 10 DEFAULT_DB_ALIAS = 'default' 10 11 11 12 12 # For backwards compatibility - Port any old database settings over to 13 13 # the new values. … … 60 60 database['ENGINE'] = full_engine 61 61 62 62 connections = ConnectionHandler(settings.DATABASES) 63 63 router = ConnectionRouter(settings.DATABASE_ROUTERS) 64 64 65 65 # `connection`, `DatabaseError` and `IntegrityError` are convenient aliases 66 66 # for backend bits. -
django/db/models/base.py
diff -r e165fea06e06 django/db/models/base.py
a b 10 10 from django.db.models.query import delete_objects, Q 11 11 from django.db.models.query_utils import CollectedObjects, DeferredAttribute 12 12 from django.db.models.options import Options 13 from django.db import connections, transaction, DatabaseError, DEFAULT_DB_ALIAS13 from django.db import connections, router, transaction, DatabaseError, DEFAULT_DB_ALIAS 14 14 from django.db.models import signals 15 15 from django.db.models.loading import register_models, get_model 16 16 from django.utils.translation import ugettext_lazy as _ … … 439 439 need for overrides of save() to pass around internal-only parameters 440 440 ('raw', 'cls', and 'origin'). 441 441 """ 442 using = using or self._state.db or DEFAULT_DB_ALIAS442 using = using or router.db_for_write(self.__class__, self) 443 443 connection = connections[using] 444 444 assert not (force_insert and force_update) 445 445 if cls is None: … … 592 592 parent_obj._collect_sub_objects(seen_objs) 593 593 594 594 def delete(self, using=None): 595 using = using or self._state.db or DEFAULT_DB_ALIAS595 using = using or router.db_for_write(self.__class__, self) 596 596 connection = connections[using] 597 597 assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname) 598 598 … … 719 719 # no value, skip the lookup 720 720 continue 721 721 if f.primary_key and not getattr(self, '_adding', False): 722 # no need to check for unique primary key when edit ting722 # no need to check for unique primary key when editing 723 723 continue 724 724 lookup_kwargs[str(field_name)] = lookup_value 725 725 -
django/db/models/fields/related.py
diff -r e165fea06e06 django/db/models/fields/related.py
a b 1 from django.db import connection, transaction, DEFAULT_DB_ALIAS 1 from django.conf import settings 2 from django.db import connection, router, transaction 2 3 from django.db.backends import util 3 4 from django.db.models import signals, get_model 4 5 from django.db.models.fields import (AutoField, Field, IntegerField, … … 218 219 raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' % 219 220 (value, instance._meta.object_name, 220 221 self.related.get_accessor_name(), self.related.opts.object_name)) 222 elif value is not None: 223 if instance._state.db is None: 224 instance._state.db = router.db_for_write(instance.__class__, value) 225 elif value._state.db is None: 226 value._state.db = router.db_for_write(value.__class__, instance) 227 elif value._state.db is not None and instance._state.db is not None: 228 if not router.allow_relation(value, instance): 229 raise ValueError('Cannot assign "%r": instance is on database "%s", value is is on database "%s"' % 230 (value, instance._state.db, value._state.db)) 221 231 222 232 # Set the value of the related field to the value of the related object's related field 223 233 setattr(value, self.related.field.attname, getattr(instance, self.related.field.rel.get_related_field().attname)) … … 260 270 # If the related manager indicates that it should be used for 261 271 # related fields, respect that. 262 272 rel_mgr = self.field.rel.to._default_manager 263 using = instance._state.db or DEFAULT_DB_ALIAS273 using = router.db_for_read(self.field.rel.to, instance) 264 274 if getattr(rel_mgr, 'use_for_related_fields', False): 265 275 rel_obj = rel_mgr.using(using).get(**params) 266 276 else: … … 281 291 raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' % 282 292 (value, instance._meta.object_name, 283 293 self.field.name, self.field.rel.to._meta.object_name)) 284 elif value is not None and value._state.db != instance._state.db:294 elif value is not None: 285 295 if instance._state.db is None: 286 instance._state.db = value._state.db 287 else:#elif value._state.db is None: 288 value._state.db = instance._state.db 289 # elif value._state.db is not None and instance._state.db is not None: 290 # raise ValueError('Cannot assign "%r": instance is on database "%s", value is is on database "%s"' % 291 # (value, instance._state.db, value._state.db)) 296 instance._state.db = router.db_for_write(instance.__class__, value) 297 elif value._state.db is None: 298 value._state.db = router.db_for_write(value.__class__, instance) 299 elif value._state.db is not None and instance._state.db is not None: 300 if not router.allow_relation(value, instance): 301 raise ValueError('Cannot assign "%r": instance is on database "%s", value is is on database "%s"' % 302 (value, instance._state.db, value._state.db)) 292 303 293 304 # If we're setting the value of a OneToOneField to None, we need to clear 294 305 # out the cache on any old related object. Otherwise, deleting the … … 370 381 371 382 class RelatedManager(superclass): 372 383 def get_query_set(self): 373 using = instance._state.db or DEFAULT_DB_ALIAS384 using = router.db_for_read(rel_model, instance) 374 385 return superclass.get_query_set(self).using(using).filter(**(self.core_filters)) 375 386 376 387 def add(self, *objs): … … 378 389 if not isinstance(obj, self.model): 379 390 raise TypeError("'%s' instance expected" % self.model._meta.object_name) 380 391 setattr(obj, rel_field.name, instance) 381 obj.save( using=instance._state.db)392 obj.save() 382 393 add.alters_data = True 383 394 384 395 def create(self, **kwargs): … … 390 401 # Update kwargs with the related object that this 391 402 # ForeignRelatedObjectsDescriptor knows about. 392 403 kwargs.update({rel_field.name: instance}) 393 using = instance._state.db or DEFAULT_DB_ALIAS404 using = router.db_for_write(rel_model, instance) 394 405 return super(RelatedManager, self).using(using).get_or_create(**kwargs) 395 406 get_or_create.alters_data = True 396 407 … … 402 413 # Is obj actually part of this descriptor set? 403 414 if getattr(obj, rel_field.attname) == val: 404 415 setattr(obj, rel_field.name, None) 405 obj.save( using=instance._state.db)416 obj.save() 406 417 else: 407 418 raise rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, instance)) 408 419 remove.alters_data = True … … 410 421 def clear(self): 411 422 for obj in self.all(): 412 423 setattr(obj, rel_field.name, None) 413 obj.save( using=instance._state.db)424 obj.save() 414 425 clear.alters_data = True 415 426 416 427 manager = RelatedManager() … … 505 516 new_ids = set() 506 517 for obj in objs: 507 518 if isinstance(obj, self.model): 508 # if obj._state.db != self.instance._state.db:509 #raise ValueError('Cannot add "%r": instance is on database "%s", value is is on database "%s"' %510 #(obj, self.instance._state.db, obj._state.db))519 if not router.allow_relation(obj, self.instance): 520 raise ValueError('Cannot add "%r": instance is on database "%s", value is is on database "%s"' % 521 (obj, self.instance._state.db, obj._state.db)) 511 522 new_ids.add(obj.pk) 512 523 elif isinstance(obj, Model): 513 524 raise TypeError("'%s' instance expected" % self.model._meta.object_name) 514 525 else: 515 526 new_ids.add(obj) 516 vals = self.through._default_manager.using(self.instance._state.db).values_list(target_field_name, flat=True) 527 db = router.db_for_read(self.through.__class__, self.instance) 528 vals = self.through._default_manager.using(db).values_list(target_field_name, flat=True) 517 529 vals = vals.filter(**{ 518 530 source_field_name: self._pk_val, 519 531 '%s__in' % target_field_name: new_ids, 520 532 }) 521 533 new_ids = new_ids - set(vals) 522 534 # Add the ones that aren't there already 535 db = router.db_for_write(self.through.__class__, self.instance) 523 536 for obj_id in new_ids: 524 self.through._default_manager.using( self.instance._state.db).create(**{537 self.through._default_manager.using(db).create(**{ 525 538 '%s_id' % source_field_name: self._pk_val, 526 539 '%s_id' % target_field_name: obj_id, 527 540 }) -
django/db/models/manager.py
diff -r e165fea06e06 django/db/models/manager.py
a b 1 1 from django.utils import copycompat as copy 2 3 from django.db import DEFAULT_DB_ALIAS 2 from django.conf import settings 4 3 from django.db.models.query import QuerySet, EmptyQuerySet, insert_query, RawQuerySet 5 4 from django.db.models import signals 6 5 from django.db.models.fields import FieldDoesNotExist 7 6 7 8 8 def ensure_default_manager(sender, **kwargs): 9 9 """ 10 10 Ensures that a Model subclass contains a default manager and sets the … … 87 87 mgr._inherited = True 88 88 return mgr 89 89 90 def db_manager(self, alias):90 def db_manager(self, using): 91 91 obj = copy.copy(self) 92 obj._db = alias92 obj._db = using 93 93 return obj 94 94 95 95 @property 96 96 def db(self): 97 return self._db or DEFAULT_DB_ALIAS97 return self._db 98 98 99 99 ####################### 100 100 # PROXIES TO QUERYSET # 101 101 ####################### 102 102 103 103 def get_empty_query_set(self): 104 return EmptyQuerySet(self.model )104 return EmptyQuerySet(self.model, using=self.db) 105 105 106 106 def get_query_set(self): 107 107 """Returns a new QuerySet object. Subclasses can override this method 108 108 to easily customize the behavior of the Manager. 109 109 """ 110 qs = QuerySet(self.model) 111 if self._db is not None: 112 qs = qs.using(self._db) 113 return qs 110 return QuerySet(self.model, using=self.db) 114 111 115 112 def none(self): 116 113 return self.get_empty_query_set() -
django/db/models/query.py
diff -r e165fea06e06 django/db/models/query.py
a b 4 4 5 5 from copy import deepcopy 6 6 7 from django.db import connections, transaction, IntegrityError, DEFAULT_DB_ALIAS7 from django.db import connections, router, transaction, IntegrityError 8 8 from django.db.models.aggregates import Aggregate 9 9 from django.db.models.fields import DateField 10 10 from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory, InvalidQuery … … 429 429 430 430 if not seen_objs: 431 431 break 432 delete_objects(seen_objs, del_query. db)432 delete_objects(seen_objs, del_query._db or router.db_for_write(self.model)) 433 433 434 434 # Clear the result cache, in case this QuerySet gets reused. 435 435 self._result_cache = None … … 444 444 "Cannot update a query once a slice has been taken." 445 445 query = self.query.clone(sql.UpdateQuery) 446 446 query.add_update_values(kwargs) 447 if not transaction.is_managed(using=self.db): 448 transaction.enter_transaction_management(using=self.db) 447 db = self._db or router.db_for_write(self.model) 448 if not transaction.is_managed(using=db): 449 transaction.enter_transaction_management(using=db) 449 450 forced_managed = True 450 451 else: 451 452 forced_managed = False 452 453 try: 453 rows = query.get_compiler( self.db).execute_sql(None)454 rows = query.get_compiler(db).execute_sql(None) 454 455 if forced_managed: 455 transaction.commit(using= self.db)456 transaction.commit(using=db) 456 457 else: 457 transaction.commit_unless_managed(using= self.db)458 transaction.commit_unless_managed(using=db) 458 459 finally: 459 460 if forced_managed: 460 transaction.leave_transaction_management(using= self.db)461 transaction.leave_transaction_management(using=db) 461 462 self._result_cache = None 462 463 return rows 463 464 update.alters_data = True … … 714 715 @property 715 716 def db(self): 716 717 "Return the database that will be used if this query is executed now" 717 return self._db or DEFAULT_DB_ALIAS718 return self._db or router.db_for_read(self.model) 718 719 719 720 ################### 720 721 # PRIVATE METHODS # … … 988 989 989 990 990 991 class EmptyQuerySet(QuerySet): 991 def __init__(self, model=None, query=None ):992 super(EmptyQuerySet, self).__init__(model, query )992 def __init__(self, model=None, query=None, using=None): 993 super(EmptyQuerySet, self).__init__(model, query, using) 993 994 self._result_cache = [] 994 995 995 996 def __and__(self, other): … … 1254 1255 @property 1255 1256 def db(self): 1256 1257 "Return the database that will be used if this query is executed now" 1257 return self._db or DEFAULT_DB_ALIAS1258 return self._db or router.db_for_read(self.model) 1258 1259 1259 1260 def using(self, alias): 1260 1261 """ -
django/db/utils.py
diff -r e165fea06e06 django/db/utils.py
a b 5 5 from django.core.exceptions import ImproperlyConfigured 6 6 from django.utils.importlib import import_module 7 7 8 DEFAULT_DB_ALIAS = 'default' 9 8 10 def load_backend(backend_name): 9 11 try: 10 12 module = import_module('.base', 'django.db.backends.%s' % backend_name) … … 55 57 conn = self.databases[alias] 56 58 except KeyError: 57 59 raise ConnectionDoesNotExist("The connection %s doesn't exist" % alias) 60 58 61 conn.setdefault('ENGINE', 'django.db.backends.dummy') 59 62 if conn['ENGINE'] == 'django.db.backends.' or not conn['ENGINE']: 60 63 conn['ENGINE'] = 'django.db.backends.dummy' … … 82 85 83 86 def all(self): 84 87 return [self[alias] for alias in self] 88 89 class ConnectionRouter(object): 90 def __init__(self, routers): 91 self.routers = [] 92 for r in routers: 93 if isinstance(r, basestring): 94 module_name, klass_name = r.rsplit('.', 1) 95 module = import_module(module_name) 96 router = getattr(module, klass_name)() 97 else: 98 router = r 99 self.routers.append(router) 100 101 def _router_func(action): 102 def _route_db(self, model, instance=None): 103 chosen_db = None 104 for router in self.routers: 105 chosen_db = getattr(router, action)(model, instance=instance) 106 if chosen_db: 107 return chosen_db 108 if instance: 109 return instance._state.db or DEFAULT_DB_ALIAS 110 return DEFAULT_DB_ALIAS 111 return _route_db 112 113 db_for_read = _router_func('db_for_read') 114 db_for_write = _router_func('db_for_write') 115 116 def allow_relation(self, obj1, obj2): 117 for router in self.routers: 118 allow = router.allow_relation(obj1, obj2) 119 if allow is not None: 120 return allow 121 return obj1._state.db == obj2._state.db -
tests/regressiontests/multiple_database/models.py
diff -r e165fea06e06 tests/regressiontests/multiple_database/models.py
a b 2 2 from django.contrib.auth.models import User 3 3 from django.contrib.contenttypes.models import ContentType 4 4 from django.contrib.contenttypes import generic 5 from django.db import models , DEFAULT_DB_ALIAS5 from django.db import models 6 6 7 7 class Review(models.Model): 8 8 source = models.CharField(max_length=100) -
tests/regressiontests/multiple_database/tests.py
diff -r e165fea06e06 tests/regressiontests/multiple_database/tests.py
a b 3 3 4 4 from django.conf import settings 5 5 from django.contrib.auth.models import User 6 from django.db import connections 6 from django.db import connections, router, DEFAULT_DB_ALIAS 7 from django.db.utils import ConnectionRouter 7 8 from django.test import TestCase 8 9 9 10 from models import Book, Person, Review, UserProfile … … 259 260 self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into HTML5').values_list('name', flat=True)), 260 261 [u'Mark Pilgrim']) 261 262 262 #def test_m2m_cross_database_protection(self):263 #"Operations that involve sharing M2M objects across databases raise an error"264 ## Create a book and author on the default database265 #pro = Book.objects.create(title="Pro Django",266 #published=datetime.date(2008, 12, 16))263 def test_m2m_cross_database_protection(self): 264 "Operations that involve sharing M2M objects across databases raise an error" 265 # Create a book and author on the default database 266 pro = Book.objects.create(title="Pro Django", 267 published=datetime.date(2008, 12, 16)) 267 268 268 #marty = Person.objects.create(name="Marty Alchin")269 marty = Person.objects.create(name="Marty Alchin") 269 270 270 ## Create a book and author on the other database271 #dive = Book.objects.using('other').create(title="Dive into Python",272 #published=datetime.date(2009, 5, 4))271 # Create a book and author on the other database 272 dive = Book.objects.using('other').create(title="Dive into Python", 273 published=datetime.date(2009, 5, 4)) 273 274 274 #mark = Person.objects.using('other').create(name="Mark Pilgrim")275 ## Set a foreign key set with an object from a different database276 #try:277 #marty.book_set = [pro, dive]278 #self.fail("Shouldn't be able to assign across databases")279 #except ValueError:280 #pass275 mark = Person.objects.using('other').create(name="Mark Pilgrim") 276 # Set a foreign key set with an object from a different database 277 try: 278 marty.book_set = [pro, dive] 279 self.fail("Shouldn't be able to assign across databases") 280 except ValueError: 281 pass 281 282 282 ## Add to an m2m with an object from a different database283 #try:284 #marty.book_set.add(dive)285 #self.fail("Shouldn't be able to assign across databases")286 #except ValueError:287 #pass283 # Add to an m2m with an object from a different database 284 try: 285 marty.book_set.add(dive) 286 self.fail("Shouldn't be able to assign across databases") 287 except ValueError: 288 pass 288 289 289 ## Set a m2m with an object from a different database290 #try:291 #marty.book_set = [pro, dive]292 #self.fail("Shouldn't be able to assign across databases")293 #except ValueError:294 #pass290 # Set a m2m with an object from a different database 291 try: 292 marty.book_set = [pro, dive] 293 self.fail("Shouldn't be able to assign across databases") 294 except ValueError: 295 pass 295 296 296 ## Add to a reverse m2m with an object from a different database297 #try:298 #dive.authors.add(marty)299 #self.fail("Shouldn't be able to assign across databases")300 #except ValueError:301 #pass297 # Add to a reverse m2m with an object from a different database 298 try: 299 dive.authors.add(marty) 300 self.fail("Shouldn't be able to assign across databases") 301 except ValueError: 302 pass 302 303 303 ## Set a reverse m2m with an object from a different database304 #try:305 #dive.authors = [mark, marty]306 #self.fail("Shouldn't be able to assign across databases")307 #except ValueError:308 #pass304 # Set a reverse m2m with an object from a different database 305 try: 306 dive.authors = [mark, marty] 307 self.fail("Shouldn't be able to assign across databases") 308 except ValueError: 309 pass 309 310 310 311 def test_foreign_key_separation(self): 311 312 "FK fields are constrained to a single database" … … 401 402 self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)), 402 403 []) 403 404 404 #def test_foreign_key_cross_database_protection(self):405 #"Operations that involve sharing FK objects across databases raise an error"406 ## Create a book and author on the default database407 #pro = Book.objects.create(title="Pro Django",408 #published=datetime.date(2008, 12, 16))405 def test_foreign_key_cross_database_protection(self): 406 "Operations that involve sharing FK objects across databases raise an error" 407 # Create a book and author on the default database 408 pro = Book.objects.create(title="Pro Django", 409 published=datetime.date(2008, 12, 16)) 409 410 410 #marty = Person.objects.create(name="Marty Alchin")411 marty = Person.objects.create(name="Marty Alchin") 411 412 412 ## Create a book and author on the other database413 #dive = Book.objects.using('other').create(title="Dive into Python",414 #published=datetime.date(2009, 5, 4))413 # Create a book and author on the other database 414 dive = Book.objects.using('other').create(title="Dive into Python", 415 published=datetime.date(2009, 5, 4)) 415 416 416 #mark = Person.objects.using('other').create(name="Mark Pilgrim")417 mark = Person.objects.using('other').create(name="Mark Pilgrim") 417 418 418 ## Set a foreign key with an object from a different database419 #try:420 #dive.editor = marty421 #self.fail("Shouldn't be able to assign across databases")422 #except ValueError:423 #pass419 # Set a foreign key with an object from a different database 420 try: 421 dive.editor = marty 422 self.fail("Shouldn't be able to assign across databases") 423 except ValueError: 424 pass 424 425 425 ## Set a foreign key set with an object from a different database426 #try:427 #marty.edited = [pro, dive]428 #self.fail("Shouldn't be able to assign across databases")429 #except ValueError:430 #pass426 # Set a foreign key set with an object from a different database 427 try: 428 marty.edited = [pro, dive] 429 self.fail("Shouldn't be able to assign across databases") 430 except ValueError: 431 pass 431 432 432 ## Add to a foreign key set with an object from a different database433 #try:434 #marty.edited.add(dive)435 #self.fail("Shouldn't be able to assign across databases")436 #except ValueError:437 #pass433 # Add to a foreign key set with an object from a different database 434 try: 435 marty.edited.add(dive) 436 self.fail("Shouldn't be able to assign across databases") 437 except ValueError: 438 pass 438 439 439 ## BUT! if you assign a FK object when the base object hasn't440 ## been saved yet, you implicitly assign the database for the441 ## base object.442 #chris = Person(name="Chris Mills")443 #html5 = Book(title="Dive into HTML5", published=datetime.date(2010, 3, 15))444 ## initially, no db assigned445 #self.assertEquals(chris._state.db, None)446 #self.assertEquals(html5._state.db, None)440 # BUT! if you assign a FK object when the base object hasn't 441 # been saved yet, you implicitly assign the database for the 442 # base object. 443 chris = Person(name="Chris Mills") 444 html5 = Book(title="Dive into HTML5", published=datetime.date(2010, 3, 15)) 445 # initially, no db assigned 446 self.assertEquals(chris._state.db, None) 447 self.assertEquals(html5._state.db, None) 447 448 448 ## old object comes from 'other', so the new object is set to use 'other'...449 #dive.editor = chris450 #html5.editor = mark451 # #self.assertEquals(chris._state.db, 'other')452 #self.assertEquals(html5._state.db, 'other')453 ## ... but it isn't saved yet454 #self.assertEquals(list(Person.objects.using('other').values_list('name',flat=True)),455 #[u'Mark Pilgrim'])456 #self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),457 #[u'Dive into Python'])449 # old object comes from 'other', so the new object is set to use 'other'... 450 dive.editor = chris 451 html5.editor = mark 452 self.assertEquals(chris._state.db, 'other') 453 self.assertEquals(html5._state.db, 'other') 454 # ... but it isn't saved yet 455 self.assertEquals(list(Person.objects.using('other').values_list('name',flat=True)), 456 [u'Mark Pilgrim']) 457 self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)), 458 [u'Dive into Python']) 458 459 459 ## When saved (no using required), new objects goes to 'other'460 #chris.save()461 #html5.save()462 #self.assertEquals(list(Person.objects.using('default').values_list('name',flat=True)),463 #[u'Marty Alchin'])464 #self.assertEquals(list(Person.objects.using('other').values_list('name',flat=True)),465 #[u'Chris Mills', u'Mark Pilgrim'])466 #self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)),467 #[u'Pro Django'])468 #self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),469 #[u'Dive into HTML5', u'Dive into Python'])460 # When saved (no using required), new objects goes to 'other' 461 chris.save() 462 html5.save() 463 self.assertEquals(list(Person.objects.using('default').values_list('name',flat=True)), 464 [u'Marty Alchin']) 465 self.assertEquals(list(Person.objects.using('other').values_list('name',flat=True)), 466 [u'Chris Mills', u'Mark Pilgrim']) 467 self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)), 468 [u'Pro Django']) 469 self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)), 470 [u'Dive into HTML5', u'Dive into Python']) 470 471 471 ## This also works if you assign the FK in the constructor472 #water = Book(title="Dive into Water", published=datetime.date(2001, 1, 1), editor=mark)473 #self.assertEquals(water._state.db, 'other')474 ## ... but it isn't saved yet475 #self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)),476 #[u'Pro Django'])477 #self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),478 #[u'Dive into HTML5', u'Dive into Python'])472 # This also works if you assign the FK in the constructor 473 water = Book(title="Dive into Water", published=datetime.date(2001, 1, 1), editor=mark) 474 self.assertEquals(water._state.db, 'other') 475 # ... but it isn't saved yet 476 self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)), 477 [u'Pro Django']) 478 self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)), 479 [u'Dive into HTML5', u'Dive into Python']) 479 480 480 ## When saved, the new book goes to 'other'481 #water.save()482 #self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)),483 #[u'Pro Django'])484 #self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)),485 #[u'Dive into HTML5', u'Dive into Python', u'Dive into Water'])481 # When saved, the new book goes to 'other' 482 water.save() 483 self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)), 484 [u'Pro Django']) 485 self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)), 486 [u'Dive into HTML5', u'Dive into Python', u'Dive into Water']) 486 487 487 488 def test_generic_key_separation(self): 488 489 "Generic fields are constrained to a single database" … … 555 556 self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)), 556 557 [u'Python Daily']) 557 558 558 #def test_generic_key_cross_database_protection(self):559 ## "Operations that involve sharing FKobjects across databases raise an error"560 ### Create a book and author on the default database561 ##pro = Book.objects.create(title="Pro Django",562 ##published=datetime.date(2008, 12, 16))559 def test_generic_key_cross_database_protection(self): 560 "Operations that involve sharing generic key objects across databases raise an error" 561 # Create a book and author on the default database 562 pro = Book.objects.create(title="Pro Django", 563 published=datetime.date(2008, 12, 16)) 563 564 564 ##review1 = Review.objects.create(source="Python Monthly", content_object=pro)565 review1 = Review.objects.create(source="Python Monthly", content_object=pro) 565 566 566 ### Create a book and author on the other database567 ##dive = Book.objects.using('other').create(title="Dive into Python",568 ##published=datetime.date(2009, 5, 4))567 # Create a book and author on the other database 568 dive = Book.objects.using('other').create(title="Dive into Python", 569 published=datetime.date(2009, 5, 4)) 569 570 570 ##review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive)571 review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive) 571 572 572 ### Set a foreign key with an object from a different database573 ##try:574 ##review1.content_object = dive575 ##self.fail("Shouldn't be able to assign across databases")576 ##except ValueError:577 ##pass573 # Set a foreign key with an object from a different database 574 try: 575 review1.content_object = dive 576 self.fail("Shouldn't be able to assign across databases") 577 except ValueError: 578 pass 578 579 579 ## Add to a foreign key set with an object from a different database580 #try:581 #dive.reviews.add(review1)582 #self.fail("Shouldn't be able to assign across databases")583 #except ValueError:584 #pass580 # Add to a foreign key set with an object from a different database 581 try: 582 dive.reviews.add(review1) 583 self.fail("Shouldn't be able to assign across databases") 584 except ValueError: 585 pass 585 586 586 ## BUT! if you assign a FK object when the base object hasn't587 ## been saved yet, you implicitly assign the database for the588 ## base object.589 #review3 = Review(source="Python Daily")590 ## initially, no db assigned591 #self.assertEquals(review3._state.db, None)587 # BUT! if you assign a FK object when the base object hasn't 588 # been saved yet, you implicitly assign the database for the 589 # base object. 590 review3 = Review(source="Python Daily") 591 # initially, no db assigned 592 self.assertEquals(review3._state.db, None) 592 593 593 ## Dive comes from 'other', so review3 is set to use 'other'...594 #review3.content_object = dive595 #self.assertEquals(review3._state.db, 'other')596 ## ... but it isn't saved yet597 #self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)),598 #[u'Python Monthly'])599 #self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),600 #[u'Python Weekly'])594 # Dive comes from 'other', so review3 is set to use 'other'... 595 review3.content_object = dive 596 self.assertEquals(review3._state.db, 'other') 597 # ... but it isn't saved yet 598 self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)), 599 [u'Python Monthly']) 600 self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)), 601 [u'Python Weekly']) 601 602 602 ## When saved, John goes to 'other'603 #review3.save()604 #self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)),605 #[u'Python Monthly'])606 #self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)),607 #[u'Python Daily', u'Python Weekly'])603 # When saved, John goes to 'other' 604 review3.save() 605 self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)), 606 [u'Python Monthly']) 607 self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)), 608 [u'Python Daily', u'Python Weekly']) 608 609 609 610 def test_ordering(self): 610 611 "get_next_by_XXX commands stick to a single database" … … 630 631 val = Book.objects.raw('SELECT id FROM "multiple_database_book"').using('other') 631 632 self.assertEqual(map(lambda o: o.pk, val), [dive.pk]) 632 633 634 class CommonSourceRouter(object): 635 def db_for_read(self, model, instance): 636 if instance: 637 return instance._state.db or DEFAULT_DB_ALIAS 638 return DEFAULT_DB_ALIAS 639 640 def db_for_write(self, model, instance): 641 return DEFAULT_DB_ALIAS 642 643 def allow_relation(self, obj1, obj2): 644 return obj1._state.db in ('default', 'other') and obj2._state.db in ('default', 'other') 645 646 class CommonSourceTestCase(TestCase): 647 multi_db = True 648 649 def setUp(self): 650 # Make the 'other' database appear to be a slave of the 'default' 651 self.old_routers = router.routers 652 router.routers = [CommonSourceRouter()] 653 654 def tearDown(self): 655 # Restore the 'other' database as an independent database 656 router.routers = self.old_routers 657 658 def test_foreign_key_cross_database_protection(self): 659 "Foreign keys can cross databases if they two databases have a common source" 660 # Create a book and author on the default database 661 pro = Book.objects.create(title="Pro Django", 662 published=datetime.date(2008, 12, 16)) 663 664 marty = Person.objects.create(name="Marty Alchin") 665 666 # Create a book and author on the other database 667 dive = Book.objects.using('other').create(title="Dive into Python", 668 published=datetime.date(2009, 5, 4)) 669 670 mark = Person.objects.using('other').create(name="Mark Pilgrim") 671 672 # Set a foreign key with an object from a different database 673 try: 674 dive.editor = marty 675 except ValueError: 676 self.fail("Assignment across master/slave databases with a common source should be ok") 677 678 # Database assignments of original objects haven't changed... 679 self.assertEquals(marty._state.db, 'default') 680 self.assertEquals(pro._state.db, 'default') 681 self.assertEquals(dive._state.db, 'other') 682 self.assertEquals(mark._state.db, 'other') 683 684 # ... but they will when the affected object is saved. 685 dive.save() 686 self.assertEquals(dive._state.db, 'default') 687 688 # ...and the source database now has a copy of any object saved 689 try: 690 Book.objects.using('default').get(title='Dive into Python').delete() 691 except Book.DoesNotExist: 692 self.fail('Source database should have a copy of saved object') 693 694 # This isn't a real master-slave database, so restore the original from other 695 dive = Book.objects.using('other').get(title='Dive into Python') 696 self.assertEquals(dive._state.db, 'other') 697 698 # Set a foreign key set with an object from a different database 699 try: 700 marty.edited = [pro, dive] 701 except ValueError: 702 self.fail("Assignment across master/slave databases with a common source should be ok") 703 704 # Assignment implies a save, so database assignments of original objects have changed... 705 self.assertEquals(marty._state.db, 'default') 706 self.assertEquals(pro._state.db, 'default') 707 self.assertEquals(dive._state.db, 'default') 708 self.assertEquals(mark._state.db, 'other') 709 710 # ...and the source database now has a copy of any object saved 711 try: 712 Book.objects.using('default').get(title='Dive into Python').delete() 713 except Book.DoesNotExist: 714 self.fail('Source database should have a copy of saved object') 715 716 # This isn't a real master-slave database, so restore the original from other 717 dive = Book.objects.using('other').get(title='Dive into Python') 718 self.assertEquals(dive._state.db, 'other') 719 720 # Add to a foreign key set with an object from a different database 721 try: 722 marty.edited.add(dive) 723 except ValueError: 724 self.fail("Assignment across master/slave databases with a common source should be ok") 725 726 # Add implies a save, so database assignments of original objects have changed... 727 self.assertEquals(marty._state.db, 'default') 728 self.assertEquals(pro._state.db, 'default') 729 self.assertEquals(dive._state.db, 'default') 730 self.assertEquals(mark._state.db, 'other') 731 732 # ...and the source database now has a copy of any object saved 733 try: 734 Book.objects.using('default').get(title='Dive into Python').delete() 735 except Book.DoesNotExist: 736 self.fail('Source database should have a copy of saved object') 737 738 # This isn't a real master-slave database, so restore the original from other 739 dive = Book.objects.using('other').get(title='Dive into Python') 740 741 # If you assign a FK object when the base object hasn't 742 # been saved yet, you implicitly assign the database for the 743 # base object. 744 chris = Person(name="Chris Mills") 745 html5 = Book(title="Dive into HTML5", published=datetime.date(2010, 3, 15)) 746 # initially, no db assigned 747 self.assertEquals(chris._state.db, None) 748 self.assertEquals(html5._state.db, None) 749 750 # old object comes from 'other', so the new object is set to use the 751 # source of 'other'... 752 self.assertEquals(dive._state.db, 'other') 753 dive.editor = chris 754 html5.editor = mark 755 756 self.assertEquals(dive._state.db, 'other') 757 self.assertEquals(mark._state.db, 'other') 758 self.assertEquals(chris._state.db, 'default') 759 self.assertEquals(html5._state.db, 'default') 760 761 # This also works if you assign the FK in the constructor 762 water = Book(title="Dive into Water", published=datetime.date(2001, 1, 1), editor=mark) 763 self.assertEquals(water._state.db, 'default') 764 765 def test_m2m_cross_database_protection(self): 766 "M2M relations can cross databases if the database share a source" 767 # Create books and authors on the inverse to the usual database 768 pro = Book.objects.using('other').create(pk=1, title="Pro Django", 769 published=datetime.date(2008, 12, 16)) 770 771 marty = Person.objects.using('other').create(pk=1, name="Marty Alchin") 772 773 dive = Book.objects.using('default').create(pk=2, title="Dive into Python", 774 published=datetime.date(2009, 5, 4)) 775 776 mark = Person.objects.using('default').create(pk=2, name="Mark Pilgrim") 777 778 # Now save back onto the usual databse. 779 # This simulates master/slave - the objects exist on both database, 780 # but the _state.db is as it is for all other tests. 781 pro.save(using='default') 782 marty.save(using='default') 783 dive.save(using='other') 784 mark.save(using='other') 785 786 # Check that we have 2 of both types of object on both databases 787 self.assertEquals(Book.objects.using('default').count(), 2) 788 self.assertEquals(Book.objects.using('other').count(), 2) 789 self.assertEquals(Person.objects.using('default').count(), 2) 790 self.assertEquals(Person.objects.using('other').count(), 2) 791 792 # Set a m2m set with an object from a different database 793 try: 794 marty.book_set = [pro, dive] 795 except ValueError: 796 self.fail("Assignment across master/slave databases with a common source should be ok") 797 798 # Database assignments don't change 799 self.assertEquals(marty._state.db, 'default') 800 self.assertEquals(pro._state.db, 'default') 801 self.assertEquals(dive._state.db, 'other') 802 self.assertEquals(mark._state.db, 'other') 803 804 # All m2m relations should be saved on the default database 805 self.assertEquals(Book.authors.through.objects.using('default').count(), 2) 806 self.assertEquals(Book.authors.through.objects.using('other').count(), 0) 807 808 # Reset relations 809 Book.authors.through.objects.using('default').delete() 810 811 # Add to an m2m with an object from a different database 812 try: 813 marty.book_set.add(dive) 814 except ValueError: 815 self.fail("Assignment across master/slave databases with a common source should be ok") 816 817 # Database assignments don't change 818 self.assertEquals(marty._state.db, 'default') 819 self.assertEquals(pro._state.db, 'default') 820 self.assertEquals(dive._state.db, 'other') 821 self.assertEquals(mark._state.db, 'other') 822 823 # All m2m relations should be saved on the default database 824 self.assertEquals(Book.authors.through.objects.using('default').count(), 1) 825 self.assertEquals(Book.authors.through.objects.using('other').count(), 0) 826 827 # Reset relations 828 Book.authors.through.objects.using('default').delete() 829 830 # Set a reverse m2m with an object from a different database 831 try: 832 dive.authors = [mark, marty] 833 except ValueError: 834 self.fail("Assignment across master/slave databases with a common source should be ok") 835 836 # Database assignments don't change 837 self.assertEquals(marty._state.db, 'default') 838 self.assertEquals(pro._state.db, 'default') 839 self.assertEquals(dive._state.db, 'other') 840 self.assertEquals(mark._state.db, 'other') 841 842 # All m2m relations should be saved on the default database 843 self.assertEquals(Book.authors.through.objects.using('default').count(), 2) 844 self.assertEquals(Book.authors.through.objects.using('other').count(), 0) 845 846 # Reset relations 847 Book.authors.through.objects.using('default').delete() 848 849 self.assertEquals(Book.authors.through.objects.using('default').count(), 0) 850 self.assertEquals(Book.authors.through.objects.using('other').count(), 0) 851 852 # Add to a reverse m2m with an object from a different database 853 try: 854 dive.authors.add(marty) 855 except ValueError: 856 self.fail("Assignment across master/slave databases with a common source should be ok") 857 858 # Database assignments don't change 859 self.assertEquals(marty._state.db, 'default') 860 self.assertEquals(pro._state.db, 'default') 861 self.assertEquals(dive._state.db, 'other') 862 self.assertEquals(mark._state.db, 'other') 863 864 # All m2m relations should be saved on the default database 865 self.assertEquals(Book.authors.through.objects.using('default').count(), 1) 866 self.assertEquals(Book.authors.through.objects.using('other').count(), 0) 867 868 def test_generic_key_cross_database_protection(self): 869 "Generic Key operations can span databases if they share a source" 870 # Create a book and author on the default database 871 pro = Book.objects.create(title="Pro Django", 872 published=datetime.date(2008, 12, 16)) 873 874 review1 = Review.objects.create(source="Python Monthly", content_object=pro) 875 876 # Create a book and author on the other database 877 dive = Book.objects.using('other').create(title="Dive into Python", 878 published=datetime.date(2009, 5, 4)) 879 880 review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive) 881 882 # Set a generic foreign key with an object from a different database 883 try: 884 review1.content_object = dive 885 except ValueError: 886 self.fail("Assignment across master/slave databases with a common source should be ok") 887 888 # Database assignments of original objects haven't changed... 889 self.assertEquals(pro._state.db, 'default') 890 self.assertEquals(review1._state.db, 'default') 891 self.assertEquals(dive._state.db, 'other') 892 self.assertEquals(review2._state.db, 'other') 893 894 # ... but they will when the affected object is saved. 895 dive.save() 896 self.assertEquals(review1._state.db, 'default') 897 self.assertEquals(dive._state.db, 'default') 898 899 # ...and the source database now has a copy of any object saved 900 try: 901 Book.objects.using('default').get(title='Dive into Python').delete() 902 except Book.DoesNotExist: 903 self.fail('Source database should have a copy of saved object') 904 905 # This isn't a real master-slave database, so restore the original from other 906 dive = Book.objects.using('other').get(title='Dive into Python') 907 self.assertEquals(dive._state.db, 'other') 908 909 # Add to a generic foreign key set with an object from a different database 910 try: 911 dive.reviews.add(review1) 912 except ValueError: 913 self.fail("Assignment across master/slave databases with a common source should be ok") 914 915 # Database assignments of original objects haven't changed... 916 self.assertEquals(pro._state.db, 'default') 917 self.assertEquals(review1._state.db, 'default') 918 self.assertEquals(dive._state.db, 'other') 919 self.assertEquals(review2._state.db, 'other') 920 921 # ... but they will when the affected object is saved. 922 dive.save() 923 self.assertEquals(dive._state.db, 'default') 924 925 # ...and the source database now has a copy of any object saved 926 try: 927 Book.objects.using('default').get(title='Dive into Python').delete() 928 except Book.DoesNotExist: 929 self.fail('Source database should have a copy of saved object') 930 931 # BUT! if you assign a FK object when the base object hasn't 932 # been saved yet, you implicitly assign the database for the 933 # base object. 934 review3 = Review(source="Python Daily") 935 # initially, no db assigned 936 self.assertEquals(review3._state.db, None) 937 938 # Dive comes from 'other', so review3 is set to use the source of 'other'... 939 review3.content_object = dive 940 self.assertEquals(review3._state.db, 'default') 941 633 942 634 943 class UserProfileTestCase(TestCase): 635 944 def setUp(self):