오늘은 회원 기능을 구현할 차례이다.
회원기능에 있어야 하는 필수 조건은 다음과 같다.
- 회원가입
- Endpoint: /api/accounts
- Method: POST
- 조건: username, 비밀번호, 이메일, 이름, 닉네임, 생일 필수 입력하며 성별, 자기소개 생략 가능
- 검증: username과 이메일은 유일해야 하며, 이메일 중복 검증(선택 기능).
- 구현: 데이터 검증 후 저장.
- 로그인
- Endpoint: /api/accounts/login
- Method: POST
- 조건: 사용자명과 비밀번호 입력 필요.
- 검증: 사용자명과 비밀번호가 데이터베이스의 기록과 일치해야 함.
- 구현: 성공적인 로그인 시 토큰을 발급하고, 실패 시 적절한 에러 메시지를 반환.
- 프로필 조회
- Endpoint: /api/accounts/<str:username>
- Method: GET
- 조건: 로그인 상태 필요.
- 검증: 로그인 한 사용자만 프로필 조회 가능
- 구현: 로그인한 사용자의 정보를 JSON 형태로 반환.
회원가입
어제와 똑같이 User모델을 먼저 만들어줘야 하는데, Django는 기본적으로 제공하는 User모델이 있기 때문에,
회원가입에서 필수적으로 입력해야 하는 정보를 가진 Custom User Model을 만들어 주려고 한다.
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
nickname = models.CharField(max_length=50)
birthday = models.DateField()
gender = models.CharField(max_length=10, blank=True, null=True)
self_introduction = models.TextField(blank=True, null=True)
def __str__(self):
return self.username
Django의 User Model은 기본적으로 username, password, email, first_name, last_name 등을
가지고 있기 때문에 새로운 속성만 정의를 해준다.
gender, self_introduction은 생략이 가능하다는 조건이 있기 때문에 null 값을 허용해준다.
유저 모델을 정의했으면 이제 요청할 url을 만들어 준다.
# 프로젝트 메인 urls.py
urlpatterns = [
path('api/accounts/', include("accounts.urls")),
]
# accounts.urls.py
urlpatterns = [
path('', views.SingupView.as_view()),]
accounts의 url로 요청이 왔을 때 뒤에 아무것도 붙이지 않는 url로 회원가입 처리를 해보겠다.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import User
from .serializers import UserSerializer
class SingupView(APIView):
def post(self, request):
user = User.objects.create_user(
username=request.data.get("username"),
password=request.data.get("password"),
first_name=request.data.get("first_name"),
last_name=request.data.get("last_name"),
email=request.data.get("email"),
nickname=request.data.get("nickname"),
birthday=request.data.get("birthday"),
)
serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email', 'first_name', 'last_name',
'nickname', 'birthday', 'gender', 'self_introduction']
원래 성별과 자기소개도 데이터를 넘겨야하지만 생략이 가능하므로 일단 기능에 집중하기로 했다.
create_user 함수를 사용하면 비밀번호를 저장할 때 비밀번호를 해시화해주기 때문에 사용했다.
응답은 받은 데이터를 직렬화해서 json형식으로 바꿔준 뒤에 다시 담아서 보내준다.
위의 로직으로도 회원가입은 잘 작동하지만 올바른 데이터가 들어온건지 검증하는 단계가 아직 없다.
여러가지 검증이 필요하지만 아직은 간단하게나마 생각나는대로 만들었다.
# validators.py
from django.core.validators import validate_email
from .models import User
def validate_signup(signup_data):
username = signup_data.get("username")
password = signup_data.get("password")
first_name = signup_data.get("first_name")
last_name = signup_data.get("last_name")
email = signup_data.get("email")
nickname = signup_data.get("nickname")
birthday = signup_data.get("birthday")
if not all([username, password, first_name, last_name, nickname, email, birthday]):
return False, "필수 입력값이 누락되었습니다."
error_messages = []
if User.objects.filter(username=username).exists():
error_messages.append("이미 존재하는 username입니다.")
if len(nickname) > 50:
error_messages.append("닉네임의 길이는 50자 이하여야 합니다.")
if len(first_name) > 50:
error_messages.append("이름이 너무 깁니다.")
if len(last_name) > 50:
error_messages.append("성이 너무 깁니다.")
try:
validate_email(email)
except:
error_messages.append("email 형식이 잘못되었습니다.")
if User.objects.filter(email=email).exists():
error_messages.append("이미 존재하는 email입니다.")
return not bool(error_messages), error_messages
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import User
from .serializers import UserSerializer
from .validators import validate_signup
class SingupView(APIView):
def post(self, request):
is_valid, error_message = validate_signup(request.data)
print(is_valid, error_message)
if not is_valid:
return Response({"error": error_message}, status=status.HTTP_400_BAD_REQUEST)
user = User.objects.create_user(
username=request.data.get("username"),
password=request.data.get("password"),
first_name=request.data.get("first_name"),
last_name=request.data.get("last_name"),
email=request.data.get("email"),
nickname=request.data.get("nickname"),
birthday=request.data.get("birthday"),
)
serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
먼저 요청된 데이터를 받으면 validate_signup에 담아서 데이터를 검증하고,
올바르지 않은 데이터를 받았으면 에러메세지를 담아서 400 상태 코드와 함께 돌려보낸다.
로그인
회원가입을 마쳤으니 로그인이 필요하다.
로그인을 성공적으로 마치면 토큰을 발급해야 되기 때문에 simplejwt를 설치해주려고 한다.
pip install djangorestframework-simplejwt
설치를 성공적으로 마치면 settings.py에 가서 설정을 해줘야 한다.
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
}
위에는 필수 사항이고 밑에는 토큰의 설정을 해주는 곳인데 access토큰의 만료기한은 5분으로,
refresh토큰의 만료기한은 1일로 하겠다는 뜻이다. (사실 안바꿔서 Default값이랑 똑같긴 하다)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import authenticate
from .models import User
from .serializers import UserSerializer
class LoginView(APIView):
def post(self, request):
user = authenticate(username=request.data.get(
"username"), password=request.data.get("password"))
if not user:
return Response({"error": "아이디나 비밀번호가 틀립니다."}, status=status.HTTP_400_BAD_REQUEST)
serializer = UserSerializer(user)
res_data = serializer.data
refresh = RefreshToken.for_user(user)
res_data['refresh_token'] = str(refresh)
res_data['access_token'] = str(refresh.access_token)
return Response(res_data)
성공적으로 로그인을 하면 user정보와 함께 토큰을 발급해준다.
프로필 조회
마지막으로 프로필을 조회하는 페이지를 만들어야하는데 여기는 로그인 처리만 잘 봐주면 된다.
# accounts.urls.py
urlpatterns = [path('<str:username>/', views.UserProfileView.as_view())]
# accounts.views.py
class UserProfileView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, username):
user = get_object_or_404(User, username=username)
if user == request.user:
serializer = UserSerializer(user)
return Response(serializer.data)
else:
return Response({"message": "로그인한 유저와 다릅니다."})
access토큰을 가지고 있지 않다면 접근을 못하게 제한하고
url의 유저네임으로 유저를 꺼내서, 만약 로그인한 유저와 url의 유저와 같다면
프로필 페이지를 보여주고, 아니면 로그인한 유저와 다르다는 메세지를 보내준다.
Bearer Token 안에 access_token을 담아서 요청을 시도해보면 잘 작동하는 것을 볼 수 있다.
'웹 개발' 카테고리의 다른 글
[Django] Pagination (1) | 2024.09.11 |
---|---|
[Django] DRF Project - 4 (0) | 2024.09.09 |
[Django] DRF Project - 2 (0) | 2024.09.05 |
[Django] DRF Project - 1 (0) | 2024.09.03 |
[Django] Token Auth with JWT (0) | 2024.08.30 |