from django.contrib import admin from django.contrib.auth.models import User as AuthUser from django.contrib.contenttypes.models import ContentType from django.core import checks, management from django.db import DEFAULT_DB_ALIAS, models from django.db.models import signals from django.test import TestCase, override_settings from django.test.utils import isolate_apps from django.urls import reverse from .admin import admin as force_admin_model_registration # NOQA from .models import ( Abstract, BaseUser, Bug, Country, Improvement, Issue, LowerStatusPerson, MultiUserProxy, MyPerson, MyPersonProxy, OtherPerson, Person, ProxyBug, ProxyImprovement, ProxyProxyBug, ProxyTrackerUser, State, StateProxy, StatusPerson, TrackerUser, User, UserProxy, UserProxyProxy, ) 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_inheritance_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"] with self.assertRaises(Person.DoesNotExist): MyPersonProxy.objects.get(name="Zathras") with self.assertRaises(Person.MultipleObjectsReturned): MyPersonProxy.objects.get(id__lt=max_id + 1) with self.assertRaises(Person.DoesNotExist): StatusPerson.objects.get(name="Zathras") StatusPerson.objects.create(name="Bazza Jr.") StatusPerson.objects.create(name="Foo Jr.") max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"] with self.assertRaises(Person.MultipleObjectsReturned): StatusPerson.objects.get(id__lt=max_id + 1) def test_abstract_base_with_model_fields(self): msg = ( "Abstract base class containing model fields not permitted for proxy model " "'NoAbstract'." ) with self.assertRaisesMessage(TypeError, msg): class NoAbstract(Abstract): class Meta: proxy = True def test_too_many_concrete_classes(self): msg = ( "Proxy model 'TooManyBases' has more than one non-abstract model base " "class." ) with self.assertRaisesMessage(TypeError, msg): class TooManyBases(User, Person): class Meta: proxy = True def test_no_base_classes(self): msg = "Proxy model 'NoBaseClasses' has no non-abstract model base class." with self.assertRaisesMessage(TypeError, msg): class NoBaseClasses(models.Model): class Meta: proxy = True @isolate_apps("proxy_models") def test_new_fields(self): class NoNewFields(Person): newfield = models.BooleanField() class Meta: proxy = True errors = NoNewFields.check() expected = [ checks.Error( "Proxy model 'NoNewFields' contains model fields.", id="models.E017", ) ] self.assertEqual(errors, expected) @override_settings(TEST_SWAPPABLE_MODEL="proxy_models.AlternateModel") @isolate_apps("proxy_models") def test_swappable(self): 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 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 Permission.objects.get(name="May display users information") 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) 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) 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.assertIs(ctype(Person), ctype(OtherPerson)) def test_user_proxy_models(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"]) self.assertEqual([u.name for u in MultiUserProxy.objects.all()], ["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_proxy_update(self): user = User.objects.create(name="Bruce") with self.assertNumQueries(1): UserProxy.objects.filter(id=user.id).update(name="George") user.refresh_from_db() self.assertEqual(user.name, "George") 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.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_filter_proxy_relation_reverse(self): tu = TrackerUser.objects.create(name="Contributor", status="contrib") ptu = ProxyTrackerUser.objects.get() issue = Issue.objects.create(assignee=tu) self.assertEqual(tu.issues.get(), issue) self.assertEqual(ptu.issues.get(), issue) self.assertSequenceEqual(TrackerUser.objects.filter(issues=issue), [tu]) self.assertSequenceEqual(ProxyTrackerUser.objects.filter(issues=issue), [ptu]) def test_proxy_bug(self): contributor = ProxyTrackerUser.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), "") # Select related + filter on proxy resp = ProxyBug.objects.select_related().get(version__icontains="beta") self.assertEqual(repr(resp), "") # Proxy of proxy, select_related + filter resp = ProxyProxyBug.objects.select_related().get(version__icontains="beta") self.assertEqual(repr(resp), "") # Select related + filter on a related proxy field resp = ProxyImprovement.objects.select_related().get( reporter__name__icontains="butor" ) self.assertEqual( repr(resp), "" ) # 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), "" ) def test_proxy_load_from_fixture(self): management.call_command("loaddata", "mypeople.json", verbosity=0) p = MyPerson.objects.get(pk=100) self.assertEqual(p.name, "Elvis Presley") def test_select_related_only(self): user = ProxyTrackerUser.objects.create(name="Joe Doe", status="test") issue = Issue.objects.create(summary="New issue", assignee=user) qs = Issue.objects.select_related("assignee").only("assignee__status") self.assertEqual(qs.get(), issue) def test_eq(self): self.assertEqual(MyPerson(id=100), Person(id=100)) @override_settings(ROOT_URLCONF="proxy_models.urls") class ProxyModelAdminTests(TestCase): @classmethod def setUpTestData(cls): cls.superuser = AuthUser.objects.create(is_superuser=True, is_staff=True) cls.tu1 = ProxyTrackerUser.objects.create(name="Django Pony", status="emperor") cls.i1 = Issue.objects.create(summary="Pony's Issue", assignee=cls.tu1) def test_cascade_delete_proxy_model_admin_warning(self): """ Test if admin gives warning about cascade deleting models referenced to concrete model by deleting proxy object. """ tracker_user = TrackerUser.objects.all()[0] base_user = BaseUser.objects.all()[0] issue = Issue.objects.all()[0] with self.assertNumQueries(6): collector = admin.utils.NestedObjects("default") collector.collect(ProxyTrackerUser.objects.all()) self.assertIn(tracker_user, collector.edges.get(None, ())) self.assertIn(base_user, collector.edges.get(None, ())) self.assertIn(issue, collector.edges.get(tracker_user, ())) def test_delete_str_in_model_admin(self): """ Test if the admin delete page shows the correct string representation for a proxy model. """ user = TrackerUser.objects.get(name="Django Pony") proxy = ProxyTrackerUser.objects.get(name="Django Pony") user_str = 'Tracker user: %s' % ( reverse("admin_proxy:proxy_models_trackeruser_change", args=(user.pk,)), user, ) proxy_str = 'Proxy tracker user: %s' % ( reverse( "admin_proxy:proxy_models_proxytrackeruser_change", args=(proxy.pk,) ), proxy, ) self.client.force_login(self.superuser) response = self.client.get( reverse("admin_proxy:proxy_models_trackeruser_delete", args=(user.pk,)) ) delete_str = response.context["deleted_objects"][0] self.assertEqual(delete_str, user_str) response = self.client.get( reverse( "admin_proxy:proxy_models_proxytrackeruser_delete", args=(proxy.pk,) ) ) delete_str = response.context["deleted_objects"][0] self.assertEqual(delete_str, proxy_str)