1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59: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).
- 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

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.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'):

View File

@ -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
)

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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'))

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 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)

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.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)
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()

View File

@ -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()

View File

@ -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()

View File

@ -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()