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)