1. from django.contrib import admin
    
  2. from django.contrib.auth.models import User as AuthUser
    
  3. from django.contrib.contenttypes.models import ContentType
    
  4. from django.core import checks, management
    
  5. from django.db import DEFAULT_DB_ALIAS, models
    
  6. from django.db.models import signals
    
  7. from django.test import TestCase, override_settings
    
  8. from django.test.utils import isolate_apps
    
  9. from django.urls import reverse
    
  10. 
    
  11. from .admin import admin as force_admin_model_registration  # NOQA
    
  12. from .models import (
    
  13.     Abstract,
    
  14.     BaseUser,
    
  15.     Bug,
    
  16.     Country,
    
  17.     Improvement,
    
  18.     Issue,
    
  19.     LowerStatusPerson,
    
  20.     MultiUserProxy,
    
  21.     MyPerson,
    
  22.     MyPersonProxy,
    
  23.     OtherPerson,
    
  24.     Person,
    
  25.     ProxyBug,
    
  26.     ProxyImprovement,
    
  27.     ProxyProxyBug,
    
  28.     ProxyTrackerUser,
    
  29.     State,
    
  30.     StateProxy,
    
  31.     StatusPerson,
    
  32.     TrackerUser,
    
  33.     User,
    
  34.     UserProxy,
    
  35.     UserProxyProxy,
    
  36. )
    
  37. 
    
  38. 
    
  39. class ProxyModelTests(TestCase):
    
  40.     def test_same_manager_queries(self):
    
  41.         """
    
  42.         The MyPerson model should be generating the same database queries as
    
  43.         the Person model (when the same manager is used in each case).
    
  44.         """
    
  45.         my_person_sql = (
    
  46.             MyPerson.other.all().query.get_compiler(DEFAULT_DB_ALIAS).as_sql()
    
  47.         )
    
  48.         person_sql = (
    
  49.             Person.objects.order_by("name")
    
  50.             .query.get_compiler(DEFAULT_DB_ALIAS)
    
  51.             .as_sql()
    
  52.         )
    
  53.         self.assertEqual(my_person_sql, person_sql)
    
  54. 
    
  55.     def test_inheritance_new_table(self):
    
  56.         """
    
  57.         The StatusPerson models should have its own table (it's using ORM-level
    
  58.         inheritance).
    
  59.         """
    
  60.         sp_sql = (
    
  61.             StatusPerson.objects.all().query.get_compiler(DEFAULT_DB_ALIAS).as_sql()
    
  62.         )
    
  63.         p_sql = Person.objects.all().query.get_compiler(DEFAULT_DB_ALIAS).as_sql()
    
  64.         self.assertNotEqual(sp_sql, p_sql)
    
  65. 
    
  66.     def test_basic_proxy(self):
    
  67.         """
    
  68.         Creating a Person makes them accessible through the MyPerson proxy.
    
  69.         """
    
  70.         person = Person.objects.create(name="Foo McBar")
    
  71.         self.assertEqual(len(Person.objects.all()), 1)
    
  72.         self.assertEqual(len(MyPerson.objects.all()), 1)
    
  73.         self.assertEqual(MyPerson.objects.get(name="Foo McBar").id, person.id)
    
  74.         self.assertFalse(MyPerson.objects.get(id=person.id).has_special_name())
    
  75. 
    
  76.     def test_no_proxy(self):
    
  77.         """
    
  78.         Person is not proxied by StatusPerson subclass.
    
  79.         """
    
  80.         Person.objects.create(name="Foo McBar")
    
  81.         self.assertEqual(list(StatusPerson.objects.all()), [])
    
  82. 
    
  83.     def test_basic_proxy_reverse(self):
    
  84.         """
    
  85.         A new MyPerson also shows up as a standard Person.
    
  86.         """
    
  87.         MyPerson.objects.create(name="Bazza del Frob")
    
  88.         self.assertEqual(len(MyPerson.objects.all()), 1)
    
  89.         self.assertEqual(len(Person.objects.all()), 1)
    
  90. 
    
  91.         LowerStatusPerson.objects.create(status="low", name="homer")
    
  92.         lsps = [lsp.name for lsp in LowerStatusPerson.objects.all()]
    
  93.         self.assertEqual(lsps, ["homer"])
    
  94. 
    
  95.     def test_correct_type_proxy_of_proxy(self):
    
  96.         """
    
  97.         Correct type when querying a proxy of proxy
    
  98.         """
    
  99.         Person.objects.create(name="Foo McBar")
    
  100.         MyPerson.objects.create(name="Bazza del Frob")
    
  101.         LowerStatusPerson.objects.create(status="low", name="homer")
    
  102.         pp = sorted(mpp.name for mpp in MyPersonProxy.objects.all())
    
  103.         self.assertEqual(pp, ["Bazza del Frob", "Foo McBar", "homer"])
    
  104. 
    
  105.     def test_proxy_included_in_ancestors(self):
    
  106.         """
    
  107.         Proxy models are included in the ancestors for a model's DoesNotExist
    
  108.         and MultipleObjectsReturned
    
  109.         """
    
  110.         Person.objects.create(name="Foo McBar")
    
  111.         MyPerson.objects.create(name="Bazza del Frob")
    
  112.         LowerStatusPerson.objects.create(status="low", name="homer")
    
  113.         max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"]
    
  114. 
    
  115.         with self.assertRaises(Person.DoesNotExist):
    
  116.             MyPersonProxy.objects.get(name="Zathras")
    
  117.         with self.assertRaises(Person.MultipleObjectsReturned):
    
  118.             MyPersonProxy.objects.get(id__lt=max_id + 1)
    
  119.         with self.assertRaises(Person.DoesNotExist):
    
  120.             StatusPerson.objects.get(name="Zathras")
    
  121. 
    
  122.         StatusPerson.objects.create(name="Bazza Jr.")
    
  123.         StatusPerson.objects.create(name="Foo Jr.")
    
  124.         max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"]
    
  125. 
    
  126.         with self.assertRaises(Person.MultipleObjectsReturned):
    
  127.             StatusPerson.objects.get(id__lt=max_id + 1)
    
  128. 
    
  129.     def test_abstract_base_with_model_fields(self):
    
  130.         msg = (
    
  131.             "Abstract base class containing model fields not permitted for proxy model "
    
  132.             "'NoAbstract'."
    
  133.         )
    
  134.         with self.assertRaisesMessage(TypeError, msg):
    
  135. 
    
  136.             class NoAbstract(Abstract):
    
  137.                 class Meta:
    
  138.                     proxy = True
    
  139. 
    
  140.     def test_too_many_concrete_classes(self):
    
  141.         msg = (
    
  142.             "Proxy model 'TooManyBases' has more than one non-abstract model base "
    
  143.             "class."
    
  144.         )
    
  145.         with self.assertRaisesMessage(TypeError, msg):
    
  146. 
    
  147.             class TooManyBases(User, Person):
    
  148.                 class Meta:
    
  149.                     proxy = True
    
  150. 
    
  151.     def test_no_base_classes(self):
    
  152.         msg = "Proxy model 'NoBaseClasses' has no non-abstract model base class."
    
  153.         with self.assertRaisesMessage(TypeError, msg):
    
  154. 
    
  155.             class NoBaseClasses(models.Model):
    
  156.                 class Meta:
    
  157.                     proxy = True
    
  158. 
    
  159.     @isolate_apps("proxy_models")
    
  160.     def test_new_fields(self):
    
  161.         class NoNewFields(Person):
    
  162.             newfield = models.BooleanField()
    
  163. 
    
  164.             class Meta:
    
  165.                 proxy = True
    
  166. 
    
  167.         errors = NoNewFields.check()
    
  168.         expected = [
    
  169.             checks.Error(
    
  170.                 "Proxy model 'NoNewFields' contains model fields.",
    
  171.                 id="models.E017",
    
  172.             )
    
  173.         ]
    
  174.         self.assertEqual(errors, expected)
    
  175. 
    
  176.     @override_settings(TEST_SWAPPABLE_MODEL="proxy_models.AlternateModel")
    
  177.     @isolate_apps("proxy_models")
    
  178.     def test_swappable(self):
    
  179.         class SwappableModel(models.Model):
    
  180.             class Meta:
    
  181.                 swappable = "TEST_SWAPPABLE_MODEL"
    
  182. 
    
  183.         class AlternateModel(models.Model):
    
  184.             pass
    
  185. 
    
  186.         # You can't proxy a swapped model
    
  187.         with self.assertRaises(TypeError):
    
  188. 
    
  189.             class ProxyModel(SwappableModel):
    
  190.                 class Meta:
    
  191.                     proxy = True
    
  192. 
    
  193.     def test_myperson_manager(self):
    
  194.         Person.objects.create(name="fred")
    
  195.         Person.objects.create(name="wilma")
    
  196.         Person.objects.create(name="barney")
    
  197. 
    
  198.         resp = [p.name for p in MyPerson.objects.all()]
    
  199.         self.assertEqual(resp, ["barney", "fred"])
    
  200. 
    
  201.         resp = [p.name for p in MyPerson._default_manager.all()]
    
  202.         self.assertEqual(resp, ["barney", "fred"])
    
  203. 
    
  204.     def test_otherperson_manager(self):
    
  205.         Person.objects.create(name="fred")
    
  206.         Person.objects.create(name="wilma")
    
  207.         Person.objects.create(name="barney")
    
  208. 
    
  209.         resp = [p.name for p in OtherPerson.objects.all()]
    
  210.         self.assertEqual(resp, ["barney", "wilma"])
    
  211. 
    
  212.         resp = [p.name for p in OtherPerson.excluder.all()]
    
  213.         self.assertEqual(resp, ["barney", "fred"])
    
  214. 
    
  215.         resp = [p.name for p in OtherPerson._default_manager.all()]
    
  216.         self.assertEqual(resp, ["barney", "wilma"])
    
  217. 
    
  218.     def test_permissions_created(self):
    
  219.         from django.contrib.auth.models import Permission
    
  220. 
    
  221.         Permission.objects.get(name="May display users information")
    
  222. 
    
  223.     def test_proxy_model_signals(self):
    
  224.         """
    
  225.         Test save signals for proxy models
    
  226.         """
    
  227.         output = []
    
  228. 
    
  229.         def make_handler(model, event):
    
  230.             def _handler(*args, **kwargs):
    
  231.                 output.append("%s %s save" % (model, event))
    
  232. 
    
  233.             return _handler
    
  234. 
    
  235.         h1 = make_handler("MyPerson", "pre")
    
  236.         h2 = make_handler("MyPerson", "post")
    
  237.         h3 = make_handler("Person", "pre")
    
  238.         h4 = make_handler("Person", "post")
    
  239. 
    
  240.         signals.pre_save.connect(h1, sender=MyPerson)
    
  241.         signals.post_save.connect(h2, sender=MyPerson)
    
  242.         signals.pre_save.connect(h3, sender=Person)
    
  243.         signals.post_save.connect(h4, sender=Person)
    
  244. 
    
  245.         MyPerson.objects.create(name="dino")
    
  246.         self.assertEqual(output, ["MyPerson pre save", "MyPerson post save"])
    
  247. 
    
  248.         output = []
    
  249. 
    
  250.         h5 = make_handler("MyPersonProxy", "pre")
    
  251.         h6 = make_handler("MyPersonProxy", "post")
    
  252. 
    
  253.         signals.pre_save.connect(h5, sender=MyPersonProxy)
    
  254.         signals.post_save.connect(h6, sender=MyPersonProxy)
    
  255. 
    
  256.         MyPersonProxy.objects.create(name="pebbles")
    
  257. 
    
  258.         self.assertEqual(output, ["MyPersonProxy pre save", "MyPersonProxy post save"])
    
  259. 
    
  260.         signals.pre_save.disconnect(h1, sender=MyPerson)
    
  261.         signals.post_save.disconnect(h2, sender=MyPerson)
    
  262.         signals.pre_save.disconnect(h3, sender=Person)
    
  263.         signals.post_save.disconnect(h4, sender=Person)
    
  264.         signals.pre_save.disconnect(h5, sender=MyPersonProxy)
    
  265.         signals.post_save.disconnect(h6, sender=MyPersonProxy)
    
  266. 
    
  267.     def test_content_type(self):
    
  268.         ctype = ContentType.objects.get_for_model
    
  269.         self.assertIs(ctype(Person), ctype(OtherPerson))
    
  270. 
    
  271.     def test_user_proxy_models(self):
    
  272.         User.objects.create(name="Bruce")
    
  273. 
    
  274.         resp = [u.name for u in User.objects.all()]
    
  275.         self.assertEqual(resp, ["Bruce"])
    
  276. 
    
  277.         resp = [u.name for u in UserProxy.objects.all()]
    
  278.         self.assertEqual(resp, ["Bruce"])
    
  279. 
    
  280.         resp = [u.name for u in UserProxyProxy.objects.all()]
    
  281.         self.assertEqual(resp, ["Bruce"])
    
  282. 
    
  283.         self.assertEqual([u.name for u in MultiUserProxy.objects.all()], ["Bruce"])
    
  284. 
    
  285.     def test_proxy_for_model(self):
    
  286.         self.assertEqual(UserProxy, UserProxyProxy._meta.proxy_for_model)
    
  287. 
    
  288.     def test_concrete_model(self):
    
  289.         self.assertEqual(User, UserProxyProxy._meta.concrete_model)
    
  290. 
    
  291.     def test_proxy_delete(self):
    
  292.         """
    
  293.         Proxy objects can be deleted
    
  294.         """
    
  295.         User.objects.create(name="Bruce")
    
  296.         u2 = UserProxy.objects.create(name="George")
    
  297. 
    
  298.         resp = [u.name for u in UserProxy.objects.all()]
    
  299.         self.assertEqual(resp, ["Bruce", "George"])
    
  300. 
    
  301.         u2.delete()
    
  302. 
    
  303.         resp = [u.name for u in UserProxy.objects.all()]
    
  304.         self.assertEqual(resp, ["Bruce"])
    
  305. 
    
  306.     def test_proxy_update(self):
    
  307.         user = User.objects.create(name="Bruce")
    
  308.         with self.assertNumQueries(1):
    
  309.             UserProxy.objects.filter(id=user.id).update(name="George")
    
  310.         user.refresh_from_db()
    
  311.         self.assertEqual(user.name, "George")
    
  312. 
    
  313.     def test_select_related(self):
    
  314.         """
    
  315.         We can still use `select_related()` to include related models in our
    
  316.         querysets.
    
  317.         """
    
  318.         country = Country.objects.create(name="Australia")
    
  319.         State.objects.create(name="New South Wales", country=country)
    
  320. 
    
  321.         resp = [s.name for s in State.objects.select_related()]
    
  322.         self.assertEqual(resp, ["New South Wales"])
    
  323. 
    
  324.         resp = [s.name for s in StateProxy.objects.select_related()]
    
  325.         self.assertEqual(resp, ["New South Wales"])
    
  326. 
    
  327.         self.assertEqual(
    
  328.             StateProxy.objects.get(name="New South Wales").name, "New South Wales"
    
  329.         )
    
  330. 
    
  331.         resp = StateProxy.objects.select_related().get(name="New South Wales")
    
  332.         self.assertEqual(resp.name, "New South Wales")
    
  333. 
    
  334.     def test_filter_proxy_relation_reverse(self):
    
  335.         tu = TrackerUser.objects.create(name="Contributor", status="contrib")
    
  336.         ptu = ProxyTrackerUser.objects.get()
    
  337.         issue = Issue.objects.create(assignee=tu)
    
  338.         self.assertEqual(tu.issues.get(), issue)
    
  339.         self.assertEqual(ptu.issues.get(), issue)
    
  340.         self.assertSequenceEqual(TrackerUser.objects.filter(issues=issue), [tu])
    
  341.         self.assertSequenceEqual(ProxyTrackerUser.objects.filter(issues=issue), [ptu])
    
  342. 
    
  343.     def test_proxy_bug(self):
    
  344.         contributor = ProxyTrackerUser.objects.create(
    
  345.             name="Contributor", status="contrib"
    
  346.         )
    
  347.         someone = BaseUser.objects.create(name="Someone")
    
  348.         Bug.objects.create(
    
  349.             summary="fix this",
    
  350.             version="1.1beta",
    
  351.             assignee=contributor,
    
  352.             reporter=someone,
    
  353.         )
    
  354.         pcontributor = ProxyTrackerUser.objects.create(
    
  355.             name="OtherContributor", status="proxy"
    
  356.         )
    
  357.         Improvement.objects.create(
    
  358.             summary="improve that",
    
  359.             version="1.1beta",
    
  360.             assignee=contributor,
    
  361.             reporter=pcontributor,
    
  362.             associated_bug=ProxyProxyBug.objects.all()[0],
    
  363.         )
    
  364. 
    
  365.         # Related field filter on proxy
    
  366.         resp = ProxyBug.objects.get(version__icontains="beta")
    
  367.         self.assertEqual(repr(resp), "<ProxyBug: ProxyBug:fix this>")
    
  368. 
    
  369.         # Select related + filter on proxy
    
  370.         resp = ProxyBug.objects.select_related().get(version__icontains="beta")
    
  371.         self.assertEqual(repr(resp), "<ProxyBug: ProxyBug:fix this>")
    
  372. 
    
  373.         # Proxy of proxy, select_related + filter
    
  374.         resp = ProxyProxyBug.objects.select_related().get(version__icontains="beta")
    
  375.         self.assertEqual(repr(resp), "<ProxyProxyBug: ProxyProxyBug:fix this>")
    
  376. 
    
  377.         # Select related + filter on a related proxy field
    
  378.         resp = ProxyImprovement.objects.select_related().get(
    
  379.             reporter__name__icontains="butor"
    
  380.         )
    
  381.         self.assertEqual(
    
  382.             repr(resp), "<ProxyImprovement: ProxyImprovement:improve that>"
    
  383.         )
    
  384. 
    
  385.         # Select related + filter on a related proxy of proxy field
    
  386.         resp = ProxyImprovement.objects.select_related().get(
    
  387.             associated_bug__summary__icontains="fix"
    
  388.         )
    
  389.         self.assertEqual(
    
  390.             repr(resp), "<ProxyImprovement: ProxyImprovement:improve that>"
    
  391.         )
    
  392. 
    
  393.     def test_proxy_load_from_fixture(self):
    
  394.         management.call_command("loaddata", "mypeople.json", verbosity=0)
    
  395.         p = MyPerson.objects.get(pk=100)
    
  396.         self.assertEqual(p.name, "Elvis Presley")
    
  397. 
    
  398.     def test_eq(self):
    
  399.         self.assertEqual(MyPerson(id=100), Person(id=100))
    
  400. 
    
  401. 
    
  402. @override_settings(ROOT_URLCONF="proxy_models.urls")
    
  403. class ProxyModelAdminTests(TestCase):
    
  404.     @classmethod
    
  405.     def setUpTestData(cls):
    
  406.         cls.superuser = AuthUser.objects.create(is_superuser=True, is_staff=True)
    
  407.         cls.tu1 = ProxyTrackerUser.objects.create(name="Django Pony", status="emperor")
    
  408.         cls.i1 = Issue.objects.create(summary="Pony's Issue", assignee=cls.tu1)
    
  409. 
    
  410.     def test_cascade_delete_proxy_model_admin_warning(self):
    
  411.         """
    
  412.         Test if admin gives warning about cascade deleting models referenced
    
  413.         to concrete model by deleting proxy object.
    
  414.         """
    
  415.         tracker_user = TrackerUser.objects.all()[0]
    
  416.         base_user = BaseUser.objects.all()[0]
    
  417.         issue = Issue.objects.all()[0]
    
  418.         with self.assertNumQueries(6):
    
  419.             collector = admin.utils.NestedObjects("default")
    
  420.             collector.collect(ProxyTrackerUser.objects.all())
    
  421.         self.assertIn(tracker_user, collector.edges.get(None, ()))
    
  422.         self.assertIn(base_user, collector.edges.get(None, ()))
    
  423.         self.assertIn(issue, collector.edges.get(tracker_user, ()))
    
  424. 
    
  425.     def test_delete_str_in_model_admin(self):
    
  426.         """
    
  427.         Test if the admin delete page shows the correct string representation
    
  428.         for a proxy model.
    
  429.         """
    
  430.         user = TrackerUser.objects.get(name="Django Pony")
    
  431.         proxy = ProxyTrackerUser.objects.get(name="Django Pony")
    
  432. 
    
  433.         user_str = 'Tracker user: <a href="%s">%s</a>' % (
    
  434.             reverse("admin_proxy:proxy_models_trackeruser_change", args=(user.pk,)),
    
  435.             user,
    
  436.         )
    
  437.         proxy_str = 'Proxy tracker user: <a href="%s">%s</a>' % (
    
  438.             reverse(
    
  439.                 "admin_proxy:proxy_models_proxytrackeruser_change", args=(proxy.pk,)
    
  440.             ),
    
  441.             proxy,
    
  442.         )
    
  443. 
    
  444.         self.client.force_login(self.superuser)
    
  445.         response = self.client.get(
    
  446.             reverse("admin_proxy:proxy_models_trackeruser_delete", args=(user.pk,))
    
  447.         )
    
  448.         delete_str = response.context["deleted_objects"][0]
    
  449.         self.assertEqual(delete_str, user_str)
    
  450.         response = self.client.get(
    
  451.             reverse(
    
  452.                 "admin_proxy:proxy_models_proxytrackeruser_delete", args=(proxy.pk,)
    
  453.             )
    
  454.         )
    
  455.         delete_str = response.context["deleted_objects"][0]
    
  456.         self.assertEqual(delete_str, proxy_str)