[Django REST Framework] Lambdaで定期的に実行するバッチ削除処理APIの作成方法を見てみよう！


# 概要
Lambdaで定期的に実行するバッチ削除処理APIの作成方法
# 目的
掲題の方法を説明する

# 使用技術

```application/pyproject.toml
python = "^3.11.2"
Django = "^4.2.7"
djangorestframework = "^3.14.0"
pytest = "^7.4.3"
pytest-django = "^4.7.0"
```

# 早速コードを見ていきましょう！
今回の削除する対象はSMS送信履歴とします

```application/project/serializers.py
from rest_framework import serializers

class DeleteSmsSendHistorySerializer(serializers.Serializer):
    """SMS送信履歴削除用のシリアライザ"""

    token = serializers.CharField(max_length=64)

```

```application/project/views/sms.py
from datetime import timedelta

from django.db import DatabaseError, transaction
from django.utils import timezone
from rest_framework import mixins, status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

# マジックナンバーを防ぐため、定数化しています
from common.constant import (
    DELETE_THRESHOLD_DAYS,
    DELETED_RECORDS_COUNT_INDEX,
    DELETED_SMS_SEND_HISTORY_RECORDS_INDEX,
)
# settingsはpydanticを使用して環境変数情報を持っています
from common.environment_variable import settings
from project.models import SmsSendHistory
from project.serializers import DeleteSmsSendHistorySerializer


class SmsSendHistoryViewSet(viewsets.GenericViewSet):
    """SMS送信履歴削除用のAPI"""

    serializer_class = DeleteSmsSendHistorySerializer

    @action(methods=["post"], detail=False)
    def delete_old_record(self, request):
        """Lambdaで使用するSMS送信履歴削除API
        180日以前の古いSMS送信履歴を削除する

        Parameters:
            token（str）: Lambda実行用のトークン

        Returns:
            detail (str): メッセージ
        """
        serializer = self.get_serializer_class()(data=request.data)
        serializer.is_valid(raise_exception=True)
        if serializer.validated_data.get("token") != settings.LAMBDA_TOKEN:
            return Response(
                {"detail": "無効なトークンです"},
                status.HTTP_400_BAD_REQUEST,
            )

        delete_date = timezone.localtime() - timedelta(
            days=DELETE_THRESHOLD_DAYS
        )
        try:
            with transaction.atomic():
                deleted_records_info = SmsSendHistory.objects.filter(
                    created_at__lte=delete_date
                ).delete()
        except DatabaseError:
            return Response(
                {"detail": "SMS送信履歴の削除に失敗しました"},
                status.HTTP_500_INTERNAL_SERVER_ERROR,
            )
        deleted_count = 0
        if deleted_records_info[DELETED_RECORDS_COUNT_INDEX] != 0:
            deleted_count = deleted_records_info[
                DELETED_SMS_SEND_HISTORY_RECORDS_INDEX
            ]["project.SmsSendHistory"]

        return Response(
            {"detail": f"180日前のSMS送信履歴を{deleted_count}件削除しました"},
            status.HTTP_200_OK,
        )

```
 ## 重要なポイントを解説していきましょう！

期待しているLambdaからのリクエストかどうかをチェックしています
 ```python
if serializer.validated_data.get("token") != settings.LAMBDA_TOKEN:
        return Response(
            {"detail": "無効なトークンです"},
            status.HTTP_400_BAD_REQUEST,
        )
```

現在の時間から指定した過去の日付を取得します（今回は180日前）
```python
delete_date = timezone.localtime() - timedelta(
            days=DELETE_THRESHOLD_DAYS
        )
```

```
# 結果例
delete_date: 2023-09-24 15:52:39.155157+09:00
type(delete_date): <class 'datetime.datetime'>
```


objectsマネージャーを使用し、180日以前のSMS送信履歴を削除します
トランザクション処理に失敗した場合は500エラーを返すようにしています

```python
try:
    with transaction.atomic():
        deleted_records_info = SmsSendHistory.objects.filter(
            created_at__lte=delete_date
        ).delete()
except DatabaseError:
    return Response(
        {"detail": "SMS送信履歴の削除に失敗しました"},
        status.HTTP_500_INTERNAL_SERVER_ERROR,
    )
```

## 定数化している数字を変更するだけで、要件に沿ったバッチ削除処理APIが開発できます！ご参考までに🐰


# 参考
- https://www.django-rest-framework.org/api-guide/serializers/
- https://docs.djangoproject.com/en/5.0/topics/db/transactions/