Fixed #30421 -- Allowed symmetrical intermediate table for self-referential ManyToManyField.

This commit is contained in:
Nadège Michel 2019-04-19 18:12:04 +02:00 committed by Mariusz Felisiak
parent a9179ab032
commit 87b1ad6e73
11 changed files with 167 additions and 90 deletions

View file

@ -72,6 +72,7 @@ class TestNoDefaultsOrNulls(models.Model):
class PersonSelfRefM2M(models.Model):
name = models.CharField(max_length=5)
friends = models.ManyToManyField('self', through="Friendship", symmetrical=False)
sym_friends = models.ManyToManyField('self', through='SymmetricalFriendship', symmetrical=True)
def __str__(self):
return self.name
@ -83,6 +84,12 @@ class Friendship(models.Model):
date_friended = models.DateTimeField()
class SymmetricalFriendship(models.Model):
first = models.ForeignKey(PersonSelfRefM2M, models.CASCADE)
second = models.ForeignKey(PersonSelfRefM2M, models.CASCADE, related_name='+')
date_friended = models.DateField()
# Custom through link fields
class Event(models.Model):
title = models.CharField(max_length=50)

View file

@ -1,4 +1,4 @@
from datetime import datetime
from datetime import date, datetime
from operator import attrgetter
from django.db import IntegrityError
@ -7,7 +7,7 @@ from django.test import TestCase
from .models import (
CustomMembership, Employee, Event, Friendship, Group, Ingredient,
Invitation, Membership, Person, PersonSelfRefM2M, Recipe, RecipeIngredient,
Relationship,
Relationship, SymmetricalFriendship,
)
@ -401,7 +401,7 @@ class M2mThroughReferentialTests(TestCase):
attrgetter("name")
)
def test_self_referential_symmetrical(self):
def test_self_referential_non_symmetrical_both(self):
tony = PersonSelfRefM2M.objects.create(name="Tony")
chris = PersonSelfRefM2M.objects.create(name="Chris")
Friendship.objects.create(
@ -439,6 +439,71 @@ class M2mThroughReferentialTests(TestCase):
attrgetter('name')
)
def test_self_referential_symmetrical(self):
tony = PersonSelfRefM2M.objects.create(name='Tony')
chris = PersonSelfRefM2M.objects.create(name='Chris')
SymmetricalFriendship.objects.create(
first=tony, second=chris, date_friended=date.today(),
)
self.assertSequenceEqual(tony.sym_friends.all(), [chris])
# Manually created symmetrical m2m relation doesn't add mirror entry
# automatically.
self.assertSequenceEqual(chris.sym_friends.all(), [])
SymmetricalFriendship.objects.create(
first=chris, second=tony, date_friended=date.today()
)
self.assertSequenceEqual(chris.sym_friends.all(), [tony])
def test_add_on_symmetrical_m2m_with_intermediate_model(self):
tony = PersonSelfRefM2M.objects.create(name='Tony')
chris = PersonSelfRefM2M.objects.create(name='Chris')
date_friended = date(2017, 1, 3)
tony.sym_friends.add(chris, through_defaults={'date_friended': date_friended})
self.assertSequenceEqual(tony.sym_friends.all(), [chris])
self.assertSequenceEqual(chris.sym_friends.all(), [tony])
friendship = tony.symmetricalfriendship_set.get()
self.assertEqual(friendship.date_friended, date_friended)
def test_set_on_symmetrical_m2m_with_intermediate_model(self):
tony = PersonSelfRefM2M.objects.create(name='Tony')
chris = PersonSelfRefM2M.objects.create(name='Chris')
anne = PersonSelfRefM2M.objects.create(name='Anne')
kate = PersonSelfRefM2M.objects.create(name='Kate')
date_friended_add = date(2013, 1, 5)
date_friended_set = date.today()
tony.sym_friends.add(
anne, chris,
through_defaults={'date_friended': date_friended_add},
)
tony.sym_friends.set(
[anne, kate],
through_defaults={'date_friended': date_friended_set},
)
self.assertSequenceEqual(tony.sym_friends.all(), [anne, kate])
self.assertSequenceEqual(anne.sym_friends.all(), [tony])
self.assertSequenceEqual(kate.sym_friends.all(), [tony])
self.assertEqual(
kate.symmetricalfriendship_set.get().date_friended,
date_friended_set,
)
# Date is preserved.
self.assertEqual(
anne.symmetricalfriendship_set.get().date_friended,
date_friended_add,
)
# Recreate relationship.
tony.sym_friends.set(
[anne],
clear=True,
through_defaults={'date_friended': date_friended_set},
)
self.assertSequenceEqual(tony.sym_friends.all(), [anne])
self.assertSequenceEqual(anne.sym_friends.all(), [tony])
self.assertEqual(
anne.symmetricalfriendship_set.get().date_friended,
date_friended_set,
)
class M2mThroughToFieldsTests(TestCase):
@classmethod