mirror of
https://github.com/django/django.git
synced 2025-10-24 14:16:09 +00:00
Thanks to the many people that contributed to the development and review of this patch, including (but not limited to) Jacob Kaplan-Moss, Anssi Kääriäinen, Ramiro Morales, Preston Holmes, Josh Ourisman, Thomas Sutton, and Roger Barnes, as well as the many, many people who have contributed to the design discussion around this ticket over many years. Squashed commit of the following: commitd84749a0f0Merge:531e7717c11b1aAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Wed Sep 26 18:37:04 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit531e7715daMerge:29d1abb1f84b04Author: Russell Keith-Magee <russell@keith-magee.com> Date: Wed Sep 26 07:09:23 2012 +0800 Merged recent trunk changes. commit29d1abbe35Merge:8a527dd54c81a1Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 24 07:49:46 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit8a527dda13Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 24 07:48:05 2012 +0800 Ensure sequences are reset correctly in the presence of swapped models. commite2b6e22f29Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 23 17:53:05 2012 +0800 Modifications to the handling and docs for auth forms. commit98aba856b5Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 23 15:28:57 2012 +0800 Improved error handling and docs for get_user_model() commit0229209c84Merge:6494bf98599f64Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 23 14:50:11 2012 +0800 Merged recent Django trunk changes. commit6494bf91f2Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 17 21:38:44 2012 +0800 Improved validation of swappable model settings. commit5a04cde342Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 17 07:15:14 2012 +0800 Removed some unused imports. commitffd535e413Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 20:31:28 2012 +0800 Corrected attribute access on for get_by_natural_key commit913e1ac84cAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 20:12:34 2012 +0800 Added test for proxy model safeguards on swappable models. commit280bf19e94Merge:dbb3900935a863Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 18:16:49 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commitdbb3900775Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 18:09:27 2012 +0800 Fixes for Python 3 compatibility. commitdfd72131d8Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 15:54:30 2012 +0800 Added protection against proxying swapped models. commitabcb027190Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 15:11:10 2012 +0800 Cleanup and documentation of AbstractUser base class. commita9491a8776Merge:fd8bb4e08bcb4aAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 14:46:49 2012 +0800 Merge commit '08bcb4aec1ed154cefc631b8510ee13e9af0c19d' into t3011 commitfd8bb4e3e4Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 14:20:14 2012 +0800 Documentation improvements coming from community review. commitb550a6d06dAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 13:52:47 2012 +0800 Refactored skipIfCustomUser into the contrib.auth tests. commit52a02f1110Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 13:46:10 2012 +0800 Refactored common 'get' pattern into manager method. commitb441a6bbc7Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 13:41:33 2012 +0800 Added note about backwards incompatible change to admin login messages. commit08bcb4aec1Author: Anssi Kääriäinen <akaariai@gmail.com> Date: Sat Sep 15 18:30:33 2012 +0300 Splitted User to AbstractUser and User commitd9f5e5addbAuthor: Anssi Kääriäinen <akaariai@gmail.com> Date: Sat Sep 15 18:30:02 2012 +0300 Reworked REQUIRED_FIELDS + create_user() interaction commit579f152e4aMerge:918497293e6733Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 20:18:37 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit918497218cAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 20:18:19 2012 +0800 Deprecate AUTH_PROFILE_MODULE and get_profile(). commit334cdfc1bbAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 20:00:12 2012 +0800 Added release notes for new swappable User feature. commit5d7bb22e8dAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 19:59:49 2012 +0800 Ensure swapped models can't be queried. commit57ac6e3d32Merge:f2ec915abfba3bAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 14:31:54 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commitf2ec915b20Merge:19526565e99a3dAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 9 08:29:51 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit19526563b5Merge:2c5e833c4aa26aAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 9 08:22:26 2012 +0800 Merge recent changes from master. commit2c5e833a30Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 9 07:53:46 2012 +0800 Corrected admin_views tests following removal of the email fallback on admin logins. commit20d1892491Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 9 01:00:37 2012 +0800 Added conditional skips for all tests dependent on the default User model commit40ea8b8882Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 8 23:47:02 2012 +0800 Added documentation for REQUIRED_FIELDS in custom auth. commite6aaf65970Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 8 23:20:02 2012 +0800 Added first draft of custom User docs. Thanks to Greg Turner for the initial text. commit75118bd242Author: Thomas Sutton <me@thomas-sutton.id.au> Date: Mon Aug 20 11:17:26 2012 +0800 Admin app should not allow username discovery The admin app login form should not allow users to discover the username associated with an email address. commitd088b3af58Author: Thomas Sutton <me@thomas-sutton.id.au> Date: Mon Aug 20 10:32:13 2012 +0800 Admin app login form should use swapped user model commit7e82e83d67Merge:e29c01039aa890Author: Russell Keith-Magee <russell@keith-magee.com> Date: Fri Sep 7 23:45:03 2012 +0800 Merged master changes. commite29c010bebMerge:8e3fd7030bdf22Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Aug 20 13:12:57 2012 +0800 Merge remote-tracking branch 'django/master' into t3011 commit8e3fd703d0Merge:507bb5026e0ba0Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Aug 20 13:09:09 2012 +0800 Merged recent changes from trunk. commit507bb50a92Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Jun 4 20:41:37 2012 +0800 Modified auth app so that login with alternate auth app is possible. commitdabe362836Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Jun 4 20:10:51 2012 +0800 Modified auth management commands to handle custom user definitions. commit7cc0baf89dAuthor: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Jun 4 14:17:28 2012 +0800 Added model Meta option for swappable models, and made auth.User a swappable model
363 lines
13 KiB
Python
363 lines
13 KiB
Python
from __future__ import absolute_import, unicode_literals
|
|
import copy
|
|
|
|
from django.conf import settings
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core import management
|
|
from django.core.exceptions import FieldError
|
|
from django.db import models, DEFAULT_DB_ALIAS
|
|
from django.db.models import signals
|
|
from django.db.models.loading import cache
|
|
from django.test import TestCase
|
|
|
|
|
|
from .models import (MyPerson, Person, StatusPerson, LowerStatusPerson,
|
|
MyPersonProxy, Abstract, OtherPerson, User, UserProxy, UserProxyProxy,
|
|
Country, State, StateProxy, TrackerUser, BaseUser, Bug, ProxyTrackerUser,
|
|
Improvement, ProxyProxyBug, ProxyBug, ProxyImprovement)
|
|
|
|
|
|
class ProxyModelTests(TestCase):
|
|
def test_same_manager_queries(self):
|
|
"""
|
|
The MyPerson model should be generating the same database queries as
|
|
the Person model (when the same manager is used in each case).
|
|
"""
|
|
my_person_sql = MyPerson.other.all().query.get_compiler(
|
|
DEFAULT_DB_ALIAS).as_sql()
|
|
person_sql = Person.objects.order_by("name").query.get_compiler(
|
|
DEFAULT_DB_ALIAS).as_sql()
|
|
self.assertEqual(my_person_sql, person_sql)
|
|
|
|
def test_inheretance_new_table(self):
|
|
"""
|
|
The StatusPerson models should have its own table (it's using ORM-level
|
|
inheritance).
|
|
"""
|
|
sp_sql = StatusPerson.objects.all().query.get_compiler(
|
|
DEFAULT_DB_ALIAS).as_sql()
|
|
p_sql = Person.objects.all().query.get_compiler(
|
|
DEFAULT_DB_ALIAS).as_sql()
|
|
self.assertNotEqual(sp_sql, p_sql)
|
|
|
|
def test_basic_proxy(self):
|
|
"""
|
|
Creating a Person makes them accessible through the MyPerson proxy.
|
|
"""
|
|
person = Person.objects.create(name="Foo McBar")
|
|
self.assertEqual(len(Person.objects.all()), 1)
|
|
self.assertEqual(len(MyPerson.objects.all()), 1)
|
|
self.assertEqual(MyPerson.objects.get(name="Foo McBar").id, person.id)
|
|
self.assertFalse(MyPerson.objects.get(id=person.id).has_special_name())
|
|
|
|
def test_no_proxy(self):
|
|
"""
|
|
Person is not proxied by StatusPerson subclass.
|
|
"""
|
|
Person.objects.create(name="Foo McBar")
|
|
self.assertEqual(list(StatusPerson.objects.all()), [])
|
|
|
|
def test_basic_proxy_reverse(self):
|
|
"""
|
|
A new MyPerson also shows up as a standard Person.
|
|
"""
|
|
MyPerson.objects.create(name="Bazza del Frob")
|
|
self.assertEqual(len(MyPerson.objects.all()), 1)
|
|
self.assertEqual(len(Person.objects.all()), 1)
|
|
|
|
LowerStatusPerson.objects.create(status="low", name="homer")
|
|
lsps = [lsp.name for lsp in LowerStatusPerson.objects.all()]
|
|
self.assertEqual(lsps, ["homer"])
|
|
|
|
def test_correct_type_proxy_of_proxy(self):
|
|
"""
|
|
Correct type when querying a proxy of proxy
|
|
"""
|
|
Person.objects.create(name="Foo McBar")
|
|
MyPerson.objects.create(name="Bazza del Frob")
|
|
LowerStatusPerson.objects.create(status="low", name="homer")
|
|
pp = sorted([mpp.name for mpp in MyPersonProxy.objects.all()])
|
|
self.assertEqual(pp, ['Bazza del Frob', 'Foo McBar', 'homer'])
|
|
|
|
def test_proxy_included_in_ancestors(self):
|
|
"""
|
|
Proxy models are included in the ancestors for a model's DoesNotExist
|
|
and MultipleObjectsReturned
|
|
"""
|
|
Person.objects.create(name="Foo McBar")
|
|
MyPerson.objects.create(name="Bazza del Frob")
|
|
LowerStatusPerson.objects.create(status="low", name="homer")
|
|
max_id = Person.objects.aggregate(max_id=models.Max('id'))['max_id']
|
|
|
|
self.assertRaises(Person.DoesNotExist,
|
|
MyPersonProxy.objects.get,
|
|
name='Zathras'
|
|
)
|
|
self.assertRaises(Person.MultipleObjectsReturned,
|
|
MyPersonProxy.objects.get,
|
|
id__lt=max_id + 1
|
|
)
|
|
self.assertRaises(Person.DoesNotExist,
|
|
StatusPerson.objects.get,
|
|
name='Zathras'
|
|
)
|
|
|
|
sp1 = StatusPerson.objects.create(name='Bazza Jr.')
|
|
sp2 = StatusPerson.objects.create(name='Foo Jr.')
|
|
max_id = Person.objects.aggregate(max_id=models.Max('id'))['max_id']
|
|
|
|
self.assertRaises(Person.MultipleObjectsReturned,
|
|
StatusPerson.objects.get,
|
|
id__lt=max_id + 1
|
|
)
|
|
|
|
def test_abc(self):
|
|
"""
|
|
All base classes must be non-abstract
|
|
"""
|
|
def build_abc():
|
|
class NoAbstract(Abstract):
|
|
class Meta:
|
|
proxy = True
|
|
self.assertRaises(TypeError, build_abc)
|
|
|
|
def test_no_cbc(self):
|
|
"""
|
|
The proxy must actually have one concrete base class
|
|
"""
|
|
def build_no_cbc():
|
|
class TooManyBases(Person, Abstract):
|
|
class Meta:
|
|
proxy = True
|
|
self.assertRaises(TypeError, build_no_cbc)
|
|
|
|
def test_no_base_classes(self):
|
|
def build_no_base_classes():
|
|
class NoBaseClasses(models.Model):
|
|
class Meta:
|
|
proxy = True
|
|
self.assertRaises(TypeError, build_no_base_classes)
|
|
|
|
def test_new_fields(self):
|
|
def build_new_fields():
|
|
class NoNewFields(Person):
|
|
newfield = models.BooleanField()
|
|
|
|
class Meta:
|
|
proxy = True
|
|
self.assertRaises(FieldError, build_new_fields)
|
|
|
|
def test_swappable(self):
|
|
try:
|
|
# This test adds dummy applications to the app cache. These
|
|
# need to be removed in order to prevent bad interactions
|
|
# with the flush operation in other tests.
|
|
old_app_models = copy.deepcopy(cache.app_models)
|
|
old_app_store = copy.deepcopy(cache.app_store)
|
|
|
|
settings.TEST_SWAPPABLE_MODEL = 'proxy_models.AlternateModel'
|
|
|
|
class SwappableModel(models.Model):
|
|
|
|
class Meta:
|
|
swappable = 'TEST_SWAPPABLE_MODEL'
|
|
|
|
class AlternateModel(models.Model):
|
|
pass
|
|
|
|
# You can't proxy a swapped model
|
|
with self.assertRaises(TypeError):
|
|
class ProxyModel(SwappableModel):
|
|
|
|
class Meta:
|
|
proxy = True
|
|
finally:
|
|
del settings.TEST_SWAPPABLE_MODEL
|
|
cache.app_models = old_app_models
|
|
cache.app_store = old_app_store
|
|
|
|
def test_myperson_manager(self):
|
|
Person.objects.create(name="fred")
|
|
Person.objects.create(name="wilma")
|
|
Person.objects.create(name="barney")
|
|
|
|
resp = [p.name for p in MyPerson.objects.all()]
|
|
self.assertEqual(resp, ['barney', 'fred'])
|
|
|
|
resp = [p.name for p in MyPerson._default_manager.all()]
|
|
self.assertEqual(resp, ['barney', 'fred'])
|
|
|
|
def test_otherperson_manager(self):
|
|
Person.objects.create(name="fred")
|
|
Person.objects.create(name="wilma")
|
|
Person.objects.create(name="barney")
|
|
|
|
resp = [p.name for p in OtherPerson.objects.all()]
|
|
self.assertEqual(resp, ['barney', 'wilma'])
|
|
|
|
resp = [p.name for p in OtherPerson.excluder.all()]
|
|
self.assertEqual(resp, ['barney', 'fred'])
|
|
|
|
resp = [p.name for p in OtherPerson._default_manager.all()]
|
|
self.assertEqual(resp, ['barney', 'wilma'])
|
|
|
|
def test_permissions_created(self):
|
|
from django.contrib.auth.models import Permission
|
|
try:
|
|
Permission.objects.get(name="May display users information")
|
|
except Permission.DoesNotExist:
|
|
self.fail("The permission 'May display users information' has not been created")
|
|
|
|
def test_proxy_model_signals(self):
|
|
"""
|
|
Test save signals for proxy models
|
|
"""
|
|
output = []
|
|
|
|
def make_handler(model, event):
|
|
def _handler(*args, **kwargs):
|
|
output.append('%s %s save' % (model, event))
|
|
return _handler
|
|
|
|
h1 = make_handler('MyPerson', 'pre')
|
|
h2 = make_handler('MyPerson', 'post')
|
|
h3 = make_handler('Person', 'pre')
|
|
h4 = make_handler('Person', 'post')
|
|
|
|
signals.pre_save.connect(h1, sender=MyPerson)
|
|
signals.post_save.connect(h2, sender=MyPerson)
|
|
signals.pre_save.connect(h3, sender=Person)
|
|
signals.post_save.connect(h4, sender=Person)
|
|
|
|
dino = MyPerson.objects.create(name="dino")
|
|
self.assertEqual(output, [
|
|
'MyPerson pre save',
|
|
'MyPerson post save'
|
|
])
|
|
|
|
output = []
|
|
|
|
h5 = make_handler('MyPersonProxy', 'pre')
|
|
h6 = make_handler('MyPersonProxy', 'post')
|
|
|
|
signals.pre_save.connect(h5, sender=MyPersonProxy)
|
|
signals.post_save.connect(h6, sender=MyPersonProxy)
|
|
|
|
dino = MyPersonProxy.objects.create(name="pebbles")
|
|
|
|
self.assertEqual(output, [
|
|
'MyPersonProxy pre save',
|
|
'MyPersonProxy post save'
|
|
])
|
|
|
|
signals.pre_save.disconnect(h1, sender=MyPerson)
|
|
signals.post_save.disconnect(h2, sender=MyPerson)
|
|
signals.pre_save.disconnect(h3, sender=Person)
|
|
signals.post_save.disconnect(h4, sender=Person)
|
|
signals.pre_save.disconnect(h5, sender=MyPersonProxy)
|
|
signals.post_save.disconnect(h6, sender=MyPersonProxy)
|
|
|
|
def test_content_type(self):
|
|
ctype = ContentType.objects.get_for_model
|
|
self.assertTrue(ctype(Person) is ctype(OtherPerson))
|
|
|
|
def test_user_userproxy_userproxyproxy(self):
|
|
User.objects.create(name='Bruce')
|
|
|
|
resp = [u.name for u in User.objects.all()]
|
|
self.assertEqual(resp, ['Bruce'])
|
|
|
|
resp = [u.name for u in UserProxy.objects.all()]
|
|
self.assertEqual(resp, ['Bruce'])
|
|
|
|
resp = [u.name for u in UserProxyProxy.objects.all()]
|
|
self.assertEqual(resp, ['Bruce'])
|
|
|
|
def test_proxy_for_model(self):
|
|
self.assertEqual(UserProxy, UserProxyProxy._meta.proxy_for_model)
|
|
|
|
def test_concrete_model(self):
|
|
self.assertEqual(User, UserProxyProxy._meta.concrete_model)
|
|
|
|
def test_proxy_delete(self):
|
|
"""
|
|
Proxy objects can be deleted
|
|
"""
|
|
User.objects.create(name='Bruce')
|
|
u2 = UserProxy.objects.create(name='George')
|
|
|
|
resp = [u.name for u in UserProxy.objects.all()]
|
|
self.assertEqual(resp, ['Bruce', 'George'])
|
|
|
|
u2.delete()
|
|
|
|
resp = [u.name for u in UserProxy.objects.all()]
|
|
self.assertEqual(resp, ['Bruce'])
|
|
|
|
def test_select_related(self):
|
|
"""
|
|
We can still use `select_related()` to include related models in our
|
|
querysets.
|
|
"""
|
|
country = Country.objects.create(name='Australia')
|
|
state = State.objects.create(name='New South Wales', country=country)
|
|
|
|
resp = [s.name for s in State.objects.select_related()]
|
|
self.assertEqual(resp, ['New South Wales'])
|
|
|
|
resp = [s.name for s in StateProxy.objects.select_related()]
|
|
self.assertEqual(resp, ['New South Wales'])
|
|
|
|
self.assertEqual(StateProxy.objects.get(name='New South Wales').name,
|
|
'New South Wales')
|
|
|
|
resp = StateProxy.objects.select_related().get(name='New South Wales')
|
|
self.assertEqual(resp.name, 'New South Wales')
|
|
|
|
def test_proxy_bug(self):
|
|
contributor = TrackerUser.objects.create(name='Contributor',
|
|
status='contrib')
|
|
someone = BaseUser.objects.create(name='Someone')
|
|
Bug.objects.create(summary='fix this', version='1.1beta',
|
|
assignee=contributor, reporter=someone)
|
|
pcontributor = ProxyTrackerUser.objects.create(name='OtherContributor',
|
|
status='proxy')
|
|
Improvement.objects.create(summary='improve that', version='1.1beta',
|
|
assignee=contributor, reporter=pcontributor,
|
|
associated_bug=ProxyProxyBug.objects.all()[0])
|
|
|
|
# Related field filter on proxy
|
|
resp = ProxyBug.objects.get(version__icontains='beta')
|
|
self.assertEqual(repr(resp), '<ProxyBug: ProxyBug:fix this>')
|
|
|
|
# Select related + filter on proxy
|
|
resp = ProxyBug.objects.select_related().get(version__icontains='beta')
|
|
self.assertEqual(repr(resp), '<ProxyBug: ProxyBug:fix this>')
|
|
|
|
# Proxy of proxy, select_related + filter
|
|
resp = ProxyProxyBug.objects.select_related().get(
|
|
version__icontains='beta'
|
|
)
|
|
self.assertEqual(repr(resp), '<ProxyProxyBug: ProxyProxyBug:fix this>')
|
|
|
|
# Select related + filter on a related proxy field
|
|
resp = ProxyImprovement.objects.select_related().get(
|
|
reporter__name__icontains='butor'
|
|
)
|
|
self.assertEqual(repr(resp),
|
|
'<ProxyImprovement: ProxyImprovement:improve that>'
|
|
)
|
|
|
|
# Select related + filter on a related proxy of proxy field
|
|
resp = ProxyImprovement.objects.select_related().get(
|
|
associated_bug__summary__icontains='fix'
|
|
)
|
|
self.assertEqual(repr(resp),
|
|
'<ProxyImprovement: ProxyImprovement:improve that>'
|
|
)
|
|
|
|
def test_proxy_load_from_fixture(self):
|
|
management.call_command('loaddata', 'mypeople.json', verbosity=0, commit=False)
|
|
p = MyPerson.objects.get(pk=100)
|
|
self.assertEqual(p.name, 'Elvis Presley')
|