Создаем API блога на Django REST Framefork

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

Django REST Framework — это набор инструментов для создания REST API с помощью Django. В этом руководстве рассмотрим, как правильно его использовать. Создадим эндпоинты(точки доступа к ресурсам) для пользователей, постов в блоге, комментариев и категорий.

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

Вот чему вы научитесь:

  • Добавлять новые и существующие модели Django в API.
  • Сериализовать модели с помощью встроенных сериализаторов для распространенных API-паттернов.
  • Создавать представления и URL-паттерны.
  • Создавать отношения многие-к-одному и многие-ко-многим.
  • Аутентифицировать пользовательские действия.
  • Использовать созданный API Django REST Framework.

Код урока можно скачать в репозитории https://gitlab.com/PythonRu/blogapi.

Требования

У вас в системе должен быть установлен Python 3, желательно 3.8. Также понадобится опыт работы с REST API. Вы должны быть знакомы с реляционными базами данными, включая основные и внешние ключи, модели баз данных, миграции, а также отношения многие-к-одному и многие-ко-многим.

Наконец, потребуется опыт работы с Python и Django.

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

Для создания нового API-проекта для начала создайте виртуальную среду Python в своей рабочей директории. Для этого запустите следующую команду в терминале:

python3 -m venv env
source env/bin/activate

В Windows это будет source env\Scripts\activate.

Не забывайте запускать все команды из этого руководства в виртуальной среде. Убедиться в том, что она активирована можно благодаря надписи (env) в начале строки приглашения к вводу в терминале.

Чтобы деактивировать среду, введите deactivate.

После этого установите Django и REST Framework в среду:

pip install django==3.1.7 djangorestframework==3.12.4

Создайте новый проект «blog» и новое приложение «api»:

django-admin startproject blog
cd blog
django-admin startapp api
Создание нового API-проекта

Из корневой директории «blog» (там где находится файл «manage.py»), синхронизируйте базу данных. Это запустит миграции для admin, auth, contenttypes и sessions.

python manage.py migrate

Вам также понадобится пользователь admin для взаимодействия с панелью управления Django и API. Из терминала запустите следующее:

python manage.py createsuperuser --email admin@example.com --username admin

Установите любой пароль (у него должно быть как минимум 8 символов). Если вы введете слишком простой пароль, то можете получить ошибку.

Для настройки, добавьте rest_framework и api в файл конфигурации (blog/blog/settings.py):

INSTALLED_APPS = [
    ...
    'rest_framework',
    'api.apps.ApiConfig',
]

Добавление ApiConfig позволит добавлять параметры конфигурации в приложение. Другие настройки для этого руководства не потребуются.

Наконец, запустите локальный сервер с помощью команды python manage.py runserver.

Перейдите по ссылке http://127.0.0.1:8000/admin и войдите в админ-панель сайта. Нажмите на «Users», чтобы увидеть пользователя и добавить новых при необходимости.

админ-панель сайта

Создание API для пользователей

Теперь с пользователем «admin» можно переходить к созданию самого API. Это предоставит доступ только для чтения списку пользователей из списка API-эндпоинтов.

Сериализатор для User

REST Framework Django использует сериализаторы, чтобы переводить наборы запросов и экземпляры моделей в JSON-данные. Сериализация также определяет, какие данные вернет API в ответ на запрос клиента.

Пользователи Django создаются из модели User, которая определена в django.contrib.auth. Для создания сериализатора для модели добавьте следующее в blog/api/serializers.py (файл нужно создать):

from rest_framework import serializers
from django.contrib.auth.models import User


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username']

По примеру импортируйте модель User из Django вместе с набором сериализаторов из REST Framework Django.

Теперь создайте класс UserSerializer, который должен наследоваться от класса ModelSerializer.

Определите модель, которая должна ассоциироваться с сериализатором (model = User). Массив fields определяет, какие поля модели должны быть включены. Например, можно добавлять поля first_name и last_name.

Класс ModelSerializer генерирует поля сериализатора, которые основаны на соответствующих свойствах модели. Это значит, что не нужно вручную указывать все атрибуты для поля сериализации, поскольку они вытягиваются напрямую из модели.

Этот сериализатор также создает простые методы create() и update(). При необходимости их можно переписать.

Ознакомиться подробнее с работой ModelSerializer можно на официальном сайте.

Представления для User

Есть несколько способов создавать представления в REST Framework Django. Чтобы получить возможность повторного использования кода и избегать повторений, используйте классовые представления.

REST Framework предоставляет несколько обобщенных представлений, основанных на классе APIView. Они представляют собой самые распространенные паттерны.

Например, ListAPIView используется для эндпоинтов с доступом только для чтения. Он предоставляет метод-обработчик get. ListCreateAPIView используется для эндпоинтов с разрешением чтения-записи, а также обработчики get и post.

Для создания эндпоинта только для чтения, который возвращал бы список пользователей, добавьте следующее в blog/api/views.py:

from rest_framework import generics
from . import serializers
from django.contrib.auth.models import User


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = serializers.UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = serializers.UserSerializer

В первую очередь здесь импортируется generics коллекция представлений, а также модель User и UserSerialized из предыдущего шага. Представление UserList предоставляет доступ только для чтения (через get) к списку пользователей, а UserDetails — к одному пользователю.

Названия представлений должны быть в следующем формате: {ModelName}List и {ModelName}Details для коллекции объектов и одного объекта соответственно.

Для каждого представления переменная queryset содержит коллекцию экземпляров модели, которую возвращает User.objects.all(). Значением serializer_class должно быть UserSerializer, который и сериализует данные модели User.

Пути к эндпоинтам будут настроены на следующем шаге.

URL-паттерны

С моделью, сериализатором и набором представлений для User финальным шагом будет создание эндпоинтов (которые в Django называются URL-паттернами) для каждого представления.

В первую очередь добавьте следующее в blog/api/urls.py (этот файл тоже нужно создать):

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from . import views

urlpatterns = [
    path('users/', views.UserList.as_view()),
    path('users/<int:pk>/', views.UserDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

Здесь импортируется функция path и также коллекция представлений приложения api.

Функция path создает элемент, который Django использует для показа страницы приложения. Для этого Django в первую очередь ищет нужный элемент с соответствующим URL (например, users/) для запрошенного пользователем. После этого он импортирует и вызывает соответствующее представление (то есть, UserList)

Последовательность <int:pk> указывает на целочисленное значение, которое является основным ключом (pk). Django захватывает эту часть URL и отправляет в представление в виде аргумента-ключевого слова.

В этом случае основным ключом для User является поле id, поэтому http://127.0.0.1:8000/users/1 вернет id со значением 1.

Прежде чем можно будет взаимодействовать с этими URL-паттернами (и теми, которые будут созданы позже) их нужно добавить в проект. Добавьте следующее в blog/blog/urls.py:

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

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

Чтобы убедиться, что все элементы работают корректно, перейдите по ссылке http://127.0.0.1:8000/users, чтобы увидеть список пользователей приложения.

Django REST framework User List

В этом руководстве используется графическое представление API из Django REST Framework для демонстрации эндпоинтов. Интерфейс предоставляет элементы аутентификации и формы, имитирующие фронтенд-клиент. Для тестирования API также можно использовать cURL или httpie.

Обратите внимание на то, что значение пользователя admin равно 1. Можете перейти к нему, открыв для этого http://127.0.0.1:8000/users/1.

Django REST framework User Detail

В итоге класс модели Django сериализуется с помощью UserSerializaer. Он предоставляет данные представлениям UserList и UserDetail, доступ к которым можно получить с помощью паттернов users/ и users/<int:pk>.

Создание API для Post

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

Модель Post

В blog/api/models.py создайте модель Post, которая наследуется от класса Model из Django и определите ее поля:

from django.db import models


class Post(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    body = models.TextField(blank=True, default='')
    owner = models.ForeignKey('auth.User', related_name='posts', on_delete=models.CASCADE)

    class Meta:
        ordering = ['created']

Типы полей соответствуют таковым в реляционных базах данные. Можете ознакомиться со страницей Models на официальном сайте фреймворка.

Обратите внимание на то, что тип ForeignKey создает отношение многие-к-одному между текущей моделью и моделью, указанной в первом аргументе (auth.User — то есть, модель User, с которой вы работаете).

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

Аргумент related_name позволяет задать другое имя доступа к текущей модели (posts) вместо стандартного (post_set). Список постов будет добавлен в сериализатор User на следующем шаге для завершения отношения многие-к-одному.

Каждый раз при обновлении модели запускайте следующие команды для обновления базы данных:

python manage.py makemigrations api
python manage.py migrate

Поскольку мы работаем с моделями Django, таким как User, посты можно изменить из административной панели Django, зарегистрировав ее в blog/api/admin.py:

from django.contrib import admin
from .models import Post


admin.site.register(Post)

Позже их можно будет создавать и через графическое представление API.

Перейдите на http://127.0.0.1:8000/admin, кликните на Posts и добавьте новые посты. Вы заметите, что поля title и body в форме соответствуют типам CharField и TextField из модели Post.

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

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

Чтобы добавить модель Post в API, нужно повторить шаги добавления модели User.

Сначала нужно сериализовать данные модели Post. В blog/api/serializers.py добавьте следующее:

from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Post


class PostSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Post
        fields = ['id', 'title', 'body', 'owner']


class UserSerializer(serializers.ModelSerializer):
    posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = User
        fields = ['id', 'username', 'posts']

Импортируйте модель Post из приложения api и создайте PostSerializer, который будет наследоваться от класса ModelSerializer. Задайте модель и поля, которые будут использоваться сериализатором.

ReadOnlyField — это класс, возвращающий данные без изменения. В этом случае он используется для возвращения поля username вместо стандартного id.

Дальше добавьте поле posts в UserSerializer. Отношение многие-к-одному между постами и пользователями определено моделью Post в прошлом шаге. Название поля (posts) должно быть равным аргументу related_field поля Post.owner. Замените posts на post_set (значение по умолчанию), если вы не задали значение related_field в прошлом шаге.

PrimaryKeyRelatedField представляет список публикаций в этом отношении многие-к-одному (many=True указывает на то, что постов может быть больше чем один).

Если не задать read_only=True поле posts будет иметь права записи по умолчанию. Это значит, что будет возможность вручную задавать список статей, принадлежащих пользователю при его создании. Вряд ли это желаемое поведение.

Перейдите по ссылке http://127.0.0.1:8000/users, чтобы увидеть поле posts каждого пользователя.

Обратите внимание на то, что список posts — это, по сути, список id. Вместо этого можно возвращать список URL с помощью HyperLinkModelSerializer.

Представления Post

Следующий шаг — создать набор представлений для Post API. Добавьте следующее в blog/api/views.py:

...
from .models import Post

...

class PostList(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = serializers.PostSerializer

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = serializers.PostSerializer

ListCreateAPIView и RetrieveUpdateDestroyAPIView предоставляют самые распространенные обработчики API-методов: get и post для списка (ListCreateAPIView) и get, update и delete для одной сущности (RetrieveUpdateDestroyAPIView).

Также нужно перезаписать функцию по умолчанию perform_create, чтобы задать поле owner текущего пользователя (значение self.request.user).

URL-паттерны Post

Чтобы закончить с эндпоинтами для Post API создайте URL-паттерны Post. Добавьте следующее в массив urlpatterns в blog/api/urls.py:

urlpatterns = [
    ...
    path('posts/', views.PostList.as_view()),
    path('posts/<int:pk>/', views.PostDetail.as_view()),
]

Объединение представлений с этими URL-паттернами создает эндпоинты:

  • get posts/,
  • post posts/,
  • get posts/<int:pk>/,
  • put posts/<int:pk>/
  • и delete posts/<int:pk>/.

Чтобы протестировать их, перейдите на http://127.0.0.1:8000/posts и создайте публикации. Я взял несколько статей из Медиума.

Перейдите на один пост (например, http://127.0.0.1:8000/posts/1 и нажмите DELETE. Чтобы поменять название поста, обновите поле «title» и нажмите PUT.

Django REST framework Post Detail

После этого перейдите на http://127.0.0.1:8000/posts, чтобы увидеть список существующих публикаций или создать новый. Убедитесь, что вы залогинены, потому что при создании поста его автор создается на основе данных текущего пользователя.

Django REST framework Post List

Настройка разрешений

Для удобства добавим кнопку «Log in» в графическое представление API с помощью следующего кода в blog/urls.py:

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

Теперь можно заходить под разными аккаунтами, чтобы проверять работу разрешений и изменять посты через интерфейс.

Сейчас можно создать пост, будучи зарегистрированным, но для удаления или изменения данных этого не требуется — даже если пост вам не принадлежит. Попробуйте зайти под другим аккаунтом, и вы сможете удалить посты, принадлежащие admin.

Чтобы аутентифицировать пользователя и быть уверенным в том, что только владелец поста может обновлять и удалять его, нужно добавить разрешения.

Начните с этого кода в blog/api/permisisions.api (файл необходимо создать):

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True

        return obj.owner == request.user

Разрешение IsOwnerOrReadOnly проверяет, является ли пользователь владельцем этого объекта. Таким образом только при этом условии можно будет обновлять или удалять пост. Не-владельцы смогут получать пост, потому что это действие только для чтения.

Также есть встроенное разрешение IsAuthenticatedOrReadOnly. С ним любом аутентифицированный пользователь может выполнять любой запрос, а остальные — только на чтение.

Добавьте эти разрешения в представления Post:

from rest_framework import generics, permissions
...
from .serializers import PostSerializer
from .permissions import IsOwnerOrReadOnly

...

class PostList(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly]

Представлению PostList требуется разрешение IsAuthenticatedOrReadOnly, потому что пользователь должен аутентифицироваться, чтобы создать пост, а вот просматривать список может любой пользователь.

Для PostDetails нужны оба разрешения, поскольку обновлять или удалять пост должен только залогиненный пользователь, а также его владелец. Для получения поста прав не нужно. Вернитесь на http://127.0.0.1:8000/posts. Зайдите в учетную запись admin и другие, чтобы проверить, какие действия доступны аутентифицированным и анонимным пользователям.

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

Django REST framework Log Out

Создание API для Comments

Теперь у вас есть базовый API для постов. Можно добавить в систему комментарии.

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

Модель Comment

Сначала создайте модель в blog/api/models.py:

...

class Comment(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    body = models.TextField(blank=False)
    owner = models.ForeignKey('auth.User', related_name='comments', on_delete=models.CASCADE)
    post = models.ForeignKey('Post', related_name='comments', on_delete=models.CASCADE)

    class Meta:
        ordering = ['created']

Модель Comment похожа на Post и имеет отношение многие-к-одному с пользователями через поле owner. У комментария есть отношение многие-к-одному с одним постом через поле post.

Запустите миграции базы данных:

python manage.py makemigrations api
python manage.py migrate

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

Для создания API комментариев нужно добавить модель Comment в PostSerializer и UserSerializer, чтобы убедиться, что связанные комментарии отправляются вместе с данными о пользователе и посте.

Обновите код в blog/api/serializers.py:

...

from .models import Post, Comment


class PostSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    comments = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ['id', 'title', 'body', 'owner', 'comments']


class UserSerializer(serializers.ModelSerializer):
    posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    comments = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = User
        fields = ['id', 'username', 'posts', 'comments']

Процесс напоминает добавление posts в UserSerializer. Это настраивает часть «многие» отношения многие-к-одному между комментариями и пользователем, а также между комментариями и постом. Список комментариев должен быть доступен только для чтения (read_only=True)

Теперь добавим CommentSerializer в тот же файл:

...

class CommentSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Comment
        fields = ['id', 'body', 'owner', 'post']

Обратите внимание на то, что поле post не меняется. После добавления поля post в массив fields он будет сериализоваться по умолчанию (согласно ModelSerializer). Это эквивалентно post=serializers.PrimaryKeyRelatedField(queryset=Post.objects.all()).

Это значит, что у поля post есть право на запись по умолчанию: при создании комментария настраивается, какому посту он принадлежит.

Представления комментариев

Наконец, создадим представления и паттерны для комментариев. Процесс похож на тот, что был при настройке API Post.

Добавьте этот код в blog/api/views.py:

...
from .models import Post, Comment

...

class CommentList(generics.ListCreateAPIView):
    queryset = Comment.objects.all()
    serializer_class = serializers.CommentSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

class CommentDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Comment.objects.all()
    serializer_class = serializers.CommentSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly]

Представления похожи на PostList и PostDetails.

URL-Паттерны комментариев

Чтобы закончить API комментариев, определите URL-паттерны в blog/api/urls.py:

urlpatterns = [
    ...
    path('comments/', views.CommentList.as_view()),
    path('comments/<int:pk>/', views.CommentDetail.as_view()),
]

Теперь по ссылке http://127.0.0.1:8000/comments можете увидеть список существующих комментариев и создавать новые.

Django REST framework Comment List

Также обратите внимание на то, что при создании нужно выбрать пост из списка существующих.

Создание API для Category

Финальный элемент блога — система категорий.

Пост может принадлежать к одной или нескольким категориям. Также одна категория может принадлежать нескольким постам, значит это отношение многие-ко-многим.

Модель категории

Создайте модель Category в blog/api/models.py:

urlpatterns = [
    ...
    path('comments/', views.CommentList.as_view()),
    path('comments/<int:pk>/', views.CommentDetail.as_view()),
]

Здесь класс ManyToManyField создает отношение многие-ко-многим между текущей моделью и моделью из первого аргумента. Как и в случае с классом ForeignKey отношение завершает сериализатор.

Обратите внимание на то, что verbose_name_plural определяет, как правильно писать название модели во множественном числе. Это нужно, например, для административной панели. Так, вы можете указать, что во множественном числе правильно писать categories, а не categorys.

Запустите миграции базы данных:

python manage.py makemigrations api
python manage.py migrate

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

Процесс создания похож на описанный в прошлых шагах. Сперва создайте сериализатор для Category, добавив код в blog/api/serializers.py:

...
from .models import Post, Comment, Category

...

class CategorySerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Category
        fields = ['id', 'name', 'owner', 'posts']

class PostSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    comments = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ['id', 'title', 'body', 'owner', 'comments', 'categories']

class UserSerializer(serializers.ModelSerializer):
    posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    comments = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    categories = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = User
        fields = ['id', 'username', 'posts', 'comments', 'categories']

Не забудьте добавить имя поля categories в список полей PostSerializer и UserSerializer. Обратите внимание на то, что UserSerializer.categories можно отметить как read_only=True. Это поле представляет список всех созданных категорий.

С другой стороны, поле PostSerializer.categories будет иметь право на запись по умолчанию. То же самое, что указать categories = serializers.PrimaryKeyRelatedField(many=True, queryset=Category.objects.all()). Это позволит пользователю выбирать одну или нескольких категорий для поста.

Представления для категории

Дальше создайте представления в blog/api/views.api:

...
from .models import Post, Comment, Category

...

class CategoryList(generics.ListCreateAPIView):
    queryset = Category.objects.all()
    serializer_class = serializers.CategorySerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Category.objects.all()
    serializer_class = serializers.PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly]

По аналогии с прошлыми.

URL-паттерны категорий

Наконец, добавьте этот код в blog/api/urls.py:

...
from .models import Post, Comment, Category

urlpatterns = [
    ...
    path('categories/', views.CategoryList.as_view()),
    path('categories/<int:pk>/', views.CategoryDetail.as_view()),
]

Теперь отправляйтесь на http://127.0.0.1:8000/categories и создайте пару категорий. А на http://127.0.0.1:8000/posts создайте пост и выберите для него категории.

Django REST framework Categories

Выводы

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

Чтобы расширить возможности своего API, переходите к официальной документации Django REST Framework. А ссылку на код этого урока можно найти в начале статьи.

Подписывайтесь на канал в Дзене

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

Обучение Python и Data Science

Профессия Data Scientist

Профессия Data Scientist

11 520 5 760 ₽/мес.
Профессия Python-разработчик

Профессия Python-разработчик

7 820 3 910 ₽/мес.
Профессия Python Fullstack

Профессия Python Fullstack

7 820 3 910 ₽/мес.
Курс Аналитик данных с нуля

Курс Аналитик данных с нуля

6 500 3 900 ₽/мес.

Появились вопросы? Задайте на Яндекс.Кью

У сайта есть сообщество на Кью >> Python Q <<. Там я, эксперты и участники отвечаем на вопросы по python и программированию.