$ git branch -d work3
error: The branch 'work3'isnot fully merged.
If you are sure you want to delete it, run 'git branch -D work3'.
$ git branch -d work_new
Deleted branch work_new (was 4bbc62f).
# api/serializers.pyfrom rest_framework import serializers
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
# 회원가입classCreateUserSerializer(serializers.ModelSerializer):classMeta:
model = User
fields = ("id", "username", "password")
extra_kwargs = {"password": {"write_only": True}}
defcreate(self, validated_data):
user = User.objects.create_user(
validated_data["username"], None, validated_data["password"]
)
return user
# 접속 유지중인지 확인classUserSerializer(serializers.ModelSerializer):classMeta:
model = User
fields = ("id", "username")
# 로그인classLoginUserSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
defvalidate(self, data):
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError("Unable to log in with provided credentials.")
지난 시간에 해당 주소로 요청을 보내면 hello world를 print하는 api를 만들었습니다.
각 단계를 다시 따라가보며 세부적인 내용을 살펴보겠습니다.
첫번째 단계는 views.py를 작성하는 것입니다.
현재 사용하고 있는 view의 구조는 함수 기반 뷰, 이른바 FBV 입니다.
# api/views.pyfrom rest_framework.response import Response
from rest_framework.decorators import api_view
# Create your views here.@api_view(['GET']) # 해당 함수 view에서 처리할 http 메소드defHelloAPI(request):return Response("hello world!") # http response 형태로 return
views.py에서 다룬 내용은 그냥 Django에서 다룬 것과 크게 다를 것 없이,
요청을 받아 해당 요청에 대한 응답을 제공하는 기능을 합니다.
오늘 배우게 될 serializer와 함께 하면 어떻게 할 수 있는지 이따 알아보겠습니다.
그 다음은 api/urls.py 입니다.
# api/urls.pyfrom django.urls import path, include
from .views import HelloAPI
urlpatterns = [
path("hello/", HelloAPI),
]
크게 특별할 것 없이 view에 있는 HelloAPI를 가져와 url로 할당시켰습니다.
path 사용을 권장한다는 것을 지난 시간에 말씀드렸습니다.
2. Serializer?
Serializer는 Django Rest Framework에서 나온 새로운 요소입니다.
사전적 의미는 직렬화 하는 무언가 정도로 볼 수 있습니다.
직렬화라는 말이 와닿지 않는데,
이는 간단하게 파이썬 데이터를 JSON 타입의 데이터로 변환해준다 정도로 생각하시면 됩니다.
기본적으로 웹에서 통신을 할 때, 즉 데이터를 주고 받을 때는 어느 정도 정해진 포맷이 있습니다.
대표적인 타입이 JSON이나 XML인데, 대부분의 REST API에서는 JSON으로 주고 받기 때문에
우리는 그냥 JSON만 잘 알고 있으면 됩니다:)
정확한 의미의 직렬화는 Django 프로젝트에서 내가 만든 모델로부터 뽑은 queryset,
즉 모델 인스턴스를 JSON 타입으로 바꾸는 것입니다.
기존 일반적인 Django에서의 폼과 같은 개념이라고 비유하는 경우가 있는데,
저는 그냥 Django 모델을 JSON으로 변환하기 위한 모양 틀 정도로 이해하고 있고 그게 제일 깔끔한 것 같습니다!
간단한 코드로 예를 들어보겠습니다.(게시판 프로젝트 코드가 아닙니다)
# test/models.pyfrom django.db import models
# 설명만을 위한 모델로, 상당히 대충 작성 되었습니다:)
class Person(models.Model):
id = models.IntegerField()
name = models.CharField()
phone = models.CharField()
addr = models.CharField()
email = models.CharField()
# test/serializers.pyfrom rest_framework import serializers
from .models import Person
# ModelSerializer 뒤에서 설명합니다.classBasePersonSerializer(serializers.ModelSerializer):classMeta:
model = Person
fields = ('id', 'name', 'phone', 'addr')
위에서 보시면 Person이라는 모델을 models.py에 만들고,
BasePersonSerializer라는 시리얼라이저를 serializers.py에 만들었습니다.(앱 내에 파일을 생성하셔야 합니다)
이게 어떤 뜻이냐면,
"내가 만든 Person이라는 모델에서 데이터를 뽑아서 응답으로 보낼텐데,
응답의 형태 중 하나인 Base 형태를 BasePersonSerializer라고 정의할게!"라는 뜻입니다.
자세히 보시면 모델에는 email도 정의되어있는데 Base의 fields에는 email이 없습니다.
이는 개발자가 정의한 Base 형태에는 email이 없다는 뜻이죠.
그러면 사람의 이메일 정보를 요청할 때 쓸 수 있는 시리얼라이저는 어떻게 만들 수 있을까요?
# test/serializers.pyfrom rest_framework import serializers
from .models import Person
# ModelSerializer 뒤에서 설명합니다.classEmailPersonSerializer(serializers.ModelSerializer):classMeta:
model = Person
fields = ('id', 'email')
이렇게 만들어 볼 수 있습니다.
이제 우리는 한 가지 깨달음을 얻을 수 있습니다.
시리얼라이저는 응답으로 보낼 데이터의 형태를 정해주는 하나의 틀과 같구나!
3. Serializer - View 연결하기
여기까지 잘 이해되셨다면 이후 단계에서는 더 잘 이해될 것입니다.
이제는 그럼 views.py에 실제로 시리얼라이저가 어떻게 사용되는지 알려드리겠습니다.
# test/views.py
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import Person
from .serializers import BasePersonSerializer, EmailPersonSerializer
@api_view(['GET'])
def PersonAPI(request, id):
now_person = Person.object.get(id=id)
serializer = BasePersonSerializer(now_person)
return Response(serializer.data)
# => id=1에 대해 리턴된 Response: {'id': 1, 'name': '태뽕', 'phone': '01012345678', 'addr': '주소주소'}
@api_view(['GET'])
def EmailAPI(request, id):
now_person = Person.object.get(id=id)
serializer = EmailPersonSerializer(now_person)
return Response(serializer.data)
# => id=1에 대해 리턴된 Response: {'id': 1, 'email': 'email@email.com'}
어떠신가요 바로 이해가 되셨나요?
제가 시리얼라이저를 변환기, 모양 틀로 설명드린게 이런 맥락이었습니다.
각 view에서 무언가 데이터를 요청할 때,
지금 예시에서는 PersonAPI는 사람에 대한 데이터, EmailAPI에서는 이메일에 대한 데이터를 요청할 때 각각 원하는 형태로 응답해줘야 합니다. 하지만 모델은 하나니 필요한 데이터만 골라서 보내줘야겠죠?
이 역할을 해주는게 시리얼라이저라고 이해하시면 됩니다.
내가 보낼 데이터(now_person(즉, 모델 인스턴스))를 시리얼라이저에 넣어 변환시키고
그 데이터를 응답으로 보내주는 것이 시리얼라이저 - 뷰 연동 개념입니다.
4. ModelSerializer
앞서 진행한 코드에서 한가지 설명 안하고 그냥 넘어간 코드가 있습니다.
바로 시리얼라이저를 선언할 때 사용한 serializers.ModelSerializer입니다.
# test/serializers.py# ModelSerializer 뒤에서 설명합니다.classBasePersonSerializer(serializers.ModelSerializer):classMeta:
model = Person
fields = ('id', 'name', 'phone', 'addr')
원래 가장 기본적인 형태의 시리얼라이저 선언 방법은 serializers.Serializer를 상속 받는 것입니다.
이 형태로 선언하면 다음과 같이 코드를 작성할 수 있습니다.
# test/serializers.pyclassBasePersonSerializer(serializers.Serializer):id = serializers.IntegerField()
name = serializers.CharField()
phone = serializers.CharField()
addr = serializers.CharField()
defcreate(self, validated_data):"""
검증한 데이터로 새 `Person` 인스턴스를 생성하여 리턴합니다.
"""return Snippet.objects.create(**validated_data)
defupdate(self, instance, validated_data):"""
검증한 데이터로 기존 `Person` 인스턴스를 업데이트한 후 리턴합니다.
"""
...
...
return instance
위에서 작성한 시리얼라이저보다 훨씬 길고 복잡해졌죠?
원래 시리얼라이저를 선언할 때는
1. 데이터 형태에 대한 필드(앞선 방법에서는 fields, 지금은 일일이 선언),
2. 그리고 그 데이터를 어떻게 처리할건지에 대한 메소드가 필요합니다.
이 메소드는create나 update와 같이 이미 선언되어있는 메소드를 재작성하는 것이며,
views.py에서 직접 create나 update를 해야 할 것을 시리얼라이저에서 대신 해주는 것이라고 보면 됩니다.
대략 그런 역할을 선언해주고 필드에 대한 상세적인 내용을 직접 정의해야 했다면
serializers.ModelSerializer는 이 내용들을 알아서 해준 형태의 시리얼라이저라고 보시면 됩니다.
따라서 웬만하면 저희는 간편한 ModelSerializer의 형태로 선언하겠습니다. ★★
5. 마치며
여태까지 Django Rest Framework에서 가장 중요한 개념인 시리얼라이저에 대해 배웠습니다.
최근 정말 인기 있는 node.js의 express, python의 flask, java의 spring boot까지.
정말 다양한 언어로 개발된 프레임워크들이 있고
모든 분야가 그렇듯 다 경험 해보고 본인이 쓰기 편한 것으로 개발하시면 됩니다.
저 또한 express, flask, 그리고 Django Rest Framework 이렇게 세 종류를 경험해보았고
이 중에서 Django Rest Framework를 주 활용 스택으로 선택한 이유는 다음과 같습니다.
(1) Django!
: Django의 가장 큰 장점은 "Django스럽다" 라고 말할 수 있습니다. 이게 무슨 뜻이냐 하면, 개발해야하는 여러 대표적인 기능들이 거의 모두 이미 개발되었으니 그냥 가져다 쓰면 된다는 것입니다. 처음엔 오히려 이것저것 복잡해서 낯설고 친해지는데 오래 걸릴 수 있지만, 나중에는 이미 구현된 이 기능 하나하나에 정말 큰 고마움을 느낄 것입니다.
(2) Django의 강력한 ORM
: Django를 사용하다보면 내가 데이터베이스를 쓰고 있던가 싶을 정도로 개발자가 쓰기 좋게 잘 추상화 되어있습니다. 상당히 python 스럽고 직관적인 방법으로 데이터에 접근, 필터링할 수 있으며, 다른 프레임워크로는 얻을 수 없는 경험이라고 생각됩니다.
3. 프로젝트 소개
본 프로젝트에서는 Django Rest Framework를 활용하여 게시판을 만들어보겠습니다.
게시판을 만들면서 당연히 CRUD를 구현하게 될 것이고, 이 과정을 시작하면서 최대한 다양한 방법을 제시하고 그 중에서 한 가지 방법으로 통일하여 쭉 작업할 예정입니다.
또한 항상 프로젝트의 시작이 되면서 은근 귀찮고 어려운 회원 가입 / 로그인 인증도 개발할 예정입니다.
아직 살펴보지는 않았지만, ForeignKey, ManyToManyField 그리고 OneToOneField 들은
첫번째 인자로 모델 클래스가 옵니다.
따라서,verbose_name을 키워드 인자로 사용해줍니다.
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, verbose_name="the related poll")
# poll 은 ForeignKey (외래키) 로 Poll 이라는 클래스를 첫번째 인자로 받음 # verbose_name 은 키워드 인자로 넣어줌
sites = models.ManyToManyField(Site, verbose_name="list of sites")
# sites 는 ManyToManyField 로 Site 라는 클래스를 첫번째 인자로 받음 # verbose_name 은 키워드 인자로 넣어줌
place = models.OneToOneField(Place, on_delete=models.CASCADE, verbose_name='related place')
# place 는 OneToOneField 로 Place 라는 클래스를 첫번째 인자로 받음 # verbose_name 은 키워드 인자로 넣어줌
위의 예시에서는, 단지 ForeignKey, ManyToManyField 그리고 OneToOneField 를 사용할때에,
verbose_name 이 어떻게 키워드 인자로 들어가는지에 대한 코딩 스타일만 보여주고 있습니다.
관례적으로,
verbose_name 의 첫번째 글자는 대문자로 써주지 않습니다.
Django 가 필요할때 자동으로 첫번째 글자를 대문자화 해줍니다.
2. 마치며..
verbose_name 에 대해서 알아보았습니다. verbose_name 을 사용하면, 어떤것이 달라지는지 완벽한 예시가 있으면 좋았을텐데,
이렇게 업데이트 한 후, updated_at에 업데이트한 시점의 시간이 잘 기록되었는지 확인합니다.
5. 테스트 실행
이렇게 모델을 가지고 우리가 할 수 있는 동작들을 테스트 케이스로 작성한 후,
다음 명령어를 사용하여 테스트 코드를 실행합니다.
python manage.py test
그럼 다음과 같이 우리가 작성한 테스트 코드가 문제없이 통과되는 것을 확인할 수 있습니다.
Creating test database for alias 'default'...
Systemcheck identified no issues (0 silenced).
......................
----------------------------------------------------------------------
Ran 22 tests in2.453s
6. 완료
이것으로 Django에서 모델을 테스트하는 방법에 대해서 알아보았습니다.
모델에 큰 로직이 포함되어있지 않기 때문에, 간단한 테스트 코드로 모델을 테스트할 수 있음을 알 수 있었습니다.
이제 여러분도, 여러분의 Django 프로젝트에 모델 테스트 코드를 작성해 보시기 바랍니다.
# See https://pre-commit.com for more information# See https://pre-commit.com/hooks.html for more hooksrepos:-repo:https://github.com/pre-commit/pre-commit-hooksrev:v3.2.0hooks:-id:trailing-whitespace-id:end-of-file-fixer-id:check-yaml-id:check-added-large-files
pre-commit에서 제공하는 샘플 내용을 확인할 수 있습니다.
4. flake8 설정하기
이제 pre-commit의 설정 파일에 flake8을 설정하는 방법에 대해서 알아봅시다.
pre-commit의 설정 파일인 .pre-commit-config.yaml은 기본적으로 다음과 같은 구조를 가지고 있습니다.
repos:
- repo: repo-url
rev: version
hooks:
- id: hook-id
- repo
: pre-commit이 제공하는 기능의 Repository URL
- rev
: 사용하려는 기능의 버전
- id
: pre-commit이 제공하는 기능
pre-commit은 기본적으로 제공하는 기능들이 있습니다. 다음 링크를 통해 어떤 기능들이 있는지 확인할 수 있습니다.
Django에서 flake8을 사용하기 위해서는 우선, flake8을 설치할 필요가 있습니다.
다음 명령어를 사용하여 flake8을 설치합니다.
pip install flake8
설치를 하였다면, 잊지말고requirements.txt에 저장해 둡니다.
pip freeze > requirements.txt
이것으로 flake8을 설치하는 방법에 대해서 알아보았습니다.
3. flake8 사용법
다음 명령어를 실행하여 flake8을 실행할 수 있습니다.
flake8
Django 프로젝트 폴더에서 flake8을 실행하면 다음과 같은 내용을 확인할 수 있습니다.
./venv/lib/python3.8/site-packages/pyflakes/checker.py:153:31: F821 undefined name 'PercentFormat'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:160:9: F821 undefined name 'Generator'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:160:9: F821 undefined name 'PercentFormat'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:180:47: F821 undefined name 'Optional'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:759:35: F821 undefined name 'List'
./venv/lib/python3.8/site-packages/pyflakes/checker.py:760:35: F821 undefined name 'Dict'
flake8의 결과를 보면 checker.py:153:31: F821 undefined name 'PercentFormat'와 같이 파일명:에러위치 에러ID 에러 내용을 확인할 수 있습니다.
4. flake8 설정하기
저는 djanog 프로젝트 폴더에 virtualenv를 생성하여 사용하고 있습니다. 그래서, flake8이 불필요하게 virtualenv 폴더까지 분석하고 있으니, flake8이 virtualenv 폴더를 분석하지 않도록 설정할 필요가 있었습니다.
falke8이 virtualenv 폴더를 무시할 수 있도록 설정하기 위해, .flake8 파일을 Django 프로젝트 폴더에 생성하고 다음과 같이 수정하였습니다.
이렇게 설정하고, 설정이 잘 적용되었는지 확인하기 위해 아래에 명령어를 사용하여 flake8을 실행해 봅니다.
flake8
그럼 이전과는 다르게 다음과 같은 결과를 확인할 수 있습니다.
./petmeeting/settings.py:98:80: E501 line too long (91 > 79 characters)./petmeeting/settings.py:101:80: E501 line too long (81 > 79 characters)./petmeeting/settings.py:104:80: E501 line too long (82 > 79 characters)./petmeeting/settings.py:107:80: E501 line too long (83 > 79 characters)./petmeeting/settings.py:132:33: W292 no newline at end of file
flake8은 많은 내용을 체크하지만, 일반적으로 생각하지 않아도 되는 부분까지 에러로 표시하는 경우가 있습니다. 이런 부분들을 무시하기 위해서 저는 다음과 같이 무시할 내용들을 추가하였습니다.