[DRF Project] 게시판 만들기#2

Gyubster·2022년 1월 17일
0

게시판만들기

목록 보기
2/4

가상환경 설정하기

필자는 anaconda 환경에서 개발을 작업함으로 코드가 약간 다를 수 있다. 기본적인 환경 설정을 위한 코드들은 아래와 같다.

# 가상환경 생성
conda create --name=drf_forum python='3.9'

# 가상환경 활성화
conda activate drf_forum

# 가상환경내 mysqlclient 설치
pip install mysqlclient

# 가상환경내 Django REST Framework 설치
pip install djangorestframework

Database 생성

Database 생성을 위해서 mysql을 사용할 것이다. Database 생성을 위해서 mysql shell에서 사용한 코드는 아래와 같다.


# mysql shell 열기
mysql -u root -p

# mysql shell에서 database 생성
> CREATE DATABASE drf_forum CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci

이후, Django models.py내에 작성한 내용을 기반으로 migrate를 하여 본 프로젝트를 위한 데이터 베이스를 만들 예정이다.

DRF Directory 구성

필자가 DRF를 사용하면서 어렵게 느낀 부분은 "어떻게 RESTful 하게 디렉토리를 구성할 것인가?"이다. DRF의 경우, 사람들마다 다양한 방식으로 폴더 구성을 하여 사용하는 것으로 보인다.(필자의 생각이다.) 따라서, 아래의 예시가 정답은 아니니 참고만 하면 도움이 될 것 같다.

처음으로 해아할 것은 Django app 설치이다. 프로젝트를 위해서 필요한 Django app은 user(유저), post(게시글)로 두개 임으로, 아래의 코드로 app을 설치한다.

# user app 설치
django-admin startapp user

# post app 설치
django-admin startapp post

설치를 하게되면 보이게 되는 Directory 구조는 아래와 같다.

하지만, 아래와 같은 구조로 디렉토리를 수정하여 사용할 것이다. ursl.py, views.py의 경우, 상위 디렉토리의 post, user 폴더내 파일에 작성하지 않고 api 폴더의 post, user 폴더내 파일에 작성하는 것이 특징이다.

DRF 기본 설정하기

Django 기본적인 설정은 my_settings.py 만들기, drf_forum/settings.py 설정 등 프로젝트를 진행하기 위해서 아래와 같은 나름(?) 간단한 작업을 진행해야된다. 설정한 파일의 디렉토리와 설정한 사항은 대략적으로 아래와 같다.

/drf_forum/settings.py
/my_settings.py

# 1. 주석제거하기
# 2. SECRET_KEY 내용 my_settings.py 옮기기
# 3. DATABASES 내용 my_settings.py 옮기기
# 4. 기타사항 (ALLOWED_HOST 세팅, ALGRORITHM 설정 , 설치된 앱 추가하기 등)

User app 설정하기

DRF에서는 User 관련 app을 설정하는 방법은 되게 다양하다. 본 프로젝트에서는 JWT 토큰 인증/ view: generics view / serializer: model serializer를 이용하여 세팅을 하려고한다.

  1. models.py 세팅하기
    BaseUserManager를 이용하여 create_user, create_staff, create_superuser의 메쏘드를 본 프로젝트의 상황에 맞게 수정하였다.
/user/models.py
from django.db                  import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager

class UserManager(BaseUserManager):
    
    def create_user(self, email, nickname, password):
        
        if not email:    
            raise ValueError('must have user email')
        
        if not nickname:    
            raise ValueError('must have user nickname')

        user = self.model(
                email       = self.normalize_email(email),
                nickname    = nickname
                )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_staff(self, email, nickname, password):
        user = self.create_user(
                email,
                nickname = nickname,
                password = password
                )
        user.is_staff = True
        user.save(using=self._db)
        return user

    def create_superuser(self, email, nickname, password):
        user = self.create_user(
                email,
                nickname = nickname,
                password = password
                )
        user.is_superuser = True
        user.save(using=self._db)
        return user

class User(AbstractBaseUser):
    
    email = models.CharField(
            verbose_name    = 'email',
            max_length      = 100,
            unique          = True
            )

    nickname = models.CharField(
            verbose_name    = 'nickname',
            max_length      = 10,
            unique          = True
            )

    is_active = models.BooleanField(
            verbose_name    = 'is_active',
            default         = True 
            )

    is_staff = models.BooleanField(
            verbose_name    = 'is_staff',
            default         = False
            )

    is_superuser = models.BooleanField(
            verbose_name    = 'is_superuser',
            default         = False
            )
    
    USERNAME_FIELD = 'email'

    objects = UserManager()

    class Meta:
        db_table = 'users'
  1. serializers.py 세팅하기

회원가입, 로그인에 대한 Serializer을 작성하였다. 회원가입의 경우, ModelSerializer를 이용하여 비교적 간편하게 작성하였다. 로그인의 경우는 ModelSerializer를 사용하여 만들어볼 수 있을까? 라는 고민을 하였지만 로그인을 할때 사용하는 변수가 email, password 밖에 없기 때문에 nickname, is_active 등 다른 변수들이 없기 때문에 오류를 뿜어냈다. 그래서 결국 로그인만 기본 Serializer에 validate 메쏘드를 상황에 맞게 수정하였다.

/api/user/serializers.py

from django.contrib.auth.models     import update_last_login
from django.contrib.auth            import authenticate, get_user_model
from rest_framework                 import serializers
from rest_framework_jwt.settings    import api_settings

JWT_PAYLOAD_HANDLER = api_settings.JWT_PAYLOAD_HANDLER
JWT_ENCODE_HANDLER  = api_settings.JWT_ENCODE_HANDLER
User = get_user_model()

class UserSignUpSerializer(serializers.ModelSerializer):
    
    class Meta:
        model        = User
        fields       = ['email', 'nickname', 'password']
        extra_kwargs = {
                'email': {'required':True},
                'nickname': {'required':True},
                'password': {'required':True},
                } 

    def create(self, validated_data):
        user = User.objects.create(
                email       = validated_data['email'],
                nickname    = validated_data['nickname']
                )
        user.set_password(validated_data['password'])
        user.save()
        return user

class UserSignInSerializer(serializers.Serializer):
    email       = serializers.CharField(max_length=64)
    password    = serializers.CharField(max_length=128, write_only=True)
    token       = serializers.CharField(max_length=255, read_only=True)
                    
    def validate(self, data):
        email       = data.get('email', None)
        password    = data.get('password', None)
        
        user = authenticate(email=email, password=password)
        if user is None:
            results = {
                    'email':'None'
                    }
            return results
        
        try:
            payload     = JWT_PAYLOAD_HANDLER(user)
            jwt_token   = JWT_ENCODE_HANDLER(payload)

            update_last_login(None, user)

        except User.DoesNotExist:
            raise serializers.ValidationError(
                'User with given email and password does not exists'
            )
        
        results = {
                'email'       : user.email,
                'access_token': jwt_token
            }

        return results 
  1. views.py 세팅하기
/api/user/views.py
from rest_framework                 import generics, status
from rest_framework.permissions     import AllowAny
from rest_framework.response        import Response

from .serializers import UserSignInSerializer, UserSignUpSerializer
from user.models  import User

class UserSignIn(generics.GenericAPIView):
    queryset            = User.objects.all()
    serializer_class    = UserSignInSerializer
    permission_classes  = [AllowAny]

    def post(self, request):
        serializer = UserSignInSerializer(data = request.data)
        
        if not serializer.is_valid(raise_exception=True):
            return Response({"message": "Request Body Error."}, status=status.HTTP_409_CONFLICT)
        
        if serializer.validated_data['email'] == 'None':
            return Response({"message": "fail"}, status=status.HTTP_200_OK)
        response = {
                "access_token": serializer.validated_data['access_token']
                }

        return Response(response, status=status.HTTP_200_OK)

class UserSignUp(generics.CreateAPIView):
    queryset            = User.objects.all()
    serializer_class    = UserSignUpSerializer
    permission_classes  = [AllowAny]

  1. urls.py 세팅하기
/drf_forum/urls.py
from django.contrib import admin
from django.urls    import path, include

urlpatterns = [
    path('admin', admin.site.urls),
    path('api/user', include('api.user.urls')),
    ]

/api/user/urls.py
from django.urls             import path
from django.utils.functional import unpickle_lazyobject

from .views import UserSignIn, UserSignUp

app_name = 'users'

urlpatterns = [
        path('/signin', UserSignIn.as_view(), name='user-signin'),
        path('/signup', UserSignUp.as_view(), name='user-signup')
        ]

세팅을 진행하면서 JWT 관련한 에러가 많이 일어나 생각보다 오랜시간이 걸렸다. 그래도, 일단 회원가입 및 로그인 모두 잘되는 것을 확인하였다. 다..다행히 다 잊어먹지는 않은 것 같다..

profile
공부하는 예비 개발자

0개의 댓글