Python

Разработка на основе тестов Django RESTful API

Spread the love

В этой статье рассматривается процесс разработки CRUD RESTful API с использованием Django и Django REST Framework, который используется для быстрого создания API на основе моделей Django.

Это приложение использует:

  • Python v3.6.0
  • Django v2.1.7
  • Django REST Framework v3.9.2
  • Postgres v9.6.1
  • Psycopg2 v2.7.7

Цели

К концу этой статьи вы сможете…

  1. Узнаете преимущества использования Django REST Framework для разработки RESTful API
  2. Научитесь создавать валидарованные запросы к базе с использованием сериализаторов
  3. Оцените возможность просмотра API в Django REST Framework, чтобы получить более чистую и хорошо документированную версию ваших API.
  4. Попрактикуетесь в разработке через тестирование

Почему Django REST Framework?

Django REST Framework (REST Framework) предоставляет ряд мощных фич из коробки, которые хорошо сочетаются с идиоматическим Django, включая:

  1. Browsable API(API с возможностью просмотра): ваше API автоматически документируется с помощью удобного и понятного для пользователя вывода в формате HTML, предоставляя красивый интерфейс в виде формы для отправки данных и извлечения из них стандартными методами HTTP.
  2. Поддержка аутентификации: REST Framework имеет расширенную поддержку различных протоколов аутентификации, а также политик разрешений и регулирования, которые можно настраивать для каждой вьюхи отдельно.
  3. Сериализаторы: Сериализаторы — это элегантный способ создания валидированных запросов к моделям и преобразования их в собственные типы данных Python, которые можно легко преобразовать в JSON и XML.
  4. Throttling: Throttling — это способ определить, является ли запрос авторизованным или нет, и его можно интегрировать с различными разрешениями. Обычно используется для ограничения скорости запросов API от одного пользователя.

Настройка проекта Django

Создайте и активируйте shell pipenv:

$ mkdir django-puppy-store
$ cd django-puppy-store
$ pipenv --python 3.6
$ pipenv shell

Установите Django и настройте новый проект:

(django-puppy-store) bash-3.2$ pipenv install django==2.1.7
(django-puppy-store) bash-3.2$ django-admin startproject puppy_store

Ваша текущая структура проекта должна выглядеть следующим образом:

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

Настройка Django приложения и REST Framework

Начните с создания приложения puppies и установки REST Framework внутри вашего virtualenv:

(django-puppy-store) bash-3.2$ cd puppy_store
(django-puppy-store) bash-3.2$ python manage.py startapp puppies
(django-puppy-store) bash-3.2$ pipenv install djangorestframework==3.9.2

Теперь нам нужно настроить наш проект Django для использования REST Framework.

Сначала добавьте приложение puppies и rest_framework в раздел INSTALLED_APPS в puppy_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'
]

Затем определите глобальные параметры для REST Framework в файле 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 для всех запросов.

ПРИМЕЧАНИЕ. Неограниченный доступ подходит для локальной разработки, но в производственной среде вам может потребоваться ограничить доступ к определенным url. Обязательно поправте это. Просмотрите документацию для получения дополнительной информации.

Ваша текущая структура проекта теперь должна выглядеть так:

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

Настройка базы данных и модели

Давайте настроим базу данных Postgres и применим к ней все миграции.

Когда в вашей системе будет работающий сервер Postgres, откройте интерактивную оболочку Postgres и создайте базу данных:

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

Установите psycopg2, так чтобы мы могли взаимодействовать с сервером Postgres через Python:

(django-puppy-store) bash-3.2$ pipenv install psycopg2==2.7.7

Обновите конфигурацию базы данных в файле settings.py, добавив соответствующее имя пользователя и пароль:

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

Затем определите модель puppy с некоторыми основными атрибутами в 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.'

Теперь примените миграцию:

(django-puppy-store) bash-3.2$ python manage.py makemigrations
(django-puppy-store) bash-3.2$ python manage.py migrate

Дополнительная проверка

Перейдите в psql снова и убедитесь, что puppies_puppy был создан:

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

ПРИМЕЧАНИЕ. Вы можете запустить \ d + puppies_puppy, если хотите посмотреть детали таблицы.

Прежде чем двигаться дальше, давайте напишем быстрый модульный тест для модели Puppy.

Добавьте следующий код в новый файл с именем test_models.py в новую папку с именем «tests» в «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.")

В приведенном выше тесте мы добавили фиктивные записи в нашу таблицу puppy с помощью метода setUp() из django.test.TestCase и заявили, что метод get_breed() будет возвращать определенную строку.

Добавте файл __init__.py в каталог “tests” и удалите файл tests.py из каталога “django-puppy-store/puppy_store/puppies”.

Давайте запустим наш первый тест:

(django-puppy-store) bash-3.2$ python manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.007s

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

Наш первый модульный тест прошел!

Сериализаторы

Прежде чем перейти к созданию API, давайте определим сериализатор для нашей модели Puppy, который будет валидировать запросы (querysets) к модели и конвертировать данные из базы в типы данных Python для работы с ними.

Добавьте следующий фрагмент в 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 для нашей модели puppy, проверяя все упомянутые поля. Короче говоря, если у вас есть взаимно-однозначные отношения между вашими url API и вашими моделями — что вам, вероятно, всегда стараться делать — тогда вам проще всего использовать ModelSerializer для создания сериализатора.

Теперь приступим к созданию RESTful API…

Структура RESTful

В RESTful API конечные точки (URL) определяют структуру API и то, как пользователи смогут получить доступ к данным из нашего приложения с помощью методов HTTP — GET, POST, PUT, DELETE. Конечные точки должны быть логически организованы вокруг коллекций и элементов, которые являются ресурсами.

В нашем случае у нас есть один единственный ресурс, таблица puppies, поэтому мы будем использовать следующие URL-адреса — /puppies/ и /puppies/<id> для коллекций и элементов соответственно:

EndpointHTTP MethodCRUD MethodResult
puppiesGETREADПолучить всех puppies
puppies/:idGETREADПолучить одного выбранного puppy
puppiesPOSTCREATEДобавить одного puppy
puppies/:idPUTUPDATEОбновить одного puppy
puppies/:idDELETEDELETEУдалить одного puppy

Маршруты и тестирование (TDD)

Далее мы будем использовать упрощенный подход «вначале тестирование» (test-first), а не возможно более тщательный, основанный на разработке через тестирование (thorough test-driven). Наш подход будет заключаться в следующем процессе:

  • добавление модульного (unit) тест, и достаточно кода, чтобы тест был неудачным
  • затем обновляем код, чтобы тест проходил успешно

После того, как тест будет пройден, можно будет начать процесс заново для нового теста.

Начнем с создания нового файла 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, давайте сначала создадим скелет всех функций вьюх, которые будут возвращать пустые ответы и сопоставим их с соответствующими URL-адресами в файле django-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({})

Далее создадим соответствующие URL-адреса в соответствии с вьюхами в django-puppy-store/puppy_store/puppies/urls.py:

from django.urls import path
from . import views


urlpatterns = [
    path(
        'api/v1/puppies/(?P<pk>[0-9]+)',
        views.get_delete_update_puppy,
        name='get_delete_update_puppy'
    ),
    path(
        '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
from django.urls import path
from django.contrib import admin

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

Browsable API

Теперь, когда все маршруты подключены к функциям вьюх, давайте откроем интерфейс REST Framework Browsable API и проверим, все ли URL работают должным образом.

Сначала запустите сервер разработки:

(django-puppy-store) bash-3.2$ python manage.py runserver

Обязательно закомментируйте все атрибуты в разделе REST_FRAMEWORK вашего файла settings.py, чтобы обойти вход в систему. Теперь зайдите на http://localhost:8000/api/v1/puppies

Вы увидите интерактивный HTML-макет для ответа API. Точно так же мы можем проверить другие URL и убедиться, что все URL работают отлично.

Давайте начнем с наших юнит-тестов для каждого маршрута.

Маршруты

Получение всех записей GET ALL

Начнем с тестов, чтобы проверить извлечение записей. Занесите следующий код в файл django-puppy-store/puppy_store/puppies/tests/test_views.py:

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

Здесь мы получаем все записи для puppies и проверяем каждого с помощью PuppySerializer.

Запустите тесты, чтобы убедиться, что они все прошли:

Ran 2 tests in 0.072s

OK

Получение одной записи GET Single

Выбор одного puppy включает в себя два теста:

  1. Получить действительную запись — например, запись puppy существует
  2. Получите недействительного запись — например, запись puppy не существует

Добавим тесты:

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)

В приведенном выше фрагменте мы получаем одну запись с помощью идентификатора. Далее запустите тесты, чтобы убедиться, что они все прошли.

POST запрос

Вставка новой записи также включает два случая:

  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)

Запустите тесты. Вы должны увидеть две ошибки:

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

Чтобы удалить одну запись, требуется ее идентификатор:

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)

Запустите тесты снова. Убедитесь, что все они прошли. Обязательно проверьте функциональность UPDATE и DELETE в Browsable API!

Заключение и последующие шаги

В этом руководстве мы рассмотрели процесс создания RESTful API с использованием Django REST Framework с подходом, основанным на тестировании.

Что дальше? Чтобы сделать наш RESTful API надежным и безопасным, мы можем реализовать авторизацию и аутентификацию, чтобы разрешить ограниченный доступ на основе учетных данных, так же можно добавить ограничения скорости, чтобы избежать любого рода DDoS-атак. Кроме того, не забудьте запретить доступ к API Browsable в производственной среде.

Не стесняйтесь делиться своими комментариями, вопросами или советами в комментариях ниже.

Оригинал: Test Driven Development of a Django RESTful API

Была ли вам полезна эта статья?
[9 / 3.6]

Spread the love
Editorial Team

View Comments

  • Спасибо за статью и в целом за труд/блог!
    Хотел бы на не точность указать - в path нужно передавать строку, а если нужно регулярку, то надо re_path использовать.

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

11 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

11 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago