seminar-2020 icon indicating copy to clipboard operation
seminar-2020 copied to clipboard

SerializerMethodField()의 get_fieldname()

Open tictactoeid opened this issue 4 years ago • 22 comments

nested serializer를 구현하려면 SeminarSerializer 안에 instructors = serializers.SerializerMethodField()를 선언하고 get_instructors()를 선언하여 InstructorProfileSerializer를 return하도록 하면 된다는 것을 알게 되었습니다.

그러나, 이때

def get_instructors(self, seminar):    
    return InstructorProfileSerializer(profile, many=True).data

에서, get_instructors()의 input, 즉 seminar에는 seminar model이 와야 하는 것 같은데, 실제로 request를 날려보면 OrderedDict가 옵니다.

여기서 OrderedDict는 postman으로 날린 request body의 key, value만을 포함하고, seminar object에 대한 정보를 담고 있지 않아서 InstructorProfileSerializer에 접근할 수 없게 됩니다. 해결책을 알고 계신 분 계실까요?

#195 를 참고하여, https://github.com/wafflestudio/rookies/issues/195#issuecomment-699434218 와 같은 방법을 의도했습니다.

tictactoeid avatar Sep 29 '20 04:09 tictactoeid

리퀘스트를 날리셨다는게 무슨뜻일까요?

그리고 get_instructor 함수는 우리가 직접 실행하는 것이 아닌 serializer가 실행하는 method이기 때문에, 우리가 어떤 인풋을 넣어주어야 하는지는 상관 없습니다. 하지만 그전에 serializer에서 사용할 instance(이경우에는 seminar)가 무엇인지 선언해주어야 합니다.

어떤 api를 구현하시려고 하는 상황인지는 모르겠지만, create할 때는 create() method에서 반환하는 instance를, 그 외에는 일반적으로 serializer를 호출할때 선언해주는 seminar가 그 대상이 될겁니다.

다시말해, create() method를 잘 구현하셨거나 serializer 호출을 잘 하셨다면 input은 신경쓰실 필요가 없습니다. 그리고 get_instructor method 안에서 seminar을 갖다 쓰시면 됩니다.

just-dodo avatar Sep 29 '20 04:09 just-dodo

뭘 구현하고 있는지 안 썼네요.. POST http://127.0.0.1:8000/api/v1/seminar/ (seminar 생성 API) 구현 중입니다. 리퀘스트를 날렸다는 건 postman으로 POST http://127.0.0.1:8000/api/v1/seminar/에 값을 날렸다는 것입니다. serializer에서 사용할 instance의 경우는

    class Meta:
        model = Seminar

와 같이 선언해 주었는데요, 답변해주신 바와 같이 get_instructor()에서 그냥 seminar를 갖다 쓰면 될 줄 알았는데 seminar 자리에 seminar model이 오지 않고 OrderedDict가 오는 상황입니다.

tictactoeid avatar Sep 29 '20 04:09 tictactoeid

create() method 보여주실 수 있나요?

just-dodo avatar Sep 29 '20 04:09 just-dodo

serializers.py의 create() 입니다.

    def create(self, validated_data):
        return Seminar(validated_data)

사실 views.py에서 seminar object를 생성할 때

serializer.save(**serializer.validated_data)

의 사용을 의도하였으나

serializer.data에 접근 시 TypeError: int() argument must be a string, a bytes-like object or a number, not 'dict' 가 발생해서(#210 ) 우선 views.py에서

seminar = Seminar.objects.create(**serializer.validated_data)

로 저장하고 있습니다.

tictactoeid avatar Sep 29 '20 04:09 tictactoeid

일단 Seminar(validated_data)는 생성만 하고 저장은 해주지 않아, 저장을 해주셔야 할 것 같습니다. save()를 찍든, 원래 선언을 create로 바꾸든지 해야할 것 같습니다, 그리고 저렇게 dict 형태로 args들을 넣어주어도 되나요? 신기하네요. 제가 해보지 않은 방식이라 유효한지 잘 모르겠습니다.

just-dodo avatar Sep 29 '20 04:09 just-dodo

dict 형태로 넣어주는것은 문제가 없는 것 같습니다. 그리고

def create(self, validated_data):
        seminar = Seminar(validated_data)
        seminar.save()
        return seminar

으로 바꾸었고 다시 시도해보고 있습니다.

tictactoeid avatar Sep 29 '20 04:09 tictactoeid

저 중간중간에 print(seminar) 찍어봐주시겠어요? save 직후에요

just-dodo avatar Sep 29 '20 05:09 just-dodo

create()에서는 아예 print() 전에 오류가 나네요

수정) 이건 제가 views.py에서 serializer.save()로 하지 않고 seminar = Seminar.objects.create()로 생성하기 때문인데 이렇게 하는 이유는 UserSeminar 생성을 위해 생성된 seminar object id가 필요하기 때문입니다.

serializer.save()로 하면 id에 접근할 수가 없네요...

tictactoeid avatar Sep 29 '20 05:09 tictactoeid

아 잠시만요 오류 원인은 다른 코드에 있었고 그거 수정했더니 일단 201 Created가 뜨기는 합니다.

tictactoeid avatar Sep 29 '20 05:09 tictactoeid

    def get_instructors(self, seminar):
        userseminar = UserSeminar.objects.filter(seminar = seminar)
        user = userseminar.user
        profile = user.instructor
        return InstructorProfileSerializer(profile, many=True).data

이거 해결방법이 없을까요... 여전히 이 코드에서 seminar에 Seminar Object가 오지 않고 Postman에서 보낸 Response body를 담은 OrderedDict가 들어와 userseminar 정보에 접근할 수 없습니다.

tictactoeid avatar Sep 29 '20 08:09 tictactoeid

  1. views.py 에서도 serializer.save()가 반환하는 값으로 instance를 불러올 수 있습니다.
  2. 저 validated_data를 넣어서 db에 정상적으로 생성이 되었나요?

just-dodo avatar Sep 29 '20 08:09 just-dodo

  1. views.py에서 instance를 불러오는 데는 문제가 없습니다만 nested serializer 구조를 만들기 위해 이 문제를 해결할 필요가 있습니다.

  2. mysql에서 확인한 결과 db에는 정상적으로 생성된 것 같습니다.

tictactoeid avatar Sep 29 '20 08:09 tictactoeid

추가로, 사실 serializer.save()는 이용하지 못하고 있습니다. 아마 같은 error가 발생했던 것으로 기억합니다.

seminar = Seminar.objects.create(**serializer.validated_data) serializer.create(seminar)

이런 식으로, create()를 직접 호출하고 있습니다.

tictactoeid avatar Sep 29 '20 08:09 tictactoeid

veiws.py의 create 함수를 전부 보여주시겠어요?

just-dodo avatar Sep 29 '20 08:09 just-dodo

    def create(self, request):
        user = request.user
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        if not hasattr(user, 'instructor'):
            return Response({"error": "Only instructors can use this method."}, status=status.HTTP_403_FORBIDDEN)

        seminar = Seminar.objects.create(**serializer.validated_data)

        userseminar = UserSeminar.objects.create(user=user, seminar=seminar, joined_at = datetime.datetime.now())

        serializer.create(seminar)
        return Response(serializer.data, status = status.HTTP_201_CREATED)

tictactoeid avatar Sep 29 '20 09:09 tictactoeid

  1. seminar = Seminar.objects.create(**serializer.validated_data)seminar = serializer.save() 로 변경, 마지막에서 2번째 줄의 serializer.create(seminar)은 삭제

  2. serializer.create(seminar)serializer.create(**serializer.validated_data)로 변경

두 방법 시도해보시겠어요? 1번 방법을 더 권장드리긴 합니다.

just-dodo avatar Sep 29 '20 09:09 just-dodo

오류가 왜 일어나는지 보기 전에 결과부터 먼저 알려드리겠습니다.

  1. AttributeError: 'dict' object has no attribute 'save'

  2. TypeError: create() got an unexpected keyword argument 'name'

tictactoeid avatar Sep 29 '20 09:09 tictactoeid

Internal Server Error: /api/v1/seminar/
Traceback (most recent call last):
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/viewsets.py", line 114, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
    raise exc
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "/data/onedrive/Git/waffle-rookies-18.5-backend-2/waffle_backend/seminar/views.py", line 40, in create
    seminar = serializer.save()
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/serializers.py", line 212, in save
    self.instance = self.create(validated_data)
  File "/data/onedrive/Git/waffle-rookies-18.5-backend-2/waffle_backend/seminar/serializers.py", line 238, in create
    seminar.save()
AttributeError: 'dict' object has no attribute 'save'
[29/Sep/2020 09:09:29] "POST /api/v1/seminar/ HTTP/1.1" 500 106202
Internal Server Error: /api/v1/seminar/
Traceback (most recent call last):
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/viewsets.py", line 114, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
    raise exc
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "/data/onedrive/Git/waffle-rookies-18.5-backend-2/waffle_backend/seminar/views.py", line 45, in create
    serializer.create(**serializer.validated_data)
TypeError: create() got an unexpected keyword argument 'name'

tictactoeid avatar Sep 29 '20 09:09 tictactoeid

1번은 serializer을 건드려야 할 것 같고, 2번에서 **을 빼보시겠어요?

just-dodo avatar Sep 29 '20 09:09 just-dodo

잠시만요 테스트하면서 만든 seminar가 너무 많아서 db 초기화중입니다.

tictactoeid avatar Sep 29 '20 09:09 tictactoeid

Internal Server Error: /api/v1/seminar/
Traceback (most recent call last):
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/viewsets.py", line 114, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
    raise exc
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/rest_framework/views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "/data/onedrive/Git/waffle-rookies-18.5-backend-2/waffle_backend/seminar/views.py", line 45, in create
    serializer.create(serializer.validated_data)
  File "/data/onedrive/Git/waffle-rookies-18.5-backend-2/waffle_backend/seminar/serializers.py", line 233, in create
    seminar = Seminar.objects.create(validated_data)
  File "/home/jungjimin/anaconda3/envs/waffleBackend/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
TypeError: create() takes 1 positional argument but 2 were given
[29/Sep/2020 09:29:36] "POST /api/v1/seminar/ HTTP/1.1" 500 106025

tictactoeid avatar Sep 29 '20 09:09 tictactoeid

어....아마 해결된 것 같습니다. 1번으로 했고, serializers.py의 create()를

    def create(self, validated_data):
        seminar = Seminar.objects.create(**validated_data)
        return seminar

이렇게 바꿨습니다.

현재는 POST http://127.0.0.1:8000/api/v1/seminar/, GET http://127.0.0.1:8000/api/v1/seminar/, PUT http://127.0.0.1:8000/api/v1/seminar/pk/ 모두 잘 작동하는 것 같습니다.

tictactoeid avatar Sep 29 '20 10:09 tictactoeid