Django RESTful APIのテスト駆動開発

Django RESTful APIのテスト駆動開発

この投稿では、DjangoとDjango REST Frameworkを使用してCRUDベースのRESTful APIを開発するプロセスについて説明します。これは、Djangoモデルに基づいてRESTfulAPIを迅速に構築するために使用されます。

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

  • Python v3.6.0

  • Django v1.11.0

  • Django REST Framework v3.6.2

  • Postgres v9.6.1

  • Psycopg2 v2.7.1

Free Bonus:Click here to download a copy of the "REST API Examples" Guideを実行し、実用的な例を使用してPython + RESTAPIの原則を実践的に紹介します。

NOTE: Django RESTフレームワークのより詳細なチュートリアルについては、3番目の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:人間にわかりやすいHTML出力を使用してAPIを文書化し、標準のHTTPメソッドを使用してリソースにデータを送信してリソースからフェッチするための美しいフォームのようなインターフェイスを提供します。

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

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

  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アプリとinstalling REST Framework inside your virtualenvを作成することから始めます。

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

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

まず、puppiesアプリとrest_frameworkpuppy_store/puppy_store/settings.py内のINSTALLED_APPSセクションに追加します。

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

次に、RESTフレームワークのグローバル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に設定されます。

NOTE:ローカル開発では無制限のアクセスで問題ありませんが、実稼働環境では、特定のエンドポイントへのアクセスを制限する必要がある場合があります。 必ずこれを更新してください。 詳細については、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データベースをセットアップして、すべての移行を適用します。

NOTE: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': '',
        '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.'
(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)

NOTE:実際のテーブルの詳細を確認したい場合は、\d+ puppies_puppyを実行できます。

先に進む前に、パピーモデルのquick unit testを記述しましょう。

「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」に追加し、tests.pyファイルを「django-puppy-store / puppy_store / puppies」から削除します。

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

(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の作成に進む前に、モデルquerysetsを検証し、使用するPythonicデータ型を生成するPuppyモデルのserializerを定義しましょう。

次のスニペットを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を作成する場合はおそらくそうする必要があります)、ModelSerializerを使用してシリアライザーを作成できます。

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

RESTful構造

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

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

終点 HTTPメソッド CRUDメソッド 結果

puppies

GET

READ

すべての子犬を取得します

puppies/:id

GET

READ

子犬を1匹ゲット

puppies

POST

作成

子犬を1匹追加する

puppies/:id

PUT

更新

単一の子犬を更新します

puppies/:id

DELETE

DELETE

子犬を1匹削除する

ルートとテスト(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[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を作成するプロセスを実行しました。

Free Bonus:Click here to download a copy of the "REST in a Nutshell" GuideとRESTAPIの原則と例の実践的な紹介。

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

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