From dfa0db6b6d3f0c30ade80dab25f6c706acf2fa1f Mon Sep 17 00:00:00 2001 From: jiyu0903 Date: Sat, 31 May 2025 14:31:00 +0800 Subject: [PATCH 1/6] Added a full REST API feature to existing Flask Todo app --- app.py | 96 ++++++++++++++++++++++-- base.html | 148 +++++++++++++++++++++++++++++++++++++ bg.jpg | Bin 0 -> 35944 bytes init_db.py | 6 ++ static/images/bg.jpg | Bin 0 -> 35944 bytes templates/base.html | 171 ++++++++++++++++++++++++++++++++++--------- test.html | 14 ++++ test_app.py | 116 +++++++++++++++++++++++++++++ 8 files changed, 510 insertions(+), 41 deletions(-) create mode 100644 base.html create mode 100644 bg.jpg create mode 100644 init_db.py create mode 100644 static/images/bg.jpg create mode 100644 test.html create mode 100644 test_app.py diff --git a/app.py b/app.py index 4b6ccca..fc98dfc 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,8 @@ -from flask import Flask, render_template, request, redirect, url_for +from flask import Flask, render_template, request, redirect, url_for, jsonify, flash from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) +app.secret_key = "your_secret_key_here" # Needed for flash messages # /// = relative path, //// = absolute path app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite' @@ -24,27 +25,108 @@ def home(): @app.route("/add", methods=["POST"]) def add(): title = request.form.get("title") - new_todo = Todo(title=title, complete=False) + if not title or title.strip() == "": + flash("Todo title cannot be empty.", "warning") + return redirect(url_for("home")) + new_todo = Todo(title=title.strip(), complete=False) db.session.add(new_todo) db.session.commit() + flash(f'Todo "{title}" added.', "success") return redirect(url_for("home")) -@app.route("/update/") -def update(todo_id): - todo = Todo.query.filter_by(id=todo_id).first() +@app.route("/toggle/") +def toggle(todo_id): + todo = Todo.query.get_or_404(todo_id) todo.complete = not todo.complete db.session.commit() + flash(f'Todo "{todo.title}" toggled to {"done" if todo.complete else "not done"}.', "info") return redirect(url_for("home")) @app.route("/delete/") def delete(todo_id): todo = Todo.query.filter_by(id=todo_id).first() + if todo: + db.session.delete(todo) + db.session.commit() + flash(f'Todo "{todo.title}" deleted.', "error") + else: + flash("Todo not found.", "error") + return redirect(url_for("home")) + + +# ----------------------- +# REST API ENDPOINTS +# ----------------------- + +# GET all todos +@app.route("/api/todos", methods=["GET"]) +def api_get_todos(): + todos = Todo.query.all() + return jsonify([{"id": t.id, "title": t.title, "complete": t.complete} for t in todos]), 200 + + +# GET a single todo +@app.route("/api/todos/", methods=["GET"]) +def api_get_single_todo(todo_id): + todo = Todo.query.get_or_404(todo_id) + return jsonify({"id": todo.id, "title": todo.title, "complete": todo.complete}), 200 + + +# CREATE a new todo +@app.route("/api/todos", methods=["POST"]) +def api_create_todo(): + data = request.get_json() + title = data.get("title") + if not title: + return jsonify({"error": "Title is required"}), 400 + + new_todo = Todo(title=title, complete=False) + db.session.add(new_todo) + db.session.commit() + return jsonify({"message": "Todo created", "id": new_todo.id}), 201 + + +# UPDATE a todo +@app.route("/api/todos/", methods=["PUT"]) +def api_update_todo(todo_id): + todo = Todo.query.get_or_404(todo_id) + data = request.get_json() + todo.title = data.get("title", todo.title) + todo.complete = data.get("complete", todo.complete) + db.session.commit() + return jsonify({"message": "Todo updated"}), 200 + + +# DELETE a todo +@app.route("/api/todos/", methods=["DELETE"]) +def api_delete_todo(todo_id): + todo = Todo.query.get_or_404(todo_id) db.session.delete(todo) db.session.commit() - return redirect(url_for("home")) + return jsonify({"message": "Todo deleted"}), 200 + + +# MARK as done (PATCH) +@app.route("/api/todos//done", methods=["PATCH"]) +def api_mark_done(todo_id): + todo = Todo.query.get_or_404(todo_id) + todo.complete = True + db.session.commit() + return jsonify({"message": f"Todo {todo_id} marked as done"}), 200 + + +# TOGGLE done/not done (PATCH) +@app.route("/api/todos//toggle", methods=["PATCH"]) +def api_toggle_done(todo_id): + todo = Todo.query.get_or_404(todo_id) + todo.complete = not todo.complete + db.session.commit() + return jsonify({"message": f"Todo {todo_id} toggled to {'done' if todo.complete else 'not done'}"}), 200 + if __name__ == "__main__": - db.create_all() + with app.app_context(): + db.create_all() app.run(debug=True) diff --git a/base.html b/base.html new file mode 100644 index 0000000..b022bc7 --- /dev/null +++ b/base.html @@ -0,0 +1,148 @@ + + + + + + To-Do App + + + + + + + +
+

To Do App

+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +
+ + + +
+ +
+ + {% for todo in todo_list %} +
+

{{ todo.id }}. {{ todo.title }}

+ + {% if not todo.complete %} + Not Complete + {% else %} + Completed + {% endif %} + + {% if not todo.complete %} + Mark Done + {% else %} + Mark Undone + {% endif %} + + + Delete + +
+ {% else %} +

No todos yet. Add one above!

+ {% endfor %} + +
+ + \ No newline at end of file diff --git a/bg.jpg b/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fd9a2ec9c0aae8ab00b082a54b12874287a1fd99 GIT binary patch literal 35944 zcmb@tWmFtn*RI{TySqd1;2tb^aCZytu7Lyz!Gc3@cS~?5xCD21*WeCcLH2(3e$S6@ zjPsngGSa>3uDRx#tGc^t*0m;2GfyiZRB3T3aS#|72m}WF13j&R=)~MDOhF(iDLN1= z=vig~0|&tXEqvhr^QK^7Z0KYN*sdX5ONol=D=5i}OT80&ae}b(Yyl4Zx3P70RFV)O z)zH);g_#9GfCxZXAW9I4p|O+wv(5jp0b~EYT~0km0s@UQKEKjl3oMnNs+PHn+-~&y z^B_HI|34e*dlM&PAnLb(jfW91IMDtHv@wlTlth8{1PBC+_}=FIdk_e21<=2kz1YB; zKiio7%f{Np#sUO__kPxGObnfXb|lcIG_taH2HI86y5pa|y)W&5^>sEe2K>P<06L

A0bzph zK*S&l&?^ulh!w;I;sXhR#6Z#@d5{W76Ql<+2AP9wK#m|ckT>WP=rbr36bXt0C4tgG zIiNyN8K?qO3u*$jfx1DzKqH_@&^%}bvTFu?G@NWiGT=)qXQ zxWV3liGjTXQv%ZjGXOIKvjuYj^9J(=3k8b?O9IOTD+K!iRu9$&)(18UHVd{2whML& zb_)&xjtGtkP6SR3&J4~CE(|UMt^%$LZU*iE?g<_M{uTTicoujGcnx?P_%HBD@D=cV z@CyhK1Ofy$1Q`S)1UG~zggk^6gein0gbzdrL_9WW_L=VI`#0ta##0?}gBnBiY zBqJmrq$H#&q%oueqz_~mWD;ZnWDR5|&@|9o(2~%a(3a3%&|%Q2(B;rSp+}+Dp)X)yVDMn*VFY00 zVGLoMVLrno!j!Re!d$~5z>>hS!ivLc!P>(5!N$WD!M4DT!EVFe!J)!Y!STY$ z!@Y;|fQx|3fop&phTDX@gGYm>ffs;RfwzSJ1pf`b4899~0sagD0f8KW2SE|R0^uXV zH-zs9y$GuaH;CwnbcmveI*2ZaUlH>WeOfjTx<f zl08x^QejeS(r=`lq`%1s$t1~K$TG=>$nMB#$W_Vx$jiwWDc~u%DNHG1DcUK1Q<6~1 zQhHMsQ_fMrQE^k5QzcOKP+d{eP^(i1QP)!M(BRTY(Rk7n(=5C~dL{VE;Z@eF30i1c zZdxnaRN4_ba5@e;bGl@@VR~?SPI?RaRQgc{CXg!a>`1_YRsCCm-CeG%|R>yY2 zPS0+_p2j}Ifx#im5ya8X@xaN&>Bw2exywb#Wx$ofHO-C5t-u||-OB^TBf{g$)5LSb z%gO7^TfuwG$H-^JSH!o&Ps4A*pToZ{KrUb?kSVb8hV+fWn~XOrf@Fe*f?0xVLKH&p zh4O^9g=vK?g-e8wL|%(Hid2hSiSmkm5dHZU;;qEnkheo(=whm3Nn%UlF<8A}yP{g&pE{vh2cgD9gclO(hDj_#e~y9QZsS!vlA*#$W& zIa|3}d62x6e2n~}0*!)$LZc$IqJmt%Dn0;RTtHE zH54@+wL-Nk^|$KL>MI(|8Xq)%Y2s^IXx3@LXsK!CXTu|M)|t|!(RJ7D z)5F)Z)@#;B(KpnuG=MTtHz+c=HV7&ic=zZ+_T@wM5Xp?PI z0n=#H9Ww#57_(h-LGyU?0}D}$B#RSENy~K0D=T@ce5)sGb?b5)SQ`VIdRuf`E89*x zVmo)cQG0s(&-N=0ybf^=za3>9^PIq(be-y)F@YP*fXgeFK$lh5H?GO9*KTTVmF{Tn zw(bKSbRNMT+n(Z{xn7W7#$N5-WZs{=S3d}U$n*j8G4g5mrSJ{#-TWx=vEUQ@C#z3` zey{za{m%W>{Tl*^0zL+;2TBAMe@6c7_<1^rFDU&BBe+z&5YWj63f-@p55<1d0aw8Mt}mWF zK0N_G!6jkk+q-XdiIjk}8DZwdMsqa(A(uC5=(uvYT({D2@GG;O* zGHbG^v*NR%vt6^da@2Bqa=CL0^YHUR^B(eT^H&O#3OWn93JZ$}iz14_i(QI$OLR&` zO5c{&l`)oOmSdNPeg}Pb`M&qV;Kx*jOhtPocV$@>RaHtgMs-LHSdB-`?^^TPl{(G3 z(R%6n_6EL&>PDu<{3f!dC)6x%+E>asH|q;25YL>hiAvN)9Qf_i|N_A>xT4#D?=Kajhto7{4oa@}({Ko~Tg^)$m#rP${rL1Mz zI>-!r{8~2+5TS!~++a%kCJFGiRyW+bed)j-O`}X^H2cHko z4pWX^9aS9*9S{80_`P}Jc=B`_c7}JBf6jj1exY!&bZK*WcNKDtcU^G9ebaNRe!G3= zb`Nv^?Sb~8;Zf#s;mPLd>0h}C6OfjW{*#xyH8C}Gv34eXPFO_ktnFMJNtNvljZH`y zNuQILKMBPD+Uq}gicum6R2v8a;m-Y|0mCT#8R}2o_R{`We)&Jb0RN{S1U3i+!3Jcn zIsa&OSs)Pi1)xVdS(uSN_Y_r9A^q2RU{o?OHE}et1*WQIVd7?CYX;2U_PvF(g`F*_ zh3#|p_RnehA0z)S?EDkwKRL6ri93*20&&{edpKH{nLCq8**cpz+8RDbWN7{J={P>e z=w#>UY~fDEjr>6(tTtLi& zKtElc=0O1<2yk%Ne{V2<@4&$RrwK^F9(dfrWzwgMfsDhJuEL zhK5H1Z2t5{LjLpq*DKQh*!w?ro_>PRV8K3ugCM}rK;UR#5NKdeoggCM;Q<^R;`xc@ z?+pwb1{MMe8WIjTY=jB|g8;gqAwVI(AV5GpKj45vKtjQw#XzIukg+H#tEf6e#r_x_ z8=rv1z{Fw|R&x9l-G)u>6xWISR?N^pATYC{vV($(jYHJA3XYPRUF5w3WV zan|~8U<^Vag9QVk`d18Kz!)&VWPzhfXdp0fFgS2n1PFLYC`bq(O5i9OItHeqQdIOP zq%ss4i=jj2k1;H4R$(K@Pg!lNzoFSG$SK%GRGeZe$JaQV+dGW?va3!gsYLzXz7Kes z10g~Hvp|DD0||m2cyn@09a=eBWPE3dzue0NmP!?NrUcKZDkuD$-Qij!>>srGLPckR z_uTH|s3DDYClG+X%T5%tvKFQ#h8`Af+1b)enn#tE@T(lLYv75t)S#kh`^%hxGRK|b z#oW|VBQQ1s(kyG)+38iO3>b7=TIh$-ft-o&()?I0e>KroO3t;9zM5ba8fGEUf4~NY zkt#F<+$HbS0;7{_yjA+kxRPAM-2dIOSQErw7TSMVD6W*gxJX1rwRq{(FZnS7+U|K+ zT5@oHygx&Ifn`5D*Z#>98;Q0u_+n2f^nA=6hIQa$VZL3~5-8^jjDPK@9cW7dZROb( zGg4ak0vMce#N?Z;QJuepHZ3TR6NJ6}N+fA+Q%3RHeBzqN;gmvFbs;sy`NC2fD+ zjg)Y={k{S#yhKpK4c;ex6$QMZy2bA!gDPnyS-3Rpp{O9;p2cWCzL6tY(g}L z1KrISGyK|~?KZ2>Qt&%I0ms8urU(((;zv}zTOmK&n?ZkH(M+rHLxBD+wQo+r%@G7X zaHW;uH(G+NdQ>f}vofXRRjR<2t-&ztQXDKf6#hPKUmYDS75^98qUD;3NSV26g!9I_ zp32#o|B~Vj;)V7dnJ1rj?2cSVSw1aYWKjtCN579n9~t~mQPre#j1>Z2<-%i@v9pS_ zWx)xF_=#Xk!wIQMNn%S2(~}kf=aEWC>$=);9n;$q&ug6*z=6%y$s zjRS*!1-z(YLPe^?MPkxImiQHb_hsdjL+o8 z2nz{S1(E?A6}9p?QutT66HvguADkxSDjZsM5V; zKm$H54opZ`14>0HQU!2~Kt(17D_V_Z7P}OC2-A>#inmvLDRN$OLpu_rDfgZX3M}OH z_-8KdU!?g~y2=z@t4?hUA>?aQDLWMWSMhc1=n$e(+$SG>2|5zsM3nDP6BI(LGR3C%5Hw+ zIxk8lKS}$Sgn`fQ-Dr~;nZFm)&4ju{t+qo=hW|#lrngu5LTVj*x#?Z$$UEWe#cAs> zz*Byhw$BM`tuRZrL}S@39=uN>ztQ|va9jgiIKF_c>z_`@e>#b76l9%}-qBDS-HB!U$Z>`xhZQDI`Mwj3kfCxxWjH z^o8C1Z@)+lwEFLUxHNQa3@}EhUj$4NX5NWx4+5F{I=Eb7w_vQSH57^PM z-5PNb?f#_k>(un5m@_b@$YLdVBUD^yDE^pQErCHxaTR%%Af-*0I_Xc|tzo(vsz@z~4#Xnxt@aLKDN=E=6 zHh=lBvT8|Ges=nokcKyRd{>wObea9D3n1jFj8rQ8E#!Z8ZG7A_r_LBH+pK{WM=s@O zsb0QP7UqBscVqm{`r5@}HN5PqAqlR4-Ll^1Qxq^cL>u+yi4n;F^GU})lMB3TTzYYl z_m>OiYCXUOIa;>Zub0WD;mnnL6=p<1hvWR^)28T3$(&O7KL*%yKYBQ8QQR{}IagO#TSOw+4Yp!X3ID*0oeH zY86dvc!D<=ix`sJ)^#@V8yPzc%t)PQU3LxBXK2|bKC`yc$bM;rJ{K*!rcOxs`&wRjRPHvp{Ugpw4dm1G~;B69Z-%(^Rwy* z_bey*qJ3tN@WlXIA#dxL=XcTX65S`hoT( zTG#@2C`vGM=CYv!OO?{eJteqrpm3n+J|rZ?I}Zy*t#RyQ23>^`an&$wONDFXm9vki zfZAiPUhFazFUI$M+J3b!4*mB+>75@Zn0AUo!@VQ8e}uT7SXg*3+z`90r(0p$&TZ4s zSr6!7(`C*D^&O53#6I-LZ?ojLs;x7^NNc!A!pIQuK4g7}otfm(@3VB1qZ4DiqS3zW zvQ6KMP~(ZVCL zd?7-rmR@kfh3+4jR;`o%iel_=t_7C2>V(T|`Rx^-U&1&4g$UVN%jOwR`0LclV-Utej7Mg>*nY7t-=!{%#c)ogk6b|RdnRWht_jHIstj7u4FSI-% zX&V|f_p`XS((dUno(BXOxm&bXr*T3~0XQZ16B0ZALnpaJ`5LBp2H+Ho- zP<s{J}NBW_8+&00aaz|ESN6uW`44far@FV5go2?ST*r3X?w|5jjmcF!1 zT(if9IC9nNEmlmC^mk1#y66AUy6;=W3~@x>Y_!W&jW<)NUCZm(-MWDnV_Mq0GrQ}% zuVn*op*z6lPw}r0nJ!gKaQg}6t-5koHgqUP9#1q-Q!e7&l#VF1mJg%rc0y8+ zqa5kRKIckX*SQQsyOs{U;Cn*u7D(R`S$AYzWsUBT^!0~6<~8SfYbZtwt?v03-R2?e zPgjjY53&+kRp}<~9D<5VGf_CzYf;ORK)7?G)wa?NQ80JlJSiHlr-KA^fix z_w^<*7A;&?ILE89E)!Acr<$PJ?&7_a^hO1y5^&M04sqRy#?*i6kkpxidzJD)Lo;fN zq%S;y`rQ%yTg`&vAg{D^>ximt;PtI#`7`A~zwHi2D!7Ct_hk@K=dRwwkdd&qfUPXg zX8IyEz#Ub~gkfl1y@?3z8U=h3+_96a)(Q(u752W?wOP1jVnh+P;l}YFe**Oc-gEBI zG>k{pan>S9!!yyt2s_e19!T|pqdSY*2Bg%agtuLQCNi_vM$U2Q>|J|PA`SEJ!$;(_ z<%GXkGUm&4t?n@9byen5a+=7(@yIEDyOk}LjSS7Ir~7WpMQ2W>bU0A`Y9h~{f<#QK zi9WmyvdEE(ZDL&sR{Z?kPN_nN{Ny`S>3q^k(H{~=T#1(9@zmf-YxBh2!!Awz12(}Q zW9zcbGJr&$o`V5B!J5<(B=AWSH7KG3t47MkE+?SX4x1_1M|f;(oQK6co}q5>-+ee9av6-YjFh`LT!2vfHYT ze%Aya%*!mwr)ScwpWQ08O*X$SA7FG{K30s{HZSZ3qk?vOlK@^ddQoBE0^jbZSInPx zo^LL&NfJ4%+FF5=%N50ox=-5+Tqfl=QrA9?x9mh*^!a>BiO}P@Y&@?^-%NhooeKo6 zi;L1|XFyID$!umv?o2kxGxbr(`wj6n#?lNSzKl%D{SFfhhQ9+x=e7=l6sd(DG{2ao z%)9;;Bd_~;0@nOuIWoE#esbOLdif`i#>)=1?iUjnF-l=TXOjJLcPNEE3N%?|1DEc2 zg(+p;790GYK48Ae-VKOiL@)L4NdMlt6j~z1(+M2dK0x;xxhN#pqSEGrJ@LPU`>^A= z5t9;Knwk`WD`=4L8M&TP#_R3o56^1i*FJ5`h(wN(@s~TAYF&F1?rnFFO|M=0Xt?97 z^-3keNWRN-!01Ff1^;vKqK{mAK9hzqbh7ZxYu65DL|sd%AsQ!Uwd`)>8I#q}QYoHa z%&ZZ(e1-{+nj4WhDQkL$>li;|elGi;{MiwFTxdCdR1)S(ln95(WRhLzm!v_#M-kVF zxuxp`TD7AIR5W6g7%e%_2g~9*VXe@VqkMZJ{Px4*IsuDXQWvjMQm$W}ba3SM%1`Dj z7b?WWq(c$&m{!%p-Bu-E(82W$`$Z5|YUu<#XvyBjL2hifNqp5TWfJizrGAk1b97qC z?qQfpAQM!JQQuXNlk>)OUrZX?pxzs^o5%%R)4t&XeBm`z#q25ayor(spM3(A&tqoC z1+&Y^=MZy8=yv$s{dBdg_1Vnn)cF~9R^d7qT!h(g#^!4*hdkP;sO zwAhy5INokl5iIaA_}qDnS{yWD*Pdn+Claa!#s&JFJF@Seec2XG^6n_4Be3Uf34US1#ay zuUoQBJ%Ov3gnoKyMV}71F6o$bE9L9wDZ`k^)y0m^jWc1C6!@a5A zj*>nfwv_KPxugju&{U@?T0UzP&=!+=ksim6PV|#okF-FG?<`}a&AxLqo2Rt)64la~ zC1{~qd%dd_vhko2XR@Ip$Hkl`_BKvQ3S*!qe|K?rCd)_Qosh+zMI`$Z2t9n0hp5-7 zranmVkZ*`^Pckd{ikts79 zUGHTuQNQ%soSLMazOXM`ytjJQh%}>c6V6%BUcLy?8O2KRujI*yxc#Hw%yBrGsb#F| zuawKj3jM3|bfnD>cBkQEZxL3rT9NzxkklsaWf^aCn)Fk7sJgaN-y9nS0f)EoD$HUWLY^~1cqa7J^nx$<55 zjnWV7(~ww%@Sj`|*qMc5u$O!}abFT*tGRJdKYg;&EC1BZl}ftdsYb3vXK$O~3RRk{ z+R{f&7)@nFm6Z>DZ0-tmn15`1ke1;n9rq>vg0x7aveX41NxKH-B&n>K9;LRB9ZOP; ziN4U(z;lU%Jl0Ni3S~W)@8-KKf=Zy#mTQKA=SNFchh}ZSL}hjE{`eU6wS;V?jz9wM<4CFD6c@cS z7N-^S7NI&qB3iB*4iZ1#?NQDAt zrBM5ukk-6=AF(Pf*Ik{{6K?~hIHp>tiE5XYdu0)5Wc>y={wktGEtQ6)W+`>u_1h}4 zI-Xb`tolvb2u3=k-0q7#>BicKE3X`L7tVd1<~`A0;(!OPaz%C5i^pSUKA4!J>Ym5Y zAH~x1;rkP+%gw%xi@9XaV`Eno9R>`YyHojqv1Am#duri}6)8mV5H_ z%Fv3fAw8E-ErswbdRzhqjwRg*S6N%?2pfuaBbLvcCvtymSlzd>+>GF6SHz>3Q@Tc1 zy1Hr@Q+OrTHKT?Q*yL%0rCp|h557sqf;G_rr$Xpa2^d%tRV-Yg{>~(nOa$f3HD4o& z;RFB`Q_(Fnt^swSPsdGj`;1QCUqs$ysQN_(4dniE30}_3zebH&UD#I?!|^!ExM6f$ zR6NY^U-=!gy11`2+O_0WpgEgVe^VBAIU?>GhVISR>`Nwre`|6R>BQrFrn7cAKRfrL zk}Gz%B>KEZ9M*M{Mkx+zFQD8}SLf}g5#t^2&?bJ(ELN51O z@1Zl4XxZ5U`Jw&^1iu^-lc}q-JV$MVGau!(I%U?2TZuZL579_E0bNM_qhTIhQ@=)z)v~By6M%H4c$?9&mX7FAC{m_v(DM7yLC+9O>V_ryTuGzn-Pzlm&jnM)3#OmfN<0e@C!-$ISb~NHy>Q~$g-SYG$J&DUA z)w!h!IgZ(+VC!PZlzbx+Ah&Y0IQ&tyZY9Kfn;)=FKl}B^dBz+1)~F{?3t0y}c8p#J z)5c}KC5dHU#($MO{hf22CKktFHLSQ~l<_0Qxo~dAn1$4c_^v3it)H-S#tgKkFZQ~H zh5NNK*{QuzXRiIZq$2m(weD9~=DSwOJh`z%`@(GC_DF}4+23(GzfWU<;wH1fHEDMc zxLdj*7r@&?`wE%%0{KMYbLQ$v-Tnf{+83W%X6ifXkVele#}ZiI5VEiRK%orf44(k8$s^J%%XXKd4= z{IV-aEf8H&x{Vq|GR$!c!9o)6D3%X%@HmU~8Bd7&03^-m@{7Xv zc6j%4mG)J?TUv+>NjB zUvi`Ti5YB-33Uh}WCTL71$is*B(hFqo#C+9ch>W62?)ue5*(Al#q*P8Ae4yt)6dJf~IbU}uVWd+b zQawZCzArb&RLOb;%f&Y|UYkCge;_>==rOC@By}g-ZKk*4GIv&0z)C45j95l;Fao$U!^!8AxU?zqM`4ef2= z_b$l8@&1<-{+j6xx|6KaLXE@qZEPa)mHI$cR2j8hlpyVqc05<0vmkCyn^fen3%q2l zz{W_ypz1G2dE)#nHDhL%SGl2$SCOBR=D%(T&T7k?*h_`APUHZhPA{_65nmMTQc3o8 zFM4>wIMj%;*kYOKp44Et&-!rpj^}x{$$6VI<|vm;s0+cfv$(J#if=|@-o_7O~*Iz1b^URnydmlr$q$cwFu$6x_LN4f(( z;t+jGu~Ht*p4i_TYgba&C-~sr`XJZe(SGxwrWM=e zB8e>VMCsTP4{rT^myfv>_38ulnsiUbJ*+Jz^hg8JEzLclQqY(!c|`(AoFuM_>1YGW z0QEI^EWJlvMk&?wrjoO==~%-DeZ}7v(pc5?k#Tv8J`(0K-wRPEbLt;-)hBbo=N9@3 zN;&sW`9zShR_CUfi>-KbGPJc5Ik0MU0b8N=#W&M+58B!j^y)6914+>}zeu9ypjTa! z4eL-wZB{*U&xWrvAHun@YIU~H zyggx6pSl~T=^lOLfvd!oa0e1jdVBXnOKT;S?)_|Xp;hDqL&Q<^>kiRfl@s`*fm}e1 zQPc_Aeee3I&L~50PW~{f^k7%rmy@>8%D(=zQA8Zq=8mCdPP*+LG6PTxO53X)8%2md z%h*q*uKOevIR1=9sVwEds#7aN$VBcCsIr_uYlRYegjZeDoJtB=p!ZA=&X%oIZ%;n_ zYWNFZbxngl(#~~9)PCAZ&`09X+CMH>J=i&}mn$-UwP?cUA!tsAM+?0ieJ^AKVMi_; zyR=pQ_zAR1cVb&m_OoR{xI--8ZrlW1b!|}x%FS$v3*FhQ>L`#}vuRg7)WsfaG2MMZ zA?mZ0+|KCcI$@b~lXmeZ`uR5}^^uMC9hQOt=FQbC$m&#jjFW9Ly0cf|14;3?I@Pw} zIQT(%aqhM1P>g_6Hdae-p4|E#jlbzFKBw74;T&hu+9+i%RVJ{gy}<>ix#>2T$Db|L3C1{_9-& z{UL4tnzDq&yE+||%q|Td?GNt{g}fS+&j_BGo$L#iR$G(A2o9q z79w6|V+9RH8bj1&)70W&2RE|)Hf^Sex*WF~6vy$1sw$qS0@Ea!%_BYbNy{#hq7lk1{D=ch=U=98v~VlFZU} z^$rlMYx)Ra>%==qty^#;7z?B!2)DY&-8e6sc-rz&DUHp0LeccT1*s~j%3Izk#~^d; zzSU>BSp@=6k6?6h-Fm|_>yUvtWYsZHR#^0&IsZ6vdv}@eMAE>^FwL~Sq7lzFdW?BE zW8kfr(cFYO*VJ3hX7q9#ks@aFB9aE2bkYwDBx@9ktxyNbaxZyQ~CDvC21JQ-lxj|8bjiNRz=Uvqikrg;1#^SB1L5NzTi+=8@ACv7@mv10Az z#vG7dzyB-*OEDZOFRP-29}|cOJ2UGT9yMDsV90(wOH>p4dOZg1CJThjk|WN@aZQXp zmt7f<`6G&WicTo5)Ui60K};+T?Y*XQuzTIerX}Tq7gAG=0r4cEe13kFLL1_@jzlOd z4n#A&o(L><>lj=?Zk{r$PvekSyAt3zh2*KGD2)*%u6t7p*?G>@KeihOVt!E$<}pK+ zIiodqg(Yz74Bd<7A~}6@N3rKr#^BymRgH88F%JiF^}}a)MTr4t2nO zByZ`OdBu?QUy!oDlRZ*c6r=zcUv`}*P@|6T*A{uCl4R(K*2gOS%~yf_X%&xD?2d`< z+CzJI7*J_COC{cA`o^E`3sMme9TMVzumJ2`euzAot9yU z(@r3V{IdQ^seFVsmQTy1J(o|~<>!iHbP4kxT3FR0`a$g!YWHwM&c7_TPm9%Iu)r;( zFI&~V^1%V7T;lF`)9~lkE<@k@)**+XWg6lRN1W1BgO7V>65@U2Lz3OiU!$?>Bg~z2 z#`)bll2(LaGy-erviehG$S-Mo(9i~>lvGru6x~PNNA~3qZ5q?Xx(s$0$KN*96?Ull zJJc(Sd~GxN{)n8p_T3pPKiI=TiK!3F_j*2#oz7m~YGIEUScRfTvty!@P8)7CRT;@_ z!zy+vs4EeGvU*nOwBH;8bq5W|bG5wBD_qi@+P^}gvn!A0MU@@ki3;~ZD=wkazYd=1MK@=#RiD@)TZ%)=f)XjEoa^7k?+z^4Y> z?=c~+bH6!C0!$_(g`l}1+Mdm3Zkt(whYF}V_l#5})0}FEcZDh4zQg05)YwRscAK9_ z!Y+pI8fXZIDZRsN>2DYsDSgH?a4(o9hYO+#LNO+MZ*KH5Am5i0E~7}~nbY7BWu9^7 z*r`uPROK4p&nkmM6?uIkSzeSZXy%(J?@L^VU6bG|RJOZNl{mr_ez znAOfG?5LhyULsxjnqX3X+uvG6^@3!mWaC@;?%L`o@<`@&-c*$BkEQ-YYM@u*Q$CzH z-1gU&J#(GHOv|w9XZnIEx>ZzPQP2^k6iRD zOLEhjE;gWyagg9(=4lheLA|h=PkvK}`}ri_PS>SP8WvEi-#6X&%h>C|EMLjnSPj!F zC6C&Im+rQ{#$|m1ebMD!dZW>=?8*4E+E@1aKG+pWZfBuqo8h`$DPh!>ghgHZxQiXR zt+G~bOOP4GOj@O30Y^!7>h44qItbl1cbHzz!{V9Z01QTV{X}jTscj(pcj+*#w1)-y zGn!FSU2Fm<3{u-d$M5Q2pTkZ^V{>Jm;mnH+12ai%)!gIv(aS2w@I1ggc;Jy6>)=Ob6D zSTj15QGa1G(F>hwz;dRy-p7YHX>px*x&?ojjsLfXhnd35u@REWfE<&~E*DHRfbcYr z5q+BvN;-LlHcucB6$KMDeL-euvkpNf(?EE~pj$%l=!GFIo&$^0sMr#d+K%KvYL82L zXQ2;>@=^zYF=7CWAxN}}-&=p7HaXXByc3GggyxORLal1c9IWThl5025E<)|cJxk$% z!C@D?!`hx{$aLgm{oYuI+TesOnM$r_Xmi`#oI`o-_Kar$XyaSwoz`%aDp<4l1o|a4 za`a4R)|cO;rrRT+(97r8yGjCZ4UFJPlU==p5gVUMrrM2o<>i#yfy`GF`g^;ENHvx4d7*;;~ z`#cV*OpdZxELu<9J|z-|8W z4@?l+Bqz#cT~$ggfst~VAPlr6iC@-i2A;OZ(vx05yO#$>#y;(YX(Q)^>ot+GzPhZK z+!+4u-gZbD={BLPop;=Ttw-k?QcqYk2ue>+$hc|5O<8lLd%=zN(boM3uXnoPG4!uyl*poeA##VFm2Eb?*_2+Rz*S5w0x*@F<@Rzc*K zs3+Rb(b<;}rzYI&7rtDK9h|(9zu8O-7p3!SEmHsZh|_x(fq@SPfd@)?{}Dtp&}=JO zuq`##B$=-9AZg`l2ow?E2@8xN*qBhpzQLil*e&upGYyy!X!{{1cmeY{?va4u+} z!u-EkJNr*Y!19Rk1S%}^71q6TED!HkUcFPlF>mcxM2HhGxtF$JWCd<*;sip1N;mgp$qy*i$3B9ijHLa&vcdYON-+8TT;TBm;+IRPWWKMjvQlN!hTMgpZJ>j zOPxIuzN=4=q_dA{TGwh0)O|w7?@#XfY(dE4VH(N3%e^i4nu`Z#s8u7f`${{{Vz)RW zJl@mOEVwYOm$Yg=Bdl*ZIptBBaE6!P&Pi+hzL@1Bm=lXJ$(L1bZGy{dxsV(3q1mk( z$DE=lUy4&D#J8G5Yj;z>dW(i%ZCY0t&zK&NPi(Di%1~GqtcAVxS||G&?U$yWvhN@2 z)op$za)p+5ip1?odZ4+UYH^OqJG8c5xxRgSCT-D;;+0&U;5A9a^EI=pT4iiy*WR#~@q=EWspfX@ zP=xq0q0g>pNvK%>AfNVx0i}4n$>DX;+lUglU3uH=VWI=tTfCiQvIgypXeX1Tq!z5J&14Qu>AL}y;JVQ^4XM6)#qny7O(}SZuP64iK$k@jC4=%Gjs<(&Dw(~5c$Q@X-0#jO88l9e@avjpre-Odyi>eWnu0K; z#(k-!Or)qbHf4}QTl7947?Q}S2?nS$Z#6TO%E7sY;)U!G32YByRWhs2j!CLDLQcq3 zX$+_f?G+CvA=u#+9TZa1>-t$zy&y7)&r7b=wc!(&?9o_dZ5$Xip(O*&V^Hm9)iw*R z*+1xCOr_?P;J+sCYnpj*@bO1KLZG_PCOUmzA}v~{7GVmhGZ#M=Dz(C#zpP(NKKV|S%5 zP?NlmF*EjTyrp?^#3gw7dF%83c1_zs9ddt+;gU`(ixlyi>Er0>*WHNQ3TBhGbbR|5 zvKRdsroosdZP#1#O`dog!m$z^l_y$I=v=i3ZDnoNi$(~a82n*rjjbN+5FF~v0tI*U zg*SfhOdfMFf4KRaLT}ZqZ&$U>YD@bb!LcOV#>W0|vq}UFx^$3SysfM5G9l?!h_!C{ z#^e{zbm(B4Ia^Uq;zz?bsiNztSaEC60B@dL-PI6eI#%mQS2u`aAc)Ysf=P}Tb7!k4Da&qQdbVhhU>YQZz=jT{!yFP zoJErz%k&Ou+Dl)U8l&NS?OpSlc7T){(}p08>|As(GgrB=hU?+uvHRy$qPs*Vk|_VG zTy#ASu3 z*WPXIcK2^~yI4*oW8cI$FdB~le~wIpl<7p0D7dorEMY|m--y1>7|eDiILGh?lg4UV zP>73dxGxe90oKUBJ&-8>Hhf`fYRN7)L>aDFMkJm(k7dQz;;{%;PSiPnm>N-_yTnp0 z0M$HW{{_|9HS_1L$ZlC13)DVM)v{VjuQRn$+~w3FsLUh%^a(G&R*ua8VB4aYL%blx zU524+%t#qYw$&YPEb`XCqKr*P@K|AHw14Wt?xXsWzv+A{h*^)LMAs zT{cqCLDgs)6QbqG3?NcjDhm?l#)5WV%ccMM?065#Fx3?oh z>JTWm?Kos2ECqTN(MK}p0X4{K%lVhBIIjRERwR9ZaM(%6q2(r$K!;T<)=-DuTzO0o zLL`SypU4jEL&bl}a>zZm)gr;olVe%sA4X(y^GGBn8)PB2rs8)V^Qc@8)w^3} z*&kAol~0~kQjwX5`Kx`&ZNT4e`DGoYyu6M72QS{-8f`3X|6S4hLko3;5Z9Jf#tyR*q64>)`~-aqv0(Vf}9|BksCd@0Hb)?1!(bQ1p$S zK=nMc8|Ag%7ohg@RY>2^=ViZ%7o3M#sY-`pJr4PQTO|UJkC_7QJ_5-^-11kr2*jXN zZy$mO7>kErPaxR9t=;P+^>x_0lnUm-((`FA|s?cQB*Z!UUn9(Opt(3jo3*%S=S?`KOt8;abY3C#g>M+rzHv)lW zKs!s+tEzwU9~iIzuiU%hoila|>P?re&1#5HubIiu4Gll-x1>78T^6Hs{$Y+Z2Q7>$N* zYZ7ehmtYc0u;L)(ToHI3oz&2E=!+oWAu2$3tty&9J0b)Q@f#%gt*RZkH^uK7pUL6H zw9oS=4~9OjBAa^9A0BWfH(Ch4qWB=!65PeOJ}zNO8<^Jjr@2T`txn(6}M)ouRVz zIJP&|&T%ky`T{}lgWZ_-{ikj5ta5aRlFw-ymgGgf(Wm8TXSH0I636{|EI6`STr+ie zc|jg5B2OR&3(iSSJ*MawBBK>8HNAHV8+ZkI^Qb1-yOcc|xj8n4!465%AJPbmd8K7t zLbSx^zQx--LI7w<{BN}My}9GBIz9xTbkB}JtfJ?*HdP8!PDV~1#Vl{w-6v_FY4%DeoAKL)vhOlsLE!)O z&BMGp|JOH9xfxfXv;g%l+WE}MK=!&|eNf*)H?fgp@g)3mhA@c^;oS(1EVH|pA+d#hl; zcjcgq=*DYFt){{YXO?SW7s_B@6zy6)lZBl(UU&28%>;QP+#%(oZoNv=u?nl8HT9_V zQ_Qn|XO{%sD%wgXT#rerx1i~(1lN~24lJtO1GcL4jh!mcfhvl1r!VCb2*=bBhUNMP z448F=l1i9Ce0)eeINaGs^&>f~txf)J_$N@`NAd<`bsbAhDn`oD30CN&;3B#WKVe2b zOFB9oeIhn0a0#KM>-VHw{N3CB7o848JPR_NX}Q zsHCB~S5#*(s}A9Pop8w1DuKGG_+f`S44{(ALi7Nc6h9mI$$;LXu@1fA_A8KU~;-Zq16IF>Qu=9Qdi&# zXCc75Tw(>(Sv3N6R&f;|8SN=ldGKyHIw_S4(F*^Vn7x7HvluLn65rP)>X{B4njxD(69bjKp48>Bm=8|m&&&s>YW_q(6# zoO500_de$zt~tMR%(;@e#<<7lzK8oo_LoT*Jk`0ncrlX2sAY+sn_qao6p!ScvzIi; zBtHe$;=sxi&UZx{M0~LhEZVmWt65SIKt&VNn(4f57$))F^k38nFaMRSE)oTWi*7Kc zDr4oL__iiDeH}5la@qA1PDwQk*&!*_j@|8I>r1K3T4|>j-EQ9wY&hyN)=#+Zf3!#4 z(FJ5Xfn6~GsroRmafz*3ds;(`boMsOqOE-z&O;4sSnB0VECw~QTJe`d-}MFtSHLAV z0IDL zx|ED1Kh^$`1EpOxj{gmV`;YQKRE8OL_aXPjM#-Z#z~iAC3utLl|AcPYRM(}kz9UhI z^@+>uD-`sbng$iy8h35<)xe>J(`PnO46)PJe6GqrQ+FOLD!pNOr{fG02eCb6SE^vl zb!wtNWaSNf-boFmYAvbySWNWf#Mn#I_?rjlqolLh1CK@PPZPgwb0Qi?EC_SStbvXs z>bmGj&iVhZuPz63{T7t60Tt2`(|3P>-51Uyl z)=~Ak)U)TRXj7tBVl7=Ft6=DsA6tpOZ@@;0 z63)4pwoRsMVg3XuN287I#@s2RO}OQU2eViKTGIHRfKL){I&XShyi<~Dx`Qoi@Y5{u zcsYjQfDJhU3X~4#WVs+Oup=k9FO2Nt=_yubX7z|HWW44MYl=;3jh(=KN555ziX_B1 zg~(2vEhI!&4I^VDBMbj!jHJA9ykDONq4#&6hV!+!_;ZO{yqcW@{qM`ic9SLCrb}Z) zEi=^1N!@e_vC%KsaqI(MOByc4hVl?}^T{Q)yG*_CKT;OGXt(t+>(q-UeI-%xoh7ZW z{xa#~Ib&^04xq=RUH(CkcsQz8zdggYuZ$fi-|wm=dVClD-n4#OgWJsi=Rb`KIL}k~ zEc}n6MK26>fL0>!&)D&Yo)zfYgh9^mVDb)MTT;1}k>TExnds#~ zNvq|u@De;gviTu*a9_tJo_`(v_~y>C^#G>uFgp3%rZ3wSTSqzrN)BrQ&3`I3k_}pw zzMygJ9Z9&3)1LAi))swr!fw+&7S)jhMp0b`0x}NoxhT7^{qf9)Gfklj=xego-ecV5 zS($}t6qCJ#IKtB3^L;o5e1PV|K&2_OKyD{(H(Pxh`{?fFLt4>>vEvui=79+-Sf)i9 zF(a2ZTIw?XpK`%sHT$$aiKCiMtX{R)!4l21ug<|iFieV~CcpY}SME$@1QY;(Qm6Nz z6c^_Nwm`Oyq2aST@_$IF{!!9N)xEcOJ++{aMfxfCms$@wWT%(r-Z_5EKYyOOudLu^ zCIXjsaN=n2Mw-Oj<(m2+mRAuXSW9EY|tMBSN}?&S_Mcj9-DXT&ZG;Yma?sW!T&1m zJICriu(@Dd-oZ1DD9FOm0hE~>$KCmTXA^&x)!Bv~rL*#M>D}!i6|y=2qQY$9NQkrR zRyxZB5GbHX=y+Fji1Zk1cINvH07iKMxKwqK*N-ZYmzi83*6el)VaSmjIC$7nh;p1@ z`9N0mAUgQKeQ-(&o7z{3RkAv$Gj&Js!{SQ|^b}m16&n>88db8kM%{>`n9S`cm?Rx835C-$Kgn<Bxmrz{w~mlSS=K*bpg` zXoaPx%D98n&~r~p({ssl8xHfV*9?Az_o%PPqYm0K;Hso^iP0Oh0s*Ke>0O*(S( zNzSeofyPd+4MtUf8BDS?mX_T|W1LhTUUhJ_hc*i)?9>UT-=gLSJuTYl2E-mE;0lA$ z0Gs9IZIBQ+j$M|8bXg)RW{IB9i z^`a>V4wM6r252~_ixGgZ^LZgm_l6_Ct?UQO#A6|iD$!ouyj(u^Ql1q15fPe?*Y{luG_at zbi>8Kt+s)2J1<`Fpc;aMw&Hsp0-v@xnZKX55H#S;E^{)`Vi$XsN?=2u>gEtyBl)P1 z#h~cPt8hUJHd&{q2v>2E=?+BLLF_aJ7{i_%JoUCmmyNLjetB!H9F>nrr!@-2tlU2@ z+eEl^s)XD*Ne({Ctko*AxGe5KE(r{<;TiYW)9ea6cJsg?=S#%VRp~Uz&%_&+xiW&h zE{N!*;koR8w#)P>H`m5k9m|vV@p1i<3FzzeYwn^&6U8RQ+-?`Qt`i-OWcyW}FXcMj zUwgP+_pU278GLC-!9qgxgWQ_OyoXexni@gG=5t~i@npxnZiE7%pO@&K2-S;z>F!9; z^aP)}^gm1OH*7IqtvIo6B#xP*7km2Q`@7424PtRuai#i_Uxbw^rF3sU*jKN9l8$Gz zz5V1G(so3)Rghe@bv;aJmr^@{&4QgJw7B;6_{2?N_$WudS-!?#=znF>`MZOl$ILH8 zV7z+sF`bl?WBXXgbdKvo@OF!geye=ohs#<)l(dmzB9~c#GYtOwE`l7!s;8*uC@?N& zyvCxK8D^;q1-ULNJXrOHf39ETJ>~xCAvs8=@}Hi4=Gn6kHJ?cu-v1W&Y-Nd8^!W=} z5jK29;i3s?4mu9V2Vs=NuZc3*x+Corok@1$?CT>Jtli?a7e;7OrLx9&^_t(@<~{Q< z@A7WnTOh~9ek`e_``IZ*WmjyFmd?fXzLf}J=@^w%bg}{OHzYPtp6k2Z+9#UY{8nf# zCI0us@lpP6rX}+Zx=O68OTM4Z!%nUPv6z(ahE?l~*MjLnsQz)76s zJTtJk|3eqh9m=A6HxG=Nu7DmWL*%CFqz%^EH-}l1Yt=`Hwj$RZuKfU&cLnHpxhv?S zkzcnGgOI}X0TBB0#Ed`CwH>-T^k~aJENSW_cP+SK3xgaHZnc`JW+z#H-n5f-@xwZ% zKI+8K`zUtQQ1Sy8`R9Z6ud@c5h~cufYcHbXd*Xp7$tL^&xk@H5b4sCdSjuREY$K3XjuZNauUS7YaAdc0+Z2G&~kEmKE$r z(*hl=!Q;9*mh@WHZ&G=q%l%!mpT{c-ZvD-kJ}Z~KT8*ZXRM=-5a+)}z`;3#HYJO$N4cmWP>!&_LbzAd3#E7N;y@Ny&U&2n&TM97EjJJjxZy^$JEQtM+&8p$&da|9B?S4t(!n+YVKl2_>L0k zKquxJ$P6Gn|0=Xc5&vcF)B`0;shHGdMqm#E0G41KZ9&X`{)?^#9y#IV7cHfjYQFib zN)4(vE|?!hd6Ny`c42QC#IT^Y3Z%AMWUGju8Jn)+{l|^>jCI07?|90t3hy-SqWOKV zeoQ-T=4g`h&Xi-?rDB;_@8?MVe0qyKuU9@6p&cGXWGhWwd4sp&5QLPEq4wI2dbM1} zB0zB}DMx5hG{RVn?nS!(M;Wxlz7Y%OSBfV<3KCecvh8P1H?LNq{I~G*f^+ripx5ue zlNz%@K7kV`2!+)p)T<->$VQvd_+CcQsRVu9A88$uQk|}j@PZdnM^RP$LiR^Qhg71^ zWAWKleQl~KWU*e%hy({Uk7aQHDoaEm%Cc>?kBNmPV9jSjRVjoOgUaE0%uwS@&55SU zb1x+rJ$3J?SDs{oi8KWM_aaRM6v+{Qazwq%wbvDGOk5w!1L#vG^ygd^qY319&{)l= zraHwR1hWdjw1!V34fxYwbw{A?rY#r*z)qTC@FJ-zhPo4PB;|miIhz#=b?(k}Jo zCezs~9Rpd?K$yD?p9}_1k)o?R`U=$qe}w`vjWmpu!r8%?K8xZcarG1B$G<+BUpn4d;TBc$C><2WNaw|!$_b^H-lcXUN14F~H^2O#BDm7H@`Y%cf z;I%A^ot;Xvb8Tf{DhuJHa<)MCcNA`+)m2)z$lOCo96va-C-KbF%VV&@!vFd%rp^}D1OgX#Ce;= zF!#5?4Ul8->2kDAqdhwS?Z*yC4$8rrF!pqg^isEo|1Bgrabt!9=cDeisa6p)_YxaW z=63BV!yF8R%I?)kx?Ljia+X-&VlBvbT{RsCi+yDpN4vvU(ICAdfFMQD$2Ughy|9azij%wp#Id~w2(l+ugw-(Cf=DRJU{R93SW z+jr!5<$!bTi`jhS@snAir5g(SWrL#sIcDa!&DWtg zNj~#0oC~V=MKzRC0oW+02_;NhxlHpf;gw{rWuv~Aecz*)Qo4P2iI%n zI?oE38hJkD9$2Zd7+s0ml^i4qL3$%Q@^SKlDCvR*P zE9Jx@9eHmT9Y8R6Dw8JFSoUQW%u*NR?rZJ!cW>Zx7vB0>)fnj)*9bDD^nMSkja+1- zqHJ!N0Xy=Lh|N4|bM!efPJZ3vQ7NL~W74Nx2Sai+2Ffca)cq2UzVY#NQfK2886OrQUeI)p@NWJcjvh5pw~A@OBu#}|n^-Z1hfgO& zMqMP-qhZbjJ*Bql-@iRDi{T)M^~m!%uB=$7I2@Wnk*T znm}qn&MWzdn(NuHNgI0N5*Dp@=5hc?MzmlxGBQ*(a=rhw$1K@EsE6-at&Lx=-TkZ- zHTd(ILhtrxdji)tF4D4ANE7_3$rT2{3^Es%=wR6AmyLsyYUym;%4hU(N{B(?eOs=1>Q!%y{j45BGrun%7@-7G=Vp;NHG(1f zmQFFGk`c$=*lWfQYH}(XPEMFBU&m#wy;cHP&+%A7j2*T$@yWBAe(qTvEeh0Yi9}2O zpf$c2?FQ8!{-UHj*dX=o2iD8G#)n7IkA5-U5q@Ev@4n=( ztNw#xeYEnT0EV* zj>D4^yE;w`?nx4QC^!}={J*o&!+Yotb#fJmtZG@W0$fKuE88!vDuhzs-mZYwUyg#f zBz}%gJ1krA8P_+5!}UwntmfI`4XdkwY=ul6r4LlnOG2;ilJSO-mek6r~(QA4s`*v1_sQH)>`&2!JJ+Kqn)kK58a6R9b z=w`Zeos@xVk`mFRZU5SXkLVAA#enmP{r5qKYOj+FpB<_Y!~g_@QKWa|y!_+B3VTNk z20;vbi#(g@_I(3RIm^#Fq@NDr2IH1py&w*3Q8^mwx=J*x7Gn-ciGtFmIQEnTagOgnIgg5&AsY)@k^a zv>HfM1$(~-u8=)`9jxZHMcO-&W!^m&zJzx(ku{e32Vqpbj}ZJ0kjS_hYes?EsLtRO znSr;y@p?=aHdaL`&epWz-;{SS%kIml(Ya^!MXUhe{~r_J9pP(N8z zlX9$H__w1F&OjZuFPkXb>ayy>Jb#e=$Jb&V z>)MO7=T5U%LIt41^v8cmWN_yoj#ZV#Ko;Kwp__?6W2COOX49)dz&toni(+C~mmq%{ z{s-YHZEJmvhhBt0+u&^`7e}nTKoQ8Wz2Q4`ms2cjA6R@Y+iDlYQ1mGlRJsW2KmZu1 zt;?WyGv)n9)N9G>HL}YGr;-2xl|C#E)5+>MxyX7t8?@vPO~5ekU?$8v7#)#uN;?i8Y$OVKXKH9USFSnJ5VkFs``KWI{XzVYb;P_}|AXNShl zCfXYKR}?l9}$?a1x?AxjHs>w#?VB>f%QMa3$;M$BaU59Ug$bmZS6mqrLMDM(LZ`}!-xZLQTQ;@MsjuW;v z+qu?vAp=Q=!A=#v)i<~bJG6Q?WcFuZ?{12}L@^=kIII?Dkh7LRlqG+d6iBisdHQ%ySwU-9NI zK9hO~cnj}2C~g%Wv*B6)jp+d>J;C^zte8cW&bR3&JIp@?&*3)0ETlq9K-8MjKjM31 zT_yWtRT9Z{V$VjU0vWzwz;PbooHq}F34jIc2 zCYplk^ZgU9YXcE)CbQEVtKBZO(pElYVjq*YmxA@q=RZ?Zw$~p@5kgrTd_2V zk`;jYnus>hGmAqozo~hQwX5w90@+lp(B!n7&Ztf?FUgw*j%^>*cGX*Ijs07%^28uR z7p3X{(sqqSJaP=$s{5Lkn5iv_eJ+c=rh76vn}3vI3cxkMKhGTp4+ZSST#G{=ZS+vt^~)ygU6n1#VFtbYK~UQp5Mw7Q*vQH3l6TjKShw1p?5JaHnF#mmQ*6Y$rLkwlCSu$ zlq$qyP5C2R*Rb1&Jlk(BgsE);qxzR=P4f(Y5Dd4EJb_m!qBAdgBS9^wqYFLR?$mY+ zck%|^cTz>YFM&>KAYKng@Mbl|>cAf;0NZj*6wpj;@Pl;`L& z^WxU-1PRB8q?Y#DAxw>7yP!F3(pxvaqm#`S_#L6w?Z+|+6G}uK2&3D?oj>GeQ57|J z>zznPs`?qdmERdvTst~aYkd{*yeHn}JvoO`z!x_T$1|kt{dip#~9@>4}$y_<3Tdvaue5alz)T-7}2F<;ZagsuF$#!xzpHq|REUB3w_yfh!C?LNECqVN=ar+SxlHP05z76*SWDtNpy+d>!_Zwf|QMDGHz`yD9KFMx@0B0 zn$_|KCXo8E+%2^-sZqr6Yz7Q;jdKp#NiTcUIEM)2j^{@Pst4^wy^U71h{k!Vyb_~y zI>PxZJ15ir6{Mf^Y2r1~su>vPNMC%cC4QmtK`1t{cXk#spi9NnAUOlNl#MUC<%Xh7F0Yxw z+uCXdb8A+B!_;}3@3@c-h*iUWG=TqB&D*^u61@DV^0q;uBXtw{_8K_71nt^Jr1cRV ziEDUB>xdK`@bDD@stG{^mX;fGS%wtzx&GN%Bs-eu@>oZDzB38x>`+x1PgX)%`Z)lL zGI4?_SdB0RYYDDkJ%DLiJRaZ?i-1=pCsFa+cw_stbUcuwh78FZd2q^EqzTK)$i&GU?X z$+mU%j5tHC-euk7+?frI9j=~m3q{{zUVMX%{vyB3fs68kY$K|FTIMdG$NYV7B!=G` zwR8O;8^=1|(U`pTEi9PvDWCD3pr{~G<01w~%hR_r<08DnX*uJ^K8u!MazXUl*Xnkx zY7}2DveY%VsWgu^>@OUqTWfcWX?z_a+3~yILb>OgR{zu#1qOxmteug=6|v)+Cs=vej76yD|B<%i|J!1bZ>tHd6r4!Qg9F z7wE!&WLsc$wdUy}nh!W-Q4hyu`}D z(2|#oJG~=72r||U+lg}WHYCcTD{#| zjzTLOvO@E@U6pK4+hCFc7%O^;Im<_o-)G(?zYYPR4JZW#!9p$3>QG-FpZmhNse72J z94KTC+g0U9NM105Z%Dsl+J& z=xoq5B`-deb%n$Cu8(0n6d0C5^(xz!?8~*{4Vl#R%Cq12IzM(!S{Yhby)4uo1YCiCQ1e^|0sXR#DMh?5cK{aL=}j^e2&Z3 z6c_jvpnBaiR4fT(nD;T~!O|0JCi7{VYchB?mG%oc7-LmYU0?? zxN>qq9dO$tWD#b2BqFXd3wX|zXXa1c4aj_v!hpkZsWxH;-qq|t^1efF$s$K^YqE+K}mykG00S^^|CbE zD+hac;O>BkJmZ+R+)C3D={~ip7Uy2RBYg_qiC#X5_B4@dEwEJCV6@)#a5@ige6(3< z{`r3{h`v|U{3-VF(l!(M`iHwF#}7p z^US&M<@Vi+`;02-{l3f)U{Kk{l4u2DHU&Vd2V=KfOVTe*Bo;|hDzkE8aH1N4m@1zs zn6gJq!e?LP;@rrwDHW^XwFSqoeE3lI=lH(gl!J&3vZM>QN249)h4A+LKdfnu)r9we z&;G6LiTf*AR`kwi2%49$Y%gwQoTH{@SD{U54oeiKB_=%l^4r;zejA{C6l z`aQ4+A~!qNYodGO(E;ds3!fALf1?M+9?njSic2wm4KY?1PIT2jNmV0jwML9T#3&9M z)Y?e~s2-vu(lUA!Ch z@cn}@J$MR|1F`-X<8@fU=A)}?j)jl=S?-#9?tEbPVe!V`Ab$t>+}a`qTjmEEd0Z3B zpa$cjg|^RKPXM+>f?PY-L0Vq{#A!i7`q)mGJ1ix|#}rEcwPl=7AIo&J!|9zcMVn1Z zL-upKi-{JWy!uyd$JEc(H_De!<)&d0#Ul{^MC@unSY-Kpk#iR>lo5NDzOH`E zD&+=r^XyrF{+D<-2!H1f0u|RU$?J!v_0VBIot0vd@BX!efTL!=#AD6jV!r~Emb0%m zxf~B-$g|(>dhRIiNdoOLv{~?0>Z|5Mk-{mdxf*9Qne)dt06p?2{1DYPz zw$jZE_W5L!jY1RES(4C+*3)N)Jnx&Ll1G$HC&KD?llTA9@anFyw@v`#DC zi>se~22ZiPX5R(VT%CU<8G%!h9K*o(dijeoZm0)4ktz6W?OJOD|MrXWwPP2K#UpvmJYQ@yrWVR z4};x`cavA&2^BBNf?k_ldyYUd@0$>H=Z?w3w`$#tHnz+$#|)kL&+{VjiR<0WBCD(G zhpWoBll1FcittiY`6>$sfb26*)Am;aXDiy5&uQv*uY~efcJtt6lelsnOR@d0%;%vE zqNi>RHFP4iJKb~CKqQ(i5Q(O|Y&ir1!gW-EBsQ%$zI3Bs?s0^T{2DGZj>nfF%ez_k z&EWzQt6QwXyxZ98k;|la*QVh2Dtvf*fpubQhT0bg8smc>mnyUh;VKpKy&UyA`E)&I zs(s6UT1pxXAhYmsO)9>#I=^O`NS4;UiB`B{lkW!-*Mw<*nZMMM9ME=WGGYbidOm@{ zSm}2j;~#4Mo!9%9S{oVt+n|@l(zzaD_T+=^n8Iy#bQXkaai7vA&<1dG}*y=Ym^NvYP;qsHp_a-|a z;fkI29LcgOTmT;xl<*_!Z|_|KEXzu4X@5N}?M#zqhY=Q9mE&A?792Xz@Y5>ReJ~SH zo^DJ;{xi&K`*z08RPMCx#>A@(rbw~Dyn4XhP-7Ky!tTRWo1++9kun5Qu7ETv6HAl5 zw8;XJS||$%UI2FTpk4ytv*TJqg%;vbW60%Mx*-FqbCS!6sA6* zr{=(QCRC%XGMjDNQL;k%IQN&1J_T=KzC8clo`%k%o*@v7#mZ?&1*$>>PTiJPd2VOH z{!MQ@CUF9*X7)|)U3o_+Pqw%ym;WHV^_cvZ|86mzc_z~K+yoG%z=5nV%*$~TaN0%s zm^v}8M`rOyOTLYrnMgB-hg#vLv!B0OkH`@+m7=bP`T77&YBXk%4DjE9z6O`m;AmSi zrJ1AKT+n8T<6^CAahJKS{Ez#t>z9x`VQ^8R645OfGzz!dr2wY9)*q>;zpry}Y*uU) zu4LO(+)-Dl7lrG@H`|Qh{yP=$_7Ri{jScR{p=Zf?I?DB^GqiHj8%n9p1< z{ZQzer{e8Mmy<@_Wi9r8$=y<#2h%Rs2Gezyd!9SWrR468lJX`n?XvfL8$}yF?NT>G zxP3y`7G%iCId3Dk_Cjn-LCpXqiLH^48T;=aX+Ug&*s?0~u2r4uzAqDIFYFjLi<~t+621((of2+RO^^SE7{G$D01#o*b@|o>I^JY$d~L!8G#@=pGXtsn<%e> z2npL>)~Ld`ofN+BrFSxkYZ|S_j!Kq}5m(Y5FPrIY{vh!9l@vXjKq2Jqn)&(BaL;s* zPg=IReOa)#U)YN15ULUvj* zpoUIl7(EOg30N{HvU_J90PQ{PUf*JSN~0ec8)$Iaq%!b5N`C`OkVR47HqqYPYQZ3EZs~oBuH$$ zuu6!2F{9A;wGERFV~DL>kem`B9@%)7+gFIkUw(Q;=Z7<0dU?zA;8Jizpj+QQwNjj; z;%7yR-9kxu$XlnTjV`Kp{tpQE!tf=Xqqf*dK3~fs@8xBtX8A#PnH@waJ|u82hxX|o z>W960(@E+wo+wJh?Nu80J)(DAPSSZLHb&A#z`v+QAm|fHFOC-J~Pp~^XZiiwDor+r0gq6pZ0f> zi%v#aMV?28vwTznnOG1YB3@lSY;j8edhK;)4aPMR)cL9(8L8YkxC&n+O$Tcqbb&;J+IvhoY*R6EXGc0^@%vxlWEk<7aLnaZ;g9+E_~hz`aG1i5%z-hAHA98t_H|IJm4wL~n^>Q!0HFHUBrC8F0vr61QMdrfIyw>_uu)(xf|Fo0_pr~%}Y z6y0U%kuks6Dru40UVdaVnCgS?kssob{13z=ACL-{L*NLL@Dlf57c>FX5a%$mAR(Lj zZXOF<5?#t(tacEvWS?z}*?WFjf{sCZ>Q!8D^O8yB>w~|x8W3AthHV3LB?2?X1+T9p zdESoFwE;tsv7HFvu9N7uqtEG$>XW;(NW&%Hj$SEKwPQ@gvU0tTxP3>PGn{+K$GO@& zI8NOJeheK9`nF_b-W!`zAlZ8oAPMY8{=L5dCXY|N$US7gZ@mwFA}i+xaZZXqT3z49 zaT*Pv%@Jhem+N=2Jw6TO+j@(ul!E`9&MJ#V+cIAIl)?x&Dj%e=DIF7=9TC_B4#(#j zj%A{gq`{R4>dawL1OiaOiX6@buC}Y#?LaYD=sMzh;X?B5yv@>bX>ZGbmvkTXI;;E} zV!O7Qf%c?9?q!PVW{Qk=95{&FpIO>wosSFK@kQton{Oxf{vf={0PUw9D9H~rA)Pht zz4kl??TGU|slNr`afUry9ppo;0mh%KfNzFnt@CPrFfjU6H1QsPj^&)#xPnwmR16tg zBabQv?5Az8?aSZaK85WVktZq;B2?^3wKS-qf#FW*;cG(4XLjh+=HdNz--R*15-fGt z5ORcSN*}lhVKLM9PZJlJleYO4>*X8Okd{&TZ!z;i(u?26k9W<_G5jP4Lw!{ zl0s=Wi~Cjdfpv>01^7r!s(SJos+2p7xA6)j7R2tDBegL?S8o42aUJrehWp*(E?_w zuM6SDK%BN_^bo39$6G7nlrv6=i|#c%R6^6GXFar8z8wTCpFDCHr6&9; zZ;!9aaltLqEShJx@=bpbeBHRJ_UHCCBm;FVh*%OlGUlzsbTAM6Zs+&y%fKzI7*1ME zLg?5`xUFs!O^%p*flyt?#pEr)KL`n4RK3rXTHUDDrb&}KSk3WdEqp>a=PGaJcj#iM z4;CJK4Bh;=_A>wYBEw<9s&`L#G{>J?`pkdT|CWuj(lO>&$9`32_sA*3;sDNV+NJIv z1Q}V2q%1!&%WIcQM$0rEtb^EHtX}jhJ@b#uv;05`)?nB0%?H)idb(@TQjxpw@iTnV zT-Z*b^*g?F#`4(KDZ5zR@YcJdKUz?Nu zGbtx04N_}|!9G><%Y1-BiXOJ{sd3thFlQ^4(miXDY{|gV2dGxnGVWIrIsNwk{j)N~ zv|3n5D}Q+O;vFqV*{6)68Gd7rM~#^1l4lt{tel>!zZ4YHbmu4>r_#G5`jR=UeG9se z@k})KBI-W=SRpD7<)<)z<txl)t;`6o(4)`O#GP3DK%ZBwYHEN=z0$ntsJok>i)E1QY;S4?U`Th!k{kABq7&w~4X+0BkI|^hzs;E|mh1L5@Fhd+($vT~4*kvW0&*Ahff42c*iqQ>1ASyW5WA5=gX|i_;-pz8hFrDn9YW(E#i~ z=UPH`ah=$Ps-4^mWn35ex0FC@omc&PwNA;i3x;Yt`KD(BbScL22Ff~TL$G7)ExMmD zI(9K$#_P(@XYWt#-7eTIbT`D!YHjgrYx6q~WHYOqFIiV%6+bE^AwvrbqoK~dJh~H| zqK;Ifi)Ah?26C*;X}=G=*3*fz?fKZ{%w5HkcJwqrh?gXMfFvZ=3daW1O=9MxQk;=f zv#F3e>GAqDWgKy+9e0l9*f`iXBBEdMuMx;#n~%0Axrm3klRxFDevz6OITQJE#7kl1 z2ano>iJFPq$S8%LDjI0hpo%GeCniL?GnK(AVncqjyfDBLDs2>_PHSlC2cXW}oSD<3 zmqb?#;HfH^t~umYDp7S=I7^%*Z8XPDWsmdK+Cs7ddc<}Df$q#?8(4ZNmlyes)~Tle zOJ+$W_tzS!Y|wx?xU%4vVd4}>u_APVU<*CLI?teYuO#?jN zB6whm>`HsSjU#*?wD}HxN`~nvu0S}xS1#MmxobZP|OgYS#?ghreW&k2=VHf>v z38Y7o8jwC8*>$#6Jb~oH@nVY5y_%3%Bw~)7aYlQKuehfIC4ediFL55u| z4IXRx^ymSm7ilCfqe3%qb}IQA3T&4r>i%f-B@#+Afn*Xs{fAj#zUGPD!+-DMU&3}} zfupIgqdug~%|FC|2`wplm|LF#_2t){rv_8Mz(KHCBt)IB_q%Et-Zs^=pS{1ocYIX( zaU+WqHt~c^+R}P|F1IR)*MppwLp!ieRM?FA43&-TBiqVMU)d}C@!y||yK*aujx5$B ze-u?ABfA9$>xR?L8^(Pt%6^5EM03)4fYFENi!Gq#O&G3QU-xPh!kqNQ3*ffTIUBn!zJCxYXVGzP5`ISe7_5 zmBR21jRC$Iq8I}hf=vI2#^Bfe^1{ICzBuqzhZ$k(_o96cF(>y@5q|haoHOF94}~sd z_-QDxjV8?W>Z>pC7nuT0+2Bd~=Tfv~H3f0b`xS_RMT$j&5Y--{eL;gVq_Z*SQWy#o zMIq|}YZBvp)>BhPev-mSRzF&o5VJ105RE?oFEqLGFuw&Y^5Ey`?_cJuDeZIYu0G4C& zVeZNfcm+TFk%H{RJPbdMpyjI}FugBH@KeGKXuv=@iTD+`OlfZAgN6g30d3*uunW#k zv`Ceo!oUIixcm=Pg1~;zSc&kn!y;}-d?h`+&{HuE+h~c@((~f`J)!n}8}Dff@fH&h z>H;CJH}|FGZU4#euvs#!#QKrA8*`sS@RxUkm=4kU1)2zhFi#Fb$WdSC`|1bmJu(i# zpP7=up=T@z`fBg3N~{$i(ew*?zfm{|NcM`*&?3sj0iwc-z+}O}TH!G`^j!7fLrA*& zSTnGW^)J8|l$sYMUt3L&&H?*N4#zLNs-s{HWbrBBQ4TUC%C|1jB18p7bM>FxE7BT5YnuCp{?%@#rK$8B?EDY(Q zW_u)VhoFA=)$|R zdxvmDwN%))FarvEfir;#<*cLfkdIRtux)l|$;Y^%CUp+%;N`bh@8qXOKMrFLF(p%6 ayD^VJegwr2DsYM!9MkCTQ&##j^?w28-zvud literal 0 HcmV?d00001 diff --git a/init_db.py b/init_db.py new file mode 100644 index 0000000..e40b9c2 --- /dev/null +++ b/init_db.py @@ -0,0 +1,6 @@ +# init_db.py +from app import app, db + +with app.app_context(): + db.create_all() + print("Database initialized successfully.") diff --git a/static/images/bg.jpg b/static/images/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fd9a2ec9c0aae8ab00b082a54b12874287a1fd99 GIT binary patch literal 35944 zcmb@tWmFtn*RI{TySqd1;2tb^aCZytu7Lyz!Gc3@cS~?5xCD21*WeCcLH2(3e$S6@ zjPsngGSa>3uDRx#tGc^t*0m;2GfyiZRB3T3aS#|72m}WF13j&R=)~MDOhF(iDLN1= z=vig~0|&tXEqvhr^QK^7Z0KYN*sdX5ONol=D=5i}OT80&ae}b(Yyl4Zx3P70RFV)O z)zH);g_#9GfCxZXAW9I4p|O+wv(5jp0b~EYT~0km0s@UQKEKjl3oMnNs+PHn+-~&y z^B_HI|34e*dlM&PAnLb(jfW91IMDtHv@wlTlth8{1PBC+_}=FIdk_e21<=2kz1YB; zKiio7%f{Np#sUO__kPxGObnfXb|lcIG_taH2HI86y5pa|y)W&5^>sEe2K>P<06L

A0bzph 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%0Axrm3klRxFDevz6OITQJE#7kl1 z2ano>iJFPq$S8%LDjI0hpo%GeCniL?GnK(AVncqjyfDBLDs2>_PHSlC2cXW}oSD<3 zmqb?#;HfH^t~umYDp7S=I7^%*Z8XPDWsmdK+Cs7ddc<}Df$q#?8(4ZNmlyes)~Tle zOJ+$W_tzS!Y|wx?xU%4vVd4}>u_APVU<*CLI?teYuO#?jN zB6whm>`HsSjU#*?wD}HxN`~nvu0S}xS1#MmxobZP|OgYS#?ghreW&k2=VHf>v z38Y7o8jwC8*>$#6Jb~oH@nVY5y_%3%Bw~)7aYlQKuehfIC4ediFL55u| z4IXRx^ymSm7ilCfqe3%qb}IQA3T&4r>i%f-B@#+Afn*Xs{fAj#zUGPD!+-DMU&3}} zfupIgqdug~%|FC|2`wplm|LF#_2t){rv_8Mz(KHCBt)IB_q%Et-Zs^=pS{1ocYIX( zaU+WqHt~c^+R}P|F1IR)*MppwLp!ieRM?FA43&-TBiqVMU)d}C@!y||yK*aujx5$B ze-u?ABfA9$>xR?L8^(Pt%6^5EM03)4fYFENi!Gq#O&G3QU-xPh!kqNQ3*ffTIUBn!zJCxYXVGzP5`ISe7_5 zmBR21jRC$Iq8I}hf=vI2#^Bfe^1{ICzBuqzhZ$k(_o96cF(>y@5q|haoHOF94}~sd z_-QDxjV8?W>Z>pC7nuT0+2Bd~=Tfv~H3f0b`xS_RMT$j&5Y--{eL;gVq_Z*SQWy#o zMIq|}YZBvp)>BhPev-mSRzF&o5VJ105RE?oFEqLGFuw&Y^5Ey`?_cJuDeZIYu0G4C& zVeZNfcm+TFk%H{RJPbdMpyjI}FugBH@KeGKXuv=@iTD+`OlfZAgN6g30d3*uunW#k zv`Ceo!oUIixcm=Pg1~;zSc&kn!y;}-d?h`+&{HuE+h~c@((~f`J)!n}8}Dff@fH&h z>H;CJH}|FGZU4#euvs#!#QKrA8*`sS@RxUkm=4kU1)2zhFi#Fb$WdSC`|1bmJu(i# zpP7=up=T@z`fBg3N~{$i(ew*?zfm{|NcM`*&?3sj0iwc-z+}O}TH!G`^j!7fLrA*& zSTnGW^)J8|l$sYMUt3L&&H?*N4#zLNs-s{HWbrBBQ4TUC%C|1jB18p7bM>FxE7BT5YnuCp{?%@#rK$8B?EDY(Q zW_u)VhoFA=)$|R zdxvmDwN%))FarvEfir;#<*cLfkdIRtux)l|$;Y^%CUp+%;N`bh@8qXOKMrFLF(p%6 ayD^VJegwr2DsYM!9MkCTQ&##j^?w28-zvud literal 0 HcmV?d00001 diff --git a/templates/base.html b/templates/base.html index b40815c..2e2097b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,44 +1,147 @@ - - - - Todo App + + + Todo App + + + + -

-

To Do App

- -
-
- -
-
- -
- -
- - {% for todo in todo_list %} -
+
+

To Do App

+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
{% endfor %} -
- + {% endif %} + {% endwith %} + +
+ + + +
+ +
+ {% for todo in todo_list %} +
+

{{ todo.id }}. {{ todo.title }}

+ + {% if not todo.complete %} + Not Complete + {% else %} + Completed + {% endif %} + + {% if not todo.complete %} + Mark Done + {% else %} + Mark Undone + {% endif %} + + + Delete + +
+ {% else %} +

No todos yet. Add one above!

+ {% endfor %} + +
+ \ No newline at end of file diff --git a/test.html b/test.html new file mode 100644 index 0000000..191dc6c --- /dev/null +++ b/test.html @@ -0,0 +1,14 @@ + + + + + Semantic UI Test + + + +
+ +
+ + + diff --git a/test_app.py b/test_app.py new file mode 100644 index 0000000..b6a3263 --- /dev/null +++ b/test_app.py @@ -0,0 +1,116 @@ +import pytest +from app import app, db, Todo + +@pytest.fixture +def client(): + app.config["TESTING"] = True + app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" + app.config["WTF_CSRF_ENABLED"] = False + with app.test_client() as client: + with app.app_context(): + db.create_all() + yield client + +# Utility function +def create_todo(client, title="Test Todo"): + return client.post("/api/todos", json={"title": title}) + +# ---------- REST API Tests ---------- + +def test_api_create_todo_success(client): + response = create_todo(client) + assert response.status_code == 201 + +def test_api_create_todo_fail(client): + response = client.post("/api/todos", json={}) + assert response.status_code == 400 + +def test_api_get_all_todos(client): + create_todo(client, "Task 1") + response = client.get("/api/todos") + assert response.status_code == 200 + assert len(response.get_json()) >= 1 + +def test_api_get_single_todo(client): + todo_id = create_todo(client).get_json()["id"] + response = client.get(f"/api/todos/{todo_id}") + assert response.status_code == 200 + +def test_api_get_single_not_found(client): + response = client.get("/api/todos/999") + assert response.status_code == 404 + +def test_api_update_todo(client): + todo_id = create_todo(client).get_json()["id"] + response = client.put(f"/api/todos/{todo_id}", json={"title": "Updated", "complete": True}) + assert response.status_code == 200 + +def test_api_update_todo_not_found(client): + response = client.put("/api/todos/999", json={"title": "Fail"}) + assert response.status_code == 404 + +def test_api_delete_todo(client): + todo_id = create_todo(client).get_json()["id"] + response = client.delete(f"/api/todos/{todo_id}") + assert response.status_code == 200 + +def test_api_delete_todo_not_found(client): + response = client.delete("/api/todos/999") + assert response.status_code == 404 + +def test_api_mark_done(client): + todo_id = create_todo(client).get_json()["id"] + response = client.patch(f"/api/todos/{todo_id}/done") + assert response.status_code == 200 + +def test_api_mark_done_not_found(client): + response = client.patch("/api/todos/999/done") + assert response.status_code == 404 + +def test_api_toggle_done(client): + todo_id = create_todo(client).get_json()["id"] + response = client.patch(f"/api/todos/{todo_id}/toggle") + assert response.status_code == 200 + +def test_api_toggle_done_not_found(client): + response = client.patch("/api/todos/999/toggle") + assert response.status_code == 404 + +# ---------- HTML Route Tests ---------- + +def test_home_route(client): + response = client.get("/") + assert response.status_code == 200 + assert b" Date: Sat, 31 May 2025 14:57:48 +0800 Subject: [PATCH 2/6] Update README.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e2a98cb..250bc52 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ Simple Flask Todo App using SQLAlchemy and SQLite database. +Updated with full REST API feature. + +For styling, [semantic-ui](https://semantic-ui.com/) was used on the original version of the repository. +To lessen the workload of browsing into semantic-ui.com, I just made the styles inside the Base.html using CSS in this new repository. + + +### New Feature (Full REST API) +The new feature allows the Todo list to be used not just through the HTML interface, but also programmatically—via API calls. +This means other apps, JavaScript front-ends, or even mobile apps can now create, read, update, and delete todos. +It adds flexibility and modern integration potential, making the app much more powerful and scalable. -For styling [semantic-ui](https://semantic-ui.com/) is used. ### Setup Create project with virtual environment From 97b2c5a2f1af1c96e453e7633de7b8a5953be40f Mon Sep 17 00:00:00 2001 From: jiyu0903 Date: Sat, 31 May 2025 23:52:11 +0800 Subject: [PATCH 3/6] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 250bc52..da8d425 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ The new feature allows the Todo list to be used not just through the HTML interf This means other apps, JavaScript front-ends, or even mobile apps can now create, read, update, and delete todos. It adds flexibility and modern integration potential, making the app much more powerful and scalable. +I aldo added a Due Date feature for the to-do list. So that the user will be able to add the to-dos deadlines. + ### Setup Create project with virtual environment From 1072dfc5cf26bda650adb1c4fc1f296cd65fcad7 Mon Sep 17 00:00:00 2001 From: jiyu0903 Date: Sat, 31 May 2025 23:53:19 +0800 Subject: [PATCH 4/6] Add files via upload --- app.py | 286 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 154 insertions(+), 132 deletions(-) diff --git a/app.py b/app.py index fc98dfc..ebcebf3 100644 --- a/app.py +++ b/app.py @@ -1,132 +1,154 @@ -from flask import Flask, render_template, request, redirect, url_for, jsonify, flash -from flask_sqlalchemy import SQLAlchemy - -app = Flask(__name__) -app.secret_key = "your_secret_key_here" # Needed for flash messages - -# /// = relative path, //// = absolute path -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite' -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -db = SQLAlchemy(app) - - -class Todo(db.Model): - id = db.Column(db.Integer, primary_key=True) - title = db.Column(db.String(100)) - complete = db.Column(db.Boolean) - - -@app.route("/") -def home(): - todo_list = Todo.query.all() - return render_template("base.html", todo_list=todo_list) - - -@app.route("/add", methods=["POST"]) -def add(): - title = request.form.get("title") - if not title or title.strip() == "": - flash("Todo title cannot be empty.", "warning") - return redirect(url_for("home")) - new_todo = Todo(title=title.strip(), complete=False) - db.session.add(new_todo) - db.session.commit() - flash(f'Todo "{title}" added.', "success") - return redirect(url_for("home")) - - -@app.route("/toggle/") -def toggle(todo_id): - todo = Todo.query.get_or_404(todo_id) - todo.complete = not todo.complete - db.session.commit() - flash(f'Todo "{todo.title}" toggled to {"done" if todo.complete else "not done"}.', "info") - return redirect(url_for("home")) - - -@app.route("/delete/") -def delete(todo_id): - todo = Todo.query.filter_by(id=todo_id).first() - if todo: - db.session.delete(todo) - db.session.commit() - flash(f'Todo "{todo.title}" deleted.', "error") - else: - flash("Todo not found.", "error") - return redirect(url_for("home")) - - -# ----------------------- -# REST API ENDPOINTS -# ----------------------- - -# GET all todos -@app.route("/api/todos", methods=["GET"]) -def api_get_todos(): - todos = Todo.query.all() - return jsonify([{"id": t.id, "title": t.title, "complete": t.complete} for t in todos]), 200 - - -# GET a single todo -@app.route("/api/todos/", methods=["GET"]) -def api_get_single_todo(todo_id): - todo = Todo.query.get_or_404(todo_id) - return jsonify({"id": todo.id, "title": todo.title, "complete": todo.complete}), 200 - - -# CREATE a new todo -@app.route("/api/todos", methods=["POST"]) -def api_create_todo(): - data = request.get_json() - title = data.get("title") - if not title: - return jsonify({"error": "Title is required"}), 400 - - new_todo = Todo(title=title, complete=False) - db.session.add(new_todo) - db.session.commit() - return jsonify({"message": "Todo created", "id": new_todo.id}), 201 - - -# UPDATE a todo -@app.route("/api/todos/", methods=["PUT"]) -def api_update_todo(todo_id): - todo = Todo.query.get_or_404(todo_id) - data = request.get_json() - todo.title = data.get("title", todo.title) - todo.complete = data.get("complete", todo.complete) - db.session.commit() - return jsonify({"message": "Todo updated"}), 200 - - -# DELETE a todo -@app.route("/api/todos/", methods=["DELETE"]) -def api_delete_todo(todo_id): - todo = Todo.query.get_or_404(todo_id) - db.session.delete(todo) - db.session.commit() - return jsonify({"message": "Todo deleted"}), 200 - - -# MARK as done (PATCH) -@app.route("/api/todos//done", methods=["PATCH"]) -def api_mark_done(todo_id): - todo = Todo.query.get_or_404(todo_id) - todo.complete = True - db.session.commit() - return jsonify({"message": f"Todo {todo_id} marked as done"}), 200 - - -# TOGGLE done/not done (PATCH) -@app.route("/api/todos//toggle", methods=["PATCH"]) -def api_toggle_done(todo_id): - todo = Todo.query.get_or_404(todo_id) - todo.complete = not todo.complete - db.session.commit() - return jsonify({"message": f"Todo {todo_id} toggled to {'done' if todo.complete else 'not done'}"}), 200 - - -if __name__ == "__main__": - with app.app_context(): - db.create_all() - app.run(debug=True) +from flask import Flask, render_template, request, redirect, url_for, jsonify, flash +from flask_sqlalchemy import SQLAlchemy +from datetime import datetime + +app = Flask(__name__) +app.secret_key = "your_secret_key_here" + +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +db = SQLAlchemy(app) + +class Todo(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(100)) + complete = db.Column(db.Boolean) + due_date = db.Column(db.Date) + +@app.route("/") +def home(): + # ✅ Sort by due date, nulls (None) last + todo_list = Todo.query.order_by(Todo.due_date.asc().nullslast()).all() + return render_template("base.html", todo_list=todo_list) + +@app.route("/add", methods=["POST"]) +def add(): + title = request.form.get("title") + due_date_str = request.form.get("due_date") + + if not title or title.strip() == "": + flash("Todo title cannot be empty.", "warning") + return redirect(url_for("home")) + + due_date = None + if due_date_str: + try: + due_date = datetime.strptime(due_date_str, "%Y-%m-%d").date() + except ValueError: + flash("Invalid due date format.", "warning") + return redirect(url_for("home")) + + new_todo = Todo(title=title.strip(), complete=False, due_date=due_date) + db.session.add(new_todo) + db.session.commit() + flash(f'Todo "{title}" added.', "success") + return redirect(url_for("home")) + +@app.route("/toggle/") +def toggle(todo_id): + todo = Todo.query.get_or_404(todo_id) + todo.complete = not todo.complete + db.session.commit() + flash(f'Todo "{todo.title}" toggled to {"done" if todo.complete else "not done"}.', "info") + return redirect(url_for("home")) + +@app.route("/delete/") +def delete(todo_id): + todo = Todo.query.filter_by(id=todo_id).first() + if todo: + db.session.delete(todo) + db.session.commit() + flash(f'Todo "{todo.title}" deleted.', "error") + else: + flash("Todo not found.", "error") + return redirect(url_for("home")) + +# ----------------------- +# REST API ENDPOINTS +# ----------------------- + +@app.route("/api/todos", methods=["GET"]) +def api_get_todos(): + todos = Todo.query.order_by(Todo.due_date.asc().nullslast()).all() + return jsonify([ + { + "id": t.id, + "title": t.title, + "complete": t.complete, + "due_date": t.due_date.strftime("%Y-%m-%d") if t.due_date else None + } for t in todos + ]), 200 + +@app.route("/api/todos/", methods=["GET"]) +def api_get_single_todo(todo_id): + todo = Todo.query.get_or_404(todo_id) + return jsonify({ + "id": todo.id, + "title": todo.title, + "complete": todo.complete, + "due_date": todo.due_date.strftime("%Y-%m-%d") if todo.due_date else None + }), 200 + +@app.route("/api/todos", methods=["POST"]) +def api_create_todo(): + data = request.get_json() + title = data.get("title") + due_date_str = data.get("due_date") + + if not title: + return jsonify({"error": "Title is required"}), 400 + + due_date = None + if due_date_str: + try: + due_date = datetime.strptime(due_date_str, "%Y-%m-%d").date() + except ValueError: + return jsonify({"error": "Invalid due_date format. Use YYYY-MM-DD"}), 400 + + new_todo = Todo(title=title, complete=False, due_date=due_date) + db.session.add(new_todo) + db.session.commit() + return jsonify({"message": "Todo created", "id": new_todo.id}), 201 + +@app.route("/api/todos/", methods=["PUT"]) +def api_update_todo(todo_id): + todo = Todo.query.get_or_404(todo_id) + data = request.get_json() + todo.title = data.get("title", todo.title) + todo.complete = data.get("complete", todo.complete) + + due_date_str = data.get("due_date") + if due_date_str is not None: + try: + todo.due_date = datetime.strptime(due_date_str, "%Y-%m-%d").date() + except ValueError: + return jsonify({"error": "Invalid due_date format. Use YYYY-MM-DD"}), 400 + + db.session.commit() + return jsonify({"message": "Todo updated"}), 200 + +@app.route("/api/todos/", methods=["DELETE"]) +def api_delete_todo(todo_id): + todo = Todo.query.get_or_404(todo_id) + db.session.delete(todo) + db.session.commit() + return jsonify({"message": "Todo deleted"}), 200 + +@app.route("/api/todos//done", methods=["PATCH"]) +def api_mark_done(todo_id): + todo = Todo.query.get_or_404(todo_id) + todo.complete = True + db.session.commit() + return jsonify({"message": f"Todo {todo_id} marked as done"}), 200 + +@app.route("/api/todos//toggle", methods=["PATCH"]) +def api_toggle_done(todo_id): + todo = Todo.query.get_or_404(todo_id) + todo.complete = not todo.complete + db.session.commit() + return jsonify({"message": f"Todo {todo_id} toggled to {'done' if todo.complete else 'not done'}"}), 200 + +if __name__ == "__main__": + with app.app_context(): + db.create_all() + app.run(debug=True) From 8056a6908d3c5fca8a7497067b40cb2ca698ab6e Mon Sep 17 00:00:00 2001 From: jiyu0903 Date: Sat, 31 May 2025 23:54:32 +0800 Subject: [PATCH 5/6] Add files via upload --- templates/base.html | 287 ++++++++++++++++++++++---------------------- 1 file changed, 141 insertions(+), 146 deletions(-) diff --git a/templates/base.html b/templates/base.html index 2e2097b..452879f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,147 +1,142 @@ - - - - - - Todo App - - - - - - - -
-

To Do App

- - - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} - -
- - - -
- -
- - {% for todo in todo_list %} -
-

{{ todo.id }}. {{ todo.title }}

- - {% if not todo.complete %} - Not Complete - {% else %} - Completed - {% endif %} - - {% if not todo.complete %} - Mark Done - {% else %} - Mark Undone - {% endif %} - - - Delete - -
- {% else %} -

No todos yet. Add one above!

- {% endfor %} - -
- + + + + + + To-Do App + + + + + + +
+

To Do App

+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+ + +
+
+ + +
+ +
+ + +
+ + {% for todo in todo_list %} +
+

{{ todo.id }}. {{ todo.title }}

+ + {% if todo.due_date %} +

Due: {{ todo.due_date.strftime("%Y-%m-%d") }}

+ {% endif %} + + {% if not todo.complete %} + Not Complete + Mark Done + {% else %} + Completed + Mark Undone + {% endif %} + + Delete +
+ {% else %} +

No todos yet. Add one above!

+ {% endfor %} +
+ \ No newline at end of file From f3d08f55580f948a646ad7ff0b5e4a661e25a7a0 Mon Sep 17 00:00:00 2001 From: jiyu0903 Date: Sat, 31 May 2025 23:56:32 +0800 Subject: [PATCH 6/6] Add files via upload --- db.sqlite | Bin 0 -> 8192 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 db.sqlite diff --git a/db.sqlite b/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..16cc344352551a69d5121a95712077b0825ba6f3 GIT binary patch literal 8192 zcmeI#K}&={6bJB`t)&zeTT1r&_NLH=DRs_WhFUV+%z{0IrIFy~5>wEzZhgPLMEfZk z5zpP_|L~#n_c1U(Pu^Syh0N)xdR?WGJhsO;XJbAIg*lri$Ta!aK&B66E~~7nyElu4p9o6) zX&|Uh$kMeUqj{K!a}iS*B^2HU!6E6IEM%F}ogdFG{Mb5n9J@{*tJPXR literal 0 HcmV?d00001