All Articles

Django Rest Framework 활용하기 6 - Nested Relationships

Udemy 강의를 들으면서, DRF 공식문서를 보면서, 그리고 구글링하면서 정리한 내용입니다.

Django REST Framework - Level One

Nested Relationships

ForeignKey를 이용해 두 테이블을 연결하는 방법을 알아보자. 장고에서 FK를 걸던 방식과 동일하다.

from django.db import models

# 추가
class Journalist(models.Model):
  first_name = models.CharField(max_length = 60)
  last_name  = models.CharField(max_length = 60)
  biography  = models.TextField(blank = True)

  def __str__(self):
    return f'{self.first_name} {self.last_name}'


class Article(models.Model):
  # ForeignKey 필드로 변경
  author          = models.ForeignKey(Journalist,
                                      on_delete = models.CASCADE,
                                      related_name = 'articles')
  title            = models.CharField(max_length = 120)
  description      = models.CharField(max_length = 200)
  body             = models.TextField()
  location         = models.CharField(max_length = 120)
  publication_date = models.DateField()
  active           = models.BooleanField(default = True)
  created_at       = models.DateTimeField(auto_now_add = True)
  updated_at       = models.DateTimeField(auto_now = True)

  def __str__(self):
    return f"{self.author} {self.title}"

Journalist 테이블 생성 후, Article 테이블 내 CharField였던 author 필드 속성을 ForeignKey로 바꿔주고 Journalist와 연결해준다.

Nested relationship

Journalist 테이블의 1번에 해당하는 저자가 발행한 기사임을 확인할 수 있다. 이것을 좀 더 명확하게 표시할 수도 있다.

serializers.py에서 author 항목을 추가한다.

class ArticleSerializer(serializers.ModelSerializer):
  time_since_publication = serializers.SerializerMethodField()
  # 추가
  author = serializers.StringRelatedField()

Nested relationship

author 항목에 integer 말고 이름이 표시된다. models.py에 __str__ 메소드를 이용해 firstname과 lastname이 출력되도록 지정했기 때문이다.

  def __str__(self):
    return f"{self.author} {self.title}"

출력되는 이름을 지정하지 않았다면 "Journalist object (1)" 이렇게 객체로 출력된다.

참조하는 테이블의 모든 필드 보기

author 이름만 출력하는 것 말고도 참고하는 테이블 행의 모든 값을 볼 수도 있다.

serializers.py에 JournalistSerializer 클래스를 추가하고, ArticleSerializer에 추가해준다.

# Journalist 테이블 데이터를 직렬화 할 클래스 추가
class JournalistSerializer(serializers.ModelSerializer):

  class Meta:
    model = Journalist
    # 테이블 내 모든 필드 출력
    fields = '__all__'


class ArticleSerializer(serializers.ModelSerializer):
  time_since_publication = serializers.SerializerMethodField()
  # author = serializers.StringRelatedField()에서 변경
  author = JournalistSerializer(read_onle = True)

Nested relationship

author Jane Doe에 대한 Journalist 테이블 내 모든 값이 출력된다.

반대로 Journalist api에서도 해당 journalist의 모든 Article을 볼 수도 있다. Journalist api를 생성하기 위해 serializers.py와 views.py, urls.py를 손봐준다.

먼저 serializers.py다. JournalistSerializer가 ArticleSerializer를 끌어와야 하므로 순서를 바꿔준다.

class ArticleSerializer(serializers.ModelSerializer):
  time_since_publication = serializers.SerializerMethodField()
  # 필드를 없애주지 않으면 'JournalistSerializer'가 정의되지 않았으므로 NameError가 발생한다.
  #author = JournalistSerializer(read_onle = True)

  '''
  이하 ArticleSerializer 코드 생략
  '''

class JournalistSerializer(serializers.ModelSerializer):
  # 한 저자가 여러 article을 가질 수 있으므로 many = True 속성 추가
  articles = ArticleSerializer(many = True, read_only = True)

  class Meta:
    model = Journalist
    fields = '__all__'

다음은 views.py에서 JournalistListCreateAPIView 클래스를 만들어준다. 이전에 만들었던 ArticleListCreateAPIView 클래스와 뼈대는 같다.

class JournalistListCreateAPIView(APIView):
  def get(self, request):
    # 모든 저자를 불러올 것이므로 ORM은 .all()을 사용한다.
    journalists = Journalist.objects.all()
    serializer = JournalistSerializer(journalists, many = True)
    return Response(serializer.data)

  def post(self, request):
    serializer = JournalistSerializer(data = request.data)
    if serializer.is_valid():
      serializer.save()
      return Response(serializer.data, status = status.HTTP_201_CREATED)
    return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

urls.py에서 api url도 만들어준다.

urlpatterns = [
  '''
  이전 path 생략
  '''
  path('journalists/',
    JournalistListCreateAPIView.as_view(),
    name = 'journalist-list'
  )
]

localhost:포트번호/api/journalists/로 접속하면 아래처럼 journalist와 해당 journalist의 모든 article이 출력되는 것을 볼 수 있다.

Nested relationship

하이퍼링크로 연결하기

HyperlinkedRelatedField를 이용하면 journalist의 article을 링크로 연결할 수도 있다. serializers.py와 views.py를 수정해준다.

먼저 serializers.py에서 articles 항목을 HyperlinkedRelatedField로 바꿔준다. view_name에는 urls.py에서 name으로 설정한 값을 넣어준다.

class JournalistSerializer(serializers.ModelSerializer):
  articles = serializers.HyperlinkedRelatedField(many = True,
                                                 read_only = True,
                                                 view_name = 'article-detail')
  class Meta:
    model = Journalist
    fields = '__all__'

views.py에는 context를 추가한다. context가 있어야만 article에 접근하는 절대(absolute) URL을 빌드할 수 있다.

class JournalistListCreateAPIView(APIView):
  def get(self, request):
    journalists = Journalist.objects.all()
    # context 추가
    serializer = JournalistSerializer(journalists,
                                      many = True,
                                      context = {'request' : request})
    return Response(serializer.data)

  def post(self, request):
    serializer = JournalistSerializer(data = request.data)
    if serializer.is_valid():
      serializer.save()
      return Response(serializer.data, status = status.HTTP_201_CREATED)
    return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

Nested relationship

Json 형태로 함께 출력되던 article이 링크로 대체됐다. 이 링크를 클릭하면 해당하는 article 인스턴스의 세부 내용을 볼 수 있다.

Nested relationship