【Django REST Framework】外部APIのデータをお気に入りに登録したい

外部APIのデータをお気に入りしていく機能を実装したいと思います。
一応実装できたかなというくらいで、もっと良い書き方があるかもしれませんが、そこは勉強中なのでご容赦ください。。。

前回書いた記事同様、Finaicial Modeling Prepを使用していきます。
[【Django REST Framework】外部APIをAPIViewで取得する](https://qiita.com/kotarott/items/ed644b5d3094ba5695be)

##1.モデルの作成
```Python:models.py
from django.conf import settings

class FavoStock(TimeStampModel):
    profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='favorites')
    symbol = models.CharField(max_length=20, blank=False)
    isin = models.CharField(max_length=12, blank=True)
    vender = models.CharField(max_length=10, blank=True)
    description = models.CharField(max_length=100, blank=False)

    def __str__(self):
        return f"{self.profile.user.username} likes {self.symbol}"

```
本来のお気に入り機能であれば、ForeignKeyFieldやManyToManyFieldで対象のモデルと接続するんでしょうが、今回はデータベースに銘柄リストを登録していないため、普通の登録・削除の機能に近いです。


##2.シリアライザの作成
```Python:models.py
from rest_framework import serializers
from profiles.models import Profile, FavoStock


class FavoStockSerializer(serializers.ModelSerializer):
    profile = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = FavoStock
        exclude = ('updated_at', )
```

#3.ビューの作成
```Python:views.py
class FavoStockListAPIView(mixins.ListModelMixin,
                           generics.GenericAPIView):
    serializer_class = FavoStockSerializer
    permission_classes = [IsAuthenticated, ]

    def get_queryset(self):
        queryset = FavoStock.objects.all()
        request_uuid = self.kwargs.get('uuid')
        return queryset.filter(profile__uuid=request_uuid).order_by('-created_at')

    def get(self, request, uuid):
        return self.list(request)


class FavoStockCreateAPIView(generics.CreateAPIView):
    queryset = FavoStock.objects.all()
    serializer_class = FavoStockSerializer
    permission_classes = [IsAuthenticated, IsOwnFavoStockOrReadOnly]

    def perform_create(self, serializer):
        symbol = self.request.data.get('symbol')
        user_profile = self.request.user.profile
        queryset = self.get_queryset()

        has_user_added = self.queryset.filter(symbol=symbol, profile=user_profile).exists()

        if has_user_added:
            raise ValidationError('already added.')
        
        serializer.save(profile=user_profile)


class FavoStockDestroyAPIView(generics.DestroyAPIView):
    queryset = FavoStock.objects.all()
    serializer_class = FavoStockSerializer
    permission_classes = [IsAuthenticated, IsOwnFavoStockOrReadOnly]
    lookup_field ='symbol'

    def perform_destroy(self, instance):
        symbol = self.kwargs.get('symbol')
        user_profile = self.request.user.profile
        queryset = self.get_queryset()

        has_user_added = self.queryset.filter(symbol=symbol, profile=user_profile).exists()

        if not has_user_added:
            raise ValidationError('not exists.')

        instance.delete()

```
ここはもう少し短くできるだろうなとモヤモヤしています。

ListViewではプロファイルのuuidをパラメータとして渡すことで、そこ人のお気に入り一覧を見ることができます。

Create、Destroyでは自分が登録したレコード以外は操作できないようにしています。
重複などを避けるためにhas_user_addedでレコードのチェックを行っています。

##4.URLの設定
```python:urls.py
    path('addfavorite/', FavoStockCreateAPIView.as_view(), name="add-favorite"),
    path('removefavorite/<str:symbol>/', FavoStockDestroyAPIView.as_view(), name='remove-favorite'),

```
上記をurlpatternsに追加しました。

##（メモ：permissions.py）
```python:permissions.py
class IsOwnFavoStockOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        
        return obj.profile == request.user.profile
```

##結果
↓リスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1182517/8ad59a1f-364a-c2a6-224c-f65c243b0339.png)

↓登録成功
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1182517/f1f087b5-095a-7ae7-2df7-87788309d69f.png)

↓登録失敗（すでに登録済み）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1182517/9e321ff1-c575-c9df-8466-ca8d18ca73c7.png)

##まとめ
とりあえず実装できました。
FavoStockモデルはProfileアプリの中にある一方で、株式の情報取得は別のアプリで行っているなど、まだまだ理解ができていないところが多いですが、少しづつ勉強していきます。

（なんでFavoStockって中途半端な名前にしたんだろう…）