Python 事件集成中的 QML Calendar 和 Google Calendar API (QML Calendar and Google Calendar API in Python events integration)


問題描述

Python 事件集成中的 QML Calendar 和 Google Calendar API (QML Calendar and Google Calendar API in Python events integration)

我目前正在創建一個 QML 日曆,它可以理想地顯示來自 Google 日曆的事件。

這是我想在 python 中模擬的示例:https://doc.qt.io/qt‑5.9/qtquickcontrols‑calendar‑example.html

現在,一個 python 文件正在從 Google Calendar API 獲取日期和事件摘要,並將它們作為 {date: event summary} 的字典(python 代碼中的 tenevents 變量)返回. 我有一個非常簡單的 QML 窗口,顯示一個日曆和一個矩形。我想單擊日曆上的一個日期,並讓該日期的事件顯示在矩形中,並標記有事件的日期。我想我有我需要的大部分數據,我只是不知道如何使用它。我不確定如何在單擊時提取日期信息,也不確定如何傳遞 python 字典並顯示我想要的內容——我感謝任何有助於文檔的方向和/或指針!

我'已經在下麵包含了我的代碼!

getevents() 返回如下內容:

{'2020‑07‑30T13:00:00‑05:00': 'Buy a Single Olive', '2020‑07‑31T10:00:00‑05:00': 'Jarvis', '2020‑08‑02': 'Peel an Apple', '2020‑08‑04T02:30:00‑05:00': 'Eat Two Crisp Grapes'}

python.py:

from __future__ import print_function
import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import os.path
import os
from PySide2 import QtCore
from PySide2.QtCore import Property, Signal, Slot, QObject, QUrl, QUrlQuery
from PySide2 import QtGui
from PySide2 import QtQml

class CalendarModule(QtCore.QObject):

    def __init__(self):
        super(CalendarModule, self).__init__()
        self.scopes = ['https://www.googleapis.com/auth/calendar']
        self.service = self.use_token_pickle_to_get_service()
    def use_token_pickle_to_get_service(self):
        creds = None
        if os.path.exists('token.pickle'):
            with open('token.pickle', 'rb') as token:
                creds = pickle.load(token)
        # If there are no (valid) credentials available, let the user log in.
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file(
                    'credentials.json', SCOPES)
                creds = flow.run_local_server(port=0)
            # Save the credentials for the next run
            with open('token.pickle', 'wb') as token:
                pickle.dump(creds, token)
        service = build('calendar', 'v3', credentials=creds)
        return service

    @Slot(result='QVariant')
    def getevents(self):
        # Call the Calendar API
        now = datetime.datetime.utcnow().isoformat() + 'Z'  # 'Z' indicates UTC time
        events_result = self.service.events().list(calendarId='primary', timeMin=now,
                                              maxResults=10, singleEvents=True,
                                              orderBy='startTime').execute()
        events = events_result.get('items', [])
        tenevents = {}
        if not events:
            return 'No upcoming events found.'

        for event in events:
            start = event['start'].get('dateTime', event['start'].get('date'))
            tenevents[start] = event['summary']
        return tenevents



def main():
    import os
    import sys

    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    app = QtGui.QGuiApplication(sys.argv)
    engine = QtQml.QQmlApplicationEngine()
    filename = os.path.join(CURRENT_DIR, "calendar2.qml")
    cal = CalendarModule()
    engine.rootContext().setContextProperty("Cal", cal)
    engine.load(QUrl.fromLocalFile(filename))

    if not engine.rootObjects():
        sys.exit(‑1)

    sys.exit(app.exec_())

if __name__ == '__main__':
    print(CalendarModule().getevents())
    main() 

calendar2.qml:

import QtQuick 2.3
import QtQuick.Controls.Styles 1.2
import QtQuick.Layouts 1.11

ApplicationWindow {
    title: qsTr("Calendar")
    width: 700
    height: 400
    visible: true


    RowLayout {
    anchors.fill: parent

        Rectangle {
        id: tasks
        Layout.fillWidth: true
        Layout.fillHeight: true
        height: 400
        width: 150
        color: "white"
        Text {
            anchors.horizontalCenter: parent.horizontalCenter
            font.bold: true
            font.pointSize:14
            text: 'To‑Do'
        }
        }
        Calendar {
        Layout.fillWidth: true
        Layout.fillHeight: true
        frameVisible: true
        style: CalendarStyle {
            gridVisible: false
            dayDelegate: Rectangle {
                gradient: Gradient {
                    GradientStop {
                        position: 0.00
                        color: styleData.selected ? "#111" : (styleData.visibleMonth && styleData.valid ? "#444" : "#666");
                    }
                    GradientStop {
                        position: 1.00
                        color: styleData.selected ? "#444" : (styleData.visibleMonth && styleData.valid ? "#111" : "#666");
                    }
                    GradientStop {
                        position: 1.00
                        color: styleData.selected ? "#444" : (styleData.visibleMonth && styleData.valid ? "#111" : "#666");
                    }
                }

                Label {
                    text: styleData.date.getDate()
                    anchors.centerIn: parent
                    color: styleData.valid ? "white" : "white"
                }

                Rectangle {
                    width: parent.width
                    height: 1
                    color: "#555"
                    anchors.bottom: parent.bottom
                }

                Rectangle {
                    width: 1
                    height: parent.height
                    color: "#555"
                    anchors.right: parent.right
                }
            }
        }
        }
    }
}

參考解法

方法 1:

Before modifying any code you must understand the code and understand that it is different with what you want. And in your case there is a difference: The data is not accessed quickly but you have to make a request that is time consuming and can block the GUI.

So before the above it is better to download the events (perhaps save it in a database like sqlite and use the original example) to have it as a cache and thus minimize the amount of downloads.

Then you must separate the logic: Create a class that exports the information to QML, and another class that obtains it.

Since the information will be reloaded every time there is an event, it is better to use a Loader that redraws the GUI every time the cache is updated.

Note: I have used the official example

main.py

import logging
import os
import pickle
import sys
import threading

from PySide2 import QtCore, QtGui, QtQml

from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request


SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))


logging.basicConfig(level=logging.DEBUG)


class CalendarBackend(QtCore.QObject):
    eventsChanged = QtCore.Signal(list)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._service = None

    @property
    def service(self):
        return self._service

    def updateListEvents(self, kw):
        threading.Thread(target=self._update_list_events, args=(kw,)).start()

    def _update_list_events(self, kw):
        self._update_credentials()

        events_result = self.service.events().list(**kw).execute()
        events = events_result.get("items", [])

        qt_events = []
        if not events:
            logging.debug("No upcoming events found.")
        for event in events:
            start = event["start"].get("dateTime", event["start"].get("date"))
            end = event["end"].get("dateTime", event["end"].get("date"))
            logging.debug(f"From {start} ‑ To {end}:  {event['summary']}")

            start_dt = QtCore.QDateTime.fromString(start, QtCore.Qt.ISODate)
            end_dt = QtCore.QDateTime.fromString(end, QtCore.Qt.ISODate)
            summary = event["summary"]

            e = {"start": start_dt, "end": end_dt, "summary": summary}
            qt_events.append(e)

        self.eventsChanged.emit(qt_events)

    def _update_credentials(self):
        creds = None
        if os.path.exists("token.pickle"):
            with open("token.pickle", "rb") as token:
                creds = pickle.load(token)
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file(
                    "credentials.json", SCOPES
                )
                creds = flow.run_local_server(port=0)
            with open("token.pickle", "wb") as token:
                pickle.dump(creds, token)

        self._service = build("calendar", "v3", credentials=creds)


class CalendarProvider(QtCore.QObject):
    loaded = QtCore.Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._cache_events = []
        self._backend = CalendarBackend()
        self._backend.eventsChanged.connect(self._handle_events)

    @QtCore.Slot("QVariant")
    def updateListEvents(self, parameters):
        d = dict()
        for k, v in parameters.toVariant().items():
            if isinstance(v, QtCore.QDateTime):
                v = v.toTimeSpec(QtCore.Qt.OffsetFromUTC).toString(
                    QtCore.Qt.ISODateWithMs
                )
            d[k] = v
        self._backend.updateListEvents(d)

    @QtCore.Slot(QtCore.QDate, result="QVariantList")
    def eventsForDate(self, date):
        events = []
        for event in self._cache_events:
            start = event["start"]
            if start.date() == date:
                events.append(event)
        return events

    @QtCore.Slot(list)
    def _handle_events(self, events):
        self._cache_events = events
        self.loaded.emit()
        logging.debug("Loaded")


def main():
    app = QtGui.QGuiApplication(sys.argv)

    QtQml.qmlRegisterType(CalendarProvider, "MyCalendar", 1, 0, "CalendarProvider")

    engine = QtQml.QQmlApplicationEngine()
    filename = os.path.join(CURRENT_DIR, "main.qml")
    engine.load(QtCore.QUrl.fromLocalFile(filename))

    if not engine.rootObjects():
        sys.exit(‑1)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

main.qml

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0
import QtQuick.Controls.Styles 1.1
import MyCalendar 1.0

ApplicationWindow {
    visible: true
    width: 640
    height: 400
    minimumWidth: 400
    minimumHeight: 300
    color: "#f4f4f4"

    title: "Calendar Example"

    SystemPalette {
        id: systemPalette
    }

    CalendarProvider {
        id: eventModel
        onLoaded: {
            // reload
            loader.sourceComponent = null
            loader.sourceComponent = page_component
        }
        Component.onCompleted: {
            eventModel.updateListEvents({
                calendarId: "primary",
                timeMin: new Date(),
                maxResults: 10,
                singleEvents: true,
                orderBy: "startTime",
            })
        }
    }

    Loader{
        id: loader
        anchors.fill: parent
        sourceComponent: page_component
    }

    Component{
        id: page_component
        Flow {
            id: row
            anchors.fill: parent
            anchors.margins: 20
            spacing: 10
            layoutDirection: Qt.RightToLeft

            Calendar {
                id: calendar
                width: (parent.width > parent.height ? parent.width * 0.6 ‑ parent.spacing : parent.width)
                height: (parent.height > parent.width ? parent.height * 0.6 ‑ parent.spacing : parent.height)
                frameVisible: true
                weekNumbersVisible: true
                selectedDate: new Date()
                focus: true
                style: CalendarStyle {
                    dayDelegate: Item {
                        readonly property color sameMonthDateTextColor: "#444"
                        readonly property color selectedDateColor: Qt.platform.os === "osx" ? "#3778d0" : systemPalette.highlight
                        readonly property color selectedDateTextColor: "white"
                        readonly property color differentMonthDateTextColor: "#bbb"
                        readonly property color invalidDatecolor: "#dddddd"

                        Rectangle {
                            anchors.fill: parent
                            border.color: "transparent"
                            color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "transparent"
                            anchors.margins: styleData.selected ? ‑1 : 0
                        }

                        Image {
                            visible: eventModel.eventsForDate(styleData.date).length > 0
                            anchors.top: parent.top
                            anchors.left: parent.left
                            anchors.margins: ‑1
                            width: 12
                            height: width
                            source: "images/eventindicator.png"
                        }

                        Label {
                            id: dayDelegateText
                            text: styleData.date.getDate()
                            anchors.centerIn: parent
                            color: {
                                var color = invalidDatecolor;
                                if (styleData.valid) {
                                    // Date is within the valid range.
                                    color = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor;
                                    if (styleData.selected) {
                                        color = selectedDateTextColor;
                                    }
                                }
                                color;
                            }
                        }
                    }
                }
            }

            Component {
                id: eventListHeader

                Row {
                    id: eventDateRow
                    width: parent.width
                    height: eventDayLabel.height
                    spacing: 10

                    Label {
                        id: eventDayLabel
                        text: calendar.selectedDate.getDate()
                        font.pointSize: 35
                    }

                    Column {
                        height: eventDayLabel.height

                        Label {
                            readonly property var options: { weekday: "long" }
                            text: Qt.locale().standaloneDayName(calendar.selectedDate.getDay(), Locale.LongFormat)
                            font.pointSize: 18
                        }
                        Label {
                            text: Qt.locale().standaloneMonthName(calendar.selectedDate.getMonth())
                                  + calendar.selectedDate.toLocaleDateString(Qt.locale(), " yyyy")
                            font.pointSize: 12
                        }
                    }
                }
            }

            Rectangle {
                width: (parent.width > parent.height ? parent.width * 0.4 ‑ parent.spacing : parent.width)
                height: (parent.height > parent.width ? parent.height * 0.4 ‑ parent.spacing : parent.height)
                border.color: Qt.darker(color, 1.2)

                ListView {
                    id: eventsListView
                    spacing: 4
                    clip: true
                    header: eventListHeader
                    anchors.fill: parent
                    anchors.margins: 10
                    model: eventModel.eventsForDate(calendar.selectedDate)

                    delegate: Rectangle {
                        width: eventsListView.width
                        height: eventItemColumn.height
                        anchors.horizontalCenter: parent.horizontalCenter

                        Image {
                            anchors.top: parent.top
                            anchors.topMargin: 4
                            width: 12
                            height: width
                            source: "images/eventindicator.png"
                        }

                        Rectangle {
                            width: parent.width
                            height: 1
                            color: "#eee"
                        }

                        Column {
                            id: eventItemColumn
                            anchors.left: parent.left
                            anchors.leftMargin: 20
                            anchors.right: parent.right
                            height: timeLabel.height + nameLabel.height + 8

                            Label {
                                id: nameLabel
                                width: parent.width
                                wrapMode: Text.Wrap
                                text: modelData["summary"]
                            }
                            Label {
                                id: timeLabel
                                width: parent.width
                                wrapMode: Text.Wrap
                                text: modelData.start.toLocaleTimeString(calendar.locale, Locale.ShortFormat) + "‑" + modelData.end.toLocaleTimeString(calendar.locale, Locale.ShortFormat)
                                color: "#aaa"
                            }
                        }
                    }
                }
            }
        }
    }
}

enter image description here

The full example is here

(by suneyllanesc)

參考文件

  1. QML Calendar and Google Calendar API in Python events integration (CC BY‑SA 2.5/3.0/4.0)

#google-api #Python #qml #pyside2






相關問題

在 PHP 中壓縮 JSON 字符串並在 Javascript 中解壓縮以進行 Google API 的數據庫查詢 (Compress JSON string in PHP and decompress in Javascript for Database query for Google API)

將 Google Places API 與 MonoTouch 一起使用? (Using Google Places API with MonoTouch?)

如何獲取我的 Gmail 帳戶的個人資料圖片? (How to get the profile picture of my Gmail account?)

google.elements.newsShow 顯示時間不起作用 (google.elements.newsShow display Time not working)

gapi.client.load 未調用回調:console.log 中指定了無效或非法字符串錯誤 (gapi.client.load not calling callback: An invalid or illegal string was specified error in console.log)

Google Developer Console 和已安裝的應用 (Google Developer Console and Installed App)

如何從 Google 自定義搜索 API 獲得 100 多個結果 (How to get more than 100 results from Google Custom Search API)

Pandas / Google Analytics API 身份驗證嘗試給我帶來了一個奇怪的 python 錯誤 (Pandas / Google Analytics API authentication attempt throws me a weird python error)

在 alpha 階段使用谷歌云功能進行生產 (Using a google cloud feature in alpha stage for production)

流量中的 Google Maps Distance Matrix API 持續時間添加返回錯誤結果的所有段 (Google Maps Distance Matrix API duration in traffic adding all segments returning wrong result)

Python 事件集成中的 QML Calendar 和 Google Calendar API (QML Calendar and Google Calendar API in Python events integration)

獲取錯誤 {“error”:“invalid_grant”,“error_description”:“令牌已過期或撤銷。” 來自谷歌 oauth2 API (Getting error { "error" : "invalid_grant", "error_description" : "Token has been expired or revoked." } from Google oauth2 API)







留言討論