Skip to content

Commit

Permalink
Add some features + edit docs + fixes
Browse files Browse the repository at this point in the history
- Add `Website.redirect_response`
- Add `Page.set_footer`
- Fix `Element.get_attr` to return string instead of list when getting class
- Add html_file parameter in `set_navigation_bar`
- Add example for connecting to SQL for user data
- Add error when an set_current_user_data tries to set a key for a column that was not added before in a SQL database.
  • Loading branch information
mubarakalmehairbi committed Jul 31, 2023
1 parent 51e2e5a commit 3d0d3dd
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 15 deletions.
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ToUI
====

Version: v3.0.4
Version: v3.1.0

.. include:: ../README.md
:parser: myst_parser.sphinx_
Expand Down
4 changes: 4 additions & 0 deletions docs/toui.apps.DesktopApp.redirect_response.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DesktopApp.redirect_response
----------------------------

.. automethod:: toui.apps.DesktopApp.redirect_response
1 change: 1 addition & 0 deletions docs/toui.apps.DesktopApp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Methods
toui.apps.DesktopApp.get_user_page
toui.apps.DesktopApp.is_signed_in
toui.apps.DesktopApp.open_new_page
toui.apps.DesktopApp.redirect_response
toui.apps.DesktopApp.register_toui_blueprint
toui.apps.DesktopApp.run
toui.apps.DesktopApp.set_current_user_data
Expand Down
4 changes: 4 additions & 0 deletions docs/toui.apps.Website.redirect_response.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Website.redirect_response
-------------------------

.. automethod:: toui.apps.Website.redirect_response
1 change: 1 addition & 0 deletions docs/toui.apps.Website.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Methods
toui.apps.Website.get_user_page
toui.apps.Website.is_signed_in
toui.apps.Website.open_new_page
toui.apps.Website.redirect_response
toui.apps.Website.register_toui_blueprint
toui.apps.Website.run
toui.apps.Website.set_current_user_data
Expand Down
1 change: 1 addition & 0 deletions docs/toui.pages.Page.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Methods
toui.pages.Page.get_html_element
toui.pages.Page.get_window
toui.pages.Page.on_url_request
toui.pages.Page.set_footer
toui.pages.Page.set_navigation_bar
toui.pages.Page.to_bs4_soup
toui.pages.Page.to_html_file
Expand Down
4 changes: 4 additions & 0 deletions docs/toui.pages.Page.set_footer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Page.set_footer
---------------

.. automethod:: toui.pages.Page.set_footer
Binary file added examples.test.db
Binary file not shown.
2 changes: 1 addition & 1 deletion examples/advanced_example_4_toui_with_firebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
ToUI can be used with Firebase. Create a Firebase app to use this example. In this example, ToUI is used with Firebase for:
- User authentication
- Stroing user data in database
- Storing user data in database
- File storage
- File retrieval
Expand Down
90 changes: 90 additions & 0 deletions examples/advanced_example_5_toui_with_sql_user_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
ToUI with SQL User Database
ToUI can be easily used with SQL database for:
- User authentication
- Storing user data in database
This example uses the HTML file "test8.html":
.. code-block:: html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Sign in users:</h1>
<input id="username"/>
<input id="password"/>
<button id="sign-in">Sign in</button>
<button id="sign-up">Sign up</button>
<p id="output"></p>
</body>
</html>
Python code:
"""
import sys
sys.path.append("..")
import os
from toui import Website, Page

app = Website(__name__, assets_folder="assets", secret_key="some text")
SQL_URI = f"sqlite:///{os.getcwd()}/.test.db" # Change this value to match your SQL database URI
app.add_user_database_using_sql(SQL_URI, other_columns=["age"]) # Connects to sql database.
main_pg = Page(html_file="assets/test8.html", url="/")

def sign_in():
pg = app.get_user_page()
username = pg.get_element("username").get_value()
password = pg.get_element("password").get_value()
pg.get_element("output").set_content("loading")
success = app.signin_user(username=username, password=password)
if success:
age = app.get_current_user_data("age")
pg.get_element("output").set_content(f"Signed in successfully. Age of user: {age}")
else:
pg.get_element("output").set_content("Sign in failed")

def sign_up():
pg = app.get_user_page()
username = pg.get_element("username").get_value()
password = pg.get_element("password").get_value()
pg.get_element("output").set_content("loading")
success = app.signup_user(username=username, password=password)
if success:
app.signin_user(username=username, password=password)
value_added = app.set_current_user_data("age", 20)
pg.get_element("output").set_content(f"Signed up successfully. Age added: {value_added}")
else:
pg.get_element("output").set_content("Sign up failed")

def set_age():
pg = app.get_user_page()
age = pg.get_element("age").get_value()
app.set_current_user_data("age", age)
pg.get_element("age-output").set_content(f"Age set to {age}")

def get_age():
pg = app.get_user_page()
age = app.get_current_user_data("age")
pg.get_element("age-output").set_content(f"Age is {age}")

main_pg.get_element("sign-in").onclick(sign_in)
main_pg.get_element("sign-up").onclick(sign_up)
main_pg.get_element("set-age").onclick(set_age)
main_pg.get_element("get-age").onclick(get_age)


app.add_pages(main_pg)

if __name__ == '__main__':
app.run()


24 changes: 24 additions & 0 deletions examples/assets/test8.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<h1>Sign in users:</h1>
<input id="username" />
<input id="password" />
<button id="sign-in">Sign in</button>
<button id="sign-up">Sign up</button>
<p id="output"></p>
<p>
<input type="text" id="age"><button id="set-age">Set Age</button>
</p>
<button id="get-age">Get Age</button>
<p id="age-output"></p>
</body>

</html>
2 changes: 1 addition & 1 deletion toui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
from .structure import ToUIBlueprint
from . import exceptions

__version__ = "v3.0.4"
__version__ = "v3.1.0"
54 changes: 47 additions & 7 deletions toui/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,29 @@ def get_request():
"""
return request

def redirect_response(self, url):
"""
Use it with `Page.on_url_request` to redirect the user to another page.
`Page.on_url_request` is called when the user makes an HTTP request to the page. If you want to redirect the user
to another page, define a function then use this method as the return value of a function. Then add the function to
`Page.on_url_request` and set `display_return_value` as ``True``.
Examples
--------
>>> def redirect_function():
... return app.redirect_response("/another-page-url")
>>> home_page.on_url_request(redirect_function, display_return_value=True)
Parameters
----------
url: str
The URL of the page that the user will be redirected to.
"""
return redirect(url)

def signup_user(self, username, password=None, email=None, **other_info):
"""
Expand Down Expand Up @@ -593,6 +616,9 @@ def get_current_user_data(self, key):
error("No user is signed in.")
return None
if self._user_db_type == "sql":
if not key in current_user.__table__.columns:
error(f"'{key}' was not added as a column in users table")
return None
return getattr(current_user, key)
elif self._user_db_type == "firebase":
return self._firebase_db.document(self._user_vars._get("user-id")).get().to_dict().get(key)
Expand All @@ -601,6 +627,12 @@ def set_current_user_data(self, key, value):
"""
Sets data specific to the currently signed in user in the database.
Warning
-------
Currently, if you are using SQL database, you can only set data that
was already added as a column in the users table.
Parameters
----------
key: str
Expand All @@ -620,6 +652,9 @@ def set_current_user_data(self, key, value):
error("No user is signed in.")
return False
if self._user_db_type == "sql":
if not key in current_user.__table__.columns:
error(f"'{key}' was not added as a column in users table")
return False
setattr(current_user, key, value)
self._db.session.commit()
return True
Expand Down Expand Up @@ -797,14 +832,19 @@ def add_restriction(self, username, password):

def set_ws_validation(self, func):
"""
Validate `simple_websocket.ws.Server` object before sending and accepting data.
Validates a WebSocket connection before sending and accepting data.
ToUI uses Flask-Sock for websocket communication. Flask-Sock generates a
`simple_websocket.ws.Server` object when a connection is established. If you
wanted to access this object before sending and receiving data, input a function
that has one argument `ws`. This function should either return ``True`` or ``False``.
If the function returns ``False``, no data will be sent or received using ToUI with
the client.
`simple_websocket.ws.Server <https://simple-websocket.readthedocs.io/en/latest/api.html#the-server-class>`_
object when a connection is established. If you wanted to access this object before
sending and receiving data, input a function that has one argument `ws`. This function
should either return ``True`` or ``False``. If the function returns ``False``, no data
will be sent or received using ToUI with the client.
The `ws` argument is a `simple_websocket.ws.Server` object which you can learn about in its
`documentation <https://simple-websocket.readthedocs.io/en/latest/api.html#the-server-class>`_.
You might need to do some testing in order to explore the types of data that you can find in
this object.
Parameters
----------
Expand All @@ -822,7 +862,7 @@ def set_ws_validation(self, func):

def set_data_validation(self, func):
"""
Validate data received from JavaScript before using it.
Validates data received from JavaScript before using it.
ToUI receives data from JavaScript in the form of a JSON object. To validate this data
before allowing ToUI to use it, input a function that checks the data. This function
Expand Down
5 changes: 4 additions & 1 deletion toui/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,10 @@ def get_attr(self, name):
If the attribute does not exist.
"""
return self._element.attrs.get(name)
value = self._element.attrs.get(name)
if type(value) == list:
value = ' '.join(value)
return value

@_ElementSignal()
def set_attr(self, name, value):
Expand Down
41 changes: 37 additions & 4 deletions toui/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def __init__(self, html_file=None, html_str=None, url=None, title=None):
self._view_func = self._on_url_request
self._uid = None
self._navigation_bar = ""
self._footer = ""

def __str__(self):
return self.to_str()
Expand All @@ -167,6 +168,7 @@ def __copy__(self):
new_pg.from_bs4_soup(self.to_bs4_soup())
new_pg._signal_mode = self._signal_mode
new_pg._navigation_bar = self._navigation_bar
new_pg._footer = self._footer
new_pg._app = self._app
return new_pg

Expand Down Expand Up @@ -486,21 +488,51 @@ def on_url_request(self, func, display_return_value=False):
new_func = lambda: self._on_url_request(func=func, display_return_value=display_return_value)
self._view_func = new_func

def set_navigation_bar(self, html_str):
def set_navigation_bar(self, html_file=None, html_str=None):
"""
Adds a navigation bar to the page.
Parameters
----------
html_str: str
html_file: str, default=None
Path to the HTML file that contains the navigation bar.
html_str: str, default=None
HTML code of the navigation bar.
Returns
-------
None
"""
self._navigation_bar = html_str
if html_file is not None:
with open(html_file, "r") as f:
self._navigation_bar = f.read()
if html_str is not None:
self._navigation_bar = html_str

def set_footer(self, html_file=None, html_str=None):
"""
Adds a footer to the page.
Parameters
----------
html_file: str, default=None
Path to the HTML file that contains the footer.
html_str: str
HTML code of the footer.
Returns
-------
None
"""
if html_file is not None:
with open(html_file, "r") as f:
self._footer = f.read()
if html_str is not None:
self._footer = html_str

def get_window(self):
for window in webview.windows:
Expand Down Expand Up @@ -529,8 +561,9 @@ def _on_url_request(self, func=None, display_return_value=False):
body_element = pg.get_body_element()
if body_element:
body_element.set_content(pg._navigation_bar + body_element.to_str())
body_element.add_content(pg._footer)
else:
pg.from_str(pg._navigation_bar + pg.to_str())
pg.from_str(pg._navigation_bar + pg.to_str() + pg._footer)
del session['user page']
if "toui-response" in session:
response = session['toui-response']
Expand Down

0 comments on commit 3d0d3dd

Please sign in to comment.