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…
-
Erläutern Sie die Vorteile der Verwendung von Django REST Framework für das Bootstrapping der Entwicklung einer RESTful-API
-
Überprüfen Sie Modellabfragesätze mithilfe von Serialisierern
-
Schätzen Sie die durchsuchbare API-Funktion von Django REST Framework für eine sauberere und gut dokumentierte Version Ihrer APIs
-
Ü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:
-
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.
-
Auth Support: REST Framework bietet umfassende Unterstützung für verschiedene Authentifizierungsprotokolle sowie Berechtigungen und Drosselungsrichtlinien, die pro Ansicht konfiguriert werden können.
-
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.
-
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.'
Jetztapply the migration:
(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 |
---|---|---|---|
|
GET |
READ |
Holen Sie sich alle Welpen |
|
GET |
READ |
Holen Sie sich einen einzigen Welpen |
|
POST |
ERSTELLEN |
Fügen Sie einen einzelnen Welpen hinzu |
|
PUT |
AKTUALISIEREN |
Aktualisieren Sie einen einzelnen Welpen |
|
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_FRAMEWORK
unserersettings.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:
-
Holen Sie sich einen gültigen Welpen - z. B. existiert der Welpe
-
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:
-
Einfügen eines gültigen Welpen
-
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.