From 633326ac89760fcea7f937921ac82c453cb475c9 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 14 Apr 2021 20:26:06 +0300 Subject: [PATCH 1/6] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20api=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/api.py | 91 ++++++++++++++++++++++++++++++++++++++++++++ frontend/api_demo.py | 11 ++++++ 2 files changed, 102 insertions(+) create mode 100644 frontend/api.py create mode 100644 frontend/api_demo.py diff --git a/frontend/api.py b/frontend/api.py new file mode 100644 index 0000000..9a0f9f3 --- /dev/null +++ b/frontend/api.py @@ -0,0 +1,91 @@ +import requests +from json import loads, dumps +from types import SimpleNamespace + +URL = "http://127.0.0.1:8000" +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_TOKEN = "api/token/" + +CODE_SUCCESS = 200 +JSON_HEADERS = {'content-type': 'application/json', 'accept': 'application/json'} + +get_api = lambda x: URL + "/" + x +dump = lambda x: bytes(dumps(x), encoding="utf-8") + +class User(object): + + def __init__(self): + pass + + def auth(self, user, passwd): + url = get_api(API_TOKEN) + data = dump({"username": f"{user}", "password": f"{passwd}"}) + code, self.token = User.post(url=url, data=data, headers=JSON_HEADERS) + return code + + def list(self): + return User.get( + url=get_api(API_LISTS_LIST), + headers=self._authorised_()) + + def create(self, title="Untitled"): + return User.post( + url=get_api(API_LISTS_CREATE), + data=dump({"title": title}), + headers=self._authorised_()) + + def read(self, id): + return User.post( + url=get_api(API_LISTS_READ).format(id), + headers=self._authorised_()) + + def update(self, id, title="Untitled"): + return User.__request__( + func=requests.put, + data=dump({"title": title}), + url=get_api(API_LISTS_UPDATE).format(id), + headers=self._authorised_()) + + def partial_update(self, id, title="Untitled"): + return User.__request__( + func=requests.patch, + data=dump({"title": title}), + url=get_api(API_LISTS_PARTIAL_UPDATE).format(id), + headers=self._authorised_()) + + def delete(self, id): + return User.__request__( + func=requests.delete, + url=get_api(API_LISTS_DELETE).format(id), + headers=self._authorised_()) + + def _authorised_(self, headers=JSON_HEADERS): + if not hasattr(self, "token"): + raise RuntimeError("Authosization required for requested operation!") + headers = headers.copy() + headers['Authorization'] = f'Bearer {self.token.access}' + return headers + + @staticmethod + def get(**argv): + return User.__request__(requests.get, **argv) + + @staticmethod + def post(**argv): + return User.__request__(requests.post, **argv) + + @staticmethod + def __request__(func, verbose=True, **argv): + with func(**argv) as r: + if r.status_code == CODE_SUCCESS: + data = loads(r.text) + if type(data) is dict: + data = SimpleNamespace(**data) + else: + data = None + return r.status_code, data \ No newline at end of file diff --git a/frontend/api_demo.py b/frontend/api_demo.py new file mode 100644 index 0000000..e77ad51 --- /dev/null +++ b/frontend/api_demo.py @@ -0,0 +1,11 @@ +from api import User, CODE_SUCCESS + +user = User() +print("testing api methods...") +print("auth...", user.auth("root", "root") == CODE_SUCCESS) +print("list...", user.list()[0] == CODE_SUCCESS) +print("create...", user.create()[0] == CODE_SUCCESS) +print("read...", user.read(id=0)[0] == CODE_SUCCESS) +print("update...", user.update(id=0, title="Title")[0] == CODE_SUCCESS) +print("partial_update...", user.partial_update(id=0, title="Title")[0] == CODE_SUCCESS) +print("delete...", user.update(id=0)[0] == CODE_SUCCESS) \ No newline at end of file From 6bf6f9d3f1ca8df8cc299ddf0a1d00612b81f41c Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 15 Apr 2021 20:31:24 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D1=82=D0=B8=D0=BB=D1=8F,=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B7=D0=BE=D0=B2=D0=BE=D0=B9=20=D0=B4=D0=BE=D0=BA=D1=83?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8,=20=D1=81?= =?UTF-8?q?=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D1=83?= =?UTF-8?q?=D1=8E=D1=89=D0=B5=D0=B9=20=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B8?= =?UTF-8?q?=D0=BC=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/api.py | 211 +++++++++++++++++++++++++++++-------------- frontend/api_demo.py | 24 +++-- 2 files changed, 158 insertions(+), 77 deletions(-) diff --git a/frontend/api.py b/frontend/api.py index 9a0f9f3..8292761 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -1,8 +1,7 @@ +import urllib import requests -from json import loads, dumps -from types import SimpleNamespace -URL = "http://127.0.0.1:8000" +DEFAULT_URL = "http://127.0.0.1:8000" API_LISTS_LIST = "api/lists/" API_LISTS_CREATE = "api/lists/" API_LISTS_READ = "lists/{0}/" @@ -11,81 +10,155 @@ API_LISTS_PARTIAL_UPDATE = "lists/{0}/" API_LISTS_DELETE = "lists/{0}/" API_TOKEN = "api/token/" -CODE_SUCCESS = 200 -JSON_HEADERS = {'content-type': 'application/json', 'accept': 'application/json'} - -get_api = lambda x: URL + "/" + x -dump = lambda x: bytes(dumps(x), encoding="utf-8") class User(object): + def __init__(self, url=DEFAULT_URL, token=None): + """ + Constructor - def __init__(self): - pass + Parameters + ---------- + url : str, optional + Server url. The default is DEFAULT_URL. + token : dict, optional + Existing user tokens to bypass authorization. + The default is None. + Returns + ------- + None. + + """ + self.token = token + self.get_api = lambda x: urllib.parse.urljoin(url, x) + + # ToDo - store tokens in config def auth(self, user, passwd): - url = get_api(API_TOKEN) - data = dump({"username": f"{user}", "password": f"{passwd}"}) - code, self.token = User.post(url=url, data=data, headers=JSON_HEADERS) - return code - + """ + Authosization + + Parameters + ---------- + user : str + Login. + passwd : str + Password. + + Returns + ------- + dict + Generated auth token. + + """ + url = self.get_api(API_TOKEN) + data = {"username": user, "password": passwd} + response = requests.post(url=url, json=data) + response.raise_for_status() + self.token = response.json() + return self.token + def list(self): - return User.get( - url=get_api(API_LISTS_LIST), - headers=self._authorised_()) - + """ + List all the exsiting to-do lists. + Auth required + + Returns + ------- + list + to-do lists. + + """ + response = requests.get(url=self.get_api(API_LISTS_LIST), headers=self._access_token_()) + response.raise_for_status() + return response.json() + def create(self, title="Untitled"): - return User.post( - url=get_api(API_LISTS_CREATE), - data=dump({"title": title}), - headers=self._authorised_()) - + """ + Create a new to-do list + + Parameters + ---------- + title : str, optional + New list name. The default is "Untitled". + + """ + response = requests.post( + url=self.get_api(API_LISTS_CREATE), json={"title": title}, headers=self._access_token_() + ) + response.raise_for_status() + return response.json() + def read(self, id): - return User.post( - url=get_api(API_LISTS_READ).format(id), - headers=self._authorised_()) - + """ + Read a to-do list contents + + Parameters + ---------- + id : int + List id. + + Returns + ------- + list + Requested contents + + """ + response = requests.post( + url=self.get_api(API_LISTS_READ).format(id), headers=self._access_token_() + ) + response.raise_for_status() + return response.json() + def update(self, id, title="Untitled"): - return User.__request__( - func=requests.put, - data=dump({"title": title}), - url=get_api(API_LISTS_UPDATE).format(id), - headers=self._authorised_()) - + """ + Add a to-do item to the list + + Parameters + ---------- + id : int + List id. + title : str, optional + To-do item title. The default is "Untitled". + + """ + response = requests.put( + json={"title": title}, + url=self.get_api(API_LISTS_UPDATE).format(id), + headers=self._access_token_(), + ) + response.raise_for_status() + return response.json() + def partial_update(self, id, title="Untitled"): - return User.__request__( - func=requests.patch, - data=dump({"title": title}), - url=get_api(API_LISTS_PARTIAL_UPDATE).format(id), - headers=self._authorised_()) - + """ + Update list item - untrusted + + """ + response = requests.patch( + json={"title": title}, + url=self.get_api(API_LISTS_PARTIAL_UPDATE).format(id), + headers=self._access_token_(), + ) + response.raise_for_status() + return response.json() + def delete(self, id): - return User.__request__( - func=requests.delete, - url=get_api(API_LISTS_DELETE).format(id), - headers=self._authorised_()) - - def _authorised_(self, headers=JSON_HEADERS): - if not hasattr(self, "token"): + """ + Delete list + + Parameters + ---------- + id : int + List id to delete. + + """ + response = requests.delete( + url=self.get_api(API_LISTS_DELETE).format(id), headers=self._access_token_() + ) + response.raise_for_status() + return response.json() + + def _access_token_(self): + if self.token is None: raise RuntimeError("Authosization required for requested operation!") - headers = headers.copy() - headers['Authorization'] = f'Bearer {self.token.access}' - return headers - - @staticmethod - def get(**argv): - return User.__request__(requests.get, **argv) - - @staticmethod - def post(**argv): - return User.__request__(requests.post, **argv) - - @staticmethod - def __request__(func, verbose=True, **argv): - with func(**argv) as r: - if r.status_code == CODE_SUCCESS: - data = loads(r.text) - if type(data) is dict: - data = SimpleNamespace(**data) - else: - data = None - return r.status_code, data \ No newline at end of file + return {"Authorization": f'Bearer {self.token["access"]}'} diff --git a/frontend/api_demo.py b/frontend/api_demo.py index e77ad51..92ad4c1 100644 --- a/frontend/api_demo.py +++ b/frontend/api_demo.py @@ -1,11 +1,19 @@ -from api import User, CODE_SUCCESS +from api import User + + +def ignore_exceptions(*args, **argv): + try: + args[0](*(args[1:]), **argv) + except Exception as e: + print(e) + user = User() print("testing api methods...") -print("auth...", user.auth("root", "root") == CODE_SUCCESS) -print("list...", user.list()[0] == CODE_SUCCESS) -print("create...", user.create()[0] == CODE_SUCCESS) -print("read...", user.read(id=0)[0] == CODE_SUCCESS) -print("update...", user.update(id=0, title="Title")[0] == CODE_SUCCESS) -print("partial_update...", user.partial_update(id=0, title="Title")[0] == CODE_SUCCESS) -print("delete...", user.update(id=0)[0] == CODE_SUCCESS) \ No newline at end of file +print("auth..."), ignore_exceptions(user.auth, "root", "root") +print("list..."), ignore_exceptions(user.list) +print("create..."), ignore_exceptions(user.create) +print("read..."), ignore_exceptions(user.read, id=0) +print("update..."), ignore_exceptions(user.update, id=0, title="Title") +print("partial_update..."), ignore_exceptions(user.partial_update, id=0, title="Title") +print("delete..."), ignore_exceptions(user.update, id=0) From 0d1e321b7e331ff52ae9e6cb33c0e88e0321dd7f Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 18 Apr 2021 16:07:59 +0300 Subject: [PATCH 3/6] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20api=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D1=8B=20=D1=81=D0=BE=20=D1=81=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=D0=B0=D0=BC=D0=B8=20=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/api.py | 192 +++++++++++++++++++++++++++---------------- frontend/api_demo.py | 59 +++++++++---- frontend/user.py | 95 +++++++++++++++++++++ 3 files changed, 262 insertions(+), 84 deletions(-) create mode 100644 frontend/user.py diff --git a/frontend/api.py b/frontend/api.py index 8292761..e180c72 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -1,17 +1,27 @@ +from types import SimpleNamespace import urllib import requests DEFAULT_URL = "http://127.0.0.1:8000" + +API_TODO_ITEMS_LIST = "api/todo_items/" +API_TODO_ITEMS_CREATE = "api/todo_items/" +API_TODO_ITEMS_READ = "api/todo_items/{0}/" +API_TODO_ITEMS_UPDATE = "api/todo_items/{0}/" +API_TODO_ITEMS_PARTIAL_UPDATE = "api/todo_items/{0}/" +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_TOKEN = "api/token/" -class User(object): +class UserApi(object): def __init__(self, url=DEFAULT_URL, token=None): """ Constructor @@ -50,14 +60,13 @@ class User(object): Generated auth token. """ - url = self.get_api(API_TOKEN) - data = {"username": user, "password": passwd} - response = requests.post(url=url, json=data) - response.raise_for_status() - self.token = response.json() + token = UserApi._raise_or_return_( + requests.post(url=self.get_api(API_TOKEN), json={"username": user, "password": passwd}) + ) + self.token = SimpleNamespace(**token) return self.token - def list(self): + def lists_list(self, **argv): """ List all the exsiting to-do lists. Auth required @@ -68,13 +77,16 @@ class User(object): to-do lists. """ - response = requests.get(url=self.get_api(API_LISTS_LIST), headers=self._access_token_()) - response.raise_for_status() - return response.json() + return UserApi._raise_or_return_( + requests.get( + url=self.get_api(API_LISTS_LIST), headers=self._access_token_(), params=argv + ) + ) - def create(self, title="Untitled"): + def lists_create(self, title="Untitled"): """ Create a new to-do list + Auth required Parameters ---------- @@ -82,83 +94,125 @@ class User(object): New list name. The default is "Untitled". """ - response = requests.post( - url=self.get_api(API_LISTS_CREATE), json={"title": title}, headers=self._access_token_() + return UserApi._raise_or_return_( + requests.post( + url=self.get_api(API_LISTS_CREATE), + json={"title": title}, + headers=self._access_token_(), + ) ) - response.raise_for_status() - return response.json() - def read(self, id): + def todo_items_list(self, **argv): """ - Read a to-do list contents - - Parameters - ---------- - id : int - List id. + List all the exsiting to-do items. + Auth required Returns ------- list - Requested contents + to-do items. """ - response = requests.post( - url=self.get_api(API_LISTS_READ).format(id), headers=self._access_token_() + return UserApi._raise_or_return_( + requests.get( + url=self.get_api(API_TODO_ITEMS_LIST), headers=self._access_token_(), params=argv + ) ) - response.raise_for_status() - return response.json() - def update(self, id, title="Untitled"): - """ - Add a to-do item to the list + # def create(self, title="Untitled"): + # """ + # Create a new to-do list + # Auth required - Parameters - ---------- - id : int - List id. - title : str, optional - To-do item title. The default is "Untitled". + # Parameters + # ---------- + # title : str, optional + # New list name. The default is "Untitled". - """ - response = requests.put( - json={"title": title}, - url=self.get_api(API_LISTS_UPDATE).format(id), - headers=self._access_token_(), - ) - response.raise_for_status() - return response.json() + # """ + # response = requests.post( + # url=self.get_api(API_LISTS_CREATE), json={"title": title}, headers=self._access_token_() + # url=self.get_api(API_TODO_ITEMS_CREATE), json={"title": title}, headers=self._access_token_() + # ) + # response.raise_for_status() + # return response.json() - def partial_update(self, id, title="Untitled"): - """ - Update list item - untrusted + # def read(self, id): + # """ + # Read a to-do list contents - """ - response = requests.patch( - json={"title": title}, - url=self.get_api(API_LISTS_PARTIAL_UPDATE).format(id), - headers=self._access_token_(), - ) - response.raise_for_status() - return response.json() + # Parameters + # ---------- + # id : int + # List id. - def delete(self, id): - """ - Delete list + # Returns + # ------- + # list + # Requested contents - Parameters - ---------- - id : int - List id to delete. + # """ + # response = requests.post( + # url=self.get_api(API_LISTS_READ).format(id), headers=self._access_token_() + # ) + # response.raise_for_status() + # return response.json() - """ - response = requests.delete( - url=self.get_api(API_LISTS_DELETE).format(id), headers=self._access_token_() - ) - response.raise_for_status() - return response.json() + # def update(self, id, title="Untitled"): + # """ + # Add a to-do item to the list + + # Parameters + # ---------- + # id : int + # List id. + # title : str, optional + # To-do item title. The default is "Untitled". + + # """ + # response = requests.put( + # json={"title": title}, + # url=self.get_api(API_LISTS_UPDATE).format(id), + # headers=self._access_token_(), + # ) + # response.raise_for_status() + # return response.json() + + # def partial_update(self, id, title="Untitled"): + # """ + # Update list item - untrusted + + # """ + # response = requests.patch( + # json={"title": title}, + # url=self.get_api(API_LISTS_PARTIAL_UPDATE).format(id), + # headers=self._access_token_(), + # ) + # response.raise_for_status() + # return response.json() + + # def delete(self, id): + # """ + # Delete list + + # Parameters + # ---------- + # id : int + # List id to delete. + + # """ + # response = requests.delete( + # url=self.get_api(API_LISTS_DELETE).format(id), headers=self._access_token_() + # ) + # response.raise_for_status() + # return response.json() def _access_token_(self): if self.token is None: raise RuntimeError("Authosization required for requested operation!") - return {"Authorization": f'Bearer {self.token["access"]}'} + return {"Authorization": f"Bearer {self.token.access}"} + + @staticmethod + def _raise_or_return_(response): + response.raise_for_status() + return response.json() diff --git a/frontend/api_demo.py b/frontend/api_demo.py index 92ad4c1..f79bbc3 100644 --- a/frontend/api_demo.py +++ b/frontend/api_demo.py @@ -1,19 +1,48 @@ -from api import User +from user import User -def ignore_exceptions(*args, **argv): - try: - args[0](*(args[1:]), **argv) - except Exception as e: - print(e) +def print_lists(lists): + for item in lists: + print(f"List: '{item}'", f"Id: {item.id}", "|", "|".join([str(x) for x in item.items])) -user = User() -print("testing api methods...") -print("auth..."), ignore_exceptions(user.auth, "root", "root") -print("list..."), ignore_exceptions(user.list) -print("create..."), ignore_exceptions(user.create) -print("read..."), ignore_exceptions(user.read, id=0) -print("update..."), ignore_exceptions(user.update, id=0, title="Title") -print("partial_update..."), ignore_exceptions(user.partial_update, id=0, title="Title") -print("delete..."), ignore_exceptions(user.update, id=0) +DEFAULT_URL = "http://127.0.0.1:8000" + +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) + +# Append a new list to user: +print("Appending list...") +scroll = user.appendUserList(title="a new list!") +print_lists(lists) + +# Modify list 0: +print("Modifyng list...") +lists[0].modify(title="A new title") +print_lists(lists) + +# Append item to list: +print("Appending item to last list...") +item = lists[-1].append(text="this is an item") +print_lists(lists) + +# Modifying item +print("Modifyng appended item...") +item.modify(finished=True, text="this is an updated item") +print_lists(lists) + +# Removing item at 0 +print("Removing item 0 from list 0...") +lists[0].remove(0) +print_lists(lists) diff --git a/frontend/user.py b/frontend/user.py new file mode 100644 index 0000000..aa91e63 --- /dev/null +++ b/frontend/user.py @@ -0,0 +1,95 @@ +from datetime import datetime +from api import UserApi + + +class ToDoList(object): + def __init__(self, id, title, created_at=None, items=[], parent=None): + self.id = id + self.title = title + self.items = items + self.created_at = created_at + + def __getitem__(self, index): + return self.items[index] + + def __len__(self): + return len(self.items) + + def __str__(self): + return f"[{self.id}] {self.title}" + + # ToDo + def remove(self, index): + self.items.remove(self.items[0]) + self.sync() + + # ToDo + def append(self, text): + item = ToDoItem(id=None, text=text, created_at=datetime.now()) + self.items.append(item) + item.sync() + self.sync() + return item + + def modify(self, **argv): + 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 ToDoItem(object): + def __init__(self, id, text, finished=False, created_at=None, parent=None): + self.id = id + self.text = text + self.finished = finished + self.created_at = created_at + + def __str__(self): + return f"[{self.id}] {self.text}" + + def modify(self, **argv): + 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): + + # ToDo + items = [ + ToDoList( + id=i, + title=f"List {i}", + created_at=datetime.now(), + items=[ + ToDoItem(id=i * 10 + j, text=f"Item {i*10+j}", created_at=datetime.now()) + for j in range(10) + ], + ) + for i in range(10) + ] + + # ToDo + def fetchUserLists(self): + return self.items + + # ToDo + def removeUserList(self, id): + self.items = [item for item in self.items if item.id != id] + + # ToDo + def appendUserList(self, title): + item = ToDoList(id=None, title=title, created_at=datetime.now()) + self.items.append(item) + item.sync() + return item From 5836783e699e936c8dfde9b4e97a90f9b60a9618 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 18 Apr 2021 16:16:19 +0300 Subject: [PATCH 4/6] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/user.py b/frontend/user.py index aa91e63..38cda9b 100644 --- a/frontend/user.py +++ b/frontend/user.py @@ -20,7 +20,7 @@ class ToDoList(object): # ToDo def remove(self, index): - self.items.remove(self.items[0]) + self.items.remove(self.items[index]) self.sync() # ToDo From 66beba6b0d1b29cfc137edb40bd1af6fde651924 Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 22 Apr 2021 20:35:48 +0300 Subject: [PATCH 5/6] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=84=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=20=D1=81=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D1=8C=D1=8E=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B9.=20=D0=94=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D0=BA=D0=B0=20=D0=BD=D0=B5=D0=BE=D0=B1=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=D0=B8=D0=BC=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=B0=D1=8F=20=D1=81=D1=80=D0=B5=D0=B4=D1=8B=20DEB?= =?UTF-8?q?UG,=20=D0=BF=D0=BE=D0=BA=D0=B0=20=D1=87=D1=82=D0=BE=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D0=BC=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=B3=D1=80=D0=BE=D0=B2=D1=8B=D1=85=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20-=20=D0=B1=D0=B5=D0=B7=20=D0=B1=D1=8D?= =?UTF-8?q?=D0=BA=D1=8D=D0=BD=D0=B4=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/login.py | 68 +++++++++++++++++++++++ frontend/message.py | 20 +++++++ frontend/todo_tk.py | 51 +++++++++++++++-- frontend/user.py | 13 +++++ frontend/workspace.py | 124 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 frontend/login.py create mode 100644 frontend/message.py create mode 100644 frontend/workspace.py diff --git a/frontend/login.py b/frontend/login.py new file mode 100644 index 0000000..64ba0e3 --- /dev/null +++ b/frontend/login.py @@ -0,0 +1,68 @@ +import tkinter as tk +from user import User +import message + + +class LoginFrame(tk.Frame): + + loggedIn = False + + def __init__(self, master=None, url=None) -> None: + """ + Функция инициаизации класса + """ + super().__init__(master) + + # Иницализируем параметры окна + self.master = master + self.pack(fill=tk.BOTH, expand=1) + + # self.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W) + # tk.Grid.rowconfigure(master, 0, weight=1) + # tk.Grid.columnconfigure(master, 0, weight=1) + + # Иницализируем параметры пользователя + self.user = User(url=url) + + # Настраиваем размеры и включаем иницализацию + self.initAUTH() + + def login_clicked(self) -> None: + """ + Функция авторизации + """ + try: + self.user.auth(self.login.get(), self.password.get()) + self.loggedIn = True + except Exception as ex: + print(ex) + message.invalid_login() + + def initAUTH(self) -> None: + """ + Создает окно авторизации программы + """ + # Конфигурируем сетку + for rows in range(25): + tk.Grid.rowconfigure(self, rows, weight=1) + + for columns in range(25): + tk.Grid.columnconfigure(self, columns, weight=1) + + # Подпись и поле ввода для логина + login_label = tk.Label(self, text="Введите логин") + login_label.grid(row=9, column=12, columnspan=3, rowspan=1, sticky="nsew") + + self.login = tk.Entry(self) + self.login.grid(row=10, column=12, columnspan=3, rowspan=1, sticky="nsew") + + # Подпись и поле ввода для пароля + password_label = tk.Label(self, text="Введите пароль") + password_label.grid(row=11, column=12, columnspan=3, rowspan=1, sticky="nsew") + + self.password = tk.Entry(self, show="*") + self.password.grid(row=12, column=12, columnspan=3, rowspan=1, sticky="nsew") + + # Кнопка авториазции + btn = tk.Button(self, text="Войти", command=self.login_clicked) + btn.grid(row=14, column=12, columnspan=3, rowspan=1, sticky="nsew") diff --git a/frontend/message.py b/frontend/message.py new file mode 100644 index 0000000..824d07b --- /dev/null +++ b/frontend/message.py @@ -0,0 +1,20 @@ +from tkinter import messagebox as mb + +TITLE_INFO_BOX = "Сообщение!" +MESSAGE_INVALID_LOGIN = "Неправильный логин или пароль" +MESSAGE_EMPTY = "Сдесь могло быть ваше сообщение" + + +def infobox(msg: str = None) -> None: + """ + Показывает передаваемое сообщение в messagebox + + :param msg: передаваемое сообщение + """ + if msg is None: + msg = MESSAGE_EMPTY + mb.showinfo(TITLE_INFO_BOX, msg) + + +def invalid_login(): + infobox(MESSAGE_INVALID_LOGIN) diff --git a/frontend/todo_tk.py b/frontend/todo_tk.py index 41e0e99..831ac6c 100644 --- a/frontend/todo_tk.py +++ b/frontend/todo_tk.py @@ -1,14 +1,55 @@ #!/usr/bin/env python3 +import sys import tkinter as tk +from login import LoginFrame +from workspace import WorkSpaceFrame + +if "win" in sys.platform.lower(): + DEFAULT_URL = "http://localhost:8000" +else: + DEFAULT_URL = "http://0.0.0.0:8000" + +BASE_W = 580 +BASE_H = 400 + +TITLE_APP = "ToDo Application" -class Application(tk.Frame): - def __init__(self, master=None): - super().__init__(master) +class Application(tk.Tk): + def __init__(self): + super().__init__() + self.center_window() + self.title(TITLE_APP) + + def login(self): + """Возвращает пользователя - его можно потом сериализовать""" + self.frame = LoginFrame(master=self, url=DEFAULT_URL) + while not self.frame.loggedIn: + self.update_idletasks() + self.update() + self.frame.destroy() + return self.frame.user + + def main(self, user): + self.frame = WorkSpaceFrame(master=self, user=user) + self.mainloop() + + def center_window(self, width: str = BASE_W, heigh: str = BASE_H) -> None: + """ + Центрирует приложение по центру экрана + + :param width: ширина окна + :param heigh: высота окна + """ + sw = self.winfo_screenwidth() + sh = self.winfo_screenheight() + + x = (sw - width) / 2 + y = (sh - heigh) / 2 + self.geometry("%dx%d+%d+%d" % (width, heigh, x, y)) if __name__ == "__main__": app = Application() - app.master.title("ToDo") - app.mainloop() + app.main(app.login()) diff --git a/frontend/user.py b/frontend/user.py index 38cda9b..5cdff8e 100644 --- a/frontend/user.py +++ b/frontend/user.py @@ -1,3 +1,5 @@ +import os + from datetime import datetime from api import UserApi @@ -9,6 +11,10 @@ class ToDoList(object): self.items = items self.created_at = created_at + def __iter__(self): + for item in self.items: + yield item + def __getitem__(self, index): return self.items[index] @@ -18,6 +24,9 @@ class ToDoList(object): def __str__(self): return f"[{self.id}] {self.title}" + def index(self, value): + return self.items.index(value) + # ToDo def remove(self, index): self.items.remove(self.items[index]) @@ -64,6 +73,10 @@ class ToDoItem(object): class User(UserApi): + def auth(self, user, passwd): + if "DEBUG" in os.environ: + return + UserApi.auth(self, user, passwd) # ToDo items = [ diff --git a/frontend/workspace.py b/frontend/workspace.py new file mode 100644 index 0000000..9a33bd1 --- /dev/null +++ b/frontend/workspace.py @@ -0,0 +1,124 @@ +import tkinter as tk + + +def str_time(time): + return time.strftime("%Y-%m-%d %H:%M:%S") + + +class ToDoItemWidget(tk.Frame): + def __init__(self, *args, item, parent=None, **argv): + super().__init__(*args, **argv) + + self.parent = parent + self.item = item + + self.noteLabel = tk.Label(self, text=item.text, width=15) + self.noteLabel.pack(side="left") + + self.finished = tk.IntVar(value=int(item.finished)) + self.finishedButton = tk.Checkbutton( + self, variable=self.finished, command=self.finishedButton_command + ) + self.finishedButton.pack(side="left") + + self.createdAt = tk.Label(self, text=str_time(item.created_at), width=15) + self.createdAt.pack(side="left") + + self.remove = tk.Button(self, text="Удалить", command=lambda: parent.remove(self.item)) + self.remove.pack(side="left") + + def finishedButton_command(self): + self.item.modify(finished=self.finished.get() > 0) + + +class ToDoListWidget(tk.Frame): + def __init__(self, *args, **argv): + super().__init__(*args, **argv) + + def fill(self, itemList): + + self.header = tk.Label(self, text="Текст | Выполнено | Время создания") + self.header.pack(side="top", fill="y") + + self.itemList = itemList + + for item in itemList: + item = ToDoItemWidget(self, item=item, parent=self) + item.pack(side="top", fill="y") + + self.itemToAdd = tk.Text(self, width=15, height=1) + self.itemToAdd.pack(side="top") + + add = tk.Button(self, text="Добавить", command=self.add_command) + add.pack(side="top") + + def update(self, itemList=None): + self.clear() + if itemList is None: + self.fill(self.itemList) + else: + self.fill(itemList) + + def add_command(self): + self.itemList.append(self.itemToAdd.get(1.0, "end")) + self.update() + + def remove(self, item): + self.itemList.remove(self.itemList.index(item)) + self.update() + + def clear(self): + for widget in self.winfo_children(): + widget.destroy() + + +class WorkSpaceFrame(tk.Frame): + def __init__(self, user, master=None, url=None) -> None: + """ + Функция инициаизации класса + """ + super().__init__(master) + + self.master = master + self.user = user + + self.pack(fill=tk.BOTH, expand=1) + self.initLayout(user) + + def initLayout(self, user): + + # data + self.lists = user.fetchUserLists() + + # select list box + self.listBox = tk.Listbox(self, width=30, selectmode=tk.SINGLE) + self.listBox.pack(side="left", fill="y") + self.listBox.bind("<>", self.listBox_selected) + + # scroll bar + scrollbar = tk.Scrollbar(self, orient="vertical") + scrollbar.config(command=self.listBox.yview) + scrollbar.pack(side="left", fill="y") + + # add scroll bar to list box + self.listBox.config(yscrollcommand=scrollbar.set) + + # fill list box + 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) + + def listBox_selected(self, *args): + + self.toToList.clear() + + selection = self.listBox.curselection() + cur = selection[0] + + self.toToList.fill(self.lists[cur]) From b6eb0b6d306e6e55cebac6c04aa30a9021f3f50e Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 22 Apr 2021 21:25:40 +0300 Subject: [PATCH 6/6] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/workspace.py | 59 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/frontend/workspace.py b/frontend/workspace.py index 9a33bd1..34830ec 100644 --- a/frontend/workspace.py +++ b/frontend/workspace.py @@ -5,26 +5,55 @@ def str_time(time): return time.strftime("%Y-%m-%d %H:%M:%S") +TODO_ITEM_TABLE_TEXT_WIDTH = 15 +TODO_ITEM_TABLE_FINISHED_WIDTH = 8 +TODO_ITEM_TABLE_CREATED_AT_WIDTH = 15 + + +def placeholder(): + print("Не реализовано") + + class ToDoItemWidget(tk.Frame): - def __init__(self, *args, item, parent=None, **argv): + @staticmethod + def header(parent): + body = tk.Frame(parent) + + text = tk.Label(body, text="Текст", width=TODO_ITEM_TABLE_TEXT_WIDTH) + text.pack(side="left") + + text = tk.Label(body, text="Выполнено", width=TODO_ITEM_TABLE_FINISHED_WIDTH) + text.pack(side="left") + + text = tk.Label(body, text="Создано", width=TODO_ITEM_TABLE_CREATED_AT_WIDTH) + text.pack(side="left") + + return body + + def __init__(self, *args, item, **argv): super().__init__(*args, **argv) - self.parent = parent + self.parent = self.master self.item = item - self.noteLabel = tk.Label(self, text=item.text, width=15) + self.noteLabel = tk.Label(self, text=item.text, width=TODO_ITEM_TABLE_TEXT_WIDTH) self.noteLabel.pack(side="left") self.finished = tk.IntVar(value=int(item.finished)) self.finishedButton = tk.Checkbutton( - self, variable=self.finished, command=self.finishedButton_command + self, + variable=self.finished, + command=self.finishedButton_command, + width=TODO_ITEM_TABLE_FINISHED_WIDTH, ) self.finishedButton.pack(side="left") - self.createdAt = tk.Label(self, text=str_time(item.created_at), width=15) + self.createdAt = tk.Label( + self, text=str_time(item.created_at), width=TODO_ITEM_TABLE_CREATED_AT_WIDTH + ) self.createdAt.pack(side="left") - self.remove = tk.Button(self, text="Удалить", command=lambda: parent.remove(self.item)) + self.remove = tk.Button(self, text="Удалить", command=lambda: self.parent.remove(self.item)) self.remove.pack(side="left") def finishedButton_command(self): @@ -37,21 +66,25 @@ class ToDoListWidget(tk.Frame): def fill(self, itemList): - self.header = tk.Label(self, text="Текст | Выполнено | Время создания") - self.header.pack(side="top", fill="y") + header = ToDoItemWidget.header(self) + header.pack(side="left") + header.pack(side="top", fill="y") self.itemList = itemList for item in itemList: - item = ToDoItemWidget(self, item=item, parent=self) + item = ToDoItemWidget(self, item=item) item.pack(side="top", fill="y") self.itemToAdd = tk.Text(self, width=15, height=1) self.itemToAdd.pack(side="top") - add = tk.Button(self, text="Добавить", command=self.add_command) + add = tk.Button(self, text="Добавить заметку", command=self.add_command) add.pack(side="top") + delete = tk.Button(self, text="Удалить лист", command=placeholder) + delete.pack(side="top") + def update(self, itemList=None): self.clear() if itemList is None: @@ -90,6 +123,12 @@ class WorkSpaceFrame(tk.Frame): # data self.lists = user.fetchUserLists() + text = tk.Text(self, width=15, height=1) + text.pack(anchor="sw") + + add = tk.Button(self, text="Добавить лист", command=placeholder) + add.pack(anchor="sw") + # select list box self.listBox = tk.Listbox(self, width=30, selectmode=tk.SINGLE) self.listBox.pack(side="left", fill="y")