1. import datetime
    
  2. 
    
  3. from django.test import TestCase
    
  4. 
    
  5. from .models import Person
    
  6. 
    
  7. 
    
  8. class RecursiveM2MTests(TestCase):
    
  9.     @classmethod
    
  10.     def setUpTestData(cls):
    
  11.         cls.a, cls.b, cls.c, cls.d = [
    
  12.             Person.objects.create(name=name)
    
  13.             for name in ["Anne", "Bill", "Chuck", "David"]
    
  14.         ]
    
  15.         cls.a.friends.add(cls.b, cls.c)
    
  16.         # Add m2m for Anne and Chuck in reverse direction.
    
  17.         cls.d.friends.add(cls.a, cls.c)
    
  18. 
    
  19.     def test_recursive_m2m_all(self):
    
  20.         for person, friends in (
    
  21.             (self.a, [self.b, self.c, self.d]),
    
  22.             (self.b, [self.a]),
    
  23.             (self.c, [self.a, self.d]),
    
  24.             (self.d, [self.a, self.c]),
    
  25.         ):
    
  26.             with self.subTest(person=person):
    
  27.                 self.assertSequenceEqual(person.friends.all(), friends)
    
  28. 
    
  29.     def test_recursive_m2m_reverse_add(self):
    
  30.         # Add m2m for Anne in reverse direction.
    
  31.         self.b.friends.add(self.a)
    
  32.         self.assertSequenceEqual(self.a.friends.all(), [self.b, self.c, self.d])
    
  33.         self.assertSequenceEqual(self.b.friends.all(), [self.a])
    
  34. 
    
  35.     def test_recursive_m2m_remove(self):
    
  36.         self.b.friends.remove(self.a)
    
  37.         self.assertSequenceEqual(self.a.friends.all(), [self.c, self.d])
    
  38.         self.assertSequenceEqual(self.b.friends.all(), [])
    
  39. 
    
  40.     def test_recursive_m2m_clear(self):
    
  41.         # Clear m2m for Anne.
    
  42.         self.a.friends.clear()
    
  43.         self.assertSequenceEqual(self.a.friends.all(), [])
    
  44.         # Reverse m2m relationships should be removed.
    
  45.         self.assertSequenceEqual(self.c.friends.all(), [self.d])
    
  46.         self.assertSequenceEqual(self.d.friends.all(), [self.c])
    
  47. 
    
  48.     def test_recursive_m2m_add_via_related_name(self):
    
  49.         # Add m2m with custom related name for Anne in reverse direction.
    
  50.         self.d.stalkers.add(self.a)
    
  51.         self.assertSequenceEqual(self.a.idols.all(), [self.d])
    
  52.         self.assertSequenceEqual(self.a.stalkers.all(), [])
    
  53. 
    
  54.     def test_recursive_m2m_add_in_both_directions(self):
    
  55.         # Adding the same relation twice results in a single relation.
    
  56.         self.a.idols.add(self.d)
    
  57.         self.d.stalkers.add(self.a)
    
  58.         self.assertSequenceEqual(self.a.idols.all(), [self.d])
    
  59. 
    
  60.     def test_recursive_m2m_related_to_self(self):
    
  61.         self.a.idols.add(self.a)
    
  62.         self.assertSequenceEqual(self.a.idols.all(), [self.a])
    
  63.         self.assertSequenceEqual(self.a.stalkers.all(), [self.a])
    
  64. 
    
  65. 
    
  66. class RecursiveSymmetricalM2MThroughTests(TestCase):
    
  67.     @classmethod
    
  68.     def setUpTestData(cls):
    
  69.         cls.a, cls.b, cls.c, cls.d = [
    
  70.             Person.objects.create(name=name)
    
  71.             for name in ["Anne", "Bill", "Chuck", "David"]
    
  72.         ]
    
  73.         cls.a.colleagues.add(
    
  74.             cls.b,
    
  75.             cls.c,
    
  76.             through_defaults={
    
  77.                 "first_meet": datetime.date(2013, 1, 5),
    
  78.             },
    
  79.         )
    
  80.         # Add m2m for Anne and Chuck in reverse direction.
    
  81.         cls.d.colleagues.add(
    
  82.             cls.a,
    
  83.             cls.c,
    
  84.             through_defaults={
    
  85.                 "first_meet": datetime.date(2015, 6, 15),
    
  86.             },
    
  87.         )
    
  88. 
    
  89.     def test_recursive_m2m_all(self):
    
  90.         for person, colleagues in (
    
  91.             (self.a, [self.b, self.c, self.d]),
    
  92.             (self.b, [self.a]),
    
  93.             (self.c, [self.a, self.d]),
    
  94.             (self.d, [self.a, self.c]),
    
  95.         ):
    
  96.             with self.subTest(person=person):
    
  97.                 self.assertSequenceEqual(person.colleagues.all(), colleagues)
    
  98. 
    
  99.     def test_recursive_m2m_reverse_add(self):
    
  100.         # Add m2m for Anne in reverse direction.
    
  101.         self.b.colleagues.add(
    
  102.             self.a,
    
  103.             through_defaults={
    
  104.                 "first_meet": datetime.date(2013, 1, 5),
    
  105.             },
    
  106.         )
    
  107.         self.assertCountEqual(self.a.colleagues.all(), [self.b, self.c, self.d])
    
  108.         self.assertSequenceEqual(self.b.colleagues.all(), [self.a])
    
  109. 
    
  110.     def test_recursive_m2m_remove(self):
    
  111.         self.b.colleagues.remove(self.a)
    
  112.         self.assertSequenceEqual(self.a.colleagues.all(), [self.c, self.d])
    
  113.         self.assertSequenceEqual(self.b.colleagues.all(), [])
    
  114. 
    
  115.     def test_recursive_m2m_clear(self):
    
  116.         # Clear m2m for Anne.
    
  117.         self.a.colleagues.clear()
    
  118.         self.assertSequenceEqual(self.a.friends.all(), [])
    
  119.         # Reverse m2m relationships is removed.
    
  120.         self.assertSequenceEqual(self.c.colleagues.all(), [self.d])
    
  121.         self.assertSequenceEqual(self.d.colleagues.all(), [self.c])
    
  122. 
    
  123.     def test_recursive_m2m_set(self):
    
  124.         # Set new relationships for Chuck.
    
  125.         self.c.colleagues.set(
    
  126.             [self.b, self.d],
    
  127.             through_defaults={
    
  128.                 "first_meet": datetime.date(2013, 1, 5),
    
  129.             },
    
  130.         )
    
  131.         self.assertSequenceEqual(self.c.colleagues.order_by("name"), [self.b, self.d])
    
  132.         # Reverse m2m relationships is removed.
    
  133.         self.assertSequenceEqual(self.a.colleagues.order_by("name"), [self.b, self.d])