Написание JSON API в чистом Django для начинающих

В этом руководстве мы собираемся создать несколько API-интерфейсов CRUD (создание, чтение, обновление, удаление) на основе JSON в Django без использования каких-либо дополнительных библиотек, таких как Django Rest Framework (DRF) или Tastypie.

Установить & Настройка

1. Установите Джанго

Это руководство основано на Python 3.7 и Django 3.0. Поэтому убедитесь, что в вашей системе установлены Python 3.7 или более поздней версии и Django 3.0 или более поздней версии.

Установите pip здесь, если вы еще не уже, иначе пропустите. После установки pip вы можете установить Django с помощью pip. (вам может потребоваться запустить команду с помощью sudo, если вы не используете virtualenv)

gaurav@gaurav$ pip install django==3.0

Вы также можете выполнить подробные инструкции по установке здесь, если у вас возникнут какие-либо проблемы.

2. Создайте и настройте проект

Во-первых, нам нужно настроить проект. Для этого вы можете просто следовать приведенным ниже инструкциям, чтобы все подготовить.

gaurav@gaurav$ django-admin startproject pyscoop
gaurav@gaurav$ cd pyscoop
gaurav@gaurav$ python manage.py startapp myapp
gaurav@gaurav$ python manage.py migrate
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK
gaurav@gaurav$ pyscoop python manage.py createsuperuser
Username (leave blank to use 'gaurav.jain'):
Email address:
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
gaurav@gaurav$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks…

System check identified no issues (0 silenced).
June 12, 2020 - 06:51:26
Django version 3.0.6, using settings 'pyscoop.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Посетите: http://127.0.0.1:8000/admin/ и войдите в систему с учетными данными суперпользователя, которые вы создали выше.

Если вы видите страницу, это означает, что первоначальная настройка успешно завершена. Теперь мы можем сразу перейти к написанию API.

Потратьте немного времени, чтобы понять, что мы здесь сделали.

  • Создала базовую структуру проекта Django, используя стандартные команды, предоставляемые Django.
  • Создала таблицу в базе данных для существующих миграций, поставляемых с Django. Это создаст таблицы user, auth, session, admin и contenttypes в таблице база данных. Обратите внимание, что мы никогда не предоставляли информацию о базе данных в проекте. в этом случае Django по умолчанию создает файл базы данных SQLite в корневом каталоге проекта с именем db.sqlite3. В реальном приложении вы будете использовать MySQL, PostgreSQL или другую базу данных SQL производственного уровня. для простоты этого руководства мы будем использовать SQLite DB. Если вы хотите изменить базу данных, вы можете сделать это, обновив переменную DATABASES в файле pyscoop/settings.py. Подробнее о настройках БД читайте здесь.
  • Затем создал суперпользователя для доступа к странице администратора и просмотра вставленных данных в базу данных. вы также можете использовать другие клиенты SQL с графическим интерфейсом для просмотра данных.
  • Наконец-то запустил локальный сервер на порту 8000 по умолчанию и получил доступ к странице администратора, чтобы проверить настройку.

2. Написание простого API

Давайте сначала напишем простой API, который выдает жестко закодированный ответ в формате JSON. Как только мы с этим разберемся, мы напишем API, который фактически получает данные из базы данных. Далее мы также напишем API для обновления/удаления данных в базе данных. Начнем!

Откройте файл mpapp/views.py и скопируйте приведенный ниже код.

from django.http import JsonResponse

def my_api_view(request):
    data = {
        'name': request.user.username,  # username of current logged-in user, otherwise Anonymous
        'url': 'https://www.pyscoop.com/',
        'skills': ['Python', 'Django'],
    }
    return JsonResponse(data)

Мы создали представление (функцию), которое принимает параметр запроса и возвращает ответ. Внутри представления мы создали объект словаря, который хотим вернуть, и передали этот словарь классу JsonResponse, предоставленному Django.
Следующим шагом является создание URL/конечной точки, которую мы можем использовать для доступа к этому представлению. Для этого мы создаем новый файл с именем urls.py в каталоге myapp. Скопируйте приведенный ниже код в файл myapp/urls.py.

from django.urls import path

from .views import my_api_view

urlpatterns = [
    path('awesome-api/', my_api_view),
]

Мы импортировали представление, созданное на первом этапе. Мы также создали URL-адрес и указали на представление, которое мы только что импортировали. Просто это означает, что всякий раз, когда кто-то посещает /awesome-api/, он выполняет функцию my_api_view. Однако, когда вы получите более глубокое понимание Django, вы обнаружите, что существует ряд промежуточных программ, которые срабатывают до представления, когда вы нажимаете URL-адрес, но нам не нужно беспокоиться об этом в этом руководстве.

Последнее, что мы хотим сделать, это указать Django зарегистрировать URL-адрес, который мы только что создали, и для этого нам нужно включить файл URL-адреса, который мы только что создали выше. Откройте файл pyscoop/urls.py, и вы увидите, что URL-адреса администратора уже импортированы. Удалите все строки и скопируйте приведенный ниже код.

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls'))
]

Мы добавили 1 строку path(”, include(‘myapp.urls’)) в список шаблонов URL-адресов после URL-адреса администратора.

Теперь перейдите по URL-адресу http://127.0.0.1:8000/awesome_api/ в вашем браузере вы должны увидеть содержимое, подобное приведенному ниже.

{
  "name": "gaurav.jain",
  "url": "https://www.pyscoop.com/",
  "skills": [
    "Python",
    "Django"
  ]
}

Если вы не вошли в систему, вы увидите пустое значение в поле имени. Вы можете использовать curl для идентификации заголовков запроса. откройте новую вкладку в терминале и выполните curl -i http://127.0.0.1:8000/awesome_api/, вы сможете увидеть содержимое, подобное приведенному ниже.

gaurav@gaurav$ curl -i http://127.0.0.1:8000/awesome_api/
HTTP/1.1 200 OK
Date: Fri, 12 Jun 2020 08:00:29 GMT
Server: WSGIServer/0.2 CPython/3.7.4
Content-Type: application/json
X-Frame-Options: DENY
Content-Length: 79
Vary: Cookie
X-Content-Type-Options: nosniff

{"name": "gaurav.jain", "url": "https://www.pyscoop.com/", "skills": ["Python", "Django"]}

если вы посмотрите внимательно, вы увидите значение заголовка Content-Type, которое равно application/json.

Поздравляем! Вы только что создали свой первый JSON API с помощью Django.

Это самый простой способ создания API с помощью функции. Это имеет смысл, когда у вас есть API с минимальной логикой, но на самом деле API намного сложнее, и запись всей логики в функции просмотра становится громоздкой. Чтобы преодолеть, в определенной степени, мы можем использовать Представления на основе классов. Например, если мы хотим написать тот же API, используя представление на основе классов, мы можем написать –

from django.views import View

class MyAPIView(View):
    def get(self, request):
        data = {
            'name': request.user.username,  # username of current logged-in user, otherwise Anonymous
            'url': 'https://www.pyscoop.com/',
            'skills': ['Python', 'Django'],
        }

        return JsonResponse(data)

И в myapp/urls.py мы будем использовать

from django.urls import path

from .views import MyAPIView

urlpatterns = [
    path('awesome_api/', MyAPIView.as_view()),
]

Вы можете сказать “но это сложнее, чем то, что мы создали ранее“. Я согласен, конечно, это выглядит, но не забывайте, что это был просто тупой API, который всегда возвращает почти один и тот же ответ. В реальном приложении использование представления на основе классов может сэкономить вам гораздо больше времени и головной боли. Представление на основе классов имеет некоторые преимущества по сравнению с представлением на основе функций. Мы не будем вдаваться в эти подробности, вы можете увидеть плюсы и минусы здесь.

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

3. Написание динамического API

Давайте создадим простую таблицу для книги с тремя столбцами: название, автор и цена. В Django это делается в 3 этапа.

  1. Создайте/обновите модели.
  2. Создайте файл миграции для созданной/обновленной модели в строке выше
  3. Примените миграцию, чтобы отразить изменения на уровне базы данных.

Давайте рассмотрим каждый шаг один за другим.

1) Скопируйте приведенный ниже контент в файл myapp/models.py

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.CharField(max_length=127)
    price = models.IntegerField()

В файле pyscoop/settings.py найдите переменную INSTALLED_APPS и добавьте myapp в список, как показано ниже. Это необходимо для переноса.

# Application definition

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

2) Создайте файл миграции для вновь созданной модели книги.

gaurav@gaurav$ python manage.py makemigrations myapp

Это создаст файл myapp/migrations/0001_initial.py, который Django использует для создания или обновления базы данных.

3) Применить миграцию

gaurav@gaurav$ python manage.py migrate

4) Этот шаг необязателен, но для управления данными книги со страницы администратора нам необходимо зарегистрировать модель книги у администратора. Добавьте приведенный ниже код в файл myapp/admin.py.

from django.contrib import admin
from .models import Book

admin.site.register(Book)

Теперь, если снова запустить сервер с помощью python manage.py runserver и посетить страницу администрирования http://127.0.0.1:8000/admin/. Вы должны увидеть раздел «Книги». Если вы нажмете на это, вы найдете там 0 книг, потому что мы еще не создали ни одной книги, поэтому давайте сначала сделаем это. Добавьте новую книгу, нажав кнопку ДОБАВИТЬ КНИГУ, заполните данные и сохраните ее.

Аналогичным образом добавьте еще одну книгу со страницы администратора. На данный момент у нас должно быть 2 объекта книги в базе данных, мы можем проверить это, посетив страница http://127.0.0.1:8000/admin/myapp/book/.

Пришло время написать API, который возвращает всю книгу из базы данных.

GET API

Добавьте приведенный ниже код в файл myapp/views.py сразу после кода, который вы написали ранее.

from .models import Book

class BookView(View):
    def get(self, request):
        books_count = Book.objects.count()  # TOTAL books in the database
        books = Book.objects.all()  # Get all book objects from the database

        data = {
            'books': books,
            'count': books_count,
        }
        return JsonResponse(data)

Как вы помните, ранее в этом руководстве мы создали URL-адрес для присоединения к представлению. Мы собираемся сделать то же самое с новым представлением BookView. Откройте файл myapp/urls.py и замените предыдущий код приведенным ниже кодом.

from django.urls import path

from .views import MyAPIView, BookView  # Note the import of `BookView` view

urlpatterns = [
    path('awesome_api/', MyAPIView.as_view()),
    path('books/', BookView.as_view()),
]

Если вы вызываете API http://127.0.0.1:8000/books/ , вы получите сообщение об ошибке Объект типа QuerySet не сериализуем JSON. Это потому, что мы пытаемся отправить объекты книги напрямую, не сериализуя их. Есть много способов сериализовать объект Django или набор запросов. Некоторые из них –

  1. Вручную сериализуйте поля объекта по отдельности и подготовьте данные ответа.
  2. Используйте встроенный сериализатор для сериализации объекта.
  3. Используйте сторонние библиотеки, такие как DjangoRestFramework.

Поскольку мы хотим делать все в Pure Django, мы не будем говорить о третьем варианте.

  1. Сериализация объекта вручную.
class BookView(View):
    def get(self, request):
        books_count = Book.objects.count()  # TOTAL books in the database
        books = Book.objects.all()  # Get all book objects from the database

        books_serialized_data = []  # to store serialized data
        for book in books:
            books_serialized_data.append({
                'book_title': book.title,
                'author_name': book.author,
                'book_price': book.price,
            })

        data = {
            'books': books_serialized_data,
            'count': books_count,
        }
        return JsonResponse(data)

Добавьте этот код в свое представление и перейдите по URL-адресу (http://127.0.0. 0.1:8000/books/), вы должны увидеть приведенный ниже ответ.

{
    "books": [
        {
            "book_title": "Book title 1",
            "author_name": "Book author",
            "book_price": 100
        },
        {
            "book_title": "Book title 2",
            "author_name": "Book author 2",
            "book_price": 120
        }
    ],
    "count": 2
}

Это может быть утомительно, если у вас большие таблицы и ваши представления могут запутаться. Вы также можете использовать метод value_list в наборе запросов, чтобы избавиться от ручной сериализации данных, но этого может быть недостаточно для приложений со сложными отношениями между таблицами.

2. Использование встроенного сериализатора

from django.core.serializers import serialize  # import serializer from django 

class BookView(View):
    def get(self, request):
        books_count = Book.objects.count()  # TOTAL books in the database
        books = Book.objects.all()  # Get all book objects from the database

        # Provide the serialize type such as python, json, xml, yaml, etc.
        # Here we are using 'python' because JsonResponse will automatically convert it to 'json'
        books_serialized_data = serialize('python', books)

        data = {
            'books': books_serialized_data,
            'count': books_count,
        }
        return JsonResponse(data)

И это все! Посетите API(http://127.0.0.1:8000/books/ ) снова.

{
    "books": [
        {
            "model": "myapp.book",
            "pk": 1,
            "fields": {
                "title": "Book title 1",
                "author": "Book author",
                "price": 100
            }
        },
        {
            "model": "myapp.book",
            "pk": 2,
            "fields": {
                "title": "Book title 2",
                "author": "Book author 2",
                "price": 120
            }
        }
    ],
    "count": 2
}

Но проблема в том, что в нем есть дополнительные поля, которые мы, возможно, не хотим показывать пользователям. Вы определенно можете настроить сериализованные данные, удалив эти поля. Я призываю вас поиграть с этим и настроить данные в соответствии с вашими пожеланиями. Это может быть хорошим упражнением для знакомства с Django.
Подробнее об этом можно прочитать здесь и здесь.

Отлично! На данный момент у нас есть готовый API GET, который возвращает все данные книги.

Обратите внимание, что в рабочем приложении вы должны позаботиться о нумерация страниц, аутентификация, разрешения-и-авторизация и т. д. Это руководство предназначено для начинающих, поэтому вам не нужно об этом беспокоиться.

Чтобы продолжить обучение, нам нужно установить клиент API, например Postman. Это упрощает нашу жизнь при разработке API. После установки попробуйте выполнить получить все книги API, который мы создали ранее.

Вы можете увидеть ответ, который мы наблюдали ранее. Для API GET это не имеет большого значения, но для API POST, PUT, DELETE довольно удобно использовать Postman. Мы собираемся создать эти API и будем использовать Postman для отладки и тестирования.

Если вы не знакомы с этими запросами HTTP, я настоятельно рекомендую вам прочитать об этом здесь или здесь

Хорошо, давайте начнем с POST API.

В настоящее время наше представление поддерживает только метод GET. Чтобы поддерживать метод POST, мы должны добавить в это представление еще один метод, называемый post. Мы также должны применить к классу декоратор, освобожденный от CSRF. Это не рекомендуется для производственного приложения. Я объяснил CSRF в этом сообщении. Скопируйте приведенный ниже код в свой класс. Я удалил строки из метода get, чтобы сделать этот класс коротким, но вы можете просто скопировать другие изменения.

POST API

from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

@method_decorator(csrf_exempt, name='dispatch')
class BookView(View):
    def get(self, request):
        ...

    def post(self, request):
        data = {'message': 'This is a POST request'}
        return JsonResponse(data)

На этот раз нет необходимости вносить изменения в файл urls.py. Откройте Postman и вызовите API, используя тип POST, как показано на изображении ниже.

Отлично! Это означает, что установка работает. Теперь мы можем изменить метод post, чтобы он принимал данные книги от пользователя и создавал объекты книги.


import json

@method_decorator(csrf_exempt, name='dispatch')
class BookView(View):

    ...

    def post(self, request):
        post_body = json.loads(request.body)   # don't forget to import json

        book_title = post_body.get('title')
        book_author = post_body.get('author')
        price = post_body.get('price')

        book_data = {
            'title': book_title,
            'author': book_author,
            'price': price,
        }

        book_obj = Book.objects.create(**book_data)
        data = {
            'message': f'New book object has been created with id {book_obj.id}'
        }
        return JsonResponse(data, status=201)

Это представление принимает 3 поля в теле JSON: название, автор и цена, а затем сохраняет в базе данных с помощью Book.objects.create. Идем дальше и подключаемся к API с помощью Postman.

Выберите параметр raw на вкладке body в Postman. и в раскрывающемся списке выберите JSON (справа на GraphQL).

Теперь вы можете убедиться в этом, открыв страницу администратора http:// 127.0.0.1:8000/admin/myapp/book/. вы должны увидеть только что созданный объект книги.

Это наивная реализация. в идеале вы должны обрабатывать все поля и возвращать ответ соответствующим образом.
например, если пользователь не указал ни одно из этих трех полей, вы должны вернуть 400 НЕПРАВИЛЬНЫЙ ЗАПРОС с соответствующим сообщением. Кроме того, если вы внимательно посмотрите на изображение выше, вы увидите, что статус равен 201 Создано, потому что мы явно передали это в JsonResponse. Значение по умолчанию: 200 ОК.

PUT API

Представьте себе ситуацию, когда вам нужно обновить цену книги, которая уже существует в вашей базе данных. Как бы Вы это сделали? Обычно API-интерфейсы PUT используются в таких сценариях, когда вам необходимо обновить существующие данные. Давайте реализуем PUT API и попробуем обновить цену книги.

Нам нужно создать новое представление, а также новый шаблон URL. В отличие от GET или POST, мы просто использовали /books/. Но теперь мы собираемся работать с существующими данными, поэтому нам нужно предоставить уникальный идентификатор в запросе, чтобы идентифицировать эти объекты и обновить или удалить их соответственно. В нашем примере уникальным идентификатором будет идентификатор.
Скопируйте приведенный ниже код в myapp/views.py.

@method_decorator(csrf_exempt, name='dispatch')
class BookUpdateDeleteView(View):

    def put(self, request, book_id):  # extra url parameter book_id is manadaotry
        book = Book.objects.get(id=book_id)

        put_body = json.loads(request.body)
        book.price = put_body.get('price')
        book.save()

        data = {
            'message': f'Price of the book {book_id} has been updated'
        }
        return JsonResponse(data)

и в файле myapp/urls.py добавьте путь для этого представления. После добавления этого у вас должно быть 3 объекта пути в списке urlpatterns. Не забудьте импортировать BookUpdateDeleteView.

from .views import MyAPIView, BookView, BookUpdateDeleteView  

urlpatterns = [
    path('awesome_api/', MyAPIView.as_view()),
    path('books/', BookView.as_view()),
    path('books/<int:book_id>/', BookUpdateDeleteView.as_view()),
]

Давайте разберемся, что здесь происходит. Мы создаем новый класс представления BookUpdateDeleteView и создали метод с именем put внутри класса. Здесь важно отметить, что метод put ожидает еще один параметр, которым является book_id. Этот параметр должен быть точно таким же, как мы указали в пути
например path(‘books/<int:book_id>/’, BookUpdateDeleteView.as_view()). Этот путь преобразуется в
/books/1/, /books/37/ и т. д. Хорошо, давайте попробуем в Postman. Мы хотим изменить цену первой книги, которая сейчас составляет 100. Новая цена должна быть, пусть’ скажем, 83. Выберите тип метода PUT, как мы сделали для POST, используйте URL-адрес http://127.0.0.1:8000/books/2/, а затем укажите цену в теле JSON. р>

Если вы откроете страницу администратора http://127.0.0.1:8000/admin/myapp/book /1/change/, вы должны увидеть новую цену.

DELETE API

API удаления похож на PUT API. Мы создаем метод с именем delete в классе представления. Этот метод принимает два параметра (request и book_id) , аналогичные методу put. Мы просто получаем объект книги из базы данных на основе предоставленного book_id в URL-адресе. и удалите объект.

@method_decorator(csrf_exempt, name='dispatch')
class BookUpdateDeleteView(View):

    def put(self, request, book_id):
        ...

    def delete(self, request, book_id):
        book = Book.objects.get(id=book_id) # get the object from DB
        book.delete()  # delete the entry
        # you can also combine these 2 statements: Book.objects.get(id=book_id).delete()

        data = {
            'message': f'Book object with id {book_id} has been deleted'
        }
        return JsonResponse(data)

Давайте удалим второй объект книги из базы данных. Откройте Postman, выберите метод DELETE из раскрывающегося списка, используйте URL-адрес http://127.0.0.1:8000/books/2/, нажмите Кнопка отправки.

Посетите GET API http://127.0.0.1:8000/books/< /a> еще раз, и вы не сможете увидеть удаленную запись. Вы также можете посетить страницу администратора, чтобы просмотреть доступные записи.

Заключение

В больших приложениях целесообразнее использовать DRF. Это упрощает задачу сериализации или десериализации данных, абстрагирует большой объем стандартного кода для API, а разработка API занимает всего несколько минут. Однако теперь у вас есть все базовые знания, необходимые для создания API, и вы готовы покорять мир.
Вы можете найти код для представлений и URL-адресов здесь.

Вернуться на верх