장고(Django) 장고 쉘에서 Serializer 사용하기
만들었던 QuestionSerializer를 장고 쉘에서 사용해보자
장고 쉘에서 데이터를 업데이트할 때 시리얼라이저에서 업데이트 되었는지 쉽게 확인하기 위해
위의 블록 코드를 추가해줍니다. 학습을 위해 잠시 추가한 것이므로 실습이 끝나면 지워주세요.
Serialization (직렬화)
>>> from polls.models import Question
>>> from polls_api.serializers import QuestionSerializer
먼저 Question 모델과 우리가 만든 시리얼라이저 QuestionSerializer를 import합니다.
>>> q = Question.objects.first()
>>> q
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>
q 변수에 Question 모델의 첫번째 데이터를 담습니다. q는 '휴가를 어디서 보내고 싶으세요?'라는 질문이군요.
>>> serializer = QuestionSerializer(q)
>>> serializer
QuestionSerializer(<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>):
id = IntegerField(read_only=True)
question_text = CharField(max_length=200)
pub_date = DateTimeField(read_only=True)
>>> serializer.data
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2023-10-31T14:19:40Z'}
우리가 지난 시간 만든 QuestionSerializer에 모델 Question의 첫번째 데이터 객체를 담아 serializer라는 변수에 담았습니다. serializer의 데이터는 serializer.data로 확인할 수 있습니다.
serailizer.data의 값들을 마저 직렬화시키기 위해서는 JSONRenderer가 필요합니다.
>>> from rest_framework.renderers import JSONRenderer
>>> json_str = JSONRenderer().render(serializer.data)
>>> json_str
b'{"id":1,"question_text":"\xed\x9c\xb4\xea\xb0\x80\xeb\xa5\xbc \xec\x96\xb4\xeb\x94\x94\xec\x84\x9c \xeb\xb3\xb4\xeb\x82\xb4\xea\xb3\xa0 \xec\x8b\xb6\xec\x9c\xbc\xec\x84\xb8\xec\x9a\x94?","pub_date":"2023-10-31T14:19:40Z"}'
Django REST framework의 JSONRenderer().render() 메서드는 JSON 형식의 데이터를 문자열로 렌더링(rendering)하는 데 사용됩니다. 이 메서드는 JSON으로 직렬화된 데이터를 Python 문자열로 반환하며, 일반적으로 API 응답을 생성하는 데 활용됩니다.
왜 JSON 형태인 serializer.data를 굳이 문자열화 시킨거죠?
이렇게 의문을 가지시는 분들도 계실겁니다. 설명하자면, JSONRenderer().render(serializer.data)를 사용하여 serializer.data를 문자열화하면 API의 응답 형식을 특정하고 데이터를 클라이언트에게 적절한 형식으로 제공하는 데 도움을 줍니다. 이렇게 하면 데이터의 일관성과 호환성을 유지하고, 클라이언트가 데이터를 쉽게 처리할 수 있게 됩니다.
즉, JSONRenderer().render()로 마저 문자열(인코딩)화 시켜야 Serialization을 끝냈다고 할 수 있는 것입니다.
json_str을 보면 serializer.data와 똑같은 JSON 형태의 데이터로 보이겠지만 좀 더 자세히 살펴보면 json_str이 문자열 형태로 반환된 것을 알 수 있습니다. 그런데 자세히 보면 json_str에 값이 '\xed\x9c\...'인 부분이 보이네요. 이 이유는 question_text였던 '휴가를 어디서 보내고 싶으세요?' 내용을 인코딩했기 때문입니다.
Deserialization (역직렬화)
>>> json_str
b'{"id":1,"question_text":"\xed\x9c\xb4\xea\xb0\x80\xeb\xa5\xbc \xec\x96\xb4\xeb\x94\x94\xec\x84\x9c \xeb\xb3\xb4\xeb\x82\xb4\xea\xb3\xa0 \xec\x8b\xb6\xec\x9c\xbc\xec\x84\xb8\xec\x9a\x94?","pub_date":"2023-10-31T14:19:40Z"}'
이렇게 만든 json_str을 서버가 받았다면 이를 Deserialization하고 활용해 create와 update를 하게 됩니다.
현재 문자열인 json_str을 JSON화 시켜 모델에 로드까지하면 Deserialization이 완료됐다고 할 수 있습니다.
>>> import json
>>> data = json.loads(json_str)
>>> data
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2023-10-31T14:19:40Z'}
json_str을 json.loads()의 도움으로 문자열에서 JSON화 합니다.
>>> serializer = QuestionSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.validated_data
OrderedDict([('question_text', '휴가를 어디서 보내고 싶으세요?')])
QuestionSerializer에 우리가 JSON화 시킨 data를 주고 이렇게 만들어진 객체가 유효한 데이터인지 is_valid()로 확인했습니다.
validated_data에서 question_text만 뜨고 id와 pub_date가 뜨지 않는 이유는 우리가 지난 시간에
id와 pub_date는 read_only를 True로 했기 때문입니다.
>>> serializer.save()
이제 save()를 호출하면 save()는 이 serializer를 create할 것인지 update를 할 것인지 판단하게 됩니다. 이미 만들어진 instance가 있다면 update를, 없다면 create를 할 것입니다.
serializer = QuestionSerializer(data=data)
그런데 우리는 serializer를 만들면서 data만 주었고 instance는 주지 않았습니다. 그래서 create가 진행됩니다.
create가 진행되면 새로 만든 object를 return 해줍니다. 이를 new_question 변수로 받겠습니다.
>>> new_question = serializer.save()
>>> new_question
<Question: NEW!!! 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-11-04 07:12:32.016794+00:00>
>>> new_question.id
7
새로운 question이 생성되었고 id까지 자동 생성되었습니다.
>>> Question.objects.all()
<QuerySet [
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-10-31 14:19:40+00:00>,
<Question: 제목: 가장 좋아하는 디저트는?, 날짜: 2023-10-31 14:22:37+00:00>,
<Question: 제목: 커피 vs 녹차, 날짜: 2023-11-01 05:37:56.580508+00:00>,
<Question: 제목: abc???, 날짜: 2023-11-01 05:51:02.905096+00:00>,
<Question: 제목: 휴 가를 가실 계획인가요?, 날짜: 2023-11-02 03:36:12.245190+00:00>,
<Question: 제목: new question, 날짜: 2023-11-02 07:09:10.103398+00:00>,
<Question: NEW!!! 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-11-04 07:12:32.016794+00:00>]>
쿼리셋의 처음에 만든 Question과 방금 만든 마지막의 Questioin의 제목이 '휴가를 어디서 보내고 싶으세요?' 똑같습니다.
Update 진행하기
직렬화와 역직렬화를 학습하며 자연스럽게 create를 진행했습니다.
이제는 update를 진행해보겠습니다.
방금 만든 new_question의 제목을 변경해보겠습니다.
>>> new_question
<Question: NEW!!! 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-11-04 07:12:32.016794+00:00>
>>> data = {'question_text': '제목수정'}
원래는 API로 주어진 문자열 형태의 데이터를 JSON으로 parse해서 사용해야 하지만 바로 이전에 학습했으므로 이번에는 위와 같이 직접 JSON 형태로 작성해 진행하겠습니다.
>>> serializer = QuestionSerializer(new_question, data=data)
아까 create 실습에서는 data=data만 주었지만 이번에는 update 실습이고 update는 data뿐만 아니라 instance를 필요로 한다고 했었습니다. 이번에 serializer를 만들 때는 new_question을 instance로 줍니다.
>>> serializer.validated_data
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\Users\User\DjangoProjects\lib\site-packages\rest_framework\serializers.py", line 271, in validated_data
raise AssertionError(msg)
AssertionError: You must call `.is_valid()` before accessing `.validated_data`.
>>> serializer.save()
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\Users\User\DjangoProjects\lib\site-packages\rest_framework\serializers.py", line 180, in save
assert hasattr(self, '_errors'), (
AssertionError: You must call `.is_valid()` before calling `.save()`.
여기서 발생할 수 있는 에러 2가지를 살펴보고 가겠습니다.
먼저 serializer.validated_data 입력시 에러, 그 다음 serializer.save() 입력시 에러인데요.
둘 다 serializer.is_valid()를 먼저 실행하라는 에러입니다.
우리가 지난 시간 살펴본 Serialization의 장점들 중에서는 데이터 유효성 검사 (Data Validation)이 있다고 했었습니다. DB에 저장하거나 validated_data가 무엇인지 조회하려면 is_valid()로 검사를 마쳐야 합니다.
>>> serializer.save()
<Question: NEW!!! 제목: 제목수정[시리얼라이저에서 업데이트], 날짜: 2023-11-04 07:12:32.016794+00:00>
save()를 진행하면 잘 수정되었습니다.
Validation이 통과하지 않는 경우
>>> long_text = "abcd"*300
>>> data = {'question_text':long_text}
>>> serializer = QuestionSerializer(data=data)
>>> serializer.is_valid()
False
>>> serializer.validated_data
{}
>>> serializer.errors
{'question_text': [ErrorDetail(string='Ensure this field has no more than 200 characters.', code='max_length')]}
우리가 qeustion_text의 최대 길이를 200으로 주었는데 abcd*300 즉 길이 1200의 문자열을 quesetion_text로 is_valid() 유효성 검사를 진행하여 False가 출력된 것으 볼 수 있습니다. 유효한 데이터가 없으므로 serializer.validated_data는 비어있는 OrderedDict {}를 출력합니다.