From 5d462b9ec1f77bb99240e14a893eb50aa633ce78 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 1 Jul 2009 05:01:59 +0000 Subject: [PATCH] [soc2009/multidb] Added a using option to a Model's Meta class. This allows you to select the default database for a specific model, in addition to the global default git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11135 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- TODO.txt | 3 --- django/db/models/base.py | 6 ++--- django/db/models/options.py | 4 ++-- django/db/models/query.py | 6 +++-- docs/ref/models/options.txt | 14 ++++++++++-- docs/topics/db/multi-db.txt | 9 ++++++++ .../multiple_database/models.py | 15 ++++++++++++- .../multiple_database/tests.py | 22 +++++++++++++++++++ 8 files changed, 65 insertions(+), 14 deletions(-) diff --git a/TODO.txt b/TODO.txt index af26b64242..dd9047af92 100644 --- a/TODO.txt +++ b/TODO.txt @@ -26,7 +26,4 @@ that need to be done. I'm trying to be as granular as possible. 4) Wait on the merge of the m2m stuff. -5) Add the ``using`` Meta option. Tests and docs(these are to be assumed at - each stage from here on out). - 6) Time permitting add support for a ``DatabaseManager``. diff --git a/django/db/models/base.py b/django/db/models/base.py index c7596b37b7..e1c5a8461d 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -419,8 +419,7 @@ class Model(object): need for overrides of save() to pass around internal-only parameters ('raw', 'cls', and 'origin'). """ - if using is None: - using = DEFAULT_DB_ALIAS + using = using or self._meta.using or DEFAULT_DB_ALIAS connection = connections[using] assert not (force_insert and force_update) if cls is None: @@ -563,8 +562,7 @@ class Model(object): parent_obj._collect_sub_objects(seen_objs) def delete(self, using=None): - if using is None: - using = DEFAULT_DB_ALIAS + using = using 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) diff --git a/django/db/models/options.py b/django/db/models/options.py index 34dd2aac34..82550169db 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -21,7 +21,7 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]| DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 'unique_together', 'permissions', 'get_latest_by', 'order_with_respect_to', 'app_label', 'db_tablespace', - 'abstract', 'managed', 'proxy') + 'abstract', 'managed', 'proxy', 'using') class Options(object): def __init__(self, meta, app_label=None): @@ -47,6 +47,7 @@ class Options(object): self.proxy_for_model = None self.parents = SortedDict() self.duplicate_targets = {} + self.using = None # To handle various inheritance situations, we need to track where # managers came from (concrete or abstract base classes). @@ -487,4 +488,3 @@ class Options(object): Returns the index of the primary key field in the self.fields list. """ return self.fields.index(self.pk) - diff --git a/django/db/models/query.py b/django/db/models/query.py index c6071281b5..1e91badccd 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -33,12 +33,14 @@ class QuerySet(object): """ def __init__(self, model=None, query=None): self.model = model - connection = connections[DEFAULT_DB_ALIAS] + using = model._meta.using or DEFAULT_DB_ALIAS + connection = connections[using] self.query = query or sql.Query(self.model, connection) self._result_cache = None self._iter = None self._sticky_filter = False - self._using = connections.alias_for_connection(self.query.connection) + self._using = (query and + connections.alias_for_connection(self.query.connection) or using) ######################## # PYTHON MAGIC METHODS # diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 8402567f7a..4725a8d146 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -103,7 +103,7 @@ model handling are exactly the same as normal. This includes unmanaged model, then the intermediate table for the many-to-many join will also not be created. However, a the intermediary table between one managed and one unmanaged model *will* be created. - + If you need to change this default behavior, create the intermediary table as an explicit model (with ``managed`` set as needed) and use the :attr:`ManyToManyField.through` attribute to make the relation use your @@ -210,6 +210,17 @@ set of fields:: unique_together = ("driver", "restaurant") +``using`` +--------- + +.. attribute:: Options.using + +The alias for the default database to be used for this model. If this is not +provided the default is ``'default'``. If it is porvided it can be overidden +at the ``QuerySet`` level with the ``using()`` method. + +.. versionadded:: TODO + ``verbose_name`` ---------------- @@ -232,4 +243,3 @@ The plural name for the object:: verbose_name_plural = "stories" If this isn't given, Django will use :attr:`~Options.verbose_name` + ``"s"``. - diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index 0365004091..5a76848b5b 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -25,6 +25,15 @@ thing to note is that your primary database should have the alias ``'default'``, and any additional databases you have can have whatever alias you choose. +Selecting a Database for a ``Model`` +==================================== + +In addition to the global default database for all models, it is possible to +select a default database on a per-model level. This is done using the +``using`` option in a model's inner ``Meta`` class. When provided this +database becomes the default database for all lookups, saves, and deletes for +this model. It can be overiden on a per-query basis as described below. + Selecting a Database for a ``QuerySet`` ======================================= diff --git a/tests/regressiontests/multiple_database/models.py b/tests/regressiontests/multiple_database/models.py index bdc8acea36..c7ecc30e3d 100644 --- a/tests/regressiontests/multiple_database/models.py +++ b/tests/regressiontests/multiple_database/models.py @@ -1,4 +1,5 @@ -from django.db import models +from django.conf import settings +from django.db import models, DEFAULT_DB_ALIAS class Book(models.Model): title = models.CharField(max_length=100) @@ -9,3 +10,15 @@ class Book(models.Model): class Meta: ordering = ('title',) + +if len(settings.DATABASES) > 1: + article_using = filter(lambda o: o != DEFAULT_DB_ALIAS, settings.DATABASES.keys())[0] + class Article(models.Model): + title = models.CharField(max_length=100) + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('title',) + using = article_using diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index 0cd1253780..de312c5c14 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -6,6 +6,13 @@ from django.test import TestCase from models import Book +try: + # we only have these models if the user is using multi-db, it's safe the + # run the tests without them though. + from models import Article, article_using +except ImportError: + pass + class ConnectionHandlerTestCase(TestCase): def setUp(self): settings.DATABASES['__test_db'] = { @@ -71,3 +78,18 @@ class QueryTestCase(TestCase): months = Book.objects.dates('published', 'month').using(db) self.assertEqual(sorted(o.month for o in months), [5, 12]) + +if len(settings.DATABASES) > 1: + class MetaUsingTestCase(TestCase): + def test_meta_using_queries(self): + a = Article.objects.create(title="Django Rules!") + self.assertEqual(Article.objects.get(title="Django Rules!"), a) + for db in connections: + if db == article_using: + self.assertEqual(Article.objects.using(db).get(title="Django Rules!"), a) + else: + self.assertRaises(Article.DoesNotExist, + lambda: Article.objects.using(db).get(title="Django Rules!")) + a.delete() + self.assertRaises(Article.DoesNotExist, + lambda: Article.objects.get(title="Django Rules!"))