[DRF] Issue Report - QueryDict is Immutable

Yungsang Hwangยท2022๋…„ 7์›” 13์ผ
0

Django-Rest-Framework

๋ชฉ๋ก ๋ณด๊ธฐ
8/15

Issue Report - Querydict is Immutable


๐Ÿšฉ ์ƒํ™ฉ

  • ๊ฒŒ์‹œ๊ธ€์„ ์ž‘์„ฑํ•˜๋Š” post ๋ฉ”์„œ๋“œ API๋ฅผ ์ž‘์„ฑํ•œ ๋’ค, POSTMAN์œผ๋กœ ํ…Œ์ŠคํŠธ๊นŒ์ง€ ๋งˆ์ณค๋‹ค.
  • ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์ ์šฉํ•ด๋ณด๊ธฐ ์œ„ํ•ด์„œ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋Š”๋ฐ, ํ…Œ์ŠคํŠธ์ฝ”๋“œ๊ฐ€ view์˜ ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๋„์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•œ๋‹ค
  • ์—ฌ์ „ํžˆ ์„œ๋ฒ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š”๋ฐ ์žˆ์–ด์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋Š” ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ์ƒํƒœ์ด๋‹ค
# webmaster.view
class NoticeView(APIView):
    def post(self, request):
        request.data['user'] = request.user.id
        notice_serializer = NoticeSerializer(data=request.data)
        if notice_serializer.is_valid():
            notice_serializer.save()
            return Response({"message" : "๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑ์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ถ!"}, status=status.HTTP_200_OK)
        else:
            print(notice_serializer.errors)
            return Response({"message" : "๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑ์— ์‹คํŒจํ–ˆ๋‹ค๋ถ..."}, status=status.HTTP_400_BAD_REQUEST)
# webmaster.test
class NoticeTest(APITestCase):
    @classmethod
    def setUpTestData(cls):
        cls.user_data = {'username' : 'heejeong', 'password': '1234'}
        cls.notice_data = {'title' : '์•ˆ๋…•' , 'content' : '๋ฐ˜๊ฐ‘์Šต๋‹ˆ๋‹ค'}
        cls.user = UserModel.objects.create_user('heejeong', '1234')

    def setUp(self):
        self.access_token = self.client.post(reverse('token_obtain_pair'), self.user_data).data['access']

    # ๊ณต์ง€์‚ฌํ•ญ ๋ชฉ๋ก ์กฐํšŒ API 
    def test_list_notice(self):
        response = self.client.get(reverse('webmaster:list_notice'), self.data)
        self.assertEqual(response.status_code,200)
        
    # ๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑํ•˜๊ธฐ API
    def test_post_notice(self):
        response = self.client.post(
            path = reverse("webmaster:notice"), 
            data = self.notice_data,
            HTTP_AUTHORIZATION = f"Bearer {self.access_token}"
            )
        self.assertEqual(response.status_code, 200)

๐Ÿšฉ์—๋Ÿฌ์ฝ”๋“œ

AttributeError: This QueryDict instance is immutable

๐ŸšฉํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

This QueryDict instance is immutable

  • ํ•ด๋‹น ์˜ค๋ฅ˜๋Š” QueryDict๊ฐ€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ์ž๋ฃŒํ˜•(immutable) ์ด๋ผ๊ณ  ํ•œ๋‹ค.
  • ์˜ค๋ฅ˜์ฝ”๋“œ๋ฅผ Stacktrace ํ•ด๋ณด๋‹ˆ, views.py์—์„œ request.data ๊ฐ€ QueryDict์˜€๋‹ค.
  • QueryDict๋Š” Immutable ์ž๋ฃŒํ˜•์ด๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋Š” ์ค‘๊ฐ„์— request.user๋ผ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ƒˆ๋กœ์šด ํ‚ค๋กœ ์‚ฝ์ž…ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค!
  • ํ•˜์ง€๋งŒ, ์„œ๋ฒ„์—์„œ๋Š” ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”๋ฐ? ๋„๋Œ€์ฒด ๋ฌด์—‡์ด ๋ฌธ์ œ์ธ๊ฑธ๊นŒโ€ฆ
  • ์ผ๋‹จ ํ…Œ์ŠคํŠธ์ฝ”๋“œ์˜ ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์Šคํƒ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ์˜ ๋‚ด์šฉ์ฒ˜๋Ÿผ mutable์˜ ์ž๋ฃŒํ˜•์œผ๋กœ ์ƒˆ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ๋กœ ํ–ˆ๋‹ค
def post(self, request):
				'''
				๊ธฐ์กด์ฝ”๋“œ
        request.data['user'] = request.user.id
        notice_serializer = NoticeSerializer(data=request.data)
				'''
				print(request.data) # ํ…Œ์ŠคํŠธ์ฝ”๋“œ ์ƒ์—์„œ QueryDict์ธ request.data์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ ํ™•์ธ
				request_data_copy = request.data.copy() # mutable ํ•œ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์นดํ”ผํ•˜๋Š” ๋ฉ”์„œ๋“œ
				request_data_copy['user'] = request.user.id
				notice_serializer = NoticeSerializer(data=request_data_copy)
        ...
				์ƒ๋žต

๐Ÿšฉํ•ด๊ฒฐ

๐Ÿ“Œ ์„œ๋ฒ„์ฝ”๋“œ์™€ ํ…Œ์ŠคํŠธ์ฝ”๋“œ์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅด๋‹ค๋ฉด ์—๋Ÿฌ ์ผ€์ด์Šค๋ฅผ ์ฐพ์•„๋‚ธ ๊ฒƒ์ด๋‹ค!
  • ํ•˜๋‚˜์˜ ์ƒˆ๋กœ์šด ์—๋Ÿฌ ์ผ€์ด์Šค๊ฐ€ ์ƒ์„ฑ๋œ ๊ฒƒ์ด๋ผ๊ณ  ๋ด์•ผํ•จ
  • ํ—ค๋”์— Application JSON ์„ ๋„ฃ์–ด์ค„ ์ˆ˜๋„ ์žˆ๊ณ ,
  • QueryDict๋ฅผ mutable๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก copy ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉ
๐Ÿ“Œ immutable QueryDict๋ฅผ ๋ฐ›๋Š”๋‹ค๋ฉด, ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•˜์ž
  • is_valid()์—์„œ ํ•ด๋‹น Model์˜ ํ•„๋“œ๋“ค์„ ๊ฒ€์ฆํ•ด์ฃผ๋Š”๋ฐ
    • created_at, updated_at์€ ์ž๋™ ์ƒ์„ฑ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํŒจ์Šค ๋˜๊ณ ,
    • user๋Š” Foreignkey๋ผ ์ด๋ฏธ ๊ฒ€์ฆ์ด ๋‹ค ๋˜์–ด์žˆ๋Š” ํ•„๋“œ์ž„.
    • ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— titleํ•„๋“œ์™€ contentํ•„๋“œ ๋งŒ์„ ๊ฒ€์ฆํ•˜๊ฒŒ ๋œ๋‹ค.
  • title๊ณผ content๊ฐ€ is_valid()๋ฅผ ํ†ต๊ณผํ•˜๋ฉด ๊ทธ ๊ฐ’๋“ค๊ณผ user ๊ฐ’์„ ํ•จ๊ป˜ ์ €์žฅํ•ด์ค€๋‹ค
def post(self):
	notice_serializer = NoticeSerializer(data=request.data)
	if notice_serializer.is_valid():
		notice_serializer.save(user=self.request.user)
		return Response({"message" : "๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑ์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ถ!"}, status=status.HTTP_200_OK)
  else:
      print(notice_serializer.errors)
      return Response({"message" : "๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑ์— ์‹คํŒจํ–ˆ๋‹ค๋ถ..."}, status=status.HTTP_400_BAD_REQUEST)
profile
ํ•˜๋ฃจ์ข…์ผ ๋ชฝ์ƒ๊ฐ€

0๊ฐœ์˜ ๋Œ“๊ธ€