スポンサーリンク

【その3】チュートリアルから学ぶDjangoRESTframework

【その3】チュートリアルから学ぶDjangoRESTframework
前回はserializationについて「DjangoRESTframework」の使い方を学んでいきました。
【その2】チュートリアルから学ぶDjangoRESTframework
前回はクイックスタートで簡単に「DjangoRESTframework」の使い方を学んでいきました。今回は、公式ドキュメントのチュートリアルに沿って「Tutorial 1: Serialization」について解説していきたいと思...

今回は、公式ドキュメントのチュートリアルに沿って「 Tutorial 2: Requests and Responses」について解説していきたいと思います。
チュートリアルを簡単に日本語訳したものになりますので、英語に抵抗がない方は公式ドキュメントを参考にすることをお勧めします。
本記事の目的は「DjangoRESTframework」の入門編として読み物的な感じで読んでいただけますと幸いです。

オブジェクトをリクエストする

前回設定したオブジェクトをリクエストしていきます。
HTTPリクエストのメソッドが分からない問い方はこの辺の記事が分かりやすいと思います。

APIViewのラッピング

RESTframeworkではAPIViewに使用できるラッパーが2つあります。
(ラッパーとはすでにあるモジュールに対して、そのまま使うより利便性を上げるために用意されたものくらいの認識でとりあえず大丈夫です。もっと知りたい方の為にさんこうURL貼っておきます。)
ラッパー(Wrapper)って何?《サンプルケース付き》 - Qiita
ITの世界では,ときたま「ラッパー」とか「ラップする」とかいう言い方をすることがあります。新人さんに質問されたことがあるのでちょっと説明を書いてみます。^1概説例えていうならば 「正露●糖衣A」…

用意されているラッパー
  • @api_view関数ベースのビューを操作するためのデコレータ
  • APIViewクラスベースのビューを操作するためのクラス
    これらのラッパーは不適切なメソッドでのリクエストを弾いたり、不正な形式でのリクエストなどの例外を処理してくれるので、自分で書いていくより少ないコードで実装することができます。

ラッパーを使ってリファクタリング

前回実装したsnippets\views.pyを先程紹介したラッパーを使ってリファクタリングしていきます。
(リファクタリングとは、より良いコードにするためにコードを改善・修正する事)
変更前

from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

@csrf_exempt
def snippet_list(request):
    """
    スニペットの一覧の取得と、新規スニペットの追加
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

@csrf_exempt
def snippet_detail(request, pk):
    """
    スニペットの詳細取得、更新、削除
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

変更後

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request):
    """
    スニペットの一覧の取得と、新規スニペットの追加
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

この状態では、リクエストやレスポンスを特定のコンテンツタイプに明示的に結び付いていません。
このままでもリクエストを処理できますが、要求されたレスポンスタイプに応答できるように変更していきましょう。

URLにフォーマットサフィックスを追加

レスポンスが特定のコンテンツタイプに固定されなくなったので、APIエンドポイントにフォーマットを指定できるように変更していきます。
変更点はsnippets/views.pyの下記部分です。

def snippet_list(request, format=None):

と、

def snippet_detail(request, pk, format=None):

snippets/urls.pyも変更していきます。

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

ここまで設定できましたら、実際にリクエストを送信するとどのようなレスポンスが返ってくるか見てみましょう。

レスポンスを確認

実際にフォーマットを指定してレスポンスを確認してみましょう。

# リクエスト(JSON)
http http://127.0.0.1:8000/snippets/ Accept:text/json

# レスポンス
HTTP/1.1 406 Not Acceptable
Allow: GET, OPTIONS, POST
Content-Length: 57
Content-Type: application/json
Date: Mon, 23 Aug 2021 05:32:04 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.10
Vary: Accept
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "detail": "Could not satisfy the request Accept 
header."
}

# リクエスト(HTML)
http http://127.0.0.1:8000/snippets/ Accept:text/json

# レスポンス
HTTP/1.1 200 OK
Allow: GET, OPTIONS, POST
Content-Length: 12287
Content-Type: text/html; charset=utf-8
Date: Mon, 23 Aug 2021 05:33:18 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.10
Set-Cookie: csrftoken=KKAeT7s6HIjjEmLMjKcrfbDXrH1ZfmQPHqdS6vHTgyDDfNKCIc3HXub0pDAiK2ib; expires=Mon, 22 
Aug 2022 05:33:18 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

<!DOCTYPE html>
<html>
  <head>



        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta name="robots" content="NONE,NOARCHIVE" />


      <title>Snippet List – Django REST framework</title>



          <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/bootstrap.min.css"/>  
          <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/bootstrap-tweaks.css"/>


        <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/prettify.css"/>
        <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/default.css"/>
        <style>pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }  
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; 
}
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }       
.highlight { background: #f8f8f8; }
.highlight .c { color: #008800; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #AA22FF; font-weight: bold } 
/* Keyword */
.highlight .o { color: #666666 } /* Operator */     
.highlight .ch { color: #008800; font-style: italic 
} /* Comment.Hashbang */
.highlight .cm { color: #008800; font-style: italic 
} /* Comment.Multiline */
.highlight .cp { color: #008800 } /* Comment.Preproc */
.highlight .cpf { color: #008800; font-style: italic } /* Comment.PreprocFile */
.highlight .c1 { color: #008800; font-style: italic 
} /* Comment.Single */
.highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output 
*/
.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #AA22FF; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #AA22FF } /* Keyword.Pseudo 
*/
.highlight .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #00BB00; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #666666 } /* Literal.Number */
.highlight .s { color: #BB4444 } /* Literal.String */
.highlight .na { color: #BB4444 } /* Name.Attribute 
*/
.highlight .nb { color: #AA22FF } /* Name.Builtin */.highlight .nc { color: #0000FF } /* Name.Class */  
.highlight .no { color: #880000 } /* Name.Constant */
.highlight .nd { color: #AA22FF } /* Name.Decorator 
*/
.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #00A000 } /* Name.Function */
.highlight .nl { color: #A0A000 } /* Name.Label */  
.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #B8860B } /* Name.Variable */
.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace 
*/
.highlight .mb { color: #666666 } /* Literal.Number.Bin */
.highlight .mf { color: #666666 } /* Literal.Number.Float */
.highlight .mh { color: #666666 } /* Literal.Number.Hex */
.highlight .mi { color: #666666 } /* Literal.Number.Integer */
.highlight .mo { color: #666666 } /* Literal.Number.Oct */
.highlight .sa { color: #BB4444 } /* Literal.String.Affix */
.highlight .sb { color: #BB4444 } /* Literal.String.Backtick */
.highlight .sc { color: #BB4444 } /* Literal.String.Char */
.highlight .dl { color: #BB4444 } /* Literal.String.Delimiter */
.highlight .sd { color: #BB4444; font-style: italic 
} /* Literal.String.Doc */
.highlight .s2 { color: #BB4444 } /* Literal.String.Double */
.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #BB4444 } /* Literal.String.Heredoc */
.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
.highlight .sx { color: #008000 } /* Literal.String.Other */
.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
.highlight .s1 { color: #BB4444 } /* Literal.String.Single */
.highlight .ss { color: #B8860B } /* Literal.String.Symbol */
.highlight .bp { color: #AA22FF } /* Name.Builtin.Pseudo */
.highlight .fm { color: #00A000 } /* Name.Function.Magic */
.highlight .vc { color: #B8860B } /* Name.Variable.Class */
.highlight .vg { color: #B8860B } /* Name.Variable.Global */
.highlight .vi { color: #B8860B } /* Name.Variable.Instance */
.highlight .vm { color: #B8860B } /* Name.Variable.Magic */
.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */</style>



  </head>


  <body class="">

    <div class="wrapper">

        <div class="navbar navbar-static-top navbar-inverse"
             role="navigation" aria-label="navbar"> 
          <div class="container">
            <span>

                <a class='navbar-brand' rel="nofollow" href='https://www.django-rest-framework.org/'>
                    Django REST framework
                </a>

            </span>
            <ul class="nav navbar-nav pull-right">  





            </ul>
          </div>
        </div>


      <div class="container">

          <ul class="breadcrumb">


                <li class="active"><a href="/snippets/">Snippet List</a></li>


          </ul>


        <!-- Content -->
        <div id="content" role="main" aria-label="content">


          <div class="region"  aria-label="request form">



            <form id="get-form" class="pull-right"> 
              <fieldset>

                  <div class="btn-group format-selection">
                    <a class="btn btn-primary js-tooltip" href="/snippets/" rel="nofollow" title="Make a GET request on the Snippet List resource">GET</a>  

                    <button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request">
                      <span class="caret"></span>   
                    </button>
                    <ul class="dropdown-menu">      

                        <li>
                          <a class="js-tooltip format-option" href="/snippets/?format=json" rel="nofollow" title="Make a GET request on the Snippet List resource with the format set to `json`">json</a>       
                        </li>

                        <li>
                          <a class="js-tooltip format-option" href="/snippets/?format=api" rel="nofollow" title="Make a GET request on the Snippet List resource with the format set to `api`">api</a>
                        </li>

                    </ul>
                  </div>

              </fieldset>
            </form>



            <form class="button-form" action="/snippets/" data-method="OPTIONS">
              <button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the Snippet List resource">OPTIONS</button>
            </form>









          </div>

            <div class="content-main" role="main"  aria-label="main content">
              <div class="page-header">
                <h1>Snippet List</h1>
              </div>
              <div style="float:left">

                  <p>スニペットの一覧の取得と、新規 
スニペットの追加</p>

              </div>



              <div class="request-info" style="clear: both" aria-label="request info">
                <pre class="prettyprint"><b>GET</b> 
/snippets/</pre>
              </div>

              <div class="response-info" aria-label="response info">
                <pre class="prettyprint"><span class="meta nocode"><b>HTTP 200 OK</b>
<b>Allow:</b> <span class="lit">GET, OPTIONS, POST</span>
<b>Content-Type:</b> <span class="lit">application/json</span>
<b>Vary:</b> <span class="lit">Accept</span>        

</span>[
    {
        &quot;id&quot;: 1,
        &quot;title&quot;: &quot;&quot;,
        &quot;code&quot;: &quot;foo = \&quot;bar\&quot;\n&quot;,
        &quot;linenos&quot;: false,
        &quot;language&quot;: &quot;python&quot;,
        &quot;style&quot;: &quot;friendly&quot;     
    },
    {
        &quot;id&quot;: 2,
        &quot;title&quot;: &quot;&quot;,
        &quot;code&quot;: &quot;print(\&quot;hello, 
world\&quot;)\n&quot;,
        &quot;linenos&quot;: false,
        &quot;language&quot;: &quot;python&quot;,   
        &quot;style&quot;: &quot;friendly&quot;     
    }
]</pre>
              </div>
            </div>



                <div >


                  <div class="well tab-content">    


                    <div  id="post-generic-content-form">

                        <form action="/snippets/" method="POST" class="form-horizontal">
                          <fieldset>



  <div class="form-group">
    <label for="id__content_type" class="col-sm-2 control-label">Media type:</label>
    <div class="col-sm-10">
      <select name="_content_type" data-override="content-type" id="id__content_type" class="form-control">
  <option value="application/json" selected>application/json</option>

  <option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>

  <option value="multipart/form-data">multipart/form-data</option>

</select>
      <span class="help-block"></span>
    </div>
  </div>

  <div class="form-group">
    <label for="id__content" class="col-sm-2 control-label">Content:</label>
    <div class="col-sm-10">
      <textarea name="_content" cols="40" rows="10" 
data-override="content" id="id__content" class="form-control">
</textarea>
      <span class="help-block"></span>
    </div>
  </div>


                            <div class="form-actions">
                              <button class="btn btn-primary js-tooltip" title="Make a POST request on the Snippet List resource">POST</button>
                            </div>
                          </fieldset>
                        </form>

                    </div>
                  </div>
                </div>





        </div><!-- /.content -->
      </div><!-- /.container -->
    </div><!-- ./wrapper -->




      <script>
        window.drf = {
          csrfHeaderName: "X-CSRFTOKEN",
          csrfToken: "SYdVr3ZVY3riSD6ow0nmARLm5Rx1PpZyPEQzEreIxTLCt45eVseCiajp3N6kk5rU"
        };
      </script>
      <script src="/static/rest_framework/js/jquery-3.5.1.min.js"></script>
      <script src="/static/rest_framework/js/ajax-form.js"></script>
      <script src="/static/rest_framework/js/csrf.js"></script>
      <script src="/static/rest_framework/js/bootstrap.min.js"></script>
      <script src="/static/rest_framework/js/prettify-min.js"></script>
      <script src="/static/rest_framework/js/default.js"></script>
      <script>
        $(document).ready(function() {
          $('form').ajaxForm();
        });
      </script>


  </body>

</html>

こんな感じでレスポンスが返ってきていればOKです。
APIサーバとして構築する場合は、基本JSON形式になると思いますので、用途に応じて設定していきましょう。

まとめ

今回はフォーマットを指定してリクエストを処理する方法を解説していきました。
次回はクラスViewで実装する方法を解説していきます。
不明点等ございましたら、コメントかTwitterのDMでご質問ください。

コメント