Skip to content

Commit 52fa6a0

Browse files
committed
adv_app_strip.py
1 parent 556b5b2 commit 52fa6a0

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed

examples/adv_app_strip.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from fasthtml.common import *
2+
from hmac import compare_digest
3+
4+
db = database('data/utodos.db')
5+
class User: name:str; pwd:str
6+
class Todo: id:int; title:str; done:bool; name:str; details:str; priority:int
7+
users = db.create(User, pk='name')
8+
todos = db.create(Todo, transform=True)
9+
10+
login_redir = RedirectResponse('/login', status_code=303)
11+
12+
def before(req, sess):
13+
auth = req.scope['auth'] = sess.get('auth', None)
14+
if not auth: return login_redir
15+
todos.xtra(name=auth)
16+
17+
bware = Beforeware(before, skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', '/login', '/send_login'])
18+
19+
def _not_found(req, exc): return Titled('Oh no!', Div('We could not find that page :('))
20+
app,rt = fast_app(before=bware, exception_handlers={404: _not_found},
21+
hdrs=(SortableJS('.sortable'), MarkdownJS()))
22+
23+
@rt
24+
def login():
25+
frm = Form(action=send_login, method='post')(
26+
Input(id='name', placeholder='Name'),
27+
Input(id='pwd', type='password', placeholder='Password'),
28+
Button('login'))
29+
return Titled("Login", frm)
30+
31+
@rt
32+
def send_login(name:str, pwd:str, sess):
33+
if not name or not pwd: return login_redir
34+
try: u = users[name]
35+
except NotFoundError: u = users.insert(name=name, pwd=pwd)
36+
if not compare_digest(u.pwd.encode("utf-8"), pwd.encode("utf-8")): return login_redir
37+
sess['auth'] = u.name
38+
return RedirectResponse('/', status_code=303)
39+
40+
@rt
41+
def logout(sess):
42+
del sess['auth']
43+
return login_redir
44+
45+
def clr_details(): return Div(hx_swap_oob='innerHTML', id='current-todo')
46+
47+
@rt
48+
def update(todo: Todo): return todos.update(todo), clr_details()
49+
50+
@rt
51+
def edit(id:int):
52+
res = Form(hx_post=update, target_id=f'todo-{id}', id="edit")(
53+
Group(Input(id="title"), Button("Save")),
54+
Hidden(id="id"), CheckboxX(id="done", label='Done'),
55+
Textarea(id="details", name="details", rows=10))
56+
return fill_form(res, todos[id])
57+
58+
@rt
59+
def rm(id:int):
60+
todos.delete(id)
61+
return clr_details()
62+
63+
@rt
64+
def show(id:int):
65+
todo = todos[id]
66+
btn = Button('delete', hx_post=rm.to(id=todo.id),
67+
hx_target=f'#todo-{todo.id}', hx_swap="outerHTML")
68+
return Div(H2(todo.title), Div(todo.details, cls="marked"), btn)
69+
70+
@patch
71+
def __ft__(self:Todo):
72+
ashow = AX(self.title, show.to(id=self.id), 'current-todo')
73+
aedit = AX('edit', edit.to(id=self.id), 'current-todo')
74+
dt = '✅ ' if self.done else ''
75+
cts = (dt, ashow, ' | ', aedit, Hidden(id="id", value=self.id), Hidden(id="priority", value="0"))
76+
return Li(*cts, id=f'todo-{self.id}')
77+
78+
@rt
79+
def create(todo:Todo):
80+
new_inp = Input(id="new-title", name="title", placeholder="New Todo", hx_swap_oob='true')
81+
return todos.insert(todo), new_inp
82+
83+
@rt
84+
def reorder(id:list[int]):
85+
for i,id_ in enumerate(id): todos.update({'priority':i}, id_)
86+
return tuple(todos(order_by='priority'))
87+
88+
@rt
89+
def index(auth):
90+
title = f"{auth}'s Todo list"
91+
top = Grid(H1(title), Div(A('logout', href=logout), style='text-align: right'))
92+
new_inp = Input(id="new-title", name="title", placeholder="New Todo")
93+
add = Form(Group(new_inp, Button("Add")),
94+
hx_post=create, target_id='todo-list', hx_swap="afterbegin")
95+
frm = Form(*todos(order_by='priority'),
96+
id='todo-list', cls='sortable', hx_post=reorder, hx_trigger="end")
97+
card = Card(P('Drag/drop todos to reorder them'),
98+
Ul(frm),
99+
header=add, footer=Div(id='current-todo'))
100+
return Title(title), Container(top, card)
101+
102+
serve()
103+

0 commit comments

Comments
 (0)