venv와 docker 환경 설정
python3 -m venv venv
source venv/bin/activate
pip install django
pip install djangorestframework
pip install psycopg2
#Dockerfile 작성
#docker-compse.yml 작성
docker build . #docker build -t app . 으로 이미지에 이름붙이기
docker-compose run web django-admin startproject newsapi .
sudo chown -R user ./ #수정권한 부여
#settings.py에서 DATABASES 설정
docker-compose up -d #서비스 실행 후 콘솔로 빠져나오기
docker-compose exec web python manage.py startapp news
#startapp하고 꼭 바로 INSTALLED_APPS에 추가!
#'rest_framework'도 잊지말고 추가!
models.py 작성
#news/models.py
from django.db import models
class Article(models.Model):
author = models.CharField(max_length=50)
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}"
makemigrations & migrate
docker-compose exec web python manage.py makemigrations
docker-compose exec web python manage.py migrate
createsuperuser
docker-compose exec web python manage.py createsuperuser
admin.py register & runserver
#news/admin.py
from django.contrib import admin
from news.models import Article
admin.site.register(Article)
docker-compose exec web python manage.py runserver
먼저, serializers.py를 작성합니다.
#news/api/serializers.py
from rest_framework import serializers
from news.models import Article
class ArticleSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
author = serializers.CharField()
title = serializers.CharField()
description = serializers.CharField() # model에서 TextField
body = serializers.CharField()
location = serializers.CharField()
publication_data = serializers.DateField()
active = serializers.BooleanField()
created_at = serializers.DateTimeField(read_only=True)
updated_at = serializers.DateTimeField(read_only=True)
def create(self, validated_data):
print(validated_data)
return Article.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.author = validated_data.get('author', instance.author)
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get(
'description', instance.description)
instance.body = validated_data.get('body', instance.body)
instance.location = validated_data.get('location', instance.location)
instance.publication_data = validated_data.get(
'publication_data', instance.publication_data)
instance.active = validated_data.get('active', instance.active)
instance.save()
return instance
그리고 shell을 통해 serialization과 deserialization과정을 살펴봅니다.
#django shell
>>> from news.models import Article
>>> from news.api.serializers import ArticleSerializer
>>> article_instance = Article.objects.first()
>>> article_instance
<Article: John Doe Happy Birthday>
# complex data(e.g.queryset, model instances) -> native Python data type
>>> serializer = ArticleSerializer(article_instance)
>>> serializer
ArticleSerializer(<Article: John Doe Happy Birthday>):
id = IntegerField(read_only=True)
author = CharField()
title = CharField()
description = CharField()
body = CharField()
location = CharField()
publication_date = DateField()
active = BooleanField()
created_at = DateTimeField(read_only=True)
updated_at = DateTimeField(read_only=True)
>>> serializer.data
{'id': 1, 'author': 'John Doe', 'title': 'Happy Birthday', 'description': 'some fancy description', 'body': 'content', 'location': 'Earth', 'publication_date': '2021-06-10', 'active': True, 'created_at': '2021-06-10T06:35:39.175145Z', 'updated_at': '2021-06-10T06:35:39.175167Z'}
# native Python data types -> JSON
>>> from rest_framework.renderers import JSONRenderer
>>> json = JSONRenderer().render(serializer.data)
>>> json
b'{"id":1,"author":"John Doe","title":"Happy Birthday","description":"some fancy description","body":"content","location":"Earth","publication_date":"2021-06-10","active":true,"created_at":"2021-06-10T06:35:39.175145Z","updated_at":"2021-06-10T06:35:39.175167Z"}'
# JSON -> native Python data types
>>> import io
>>> from rest_framework.parsers import JSONParser
>>> stream = io.BytesIO(json)
>>> data = JSONParser().parse(stream)
>>> data
{'id': 1, 'author': 'John Doe', 'title': 'Happy Birthday', 'description': 'some fancy description', 'body': 'content', 'location': 'Earth', 'publication_date': '2021-06-10', 'active': True, 'created_at': '2021-06-10T06:35:39.175145Z', 'updated_at': '2021-06-10T06:35:39.175167Z'}
# native Python data types -> complex data(e.g.queryset, model instances)
>>> serializer = ArticleSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.validated_data
OrderedDict([('author', 'John Doe'), ('title', 'Happy Birthday'), ('description', 'some fancy description'), ('body', 'content'), ('location', 'Earth'), ('publication_date', datetime.date(2021, 6, 10)), ('active', True)])
>>> serializer.save()
{'author': 'John Doe', 'title': 'Happy Birthday', 'description': 'some fancy description', 'body': 'content', 'location': 'Earth', 'publication_date': datetime.date(2021, 6, 10), 'active': True}
<Article: John Doe Happy Birthday>
#save()를 통해 Article객체 추가됨
>>> Article.objects.all()
<QuerySet [<Article: John Doe Happy Birthday>, <Article: John Doe Happy Birthday>]>
@api_view를 통해 function based view를 작성합니다.
#news/api/views.py
from rest_framework import serializers, status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from news.models import Article
from news.api.serializers import ArticleSerializer
@api_view(["GET", "POST"])
def article_list_create_api_view(request):
if request.method == "GET":
articles = Article.objects.filter(active=True)
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
elif request.method == "POST":
serializer = ArticleSerializer(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)
api view에 해당되는 endpoint를 설정합니다.
#news/api/urls.py
from django.urls import path
from news.api.views import article_list_create_api_view
urlpatterns = [
path("articles/", article_list_create_api_view, name="article-list")
]
GET, PUT, DELETE에 해당하는 부분까지 모두 구현합니다.
#news/api/views.py
@api_view(["GET", "PUT", "DELETE"])
def article_detail_api_view(request, pk):
try:
article = Article.objects.get(pk=pk)
except Article.DoesNotExist:
return Response({"error": {
"code": 404,
"message": "Article not found!"
}}, status=status.HTTP_404_NOT_FOUND)
if request.method == "GET":
serializer = ArticleSerializer(article)
return Response(serializer.data)
elif request.method == "PUT":
serializer = ArticleSerializer(article, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "DELETE":
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
api view에 해당되는 endpoint를 설정합니다.
#news/api/urls.py
from django.urls import path
from news.api.views import article_list_create_api_view, article_detail_api_view
urlpatterns = [
path("articles/", article_list_create_api_view, name="article-list"),
path("articles/<int:pk>/", article_detail_api_view, name="article-detail")
]
위에서 작성한 FBV(function based view)를 APIView를 통해 class based view로 작성합니다.
#news/api/views.py
from rest_framework.views import APIView
from rest_framework.generics import get_object_or_404
class ArticleListCreateAPIView(APIView):
def get(self, request):
articles = Article.objects.filter(active=True)
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
def post(self, request):
serializer = ArticleSerializer(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)
class ArticleDetailAPIView(APIView):
def get_object(self, pk):
article = get_object_or_404(Article, pk=pk)
return article
def get(self, request, pk):
article = self.get_object(pk)
serializer = ArticleSerializer(article)
return Response(serializer.data)
def put(self, request, pk):
article = self.get_object(pk)
serializer = ArticleSerializer(article, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
article = self.get_object(pk)
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
#news/api/urls.py
from django.urls import path
from news.api.views import ArticleListCreateAPIView, ArticleDetailAPIView
urlpatterns = [
path("articles/", ArticleListCreateAPIView.as_view(), name="article-list"),
path("articles/<int:pk>/", ArticleDetailAPIView.as_view(), name="article-detail")
]
is_valid() 부분을 customizing할 수 있습니다.
object level validaion이 있고, field level validation이 있습니다.
이를 serializers.py에 적용한 결과는 다음과 같습니다.
from rest_framework import serializers
from news.models import Article
class ArticleSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
author = serializers.CharField()
title = serializers.CharField()
description = serializers.CharField() # model에서 TextField인거
body = serializers.CharField()
location = serializers.CharField()
publication_date = serializers.DateField()
active = serializers.BooleanField()
created_at = serializers.DateTimeField(read_only=True)
updated_at = serializers.DateTimeField(read_only=True)
def create(self, validated_data):
print(validated_data)
return Article.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.author = validated_data.get('author', instance.author)
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get(
'description', instance.description)
instance.body = validated_data.get('body', instance.body)
instance.location = validated_data.get('location', instance.location)
instance.publication_date = validated_data.get(
'publication_date', instance.publication_date)
instance.active = validated_data.get('active', instance.active)
instance.save()
return instance
# object level validaion
def validate(self, data):
""" check that description and title are different """
if data["title"] == data["description"]:
raise serializers.ValidationError(
"Title and Description must be different")
return data
# field level validation
def validate_title(self, value):
if len(value) < 60:
raise serializers.ValidationError(
"The title has to be at least 60 char")
return value
앞에서 작성했던 Serializer를 ModelSerializer를 통해 간단히 적을 수 있습니다. 또한, 새로운 필드를 정의해줄 수도 있습니다.
#news/api/serializers.py
from datetime import datetime
from django.utils.timesince import timesince
from rest_framework import serializers
from news.models import Article
class ArticleSerializer(serializers.ModelSerializer):
#새로운 필드
time_since_publication = serializers.SerializerMethodField()
class Meta:
model = Article
exclude = ("id", )
# fields = "__all__" # we want all the fields of our model
# fields=("title", "description", "body") #we want to choos a couple of fields
def get_time_since_publication(self, object):
publication_date = object.publication_date
now = datetime.now()
time_delta = timesince(publication_date, now)
return time_delta
django shell을 통해 확인해보면 다음과 같습니다.
>>> from news.api.serializers import ArticleSerializer
>>> serializer = ArticleSerializer()
>>> print(repr(serializer))
ArticleSerializer():
time_since_publication = SerializerMethodField()
author = CharField(max_length=50)
title = CharField(max_length=120)
description = CharField(max_length=200)
body = CharField(style={'base_template': 'textarea.html'})
location = CharField(max_length=120)
publication_date = DateField()
active = BooleanField(required=False)
created_at = DateTimeField(read_only=True)
updated_at = DateTimeField(read_only=True)
앞서 했던 validation도 ModelSerializer에 그대로 사용할 수 있습니다.
먼저 추가적인 모델을 만들어서 Foreignkey로 연결합니다.
#news/api/models.py
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):
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}"
이때, model이 바뀌었으므로, db를 초기화해야 하는데, 다음 명령어를 통해 db를 초기화하고 새로 migrate를 진행합니다.
docker-compose down --volume #db삭제
docker-compose up -d #재실행
docker-compose exec web python manage.py makemigrations
docker-compose exec web python manage.py migrate
그리고 이를 반영한 serializers.py는 다음과 같습니다.
from datetime import datetime
from django.utils.timesince import timesince
from rest_framework import serializers
from news.models import Article, Journalist
class ArticleSerializer(serializers.ModelSerializer):
# 새로운 필드
time_since_publication = serializers.SerializerMethodField()
author = serializers.StringRelatedField() #string으로 처리
# author = JournalistSerializer(read_only=True)
class Meta:
model = Article
exclude = ("id", )
# fields = "__all__" # we want all the fields of our model
# fields=("title", "description", "body") #we want to choos a couple of fields
def get_time_since_publication(self, object):
publication_date = object.publication_date
now = datetime.now()
time_delta = timesince(publication_date, now)
return time_delta
# object level validaion
def validate(self, data):
""" check that description and title are different """
if data["title"] == data["description"]:
raise serializers.ValidationError(
"Title and Description must be different")
return data
# field level validation
def validate_title(self, value):
if len(value) < 60:
raise serializers.ValidationError(
"The title has to be at least 60 char")
return value
class JournalistSerializer(serializers.ModelSerializer):
articles = serializers.HyperlinkedRelatedField(
many=True, read_only=True, view_name="article-detail") # endpoint가 들어감
#articles = ArticleSerializer(many=True, read_only=True)
class Meta:
model = Journalist
fields = "__all__"