Computer Programming/AI

TIL_Django REST Framework Project, blog 만들기(1)

JYCoder 2023. 9. 26. 12:25

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'),

 

LIST

'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