장고에서 기본으로 제공하는 유저를 커스텀하여 email을 기반으로하는 User를 만들었었다. 이제 만들었던 User에 대하여 프로필 이미지를 만들어주려고한다. 나는 지금까지 만들었던 커스텀 user에 등록하겠끔 했지만, 일반적인 User에 동일 방법으로 적용이 가능하다.
# 구현사항
- User모델에 프로필 이미지(image) 추가
- 회원탈퇴시 파일삭제 + JWT 토큰 회수
- token.py파일에서 GetToken, GetPayload ,CheckToken 분리시켜 반복되는 코드를 제거
토큰을 분리시켜 사용했지만 저방법에 대해서는 jwt자체의 모듈을 사용하거나 데코레이터를 사용하는 방법이 존재할거 같다는 생각이 들었지만 일단은 저렇게 사용했다. 프로젝트를 시작하면 인증절차를 매 페이지마다 사용할 수 있는 방법이 존재할거 같긴 하다.
## 구현시 생겼던 이슈
DB에서 user데이터만 삭제(회원탈퇴)했더니 JWT토큰이 쿠키에 남아있어 로그인은 된 상태이지만 로그아웃은 되지 않은상태가 생겼고 그로인하여 아무값도 없는 유저의 상태로 로그인이 됐었음. => 회원탈퇴시 JWT토큰을 같이 회수하여 해결
# 이미지 등록하기
이미지를 등록하기 위해서 해야할 작업은 다음과 같다.
- pip install Pillow
- settings.py에서 MEDIA_ROOT, MEDIA_URL 설정해주기
- 프로젝트 urls.py 수정
- User모델에 이미지 필드 추가
나는 User모델을 커스텀하였기 때문에 User모델 말고도 다른 곳에서도 이미지필드를 등록해줘야 했다.
이미지 필드를 추가하는 방법도 가능하다.
## pip install Pillow
pip install Pillow
을 통하여 Pillow를 설치해준다. Pillow자체를 다루는 코드는 없었지만, 기본적으로 행해지는 파일경로를 읽고쓰는 작업에서 알아서 쓰이는것 같다. 보통 이미지를 다루는데 필요한 라이브러리라고 한다. 이미지를 저장하거나 블러처리하거나 크기를 조정하는 등 다양한 작업을 할 수 있다.
예제로 배우는 파이썬 프로그래밍 - 파이썬 이미지 처리 (Pillow)
1. 파이썬에서의 이미지 처리 파이썬에서 이미지를 처리하고 핸들링하기 위해서는 Pillow, OpenCV, PIL 등의 외부 패키지를 설치해서 사용한다. 여기서는 PIL로부터 계승되어 많이 사용되는 Pillow 패키
pythonstudy.xyz
## settings.py
프로젝트에서 자체적으로 쓰이는 정적인 파일, 이미지와 같은경우는 static폴더에 담아서 보관한다.
프로젝트가 사용됨에따라 유저들이 등록하는 파일같은 경우는 media폴더에 담아서 보관한다.
프로필 이미지는 당연스럽게도 media폴더에서 관리되어야할 것이다. 이러한 폴더의 기본적인 저장위치를 settings.py에서 정해줘야 한다.
MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = '/media/'
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
BASE_DIR은 장고에서 기본적으로 다음과 같이 설정 돼 있다. 지금 파일의 위치에서 상위, 상위폴더가 지정된다.
그렇다면 MEDIA_ROOT의 위치가 = BASE_DIR / 'media'로 지정해 줬기 때문에 다음과 같이 구조가 잡히게 된다.
## 프로젝트 urls.py 수정
from django.conf import settings
from django.conf.urls.static import static
# 프로젝트폴더/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/',include('api.urls')),
path('accounts/',include('accounts.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
개발모드에서는 미디어파일을 자동으로 서빙해주지 않는다고한다. 그래서 수동으로 할 수 있게끔 위와 같이 static메서드를 이용해서 추가해줘야한다고 한다. 사실 무슨말 인지는 잘 모르겠다. 개발과 배포의 차이같기도 하다. 배포하면서 알게 될거 같다. 또 배포할때는 aws나 파이어베이스를 이용하지 않을까 싶다.
## User모델에 이미지 필드 추가
class User(AbstractBaseUser):
# ...
userImg = models.ImageField(upload_to='users' ,null=True)
# ...
난 User를 커스텀했기 때문에 이곳뿐만 아니라 UserManger도 수정해줬고, form.py와 admin.py에서도 필드를 추가해줬다.
upload_to=' ... '
속성은 media폴더내 하위폴더를 설정해줄 수 있다. 없다면 만들어지고 있다면 그 안으로 들어가게 된다. 한 폴더안에 너무 많은 파일이 있다면 리소스가 낭비 된다고 한다.
# media/2020/02/21
userImg = models.ImageField(upload_to='%Y/%m/%d/' ,null=True)
# media/20200221
userImg = models.ImageField(upload_to='%Y%m%d/' ,null=True)
와 같이 사용을 한다고 한다. 폴더깊이가 깊어지는 것은 리소스 낭비가 없다고 했다.
### serializers
class UserSerializer(serializers.ModelSerializer) :
# userImg = serializers.ImageField(use_url=True) => use_url = True is Default
class Meta :
model = User
fields = '__all__'
저렇게 userImg에서 use_url을 True로 해줘야 한다는 글도 많았는데 안해봤는데 똑같다. user_url을 별도로 지정하지 않으면 True가 기본값이라 그런거 같았다. 별도로 오버라이딩 하지 않아줘도 Meta에서 fields에서 해당 값이 들어가기 때문에 상관 없다.
## View
class RegisterView(APIView) :
def post(self, req):
serializer = UserSerializer(data=req.data)
serializer.is_valid(raise_exception=True)
# password didn't hashing
serializer.save()
return Response(serializer.data)
여기서 정말 DRF에 장점이 명실히 들어난다. 그전에 작성해놨던 코드를 수정할 필요도 없다. 자동으로 serializer되면서 안에 값들이 매핑 되기 때문이다. 내가 APIView만 썻지만 제너릭 뷰를 쓰면 더욱 편리하게 사용할 수 있을거 같다. 하나씩 차근차근 써볼거다!
# 중간점검
로그인을 통해서 JWT토큰도 잘 반환해준다. 프로필도 잘 가져오고 Img파일도 잘 보인다. 저 경로를 누르면 내가 지정해준 이미지가 짠 나타난다.
# 회원탈퇴 구현
이제는 회원탈퇴를 구현해야한다. 회원탈퇴뷰를 작성하면서 나는 다음의 사실을 고려했다.
- JWT토큰을 가지고 있는가? 올바른 토큰인가
- 토큰을 함께 회수하는가?
- media의 이미지파일이 같이 지워지는가
## 토큰확인
#token.py
from rest_framework.exceptions import AuthenticationFailed
# install PyJWT
import jwt,datetime
# using before CheckToken
def GetToken(req) :
token = req.COOKIES.get('jwt')
return token
def GetPayload(req) :
token = req.COOKIES.get('jwt')
payload = jwt.decode(token, 'secretJWTkey', algorithms=['HS256'])
return payload
def CheckToken(req) :
token = req.COOKIES.get('jwt')
if not token:
return False
try:
payload = jwt.decode(token, 'secretJWTkey', algorithms=['HS256'])
except jwt.ExpiredSignatureError:
return False
return True
class DelUserView(APIView) :
def post(self,req):
if not CheckToken(req) :
raise AuthenticationFailed('UnAuthenticated')
else :
user = User.objects.filter(id=GetPayload(req)['id']).first()
res = Response()
res.delete_cookie('jwt')
res.data = {"message" : "user delete success" }
user.delete()
return res
시작부분에서 이야기했던거와 같이 토큰을 확인하는 다른 방법이 분명히 존재할거 같았지만, 일단 이런식으로 구현했다. 분명 프로젝트를 진행할때는 view마다 토큰을 확인하는 메서드를 작성할거 같지는 않았다.. 이방법은 천천히 알아가는걸로 하자
토큰을 쿠키에서 지워주는 것으로, 로그아웃이 되게 만들었다. 또 user데이터를 delete해줌으로써 DB에서 해당 유저의 정보를 지워줬다. 그러나 앞서 예고했던거와 같이, media폴더에 등록된 이미지 파일은 삭제 되지 않았다. 이걸 삭제해줘야했다. 그러기 위해선 어딘가에서 delete()메서드를 오버라이딩 해줘야했는데 model이냐 serialize냐 고민했다.
user객체가 어떻게 생성되냐에 주목했는데 serialize를 사용하지 않았다. 그렇기때문에 model에서 오버라이딩 해줬다. 어차피 DB를 관리하는것도 model이니까 그쪽이 맞는거 같았기 때문이다.
# models.py
class User(AbstractBaseUser):
#..
#..
def delete(self,*args,**kwargs):
os.remove(os.path.join(settings.MEDIA_ROOT, self.userImg.name))
super(User, self).delete(*args,**kwargs)
media/ '업로드 시 지정한 폴더명' 에서 파일을 지우고, 부모의 delete를 온전히 실행시켜라! 라고 해석하면 된다. 이렇게하면 파일 삭제도 매끈하게 된다.
게시글에 사진들 여러개 묶어서 등록하는 법이랑 동영상파일 등록하는 법에대해서도 공부해봐야할거 같고
제너릭뷰나 뷰셋같은거 이용하는방법에 대해서도 공부해야할거 같다.
로그인 세션도 공부해야하고
쿼리셋이나 시리얼라이즈도 더 공부해야봐야하고...
일단 공부할건 많다 많아서 좋다ㅎㅎ
지금 하는거를 하나씩 프로젝트랑 연관시켜 나가고 싶다!
일단 지금 궁금한거는 APP(클라이언트)랑 React에서 어떻게 로그인 관리하는지가 가장 궁금하다.
아 그리고 티스토리 내 블로그 아무리 이쁘게 적어도 게시글 작성완료 누르면 진짜 너무 구리다. 이거 스킨 다시 바꾸고 싶은데 다 날라갈까봐 못하겠다 ㅠㅠ
이번 게시글처럼 구현시 생겼던 문제점이랑 내가 생각했던 방향을 대주제로 잡고 적으니까 적기도 수월했고 가독성도 좋아지는거 같다
'• 개발 > Django' 카테고리의 다른 글
[Django] JWT#2, 장고 JWT토큰 사용하기, payload, Pyjwt, 장고 로그인 유지 (0) | 2022.02.19 |
---|---|
[Django] JWT#1, Serializer을 이용하여 password 암호화하여 데이터베이스(DB) 저장하기, 비밀번호 암호화하기 (0) | 2022.02.19 |
[Django] 장고 커스텀 유저 만들기#2 forms.py admin.py , custom User, 장고 User 바꾸기 (0) | 2022.02.18 |
[Django] 장고 커스텀 유저 만들기#1 models.py , custom User, 장고 User 바꾸기 (0) | 2022.02.18 |
[Django] django-rest-framework 공식문서 튜토리얼 따라하기#5 (0) | 2022.02.16 |