diff options
author | Alex Gaynor <alex.gaynor@gmail.com> | 2009-12-18 01:31:53 +0000 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2009-12-18 01:31:53 +0000 |
commit | e95bc7b50de02c0f369d66f89a25a75372e4872c (patch) | |
tree | c361e9f8c05094759b917c104158e66aefa1b21f | |
parent | 601db0d2df9dfdf639b2a32f7cac41a31e6329dc (diff) | |
download | django-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-- | TODO | 2 | ||||
-rw-r--r-- | django/contrib/auth/models.py | 18 | ||||
-rw-r--r-- | django/contrib/contenttypes/generic.py | 6 | ||||
-rw-r--r-- | django/contrib/contenttypes/models.py | 26 | ||||
-rw-r--r-- | django/core/serializers/python.py | 4 | ||||
-rw-r--r-- | django/core/serializers/pyyaml.py | 6 | ||||
-rw-r--r-- | django/core/serializers/xml_serializer.py | 4 | ||||
-rw-r--r-- | django/db/models/manager.py | 21 | ||||
-rw-r--r-- | docs/topics/db/multi-db.txt | 27 | ||||
-rw-r--r-- | tests/modeltests/fixtures/models.py | 4 | ||||
-rw-r--r-- | tests/regressiontests/fixtures_regress/models.py | 4 | ||||
-rw-r--r-- | tests/regressiontests/multiple_database/models.py | 4 | ||||
-rw-r--r-- | tests/regressiontests/multiple_database/tests.py | 2 |
13 files changed, 78 insertions, 50 deletions
@@ -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() |