본문 바로가기

문돌이 존버/Django 스터디

장고(Django), 관계를 표현하는 모델 필드 ManyToManyField

반응형
본 글은 Holix의 "리액트와 함께 장고 시작하기 Complete" 강의를 듣고 작성한 일지입니다.

이번에는 ManyToManyField에 대해 알아보려고 합니다. ManyToManyField는 M:N 관계에서 어느 쪽이라도 필드 지정이 가능합니다. 따라서 아래와 같은 2가지 방법이 있는데요.

방법 1)
class Post(models.Model):
    tag_set = models.ManyToManyField('Tag', blank=True)
class Article(models.Model):
    tag_set = models.ManyToManyField('Tag', blank=True)
class Tag(models.Model):
    name = models.CharField(max_length=100, unique=True)

방법 2)
class Post(models.Model):
...
class Article(models.Model):
...
class Tag(models.Model):
    name = models.CharField(max_length=100, unique=True)
    post_set = models.ManyToManyField('Post', blank=True)
    article_set = models.ManyToManyField('Article', blank=True)

Tag를 활용한다는 관점에서 보면 아무래도 방법 1이 방법 2보다 더 적절할 것 같습니다. 하지만 이는 개인의 선택이며 M:N 어느 관계에도 필드 지정이 가능하니 좀 더 끌리는 곳을 선택하면 되겠습니다.

class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    content = models.TextField()
    photo = models.ImageField(blank=True, upload_to=uuid_name_upload_to) # blank=True: 옵션 필드(디폴트는 False)
    # 파이썬 클래스는 순서대로 진행, Tag를 참조하는데 Post 클래스 이전에 해당 클래스가 없기 때문에 문법 오류로 표시
    # 이때는 문자열로 대체 가능
    tag_set = models.ManyToManyField('Tag', blank=True) # tag는 없어도 되므로 blank=True, 그렇지 않으면 장고 form 유효성 검사 통과 X
    ...
    
class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)
    
    def __str__(self):
        return self.name
python manage.py makemigrations blog1
python manage.py migrate blog1

ManyToManyField를 적용하면 이전의 ForeignKey, OneToOneField와 달리 테이블이 하나 더 생성됩니다. Post와 Tag 클래스를 OneToOneField로 관계를 맺었기 때문에 아래와 같은 이름으로 생성되는데, 이는 장고에서 자동으로 설정하는 중간 테이블입니다.

post = Post.objects.last()
post.tag_set.all() # reverse name이기도 하지만 여기선 필드 이름으로 지정

tag = Tag.objects.first()
tag.post_set.all() # reverse name 활용

Tag.objects.create(name='django')
Tag.objects.create(name='AI')

tag = Tag.objects.get(name='django')
post.tag_set.add(tag)
post.tag_set.remove(tag)

하나씩 태그를 추가하려면 위 방법을 사용하면 되는데, 전체 테크를 다 추가하기 위해선 아래 방법을 사용하면 됩니다.

tag_qs = Tag.objects.all()
post.tag_set.add(*tag_qs) # 파이썬의 unpack 문법: 하나의 인자에 들어있는 것을 여러 개에 풀어주는 방법
post.tag_set.all()

# 파이썬 unpack 문법
def myfn(a, b, c):
    print(f'a={}, b={b}, c={c}')

myfn(1, 2, 3) # a=1, b=2, c=3

params = [1, 2, 3]
myfn(*params) # a=1, b=2, c=3

참고

장고는 기본적으로 RDBMS를 지원하지만 DB에 따라 NoSQL 기능도 지원합니다. 하나의 Post 안에 다수의 댓글을 저장할 수 있는 것이 간단한 예시인데요. 아래 라이브러리 정보를 참고하여 json 타입의 데이터가 필요하다면 충분히 사용해볼 수 있겠습니다.

djkoch/jsonfield
- 대개의 DB엔진에서 사용 가능
- TextField/CharField를 래핑
- dict 등의 타입에 대한 저장을 직렬화(serialize)하여 문자열로 저장
  하지만 내부 필드에 대해 쿼리 불가

django.contrib.postgres.fields.JSONField
- 내부적으로 PostgreSQL의 jsonb 타입(json binary)
- 내부 필드에 대해 쿼리 지원

adamchainz/django-mysql
- MySQL 5.7 이상에서 json 필드 지원
728x90
반응형