見出し画像

[GoogleAPI] ワクワクしながら、ChatGPTでサーバーサイドコーディングする!

ChatGPTでサーバーサイドコーディングをしていきます!

最近、DjangoとGoogleAPIを試しているので、そのあたりを使用する前提でChatGPTに色々コーディングをしてもらおうと思います。
ワクワクが止まらないですね!

私は、フレームワークを使用したサーバーサイド経験は10年以上ありますが、Djangoは初級レベルですが、ChatGPTを使うとどの程度効率よくコーディングをできるかを試していきたいと思います。

amazon light sailのサーバーでDjangoを使用し、vimでさくさく作っていきます笑

頭の中だけで考えると疲れるので、図を書きながら整理する

考えたことをメモに書いていき、整理する

考えたことをメモをしていき、フロー図に書きながら何を作るかを考えていきます。頭の中だけで考えると、作業を中断した際にどこまで考えたか忘れてしまったり、内容の整理も視覚的にできるので、メモに書いていくのが良いです。それと頭の中だけで考えるのは疲れますし、、

最近GoogleフォームAPIを試したので、GoogleフォームAPIを使用した何かを作っていこうと思います。

ちなみに上記フローチャートはFigjamで作成しました!


画面や機能をフローに書き、プログラミング対象を整理する

次に、実際にプログラミングするに伴い、必要な画面や機能をフローに書き、プログラミングに必要な箇所を整理していきます。
青色のオブジェクトが、今回のプログラミング対象となります。

今回はChatGPTでどの程度コーディングをできるかを試すのが目的ですので、シンプルなものを開発しようと思い、Googleフォームの回答データを表示する画面を作っていこうと思います。
Googleフォームの機能でスプレットシートで回答データは確認できますが、それの画面版のようなイメージです。


[1] GoogleOauthの同意画面へリダイレクトする処理を作る


以前、GoogleAPIのリクエストの仕方をChatGPTに聞いたところ、正しく回答がもらえなかったので、今回リクエスト内容については明示的に指示を出すようにしました。
APIなど仕様がたびたび変わるものについては、ChatGPT側に最新情報が反映されていない感じですかね。

(それと以前、GoogleAPIのリクエストの仕方を整理するのにかなり時間がかかりましたが、整理済みですので今回はそこは時短できそうです。)


1-1.プロンプト(同意画面へリダイレクトする処理)

Djangoフレームワークで、GoogleOauthの同意画面にリダイレクトするviews.pyを作成してください

定数(定数は大文字。値は空を設定する)
redirect_uri
client_id

同意画面のURL
https://accounts.google.com/o/oauth2/v2/auth

パラメータ
scope=https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/calendar
access_type=offline
include_granted_scopes=true
response_type=code
redirect_uri=上記定数のredirect_uri
client_id=上記定数のclient_id

1-2.ChatGPTからの回答

以下は、要求された要件に基づいて、DjangoフレームワークでGoogle OAuthの同意画面にリダイレクトするviews.pyの例です。定数は大文字で定義され、空の値が設定されます。

from django.shortcuts import redirect
from django.conf import settings

def google_oauth_redirect(request):
    # 定数の設定
    REDIRECT_URI = settings.GOOGLE_OAUTH_REDIRECT_URI
    CLIENT_ID = settings.GOOGLE_OAUTH_CLIENT_ID
    
    # 同意画面のURLの構築
    consent_url = "https://accounts.google.com/o/oauth2/v2/auth?"
    consent_url += f"scope=https://www.googleapis.com/auth/drive%20https://www.googleapis.com/auth/calendar"
    consent_url += "&access_type=offline"
    consent_url += "&include_granted_scopes=true"
    consent_url += "&response_type=code"
    consent_url += f"&redirect_uri={REDIRECT_URI}"
    consent_url += f"&client_id={CLIENT_ID}"
    
    # リダイレクト
    return redirect(consent_url)

このコードでは、settings.pyファイルにGOOGLE_OAUTH_REDIRECT_URIGOOGLE_OAUTH_CLIENT_IDを設定しておく必要があります。それぞれの値は実際の値に置き換えてください。そして、このviews.pyファイルをプロジェクト内の適切な場所に保存し、URLパターンとビューを結びつけることを忘れないでください。



1-3.プロンプト(上記に該当するurls.pyを作成)

上記に該当するurls.pyを作成してください
インポート
from . import views

1-4.ChatGPTからの回答

以下は、urls.pyファイルの例です。これは、google_oauth_redirectビューを適切なURLにマッピングします。

from django.urls import path
from . import views

urlpatterns = [
    path('google/oauth/', views.google_oauth_redirect, name='google_oauth_redirect'),
]

このurls.pyファイルをアプリケーション内の適切な場所に保存してください。そして、プロジェクトのurls.pyファイルでこのアプリケーションのURLをincludeすることを忘れないでください。

1-5.動作確認

プログラムに反映し動作確認したところ、問題なくGoogleOAuthの同意画面にリダイレクトされました!
10分程度、ChatGPTと1、2回のストロークで出来てしまうところが、さすがChatGTPですね!生産性は爆上がりです!!

[a]アクセストークンを保存する仕様を検討する

a-1.アクセストークンを保存する際の案をいくつか出してみる

アクセストークン取得APIから取得したアクセストークンを、何かしらの方法で保存しておかないと、回答データ表示画面を閲覧するたびに毎回GoogleOauth同意画面から遷移しないといけない仕様になってしまうので、どのような方法で保存するかを検討していきます。

おそらくフレームワークのセッション(Cookie)あたりに保存がよさそうな気がしますが、ChatGPTに相談してみます。

a-2.プロンプト(アクセストークンを保存するアイディア)

アクセストークンを保存したいですがどのようなアイディアがありますか?

a-3.ChatGPTからの回答

アクセストークンを保存する方法はいくつかあります。選択する方法は、アプリケーションの要件やセキュリティの考慮によって異なります。いくつかの一般的なアプローチは次のとおりです。

データベースに保存する:
アクセストークンをデータベースに保存し、必要に応じて取り出します。Djangoでは、モデルを使用してデータベースにアクセスできます。この場合、セキュリティを確保するために、アクセストークンを暗号化して保存することが推奨されます。

セッションに保存する:
Djangoのセッションフレームワークを使用して、アクセストークンをセッションに保存することができます。セッションは通常、デフォルトでデータベースに保存されますが、メモリやキャッシュに保存するように設定することもできます。

キャッシュに保存する:
キャッシュシステムを使用して、アクセストークンを一時的に保存することができます。例えば、Djangoのキャッシュフレームワークを使用してアクセストークンを保存できます。

環境変数に保存する:
セキュリティを強化するために、アクセストークンを環境変数に保存することができます。環境変数は、外部からアクセスできないため、セキュリティ上のリスクが低いです。

ファイルに保存する:
アクセストークンをファイルに保存することも可能です。ただし、セキュリティに関しては慎重に管理する必要があります。

これらのアプローチの中で、データベースやセッション、キャッシュを使用する方法が一般的に推奨されます。それぞれのアプローチには利点と注意点がありますので、具体的なプロジェクトの要件やセキュリティ要件に応じて選択してください。

[b]DjangoのセッションについてStudyする

セッションで保存する案が良さそうなので、この案で進めたいと思います。
ですが、Djangoのセッションは使用したことがないので、使用する前にそのあたりのStudyをしていします。

b-1.プロンプト(セッションに保存するコード)

アクセストークンをセッションに保存したいのですが、どのようなコードになりますか

b-2.ChatGPTからの回答

アクセストークンをDjangoのセッションに保存するためには、ビューでセッションにアクセストークンをセットし、他のビューでセッションからアクセストークンを取得する必要があります。以下は、そのようなコードの例です。

# views.py

from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.conf import settings

def store_access_token(request, access_token):
    """
    アクセストークンをセッションに保存する
    """
    request.session['access_token'] = access_token
    return HttpResponse("Access token stored successfully.")

def use_access_token(request):
    """
    セッションからアクセストークンを取得する
    """
    access_token = request.session.get('access_token')
    if access_token:
        # ここでアクセストークンを使用して必要な処理を実行する
        return HttpResponse("Access token retrieved successfully: " + access_token)
    else:
        return HttpResponse("Access token not found in session.")

# 別のビューでアクセストークンをセッションに保存する例
def another_view(request):
    # アクセストークンを取得するなどの処理が行われたとして、access_tokenが得られたとする
    access_token = "example_access_token"
    # アクセストークンをセッションに保存する
    return store_access_token(request, access_token)

上記の例では、store_access_token関数でアクセストークンをセッションに保存し、use_access_token関数でセッションからアクセストークンを取得します。アクセストークンがセッションに保存されていない場合は、適切なエラーメッセージが返されます。また、another_view関数は別のビューからアクセストークンをセッションに保存する方法を示しています。

b-3.プロンプト(セッションの破棄されるタイミング)

上記のセッションは、どのタイミングで破棄されますか

b-4.ChatGPTからの回答

Djangoのセッションは、デフォルトではクライアントがブラウザを閉じるか、セッションの有効期限が切れるまで維持されます。セッションの有効期限は、settings.pyファイルで設定できます。

セッションの有効期限は、SESSION_COOKIE_AGESESSION_EXPIRE_AT_BROWSER_CLOSE設定によって制御されます。SESSION_COOKIE_AGE: セッションクッキーの有効期限を秒単位で指定します。デフォルトは 1209600 秒 (2 週間) です。この設定を変更することで、セッションの有効期限を変更できます。
SESSION_EXPIRE_AT_BROWSER_CLOSE: ブラウザを閉じるとセッションが破棄されるようにします。デフォルトは False です。True に設定すると、ブラウザを閉じるとセッションが破棄されます。これにより、セッションの有効期限が無期限になります。


これらの設定を変更することで、セッションの振る舞いをカスタマイズできます。また、セッションを明示的に削除することも可能です。例えば、request.session.flush()を呼び出すことで、セッション内のすべてのデータを削除し、新しいセッションを開始します。

b-5.プロンプト(セッション保存時の安全性)

Djangoフレームワークで、セッションに値を保存するのは安全ですか?
暗号化はされますか?

b-6.ChatGPTからの回答

Djangoフレームワークでは、セッションデータはデフォルトで暗号化されます。セッションデータはサーバー側で管理され、クライアントとの間でセキュアなクッキーを介してやり取りされます。デフォルトのセッションエンジンである「django.contrib.sessions.backends.db」を使用すると、セッションデータはデータベースに保存されますが、暗号化されます。

データの暗号化はセキュリティを向上させるための重要な手段の一つであり、クライアントとサーバーの間でデータをやり取りする際に、データの機密性を保護するのに役立ちます。Djangoのセッションフレームワークは、セッションデータを安全に管理するための多くのセキュリティ機能を提供しています。

ただし、セッションに機密情報を保存する場合は、追加のセキュリティ対策が必要です。例えば、適切なセッションIDの生成、HTTPSの使用、セッションの期限の適切な設定、セッションハイジャックの防止などが挙げられます。

したがって、Djangoフレームワークを使用してセッションに値を保存することは一般的に安全ですが、セキュリティを確保するために適切な手順を実装することが重要です。

b-7.まとめ

セッション情報は暗号化された状態でDBに保存され、Cookieをキーにその情報を参照しにいくようなイメージでしょうか?

あとはセッションが残らないように、有効期限を気にしつつ、ログアウトなど特定の処理でセッションを破棄するのを忘れないようにしたほうがよさそうですね。
このあたりは他のサーバーサイドの処理でも同じことなのですが。

特に問題なく使用できそうなので、セッションに保存する案で進めたいと思います。

[2] アクセストークンAPIをリクエストし、アクセストークンを取得する処理を作る

アクセストークンの保存方法も決まったので、次の処理を作っていきます。

処理の流れとしては、GoogleOauthの同意画面にて許可ボタンを押下すると、[2]の処理に戻ってくるようなイメージで、アクセストークンを取得するための、認可コード(codeパラメータ)がリクエストパラメータとして渡されます。
この処理では、認可コードを使用しアクセストークンAPIをリクエストする処理を作っていきます。

2-1.プロンプト(同意画面からコールバックされ、アクセストークンAPIをリクエストする処理)

Djangoフレームワークで、GoogleOauthの同意画面からコールバックされるviews.pyを作成してください

パラメータチェック
code
チェックエラーの場合は処理を終了しエラー画面を表示する

アクセストークンAPIをリクエスト
URL
https://accounts.google.com/o/oauth2/token
パラメータ
code=リクエストパラメータcode
client_id=定数のclient_id
client_secret=定数のclient_id
redirect_uri=定数のredirect_uri
grant_type=authorization_code

セッションに保存する
アクセストークン
アクセストークンの有効期限日時
リフレッシュトークン
ブラウザを閉じたらセッションを破棄する

"view_google_form_responses"にリダイレクトする

2-2.ChatGPTからの回答

以下は、要求された要件に基づいて、Google OAuthの同意画面からのコールバックを処理するviews.pyの例です。

import requests
from django.shortcuts import redirect, render
from django.http import HttpResponse
from django.conf import settings
from datetime import datetime, timedelta

def google_oauth_callback(request):
    # コールバックパラメータの取得
    code = request.GET.get('code')

    # codeパラメータのチェック
    if not code:
        return render(request, 'error.html', {'message': 'Error: Missing "code" parameter.'})

    # 定数の設定
    CLIENT_ID = settings.GOOGLE_OAUTH_CLIENT_ID
    CLIENT_SECRET = settings.GOOGLE_OAUTH_CLIENT_SECRET
    REDIRECT_URI = settings.GOOGLE_OAUTH_REDIRECT_URI

    # アクセストークンAPIのURL
    token_url = "https://accounts.google.com/o/oauth2/token"

    # アクセストークンAPIへのリクエストパラメータの設定
    token_params = {
        'code': code,
        'client_id': CLIENT_ID,
        'client_secret': CLIENT_SECRET,
        'redirect_uri': REDIRECT_URI,
        'grant_type': 'authorization_code',
    }

    # アクセストークンAPIへのPOSTリクエスト
    response = requests.post(token_url, data=token_params)

    # レスポンスの処理
    if response.status_code == 200:
        # アクセストークンが正常に取得された場合の処理
        token_data = response.json()
        access_token = token_data.get('access_token')
        refresh_token = token_data.get('refresh_token')
        expires_in = token_data.get('expires_in')
        if access_token:
            # アクセストークン、有効期限、リフレッシュトークンをセッションに保存する
            request.session['access_token'] = access_token
            expiration_time = datetime.now() + timedelta(seconds=expires_in)
            request.session['token_expiry'] = expiration_time.isoformat()
            request.session['refresh_token'] = refresh_token
            request.session.set_expiry(expires_in)
            # ブラウザを閉じたらセッションを破棄する

            # "view_google_form_responses"にリダイレクトする
            return redirect('view_google_form_responses')
        else:
            return render(request, 'error.html', {'message': 'Error: Access token not found in response.'})
    else:
        # エラーが発生した場合の処理
        return render(request, 'error.html', {'message': f'Error obtaining access token. Status code: {response.status_code}'})

このコードでは、Google OAuthの同意画面からコールバックされた際に、受け取った認可コードを使用してアクセストークンを取得し、セッションに保存します。また、アクセストークンの有効期限とリフレッシュトークンもセッションに保存し、ブラウザを閉じたときにセッションを破棄するように設定します。最後に、view_google_form_responsesにリダイレクトします。

2-3.動作確認

特段問題なくアクセストークンが取得できました!
かなり順調に進んでいます!

2-4.エラー時はエラーメッセージのみを画面に表示したほうが検証しやすい

「エラー発生時はエラー画面を表示する」と指示を出していましたが、検証段階だと、エラー画面をつくるのも面倒ですので、エラーメッセージのみを画面に表示したほうが効率よく検証できそうです。
アクセストークンの有効期限が切れてしまうとエラーになってしまいますし、まずはリッチに作り込むよりエラーの内容がわかれば良いかと。

2-5.ログをこまめに表示する

レスポンス内容などは、ログに表示させておくと検証しやすいので、ログ出力の指示も出した方が良いかと思いました。

[3] GoogleDriveAPIをリクエストし、Googleフォーム一覧データを取得する

次の処理は、GoogleDriveAPIをリクエストし、ドキュメントファイルを取得し、その中からGoogleフォームのファイルを抽出し、フォーム名を表示する画面を作っていきます。
特定のフォーム名を選択すると[4]の画面に遷移しフォームの回答データを閲覧できるようなイメージです。

Googleフォームのファイルを抽出は、レスポンスの内容を見ながら、特定の文字列と比較すれば抽出できるかも。といった感じでトライアンドエラーで設計しました。

3-1.プロンプト(GoogleDriveAPIをリクエストする処理を作る)

Djangoフレームワークで、GoogleDriveAPIをリクエストするviews.pyを作成してください

def名
view_google_form_responses

GoogleDriveAPIをリクエスト
https://www.googleapis.com/drive/v3/files

header
'Authorization: Bearer [セッションから取得したアクセストークン]'
header 'Accept: application/json'

JSONレスポンスを画面で使用できるようにcontextに設定してください "mimeType"が、 "application/vnd.google-apps.form"であるもののみを設定する
logger.infoでJSONレスポンスの内容をログに表示する

templete
"APP_TEST/view_google_form_responses.html"

エラー発生時は、HttpResponseを使用し、エラーメッセージのみを画面に表示してください

3-2.ChatGPTからの回答

以下は、要求された仕様に基づいて作成されたGoogle Drive APIをリクエストするviews.pyの例です。

import requests
import logging
from django.http import HttpResponse
from django.shortcuts import render

logger = logging.getLogger(__name__)

def view_google_form_responses(request):
    # セッションからアクセストークンを取得
    access_token = request.session.get('access_token')

    # アクセストークンがない場合はエラーメッセージを表示
    if not access_token:
        return HttpResponse('Error: Access token not found.', status=400)

    # Google Drive APIにアクセスするためのURL
    url = "https://www.googleapis.com/drive/v3/files"

    # リクエストヘッダーの設定
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Accept': 'application/json',
    }

    # Google Drive APIへのGETリクエスト
    response = requests.get(url, headers=headers)

    # リクエストエラーの場合はエラーメッセージを表示
    if response.status_code != 200:
        return HttpResponse(f'Error: Request failed with status code {response.status_code}.', status=400)

    # JSONレスポンスから"mimeType"が "application/vnd.google-apps.form" のものだけを抽出
    files = [file for file in response.json().get('files', []) if file.get('mimeType') == 'application/vnd.google-apps.form']

    # JSONレスポンスをログに表示
    logger.info("Google Drive API response: %s", response.json())

    # コンテキストに設定
    context = {
        'files': files
    }

    # テンプレートをレンダリングして画面に表示
    return render(request, 'APP_TEST/view_google_form_responses.html', context)

このコードでは、Google Drive APIにアクセスしてファイル情報を取得し、条件を満たすファイルの情報だけをコンテキストに設定してテンプレートに渡します。また、JSONレスポンスの内容をログに表示します。もしリクエストが失敗した場合は、エラーメッセージのみを画面に表示します。


3-3.プロンプト(フォーム一覧を表示するhtmlを作る)

bootstrapでフォーム画面のhtmlを作成してください

css、jsは以下をインポートすること https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js

PC、スマートフォンの画面にレスポンシブで対応させること
日本語を使用する
サイト名は「パピプペポ」
フォームの内容はプルダウン一つのみ
フォームの内容は変数とする。
フォームの変数名はfilesとする
フォームの変数は、以下JSON形式のように設定される
{ "kind": "drive#fileList", "incompleteSearch": false, "files": [ { "kind": "drive#file", "mimeType": "application/vnd.google-apps.form", "id": “xxxxx”, "name": "無題のフォーム" }, { "kind": "drive#file", "mimeType": "application/vnd.google-apps.form", "id": “xxxxx”, "name": "無題のフォーム" } ] }
フォームのnameは上記JSONのname
フォームのvalueは上記JSONのid
フォームのパラメータ名はformId
プルダウンの説明として、'回答データを確認したいフォームを選択してください'

確認ボタン押下時にview_google_form_answersにPOSTする
入力値にバリデーションを設定する
画面フッターにダミーのコピーライトをつける

以下は、要求された仕様に基づいて作成されたBootstrapを使用したフォーム画面のHTMLの例です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>フォーム画面</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <h1 class="mt-5">パピプペポ</h1>
        <form action="view_google_form_answers" method="POST">
            {% csrf_token %}
            <div class="mb-3">
                <label for="formSelect" class="form-label">回答データを確認したいフォームを選択してください</label>
                <select class="form-select" id="formSelect" name="formId" required>
                    <option value="">選択してください</option>
                    {% for file in files %}
                        <option value="{{ file.id }}">{{ file.name }}</option>
                    {% endfor %}
                </select>
                <div class="invalid-feedback">
                    フォームを選択してください。
                </div>
            </div>
            <button type="submit" class="btn btn-primary">確認</button>
        </form>
        <p class="mt-3">Copyright &copy; 2024 パピプペポ. All rights reserved.</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

このHTMLコードでは、Bootstrapのクラスを使用してフォーム画面を作成しています。フォームのセレクトボックス内には、JSON形式で提供されたファイルリストが反映されています。フォームの送信時には、view_google_form_answersにPOSTリクエストが送信されます。また、フォームのバリデーションも設定されています。

3-4.JSONの形式は、どのように指示すべきか?

JSON変数をhtmlから参照する際に、JSONの形式をどのように指示出すか迷いましたが、サンプルのJSONレスポンスをChatGPTに伝えた上で、参照するフィールドを指示したところ、上手くいきました。
ただJSONがシンプルな構造であればそれでも伝わるかと思いますが、複雑な構造だとダメでしたので、このあたりの試行錯誤は次の[4]のステップで話します。

3-5.動作確認

このあたりのロジックから 少しChatGPTとのストロークが増えましたが、1時間以内で、Googleフォーム名を画面に表示させることができました。
プルダウンを選択すると、フォーム名称が表示される形となります。

3-6.処理が複雑だと、ChatGPTとのストロークも増える

処理をする内容が多くなると1発では完成せず、動作確認しつつ都度ChatGPTで修正版の指示を出していくような流れで作っていきました。

それとステップ[3]からはhtmlも増えましたしね。表示仕様については指示をだしましたが、デザインについては時間がかかりそうだったので、ほぼノータッチです。。

htmlで、POSTするAction箇所がURLタグで作成する旨の指示がなかったりでエラーになることもありました。他にもあった気がしますが。

[4] Googleフォームの回答データを表示する


最終ステージです!
GoogleフォームAPIをリクエストし、回答データを画面に表示するだけの処理ですが、APIからのレスポンスされるJSONデータの構造が複雑で、この部分をChatGPTに伝えるところで苦労しました。

4-1.プロンプト(GoogleFormsAPIをリクエストし、回答データを画面に表示する)

Djangoフレームワークで、GoogleFormsAPIをリクエストし、レスポンス内容を画面に表示するviews.pyを作成してください

def名
view_google_form_answers

リクエストパラメータ
formId

質問内容を取得
GoogleFormsAPIをリクエスト
https://forms.googleapis.com/v1/forms/[リクエストパラメータformId]'
header 'Authorization: Bearer [セッションから取得したアクセストークン]'
header 'Accept: application/json'
logger.infoでJSONレスポンスの内容をログに表示する

回答内容を取得
GoogleFormsAPIをリクエスト
https://forms.googleapis.com/v1/forms/[リクエストパラメータformId]/responses
header 'Authorization: Bearer [セッションから取得したアクセストークン]'
header 'Accept: application/json'
logger.infoでJSONレスポンスの内容をログに表示する

JSONレスポンスを画面で使用できるようにcontextに設定してください

templete
"APP_TEST/view_google_form_answers.html"

エラー発生時は、HttpResponseを使用し、エラーメッセージのみを画面に表示してください

4-2.ChatGPTからの回答

以下は、要求された仕様に基づいて作成されたGoogle Forms APIをリクエストし、レスポンス内容を表示するviews.pyの例です。

import requests
import logging
from django.http import HttpResponse
from django.shortcuts import render

logger = logging.getLogger(__name__)

def view_google_form_answers(request):
    # リクエストパラメータからフォームIDを取得
    form_id = request.GET.get('formId')

    # フォームIDがない場合はエラーメッセージを表示
    if not form_id:
        return HttpResponse('Error: formId parameter is required.', status=400)

    # Google Forms APIにアクセスするためのURL
    form_info_url = f"https://forms.googleapis.com/v1/forms/{form_id}"
    form_responses_url = f"https://forms.googleapis.com/v1/forms/{form_id}/responses"

    # リクエストヘッダーの設定
    headers = {
        'Authorization': f'Bearer {request.session.get("access_token")}',
        'Accept': 'application/json',
    }

    # Google Forms APIからフォーム情報を取得
    form_info_response = requests.get(form_info_url, headers=headers)

    # リクエストエラーの場合はエラーメッセージを表示
    if form_info_response.status_code != 200:
        return HttpResponse(f'Error: Failed to fetch form info with status code {form_info_response.status_code}.', status=400)

    # Google Forms APIから回答情報を取得
    form_responses_response = requests.get(form_responses_url, headers=headers)

    # リクエストエラーの場合はエラーメッセージを表示
    if form_responses_response.status_code != 200:
        return HttpResponse(f'Error: Failed to fetch form responses with status code {form_responses_response.status_code}.', status=400)

    # レスポンス内容をログに表示
    logger.info("Form info response: %s", form_info_response.json())
    logger.info("Form responses response: %s", form_responses_response.json())

    # フォーム情報と回答情報をコンテキストに設定
    context = {
        'form_info': form_info_response.json(),
        'form_responses': form_responses_response.json(),
    }

    # テンプレートをレンダリングして画面に表示
    return render(request, 'APP_TEST/view_google_form_answers.html', context)

このコードでは、Google Forms APIを使用してフォーム情報と回答情報を取得し、それらをコンテキストに設定してテンプレートに渡します。また、リクエストが失敗した場合は、エラーメッセージのみを画面に表示します。

4-3.仕様が複雑な場合は、人力でコーディングしたほうがよさそう

上記のロジックのままだと、JSONデータの構造がそのままですのでhtml側が複雑になってしまいます。ですのでhtml側で参照しやすくするため、views.py側で加工することにしました。
ケースバイケースですが、仕様が複雑な箇所は自分でロジックを組んでしまった方が早い気もします。

ちなみに、ステップ[3]で実施した、サンプルレスポンスをChatGPTに伝える作戦でJSON形式を解釈してくれるかも。と思い試してみましたが、JSON形式が複雑であったせいか、サンプル通り解釈されませんでした。。


4-4.プロンプト(回答データを表示するhtmlを作る)

bootstrapで表のhtmlを作成してください

css、jsは、headタグに以下をインポートすること
https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js

PC、スマートフォンの画面にレスポンシブで対応させること
bootstrapのグリッドカラムは10とする
日本語を使用する
ヘッダーに「パピプペポ」と表示し、背景色は、未設定とする

フォーム選択画面へ戻るリンクを用意し、ヘッダーの下に配置し、bootstrapのcustom-linkのcssを使用する
上記リンクは、urlタグを使用し、'APP_TEST:view_google_form_responses'へリンク

フォーム名は変数documentTitleを表示する
表の中身は変数を表示する 表の見出しの背景色は、bootstrapの青色とする
表のtheadタグは、list変数q_listをループして表示する
表のtbodyタグは、list変数res_listをループし、要素であるlist変数qa_listでループし表示する

フッターには、コピーライトを設置し、背景色は未設定とする

4-5.ChatGPTからの回答

以下は、要件に基づいてBootstrapを使用して作成したHTMLコードです。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Bootstrap Table</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<!-- ヘッダー -->
<header class="py-4">
  <div class="container">
    <h1 class="mb-0">パピプペポ</h1>
    <!-- フォーム選択画面へ戻るリンク -->
    <a href="{% url 'APP_TEST:view_google_form_responses' %}" class="btn btn-link text-decoration-none">フォーム選択画面へ戻る</a>
  </div>
</header>

<div class="container mt-4">
  <!-- フォーム名 -->
  <h2>{{ documentTitle }}</h2>

  <!-- 表示テーブル -->
  <div class="table-responsive">
    <table class="table table-bordered">
      <thead class="table-primary">
        <tr>
          <!-- theadをループして表示 -->
          {% for header in q_list %}
            <th>{{ header }}</th>
          {% endfor %}
        </tr>
      </thead>
      <tbody>
        <!-- res_listをループしてtbodyを生成 -->
        {% for response in res_list %}
          <tr>
            <!-- qa_listをループしてセルを生成 -->
            {% for qa in response %}
              <td>{{ qa }}</td>
            {% endfor %}
          </tr>
        {% endfor %}
      </tbody>
    </table>
  </div>
</div>

<!-- フッター -->
<footer class="py-4 text-center">
  <div class="container">
    &copy; 2024 パピプペポ
  </div>
</footer>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

このHTMLコードでは、ヘッダーには背景色が未設定となり、フォーム選択画面へのリンクはBootstrapのbtnクラスを使用してスタイルが適用されます。表の見出しはBootstrapの青色となり、フッターには背景色が未設定のコピーライトが配置されています。

4-6.動作確認

やっと完成しました!
この最終ステージを完成させるのにトータルで5時間ぐらいがかかってしまいました。。

4-7.Googleフォームは質問形式が複数あり、レスポンスのパターンが多いため、処理が複雑だった

そもそもGoogleフォームは汎用的に質問内容をカスタマイズできるので、その分レスポンスのパターンも多く、処理が複雑になります。(そのパターン分指示を出す必要があります。)

質問形式が変わると返却されるレスポンスのフィールド項目が変わったり、未回答だとフィールドごとなかったりと、考慮すべき点が多く結構大変でした。

おわりに

最後まで読んでいただきありがとうございます!

指示出しを考えるのに苦労したのと、思った通りの回答が返ってこず、指示内容を何度も修正したり、人力で部分的にロジック修正したりで、試行錯誤の連続でした。
今回は結構時間がかかったものの、次回以降で使える内容も多く、得られるものは大きかったとおもます。

その他

(一旦エラー画面や画面デザインは置いておいて)
設計をしていると色々とカスタマイズしたくなります。

アクセストークンは有効期限が切れるものですので、期限が切れたらリフレッシュトークンAPIをリクエストし、再度アクセストークンを取得することで、同意画面からやり直さなくて済みます。

またAPIのレスポンス内容をDBに保存し、一定時間が経過するまでAPIリクエストをせずにDBから情報を取得することで、APIのリクエスト回数を節約できます。

などなど色々とやりたくなりますね。

おまけ

最近、ChatGPTを使用し、色々なことを模索しています。
もしよければ、以下の記事も見て頂けると嬉しいです!


いいなと思ったら応援しよう!