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:
parent
601db0d2df
commit
e95bc7b50d
2
TODO
2
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
|
||||
|
@ -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'):
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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'))
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user