<회원가입>
- 회원가입부터 너무나 많은 오류를 만났다
- 그중 기억 남는 거 하나만 적는다..
- 유저필드로는 이메일, 이름, 성별, 나이, 자기소개가 있는데, 회원가입 시 자기소개만 빼고 필수값으로 설정했다
- 그랬더니 superuser를 만들 때 필요한 필드가 없다고 ,,,ㅜㅜ
- superuser는 그냥 이메일이랑 패스워드만 입력받아서 생성하고 싶은데...
- 그리고 명령어 쓰면 어차피 이메일이랑 패스워드 쓰는 것밖에 안 나온다,,
- 그래서 그냥 나머지 값들은 수동으로 적어놨다
class UserManager(BaseUserManager):
def create_user(self, email, name, gender, age, introduction, password=None):
...
def create_superuser(self, email, password=None):
user = self.create_user(
email=email,
password=password,
name="관리자",
gender="non-choice",
age="0",
introduction="관리자 계정입니다"
)
...
- 이런 식으로 ㅋㅋㅋ
- 여튼 이렇게 하니까 superuser도 잘 생성되고 일반 유저도 잘 가입된다
- models.py
- 참조: 파이썬 장고 실무 심화 3주차 : 회원기능 — 구민정의 개발일지 (tistory.com)
- 강의 들으면서 정리한 부분을 바탕으로 작성했다
# users/models.py
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
# 나이 최솟값, 최댓값을 설정하기 위해 import함
from django.core.validators import MinValueValidator, MaxValueValidator
class UserManager(BaseUserManager):
def create_user(self, email, name, gender, age, introduction, password=None):
if not email:
raise ValueError("Users must have an email address")
user = self.model(
email=self.normalize_email(email),
name=name,
gender=gender,
age=age,
introduction=introduction,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None):
user = self.create_user(
email=email,
password=password,
name="관리자",
gender="non-choice",
age="0",
introduction="관리자 계정입니다"
)
user.is_admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
GENDER_CHOICES = (
("male", "남자"),
("female", "여자"),
("unknown", "모름"),
("non-choice", "선택하지 않음")
)
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)
name = models.CharField("이름", max_length=20)
gender = models.CharField("성별", max_length=20, choices=GENDER_CHOICES)
age = models.IntegerField(
"나이", validators=[MinValueValidator(0), MaxValueValidator(120)])
introduction = models.TextField("자기소개", blank=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = "email"
# 필드에 blank=True, null=True를 하지 않으면 필수값이 되던데 이 부분이 왜 따로 있는지 궁금
REQUIRED_FIELDS = []
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
@property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
- urls.py
# users/urls.py
from django.urls import path
from users import views
urlpatterns = [
path('api/signup/', views.SignupView.as_view(), name='signup_view'),
]
- serializers.py
# users/serializers.py
from rest_framework import serializers
from users.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
def create(self, validated_data):
user = super().create(validated_data)
password = user.password
user.set_password(password)
user.save()
return user
- views.py
# users/views.py
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from users.serializers import UserSerializer
class SignupView(APIView):
def post(self, request):
"""회원가입"""
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"message": "회원가입이 완료되었습니다!"}, status=status.HTTP_201_CREATED)
else:
return Response({"message": f"{serializer.errors}"}, status=status.HTTP_400_BAD_REQUEST)
<로그인>
- 로그인은 어렵지 않았다
- simpleJWT 방식을 이용했는데 강의 때 배운 걸 써먹고 싶었다
- urls.py
# users/urls.py
from django.urls import path
from users import views
from rest_framework_simplejwt.views import (
TokenRefreshView,
)
urlpatterns = [
...
path('api/token/', views.CustomTokenObtainPairView.as_view(),
name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
- serializers.py
# users/serializers.py
...
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
...
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# 토큰 페이로드 부분에 email, name, gender, age를 추가해보았다
# Add custom claims
token['email'] = user.email
token['name'] = user.name
token['gender'] = user.gender
token['age'] = user.age
return token
- views.py
# users/views.py
...
from users.serializers import CustomTokenObtainPairSerializer
from rest_framework_simplejwt.views import (
TokenObtainPairView,
)
...
class CustomTokenObtainPairView(TokenObtainPairView):
"""payload customizing"""
serializer_class = CustomTokenObtainPairSerializer
- decoded JWT
<로그아웃>
- 참조 : [Django] DRF를 사용한 JWT Authentication #2 — 개발냥발 (tistory.com)
- 이건 구글링으로 찾아냈다
- urls.py
# users/urls.py
...
from django.urls import path
from users import views
...
urlpatterns = [
...
path('api/logout/', views.LogoutView.as_view(), name='logout_view'),
...
]
- views.py
# users/views.py
...
from rest_framework import status, permissions
from rest_framework.views import APIView
from rest_framework.response import Response
...
class LogoutView(APIView):
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
response = Response({
"message": "로그아웃 되었습니다"
}, status=status.HTTP_202_ACCEPTED)
response.delete_cookie('refreshtoken')
response.delete_cookie('accesstoken')
return response
- 포스트맨에서 실행했을 때 "로그아웃 되었습니다" 메시지가 나오긴 하는데 진짜로 로그아웃 된 건지는 모르겠다..
- 나중에 할일목록 구현할 때 이게 진짜 되는지 아닌지 알 수 있을 것 같다...
- 로그아웃이 되는 건지 안 되는 건지 잘 모르겠어서 다시 열심히 구글링을 해서 찾아봤다
- 토큰은 직접 삭제하는 건 안 되고 만료될 때까지 기다려야 한다고 한다
- 그래서 blacklist라는 기능을 사용해서 로그아웃 하려는 토큰을 blacklist에 넣어서 유효한 토큰이 아니게 만들어서 로그아웃이 되게 해보았다
- 근데 이것도 사실 로그아웃이 된 건지...뭔지 잘 모르겠다 로그아웃 누르고 유저정보 수정(헤더에 로그아웃한 access 토큰이 있는 상태)을 하면 수정이 된다;;; 그럼 로그인 안 된 거 아닌가...?
- 일단 로그아웃 기능 수정한 걸로 아래 기재하겠다
- settings.py
# 프로젝트/settings.py
SIMPLE_JWT = {
...
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
...
}
- urls.py
# users/views.py
from django.urls import path
from rest_framework_simplejwt.views import (
TokenBlacklistView,
)
urlpatterns = [
...
path('api/logout/', TokenBlacklistView.as_view(), name='logout_view'),
...
]
- DRF simple jwt 라이브러리에서 기본으로 제공하는 함수라서 따로 view를 작성하진 않아도 된다
<회원정보 수정>
- 회원정보 수정에는 조건이 걸려있다
- "아이디는 수정하게 두지 말 것"
- 아이디가 pk말하는 게 아니라 아이디로 쓰고 있는 email 말하는 거 맞겠지?
- 수정 함수는 강의 들었던 거 참고해서 금방 짰는데 저 조건을 어떻게 거는지가 문제였다
- 회원정보를 수정하기 전 get방식의 회원정보 조회를 구현했는데 UserSerializer를 쓰기엔 모든 필드가 다 나와서 원하는 필드만 볼 수 있는 시리얼라이저를 새로 만들었다
- 필드가 이메일(아이디), 이름, 성별, 나이, 소개, 마지막 로그인 시간으로 이루어져 있다
- 근데 이 중에서 이름, 성별, 나이, 소개만 수정할 수 있게 하고 싶은데 이 시리얼라이저를 쓰면 이메일이랑 마지막로그인시간까지 수정이 되어버린다
- 그래서 이름, 성별, 나이, 소개만 있는 시리얼라이저를 만들었는데, 그러고보니 수정하고 나서 원래의 회원정보 조회할 때 나오는 필드가 다 나왔으면 좋겠는데 이름, 성별, 나이, 소개만 나오는거다
- 그래서 수정은 이름, 성별, 나이, 소개만 있는 시리얼라이저를 사용하고 리턴할 때는 원래 회원정보 조회할 때 쓰는 시리얼라이저 형태로 보이게끔 하려고 했는데.. 뭔가 코드가 추가되고 지저분해지는 것 같아서 이건 아니다 싶었다
- 분명 방법이 있을 거 같은데...하다가 순간 머리에 read only라는 글자가 스쳐지나갔다...!
- 바로 구글에 serializer field read only를 검색하니 사용방법을 바로 찾을 수 있었다(Write only, read only fields in django rest framework - Stack Overflow)
- urls.py
# users/urls.py
...
from django.urls import path
from users import views
...
urlpatterns = [
...
path('api/<int:user_id>/', views.ProfileView.as_view(), name='profile_view'),
...
]
- serializers.py
# users/serializers.py
...
from rest_framework import serializers
from users.models import User
...
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("email", "name", "gender", "age",
"introduction", "last_login")
extra_kwargs = {
"email": {"read_only": True},
"last_login": {"read_only": True}
}
- views.py
# users/views.py
...
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.generics import get_object_or_404
from users.models import User
from users.serializers import UserInfoSerializer
...
class ProfileView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request, user_id):
"""회원정보 보기"""
user = get_object_or_404(User, id=user_id)
serializer = UserInfoSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request, user_id):
"""회원정보 수정"""
user = get_object_or_404(User, id=user_id)
if request.user == user:
serializer = UserInfoSerializer(
user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response({"message": "수정 권한이 없습니다!"}, status=status.HTTP_403_FORBIDDEN)
<회원탈퇴>
- 회원탈퇴는 수정이랑 url은 같고 method만 다르다
- 시리얼라이저는 사용하지 않는다
- views.py만 작성하면 된다
# users/views.py
...
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.generics import get_object_or_404
from users.models import User
...
class ProfileView(APIView):
...
@permission_classes([IsAuthenticated])
def delete(self, request, user_id):
"""회원탈퇴"""
user = get_object_or_404(User, id=user_id)
if request.user == user:
request.user.delete()
return Response({"message": "회원탈퇴가 완료되었습니다"}, status=status.HTTP_204_NO_CONTENT)
else:
return Response({"message": "권한이 없습니다!"}, status=status.HTTP_403_FORBIDDEN)