Skip to content

Commit 29859eb

Browse files
pydannyAudrey Roy Greenfeld
andcommitted
Migrate to flatfile and get login,logout and basic dashboard functional
Co-authored-by: Audrey Roy Greenfeld <[email protected]>
1 parent c94fcf9 commit 29859eb

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed

examples/adv_app2.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from fasthtml.common import *
2+
from fasthtml.jupyter import *
3+
from functools import partial
4+
from dataclasses import dataclass
5+
from hmac import compare_digest
6+
from monsterui.all import *
7+
8+
db = database(':memory:')
9+
10+
class User: name:str; pwd:str
11+
12+
class Todo:
13+
id:int; title:str; done:bool; name:str; details:str; priority:int
14+
15+
db.users = db.create(User, transform=True, pk='name')
16+
db.todos = db.create(Todo, transform=True)
17+
18+
19+
def user_auth_before(req, sess):
20+
auth = req.scope['auth'] = sess.get('auth', None)
21+
if not auth: return login_redir
22+
db.todos.xtra(name=auth)
23+
24+
beforeware = Beforeware(
25+
user_auth_before,
26+
skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', r'.*\.js', '/login']
27+
)
28+
29+
30+
app, rt = fast_app(hdrs=Theme.blue.headers(),before=beforeware)
31+
32+
login_redir = Redirect('/login')
33+
34+
35+
@rt
36+
def index(auth):
37+
top = Grid(Div(A('logout', href='/logout'), style='text-align: right'))
38+
# We don't normally need separate "screens" for adding or editing data. Here for instance,
39+
# we're using an `hx-post` to add a new todo, which is added to the start of the list (using 'afterbegin').
40+
new_inp = Input(id="new-title", name="title", placeholder="New Todo")
41+
add = Form(Group(new_inp, Button("Add")),
42+
hx_post="/", target_id='todo-list', hx_swap="afterbegin")
43+
# In the MiniDataAPI spec, treating a table as a callable (i.e with `todos(...)` here) queries the table.
44+
# Because we called `xtra` in our Beforeware, this queries the todos for the current user only.
45+
# We can include the todo objects directly as children of the `Form`, because the `Todo` class has `__ft__` defined.
46+
# This is automatically called by FastHTML to convert the `Todo` objects into `FT` objects when needed.
47+
# The reason we put the todo list inside a form is so that we can use the 'sortable' js library to reorder them.
48+
# That library calls the js `end` event when dragging is complete, so our trigger here causes our `/reorder`
49+
# handler to be called.
50+
frm = Form(*db.todos(order_by='priority'),
51+
id='todo-list', cls='sortable', hx_post="/reorder", hx_trigger="end")
52+
# We create an empty 'current-todo' Div at the bottom of our page, as a target for the details and editing views.
53+
card = Card(Ul(frm), header=add, footer=Div(id='current-todo'))
54+
# PicoCSS uses `<Main class='container'>` page content; `Container` is a tiny function that generates that.
55+
# A handler can return either a single `FT` object or string, or a tuple of them.
56+
# In the case of a tuple, the stringified objects are concatenated and returned to the browser.
57+
# The `Title` tag has a special purpose: it sets the title of the page.
58+
return Titled(f"{auth}'s Todo list", Container(top, card))
59+
60+
61+
@rt('/login')
62+
def get():
63+
frm = Form(
64+
LabelInput("Name", name='name'),
65+
LabelInput("Password", name='pwd'),
66+
Button('login'),
67+
action='/login', method='post')
68+
return Titled("Login", frm, cls=ContainerT.sm)
69+
70+
@dataclass
71+
class Login: name:str; pwd:str
72+
73+
@rt("/login")
74+
def post(login:Login, sess):
75+
if not login.name or not login.pwd: return login_redir
76+
try: u = db.users[login.name]
77+
except NotFoundError: u = db.users.insert(login)
78+
if not compare_digest(u.pwd.encode("utf-8"), login.pwd.encode("utf-8")): return login_redir
79+
sess['auth'] = u.name
80+
return Redirect('/')
81+
82+
@app.get("/logout")
83+
def logout(sess):
84+
del sess['auth']
85+
return login_redir
86+
87+
serve()

0 commit comments

Comments
 (0)