[DRF] <Level Three> Django REST Framework - 1. Signal

Alex of the year 2020 & 2021·2020년 9월 3일
0

Django Rest Framework

목록 보기
11/15
post-thumbnail

Level Three부터는 real-world use cases 중점으로 DRF를 학습해보는 시간

  • DRF의 가장 주된 인증 방법
  • REST한 registration과 authentication system 만들기
  • ViewSet & Router
  • DRF를 이용한 Filtering
  • DRF를 이용한 Automated tests (unittest)

Signal

특정 액션이 발생하고 그 후에 발생하는 이벤트 (자바스크립트의 '이벤트'와 비슷)
저장하기 전/후, 삭제하기 전/후(pre_save, post_save, pre_delete, post_delete) 특정 signal을 웹 애플리케이션에 전달
ex. 회원가입 후 '축하합니다' 메일을 보낸다면 그 때 사용되는 것이 signal

이번 강의에서는 UserProfile API에서 signal을 사용할건데,
User 인스턴스를 생성함과 동시에 바로 Profiles 모델로 연결시킬 것이다.

Code

1) profilesapi project 새로 만들고, 그 안에 profile 앱 만든 후, settings.py에서 앱 등록시키면서 rest_framework 등록하는 것까지 모두 한 번에 완료.
2) 이번 프로젝트에서는 image를 사용할 것이므로 특별히, settings.py 맨 하단에 미디어 관련한 세팅을 추가한다.

MEDIA_URL = "/media/"
MEDIA_ROOT = "uploads"

3) profilesapi/profilesapi/urls.py에 다음의 코드 추가 (미디어 파일을 사용할 때, 액션을 사용하려면 추가해야 함)

from django.conf.urls.static import static
from django.conf import settings

if settings.DEBUG:
	urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

4) profilesapi/profiles/models.py 작성
profile과 profilestatus 두 개의 모델 작성할 것.

from django.db import models
from django.contrib.auth.models import User
# 이 유저 모델을 definition을 통해 살펴보면, AbstractUser 클래스를 상속하고 있다.
# AbstractUser 클래스는 username, first_name, last_name, email, is_staff 등의 유용한 필드를 자체로 가지고 있다. 


class Profile(models.Model):
	user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.CharField(max_length=240, blank=True)
    city = models.CharField(max_length=30, blank=True)
    avatar = models.ImageField(null=True, blank=True)
    
    def __str__(self):
    	return self.user.username
        # username은 클래스가 상속하는 User, 그 User가 상속하는 AbstractUser가 가진 기본 필드 중 하나
        
class ProfileStatus(models.Model):
	user_profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
    status_content = models.CharField(max_length=240)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeFile(auto_now=True)
    
    def __str__(self):
    	return str(self.user_profile)

5) profilesapi/profiles/admin.py

from django.contrib import admin
from profiles.models import Profile, ProfileStatus

admin.site.register(Profile)
admin.site.register(ProfileStatus)

근데 여기서 주의해야할 점이 있다. admin 페이지에 이 두 모델을 등록을 하면 장고는 기본적으로 두 모델을 복수형으로 사이트에 등록시킨다. 근데 이 때 복수형을 만드는 방식이 'apple --> apples' 하듯 단순히 s만 붙인다는 것. 하지만 지금 우리 모델 중 ProfileStatus는 ProfileStatus'es'가 붙어야 맞다. 따라서 이런 혼란을 방지하기 위해 meta 클래스를 따로 정의해준다.

6) profilesapi/profiles/models.py 작성

class ProfileStatus(models.Model):
	user_profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
    status_content = models.CharField(max_length=240)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeFile(auto_now=True)
    
    class Meta:
    	verbose_name_plural = "statuses"
    # 이렇게! 
    
    def __str__(self):
    	return str(self.user_profile)

7) 이후 python manage.py makemigrations + python manage.py migrate 진행
8) python manage.py createsuperuser로 admin 계정 생성
9) python manage.py runserver

10) admin페이지에 접속해서 로그인을 해보면 지정해준대로 잘 Profiles 앱 내에 모델이 등록된 것을 확인할 수 있다.

11) 웹 페이지 상에서 Profiles에 인스턴스를 등록해준다.

(현재 admin 유저밖에는 등록이 되어있지 않은 관계로, User 옆에는 admin 밖에 없다. User를 admin으로하여 profile을 등록해본다.)

12) profilesapi/profiles/signals.py 생성

from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from profiles.models import Profile
# User instance가 저장될 때마다 post_save signal을 보내는 sender역할로 User를 사용할 것
# 그리하여 User instance가 저장된 직후 바로 signal이 보내어질테고 그 signal은 receiver 클래스를 이용한 receiver 데코레이터에서 받게 될 것임
# User instance가 저장될 때마다 singal을 받는 것뿐만 아니라 수정되었을 때도 signal을 받게 될 것임.

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    print("Created: ", created)
    if created:
        Profile.objects.create(user=instance)
        
# 이 때, receiver 데코레이터를 달았기 때문에 그 인자값을 보고
이 함수는 User로 부터의 signal이 인스턴스 save 이후에 올 것을 예측할 수 있다.

13) profilesapi/profiles/init.py 작성

default_app_config = "profiles.apps.ProfilesConfig"

14) 바로 이어 profilesapi/profiles/apps.py 작성

from django.apps import AppConfig

class ProfilesConfig(AppConfig):
    name = 'profiles'
 
    # 아래를 추가해준다.  
    def ready(self):
        import profiles.signals

여기까지하면 signal을 사용할 준비가 된 것이다.

15) 다시 runserver하여, admin 계정으로 Users를 관리하는 페이지로 들어온다. 그리고 admin 인스턴스를 수정해본다. (원래는 빈칸이었던 first_name을 채운다던가)

16) 수정 이후 다시 VS code로 돌아와 터미널을 확인하면
Created: False 라는 시그널이 찍힌 것을 확인할 수 있다. (수정만 하였기 때문에 post_save가 아니므로 False를 보낸 것. 여기서 created 인자는 bool값을 가지는 것도 알 수 있다.)

17) 다시 runserver하여, admin 계정으로 Users를 관리하는 페이지로 돌아온다. 그리고 이번에는 random유저를 생성해보자.

18) 유저 인스턴스를 하나 생성한 이후 다시 VS Code로 돌아와 터미널을 확인하면 이번에는
Created: True라는 시그널이 찍힌 것을 확인할 수 있다.

19) ⭐️ 그리고! 다시 웹 페이지로 가면, User에만 인스턴스를 생성하였을 뿐인데 우리가 목표한대로 Profile 모델에

random유저가 딱 들어와있는 것을 확인할 수 있다!

20) random을 눌러보면, Profile 모델에서 정의한 그대로 필드도 고대로 나온다.

21) 그럼 이제 serializer를 만들차례
profilesapi/profiles/api/serializer.py 생성

from rest_framework import serializers
from profiles.models import Profile, ProfileStatus

class ProfileSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField(read_only=True)
    avatar = serializers.ImageField(read_only=True)

    class Meta:
        model = Profile
        fields = "__all__"
        
        
class ProfileAvatarSerializer(serializers.ModelSerializer):

    class Meta:
        model = Profile
        fields = ("avatar", )


class ProfileStatusSerializer(serializers.ModelSerializer):
    user_profile = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = ProfileStatus
        fields = "__all__"

Q. StringRelatedField
Q. Serializer를 정의할 때 meta클래스 위에 변수를 따로 지정해주는 필드들은 어떤 기준으로 정하는 것인지

여기까지 하고, 이제 Authentication은 다음 강에서 시작하는 것으로!



references:
https://chohyeonkeun.github.io/2019/05/11/190511-django-Signal/ (Signal)
https://devkor.tistory.com/entry/03-Django-Rest-Framework-Serializer-View-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0 (Serializer)

profile
Backend 개발 학습 아카이빙 블로그입니다. (현재는 작성하지 않습니다.)

0개의 댓글