From df20e4804c99cbde77e0e711d386b45504c11bce Mon Sep 17 00:00:00 2001 From: Drew B <103056414+arbowl@users.noreply.github.com> Date: Sat, 4 May 2024 22:51:39 -0400 Subject: [PATCH 1/6] Update for Python 3.12, fix minor visuals --- ArabicLabel.py | 17 ++++ app.py | 222 +++++++++++++++++++++++++++++++++++++++++++++++++ login.html | 49 +++++++++++ main.py | 111 +++++++++++++++++++++++++ main_page.html | 26 ++++++ run_web.sh | 2 +- tasks.html | 115 +++++++++++++++++++++++++ user.py | 54 ++++++++++++ 8 files changed, 595 insertions(+), 1 deletion(-) create mode 100644 ArabicLabel.py create mode 100644 app.py create mode 100644 login.html create mode 100644 main.py create mode 100644 main_page.html create mode 100644 tasks.html create mode 100644 user.py diff --git a/ArabicLabel.py b/ArabicLabel.py new file mode 100644 index 0000000..a07d3e2 --- /dev/null +++ b/ArabicLabel.py @@ -0,0 +1,17 @@ +from kivymd.label import MDLabel +import arabic_reshaper +from bidi.algorithm import get_display + + +class ArabicLabel(MDLabel): + # def __init__( self, **kwargs ): + # super( ArabicLabel, self ).__init__( **kwargs ); + + # self.on_font_style( None, self.font_style ) + + # Overridden + def on_font_style(self, instance, style): + super(ArabicLabel, self).on_font_style(instance, style) + + self.text = get_display(arabic_reshaper.reshape(self.text)) + self.font_name = "fonts/KacstPenE" diff --git a/app.py b/app.py new file mode 100644 index 0000000..ff12757 --- /dev/null +++ b/app.py @@ -0,0 +1,222 @@ +from flask import Flask, render_template, request, make_response, redirect, Response +from hashlib import md5 +from sqlite3 import connect, Connection, Cursor +from time import strftime +from typing import Optional +from user import User + +app = Flask(__name__) + + +def get_connection() -> tuple[Connection, Cursor]: + connection = connect("data.db") + cursor = connection.cursor() + return connection, cursor + + +def is_installed() -> bool: + _, cursor = get_connection() + cursor.execute("SELECT COUNT( 1 ) FROM USERS") + has_user = cursor.fetchone()[0] + return has_user > 0 + + +def add_request(request_type) -> None: + connection, cursor = get_connection() + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "INSERT INTO UPDATE_REQUESTS( UPDATE_TYPE, DONE, CREATION_DATE ) VALUES" + " ( ?, 'N', ? )", + (request_type, creation_date), + ) + connection.commit() + + +@app.before_request +def before_request() -> Optional[str]: + if request.path == "/install": + return None + if not is_installed(): + return render_template("install_prettypi.html") + if request.path == "/login": + return None + if request.cookies.get("prettypi_username") is not None: + User.set_username(request.cookies.get("prettypi_username")) + User.set_hashed_password(request.cookies.get("prettypi_password")) + return None if User.has_permission() else render_template("login.html") + return render_template("login.html") + + +@app.route("/") +def main() -> str: + return render_template("main_page.html", name=User.get_name()) + + +@app.route("/install", methods=["POST"]) +def install() -> str: + connection, cursor = get_connection() + if is_installed(): + return render_template( + "installer_message.html", + message="PrettyPi Already Installed", + type="danger", + ) + if ( + not request.form["username"] + or not request.form["password"] + or not request.form["name"] + ): + return render_template( + "installer_message.html", message="Please fill all fields", type="danger" + ) + hash_function = md5(request.form["password"].encode("utf-8")) + hashed_password = hash_function.hexdigest() + cursor.execute( + "INSERT INTO USERS( USER_ID, USERNAME, PASSWORD, NAME ) VALUES ( NULL, ?, ?, ? )", + [request.form["username"], hashed_password, request.form["name"]], + ) + connection.commit() + return render_template( + "installer_message.html", + message="Congratulations! PrettyPi has been initialized. "\ + "Go back to homepage to start using it", + type="success", + ) + + +@app.route("/login", methods=["POST"]) +def login() -> str: + User.set_username(request.form["username"]) + User.set_password(request.form["password"].encode("utf-8")) + if User.has_permission(): + response = make_response(redirect("/")) + response.set_cookie("prettypi_username", User.get_username()) + response.set_cookie("prettypi_password", User.get_hashed_password()) + return response + return "Invalid" + + +@app.route("/logout") +def logout() -> Response: + response = make_response(redirect("/")) + response.set_cookie("prettypi_username", "") + response.set_cookie("prettypi_password", "") + return response + + +@app.route("/tasks") +def tasks_main(): + _, cursor = get_connection() + cursor.execute("SELECT * FROM TODO WHERE DONE = 'N' ORDER BY CREATION_DATE DESC") + tasks = cursor.fetchall() + cursor.execute("SELECT * FROM TODO WHERE DONE = 'Y' ORDER BY CREATION_DATE DESC") + done_tasks = cursor.fetchall() + cursor.execute("SELECT * FROM TODO WHERE WORKING_ON = 'Y'") + working_task = cursor.fetchone() + return render_template( + "tasks.html", + tasks=tasks, + done_tasks=done_tasks, + name=User.get_name(), + working_task=working_task, + ) + + +@app.route("/add_task", methods=["POST"]) +def new_task(): + connection, cursor = get_connection() + if not request.form["task_details"]: + return render_template( + "message.html", message="The details should not be empty", type="warning" + ) + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "INSERT INTO TODO( TASK_ID, TASK, CREATION_DATE ) VALUES ( NULL, ?, ? )", + [request.form["task_details"], creation_date], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/delete_task", methods=["GET"]) +def delete_task() -> Response | str: + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return "Invalid" + cursor.execute("DELETE FROM TODO WHERE TASK_ID = ?", [task_id]) + cursor.execute("DELETE FROM TASKS_LOG WHERE TASK_ID = ?", [task_id]) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/task_done", methods=["GET"]) +def mark_task_as_done() -> Response | str: + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return "Invalid" + current_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "UPDATE TODO SET WORKING_ON = 'N', DONE = 'Y', DONE_AT = ? WHERE TASK_ID = ?", + [current_date, task_id], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/start_task", methods=["GET"]) +def start_working_on_task() -> Response | str: + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return "Invalid" + cursor.execute( + "SELECT COUNT( 1 ) FROM TODO WHERE WORKING_ON = 'Y' AND TASK_ID <> ?", + [task_id] + ) + currently_working_on = cursor.fetchone()[0] + if currently_working_on > 0: + return render_template( + "message.html", + message="You're already working on another task!", + type="warning", + ) + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute("UPDATE TODO SET WORKING_ON = 'Y' WHERE TASK_ID = ?",[task_id]) + cursor.execute( + "INSERT INTO TASKS_LOG( TASK_ID, START_AT ) VALUES ( ?, ? )", + [task_id, creation_date], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/stop_task", methods=["GET"]) +def stop_working_on_task() -> Response | str: + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return "Invalid" + cursor.execute( + "SELECT MAX( log_id ) FROM tasks_log WHERE TASK_ID = ? AND ENDED_AT IS NULL", + [task_id], + ) + current_task_log_id = cursor.fetchone()[0] + current_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute("UPDATE TODO SET WORKING_ON = 'N' WHERE TASK_ID = ?", [task_id]) + cursor.execute( + "UPDATE TASKS_LOG SET ENDED_AT = ? WHERE LOG_ID = ?", + [current_date, current_task_log_id], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +if __name__ == "__main__": + app.debug = True diff --git a/login.html b/login.html new file mode 100644 index 0000000..28e19fe --- /dev/null +++ b/login.html @@ -0,0 +1,49 @@ + + + + + + PrettyPi | Log in + + + + + + + + +
+ +
+
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+
+ + + + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..35ba26c --- /dev/null +++ b/main.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf8 -*- + +# [MQH] 16 June 2017. It has been a while since last code I have written in Python! :-) +from kivy.app import App +from kivy.lang import Builder +from kivymd.theming import ThemeManager +from kivy.properties import ObjectProperty +from kivymd.label import MDLabel +from kivy.animation import Animation +from ArabicLabel import ArabicLabel +import arabic_reshaper +import sqlite3 +import threading +from kivy.clock import Clock + +kv_config = """ +#:import Toolbar kivymd.toolbar.Toolbar +#:import NavigationLayout kivymd.navigationdrawer.NavigationLayout +#:import MDNavigationDrawer kivymd.navigationdrawer.MDNavigationDrawer + +NavigationLayout: + id: navLayout + + MDNavigationDrawer: + id: navDrawer + NavigationDrawerToolbar: + title: "Navigation Drawer" + NavigationDrawerIconButton: + id: quit_button + icon: 'checkbox-blank-circle' + text: "Quit" + + BoxLayout: + id: topBox + orientation: 'vertical' + Toolbar: + id: toolbar + title: 'My Pretty Pi!' + md_bg_color: app.theme_cls.primary_color + background_palette: 'Primary' + background_hue: '500' + left_action_items: [['menu', lambda x: app.root.toggle_nav_drawer()]] + right_action_items: [['dots-vertical', lambda x: app.root.toggle_nav_drawer()]] + + Toolbar: + id: titlebar + title: 'Current TODO' + md_bg_color: app.theme_cls.primary_color + background_palette: 'Primary' + background_hue: '900' + + ScreenManager: + id: screenManager + + Screen: + name: 'mainScreen' + + BoxLayout: + id: main_box + orientation: 'vertical' + """ + + +class PrettyPiApp(App): + theme_cls = ThemeManager() + main_box = None + connection = None + cursor = None + kv_main = kv_config + + def build(self): + main_widget = Builder.load_string(self.kv_main) + self.connection = sqlite3.connect("data.db") + self.cursor = self.connection.cursor() + self.main_box = main_widget.ids.mainBox + self.quit_button = main_widget.ids.quitBtn + self.quit_button.bind(on_press=lambda e: exit()) + self.refresh_list() + Clock.schedule_interval(self.check_updates, 0.5) + return main_widget + + def refresh_list(self) -> None: + self.main_box.clear_widgets() + self.cursor.execute("SELECT * FROM TODO WHERE DONE = 'N'") + tasks = self.cursor.fetchall() + for task in tasks: + task_text = task[1] + if task[5] == "Y": + task_text += " (Working On)" + self.main_box.add_widget( + ArabicLabel(text=task_text, halign="center", font_style="Display1") + ) + + def check_updates(self) -> None: + self.cursor.execute( + "SELECT COUNT( 1 ) FROM UPDATE_REQUESTS WHERE UPDATE_TYPE = "\ + "'UPDATE_TODO_LIST' AND DONE = 'N'" + ) + result = self.cursor.fetchone() + if result[0] > 0: + self.refresh_list() + self.cursor.execute( + "UPDATE UPDATE_REQUESTS SET DONE = 'Y' WHERE UPDATE_TYPE = "\ + "'UPDATE_TODO_LIST'" + ) + self.connection.commit() + + +if __name__ == "__main__": + PrettyPiApp().run() diff --git a/main_page.html b/main_page.html new file mode 100644 index 0000000..1df378a --- /dev/null +++ b/main_page.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block content %} + +
+ +
+

+ PrettyPi + Version 0.0.1 +

Click "Tasks" to view ongoing tasks
+ + +
+ + +
+
+ +
+ + +{% endblock %} diff --git a/run_web.sh b/run_web.sh index 2c3418c..dc7ebb5 100755 --- a/run_web.sh +++ b/run_web.sh @@ -1,3 +1,3 @@ export FLASK_APP=control/app.py export FLASK_DEBUG=1 -flask run --host=0.0.0.0 +flask run --host=0.0.0.0 --port=5000 diff --git a/tasks.html b/tasks.html new file mode 100644 index 0000000..5b2e7d6 --- /dev/null +++ b/tasks.html @@ -0,0 +1,115 @@ +{% extends "base.html" %} + +{% block content %} + +
+ +
+

+ Tasks +

+ +
+ + +
+ + {% if working_task != None %} +
+ + +
+ Working On + {{ working_task.1 }} +
+
+ {% endif %} + +
+
+
+
+

New Task

+
+
+
+
+ + +
+
+ +
+
+ +
+
+

TODO

+
+
+ + + + + + + + + + {% for task in tasks %} + + + + {% if task.5 == 'N' %} + + {% else %} + + {% endif %} + + + + + {% endfor %} +
#TaskStartDoneDeleteCreated On
{{ loop.index }}.{{ task.1 }}StartStopDoneDelete{{ task.2 }}
+
+
+ +
+
+

Done!

+
+
+ + + + + + + + + {% for doneTask in doneTasks %} + + + + + + + + {% endfor %} +
#TaskCreated OnDone AtDelete
{{ loop.index }}.{{ doneTask.1 }}{{ doneTask.2 }}{{ doneTask.4 }}Delete
+
+
+
+ +
+
+ +
+ + +{% endblock %} diff --git a/user.py b/user.py new file mode 100644 index 0000000..b02b178 --- /dev/null +++ b/user.py @@ -0,0 +1,54 @@ +from hashlib import md5 +from sqlite3 import Connection, Cursor, connect +from typing import Optional + + +class User: + __username = Optional[str] + __password = Optional[str] + __name = Optional[str] + __cursor = Optional[Cursor] + + @classmethod + def get_connection(cls) -> tuple[Connection, Cursor]: + connection = connect("data.db") + cursor = connection.cursor() + return connection, cursor + + @classmethod + def has_permission(cls) -> bool: + _, cursor = cls.get_connection() + cursor.execute( + "SELECT * FROM USERS WHERE USERNAME = ? AND PASSWORD = ?", + (cls.__username, cls.__password), + ) + result = cursor.fetchall() + if len(result) > 0: + cls.__name = result[0][3] + return True + return False + + @classmethod + def set_username(cls, username: str) -> None: + cls.__username = username + + @classmethod + def set_password(cls, password: str) -> None: + hashFunction = md5(password) + cls.__password = hashFunction.hexdigest() + + @classmethod + def set_hashed_password(cls, hashedPassword) -> None: + cls.__password = hashedPassword + + @classmethod + def get_username(cls) -> str: + return cls.__username + + @classmethod + def get_hashed_password(cls) -> str: + return cls.__password + + @classmethod + def get_name(cls) -> str: + return cls.__name From 30eb53b254ec8030e88f3c432f8d449f65854184 Mon Sep 17 00:00:00 2001 From: Drew B <103056414+arbowl@users.noreply.github.com> Date: Sat, 4 May 2024 22:54:04 -0400 Subject: [PATCH 2/6] Fix file structure from desktop app push --- ArabicLabel.py | 17 -- app.py | 222 --------------- control/app.py | 464 ++++++++++++++----------------- control/templates/login.html | 99 +++---- control/templates/main_page.html | 1 + control/templates/tasks.html | 59 ++-- control/user.py | 100 +++---- login.html | 49 ---- main.py | 111 -------- main_page.html | 26 -- server/ArabicLabel.py | 19 +- server/main.py | 116 ++++---- tasks.html | 115 -------- user.py | 54 ---- 14 files changed, 397 insertions(+), 1055 deletions(-) delete mode 100644 ArabicLabel.py delete mode 100644 app.py delete mode 100644 login.html delete mode 100644 main.py delete mode 100644 main_page.html delete mode 100644 tasks.html delete mode 100644 user.py diff --git a/ArabicLabel.py b/ArabicLabel.py deleted file mode 100644 index a07d3e2..0000000 --- a/ArabicLabel.py +++ /dev/null @@ -1,17 +0,0 @@ -from kivymd.label import MDLabel -import arabic_reshaper -from bidi.algorithm import get_display - - -class ArabicLabel(MDLabel): - # def __init__( self, **kwargs ): - # super( ArabicLabel, self ).__init__( **kwargs ); - - # self.on_font_style( None, self.font_style ) - - # Overridden - def on_font_style(self, instance, style): - super(ArabicLabel, self).on_font_style(instance, style) - - self.text = get_display(arabic_reshaper.reshape(self.text)) - self.font_name = "fonts/KacstPenE" diff --git a/app.py b/app.py deleted file mode 100644 index ff12757..0000000 --- a/app.py +++ /dev/null @@ -1,222 +0,0 @@ -from flask import Flask, render_template, request, make_response, redirect, Response -from hashlib import md5 -from sqlite3 import connect, Connection, Cursor -from time import strftime -from typing import Optional -from user import User - -app = Flask(__name__) - - -def get_connection() -> tuple[Connection, Cursor]: - connection = connect("data.db") - cursor = connection.cursor() - return connection, cursor - - -def is_installed() -> bool: - _, cursor = get_connection() - cursor.execute("SELECT COUNT( 1 ) FROM USERS") - has_user = cursor.fetchone()[0] - return has_user > 0 - - -def add_request(request_type) -> None: - connection, cursor = get_connection() - creation_date = strftime("%d-%m-%Y %H:%M:%S") - cursor.execute( - "INSERT INTO UPDATE_REQUESTS( UPDATE_TYPE, DONE, CREATION_DATE ) VALUES" - " ( ?, 'N', ? )", - (request_type, creation_date), - ) - connection.commit() - - -@app.before_request -def before_request() -> Optional[str]: - if request.path == "/install": - return None - if not is_installed(): - return render_template("install_prettypi.html") - if request.path == "/login": - return None - if request.cookies.get("prettypi_username") is not None: - User.set_username(request.cookies.get("prettypi_username")) - User.set_hashed_password(request.cookies.get("prettypi_password")) - return None if User.has_permission() else render_template("login.html") - return render_template("login.html") - - -@app.route("/") -def main() -> str: - return render_template("main_page.html", name=User.get_name()) - - -@app.route("/install", methods=["POST"]) -def install() -> str: - connection, cursor = get_connection() - if is_installed(): - return render_template( - "installer_message.html", - message="PrettyPi Already Installed", - type="danger", - ) - if ( - not request.form["username"] - or not request.form["password"] - or not request.form["name"] - ): - return render_template( - "installer_message.html", message="Please fill all fields", type="danger" - ) - hash_function = md5(request.form["password"].encode("utf-8")) - hashed_password = hash_function.hexdigest() - cursor.execute( - "INSERT INTO USERS( USER_ID, USERNAME, PASSWORD, NAME ) VALUES ( NULL, ?, ?, ? )", - [request.form["username"], hashed_password, request.form["name"]], - ) - connection.commit() - return render_template( - "installer_message.html", - message="Congratulations! PrettyPi has been initialized. "\ - "Go back to homepage to start using it", - type="success", - ) - - -@app.route("/login", methods=["POST"]) -def login() -> str: - User.set_username(request.form["username"]) - User.set_password(request.form["password"].encode("utf-8")) - if User.has_permission(): - response = make_response(redirect("/")) - response.set_cookie("prettypi_username", User.get_username()) - response.set_cookie("prettypi_password", User.get_hashed_password()) - return response - return "Invalid" - - -@app.route("/logout") -def logout() -> Response: - response = make_response(redirect("/")) - response.set_cookie("prettypi_username", "") - response.set_cookie("prettypi_password", "") - return response - - -@app.route("/tasks") -def tasks_main(): - _, cursor = get_connection() - cursor.execute("SELECT * FROM TODO WHERE DONE = 'N' ORDER BY CREATION_DATE DESC") - tasks = cursor.fetchall() - cursor.execute("SELECT * FROM TODO WHERE DONE = 'Y' ORDER BY CREATION_DATE DESC") - done_tasks = cursor.fetchall() - cursor.execute("SELECT * FROM TODO WHERE WORKING_ON = 'Y'") - working_task = cursor.fetchone() - return render_template( - "tasks.html", - tasks=tasks, - done_tasks=done_tasks, - name=User.get_name(), - working_task=working_task, - ) - - -@app.route("/add_task", methods=["POST"]) -def new_task(): - connection, cursor = get_connection() - if not request.form["task_details"]: - return render_template( - "message.html", message="The details should not be empty", type="warning" - ) - creation_date = strftime("%d-%m-%Y %H:%M:%S") - cursor.execute( - "INSERT INTO TODO( TASK_ID, TASK, CREATION_DATE ) VALUES ( NULL, ?, ? )", - [request.form["task_details"], creation_date], - ) - connection.commit() - add_request("UPDATE_TODO_LIST") - return redirect("tasks") - - -@app.route("/delete_task", methods=["GET"]) -def delete_task() -> Response | str: - connection, cursor = get_connection() - task_id = request.args.get("task_id", None) - if task_id is None: - return "Invalid" - cursor.execute("DELETE FROM TODO WHERE TASK_ID = ?", [task_id]) - cursor.execute("DELETE FROM TASKS_LOG WHERE TASK_ID = ?", [task_id]) - connection.commit() - add_request("UPDATE_TODO_LIST") - return redirect("tasks") - - -@app.route("/task_done", methods=["GET"]) -def mark_task_as_done() -> Response | str: - connection, cursor = get_connection() - task_id = request.args.get("task_id", None) - if task_id is None: - return "Invalid" - current_date = strftime("%d-%m-%Y %H:%M:%S") - cursor.execute( - "UPDATE TODO SET WORKING_ON = 'N', DONE = 'Y', DONE_AT = ? WHERE TASK_ID = ?", - [current_date, task_id], - ) - connection.commit() - add_request("UPDATE_TODO_LIST") - return redirect("tasks") - - -@app.route("/start_task", methods=["GET"]) -def start_working_on_task() -> Response | str: - connection, cursor = get_connection() - task_id = request.args.get("task_id", None) - if task_id is None: - return "Invalid" - cursor.execute( - "SELECT COUNT( 1 ) FROM TODO WHERE WORKING_ON = 'Y' AND TASK_ID <> ?", - [task_id] - ) - currently_working_on = cursor.fetchone()[0] - if currently_working_on > 0: - return render_template( - "message.html", - message="You're already working on another task!", - type="warning", - ) - creation_date = strftime("%d-%m-%Y %H:%M:%S") - cursor.execute("UPDATE TODO SET WORKING_ON = 'Y' WHERE TASK_ID = ?",[task_id]) - cursor.execute( - "INSERT INTO TASKS_LOG( TASK_ID, START_AT ) VALUES ( ?, ? )", - [task_id, creation_date], - ) - connection.commit() - add_request("UPDATE_TODO_LIST") - return redirect("tasks") - - -@app.route("/stop_task", methods=["GET"]) -def stop_working_on_task() -> Response | str: - connection, cursor = get_connection() - task_id = request.args.get("task_id", None) - if task_id is None: - return "Invalid" - cursor.execute( - "SELECT MAX( log_id ) FROM tasks_log WHERE TASK_ID = ? AND ENDED_AT IS NULL", - [task_id], - ) - current_task_log_id = cursor.fetchone()[0] - current_date = strftime("%d-%m-%Y %H:%M:%S") - cursor.execute("UPDATE TODO SET WORKING_ON = 'N' WHERE TASK_ID = ?", [task_id]) - cursor.execute( - "UPDATE TASKS_LOG SET ENDED_AT = ? WHERE LOG_ID = ?", - [current_date, current_task_log_id], - ) - connection.commit() - add_request("UPDATE_TODO_LIST") - return redirect("tasks") - - -if __name__ == "__main__": - app.debug = True diff --git a/control/app.py b/control/app.py index 5a05d64..ff12757 100644 --- a/control/app.py +++ b/control/app.py @@ -1,262 +1,222 @@ -from flask import Flask, render_template, request, make_response, redirect; -from user import User; -import sqlite3; -from time import strftime; -import hashlib; +from flask import Flask, render_template, request, make_response, redirect, Response +from hashlib import md5 +from sqlite3 import connect, Connection, Cursor +from time import strftime +from typing import Optional +from user import User -app = Flask( __name__ ); -app.debug = True; +app = Flask(__name__) -user = User(); -def getConnection(): - connection = sqlite3.connect( 'data.db' ); - cursor = connection.cursor(); - - return ( connection, cursor ) +def get_connection() -> tuple[Connection, Cursor]: + connection = connect("data.db") + cursor = connection.cursor() + return connection, cursor -# ... # -def isInstalled(): - ( connection, cursor ) = getConnection(); - - cursor.execute( "SELECT COUNT( 1 ) FROM USERS" ); - hasUser = cursor.fetchone()[ 0 ]; +def is_installed() -> bool: + _, cursor = get_connection() + cursor.execute("SELECT COUNT( 1 ) FROM USERS") + has_user = cursor.fetchone()[0] + return has_user > 0 - return ( hasUser > 0 ); -def addRequest( reqType ): - ( connection, cursor ) = getConnection(); - - creationDate = strftime( '%d-%m-%Y %H:%M:%S' ); +def add_request(request_type) -> None: + connection, cursor = get_connection() + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "INSERT INTO UPDATE_REQUESTS( UPDATE_TYPE, DONE, CREATION_DATE ) VALUES" + " ( ?, 'N', ? )", + (request_type, creation_date), + ) + connection.commit() - cursor.execute( "INSERT INTO UPDATE_REQUESTS( UPDATE_TYPE, DONE, CREATION_DATE ) VALUES ( ?, 'N', ? )", [ reqType, creationDate ] ); - - connection.commit(); @app.before_request -def beforeRequest(): - if request.path == '/install': - return None; - - if not isInstalled(): - return render_template( 'install_prettypi.html' ); - - if request.path == '/login': - return None; - - if request.cookies.get( 'prettypi_username' ) != None: - user.setUsername( request.cookies.get( 'prettypi_username' ) ); - user.setHashedPassword( request.cookies.get( 'prettypi_password' ) ); - - if not user.hasPermission(): - return render_template( 'login.html' ); - else: - return None; - - return render_template( 'login.html' ); - -# ... # - -@app.route( '/' ) -def main(): - return render_template( 'main_page.html', name = user.getName() ); - -# ... # - -@app.route( '/install', methods = [ 'POST' ] ) -def install(): - ( connection, cursor ) = getConnection(); - - if isInstalled(): - return render_template( 'installer_message.html', message = 'PrettyPi Already Installed', type = 'danger' ); - - if not request.form[ 'username' ] or not request.form[ 'password' ] or not request.form[ 'name' ]: - return render_template( 'installer_message.html', message = 'Please fill all fields', type = 'danger' ); - - hashFunction = hashlib.md5( request.form[ 'password' ].encode( 'utf-8' ) ); - - hashedPassword = hashFunction.hexdigest(); - - cursor.execute( "INSERT INTO USERS( USER_ID, USERNAME, PASSWORD, NAME ) VALUES ( NULL, ?, ?, ? )", [ request.form[ 'username' ], hashedPassword, request.form[ 'name' ] ] ); - - connection.commit(); - - return render_template( 'installer_message.html', message = 'Congratulations! PrettyPi has been initialized. Go back to homepage to start using it', type = 'success' ); - -# ... # - -@app.route( '/login', methods = [ 'POST' ] ) -def login(): - user.setUsername( request.form[ 'username' ] ); - user.setPassword( request.form[ 'password' ].encode( 'utf-8' ) ); - - if user.hasPermission(): - response = make_response( redirect( '/' ) ); - response.set_cookie( 'prettypi_username', user.getUsername() ); - response.set_cookie( 'prettypi_password', user.getHashedPassword() ); - - return response; - else: - return 'Invalid'; - -# ... # - -@app.route( '/logout' ) -def logout(): - response = make_response( redirect( '/' ) ); - response.set_cookie( 'prettypi_username', '' ); - response.set_cookie( 'prettypi_password', '' ); - - return response; - -# ... # - -@app.route( '/tasks' ) -def tasksMain(): - ( connection, cursor ) = getConnection(); - - cursor.execute( "SELECT * FROM TODO WHERE DONE = 'N' ORDER BY CREATION_DATE DESC" ); - - tasks = cursor.fetchall(); - - # ... # - - cursor.execute( "SELECT * FROM TODO WHERE DONE = 'Y' ORDER BY CREATION_DATE DESC" ); - - doneTasks = cursor.fetchall(); - - # ... # - - cursor.execute( "SELECT * FROM TODO WHERE WORKING_ON = 'Y'" ); - - workingTask = cursor.fetchone(); - - print( workingTask ); - - # ... # - - return render_template( 'tasks.html', tasks = tasks, doneTasks = doneTasks, name = user.getName(), workingTask = workingTask ); - -# ... # - -@app.route( '/add_task', methods = [ 'POST' ] ) -def newTask(): - ( connection, cursor ) = getConnection(); - - if not request.form[ 'task_details' ]: - return render_template( 'message.html', message = 'The details should not be empty', type = 'warning' ); - - creationDate = strftime( '%d-%m-%Y %H:%M:%S' ); - - cursor.execute( "INSERT INTO TODO( TASK_ID, TASK, CREATION_DATE ) VALUES ( NULL, ?, ? )", [ request.form[ 'task_details' ], creationDate ] ); - - connection.commit(); - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); - -# ... # - -@app.route( '/delete_task', methods = [ 'GET' ] ) -def deleteTask(): - ( connection, cursor ) = getConnection(); - - taskId = request.args.get( 'task_id', None ); - - if taskId is None: - return 'Invalid'; - - cursor.execute( "DELETE FROM TODO WHERE TASK_ID = ?", [ taskId ] ); - cursor.execute( "DELETE FROM TASKS_LOG WHERE TASK_ID = ?", [ taskId ] ); - - connection.commit(); - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); - -@app.route( '/task_done', methods = [ 'GET' ] ) -def markTaskAsDone(): - ( connection, cursor ) = getConnection(); - - taskId = request.args.get( 'task_id', None ); - - if taskId is None: - return 'Invalid'; - - currentDate = strftime( '%d-%m-%Y %H:%M:%S' ); - - cursor.execute( "UPDATE TODO SET WORKING_ON = 'N', DONE = 'Y', DONE_AT = ? WHERE TASK_ID = ?", [ currentDate, taskId ] ); - - connection.commit(); - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); - -@app.route( '/start_task', methods = [ 'GET' ] ) -def startWorkingOnTask(): - ( connection, cursor ) = getConnection(); - - taskId = request.args.get( 'task_id', None ); - - if taskId is None: - return 'Invalid'; - - # ... # - - cursor.execute( "SELECT COUNT( 1 ) FROM TODO WHERE WORKING_ON = 'Y' AND TASK_ID <> ?", [ taskId ] ); - - currentlyWorkingOn = cursor.fetchone()[ 0 ]; - - if currentlyWorkingOn > 0: - return render_template( 'message.html', message = "You're already working on another task!", type = 'warning' ); - - # ... # - - creationDate = strftime( '%d-%m-%Y %H:%M:%S' ); - - cursor.execute( "UPDATE TODO SET WORKING_ON = 'Y' WHERE TASK_ID = ?", [ taskId ] ); - cursor.execute( "INSERT INTO TASKS_LOG( TASK_ID, START_AT ) VALUES ( ?, ? )", [ taskId, creationDate ] ); - - connection.commit(); - - # ... # - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); - -# ... # - -@app.route( '/stop_task', methods = [ 'GET' ] ) -def stopWorkingOnTask(): - ( connection, cursor ) = getConnection(); - - taskId = request.args.get( 'task_id', None ); - - if taskId is None: - return 'Invalid'; - - # ... # - - cursor.execute( "SELECT MAX( log_id ) FROM tasks_log WHERE TASK_ID = ? AND ENDED_AT IS NULL", [ taskId ] ); - - currTaskLogId = cursor.fetchone()[ 0 ]; - - # ... # - - currentDate = strftime( '%d-%m-%Y %H:%M:%S' ); - - cursor.execute( "UPDATE TODO SET WORKING_ON = 'N' WHERE TASK_ID = ?", [ taskId ] ); - cursor.execute( "UPDATE TASKS_LOG SET ENDED_AT = ? WHERE LOG_ID = ?", [ currentDate, currTaskLogId ] ); - - connection.commit(); - - # ... # - - addRequest( "UPDATE_TODO_LIST" ); - - return redirect( 'tasks' ); +def before_request() -> Optional[str]: + if request.path == "/install": + return None + if not is_installed(): + return render_template("install_prettypi.html") + if request.path == "/login": + return None + if request.cookies.get("prettypi_username") is not None: + User.set_username(request.cookies.get("prettypi_username")) + User.set_hashed_password(request.cookies.get("prettypi_password")) + return None if User.has_permission() else render_template("login.html") + return render_template("login.html") + + +@app.route("/") +def main() -> str: + return render_template("main_page.html", name=User.get_name()) + + +@app.route("/install", methods=["POST"]) +def install() -> str: + connection, cursor = get_connection() + if is_installed(): + return render_template( + "installer_message.html", + message="PrettyPi Already Installed", + type="danger", + ) + if ( + not request.form["username"] + or not request.form["password"] + or not request.form["name"] + ): + return render_template( + "installer_message.html", message="Please fill all fields", type="danger" + ) + hash_function = md5(request.form["password"].encode("utf-8")) + hashed_password = hash_function.hexdigest() + cursor.execute( + "INSERT INTO USERS( USER_ID, USERNAME, PASSWORD, NAME ) VALUES ( NULL, ?, ?, ? )", + [request.form["username"], hashed_password, request.form["name"]], + ) + connection.commit() + return render_template( + "installer_message.html", + message="Congratulations! PrettyPi has been initialized. "\ + "Go back to homepage to start using it", + type="success", + ) + + +@app.route("/login", methods=["POST"]) +def login() -> str: + User.set_username(request.form["username"]) + User.set_password(request.form["password"].encode("utf-8")) + if User.has_permission(): + response = make_response(redirect("/")) + response.set_cookie("prettypi_username", User.get_username()) + response.set_cookie("prettypi_password", User.get_hashed_password()) + return response + return "Invalid" + + +@app.route("/logout") +def logout() -> Response: + response = make_response(redirect("/")) + response.set_cookie("prettypi_username", "") + response.set_cookie("prettypi_password", "") + return response + + +@app.route("/tasks") +def tasks_main(): + _, cursor = get_connection() + cursor.execute("SELECT * FROM TODO WHERE DONE = 'N' ORDER BY CREATION_DATE DESC") + tasks = cursor.fetchall() + cursor.execute("SELECT * FROM TODO WHERE DONE = 'Y' ORDER BY CREATION_DATE DESC") + done_tasks = cursor.fetchall() + cursor.execute("SELECT * FROM TODO WHERE WORKING_ON = 'Y'") + working_task = cursor.fetchone() + return render_template( + "tasks.html", + tasks=tasks, + done_tasks=done_tasks, + name=User.get_name(), + working_task=working_task, + ) + + +@app.route("/add_task", methods=["POST"]) +def new_task(): + connection, cursor = get_connection() + if not request.form["task_details"]: + return render_template( + "message.html", message="The details should not be empty", type="warning" + ) + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "INSERT INTO TODO( TASK_ID, TASK, CREATION_DATE ) VALUES ( NULL, ?, ? )", + [request.form["task_details"], creation_date], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/delete_task", methods=["GET"]) +def delete_task() -> Response | str: + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return "Invalid" + cursor.execute("DELETE FROM TODO WHERE TASK_ID = ?", [task_id]) + cursor.execute("DELETE FROM TASKS_LOG WHERE TASK_ID = ?", [task_id]) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/task_done", methods=["GET"]) +def mark_task_as_done() -> Response | str: + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return "Invalid" + current_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute( + "UPDATE TODO SET WORKING_ON = 'N', DONE = 'Y', DONE_AT = ? WHERE TASK_ID = ?", + [current_date, task_id], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/start_task", methods=["GET"]) +def start_working_on_task() -> Response | str: + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return "Invalid" + cursor.execute( + "SELECT COUNT( 1 ) FROM TODO WHERE WORKING_ON = 'Y' AND TASK_ID <> ?", + [task_id] + ) + currently_working_on = cursor.fetchone()[0] + if currently_working_on > 0: + return render_template( + "message.html", + message="You're already working on another task!", + type="warning", + ) + creation_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute("UPDATE TODO SET WORKING_ON = 'Y' WHERE TASK_ID = ?",[task_id]) + cursor.execute( + "INSERT INTO TASKS_LOG( TASK_ID, START_AT ) VALUES ( ?, ? )", + [task_id, creation_date], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +@app.route("/stop_task", methods=["GET"]) +def stop_working_on_task() -> Response | str: + connection, cursor = get_connection() + task_id = request.args.get("task_id", None) + if task_id is None: + return "Invalid" + cursor.execute( + "SELECT MAX( log_id ) FROM tasks_log WHERE TASK_ID = ? AND ENDED_AT IS NULL", + [task_id], + ) + current_task_log_id = cursor.fetchone()[0] + current_date = strftime("%d-%m-%Y %H:%M:%S") + cursor.execute("UPDATE TODO SET WORKING_ON = 'N' WHERE TASK_ID = ?", [task_id]) + cursor.execute( + "UPDATE TASKS_LOG SET ENDED_AT = ? WHERE LOG_ID = ?", + [current_date, current_task_log_id], + ) + connection.commit() + add_request("UPDATE_TODO_LIST") + return redirect("tasks") + + +if __name__ == "__main__": + app.debug = True diff --git a/control/templates/login.html b/control/templates/login.html index 91ebd8b..28e19fe 100644 --- a/control/templates/login.html +++ b/control/templates/login.html @@ -1,70 +1,49 @@ - + - - + + PrettyPi | Log in - - - - - - - - - - - - - - - - + + + + + + -
- - -
-
-
- - -
-
- - -
-
-
- +
- -
- - - - - - - - + + diff --git a/control/templates/main_page.html b/control/templates/main_page.html index 6806ea2..1df378a 100644 --- a/control/templates/main_page.html +++ b/control/templates/main_page.html @@ -8,6 +8,7 @@

PrettyPi Version 0.0.1 +

Click "Tasks" to view ongoing tasks
-
+
-

Done!

+

TODO

+ + + - - - {% for doneTask in doneTasks %} + {% for task in tasks %} - - - - + + {% if task.5 == 'N' %} + + {% else %} + + {% endif %} + + + {% endfor %}
# TaskStartDoneDelete Created OnDone AtDelete
{{ loop.index }}.{{ doneTask.1 }}{{ doneTask.2 }}{{ doneTask.4 }}Delete{{ task.1 }}StartStopDoneDelete{{ task.2 }}
-
-
-
+
-

TODO

+

Done!

-
+
- + - - - + + - {% for task in tasks %} + {% for doneTask in doneTasks %} - - {% if task.5 == 'N' %} - - {% else %} - - {% endif %} - - - + + + + {% endfor %}
# TaskStartDoneDelete Created OnDone AtDelete
{{ loop.index }}.{{ task.1 }}StartStopDoneDelete{{ task.2 }}{{ doneTask.1 }}{{ doneTask.2 }}{{ doneTask.4 }}Delete
+
diff --git a/control/user.py b/control/user.py index bcdfb62..b02b178 100644 --- a/control/user.py +++ b/control/user.py @@ -1,50 +1,54 @@ -import sqlite3; -import hashlib; +from hashlib import md5 +from sqlite3 import Connection, Cursor, connect +from typing import Optional -class User: - __username = None; - __password = None; - __name = None; - __cursor = None; - - def getConnection( self ): - connection = sqlite3.connect( 'data.db' ); - cursor = connection.cursor(); - - return ( connection, cursor ) - - def hasPermission( self ): - ( connection, cursor ) = self.getConnection(); - - print ( self.__username, self.__password ); - - cursor.execute( 'SELECT * FROM USERS WHERE USERNAME = ? AND PASSWORD = ?', ( self.__username, self.__password ) ); - - result = cursor.fetchall(); - - if len( result ) > 0: - self.__name = result[ 0 ][ 3 ]; - - return True; - - return False; - - def setUsername( self, username ): - self.__username = username; - def setPassword( self, password ): - hashFunction = hashlib.md5( password ); - - self.__password = hashFunction.hexdigest(); - - def setHashedPassword( self, hashedPassword ): - self.__password = hashedPassword; - - def getUsername( self ): - return self.__username; - - def getHashedPassword( self ): - return self.__password; - - def getName( self ): - return self.__name; +class User: + __username = Optional[str] + __password = Optional[str] + __name = Optional[str] + __cursor = Optional[Cursor] + + @classmethod + def get_connection(cls) -> tuple[Connection, Cursor]: + connection = connect("data.db") + cursor = connection.cursor() + return connection, cursor + + @classmethod + def has_permission(cls) -> bool: + _, cursor = cls.get_connection() + cursor.execute( + "SELECT * FROM USERS WHERE USERNAME = ? AND PASSWORD = ?", + (cls.__username, cls.__password), + ) + result = cursor.fetchall() + if len(result) > 0: + cls.__name = result[0][3] + return True + return False + + @classmethod + def set_username(cls, username: str) -> None: + cls.__username = username + + @classmethod + def set_password(cls, password: str) -> None: + hashFunction = md5(password) + cls.__password = hashFunction.hexdigest() + + @classmethod + def set_hashed_password(cls, hashedPassword) -> None: + cls.__password = hashedPassword + + @classmethod + def get_username(cls) -> str: + return cls.__username + + @classmethod + def get_hashed_password(cls) -> str: + return cls.__password + + @classmethod + def get_name(cls) -> str: + return cls.__name diff --git a/login.html b/login.html deleted file mode 100644 index 28e19fe..0000000 --- a/login.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - PrettyPi | Log in - - - - - - - - - - - - - - diff --git a/main.py b/main.py deleted file mode 100644 index 35ba26c..0000000 --- a/main.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf8 -*- - -# [MQH] 16 June 2017. It has been a while since last code I have written in Python! :-) -from kivy.app import App -from kivy.lang import Builder -from kivymd.theming import ThemeManager -from kivy.properties import ObjectProperty -from kivymd.label import MDLabel -from kivy.animation import Animation -from ArabicLabel import ArabicLabel -import arabic_reshaper -import sqlite3 -import threading -from kivy.clock import Clock - -kv_config = """ -#:import Toolbar kivymd.toolbar.Toolbar -#:import NavigationLayout kivymd.navigationdrawer.NavigationLayout -#:import MDNavigationDrawer kivymd.navigationdrawer.MDNavigationDrawer - -NavigationLayout: - id: navLayout - - MDNavigationDrawer: - id: navDrawer - NavigationDrawerToolbar: - title: "Navigation Drawer" - NavigationDrawerIconButton: - id: quit_button - icon: 'checkbox-blank-circle' - text: "Quit" - - BoxLayout: - id: topBox - orientation: 'vertical' - Toolbar: - id: toolbar - title: 'My Pretty Pi!' - md_bg_color: app.theme_cls.primary_color - background_palette: 'Primary' - background_hue: '500' - left_action_items: [['menu', lambda x: app.root.toggle_nav_drawer()]] - right_action_items: [['dots-vertical', lambda x: app.root.toggle_nav_drawer()]] - - Toolbar: - id: titlebar - title: 'Current TODO' - md_bg_color: app.theme_cls.primary_color - background_palette: 'Primary' - background_hue: '900' - - ScreenManager: - id: screenManager - - Screen: - name: 'mainScreen' - - BoxLayout: - id: main_box - orientation: 'vertical' - """ - - -class PrettyPiApp(App): - theme_cls = ThemeManager() - main_box = None - connection = None - cursor = None - kv_main = kv_config - - def build(self): - main_widget = Builder.load_string(self.kv_main) - self.connection = sqlite3.connect("data.db") - self.cursor = self.connection.cursor() - self.main_box = main_widget.ids.mainBox - self.quit_button = main_widget.ids.quitBtn - self.quit_button.bind(on_press=lambda e: exit()) - self.refresh_list() - Clock.schedule_interval(self.check_updates, 0.5) - return main_widget - - def refresh_list(self) -> None: - self.main_box.clear_widgets() - self.cursor.execute("SELECT * FROM TODO WHERE DONE = 'N'") - tasks = self.cursor.fetchall() - for task in tasks: - task_text = task[1] - if task[5] == "Y": - task_text += " (Working On)" - self.main_box.add_widget( - ArabicLabel(text=task_text, halign="center", font_style="Display1") - ) - - def check_updates(self) -> None: - self.cursor.execute( - "SELECT COUNT( 1 ) FROM UPDATE_REQUESTS WHERE UPDATE_TYPE = "\ - "'UPDATE_TODO_LIST' AND DONE = 'N'" - ) - result = self.cursor.fetchone() - if result[0] > 0: - self.refresh_list() - self.cursor.execute( - "UPDATE UPDATE_REQUESTS SET DONE = 'Y' WHERE UPDATE_TYPE = "\ - "'UPDATE_TODO_LIST'" - ) - self.connection.commit() - - -if __name__ == "__main__": - PrettyPiApp().run() diff --git a/main_page.html b/main_page.html deleted file mode 100644 index 1df378a..0000000 --- a/main_page.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -
- -
-

- PrettyPi - Version 0.0.1 -

Click "Tasks" to view ongoing tasks
- - -
- - -
-
- -
- - -{% endblock %} diff --git a/server/ArabicLabel.py b/server/ArabicLabel.py index 53b80b3..a07d3e2 100644 --- a/server/ArabicLabel.py +++ b/server/ArabicLabel.py @@ -2,15 +2,16 @@ import arabic_reshaper from bidi.algorithm import get_display -class ArabicLabel( MDLabel ): - #def __init__( self, **kwargs ): - # super( ArabicLabel, self ).__init__( **kwargs ); - #self.on_font_style( None, self.font_style ) +class ArabicLabel(MDLabel): + # def __init__( self, **kwargs ): + # super( ArabicLabel, self ).__init__( **kwargs ); - # Overridden - def on_font_style( self, instance, style ): - super( ArabicLabel, self ).on_font_style( instance, style ); + # self.on_font_style( None, self.font_style ) - self.text = get_display( arabic_reshaper.reshape( self.text ) ); - self.font_name = 'fonts/KacstPenE'; + # Overridden + def on_font_style(self, instance, style): + super(ArabicLabel, self).on_font_style(instance, style) + + self.text = get_display(arabic_reshaper.reshape(self.text)) + self.font_name = "fonts/KacstPenE" diff --git a/server/main.py b/server/main.py index 76375c1..35ba26c 100644 --- a/server/main.py +++ b/server/main.py @@ -11,16 +11,10 @@ from ArabicLabel import ArabicLabel import arabic_reshaper import sqlite3 -import threading; +import threading from kivy.clock import Clock -class PrettyPiApp( App ): - theme_cls = ThemeManager(); - mainBox = None; - connection = None; - cursor = None; - - kvMain = ''' +kv_config = """ #:import Toolbar kivymd.toolbar.Toolbar #:import NavigationLayout kivymd.navigationdrawer.NavigationLayout #:import MDNavigationDrawer kivymd.navigationdrawer.MDNavigationDrawer @@ -33,7 +27,7 @@ class PrettyPiApp( App ): NavigationDrawerToolbar: title: "Navigation Drawer" NavigationDrawerIconButton: - id: quitBtn + id: quit_button icon: 'checkbox-blank-circle' text: "Quit" @@ -63,57 +57,55 @@ class PrettyPiApp( App ): name: 'mainScreen' BoxLayout: - id: mainBox + id: main_box orientation: 'vertical' - '''; - - def build( self ): - mainWidget = Builder.load_string( self.kvMain ); - - self.connection = sqlite3.connect( 'data.db' ); - self.cursor = self.connection.cursor(); - self.mainBox = mainWidget.ids.mainBox; - self.quitBtn = mainWidget.ids.quitBtn; - - self.quitBtn.bind( on_press = lambda e: exit() ); - - # ... # - - self.refreshList(); - - # .. # - - Clock.schedule_interval( self.checkUpdates, 0.5 ); - - # ... # - - return mainWidget; - - def refreshList( self ): - self.mainBox.clear_widgets(); - - self.cursor.execute( "SELECT * FROM TODO WHERE DONE = 'N'" ); - tasks = self.cursor.fetchall(); - - for task in tasks: - taskText = task[ 1 ]; - - if task[ 5 ] == 'Y': - taskText += " (Working On)"; - - self.mainBox.add_widget( ArabicLabel( text = taskText, halign = 'center', font_style = 'Display1' ) ); - - def checkUpdates( self, dt ): - self.cursor.execute( "SELECT COUNT( 1 ) FROM UPDATE_REQUESTS WHERE UPDATE_TYPE = 'UPDATE_TODO_LIST' AND DONE = 'N'" ); - - result = self.cursor.fetchone(); - - if result[ 0 ] > 0: - self.refreshList(); - - self.cursor.execute( "UPDATE UPDATE_REQUESTS SET DONE = 'Y' WHERE UPDATE_TYPE = 'UPDATE_TODO_LIST'" ); - self.connection.commit(); - - -if __name__ == '__main__': - PrettyPiApp().run(); + """ + + +class PrettyPiApp(App): + theme_cls = ThemeManager() + main_box = None + connection = None + cursor = None + kv_main = kv_config + + def build(self): + main_widget = Builder.load_string(self.kv_main) + self.connection = sqlite3.connect("data.db") + self.cursor = self.connection.cursor() + self.main_box = main_widget.ids.mainBox + self.quit_button = main_widget.ids.quitBtn + self.quit_button.bind(on_press=lambda e: exit()) + self.refresh_list() + Clock.schedule_interval(self.check_updates, 0.5) + return main_widget + + def refresh_list(self) -> None: + self.main_box.clear_widgets() + self.cursor.execute("SELECT * FROM TODO WHERE DONE = 'N'") + tasks = self.cursor.fetchall() + for task in tasks: + task_text = task[1] + if task[5] == "Y": + task_text += " (Working On)" + self.main_box.add_widget( + ArabicLabel(text=task_text, halign="center", font_style="Display1") + ) + + def check_updates(self) -> None: + self.cursor.execute( + "SELECT COUNT( 1 ) FROM UPDATE_REQUESTS WHERE UPDATE_TYPE = "\ + "'UPDATE_TODO_LIST' AND DONE = 'N'" + ) + result = self.cursor.fetchone() + if result[0] > 0: + self.refresh_list() + self.cursor.execute( + "UPDATE UPDATE_REQUESTS SET DONE = 'Y' WHERE UPDATE_TYPE = "\ + "'UPDATE_TODO_LIST'" + ) + self.connection.commit() + + +if __name__ == "__main__": + PrettyPiApp().run() diff --git a/tasks.html b/tasks.html deleted file mode 100644 index 5b2e7d6..0000000 --- a/tasks.html +++ /dev/null @@ -1,115 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -
- -
-

- Tasks -

- -
- - -
- - {% if working_task != None %} -
- - -
- Working On - {{ working_task.1 }} -
-
- {% endif %} - -
-
-
-
-

New Task

-
-
-
-
- - -
-
- -
-
- -
-
-

TODO

-
-
- - - - - - - - - - {% for task in tasks %} - - - - {% if task.5 == 'N' %} - - {% else %} - - {% endif %} - - - - - {% endfor %} -
#TaskStartDoneDeleteCreated On
{{ loop.index }}.{{ task.1 }}StartStopDoneDelete{{ task.2 }}
-
-
- -
-
-

Done!

-
-
- - - - - - - - - {% for doneTask in doneTasks %} - - - - - - - - {% endfor %} -
#TaskCreated OnDone AtDelete
{{ loop.index }}.{{ doneTask.1 }}{{ doneTask.2 }}{{ doneTask.4 }}Delete
-
-
-
- -
-
- -
- - -{% endblock %} diff --git a/user.py b/user.py deleted file mode 100644 index b02b178..0000000 --- a/user.py +++ /dev/null @@ -1,54 +0,0 @@ -from hashlib import md5 -from sqlite3 import Connection, Cursor, connect -from typing import Optional - - -class User: - __username = Optional[str] - __password = Optional[str] - __name = Optional[str] - __cursor = Optional[Cursor] - - @classmethod - def get_connection(cls) -> tuple[Connection, Cursor]: - connection = connect("data.db") - cursor = connection.cursor() - return connection, cursor - - @classmethod - def has_permission(cls) -> bool: - _, cursor = cls.get_connection() - cursor.execute( - "SELECT * FROM USERS WHERE USERNAME = ? AND PASSWORD = ?", - (cls.__username, cls.__password), - ) - result = cursor.fetchall() - if len(result) > 0: - cls.__name = result[0][3] - return True - return False - - @classmethod - def set_username(cls, username: str) -> None: - cls.__username = username - - @classmethod - def set_password(cls, password: str) -> None: - hashFunction = md5(password) - cls.__password = hashFunction.hexdigest() - - @classmethod - def set_hashed_password(cls, hashedPassword) -> None: - cls.__password = hashedPassword - - @classmethod - def get_username(cls) -> str: - return cls.__username - - @classmethod - def get_hashed_password(cls) -> str: - return cls.__password - - @classmethod - def get_name(cls) -> str: - return cls.__name From 60b27706f20feb081bbf62ccf96742b20e4cf898 Mon Sep 17 00:00:00 2001 From: Drew B <103056414+arbowl@users.noreply.github.com> Date: Sun, 5 May 2024 09:06:05 -0400 Subject: [PATCH 3/6] Fix done_tasks variable name --- control/templates/main_page.html | 2 +- control/templates/tasks.html | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/control/templates/main_page.html b/control/templates/main_page.html index 1df378a..d235068 100644 --- a/control/templates/main_page.html +++ b/control/templates/main_page.html @@ -8,12 +8,12 @@

PrettyPi Version 0.0.1 -

Click "Tasks" to view ongoing tasks
+
Click "Tasks" to view ongoing tasks
diff --git a/control/templates/tasks.html b/control/templates/tasks.html index 5b2e7d6..6953a41 100644 --- a/control/templates/tasks.html +++ b/control/templates/tasks.html @@ -92,12 +92,12 @@

Done!

Done At Delete - {% for doneTask in doneTasks %} + {% for done_task in done_tasks %} {{ loop.index }}. - {{ doneTask.1 }} - {{ doneTask.2 }} - {{ doneTask.4 }} + {{ done_task.1 }} + {{ done_task.2 }} + {{ done_task.4 }} Delete {% endfor %} From 92ca381af397c0bd2c78ca62ba88578d88ff5643 Mon Sep 17 00:00:00 2001 From: arbowl Date: Sun, 5 May 2024 10:05:27 -0400 Subject: [PATCH 4/6] Satisfy pylint requirements --- control/__init__.py | 0 control/app.py | 51 ++++++++++++++++++++++++-------- control/templates/main_page.html | 7 ++--- control/user.py | 24 +++++++++++---- server/ArabicLabel.py | 17 ----------- server/__init__.py | 0 server/arabic_label.py | 26 ++++++++++++++++ server/main.py | 43 +++++++++++++++++---------- 8 files changed, 113 insertions(+), 55 deletions(-) create mode 100644 control/__init__.py delete mode 100644 server/ArabicLabel.py create mode 100644 server/__init__.py create mode 100644 server/arabic_label.py diff --git a/control/__init__.py b/control/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/control/app.py b/control/app.py index ff12757..957dace 100644 --- a/control/app.py +++ b/control/app.py @@ -1,27 +1,42 @@ -from flask import Flask, render_template, request, make_response, redirect, Response +"""App + +Handles the I/O and database queries +""" + from hashlib import md5 from sqlite3 import connect, Connection, Cursor from time import strftime from typing import Optional -from user import User + +from flask import Flask, render_template, request, make_response, redirect, Response + +from control.user import User app = Flask(__name__) +TASK_ID_IS_NONE_ERROR =( + "Error: No task associated with that ID. Maybe someone else already deleted it?" +) + + def get_connection() -> tuple[Connection, Cursor]: + """Returns the SQLite""" connection = connect("data.db") cursor = connection.cursor() return connection, cursor def is_installed() -> bool: + """Returns True if a user already exists""" _, cursor = get_connection() cursor.execute("SELECT COUNT( 1 ) FROM USERS") has_user = cursor.fetchone()[0] return has_user > 0 -def add_request(request_type) -> None: +def add_request(request_type: str) -> None: + """Adds a task to the DB""" connection, cursor = get_connection() creation_date = strftime("%d-%m-%Y %H:%M:%S") cursor.execute( @@ -34,6 +49,7 @@ def add_request(request_type) -> None: @app.before_request def before_request() -> Optional[str]: + """Determines which page to show the user based on install and login status""" if request.path == "/install": return None if not is_installed(): @@ -49,11 +65,13 @@ def before_request() -> Optional[str]: @app.route("/") def main() -> str: + """This is the landing page""" return render_template("main_page.html", name=User.get_name()) @app.route("/install", methods=["POST"]) def install() -> str: + """Initializes the PrettyPi site for the network""" connection, cursor = get_connection() if is_installed(): return render_template( @@ -78,14 +96,15 @@ def install() -> str: connection.commit() return render_template( "installer_message.html", - message="Congratulations! PrettyPi has been initialized. "\ + message="Congratulations! PrettyPi has been initialized. " "Go back to homepage to start using it", type="success", ) @app.route("/login", methods=["POST"]) -def login() -> str: +def login() -> Response | str: + """Determines if the login information is valid""" User.set_username(request.form["username"]) User.set_password(request.form["password"].encode("utf-8")) if User.has_permission(): @@ -93,11 +112,12 @@ def login() -> str: response.set_cookie("prettypi_username", User.get_username()) response.set_cookie("prettypi_password", User.get_hashed_password()) return response - return "Invalid" + return f'Invalid username ("{User.get_username()}") or password' @app.route("/logout") def logout() -> Response: + """Logs the current user out""" response = make_response(redirect("/")) response.set_cookie("prettypi_username", "") response.set_cookie("prettypi_password", "") @@ -106,6 +126,7 @@ def logout() -> Response: @app.route("/tasks") def tasks_main(): + """Retrieves the completed, running, and done tasks from the DB to display""" _, cursor = get_connection() cursor.execute("SELECT * FROM TODO WHERE DONE = 'N' ORDER BY CREATION_DATE DESC") tasks = cursor.fetchall() @@ -124,6 +145,7 @@ def tasks_main(): @app.route("/add_task", methods=["POST"]) def new_task(): + """Adds a new task to the DB""" connection, cursor = get_connection() if not request.form["task_details"]: return render_template( @@ -141,10 +163,11 @@ def new_task(): @app.route("/delete_task", methods=["GET"]) def delete_task() -> Response | str: + """Deletes""" connection, cursor = get_connection() task_id = request.args.get("task_id", None) if task_id is None: - return "Invalid" + return TASK_ID_IS_NONE_ERROR cursor.execute("DELETE FROM TODO WHERE TASK_ID = ?", [task_id]) cursor.execute("DELETE FROM TASKS_LOG WHERE TASK_ID = ?", [task_id]) connection.commit() @@ -154,10 +177,11 @@ def delete_task() -> Response | str: @app.route("/task_done", methods=["GET"]) def mark_task_as_done() -> Response | str: + """Move a task to the "Done" table""" connection, cursor = get_connection() task_id = request.args.get("task_id", None) if task_id is None: - return "Invalid" + return TASK_ID_IS_NONE_ERROR current_date = strftime("%d-%m-%Y %H:%M:%S") cursor.execute( "UPDATE TODO SET WORKING_ON = 'N', DONE = 'Y', DONE_AT = ? WHERE TASK_ID = ?", @@ -170,13 +194,13 @@ def mark_task_as_done() -> Response | str: @app.route("/start_task", methods=["GET"]) def start_working_on_task() -> Response | str: + """Create the banner to indicate a task is currently pending""" connection, cursor = get_connection() task_id = request.args.get("task_id", None) if task_id is None: - return "Invalid" + return TASK_ID_IS_NONE_ERROR cursor.execute( - "SELECT COUNT( 1 ) FROM TODO WHERE WORKING_ON = 'Y' AND TASK_ID <> ?", - [task_id] + "SELECT COUNT( 1 ) FROM TODO WHERE WORKING_ON = 'Y' AND TASK_ID <> ?", [task_id] ) currently_working_on = cursor.fetchone()[0] if currently_working_on > 0: @@ -186,7 +210,7 @@ def start_working_on_task() -> Response | str: type="warning", ) creation_date = strftime("%d-%m-%Y %H:%M:%S") - cursor.execute("UPDATE TODO SET WORKING_ON = 'Y' WHERE TASK_ID = ?",[task_id]) + cursor.execute("UPDATE TODO SET WORKING_ON = 'Y' WHERE TASK_ID = ?", [task_id]) cursor.execute( "INSERT INTO TASKS_LOG( TASK_ID, START_AT ) VALUES ( ?, ? )", [task_id, creation_date], @@ -198,10 +222,11 @@ def start_working_on_task() -> Response | str: @app.route("/stop_task", methods=["GET"]) def stop_working_on_task() -> Response | str: + """Remove a pending task from the banner""" connection, cursor = get_connection() task_id = request.args.get("task_id", None) if task_id is None: - return "Invalid" + return TASK_ID_IS_NONE_ERROR cursor.execute( "SELECT MAX( log_id ) FROM tasks_log WHERE TASK_ID = ? AND ENDED_AT IS NULL", [task_id], diff --git a/control/templates/main_page.html b/control/templates/main_page.html index d235068..1b64b0a 100644 --- a/control/templates/main_page.html +++ b/control/templates/main_page.html @@ -5,10 +5,9 @@
-

- PrettyPi - Version 0.0.1 -

+

Home

+ PrettyPi + Version 0.0.1 -
Click "Tasks" to view ongoing tasks
+
+
+
+ +
diff --git a/control/user.py b/control/user.py index c4e0b22..54e0596 100644 --- a/control/user.py +++ b/control/user.py @@ -9,6 +9,7 @@ class User: """Stores the username, password, and name in memory""" + __name = Optional[str] __username = Optional[str] __password = Optional[str] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3a1c340 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,34 @@ +arabic-reshaper==3.0.0 +astroid==3.1.0 +black==24.4.2 +blinker==1.8.1 +certifi==2024.2.2 +charset-normalizer==3.3.2 +click==8.1.7 +dill==0.3.8 +docutils==0.21.2 +Flask==3.0.3 +idna==3.7 +isort==5.13.2 +itsdangerous==2.2.0 +Jinja2==3.1.3 +Kivy==2.3.0 +Kivy-Garden==0.1.5 +kivymd==1.2.0 +MarkupSafe==2.1.5 +mccabe==0.7.0 +mypy==1.10.0 +mypy-extensions==1.0.0 +packaging==24.0 +pathspec==0.12.1 +pillow==10.3.0 +platformdirs==4.2.1 +Pygments==2.18.0 +pylint==3.1.0 +python-bidi==0.4.2 +requests==2.31.0 +six==1.16.0 +tomlkit==0.12.4 +typing_extensions==4.11.0 +urllib3==2.2.1 +Werkzeug==3.0.2 From aea1c014881c144db593351cdea0c0e8cc77fd85 Mon Sep 17 00:00:00 2001 From: arbowl Date: Wed, 15 May 2024 13:56:25 -0400 Subject: [PATCH 6/6] Add return link to error messages --- control/app.py | 6 ++++-- control/templates/installer_message.html | 2 ++ control/templates/message.html | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/control/app.py b/control/app.py index 6950dfa..c140444 100644 --- a/control/app.py +++ b/control/app.py @@ -96,7 +96,7 @@ def install() -> str: connection.commit() return render_template( "installer_message.html", - message="Congratulations! PrettyPi has been initialized. " + message="Congratulations! PrettyPi has been installed. " "Go back to homepage to start using it", type="success", ) @@ -149,7 +149,9 @@ def new_task(): connection, cursor = get_connection() if not request.form["task_details"]: return render_template( - "message.html", message="The details should not be empty", type="warning" + "message.html", + message="The details should not be empty.", + type="warning" ) creation_date = strftime("%d-%m-%Y %H:%M:%S") cursor.execute( diff --git a/control/templates/installer_message.html b/control/templates/installer_message.html index a6f05a4..2150f7b 100644 --- a/control/templates/installer_message.html +++ b/control/templates/installer_message.html @@ -25,6 +25,8 @@

Alert!

{{ message }} +

+ Return
diff --git a/control/templates/message.html b/control/templates/message.html index 7329b41..2be4015 100644 --- a/control/templates/message.html +++ b/control/templates/message.html @@ -21,6 +21,8 @@

Alert!

{{ message }} +

+ Return
{% endif %} @@ -29,6 +31,8 @@

Alert!

Alert!

{{ message }} +

+ Return
{% endif %}