Когда требуется реализация сложных связей между элементами моделей следует использовать M2M поля, которые хорошо описаны в документации. Один абзац документации посвящён симметричности M2M полей. Мы остановимся на этой теме подробнее.
Симметричная M2M связь
По умолчанию поле ManyToManyField
обладает симметричностью. Это выражается тем, что если одна запись модели ссылается на другую, то автоматически происходит создание ссылки и в обратную сторону.
Для проверки этого утверждения нам понадобится модель:
from django.db import models
class Test(models.Model):
linked = models.ManyToManyField(u'self')
А также нам потребуются вспомогательные функции clean
, init
, link
и show
, код которых приведён в конце рецепта.
Очистка окружения и создание пяти записей в модели:
>>> clean()
>>> init()
Привязываем 2, 3, 4 записи к первой. Обратите внимание, на обратную привязку первой модели к моделям 2, 3 и 4.
>>> link(1, (3,4,2,))
1 : [2, 3, 4]
2 : [1]
3 : [1]
4 : [1]
5 : []
Теперь привязываем 3, 4, 5 записи ко второй. Обратите внимание, раз мы не указали привязку 1 записи ко второй, она удаляется.
>>> link(2, (4,5,3,))
1 : [3, 4]
2 : [3, 4, 5]
3 : [1, 2]
4 : [1, 2]
5 : [2]
Далее всё происходит аналогично:
>>> link(3, (1,2,5,))
1 : [3, 4]
2 : [3, 4, 5]
3 : [1, 2, 5]
4 : [1, 2]
5 : [2, 3]
>>> link(4, (1,2,3,))
1 : [3, 4]
2 : [3, 4, 5]
3 : [1, 2, 4, 5]
4 : [1, 2, 3]
5 : [2, 3]
>>> link(5, (4,2,1,))
1 : [3, 4, 5]
2 : [3, 4, 5]
3 : [1, 2, 4]
4 : [1, 2, 3, 5]
5 : [1, 2, 4]
>>>
Несимметричная M2M связь
Симметричное поведение поля ManyToManyField
не всегда полезно. Для отключения этой возможности следует воспользоваться атрибутом symmetrical
. Его применение приведёт к тому, что если одна запись модели ссылается на другую, то создание обратной ссылки производиться не будет.
Для проверки этого утверждения нам понадобится модель:
from django.db import models
class Test(models.Model):
linked = models.ManyToManyField(u'self', symmetrical=False, related_name='they_link_me')
А также нам потребуются вспомогательные функции clean
, init
, link
и show
, код которых приведён в конце рецепта.
Очистка окружения и создание пяти записей в модели:
>>> clean()
>>> init()
Привязываем записи 2, 3, 4 к первой. Обратите внимание на отсутствие обратных ссылок.
>>> link(1, (3,4,2,))
1 : [2, 3, 4]
2 : []
3 : []
4 : []
5 : []
Привязываем записи 3, 4, 5 ко второй. Никакого влияния на первую запись не происходит.
>>> link(2, (4,5,3,))
1 : [2, 3, 4]
2 : [3, 4, 5]
3 : []
4 : []
5 : []
Далее всё происходит аналогично:
>>> link(3, (1,2,5,))
1 : [2, 3, 4]
2 : [3, 4, 5]
3 : [1, 2, 5]
4 : []
5 : []
>>> link(4, (1,2,3,))
1 : [2, 3, 4]
2 : [3, 4, 5]
3 : [1, 2, 5]
4 : [1, 2, 3]
5 : []
>>> link(5, (4,2,1,))
1 : [2, 3, 4]
2 : [3, 4, 5]
3 : [1, 2, 5]
4 : [1, 2, 3]
5 : [1, 2, 4]
>>>
Вспомогательные функции
from demo.models import Test
def clean():
Test.objects.all().delete()
def init():
for i in xrange(1, 6):
Test().save()
def link(a, l=()):
parent = Test.objects.get(id=a)
children = Test.objects.filter(id__in=l)
parent.linked = children
parent.save()
show()
def show():
for i in xrange(1,6):
try:
z = Test.objects.get(id=i)
except Test.DoesNotExist:
print 'no model with id =', i
else:
print i, ':', [a.id for a in z.linked.all()]