Développement piloté par les tests d’une API Django RESTful

Développement piloté par les tests d'une API Django RESTful

Cet article décrit le processus de développement d'une API RESTful basée sur CRUD avec Django etDjango REST Framework, qui est utilisée pour créer rapidement des API RESTful basées sur des modèles Django.

Cette application utilise:

  • 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 et obtenez une introduction pratique aux principes de Python + REST API avec des exemples exploitables.

NOTE: Consultez le troisième coursReal Python pour un didacticiel plus approfondi sur Django REST Framework.

Objectifs

À la fin de ce didacticiel, vous serez en mesure de…

  1. Discutez des avantages de l'utilisation de Django REST Framework pour démarrer le développement d'une API RESTful

  2. Valider les ensembles de requêtes de modèle à l'aide de sérialiseurs

  3. Appréciez la fonctionnalité d'API navigable de Django REST Framework pour une version plus propre et bien documentée de vos API

  4. Pratiquer le développement piloté par les tests

Pourquoi Django REST Framework?

Django REST Framework (REST Framework) fournit un certain nombre de fonctionnalités puissantes prêtes à l'emploi qui vont bien avec Django idiomatique, notamment:

  1. Browsable API: documente votre API avec une sortie HTML conviviale, fournissant une belle interface de type formulaire pour soumettre des données aux ressources et les récupérer à l'aide des méthodes HTTP standard.

  2. Auth Support: REST Framework offre une prise en charge complète de divers protocoles d'authentification, ainsi que des autorisations et des stratégies de limitation qui peuvent être configurées par vue.

  3. Serializers: Les sérialiseurs sont un moyen élégant de valider des ensembles de requêtes / instances de modèle et de les convertir en types de données Python natifs qui peuvent être facilement rendus en JSON et XML.

  4. Throttling: La limitation est un moyen de déterminer si une demande est autorisée ou non et peut être intégrée avec différentes autorisations. Il est généralement utilisé pour les demandes d'API à limitation de débit d'un seul utilisateur.

De plus, la documentation est facile à lire et pleine d'exemples. Si vous créez une API RESTful où vous avez une relation un à un entre vos points de terminaison API et vos modèles, alors REST Framework est la solution.

Configuration du projet Django

Créez et activez un virtualenv:

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

Installez Django et configurez un nouveau projet:

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

La structure actuelle de votre projet devrait ressembler à ceci:

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

Configuration de l'application Django et du cadre REST

Commencez par créer l'applicationpuppies etinstalling REST Framework inside your virtualenv:

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

Nous devons maintenant configurer notre projet Django pour utiliser REST Framework.

Tout d'abord, ajoutez l'applicationpuppies etrest_framework à la sectionINSTALLED_APPS danspuppy_store/puppy_store/settings.py:

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

Ensuite, définissez lessettingsglobaux pour REST Framework dans un seul dictionnaire, encore une fois, dans le fichiersettings.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'
}

Cela permet un accès illimité à l'API et définit le format de test par défaut sur JSON pour toutes les demandes.

NOTE: Un accès illimité convient au développement local, mais dans un environnement de production, vous devrez peut-être restreindre l'accès à certains points de terminaison. Assurez-vous de mettre à jour cela. Consultez lesdocs pour plus d'informations.

La structure actuelle de votre projet devrait maintenant ressembler à:

└── 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

Configuration de la base de données et du modèle

Configurons la base de données Postgres et y appliquons toutes les migrations.

NOTE: N'hésitez pas à remplacer Postgres par la base de données relationnelle de votre choix!

Une fois que vous disposez d'un serveur Postgres fonctionnel sur votre système, ouvrez le shell interactif Postgres et créez la base de données:

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

Installezpsycopg2 pour pouvoir interagir avec le serveur Postgres via Python:

(env)$ pip install psycopg2==2.7.1

Mettez à jour la configuration de la base de données danssettings.py, en ajoutant le nom d'utilisateur et le mot de passe appropriés:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'puppy_store_drf',
        'USER': '',
        'PASSWORD': '',
        'HOST': '127.0.0.1',
        'PORT': '5432'
    }
}

Ensuite, définissez un modèle de chiot avec quelques attributs de base dansdjango-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.'

Maintenantapply the migration:

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

Verification sanitaire

Sautez à nouveau danspsql et vérifiez que lepuppies_puppy a été créé:

$ 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: Vous pouvez exécuter\d+ puppies_puppy si vous souhaitez consulter les détails réels de la table.

Avant de continuer, écrivons unquick unit test pour le modèle Puppy.

Ajoutez le code suivant à un nouveau fichier appelétest_models.py dans un nouveau dossier appelé «tests» dans «django-puppy-store / puppy_store / puppies»:

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.")

Dans le test ci-dessus, nous avons ajouté des entrées factices dans notre table chiot via la méthodesetUp() dedjango.test.TestCase et affirmé que la méthodeget_breed() renvoyait la chaîne correcte.

Ajoutez un fichierinit.py à «tests» et supprimez le fichiertests.py de «django-puppy-store / puppy_store / puppies».

Lançons notre premier test:

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

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

Génial! Notre premier test unitaire a réussi!

Sérialiseurs

Avant de passer à la création de l'API proprement dite, définissons unserializer pour notre modèle Puppy qui valide le modèlequerysets et produit des types de données Pythonic avec lesquels travailler.

Ajoutez l'extrait de code suivant à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')

Dans l'extrait ci-dessus, nous avons défini unModelSerializer pour notre modèle chiot, validant tous les champs mentionnés. En bref, si vous avez une relation univoque entre vos points de terminaison d'API et vos modèles - ce que vous devriez probablement si vous créez une API RESTful -, vous pouvez utiliser unModelSerializer pour créer un sérialiseur.

Avec notre base de données en place, nous pouvons maintenant commencer à construire l'API RESTful…

Structure RESTful

Dans une API RESTful, les points de terminaison (URL) définissent la structure de l'API et la façon dont les utilisateurs finaux accèdent aux données de notre application à l'aide des méthodes HTTP - GET, POST, PUT, DELETE. Les points de terminaison doivent être organisés logiquement autour decollections etelements, qui sont tous deux des ressources.

Dans notre cas, nous avons une seule ressource,puppies, nous allons donc utiliser les URL suivantes -/puppies/ et/puppies/<id> pour les collections et les éléments, respectivement:

Endpoint Méthode HTTP Méthode CRUD Résultat

puppies

GET

READ

Obtenez tous les chiots

puppies/:id

GET

READ

Obtenez un seul chiot

puppies

POST

CRÉER

Ajouter un seul chiot

puppies/:id

PUT

MISE À JOUR

Mettre à jour un seul chiot

puppies/:id

EFFACER

EFFACER

Supprimer un seul chiot

Routes et tests (TDD)

Nous adopterons une approche axée sur les tests plutôt qu'une approche approfondie axée sur les tests, dans laquelle nous passerons par le processus suivant:

  • ajouter un test unitaire, juste assez de code pour échouer

  • puis mettez à jour le code pour lui faire passer le test.

Une fois le test réussi, recommencez avec le même processus pour le nouveau test.

Commencez par créer un nouveau fichier,django-puppy-store/puppy_store/puppies/tests/test_views.py, pour contenir tous les tests de nos vues et créez un nouveau client de test pour notre application:

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()

Avant de commencer avec toutes les routes API, créons d'abord un squelette de toutes les fonctions d'affichage qui renvoient des réponses vides et les mappons avec leurs URL appropriées dans le fichierdjango-puppy-store/puppy_store/puppies/views.py:

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({})

Créez les URL respectives pour correspondre aux vues dansdjango-puppy-store/puppy_store/puppies/urls.py:

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'
    )
]

Mettez également à jourdjango-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 navigable

Avec toutes les routes désormais connectées aux fonctions d'affichage, ouvrons l'interface d'API navigable du cadre REST et vérifions si toutes les URL fonctionnent comme prévu.

Tout d'abord, lancez le serveur de développement:

(env)$ python manage.py runserver

Assurez-vous de commenter tous les attributs de la sectionREST_FRAMEWORK de notre fichiersettings.py, pour contourner la connexion. Maintenant, visitezhttp://localhost:8000/api/v1/puppies

Vous verrez une disposition HTML interactive pour la réponse de l'API. De même, nous pouvons tester les autres URL et vérifier que toutes les URL fonctionnent parfaitement.

Commençons par nos tests unitaires pour chaque itinéraire.

Itinéraires

AVOIR TOUT

Commencez par un test pour vérifier les enregistrements récupérés:

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)

Exécutez le test. Vous devriez voir l'erreur suivante:

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

Mettez à jour la vue pour réussir le test.

@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({})

Ici, nous obtenons tous les enregistrements des chiots et validons chacun à l'aide desPuppySerializer.

Exécutez les tests pour vous assurer qu'ils réussissent tous:

Ran 2 tests in 0.072s

OK

GET Single

Aller chercher un seul chiot implique deux cas de test:

  1. Obtenez un chiot valide - par exemple, le chiot existe

  2. Obtenez un chiot invalide - par exemple, le chiot n'existe pas

Ajoutez les tests:

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)

Exécutez les tests. Vous devriez voir l'erreur suivante:

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

Mettez à jour la vue:

@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)

Dans l'extrait ci-dessus, nous obtenons le chiot en utilisant une pièce d'identité. Exécutez les tests pour vous assurer qu'ils réussissent tous.

POST

L'insertion d'un nouvel enregistrement implique également deux cas:

  1. Insérer un chiot valide

  2. Insertion d'un chiot non valide

Tout d'abord, écrivez des tests pour cela:

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)

Exécutez les tests. Vous devriez voir deux échecs:

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

Encore une fois, mettez à jour la vue pour réussir les tests:

@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)

Ici, nous avons inséré un nouvel enregistrement en sérialisant et en validant les données de demande avant de les insérer dans la base de données.

Exécutez à nouveau les tests pour vous assurer qu'ils réussissent.

Vous pouvez également tester cela avec l'API navigable. Relancez le serveur de développement et accédez àhttp://localhost:8000/api/v1/puppies/. Ensuite, dans le formulaire POST, soumettez ce qui suit en tant queapplication/json:

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

Assurez-vous également que GET ALL et Get Single fonctionnent également.

PUT

Commencez par un test pour mettre à jour un enregistrement. Comme pour l'ajout d'un enregistrement, nous devons à nouveau tester les mises à jour valides et non valides:

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)

Exécutez les tests.

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

Mettez à jour la vue:

@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({})

Dans l'extrait ci-dessus, similaire à un encart, nous sérialisons et validons les données de demande, puis répondons de manière appropriée.

Exécutez à nouveau les tests pour vous assurer que tous les tests réussissent.

EFFACER

Pour supprimer un seul enregistrement, un identifiant est requis:

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)

Exécutez les tests. Tu devrais voir:

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

Mettez à jour la vue:

@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)

Exécutez à nouveau les tests. Assurez-vous que tous passent. Assurez-vous également de tester les fonctionnalités UPDATE et DELETE dans l'API navigable!

Conclusion et prochaines étapes

Dans ce didacticiel, nous avons suivi le processus de création d'une API RESTful à l'aide de Django REST Framework avec une approche test-first.

Free Bonus:Click here to download a copy of the "REST in a Nutshell" Guide avec une introduction pratique aux principes et exemples de l'API REST.

Et après? Pour rendre notre API RESTful robuste et sécurisée, nous pouvons implémenter des autorisations et une limitation pour un environnement de production pour permettre un accès restreint sur la base des informations d'authentification et une limitation de débit pour éviter toute sorte d'attaque DDoS. N'oubliez pas non plus d'empêcher l'API navigable d'être accessible dans un environnement de production.

N'hésitez pas à partager vos commentaires, questions ou conseils dans les commentaires ci-dessous. Le code complet se trouve dans le référentieldjango-puppy-store.