Создание события календаря Google с использованием Python из окна QML - PullRequest
0 голосов
/ 07 августа 2020

Я пытаюсь добавить событие в свой api календаря Google через несколько текстовых полей qml на buttonClick. Во-первых, я определенно принимаю конструктивную критику своих методов!

Я не на 100% уверен, где находится моя текущая ошибка, но вот две ошибки, с которыми я столкнулся, которые, как мне кажется, находятся в root из моих проблем, я ценю любые предложения!

Я считаю, что вызывал ошибку при попытке "повторно использовать" self.service в классе AddToCalendar. Это когда я беру переменную экземпляра из другого класса. Моя ошибка заключалась в следующем (моя вторая ошибка заменила эту): Error:'NoneType' object has no attribute 'events' в моем Cal2.qml в строке cal2.createevent(eventinfo.text, eventstart.text, eventend.text)

Моя вторая и самая последняя ошибка (в той же строке) связана с отправкой информации в событие createevent при нажатии кнопки из нескольких текстовых полей. TypeError: createevent() missing 2 required positional arguments: 'eventstart' and 'eventend'

Я включил фрагменты соответствующего кода, дайте мне знать, если еще будет полезно!

Cal2.py

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

class AddToCalendar(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._data = dict()
        self.A = CalendarBackend()
    @QtCore.Slot(str)
    def createevent(self, eventinfo: str, eventstart: str, eventend: str):
        starttime = str(datetime.datetime.strptime(eventstart, "%m/%d/%Y %H:%M:%S"))
        endtime = str(datetime.datetime.strptime(eventend, "%m/%d/%Y %H:%M:%S"))
        try:
            event = {
                'summary': eventinfo,
                'start': {
                    'dateTime':  (starttime[0:10]+"T"+starttime[11:]+"-06:00"),
                    'timeZone': 'America/Chicago',
                },
                'end': {
                    'dateTime': (endtime[0:10]+"T"+endtime[11:]+"-06:00"),
                    'timeZone': 'America/Chicago',
                }
            }
        except:
            pass
        event = self.A._service.events().insert(calendarId='primary', body=event).execute()
        print('Event created: %s' % (event.get('htmlLink')))

if __name__ == "__main__":
    app = QtGui.QGuiApplication(sys.argv)

    QtQml.qmlRegisterType(CalendarProvider, "MyCalendar", 1, 0, "CalendarProvider")
    cal2 = AddToCalendar()
    engine = QtQml.QQmlApplicationEngine()
    engine.rootContext().setContextProperty("cal2", cal2)
    filename = os.path.join(CURRENT_DIR, "Calendar2.qml")
    engine.load(QtCore.QUrl.fromLocalFile(filename))

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

    sys.exit(app.exec_())

Cal2.qml

TextField {
    id: eventstart
    placeholderText: qsTr("Start Time 01/12/2020 14:35:00")
    selectByMouse: true
}
TextField {
    id: eventend
    placeholderText: qsTr("End Time 01/12/2020 16:35:00")
    selectByMouse: true
}
TextField {
    id: eventinfo
    placeholderText: qsTr("Event Name")
    selectByMouse: true
}
Butt {
    id: buttonn
    width: rect.width
    height: rect.height

    Rectangle {
        id: rect
        implicitWidth: rect.width
        implicitHeight: 25
    }

    Text {
        text: "Add Event"
    }


    onTouched: {
        console.log("touched")
        cal2.createevent(eventinfo.text, eventstart.text, eventend.text)
    }

1 Ответ

2 голосов
/ 07 августа 2020

Причины ошибок тривиальны:

  • Первая ошибка указывает, что _service is None, и это очевидно.
  • Вторая ошибка вызвана сигнатурами, согласно python при использовании @Slot (str) это означает, что он получит только один параметр из QML, но в QML вы передаете 3 параметра.

Кажется, что вы скопировали и вставил мое предыдущее решение, не понимая, как оно работает.

В мое предыдущее решение служба - это экземпляр календаря Google, а CalendarBackend - это объект QObject, который позволяет использовать службу без блокировки Qt eventl oop (поэтому используются потоки), а CalendarProvider предоставляет QML только некоторые методы.

import functools
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"]
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))


logging.basicConfig(level=logging.DEBUG)


def qdatetime_to_string(x):
    if isinstance(x, dict):
        for k, v in x.items():
            if isinstance(v, QtCore.QDateTime):
                x[k] = v.toString(QtCore.Qt.ISODate)
            else:
                qdatetime_to_string(v)
    elif isinstance(x, list):
        for i, e in enumerate(x):
            if isinstance(e, QtCore.QDateTime):
                x[i] = e.toString(QtCore.Qt.ISODate)
            else:
                qdatetime_to_string(v)


class Reply(QtCore.QObject):
    finished = QtCore.Signal()

    def __init__(self, func, args=(), kwargs=None, parent=None):
        super().__init__(parent)
        self._results = None
        self._is_finished = False
        self._error_str = ""
        threading.Thread(
            target=self._execute, args=(func, args, kwargs), daemon=True
        ).start()

    @property
    def results(self):
        return self._results

    @property
    def error_str(self):
        return self._error_str

    def is_finished(self):
        return self._is_finished

    def has_error(self):
        return bool(self._error_str)

    def _execute(self, func, args, kwargs):
        if kwargs is None:
            kwargs = {}
        try:
            self._results = func(*args, **kwargs)
        except Exception as e:
            self._error_str = str(e)
        self._is_finished = True
        self.finished.emit()


def convert_to_reply(func):
    def wrapper(*args, **kwargs):
        reply = Reply(func, args, kwargs)
        return reply

    return wrapper


class CalendarBackend(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._service = None

    @property
    def service(self):
        if self._service is None:
            reply = self._update_credentials()
            loop = QtCore.QEventLoop()
            reply.finished.connect(loop.quit)
            loop.exec_()
            if not reply.has_error():
                self._service = reply.results
            else:
                logging.debug(reply.error_str)
        return self._service

    @convert_to_reply
    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)
        return build("calendar", "v3", credentials=creds, cache_discovery=False)

    @convert_to_reply
    def insert(self, **kwargs):
        return self.service.events().insert(**kwargs).execute()


class CalendarProvider(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._backend = CalendarBackend()

    @QtCore.Slot("QVariant")
    def createEvent(self, parameters):
        kw = parameters.toVariant()
        if isinstance(kw, dict):
            qdatetime_to_string(kw)
            reply = self._backend.insert(**kw)
            wrapper = functools.partial(self.handle_finished_create_event, reply)
            reply.finished.connect(wrapper)

    def handle_finished_create_event(self, reply):
        if reply.has_error():
            logging.debug(reply.error_str)
        else:
            event = reply.results
            link = event.get("htmlLink", "")
            logging.debug("Event created: %s" % (link,))
            QtGui.QDesktopServices.openUrl(QtCore.QUrl(link))


if __name__ == "__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_())
import QtQuick 2.15
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.15

import MyCalendar 1.0

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

    title: "Calendar Example"

    CalendarProvider {
        id: provider
    }
    GridLayout{
        anchors.centerIn: parent
        columns: 3
        TextField {
            id: eventstart
            placeholderText: qsTr("Start Time 01/12/2020 14:35:00")
            selectByMouse: true
        }
        TextField {
            id: eventend
            placeholderText: qsTr("End Time 01/12/2020 16:35:00")
            selectByMouse: true
        }
        TextField {
            id: eventinfo
            placeholderText: qsTr("Event Name")
            selectByMouse: true
        }
        Button{
            text: "Create Event"
            Layout.row: 1
            Layout.column: 1
            Layout.alignment: Qt.AlignHCenter
            onClicked: {
                var dt_start = Date.fromLocaleString(Qt.locale(), eventstart.text, "dd/MM/yyyy hh:mm:ss")
                var dt_end = Date.fromLocaleString(Qt.locale(), eventend.text, "dd/MM/yyyy hh:mm:ss")
                if(dt_start.getDate() && dt_end.getDate()){
                    provider.createEvent({
                        calendarId: "primary",
                        body: {
                            summary: eventinfo.text,
                            start: {
                                dateTime: dt_start,
                                timeZone: "America/Chicago",
                            },
                            end: {
                                dateTime: dt_end,
                                timeZone: "America/Chicago",
                            },
                        }
                    })
                }
            }
        }
    }
}
...