From b2b447e39237737abedf3c78f684837f666d317a Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 26 Apr 2021 22:42:56 +0300 Subject: [PATCH 1/7] Implemented most of the api placeholders --- frontend/api.py | 111 +++++++++++++++++++++++-- frontend/api_demo.py | 37 ++++----- frontend/user.py | 186 ++++++++++++++++++++++++++++++++---------- frontend/workspace.py | 25 +++++- 4 files changed, 286 insertions(+), 73 deletions(-) diff --git a/frontend/api.py b/frontend/api.py index e180c72..f3c5f0d 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -13,10 +13,10 @@ API_TODO_ITEMS_DELETE = "api/todo_items/{0}/" API_LISTS_LIST = "api/lists/" API_LISTS_CREATE = "api/lists/" -API_LISTS_READ = "lists/{0}/" -API_LISTS_UPDATE = "lists/{0}/" -API_LISTS_PARTIAL_UPDATE = "lists/{0}/" -API_LISTS_DELETE = "lists/{0}/" +API_LISTS_READ = "api/lists/{0}/" +API_LISTS_UPDATE = "api/lists/{0}/" +API_LISTS_PARTIAL_UPDATE = "api/lists/{0}/" +API_LISTS_DELETE = "api/lists/{0}/" API_TOKEN = "api/token/" @@ -101,7 +101,44 @@ class UserApi(object): headers=self._access_token_(), ) ) + + def lists_delete(self, id): + """ + Auth required + + Deletes a to-do list by id + + Parameters + ---------- + id: to-do list id to delete + """ + return UserApi._raise_or_return_( + requests.delete( + url=self.get_api(API_LISTS_DELETE.format(id)), + headers=self._access_token_(), + ) + ) + + def lists_update(self, title, id): + """ + Rename a new to-do list + Auth required + Parameters + ---------- + title : str + New name for a list. + id : int + + """ + return UserApi._raise_or_return_( + requests.put( + url=self.get_api(API_LISTS_UPDATE.format(id)), + json={"title": title}, + headers=self._access_token_(), + ) + ) + def todo_items_list(self, **argv): """ List all the exsiting to-do items. @@ -118,7 +155,68 @@ class UserApi(object): url=self.get_api(API_TODO_ITEMS_LIST), headers=self._access_token_(), params=argv ) ) + + def todo_items_create(self, parent, text="Note"): + """ + Create a new to-do item + Auth required + Parameters + ---------- + parent : id of parent list + text : str, optional + New note. The default is "Note". + + """ + return UserApi._raise_or_return_( + requests.post( + url=self.get_api(API_TODO_ITEMS_CREATE), + json={"text": text, "parent":parent, "finished":False}, + headers=self._access_token_(), + ) + ) + + def todo_items_delete(self, id): + """ + Auth required + + Deletes a to-do item by id + + Parameters + ---------- + id: to-do item id to delete + """ + return UserApi._raise_or_return_( + requests.delete( + url=self.get_api(API_TODO_ITEMS_DELETE.format(id)), + headers=self._access_token_(), + ) + ) + + def todo_items_update(self, id, text, finished, parent): + """ + Rename a new to-do list + Auth required + + Parameters + ---------- + id : int + Note id + text : str + New note for the item. + finished : bool + New state for the item + parent : int + Parent list id + """ + return UserApi._raise_or_return_( + requests.put( + url=self.get_api(API_TODO_ITEMS_UPDATE.format(id)), + json={"text": text, "finished":finished, "parent":parent}, + headers=self._access_token_(), + ) + ) + # def create(self, title="Untitled"): # """ # Create a new to-do list @@ -215,4 +313,7 @@ class UserApi(object): @staticmethod def _raise_or_return_(response): response.raise_for_status() - return response.json() + try: + return response.json() + except: + return response.content diff --git a/frontend/api_demo.py b/frontend/api_demo.py index f79bbc3..ee1959b 100644 --- a/frontend/api_demo.py +++ b/frontend/api_demo.py @@ -1,9 +1,10 @@ +import numpy as np from user import User def print_lists(lists): for item in lists: - print(f"List: '{item}'", f"Id: {item.id}", "|", "|".join([str(x) for x in item.items])) + print(f"List: '{item.title}'", f"Id: {item.id}", "|", "|".join([str(x) for x in item.items_])) DEFAULT_URL = "http://127.0.0.1:8000" @@ -12,37 +13,35 @@ user = User(url=DEFAULT_URL) user.auth("root", "root") # Fetch existing lists: -lists = user.fetchUserLists() -print("Fecthing...") -print_lists(lists) - -# Remove user list by id: -user.removeUserList(5) -lists = user.fetchUserLists() -print(f"Removing {5}...") -print_lists(lists) +print_lists(user.fetchUserLists()) # Append a new list to user: print("Appending list...") scroll = user.appendUserList(title="a new list!") -print_lists(lists) +print_lists(user.fetchUserLists()) + +# Remove user list by id: +i = user.lists_[0].id +print(f"Removing {i}...") +user.removeUserList(i) +print_lists(user.fetchUserLists()) # Modify list 0: print("Modifyng list...") -lists[0].modify(title="A new title") -print_lists(lists) +user.lists_[0].modify(title=f"A new title {np.random.random()}") +print_lists(user.fetchUserLists()) # Append item to list: print("Appending item to last list...") -item = lists[-1].append(text="this is an item") -print_lists(lists) +item = user.lists_[-1].append(text="this is an item") +print_lists(user.fetchUserLists()) # Modifying item print("Modifyng appended item...") item.modify(finished=True, text="this is an updated item") -print_lists(lists) +print_lists(user.fetchUserLists()) # Removing item at 0 -print("Removing item 0 from list 0...") -lists[0].remove(0) -print_lists(lists) +print("Removing last item from last list...") +user.lists_[-1].remove(-1) +print_lists(user.fetchUserLists()) diff --git a/frontend/user.py b/frontend/user.py index 5cdff8e..51fab7d 100644 --- a/frontend/user.py +++ b/frontend/user.py @@ -1,85 +1,135 @@ import os +from types import SimpleNamespace + from datetime import datetime + +import numpy as np + from api import UserApi +LIST_UPDATEBLE = ["title"] +TODO_ITEM_UPDATEBLE = ["text", "finished"] +UPDATE_ERROR = "Failed to update property: {0}" + +DATETIME_STR = "%Y-%m-%dT%H:%M:%S.%fZ" + +def bad_arguments(x, d): + return list((set(x) - set(d))) class ToDoList(object): - def __init__(self, id, title, created_at=None, items=[], parent=None): + def __init__(self, id, title, created_at=None, items=None, + parent=None, user=None): self.id = id self.title = title - self.items = items - self.created_at = created_at + self.items_ = [] if items is None else items + if type(created_at) is datetime: + self.created_at = created_at + else: + self.created_at = datetime.strptime(created_at, DATETIME_STR) + self.user = user def __iter__(self): - for item in self.items: + for item in self.items_: yield item def __getitem__(self, index): - return self.items[index] + return self.items_[index] def __len__(self): - return len(self.items) + return len(self.items_) def __str__(self): return f"[{self.id}] {self.title}" def index(self, value): - return self.items.index(value) - - # ToDo + return self.items_.index(value) + def remove(self, index): - self.items.remove(self.items[index]) - self.sync() - - # ToDo + """ + Remove item at index from db + """ + item = self.items_[index] + self.items_.remove(item) + item.dispose() + def append(self, text): - item = ToDoItem(id=None, text=text, created_at=datetime.now()) - self.items.append(item) - item.sync() - self.sync() - return item + """ + Add a new item to db + """ + if "DEBUG" in os.environ: + created_item = ToDoItem(id=np.random.randint(100, 1000), text=text, user=self.user) + else: + created_item = self.user.todo_items_create(parent=self.id, text=text) + created_item = ToDoItem(**created_item, user=self.user) + self.items_.append(created_item) + return created_item def modify(self, **argv): + bad = bad_arguments(argv.keys(), LIST_UPDATEBLE) + if len(bad) > 0: + raise RuntimeError(UPDATE_ERROR.format(bad[0])) for key, value in argv.items(): setattr(self, key, value) self.sync() - - # ToDo + + def dispose(self): + print(f"To-do list id '{self.id}' is being disposed of...") + if "DEBUG" in os.environ: + return + for item in self.items_: + item.dispose() + self.user.lists_delete(self.id) + def sync(self): - # ToDo send request or store in form print(f"Item '{self}' is being synchronized...") + if "DEBUG" in os.environ: + return + self.user.lists_update(title=self.title, id=self.id) + class ToDoItem(object): - def __init__(self, id, text, finished=False, created_at=None, parent=None): + def __init__(self, id, text, finished=False, created_at=None, + parent=None, user=None): self.id = id self.text = text self.finished = finished - self.created_at = created_at + if type(created_at) is datetime: + self.created_at = created_at + else: + self.created_at = datetime.strptime(created_at, DATETIME_STR) + self.parent = parent + self.user = user def __str__(self): return f"[{self.id}] {self.text}" def modify(self, **argv): + bad = bad_arguments(argv.keys(), TODO_ITEM_UPDATEBLE) + if len(bad) > 0: + print(argv) + raise RuntimeError(UPDATE_ERROR.format(bad[0])) for key, value in argv.items(): setattr(self, key, value) self.sync() - - # ToDo - def sync(self): - # ToDo send request or store in form - print(f"Item '{self}' is being synchronized...") - - -class User(UserApi): - def auth(self, user, passwd): + + def dispose(self): + print(f"To-do item id '{self.id}' is being disposed of...") if "DEBUG" in os.environ: return - UserApi.auth(self, user, passwd) - + self.user.todo_items_delete(self.id) + # ToDo - items = [ + def sync(self): + print(f"Item '{self}' is being synchronized...") + if "DEBUG" in os.environ: + return + self.user.todo_items_update(id=self.id, text=self.text, finished=self.finished, parent=self.parent) + + +def make_debug_lists(): + return [ ToDoList( id=i, title=f"List {i}", @@ -92,17 +142,63 @@ class User(UserApi): for i in range(10) ] - # ToDo +class User(UserApi): + + def auth(self, user, passwd): + """ + Basic authentification + """ + if "DEBUG" in os.environ: + return + UserApi.auth(self, user, passwd) + + # Storing lists - mostly for debug purposes + lists_ = make_debug_lists() + def fetchUserLists(self): - return self.items + """ + Fetch existing user lists from the server + returns: fetched list of ToDoList sorted by creation datetime + """ + print("Fetching lists...") + if "DEBUG" in os.environ: + return self.lists_ + user_lists = self.lists_list()["results"] + user_items = self.todo_items_list()["results"] + toDoLists = {x["id"]:ToDoList(**x, user=self) for x in user_lists} + toDoItems = [ToDoItem(**x, user=self) for x in user_items] + for toDoItem in toDoItems: + # Catching stray items + if not hasattr(toDoItem, "parent"): + toDoItem.dispose() + continue + toDoLists[toDoItem.parent].items_.append(toDoItem) + for toDoList in toDoLists.values(): + toDoList.items_ = sorted(toDoList.items_, key=lambda x: x.created_at) + self.lists_ = sorted(toDoLists.values(), key=lambda x: x.created_at) + return self.lists_ - # ToDo def removeUserList(self, id): - self.items = [item for item in self.items if item.id != id] - - # ToDo + """ + Remove existing user to-do list from the serverreturns: + """ + to_remove = [item for item in self.lists_ if item.id == id][0] + self.lists_.remove(to_remove) + if not ("DEBUG" in os.environ): + to_remove.dispose() + return self.lists_ + + def appendUserList(self, title): - item = ToDoList(id=None, title=title, created_at=datetime.now()) - self.items.append(item) - item.sync() - return item + """ + Create a new user list + title: title of list to create + returns: created item + """ + if "DEBUG" in os.environ: + item = ToDoList(id=np.random.randint(100, 1000), title=title, created_at=datetime.now()) + self.lists_.append(item) + return item + created_list = self.lists_create(title=title) + created_list = ToDoList(**created_list) + return created_list diff --git a/frontend/workspace.py b/frontend/workspace.py index 34830ec..c3eedcf 100644 --- a/frontend/workspace.py +++ b/frontend/workspace.py @@ -123,10 +123,10 @@ class WorkSpaceFrame(tk.Frame): # data self.lists = user.fetchUserLists() - text = tk.Text(self, width=15, height=1) - text.pack(anchor="sw") + self.add_list_text = tk.Text(self, width=15, height=1) + self.add_list_text.pack(anchor="sw") - add = tk.Button(self, text="Добавить лист", command=placeholder) + add = tk.Button(self, text="Добавить лист", command=self.add_list) add.pack(anchor="sw") # select list box @@ -152,9 +152,26 @@ class WorkSpaceFrame(tk.Frame): # todo lists self.toToList = ToDoListWidget(self) self.toToList.pack(side="left", fill="both", expand=1) + + def add_list(self, *args): + text = self.add_list_text.get(1.0, "end").strip() + if len(text) == 0: + print("empty name! Not adding!") + return + self.user.appendUserList(title=text) + self.lists = self.user.fetchUserLists() + + # fill list box + self.listBox.delete(0,'end') + for item in self.lists: + s = f"{str(item)}: {item.created_at.strftime('%Y-%m-%d %H:%M:%S')}" + self.listBox.insert(tk.END, s) + self.listBox.pack() + len(self.lists) > 0 and self.listBox.selection_set(first=0) + def listBox_selected(self, *args): - + self.toToList.clear() selection = self.listBox.curselection() From c9610f77658baf6c690d0f00a326deb904d43d51 Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 26 Apr 2021 22:50:40 +0300 Subject: [PATCH 2/7] =?UTF-8?q?=D0=9A=D0=BE=D1=81=D0=BC=D0=B5=D1=82=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D0=B4?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/api.py | 33 +++++++++++++++++---------------- frontend/api_demo.py | 4 +++- frontend/user.py | 42 ++++++++++++++++++++---------------------- frontend/workspace.py | 9 ++++----- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/frontend/api.py b/frontend/api.py index f3c5f0d..ad54387 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -101,13 +101,13 @@ class UserApi(object): headers=self._access_token_(), ) ) - + def lists_delete(self, id): """ Auth required - + Deletes a to-do list by id - + Parameters ---------- id: to-do list id to delete @@ -118,7 +118,7 @@ class UserApi(object): headers=self._access_token_(), ) ) - + def lists_update(self, title, id): """ Rename a new to-do list @@ -126,7 +126,7 @@ class UserApi(object): Parameters ---------- - title : str + title : str New name for a list. id : int @@ -138,7 +138,7 @@ class UserApi(object): headers=self._access_token_(), ) ) - + def todo_items_list(self, **argv): """ List all the exsiting to-do items. @@ -155,7 +155,7 @@ class UserApi(object): url=self.get_api(API_TODO_ITEMS_LIST), headers=self._access_token_(), params=argv ) ) - + def todo_items_create(self, parent, text="Note"): """ Create a new to-do item @@ -171,17 +171,17 @@ class UserApi(object): return UserApi._raise_or_return_( requests.post( url=self.get_api(API_TODO_ITEMS_CREATE), - json={"text": text, "parent":parent, "finished":False}, + json={"text": text, "parent": parent, "finished": False}, headers=self._access_token_(), ) ) - + def todo_items_delete(self, id): """ Auth required - + Deletes a to-do item by id - + Parameters ---------- id: to-do item id to delete @@ -192,7 +192,7 @@ class UserApi(object): headers=self._access_token_(), ) ) - + def todo_items_update(self, id, text, finished, parent): """ Rename a new to-do list @@ -202,7 +202,7 @@ class UserApi(object): ---------- id : int Note id - text : str + text : str New note for the item. finished : bool New state for the item @@ -212,11 +212,11 @@ class UserApi(object): return UserApi._raise_or_return_( requests.put( url=self.get_api(API_TODO_ITEMS_UPDATE.format(id)), - json={"text": text, "finished":finished, "parent":parent}, + json={"text": text, "finished": finished, "parent": parent}, headers=self._access_token_(), ) ) - + # def create(self, title="Untitled"): # """ # Create a new to-do list @@ -315,5 +315,6 @@ class UserApi(object): response.raise_for_status() try: return response.json() - except: + except Exception as e: + print(e) return response.content diff --git a/frontend/api_demo.py b/frontend/api_demo.py index ee1959b..9ac61b9 100644 --- a/frontend/api_demo.py +++ b/frontend/api_demo.py @@ -4,7 +4,9 @@ from user import User def print_lists(lists): for item in lists: - print(f"List: '{item.title}'", f"Id: {item.id}", "|", "|".join([str(x) for x in item.items_])) + print( + f"List: '{item.title}'", f"Id: {item.id}", "|", "|".join([str(x) for x in item.items_]) + ) DEFAULT_URL = "http://127.0.0.1:8000" diff --git a/frontend/user.py b/frontend/user.py index 51fab7d..bce6629 100644 --- a/frontend/user.py +++ b/frontend/user.py @@ -1,7 +1,5 @@ import os -from types import SimpleNamespace - from datetime import datetime import numpy as np @@ -14,12 +12,13 @@ UPDATE_ERROR = "Failed to update property: {0}" DATETIME_STR = "%Y-%m-%dT%H:%M:%S.%fZ" + def bad_arguments(x, d): return list((set(x) - set(d))) + class ToDoList(object): - def __init__(self, id, title, created_at=None, items=None, - parent=None, user=None): + def __init__(self, id, title, created_at=None, items=None, parent=None, user=None): self.id = id self.title = title self.items_ = [] if items is None else items @@ -44,7 +43,7 @@ class ToDoList(object): def index(self, value): return self.items_.index(value) - + def remove(self, index): """ Remove item at index from db @@ -52,7 +51,7 @@ class ToDoList(object): item = self.items_[index] self.items_.remove(item) item.dispose() - + def append(self, text): """ Add a new item to db @@ -72,26 +71,24 @@ class ToDoList(object): for key, value in argv.items(): setattr(self, key, value) self.sync() - + def dispose(self): print(f"To-do list id '{self.id}' is being disposed of...") if "DEBUG" in os.environ: return for item in self.items_: item.dispose() - self.user.lists_delete(self.id) - + self.user.lists_delete(self.id) + def sync(self): print(f"Item '{self}' is being synchronized...") if "DEBUG" in os.environ: return self.user.lists_update(title=self.title, id=self.id) - class ToDoItem(object): - def __init__(self, id, text, finished=False, created_at=None, - parent=None, user=None): + def __init__(self, id, text, finished=False, created_at=None, parent=None, user=None): self.id = id self.text = text self.finished = finished @@ -113,19 +110,21 @@ class ToDoItem(object): for key, value in argv.items(): setattr(self, key, value) self.sync() - + def dispose(self): print(f"To-do item id '{self.id}' is being disposed of...") if "DEBUG" in os.environ: return self.user.todo_items_delete(self.id) - + # ToDo def sync(self): print(f"Item '{self}' is being synchronized...") if "DEBUG" in os.environ: return - self.user.todo_items_update(id=self.id, text=self.text, finished=self.finished, parent=self.parent) + self.user.todo_items_update( + id=self.id, text=self.text, finished=self.finished, parent=self.parent + ) def make_debug_lists(): @@ -142,8 +141,8 @@ def make_debug_lists(): for i in range(10) ] + class User(UserApi): - def auth(self, user, passwd): """ Basic authentification @@ -151,10 +150,10 @@ class User(UserApi): if "DEBUG" in os.environ: return UserApi.auth(self, user, passwd) - + # Storing lists - mostly for debug purposes lists_ = make_debug_lists() - + def fetchUserLists(self): """ Fetch existing user lists from the server @@ -165,7 +164,7 @@ class User(UserApi): return self.lists_ user_lists = self.lists_list()["results"] user_items = self.todo_items_list()["results"] - toDoLists = {x["id"]:ToDoList(**x, user=self) for x in user_lists} + toDoLists = {x["id"]: ToDoList(**x, user=self) for x in user_lists} toDoItems = [ToDoItem(**x, user=self) for x in user_items] for toDoItem in toDoItems: # Catching stray items @@ -180,15 +179,14 @@ class User(UserApi): def removeUserList(self, id): """ - Remove existing user to-do list from the serverreturns: + Remove existing user to-do list from the serverreturns: """ to_remove = [item for item in self.lists_ if item.id == id][0] self.lists_.remove(to_remove) if not ("DEBUG" in os.environ): to_remove.dispose() return self.lists_ - - + def appendUserList(self, title): """ Create a new user list diff --git a/frontend/workspace.py b/frontend/workspace.py index c3eedcf..c82d2fd 100644 --- a/frontend/workspace.py +++ b/frontend/workspace.py @@ -152,7 +152,7 @@ class WorkSpaceFrame(tk.Frame): # todo lists self.toToList = ToDoListWidget(self) self.toToList.pack(side="left", fill="both", expand=1) - + def add_list(self, *args): text = self.add_list_text.get(1.0, "end").strip() if len(text) == 0: @@ -160,18 +160,17 @@ class WorkSpaceFrame(tk.Frame): return self.user.appendUserList(title=text) self.lists = self.user.fetchUserLists() - + # fill list box - self.listBox.delete(0,'end') + self.listBox.delete(0, "end") for item in self.lists: s = f"{str(item)}: {item.created_at.strftime('%Y-%m-%d %H:%M:%S')}" self.listBox.insert(tk.END, s) self.listBox.pack() len(self.lists) > 0 and self.listBox.selection_set(first=0) - def listBox_selected(self, *args): - + self.toToList.clear() selection = self.listBox.curselection() From 3e307506e982c345cbbacf68b8b02d83865d5eeb Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 27 Apr 2021 17:05:55 +0300 Subject: [PATCH 3/7] =?UTF-8?q?=D0=9A=D0=BE=D1=81=D0=BC=D0=B5=D1=82=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/api_demo.py | 20 +++++++++---------- frontend/user.py | 47 ++++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/frontend/api_demo.py b/frontend/api_demo.py index 9ac61b9..e3ec5f9 100644 --- a/frontend/api_demo.py +++ b/frontend/api_demo.py @@ -1,4 +1,4 @@ -import numpy as np +import random from user import User @@ -22,15 +22,9 @@ print("Appending list...") scroll = user.appendUserList(title="a new list!") print_lists(user.fetchUserLists()) -# Remove user list by id: -i = user.lists_[0].id -print(f"Removing {i}...") -user.removeUserList(i) -print_lists(user.fetchUserLists()) - # Modify list 0: print("Modifyng list...") -user.lists_[0].modify(title=f"A new title {np.random.random()}") +user.lists_[0].modify(title=f"A new title №{random.random()}") print_lists(user.fetchUserLists()) # Append item to list: @@ -43,7 +37,13 @@ print("Modifyng appended item...") item.modify(finished=True, text="this is an updated item") print_lists(user.fetchUserLists()) -# Removing item at 0 -print("Removing last item from last list...") +# Removing item at from last list +print("Removing the last item from the last list...") user.lists_[-1].remove(-1) print_lists(user.fetchUserLists()) + +# Remove user list by id: +i = user.lists_[0].id +print(f"Removing {i}...") +user.removeUserList(i) +print_lists(user.fetchUserLists()) diff --git a/frontend/user.py b/frontend/user.py index bce6629..ed264df 100644 --- a/frontend/user.py +++ b/frontend/user.py @@ -1,8 +1,8 @@ import os +import random from datetime import datetime -import numpy as np from api import UserApi @@ -17,15 +17,21 @@ def bad_arguments(x, d): return list((set(x) - set(d))) +def date_or_str(inpt): + if type(inpt) is datetime: + return inpt + elif type(inpt) is str: + return datetime.strptime(inpt, DATETIME_STR) + else: + return datetime.now() + + class ToDoList(object): def __init__(self, id, title, created_at=None, items=None, parent=None, user=None): self.id = id self.title = title self.items_ = [] if items is None else items - if type(created_at) is datetime: - self.created_at = created_at - else: - self.created_at = datetime.strptime(created_at, DATETIME_STR) + self.created_at = date_or_str(created_at) self.user = user def __iter__(self): @@ -57,7 +63,7 @@ class ToDoList(object): Add a new item to db """ if "DEBUG" in os.environ: - created_item = ToDoItem(id=np.random.randint(100, 1000), text=text, user=self.user) + created_item = ToDoItem(id=random.randint(100, 1000), text=text, user=self.user) else: created_item = self.user.todo_items_create(parent=self.id, text=text) created_item = ToDoItem(**created_item, user=self.user) @@ -92,10 +98,7 @@ class ToDoItem(object): self.id = id self.text = text self.finished = finished - if type(created_at) is datetime: - self.created_at = created_at - else: - self.created_at = datetime.strptime(created_at, DATETIME_STR) + self.created_at = date_or_str(created_at) self.parent = parent self.user = user @@ -105,7 +108,6 @@ class ToDoItem(object): def modify(self, **argv): bad = bad_arguments(argv.keys(), TODO_ITEM_UPDATEBLE) if len(bad) > 0: - print(argv) raise RuntimeError(UPDATE_ERROR.format(bad[0])) for key, value in argv.items(): setattr(self, key, value) @@ -117,7 +119,6 @@ class ToDoItem(object): return self.user.todo_items_delete(self.id) - # ToDo def sync(self): print(f"Item '{self}' is being synchronized...") if "DEBUG" in os.environ: @@ -164,17 +165,17 @@ class User(UserApi): return self.lists_ user_lists = self.lists_list()["results"] user_items = self.todo_items_list()["results"] - toDoLists = {x["id"]: ToDoList(**x, user=self) for x in user_lists} - toDoItems = [ToDoItem(**x, user=self) for x in user_items] - for toDoItem in toDoItems: + todo_lists = {x["id"]: ToDoList(**x, user=self) for x in user_lists} + todo_items = [ToDoItem(**x, user=self) for x in user_items] + for todo_item in todo_items: # Catching stray items - if not hasattr(toDoItem, "parent"): - toDoItem.dispose() - continue - toDoLists[toDoItem.parent].items_.append(toDoItem) - for toDoList in toDoLists.values(): - toDoList.items_ = sorted(toDoList.items_, key=lambda x: x.created_at) - self.lists_ = sorted(toDoLists.values(), key=lambda x: x.created_at) + # if not hasattr(toDoItem, "parent"): + # toDoItem.dispose() + # continue + todo_lists[todo_item.parent].items_.append(todo_item) + for todo_list in todo_lists.values(): + todo_list.items_ = sorted(todo_list.items_, key=lambda x: x.created_at) + self.lists_ = sorted(todo_lists.values(), key=lambda x: x.created_at) return self.lists_ def removeUserList(self, id): @@ -194,7 +195,7 @@ class User(UserApi): returns: created item """ if "DEBUG" in os.environ: - item = ToDoList(id=np.random.randint(100, 1000), title=title, created_at=datetime.now()) + item = ToDoList(id=random.randint(100, 1000), title=title, created_at=datetime.now()) self.lists_.append(item) return item created_list = self.lists_create(title=title) From 277f1e1aff5927b2d4030291ac4e94237d200f85 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 27 Apr 2021 19:59:31 +0300 Subject: [PATCH 4/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5/=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=BE=D0=BA=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend/urls.py | 16 +++++++-------- frontend/api.py | 17 ++++++++++++++-- frontend/api_demo.py | 1 + frontend/login.py | 20 ++++++++++++++++++ frontend/todo_tk.py | 9 +++++++++ frontend/user.py | 45 +++++++++++++++++++++++++++++++++++++++-- frontend/workspace.py | 12 +++++++++++ 7 files changed, 108 insertions(+), 12 deletions(-) diff --git a/backend/backend/urls.py b/backend/backend/urls.py index d123db5..cdcbc7f 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -13,13 +13,13 @@ from drf_yasg import openapi from .api import router schema_view = get_schema_view( - openapi.Info( - title="ToDo List", - default_version='v1', - description="Swagger Interface for ToDo List", - ), - public=True, - permission_classes=(permissions.AllowAny,), + openapi.Info( + title="ToDo List", + default_version="v1", + description="Swagger Interface for ToDo List", + ), + public=True, + permission_classes=(permissions.AllowAny,), ) urlpatterns = [ @@ -28,5 +28,5 @@ urlpatterns = [ path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), path("api/", include(router.urls)), path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), - path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + path("swagger/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"), ] diff --git a/frontend/api.py b/frontend/api.py index ad54387..458c0d8 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -18,7 +18,8 @@ API_LISTS_UPDATE = "api/lists/{0}/" API_LISTS_PARTIAL_UPDATE = "api/lists/{0}/" API_LISTS_DELETE = "api/lists/{0}/" -API_TOKEN = "api/token/" +API_TOKEN_CREATE = "api/token/" +API_TOKEN_REFRESH = "api/token/refresh/" class UserApi(object): @@ -61,11 +62,23 @@ class UserApi(object): """ token = UserApi._raise_or_return_( - requests.post(url=self.get_api(API_TOKEN), json={"username": user, "password": passwd}) + requests.post( + url=self.get_api(API_TOKEN_CREATE), json={"username": user, "password": passwd} + ) ) self.token = SimpleNamespace(**token) return self.token + def refresh(self): + """ + Refresh existing token + """ + token = UserApi._raise_or_return_( + requests.post(url=self.get_api(API_TOKEN_REFRESH), json={"refresh": self.token.refresh}) + ) + self.token.access = token["access"] + return self.token + def lists_list(self, **argv): """ List all the exsiting to-do lists. diff --git a/frontend/api_demo.py b/frontend/api_demo.py index e3ec5f9..329297c 100644 --- a/frontend/api_demo.py +++ b/frontend/api_demo.py @@ -13,6 +13,7 @@ DEFAULT_URL = "http://127.0.0.1:8000" user = User(url=DEFAULT_URL) user.auth("root", "root") +user.refresh() # Fetch existing lists: print_lists(user.fetchUserLists()) diff --git a/frontend/login.py b/frontend/login.py index 64ba0e3..0efd7f9 100644 --- a/frontend/login.py +++ b/frontend/login.py @@ -38,6 +38,13 @@ class LoginFrame(tk.Frame): print(ex) message.invalid_login() + # Если захочется реализовать в логине + """ + @property + def remember(self): + return self.rbtn_var.get() + """ + def initAUTH(self) -> None: """ Создает окно авторизации программы @@ -66,3 +73,16 @@ class LoginFrame(tk.Frame): # Кнопка авториазции btn = tk.Button(self, text="Войти", command=self.login_clicked) btn.grid(row=14, column=12, columnspan=3, rowspan=1, sticky="nsew") + + # Если захочется реализовать в логине + """ + # Запомнить пользователя + self.rbtn_var = tk.IntVar(value=0) + rbtn = tk.Checkbutton( + self, + text="Запомнить меня", + variable=self.rbtn_var, + command=None + ) + rbtn.grid(row=15, column=12, columnspan=3, rowspan=1, sticky="nsew") + """ diff --git a/frontend/todo_tk.py b/frontend/todo_tk.py index 831ac6c..e97c352 100644 --- a/frontend/todo_tk.py +++ b/frontend/todo_tk.py @@ -4,6 +4,7 @@ import sys import tkinter as tk from login import LoginFrame from workspace import WorkSpaceFrame +from user import User if "win" in sys.platform.lower(): DEFAULT_URL = "http://localhost:8000" @@ -24,11 +25,19 @@ class Application(tk.Tk): def login(self): """Возвращает пользователя - его можно потом сериализовать""" + # Пользователь сохранен! Авторизация не нужна! + user = User.load() + if user is not None: + return user + # Не удалось - нужен логин self.frame = LoginFrame(master=self, url=DEFAULT_URL) while not self.frame.loggedIn: self.update_idletasks() self.update() self.frame.destroy() + # Нужно запомнить пользователя + # if self.frame.remember: + # self.frame.user.save() return self.frame.user def main(self, user): diff --git a/frontend/user.py b/frontend/user.py index ed264df..e5e6baf 100644 --- a/frontend/user.py +++ b/frontend/user.py @@ -1,8 +1,11 @@ import os import random - from datetime import datetime +from types import SimpleNamespace +from pathlib import Path + +import json from api import UserApi @@ -12,6 +15,8 @@ UPDATE_ERROR = "Failed to update property: {0}" DATETIME_STR = "%Y-%m-%dT%H:%M:%S.%fZ" +USER_TOKEN_PATH = os.path.join(Path.home(), ".todo_config.json") + def bad_arguments(x, d): return list((set(x) - set(d))) @@ -150,7 +155,43 @@ class User(UserApi): """ if "DEBUG" in os.environ: return - UserApi.auth(self, user, passwd) + return UserApi.auth(self, user, passwd) + + def remove(self): + """ + Remove the login file from homedir + """ + if not os.path.exists(USER_TOKEN_PATH): + return + try: + os.remove(USER_TOKEN_PATH) + except Exception as e: + raise RuntimeError("Failed to remove tokens:", e) + + def save(self): + """ + Store user token in homedir + """ + try: + with open(USER_TOKEN_PATH, "w") as handler: + json.dump(self.token.__dict__, handler) + except Exception as e: + raise RuntimeError("Failed to store tokens:", e) + + @staticmethod + def load(): + """ + Restore user token from the file in homedir + """ + if os.path.exists(USER_TOKEN_PATH): + try: + with open(USER_TOKEN_PATH, "r") as handler: + user = User(token=SimpleNamespace(**json.load(handler))) + user.refresh() + return user + except Exception as e: + raise RuntimeError("Failed to restore tokens:", e) + return None # Storing lists - mostly for debug purposes lists_ = make_debug_lists() diff --git a/frontend/workspace.py b/frontend/workspace.py index c82d2fd..8a488bc 100644 --- a/frontend/workspace.py +++ b/frontend/workspace.py @@ -118,8 +118,20 @@ class WorkSpaceFrame(tk.Frame): self.pack(fill=tk.BOTH, expand=1) self.initLayout(user) + def destroy(self): + tk.Tk.destroy(self) + if self.rbtn_var.get() > 0: + self.user.save() + else: + self.user.remove() + def initLayout(self, user): + # Запомнить пользователя + self.rbtn_var = tk.IntVar(value=1) + rbtn = tk.Checkbutton(self, text="Запомнить меня", variable=self.rbtn_var, command=None) + rbtn.pack(anchor="n") + # data self.lists = user.fetchUserLists() From 5ddadac47c94d1139e8d4492a0e8fc8eec8a8146 Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 28 Apr 2021 14:46:28 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D0=9F=D0=BE=D1=84=D0=B8=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B0=D0=BF=D0=B8=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BB=D0=B0=D0=B4=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/todo_tk.py | 9 ++++++--- frontend/user.py | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/todo_tk.py b/frontend/todo_tk.py index e97c352..0336afd 100644 --- a/frontend/todo_tk.py +++ b/frontend/todo_tk.py @@ -26,9 +26,12 @@ class Application(tk.Tk): def login(self): """Возвращает пользователя - его можно потом сериализовать""" # Пользователь сохранен! Авторизация не нужна! - user = User.load() - if user is not None: - return user + try: + user = User.load() + if user is not None: + return user + except Exception as e: + print("Failed to restore login:", e) # Не удалось - нужен логин self.frame = LoginFrame(master=self, url=DEFAULT_URL) while not self.frame.loggedIn: diff --git a/frontend/user.py b/frontend/user.py index e5e6baf..7c58714 100644 --- a/frontend/user.py +++ b/frontend/user.py @@ -161,6 +161,9 @@ class User(UserApi): """ Remove the login file from homedir """ + if "DEBUG" in os.environ: + print("Debug mode is on - no login storing") + return if not os.path.exists(USER_TOKEN_PATH): return try: @@ -172,6 +175,9 @@ class User(UserApi): """ Store user token in homedir """ + if "DEBUG" in os.environ: + print("Debug mode is on - no login storing") + return try: with open(USER_TOKEN_PATH, "w") as handler: json.dump(self.token.__dict__, handler) @@ -183,6 +189,8 @@ class User(UserApi): """ Restore user token from the file in homedir """ + if "DEBUG" in os.environ: + raise RuntimeError("Debug mode is on - no login storing") if os.path.exists(USER_TOKEN_PATH): try: with open(USER_TOKEN_PATH, "r") as handler: From 3d779590e9e393d329760e76c1f2ca59a055d523 Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 28 Apr 2021 17:53:38 +0300 Subject: [PATCH 6/7] =?UTF-8?q?=D0=A4=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D0=BA=D0=BD=D0=BE?= =?UTF-8?q?=D0=BF=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/todo_tk.py | 2 +- frontend/user.py | 12 ++++----- frontend/workspace.py | 58 +++++++++++++++++++++++++++++++------------ 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/frontend/todo_tk.py b/frontend/todo_tk.py index 0336afd..50a43e5 100644 --- a/frontend/todo_tk.py +++ b/frontend/todo_tk.py @@ -11,7 +11,7 @@ if "win" in sys.platform.lower(): else: DEFAULT_URL = "http://0.0.0.0:8000" -BASE_W = 580 +BASE_W = 600 BASE_H = 400 TITLE_APP = "ToDo Application" diff --git a/frontend/user.py b/frontend/user.py index 7c58714..588dac8 100644 --- a/frontend/user.py +++ b/frontend/user.py @@ -57,7 +57,7 @@ class ToDoList(object): def remove(self, index): """ - Remove item at index from db + Remove item AT INDEX from db """ item = self.items_[index] self.items_.remove(item) @@ -85,10 +85,10 @@ class ToDoList(object): def dispose(self): print(f"To-do list id '{self.id}' is being disposed of...") - if "DEBUG" in os.environ: - return for item in self.items_: item.dispose() + if "DEBUG" in os.environ: + return self.user.lists_delete(self.id) def sync(self): @@ -229,12 +229,12 @@ class User(UserApi): def removeUserList(self, id): """ - Remove existing user to-do list from the serverreturns: + Remove existing user to-do list BY ID from the serverreturns: """ to_remove = [item for item in self.lists_ if item.id == id][0] self.lists_.remove(to_remove) - if not ("DEBUG" in os.environ): - to_remove.dispose() + # if not ("DEBUG" in os.environ): + to_remove.dispose() return self.lists_ def appendUserList(self, title): diff --git a/frontend/workspace.py b/frontend/workspace.py index 8a488bc..eaabd0a 100644 --- a/frontend/workspace.py +++ b/frontend/workspace.py @@ -82,7 +82,7 @@ class ToDoListWidget(tk.Frame): add = tk.Button(self, text="Добавить заметку", command=self.add_command) add.pack(side="top") - delete = tk.Button(self, text="Удалить лист", command=placeholder) + delete = tk.Button(self, text="Удалить лист", command=self.master.delete_list) delete.pack(side="top") def update(self, itemList=None): @@ -106,6 +106,17 @@ class ToDoListWidget(tk.Frame): class WorkSpaceFrame(tk.Frame): + def delete_list(self, *args): + selection = self.listBox.curselection() + cur = selection[0] + self.user.removeUserList(self.lists[cur].id) + self.lists = self.user.fetchUserLists() + self.update_lists() + if len(self.lists) > 1: + self.listBox.selection_set(first=cur - 1) + elif len(self.lists) > 0: + self.listBox.selection_set(first=0) + def __init__(self, user, master=None, url=None) -> None: """ Функция инициаизации класса @@ -154,16 +165,36 @@ class WorkSpaceFrame(tk.Frame): # add scroll bar to list box self.listBox.config(yscrollcommand=scrollbar.set) - # fill list box + # init list view + self.update_lists() + + # canvas for todo lists + # canvas = tk.Canvas(self) + # canvas.pack(side="left", fill="both", expand=1) + # scrollbar = tk.Scrollbar(self, orient="vertical") + # scrollbar.config(command=canvas.yview) + # scrollbar.pack(side="left", fill="y") + # canvas.configure(yscrollcommand=scrollbar.set) + + # todo lists + self.toDoList = ToDoListWidget(self) + # self.toDoList = ToDoListWidget(canvas) + self.toDoList.pack(side="left", fill="y") + # canvas.bind('', lambda *argv: print(self.toDoList.winfo_height())) + # canvas.create_window((0,0), window=self.toDoList, anchor='nw') + + # select list! + if len(self.lists) > 0: + self.listBox.selection_set(first=0) + self.listBox_selected() + + def update_lists(self): + self.listBox.delete(0, "end") for item in self.lists: s = f"{str(item)}: {item.created_at.strftime('%Y-%m-%d %H:%M:%S')}" self.listBox.insert(tk.END, s) self.listBox.pack() - len(self.lists) > 0 and self.listBox.selection_set(first=0) - - # todo lists - self.toToList = ToDoListWidget(self) - self.toToList.pack(side="left", fill="both", expand=1) + return len(self.lists) def add_list(self, *args): text = self.add_list_text.get(1.0, "end").strip() @@ -174,18 +205,13 @@ class WorkSpaceFrame(tk.Frame): self.lists = self.user.fetchUserLists() # fill list box - self.listBox.delete(0, "end") - for item in self.lists: - s = f"{str(item)}: {item.created_at.strftime('%Y-%m-%d %H:%M:%S')}" - self.listBox.insert(tk.END, s) - self.listBox.pack() - len(self.lists) > 0 and self.listBox.selection_set(first=0) + self.update_lists() + len(self.lists) > 0 and self.listBox.selection_set(first=len(self.lists) - 1) def listBox_selected(self, *args): - - self.toToList.clear() + self.toDoList.clear() selection = self.listBox.curselection() cur = selection[0] - self.toToList.fill(self.lists[cur]) + self.toDoList.fill(self.lists[cur]) From 7afb99d3d0a7c6d71b59230e0e367c204ec0e207 Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 28 Apr 2021 18:08:07 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=81=D0=BA=D1=80=D0=BE=D0=BB=D0=BB=D0=B1=D0=B0=D1=80=20?= =?UTF-8?q?=D0=B2=20=D0=BF=D1=80=D0=B5=D0=B4=D1=81=D1=82=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B0=D0=B9=D1=82=D0=B5=D0=BC=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/workspace.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/frontend/workspace.py b/frontend/workspace.py index eaabd0a..73875e7 100644 --- a/frontend/workspace.py +++ b/frontend/workspace.py @@ -61,8 +61,9 @@ class ToDoItemWidget(tk.Frame): class ToDoListWidget(tk.Frame): - def __init__(self, *args, **argv): + def __init__(self, *args, delete_list, **argv): super().__init__(*args, **argv) + self.delete_list = delete_list def fill(self, itemList): @@ -82,7 +83,7 @@ class ToDoListWidget(tk.Frame): add = tk.Button(self, text="Добавить заметку", command=self.add_command) add.pack(side="top") - delete = tk.Button(self, text="Удалить лист", command=self.master.delete_list) + delete = tk.Button(self, text="Удалить лист", command=self.delete_list) delete.pack(side="top") def update(self, itemList=None): @@ -169,19 +170,20 @@ class WorkSpaceFrame(tk.Frame): self.update_lists() # canvas for todo lists - # canvas = tk.Canvas(self) - # canvas.pack(side="left", fill="both", expand=1) - # scrollbar = tk.Scrollbar(self, orient="vertical") - # scrollbar.config(command=canvas.yview) - # scrollbar.pack(side="left", fill="y") - # canvas.configure(yscrollcommand=scrollbar.set) + canvas = tk.Canvas(self) + canvas.pack(side="left", fill="both", expand=1) + scrollbar = tk.Scrollbar(self, orient="vertical") + scrollbar.config(command=canvas.yview) + scrollbar.pack(side="left", fill="y") + canvas.configure(yscrollcommand=scrollbar.set) # todo lists - self.toDoList = ToDoListWidget(self) + self.toDoList = ToDoListWidget(self, delete_list=self.delete_list) + self.toDoList.grid_propagate(True) # self.toDoList = ToDoListWidget(canvas) self.toDoList.pack(side="left", fill="y") - # canvas.bind('', lambda *argv: print(self.toDoList.winfo_height())) - # canvas.create_window((0,0), window=self.toDoList, anchor='nw') + canvas.bind("", lambda *argv: canvas.configure(scrollregion=canvas.bbox("all"))) + canvas.create_window((0, 0), window=self.toDoList, anchor="nw") # select list! if len(self.lists) > 0: