From 633326ac89760fcea7f937921ac82c453cb475c9 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 14 Apr 2021 20:26:06 +0300 Subject: [PATCH 01/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20api?= =?UTF-8?q?=20=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 -- 2.49.1 From 6bf6f9d3f1ca8df8cc299ddf0a1d00612b81f41c Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 15 Apr 2021 20:31:24 +0300 Subject: [PATCH 02/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D1=82=D0=B8=D0=BB=D1=8F?= =?UTF-8?q?,=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=BE=D0=B9=20=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8,=20?= =?UTF-8?q?=D1=81=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D1=83=D1=8E=D1=89=D0=B5=D0=B9=20=D1=82=D0=B5=D0=BA=D1=83=D1=89?= =?UTF-8?q?=D0=B8=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) -- 2.49.1 From 6352deda3c4cc1ef6a1971f0e51edf1a011be04c Mon Sep 17 00:00:00 2001 From: Aleksey Lobanov Date: Sat, 17 Apr 2021 14:57:38 +0300 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=D0=9F=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B0=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BD=D0=B0=20=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=BD=D0=B5=20Django?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend/settings.py | 17 +++++++---------- backend/requirements.txt | 1 + 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 54f089b..56e9dba 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -27,8 +27,8 @@ SECRET_KEY = "django-insecure-toz+*lt(ejm!l*)92w2ciqoh^1kz#a(abbpcn54-dbw(nxoy&7 DEBUG = True ALLOWED_HOSTS = [] -if DEBUG: - ALLOWED_HOSTS = ["0.0.0.0"] +if DEBUG: + ALLOWED_HOSTS = ["0.0.0.0", "localhost", "127.0.0.1"] # Application definition @@ -45,13 +45,7 @@ INSTALLED_APPS = [ ] SWAGGER_SETTINGS = { - 'SECURITY_DEFINITIONS': { - 'Bearer': { - 'type': 'apiKey', - 'name': 'Authorization', - 'in': 'header' - } - } + "SECURITY_DEFINITIONS": {"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}} } MIDDLEWARE = [ @@ -130,7 +124,10 @@ SIMPLE_JWT = { } REST_FRAMEWORK = { - "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework_simplejwt.authentication.JWTAuthentication",) + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + ), + "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], } diff --git a/backend/requirements.txt b/backend/requirements.txt index 733426f..2c9488b 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,4 +1,5 @@ djangorestframework==3.12.4 +django-filter==2.4.0 markdown==3.3.4 appdirs==1.4.4 asgiref==3.3.4 -- 2.49.1 From 64607efaf36594b28308db4e6fb6b9d57195fc07 Mon Sep 17 00:00:00 2001 From: Aleksey Lobanov Date: Sat, 17 Apr 2021 14:58:49 +0300 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=D0=9D=D0=BE=D0=B2=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20=D0=B2=20=D0=B0=D0=B4?= =?UTF-8?q?=D0=BC=D0=B8=D0=BD=D0=BA=D0=B5=20=D0=B8=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8F=20finished?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend/admin.py | 9 ++++++++- .../migrations/0002_todoitem_finished.py | 18 ++++++++++++++++++ backend/backend/models.py | 5 +++-- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 backend/backend/migrations/0002_todoitem_finished.py diff --git a/backend/backend/admin.py b/backend/backend/admin.py index 30e3873..e409db7 100644 --- a/backend/backend/admin.py +++ b/backend/backend/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import ToDoList +from .models import ToDoList, ToDoItem class ToDoListAdmin(admin.ModelAdmin): @@ -9,4 +9,11 @@ class ToDoListAdmin(admin.ModelAdmin): list_editable = ["title"] +class ToDoItemAdmin(admin.ModelAdmin): + model = ToDoItem + list_display = ["parent", "finished", "text", "created_at"] + list_editable = ["finished", "text"] + + admin.site.register(ToDoList, ToDoListAdmin) +admin.site.register(ToDoItem, ToDoItemAdmin) diff --git a/backend/backend/migrations/0002_todoitem_finished.py b/backend/backend/migrations/0002_todoitem_finished.py new file mode 100644 index 0000000..2b47d01 --- /dev/null +++ b/backend/backend/migrations/0002_todoitem_finished.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-04-17 11:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("backend", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="todoitem", + name="finished", + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/backend/models.py b/backend/backend/models.py index 1213ca4..8770082 100644 --- a/backend/backend/models.py +++ b/backend/backend/models.py @@ -5,10 +5,11 @@ from django.contrib.auth.models import User class ToDoList(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, default=None) title = models.CharField(max_length=250) - created_at = models.DateTimeField(auto_now_add=True) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) class ToDoItem(models.Model): parent = models.ForeignKey(ToDoList, on_delete=models.CASCADE, null=False, default=None) text = models.TextField() - created_at = models.DateTimeField(auto_now_add=True) + finished = models.BooleanField(default=False, null=False, db_index=True) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) -- 2.49.1 From 8a2691065159887ab5554eb9d1aeb8c5f11c8ef4 Mon Sep 17 00:00:00 2001 From: Aleksey Lobanov Date: Sat, 17 Apr 2021 14:59:08 +0300 Subject: [PATCH 05/12] =?UTF-8?q?db:=20=D0=91=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B5=20=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=B2=D0=B0=D0=B6=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0003_auto_20210417_1157.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 backend/backend/migrations/0003_auto_20210417_1157.py diff --git a/backend/backend/migrations/0003_auto_20210417_1157.py b/backend/backend/migrations/0003_auto_20210417_1157.py new file mode 100644 index 0000000..1ddd3fc --- /dev/null +++ b/backend/backend/migrations/0003_auto_20210417_1157.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2 on 2021-04-17 11:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("backend", "0002_todoitem_finished"), + ] + + operations = [ + migrations.AlterField( + model_name="todoitem", + name="created_at", + field=models.DateTimeField(auto_now_add=True, db_index=True), + ), + migrations.AlterField( + model_name="todoitem", + name="finished", + field=models.BooleanField(db_index=True, default=False), + ), + migrations.AlterField( + model_name="todolist", + name="created_at", + field=models.DateTimeField(auto_now_add=True, db_index=True), + ), + ] -- 2.49.1 From 0f2f291c18f888b9dfc0ff636480a606a3115fc6 Mon Sep 17 00:00:00 2001 From: Aleksey Lobanov Date: Sat, 17 Apr 2021 14:59:26 +0300 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 56e9dba..32b39d6 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -128,6 +128,8 @@ REST_FRAMEWORK = { "rest_framework_simplejwt.authentication.JWTAuthentication", ), "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", + "PAGE_SIZE": 100, } -- 2.49.1 From 3f5083f8e4a66c5905f64308d8f429639c3683e1 Mon Sep 17 00:00:00 2001 From: Aleksey Lobanov Date: Sat, 17 Apr 2021 15:00:04 +0300 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=BE=20=D0=B1=D0=B0=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=BE=D0=B5=20=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE=D0=B5?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Минимальное, но самодостаточное --- backend/backend/api.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/backend/backend/api.py b/backend/backend/api.py index 3239ffc..70a43fe 100644 --- a/backend/backend/api.py +++ b/backend/backend/api.py @@ -1,20 +1,50 @@ from rest_framework import viewsets, serializers, permissions from rest_framework import routers +from django_filters.rest_framework import DjangoFilterBackend -from .models import ToDoList +from .models import ToDoList, ToDoItem + + +class ToDoItemSerializer(serializers.HyperlinkedModelSerializer): + parent = serializers.PrimaryKeyRelatedField(many=False, read_only=True) + + class Meta: + model = ToDoItem + fields = ["id", "text", "finished", "created_at", "parent"] + + +class ToDoItemViewSet(viewsets.ModelViewSet): + serializer_class = ToDoItemSerializer + permission_classes = [permissions.IsAuthenticated] + filter_backends = [DjangoFilterBackend] + filterset_fields = ["parent", "finished"] + + def get_queryset(self): + user = self.request.user + if not user.is_authenticated: + # ветка только для генерации схемы + return ToDoItem.objects.all() + return ToDoItem.objects.filter(parent__user=user) class ToDoListSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = ToDoList - fields = ["title", "created_at"] + fields = ["id", "title", "created_at"] class ToDoListViewSet(viewsets.ModelViewSet): - queryset = ToDoList.objects.all() serializer_class = ToDoListSerializer permission_classes = [permissions.IsAuthenticated] + def get_queryset(self): + user = self.request.user + if not user.is_authenticated: + # ветка только для генерации схемы + return ToDoList.objects.all() + return ToDoList.objects.filter(user=user) + router = routers.DefaultRouter() -router.register(r"lists", ToDoListViewSet) +router.register(r"lists", ToDoListViewSet, basename="ToDoLists") +router.register(r"todo_items", ToDoItemViewSet, basename="ToDoItems") -- 2.49.1 From f43c6526710b379fbbcad09f298cc759e599b3b2 Mon Sep 17 00:00:00 2001 From: Aleksey Lobanov Date: Sun, 18 Apr 2021 12:24:12 +0300 Subject: [PATCH 08/12] =?UTF-8?q?hotfix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=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=D1=81=D0=BF=D0=B8=D1=81=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=20=D0=B8=D1=85=20=D1=8D=D0=BB=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20=D0=B8=D0=B7=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend/api.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/backend/backend/api.py b/backend/backend/api.py index 70a43fe..75c2b7c 100644 --- a/backend/backend/api.py +++ b/backend/backend/api.py @@ -5,8 +5,14 @@ from django_filters.rest_framework import DjangoFilterBackend from .models import ToDoList, ToDoItem +class ToDoListField(serializers.PrimaryKeyRelatedField): + def get_queryset(self): + user = self.context["request"].user + return ToDoList.objects.filter(user=user) + + class ToDoItemSerializer(serializers.HyperlinkedModelSerializer): - parent = serializers.PrimaryKeyRelatedField(many=False, read_only=True) + parent = ToDoListField(many=False, read_only=False, help_text="ID родительского списка") class Meta: model = ToDoItem @@ -32,6 +38,12 @@ class ToDoListSerializer(serializers.HyperlinkedModelSerializer): model = ToDoList fields = ["id", "title", "created_at"] + def create(self, validated_data): + todo_list = ToDoList.objects.create( + user=self.context["request"].user, title=validated_data["title"] + ) + return todo_list + class ToDoListViewSet(viewsets.ModelViewSet): serializer_class = ToDoListSerializer -- 2.49.1 From 0d1e321b7e331ff52ae9e6cb33c0e88e0321dd7f Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 18 Apr 2021 16:07:59 +0300 Subject: [PATCH 09/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20api=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=D0=BE=20=D1=81=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=B0=D0=BC=D0=B8=20=D0=B8=20=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=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 -- 2.49.1 From 5836783e699e936c8dfde9b4e97a90f9b60a9618 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 18 Apr 2021 16:16:19 +0300 Subject: [PATCH 10/12] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=BE=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=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 -- 2.49.1 From 66beba6b0d1b29cfc137edb40bd1af6fde651924 Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 22 Apr 2021 20:35:48 +0300 Subject: [PATCH 11/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D1=84=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=20=D1=81?= =?UTF-8?q?=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=D1=8E=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=B9.=20=D0=94=D0=BB=D1=8F=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BF=D1=83=D1=81=D0=BA=D0=B0=20=D0=BD=D0=B5=D0=BE=D0=B1=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D0=BC=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D0=B0=D1=8F=20=D1=81=D1=80=D0=B5=D0=B4=D1=8B?= =?UTF-8?q?=20DEBUG,=20=D0=BF=D0=BE=D0=BA=D0=B0=20=D1=87=D1=82=D0=BE=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D0=BC=20=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=B8=D0=B3=D1=80=D0=BE=D0=B2=D1=8B=D1=85=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20-=20=D0=B1=D0=B5=D0=B7=20=D0=B1?= =?UTF-8?q?=D1=8D=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]) -- 2.49.1 From b6eb0b6d306e6e55cebac6c04aa30a9021f3f50e Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 22 Apr 2021 21:25:40 +0300 Subject: [PATCH 12/12] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=B8=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BD=D0=BE=D0=BF?= =?UTF-8?q?=D0=BE=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") -- 2.49.1