アラフォーから色々始めるリケジョの独り言

アラフォーになって色々始めてみました手探りでがんばります。

djangoでGoogleカレンダーに予定を登録するアプリを作りたい【2】

前回から時間があいてしまって(引っ越しとかで忙しく)、色々忘れてしまってたのだけど、また一歩ずつ。

 

前回はきっと認証がとりあえずできた、ってところで終わってた??

 

  • やりたいこと①予定の登録をできるようにする
  • やりたいこと②予定の一覧を出せるようにする
  • やりたいこと③予定の編集・削除をできるようにする
  • やりたいこと④予定の一覧を他の人のGoogleカレンダーに登録できるようにする
  • やりたいこと⑤ChatGPTと連携したい

みたいな感じで、とりあえずこの土日で①②ができるといいなという感じ。

 

【やりたいこと①予定の登録をできるようにする】

新しく作ったものは以下3つの関数。

ChatGPTに「djangoGoogleカレンダーに予定を登録するプログラムを作りたいです。setting.pyとviews.pyとurls.pyに記載することと、必要なHTMLを教えてください。」という何とも曖昧な聞き方からのスタート。

あとはググったんだけど、参考URLを忘れてしまった・・・

 

def create_event(request):

メインの関数。

認証してるかの確認。してなければauthorizeを呼ぶ。

POSTなら投げられてきたFormの情報をGoogleカレンダーに登録する。

 

def authorize(request):

Googleの認証ページにリダイレクトし、アプリケーションにアクセスを許可するための関数。(by chatGPT)

 

def oauth2callback(request):

これが何かいまだによくわかってない。setting.pyで

LOGIN_URL = '/oauth2callback/'

と入れてるので呼ばれてるもの。

ユーザーがGoogleの認証ページで承認した後にリダイレクトされるコールバック関数。(by chatGPT)

 

 

すんごい色々エラーが出て、それをひとつひとつChatGPTに聞きながら解決していくんだけど、一番時間がかかったのは

'str' object has no attribute 'keys'

これがもうずっと出て、認証してくれないって言う・・・

credential.jsonファイルがうまくセッションに入れられなくて・・・

文字列だとか辞書型だとかの言い回しがchatGPTの言うことがころころ変わって初心者の私は大混乱・・・

chatGPTへの問いかけの仕方が大事なんだなーと実感です。

 

最終的なview.pyはこちら。

from django.shortcuts import render,redirect
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import Flow,InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from django.conf import settings
from django.contrib import messages
from .forms import EventForm
from datetime import datetime
import pytz
import os
import json

credentials_path = os.path.join(settings.BASE_DIR, 'credentials.json')

def create_event(request):
    #認証の確認
    #最初に、セッションにcredentialsが含まれているかどうかをチェックします。これは、ユーザーが事前にGoogleカレンダーへのアクセスを許可したかどうかを確認します。
    #もしcredentialsがセッションに含まれていない場合、authorizeビューにリダイレクトします。これにより、ユーザーはGoogleにログインしてアプリケーションへのアクセスを許可できます。
    if 'credentials' not in request.session:
        return redirect('authorize')
   
    #認証情報の取得:
    #もしcredentialsがセッションに含まれている場合、それを使って認証情報を取得します。
    #from_authorized_user_infoメソッドは、セッションから認証情報を取得し、Credentialsオブジェクトを作成します。
    #Googleカレンダーサービスの作成:
    #認証情報が取得できたら、buildメソッドを使用してGoogleカレンダーサービスを作成します。
    #これにより、GoogleカレンダーAPIにアクセスするためのサービスオブジェクトが作成されます。
    credentials = Credentials.from_authorized_user_info(json.loads(request.session['credentials']))
    service = build('calendar', 'v3', credentials=credentials)

    #フォームの処理:
    #フォームがPOSTリクエストで送信された場合、入力されたデータを使用して新しい予定を作成します。
    #フォームが有効な場合、入力されたデータから予定の情報を取得し、Googleカレンダーに予定を追加します。
    #この際、service.events().insert()メソッドを使用してAPIリクエストを行います。
    #予定が作成された後、成功メッセージを表示し、再度予定を作成するためのフォームを表示します。
    if request.method == 'POST':
        form = EventForm(request.POST)
        if form.is_valid():
            #GoogleAPIに渡すには文字列にしないといけない
            start_date = form.cleaned_data['start_date'].strftime('%Y-%m-%d')
            end_date = form.cleaned_data['end_date'].strftime('%Y-%m-%d')
            print(form.cleaned_data['all_day'])
           
            if form.cleaned_data['all_day']:
                start_datetime = start_date
                end_datetime = end_date
               
                event = {
                    'summary': form.cleaned_data['summary'],
                    'location': form.cleaned_data['location'],
                    'description': form.cleaned_data['description'],
                    'start': {
                        'date': start_datetime,
                        'timeZone': 'Asia/Tokyo',
                    },
                    'end': {
                        'date': end_datetime,
                        'timeZone': 'Asia/Tokyo',
                    },
                }
            else:
                start_time = form.cleaned_data['start_time'].strftime('T%H:%M:%S')
                end_time = form.cleaned_data['end_time'].strftime('T%H:%M:%S')
 
                start_datetime = start_date+start_time
                end_datetime = end_date+end_time
               
                event = {
                    'summary': form.cleaned_data['summary'],
                    'location': form.cleaned_data['location'],
                    'description': form.cleaned_data['description'],
                    'start': {
                        'dateTime': start_datetime,
                        'timeZone': 'Asia/Tokyo',
                    },
                    'end': {
                        'dateTime': end_datetime,
                        'timeZone': 'Asia/Tokyo',
                    },
                }
            try:
                # GoogleカレンダーAPIを呼び出すコード
                service.events().insert(calendarId='primary', body=event).execute()
                messages.success(request, '登録成功!')
                return render(request, 'create_event.html', {'form': form})
                #return redirect('create_event')
            except Exception as e:
                # エラーが発生した場合の処理
                print(f"GoogleカレンダーAPIからエラーが返されました: {e}")
                #print(f"エラーレスポンスの詳細: {e.response}")        
                messages.error(request, 'フォーム形式がおかしい失敗・・・')
                return render(request, 'create_event.html', {'form': form})

        else:
            messages.error(request, '登録失敗・・・')
            return render(request, 'create_event.html', {'form': form})
    else:
        #フォームの表示:
        #GETリクエストの場合、新しい予定を作成するためのフォームを表示します。
        #フォームはEventFormクラスからインスタンス化されます。
        form = EventForm()
    #テンプレートへのデータの渡し方:
    #render関数を使用して、event_create.htmlテンプレートにフォームを渡します。
    #テンプレート内でフォームを使用して、ユーザーが予定の詳細を入力できるようになります。
    return render(request, 'create_event.html', {'form': form})


def authorize(request):
    flow = Flow.from_client_secrets_file(
        credentials_path,
        redirect_uri=settings.GOOGLE_CALENDAR_REDIRECT_URI
        )
    authorization_url, state = flow.authorization_url(access_type='offline', include_granted_scopes='true')
    request.session['state'] = state
    return redirect(authorization_url)


def oauth2callback(request):
    state = request.session.get('state', None)
    flow = Flow.from_client_secrets_file(
        credentials_path,
        state=state,
        redirect_uri=settings.GOOGLE_CALENDAR_REDIRECT_URI)
    flow.fetch_token(authorization_response=request.build_absolute_uri())
   
    request.session['credentials'] = flow.credentials.to_json()    
    return redirect('create_event')



 

で、見た目はともかくとして

予定登録画面

こんなのができました。

 

雑すぎる備忘録・・・

備忘録つけながらだと余計に時間がかかるから並行してできなくて💦

いやほんと時間が溶ける~~~~~本日も気づいたら朝6時になってしまいました。

寝なきゃ~~~

djangoでGoogleカレンダーに予定を登録するアプリを作りたい【1】

DjangoをUdemyの

【徹底的に解説!】Djangoの基礎をマスターして、3つのアプリを作ろう!

でとりあえずかじってみたので、GCPとの連携にチャレンジしたいメモ。

 

【私のレベル】

・100本ノックでPythonを少しかじって、webスクレイピングを何となくと、データ操作を何となく。

・応用情報を学生時代にとって早10数年で、プログラミングとかをやるのはそれ以来ぶり(要はひよっこ以下)

・HTML/CSS/JavaScript等々雰囲気はわかるけどググりながらじゃないと書けない

 

【やりたいこと】

推し関連のイベントの情報をGoogleカレンダーにまとめたい。

登録された情報を任意のGoogleアカウントのカレンダーにエクスポートできるようにしたい。

同じイベントについては情報を更新できるようにしたい。

 

----------------------------

ではスタート。

 

(1)djangoの環境構築

(2)ChatGPTに「djangoGoogleカレンダーに予定を登録するプログラムを作りたい」と聞く

GCP認証情報(クライアント ID、クライアント シークレット)を取得しろとのこと。

(3)以下のサイトを見ながら「クライアントID」と「クライアントシークレット」を発行。(結局これが何なのかは理解してない)

credentials.jsonはmanage.pyと同じフォルダに保存。

dev.classmethod.jp

 

(4)Pythonコードの作成。

 

●ライブラリインストール

pip install google-api-python-client

 

●setting.pyの編集

 ・appの追加(「calenderapp」を作りました)

 ・TEMPLATESにtemplatesフォルダの追加

'DIRS': [BASE_DIR, "templates"],

 ・Googleの認証情報の追加

#Googleの認証情報
GOOGLE_CALENDAR_CLIENT_ID = 'your_client_id'
GOOGLE_CALENDAR_CLIENT_SECRET = 'your_client_secret'
GOOGLE_CALENDAR_REDIRECT_URI = 'your_redirect_uri'

最後のURIって言うのがよくわからず。更にChatGPTに聞く。

すると

GOOGLE_CALENDAR_REDIRECT_URI は、Google Cloud Platform の認証情報設定で指定したリダイレクト URI と一致する必要があります。これは、ユーザーが認証を完了した後にリダイレクトされる URI です。一般的な場合、ローカル環境で開発している場合は、http://localhost:8000 のようなローカルホストの URI を使用できます。ただし、本番環境にデプロイする場合は、実際のドメインやサーバーの URI を使用する必要があります。例えば、https://example.com/google-auth-callback のような URI を使用できます。リダイレクト URI は、Google Cloud Platform の認証情報設定で許可されたものでなければなりません。許可された URI のリストに、開発中や本番環境で使用する URI を追加してください。

とのこと。いったん「http://localhost:8000」で設定しておく。

 

これでsetting.pyはおしまい。

 

●views.pyの編集

ChatGPTの言うままに。

 

# views.py

from django.shortcuts import render
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
import datetime
import pytz

def create_event(request):
    if request.method == 'POST':
        # Google Calendar API にアクセスするための認証フロー
        flow = InstalledAppFlow.from_client_secrets_file(
            'client_secret.json', ['https://www.googleapis.com/auth/calendar.events'])

        creds = flow.run_local_server(port=0)

        # 認証情報をセッションに保存
        request.session['google_token'] = creds.to_json()

        # Google Calendar API のクライアントを構築
        service = build('calendar', 'v3', credentials=creds)

        # イベントの開始時間と終了時間を設定
        start_time = datetime.datetime.now(pytz.utc)
        end_time = start_time + datetime.timedelta(hours=1)

        # イベントのデータ
        event = {
            'summary': 'Test Event',
            'description': 'This is a test event',
            'start': {
                'dateTime': start_time.isoformat(),
                'timeZone': 'UTC',
            },
            'end': {
                'dateTime': end_time.isoformat(),
                'timeZone': 'UTC',
            },
        }

        # イベントをカレンダーに追加
        event = service.events().insert(calendarId='primary', body=event).execute()

        return render(request, 'success.html')
    else:
        return render(request, 'create_event.html')

 

ソースコードの上手い貼り方がわからない・・・)

 

timezoneを「Asia/Tokyo」とかにしてみる。

runserverしてみると、無いModuleが複数あったので、都度pipで補充していく。(この辺も全部ChatGPTに聞きながら)

 

create_event.htmlにPOSTで「登録する」ボタンを作る。

 

プログラムにエラーが出なくなったと思ったら次はGoogle側のエラー。

エラー 400: redirect_uri_mismatch」

って言うのが出た。

 

GCPの方を色々いじってみたけど、結局私はこれで解決した。理由は不明。

note.com

 

まだまだエラーは続く。

次は「このアプリは現在テスト中で、デベロッパーに承認されたテスターのみがアクセスできます。」って出た。

 

これはGCPの「OAuth同意画面」のテストユーザを追加することで解決。

 

これで一応「The authentication flow has completed. You may close this window.」って言うのが出た。

長かった~~~~~

 

 

 

 

 

 

【Python】100本ノックやってます。p‐89

P-089: 売上実績がある顧客を、予測モデル構築のため学習用データとテスト用データに分割したい。それぞれ8:2の割合でランダムにデータを分割せよ。

pip install scikit-learn

して

from sklearn.model_selection import train_test_split

した上で

df_train, df_test = train_test_split(df_sales_customer, test_size=0.2)

【Python】100本ノックやってます。p‐87~p-88

P-087: 顧客データ(df_customer)では、異なる店舗での申込みなどにより同一顧客が複数登録されている。名前(customer_name)と郵便番号(postal_cd)が同じ顧客は同一顧客とみなして1顧客1レコードとなるように名寄せした名寄顧客データを作成し、顧客データの件数、名寄顧客データの件数、重複数を算出せよ。ただし、同一顧客に対しては売上金額合計が最も高いものを残し、売上金額合計が同一もしくは売上実績がない顧客については顧客ID(customer_id)の番号が小さいものを残すこととする。

 

P-088: 087で作成したデータを元に、顧客データに統合名寄IDを付与したデータを作成せよ。ただし、統合名寄IDは以下の仕様で付与するものとする。

・重複していない顧客:顧客ID(customer_id)を設定
・重複している顧客:前設問で抽出したレコードの顧客IDを設定

顧客IDのユニーク件数と、統合名寄IDのユニーク件数の差も確認すること。

 

この二つめちゃくちゃ難しかった、、、何だろうはまった、、、

 

考え方としては

ダブっている人を抜かしたリストを作る。(名寄席顧客データ)

それと元の顧客リストを名前と郵便番号で内部結合すると。

そうするとダブった人のところにも残す人のcustomer_idが入ると。

 

難しくなさそうなのになんかはまった・・・

【Python】100本ノックやってます。p‐84~p-86

P-084: 顧客データ(df_customer)の全顧客に対して全期間の売上金額に占める2019年売上金額の割合を計算し、新たなデータを作成せよ。ただし、売上実績がない場合は0として扱うこと。そして計算した割合が0超のものを抽出し、結果を10件表示せよ。また、作成したデータに欠損が存在しないことを確認せよ。

スムーズな方法になってるかはわからないけど、昨日理解したapplyを使いこなしたくて使ってみた!あまり時間かからずできた!

 

P-085: 顧客データ(df_customer)の全顧客に対し、郵便番号(postal_cd)を用いてジオコードデータ(df_geocode)を紐付け、新たな顧客データを作成せよ。ただし、1つの郵便番号(postal_cd)に複数の経度(longitude)、緯度(latitude)情報が紐づく場合は、経度(longitude)、緯度(latitude)の平均値を算出して使用すること。また、作成結果を確認するために結果を10件表示せよ。

突然簡単になってびっくりだったけど、次へのつながりがあるのか。

 

P-086: 085で作成した緯度経度つき顧客データに対し、会員申込店舗コード(application_store_cd)をキーに店舗データ(df_store)と結合せよ。そして申込み店舗の緯度(latitude)・経度情報(longitude)と顧客住所(address)の緯度・経度を用いて申込み店舗と顧客住所の距離(単位:km)を求め、顧客ID(customer_id)、顧客住所(address)、店舗住所(address)とともに表示せよ。計算式は以下の簡易式で良いものとするが、その他精度の高い方式を利用したライブラリを利用してもかまわない。結果は10件表示せよ。

math.sin、math.cos、math.acosが分かれば問題なし。

mathにはデータフレームは渡せないからapply使わないといけない。

【Python】100本ノックやってます。p‐81~p-83

P-081: 単価(unit_price)と原価(unit_cost)の欠損値について、それぞれの平均値で補完した新たな商品データを作成せよ。なお、平均値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。

平均出して、fillna使う。

[inplace=True]をつけないと反映されないの不思議。要注意。

 

P-082: 単価(unit_price)と原価(unit_cost)の欠損値について、それぞれの中央値で補完した新たな商品データを作成せよ。なお、中央値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。

中央値出してfillna使う。

 

P-083: 単価(unit_price)と原価(unit_cost)の欠損値について、各商品のカテゴリ小区分コード(category_small_cd)ごとに算出した中央値で補完した新たな商品データを作成せよ。なお、中央値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。

小区分コードごとの中央値は出せるけど、それをNaNにどう入れれば・・・

大変だった、、、3~4時間悩んでしまった・・・

先に全部マージして、NaNのところに入れるということが思いつかず。

いまいちapplyの使い方がつかみ切れてなくて。

xをなんとするかが難しかった・・・

【Python】100本ノックやってます。p‐81~p-83

P-081: 単価(unit_price)と原価(unit_cost)の欠損値について、それぞれの平均値で補完した新たな商品データを作成せよ。なお、平均値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。

平均出して、fillna使う。

[inplace=True]をつけないと反映されないの不思議。要注意。

 

P-082: 単価(unit_price)と原価(unit_cost)の欠損値について、それぞれの中央値で補完した新たな商品データを作成せよ。なお、中央値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。

中央値出してfillna使う。

 

P-083: 単価(unit_price)と原価(unit_cost)の欠損値について、各商品のカテゴリ小区分コード(category_small_cd)ごとに算出した中央値で補完した新たな商品データを作成せよ。なお、中央値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。

小区分コードごとの中央値は出せるけど、それをNaNにどう入れれば・・・

大変だった、、、3~4時間悩んでしまった・・・

先に全部マージして、NaNのところに入れるということが思いつかず。

いまいちapplyの使い方がつかみ切れてなくて。

xをなんとするかが難しかった・・・