【DRF】PUTメソッドをPOSTメソッドの代わりに使う

# Django REST Frameworkで`PUT`メソッドでレコード（インスタンス）を作成したい場合

Django REST Framework(DRF)に限らず、REST APIでは通常、HTTPプロトコルの`POST`メソッドでインスタンス（＝レコード）を作成して`PUT`メソッドで更新をするが、`PUT`で作成することはできないか調べてみたところ方法があったので備忘録として残しておく。

## 起きた問題とやりたいこと
レコードの更新（一度作成したレコードのデータをユーザーが編集した場合など）に`PUT`を使っていたが、DBに存在しないレコードを`PUT`で送ろうとしたところ、`404`エラーが返ってきた。通常は`POST`でレコードで作成するようにしているが、`PUT`でもレコードの作成ができるようにしたい。

もう一度、説明すると想定している挙動は次の通り。

[ こうなってほしい ]

- 通常は`POST`でレコードを新規作成して、`PUT`か`PATCH`（部分更新）でレコードの更新
- `PUT`で更新しようとしたレコードがDBに存在しない場合、**404エラーにならずに新規作成される**

POSTとかPUT、PATCHの違いがよくわからないという場合は[PUT か POST か PATCH か？](https://qiita.com/suin/items/d17bdfc8dba086d36115)を参照

## 解決法

公式チュートリアルの[PUT as create](https://www.django-rest-framework.org/api-guide/generic-views/#put-as-create)に解決法が載ってた。

> Prior to version 3.0 the REST framework mixins treated PUT as either an update or a create operation, depending on if the object already existed or not.

> Allowing PUT as create operations is problematic, as it necessarily exposes information about the existence or non-existence of objects. It's also not obvious that transparently allowing re-creating of previously deleted instances is necessarily a better default behavior than simply returning 404 responses.

> Both styles "PUT as 404" and "PUT as create" can be valid in different circumstances, but from version 3.0 onwards we now use 404 behavior as the default, due to it being simpler and more obvious.

> If you need to generic PUT-as-create behavior you may want to include something like this AllowPUTAsCreateMixin class as a mixin to your views.

要は、「DRFのバージョン3.0以上では`PUT`で存在しないレコードを更新しようとすると`404`ステータスが返ってくるようになってるけど、[AllowPUTAsCreateMixin](https://gist.github.com/tomchristie/a2ace4577eff2c603b1b)を使えば`PUT`でも`POST`のようにレコードを新規作成できるようになるよ」ということのよう。

上記リンク先のスニペットと同じものだが、必要なモジュールのインポート文が抜けていたので足したものを載せておく。これをインポートしてレコード更新に使っているViewクラスにMixinすれば`PUT`でもレコード作成ができるようになる。フロントエンドで条件分岐で`POST`メソッドと`PUT`メソッドを使い分けている場合などに、`PUT`のメソッドだけで作成と更新の両方を済ませられるので、コードが簡潔になりそう:v:


```python:put_as_create.py
from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.request import clone_request


class AllowPUTAsCreateMixin(object):
    """
    The following mixin class may be used in order to support PUT-as-create
    behavior for incoming requests.
    """

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object_or_none()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)

        if instance is None:
            lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
            lookup_value = self.kwargs[lookup_url_kwarg]
            extra_kwargs = {self.lookup_field: lookup_value}
            serializer.save(**extra_kwargs)
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        serializer.save()
        return Response(serializer.data)

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

    def get_object_or_none(self):
        try:
            return self.get_object()
        except Http404:
            if self.request.method == 'PUT':
                # For PUT-as-create operation, we need to ensure that we have
                # relevant permissions, as if this was a POST request.  This
                # will either raise a PermissionDenied exception, or simply
                # return None.
                self.check_permissions(clone_request(self.request, 'POST'))
            else:
                # PATCH requests where the object does not exist should still
                # return a 404 response.
                raise

```