Level Three부터는 real-world use cases 중점으로 DRF를 학습해보는 시간
특정 액션이 발생하고 그 후에 발생하는 이벤트 (자바스크립트의 '이벤트'와 비슷)
저장하기 전/후, 삭제하기 전/후(pre_save, post_save, pre_delete, post_delete) 특정 signal을 웹 애플리케이션에 전달
ex. 회원가입 후 '축하합니다' 메일을 보낸다면 그 때 사용되는 것이 signal
이번 강의에서는 UserProfile API에서 signal을 사용할건데,
User 인스턴스를 생성함과 동시에 바로 Profiles 모델로 연결시킬 것이다.
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)