From b2b447e39237737abedf3c78f684837f666d317a Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 26 Apr 2021 22:42:56 +0300 Subject: [PATCH] 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()