Author: Alex
Date: 2009-11-23 10:43:54 -0600 (Mon, 23 Nov 2009)
New Revision: 11769
Modified:
django/branches/soc2009/multidb/TODO
django/branches/soc2009/multidb/django/db/models/base.py
django/branches/soc2009/multidb/django/db/models/fields/related.py
django/branches/soc2009/multidb/django/db/models/query.py
django/branches/soc2009/multidb/django/forms/models.py
django/branches/soc2009/multidb/tests/modeltests/many_to_one/models.py
django/branches/soc2009/multidb/tests/regressiontests/aggregation_regress/models.py
django/branches/soc2009/multidb/tests/regressiontests/model_inheritance_regress/models.py
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/fixtures/multidb.default.json
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/fixtures/multidb.other.json
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/tests.py
django/branches/soc2009/multidb/tests/regressiontests/queries/models.py
Log:
[soc2009/multidb] Made instances sticky to the database that created them.
This involves:
* Adding a _state attribute to instances to track instance state
* Making db a state attribute, and making the db a public attribute on
querysets.
Patch from Russell Keith-Magee.
Modified: django/branches/soc2009/multidb/TODO
===================================================================
--- django/branches/soc2009/multidb/TODO 2009-11-23 16:43:35 UTC (rev
11768)
+++ django/branches/soc2009/multidb/TODO 2009-11-23 16:43:54 UTC (rev
11769)
@@ -11,14 +11,10 @@
* Should we take the opportunity to modify DB backends to use fully
qualified paths?
* Should we clean up DATABASES['DATABASE_NAME'] to DATABASES['NAME'] etc?
* Meta.using? Is is still required/desirable?
- * Fix the regressiontests/multiple_database test failures
- * Give instances knowledge of the database from which they were loaded.
- * Cascade instance using to m2m queries
* Cleanup of new API entry points
* validate() on a field
* name/purpose clash with Honza?
* any overlap with existing methods?
- * Accessing _using in BaseModelFormSet.
Optional for v1.2
~~~~~~~~~~~~~~~~~
Modified: django/branches/soc2009/multidb/django/db/models/base.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/base.py 2009-11-23
16:43:35 UTC (rev 11768)
+++ django/branches/soc2009/multidb/django/db/models/base.py 2009-11-23
16:43:54 UTC (rev 11769)
@@ -230,6 +230,13 @@
signals.class_prepared.send(sender=cls)
+class ModelState(object):
+ """
+ A class for storing instance state
+ """
+ def __init__(self, db=None):
+ self.db = db
+
class Model(object):
__metaclass__ = ModelBase
_deferred = False
@@ -237,6 +244,9 @@
def __init__(self, *args, **kwargs):
signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
+ # Set up the storage for instane state
+ self._state = ModelState()
+
# There is a rather weird disparity here; if kwargs, it's set, then
args
# overrides it. It should be one or the other; don't duplicate the work
# The reason for the kwargs check is that standard iterator passes in
by
@@ -428,7 +438,7 @@
need for overrides of save() to pass around internal-only parameters
('raw', 'cls', and 'origin').
"""
- using = using or self._meta.using or DEFAULT_DB_ALIAS
+ using = using or self._state.db or self._meta.using or DEFAULT_DB_ALIAS
connection = connections[using]
assert not (force_insert and force_update)
if cls is None:
@@ -514,6 +524,10 @@
setattr(self, meta.pk.attname, result)
transaction.commit_unless_managed(using=using)
+ # Store the database on which the object was saved
+ self._state.db = using
+
+ # Signal that the save is complete
if origin and not meta.auto_created:
signals.post_save.send(sender=origin, instance=self,
created=(not record_exists), raw=raw)
@@ -577,7 +591,7 @@
parent_obj._collect_sub_objects(seen_objs)
def delete(self, using=None):
- using = using or self._meta.using or DEFAULT_DB_ALIAS
+ using = using or self._state.db or self._meta.using or DEFAULT_DB_ALIAS
connection = connections[using]
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)
Modified: django/branches/soc2009/multidb/django/db/models/fields/related.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/fields/related.py
2009-11-23 16:43:35 UTC (rev 11768)
+++ django/branches/soc2009/multidb/django/db/models/fields/related.py
2009-11-23 16:43:54 UTC (rev 11769)
@@ -195,7 +195,7 @@
return getattr(instance, self.cache_name)
except AttributeError:
params = {'%s__pk' % self.related.field.name:
instance._get_pk_val()}
- rel_obj = self.related.model._base_manager.get(**params)
+ rel_obj =
self.related.model._base_manager.using(instance._state.db).get(**params)
setattr(instance, self.cache_name, rel_obj)
return rel_obj
@@ -259,9 +259,9 @@
# related fields, respect that.
rel_mgr = self.field.rel.to._default_manager
if getattr(rel_mgr, 'use_for_related_fields', False):
- rel_obj = rel_mgr.get(**params)
+ rel_obj = rel_mgr.using(instance._state.db).get(**params)
else:
- rel_obj = QuerySet(self.field.rel.to).get(**params)
+ rel_obj =
QuerySet(self.field.rel.to).using(instance._state.db).get(**params)
setattr(instance, cache_name, rel_obj)
return rel_obj
@@ -359,14 +359,14 @@
class RelatedManager(superclass):
def get_query_set(self):
- return
superclass.get_query_set(self).filter(**(self.core_filters))
+ return
superclass.get_query_set(self).using(instance._state.db).filter(**(self.core_filters))
def add(self, *objs):
for obj in objs:
if not isinstance(obj, self.model):
raise TypeError, "'%s' instance expected" %
self.model._meta.object_name
setattr(obj, rel_field.name, instance)
- obj.save()
+ obj.save(using=instance._state.db)
add.alters_data = True
def create(self, **kwargs):
@@ -378,7 +378,7 @@
# Update kwargs with the related object that this
# ForeignRelatedObjectsDescriptor knows about.
kwargs.update({rel_field.name: instance})
- return super(RelatedManager, self).get_or_create(**kwargs)
+ return super(RelatedManager,
self).using(instance._state.db).get_or_create(**kwargs)
get_or_create.alters_data = True
# remove() and clear() are only provided if the ForeignKey can
have a value of null.
@@ -389,7 +389,7 @@
# Is obj actually part of this descriptor set?
if getattr(obj, rel_field.attname) == val:
setattr(obj, rel_field.name, None)
- obj.save()
+ obj.save(using=instance._state.db)
else:
raise rel_field.rel.to.DoesNotExist, "%r is not
related to %r." % (obj, instance)
remove.alters_data = True
@@ -397,7 +397,7 @@
def clear(self):
for obj in self.all():
setattr(obj, rel_field.name, None)
- obj.save()
+ obj.save(using=instance._state.db)
clear.alters_data = True
manager = RelatedManager()
@@ -463,14 +463,14 @@
if not rel.through._meta.auto_created:
opts = through._meta
raise AttributeError, "Cannot use create() on a
ManyToManyField which specifies an intermediary model. Use %s.%s's Manager
instead." % (opts.app_label, opts.object_name)
- new_obj = super(ManyRelatedManager, self).create(**kwargs)
+ new_obj = super(ManyRelatedManager,
self).using(self.instance._state.db).create(**kwargs)
self.add(new_obj)
return new_obj
create.alters_data = True
def get_or_create(self, **kwargs):
obj, created = \
- super(ManyRelatedManager, self).get_or_create(**kwargs)
+ super(ManyRelatedManager,
self).using(self.instance._state.db).get_or_create(**kwargs)
# We only need to add() if created because if we got an object back
# from get() then the relationship already exists.
if created:
@@ -495,7 +495,7 @@
raise TypeError, "'%s' instance expected" %
self.model._meta.object_name
else:
new_ids.add(obj)
- vals =
self.through._default_manager.values_list(target_field_name, flat=True)
+ vals =
self.through._default_manager.using(self.instance._state.db).values_list(target_field_name,
flat=True)
vals = vals.filter(**{
source_field_name: self._pk_val,
'%s__in' % target_field_name: new_ids,
@@ -504,7 +504,7 @@
# Add the ones that aren't there already
for obj_id in (new_ids - vals):
- self.through._default_manager.create(**{
+
self.through._default_manager.using(self.instance._state.db).create(**{
'%s_id' % source_field_name: self._pk_val,
'%s_id' % target_field_name: obj_id,
})
@@ -524,14 +524,14 @@
else:
old_ids.add(obj)
# Remove the specified objects from the join table
- self.through._default_manager.filter(**{
+
self.through._default_manager.using(self.instance._state.db).filter(**{
source_field_name: self._pk_val,
'%s__in' % target_field_name: old_ids
}).delete()
def _clear_items(self, source_field_name):
# source_col_name: the PK colname in join_table for the source
object
- self.through._default_manager.filter(**{
+
self.through._default_manager.using(self.instance._state.db).filter(**{
source_field_name: self._pk_val
}).delete()
Modified: django/branches/soc2009/multidb/django/db/models/query.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/query.py 2009-11-23
16:43:35 UTC (rev 11768)
+++ django/branches/soc2009/multidb/django/db/models/query.py 2009-11-23
16:43:54 UTC (rev 11769)
@@ -38,7 +38,7 @@
self._result_cache = None
self._iter = None
self._sticky_filter = False
- self._using = using
+ self.db = using
########################
# PYTHON MAGIC METHODS #
@@ -236,8 +236,8 @@
else:
init_list.append(field.attname)
model_cls = deferred_class_factory(self.model, skip)
-
- compiler = self.query.get_compiler(using=self._using)
+
+ compiler = self.query.get_compiler(using=self.db)
for row in compiler.results_iter():
if fill_cache:
obj, _ = get_cached_row(self.model, row,
@@ -260,6 +260,9 @@
for i, aggregate in enumerate(aggregate_select):
setattr(obj, aggregate, row[i+aggregate_start])
+ # Store the source database of the object
+ obj._state.db = self.db
+
yield obj
def aggregate(self, *args, **kwargs):
@@ -279,7 +282,7 @@
query.add_aggregate(aggregate_expr, self.model, alias,
is_summary=True)
- return query.get_aggregation(using=self._using)
+ return query.get_aggregation(using=self.db)
def count(self):
"""
@@ -292,7 +295,7 @@
if self._result_cache is not None and not self._iter:
return len(self._result_cache)
- return self.query.get_count(using=self._using)
+ return self.query.get_count(using=self.db)
def get(self, *args, **kwargs):
"""
@@ -315,7 +318,7 @@
and returning the created object.
"""
obj = self.model(**kwargs)
- obj.save(force_insert=True, using=self._using)
+ obj.save(force_insert=True, using=self.db)
return obj
def get_or_create(self, **kwargs):
@@ -334,12 +337,12 @@
params = dict([(k, v) for k, v in kwargs.items() if '__' not
in k])
params.update(defaults)
obj = self.model(**params)
- sid = transaction.savepoint(using=self._using)
- obj.save(force_insert=True, using=self._using)
- transaction.savepoint_commit(sid, using=self._using)
+ sid = transaction.savepoint(using=self.db)
+ obj.save(force_insert=True, using=self.db)
+ transaction.savepoint_commit(sid, using=self.db)
return obj, True
except IntegrityError, e:
- transaction.savepoint_rollback(sid, using=self._using)
+ transaction.savepoint_rollback(sid, using=self.db)
try:
return self.get(**kwargs), False
except self.model.DoesNotExist:
@@ -399,7 +402,7 @@
if not seen_objs:
break
- delete_objects(seen_objs, del_query._using)
+ delete_objects(seen_objs, del_query.db)
# Clear the result cache, in case this QuerySet gets reused.
self._result_cache = None
@@ -414,20 +417,20 @@
"Cannot update a query once a slice has been taken."
query = self.query.clone(sql.UpdateQuery)
query.add_update_values(kwargs)
- if not transaction.is_managed(using=self._using):
- transaction.enter_transaction_management(using=self._using)
+ if not transaction.is_managed(using=self.db):
+ transaction.enter_transaction_management(using=self.db)
forced_managed = True
else:
forced_managed = False
try:
- rows = query.get_compiler(self._using).execute_sql(None)
+ rows = query.get_compiler(self.db).execute_sql(None)
if forced_managed:
- transaction.commit(using=self._using)
+ transaction.commit(using=self.db)
else:
- transaction.commit_unless_managed(using=self._using)
+ transaction.commit_unless_managed(using=self.db)
finally:
if forced_managed:
- transaction.leave_transaction_management(using=self._using)
+ transaction.leave_transaction_management(using=self.db)
self._result_cache = None
return rows
update.alters_data = True
@@ -444,12 +447,12 @@
query = self.query.clone(sql.UpdateQuery)
query.add_update_fields(values)
self._result_cache = None
- return query.get_compiler(self._using).execute_sql(None)
+ return query.get_compiler(self.db).execute_sql(None)
_update.alters_data = True
def exists(self):
if self._result_cache is None:
- return self.query.has_results(using=self._using)
+ return self.query.has_results(using=self.db)
return bool(self._result_cache)
##################################################
@@ -661,7 +664,7 @@
Selects which database this QuerySet should excecute it's query
against.
"""
clone = self._clone()
- clone._using = alias
+ clone.db = alias
return clone
###################################
@@ -692,7 +695,7 @@
if self._sticky_filter:
query.filter_is_sticky = True
c = klass(model=self.model, query=query)
- c._using = self._using
+ c.db = self.db
c.__dict__.update(kwargs)
if setup and hasattr(c, '_setup_query'):
c._setup_query()
@@ -747,7 +750,7 @@
Returns the internal query's SQL and parameters (as a tuple).
"""
obj = self.values("pk")
- if connection == connections[obj._using]:
+ if connection == connections[obj.db]:
return
obj.query.get_compiler(connection=connection).as_nested_sql()
raise ValueError("Can't do subqueries with queries on different DBs.")
@@ -779,7 +782,7 @@
names = extra_names + field_names + aggregate_names
- for row in self.query.get_compiler(self._using).results_iter():
+ for row in self.query.get_compiler(self.db).results_iter():
yield dict(zip(names, row))
def _setup_query(self):
@@ -876,7 +879,7 @@
% self.__class__.__name__)
obj = self._clone()
- if connection == connections[obj._using]:
+ if connection == connections[obj.db]:
return
obj.query.get_compiler(connection=connection).as_nested_sql()
raise ValueError("Can't do subqueries with queries on different DBs.")
@@ -894,10 +897,10 @@
class ValuesListQuerySet(ValuesQuerySet):
def iterator(self):
if self.flat and len(self._fields) == 1:
- for row in self.query.get_compiler(self._using).results_iter():
+ for row in self.query.get_compiler(self.db).results_iter():
yield row[0]
elif not self.query.extra_select and not self.query.aggregate_select:
- for row in self.query.get_compiler(self._using).results_iter():
+ for row in self.query.get_compiler(self.db).results_iter():
yield tuple(row)
else:
# When extra(select=...) or an annotation is involved, the extra
@@ -916,7 +919,7 @@
else:
fields = names
- for row in self.query.get_compiler(self._using).results_iter():
+ for row in self.query.get_compiler(self.db).results_iter():
data = dict(zip(names, row))
yield tuple([data[f] for f in fields])
@@ -928,7 +931,7 @@
class DateQuerySet(QuerySet):
def iterator(self):
- return self.query.get_compiler(self._using).results_iter()
+ return self.query.get_compiler(self.db).results_iter()
def _setup_query(self):
"""
Modified: django/branches/soc2009/multidb/django/forms/models.py
===================================================================
--- django/branches/soc2009/multidb/django/forms/models.py 2009-11-23
16:43:35 UTC (rev 11768)
+++ django/branches/soc2009/multidb/django/forms/models.py 2009-11-23
16:43:54 UTC (rev 11769)
@@ -472,7 +472,7 @@
pk = self.data[pk_key]
pk_field = self.model._meta.pk
pk = pk_field.get_db_prep_lookup('exact', pk,
- connection=connections[self.get_queryset()._using])
+ connection=connections[self.get_queryset().db])
if isinstance(pk, list):
pk = pk[0]
kwargs['instance'] = self._existing_object(pk)
Modified: django/branches/soc2009/multidb/tests/modeltests/many_to_one/models.py
===================================================================
--- django/branches/soc2009/multidb/tests/modeltests/many_to_one/models.py
2009-11-23 16:43:35 UTC (rev 11768)
+++ django/branches/soc2009/multidb/tests/modeltests/many_to_one/models.py
2009-11-23 16:43:54 UTC (rev 11769)
@@ -157,7 +157,7 @@
# The underlying query only makes one join when a related table is referenced
twice.
>>> queryset = Article.objects.filter(reporter__first_name__exact='John',
>>> reporter__last_name__exact='Smith')
->>> sql = queryset.query.get_compiler(queryset._using).as_sql()[0]
+>>> sql = queryset.query.get_compiler(queryset.db).as_sql()[0]
>>> sql.count('INNER JOIN')
1
Modified:
django/branches/soc2009/multidb/tests/regressiontests/aggregation_regress/models.py
===================================================================
---
django/branches/soc2009/multidb/tests/regressiontests/aggregation_regress/models.py
2009-11-23 16:43:35 UTC (rev 11768)
+++
django/branches/soc2009/multidb/tests/regressiontests/aggregation_regress/models.py
2009-11-23 16:43:54 UTC (rev 11769)
@@ -250,10 +250,10 @@
>>> out = pickle.dumps(qs)
# Then check that the round trip works.
->>> query = qs.query.get_compiler(qs._using).as_sql()[0]
+>>> query = qs.query.get_compiler(qs.db).as_sql()[0]
>>> select_fields = qs.query.select_fields
>>> query2 = pickle.loads(pickle.dumps(qs))
->>> query2.query.get_compiler(query2._using).as_sql()[0] == query
+>>> query2.query.get_compiler(query2.db).as_sql()[0] == query
True
>>> query2.query.select_fields = select_fields
Modified:
django/branches/soc2009/multidb/tests/regressiontests/model_inheritance_regress/models.py
===================================================================
---
django/branches/soc2009/multidb/tests/regressiontests/model_inheritance_regress/models.py
2009-11-23 16:43:35 UTC (rev 11768)
+++
django/branches/soc2009/multidb/tests/regressiontests/model_inheritance_regress/models.py
2009-11-23 16:43:54 UTC (rev 11769)
@@ -315,7 +315,7 @@
# Regression test for #9390. This necessarily pokes at the SQL string for the
# query, since the duplicate problems are only apparent at that late stage.
>>> qs = ArticleWithAuthor.objects.order_by('pub_date', 'pk')
->>> sql = qs.query.get_compiler(qs._using).as_sql()[0]
+>>> sql = qs.query.get_compiler(qs.db).as_sql()[0]
>>> fragment = sql[sql.find('ORDER BY'):]
>>> pos = fragment.find('pub_date')
>>> fragment.find('pub_date', pos + 1) == -1
Modified:
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/fixtures/multidb.default.json
===================================================================
---
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/fixtures/multidb.default.json
2009-11-23 16:43:35 UTC (rev 11768)
+++
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/fixtures/multidb.default.json
2009-11-23 16:43:54 UTC (rev 11769)
@@ -3,8 +3,8 @@
"pk": 2,
"model": "multiple_database.book",
"fields": {
- "title": "Dive into Python",
- "published": "2009-5-4"
+ "title": "Pro Django",
+ "published": "2008-12-16"
}
}
-]
\ No newline at end of file
+]
Modified:
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/fixtures/multidb.other.json
===================================================================
---
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/fixtures/multidb.other.json
2009-11-23 16:43:35 UTC (rev 11768)
+++
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/fixtures/multidb.other.json
2009-11-23 16:43:54 UTC (rev 11769)
@@ -3,8 +3,8 @@
"pk": 2,
"model": "multiple_database.book",
"fields": {
- "title": "Pro Django",
- "published": "2008-12-16"
+ "title": "Dive into Python",
+ "published": "2009-5-4"
}
}
]
\ No newline at end of file
Modified:
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/tests.py
===================================================================
---
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/tests.py
2009-11-23 16:43:35 UTC (rev 11768)
+++
django/branches/soc2009/multidb/tests/regressiontests/multiple_database/tests.py
2009-11-23 16:43:54 UTC (rev 11769)
@@ -20,78 +20,78 @@
def test_default_creation(self):
"Objects created on the default database don't leak onto other
databases"
# Create a book on the default database using create()
- Book.objects.create(title="Dive into Python",
- published=datetime.date(2009, 5, 4))
+ Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
# Create a book on the default database using a save
- pro = Book()
- pro.title="Pro Django"
- pro.published = datetime.date(2008, 12, 16)
- pro.save()
+ dive = Book()
+ dive.title="Dive into Python"
+ dive.published = datetime.date(2009, 5, 4)
+ dive.save()
# Check that book exists on the default database, but not on other
database
try:
- Book.objects.get(title="Dive into Python")
- Book.objects.using('default').get(title="Dive into Python")
+ Book.objects.get(title="Pro Django")
+ Book.objects.using('default').get(title="Pro Django")
except Book.DoesNotExist:
self.fail('"Dive Into Python" should exist on default database')
self.assertRaises(Book.DoesNotExist,
Book.objects.using('other').get,
- title="Dive into Python"
+ title="Pro Django"
)
try:
- Book.objects.get(title="Pro Django")
- Book.objects.using('default').get(title="Pro Django")
+ Book.objects.get(title="Dive into Python")
+ Book.objects.using('default').get(title="Dive into Python")
except Book.DoesNotExist:
- self.fail('"Pro Django" should exist on default database')
+ self.fail('"Dive into Python" should exist on default database')
self.assertRaises(Book.DoesNotExist,
Book.objects.using('other').get,
- title="Pro Django"
+ title="Dive into Python"
)
def test_other_creation(self):
"Objects created on another database don't leak onto the default
database"
# Create a book on the second database
- Book.objects.using('other').create(title="Dive into Python",
- published=datetime.date(2009, 5, 4))
+ Book.objects.using('other').create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
# Create a book on the default database using a save
- pro = Book()
- pro.title="Pro Django"
- pro.published = datetime.date(2008, 12, 16)
- pro.save(using='other')
+ dive = Book()
+ dive.title="Dive into Python"
+ dive.published = datetime.date(2009, 5, 4)
+ dive.save(using='other')
# Check that book exists on the default database, but not on other
database
try:
- Book.objects.using('other').get(title="Dive into Python")
+ Book.objects.using('other').get(title="Pro Django")
except Book.DoesNotExist:
self.fail('"Dive Into Python" should exist on other database')
self.assertRaises(Book.DoesNotExist,
Book.objects.get,
- title="Dive into Python"
+ title="Pro Django"
)
self.assertRaises(Book.DoesNotExist,
Book.objects.using('default').get,
- title="Dive into Python"
+ title="Pro Django"
)
try:
- Book.objects.using('other').get(title="Pro Django")
+ Book.objects.using('other').get(title="Dive into Python")
except Book.DoesNotExist:
- self.fail('"Pro Django" should exist on other database')
+ self.fail('"Dive into Python" should exist on other database')
self.assertRaises(Book.DoesNotExist,
Book.objects.get,
- title="Pro Django"
+ title="Dive into Python"
)
self.assertRaises(Book.DoesNotExist,
Book.objects.using('default').get,
- title="Pro Django"
+ title="Dive into Python"
)
def test_basic_queries(self):
@@ -126,23 +126,23 @@
months = Book.objects.using('default').dates('published', 'month')
self.assertEqual([o.month for o in months], [])
- def test_m2m(self):
+ def test_m2m_separation(self):
"M2M fields are constrained to a single database"
# Create a book and author on the default database
- dive = Book.objects.create(title="Dive into Python",
- published=datetime.date(2009, 5, 4))
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
- mark = Author.objects.create(name="Mark Pilgrim")
+ marty = Author.objects.create(name="Marty Alchin")
# Create a book and author on the other database
- pro = Book.objects.using('other').create(title="Pro Django",
-
published=datetime.date(2008, 12, 16))
+ dive = Book.objects.using('other').create(title="Dive into Python",
+
published=datetime.date(2009, 5, 4))
- marty = Author.objects.using('other').create(name="Marty Alchin")
+ mark = Author.objects.using('other').create(name="Mark Pilgrim")
# Save the author relations
+ pro.authors = [marty]
dive.authors = [mark]
- pro.authors = [marty]
# Inspect the m2m tables directly.
# There should be 1 entry in each database
@@ -150,91 +150,223 @@
self.assertEquals(Book.authors.through.objects.using('other').count(),
1)
# Check that queries work across m2m joins
-
self.assertEquals(Book.objects.using('default').filter(authors__name='Mark
Pilgrim').values_list('title', flat=True),
- ['Dive into Python'])
-
self.assertEquals(Book.objects.using('other').filter(authors__name='Mark
Pilgrim').values_list('title', flat=True),
+
self.assertEquals(list(Book.objects.using('default').filter(authors__name='Marty
Alchin').values_list('title', flat=True)),
+ [u'Pro Django'])
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='Marty
Alchin').values_list('title', flat=True)),
[])
-
self.assertEquals(Book.objects.using('default').filter(authors__name='Marty
Alchin').values_list('title', flat=True),
+
self.assertEquals(list(Book.objects.using('default').filter(authors__name='Mark
Pilgrim').values_list('title', flat=True)),
[])
-
self.assertEquals(Book.objects.using('other').filter(authors__name='Marty
Alchin').values_list('title', flat=True),
- ['Pro Django'])
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
- def test_foreign_key(self):
+ def test_m2m_forward_operations(self):
+ "M2M forward manipulations are all constrained to a single DB"
+ # 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")
+
+ # Save the author relations
+ dive.authors = [mark]
+
+ # Add a second author
+ john = Author.objects.using('other').create(name="John Smith")
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='John
Smith').values_list('title', flat=True)),
+ [])
+
+
+ dive.authors.add(john)
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='John
Smith').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
+ # Remove the second author
+ dive.authors.remove(john)
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='John
Smith').values_list('title', flat=True)),
+ [])
+
+ # Clear all authors
+ dive.authors.clear()
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [])
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='John
Smith').values_list('title', flat=True)),
+ [])
+
+ # Create an author through the m2m interface
+ dive.authors.create(name='Jane Brown')
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [])
+
self.assertEquals(list(Book.objects.using('other').filter(authors__name='Jane
Brown').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
+ def test_m2m_reverse_operations(self):
+ "M2M reverse manipulations are all constrained to a single DB"
+ # 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")
+
+ # Save the author relations
+ dive.authors = [mark]
+
+ # Create a second book on the other database
+ grease = Book.objects.using('other').create(title="Greasemonkey Hacks",
+
published=datetime.date(2005, 11, 1))
+
+ # Add a books to the m2m
+ mark.book_set.add(grease)
+
self.assertEquals(list(Author.objects.using('other').filter(book__title='Dive
into Python').values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+
self.assertEquals(list(Author.objects.using('other').filter(book__title='Greasemonkey
Hacks').values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+
+ # Remove a book from the m2m
+ mark.book_set.remove(grease)
+
self.assertEquals(list(Author.objects.using('other').filter(book__title='Dive
into Python').values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+
self.assertEquals(list(Author.objects.using('other').filter(book__title='Greasemonkey
Hacks').values_list('name', flat=True)),
+ [])
+
+ # Clear the books associated with mark
+ mark.book_set.clear()
+
self.assertEquals(list(Author.objects.using('other').filter(book__title='Dive
into Python').values_list('name', flat=True)),
+ [])
+
self.assertEquals(list(Author.objects.using('other').filter(book__title='Greasemonkey
Hacks').values_list('name', flat=True)),
+ [])
+
+ # Create a book through the m2m interface
+ mark.book_set.create(title="Dive into HTML5",
published=datetime.date(2020, 1, 1))
+
self.assertEquals(list(Author.objects.using('other').filter(book__title='Dive
into Python').values_list('name', flat=True)),
+ [])
+
self.assertEquals(list(Author.objects.using('other').filter(book__title='Dive
into HTML5').values_list('name', flat=True)),
+ [u'Mark Pilgrim'])
+
+
+ def test_foreign_key_separation(self):
"FK fields are constrained to a single database"
# Create a book and author on the default database
- dive = Book.objects.create(title="Dive into Python",
- published=datetime.date(2009, 5, 4))
+ pro = Book.objects.create(title="Pro Django",
+ published=datetime.date(2008, 12, 16))
- mark = Author.objects.create(name="Mark Pilgrim")
+ marty = Author.objects.create(name="Marty Alchin")
# Create a book and author on the other database
- pro = Book.objects.using('other').create(title="Pro Django",
-
published=datetime.date(2008, 12, 16))
+ dive = Book.objects.using('other').create(title="Dive into Python",
+
published=datetime.date(2009, 5, 4))
- marty = Author.objects.using('other').create(name="Marty Alchin")
+ mark = Author.objects.using('other').create(name="Mark Pilgrim")
# Save the author's favourite books
+ marty.favourite_book = pro
+ marty.save()
+
mark.favourite_book = dive
mark.save()
- marty.favourite_book = pro
- marty.save() # FIXME Should this be save(using=alias)?
+ marty = Author.objects.using('default').get(name="Marty Alchin")
+ self.assertEquals(marty.favourite_book.title, "Pro Django")
- mark = Author.objects.using('default').get(name="Mark Pilgrim")
+ mark = Author.objects.using('other').get(name='Mark Pilgrim')
self.assertEquals(mark.favourite_book.title, "Dive into Python")
- marty = Author.objects.using('other').get(name='Marty Alchin')
- self.assertEquals(marty.favourite_book.title, "Dive into Python")
-
try:
- mark.favourite_book = marty
+ marty.favourite_book = mark
self.fail("Shouldn't be able to assign across databases")
except Exception: # FIXME - this should be more explicit
pass
# Check that queries work across foreign key joins
-
self.assertEquals(Book.objects.using('default').filter(favourite_of__name='Mark
Pilgrim').values_list('title', flat=True),
- ['Dive into Python'])
-
self.assertEquals(Book.objects.using('other').filter(favourite_of__name='Mark
Pilgrim').values_list('title', flat=True),
+
self.assertEquals(list(Book.objects.using('default').filter(favourite_of__name='Marty
Alchin').values_list('title', flat=True)),
+ [u'Pro Django'])
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='Marty
Alchin').values_list('title', flat=True)),
[])
-
self.assertEquals(Book.objects.using('default').filter(favourite_of__name='Marty
Alchin').values_list('title', flat=True),
+
self.assertEquals(list(Book.objects.using('default').filter(favourite_of__name='Mark
Pilgrim').values_list('title', flat=True)),
[])
-
self.assertEquals(Book.objects.using('other').filter(favourite_of__name='Marty
Alchin').values_list('title', flat=True),
- ['Pro Django'])
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
+ def test_foreign_key_reverse_operations(self):
+ "FK 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))
+
+ mark = Author.objects.using('other').create(name="Mark Pilgrim")
+
+ # Save the author relations
+ mark.favourite_book = dive
+ mark.save()
+
+ # Add a second author
+ john = Author.objects.using('other').create(name="John Smith")
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='John
Smith').values_list('title', flat=True)),
+ [])
+
+
+ dive.favourite_of.add(john)
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='John
Smith').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
+ # Remove the second author
+ dive.favourite_of.remove(john)
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='John
Smith').values_list('title', flat=True)),
+ [])
+
+ # Clear all favourite_of
+ dive.favourite_of.clear()
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [])
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='John
Smith').values_list('title', flat=True)),
+ [])
+
+ # Create an author through the m2m interface
+ dive.favourite_of.create(name='Jane Brown')
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='Mark
Pilgrim').values_list('title', flat=True)),
+ [])
+
self.assertEquals(list(Book.objects.using('other').filter(favourite_of__name='Jane
Brown').values_list('title', flat=True)),
+ [u'Dive into Python'])
+
class FixtureTestCase(TestCase):
multi_db = True
fixtures = ['multidb-common', 'multidb']
def test_fixture_loading(self):
"Multi-db fixtures are loaded correctly"
- # Check that "Dive into Python" exists on the default database, but
not on other database
+ # Check that "Pro Django" exists on the default database, but not on
other database
try:
- Book.objects.get(title="Dive into Python")
- Book.objects.using('default').get(title="Dive into Python")
+ Book.objects.get(title="Pro Django")
+ Book.objects.using('default').get(title="Pro Django")
except Book.DoesNotExist:
self.fail('"Dive Into Python" should exist on default database')
self.assertRaises(Book.DoesNotExist,
Book.objects.using('other').get,
- title="Dive into Python"
+ title="Pro Django"
)
- # Check that "Pro Django" exists on the default database, but not on
other database
+ # Check that "Dive into Python" exists on the default database, but
not on other database
try:
- Book.objects.using('other').get(title="Pro Django")
+ Book.objects.using('other').get(title="Dive into Python")
except Book.DoesNotExist:
- self.fail('"Pro Django" should exist on other database')
+ self.fail('"Dive into Python" should exist on other database')
self.assertRaises(Book.DoesNotExist,
Book.objects.get,
- title="Pro Django"
+ title="Dive into Python"
)
self.assertRaises(Book.DoesNotExist,
Book.objects.using('default').get,
- title="Pro Django"
+ title="Dive into Python"
)
# Check that "Definitive Guide" exists on the both databases
@@ -251,6 +383,6 @@
def test_pickling(self):
for db in connections:
- Book.objects.using(db).create(title='Pro Django',
published=datetime.date(2008, 12, 16))
+ Book.objects.using(db).create(title='Dive into Python',
published=datetime.date(2009, 5, 4))
qs = Book.objects.all()
- self.assertEqual(qs._using, pickle.loads(pickle.dumps(qs))._using)
+ self.assertEqual(qs.db, pickle.loads(pickle.dumps(qs)).db)
Modified:
django/branches/soc2009/multidb/tests/regressiontests/queries/models.py
===================================================================
--- django/branches/soc2009/multidb/tests/regressiontests/queries/models.py
2009-11-23 16:43:35 UTC (rev 11768)
+++ django/branches/soc2009/multidb/tests/regressiontests/queries/models.py
2009-11-23 16:43:54 UTC (rev 11769)
@@ -822,8 +822,8 @@
Bug #7045 -- extra tables used to crash SQL construction on the second use.
>>> qs = Ranking.objects.extra(tables=['django_site'])
->>> s = qs.query.get_compiler(qs._using).as_sql()
->>> s = qs.query.get_compiler(qs._using).as_sql() # test passes if this
doesn't raise an exception.
+>>> s = qs.query.get_compiler(qs.db).as_sql()
+>>> s = qs.query.get_compiler(qs.db).as_sql() # test passes if this doesn't
raise an exception.
Bug #7098 -- Make sure semi-deprecated ordering by related models syntax still
works.
@@ -912,9 +912,9 @@
tricky thing here is to ensure that we do the related selections properly after
unpickling.
>>> qs = Item.objects.select_related()
->>> query = qs.query.get_compiler(qs._using).as_sql()[0]
+>>> query = qs.query.get_compiler(qs.db).as_sql()[0]
>>> query2 = pickle.loads(pickle.dumps(qs.query))
->>> query2.get_compiler(qs._using).as_sql()[0] == query
+>>> query2.get_compiler(qs.db).as_sql()[0] == query
True
Check pickling of deferred-loading querysets
@@ -1051,7 +1051,7 @@
Calling order_by() with no parameters removes any existing ordering on the
model. But it should still be possible to add new ordering after that.
>>> qs = Author.objects.order_by().order_by('name')
->>> 'ORDER BY' in qs.query.get_compiler(qs._using).as_sql()[0]
+>>> 'ORDER BY' in qs.query.get_compiler(qs.db).as_sql()[0]
True
Incorrect SQL was being generated for certain types of exclude() queries that
@@ -1086,7 +1086,7 @@
Nested queries should not evaluate the inner query as part of constructing the
SQL (so we should see a nested query here, indicated by two "SELECT" calls).
>>> qs = Annotation.objects.filter(notes__in=Note.objects.filter(note="xyzzy"))
->>> qs.query.get_compiler(qs._using).as_sql()[0].count('SELECT')
+>>> qs.query.get_compiler(qs.db).as_sql()[0].count('SELECT')
2
Bug #10181 -- Avoid raising an EmptyResultSet if an inner query is provably
--
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=.