[0616] Django Rest Framework

nikevapormax·2022년 6월 16일
0

TIL

목록 보기
51/116
post-custom-banner

DRF 특강

rest api

  • django rest framework에서는 아래의 http method를 사용한다. 또한 CBV(Class Base View)를 사용해 하나의 클래스 안에서 모든 api를 다룰 수 있게 된다.

😠 http method

  • GET : 조회
  • POST : 생성
  • PUT : 수정
  • DELETE : 삭제

FBV, CBV

- FBV

  • Function Base View
  • def 명령어를 통해 선언되는 단독의 함수들을 통해 구현된 View
  • 지금까지 우리가 작성해 온 형식이 바로 FBV이다.
def edit(request, id):
    article = PostModel.objects.get(id=id)
    movie = Movie.objects.get(id=article.title_id)

    context = {
        'article': article,
        'movie': movie,
    }

    return render(request, 'post/edit.html', context)

- CBV

  • Class Base View
  • Class를 사용하며, django에서 이미 구현되어 있는(django.views) generic view들을 import 하고, 이 클래스 내의 변수와 메소드를 조작하여 보다 쉽게 구현할 수 있는 View
  • 이제 작성해 갈 방식이다.
class UserView(APIView): 
    permission_classes = [permissions.AllowAny] # 누구나 view 조회 가능

    def get(self, request):
        return Response({'message': 'get method!!'})
        
    def post(self, request):
        return Response({'message': 'post method!!'})

    def put(self, request):
        return Response({'message': 'put method!!'})

    def delete(self, request):
        return Response({'message': 'delete method!!'})

django project 세팅

😠 가상환경 세팅

$ python -m venv venv
$ source venv/bin/activate
$ pip install django
$ pip install djangoframework

😠 가상환경 관리

$ pip freeze > requirement.txt
  • 위의 명령어를 통해 requirement.txt를 만들어 깃에 올리게 되면, 다른 팀원이 모듈을 찾아서 설치할 필요없이 아래의 명령어를 통해 한 번에 다운받을 수 있다.
$ pip install -r requirements.txt

😠 django project 생성

$ django-admin startproject django_rest_framework .

😠 user app 생성

$ django-admin startapp user 
  • app을 생성한 후 django_rest_framework/settings.py에 입력한다.
#### 상단 생략 ####

INSTALLED_APPS = [
    'user',
    'rest_framework',
    
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

#### 하단 생략 ####

😠 user app - urls.py 세팅

  • 먼저 전체적인 프로젝트의 url을 설정한다.
  • django_rest_framework/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('user.urls')), 
]
  • 이제 user의 url을 세팅해 준다.
    • CBV는 url을 세팅할 때 꼭 마지막에 as_view()를 붙여줘야 한다.
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('user/', views.UserView.as_view()),
]

views.py 작성

😠 user/views.py

  • django의 rest framework에서 지원하는 permissions를 사용하기 위해서는 APIView를 import하고, permissions 또한 import 해야 한다.

  • permissions의 종류

    • AllowAny : 인증여부에 상관없이 view 호출 허용 (default)
    • IsAuthenticated : 인증된 요청에 한해서 view 호출 허용
    • IsAdminUser : Staff 인증 요청에 한해서 view 호출 허용
    • IsAuthenticatedOrReadOnly : 비인증 요청에게는 읽기 권한만 허용
    • DjangoModelPermissions : 인증된 요청에 한해서만 view 호출 허용, 추가로 유저별 인증 권한체크를 수행
    • DjangoModelPermissionsOrAnonReadOnly : DjangoModelPermissions 와 유사하나 비인증 요청에 대해서는 읽기 권한만 허용
    • DjangoObjectPermissions :비인증된 요청 거부. 인증된 레코드 접근에 대한 권한체크를 추가로 수행
  • 우리가 사용할 세 가지의 permissions에 대해 확인해보자.(command + 클릭)

    • AllowAny의 경우, 알다시피 모든 값이 True로 반환된다. 따라서 모두가 view에 접근할 수 있는 것이다.
    • IsAuthenticated의 경우, 불리언 값으로 사용자가 로그인을 해야 하고 인증이 되어 있는 경우에만 view에 접근할 수 있는지 반환하는 것을 볼 수 있다.
    • IsAdminUser의 경우, 위와 동일하게 불리언 값으로 사용자가 로그인을 해야 하고 관리자이어야만 view에 접근할 수 있다는 것을 알 수 있다.
  • 그리고 class에서 APIView를 상속받아 사용한다.

  • user/views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import permissions

class UserView(APIView):
    permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    # permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    def get(self, request):
        return Response({"msg": "get method!!" })
    def post(self, request):
        return Response({"msg": "post method!!" })
    def put(self, request):
        return Response({"msg": "put method!!" })
    def delete(self, request):
        return Response({"msg": "delete method!!" })
  • postman을 활용해 테스트해보자.
    • get 요청에 대한 리턴값이 제대로 돌아온 것을 볼 수 있다.
    • delete도 확인해보자.
    • 이게 되는 이유는 우리가 class 안에서 함수로 선언을 해주었기 때문이다. 우리는 그저 선언만 한다면 알아서 진행된다. 하지만 코드에 써져있듯이 함수명은 반드시 그대로 써줘야 한다. 이는 django에서 선언한 것들이기 때문에 만약 get을 gets로 바꿔서 신호를 보낸다면 아무런 결과값이 돌아오지 않는다.

models.py 작성

😠 custom user model

  • django에서 제공하는 기본 유저 모델이 아닌 사용자가 커스텀하는 유저 모델을 생성해보도록 하겠다.
  • 실제로 실무에서는 django의 기본 유저 모델을 잘 활용하지 않는다고 한다.
  • user/models.py
    • 우선 커스텀 유저 모델을 만들기 위해서 BaseUserManagerAbstractBaseUser를 import 해야 한다.
    • 그리고 예전에 했던 것과 달리 AbstractUser가 아닌 AbstractBaseUser를 상속해 사용하면 된다.
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser

# custom user model
class User(AbstractBaseUser):
    
    # "사용자 계정"은 admin 페이지에서 나오는 메뉴의 이름 / unique=True를 설정해 단 하나의 값만 입력할 수 있도록 함
    username = models.CharField("사용자 계정", max_length=50, unique=True)
    password = models.CharField("비밀번호", max_length=128)
    email = models.EmailField("이메일", max_length=100)
    name = models.CharField("이름", max_length=20)
    # auto_now_add : 최초 생성 시의 시간을 자동으로 입력해줌.(그 후 업데이트에 대한 시간 기록은 x) 주로 가입일, 최초 생성일 등에 사용함
    join_data = models.DateTimeField("가입일자", auto_now_add=True)
  • 위와 같이 세팅한 후, django에게 우리는 위의 모델을 user의 기본 모델로 사용할 것이라고 알려줘야 한다.
  • django_rest_framework/settings.py
    • 해당 파일의 맨 아래에 선언하도록 한다.
    • 우리가 아래와 같이 AUTH_USER_MODEL을 선언해 주지 않는다면, django는 자동으로 AUTH_USER_MODEL = "auth.User"라는 기본 유저 모델을 사용한다. 그러므로 우리가 만든 모델을 사용하려면 아래와 같이 선언해 주어야 한다.
    • REST_FRAMEWORK에 대한 설정을 다음과 같이 하면 된다. 주석의 내용과 같이 해도 되고 하지 않아도 당장 프로그램을 돌리는데에는 무방하다.
#### 상단 생략 ####

AUTH_USER_MODEL = 'user.User'

# 이 부분은 없어도 상관은 없다. 아래와 같이 세팅하지 않으면 장고가 알아서 기본 세팅을 가져간다. 
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [ # 기본적인 view 접근 권한 지정
        'rest_framework.permissions.AllowAny'
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [ # session 혹은 token을 인증 할 클래스 설정
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication'
    ],
    'DEFAULT_PARSER_CLASSES': [ # request.data 속성에 액세스 할 때 사용되는 파서 지정
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ]
}
  • 하지만 위와 같이 모델을 생성하고 서버를 돌려보면 아래와 같은 에러를 만나게 된다.
AttributeError: type object 'User' has no attribute 'USERNAME_FIELD'
  • 커스텀 모델을 만들긴 했지만, django에서 요구하는 필드값들과의 매칭이 됮 않아서 그런 것이다. 따라서 매칭을 해주도록 하겠다.
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser

# custom user model 사용 시 UserManager 클래스와 create_user, create_superuser 함수가 정의되어 있어야 함
class UserManager(BaseUserManager):
    def create_user(self, username, password=None):
        if not username:
            raise ValueError('Users must have an username')
        user = self.model(
            username=username,
        )
        user.set_password(password)
        user.save(using=self._db)
        return user
    
    # python manage.py createsuperuser 사용 시 해당 함수가 사용됨
    def create_superuser(self, username, password=None):
        user = self.create_user(
            username=username,
            password=password
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

# custom user model
class User(AbstractBaseUser):
    
    # "사용자 계정"은 admin 페이지에서 나오는 메뉴의 이름 / unique=True를 설정해 단 하나의 값만 입력할 수 있도록 함
    username = models.CharField("사용자 계정", max_length=50, unique=True)
    password = models.CharField("비밀번호", max_length=128)
    email = models.EmailField("이메일", max_length=100)
    name = models.CharField("이름", max_length=20)
    # auto_now_add : 최초 생성 시의 시간을 자동으로 입력해줌.(그 후 업데이트에 대한 시간 기록은 x) 주로 가입일, 최초 생성일 등에 사용함
    join_data = models.DateTimeField("가입일자", auto_now_add=True)
    
    # 사용자 계정이 활성화 되었는지(False이면 비활성화)
    is_active = models.BooleanField(default=True)
    # admin 권한을 사용할 것인지(is_staff에서 해당 값 사용)
    is_admin = models.BooleanField(default=True)
    
    # 사용자가 로그인할 때 사용하는 id로 어떤 것을 사용할래? 라는 것에 지정을 해준 것.
    # 물론 다른 값으로도 사용 가능하다!! 
    USERNAME_FIELD = 'username'
    
    # 슈퍼계정을 생성할 때 입력해야 할 값들을 지정할 수 있음.
    # 아래와 같이 사용하지 않아도 되지만, 선언은 해야 함
    REQUIRED_FIELDS = []
    
    objects = UserManager()
    
    def __str__(self):
        return self.username
    
    # 로그인 사용자의 특정 테이블의 crud 권한을 설정, perm table의 crud 권한이 들어간다.
    # admin일 경우 항상 True, 비활성 사용자(is_active=False)의 경우 항상 False
    def has_perm(self, perm, obj=None):
        return True
    
    # 로그인 사용자의 특정 app에 접근 가능 여부를 설정, app_label에는 app 이름이 들어간다.
    # admin일 경우 항상 True, 비활성 사용자(is_active=False)의 경우 항상 False
    def has_module_perms(self, app_label): 
        return True
        
    # admin 권한 설정
    @property
    def is_staff(self):
        return self.is_admin
  • 이제 migration을 해주면 된다.
  • 앞에서 언급했던 REQUIRED_FIELDS에 REQUIRED_FIELDS = ['email', 'name']를 해주면 다음과 같이 슈퍼유저를 생성할 때 이메일과 이름을 넣어주게 된다.

로그인 / 로그아웃 구현

😠 로그인

  • 아까와 다르게 permissions를 한 번 바꿔서 포스트맨을 실행해보도록 하겠다.
  • user/views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import permissions

class UserView(APIView):
    # permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    def get(self, request):
        return Response({"msg": "get method!!" })
    def post(self, request):
        return Response({"msg": "post method!!" })
    def put(self, request):
        return Response({"msg": "put method!!" })
    def delete(self, request):
        return Response({"msg": "delete method!!" })

  • 현재 우리는 유저 모델을 만들긴 했으나 로그인도 되어 있지 않고 인증도 되어 있지 않다. 따라서 위와 같은 에러 메세지가 리턴되는 것이다.
  • 다음 class를 views.py에 추가하고 테스트를 진행해보자.
# 로그인 및 로그아웃에 사용
from django.contrib.auth import login, authenticate, logout

class UserAPIView(APIView):
    permission_classes = [permissions.AllowAny]
    
    # 로그인
    def post(self, request):
        username = request.data.get('username', '')
        password = request.data.get('password', '')
        
        # 변수 user에는 인증에 성공하면 user가 담기고, 인증 실패하면 None이 담기게 됨
        user = authenticate(request, username=username, password=password)
        
        if not user:
            return Response({"error": "존재하지 않는 계정 또는 일치하지 않는 비밀번호를 입력하셨습니다."})
        login(request, user)
        return Response({"msg": "login success!!"})
  • user/urls.py를 설정하는 것을 잊지말자!
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('user/', views.UserView.as_view()),
    path('login/', views.UserAPIView.as_view()),
]
  • 다음과 같은 결과를 볼 수 있다. 당연히 아무런 값도 입력하지 않고 무작정 전송 버튼을 누른 것이라 그렇지만, 로직이 잘 작동하고 있다는 것을 알 수 있다.
  • 아까 생성했던 어드민 계정을 넣고 로그인을 진행하였더니 로그인에 성공한 것을 볼 수 있다.
  • 하지만 여기서 내가 비밀번호를 잘못 친다면 어떻게 될까? 바로 CSRF token이 없다고 울부짖는 것을 볼 수 있다.
  • 이유는 로그인한 사용자가 post, put, delete를 사용하면 csrf 에러가 발생하기 때문이다. 그러므로 나는 아래의 코드를 붙여넣겠다.
    • csrf token을 쿠키에서 뽑아다가 헤더에 넣어서 보내주겠다는 의미.
var xsrfCookie = postman.getResponseCookie("csrftoken");
postman.setGlobalVariable('csrftoken', xsrfCookie.value);

  • 그 다음 헤더 메뉴로 와서 key 값과 value 값에 다음과 같은 값을 채워 넣어준다. 그리고 실행하면 다시 이전과 같이 비밀번호가 틀렸다는 에러 메세지를 정상적으로 볼 수 있다.

😠 로그아웃

  • 로그아웃은 위에서 다뤘듯이 delete 메서드를 활용하면 된다.
  • 그 전에 로그인에서와 같이 패키지를 임포트해줘야 한다.
  • user/views.py
# 로그인 및 로그아웃에 사용
from django.contrib.auth import login, authenticate, logout

class UserAPIView(APIView):
    permission_classes = [permissions.AllowAny]
    
    # 로그인
    def post(self, request):
        username = request.data.get('username', '')
        password = request.data.get('password', '')
        
        # 변수 user에는 인증에 성공하면 user가 담기고, 인증 실패하면 None이 담기게 됨
        user = authenticate(request, username=username, password=password)
        
        if not user:
            return Response({"error": "존재하지 않는 계정 또는 일치하지 않는 비밀번호를 입력하셨습니다."})
        login(request, user)
        return Response({"msg": "login success!!"})
    
    # 로그아웃
    def delete(self, request):
        logout(request)
        return Response({"msg": "logout success!!"})
  • user/urls.py
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('user/', views.UserView.as_view()),
    path('login/', views.UserAPIView.as_view()),
    path('logout/', views.UserAPIView.as_view()),
]
  • 로그인에서와 마찬가지로 csrf token 코드를 test 탭에 넣어주고, 헤더에 사진에서와 같이 token을 실어준 뒤, 진행하면 로그아웃 성공 메세지를 볼 수 있다.

외래키에 대한 이해

😠 OneToOneField

  • 일대일 관계에 대한 class를 생성해보도록 하자.
  • 어제 공부를 하며 알아낸 것과 같이 user = models.ForeignKey(User, verbose_name="사용자", on_delete=models.CASCADE, unique=True)를 사용해도 문제는 없다. 하지만 django는 아래와 같은 메세지를 보낸다. 뜻은 야 그거 OneToOneField이랑 별차이 없어. OneToOneField 써.이다.
user.UserProfile.user: (fields.W342) Setting unique=True on a ForeignKey has the same effect as using a OneToOneField.
HINT: ForeignKey(unique=True) is usually better served by a OneToOneField.
  • 그래서 깔끔하게 OneToOneField를 사용해서 작성해보았다.
  • user/models.py
# User Profile model
class UserProfile(models.Model):
    user = models.OneToOneField(User, verbose_name="사용자", on_delete=models.CASCADE)
    introduction = models.TextField("자기소개")
    birth = models.DateField("생일")
    age = models.IntegerField("나이")
    hobby = models.CharField("취미", max_length=50)
    
    def __str__(self):
        return f'{self.user.username} 님의 프로필'    
  • 이제 admin 페이지에서 우리가 작성한 모델들을 확인해보기 위해 user/admin.py에 다음 코드를 작성해보자.
from django.contrib import admin
from .models import User as UserModel, UserProfile as UserProfileModel

# Register your models here.
admin.site.register(UserModel)
admin.site.register(UserProfileModel)
  • admin 페이지로 가 user1에 대한 프로필을 하나 설정하고, 똑같이 user1에 대한 프로필을 또 설정해보려 했더니 아래 파란색 박스와 같이 이미 존재하는 user라는 에러 메세지가 나오는 것을 볼 수 있다.
  • 만약 OneToOneField가 아닌 ForeingKey로 설정했다면 다음과 같이 두 개의 프로필 설정이 가능하게 된다.
  • 즉, OneToOneField는 하나의 레코드만을 바라볼 수 있도록 설정하는 것이다. user와 user profile의 케이스처럼 하나씩 밖에 존재할 수 없는 경우에 사용하면 된다.

😠 ManyToManyField

  • 방금 유저 프로필을 작성하면서 취미를 여러 개 등록하면 좋겠다라는 생각이 들었다. 그러므로 취미 클래스를 따로 만들어 다대다 관계를 설정해보도록 하겠다.
  • user/models.py
# Hobby model
class Hobby(models.Model):
    name = models.CharField("취미 이름", max_length=50)    
    
    def __str__(self):
        return self.name

# User Profile model
class UserProfile(models.Model):
    user = models.OneToOneField(User, verbose_name="사용자", on_delete=models.CASCADE)
    introduction = models.TextField("자기소개")
    birth = models.DateField("생일")
    age = models.IntegerField("나이")
    hobby = models.ManyToManyField(Hobby, verbose_name="취미")
    
    def __str__(self):
        return f'{self.user.username} 님의 프로필'    
       
  • 모델을 위와 같이 설정한 뒤 user/admin.py를 설정한다.
from django.contrib import admin
from .models import User as UserModel, UserProfile as UserProfileModel, Hobby as HobbyModel

# Register your models here.
admin.site.register(UserModel)
admin.site.register(UserProfileModel)
admin.site.register(HobbyModel)
  • 그 다음 admin 페이지에서 확인해보면 여러가지의 취미를 선택해 프로필에 넣을 수 있는 것을 볼 수 있다.
profile
https://github.com/nikevapormax
post-custom-banner

0개의 댓글