ゆーじぇいブログ

ゆーじぇいブログ

プログラミングとか色々

DjangoでAPI&Nuxtでフォロー機能を実装

f:id:jyouj:20181124165447p:plain

以前、Djangoでフォロー機能を実装したことがありましたが、今回はDjangoの方でAPIを用意してあげて、Nuxt.jsを使い、実装する方法を紹介します。

<過去記事>
jyouj.hatenablog.com

Djangoでフォロー関係のAPI

rest_framework関連のsettings.pyへの設定は詳しい記事がありますので、そちらをご覧ください。


Django REST Frameworkを使って爆速でAPIを実装する - Qiita


まずはserializers.pyを作成しておきましょう。すでにUserモデルはできていると仮定します。

from rest_framework import serializers

from users.models import User

class UserMiniSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'name', 'username', 'icon', 'about_me')

class UserSerializer(serializers.ModelSerializer):
    followings_count = serializers.SerializerMethodField()
    followers_count = serializers.SerializerMethodField()
    is_following = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = ('id', 'username', 'name', 'icon', 'about_me', 'date_joined', 'updated_at',
                  'followings_count', 'followers_count', 'is_following')

    def get_followings_count(self, obj):
        return obj.get_followings().count()

    def get_followers_count(self, obj):
        return obj.get_followers().count()

    def get_is_following(self, obj):
        user = self.context['request'].user

        if user.is_authenticated:
            return obj in user.get_followings()
        else:
            return False

ログインしている時のみの動作に注意しておきます。

ここで、一つpermissionを作っておきます。

from rest_framework import permissions


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

        return obj == request.user

次にviewsetを作っていきましょう。

from django.shortcuts import render
from rest_framework import viewsets, filters, status, permissions
from rest_framework.response import Response
from rest_framework.decorators import action

from users.models import User, Connection
from .serializers import UserSerializer, UserMiniSerializer
from .permissions import IsMyselfOrReadOnly

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsMyselfOrReadOnly]
    lookup_field = 'username'
    filter_backends = [filters.SearchFilter]
    search_fields = ['name', 'username', 'about_me']

    @action(detail=True, permission_classes=[permissions.IsAuthenticated])
    def follow(self, request, username=None):
        follower = request.user
        following = self.get_object()

        if follower == following:
            return Response({'errors': ['You cannot follow your account']}, status=status.HTTP_400_BAD_REQUEST)

        obj, created = Connection.objects.get_or_create(follower=follower, following=following)

        if (created):
            return Response({'data': ['Follow successfully']})
        else:
            return Response({'errors': ['You already follow']}, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True, permission_classes=[permissions.IsAuthenticated])
    def unfollow(self, request, username=None):
        follower = request.user
        following = self.get_object()

        if follower == following:
            return Response({'errors': ['You cannot unfollow your account']}, status=status.HTTP_400_BAD_REQUEST)

        if not Connection.objects.filter(follower=follower, following=following).exists():
            return Response({'errors': {"You aren't following that account"}}, status=status.HTTP_400_BAD_REQUEST)

        Connection.objects.get(follower=follower, following=following).delete()

        return Response({'data': ['Unfollow successfully']})

    @action(detail=True)
    def followings(self, request, username=None):
        users = self.get_object().get_followings()
        serializer = UserMiniSerializer(users, many=True, context={'request': request})

        return Response(serializer.data)

    @action(detail=True)
    def followers(self, request, username=None):
        users = self.get_object().get_followers()
        serializer = UserMiniSerializer(users, many=True, context={'request': request})

        return Response(serializer.data)

そして、urls.pyを作成し、DjangoでのAPIは完成です。

from django.urls import path, include

from rest_framework.routers import DefaultRouter

from apis.views import UserViewSet

router = DefaultRouter()
router.register('users', UserViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Nuxt側の実装

APIの受け渡しにはaxiosを使います。詳しくは下記をご覧ください。

Setup - Axios Module

それでは、フォロー機能を実装していきましょう。pages/_id/index.vueに書いておきましょう。

<template>
  <div>
     <b-button v-else-if="$auth.loggedIn && user.is_following"
                @click="unfollow()" variant="success">アンフォロー</b-button>

      <b-button v-else-if="$auth.loggedIn && !user.is_following" variant="success" @click="follow()">フォロー</b-button>
  </div>
</template>

もちろんログインしていることが条件です。rest_authは今度記事にします。公式ドキュメントがわかりやすいので、読んでみてもいいかも知れないですね。

<script></script>の方には、

  asyncData(context) {
    return context.$axios
      .$get(`/api/users/${context.params.id}/`)
      .then(res => {
        return { user: res }
      })
      .catch(e => {
        context.error({ statusCode: 404 })
      })
  },
  methods: {
    async follow() {
      await this.$axios.get(`/api/users/${this.user.username}/follow/`)
      this.user.is_following = true
      this.user.followers_count++
    },
    async unfollow() {
      await this.$axios.get(`/api/users/${this.user.username}/unfollow/`)
      this.user.is_following = false
      this.user.followers_count--
    }
  }

認証関連をちゃんと作成できたら動くかな、と思います。

何かありましたら、じぇい👨‍💻 (@jyouj__) | Twitterに!