summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2009-12-18 01:31:53 +0000
committerAlex Gaynor <alex.gaynor@gmail.com>2009-12-18 01:31:53 +0000
commite95bc7b50de02c0f369d66f89a25a75372e4872c (patch)
treec361e9f8c05094759b917c104158e66aefa1b21f
parent601db0d2df9dfdf639b2a32f7cac41a31e6329dc (diff)
downloaddjango-e95bc7b50de02c0f369d66f89a25a75372e4872c.tar.gz
[soc2009/multidb] Cleaned up the interaction between managers and the using() method by the addition of a db_manager() method.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11908 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--TODO2
-rw-r--r--django/contrib/auth/models.py18
-rw-r--r--django/contrib/contenttypes/generic.py6
-rw-r--r--django/contrib/contenttypes/models.py26
-rw-r--r--django/core/serializers/python.py4
-rw-r--r--django/core/serializers/pyyaml.py6
-rw-r--r--django/core/serializers/xml_serializer.py4
-rw-r--r--django/db/models/manager.py21
-rw-r--r--docs/topics/db/multi-db.txt27
-rw-r--r--tests/modeltests/fixtures/models.py4
-rw-r--r--tests/regressiontests/fixtures_regress/models.py4
-rw-r--r--tests/regressiontests/multiple_database/models.py4
-rw-r--r--tests/regressiontests/multiple_database/tests.py2
13 files changed, 78 insertions, 50 deletions
diff --git a/TODO b/TODO
index 53294b7fd7..23da25a1e3 100644
--- a/TODO
+++ b/TODO
@@ -7,8 +7,6 @@ Required for v1.2
* Modify the admin interface to support multiple databases (doh).
- Document how it is done
- * Make sure we can't get rid of using= arguments everywhere
-
* Resolve uses of _default_manager
* django/contrib/databrowse/datastructures.py
* django/contrib/databrowse/fieldchoices.py
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index be12dc6938..8148d8a992 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -10,12 +10,8 @@ from django.utils.encoding import smart_str
from django.utils.hashcompat import md5_constructor, sha_constructor
from django.utils.translation import ugettext_lazy as _
-UNUSABLE_PASSWORD = '!' # This will never be a valid hash
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
+UNUSABLE_PASSWORD = '!' # This will never be a valid hash
def get_hexdigest(algorithm, salt, raw_password):
"""
@@ -48,8 +44,8 @@ class SiteProfileNotAvailable(Exception):
pass
class PermissionManager(models.Manager):
- def get_by_natural_key(self, codename, app_label, model, using=None):
- return self.using(using).get(
+ def get_by_natural_key(self, codename, app_label, model):
+ return self.get(
codename=codename,
content_type=ContentType.objects.get_by_natural_key(app_label, model)
)
@@ -106,7 +102,7 @@ class Group(models.Model):
return self.name
class UserManager(models.Manager):
- def create_user(self, username, email, password=None, using=None):
+ def create_user(self, username, email, password=None):
"Creates and saves a User with the given username, e-mail and password."
now = datetime.datetime.now()
user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now)
@@ -114,15 +110,15 @@ class UserManager(models.Manager):
user.set_password(password)
else:
user.set_unusable_password()
- user.save(using=using)
+ user.save(using=self.db)
return user
- def create_superuser(self, username, email, password, using=None):
+ def create_superuser(self, username, email, password):
u = self.create_user(username, email, password)
u.is_staff = True
u.is_active = True
u.is_superuser = True
- u.save(using=using)
+ u.save(using=self.db)
return u
def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index 0ed8ea5e03..8f3db698e7 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -50,9 +50,9 @@ class GenericForeignKey(object):
# using this model
ContentType = get_model("contenttypes", "contenttype")
if obj:
- return ContentType.objects.get_for_model(obj, using=obj._state.db)
+ return ContentType.objects.db_manager(obj._state.db).get_for_model(obj)
elif id:
- return ContentType.objects.get_for_id(id, using=using)
+ return ContentType.objects.db_manager(using).get_for_id(id)
else:
# This should never happen. I love comments like this, don't you?
raise Exception("Impossible arguments to GFK.get_content_type!")
@@ -201,7 +201,7 @@ class ReverseGenericRelatedObjectsDescriptor(object):
join_table = qn(self.field.m2m_db_table()),
source_col_name = qn(self.field.m2m_column_name()),
target_col_name = qn(self.field.m2m_reverse_name()),
- content_type = ContentType.objects.get_for_model(instance, using=instance._state.db),
+ content_type = ContentType.objects.db_manager(instance._state.db).get_for_model(instance),
content_type_field_name = self.field.content_type_field_name,
object_id_field_name = self.field.object_id_field_name
)
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index 0e572742e1..ed64d1ea0c 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -8,55 +8,51 @@ class ContentTypeManager(models.Manager):
# This cache is shared by all the get_for_* methods.
_cache = {}
- def get_by_natural_key(self, app_label, model, using=None):
- db = using or DEFAULT_DB_ALIAS
+ def get_by_natural_key(self, app_label, model):
try:
- ct = self.__class__._cache[db][(app_label, model)]
+ ct = self.__class__._cache[self.db][(app_label, model)]
except KeyError:
- ct = self.using(db).get(app_label=app_label, model=model)
+ ct = self.get(app_label=app_label, model=model)
return ct
- def get_for_model(self, model, using=None):
+ def get_for_model(self, model):
"""
Returns the ContentType object for a given model, creating the
ContentType if necessary. Lookups are cached so that subsequent lookups
for the same model don't hit the database.
"""
- db = using or DEFAULT_DB_ALIAS
opts = model._meta
while opts.proxy:
model = opts.proxy_for_model
opts = model._meta
key = (opts.app_label, opts.object_name.lower())
try:
- ct = self.__class__._cache[db][key]
+ ct = self.__class__._cache[self.db][key]
except KeyError:
# Load or create the ContentType entry. The smart_unicode() is
# needed around opts.verbose_name_raw because name_raw might be a
# django.utils.functional.__proxy__ object.
- ct, created = self.using(db).get_or_create(
+ ct, created = self.get_or_create(
app_label = opts.app_label,
model = opts.object_name.lower(),
defaults = {'name': smart_unicode(opts.verbose_name_raw)},
)
- self._add_to_cache(db, ct)
+ self._add_to_cache(self.db, ct)
return ct
- def get_for_id(self, id, using=None):
+ def get_for_id(self, id):
"""
Lookup a ContentType by ID. Uses the same shared cache as get_for_model
(though ContentTypes are obviously not created on-the-fly by get_by_id).
"""
- db = using or DEFAULT_DB_ALIAS
try:
- ct = self.__class__._cache[db][id]
-
+ ct = self.__class__._cache[self.db][id]
except KeyError:
# This could raise a DoesNotExist; that's correct behavior and will
# make sure that only correct ctypes get stored in the cache dict.
- ct = self.using(db).get(pk=id)
- self._add_to_cache(db, ct)
+ ct = self.get(pk=id)
+ self._add_to_cache(self.db, ct)
return ct
def clear_cache(self):
diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
index b29ae15c3c..3defb6aa27 100644
--- a/django/core/serializers/python.py
+++ b/django/core/serializers/python.py
@@ -97,7 +97,7 @@ def Deserializer(object_list, **options):
if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
def m2m_convert(value):
if hasattr(value, '__iter__'):
- return field.rel.to._default_manager.get_by_natural_key(*value, **{'using':db}).pk
+ return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk
else:
return smart_unicode(field.rel.to._meta.pk.to_python(value))
else:
@@ -109,7 +109,7 @@ def Deserializer(object_list, **options):
if field_value is not None:
if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
if hasattr(field_value, '__iter__'):
- obj = field.rel.to._default_manager.get_by_natural_key(*field_value, **{'using':db})
+ obj = field.rel.to._default_manager.db_manager(db).get_by_natural_key(*field_value)
value = getattr(obj, field.rel.field_name)
else:
value = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py
index e136c88952..2ca68fe442 100644
--- a/django/core/serializers/pyyaml.py
+++ b/django/core/serializers/pyyaml.py
@@ -5,13 +5,9 @@ Requires PyYaml (http://pyyaml.org/), but that's checked for in __init__.
"""
from StringIO import StringIO
+import decimal
import yaml
-try:
- import decimal
-except ImportError:
- from django.utils import _decimal as decimal # Python 2.3 fallback
-
from django.db import models
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
index 0b808801f7..b0d8d49397 100644
--- a/django/core/serializers/xml_serializer.py
+++ b/django/core/serializers/xml_serializer.py
@@ -219,7 +219,7 @@ class Deserializer(base.Deserializer):
if keys:
# If there are 'natural' subelements, it must be a natural key
field_value = [getInnerText(k).strip() for k in keys]
- obj = field.rel.to._default_manager.get_by_natural_key(*field_value, **{'using':self.db})
+ obj = field.rel.to._default_manager.db_manager(self.db).get_by_natural_key(*field_value)
obj_pk = getattr(obj, field.rel.field_name)
else:
# Otherwise, treat like a normal PK
@@ -240,7 +240,7 @@ class Deserializer(base.Deserializer):
if keys:
# If there are 'natural' subelements, it must be a natural key
field_value = [getInnerText(k).strip() for k in keys]
- obj_pk = field.rel.to._default_manager.get_by_natural_key(*field_value, **{'using':self.db}).pk
+ obj_pk = field.rel.to._default_manager.db_manager(self.db).get_by_natural_key(*field_value).pk
else:
# Otherwise, treat like a normal PK value.
obj_pk = field.rel.to._meta.pk.to_python(n.getAttribute('pk'))
diff --git a/django/db/models/manager.py b/django/db/models/manager.py
index baaa1c9cd9..e230e35b8d 100644
--- a/django/db/models/manager.py
+++ b/django/db/models/manager.py
@@ -1,4 +1,6 @@
-import django.utils.copycompat as copy
+from django.utils import copycompat as copy
+
+from django.db import DEFAULT_DB_ALIAS
from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
from django.db.models import signals
from django.db.models.fields import FieldDoesNotExist
@@ -49,6 +51,7 @@ class Manager(object):
self._set_creation_counter()
self.model = None
self._inherited = False
+ self._db = None
def contribute_to_class(self, model, name):
# TODO: Use weakref because of possible memory leak / circular reference.
@@ -84,6 +87,15 @@ class Manager(object):
mgr._inherited = True
return mgr
+ def db_manager(self, alias):
+ obj = copy.copy(self)
+ obj._db = alias
+ return obj
+
+ @property
+ def db(self):
+ return self._db or DEFAULT_DB_ALIAS
+
#######################
# PROXIES TO QUERYSET #
#######################
@@ -95,7 +107,10 @@ class Manager(object):
"""Returns a new QuerySet object. Subclasses can override this method
to easily customize the behavior of the Manager.
"""
- return QuerySet(self.model)
+ qs = QuerySet(self.model)
+ if self._db is not None:
+ qs = qs.using(self._db)
+ return qs
def none(self):
return self.get_empty_query_set()
@@ -174,7 +189,7 @@ class Manager(object):
def using(self, *args, **kwargs):
return self.get_query_set().using(*args, **kwargs)
-
+
def exists(self, *args, **kwargs):
return self.get_query_set().exists(*args, **kwargs)
diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt
index f3d2b9af95..15c1ba2aca 100644
--- a/docs/topics/db/multi-db.txt
+++ b/docs/topics/db/multi-db.txt
@@ -109,3 +109,30 @@ the ``'legacy_users'`` database to the ``'new_users'`` database you might do::
>>> user_obj.save(using='new_users')
>>> user_obj.delete(using='legacy_users')
+
+
+Using ``Managers`` with Multiple Databases
+==========================================
+
+When you call ``using()`` Django returns a ``QuerySet`` that will be evaluated
+against that database. However, sometimes you want to chain ``using()``
+together with a cusotm manager method that doesn't return a ``QuerySet``,
+such as the ``get_by_natural_key`` method. To solve this issue you can use the
+``db_manager()`` method on a manager. This method returns a copy of the
+*manager* bound to that specific database. This let's you do things like::
+
+ >>> Book.objects.db("other").get_by_natural_key(...)
+
+If you are overiding ``get_query_set()`` on your manager you must be sure to
+either, a) call the method on the parent (using ``super()``), or b) do the
+appropriate handling of the ``_db`` attribute on the manager. For example if
+you wanted to return a custom ``QuerySet`` class from the ``get_query_set``
+method you could do this::
+
+ class MyManager(models.Manager):
+ ...
+ def get_query_set(self):
+ qs = CustomQuerySet(self.model)
+ if self._db is not None:
+ qs = qs.using(self._db)
+ return qs
diff --git a/tests/modeltests/fixtures/models.py b/tests/modeltests/fixtures/models.py
index 79b93c50f9..7fcf89d561 100644
--- a/tests/modeltests/fixtures/models.py
+++ b/tests/modeltests/fixtures/models.py
@@ -57,8 +57,8 @@ class Tag(models.Model):
self.tagged, self.name)
class PersonManager(models.Manager):
- def get_by_natural_key(self, name, using=None):
- return self.using(using).get(name=name)
+ def get_by_natural_key(self, name):
+ return self.get(name=name)
class Person(models.Model):
objects = PersonManager()
diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py
index 632234256f..4294ffb293 100644
--- a/tests/regressiontests/fixtures_regress/models.py
+++ b/tests/regressiontests/fixtures_regress/models.py
@@ -83,8 +83,8 @@ class WidgetProxy(Widget):
# Check for forward references in FKs and M2Ms with natural keys
class TestManager(models.Manager):
- def get_by_natural_key(self, key, using=None):
- return self.using(using).get(name=key)
+ def get_by_natural_key(self, key):
+ return self.get(name=key)
class Store(models.Model):
objects = TestManager()
diff --git a/tests/regressiontests/multiple_database/models.py b/tests/regressiontests/multiple_database/models.py
index 8f5339854b..33cb7fb745 100644
--- a/tests/regressiontests/multiple_database/models.py
+++ b/tests/regressiontests/multiple_database/models.py
@@ -17,8 +17,8 @@ class Review(models.Model):
ordering = ('source',)
class PersonManager(models.Manager):
- def get_by_natural_key(self, name, using=None):
- return self.using(using).get(name=name)
+ def get_by_natural_key(self, name):
+ return self.get(name=name)
class Person(models.Model):
objects = PersonManager()
diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py
index 4030d74540..9bdc769ce4 100644
--- a/tests/regressiontests/multiple_database/tests.py
+++ b/tests/regressiontests/multiple_database/tests.py
@@ -632,7 +632,7 @@ class UserProfileTestCase(TestCase):
def test_user_profiles(self):
alice = User.objects.create_user('alice', 'alice@example.com')
- bob = User.objects.create_user('bob', 'bob@example.com', using='other')
+ bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com')
alice_profile = UserProfile(user=alice, flavor='chocolate')
alice_profile.save()