DRF를 이용하여 blog를 웹사이트를 만들려고 한다.
프로젝트 생성부터 user, article, comments, 좋아요 등의 기능 구현 모든 것을 정리해 보았다.
Project 기본 Settings
바탕화면에 'drf_blog_project'라는 folder 생성
=> Visual Studio Code켜서 해당 폴더 열기
=> venv(가상환경) 생성: python -m venv venv
=> venv 켜기(Windows 경우): source venv/Scripts/activate
=> Django install: pip install django
=> Django REST Framework install: pip install djangorestframework
=> Simple JWT install: pip install djangorestframework-simplejwt
=>install 한 version 정보 파일에 정리: pip freeze > requirements.txt
(*pip install -r requirements.txt 로 다른 팀원들이 install 할 수 있음)
=> .gitignore 파일 생성 후, 'gitignore.io' site에서 'Windows', 'macOS', 'Python', 'django', 'VisualStudioCode' 검색 후 저장
=>github에서 repository 생성 후: git init => git remote add origin <ssh 주소> => git add . => git commit -m '<message>' => git push origin main
=> django project 생성: django-admin startproject drf_blog_project .
=> settings.py에서 다음과 같이 설정
INSTALLED_APPS = [
...
'rest_framework',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
TIME_ZONE = 'Asia/Seoul'로 지정
users App 생성
users app 생성: python manage.py startapp users
=> settings.py에 app 등록: INSTALLED_APPS = ['users',]
Simple JWT 사용해서 기본 Login 기능으로 JWT(Token) 정보 확인
urls.py에 다음과 같이 붙여넣기
from django.urls import path
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('api/token/', views.CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
POSTMAN에서 'POST'로 'user'과 'password' 값을 넣고 send하면 'refresh'와 'access' 값을 받을 수 있음
이 access 값을 https://jwt.io/에서 확인해 보면 정보를 알 수 있음
custom user 만들고 admin에 등록
'django documentation' site의 Customizing authentication in Django에서 기본 customizing user model 복사해서 models.py에 붙여넣고 customize
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
class MyUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError("Users must have an email address")
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, date_of_birth, password=None):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
date_of_birth=date_of_birth,
)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = MyUserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["date_of_birth"]
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
위의 models.py 코드에서 user model 수정 가능.
AbstractBaseUser을 사용할 때는 BaseUserManager이 모두 필요함.
=> admin.py에 아래의 코드를 복사해서 넣고 내 코드에 맞게 수정
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
from customauth.models import MyUser
class UserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
password2 = forms.CharField(
label="Password confirmation", widget=forms.PasswordInput
)
class Meta:
model = MyUser
fields = ["email", "date_of_birth"]
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
disabled password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = MyUser
fields = ["email", "password", "date_of_birth", "is_active", "is_admin"]
class UserAdmin(BaseUserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ["email", "date_of_birth", "is_admin"]
list_filter = ["is_admin"]
fieldsets = [
(None, {"fields": ["email", "password"]}),
("Personal info", {"fields": ["date_of_birth"]}),
("Permissions", {"fields": ["is_admin"]}),
]
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = [
(
None,
{
"classes": ["wide"],
"fields": ["email", "date_of_birth", "password1", "password2"],
},
),
]
search_fields = ["email"]
ordering = ["email"]
filter_horizontal = []
# Now register the new UserAdmin...
admin.site.register(MyUser, UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)
=> settings.py에 아래의 코드를 넣어줌
AUTH_USER_MODEL = "customauth.MyUser"
=> model이 수정되었으니 변화된 내용을 DB에 적용시킨다.
python manage.py makemigrations
python manage.py migrate
signup 기능 만들기
urls.py에 다음과 같이 작성한다.
path('signup/', views.UserView.as_view(), name='user_view'),
이에 따라 views.py도 작성한다.
class UserView(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)
이에 따라 serializers.py도 작성한다.
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
def update(self, validated_data):
user = super().create(validated_data)
password = user.password
user.set_password(password)
user.save()
return user
POSTMAN으로 'POST'로 'email'과 'password'로 signup 기능을 확인한다.
JWT payload customize
JWT payload에서 email 값을 보여주기 위해 다음과 같이 customize 한다.
views.py에서 TokenObtainPariView를 inherit해서 customize한다.
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
serializers.py에서 TokenObtainPairSerializer를 inherit해서 customize 한다.
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
token['email'] = user.email
return token
POSTMAN에서 login 한 결과 값 access를 https://jwt.io/에서 확인하면 payload에 'email' 값을 확인 할 수 있다.
Token 주기 설정과 refreshtoken으로 새로운 access 받기
SimpleJWT site에 있는 코드를 덮어쓰면 token의 기본 값을 바꿀 수 있다. settings.py에 넣는다.
여기서 'timedelta(minutes=5)'를 수정하여 login 유지 시간을 설정해 줄 수 있다.
from datetime import timedelta
...
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": False,
"BLACKLIST_AFTER_ROTATION": False,
"UPDATE_LAST_LOGIN": False,
"ALGORITHM": "HS256",
"SIGNING_KEY": settings.SECRET_KEY,
"VERIFYING_KEY": "",
"AUDIENCE": None,
"ISSUER": None,
"JSON_ENCODER": None,
"JWK_URL": None,
"LEEWAY": 0,
"AUTH_HEADER_TYPES": ("Bearer",),
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
"USER_ID_FIELD": "id",
"USER_ID_CLAIM": "user_id",
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
"TOKEN_TYPE_CLAIM": "token_type",
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
"JTI_CLAIM": "jti",
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
urls.py에서의 TokenRefreshView를 통해 refresh 값을 전달해 주고 새로운 access token 값을 받을 수 있다.
POSTMAN에서 확인 해 볼 수 있다.
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
'Computer Programming > AI' 카테고리의 다른 글
TIL_HTTP, HTTPS, SSH란? (0) | 2023.10.02 |
---|---|
TIL_DRF에서 Meta class란? (0) | 2023.09.27 |
TIL_Django Basics, Migration (0) | 2023.09.25 |
WIL_일곱 번째 주 (0) | 2023.09.22 |
TIL_Django REST Framwork, POSTMAN을 사용하는 이유 (0) | 2023.09.22 |