Begin implementing staged undo system

This commit is contained in:
Xevion
2021-08-25 04:55:10 -05:00
parent 11c63995b9
commit 3f79bc401b
4 changed files with 86 additions and 21 deletions

View File

@@ -12,6 +12,8 @@ google-auth-httplib2 = "*"
google-auth-oauthlib = "*" google-auth-oauthlib = "*"
dateutil = "*" dateutil = "*"
python-dateutil = "*" python-dateutil = "*"
pytz = "*"
tzlocal = "*"
[requires] [requires]
python_version = "3.7" python_version = "3.7"

View File

@@ -3,12 +3,16 @@ from __future__ import print_function
import datetime import datetime
import os.path import os.path
import traceback import traceback
from typing import Any, Iterator, List, Optional, Tuple from typing import Any, Iterator, List, Optional, Tuple, Union
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem
from dateutil.parser import isoparse
from google.auth.transport.requests import Request from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import Resource, build from googleapiclient.discovery import Resource, build
from tzlocal import get_localzone
# If modifying these scopes, delete the file token.json. # If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/calendar'] SCOPES = ['https://www.googleapis.com/auth/calendar']
@@ -71,6 +75,7 @@ class Calendar(object):
break break
def getEvents(self, calendarID: str) -> List[Any]: def getEvents(self, calendarID: str) -> List[Any]:
"""Retrieves up to 2500 events for a given calendar ordered by occurrence that happen in the future."""
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
events = self.service.events().list(calendarId=calendarID, timeMin=now, events = self.service.events().list(calendarId=calendarID, timeMin=now,
maxResults=2500, singleEvents=True, maxResults=2500, singleEvents=True,
@@ -82,6 +87,50 @@ class Calendar(object):
return [(calendar['id'], calendar['summary']) for calendar in self.getCalendars()] return [(calendar['id'], calendar['summary']) for calendar in self.getCalendars()]
class Event(): class Event(object):
def __init__(self, title, date, time): def __init__(self, summary: str, start: Union[datetime.date, datetime.datetime], end: Union[datetime.date, datetime.datetime],
pass description: Optional[str] = None):
if type(start) != type(end):
raise Exception("Both start and end times need to be either simple dates or advanced datetime objects.")
self.summary, self.start, self.end, self.description = summary, start, end, description
@classmethod
def from_api(cls, event: dict) -> 'Event':
"""Returns a Event object from a Google API Engine item."""
print(event)
return Event(summary=event.get('summary'),
start=isoparse(event['start'].get('dateTime', event['start'].get('date'))),
end=isoparse(event['end'].get('dateTime', event['end'].get('date'))),
description=event.get('description'))
@property
def is_datetime(self) -> bool:
"""Returns true if the Event object is based on full datetime objects instead of simple date objects."""
return type(self.start) is datetime.datetime
@property
def api_start(self) -> dict:
"""Provides a proper object for the 'start' field in the body of a new event."""
if type(self.start) is datetime.date:
return {'date': self.start.strftime('%Y-%m-%d')}
elif type(self.start) is datetime.datetime:
return {'datetime': self.start.astimezone(get_localzone()).isoformat()}
@property
def api_end(self) -> dict:
"""Provides a proper object for the 'end' field in the body of a new event."""
if type(self.end) is datetime.date:
return {'date': self.end.strftime('%Y-%m-%d')}
elif type(self.end) is datetime.datetime:
return {'datetime': self.end.astimezone(get_localzone()).isoformat()}
def fill_row(self, row: int, table: QTableWidget) -> None:
"""Fills a specific row on a QTableWidget object with the information stored in the Event object."""
summaryItem = QTableWidgetItem(self.summary)
summaryItem.setForeground(QtGui.QColor("blue"))
table.setItem(row, 0, summaryItem)
formatString = '%b %d, %Y' if self.start is not None else '%b %d, %Y %I:%M %Z'
table.setItem(row, 1, QTableWidgetItem('Foreign'))
table.setItem(row, 2, QTableWidgetItem(self.start.strftime(formatString)))
table.setItem(row, 3, QTableWidgetItem(self.end.strftime(formatString)))

View File

@@ -4,6 +4,7 @@ from PyQt5.QtWidgets import QDialog, QMainWindow, QTableWidgetItem
from dateutil.parser import isoparse from dateutil.parser import isoparse
from bulk_reminders import api, undo from bulk_reminders import api, undo
from bulk_reminders.api import Event
from bulk_reminders.gui_base import Ui_MainWindow from bulk_reminders.gui_base import Ui_MainWindow
from bulk_reminders.oauth import OAuthDialog from bulk_reminders.oauth import OAuthDialog
@@ -45,8 +46,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
self.eventsView.verticalHeader().hide() self.eventsView.verticalHeader().hide()
self.undoButton.clicked().connect(self.undoEvents) self.undoButton.clicked.connect(self.undo)
self.submitButton.clicked().connect(self.submitEvents) self.submitButton.clicked.connect(self.submit)
# Disable the undo button until undo stages are available # Disable the undo button until undo stages are available
if len(undo.stages) == 0: if len(undo.stages) == 0:
@@ -55,32 +56,25 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.populate() self.populate()
def undo(self) -> None: def undo(self) -> None:
# Get the latest undo stage and delete all events in that stage
latest = undo.stages.pop(0) latest = undo.stages.pop(0)
for entry in latest: for entry in latest:
self.calendar.service.events().delete(calendarId=entry.get('calendarId'), eventId=entry.get('eventId')).execute()
# Disable the undo button until undo stages are available # Disable the undo button until undo stages are available
if len(undo.stages) == 0: if len(undo.stages) == 0:
self.undoButton.setDisabled(True) self.undoButton.setDisabled(True)
def submit(self) -> None:
pass
def populate(self) -> None: def populate(self) -> None:
"""Re-populate the table with all of the events""" """Re-populate the table with all of the events"""
self.events = self.calendar.getEvents(self.currentCalendarID) self.events = [Event.from_api(event) for event in self.calendar.getEvents(self.currentCalendarID)]
self.eventsView.setRowCount(len(self.events)) self.eventsView.setRowCount(len(self.events))
for row, event in enumerate(self.events): for row, event in enumerate(self.events):
print(event) event.fill_row(row, self.eventsView)
summaryItem = QTableWidgetItem(event['summary'])
summaryItem.setData(QtCore.Qt.UserRole)
summaryItem.setForeground(QtGui.QColor("blue"))
self.eventsView.setItem(row, 0, summaryItem)
start, end = event['start'], event['end']
start, end = start.get('date') or start.get('dateTime'), end.get('date') or end.get('dateTime')
start, end = isoparse(start), isoparse(end)
formatString = '%b %d, %Y'if event['start'].get('date') is not None else '%b %d, %Y %I:%M %Z'
self.eventsView.setItem(row, 1, QTableWidgetItem('Foreign'))
self.eventsView.setItem(row, 2, QTableWidgetItem(start.strftime(formatString)))
self.eventsView.setItem(row, 3, QTableWidgetItem(end.strftime(formatString)))
@QtCore.pyqtSlot(int) @QtCore.pyqtSlot(int)
def comboBoxChanged(self, row) -> None: def comboBoxChanged(self, row) -> None:

20
bulk_reminders/undo.py Normal file
View File

@@ -0,0 +1,20 @@
import json
import os
stages = []
def load() -> None:
"""Load data from the undo history file"""
with open('history.json', 'r') as history:
stages = json.load(history)
def save() -> None:
"""Save data to the undo history file."""
with open('history.json', 'w') as history:
stages = json.dump(history)
if os.path.exists('history.json'):
load()