이전 포스팅에서 Model Serializers를 사용하여 Model을 사용하는 방법에 대해 알아봤습니다.
https://android-developer.tistory.com/84
이번 포스팅에서는 다수의 Model에 관계를 부여하는 방법에 대해 알아보겠습니다.
Model의 관계(Relationship)은 다음과 같은 종류가 있습니다.
- Many-to many relationships
- One-to One relationships
- Many to one relationships
Model 하나 더 생성하기
이전 방법과 똑같이 Model을 하나 더 생성하면 됩니다.
단, 조건은 다음과 같습니다.
- 모델 이름은 StreamPlatform
- id, name, about, website라는 아이템을 가짐
- movie/stream 에서 해당 Model의 리스트를 볼 수 있어야함(= 현재 movie/list와 동일하게)
정답은 아래의 풀코드를 확인하시길 바랍니다.
# api/serializers.p
from rest_framework import serializers
from watchlist_app.models import Movie, StreamPlatform
class StreamPlatfromSerializer(serializers.ModelSerializer):
class Meta:
model = StreamPlatform
fields = "__all__"
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField()
class Meta:
model = Movie
# 모든 Field를 사용하게 한다.
fields = "__all__"
# 사용하고 싶은 Field를 지정하고 싶으면 다음과 같이 한다
# fields = ['id', 'name', 'description']
# 또는 특정 Field만 제외하고싶다면 다음과 같이 한다.
# exclude = ['activity']
def get_len_name(self, object):
return len(object.name)
def validate(self, data):
if data['name'] == data['description']:
raise serializers.ValidationError("이름과 내용이 같아선 안됩니다.")
else:
return data
# field level validation
# validate_필드 이름으로 만들면 자동으로 오버라이드 해준다.
def validate_name(self, value):
if len(value) < 2:
raise serializers.ValidationError("이름이 너무 짧습니다.")
else :
return value
# api/urls.py
from django.urls import path
from api.views import MovieListAV, MovieDetailAV, StreamPlatformAV, StreamPlatformDetailAV
urlpatterns = [
path('list/', MovieListAV.as_view(), name='movie-list'),
path('list/<int:pk>', MovieDetailAV.as_view(), name='movie-detail'),
path('stream/', StreamPlatformAV.as_view(), name='stream-list'),
path('stream/<int:pk>', StreamPlatformDetailAV.as_view(), name='stream-detail'),
]
# api/views.py
from watchlist_app.models import Movie, StreamPlatform
from api.serializers import MovieSerializer, StreamPlatfromSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status
from rest_framework.views import APIView
class StreamPlatformDetailAV(APIView):
def get(self, request, pk):
try:
platform = StreamPlatform.objects.get(pk=pk)
except StreamPlatform.DoesNotExist:
return Response({'Error': '존재하지 않는 데이터를 참조했습니다'}, status=status.HTTP_404_NOT_FOUND)
serializer = StreamPlatfromSerializer(platform)
return Response(serializer.data)
def put(self, request, pk):
platform = StreamPlatform.objects.get(pk=pk)
serializer = StreamPlatfromSerializer(movie, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
platform = StreamPlatform.objects.get(pk=pk)
platform.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class StreamPlatformAV(APIView):
def get(self, request):
platform = StreamPlatform.objects.all()
serializer = StreamPlatfromSerializer(platform, many=True)
return Response(serializer.data)
def post(self, request):
serializer = StreamPlatfromSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors)
class MovieListAV(APIView):
def get(self, request):
movies = Movie.objects.all()
serializer = MovieSerializer(movies, many=True)
return Response(serializer.data)
def post(self, request):
serializer = MovieSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors)
class MovieDetailAV(APIView):
def get(self, request, pk):
try:
movie = Movie.objects.get(pk=pk)
except Movie.DoesNotExist:
return Response({'Error': '존재하지 않는 데이터를 참조했습니다'}, status=status.HTTP_404_NOT_FOUND)
serializer = MovieSerializer(movie)
return Response(serializer.data)
def put(self, request, pk):
movie = Movie.objects.get(pk=pk)
serializer = MovieSerializer(movie, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
movie = Movie.objects.get(pk=pk)
movie.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# 해당앱폴더/admin.py
from django.contrib import admin
from watchlist_app.models import Movie, StreamPlatform
# Register your models here.
admin.site.register(Movie)
admin.site.register(StreamPlatform)
# 해당앱폴더/models.py
from django.db import models
class StreamPlatform(models.Model):
# Model(=DB)에서 사용할 Column 정의
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=50)
about = models.TextField(max_length=200)
website = models.URLField(max_length=100)
def __str__(self):
return self.name
class Movie(models.Model):
# Model(=DB)에서 사용할 Column 정의
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=50)
description = models.TextField(max_length=200)
active = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
Many-to many relationships
장고의 공식 문서에 따르면
https://docs.djangoproject.com/en/5.0/topics/db/examples/many_to_many/
Many-to-many relationships는 2개 이상의 Model이 여러 Model의 아이템으로써 사용되는 관계입니다.
예를 들어 다음과 같은 Model의 관계를 생각해볼 수 있습니다.
from django.db import models
class Publication(models.Model):
title = models.CharField(max_length=30)
class Meta:
ordering = ["title"]
def __str__(self):
return self.title
class Article(models.Model):
headline = models.CharField(max_length=100)
# Article Model 안에서 Publication Model과 관계를 맺음
# ManyToManyField를 사용할 경우 반대로 Publication에서 article_set이라는 항목을 자동으로 추가함
publications = models.ManyToManyField(Publication)
class Meta:
ordering = ["headline"]
def __str__(self):
return self.headline
ManyToManyField 같이 다수의 Model을 참조하는 함수를 사용하면 반대 Model에서도 자동으로 "Model이름_set"이라는 이름으로 항목을 생성합니다.
신문사(Publication)은 여러 개의 신문 기사(Article)을 낼 수 있고 신문 기사는 여러 신문사에서 발간될 수 있습니다.
그렇기 때문에 Many-to-many relationships라고 할 수 있습니다.
예를 들어 다음과 같이 여러 신문사를 만듭니다.
>>> p1 = Publication(title="The Python Journal")
>>> p1.save()
>>> p2 = Publication(title="Science News")
>>> p2.save()
>>> p3 = Publication(title="Science Weekly")
>>> p3.save()
그리고 다음과 같이 신문 기사를 하나 생성합니다.
>>> a1 = Article(headline="Django lets you build web apps easily")
>>> a1.save()
그리고 해당 신문 기사에 신문사를 추가해줍니다.
>>> a1.publications.add(p1)
그리고 신문을 하나 더 생성하고 해당 신문을 여러 신문사에서 발간했다고 생각합니다.
>>> a2 = Article(headline="NASA uses Python")
>>> a2.save()
>>> a2.publications.add(p1, p2)
>>> a2.publications.add(p3)
여기까지 보면 현재 3개의 신문사와 2개의 신문 기사가 있고 신문 기사 2개를 여러 신문사가 발간했다는 것을 알 수 있습니다.
그렇기 때문에 하나의 신문 기사를 어떤 신문사에서 발간했는지 다음과 같은 방법으로 확인할 수 있습니다.
>>> a1.publications.all()
<QuerySet [<Publication: The Python Journal>]>
>>> a2.publications.all()
<QuerySet [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>]>
그리고 반대로 특정 신문사에서 발간한 모든 뉴스 기사를 확인할 수 있습니다.
>>> p2.article_set.all()
<QuerySet [<Article: NASA uses Python>]>
>>> p1.article_set.all()
<QuerySet [<Article: Django lets you build web apps easily>, <Article: NASA uses Python>]>
>>> Publication.objects.get(id=4).article_set.all()
<QuerySet [<Article: NASA uses Python>]>
One-to One relationships
One-to One relationships은 특정 Model과 1:1 관계를 갖는 갖는 것을 의미합니다.
OneToOneField 함수를 통해 구현할 수 있습니다.
예시는 다음과 같습니다.
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return f"{self.name} the place"
class Restaurant(models.Model):
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
primary_key=True,
)
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
class Waiter(models.Model):]
# models.CASCADE는 부모 모델이 삭제될 때 자식 모델의 행동을 정의한다.
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
def __str__(self):
return "%s the waiter at %s" % (self.name, self.restaurant)
Place는 장소를 의미하고 해당 장소에는 하나의 Restaurant만 있을 수 있습니다.
그리고 Restaurant에는 Waiter가 존재합니다.
하나의 Restaurant에는 다수의 Waiter가 존재할 수 있습니다.
위 코드에서 각각의 Model은 다음과 같은 특징을 갖습니다.
Place
- 3개의 항목을 갖고 있다
- Restaurant의 models.OneToOneField 함수에 의해 restraurant라는 항목이 자동으로 생성된다.
Restarurant
- Place Model과 1:1 관계를 가지는 항목을 하나 갖고 있다.(models.OneToOneField)
- Place가 삭제되면 해당 Restaurant도 삭제(on_delete=models.CASCADE)
- 반대로 특정 restaurant가 삭제된다고 해서 해당 place가 삭제되지는 않는다.
- restaurant의 각 아이템이 place의 각 아이템과 정확히 일치해야한다.(primary_key=True)
- 즉, restaurant의 1번 아이템 = place의 1번 아이템
- 각 Model의 각 아이템이 1:1 매핑되는 것을 의미
- Place가 삭제되면 해당 Restaurant도 삭제(on_delete=models.CASCADE)
- 총 항목을 3개 갖고 있다.
Waiter
- restaurant Model과 외래키 관계를 갖고 있다.
- 특정 restaurant가 삭제되면 해당 waiter Model도 삭제됨을 의미한다.(on_delete=models.CASCADE)
- 총 2개의 항목을 갖고 있다
다음과 같이 Place 아이템을 2개 생성합니다.
>>> p1 = Place(name="Demon Dogs", address="944 W. Fullerton")
>>> p1.save()
>>> p2 = Place(name="Ace Hardware", address="1013 N. Ashland")
>>> p2.save()
그리고 p1에 대해 restaurant Model을 하나 생성해줍니다.
>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
만약 Place Model에 등록되지 않은 Restaurant가 있다면 다음과 같은 방법으로 확인할 수 있습니다.
>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
... p2.restaurant
... except ObjectDoesNotExist:
... print("There is no restaurant here.")
...
There is no restaurant here.
또는 hasattr 함수를 사용해서 해당 Model에서 특정 항목에 대한 값을 갖고 있는지 확인할 수 있다.
>>> hasattr(p2, "restaurant")
False
그렇기 때문에 다음과 같이 place를 restaurant에 등록하거나
>>> r.place = p2
>>> r.save()
>>> p2.restaurant
<Restaurant: Ace Hardware the restaurant>
>>> r.place
<Place: Ace Hardware the place>
반대로 restaurant를 place에 등록할 수 있다.
>>> p1.restaurant = r
>>> p1.restaurant
<Restaurant: Demon Dogs the restaurant>
Many to one relationships
Many to one relationships는 ForeignKey 함수로 사용할 수 있습니다.
예시 코드는 다음과 같습니다.
from django.db import models
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
def __str__(self):
return self.headline
class Meta:
ordering = ["headline"]
Reporter는 여러 개의 Article을 쓸 수 있지만
Article에는 하나의 Reporter만 존재할 수 있습니다.
각 Model은 다음과 같은 특징을 가집니다.
Reporter
- 4개의 항목을 갖고 있습니다.
- Article에서 ForeignKey를 사용하여 다수의 Model 아이템을 참조할 수 있게 했기 때문에 "모델이름_set"이라는 이름으로 항목이 자동으로 추가된다
- 그래서 여기서는 article_set이라는 이름으로 항목이 추가될 예정
Article
- 3개의 항목을 갖고 있습니다.
- Repoter Model과 연결된 항목을 갖습니다
- 해당 Reporter Model이 삭제되면 해당 Article도 삭제됩니다.(on_delete=models.CASCADE)
다음과 2개의 Reporter 데이터를 생성합니다.
>>> r = Reporter(first_name="John", last_name="Smith", email="john@example.com")
>>> r.save()
>>> r2 = Reporter(first_name="Paul", last_name="Jones", email="paul@example.com")
>>> r2.save()
다음과 같이 Article 데이터를 1개 생성합니다.
>>> from datetime import date
# 반드시 이미 저장되어있는 reporter를 여기서 사용해야한다.
>>> a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27), reporter=r)
>>> a.save()
>>> a.reporter.id
1
>>> a.reporter
<Reporter: John Smith>
반대로 Article에 아이템을 만들 때도 Reporter의 아이템을 참조할 수 있습니다.
>>> new_article = r.article_set.create(
... headline="John's second story", pub_date=date(2005, 7, 29)
... )
>>> new_article
<Article: John's second story>
>>> new_article.reporter
<Reporter: John Smith>
>>> new_article.reporter.id
1
'파이썬(Python) > 장고(Django)' 카테고리의 다른 글
[기초] 파이썬 장고 REST API - Serializer relations (1) | 2024.02.18 |
---|---|
[기초] 파이썬 장고 REST API - Nested Relationships (0) | 2024.02.15 |
[기초] 장고 REST API 프레임워크 - Model Serializer (0) | 2024.02.05 |
[기초] 파이썬 장고 REST API 프레임워크 - Validation (0) | 2024.02.02 |
[기초] 파이썬 장고 REST API 프레임 워크 - Class-based Views (0) | 2024.02.01 |
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
댓글