Testgetriebene Entwicklung einer Django RESTful API

Testgetriebene Entwicklung einer Django RESTful API

In diesem Beitrag wird der Prozess der Entwicklung einer CRUD-basierten RESTful-API mit Django undDjango REST Framework beschrieben, mit der RESTful-APIs basierend auf Django-Modellen schnell erstellt werden können.

Diese Anwendung verwendet:

  • 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 und erhalten eine praktische Einführung in die Python + REST-API-Prinzipien mit umsetzbaren Beispielen.

NOTE: Im drittenReal Python-Kurs finden Sie ein ausführlicheres Tutorial zum Django REST Framework.

Ziele

Am Ende dieses Tutorials können Sie…

  1. Erläutern Sie die Vorteile der Verwendung von Django REST Framework für das Bootstrapping der Entwicklung einer RESTful-API

  2. Überprüfen Sie Modellabfragesätze mithilfe von Serialisierern

  3. Schätzen Sie die durchsuchbare API-Funktion von Django REST Framework für eine sauberere und gut dokumentierte Version Ihrer APIs

  4. Übe testgetriebene Entwicklung

Warum Django REST Framework?

Das Django REST Framework (REST Framework) bietet eine Reihe leistungsstarker Funktionen, die sofort zu idiomatischem Django passen, darunter:

  1. Browsable API: Dokumentiert Ihre API mit einer benutzerfreundlichen HTML-Ausgabe und bietet eine schöne formularähnliche Oberfläche zum Senden von Daten an Ressourcen und zum Abrufen von Daten mithilfe der Standard-HTTP-Methoden.

  2. Auth Support: REST Framework bietet umfassende Unterstützung für verschiedene Authentifizierungsprotokolle sowie Berechtigungen und Drosselungsrichtlinien, die pro Ansicht konfiguriert werden können.

  3. Serializers: Serializer sind eine elegante Methode, um Modellabfragesätze / -instanzen zu validieren und in native Python-Datentypen zu konvertieren, die einfach in JSON und XML gerendert werden können.

  4. Throttling: Mit der Drosselung können Sie feststellen, ob eine Anforderung autorisiert ist oder nicht, und sie kann mit unterschiedlichen Berechtigungen integriert werden. Es wird im Allgemeinen zur Ratenbegrenzung von API-Anforderungen eines einzelnen Benutzers verwendet.

Außerdem ist die Dokumentation leicht zu lesen und voller Beispiele. Wenn Sie eine RESTful-API erstellen, in der Sie eine Eins-zu-Eins-Beziehung zwischen Ihren API-Endpunkten und Ihren Modellen haben, ist REST Framework der richtige Weg.

Django-Projekt-Setup

Erstellen und aktivieren Sie eine virtuelle Umgebung:

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

Installieren Sie Django und richten Sie ein neues Projekt ein:

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

Ihre aktuelle Projektstruktur sollte folgendermaßen aussehen:

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

Django App und REST Framework Setup

Erstellen Sie zunächst die Apppuppies undinstalling REST Framework inside your virtualenv:

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

Jetzt müssen wir unser Django-Projekt so konfigurieren, dass REST Framework verwendet wird.

Fügen Sie zunächst diepuppies-App undrest_framework zum AbschnittINSTALLED_APPS innerhalb vonpuppy_store/puppy_store/settings.py hinzu:

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

Definieren Sie als Nächstes globalesettings für REST Framework in einem einzelnen Wörterbuch erneut in der Dateisettings.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'
}

Dies ermöglicht den uneingeschränkten Zugriff auf die API und setzt das Standardtestformat für alle Anforderungen auf JSON.

NOTE: Der uneingeschränkte Zugriff ist für die lokale Entwicklung in Ordnung. In einer Produktionsumgebung müssen Sie jedoch möglicherweise den Zugriff auf bestimmte Endpunkte beschränken. Stellen Sie sicher, dass Sie dies aktualisieren. Überprüfen Sie diedocs für weitere Informationen.

Ihre aktuelle Projektstruktur sollte nun folgendermaßen aussehen:

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

Datenbank- und Modelleinrichtung

Richten wir die Postgres-Datenbank ein und wenden alle Migrationen darauf an.

NOTE: Sie können Postgres gegen die relationale Datenbank Ihrer Wahl austauschen!

Sobald Sie einen funktionierenden Postgres-Server auf Ihrem System haben, öffnen Sie die interaktive Postgres-Shell und erstellen Sie die Datenbank:

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

Installieren Siepsycopg2, damit wir über Python mit dem Postgres-Server interagieren können:

(env)$ pip install psycopg2==2.7.1

Aktualisieren Sie die Datenbankkonfiguration insettings.py und fügen Sie den entsprechenden Benutzernamen und das entsprechende Kennwort hinzu:

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

Definieren Sie als Nächstes ein Welpenmodell mit einigen grundlegenden Attributen indjango-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

Gesundheitsüberprüfung

Springen Sie erneut inpsql und stellen Sie sicher, dasspuppies_puppy erstellt wurde:

$ 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: Sie können\d+ puppies_puppy ausführen, wenn Sie die tatsächlichen Tabellendetails anzeigen möchten.

Bevor wir fortfahren, schreiben wir einquick unit test für das Welpenmodell.

Fügen Sie den folgenden Code zu einer neuen Datei mit dem Namentest_models.py in einem neuen Ordner mit dem Namen "tests" in "django-pupy-store / puppy_store / puppies" hinzu:

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

Im obigen Test haben wir Dummy-Einträge über diesetUp()-Methode vondjango.test.TestCase zu unserer Welpentabelle hinzugefügt und festgestellt, dass dieget_breed()-Methode die richtige Zeichenfolge zurückgegeben hat.

Fügen Sie eine "init.py-Datei zu" tests "hinzu und entfernen Sie dietests.py-Datei aus" django-pupy-store / puppy_store / puppies ".

Lassen Sie uns unseren ersten Test durchführen:

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

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

Toll! Unser erster Unit Test ist bestanden!

Serialisierer

Bevor wir mit der Erstellung der eigentlichen API fortfahren, definieren wir einserializer für unser Welpenmodell, das das Modellquerysets validiert und Pythonic-Datentypen erzeugt, mit denen gearbeitet werden kann.

Fügen Siedjango-puppy-store/puppy_store/puppies/serializers.py das folgende Snippet hinzu:

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

Im obigen Snippet haben wir einModelSerializer für unser Welpenmodell definiert und alle genannten Felder validiert. Kurz gesagt, wenn Sie eine Eins-zu-Eins-Beziehung zwischen Ihren API-Endpunkten und Ihren Modellen haben - was Sie wahrscheinlich tun sollten, wenn Sie eine RESTful-API erstellen -, können Sie mitModelSerializer einen Serializer erstellen.

Mit unserer Datenbank können wir jetzt mit dem Erstellen der RESTful-API beginnen…

RESTful Struktur

In einer RESTful-API definieren Endpunkte (URLs) die Struktur der API und wie Endbenutzer mithilfe der HTTP-Methoden GET, POST, PUT, DELETE auf Daten aus unserer Anwendung zugreifen. Endpunkte sollten logisch umcollections undelements organisiert sein, die beide Ressourcen sind.

In unserem Fall haben wir eine einzige Ressource,puppies, daher verwenden wir die folgenden URLs -/puppies/ und/puppies/<id> für Sammlungen bzw. Elemente:

Endpunkt HTTP-Methode CRUD-Methode Ergebnis

puppies

GET

READ

Holen Sie sich alle Welpen

puppies/:id

GET

READ

Holen Sie sich einen einzigen Welpen

puppies

POST

ERSTELLEN

Fügen Sie einen einzelnen Welpen hinzu

puppies/:id

PUT

AKTUALISIEREN

Aktualisieren Sie einen einzelnen Welpen

puppies/:id

LÖSCHEN

LÖSCHEN

Löschen Sie einen einzelnen Welpen

Routen und Tests (TDD)

Wir werden eher einen Test-First-Ansatz als einen gründlichen testgetriebenen Ansatz verfolgen, bei dem wir den folgenden Prozess durchlaufen:

  • Fügen Sie einen Komponententest hinzu, gerade genug Code, um fehlzuschlagen

  • Aktualisieren Sie dann den Code, damit er den Test besteht.

Beginnen Sie nach bestandenem Test mit demselben Vorgang für den neuen Test.

Beginnen Sie mit dem Erstellen einer neuen Datei,django-puppy-store/puppy_store/puppies/tests/test_views.py, um alle Tests für unsere Ansichten zu speichern und einen neuen Testclient für unsere App zu erstellen:

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

Bevor Sie mit allen API-Routen beginnen, erstellen wir zunächst ein Skelett aller Ansichtsfunktionen, die leere Antworten zurückgeben, und ordnen sie den entsprechenden URLs in der Dateidjango-puppy-store/puppy_store/puppies/views.pyzu:

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

Erstellen Sie die entsprechenden URLs, um den Ansichten indjango-puppy-store/puppy_store/puppies/urls.py zu entsprechen:

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

Aktualisieren Sie auchdjango-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),
]

Durchsuchbare API

Nachdem alle Routen jetzt mit den Ansichtsfunktionen verbunden sind, öffnen wir die Browsable API-Oberfläche von REST Framework und überprüfen, ob alle URLs wie erwartet funktionieren.

Starten Sie zunächst den Entwicklungsserver:

(env)$ python manage.py runserver

Stellen Sie sicher, dass Sie alle Attribute im AbschnittREST_FRAMEWORKunserersettings.py-Datei auskommentieren, um die Anmeldung zu umgehen. Besuchen Sie jetzthttp://localhost:8000/api/v1/puppies

Sie sehen ein interaktives HTML-Layout für die API-Antwort. Ebenso können wir die anderen URLs testen und sicherstellen, dass alle URLs einwandfrei funktionieren.

Beginnen wir mit unseren Unit-Tests für jede Route.

Routen

NIMM ALLE

Beginnen Sie mit einem Test, um die abgerufenen Datensätze zu überprüfen:

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)

Führen Sie den Test aus. Sie sollten den folgenden Fehler sehen:

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

Aktualisieren Sie die Ansicht, damit der Test bestanden wird.

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

Hier erhalten wir alle Datensätze für Welpen und validieren sie jeweils mitPuppySerializer.

Führen Sie die Tests aus, um sicherzustellen, dass alle erfolgreich sind:

Ran 2 tests in 0.072s

OK

GET Single

Das Holen eines einzelnen Welpen umfasst zwei Testfälle:

  1. Holen Sie sich einen gültigen Welpen - z. B. existiert der Welpe

  2. Erhalten Sie einen ungültigen Welpen - z. B. existiert der Welpe nicht

Fügen Sie die Tests hinzu:

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)

Führen Sie die Tests aus. Sie sollten den folgenden Fehler sehen:

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

Aktualisieren Sie die Ansicht:

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

Im obigen Ausschnitt erhalten wir den Welpen mit einem Ausweis. Führen Sie die Tests aus, um sicherzustellen, dass alle erfolgreich sind.

POST

Das Einfügen eines neuen Datensatzes umfasst auch zwei Fälle:

  1. Einfügen eines gültigen Welpen

  2. Einfügen eines ungültigen Welpen

Schreiben Sie zunächst Tests dafür:

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)

Führen Sie die Tests aus. Sie sollten zwei Fehler sehen:

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

Aktualisieren Sie die Ansicht erneut, damit die Tests bestanden werden:

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

Hier haben wir einen neuen Datensatz eingefügt, indem wir die Anforderungsdaten vor dem Einfügen in die Datenbank serialisiert und validiert haben.

Führen Sie die Tests erneut aus, um sicherzustellen, dass sie bestanden wurden.

Sie können dies auch mit der Browsable-API testen. Starten Sie den Entwicklungsserver erneut und navigieren Sie zuhttp://localhost:8000/api/v1/puppies/. Senden Sie dann im POST-Formular Folgendes alsapplication/json:

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

Stellen Sie sicher, dass GET ALL und Get Single ebenfalls funktionieren.

PUT

Beginnen Sie mit einem Test, um einen Datensatz zu aktualisieren. Ähnlich wie beim Hinzufügen eines Datensatzes müssen wir erneut auf gültige und ungültige Updates testen:

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)

Führen Sie die Tests aus.

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

Aktualisieren Sie die Ansicht:

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

Im obigen Snippet serialisieren und validieren wir ähnlich wie bei einer Einfügung die Anforderungsdaten und antworten dann entsprechend.

Führen Sie die Tests erneut aus, um sicherzustellen, dass alle Tests bestanden wurden.

LÖSCHEN

Um einen einzelnen Datensatz zu löschen, ist eine ID erforderlich:

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)

Führen Sie die Tests aus. Das solltest du sehen:

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

Aktualisieren Sie die Ansicht:

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

Führen Sie die Tests erneut aus. Stellen Sie sicher, dass alle bestanden haben. Testen Sie auch die Funktionen UPDATE und DELETE in der Browsable-API!

Fazit und nächste Schritte

In diesem Tutorial haben wir den Prozess des Erstellens einer RESTful-API unter Verwendung des Django REST Framework mit einem Test-First-Ansatz durchlaufen.

Free Bonus:Click here to download a copy of the "REST in a Nutshell" Guide mit einer praktischen Einführung in die Prinzipien und Beispiele der REST-API.

Was kommt als nächstes? Um unsere RESTful-API robust und sicher zu machen, können wir Berechtigungen und Drosseln für eine Produktionsumgebung implementieren, um einen eingeschränkten Zugriff auf der Grundlage von Authentifizierungsdaten und Ratenbeschränkungen zu ermöglichen, um jede Art von DDoS-Angriff zu vermeiden. Vergessen Sie auch nicht, zu verhindern, dass auf die Browsable-API in einer Produktionsumgebung zugegriffen werden kann.

Fühlen Sie sich frei, Ihre Kommentare, Fragen oder Tipps in den Kommentaren unten zu teilen. Der vollständige Code befindet sich im Repository vondjango-puppy-store.