Django/DRF

DRF CBV와 User토큰

UserDonghu 2023. 10. 20. 19:08

실습

blog / models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    head_image = models.ImageField(
        upload_to='blog/images/%Y/%m/%d/', blank=True)
    file_upload = models.FileField(
        upload_to='blog/files/%Y/%m/%d/', blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateField(auto_now=True)

    def __str__(self):
        return self.title

 

makemigrations, migrate

 

pip install djangorestframework

pip install django-cors-headers

pip install django-rest-auth

 

settings.py에 추가

authtoken 추가한 후, migrate 꼭 해야함

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # django lib app
    'rest_framework',
    'rest_framework.authtoken', # migrate를 해서 token을 생성하게 해야 합니다! 그 다음 가입부터 token생성!
    'rest_auth',
    'corsheaders',
    # custom app
    'blog',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', #최상단 추가
]

CORS_ORIGIN_ALLOW_ALL=True
CORS_ALLOW_CREDENTIALS=True

 

blog / serializers.py

from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'

 

blog / views.py

from django.shortcuts import render
from .models import Post
from rest_framework.views import APIView
from .serializers import PostSerializer
from rest_framework.response import Response

class PostListAPIView(APIView):
    
    def get(self, request): # get요청일때
        post_list = Post.objects.all()
        serializer = PostSerializer(post_list, many=True)
        return Response(serializer.data)
    
    def post(self, request):
        serializer = PostSerializer(data=request.data) # request의 데이터를 전달
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        
postlist = PostListAPIView.as_view()

 

127.0.0.1:8000/blog 가 정상적으로 나오는지 확인

 

인증된 사용자만 보게하려면 permission_classes를 이용한다

blog / views.py

from django.shortcuts import render
from .models import Post
from rest_framework.views import APIView
from .serializers import PostSerializer
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

class PostListAPIView(APIView):
    permission_classes = [IsAuthenticated] # 이 API뷰에 대한 접근 권한 설정. 로그인된 사용자만 접근가능
    
    def get(self, request): # get요청일때
        post_list = Post.objects.all()
        serializer = PostSerializer(post_list, many=True)
        return Response(serializer.data)
    
    def post(self, request):
        serializer = PostSerializer(data=request.data) # request의 데이터를 전달
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        
postlist = PostListAPIView.as_view()

로그인안하면 이렇게 뜸

 

회원가입, 로그인 등 만들어보기

 

settings.py에 추가

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}

 

blog / serializers.py 수정

from rest_framework import serializers
from .models import Post
from rest_framework.authtoken.models import Token # 토큰 모델 Token.objects.get()이런식으로 토큰 확인 가능
from rest_framework.validators import UniqueValidator # 중복 검사(회원 가입할 때 동일한 아이디가 있는지 검사 등)
from django.contrib.auth.password_validation import validate_password # 비밀번호 유효성 검사
from django.contrib.auth.models import User # User 모델(기본 User모델 사용시 사용자명, 비밀번호, 이메일 필드만 사용 가능 => 상속받아 커스터마이징 가능)

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'
        
class RegisterSerializer(serializers.ModelSerializer):
    username = serializers.CharField(
        required = True,
        validator = [UniqueValidator(queryset=User.objects.all())] # 중복 검사
    )
    email = serializers.EmailField(
        required = True,
        validators = [UniqueValidator(queryset=User.objects.all())] # 중복 검사
    )
    password = serializers.CharField(
        write_only = True, # 쓰기 전용
        required = True, 
        validators = [validate_password] # 비밀번호 유효성 검사
    )
    password2 = serializers.CharField( # 비밀번호 확인 필드
        write_only = True, 
        required = True
    )
    
    class Meta:
        model = User
        fields = '__all__'
        
    def validate(self, attrs): # 직렬화되기전 데이터 검사. attrs : Serializer에 제출된 데이터
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({'password': '비밀번호가 일치하지 않습니다.'})
        return attrs
    
    def create(self, validated_data): # 직렬화된 데이터를 기반으로 새로운 객체를 생성. validated_data : 유효성 검사를 마친 데이터
        user = User.objects.create(
            username = validated_data['username'],
            email = validated_data['email']
        )
        user.set_password(validated_data['password'])
        user.save()
        Token.objects.create(user=user)
        return user

 

blog / views.py

RegisterView 추가

from django.shortcuts import render
from .models import Post
from rest_framework.views import APIView
from .serializers import PostSerializer, RegisterSerializer
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import CreateAPIView
from django.contrib.auth.models import User

class PostListAPIView(APIView):
    permission_classes = [IsAuthenticated] # 이 API뷰에 대한 접근 권한 설정. 로그인된 사용자만 접근가능
    
    def get(self, request): # get요청일때
        post_list = Post.objects.all()
        serializer = PostSerializer(post_list, many=True)
        return Response(serializer.data)
    
    def post(self, request):
        serializer = PostSerializer(data=request.data) # request의 데이터를 전달
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        
postlist = PostListAPIView.as_view()

class RegisterView(CreateAPIView): # post요청을 받아서 새로운 객체를 생성
    queryset = User.objects.all()
    serializer_class = RegisterSerializer
    
userregister = RegisterView.as_view()

 

blog / urls.py에 urlpatterns추가한 후,

 

Django와 구분되는 다른 폴더에 html파일 만들기 (다른 서버)

register.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>register</title>
</head>
<body>
    <form action="http://127.0.0.1:8000/blog/register/" method="post">
        유저이름 : <input type="text" name="username"><br>
        이메일 : <input type="text" name="email"><br>
        패스워드 : <input type="password" name="password"><br>
        패스워드2 : <input type="password" name="password2"><br>
        <input type="submit" value="회원가입">
    </form>
</body>
</html>

 

Live Server로 켜서 회원가입되는지 확인, admin으로 접속해서 DB에 잘 들어갔는지 확인

 

로그인 만들기

blog / serializers.py

LoginSerializer 추가

from rest_framework import serializers
from .models import Post
from rest_framework.authtoken.models import Token # 토큰 모델 Token.objects.get()이런식으로 토큰 확인 가능
from rest_framework.validators import UniqueValidator # 중복 검사(회원 가입할 때 동일한 아이디가 있는지 검사 등)
from django.contrib.auth.password_validation import validate_password # 비밀번호 유효성 검사
from django.contrib.auth.models import User # User 모델(기본 User모델 사용시 사용자명, 비밀번호, 이메일 필드만 사용 가능 => 상속받아 커스터마이징 가능)
from django.contrib.auth import authenticate

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'
        
class RegisterSerializer(serializers.ModelSerializer):
    username = serializers.CharField(
        required = True,
        validators = [UniqueValidator(queryset=User.objects.all())] # 중복 검사
    )
    email = serializers.EmailField(
        required = True,
        validators = [UniqueValidator(queryset=User.objects.all())] # 중복 검사
    )
    password = serializers.CharField(
        write_only = True, # 쓰기 전용
        required = True, 
        validators = [validate_password] # 비밀번호 유효성 검사
    )
    password2 = serializers.CharField( # 비밀번호 확인 필드
        write_only = True, 
        required = True
    )
    
    class Meta:
        model = User
        fields = '__all__'
        
    def validate(self, attrs): # 직렬화되기전 데이터 검사. attrs : Serializer에 제출된 데이터
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({'password': '비밀번호가 일치하지 않습니다.'})
        return attrs
    
    def create(self, validated_data): # 직렬화된 데이터를 기반으로 새로운 객체를 생성. validated_data : 유효성 검사를 마친 데이터
        user = User.objects.create(
            username = validated_data['username'],
            email = validated_data['email']
        )
        user.set_password(validated_data['password'])
        user.save()
        Token.objects.create(user=user)
        return user
    
class LoginSerializer(serializers.ModelSerializer):
    username = serializers.CharField(required=True)
    password = serializers.CharField(write_only=True, required=True)

    class Meta:
        model = User
        fields = ['username', 'password'] # 로그인 시 아이디와 비밀번호만 필요

    def validate(self, data):
        user = authenticate(**data) # 직렬화되기전 data로 사용자를 인증햇 인증에 성공한경우 사용자 객체를 반환.
        if user: # 사용자 인증이 됐으면 사용자 토큰 반환
            token = Token.objects.get(user=user)
            return token
        raise serializers.ValidationError("유효하지 않은 로그인입니다.")

 

blog / views.py

LoginView 추가

from django.shortcuts import render
from .models import Post
from rest_framework.views import APIView
from .serializers import PostSerializer, RegisterSerializer, LoginSerializer
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import CreateAPIView, GenericAPIView
from django.contrib.auth.models import User

class PostListAPIView(APIView):
    permission_classes = [IsAuthenticated] # 이 API뷰에 대한 접근 권한 설정. 로그인된 사용자만 접근가능
    
    def get(self, request): # get요청일때
        post_list = Post.objects.all()
        serializer = PostSerializer(post_list, many=True)
        return Response(serializer.data)
    
    def post(self, request):
        serializer = PostSerializer(data=request.data) # request의 데이터를 전달
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors)
        
postlist = PostListAPIView.as_view()

class RegisterView(CreateAPIView): # post요청을 받아서 새로운 객체를 생성
    queryset = User.objects.all()
    serializer_class = RegisterSerializer
    
userregister = RegisterView.as_view()

class LoginView(GenericAPIView):
    serializer_class = LoginSerializer
    
    def post(self, request):
        serializer = LoginSerializer(data=request.data)
        if serializer.is_valid():
            token = serializer.validated_data
            return Response({'token': token.key})
        return Response(serializer.errors)
    
userlogin = LoginView.as_view()

 

blog / urls.py에 패턴 추가, 

 

다른 폴더에 login.html 파일 만들기

login.html

폼의 제출을 js로 가로채서 json형식으로 fetch를 이용해서 post보내기

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>login</title>
</head>
<body>
    <form action="http://127.0.0.1:8000/blog/login/" method="post">
        유저이름 : <input type="text" name="username"><br>
        패스워드 : <input type="password" name="password"><br>
        <input id="login" type="submit" value="로그인">
    </form>
    <script>
        const login = document.querySelector('#login');
        login.addEventListener('click', (e) => {
            e.preventDefault(); // submit의 기본동작을 막는다.
            const username = document.querySelector('input[name="username"]').value;
            const password = document.querySelector('input[name="password"]').value;
            const data = {
                username: username,
                password: password
            }
            console.log(data)


            // fetch를 이용해서 서버에 POST 요청을 보낸다.
            fetch('http://127.0.0.1:8000/blog/login/', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(data)
            })
            .then(response => response.json())
            .then(data => {
                console.log(data)
            })
        })
    </script>
</body>
</html>

 

write.html

글쓰기 까지 구현.

로그인시 로컬스토리지에 유저토큰정보를 저장하고, 글쓸때는 로컬스토리지에서 유저토큰을 가져와서 같이 보낸다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>login</title>
</head>
<body>
    <form action="http://127.0.0.1:8000/blog/login/" method="post">
        유저이름 : <input type="text" name="username"><br>
        패스워드 : <input type="password" name="password"><br>
        <input id="login" type="submit" value="로그인">
    </form>
    <form action="" method="post">
        title: <input type="text" name="title">
        content: <input type="text" name="content">
        <input id="write" type="submit" value="게시물작성">
    </form>
    <script>
        const login = document.querySelector('#login');
        const write = document.querySelector('#write');

        login.addEventListener('click', (e) => {
            e.preventDefault(); // submit의 기본동작을 막는다.
            const username = document.querySelector('input[name="username"]').value;
            const password = document.querySelector('input[name="password"]').value;
            const data = {
                username: username,
                password: password
            }
            console.log(data)

            // fetch를 이용해서 서버에 POST 요청을 보낸다.
            fetch('http://127.0.0.1:8000/blog/login/', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(data)
            })
            .then(response => response.json())
            .then(data => {
                console.log(data)
                localStorage.setItem('token', data.token)
            })

        })


        write.addEventListener('click', (e) => {
            e.preventDefault(); // submit의 기본동작을 막는다.
            const title = document.querySelector('input[name="title"]').value;
            const content = document.querySelector('input[name="content"]').value;
            const data = {
                title: title,
                content: content
            }
            console.log(data)
            const token = localStorage.getItem('token')

            if (token){
                // fetch를 이용해서 서버에 POST 요청을 보낸다.
                fetch('http://127.0.0.1:8000/blog/', {
                    method: 'POST',
                    headers: {
                        Authorization: `Bearer ${token}`,
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(data)
                })
                .then(response => response.json())
                .then(data => {
                    console.log(data)
                })
            } else {
                alert('로그인이 필요합니다.')
            }
        })
    </script>
</body>
</html>

 

하지만 이렇게 하면 제대로 글이 올라가지 않는다.

인증을 세션기반이 아니라 토큰기반으로 바꿔줘야한다.

blog / views.py

from django.shortcuts import render
from .models import Post
from rest_framework.views import APIView
from .serializers import PostSerializer, RegisterSerializer, LoginSerializer
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import CreateAPIView, GenericAPIView
from django.contrib.auth.models import User
from rest_framework.authentication import TokenAuthentication
from rest_framework.authtoken.models import Token

class PostListAPIView(APIView):
    # permission_classes = [IsAuthenticated] # 이 API뷰에 대한 접근 권한 설정. 로그인된 사용자만 접근가능
    authentication_classes = [TokenAuthentication] # 토큰으로 확인
    
    def get(self, request): # get요청일때
        post_list = Post.objects.all()
        serializer = PostSerializer(post_list, many=True)
        return Response(serializer.data)
    
    def post(self, request):
        # request에 headers에 있는 Authorization: Bearer ${token}로 넘어온 토큰 확인하여 post 처리
        print(request.headers)
        print(request.headers['Authorization'])
        print(request.headers['Authorization'].split(' ')[1])
        token = request.headers.get('Authorization', None)
        print(token)
        if token:
            print('토큰 존재!')
            try:
                token_key = token.split()[1]
                # 유효한 토근인지 확인합니다. 아래 코드에서 token이 유효하지 않으면 애러 발생하면 except로 넘어갑니다.
                token = Token.objects.get(key=token_key)
                print('사용자:', token.user.username)
            except:
                print('토큰이 유효하지 않습니다.')
                return Response({'error':'에러야!!'}, status=400)
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)
        
postlist = PostListAPIView.as_view()

class RegisterView(CreateAPIView): # post요청을 받아서 새로운 객체를 생성
    queryset = User.objects.all()
    serializer_class = RegisterSerializer
    
userregister = RegisterView.as_view()

class LoginView(GenericAPIView):
    serializer_class = LoginSerializer
    
    def post(self, request):
        serializer = LoginSerializer(data=request.data)
        if serializer.is_valid():
            token = serializer.validated_data
            return Response({'token': token.key})
        return Response(serializer.errors)
    
userlogin = LoginView.as_view()

 

이런식으로 Django서버와 외부 FE 서버를 분리해서 DRF를 사용할 수 있다.

토큰 개념은 너무 어려우니까 나중에 다시 한번 보는걸로..