DjangoBrothers BLOG ✍️

2021/02/14

このエントリーをはてなブックマークに追加
Django DjangoRESTFramework テスト

DRF ModelViewSetのDELETEが実行されない原因

DjangoRESTFrameworkのModelViewSetでAPIを作った際、DELETE処理が想定通りに動かない現象にでくわしたときのメモです。

バージョン

  • Django 3.1.5
  • djangorestframework 3.12.2
  • Python 3.8.4

スラッシュつけ忘れによるエラー

ModelViewSetを使ったViewで、ちゃんとデータが削除されるかのテストを書いていたのですが、想定通りに動かずはまりました。(ModelViewSetは、DefaultRouterを使ってURLの設定をしていました。)

最初、以下のようにテストを書きました。

test_api.py


class TestMyModelAPIViewDelete(APITestCase):

    def test_delete_success(self):
        """
        指定したIDのデータが削除されることを確認する。
        """

        user = UserFactory()
        # 削除対象のデータ作成
        my_model = MyModelFactory(user=user)

        # 削除API実行
        self.client.force_login(user)
        res = self.client.delete(f"/api/my_models/{my_model.id}", follow=True)

        # assert
        self.assertEqual(res.status_code, 204)
        self.assertIsNone(MyModel.objects.filter(id=my_model.id).first())

AssertionError: 200 != 204 のエラーが発生しました。

また、MyModel.objects.filter(id=my_model.id).first() でデータが取得できたので、データの削除もされていないようでした。

試しにModelViewSet内にdestroy関数を書いてその中にbreakpointを置いてみましたが、処理は止まらずdestroy関数も実行されていない様子。。

CURLやAPIのテストツールでもf"/api/my_models/{my_model.id}に対してDELETEリクエストを送ってみると、データは削除されず、代わりにDetailレスポンスが返ってきました。

一方、reverse関数でURLを生成してテストしてみるとdestroy関数が実行されていて、想定通りデータも削除されました。

reverseでURL生成した場合

        # 想定通りに削除処理される
        res = self.client.delete(reverse('my_model-detail', kwargs={'pk': my_model.pk}))

想定通りに動かなかった原因としては、f"/api/my_models/{my_model.id}"の最後にスラッシュをつけていないことにありました。以下のようにすると想定通りの動きになりました。

スラッシュをつけたURLでテスト


class TestMyModelAPIViewDelete(APITestCase):

    def test_delete_success(self):
        """
        指定したIDのデータが削除されることを確認する。
        """

        user = UserFactory()
        # 削除対象のデータ作成
        my_model = MyModelFactory(user=user)

        # 削除API実行
        self.client.force_login(user)
        res = self.client.delete(f"/api/my_models/{my_model.id}/")

        # assert
        self.assertEqual(res.status_code, 204)
        self.assertIsNone(MyModel.objects.filter(id=my_model.id).first())

APPEND_SLASHによるスラッシュの自動付加

Djangoではsettings.pyでAPPEND_SLASHの設定ができます。この値がデフォルトでTrueになっており、アクセスしたURLにスラッシュがついていない場合は自動的にスラッシュをつけたURLにリダイレクトしてくれます。

今回の場合、f"/api/my_models/{my_model.id}"にDELETEリクエストを送ってもf"/api/my_models/{my_model.id}/"にリダイレクトされてしまい、結果としてDetail情報を返されてしまっていました。

ちなみに、Routerに対してtrailing_slash=Falseを設定すればスラッシュのないURLで動くように設定できます。参考

urls.py

router = DefaultRouter(trailing_slash=False)