서버개발자가 되는법 [4] - API만들기, 분석/설계 단계

     

 

목차 - 2020/09/29 - [Study/서버] - 서버개발자가 되는법 - 목차

 

git - 링크

 

 

유튜브 - 

 

 

 

들어가기 전에

 

지난 시간에 Django를 이용해 REST API를 만들기 위해 DRF를 설치하고, 로그인 API를 간단하게 만들어 보았습니다. 이전 포스팅이 끝날 때 다음 포스팅에서 로그인 API를 마무리한다고 했는데, 사실 로그인 API를 태우고 나면 단순히 성공 실패만 클라이언트로 전달하는 게 아니라 로그인한 사용자의 정보를 내려주는게 국룰입니다. 

 

상용화된 서비스에서는 단순히 비밀번호를 DB와 비교하는 것이 아니라 "인증"이나 "보안", "이력관리", "세션관리" 등을 위한 이런저런 처리를 하게 됩니다. 아마 여러분이 회사에 가서 LOGIN 소스를 보면 수백, 수천 줄의 코드로 이루어져 있을 수도 있습니다. 저도 증권사에 있을 때 제일 긴 소스가 LOGIN 소스였던 거 같습니다. (그리고 제일 복잡했음,,,)

 

아무튼 LOGIN 서비스 특성상 가장 먼저 호출되기 때문에 클라이언트에서 저장해야 할 사용자의 기본 정보를 내려주기 가장 좋습니다. 이번 포스팅에서는 LOGIN에 성공했을 때 사용자의 정보를 내려주고, LOGIN이 아니라 다른 API 서비스를 만들 때 어떤 순서, 방법으로 만들어야 하는지 알아보도록 하겠습니다.

 

 

소스는 git에서 서버개발자_3 브랜치에서 시작하면 됩니다

(완성된 버전은 서버개발자_4 브랜치입니다.)

 

 

 

LOGIN API에 응답 추가하기

 

지난 포스팅에서 만든 LOGIN API는 요청을 보내면 다음과 같은 응답을 줍니다.

 

id와 pw를 입력하면 성공과 실패에 대한 msg(메시지)를 응답한다.

 

클라이언트 기준으로 생각해보면 로그인 화면에서 ID와 PW를 입력하고, 로그인 성공 여부에 따라 다음 화면으로 넘어갈지 오류 팝업을 노출할지가 결정됩니다. 사용자에게는 로그인의 성공 여부가 제일 중요하고, 눈에 가장 띄는 데이터지만, 뒤에서는 상당히 많은 양의 데이터를 받고 있을 겁니다.

 

예를 들어 페이스북이나 인스타그램과 같은 SNS 앱에 로그인한다고 가정해봅시다. ID와 PW를 입력하고 로그인에 성공하면 클라이언트는 서버에 사용자의 프로필 사진이나 담벼락, 피드와 같은 수많은 데이터를 요청하게 됩니다. 

 

클라이언트에서 이렇게 많은 데이터를 나누어 호출하게 되면 서버와 클라이언트 간에 통신이 여러 번 발생하기 때문에 상당히 비효율 적입니다. (자세한 설명은 유튜브 동영상에서..)

 

클라이언트와 서버가 여러번 통신하지 않게 하기 위해 최대한 한 번의 통신으로 모든 데이터를 내려오게 API를 만들어야 합니다. 물론 너무 많은 정보가 하나의 API에 담겨도 문제입니다. 이 양을 적절하게 조절하는 것도 서버 개발자의 몫입니다. ^^

 

아무튼 여기서 만드는 LOGIN에서도 사용자의 특정 정보를 내려주도록 만들어보겠습니다. 그러기 위해서는 먼저 가입을 할 때 특정 데이터를 올리도록 해야 합니다. 지난 시간에 회원관리 테이블을 만들 때 ID와 PW칼럼만 만들었는데, 추가로 칼럼을 더 만들어봅시다.

 

# login/models.py

class LoginUser(models.Model):
    user_id = models.CharField(max_length=20, unique=True, null=False, default=False)
    user_pw = models.CharField(max_length=255, null=False, default=False)
    # user_pw = fields.EncryptedCharField(max_length=20, null=False, default=False)
    class Meta:
        db_table = 'login_user'
        verbose_name = '로그인 테스트 테이블'

 

어떤 서비스냐에 따라서 사용자의 어떤 정보가 필요한지가 결정되는데 아마 대부분 "생일", "이메일 주소", "성별", "나이", "이름" 정도는 기본으로 들어가지 않을까 합니다. 핸드폰번호와 이메일주소의 경우 개인정보로 취급되기 때문에 여기서는 이메일주소 하나만 사용하도록 하겠습니다. 보통 이런 개인정보로 취급되는 데이터는 암호화되어 저장해야 하지만 여기서는 생략하겠습니다.

 

# login/models.py

from django.db import models
from encrypted_fields import fields

class LoginUser(models.Model):
    user_id = models.CharField(max_length=20, unique=True, null=False, default=False)
    user_pw = models.CharField(max_length=255, null=False, default=False)
    # user_pw = fields.EncryptedCharField(max_length=20, null=False, default=False)

    birth_day = models.DateField(verbose_name="생년월일", null=True)
    gender = models.CharField(verbose_name="성별", max_length=6, null=False, default='male')
    email = models.CharField(verbose_name="이메일 주소", max_length=255, null=False, default=False)
    name = models.CharField(verbose_name="이름", max_length=20, null=False, default=False)
    age = models.IntegerField(verbose_name="나이", default=20)

    class Meta:
        db_table = 'login_user'
        verbose_name = '로그인 테스트 테이블'

 

5개의 필드를 추가했습니다. 항상 model을 수정하거나 추가하면 migrations와 migrate를 하는 걸 잊지 마세요!! 

 

읭? 에러?

makemigrations를 하니까 위와 같은 메시지가 나오는데, birth_day에 대한 default값이 정해져 있지 않다는 뜻입니다. 보통 null이 가능하거나 default가 있거나 둘 중 하나여야 하는데 birth_day는 널이 불가능한데 default도 없어서 어쩌겠냐고 물어보네요. 참 자상하셔라,, 생일에 대한 default값 정하기도 뭐하니까 null이 가능하도록 바꿉시다.

 

# login/models.py
...
class LoginUser(models.Model):
    ...
    birth_day = models.DateField(verbose_name="생년월일", null=True)
    ...

 

이제 다시 makemigrations -> migrate !!

 

migrate 성공
DB에도 컬럼이 생겼는지 항상 확인합시다.

 

이제 보니 모델을 만들 때 default=False로 해놨는데,, 빈 값으로 들어갈 줄 알았더니 'False'가 문자열로 들어가 있네요 ㅋㅋ

의도한 게 아닌데... 공백으로 바꿔야겠습니다. default = '' 요렇게

 

이제 가입할 때, 나이와 생일, 이메일, 성별, 이름을 입력받도록 API를 수정합니다. 데이터를 받아서 처리하는 곳은 장고에서 views.py라는 것을 기억해내세요!

 

# login/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from .models import LoginUser
from django.contrib.auth.hashers import make_password, check_password


class AppLogin(APIView):
    def post(self, request):
        user_id = request.data.get('user_id', "")
        user_pw = request.data.get('user_pw', "")
        user = LoginUser.objects.filter(user_id=user_id).first()

        if user is None:
            return Response(dict(msg="해당 ID의 사용자가 없습니다."))
        if check_password(user_pw, user.user_pw):
            return Response(dict(msg="로그인 성공", user_id=user.user_id, birth_day=user.birth_day,
                                 gender=user.gender, email=user.email, name=user.name, age=user.age))
        else:
            return Response(dict(msg="로그인 실패. 패스워드 불일치"))


class RegistUser(APIView):
    def post(self, request):
        user_id = request.data.get('user_id', "")
        user_pw = request.data.get('user_pw', "")
        birth_day = request.data.get('birth_day', None)
        gender = request.data.get('gender', "male")
        email = request.data.get('email', "")
        name = request.data.get('name', "")
        age = request.data.get('age', 20)
        user_pw_crypted = make_password(user_pw)    # 암호화

        if LoginUser.objects.filter(user_id=user_id).exists():
            # DB에 있는 값 출력할 때 어떻게 나오는지 보려고 user 객체에 담음
            user = LoginUser.objects.filter(user_id=user_id).first()
            data = dict(
                msg="이미 존재하는 아이디입니다.",
                user_id=user.user_id,
                user_pw=user.user_pw
            )
            return Response(data)

        LoginUser.objects.create(user_id=user_id, user_pw=user_pw_crypted, birth_day=birth_day,
                                 gender=gender, email=email, name=name, age=age)

        data = dict(
            user_id=user_id,
            user_pw=user_pw_crypted,
            birth_day=birth_day,
            gender=gender,
            email=email,
            name=name,
            age=age
        )

        return Response(data=data)

 

views.py에서 login과 regist에 model에서 새로 만든 필드를 추가합니다. 5개를 추가했기 때문에 5줄의 코드가 들어가야 하는데 여기서 뭔가 불편함을 느끼는 사람이 있을 겁니다.

 

LoginUser라는 모델에 있는 값을 그대로 입력받고 그대로 출력해야 하는데 5줄씩 전부 추가해야 되는가?

 

만약 LoginUser 모델을 사용하는 곳이 100 군대라면 100 * 5 줄의 코드를 추가해야 하는데, 이런 반복적인 일은 생산성을 떨어뜨립니다. 그래서 Django에서는 Serialize(직렬화) 기능을 제공합니다. 아마 Django-Rest-Framework 공식 홈페이지에서 Tutorial을 따라 하면 Serialize 예제를 따라 해 보셨을 텐데 Serialize를 쓰게 되면 LoginUser라는 모델을 자동으로 Json형식으로 출력해줍니다. 즉 아래 긴 소스를

data = dict(user_id=user_id, user_pw=user_pw_crypted, birth_day=birth_day, gender=gender, email=email, name=name, age=age)

아래처럼 간단하게 바꿀 수 있습니다.

data = Serialize(User)

 

입력 역시 아래처럼 필드 하나하나 받지 않아도 됩니다.

# as-is
user_id = request.data.get('user_id', "")
user_pw = request.data.get('user_pw', "")
birth_day = request.data.get('birth_day', None)
gender = request.data.get('gender', "male")
email = request.data.get('email', "")
name = request.data.get('name', "")
age = request.data.get('age', 20)
# to-be
user = Serialize(Input)

 

이렇게 Serializer를 따로 만들어서 관리하면 아까와 같이 100 * 5의 수정이 필요 없게 됩니다. 이 직렬 화기 능은 많은 웹 프레임워크(rails, spring 등등)에서 기본적으로 제공하는 기능으로 생산성을 상~~~ 당히 올릴 수 있는 반면에 단점이 몇 가지 있습니다.

 

먼저 간단한 홈페이지에 적용할 경우 생산성 끝판왕이 되지만, 복잡한 로직을 담고 있는 API 서버에서는 생각보다 효율적이지 못합니다. 왜냐하면 Serialize를 사용하기 위해는 Serialize 할 모델의 모든 값을 받아야 하는데, 생각보다 모든 값을 받는 경우가 별로 없습니다. 생각해보면 회원가입할 때나 사용자의 정보를 모두 받지, 다른 API에서 사용자의 정보를 모두 받을 일은 없죠?

 

또 Serialize는 모델의 필드를 모두 받아서 추가하거나 수정하는 등의 작업을 진행하는데, 이것보다 더 복잡한 로직일 경우에는 사용이 어렵습니다. 예를 들어 클라이언트로부터 특정 값을 받아서 다른 곳에 있는 데이터와 계산하여 다시 모델에 저장해야 하는 경우에는 그에 따른 로직이 Serialize에 들어가야 합니다. 그렇게 되면 결국 Serialize안에서 여러 가지 분기를 태워서 로직을 적어놔야 하는데, 그럴 거면 그냥 Serialize를 사용하지 않고 Views.py(API 레벨)에서 로직을 넣는 것이 더 효과적입니다. 

 

따라서 간단하게 저장하고 불러오기 기능만 가지고 있는 서비스에는 Serializer가 효과적이지만, 복잡하고 계산이 많은 서비스에는 Serializer를 사용하지 않는 것이 효과적일 수도 있습니다. 사용하느냐 마느냐는 역시 서버 개발자의 몫,,,

 

 

그럼 얼마나 코드가 간결해지는지 만들어볼까요?

 

# login/serializer.py

from rest_framework import serializers
from .models import LoginUser
from django.contrib.auth.hashers import make_password


class LoginUserSerializer(serializers.ModelSerializer):
    def create(self, validated_data):
        validated_data['user_pw'] = make_password(validated_data['user_pw'])
        user = LoginUser.objects.create(**validated_data)
        return user

    def validate(self, attrs):
        return attrs

    class Meta:
        model = LoginUser
        fields = ('user_id', 'user_pw', 'birth_day', 'gender', 'email', 'name', 'age')

 

login 폴더에 serializer를 만듭니다. 베이스 모델은 당연히 LoginUser입니다. 이렇게 Serializer를 만들어 놓고 views.py에서 regist를 살짝 수정해봅시다

 

# login/views.py
from .serializer import LoginUserSerializer

class RegistUser(APIView):
    def post(self, request):
        serializer = LoginUserSerializer(request.data)

        if LoginUser.objects.filter(user_id=serializer.data['user_id']).exists():
            # DB에 있는 값 출력할 때 어떻게 나오는지 보려고 user 객체에 담음
            user = LoginUser.objects.filter(user_id=serializer.data['user_id']).first()
            data = dict(
                msg="이미 존재하는 아이디입니다.",
                user_id=user.user_id,
                user_pw=user.user_pw
            )
            return Response(data)

        user = serializer.create(request.data)

        return Response(data=LoginUserSerializer(user).data)

 

짠~~ 생각보다 많이 줄었죠? 값을 하나하나 대입하는 부분이 LoginUserSerializer로 대체되었습니다. 덕분에 코드의 가독성도 증가하고, 로직을 모듈화 했기 때문에 재사용성도 높아졌습니다!!!

 

 

이제 두 API를 다시 한번 호출해 보겠습니다.

 

로그인 호출
회원가입 호출

 

회원 가입할 경우 보통 생성된 모델 데이터를 그대로 리턴하기 때문에 만들어진 user모델을 리턴하게 됩니다. user모델 안에 있는 데이터는 Serializer를 통해 자동으로 json형태로 변환되어 클라이언트에 전달됩니다. 입력도 마찬가지로 클라이언트에서 올라온 json형태의 데이터가 Seializer를 통해 자동으로 모델에 필드들에 세팅되게 됩니다. Serialzer를 사용하면 다수의 필드를 가진 모델도 빠르게 (코드 몇 줄로) CRUD를 할 수 있습니다.

 

 

 

 

To-Do list 앱을 만들어 보자

 

이제 로그인은 만들었고 다른 서비스용 API를 만들어봐야 하는데,, 뭐 따로 서비스를 하고 있는 게 없으니까 ㅋㅋㅋ 서버만 가지고 뭔가 만들기 애매합니다. 

 

그래서 그냥 간단한 To-Do List를 관리하는 앱을 만들어볼까 합니다. 물론 웹앱으로.. ㅎㅎ

앱을 만들려면 웹 화면을 만들어야 하는데 사실 웹 화면은 프런트엔드라서 서버 개발자의 역할은 아니거든요. 하지만 또 요즘에 풀 스택(full-stack)이라고 해서 프론트엔드와 서버 개발을 다 할 수 있는 사람을 환영하기는 합니다. 제 생각에는 프론트엔드를 하다가 추가로 서버개발을 하는 사람은 있어도, 서버 개발하다가 프론트까지 해서 풀 스택 개발자가 되는 경우는 거의 못 봤네요... (저도 웹 프론트는 좀 꺼려합니다 ㅋㅋ)

 

아무튼 일단 To-Do 앱을 만든다고 했을 때, 어떤 기능이 있어야 할까요? 

 

1. To-Do 생성, 조회, 삭제

2. To-Do 진행상태 변경 (시작, 진행 중, 완료)

 

가볍게 생각하면 위 두 가지 기능만 있으면 To-Do List 앱을 만들 수 있을 것 같습니다. 

 

그럼 이렇게 작업에 대한 기획이 생겼다면 서버 개발자는 뭐.부.터 해야 할까요?

 

 

제일 먼저 데이터를 어떻게 관리할지를 정합니다. 보통은 데이터베이스를 사용하겠지만 어떤 경우에는 데이터베이스를 사용하지 않고 파일이나 메모리를 사용할 수도 있습니다. 실제로 현업에서는 데이터를 어떻게 관리할지가 생각보다 큰 의사결정 사항입니다. 서비스의 속도나 가용성 등을 고려하였을 때 최적의 데이터 보관장소를 선정해야 성공적인 서비스를 할 수 있습니다. 

 

예를 들어 빠르게 조회되어야 하는 서비스를 만들 경우(금융권 서비스) 데이터가 최대한 빠르게 클라이언트로 전달되어야 합니다. 이럴 경우 데이터베이스가 상대적으로 느릴 수 있습니다. 왜냐하면 데이터베이스는 물리적인 디스크에 데이터를 저장하기 때문에 디스크에서 데이터를 조회하는 속도가 메모리보다 느립니다. (물론 메모리를 이용한 데이터베이스도 있습니다.) 이럴 경우 메모리에 데이터를 적재하여 보다 빠른 서비스를 제공할 수 있습니다. 

 

여기서는 상용화된 서비스를 제공하는 목적이 아니니 그냥 데이터베이스를 사용합시다. :)

 

데이터베이스를 사용한다고 정했어도, 추가로 결정해야 하는 사항이 많습니다. 관계형 데이터베이스를 쓸 것인지, No-Sql 데이터베이스를 쓸 것인지, MariaDB, MySql, Oracle 등 어떤 데이터 베이스를 사용할 것인지 결정해야 합니다. 만약 먼저 쓰고 있는 데이터베이스가 있는 경우 그걸 그대로 사용할 수 있고, 새로 데이터베이스를 구축해야 한다면 적당한 것을 선택하여 골라야 합니다. 최근에는 클라우드 환경에 서버를 구축하는 일이 많기 때문에 클라우드에서 제공하는 데이터베이스 솔루션 까지 선택의 폭이 넓어졌기 때문에.. 데이터베이스 고르다가 하루가 다 갈 수도 있습니다. ㅋㅋㅋ

 

일단 여기서는 MySql을 사용한다고 가정하고, 그다음은 이제 테이블을 어떻게 만들어야 하는지 고민해야 합니다. To-Do List의 하나의 Task를 저장할 때 어떤 데이터가 필요할까요? 먼저 어떤 사용자의 To-Do리스트인지 알기 위해서는 사용자의 ID값이 필요하고, task 이름이랑, 현재 진행상태, 완료 날짜, 마감 날짜, 시작 날짜 정도로,, 지금은 생각되지만 아마 만들어 나가면서 몇 개 더 추가될 겁니다 ㅋㅋㅋ

 

만약 운영 중인 서비스가 있고, 추가로 서비스를 만드는 경우라면 테이블 작업에 신중해야 합니다. 테이블에 신규 컬럼을 추가하거나 삭제하는 작업은 데이터베이스에 영향을 주기 때문에 운영중인 서비스에 영향을 줄 수 있습니다. 따라서 대부분 회사에서는 테이블 작업 시간을 정해놓고 서비스를 중지하고 작업을 진행한다던가, 이중화가 되어있다면 하나의 서버만 동작시키고 다른 하나에서 작업하는 방식으로 진행하게 됩니다. 

 

 

 

테이블에 들어갈 컬럼을 정했다면 이제 테이블을 만들어봅시다. 테이블 이름은 대충 task라고 짓겠습니다. 간혹 테이블 이름을 복수형으로 만드는 곳이 있는데 task vs tasks.. 뭐가 맞는지는 스스로 선택하셔야 합니다 ㅋㅋㅋ 논란이 많더라고요. 테이블은 하나인데 왜 복수형으로 짓냐... Row 관점으로 봤을 때는 단수가 맞다 등 등.. 제 생각에는 단수형이 맞는 거 같습니다. 복수형으로 테이블을 사용할 때는 Row하나에 여러 개의 데이터가 들어가는 경우에만 복수형입니다.

 

드디어 뭔가 코드를 작성할 시간인데, 일단 Django에서는 새로운 앱을 만들 때 startapp을 이용해 별도의 폴더를 만들어 관리하기 때문에 startapp으로 todo를 만들어줍시다.

 

(venv) C:\Users\tkdle\PycharmProjects\server_dev>python manage.py startapp todo

(venv) C:\Users\tkdle\PycharmProjects\server_dev>

todo 앱을 만들자

 

테이블은 models.py에서 만드니까 Task라는 모델을 만들어 줍니다. 

# todo/models.py
from django.db import models
from django.utils import timezone


# Create your models here.
class Task(models.Model):
    user_id = models.CharField(max_length=20, null=False, default=False)
    name = models.CharField(verbose_name="작업이름", max_length=256, null=False, default='')
    start_date = models.DateField(verbose_name="시작날짜", default=timezone.now)
    end_date = models.DateField(verbose_name="마감날짜", null=True)
    finish_date = models.DateField(verbose_name="완료날짜", null=True)
    state = models.IntegerField(verbose_name="상태", null=False, default=0)

    class Meta:
        db_table = 'task'
        verbose_name = '작업(to-do) 테이블'

 

이제 migrate를 해야 하는데 settings.py에서 INSTALLED_APPS에 추가하는 것을 잊지 맙시다!

 

# server_dev/settings.py

INSTALLED_APPS = [
    ...
    'todo'
]

makemigrations, migrate

 

테이블 생성된 것도 확인

 

 

테이블이 완성되었으면 이제 API를 만들면 됩니다. views.py에서 Task 모델에 대한 생성, 조회 API를 먼저 만들어 보겠습니다.

 

# todo/views.py
from rest_framework.views import APIView
from .models import Task
from rest_framework.response import Response
from datetime import datetime


# Create your views here.
class TaskCreate(APIView):
    def post(self, request):
        user_id = request.data.get('user_id', "")
        name = request.data.get('name', "")
        end_date = request.data.get('end_date', None)
        if end_date:
            end_date = datetime.strptime(end_date, '%Y-%m-%d').date()
        task = Task.objects.create(user_id=user_id, name=name, end_date=end_date)

        return Response(dict(msg="To-Do 생성 완료", name=task.name, start_date=task.start_date.strftime('%Y-%m-%d'), end_date=task.end_date))


class TaskSelect(APIView):
    def post(self, request):
        user_id = request.data.get('user_id', "")

        tasks = Task.objects.filter(user_id=user_id)
        task_list = []
        for task in tasks:
            task_list.append(dict(name=task.name, start_date=task.start_date, end_date=task.end_date, state=task.state))

        return Response(dict(tasks=task_list))
# todo/urls.py
from django.conf.urls import url
from . import views


urlpatterns = [
    url('create', views.TaskCreate.as_view(), name='create'),
    url('select', views.TaskSelect.as_view(), name='select'),
]
# server_dev/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', include('login.urls')),
    path('todo/', include('todo.urls')),
]

 

TaskCreate와 TastSelect를 만들었습니다. Create의 경우 단건 생성, Select의 경우 해당 사용자의 모든 Task를 불러와 List로 보여줍니다. 태스트를 해볼까요?

 

TaskCreate
TaskSelect

Create로 여러 개의 Task를 만든 다음 Select로 조회해봅시다. 사용자 ID는 항상 들어가야 합니다.

 

 

 

 

화면 만들기

 

TaskCreate와 TaskSelect를 이용해 생성, 조회 API를 만들었습니다. 기능만 구현했기 때문에 뭔가 돌아가는 게 시원치 않은데요, 장고에서 제공하는 tamplate를 사용해서 웹페이지를 만들어보도록 하겠습니다.

 

templates/todo/todo.html

아마 프로젝트 경로에 templates 폴더가 있을 텐데(없으면 만드세요~!) 그 안에 todo라는 폴더를 만들어줍니다. todo라는 폴더를 만드는 이유는 templates 안에서 django-app 별로 파일들을 독립적으로 관리하기 위함입니다. todo폴더 안에 todo라는 html 파일을 하나 만들어주세요. 이제 이 html에 화면을 만들면 됩니다.

 

{# templates/todo/todo.html #}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>To-Do</title>
</head>
<body>
<form action="/todo/create" method="post" class="form-inline">
<div>
    <label>name</label>
    <input type="text" name="name" />
</div>
<div>
    <label>end_date</label>
    <input type="text" name="end_date"/>
</div>
<div>
    <label>user_id</label>
    <input type="text" name="user_id"/>
</div>
<button type="submit"> 추가 </button>

</form>

</body>
</html>

 

이제 특정 url로 들어올 경우 todo.html이 열리도록 설정해줍니다. 먼저 views.py에서 todo.html을 표시할 수 있는 API를 하나 만듭니다.

 

# todo/views.py
...

class Todo(APIView):
    def get(self, request):
        return render(request, 'todo/todo.html')
        
...

 

render 사용하면 클라이언트 화면에 html 페이지를 띄울 수 있습니다. 이제 urls.py에 Todo를 추가합니다.

 

# urls/todo.py

from django.conf.urls import url
from . import views


urlpatterns = [
    url('create', views.TaskCreate.as_view(), name='create'),
    url('select', views.TaskSelect.as_view(), name='select'),
    url('', views.Todo.as_view(), name='todo')
]

 

이제 ~/todo/로 접속하면 todo.html이 화면에 뜨게 됩니다. 한번 해볼까요?

 

~/todo/로 접속

우리가 만든 todo.html이 화면에 떴습니다!!

name, end_date, user_id 필드를 채우고 추가를 누를 경우 ~/todo/create API를 호출합니다. 한번 넣어보세요!!

 

날짜형식을 맞춰서 넣어줘야합니다. 일단은...
위와같은 화면이 뜰꺼임

추가를 누르게 되면 위와 같은 화면이 뜰탠대요, ~/todo/create API를 호출하게 되면 render가 아니라 Response로 응답을 주기 때문에 DRF의 응답 포맷이 화면에 보입니다. Json형태의 데이터도 보이고요. 

 

우리가 원하는 것은 추가를 누를 경우 To-do화면에서 To-do List에 새로운 Task가 추가되는 것입니다. 먼저 todo.html에서 To-Do list가 보이도록 코드를 살짝 바꿔봅시다.

 

일단 views.py에서 todo.html을 보여줄 때 모든 Task를 보여주도록 수정합니다.

그리고 get으로 요청할 경우에는 리스트만 보여주고, post로 요청하는 경우 TaskCreate를 하도록 변경합니다.

 

# todo/views.py
from rest_framework.views import APIView
from .models import Task
from rest_framework.response import Response
from datetime import datetime
from django.shortcuts import render


# Create your views here.
class Todo(APIView):
    def post(self, request):
        user_id = request.data.get('user_id', "")
        name = request.data.get('name', "")
        end_date = request.data.get('end_date', None)
        if end_date:
            end_date = datetime.strptime(end_date, '%Y-%m-%d').date()
        Task.objects.create(user_id=user_id, name=name, end_date=end_date)

        tasks = Task.objects.all()
        task_list = []
        for task in tasks:
            task_list.append(dict(name=task.name, start_date=task.start_date, end_date=task.end_date, state=task.state))
        context = dict(task_list=task_list)
        return render(request, 'todo/todo.html', context=context)

    def get(self, request):
        tasks = Task.objects.all()
        task_list = []
        for task in tasks:
            task_list.append(dict(name=task.name, start_date=task.start_date, end_date=task.end_date, state=task.state))
        context=dict(task_list=task_list)
        return render(request, 'todo/todo.html', context=context)

...

 

render 안에 context를 이용해서 html에 데이터를 보낼 수 있습니다. TaskSelect에서 만들었던 task_list랑 똑같이 리스트를 만든 다음에 render에 넣어줍니다. TaskSelect와 다른 점은 특정 사용자의 id로 filter를 걸지 않고 전체 사용자의 Task를 전달한다는 점입니다. 왜냐하면 아직 특정 사용자의 id를 입력받는 부분이 없기 때문!!!

 

post로 요청할 경우에는 TaskCreate + TaskSelect 동시에 수행하도록 합니다. 즉 단순히 조회만 할 경우 todo를 get으로 조회하고 생성까지 할 경우는 todo를 post로 조회하면 됩니다.

 

이제 todo.html도 아래와 같이 수정합니다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>To-Do</title>
</head>
<body>
<form action="/todo/" method="post" class="form-inline">
<div>
    <label>name</label>
    <input type="text" name="name" />
</div>
<div>
    <label>end_date</label>
    <input type="text" name="end_date"/>
</div>
<div>
    <label>user_id</label>
    <input type="text" name="user_id"/>
</div>
<button type="submit"> 추가 </button>
</form>

<table>
    {% for task in task_list %}
    <tr>
        <td>{{ task.name }}</td>
        <td>{{ task.start_date }}</td>
        <td>{{ task.end_date }}</td>
        <td>{{ task.finish_date }}</td>
        <td>{{ task.state }}</td>
    </tr>
    {% endfor %}
</table>

</body>
</html>

 

수정한 부분은 form이 "todo/create"를 호출했었는데 단순히 "todo/"를 호출하도록 변경하고 type을 post로 변경했습니다. 이제 단순이 url을 입력해서 들어오면 get으로 "~/todo"가 실행되고, 추가 버튼을 누르면 post로 "~/todo/"가 실행됩니다.

 

이제 다시 url로 들어가 봅시다.

 

 

요롷게 됩니다.

 

화면까지 완성!!

 

 

 

마치며

이번 포스팅에서는 지난 시간에 했던 로그인 API에 대해서 응답 값을 추가하고, To-Do list 앱을 만들어보면서 새로운 서비스를 추가할 때 서버 개발자가 고려해야 하는 것들에 대해서 알아봤습니다.

 

1. 데이터를 어디에 어떻게 적재할 것인지. (데이터베이스, 파일, 메모리 등)

2. 테이블 구조를 어떻게 잡을 것인지 (어떤 필드를 만들 것인지, 기획이 먼저 나와야 될 필요가 있음)

3. 각 필드는 어떤 속성을 가지고 있을 것인지 (문자열인지, 날짜인지 숫자인지)

4. 화면에서 어떤 방식으로 호출할 것인지 (클라이언트 입장에서 어떠한 데이터가 필요한지)

 

서비스의 특성과 클라이언트에서 필요한 데이터를 생각하면서 설계를 해야 나중에 수정하는 일이 없어집니다. (물론 완전히 없진 않습니다 ㅠㅠ) 

 

이번 포스팅에서는 To-Do 앱에 대한 아주 기본적인 기능만 구현했는데, 다음 시간에는 화면과 데이터를 좀 더 상세하게 만들어보도록 하겠습니다.

 

 

ps. 뒤로 갈수록 글로만 설명하기가 상당히 힘들어지네요. 가급적 동영상도 같이 봐주시면 이해하는데 도움이 될 것 같습니다.

반응형

댓글

Designed by JB FACTORY