[0617] Django Rest Framework

nikevapormax·2022년 6월 17일
0

TIL

목록 보기
52/116
post-custom-banner

DRF 특강

역참조

😠 정의

  • 외래키를 사용해 참조하는 object를 역으로 찾을 수 있다.
  • 외래키 지정 시 아래와 같이 related_name을 설정하게 되면, 역참조 시 해당 이름을 사용할 수 있다.
hobby = models.ManyToManyField(Hobby, verbose_name="취미", related_name="user_hobby")
hobby.user_hobby
  • related_name을 설정하지 않았다면 테이블명_set을 통해 역참조를 진행하면 된다.
hobby = models.ManyToManyField(Hobby, verbose_name="취미")
hobby.userprofile_set

😠 역참조 미사용 시 코드

  • 헷갈림을 방지하기 위해 테이블의 구조를 먼저 살펴보자.
#### 상단 생략 ####

class User(AbstractBaseUser):
    
    # "사용자 계정"은 admin 페이지에서 나오는 메뉴의 이름 / unique=True를 설정해 단 하나의 값만 입력할 수 있도록 함
    username = models.CharField("사용자 계정", max_length=50, unique=True)
    password = models.CharField("비밀번호", max_length=128)
    email = models.EmailField("이메일", max_length=100)
    name = models.CharField("이름", max_length=20)
    # auto_now_add : 최초 생성 시의 시간을 자동으로 입력해줌.(그 후 업데이트에 대한 시간 기록은 x) 주로 가입일, 최초 생성일 등에 사용함
    join_data = models.DateTimeField("가입일자", auto_now_add=True)
    
#### 하단 생략 ####

# hobby model
class Hobby(models.Model):
    name = models.CharField("취미 이름", max_length=50)    
    
    def __str__(self):
        return self.name


# User Profile model
class UserProfile(models.Model):
    user = models.OneToOneField(User, verbose_name="사용자", on_delete=models.CASCADE)
    introduction = models.TextField("자기소개")
    birth = models.DateField("생일")
    age = models.IntegerField("나이")
    hobby = models.ManyToManyField(Hobby, verbose_name="취미")
    
    def __str__(self):
        return f'{self.user.username} 님의 프로필'    
  • 사용자 정보를 조회할 때 역참조를 사용하지 않는다면 이전에 했던 것과 같이 찾아와야 한다.
class UserView(APIView):
    # permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    # 사용자 정보 조회
    def get(self, request):
        user = request.user
        
        # 역참조를 사용하지 않고 hobby를 불러올 때
        user_profile = UserProfile.objects.get(user=user)
        hobbys = user_profile.hobby.all()
        hobbys = str(hobbys)
        
        return Response({"hobbys": hobbys})
    
    #### 하단 생략 ####
  • 그러나 역참조를 사용하게 되면 아래와 같이 간단하게 작성할 수 있다.
class UserView(APIView):
    # permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    # 사용자 정보 조회
    def get(self, request):
        user = request.user
        
        # 역참조를 사용해 사용자의 취미를 불러옴
        # 그런데 우리가 위의 모델을 보면 related_name을 사용하지 않음
        # user와 user profile의 관계가 OneToOne 관계라서 그럼
        hobbys = user.userprofile.hobby.all()
        hobbys = str(hobbys)
        
        return Response({"hobbys": hobbys})
        
    #### 하단 생략 ####

😠 dir 사용

  • 위의 코드를 치고 서버를 돌리면 아래의 빨간 박스에 보이는 에러가 나게 된다.
  • 에러를 고쳐보기 위해 dir를 사용해 보았다.
  • dir을 사용하게 되면 내가 user라는 모델 안에서 사용할 수 있는 기능을 전부 볼 수 있다.
class UserView(APIView):
    # permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    # 사용자 정보 조회
    def get(self, request):
        user = request.user
        print(dir(user))
        return Response({})
  • 결과
    • 우리가 모델을 만들면서 사용했던 친숙한 이름, 에러 이름, 그리고 우리가 사용하려 했던 userprofile 또한 잘 보인다. 즉 코드에는 큰 문제는 없어 보인다.
  • admin 페이지에 혹시나 해서 들어가 보았다. 역시나 내가 일반 유저의 프로필은 작성했는데, 현재 로그인되어 있는 admin user의 계정으로 된 프로필을 작성하지 않았었다. 따라서 프로필을 작성해 주었고, 다시 포스트맨을 돌려보았다.
  • 내가 작성한데로 취미가 잘 불러와진 것을 알 수 있다.

😠 역참조 다시 한 번 뜯어보기

  • 위의 사진처럼 우리는 이미 성공했다. 그러나 다시 한 번 코드를 리마인드하는 겸해서 조금씩 뜯어서 포스트맨에게 보내기로 해보자.
  • 첫 번째
class UserView(APIView):
    # permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    # 사용자 정보 조회
    def get(self, request):
        user = request.user
        
        # 역참조를 사용해 사용자의 취미를 불러옴
        # 그런데 우리가 위의 모델을 보면 related_name을 사용하지 않음
        # user와 user profile의 관계가 OneToOne 관계라서 그럼
        hobbys = user.userprofile
        hobbys = str(hobbys)
        
        return Response({"hobbys": hobbys})
        
    #### 하단 생략 ####
  • 위의 코드를 실행해보면 아래의 결과가 나온다. 우리의 의도대로 역참조가 잘 된 것을 볼 수 있다. 우리는 지금 로그인한 유저의 프로필을 가져오기 위해 위의 코드를 작성한 것이고, 지금 로그인되어 있는 geun의 프로필이 잘 실려온 것을 볼 수 있다.
    • 여기서 궁금해할 수도 있다. 도대체 왜 'geun 님의 프로필'이지?. 이에 대한 해답은 내가 맨 위에 올려놓은 모델에서 찾을 수 있다.
    • __str__ 부분에 적어놓은 것 때문에 위와 같이 나오게 된다.
      # User Profile model
      class UserProfile(models.Model):
       user = models.OneToOneField(User, verbose_name="사용자", on_delete=models.CASCADE)
       introduction = models.TextField("자기소개")
       birth = models.DateField("생일")
       age = models.IntegerField("나이")
       hobby = models.ManyToManyField(Hobby, verbose_name="취미")
       
       def __str__(self):
           return f'{self.user.username} 님의 프로필'
  • 주의해야할 점은 User 모델에는 UserProfile의 내용이 전혀 없다. OneToOneField이기 때문에 역참조로 내용을 가져올 수 있는 것이며, OneToOneField이기 때문에 _set을 붙이지 않아도 되는 것이다.
  • 두 번째
class UserView(APIView):
    # permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    # 사용자 정보 조회
    def get(self, request):
        user = request.user
        # print(dir(user))
        # return Response({})
       
        hobbys = user.userprofile.hobby
        hobbys = str(hobbys)
        print(dir(hobbys))
        
        return Response({"hobbys": hobbys })
  • 위의 결과는 아래의 사진과 같다.
    • 역참조 시 아무런 조건을 달지 않으면 아래와 같은 메세지가 나온다. 역참조가 실패한 것이 아니다!
  • dir의 결과는 아래와 같다.
    • 우리가 쓰려하는 all이 보이는 것을 볼 수 있다!
    • hobby에서 우리가 .을 찍고 쓸 수 있는 것들이다.
  • dir를 통해 나온 값들에는 여러 값들이 섞여 있다. 앞에 던더가 붙거나 언더바가 하나 붙은 것들을 다 지우고, 나머지 값들을 한 번 살펴보자. 여기에는 함수 또는 변수들이 들어있다. 여기서 어떤 것을 우리가 쓸 수 있고, 그것의 결과가 어떻게 나오는지 아래의 코드를 통해 프린트해 볼 수 있을 것이다.
 # 사용자 정보 조회
    def get(self, request):
        user = request.user
        # print(dir(user))
        # return Response({})
       
        hobbys = user.userprofile.hobby
        hobbys = str(hobbys)
        print(dir(hobbys))
        for command in dir(hobbys):
        	try:
            	print(f"command : {command} / ", eval(f"hobbys.{command}()")
                print(f"command : {command} / ", eval(f"hobbys.{command}")
            except:
            	pass
        
        return Response({"hobbys": hobbys })
  • 이제 위에서 우리가 확인한 all()을 뒤에 붙여 쓰게 되면 우리가 코드를 뜯어보기 전에 얻었던 결과에 도달할 수 있다.

😠 역참조를 통해 나와 비슷한 취미를 가진 유저 찾기

  • 위의 코드에서 추가적으로 나와 비슷한 취미를 가진 유저를 찾는 코드를 더해보도록 하자.
    • for문을 돌리기 전에 내가 뭘 쓸 수 있을지 궁금하다 싶으면 어떤 것을 해야하는가?? print(dir(hobby))를 하면 된다!
    • print(dir(hobby))를 해보면 userprofile_set가 존재한다. 그러므로 뒤에 붙여다가 써도 된다.
    • hobby에서 userprofile을 역참조 하였고, 그 결과에서 exclude(user=user)를 사용해 현재 로그인해 있는 user는 결과값에서 제외하였다.
class UserView(APIView):
    # permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    # 사용자 정보 조회
    def get(self, request):
        user = request.user
        # print(dir(user))
        # return Response({})
        
        # 역참조를 사용해 사용자의 취미를 불러옴
        # 그런데 우리가 위의 모델을 보면 related_name을 사용하지 않음
        # user와 user profile의 관계가 OneToOne 관계라서 그럼
        hobbys = user.userprofile.hobby.all()
        
        for hobby in hobbys:
           # exclude : 매칭 된 쿼리만 제외, filter와 반대
           # annotate : 필드 이름을 변경해주기 위해 사용, 이외에도 원하는 필드를 추가하는 등 다양하게 활용 가능
           # values / values_list : 지정한 필드만 리턴 할 수 있음. values는 dict로 return, values_list는 tuple로 ruturn
           # F() : 객체에 해당되는 쿼리를 생성함
           hobby_members = hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username', flat=True)
           hobby_members = list(hobby_members)
           
           print(f"hobby : {hobby.name} / hobby members : {hobby_members}")
        
        return Response({"hobbys": str(hobbys)})
  • 아래와 같이 겹치는 취미를 가진 user를 뽑아낼 수 있다.
  • 위의 hobby_members를 하나씩 뜯어보겠다.
    • 1 : 뒤에 get이나 all, filter 등의 쿼리를 붙여주면 해당하는 결과값이 나오게 된다. 아무런 값이 붙지 않아 None이 붙어있는 것!
    • 2 : 현재 로그인한 사용자인 admin을 제외한 모든 사용자의 프로필 값들이 쿼리셋으로 불러와진다.
    • 3 : 현재 값에서는 annotate로 결과가 달라지지는 않는다.
    • 4 : values.list를 사용해서 원하는 값들을 뽑아올 수 있다. 우리는 user profile 안에 있는 user 안에 있는 username의 오브젝트값을 불러왔고, values.list 안에 있는 username 값을 출력한 것이다.
    • 5 : flat=True를 통해 해당 값들을 쿼리셋 안에서 리스트로 뽑은 것이다.
    • 6 : 위의 값을 쿼리셋이 아닌 리스트의 형태로 뽑았다.
for hobby in hobbys:
            
            print(f'1 : {hobby.userprofile_set.all()}')
            print(f'2 : {hobby.userprofile_set.exclude(user=user)}')
            print(f"3 : {hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username'))}")
            print(f"4 : {hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username')}")
            print(f"5 : {hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username', flat=True)}")
            print(f"6 : {list(hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username', flat=True))}")
            break


Serializer

😠 사용 방법

  • 먼저, user/serializers.py를 생성한다.
  • 그리고 아래와 같이 import를 진행해야 한다.
from rest_framework import serializers
  • 이제 UserSerializer를 먼저 만들어보도록하겠다. serializer에서 제일 중요한 것은 Meta class이다. 일단 아래와 같이 세팅한다.
from rest_framework import serializers

from .models import User as UserModel

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        fields = "__all__"
  • 그리고 user/views.py로 가서 serializer를 사용해보자.
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import permissions
from django.db.models import F

# 로그인 및 로그아웃에 사용
from django.contrib.auth import login, authenticate, logout

from .serializers import UserSerializer

 # 사용자 정보 조회
    def get(self, request):
        return Response(UserSerializer(request.user).data)
  • 이렇게 작성하고 포스트맨으로 돌아가 실행해보도록 하겠다.
  • 해당 코드의 의미는 fields = "__all__"이기 때문의 User 모델의 모든 값을 가져다 쓸 수 있는 것이다. 그런데 모든 값이 나오기 때문에 fields = "__all__"은 잘 사용하지 않는다.
  • 대신 가져오고 싶은 값을 리스트로 작성해 가져오게 된다. 그래서 user/serializers.py의 코드를 수정해보았다.
from rest_framework import serializers

from .models import User as UserModel
from .models import UserProfile as UserprofileModel
from .models import Hobby as HobbyModel

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        fields = ["username", "email", "name", "join_data"]
  • 위의 결과값이 잘 불러와지는 것을 볼 수 있다.
  • 그런데 우리는 좀 더 자세한 값을 알고 싶다. 그래서 프로필에서 정보를 가져와보자.
    • fields에는 아무 값이나 담을 수 없다. 모델에서 가지고 올 수 있는 값만 적을 수 있다. 역참조도 담아서 활용할 수 있다.
    • 유저 프로필의 시리얼라이저도 생성은 했으나, 가져다 쓰려면 언급을 해주어야 한다.
    • 우리는 views.py에 언급하는 대신, 아래에 있는 UserSerializer에서 역참조를 활용해 사용해보자.
    • 아래와 같이 fields에 역참조에서 불러왔던 것처럼 userprofile을 적어주고, 해당 시리얼라이저 안에서 userprofile을 선언해주면 된다.
from rest_framework import serializers

from .models import User as UserModel
from .models import UserProfile as UserProfileModel
from .models import Hobby as HobbyModel

class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfileModel
        fields = ["introduction", "birth", "age"]

class UserSerializer(serializers.ModelSerializer):
    userprofile = UserProfileSerializer()
    
    class Meta:
    
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        fields = ["username", "email", "name", "join_data", "userprofile"]
  • 포스트맨을 실행해보니 결과값이 너무 이쁘다.
  • 그런데 또 보다보니 hobby 값도 가져다가 보고 싶은 욕심이 생긴다.
from rest_framework import serializers

from .models import User as UserModel
from .models import UserProfile as UserProfileModel
from .models import Hobby as HobbyModel

class HobbySerializer(serializers.ModelSerializer):
    class Meta:
        model = HobbyModel
        fields = ["name"]

class UserProfileSerializer(serializers.ModelSerializer):
    hobby = HobbySerializer()
    
    class Meta:
        model = UserProfileModel
        fields = ["introduction", "birth", "age", "hobby"]

class UserSerializer(serializers.ModelSerializer):
    userprofile = UserProfileSerializer() # object
    
    class Meta:
    
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        fields = ["username", "email", "name", "join_data", "userprofile"]
  • 부푼 마음을 가지고 포스트맨을 보면 결과가 참담하다. 이름값이 나오지 않는다.
  • 안되는 이유를 알아보자.
    • UserProfileUserOneToOne 관계이기 때문에 object로 반환된다. 왜냐면 어짜피 결과가 하나이기 때문이다.
    • 반면, UserProfileHobbyManyToMany 관계라 QuerySet으로 반환된다. 관계의 이름에서 유추할 수 있듯이 여러 개의 결과가 object로 생성되게 되며, 이를 쿼리셋으로 묶어서 보내준다.
    • 그렇다면 어떻게 해결할 수 있을까?
    • 아래와 같이 다대다 관계를 가진 시리얼라이저의 괄호 안에 many=True를 넣어주면 된다.
from rest_framework import serializers

from .models import User as UserModel
from .models import UserProfile as UserProfileModel
from .models import Hobby as HobbyModel

class HobbySerializer(serializers.ModelSerializer):
    class Meta:
        model = HobbyModel
        fields = ["name"]

class UserProfileSerializer(serializers.ModelSerializer):
    # input data가 QuerySet일 경우 many=True를 입력해주어야 한다. 
    hobby = HobbySerializer(many=True) # QuerySet (ManyToMany 관계)
    
    class Meta:
        model = UserProfileModel
        fields = ["introduction", "birth", "age", "hobby"]

class UserSerializer(serializers.ModelSerializer):
    userprofile = UserProfileSerializer() # object (OneToOne 관계)
    
    class Meta:
    
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        fields = ["username", "email", "name", "join_data", "userprofile"]
  • 또다시 포스트맨으로 돌아가보자. 아주 이쁘다.
  • 취미를 이쁘게 잘 뽑았으니, 이제 누가 나와 같은 취미를 가졌는가에 대해서도 보여주고 싶어졌다.
  • 해당 작업을 하려면 HobbySerializer를 수정해주면 된다.
  • 앞에서 나는 fields 안에는 모델 안에 존재하는 요소만 작성해야 한다고 했다. 그러나 이 작업은 모델에 있는 내용으로는 마무리할 수 없다.
  • 따라서 나는 serializerMethodField()를 사용할 것이다. 우선 아래와 같이 작성해 포스트맨으로 정상적인지 확인해보자.
    • serializerMethodField()를 사용하게 된다면, 꼭 get_same_hobby_user와 같이 get_변수명을 함수로 만들어주어야 한다. 이렇게하지 않으면 에러가 난다.
    • 그리고 get_same_hobby_user의 인자로 들어가는hobby model의 obj의 값과 타입을 찍어보았다.
from rest_framework import serializers

from .models import User as UserModel
from .models import UserProfile as UserProfileModel
from .models import Hobby as HobbyModel

class HobbySerializer(serializers.ModelSerializer):
    same_hobby_user = serializers.SerializerMethodField()
    def get_same_hobby_user(self, obj):
        user_list = []
        print(obj, type(obj)) # hobby model의 object
        return "Test!!"
    
    class Meta:
        model = HobbyModel
        fields = ["name", "same_hobby_user"]

class UserProfileSerializer(serializers.ModelSerializer):
    # input data가 QuerySet일 경우 many=True를 입력해주어야 한다. 
    hobby = HobbySerializer(many=True) # QuerySet (ManyToMany 관계)
    
    class Meta:
        model = UserProfileModel
        fields = ["introduction", "birth", "age", "hobby"]

class UserSerializer(serializers.ModelSerializer):
    userprofile = UserProfileSerializer() # object (OneToOne 관계)
    
    class Meta:
    
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        fields = ["username", "email", "name", "join_data", "userprofile"]
  • obj의 값과 타입이 올바르게 찍혀있는 것을 볼 수 있다. 이제 나는 obj를 가지고 무엇을 할 수 있는지 알아내기 위해 print(dir(obj))를 해보도록 하겠다.
  • 그리고 우리는 아주 값진 녀석을 건졌다. 바로 userprofile_set이다. 즉, hobby의 obj에서 역참조로 userprofile의 값을 가져올 수 있는 것이고, 이는 바로 해당 hobby를 가지고 있는 user의 정보를 가져올 수 있다는 뜻이다.
  • 위에서 알아낸 내용을 바탕으로 코드를 수정하였다.
from rest_framework import serializers

from .models import User as UserModel
from .models import UserProfile as UserProfileModel
from .models import Hobby as HobbyModel

class HobbySerializer(serializers.ModelSerializer):
    same_hobby_user = serializers.SerializerMethodField()
    def get_same_hobby_user(self, obj):
        user_list = []
        for user_profile in obj.userprofile_set.all():
            user_list.append(user_profile.user.username)
        return user_list
        
        # 위의 for문을 리스트 컴프리헨션을 통해 줄일 수 있음!
        # return [user_profile.user.username for user_profile in obj.userprofile_set.all()]
    
    class Meta:
        model = HobbyModel
        fields = ["name", "same_hobby_user"]

class UserProfileSerializer(serializers.ModelSerializer):
    # input data가 QuerySet일 경우 many=True를 입력해주어야 한다. 
    hobby = HobbySerializer(many=True) # QuerySet (ManyToMany 관계)
    
    class Meta:
        model = UserProfileModel
        fields = ["introduction", "birth", "age", "hobby"]

class UserSerializer(serializers.ModelSerializer):
    userprofile = UserProfileSerializer() # object (OneToOne 관계)
    
    class Meta:
    
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        fields = ["username", "email", "name", "join_data", "userprofile"]
  • 포스트맨을 확인해보자. 아주 좋다. 여기의 단점은 admin 유저, 즉 지금 로그인해 있는 user를 exclude를 통해 제거하지 않은 점이다.
  • 여기서 로그인한 사용자와 취미가 똑같은 사람을 보는 것이 아닌, 모든 사용자에 대한 해당 값을 가져오고 싶다면 views.py를 어떻게 바꿔야 할까?
  • 아래와 같이 진행하면 되고, 리턴값의 .data가 결과값을 딕셔너리 형태로 바꿔주고, Response가 변경된 딕셔너리 값을 JSON 형태로 바꿔서 반환해 주는 것이다.
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import permissions
from django.db.models import F

# 로그인 및 로그아웃에 사용
from django.contrib.auth import login, authenticate, logout

from .serializers import UserSerializer

from .models import User as UserModel
from .models import UserProfile as UserProfileModel
from .models import Hobby as HobbyModel

# 사용자 정보 
class UserView(APIView):
    # permission_classes = [permissions.AllowAny]       # 누구나 view 접근 가능
    permission_classes = [permissions.IsAuthenticated] # 로그인된 사용자만 view 접근 가능
    # permission_classes = [permissions.IsAdminUser]     # admin 유저만 view 접근 가능
    
    # 사용자 정보 조회
    def get(self, request):
        all_user = UserModel.objects.all()
        return Response(UserSerializer(all_user, many=True).data)
  • 포스트맨을 확인해보면 다른 유저들에 대한 값도 나온 것을 확인할 수 있다. 나는 user3의 값을 확인해보았다.

😠 같은 의미 다른 방법

  • 만약 원래 userprofile이었던 변수명을 user_detail로 바꿔서 사용하고 싶다면, 아래와 같이 설정해주면 된다.
class UserSerializer(serializers.ModelSerializer):
    user_detail = UserProfileSerializer(source="userprofile") # object (OneToOne 관계)
    
    class Meta:
    
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        fields = ["username", "email", "name", "join_data", "user_detail"]
  • 포스트맨에서도 이름이 바뀐 것을 볼 수 있다.
  • HobbySerializer에서 우리는 취미가 같은 유저를 뽑아서 보여줬다. 그런데 이 방법 말고도 바로 역참조를 필드에 넣어서 활용 가능하며, 이것을 가공해 사용할 수도 있다.
class HobbySerializer(serializers.ModelSerializer):
    
    class Meta:
        model = HobbyModel
        fields = ["name", "userprofile_set"]
  • 포스트맨에서 확인해보자.

Permissions

😠 종류

  • permissions는 종류가 아주 많다. 어제 공부할 때는 주로 사용했던 3개의 permissions에 대해서만 봤었다.
  • 이제 모든 친구들을 보러가보자.
  • permissions를 찾아서 + 클릭 해보자.
  • 어제 봤던 3개는 위로 올리고 그 아래의 녀석들을 캡쳐해봤다. 코드가 총 300줄이니 아래 더 많이 남아있다.

😠 커스텀 permissions

  • 이제 나는 유저 모델과 동일하게 permissions 모델도 커스텀화해보도록 하겠다.
  • 먼저 위에서 확인했던 여러 퍼미션들 중에서 마음에 드는 친구를 하나 골라온다. 나는 이 녀석을 선택했다.
class IsAuthenticated(BasePermission):
    """
    Allows access only to authenticated users.
    """

    def has_permission(self, request, view):
        return bool(request.user and request.user.is_authenticated)
  • 위의 내용을 django_rest_framework/permissions.py 파일을 생성한 뒤 복붙해주고, BasePermission을 사용할 수 있도록 import를 해주면 된다. 여기서 나는 class의 이름을 변경해 주었다.
from rest_framework.permissions import BasePermission

class MyAuthenticate(BasePermission):
    """
    Allows access only to authenticated users.
    """

    def has_permission(self, request, view):
        return bool(request.user and request.user.is_authenticated)
  • 이제 이 permission을 사용해보자.
  • 나는 이 permission을 user/views.py에서 사용할 것이다. 따라서 import를 진행해보도록 하겠다. 그리고 이전에 permission을 사용하던 것과 같은 폼으로 사용을 선언해주면 된다.
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import permissions
from django.db.models import F

# 로그인 및 로그아웃에 사용
from django.contrib.auth import login, authenticate, logout

from .serializers import UserSerializer

from .models import User as UserModel
from .models import UserProfile as UserProfileModel
from .models import Hobby as HobbyModel

from django_rest_framework.permissions import MyAuthenticate

# 사용자 정보 
class UserView(APIView):
    permission_classes = [MyAuthenticate]
    
    # 사용자 정보 조회
    def get(self, request):
        # 다수의 사용자의 값을 불러올 때 사용!
        # all_user = UserModel.objects.all()
        # return Response(UserSerializer(all_user, many=True).data)
        
        return Response(UserSerializer(request.user).data)
  • 어떻게 적용하는지 알아냈으니 permission의 내용을 수정하도록 하겠다. 나는 가입한지 일주일이 넘는 사용자만 활동할 수 있도록 권한을 부여하고 싶다.
  • django_rest_framework/permissions.py
from rest_framework.permissions import BasePermission
from datetime import datetime, timedelta

class MyAuthenticateOverWeek(BasePermission):
    
    def has_permission(self, request, view):
        user = request.user
        if not user or not user.is_authenticated:
            return False
        
        # DateField : 2022-06-18
        # DataTimeField : 2022-06-18 10:50:55
        # datetime.now().date() 를 통해서 시간까지는 안나오게 설정함
        print(f'user join date : {user.join_data}')
        print(f'now date : {datetime.now().date()}')
        print(f'a week ago date : {datetime.now().date() - timedelta(days=7)}')
        
        return bool(user.join_data < (datetime.now().date() - timedelta(days=7)))
  • 위의 코드를 테스트할 때는 days=7minutes=5와 같이 바꿔서 테스트를 꼭 진행해봐야 한다.
  • 위의 코드는 가입일자를 DateField로 생성했을 때의 이야기이다. 만약 나처럼 DateTimeField로 생성했다면 아래와 같이 진행하면 된다.
from rest_framework.permissions import BasePermission
from datetime import timedelta
from django.utils import timezone

class MyAuthenticateOverWeek(BasePermission):
    
    def has_permission(self, request, view):
        user = request.user
        if not user or not user.is_authenticated:
            return False
        
        # DateField : 2022-06-18
        # DataTimeField : 2022-06-18 10:50:55
        # datetime.now().date() 를 통해서 시간까지는 안나오게 설정함
        
        print(f'user join date : {user.join_data}')
        print(f'now date : {timezone.now()}')
        print(f'a week ago date : {timezone.now() - timedelta(days=7)}')
        
        return bool(user.join_data < (timezone.now() - timedelta(days=7)))

profile
https://github.com/nikevapormax
post-custom-banner

0개의 댓글