🐷

Django REST Frameworkの基本事項まとめ

2023/04/20に公開

最初に

別記事でアプリを実際に作成する前に調べた内容等をまとめました。
ハマったポイントもあるので同じ状況の人の参考になればと思います。

コード

初期構築

mkdir backend
cd backend/
django-admin startproject config .
python manage.py startapp todo
python manage.py startapp apiv1

config/settings.pyの編集

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

    # 3rd party apps
    'rest_framework',

    # my apps
    'apiv1.apps.Apiv1Config',
    'todo.apps.TodoConfig',
]

...

# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

基本的なモデル

データベース内に存在する複数のオブジェクトを区別するために、primary keyを設定する必要があります。
何も設定しない場合は連続的に付番されるidが自動で作成されますが、悪意ある人に悪利用されないためにUUIDで設定します。

また、フィールドに付与したオプションにより後のシリアライザが実行するバリデーションなどが自動で決まっていきます。

モデル全体に対するオプションの詳細はこちらを参考に。
フィールドに関する詳細はこちら

import uuid
from django.db import models

class Task(models.Model):
  """ Task model
  id : primary key
  title : task name
  importance : importance of task
  """

  class Meta:
    db_table = 'task'
    ordering = ['created_at',]
    verbose_name = verbose_name_plural = 'タスク'
  
  id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  title = models.CharField(verbose_name='Title', max_length=20, unique=True)
  content = models.CharField(verbose_name='content', max_length=120, null=True)
  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)

  def __str__(self):
    return self.title

マイグレーション

アプリケーションのモデルの変更差分からマイグレーションファイルを作成します。
その後、そのマイグレーションファイルを元にDBに反映されます。

python manage.py makemigrations todo
python manage.py migrate todo

管理サイトへの登録

管理サイトで登録・編集ができるようにadmin.pyを修正します。

from django.contrib import admin

from .models import Task

# Register your models here.
class TaskModelAdmin(admin.ModelAdmin):
  list_display = ('title', 'content', 'created_at', 'updated_at',)
  ordering = ('-created_at',)
  readonly_fields = ('id', 'created_at', 'updated_at', )

admin.site.register(Task, TaskModelAdmin)

動作確認用にユーザーを作成します。(username: admin, pw: pass12345)

python manage.py createsuperuser

# 動作確認でアクセスしてみる
python manage.py runserver
# http://127.0.0.1:8000/admin/ にアクセス

django shellで動作確認

現状API実装できていないので簡単にshellで動作を見てみます。
実際に以下の作業を行うと、管理サイトでも追加されていることが確認できます。

python manage.py shell
from todo.models import Task
# 最初は空
Task.objects.all()

# 登録
task = Task(title="title1", content='content1')
task.save()
task = Task(title="title2", content='content2')
task.save()
Task.objects.all()

# フィルタ
Task.objects.filter(title='title1')

# 取得・削除
task = Task.objects.get(title='title1')
task.delete()

Task.objects.all()

シリアライザ

シリアリザは「入力データ(jsonデータ)とモデルオブジェクトの相互変換」「入力データのバリデーション」の2つの役割があります。

JSONの入出力構造がモデルのフィールド定義をベースにしたものになる場合、ModelSrializerを継承します。
これを継承することでモデル定義に基づいたバリデーションを実施するため記述量が大幅に削減されます。

from rest_framework import serializers
from todo.models import Task


class TaskSerializer(serializers.ModelSerializer):
  """ Serializer for Task Model """

  class Meta:
    # 対象のモデルクラスを指定
    model = Task
    # 利用するモデルのフィールドを指定
    fields = ['id', 'title', 'content']

django shellで動作確認

現状API実装できていないので簡単にshellで動作を見てみます。

python manage.py shell
rom django.utils.six import BytesIO
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
from apiv1.serializers import TaskSerializer
from todo.models import Task

# json to object
data = JSONParser().parse(BytesIO('{"title": "title10", "content": "content10"}'.encode()))
serializer = TaskSerializer(data=data)
# validation
serializer.is_valid()
# 登録
serializer.save()
serializer.instance

# 一部更新
serializer.instance.id
task = Task.objects.get(pk='c1478697-aa3f-4627-b47f-653f9751a733')
serializer = TaskSerializer(instance=task, data={'title': 'title100'})
serializer.is_valid()
serializer.save()
serializer.instance

# object to json
task = Task.objects.get(pk='c1478697-aa3f-4627-b47f-653f9751a733')
task
serializer = TaskSerializer(instance=task)
serializer.data
JSONRenderer().render(serializer.data)

ビュー

ビューではリクエストオブジェクトを受け取り、レスポンスオブジェクトを作成して返します。
ビューのクラス内では、上のserializerを活用してpostgetを実装します。
上の内容が理解できていればやっていることを理解できるかと思います。

from django.shortcuts import get_object_or_404
from rest_framework import status, views
from rest_framework.response import Response

from todo.models import Task
from .serializers import TaskSerializer


class TaskCreateListAPIView(views.APIView):
  """ Taskモデルの登録API """
  def post(self, request, *args, **kwargs):
    # Serializerを作成
    serializer = TaskSerializer(data=request.data)
    # バリデーション実行
    serializer.is_valid(raise_exception=True)
    # モデルオブジェクトを登録
    serializer.save()
    # JSON文字列をレスポンスとして返す
    return Response(serializer.data, status.HTTP_201_CREATED)

  def get(self, request, *args, **kwargs):
    """ Taskモデルの一覧取得API """
    # 複数のobjectの場合、many=Trueを指定します
    serializer = TaskSerializer(instance=Task.objects.all(), many=True)
    return Response(serializer.data, status.HTTP_200_OK)

class TaskRetrieveUpdataDestroyAPIView(views.APIView):
  """ Taskモデルのpk APIクラス """

  def get(self, request, pk, *args, **kwargs):
    """ Taskモデルの詳細取得API """
    # モデルオブジェクトを取得
    task = get_object_or_404(Task, pk=pk)
    serializer = TaskSerializer(instance=task)
    return Response(serializer.data, status.HTTP_200_OK)

  def put(self, request, pk, *args, **kwargs):
    """ Taskモデルの更新API """
    task = get_object_or_404(Task, pk=pk)
    serializer = TaskSerializer(instance=task, data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data, status.HTTP_200_OK)

  def patch(self, request, pk, *args, **kwargs):
    """ Taskモデルの更新API """
    task = get_object_or_404(Task, pk=pk)
    # partial=Trueにより、request.dataで指定したデータのみ更新される
    serializer = TaskSerializer(instance=task, data=request.data, partial=True)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data, status.HTTP_200_OK)

  def delete(self, request, pk, *args, **kwargs):
    """ Taskモデルの削除API """
    task = get_object_or_404(Task, pk=pk)
    task.delete()
    return Response(status.HTTP_200_OK)

URLconf

config/urls.pyをまず変更します。

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

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

apiv1/urls.pyも変更します。

from django.urls import path, include
from . import views

app_name = 'apiv1'
urlpatterns = [
  path('tasks/', views.TaskCreateListAPIView.as_view()),
  path('tasks/<pk>/', views.TaskRetrieveUpdataDestroyAPIView.as_view()),
]

python manage.py runserverでテストサーバーを起動し、http://127.0.0.1:8000/api/v1/tasks/にアクセスすると、動作確認ができます。
ここまでで単純なAPIを構築することができました。次のセクションからはより細かいケースを説明していきます。

モデルに依存しないシリアライザ

モデルに依存しない自由な形式のJSONを入出力するAPIを作成したい場合、serializers.Serializerを継承したシリアライザを作成します。

シリアライザ

モデルが関係ない追加するフィールドをserializers.?Fieldで定義します。ここでは、birth_dateとblood_typeを入力にし、current_dateとfortuneを返すシリアライザを作成します。ここで、is_valid()後の出力時にget_current_dateget_fortuneを呼ぶように、serializers.SerializerMethodFieldを使用しています。

import random
from django.utils import timezone
from rest_framework import serializers

class FortuneSerializer(serializers.Serializer):
  """ 今日の運勢を返すためのシリアらいざ """
  birth_date = serializers.DateField()
  blood_type = serializers.ChoiceField(choices=['A', 'B', 'O', 'AB'])
  # 出力時にget_current_date()が呼ばれる
  current_date = serializers.SerializerMethodField()
  # 出力時にget_fortune()が呼ばれる
  fortune = serializers.SerializerMethodField()

  def get_current_date(self, obj):
    return timezone.localdate()

  def get_fortune(self, obj):
    seed = '{}{}{}'.format(
      timezone.localdate(), obj['birth_date'], obj['blood_type']
    )
    random.seed(seed)
    return random.choice(
      ['★☆☆', '★★☆', '★★★']
    )

ビュー

ビューでは、ポストのみ実装しており、単純にデータを受け取った後にis_valid()を実施後のデータを返すように実装しています。
http://127.0.0.1:8000/api/v1/fortune/ にアクセス後、{"birth_date": "1990-01-01", "blood_type": "A"}をPOSTで送信すると運勢を見ることができます。

from .serializers import FortuneSerializer

class FortuneAPIView(views.APIView):
  """ FortuneAPI """
  def post(self, request, *args, **kwargs):
    # Serializerを作成
    serializer = FortuneSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    # JSON文字列をレスポンスとして返す
    return Response(serializer.data, status.HTTP_200_OK)

Foreign keyを使用したモデル

多対一の場合、以下のようにForeign keyを「多」側のテーブルに設けることが一般的です。

import uuid
from django.db import models

class Publisher(models.Model):
  """ Publisher Model """

  class Meta:
    db_table = 'publisher'
    ordering = ['created_at',]
    verbose_name = verbose_name_plural = '出版社'
  
  id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  name = models.CharField(verbose_name="出版社名", max_length=30)
  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)

  def __str__(self):
    return self.name

class Book(models.Model):
  """ Book Model """

  class Meta:
    db_table = 'book'
    ordering = ['created_at',]
    verbose_name = verbose_name_plural = '本'
  
  id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  title = models.CharField(verbose_name="タイトル", max_length=30)
  price = models.IntegerField(verbose_name='価格', null=True)
  publisher = models.ForeignKey(Publisher, verbose_name="出版社名", on_delete=models.SET_NULL, null=True)
  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)

  def __str__(self):
    return self.title

django shellで動作確認

現状API実装できていないので簡単にshellで動作を見てみます。
Foreign keyの場合、ForeignKeyのフィールドを定義した側のクラスには、関連先のモデルオブジェクトのprime keyを扱うための「foreignkeyのfieldname_id」という属性が自動で付与されます。オブジェクト自体を扱うためには、ForeignKeyフィールドにアクセスします。逆に、もう一方側からアクセスするには、「クラス名_set」でアクセスができます。

python manage.py shell
from shop.models import Book, Publisher

# publisherを登録
publisher = Publisher(name='publisher100')
publisher.save()

# リレーションフィールドを合わせて登録
book = Book(title="title1000", price=1500, publisher=publisher)
book.save()

シリアライザ

こちらを参考にしてシリアライザを作成しました。
BookSerializerでは、publisherとpublisher_idが追加されていますが、これはモデルに依存しないシリアライザで見たように、モデルに関係ないフィールドを定義しています。特に、publisher_idはモデル定義にないので、Bookモデルオブジェクトを作る際の処理を変更しています。

class PublisherSerializer(serializers.ModelSerializer):
  """ Serializer for Publisher Model """

  class Meta:
    model = Publisher
    fields = ['id', 'name']

class BookSerializer(serializers.ModelSerializer):
  """ Serializer for Book Model """

  publisher = PublisherSerializer(read_only=True)
  publisher_id = serializers.PrimaryKeyRelatedField(queryset=Publisher.objects.all(), write_only=True)

  class Meta:
      model = Book
      # publisher_id: モデルには存在しない追加する新フィールド
      fields = ['id', 'title', 'price', 'publisher', 'publisher_id']

  def create(self, validated_date):
      # Bookモデルオブジェクトを作る際の関数
      # 作る際にモデル定義にないフィールド(publisher_id)があるので、publisherに置き換えてから削除する
      validated_date['publisher'] = validated_date.get('publisher_id', None)
      del validated_date['publisher_id']

      return Book.objects.create(**validated_date)

ビュー

ビューは普通のモデルの時と同じように実装します。

from rest_framework import status, views, viewsets
from rest_framework.response import Response

from shop.models import Book, Publisher
from .serializers import BookSerializer, PublisherSerializer


class PublisherCreateListAPIView(views.APIView):
  """ Publisherモデルの登録API """
  def post(self, request, *args, **kwargs):
    # Serializerを作成
    serializer = PublisherSerializer(data=request.data)
    # バリデーション実行
    serializer.is_valid(raise_exception=True)
    # モデルオブジェクトを登録
    serializer.save()
    # JSON文字列をレスポンスとして返す
    return Response(serializer.data, status.HTTP_201_CREATED)

  def get(self, request, *args, **kwargs):
    """ Taskモデルの一覧取得API """
    serializer = PublisherSerializer(instance=Publisher.objects.all(), many=True)
    return Response(serializer.data, status.HTTP_200_OK)

class BookCreateListAPIView(views.APIView):
  """ Bookモデルの登録API """
  def post(self, request, *args, **kwargs):
    # Serializerを作成
    serializer = BookSerializer(data=request.data)
    # バリデーション実行
    serializer.is_valid(raise_exception=True)
    # モデルオブジェクトを登録
    serializer.save()
    # JSON文字列をレスポンスとして返す
    return Response(serializer.data, status.HTTP_201_CREATED)

  def get(self, request, *args, **kwargs):
    """ Taskモデルの一覧取得API """
    serializer = BookSerializer(instance=Book.objects.all(), many=True)
    return Response(serializer.data, status.HTTP_200_OK)

シリアライザにバリデーションを追加

シリアライザにバリデーションを追加する方法としては4通りあります。
それぞれ単体/複数のフィールド、実行順が異なります。
http://127.0.0.1:8000/api/v1/tasks/ にアクセスしてPOSTを実行してみると、validatorが機能していることが分かります。

from django.core.validators import RegexValidator
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator

from todo.models import Task

class TaskSerializer(serializers.ModelSerializer):
  """ Serializer for Task Model """

  class Meta:
    # 対象のモデルクラスを指定
    model = Task
    # 利用するモデルのフィールドを指定
    fields = ['id', 'title', 'content']
    # validatorを追加(複数field可能)
    validators = [
      UniqueTogetherValidator(
        queryset = Task.objects.all(),
        fields=('title', 'content'),
        message="titleとcontentでユニークになっている必要があります"
      ),
    ]
    # 単体のfieldに対するvalidator
    extra_kwargs = {
      'title': {
        'validators': [
          RegexValidator(
            r'^D.+$', message="titleは「D」から始めてください"
          ),
        ],
      },
    }

    def validate_title(self, value):
      """ 単体のフィールドに対するvalidator """
      if 'Java' in value:
        raise serializers.ValidationError("titleに「Java」を含めないでください")
      return value

    def validate(self, data):
      """ 複数フィールドのvalidator """
      title = data.get('title')
      content = data.get('content')
      if title and '#' in title and content and '#' in content:
        raise serializers.ValidationError(
          "titleとcontentの両方に「#」が含まれてはいけません"
        )
      return data

クエリ文字列で条件検索

django-filterというライブラリを使用することで簡単に導入できます。
「priceが?円以下」などより詳細なフィルタリングをする場合にも対応できるように、
以下のようにフィルタクラスを定義して使用しています。

実装できたら、http://127.0.0.1:8000/api/v1/book/?price__lte=1500 にアクセスし確認してみます。

from django_filters import rest_framework as filters
from rest_framework import status, views, viewsets
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from shop.models import Book
from .serializers import BookSerializer

class BookFilter(filters.FilterSet):
  """ Bookモデル用のフィルタ """

  # 「price__lte」というキーで「priceが?円以下」という条件でフィルタリング可能に
  price__lte = filters.NumberFilter(field_name='price', lookup_expr='lte')

  class Meta:
    model = Book
    # フィルタリング可能なfieldを指定
    fields = '__all__'

class BookCreateListAPIView(views.APIView):
  """ Bookモデルの登録API """
  def post(self, request, *args, **kwargs):
    # Serializerを作成
    serializer = BookSerializer(data=request.data)
    # バリデーション実行
    serializer.is_valid(raise_exception=True)
    # モデルオブジェクトを登録
    serializer.save()
    # JSON文字列をレスポンスとして返す
    return Response(serializer.data, status.HTTP_201_CREATED)

  def get(self, request, *args, **kwargs):
    """ Taskモデルの一覧取得API """

    # モデルオブジェクトをクエリ文字列を使ってフィルタリングした結果を取得
    filterset = BookFilter(request.query_params, queryset=Book.objects.all())
    if not filterset.is_valid():
      raise ValidationError(filterset.errors)

    serializer = BookSerializer(instance=filterset.qs, many=True)
    return Response(serializer.data, status.HTTP_200_OK)

ページネーション

公式Doc

ページネーションは通常API全体で同じ設定を使用しますが、個別に設定することもできます。
全体に設定する場合はconfig/settings.pyに以下を設定します。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 100
}

個々に設定する場合は以下のようにpagination_classに設定します。

from rest_framework.pagination import LimitOffsetPagination

class StandardResultsSetPagination(LimitOffsetPagination):
    default_limit = 2
    max_limit = 100

class BillingRecordsView(generics.ListAPIView):
    queryset = Billing.objects.all()
    serializer_class = BillingRecordsSerializer
    pagination_class = StandardResultsSetPagination

上記の方法はListAPIViewなどの汎用Viewを使えば適用されるのですが、
APIViewには適用されませんでした。その場合は、以下のようにするようです。
こちらを参考にしました。

from rest_framework.pagination import LimitOffsetPagination

class PaginationHandlerMixin(object):
    @property
    def paginator(self):
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                self._paginator = self.pagination_class()
        else:
            pass
        return self._paginator
    def paginate_queryset(self, queryset):
        
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset, self.request, view=self)
    def get_paginated_response(self, data):
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)

class StandardResultsSetPagination(LimitOffsetPagination):
    default_limit = 2
    max_limit = 100

# Create your views here.
class TaskCreateListAPIView(views.APIView, PaginationHandlerMixin):
  pagination_class = StandardResultsSetPagination
  serializer_class = TaskSerializer

  """ Taskモデルの登録API """
  def post(self, request, *args, **kwargs):
    # Serializerを作成
    serializer = self.serializer_class(data=request.data)
    # バリデーション実行
    serializer.is_valid(raise_exception=True)
    # モデルオブジェクトを登録
    serializer.save()
    # JSON文字列をレスポンスとして返す
    return Response(serializer.data, status.HTTP_201_CREATED)

  def get(self, request, *args, **kwargs):
    """ Taskモデルの一覧取得API """
    tasks = Task.objects.all()
    page = self.paginate_queryset(tasks)
    if page is not None:
      serializer = self.get_paginated_response(self.serializer_class(page, many=True).data)
    else:
      serializer = self.serializer_class(tasks, many=True)
    return Response(serializer.data, status=status.HTTP_200_OK)

PDFをダウンロードするとかカスタムアクションの実装

こちらをそのままコピペ。

import os
from urllib.parse import quote

from django.conf import settings
from django.http import HttpResponse

from rest_framework.views import APIView


class DownloadTermPDF(APIView):

    def get(self, request, *args, **kwargs):
        filename = 'サービス約款.pdf'
        filepath = os.path.join(settings.BASE_DIR, 'static/pdf/term.pdf')
        return _pdf_download_response(filename, filepath)


class DownloadAgreementPDF(APIView):

    def get(self, request, *args, **kwargs):
        filename = 'サービス同意書.pdf'
        filepath = os.path.join(settings.BASE_DIR, 'static/pdf/agreement.pdf')
        return _pdf_download_response(filename, filepath)


def _pdf_download_response(filename, filepath):
    """
    pdfのダウンロードをおこなう
    Args:
        filepath: ファイルのパス
        filename: ファイル名
    Returns:
        HttpResponse: HttpResponse
    """
    with open(filepath, 'rb') as pdf:
        response = HttpResponse(content=pdf)
    response['Content-Type'] = 'application/pdf'
    response['Content-Disposition'] = f"attachment; filename*=UTF-8''{quote(filename.encode('utf-8'))}"
    return response

APIドキュメント

config/settings.pyに以下を設定します。

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

    # 3rd party apps
    'rest_framework',
    'django_filters',
    'drf_spectacular',

    # my apps
    'apiv1.apps.Apiv1Config',
    'todo.apps.TodoConfig',
    'shop.apps.ShopConfig',
]

...

# REST_FRAMEWORK 
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',  # 追加
}

config/urls.pyに以下を記載します。
DEBUG環境時のみドキュメンテーションを見れるようにしています。
URLにアクセスすると綺麗なドキュメントが見れます。

from django.conf import settings
from django.contrib import admin
from django.urls import path, include
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView

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

if settings.DEBUG:
    urlpatterns += [
        path('api/schema/', SpectacularAPIView.as_view(), name='schema'),                                      # 追加
        path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),  # 追加
        path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),              # 追加
    ]

上の設定でも最小限は見れるのですが、今回のようなAPIViewを継承してViewを作成している場合細かい設定をするとより情報を見れます。
詳しくはこちらを参考。

from drf_spectacular.utils import extend_schema

class TaskCreateListAPIView(views.APIView, PaginationHandlerMixin):
  pagination_class = StandardResultsSetPagination
  serializer_class = TaskSerializer

  @extend_schema(
    request=TaskSerializer,
    responses={201: TaskSerializer},
  )
  def post(self, request, *args, **kwargs):
    """ Taskモデルの登録API """
    # Serializerを作成
    serializer = self.serializer_class(data=request.data)
    # バリデーション実行
    serializer.is_valid(raise_exception=True)
    # モデルオブジェクトを登録
    serializer.save()
    # JSON文字列をレスポンスとして返す
    return Response(serializer.data, status.HTTP_201_CREATED)

  @extend_schema(
    responses={200: TaskSerializer},
  )
  def get(self, request, *args, **kwargs):
    """ Taskモデルの一覧取得API """
    tasks = Task.objects.all()
    page = self.paginate_queryset(tasks)
    if page is not None:
      serializer = self.get_paginated_response(self.serializer_class(page, many=True).data)
    else:
      serializer = self.serializer_class(tasks, many=True)
    return Response(serializer.data, status=status.HTTP_200_OK)

Discussion