1
0
mirror of https://github.com/django/django.git synced 2025-07-05 02:09:13 +00:00

[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
This commit is contained in:
Alex Gaynor 2009-12-18 01:31:53 +00:00
parent 601db0d2df
commit e95bc7b50d
13 changed files with 78 additions and 50 deletions

2
TODO
View File

@ -7,8 +7,6 @@ Required for v1.2
* Modify the admin interface to support multiple databases (doh). * Modify the admin interface to support multiple databases (doh).
- Document how it is done - Document how it is done
* Make sure we can't get rid of using= arguments everywhere
* Resolve uses of _default_manager * Resolve uses of _default_manager
* django/contrib/databrowse/datastructures.py * django/contrib/databrowse/datastructures.py
* django/contrib/databrowse/fieldchoices.py * django/contrib/databrowse/fieldchoices.py

View File

@ -10,12 +10,8 @@ from django.utils.encoding import smart_str
from django.utils.hashcompat import md5_constructor, sha_constructor from django.utils.hashcompat import md5_constructor, sha_constructor
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
try: UNUSABLE_PASSWORD = '!' # This will never be a valid hash
set
except NameError:
from sets import Set as set # Python 2.3 fallback
def get_hexdigest(algorithm, salt, raw_password): def get_hexdigest(algorithm, salt, raw_password):
""" """
@ -48,8 +44,8 @@ class SiteProfileNotAvailable(Exception):
pass pass
class PermissionManager(models.Manager): class PermissionManager(models.Manager):
def get_by_natural_key(self, codename, app_label, model, using=None): def get_by_natural_key(self, codename, app_label, model):
return self.using(using).get( return self.get(
codename=codename, codename=codename,
content_type=ContentType.objects.get_by_natural_key(app_label, model) content_type=ContentType.objects.get_by_natural_key(app_label, model)
) )
@ -106,7 +102,7 @@ class Group(models.Model):
return self.name return self.name
class UserManager(models.Manager): 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." "Creates and saves a User with the given username, e-mail and password."
now = datetime.datetime.now() now = datetime.datetime.now()
user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, 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) user.set_password(password)
else: else:
user.set_unusable_password() user.set_unusable_password()
user.save(using=using) user.save(using=self.db)
return user 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 = self.create_user(username, email, password)
u.is_staff = True u.is_staff = True
u.is_active = True u.is_active = True
u.is_superuser = True u.is_superuser = True
u.save(using=using) u.save(using=self.db)
return u return u
def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'): def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):

View File

@ -50,9 +50,9 @@ class GenericForeignKey(object):
# using this model # using this model
ContentType = get_model("contenttypes", "contenttype") ContentType = get_model("contenttypes", "contenttype")
if obj: 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: elif id:
return ContentType.objects.get_for_id(id, using=using) return ContentType.objects.db_manager(using).get_for_id(id)
else: else:
# This should never happen. I love comments like this, don't you? # This should never happen. I love comments like this, don't you?
raise Exception("Impossible arguments to GFK.get_content_type!") raise Exception("Impossible arguments to GFK.get_content_type!")
@ -201,7 +201,7 @@ class ReverseGenericRelatedObjectsDescriptor(object):
join_table = qn(self.field.m2m_db_table()), join_table = qn(self.field.m2m_db_table()),
source_col_name = qn(self.field.m2m_column_name()), source_col_name = qn(self.field.m2m_column_name()),
target_col_name = qn(self.field.m2m_reverse_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, content_type_field_name = self.field.content_type_field_name,
object_id_field_name = self.field.object_id_field_name object_id_field_name = self.field.object_id_field_name
) )

View File

@ -8,55 +8,51 @@ class ContentTypeManager(models.Manager):
# This cache is shared by all the get_for_* methods. # This cache is shared by all the get_for_* methods.
_cache = {} _cache = {}
def get_by_natural_key(self, app_label, model, using=None): def get_by_natural_key(self, app_label, model):
db = using or DEFAULT_DB_ALIAS
try: try:
ct = self.__class__._cache[db][(app_label, model)] ct = self.__class__._cache[self.db][(app_label, model)]
except KeyError: except KeyError:
ct = self.using(db).get(app_label=app_label, model=model) ct = self.get(app_label=app_label, model=model)
return ct 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 Returns the ContentType object for a given model, creating the
ContentType if necessary. Lookups are cached so that subsequent lookups ContentType if necessary. Lookups are cached so that subsequent lookups
for the same model don't hit the database. for the same model don't hit the database.
""" """
db = using or DEFAULT_DB_ALIAS
opts = model._meta opts = model._meta
while opts.proxy: while opts.proxy:
model = opts.proxy_for_model model = opts.proxy_for_model
opts = model._meta opts = model._meta
key = (opts.app_label, opts.object_name.lower()) key = (opts.app_label, opts.object_name.lower())
try: try:
ct = self.__class__._cache[db][key] ct = self.__class__._cache[self.db][key]
except KeyError: except KeyError:
# Load or create the ContentType entry. The smart_unicode() is # Load or create the ContentType entry. The smart_unicode() is
# needed around opts.verbose_name_raw because name_raw might be a # needed around opts.verbose_name_raw because name_raw might be a
# django.utils.functional.__proxy__ object. # django.utils.functional.__proxy__ object.
ct, created = self.using(db).get_or_create( ct, created = self.get_or_create(
app_label = opts.app_label, app_label = opts.app_label,
model = opts.object_name.lower(), model = opts.object_name.lower(),
defaults = {'name': smart_unicode(opts.verbose_name_raw)}, defaults = {'name': smart_unicode(opts.verbose_name_raw)},
) )
self._add_to_cache(db, ct) self._add_to_cache(self.db, ct)
return 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 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). (though ContentTypes are obviously not created on-the-fly by get_by_id).
""" """
db = using or DEFAULT_DB_ALIAS
try: try:
ct = self.__class__._cache[db][id] ct = self.__class__._cache[self.db][id]
except KeyError: except KeyError:
# This could raise a DoesNotExist; that's correct behavior and will # This could raise a DoesNotExist; that's correct behavior and will
# make sure that only correct ctypes get stored in the cache dict. # make sure that only correct ctypes get stored in the cache dict.
ct = self.using(db).get(pk=id) ct = self.get(pk=id)
self._add_to_cache(db, ct) self._add_to_cache(self.db, ct)
return ct return ct
def clear_cache(self): def clear_cache(self):

View File

@ -97,7 +97,7 @@ def Deserializer(object_list, **options):
if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
def m2m_convert(value): def m2m_convert(value):
if hasattr(value, '__iter__'): 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: else:
return smart_unicode(field.rel.to._meta.pk.to_python(value)) return smart_unicode(field.rel.to._meta.pk.to_python(value))
else: else:
@ -109,7 +109,7 @@ def Deserializer(object_list, **options):
if field_value is not None: if field_value is not None:
if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): if hasattr(field.rel.to._default_manager, 'get_by_natural_key'):
if hasattr(field_value, '__iter__'): 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) value = getattr(obj, field.rel.field_name)
else: else:
value = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value) value = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)

View File

@ -5,13 +5,9 @@ Requires PyYaml (http://pyyaml.org/), but that's checked for in __init__.
""" """
from StringIO import StringIO from StringIO import StringIO
import decimal
import yaml 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.db import models
from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer from django.core.serializers.python import Deserializer as PythonDeserializer

View File

@ -219,7 +219,7 @@ class Deserializer(base.Deserializer):
if keys: if keys:
# If there are 'natural' subelements, it must be a natural key # If there are 'natural' subelements, it must be a natural key
field_value = [getInnerText(k).strip() for k in keys] 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) obj_pk = getattr(obj, field.rel.field_name)
else: else:
# Otherwise, treat like a normal PK # Otherwise, treat like a normal PK
@ -240,7 +240,7 @@ class Deserializer(base.Deserializer):
if keys: if keys:
# If there are 'natural' subelements, it must be a natural key # If there are 'natural' subelements, it must be a natural key
field_value = [getInnerText(k).strip() for k in keys] 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: else:
# Otherwise, treat like a normal PK value. # Otherwise, treat like a normal PK value.
obj_pk = field.rel.to._meta.pk.to_python(n.getAttribute('pk')) obj_pk = field.rel.to._meta.pk.to_python(n.getAttribute('pk'))

View File

@ -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.query import QuerySet, EmptyQuerySet, insert_query
from django.db.models import signals from django.db.models import signals
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
@ -49,6 +51,7 @@ class Manager(object):
self._set_creation_counter() self._set_creation_counter()
self.model = None self.model = None
self._inherited = False self._inherited = False
self._db = None
def contribute_to_class(self, model, name): def contribute_to_class(self, model, name):
# TODO: Use weakref because of possible memory leak / circular reference. # TODO: Use weakref because of possible memory leak / circular reference.
@ -84,6 +87,15 @@ class Manager(object):
mgr._inherited = True mgr._inherited = True
return mgr 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 # # PROXIES TO QUERYSET #
####################### #######################
@ -95,7 +107,10 @@ class Manager(object):
"""Returns a new QuerySet object. Subclasses can override this method """Returns a new QuerySet object. Subclasses can override this method
to easily customize the behavior of the Manager. 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): def none(self):
return self.get_empty_query_set() return self.get_empty_query_set()
@ -174,7 +189,7 @@ class Manager(object):
def using(self, *args, **kwargs): def using(self, *args, **kwargs):
return self.get_query_set().using(*args, **kwargs) return self.get_query_set().using(*args, **kwargs)
def exists(self, *args, **kwargs): def exists(self, *args, **kwargs):
return self.get_query_set().exists(*args, **kwargs) return self.get_query_set().exists(*args, **kwargs)

View File

@ -109,3 +109,30 @@ the ``'legacy_users'`` database to the ``'new_users'`` database you might do::
>>> user_obj.save(using='new_users') >>> user_obj.save(using='new_users')
>>> user_obj.delete(using='legacy_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

View File

@ -57,8 +57,8 @@ class Tag(models.Model):
self.tagged, self.name) self.tagged, self.name)
class PersonManager(models.Manager): class PersonManager(models.Manager):
def get_by_natural_key(self, name, using=None): def get_by_natural_key(self, name):
return self.using(using).get(name=name) return self.get(name=name)
class Person(models.Model): class Person(models.Model):
objects = PersonManager() objects = PersonManager()

View File

@ -83,8 +83,8 @@ class WidgetProxy(Widget):
# Check for forward references in FKs and M2Ms with natural keys # Check for forward references in FKs and M2Ms with natural keys
class TestManager(models.Manager): class TestManager(models.Manager):
def get_by_natural_key(self, key, using=None): def get_by_natural_key(self, key):
return self.using(using).get(name=key) return self.get(name=key)
class Store(models.Model): class Store(models.Model):
objects = TestManager() objects = TestManager()

View File

@ -17,8 +17,8 @@ class Review(models.Model):
ordering = ('source',) ordering = ('source',)
class PersonManager(models.Manager): class PersonManager(models.Manager):
def get_by_natural_key(self, name, using=None): def get_by_natural_key(self, name):
return self.using(using).get(name=name) return self.get(name=name)
class Person(models.Model): class Person(models.Model):
objects = PersonManager() objects = PersonManager()

View File

@ -632,7 +632,7 @@ class UserProfileTestCase(TestCase):
def test_user_profiles(self): def test_user_profiles(self):
alice = User.objects.create_user('alice', 'alice@example.com') 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 = UserProfile(user=alice, flavor='chocolate')
alice_profile.save() alice_profile.save()