Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions drfTuto/custom_renderer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from rest_framework import renderers

class CustomRenderer(renderers.BaseRenderer):

def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = renderer_context.get('response')

response = {
'status': response_data.status_text,
'data': data
}

return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)
18 changes: 17 additions & 1 deletion drfTuto/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-6d7u2b(=#!%%$%ty=3juq^s+(&d)fzo)!i!4v21nx4991c7f1j'
SECRET_KEY = os.environ.get('Secret_Key')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
Expand All @@ -40,6 +40,8 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'django_filters',
'rest_framework_simplejwt'
] + PSJ_APPS


Expand Down Expand Up @@ -77,14 +79,26 @@

# REST FRAMEWORK
REST_FRAMEWORK = {
'''
'DEFAULT_RENDERER_CLASSES': (
'djangorestframework_camel_case.render.CamelCaseJSONRenderer',
'djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer',
),
'''
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
'DEFAULT_PARSER_CLASSES': (
'djangorestframework_camel_case.parser.CamelCaseFormParser',
'djangorestframework_camel_case.parser.CamelCaseMultiPartParser',
'djangorestframework_camel_case.parser.CamelCaseJSONParser',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
"EXCEPTION_HANDLER": "user.utils.custom_exception_handler",
"DEFAULT_FILTER_BACKENDS": 'django_filters.rest_framework.DjangoFilterBackend',
}

# Database
Expand All @@ -97,6 +111,8 @@
}
}

LOGIN_REDIRECT_URL = '/' #로그인시 이동하는 페이지
LOGOUT_REDIRECT_URL = '/' #로그아웃시 이동하는 페이지

# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
Expand Down
3 changes: 2 additions & 1 deletion drfTuto/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@

urlpatterns = [
path('admin/', admin.site.urls),
path('users/', include('user.urls')),
path('user/', include('user.urls')),
#path("api-auth/", include("rest_framework.urls")),
]
40 changes: 40 additions & 0 deletions drfTuto/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from rest_framework.views import exception_handler

class PasswordNotMatch(APIException):
status_code = 400
default_detail = "Passwords does not match."
default_code = "bad_request"


def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)

# Now add the HTTP status code to the response.
if response is not None:
if isinstance(exc, Http404):
customized_response = {"code": response.status_code, "details": "Not Found"}
elif isinstance(exc, exceptions.NotFound):
customized_response = {"code": response.status_code, "details": exc.detail}
elif isinstance(exc, exceptions.MethodNotAllowed):
customized_response = {"code": response.status_code, "details": exc.detail}
elif isinstance(exc, exceptions.NotAcceptable):
customized_response = {"code": response.status_code, "details": exc.detail}
elif isinstance(exc, exceptions.UnsupportedMediaType):
customized_response = {"code": response.status_code, "details": exc.detail}
elif isinstance(exc, exceptions.AuthenticationFailed):
customized_response = {"code": response.status_code, "details": exc.detail}
elif isinstance(exc, exceptions.PermissionDenied):
customized_response = {"code": response.status_code, "details": exc.detail}
elif isinstance(exc, exceptions.NotAuthenticated):
customized_response = {"code": response.status_code, "details": exc.detail}
else:
customized_response = {
"code": response.status_code,
"details": response.data,
}

response.data = customized_response

return response
30 changes: 30 additions & 0 deletions user/auth_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.urls import path
from . import auth_views
from django.contrib.auth import views
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)

urlpatterns = [
path("signup/", auth_views.SignUpView.as_view(), name="signup"),
path('login/', views.LoginView.as_view(), name='login'),
path('logout/', views.LogoutView.as_view(), name='logout'),
]

urlpatterns += [
path(
"check-email/",
auth_views.CheckDuplicateUsernameView.as_view(),
name="check-email",
),
path("email-verification/", auth_views.EmailVerification.as_view(), name="verify-email"),
path('email-confirmation/', auth_views.EmailConfirmation.as_view(), name="activate"),
path(
"password-change/",
auth_views.PasswordChangeView.as_view(),
name="password-change",
),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
130 changes: 130 additions & 0 deletions user/auth_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from django.contrib.auth.models import update_last_login
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from rest_framework.permissions import AllowAny
from rest_framework.views import APIView
from rest_framework.exceptions import AuthenticationFailed

from drfTuto.utils import PasswordNotMatch
from drfTuto.custom_renderer import CustomRenderer
from user.models import User
from rest_framework.response import Response
from rest_framework import status, generics, permissions
from django.contrib.auth.hashers import check_password

from user.serializers import UserSerializer
from user.gen_codes import GenerateCode

import smtplib
from email.mime.text import MIMEText

from dotenv import load_dotenv
import os

smtp_info = {
'gmail.com': ('smtp.gmail.com', 587),
'naver.com': ('smtp.naver.com', 587),
}

smtp = smtplib.SMTP('smtp.gmail.com', 587)

smtp.ehlo()

smtp.starttls() #tls 암호화, 587 port 사용시 필요


class BasicSignUpView(APIView):
serializer_class = UserSerializer
renderer_classes = [CustomRenderer]
permission_classes = [AllowAny]

def post(self, request, *args, **kwargs):

password = request.data.get("password")
confirm_password = request.data.get("confirm_password")

if password != confirm_password:
raise PasswordNotMatch

email = request.data.get("email")
nickname = request.data.get("nickname")

user = User.objects.create_user(email=email, password=password, nickname=nickname)

serializer = UserSerializer(user)

return Response(serializer.data, status=status.HTTP_201_CREATED)

class PasswordChangeView(APIView):
serializer = UserSerializer
renderer_classes = [CustomRenderer]

def post(self, request, *args, **kwargs):
user = request.user
current_password = request.data.get("current_password")
new_password = request.data.get("new_password")

if not check_password(current_password, user.password):
raise AuthenticationFailed

user.set_password(new_password)
user.save(update_fields=["password"])

serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)

class CheckDuplicateUsernameView(APIView):
renderer_classes = [CustomRenderer]
permission_classes = [AllowAny]

def post(self, request, *args, **kwargs):
email = request.data.get("email")

existing_email = User.objects.filter(email=email).first()
if existing_email:
return Response({"details": "Provided email already exists."}, status=status.HTTP_409_CONFLICT)

return Response({"email": email}, status=status.HTTP_200_OK)

class EmailVerification(APIView):
renderer_classes = [CustomRenderer]
permission_classes = [AllowAny]

def post(self, request, *args, **kwargs):
email = request.data.get("email")
generated_code = GenerateCode.generate_random_code()

# set code in cookie
res = JsonResponse({'success': True})
res.set_cookie('email_verification_code', generated_code, max_age=300)

# 메일 내용
msg = MIMEText('이메일 인증 코드 : ', generated_code)

# 메일 제목
msg['Subject'] = '이메일 인증 코드입니다'

success = smtp.sendmail('june416412@gmail.com', email, msg.as_string()) #발신 메일, 수신 메일, 본문 내용

if success > 0:
return Response(status=status.HTTP_200_OK)
elif success == 0:
return Response({"details": "Failed to send email"},status=status.HTTP_400_BAD_REQUEST)


class EmailConfirmation(APIView):
permission_classes = [AllowAny]

def post(self, request, *args, **kwargs):
if 'email_verification_code' in request.COOKIES:
code_cookie = request.COOKIES['email_verification_code']
else:
return Response({"details": "No cookies attached"}, status=status.HTTP_400_BAD_REQUEST)

code_input = request.data.get("verification_code")
if code_cookie == code_input:
return Response(status=status.HTTP_200_OK)
else:
return Response({"details": "Verification code does not match."}, status=status.HTTP_400_BAD_REQUEST)

smtp.login('june416412@gmail.com', os.environ.get('EMAIL_LOGIN_KEY'))
16 changes: 16 additions & 0 deletions user/gen_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import string
import random

class GenerateCode:
@staticmethod
def deactivate_user(user):
user.is_active = False
user.save(update_fields=["is_active"])
return user

@staticmethod
def generate_random_code():
number_of_strings = 5
length_of_string = 8
for x in range(number_of_strings):
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length_of_string))
23 changes: 10 additions & 13 deletions user/generics_views.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
from user.models import MyUser
from user.serializers import UserSerializer
from rest_framework import generics, mixins
from rest_framework import generics, mixins, filters
from rest_framework.response import Response
from datetime import datetime
from rest_framework import status


class UserList(generics.ListCreateAPIView):
queryset = MyUser.objects.all()
queryset = MyUser.objects.filter(is_active=True).all()
serializer_class = UserSerializer
filter_backends = [filters.SearchFilter]
search_fields = ["nickname", "email"]

class UserDetail(generics.RetrieveUpdateDestroyAPIView):

class UserDetail(generics.RetireveAPIView): #검색
queryset = MyUser.objects.all()
serialzier_class = UserSerializer

class UserUpdate(generics.UpdateAPIView): #업데이트
queryset = MyUser.objects.all()
serialzier_class = UserSerializer
serializer_class = UserSerializer

def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
Expand All @@ -26,11 +27,7 @@ def update(self, request, *args, **kwargs):
serializer.save(
updated_at=datetime.now()
)
return Response(serializer.data, status=status.HTTP_200_OK)

class UserDestroy(generics.DestroyAPIView):
queryset = MyUser.objects.all()
serialzier_class = UserSerializer
return Response(serializer.dwsata, status=status.HTTP_200_OK)

def delete(self, request, *args, **kwargs):
instance = self.get_object()
Expand Down
7 changes: 3 additions & 4 deletions user/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from user.views import UserView
from user import generics_views

urlpatterns = [
#path('', views.UserList.as_view()),
#path('<int:pk>/', views.UserDetail.as_view()),
path("", generics_views.UserList.as_view(), name="user_list"),
path("<int:pk>/", generics_views.UserDetail.as_view(), name="user_detail"),
]
3 changes: 2 additions & 1 deletion user/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ class UserView(viewsets.ModelViewSet):
A simple ViewSet for viewing and editing accounts.
"""
queryset = MyUser.objects.all()
serializer_class = UserSerializer
serializer_class = UserSerializer

18 changes: 18 additions & 0 deletions user/viewsets_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.urls import path, include
from rest_framework.urlpatterns import format_suffix_patterns
from user.views import UserView

user_list = UserView.as_view({
'get': 'list',
'post': 'create'
})

user_detail = UserView.as_view({
'get': 'retrieve',
'patch': 'partial_update',
})

urlpatterns = format_suffix_patterns([
path("/", user_list, name="user_list"),
path("<int:pk>/", user_detail, name="user_detail")
])