[0621] Django Rest Framework

nikevapormax·2022년 6월 21일
0

TIL

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

DRF 특강

django orm 심화

😠 Q

  • Q를 사용해 쿼리에 and 또는 or를 적용시킬 수 있다.
  • 사용자가 admin인지 아닌지에 대해 판별하거나, 검색을 할 때 많이 사용한다.
  • 프론트에서 데이터를 받아 or를 사용해 검색을 많이 한다.
# 검색어 : title, content

ArticleModel.objects.filter(
	Q(title__contains=title) | Q(content__contains=content)
)
  • 이번에는 user/views.py에서 예시를 들어보도록 하겠다.
  • or : 나이가 20살보다 많거나 취미가 축구인 user 검색
from django.db.models.query_utils import Q

class UserView(APIView):
    permission_classes = [IsAdminOrIsAuthenticated]
    
    # 사용자 정보 조회
    def get(self, request):        
        query = Q(hobby__name='축구') | Q(age__gt=19)
        
        user_profile_list = UserProfileModel.objects.filter(query)
        print(user_profile_list)
        
        return Response({})
  • 프린트된 부분을 확인해보자. filter를 사용해 쿼리셋이 반환된 것을 볼 수 있다.
  • and : 나이가 20살보다 많고 취미가 축구인 user 검색
from django.db.models.query_utils import Q

class UserView(APIView):
    permission_classes = [IsAdminOrIsAuthenticated]
    
    # 사용자 정보 조회
    def get(self, request):        
        query = Q(hobby__name='축구') | Q(age__gt=19)
        
        user_profile_list = UserProfileModel.objects.filter(query)
        print(user_profile_list)
        
        return Response({})
  • 프린트된 부분을 확인해보자.


serializer 심화

  • serializer에서는 데이터 직렬화 이외에도 data validation, create, update 기능을 사용할 수 있다.

😠 validator

- validator 생성

  • serializer에서는 기본적으로 Meta class 내부 field에 포함되어 있는 항목에 맞게 validate를 진행해준다.
  • validator를 작성하기 앞서 ArticleSerializer를 먼저 확인해보도록 하겠다.
    • category와 같이 serializers.SerializerMethodField()인 필드의 경우에는 검사에서 제외된다.
    • comments는 read_only=True를 걸어주어야 검사를 하지 않아 검사에서 에러가 나지 않는다.
      • write나 update를 할 때 읽기만 가능하기 때문에 검사의 범주에 포함되지 않게 된다.
      • 게시글을 작성할 때 댓글을 작성하지 않는다.
class ArticleSerializer(serializers.ModelSerializer):
    category = serializers.SerializerMethodField()
    # related_name을 작성했다면 source는 쓸 필요가 없음
    comments = CommentSerializer(many=True, source='comment_set', read_only=True)
    
    # 카테고리를 리스트로 받아오기 위해 다음과 같이 작성
    def get_category(self, obj):
        return [category.name for category in obj.category.all()]
    
    class Meta:
        model = ArticleModel
        fields = ["title", "content", "comments", "category"]
  • blog/views.py에서 validator를 작성해보고, 일단 포스트맨을 돌려보도록 하자.
class ArticleView(APIView):
    
    permission_classes = [IsAdminOrIsAuthenticated]
    
    def get(self, request):      
        user = request.user
        
        today = datetime.now()
        time = ArticleModel.objects.filter(exposure_start__lte=today, exposure_end__gte=today).order_by("-exposure_start")
       
        return Response(ArticleSerializer(time, many=True).data, status=status.HTTP_200_OK)

    def post(self, request):
        user = request.user
        request.data['author'] = user.id
        
        article_serializer = ArticleSerializer(data=request.data)
        
        # request.data의 유효성 검증
        if article_serializer.is_valid():  # True or False로 결과값이 나옴
            article_serializer.save()
            return Response(article_serializer.data, status=status.HTTP_200_OK)
        
        # False인 경우 어디서 에러가 났는지 보여줌
        return Response(article_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  • 이렇게 하면 에러가 발생한다. 에러의 내용은 아래와 같다.
  • 위의 에러는 Article 모델을 만들 때 게시글을 작성한 사용자인 author에 null=True를 주지 않았기 때문에 나는 에러이다. 작성자는 null일 수가 없으니, 이를 해결하기 위해 serializer.py를 수정해보자.
    • fields에 author 추가
class ArticleSerializer(serializers.ModelSerializer):
    category = serializers.SerializerMethodField()
    # related_name을 작성했다면 source는 쓸 필요가 없음
    comments = CommentSerializer(many=True, source='comment_set', read_only=True)
    
    # 카테고리를 리스트로 받아오기 위해 다음과 같이 작성
    def get_category(self, obj):
        return [category.name for category in obj.category.all()]
    
    class Meta:
        model = ArticleModel
        fields = ["author", "title", "content", "comments",  "exposure_start", "exposure_end", "category"]
  • 위와 같이 수정하고 다시 포스트맨을 돌려보자. 아까 에러에서도 봤다싶이 우리가 넣어야 하는 것은 게시글을 작성한 user의 id이다. 주의하자.
    • 지금은 User에 대한 시리얼라이저를 돌리지 않아 id값이 나온 것!
  • admin 페이지에서도 잘 적용이 되었다.

- extra_kwargs 사용

  • 앞에서 나는 comments에 read_only 속성을 부여했다. 이것을 extra_kwargs를 통해 다른 속성들과 함께 serializer에 부여할 수 있다.
class Meta:
        ...
        # 각 필드에 해당하는 다양한 옵션 지정
        extra_kwargs = {
            # write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
            # 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
            'password': {'write_only': True}, # default : False
            'email': {
                # error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
                'error_messages': {
                    # required : 값이 입력되지 않았을 때 보여지는 메세지
                    'required': '이메일을 입력해주세요.',
                    # invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
                    'invalid': '알맞은 형식의 이메일을 입력해주세요.'
                    },
                    # required : validator에서 해당 값의 필요 여부를 판단한다.
                    'required': False # default : True
                    },
            }
  • 이것을 나의 시리얼라이저에 적용해서 아까 포스트맨에서 보였던 user의 id 값을 숨겨보도록 하겠다.
class ArticleSerializer(serializers.ModelSerializer):
    category = serializers.SerializerMethodField()
    # related_name을 작성했다면 source는 쓸 필요가 없음
    comments = CommentSerializer(many=True, source='comment_set', read_only=True)
    
    # 카테고리를 리스트로 받아오기 위해 다음과 같이 작성
    def get_category(self, obj):
        return [category.name for category in obj.category.all()]
    
    class Meta:
        model = ArticleModel
        fields = ["author", "title", "content", "comments",  "exposure_start", "exposure_end", "category"]
        
        extra_kwargs = {
            # write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
            # 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
            'author': {'write_only': True}, # default : False
            }
  • 포스트맨의 결과를 확인해보면 author가 보이지 않는 것을 알 수 있다.
    • author는 시리얼라이저가 아닌 views.py에서 부여했다. 헷갈리지 말길.
  • 이제 extra_kwargs의 사용하지 않았던 아랫 부분을 사용해보도록 하겠다. 일단 포스트맨으로 가 일부러 에러메세지를 내보도록 하자.
    • title과 content 값을 지우고 데이터를 보내보았다.
    • 아래의 에러 메세지를 보면 그냥 해당 필드가 필수값인데 왜 안줘?라고 칭얼거리는 에러 메세지를 볼 수 있다.
  • extra_kwargs의 아랫 부분을 활용해 title의 required 속성을 없애고, 다시 포스트맨에서 결과를 확인해보도록 하자.
class ArticleSerializer(serializers.ModelSerializer):
    category = serializers.SerializerMethodField()
    # related_name을 작성했다면 source는 쓸 필요가 없음
    comments = CommentSerializer(many=True, source='comment_set', read_only=True)
    
    # 카테고리를 리스트로 받아오기 위해 다음과 같이 작성
    def get_category(self, obj):
        return [category.name for category in obj.category.all()]
    
    class Meta:
        model = ArticleModel
        fields = ["author", "title", "content", "comments", "exposure_start", "exposure_end", "category"]
        
        extra_kwargs = {
            # write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
            # 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
            'author': {'write_only': True}, # default : False
            'title': {
                # error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
                'error_messages': {
                    # required : 값이 입력되지 않았을 때 보여지는 메세지
                    'required': '제목을 입력해주세요.',
                    # invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
                    'invalid': '제목이 이상해요.'
                    },
                    # required : validator에서 해당 값의 필요 여부를 판단한다.
                    'required': False # default : True
                    },
            }
  • 보이는 것과 같이 title에 대한 에러 메세지가 사라진 것을 알 수 있다.

  • 이제 title의 required 속성값을 True로 변경해서 테스트를 해보자.
    변경 후 포스트맨으로 가 실험을 한 결과는 아래와 같다. 우리가 위에서 작성한 내용이 아주 잘 적용된 것을 확인할 수 있다.

  • 구조가 만약 복잡하다면 시리얼라이저를 나눠서 작성하는 것이 나을 수도 있다.

  • 그런데 카테고리 데이터를 넣었는데 왜 값이 들어가지 않았는지에 대한 의문이 생길 수 있다. 이는 custom creator를 사용하지 않아서 그런 것이다. 추후에 작업하도록 하겠다.

- user validator 생성

  • 또다른 연습을 진행해보도록 하겠다. 이번에는 그동안 우리에게 없었던 기능인 회원가입을 만들어볼 것이다.
  • 위에서와 같이 user/serializers.pyuser/views.py를 수정해보도록 하자.
  • user/serializers.py
class UserSerializer(serializers.ModelSerializer):
    user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
    articles = ArticleSerializer(many=True, source="article_set", read_only=True)
    comments = CommentSerializer(many=True, source="comment_set", read_only=True)
    
    class Meta:
    
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        # join_data 같은 경우는 시스템에서 자동으로 등록해주는 것으로 기본적으로 read_only임
        fields = ["username", "password", "email", "name", "join_data", "user_detail", "articles", "comments"]
        
        extra_kwargs = {
            # write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
            # 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
            'password': {'write_only': True}, # default : False
            'email': {
                # error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
                'error_messages': {
                    # required : 값이 입력되지 않았을 때 보여지는 메세지
                    'required': '이메일을 입력해주세요.',
                    # invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
                    'invalid': '알맞은 형식의 이메일을 입력해주세요.'
                    },
                    # required : validator에서 해당 값의 필요 여부를 판단한다.
                    'required': False # default : True
                    },
            }
  • user/views.py
class UserView(APIView):
    # permission_classes = [MyAuthenticateOver3]
    permission_classes = [IsAdminOrIsAuthenticated]
    
    # 사용자 정보 조회
    def get(self, request):
        
        user_serializer = UserSerializer(request.user, context={"request": request}).data
        return Response(user_serializer)
        
    # 회원가입
    def post(self, request):
        user_serializer = UserSerializer(data=request.data)
        if user_serializer.is_valid():
            user_serializer.save()
            return Response(user_serializer.data, status=status.HTTP_200_OK)
        
        return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  • 위의 코드는 아직은 user profile에 대한 처리를 하지 않은 결과이다.

😠 creator

  • erializer에서는 validation을 통과할 경우 .save() 메소드를 통해 검증 된 오브젝트를 생성 할 수 있다.
  • 사용 방법은 validator에 작성한 코드와 동일하다.
  • 회원가입 부분을 확인하면 될 것 같다.

😠 updater

  • serializer를 사용해 기존 데이터들 쉽게 업데이트 할 수 있다.
  • updator는 회원 정보 수정에 대한 코드를 작성하며 익혀보도록 하겠다.
    • 수정 부분을 보면 됨!
  • user/serializers.py
class UserSerializer(serializers.ModelSerializer):
    user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
    articles = ArticleSerializer(many=True, source="article_set", read_only=True)
    comments = CommentSerializer(many=True, source="comment_set", read_only=True)
    
    class Meta:
    
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        # join_data 같은 경우는 시스템에서 자동으로 등록해주는 것으로 기본적으로 read_only임
        fields = ["username", "password", "email", "name", "join_data", "user_detail", "articles", "comments"]
        
        extra_kwargs = {
            # write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
            # 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
            'password': {'write_only': True}, # default : False
            'email': {
                # error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
                'error_messages': {
                    # required : 값이 입력되지 않았을 때 보여지는 메세지
                    'required': '이메일을 입력해주세요.',
                    # invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
                    'invalid': '알맞은 형식의 이메일을 입력해주세요.'
                    },
                    # required : validator에서 해당 값의 필요 여부를 판단한다.
                    'required': False # default : True
                    },
            }
  • user/views.py
    • 현재 obj_id를 받아오긴 하나 이를 그대로 사용하면 좋지 않아 아직 사용하지 않았다. 해당 부분에 대한 수정이 필요함!
class UserView(APIView):
    # permission_classes = [MyAuthenticateOver3]
    permission_classes = [IsAdminOrIsAuthenticated]
    
    # 사용자 정보 조회
    def get(self, request):
        
        user_serializer = UserSerializer(request.user, context={"request": request}).data
        return Response(user_serializer)
        
    # 회원가입
    def post(self, request):
        user_serializer = UserSerializer(data=request.data)
        if user_serializer.is_valid():
            user_serializer.save()
            return Response(user_serializer.data, status=status.HTTP_200_OK)
        
        return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
    # 회원 정보 수정
    def put(self, request, obj_id):
        user = request.user
        target_user = UserModel.objects.get(id=user.id)
        # fields에 있는 데이터를 다 넣을 필요가 없도록 하기 위해 partial=True 사용
        user_serializer = UserSerializer(target_user, data=request.data, partial=True)
        
        if user_serializer.is_valid():
            user_serializer.save()
            return Response(user_serializer.data, status=status.HTTP_200_OK)
        
        return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

😠 custom validator

  • custom validator는 validator 이후에 동작한다.
  • custom validator는 validator와 별개로 동작한다.
    • validator는 데이터의 requierd, invalid 등을 판단하고 custom validator에서는 사용자가 원하는 validation을 추가로 검증 할 수 있다.
  • custom validator를 생성해 사용해보도록 하자.
  • views.py에서 크게 변하는 부분은 없고, serializers.py에서 변경 사항이 생기게 된다.
  • 어떻게 동작하는지 한 번 확인해보자. 먼저 serializers.py에 아래 부분을 추가해주고 원래 있던 user 데이터를 쏴보도록 하겠다.
class UserSerializer(serializers.ModelSerializer):
    user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
    articles = ArticleSerializer(many=True, source="article_set", read_only=True)
    comments = CommentSerializer(many=True, source="comment_set", read_only=True)
    
    def validate(self, data):
        print(data)
        return data
        
    #### 하단 생략 ####
  • 포스트맨의 결과이다. 원래있던 validator에서 막힌 것을 알 수 있다.

  • 이제 새로운 user8의 회원가입을 똑같은 조건으로 실행해보았더니, 데이터가 잘 들어간 것을 볼 수 있다. 따라서 위에도 작성하였지만, 우리는 custom validator의 동작이 원래있던 validator의 동작 이후에 된다는 것을 알 수 있다.

  • 코드에 있던 print 부분도 잘 찍힌 것을 보니 회원가입이 잘 진행된 것을 볼 수 있다.

  • 이제 custom validator를 사용해 네이버 이메일을 가진 사용자만 가입할 수 있도록 걸러보자.

class UserSerializer(serializers.ModelSerializer):
    user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
    articles = ArticleSerializer(many=True, source="article_set", read_only=True)
    comments = CommentSerializer(many=True, source="comment_set", read_only=True)
    
    def validate(self, data):
        if not data.get("email", "").endswith("@naver.com"):
            raise serializers.ValidationError(
                detail={"error": "네이버 이메일만 가입할 수 있습니다."}
            )
        return data
        
    #### 하단 생략 ####
  • 포스트맨으로 가 틀린 예시와 맞는 예시를 들어보자.
    • 틀린 예시
    • 맞는 예시
  • 위의 예시를 통해 custom validator 안으로 데이터가 잘 들어왔고, custom validator 또한 제 역할을 제대로 수행한 것을 알 수 있다.
  • 만약 method별로 validation을 진행하고 싶다면 어떻게 하면 될까?
  • views.py에서 아래와 같이 context를 사용해 request를 보내준다.
if user_serializer.is_valid():
	user_serializer.save()
    return Response(user_serializer.data, context={"request": request}, status=status.HTTP_200_OK)
  • 그리고 serializer에 들어와 아래와 같이 method를 풀어 사용해주면 된다.
class UserSerializer(serializers.ModelSerializer):
    user_detail = UserProfileSerializer(source="userprofile", read_only=True) # object (OneToOne 관계)
    articles = ArticleSerializer(many=True, source="article_set", read_only=True)
    comments = CommentSerializer(many=True, source="comment_set", read_only=True)
    
    def validate(self, data):
        try:
            http_method = self.context['request'].method
        except:
            http_method=""
            
        if http_method == 'POST':
            if not data.get("email", "").endswith("@naver.com"):
                raise serializers.ValidationError(
                    detail={"error": "네이버 이메일만 가입할 수 있습니다."}
                )
        return data
  • 여러 개의 이메일 도메인을 가지고 유효성을 검사하고 싶다면 아래와 같이 하면 된다. 이때 valid_email_list는 파일 맨 위로 빼 상수로 선언해 사용하는 것이 좋다.
  • 지금까지의 흐름을 봤을 때 validator는 기존의 것에 custom validator의 기능을 더해서 사용하는 것을 알 수 있다.
  • 이제 다루게 될 custom creatorcustom updater는 기존의 것을 덮어쓰게 되며, 이 둘을 만들게 되면 원래 있던 craetor와 updater는 기능을 하지 않게 된다.

😠 custom creator

  • 일단 우리가 ModelSerializer 안에서 기본적으로 쓰고 있던 creator를 확인해보자. ( + ModelSerializer)
  • updater 또한 똑같은 방식으로 확인할 수 있다.
  • 우리가 기본 함수와 똑같은 이름을 사용하게 되면서 우리가 새로 만드는 custom creator로 기능이 덮어써지는 것이다.
  • create는 인자로 검증된 데이터가 들어가게 된다. 무언가를 생성하기 전에 검증을 하고 만들면 좋다고 생각하면 될 것 같다. (django의 공홈에서도 똑같은 방식으로 소개하고 있음)
  • views.py에서 우리가 지금껏 save()를 많이 사용해 왔다. 우리가 POST에서 save()를 하게 되면 create 함수가, PUT에서 save()를 하면 update 함수가 호출되어 왔던 것이다.
    (create에는 obj가 없고, update에는 obj가 들어가야 한다. 수정한다는 것은 수정할 대상이 있다는 뜻이고, 그 수정할 대상인 obj가 들어가는 것이다. 하지만 어떤 것을 생성할 때는 어떤 대상을 찝어서 생성하라고 하지 않는다. )
  • 아까 회원가입할 때를 생각해보자.
  • 우리는 UserSerializer 안에 user_profile을 만들어서 썼었다. 하지만 아까 그냥 readonly=True를 하고 넘어갔다. 이제 user_profile을 회원가입 시에도 적용할 수 있도록 수정해보자.
  • user/serializers.py
class UserSerializer(serializers.ModelSerializer):
    user_detail = UserProfileSerializer(source="userprofile") # object (OneToOne 관계)
    articles = ArticleSerializer(many=True, source="article_set", read_only=True)
    comments = CommentSerializer(many=True, source="comment_set", read_only=True)
    
    # custom validator
    # 기존의 validator와 같이 쓰임
    def validate(self, data):
        if not data.get("email", "").endswith("@naver.com"):
            raise serializers.ValidationError(
                detail={"error": "네이버 이메일만 가입할 수 있습니다."}
            )
        return data
    
    # custom creator
    # 기존의 creator의 기능을 덮어쓰며, 이것을 만들면 기존의 creator는 작동하지 않음
    def create(self, validate_data):
        return UserModel(**validate_data)
    class Meta:
        # 내가 serializer에서 어떤 모델을 쓰겠다는 것을 선언하는 것!(중요)
        model = UserModel
        # join_data 같은 경우는 시스템에서 자동으로 등록해주는 것으로 기본적으로 read_only임
        fields = ["username", "password", "email", "name", "join_data", "user_detail", "articles", "comments"]
        
        extra_kwargs = {
            # write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
            # 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
            'password': {'write_only': True}, # default : False
            'email': {
                # error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
                'error_messages': {
                    # required : 값이 입력되지 않았을 때 보여지는 메세지
                    'required': '이메일을 입력해주세요.',
                    # invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
                    'invalid': '알맞은 형식의 이메일을 입력해주세요.'
                    },
                    # required : validator에서 해당 값의 필요 여부를 판단한다.
                    'required': False # default : True
                    },
            }
  • 코드를 위와 같이 수정하고(userprofile의 read_only 제거), 포스트맨을 돌리면 아래와 같은 에러를 만나게 된다.
  • 그런데 우리가 여기서 hobby를 입력해 보내주게 되면 원래 있던 hobby에서 선택해 넣는 것이 아니라 hobby를 추가하는 것이 되어버린다.
  • 그러므로 HobbySerializer를 다음과 같이 수정해주어야 한다.
class UserProfileSerializer(serializers.ModelSerializer):
    # input data가 QuerySet일 경우 many=True를 입력해주어야 한다. 
    hobby = HobbySerializer(many=True, read_only=True) # QuerySet (ManyToMany 관계)
    
    class Meta:
        model = UserProfileModel
        fields = ["introduction", "birth", "age", "hobby"]
  • 이렇게 바꾸고 다시 포스트맨을 돌리면 아래의 에러를 만나게 된다. 이 에러는 create 함수 안에서는 우리가 UserModel에 대한 정보만 받기로 했는데 UserProfileModel의 내용도 같이 들어가기 때문에 일어나는 것이다.
  • 그러므로 pop()을 사용해서 userprofile의 내용을 뺀 뒤에 저장을 해주는 방식으로 진행해보자.
def create(self, validated_data):
        user_profile = validated_data.pop("userprofile")
        print(user_profile)
        print(validated_data)
        return 
  • print가 잘 찍히는 것을 볼 수 있다.
  • 위의 코드를 아래와 같이 수정하도록 하겠다.
def create(self, validated_data):
        user_profile = validated_data.pop("userprofile")
        user = UserModel(**validated_data)
        user.save()
        
        user_profile = UserProfileModel.objects.create(user=user, **user_profile)
        
        return user
  • 포스트맨에서 결과를 확인해보자.
  • 하지만 아직 부족하다. 우리는 취미도 넣을 수 있기 때문이다. 그러므로 취미도 넣어보도록 하겠다. userprofile에 취미를 넣고 싶은 것이기 때문에 다음과 같이 코드를 작성해주도록 하겠다.
class UserProfileSerializer(serializers.ModelSerializer):
    # input data가 QuerySet일 경우 many=True를 입력해주어야 한다. 
    hobby = HobbySerializer(many=True, read_only=True) # QuerySet (ManyToMany 관계)
    get_hobbys = serializers.ListField(required=False)
    
    class Meta:
        model = UserProfileModel
        fields = ["introduction", "birth", "age", "hobby", "get_hobbys"]
  • 이렇게 하고 어떤 변화가 생겼는지 저장을 하지 않고 값만 찍어보도록 하겠다.
def create(self, validated_data):
        user_profile = validated_data.pop("userprofile")
        user = UserModel(**validated_data)
        print(user_profile)
        return 
        # user.save()
        
        # user_profile = UserProfileModel.objects.create(user=user, **user_profile)
        
        # return user
  • 아래와 같이 취미까지 프린트가 되는 것을 볼 수 있다.
  • 이제 같은 취미를 가진 사용자들의 리스트도 뽑아보도록 하겠다.
  • user/views.py
    • context가 담기는 위치를 주의해서 보길 바람!
class UserView(APIView):
    # permission_classes = [MyAuthenticateOver3]
    permission_classes = [IsAdminOrIsAuthenticated]
    
    # 사용자 정보 조회
    def get(self, request):
        
        user_serializer = UserSerializer(request.user, context={"request": request}).data
        return Response(user_serializer)
        
    # 회원가입
    def post(self, request):
        user_serializer = UserSerializer(data=request.data, context={"request": request})
        
        if user_serializer.is_valid():
            user_serializer.save()
            return Response(user_serializer.data, status=status.HTTP_200_OK)
        
        return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  • user/serializer.py
# custom creator
    # 기존의 creator의 기능을 덮어쓰며, 이것을 만들면 기존의 creator는 작동하지 않음
    def create(self, validated_data):
        user_profile = validated_data.pop("userprofile")
        get_hobbys = user_profile.pop("get_hobbys", [])
        password = validated_data.pop("password")
        
        user = UserModel(**validated_data)
        user.set_password(password)
        user.save()
        
        user_profile = UserProfileModel.objects.create(user=user, **user_profile)
        user_profile.hobby.add(*get_hobbys)
        
        return user

😠 custom updater

  • 이제 회원의 정보를 변경해보도록 하자.
  • 회원의 정보를 변경하기 전, views.py로 가서 put 함수에 context를 추가해주도록 하겠다.
 # 회원 정보 수정
    def put(self, request, obj_id):
        # user = request.user
        target_user = UserModel.objects.get(id=obj_id)
        # fields에 있는 데이터를 다 넣을 필요가 없도록 하기 위해 partial=True 사용
        user_serializer = UserSerializer(target_user, data=request.data, partial=True, context={"request": request})
        
        if user_serializer.is_valid():
            user_serializer.save()
            return Response(user_serializer.data, status=status.HTTP_200_OK)
        
        return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  • UserSerializer로 넘어가서 작업을 진행하도록 하겠다.
  • 먼저 instance와 validated_data가 잘 들어는지 확인해보도록 하겠다.
def update(self, instance, validated_data):
        # instance에는 입력된 object가 담긴다.
        print(f'instance : {instance}')
        print(f'validated_data : {validated_data}')
        
        # for key, value in validated_data.items():
        #     if key == "password":
        #         instance.set_password(value)
        #         continue
            
        #     setattr(instance, key, value)
        # instance.save()
        return instance
  • 아래는 프린트의 결과이고, 작성이 잘된 것을 알 수 있다.
    • 변경이 되어 있지 않던 user18의 이메일 주소를 변경해보았다.
  • 아래와 같이 주석을 풀고 print를 해보면 user profile의 내용으로 인해 에러가 나기 때문에 변경 부분에서 user profile은 빼고 진행하도록 하겠다. 저장은 나중에 진행한다.
 def update(self, instance, validated_data):
        # instance에는 입력된 object가 담긴다.
        for key, value in validated_data.items():
            print(key, value)
            if key == "password":
                instance.set_password(value)
                continue
            
            setattr(instance, key, value)
        # instance.save()
        return instance
  • print의 결과이다. key와 value 값이 잘 찍힌 것을 볼 수 있다.
  • setattr(instance, key, value)의 의미는 instance.username = "user18"과 같은 의미이다. 만약 뒤의 구문으로 코드를 작성하게 되면 instance에는 username이라는 속성이 없기 때문에 keyerror가 발생하게 될 텐데, 앞의 구문으로 작성하게 되면 알아서 해당 값을 넣어주게 된다.
  • 내친김에 userprofile의 내용도 넣어보도록 하겠다.
def update(self, instance, validated_data):
        # instance에는 입력된 object가 담긴다.
        user_profile = validated_data.pop("userprofile")
        get_hobbys = user_profile.pop("get_hobbys", [])
        
        for key, value in validated_data.items():
            if key == "password":
                instance.set_password(value)
                continue
            setattr(instance, key, value)
        instance.save()
        
        user_profile_obj = instance.userprofile
        for key, value in user_profile.items():
            setattr(user_profile_obj, key, value)
        user_profile_obj.save()
        user_profile_obj.hobby.set(get_hobbys)
        
        return instance
  • 이렇게 하고 포스트맨에 가서 수정을 해보도록 하겠다. 테스트를 하면서 user18의 취미를 모두 날려버린 상태였고, 다시 취미를 추가해보도록 하겠다.

극한의 압축

😠 회원가입 부분

  • 분기문을 작성하지 않고도 코드를 작성할 수 있다.
    • raise_exception=True를 사용
profile
https://github.com/nikevapormax
post-custom-banner

0개의 댓글