python/Django

[Django] ORM - N:N 관계 (ManytoManyField) (다대다관계 1)

http://portfolio.wonpaper.net 2022. 5. 14. 10:50

이번에는 모델들이 N:N 관계로 다대다 관계로 연결될 경우이다.

 

아래와 같이 Album 과 Publication 모델이 다대다 관계라고 설정해 보자.

 

1
2
3
4
5
6
7
8
9
class Album(models.Model):
    name = models.CharField('NAME', max_length=30)
    description = models.CharField('One Line Description', max_length=100, blank=True)
    owner = models.ForeignKey('auth.User', on_delete=models.CASCADE, verbose_name='OWNER', blank=Truenull=True)
 
class Publication(models.Model):
    title = models.CharField(max_lendth=30)
    albums = models.ManyToManyField(Album)
 
cs

 

[ N:N 관계]

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
>>> from photo.models import Album, Publication
 
# 출판물 객체3개를 임의로 만들고 DB에 저장한다.
>>> p1 = Publication(title='The Python Journal')
>>> p1.save()
>>> p2 = Publication(title='Science News')
>>> p2.save()
>>> p3 = Publication(title='Science Weekly')
>>> p3.save()
 
# 출판물 전체 객체 확인
>>> Publication.objects.all()
<QuerySet [<Publication: publication object (1)><Publication: publication object (2)><Publication: publication object (3)>]>
 
# 앨범 객체 전체 리스트
>>> Album.objects.all()
<QuerySet [<Album: Django><Album: Nature><Album: TestAlbum1><Album: TestAlbum2><Album: 국가별><Album: 사람들>]>
 
# 테이블에 있는 앨범 객체 하나 조회한다.
>>> a1 = Album.objects.get(name='Django')
 
# 출판물 p1에 앨범 a1을 연결한다.
>>> p1.albums.add(a1)
 
# 출판물 p1에 게시된 모든 앨범 리스트를 확인한다.
>>> p1.albums.all()
<QuerySet [<Album: Django>]>
 
 
# 반대 방향으로, 앨범 a1이 게시된 모든 출판물 리스트를 확인한다.
>>> a1.publication_set.all()
<QuerySet [<Publication: Publication object (1)>]>
 
 
# 모델 간 관계에서도 다양한 필드 검색이 가능하다.
>>> Publication.objects.filter(albums=a1)
<QuerySet [<Publication: Publication object (1)>]>
 
>>> Publication.objects.filter(albums__pk=1)
<QuerySet [<Publication: Publication object (1)>]>
 
>>> Publication.objects.filter(albums__id=1)
<QuerySet [<Publication: Publication object (1)>]>
 
>>> Publication.objects.filter(albums=1)
<QuerySet [<Publication: Publication object (1)>]>
 
>>> Publication.objects.filter(albums__name__startswidth='Django')
<QuerySet [<Publication: Publication object (1)>]>
 
>>> Publication.objects.filter(albums__in=[a1])
<QuerySet [<Publication: Publication object (1)>]>
 
>>> Publication.objects.filter(albums__name__startswidth='Django').count()
1
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 반대 방향으로도 필드 검색이 가능하다.
>>> Album.objects.filter(publication=p1)
<QuerySet [<Album: Django>]>
 
>>> Album.objects.filter(publication=1)
<QuerySet [<Album: Django>]>
 
>>> Album.objects.filter(publication__title__startswidth='The')
<QuerySet [<Album: Django>]>
 
>>> Album.objects.filter(publication__in=[p1])
<QuerySet [<Album: Django>]>
 
# 모델 간 관계에서도 filter() 와 마찬가지로 exclude() 가 가능하다.
>>> Publication.objects.exclude(albums=a1)
<QuerySet [<Publication: Publication object (2)><Publication: Publication object (3)>]>
 
 
 
# [삭제1] 실습을 위해 앨범 a2 와 출판물 p2 간에 관계를 연결한다.
>>> a2 = Album.objects.get(name='TestAlbum2')
>>> a2.publication_set.add(p2)
 
>>> a2.publication_set.all()
<QuerySet [<Publication: Publication objects (2)>]>
 
>>> p2.albums.all()
<QuerySet [<Album: TestAlbum2>]
 
 
# 앨범을 삭제한다. 2개의 레코드가 삭제된다.
>>> a2.delete()
(2, {'photo.Photo'0'photo.Publication_albums'1'photo.Album'1})
 
# 앨범이 삭제된걸 확인한다.
>>> Album.objects.all()
<QuerySet [<Album: Django><Album: Nature><Album: TestAlbum1><Album: 국가별><Album: 사람들>]>
 
 
# 그런데 주의할점은 p2 출판물은 삭제가 되지 않는다는 것이다.
# (FoeignKey의 CASCADE 와는 다르다.)
>>> Publication.objects.all()
<QuerySet [<Publication: Publication object (1)><Publication: Publication object (2)><Publication: Publication object (3)>]>
 
# 삭제된 a2로 연결된 Publication 확인해도 연결이 끊어져서 조회가 안된다.
>>> a2.publication_set.all()
Trackback (most recent call last):
  File "<console>", line 1in <module> 
  File "/home/......related.py", line 527in __get___
  ....
ValueError: "<Album: TestAlbum2>" needs to have a value filed "id" before this many-to-many relationship can be used.
 
 
# p2 출판물에 연결된 앨범이 없다.
>>> p2.albums.all()
<QuerySet []>
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# [삭제2] 실습을 위해 앨범 a3 과 출판물 p3 간에 연결시킨다.
>>> a3 = Album.objects.get(name='TestAlbum1')
>>> p3.albums.add(a3)
# a3.publication_set.add(p3) 와 동일
 
 
>>> p3.albums.all()
<QuerySet [<Album: TestAlbum1>]>
 
>>> a3.publication_set.all()
<QuerySet [<Publication: Publication object (3)>]>
 
# 이번에는 출판물 쪽에서 삭제한다. 2개의 레코드가 삭제된다.
>>> p3.delete()
(2, {'photo.Publication_albums'1'photo.Publication'1})
 
# p3 출판물이 삭제되었다.
>>> Publicatioin.objects.all()
<QuerySet [<Publication: Publication object (1)><Publication: Publication object (2)>]>
 
# a3 앨범은 삭제되지 않았다.
>>> Album.objects.all()
<QuerySet [<Album: Django><Album: Nature><Album: TestAlbum1><Album: 국가별><Album: 사람들>]>
 
# 연결이 끊어져 p3 조회가 안된다.
>>> p3.albums_all()
Trackback (most recent call last):
  File "<console>", line 1in <module> 
  File "/home/......related.py", line 527in __get___
  ....
ValueError: "<Publication: Publication object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.
 
# a3 앨범에 연결된 출판물이 없습니다.
>>> a3.publication_set.all()
<QuerySet []>
cs

 

참고 : [실전편] Django 를 활용한 쉽고 빠른 웹개발 파이썬 웹프로그래밍 (김석훈) , 설명예제

참고 : https://fierycoding.tistory.com/65