DjangoでAPIを実装する方法2(Django REST framework)

DjangoでAPIを実装する方法(Django REST framework)の続きです。

以前作成した以下のモジュールに「Django REST framework」を組み込みます。

DjangoでListViewを用いて検索画面を実装する方法

条件

  • Django 2.1.7
  • Python 3.7.0
  • djangorestframework 3.9.2

実装

settings.py

INSTALLED_APPSに「rest_framework」を追加します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'search.apps.SearchConfig',
    "bootstrap4",
    'rest_framework',  # 追加
]

APIのページネーションを設定するため、以下を追記します。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

urls.py

プロジェクトのurls.py

以下を追記します。

from rest_framework.schemas import get_schema_view

schema_view = get_schema_view(title='ListSample API')

urlpatterns = [
    ・・・
    path('api-auth/', include('rest_framework.urls')),  # API認証
    path('schema/', schema_view),  # APIスキーマ
]

アプリケーションのurls.py

以下を追記します。

from django.urls import include
from rest_framework.routers import DefaultRouter

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register('posts', views.PostViewSet)
router.register('users', views.UserViewSet)

urlpatterns = [
    ・・・
    # APIのルート
    path('api/', include(router.urls)),
]

models.py

以下のようなモデルとします。

「related_name=’posts’,」を追記します。

from django.db import models
from django.urls import reverse
from django.contrib.auth.models import AbstractUser


class CustomUser(AbstractUser):

    def __str__(self):
        return self.username + ":" + self.email


class Post(models.Model):
    """投稿モデル"""
    class Meta:
        db_table = 'post'

    title = models.CharField(verbose_name='タイトル', max_length=255)
    text = models.CharField(verbose_name='内容', max_length=255, default='', blank=True)
    author = models.ForeignKey(
        'search.CustomUser',
        on_delete=models.CASCADE,
        related_name='posts',  # 追記
    )
    created_at = models.DateTimeField(verbose_name='登録日時', auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)

    def __str__(self):
        return self.title + ',' + self.text

    @staticmethod
    def get_absolute_url(self):
        return reverse('search:index')

views.py

以下を追記します。

from search.models import Post
from search.serializers import PostSerializer, UserSerializer
from rest_framework.decorators import api_view
from rest_framework.response import Response
from search.models import CustomUser
from rest_framework import permissions
from search.permissions import IsOwnerOrReadOnly
from rest_framework import renderers
from rest_framework import viewsets
from rest_framework.decorators import action


class PostViewSet(viewsets.ModelViewSet):
    """
    This viewset automatically provides `list`, `create`, `retrieve`,
    `update` and `destroy` actions.

    Additionally we also provide an extra `highlight` action.
    """
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)

    @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        post = self.get_object()
        return Response(post.highlighted)

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


class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    This viewset automatically provides `list` and `detail` actions.
    """
    queryset = CustomUser.objects.all()
    serializer_class = UserSerializer


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'posts': reverse('post-list', request=request, format=format),
    })

serializers.py

アプリケーションのディレクトリに「serializers.py」を新規作成します。
APIのシリアライズおよびリレーションなどを記述します。

from rest_framework import serializers
from search.models import Post
from search.models import CustomUser


class PostSerializer(serializers.HyperlinkedModelSerializer):
    author = serializers.ReadOnlyField(source='author.username')

    url = serializers.HyperlinkedRelatedField(
        view_name="post-detail",
        read_only=True,
        lookup_field='id'
    )

    class Meta:
        model = Post
        fields = ('url', 'id', 'author',
                  'title', 'text', 'created_at', 'updated_at',)


class UserSerializer(serializers.HyperlinkedModelSerializer):
    # posts = serializers.HyperlinkedIdentityField(many=True, view_name='post-detail', read_only=True)
    posts = PostSerializer(many=True, read_only=True)

    url = serializers.HyperlinkedRelatedField(
        view_name="user-detail",
        read_only=True,
        lookup_field='id'
    )

    class Meta:
        model = CustomUser
        fields = ('url', 'id', 'username', 'email', 'posts')

permissions.py

アプリケーションのディレクトリに「permissions.py」を新規作成します。
API処理の許可について記述します。

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the post.
        return obj.owner == request.user

実行結果

APIルート

http://127.0.0.1:8000/api/

Post一覧

http://127.0.0.1:8000/api/posts/

ユーザ一覧

http://127.0.0.1:8000/api/users/

APIスキーマ

http://127.0.0.1:8000/schema/

通常の画面

APIに加えて、通常の画面も使用できます。

サンプルソース

GitHubに当該記事のサンプルソースを公開しています。

https://github.com/kzmrt/list

参考

Django REST framework:Nested relationships

https://www.django-rest-framework.org/api-guide/relations/#nested-relationships

stackoverflow

https://stackoverflow.com/questions/50820373/could-not-resolve-url-for-hyperlinked-relationship-using-view-name-post-detail

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です