15 Commits

Author SHA1 Message Date
Ivan
7afb99d3d0 Добавил скроллбар в представление айтемов 2021-04-28 18:08:07 +03:00
Ivan
3d779590e9 Функциональная кнопка 2021-04-28 17:53:38 +03:00
Ivan
5ddadac47c Пофиксил апи сохранения токенов и режим отладки 2021-04-28 14:46:28 +03:00
Ivan
277f1e1aff Добавлено сохранение/восстановление токенов 2021-04-27 19:59:31 +03:00
Ivan
3e307506e9 Косметические правки 2021-04-27 17:05:55 +03:00
Ivan
c9610f7765 Косметические изменения в коде 2021-04-26 22:50:40 +03:00
Ivan
b2b447e392 Implemented most of the api placeholders 2021-04-26 22:42:56 +03:00
Aleksey Lobanov
7ba9f228b7 Merge pull request #22 from AlekseyLobanov/feat_8.frontend_development
Feat 8.frontend development
2021-04-26 15:37:09 +03:00
f43c652671 hotfix: Исправлено добавление списков и их элементов из API 2021-04-18 12:24:12 +03:00
Aleksey Lobanov
4886e2e9be Merge pull request #20 from AlekseyLobanov/feat_4.base_api
Добавлено базовое API + finished #4 #17
2021-04-17 15:01:03 +03:00
3f5083f8e4 feat: Реализовано базовое простое API
Минимальное, но самодостаточное
2021-04-17 15:00:04 +03:00
0f2f291c18 feat: Добавлена пагинация для API 2021-04-17 14:59:26 +03:00
8a26910651 db: Больше индексов для важных полей 2021-04-17 14:59:08 +03:00
64607efaf3 feat: Новая модель в админке и добавления поля finished 2021-04-17 14:58:49 +03:00
6352deda3c feat: Поддержка фильтрации на стороне Django 2021-04-17 14:57:38 +03:00
14 changed files with 552 additions and 107 deletions

View File

@@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import ToDoList from .models import ToDoList, ToDoItem
class ToDoListAdmin(admin.ModelAdmin): class ToDoListAdmin(admin.ModelAdmin):
@@ -9,4 +9,11 @@ class ToDoListAdmin(admin.ModelAdmin):
list_editable = ["title"] 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(ToDoList, ToDoListAdmin)
admin.site.register(ToDoItem, ToDoItemAdmin)

View File

@@ -1,20 +1,62 @@
from rest_framework import viewsets, serializers, permissions from rest_framework import viewsets, serializers, permissions
from rest_framework import routers from rest_framework import routers
from django_filters.rest_framework import DjangoFilterBackend
from .models import ToDoList 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 = ToDoListField(many=False, read_only=False, help_text="ID родительского списка")
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 ToDoListSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = ToDoList model = ToDoList
fields = ["title", "created_at"] 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): class ToDoListViewSet(viewsets.ModelViewSet):
queryset = ToDoList.objects.all()
serializer_class = ToDoListSerializer serializer_class = ToDoListSerializer
permission_classes = [permissions.IsAuthenticated] 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 = routers.DefaultRouter()
router.register(r"lists", ToDoListViewSet) router.register(r"lists", ToDoListViewSet, basename="ToDoLists")
router.register(r"todo_items", ToDoItemViewSet, basename="ToDoItems")

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -5,10 +5,11 @@ from django.contrib.auth.models import User
class ToDoList(models.Model): class ToDoList(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, default=None) user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, default=None)
title = models.CharField(max_length=250) 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): class ToDoItem(models.Model):
parent = models.ForeignKey(ToDoList, on_delete=models.CASCADE, null=False, default=None) parent = models.ForeignKey(ToDoList, on_delete=models.CASCADE, null=False, default=None)
text = models.TextField() 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)

View File

@@ -28,7 +28,7 @@ DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
if DEBUG: if DEBUG:
ALLOWED_HOSTS = ["0.0.0.0"] ALLOWED_HOSTS = ["0.0.0.0", "localhost", "127.0.0.1"]
# Application definition # Application definition
@@ -45,13 +45,7 @@ INSTALLED_APPS = [
] ]
SWAGGER_SETTINGS = { SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': { "SECURITY_DEFINITIONS": {"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}}
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
}
} }
MIDDLEWARE = [ MIDDLEWARE = [
@@ -130,7 +124,12 @@ SIMPLE_JWT = {
} }
REST_FRAMEWORK = { 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"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 100,
} }

View File

@@ -15,7 +15,7 @@ from .api import router
schema_view = get_schema_view( schema_view = get_schema_view(
openapi.Info( openapi.Info(
title="ToDo List", title="ToDo List",
default_version='v1', default_version="v1",
description="Swagger Interface for ToDo List", description="Swagger Interface for ToDo List",
), ),
public=True, public=True,
@@ -28,5 +28,5 @@ urlpatterns = [
path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("api/", include(router.urls)), path("api/", include(router.urls)),
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path("swagger/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"),
] ]

View File

@@ -1,4 +1,5 @@
djangorestframework==3.12.4 djangorestframework==3.12.4
django-filter==2.4.0
markdown==3.3.4 markdown==3.3.4
appdirs==1.4.4 appdirs==1.4.4
asgiref==3.3.4 asgiref==3.3.4

View File

@@ -13,12 +13,13 @@ API_TODO_ITEMS_DELETE = "api/todo_items/{0}/"
API_LISTS_LIST = "api/lists/" API_LISTS_LIST = "api/lists/"
API_LISTS_CREATE = "api/lists/" API_LISTS_CREATE = "api/lists/"
API_LISTS_READ = "lists/{0}/" API_LISTS_READ = "api/lists/{0}/"
API_LISTS_UPDATE = "lists/{0}/" API_LISTS_UPDATE = "api/lists/{0}/"
API_LISTS_PARTIAL_UPDATE = "lists/{0}/" API_LISTS_PARTIAL_UPDATE = "api/lists/{0}/"
API_LISTS_DELETE = "lists/{0}/" API_LISTS_DELETE = "api/lists/{0}/"
API_TOKEN = "api/token/" API_TOKEN_CREATE = "api/token/"
API_TOKEN_REFRESH = "api/token/refresh/"
class UserApi(object): class UserApi(object):
@@ -61,11 +62,23 @@ class UserApi(object):
""" """
token = UserApi._raise_or_return_( token = UserApi._raise_or_return_(
requests.post(url=self.get_api(API_TOKEN), json={"username": user, "password": passwd}) requests.post(
url=self.get_api(API_TOKEN_CREATE), json={"username": user, "password": passwd}
)
) )
self.token = SimpleNamespace(**token) self.token = SimpleNamespace(**token)
return self.token return self.token
def refresh(self):
"""
Refresh existing token
"""
token = UserApi._raise_or_return_(
requests.post(url=self.get_api(API_TOKEN_REFRESH), json={"refresh": self.token.refresh})
)
self.token.access = token["access"]
return self.token
def lists_list(self, **argv): def lists_list(self, **argv):
""" """
List all the exsiting to-do lists. List all the exsiting to-do lists.
@@ -102,6 +115,43 @@ class UserApi(object):
) )
) )
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): def todo_items_list(self, **argv):
""" """
List all the exsiting to-do items. List all the exsiting to-do items.
@@ -119,6 +169,67 @@ class UserApi(object):
) )
) )
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"): # def create(self, title="Untitled"):
# """ # """
# Create a new to-do list # Create a new to-do list
@@ -215,4 +326,8 @@ class UserApi(object):
@staticmethod @staticmethod
def _raise_or_return_(response): def _raise_or_return_(response):
response.raise_for_status() response.raise_for_status()
try:
return response.json() return response.json()
except Exception as e:
print(e)
return response.content

View File

@@ -1,48 +1,50 @@
import random
from user import User from user import User
def print_lists(lists): def print_lists(lists):
for item in 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" DEFAULT_URL = "http://127.0.0.1:8000"
user = User(url=DEFAULT_URL) user = User(url=DEFAULT_URL)
user.auth("root", "root") user.auth("root", "root")
user.refresh()
# Fetch existing lists: # Fetch existing lists:
lists = user.fetchUserLists() print_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: # Append a new list to user:
print("Appending list...") print("Appending list...")
scroll = user.appendUserList(title="a new list!") scroll = user.appendUserList(title="a new list!")
print_lists(lists) print_lists(user.fetchUserLists())
# Modify list 0: # Modify list 0:
print("Modifyng list...") print("Modifyng list...")
lists[0].modify(title="A new title") user.lists_[0].modify(title=f"A new title{random.random()}")
print_lists(lists) print_lists(user.fetchUserLists())
# Append item to list: # Append item to list:
print("Appending item to last list...") print("Appending item to last list...")
item = lists[-1].append(text="this is an item") item = user.lists_[-1].append(text="this is an item")
print_lists(lists) print_lists(user.fetchUserLists())
# Modifying item # Modifying item
print("Modifyng appended item...") print("Modifyng appended item...")
item.modify(finished=True, text="this is an updated item") item.modify(finished=True, text="this is an updated item")
print_lists(lists) print_lists(user.fetchUserLists())
# Removing item at 0 # Removing item at from last list
print("Removing item 0 from list 0...") print("Removing the last item from the last list...")
lists[0].remove(0) user.lists_[-1].remove(-1)
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())

View File

@@ -38,6 +38,13 @@ class LoginFrame(tk.Frame):
print(ex) print(ex)
message.invalid_login() message.invalid_login()
# Если захочется реализовать в логине
"""
@property
def remember(self):
return self.rbtn_var.get()
"""
def initAUTH(self) -> None: def initAUTH(self) -> None:
""" """
Создает окно авторизации программы Создает окно авторизации программы
@@ -66,3 +73,16 @@ class LoginFrame(tk.Frame):
# Кнопка авториазции # Кнопка авториазции
btn = tk.Button(self, text="Войти", command=self.login_clicked) btn = tk.Button(self, text="Войти", command=self.login_clicked)
btn.grid(row=14, column=12, columnspan=3, rowspan=1, sticky="nsew") btn.grid(row=14, column=12, columnspan=3, rowspan=1, sticky="nsew")
# Если захочется реализовать в логине
"""
# Запомнить пользователя
self.rbtn_var = tk.IntVar(value=0)
rbtn = tk.Checkbutton(
self,
text="Запомнить меня",
variable=self.rbtn_var,
command=None
)
rbtn.grid(row=15, column=12, columnspan=3, rowspan=1, sticky="nsew")
"""

View File

@@ -4,13 +4,14 @@ import sys
import tkinter as tk import tkinter as tk
from login import LoginFrame from login import LoginFrame
from workspace import WorkSpaceFrame from workspace import WorkSpaceFrame
from user import User
if "win" in sys.platform.lower(): if "win" in sys.platform.lower():
DEFAULT_URL = "http://localhost:8000" DEFAULT_URL = "http://localhost:8000"
else: else:
DEFAULT_URL = "http://0.0.0.0:8000" DEFAULT_URL = "http://0.0.0.0:8000"
BASE_W = 580 BASE_W = 600
BASE_H = 400 BASE_H = 400
TITLE_APP = "ToDo Application" TITLE_APP = "ToDo Application"
@@ -24,11 +25,22 @@ class Application(tk.Tk):
def login(self): def login(self):
"""Возвращает пользователя - его можно потом сериализовать""" """Возвращает пользователя - его можно потом сериализовать"""
# Пользователь сохранен! Авторизация не нужна!
try:
user = User.load()
if user is not None:
return user
except Exception as e:
print("Failed to restore login:", e)
# Не удалось - нужен логин
self.frame = LoginFrame(master=self, url=DEFAULT_URL) self.frame = LoginFrame(master=self, url=DEFAULT_URL)
while not self.frame.loggedIn: while not self.frame.loggedIn:
self.update_idletasks() self.update_idletasks()
self.update() self.update()
self.frame.destroy() self.frame.destroy()
# Нужно запомнить пользователя
# if self.frame.remember:
# self.frame.user.save()
return self.frame.user return self.frame.user
def main(self, user): def main(self, user):

View File

@@ -1,85 +1,140 @@
import os import os
import random
from datetime import datetime from datetime import datetime
from types import SimpleNamespace
from pathlib import Path
import json
from api import UserApi 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"
USER_TOKEN_PATH = os.path.join(Path.home(), ".todo_config.json")
def bad_arguments(x, d):
return list((set(x) - set(d)))
def date_or_str(inpt):
if type(inpt) is datetime:
return inpt
elif type(inpt) is str:
return datetime.strptime(inpt, DATETIME_STR)
else:
return datetime.now()
class ToDoList(object): 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.id = id
self.title = title self.title = title
self.items = items self.items_ = [] if items is None else items
self.created_at = created_at self.created_at = date_or_str(created_at)
self.user = user
def __iter__(self): def __iter__(self):
for item in self.items: for item in self.items_:
yield item yield item
def __getitem__(self, index): def __getitem__(self, index):
return self.items[index] return self.items_[index]
def __len__(self): def __len__(self):
return len(self.items) return len(self.items_)
def __str__(self): def __str__(self):
return f"[{self.id}] {self.title}" return f"[{self.id}] {self.title}"
def index(self, value): def index(self, value):
return self.items.index(value) return self.items_.index(value)
# ToDo
def remove(self, index): def remove(self, index):
self.items.remove(self.items[index]) """
self.sync() Remove item AT INDEX from db
"""
item = self.items_[index]
self.items_.remove(item)
item.dispose()
# ToDo
def append(self, text): def append(self, text):
item = ToDoItem(id=None, text=text, created_at=datetime.now()) """
self.items.append(item) Add a new item to db
item.sync() """
self.sync() if "DEBUG" in os.environ:
return item created_item = ToDoItem(id=random.randint(100, 1000), text=text, user=self.user)
else:
created_item = self.user.todo_items_create(parent=self.id, text=text)
created_item = ToDoItem(**created_item, user=self.user)
self.items_.append(created_item)
return created_item
def modify(self, **argv): 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(): for key, value in argv.items():
setattr(self, key, value) setattr(self, key, value)
self.sync() self.sync()
# ToDo def dispose(self):
print(f"To-do list id '{self.id}' is being disposed of...")
for item in self.items_:
item.dispose()
if "DEBUG" in os.environ:
return
self.user.lists_delete(self.id)
def sync(self): def sync(self):
# ToDo send request or store in form
print(f"Item '{self}' is being synchronized...") 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): 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.id = id
self.text = text self.text = text
self.finished = finished self.finished = finished
self.created_at = created_at self.created_at = date_or_str(created_at)
self.parent = parent
self.user = user
def __str__(self): def __str__(self):
return f"[{self.id}] {self.text}" return f"[{self.id}] {self.text}"
def modify(self, **argv): def modify(self, **argv):
bad = bad_arguments(argv.keys(), TODO_ITEM_UPDATEBLE)
if len(bad) > 0:
raise RuntimeError(UPDATE_ERROR.format(bad[0]))
for key, value in argv.items(): for key, value in argv.items():
setattr(self, key, value) setattr(self, key, value)
self.sync() self.sync()
# ToDo def dispose(self):
def sync(self): print(f"To-do item id '{self.id}' is being disposed of...")
# ToDo send request or store in form
print(f"Item '{self}' is being synchronized...")
class User(UserApi):
def auth(self, user, passwd):
if "DEBUG" in os.environ: if "DEBUG" in os.environ:
return return
UserApi.auth(self, user, passwd) self.user.todo_items_delete(self.id)
# ToDo def sync(self):
items = [ 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( ToDoList(
id=i, id=i,
title=f"List {i}", title=f"List {i}",
@@ -92,17 +147,106 @@ class User(UserApi):
for i in range(10) for i in range(10)
] ]
# ToDo
class User(UserApi):
def auth(self, user, passwd):
"""
Basic authentification
"""
if "DEBUG" in os.environ:
return
return UserApi.auth(self, user, passwd)
def remove(self):
"""
Remove the login file from homedir
"""
if "DEBUG" in os.environ:
print("Debug mode is on - no login storing")
return
if not os.path.exists(USER_TOKEN_PATH):
return
try:
os.remove(USER_TOKEN_PATH)
except Exception as e:
raise RuntimeError("Failed to remove tokens:", e)
def save(self):
"""
Store user token in homedir
"""
if "DEBUG" in os.environ:
print("Debug mode is on - no login storing")
return
try:
with open(USER_TOKEN_PATH, "w") as handler:
json.dump(self.token.__dict__, handler)
except Exception as e:
raise RuntimeError("Failed to store tokens:", e)
@staticmethod
def load():
"""
Restore user token from the file in homedir
"""
if "DEBUG" in os.environ:
raise RuntimeError("Debug mode is on - no login storing")
if os.path.exists(USER_TOKEN_PATH):
try:
with open(USER_TOKEN_PATH, "r") as handler:
user = User(token=SimpleNamespace(**json.load(handler)))
user.refresh()
return user
except Exception as e:
raise RuntimeError("Failed to restore tokens:", e)
return None
# Storing lists - mostly for debug purposes
lists_ = make_debug_lists()
def fetchUserLists(self): 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"]
todo_lists = {x["id"]: ToDoList(**x, user=self) for x in user_lists}
todo_items = [ToDoItem(**x, user=self) for x in user_items]
for todo_item in todo_items:
# Catching stray items
# if not hasattr(toDoItem, "parent"):
# toDoItem.dispose()
# continue
todo_lists[todo_item.parent].items_.append(todo_item)
for todo_list in todo_lists.values():
todo_list.items_ = sorted(todo_list.items_, key=lambda x: x.created_at)
self.lists_ = sorted(todo_lists.values(), key=lambda x: x.created_at)
return self.lists_
# ToDo
def removeUserList(self, id): def removeUserList(self, id):
self.items = [item for item in self.items if item.id != id] """
Remove existing user to-do list BY ID from the serverreturns:
"""
to_remove = [item for item in self.lists_ if item.id == id][0]
self.lists_.remove(to_remove)
# if not ("DEBUG" in os.environ):
to_remove.dispose()
return self.lists_
# ToDo
def appendUserList(self, title): def appendUserList(self, title):
item = ToDoList(id=None, title=title, created_at=datetime.now()) """
self.items.append(item) Create a new user list
item.sync() title: title of list to create
returns: created item
"""
if "DEBUG" in os.environ:
item = ToDoList(id=random.randint(100, 1000), title=title, created_at=datetime.now())
self.lists_.append(item)
return item return item
created_list = self.lists_create(title=title)
created_list = ToDoList(**created_list)
return created_list

View File

@@ -61,8 +61,9 @@ class ToDoItemWidget(tk.Frame):
class ToDoListWidget(tk.Frame): class ToDoListWidget(tk.Frame):
def __init__(self, *args, **argv): def __init__(self, *args, delete_list, **argv):
super().__init__(*args, **argv) super().__init__(*args, **argv)
self.delete_list = delete_list
def fill(self, itemList): def fill(self, itemList):
@@ -82,7 +83,7 @@ class ToDoListWidget(tk.Frame):
add = tk.Button(self, text="Добавить заметку", command=self.add_command) add = tk.Button(self, text="Добавить заметку", command=self.add_command)
add.pack(side="top") add.pack(side="top")
delete = tk.Button(self, text="Удалить лист", command=placeholder) delete = tk.Button(self, text="Удалить лист", command=self.delete_list)
delete.pack(side="top") delete.pack(side="top")
def update(self, itemList=None): def update(self, itemList=None):
@@ -106,6 +107,17 @@ class ToDoListWidget(tk.Frame):
class WorkSpaceFrame(tk.Frame): class WorkSpaceFrame(tk.Frame):
def delete_list(self, *args):
selection = self.listBox.curselection()
cur = selection[0]
self.user.removeUserList(self.lists[cur].id)
self.lists = self.user.fetchUserLists()
self.update_lists()
if len(self.lists) > 1:
self.listBox.selection_set(first=cur - 1)
elif len(self.lists) > 0:
self.listBox.selection_set(first=0)
def __init__(self, user, master=None, url=None) -> None: def __init__(self, user, master=None, url=None) -> None:
""" """
Функция инициаизации класса Функция инициаизации класса
@@ -118,15 +130,27 @@ class WorkSpaceFrame(tk.Frame):
self.pack(fill=tk.BOTH, expand=1) self.pack(fill=tk.BOTH, expand=1)
self.initLayout(user) self.initLayout(user)
def destroy(self):
tk.Tk.destroy(self)
if self.rbtn_var.get() > 0:
self.user.save()
else:
self.user.remove()
def initLayout(self, user): def initLayout(self, user):
# Запомнить пользователя
self.rbtn_var = tk.IntVar(value=1)
rbtn = tk.Checkbutton(self, text="Запомнить меня", variable=self.rbtn_var, command=None)
rbtn.pack(anchor="n")
# data # data
self.lists = user.fetchUserLists() self.lists = user.fetchUserLists()
text = tk.Text(self, width=15, height=1) self.add_list_text = tk.Text(self, width=15, height=1)
text.pack(anchor="sw") 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") add.pack(anchor="sw")
# select list box # select list box
@@ -142,22 +166,54 @@ class WorkSpaceFrame(tk.Frame):
# add scroll bar to list box # add scroll bar to list box
self.listBox.config(yscrollcommand=scrollbar.set) self.listBox.config(yscrollcommand=scrollbar.set)
# fill list box # init list view
self.update_lists()
# canvas for todo lists
canvas = tk.Canvas(self)
canvas.pack(side="left", fill="both", expand=1)
scrollbar = tk.Scrollbar(self, orient="vertical")
scrollbar.config(command=canvas.yview)
scrollbar.pack(side="left", fill="y")
canvas.configure(yscrollcommand=scrollbar.set)
# todo lists
self.toDoList = ToDoListWidget(self, delete_list=self.delete_list)
self.toDoList.grid_propagate(True)
# self.toDoList = ToDoListWidget(canvas)
self.toDoList.pack(side="left", fill="y")
canvas.bind("<Configure>", lambda *argv: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=self.toDoList, anchor="nw")
# select list!
if len(self.lists) > 0:
self.listBox.selection_set(first=0)
self.listBox_selected()
def update_lists(self):
self.listBox.delete(0, "end")
for item in self.lists: for item in self.lists:
s = f"{str(item)}: {item.created_at.strftime('%Y-%m-%d %H:%M:%S')}" s = f"{str(item)}: {item.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
self.listBox.insert(tk.END, s) self.listBox.insert(tk.END, s)
self.listBox.pack() self.listBox.pack()
len(self.lists) > 0 and self.listBox.selection_set(first=0) return len(self.lists)
# todo lists def add_list(self, *args):
self.toToList = ToDoListWidget(self) text = self.add_list_text.get(1.0, "end").strip()
self.toToList.pack(side="left", fill="both", expand=1) if len(text) == 0:
print("empty name! Not adding!")
return
self.user.appendUserList(title=text)
self.lists = self.user.fetchUserLists()
# fill list box
self.update_lists()
len(self.lists) > 0 and self.listBox.selection_set(first=len(self.lists) - 1)
def listBox_selected(self, *args): def listBox_selected(self, *args):
self.toDoList.clear()
self.toToList.clear()
selection = self.listBox.curselection() selection = self.listBox.curselection()
cur = selection[0] cur = selection[0]
self.toToList.fill(self.lists[cur]) self.toDoList.fill(self.lists[cur])