DjangoでGoogle Calendarへアクセス②

こんなことがしたかった

  • 定期的にGoogle Calendarに予定を取りに行きDBにデータを格納したかった

厳密にはもう少し複雑ではあるのだが、Djangoで作った予定管理アプリのようなものに予定を追加したときに、Google Calendarにも同期したかったのである。当記事はGoogle CalendarDjangoの方を書く

GASでイベント時にトリガー追加して、Django側にはAPIを準備してPOSTしてもよかったのだが、今回はDjango側でGoogle Calendarにアクセスしイベントを取りに行くパターンで対応する。

こんな感じ

Djangoのカスタムコマンドを準備し、python manage.py 〜で実行できるようにし、crontabに登録して定期実行することとした。

カスタムコマンドの準備

どうやら、自分のアプリケーション内にmanagementディレクトリを切り、その中で更にcommandsディレクトリを切り、その中にinit.pyとカスタムコマンド用のスクリプトをおけば良いようだ。

以下のような感じ

Project
    app
        management
            commands
                __init__.py
                customcommand.py

customcommand.pyとそれに絡む中身は以下のような感じ。ちなみに細かい要件は以下の通り

※例によって動かなかったらごめんなさい あと、実際に書いたプログラムを一部改変しているので、余計なimportがあるかもしれない

※settings.pyは先日と全く同じなため省略

  • customcommand.py
import datetime
import os.path
import requests
import httplib2

from django.conf import settings
from django.core.management.base import BaseCommand
from pytz import timezone
from app.models import Model
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
from google.auth.transport.requests import Request

class Command(BaseCommand):
    help = 'Google Calendar Import Events'

    # ここが本体
    def handle(self, *args, **kwargs):
        events = self.get_gcal()
        self.insert_db(events)
        self.stdout.write(self.style.SUCCESS('Successfully import Google Calendar'))

    # イベントを取ってくる
    def get_gcal(self):
        # 認証情報を作成
        credentials = ServiceAccountCredentials.from_json_keyfile_name(
            settings.GCAL_PRIVATE_KEY_PATH,
            scopes=settings.GCAL_SCOPES
        )
        http = credentials.authorize(httplib2.Http())
        service = build('calendar', 'v3', http=http)

        # 情報を取得
        now = datetime.datetime.utcnow().isoformat() + 'Z'
        events_result = service.events().list(calendarId=settings.GCAL_CALENDAR_ID, timeMin=now, \
                                        singleEvents=True, orderBy='startTime').execute()
        events = events_result.get('items', [])
        return events

    # DBにデータを入れる
    def insert_db(self,events):
        now = datetime.datetime.now()
        # DB内の情報を取得
        obj = Model.objects.filter(enddt__gte=now)

        for event in events:
            name = event.get("summary")
            startdt = event.get("start").get("dateTime")
            enddt = event.get("end").get("dateTime")
            gcal_eventid = event.get("id")

            # すでにDBに登録されている場合は対象外
            if obj.filter(gcal_eventid=gcal_eventid).exists():
                pass
            # 終日予定は取り込み対象外
            elif startdt is None or enddt is None:
                pass
            # 上記以外は取り込み
            else:
                Model(name=name,startdt=startdt,enddt=enddt,gcal_eventid=gcal_eventid).save()
  • models.py
from django.db import models

class Model(models.Model):
    class Meta:
        db_table = 'model'
        app_label = 'model'

    name = models.CharField(
        verbose_name = '名前',
        blank = False,
        max_length = 64,
    )

    startdt = models.DateTimeField(
        verbose_name = '開始日',
        blank = False,
    )

    enddt = models.DateTimeField(
        verbose_name = '終了日',
        blank = False,
    )

    gcal_eventid = models.CharField(
        verbose_name = 'google calendar eventid',
        blank = True,
        max_length = 256,
        null = True,
    )

これでpython manage.py customcommandを実行すれば、DBにGoogle Calendarのイベントを取り込める。

ちなみに、実行する際はcrontabで1分ごとに動かしている。

crontabの設定

詳細は省くが、1点のみちょっとだけハマったところが。

当然ではあるが、venv環境だとcrontabで単純にpython manage.py customcommandを書いても動かない。

上記のほか、cdとactivate、deactivateを使用し、venv上で実行できるように組む必要がある。

実装してみて

今回は単純に取り込む形を書いたが、予定を移動した場合もDB側に反映させる必要があるので、

Update的な処理をバッチ内に入れる必要があるように思う。

また、今回は1分ごとの実行としたが、リアルタイム性を求めるのであれば別の方法を考える必要があるかもしれない。

参考文献

[Django]カスタムコマンドでバッチ処理をする - Qiita

Django でもバッチ処理がしたい! - Qiita

【Django】カスタムコマンドを使用しcron(crontab)で登録・削除を自動化