Django RESTful APIのテスト駆動開発

Django RESTful APIのテスト駆動開発

この投稿では、Djangoおよびhttp://www.django-rest-framework.org/[Django REST Framework]を使用してCRUDベースのRESTful APIを開発するプロセスを説明します。これは、Djangoモデルに基づいてRESTful APIを迅速に構築するために使用されます。

このアプリケーションは以下を使用します:

  • Python v3.6.0

  • Django v1.11.0

  • Django REST Framework v3.6.2

  • Postgres v9.6.1

  • Psycopg2 v2.7.1

*無料ボーナス:*リンク:[ここをクリックして「REST APIの例」ガイドのコピーをダウンロードしてください]、実践的な例を含むPython + REST APIの原理の実践的な紹介を入手してください。

_ 注: Django REST Frameworkの詳細なチュートリアルについては、3番目のhttps://realpython.com [Real Python]コースをご覧ください。 _

目的

このチュートリアルの終わりまでに、次のことができるようになります…

  1. RESTful APIの開発をブートストラップするためにDjango REST Frameworkを使用する利点について説明します

  2. シリアライザーを使用してモデルクエリセットを検証する

  3. Django REST Frameworkのブラウジング可能なAPI機能を評価して、APIのよりクリーンで十分に文書化されたバージョンを作成してください

  4. テスト駆動開発の実践

Django REST Frameworkを使用する理由

Django RESTフレームワーク(RESTフレームワーク)は、次のような慣用的なDjangoに適した、すぐに使用できる多くの強力な機能を提供します。

  1. Browsable API:APIを人間に優しいHTML出力で文書化し、美しいフォームのようなインターフェースを提供しますデータをリソースに送信し、標準のHTTPメソッドを使用してリソースから取得します。

  2. 認証サポート:RESTフレームワークは、ビューごとに設定できるアクセス許可と調整ポリシーに加えて、さまざまな認証プロトコルを豊富にサポートしています。 。

  3. Serializers:シリアライザーは、モデルクエリセット/インスタンスを検証し、それらをJSONに簡単にレンダリングできるネイティブPythonデータ型に変換するエレガントな方法です。 XML。

  4. Throttling:スロットルは、リクエストが承認されているかどうかを判断する方法であり、さまざまな権限と統合できます。 一般に、単一ユーザーからのAPIリクエストのレート制限に使用されます。

さらに、ドキュメントは読みやすく、例が豊富です。 APIエンドポイントとモデルの間に1対1の関係があるRESTful APIを構築している場合は、RESTフレームワークが最適です。

Djangoプロジェクトのセットアップ

virtualenvを作成してアクティブ化します。

$ mkdir django-puppy-store
$ cd django-puppy-store
$ python3.6 -m venv env
$ source env/bin/activate

Djangoをインストールし、新しいプロジェクトをセットアップします。

(env)$ pip install django==1.11.0
(env)$ django-admin startproject puppy_store

現在のプロジェクト構造は次のようになります。

└── puppy_store
    ├── manage.py
    └── puppy_store
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

DjangoアプリとRESTフレームワークのセットアップ

`+ puppies +`アプリとhttps://realpython.com/what-is-pip/[virtualenv内にRESTフレームワークをインストール]を作成することから始めます。

(env)$ cd puppy_store
(env)$ python manage.py startapp puppies
(env)$ pip install djangorestframework==3.6.2

次に、Djangoプロジェクトを設定して、RESTフレームワークを使用する必要があります。

まず、_puppy_store/puppy_store/settings.py_内の `+ INSTALLED_APPS `セクションに ` puppies `アプリと ` rest_framework +`を追加します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'puppies',
    'rest_framework'
]

次に、REST Frameworkのグローバルhttp://www.django-rest-framework.org/tutorial/quickstart/#settings[settings]を単一のディクショナリで、再び_settings.py_ファイルで定義します。

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [],
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

これにより、APIへの無制限のアクセスが許可され、すべてのリクエストに対してデフォルトのテスト形式がJSONに設定されます。

_ *注:*ローカル開発では無制限のアクセスで問題ありませんが、実稼働環境では特定のエンドポイントへのアクセスを制限する必要がある場合があります。 必ずこれを更新してください。 詳細については、http://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy [docs]をご覧ください。 _

現在のプロジェクト構造は次のようになります。

└── puppy_store
    ├── manage.py
    ├── puppies
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   └── __init__.py
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    └── puppy_store
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

データベースとモデルのセットアップ

Postgresデータベースをセットアップして、すべての移行を適用します。

_ :お好みのリレーショナルデータベースにPostgresを自由に交換してください! _

システム上で動作するPostgresサーバーを作成したら、Postgresインタラクティブシェルを開いてデータベースを作成します。

$ psql
# CREATE DATABASE puppy_store_drf;
CREATE DATABASE
# \q

psycopg2をインストールして、Pythonを介してPostgresサーバーと対話できるようにします。

(env)$ pip install psycopg2==2.7.1

_settings.py_のデータベース構成を更新し、適切なユーザー名とパスワードを追加します。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'puppy_store_drf',
        'USER': '<your-user>',
        'PASSWORD': '<your-password>',
        'HOST': '127.0.0.1',
        'PORT': '5432'
    }
}

次に、_django-puppy-store/puppy_store/puppies/models.py_にいくつかの基本的な属性を持つ子犬モデルを定義します。

from django.db import models


class Puppy(models.Model):
    """
    Puppy Model
    Defines the attributes of a puppy
    """
    name = models.CharField(max_length=255)
    age = models.IntegerField()
    breed = models.CharField(max_length=255)
    color = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def get_breed(self):
        return self.name + ' belongs to ' + self.breed + ' breed.'

    def __repr__(self):
        return self.name + ' is added.'

これでhttps://realpython.com/django-migrations-a-primer/[移行を適用]:

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

サニティーチェック

もう一度 `+ psql `に移動して、 ` puppies_puppy +`が作成されたことを確認します。

$ psql
# \c puppy_store_drf
You are now connected to database "puppy_store_drf".
puppy_store_drf=# \dt
                      List of relations
 Schema |            Name            | Type  |     Owner
--------+----------------------------+-------+----------------
 public | auth_group                 | table | michael.herman
 public | auth_group_permissions     | table | michael.herman
 public | auth_permission            | table | michael.herman
 public | auth_user                  | table | michael.herman
 public | auth_user_groups           | table | michael.herman
 public | auth_user_user_permissions | table | michael.herman
 public | django_admin_log           | table | michael.herman
 public | django_content_type        | table | michael.herman
 public | django_migrations          | table | michael.herman
 public | django_session             | table | michael.herman
 public | puppies_puppy              | table | michael.herman
(11 rows)

_ *注意:*実際のテーブルの詳細を見たい場合は、 `+ \ d + puppies_puppy +`を実行できます。 _

先に進む前に、Puppyモデルのhttps://realpython.com/python-testing/#unit-tests-vs-integration-tests [クイックユニットテスト]を作成しましょう。

「django-puppy-store/puppy_store/puppies」内の「tests」という新しいフォルダーにある_test_models.py_という新しいファイルに次のコードを追加します。

from django.test import TestCase
from ..models import Puppy


class PuppyTest(TestCase):
    """ Test module for Puppy model """

    def setUp(self):
        Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')

    def test_puppy_breed(self):
        puppy_casper = Puppy.objects.get(name='Casper')
        puppy_muffin = Puppy.objects.get(name='Muffin')
        self.assertEqual(
            puppy_casper.get_breed(), "Casper belongs to Bull Dog breed.")
        self.assertEqual(
            puppy_muffin.get_breed(), "Muffin belongs to Gradane breed.")

上記のテストでは、 `+ django.test.TestCase `の ` setUp()`メソッドを介して子犬テーブルにダミーエントリを追加し、 ` get_breed()+`メソッドが正しい文字列を返すことをアサートしました。

_ init 。py_ファイルを「tests」に追加し、「django-puppy-store/puppy_store/puppies」から_tests.py_ファイルを削除します。

最初のテストを実行しましょう:

(env)$ python manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.007s

OK
Destroying test database for alias 'default'...

すばらしいです! 最初の単体テストに合格しました!

シリアライザー

実際のAPIの作成に移る前に、モデルhttps://docs.djangoprojectを検証するPuppyモデルのhttp://www.django-rest-framework.org/api-guide/serializers/[serializer]を定義しましょう.com/en/1.10/ref/models/querysets/[querysets]で動作するPythonicデータ型を生成します。

次のスニペットを_django-puppy-store/puppy_store/puppies/serializers.py_に追加します。

from rest_framework import serializers
from .models import Puppy


class PuppySerializer(serializers.ModelSerializer):
    class Meta:
        model = Puppy
        fields = ('name', 'age', 'breed', 'color', 'created_at', 'updated_at')

上記のスニペットでは、子犬モデル用に `+ ModelSerializer +`を定義し、言及されたすべてのフィールドを検証しました。 要するに、APIエンドポイントとモデルの間に1対1の関係がある場合(おそらくRESTful APIを作成する場合はそうするべきです)、http://www.django-rest-frameworkを使用できます.org/api-guide/serializers/#modelserializer [ModelSerializer]でシリアライザーを作成します。

データベースを配置したら、RESTful APIの構築を開始できます…

RESTful構造

RESTful APIでは、エンドポイント(URL)はAPIの構造と、エンドユーザーがHTTPメソッド(GET、POST、PUT、DELETE)を使用してアプリケーションからデータにアクセスする方法を定義します。 エンドポイントは、どちらもリソースである_collections_および_elements_を中心に論理的に整理する必要があります。

この場合、1つのリソース `+ puppies `があるため、コレクションと要素にはそれぞれ `/puppies/`と `/puppies/<id> +`のURLを使用します。

Endpoint HTTP Method CRUD Method Result

puppies

GET

READ

Get all puppies

puppies/:id

GET

READ

Get a single puppy

puppies

POST

CREATE

Add a single puppy

puppies/:id

PUT

UPDATE

Update a single puppy

puppies/:id

DELETE

DELETE

Delete a single puppy

ルートとテスト(TDD)

完全なテスト駆動型のアプローチではなく、テストファーストのアプローチを採用し、次のプロセスを実行します。

  • 単体テストを追加し、失敗するだけのコード

  • 次に、コードを更新してテストに合格するようにします。

テストに合格したら、新しいテストの同じプロセスからやり直します。

新しいファイル_django-puppy-store/puppy_store/puppies/tests/test_views.py_を作成することから始めて、ビューのすべてのテストを保持し、アプリの新しいテストクライアントを作成します。

import json
from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse
from ..models import Puppy
from ..serializers import PuppySerializer


# initialize the APIClient app
client = Client()

すべてのAPIルートを開始する前に、最初に空の応答を返すすべてのビュー関数のスケルトンを作成し、_django-puppy-store/puppy_store/puppies/views.py_ファイル内の適切なURLでマッピングします。

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Puppy
from .serializers import PuppySerializer


@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        return Response({})
    # delete a single puppy
    elif request.method == 'DELETE':
        return Response({})
    # update details of a single puppy
    elif request.method == 'PUT':
        return Response({})


@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        return Response({})
    # insert a new record for a puppy
    elif request.method == 'POST':
        return Response({})

_django-puppy-store/puppy_store/puppies/urls.py_のビューと一致するように、それぞれのURLを作成します。

from django.conf.urls import url
from . import views


urlpatterns = [
    url(
        r'^api/v1/puppies/(?P<pk>[0-9]+)$',
        views.get_delete_update_puppy,
        name='get_delete_update_puppy'
    ),
    url(
        r'^api/v1/puppies/$',
        views.get_post_puppies,
        name='get_post_puppies'
    )
]

_django-puppy-store/puppy_store/puppy_store/urls.py_も更新します:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^', include('puppies.urls')),
    url(
        r'^api-auth/',
        include('rest_framework.urls', namespace='rest_framework')
    ),
    url(r'^admin/', admin.site.urls),
]

閲覧可能なAPI

これで、すべてのルートがビュー関数と結び付けられたので、REST FrameworkのBrowsable APIインターフェースを開き、すべてのURLが期待どおりに機能しているかどうかを確認しましょう。

最初に、開発サーバーを起動します。

(env)$ python manage.py runserver

ログインをバイパスするために、 `+ settings.py `ファイルの ` REST_FRAMEWORK `セクションのすべての属性をコメント化してください。 次に、「 http://localhost:8000/api/v1/puppies +」にアクセスします

API応答のインタラクティブなHTMLレイアウトが表示されます。 同様に、他のURLをテストし、すべてのURLが完全に正常に機能していることを確認できます。

各ルートの単体テストから始めましょう。

ルート

すべて取得

取得したレコードを確認するテストから始めます。

class GetAllPuppiesTest(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')
        Puppy.objects.create(
            name='Rambo', age=2, breed='Labrador', color='Black')
        Puppy.objects.create(
            name='Ricky', age=6, breed='Labrador', color='Brown')

    def test_get_all_puppies(self):
        # get API response
        response = client.get(reverse('get_post_puppies'))
        # get data from db
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

テストを実行してください。 次のようなエラーが表示されるはずです。

self.assertEqual(response.data, serializer.data)
AssertionError: {} != [OrderedDict([('name', 'Casper'), ('age',[687 chars])])]

ビューを更新して、テストに合格するようにします。

@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        return Response(serializer.data)
    # insert a new record for a puppy
    elif request.method == 'POST':
        return Response({})

ここでは、子犬のすべてのレコードを取得し、 `+ PuppySerializer +`を使用してそれぞれを検証します。

テストを実行して、すべてがパスすることを確認します。

Ran 2 tests in 0.072s

OK

GETシングル

1つの子犬を取得するには、2つのテストケースが必要です。

  1. 有効な子犬を取得する-例えば、子犬が存在する

  2. 無効な子犬を取得する-たとえば、子犬が存在しない

テストを追加します。

class GetSinglePuppyTest(TestCase):
    """ Test module for GET single puppy API """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')
        self.rambo = Puppy.objects.create(
            name='Rambo', age=2, breed='Labrador', color='Black')
        self.ricky = Puppy.objects.create(
            name='Ricky', age=6, breed='Labrador', color='Brown')

    def test_get_valid_single_puppy(self):
        response = client.get(
            reverse('get_delete_update_puppy', kwargs={'pk': self.rambo.pk}))
        puppy = Puppy.objects.get(pk=self.rambo.pk)
        serializer = PuppySerializer(puppy)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_get_invalid_single_puppy(self):
        response = client.get(
            reverse('get_delete_update_puppy', kwargs={'pk': 30}))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

テストを実行してください。 次のようなエラーが表示されるはずです。

self.assertEqual(response.data, serializer.data)
AssertionError: {} != {'name': 'Rambo', 'age': 2, 'breed': 'Labr[109 chars]26Z'}

ビューを更新します。

@api_view(['GET', 'UPDATE', 'DELETE'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

上記のスニペットでは、IDを使用して子犬を取得しています。 テストを実行して、すべてがパスすることを確認します。

POST

新しいレコードの挿入には、次の2つのケースも含まれます。

  1. 有効な子犬を挿入する

  2. 無効な子犬を挿入する

最初に、テストを作成します。

class CreateNewPuppyTest(TestCase):
    """ Test module for inserting a new puppy """

    def setUp(self):
        self.valid_payload = {
            'name': 'Muffin',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }
        self.invalid_payload = {
            'name': '',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }

    def test_create_valid_puppy(self):
        response = client.post(
            reverse('get_post_puppies'),
            data=json.dumps(self.valid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_create_invalid_puppy(self):
        response = client.post(
            reverse('get_post_puppies'),
            data=json.dumps(self.invalid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

テストを実行してください。 次の2つのエラーが表示されるはずです。

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 200 != 400

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 200 != 201

繰り返しますが、ビューを更新してテストに合格するようにします。

@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        return Response(serializer.data)
    # insert a new record for a puppy
    if request.method == 'POST':
        data = {
            'name': request.data.get('name'),
            'age': int(request.data.get('age')),
            'breed': request.data.get('breed'),
            'color': request.data.get('color')
        }
        serializer = PuppySerializer(data=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)

ここでは、データベースに挿入する前に、要求データをシリアル化および検証して新しいレコードを挿入しました。

テストを再度実行して、テストに合格することを確認します。

Browsable APIを使用してこれをテストすることもできます。 開発サーバーを再度起動し、http://localhost:8000/api/v1/puppies/に移動します。 次に、POSTフォーム内で、次を `+ application/json +`として送信します。

{
    "name": "Muffin",
    "age": 4,
    "breed": "Pamerion",
    "color": "White"
}

GET ALLとGet Singleの動作も確認してください。

PUT

レコードを更新するテストから始めます。 レコードの追加と同様に、有効な更新と無効な更新の両方を再度テストする必要があります。

class UpdateSinglePuppyTest(TestCase):
    """ Test module for updating an existing puppy record """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffy', age=1, breed='Gradane', color='Brown')
        self.valid_payload = {
            'name': 'Muffy',
            'age': 2,
            'breed': 'Labrador',
            'color': 'Black'
        }
        self.invalid_payload = {
            'name': '',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }

    def test_valid_update_puppy(self):
        response = client.put(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
            data=json.dumps(self.valid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

    def test_invalid_update_puppy(self):
        response = client.put(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
            data=json.dumps(self.invalid_payload),
            content_type='application/json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

テストを実行してください。

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 405 != 400

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 405 != 204

ビューを更新します。

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

    # update details of a single puppy
    if request.method == 'PUT':
        serializer = PuppySerializer(puppy, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    # delete a single puppy
    elif request.method == 'DELETE':
        return Response({})

上記のスニペットでは、挿入と同様に、リクエストデータをシリアル化および検証し、適切に応答します。

テストを再度実行して、すべてのテストに合格することを確認します。

DELETE

単一のレコードを削除するには、IDが必要です。

class DeleteSinglePuppyTest(TestCase):
    """ Test module for deleting an existing puppy record """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffy', age=1, breed='Gradane', color='Brown')

    def test_valid_delete_puppy(self):
        response = client.delete(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}))
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

    def test_invalid_delete_puppy(self):
        response = client.delete(
            reverse('get_delete_update_puppy', kwargs={'pk': 30}))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

テストを実行してください。 君は見るべきだ:

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 200 != 204

ビューを更新します。

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

    # update details of a single puppy
    if request.method == 'PUT':
        serializer = PuppySerializer(puppy, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    # delete a single puppy
    if request.method == 'DELETE':
        puppy.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

テストを再度実行します。 それらがすべて合格していることを確認してください。 Browsable API内のUPDATEおよびDELETE機能も必ずテストしてください!

結論と次のステップ

このチュートリアルでは、Django REST Frameworkを使用してテスト優先のアプローチでRESTful APIを作成するプロセスを実行しました。

*無料ボーナス:*リンク:[ここをクリックして "REST in a Nutshell"ガイドのコピーをダウンロード]と、REST APIの原則と例を実践的に紹介します。

次は何ですか? RESTful APIを堅牢で安全にするために、実稼働環境にアクセス許可と調整を実装して、認証資格情報とレート制限に基づいて制限付きアクセスを許可し、あらゆる種類のDDoS攻撃を回避できます。 また、Browsable APIが実稼働環境でアクセスできないようにすることを忘れないでください。

下のコメントにコメント、質問、ヒントを自由に投稿してください。 完全なコードはhttps://github.com/realpython/django-puppy-store[django-puppy-store]リポジトリにあります。