DRF+React로 Blog(유사 velog) 만들어보기 (5) 유저 모델 Serializer, View 만들기 + Urls 연결

HEYDAY7·2021년 4월 23일
0

이전까지의 글에선 게시판에 관련된, 즉 post와 관련된 코드들을 작성했다. 이번 글에서는 User model에 대한 코드를 작성할 것이며 모두 account directory안에서 작업이 일어난다.

models.py

Django에서는 기본적으로 User model을 제공하고 나는 이 User model을 사용할 것이다. 따라서 우리가 특별하게 추가하고 싶은게 있는게 아니라면 model 코드를 작성할 필요가 없다!!

serializers.py

기본 모델을 쓴 대신 serializer에 작성할 코드가 조금 늘어난다. 우선 앞선 글에서도 해봤듯이 User model에 대한 serializer를 작성해주자.

UserSerializer

from rest_framework import serializers
from django.contrib.auth.models import User


class UserSerializer(serializers.ModelSerializer):
    confirm = serializers.CharField(max_length=100, write_only=True)
    email = serializers.EmailField(required=True)
    
    class Meta:
    model = User
    fields = ['id', 'username', 'password', 'email', 'confirm']

우선적으로 작성한 내용이다. 일반적인 ModelSerializer 처럼 생겼으며 User의 CRUD 작업시 사용될 것이다. 여기서 confirm field는 비밀번호 확인이라고 생각하면 된다. option을 보면 write_only=True가 붙어있는데, 이는 회원가입 시에만 사용되고 Data로 저장되지 않기 때문이다!

write_only=True의 경우 input으로는 받지만 DB에 저장되지 않는 값이다. 정반대의 option으로는 read_only=True가 있다.

게시판 관련 Serializer의 경우 이정도에서 코드가 끝났지만 User의 경우 아직 작성할 코드가 남았다.


다음으론 validate function을 override 해줘야한다.

def validate(self, data):
    if data['password'] != data['confirm']:
    	raise serializers.ValidationError('비밀번호와 비밀번호 확인이 일치하지 않습니다')
    return data

지난 글에서 짧게 언급만 했었지만 serializer의 기능 들 중 핵심 기능은 input에 대한 validation을 진행하는 것이다. input으로 들어온 data를 확인하여 logic에 알맞게 들어왔는지를 확인한다.
따라서 위 코드는 input으로 'confirm' 필드가 들어왔을 때! 만약 password와 다르다면 error를 발생시키고, 그렇지 않을경우에는 기본 설계대로 data를 return 한다.

여기서 기본 설계를 보고 싶다면 여기를 참고하자


이어서 create와 update function을 override 해줘야 한다. 이 두개의 function은 모든 serializer가 기본으로 지니고 있는 로직이다. 기본 구현의 구조는 다음과 같다.
1. serializer.save() method가 실행된다.
2. instance의 유무를 따져 create()나 update()가 불리고
3. 그 결과로 새로운 object가 만들어지거나 주어진 instance가 input으로 들어온 data로 update 된다.

def create(self, validated_data):
    validated_data.pop('confirm')
    user = User.objects.create(**validated_data)
    user.set_password(validated_data['password'])
    user.save()
    
    return user
    
    
def update(self, instance, validated_data):
    instance.username = validated_data['username']
    instance.password = validated_data['password']
    instance.email = validated_data['email']
    instance.save()
    
    return instance

아주 깔끔하고 쉬운 코드이다. create의 경우 confirm 데이터는 필요하지 않으므로 제거해 준 후 남은 validated_data로 새로운 User object를 만든다. 여기서 validated_data는 앞서 작성한 validate function을 통과한 data가 input으로 들어온다!
또, password만 따로 넣어주는 이유는 Django에서 기본 제공하는 User model은 password를 개별적으로 넣어줘야 하기 때문이다.(경험적으로 알고 있다... 자세한 이유는 공식 문서에서 찾아볼 수 있을 것이다.) 어쨌든 password를 따로 주입했기에 user.save()를 해주고 user를 return 해준다.

update의 경우 단순히 새로 데이터를 업데이트 시켜주면 된다.

LoginSerializer

ModelSerializer가 아닌 첫 serializer이다. 이 serializer는 login에 쓰이게 된다. login용 serializer를 따로 만드는 이유는 request 해야할 data의 형태, 즉 받을 data form이 다르기 때문이다. 결국 어떤 data든 backend로 들어와서 사용되려면 serializer를 하나는 거쳐야 한다고 생각하는게 편할 수 있다. 따라서 login용 serializer를 작성해보자

from django.contrib.auth import authenticate, login


class loginSerializer(serializers.Serializer):
    username = serializers.Charfield(max_length=100, required=True)
    passwod = serializers.Charfield(max_length=100, required=True)
    
    def validate(self, data):
    	user = authenticate(username=data['username'], password=data['password'])
        if user is None:
            raise serializers.ValidationError('아이디 혹은 비밀번호가 잘못되었습니다')
           	
        return data
        
    def perform_login(self, request):
    	user = authenticate(username=request.data['username'], password=request.data['password'])
        login(request, user)
        return user

우선 authenticate와 login을 import 해온다. authenticate의 경우 username과 password를 input으로 받아 유저가 존재할 경우 user 객체를, 존재하지 않을 경우 None을 return 해주는 function이다. login의 경우 단순하게 user를 login 시켜준다. 자세한 설명은 여기서 살펴보자

serializer의 field로는 username과 password 두 개를 받는 것이 당연하기에 넘어간다.

validate function에서는 들어온 username과 password에 부합하는 유저가 있는지를 확인해주고, 없다면 ValidationError를 띄워주고 있다면 그냥 넘어간다.

다음으로는 새로운 function인 perform_login을 만들어준다.

여기서 새로운 함수를 쓰는 이유는, 물론 save method 혹은 create method와 같이 기본적으로 존재하는 내장 함수를 override 할 수도 있지만, logic적으로 보았을 때 login 동작은 저장도, 생성도 아니기 때문에 개별적으로 함수를 만들어준다.

실제 코드는 매우 간단하다. request.data에 맞는 유저를 가져와 login 시켜주면 되는 단순한 코드이다.

코드를 보며 다음과 같은 의문을 품는 사람이 있을 수 있다. validate function과 perform_login은 function은 모두 authenticate function을 사용하기도 하고 코드의 형태가 거의 비슷한데, 왜 validate 안에서 그냥 login까지 시키지 않는거지?
이는 틀린말은 아니다. 그러나 개발에 있어 logic의 분리는 매우 중요하다. validate function의 역할은 request를 통해 들어온 데이터가 valid 한지 만을 확인하는 함수이다. 따라서 정확히 그 역할만 하는 것이 중요한 것이다. 이렇게 항상 logic의 분리를 생각하며 코드를 작성하는 것이 중요하다.(다만 나도 아직 잘 하지는 못한다.)


views.py

view 코드는 단순하다. 우선 ModelViewSet을 이용하여 user에 대한 viewset을 만들어줍니다. 또 이어서, Login과 + Logout에 대한 view까지 만들어줍니다.

from django.shortcuts import render
from rest_framework import viewsets, views
from rest_framework.response import Response
from django.contrib.auth.models import User
from .serializers import *
from django.contrib.auth import logout

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

class LoginView(views.APIView):
    def post(self, request, *args, **kwargs):
    	serializer = LoginSerializer(data=request.data)
    	serializer.is_valid(raise_exception=True)
    	serializer.perform_login(request)
    	return Response(serializer.data)

class LogoutView(views.APIView):
    def post(self, request, *args, **kwargs):
        logout(request)
        return Response() 
  • UserViewSet
    UserViewSet의 경우 일반적인 ModelViewSet이기 때문에 넘어간다.

  • LoginView
    이전 글에서도 말했었지만, view는 controller의 역할을 하고 로직이 구현되어 있는 곳이다. 이에 맞게 LoginView는 login 기능을 수행한다. post를 override 하는 이유는 Login은 POST method로 request를 받기 때문이다.

  1. serializer로 앞서 만든 LoginSerializer를 사용하고 data를 넘겨준다.
  2. serializer.is_valid(raise_exception=True)를 실행한다. 이 function은 serializer의 validate를 실행하고 오류가 있다면 멈추고, 없다면 계속 진행한다.
  3. 이 line이 실행되려면 is_valid가 통과한 것이니 만들었던 perform_login을 통해서 login을 진행한다.
  4. 결과를 return 해준다.
  • LogoutView
    단순하게 로그아웃을 실행하는 view이다. post를 override 하긴 하지만 내부에선 그냥 logout만 시킨다.

urls.py

우선 account의 urls를 root urls와 연결해주자

// blog_back/urls.py

urlpatterns = [
    ...,
    path('', include('account.urls'),
]

그리고선 account/urls.py를 작성하자

from django.urls import path, include
from rest_framework import routers
from .views import *


router = routers.DefaultRouter()
router.register(r'user', UserViewSet)


urlpatterns = [
    path('', include(router.urls)),
    path('login/', LoginView.as_view(), name='login'),
    path('logout/', LogoutView.as_view(), name='logout')
]

router를 통해 UserViewSet을 연결해주고
LoginView와 LogoutView는 각각 'login/', 'logout/ 에다가 연결해준다.

settings.py 수정

마지막이자 가장 중요한 부분이 남았다. django에서 authentication을 사용하기 위한 설정 부분이 남아있다. 방식으로는 크게 token과 sessionId 방식이 있는데 우리는 sessionId 방식을 사용할 것이다!
이를 위해서 settings.py에 내용을 추가해줘야 한다.

// in settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

확인 및 마무리

insomnia를 통해서 셋팅이 잘 되었는지 확인해보자
다음과 같이 user/에 맞는 데이터 형식으로 post를 보내면 user create, 즉 회원가입이 정상적으로 진행되는 것을 볼 수 있다!

로그인을 시도했을 때는 아래와 같은 에러를 만나보게 될 가능성이 높다.
이런 CSRF에 대하여 따로 설명하는 글을 작성할 예정이다. 이 에러가 뜨는것이 지극히 정상이니 지금 걱정하지는 말고, 우리가 만든 url에서 정상적으로 기능이 작동하는 것을 확인했다는 것에 의의를 두자.

profile
(전) Junior Android Developer (현) Backend 이직 준비생

0개의 댓글