Django × Reactでハイブリッド株式管理アプリを構築する【株式追加編】

本記事では、DjangoのViewsを使用して、株式情報を取得して前回作成したDBに登録するところまでを実装します。
株式情報の取得にスクレイピングを行っていますが、今回の記事とは関係ないので、一部コードを省略して実装します。
views名や各URLは例なので分かりやすいように設定してください

## URLパターン設定
プロジェクトのurls.py から equity_hub の home/api/register/ へ飛ぶようにします。
```py:config/urls.py
from django.contrib import admin
from django.urls import path
from django.urls.conf import include

urlpatterns = [
    path('', include('equity_hub.urls')),
]
```
```py:equity_hub/urls.py
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('home/api/register/', views.EquityHubRegisterViewAPI.as_view(), name='register'),
]
```
## view設定
equity_hub/urls.py の views.EquityHubRegisterViewAPI.as_view() の部分用に㈱登録用のviewを作成します。
最新の株式情報(銘柄名, 配当率, 現在株価, 業種)を取得してモデルに登録します。
また、デコレーターを使用してログイン状態必須としています。
```py:equity_hub/views.py
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from rest_framework.views import APIView
from .models import EquityHub
from rest_framework.response import Response

@method_decorator(login_required, name='dispatch')
class EquityHubRegisterViewAPI(APIView):
    def post(self, request):
        get_equity_hub = GetEquityHubView() # 株式情報取得クラス
        try:
            stock_name, dividend_yield, current_price, industry = get_equity_hub.search_stock(request.data.get('ticker_symbol'))
            
            if stock_name is not None:
                equity = EquityHub(
                    symbol = request.data.get('ticker_symbol'),
                    name = stock_name,
                    dividend_yield = dividend_yield,
                    price = current_price,
                    shares_owned = 0, # 初期は0固定
                    industry = industry,
                    user = request.user,
                )
                equity.save()
                return Response(status=200)
        except Exception as e:
            return Response(status=400)
```
GetEquityHubView の search_stock は株式最新情報をスクレイピングして返すのですが、今回は省略します。各自でオリジナルを作成してください。

## 登録処理のテスト
作成した EquityHubRegisterViewAPI  が正しく動作するかテストコードを作成します。
```py:equity_hub/tests.py
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase, APIClient
from django.contrib.auth import get_user_model
from equity_hub.models import EquityHub

User = get_user_model()

class EquityHubRegisterViewAPITest(APITestCase):
    def setUp(self):
        # テスト用ユーザーを作成
        self.user = User.objects.create_user(username='testuser', password='password')
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)
        self.url = reverse('register')
        
    def test_register_equity_hub_success(self):
        # ログイン状態をシミュレート
        self.client.login(username='testuser', password='password')

        # テスト用データを設定
        data = {
            'ticker_symbol': '存在する証券コード' # 存在する証券コードを指定
        }
        
        # 正常なPOSTリクエストの送信
        response = self.client.post(self.url, data, format='json')
        
        # レスポンスの内容を検証
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # データベースに登録されているか検証
        self.assertTrue(EquityHub.objects.filter(symbol='存在する証券コード', user=self.user).exists())
    
    def test_register_equity_hub_exception_handling(self):
        # ログイン状態をシミュレート
        self.client.login(username='testuser', password='password')
        
        # データが空の場合や検索結果がない場合をシミュレート
        response = self.client.post(self.url, {}, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
```
実行結果
```sh
> python manage.py test     
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 1.055s

OK
Destroying test database for alias 'default'...
```
Ran 2 tests に対して OK となっていれば成功です。

## 終わりに
以上で指定した証券コードに対して証券情報を取得/登録することができました。

次回は株式情報更新と削除機能の作成を行います。

注意: ウェブスクレイピングは、インターネット上の情報を自動で取得するために非常に便利な手法ですが、ターゲットのサイトに過度な負荷をかけてしまう可能性があります。連続して使用する際は待ち時間を入れるなど注意して実行してください。