Webpy For Simplest Thing
Trying WebPy For Simplest Thing, coming from Pyramid For Simplest Thing.
May06'2012: Install into same VirtualEnv. Also easy_install pysqlite
.
- take copy of
mf.db
created by Django. - run
code.py
Hello World fine (May07)- also fine once tweak for template
try db section, using SQLite
- get unintelligible error
- realize I'm using wrong table name (users instead of Django-created
auth_user
), switch to using tabletasks
- same error:
<type 'exceptions.KeyError'> at /tasks
- hit SQLite command-line, sniff around. Doh! That table is named
family_task
. Tweak just the mapping of table-name, leave everything else as plaintasks
. - still get same error - looking more closely it's
KeyError: u'/tasks'
- doh! Problem was in my
urls
list! Fixed that, now works! - also map
/users
toauth_user
table - listing also works - next: figure out how to list query-results (
from family_task where parent_id is null
)- done, just use
db.select
withwhere
argument
- done, just use
Next: combine some lists into an integrated page for my family-dashboard...
- getting single-record - use
db.select()[0]
- do simple
db.select
to include multiple related lists (May08) - next: isolate base layout template bits
- first line of specific template needs to be the
$def with
, then 2nd line can be$var title:
(May13)
- first line of specific template needs to be the
Next: work in SQLite to figure out proper join to generate key list for that page
- this is going to be a self-outer-join!
- overall master tasks: {{{ SELECT id, name, description, sort_order FROM family_task WHERE master_id IS NULL AND parent_id IS NULL }}}
- status from family-specific list: {{{ SELECT id, master_id, status FROM family_task WHERE parent_id IS NULL AND family_id = $family_id }}}
- These 2 lists will intersect, but there can be exceptions on both sides. So will want a
FULL OUTER JOIN
- SQLite does not support a
FULL OUTER JOIN
! - So it looks like I need to do a UNION ALL or UNION.
- Actually, there are enough weird cases I'm thinking it makes sense to just do a couple separate queries and then use code to combine the results.
- hmm, in building up custom result-set list, discover
IterBetter instance has no attribute 'append'
. So immediately convertdb.select()
result into a list.- then when rendering an item in layout, have to use
obj["col"]
instead ofobj.col
- should I improve consistency by building a list of objects instead of a list of dictionaries? Nah.
- then when rendering an item in layout, have to use
Next: bring in BootStrap CSS, then figure out how to generate Drop Down of status
options for each goal and have changes do AJAX update.
- make a
static
folder, copy BootStrapassets
folder inside it - copy top and bottom bits of BootStrap
starter-template.html
intolayout.html
, tweak some paths to/assets
- looks a little nicer. Going to delay Drop Down plus more cosmetic stuff until after getting authentication stuff in.
Next: user authentication (Web Authentication), and maybe sessions.
- yikes, this hasn't been written yet.
- seems like a number of people are using jpscalleti's code so I'll give that a try. Of couse I still have auth-related tables in db from starting with Django For Simplest Thing. So I guess it's time to drop those tables and bring in the new ones...
- dump
mf.db
tomf.sql
, edit sql file to remove Django bits. Also removefamily_
prefix from every table (and tweak code that references those tables). - jpscaletti's schema generates some errors, so have to tweak
AUTOINCREMENT
instead ofAUTO_INCREMENT
- add
AUTOINCREMENT
to otherid
fields
- add
- remove comments
- restore using
sqlite3 mf.db < mf.sql
- stick his code lines up where they belong. Launch. (May14)
- error
name 'app' is not defined
because ofmysession = web.session.Session(app, web.session.DiskStore('sessions'))
- need the rest of the sessions stuff. Copy from here, now existing pages load fine. - trying hitting
/login
URL - get 404 (not found
) - try adding param to
settings
dict, no change. Setsettings
back to empty dict - try adding
@auth.protected()
to a page method, hit that URL. It redirects me to/login
returning 404 again. - try copying
login.html
template to my app's templates directory - no difference. - I'm not the only one who's had this issue.
- emailed jpscaletti to ask for help. Try mucking around with
pdb
but can't figure out what to do, so just going to wait...
- dump
Back to some BootStrap CSS work...
- Copy header ribbon from
fluid
example. - Copy
sidebar-nav
section fromfluid
- Use
<table class="table table-striped">
, addthead/tbody
bits - next: status Drop Down-s - ideally want to do AJAX db update when user changes the drop-down status value for any given Goal. (May15)
- Looking through Head First Jquery, p427 (p465 of the PDF).
- but going to revisit user-authentication first...
User authentication - continuing from above. (May15)
- Haven't heard from jpscaletti.
- David Montgomery, who had similar problems last year, never got them solved.
- I'm going to try a couple hacks and see if they work...
- though maybe I could a map for
DBAuth.login
inurls
butDBAuth
gets called afterweb.application(urls...)
- maybe someone smarter can make that work... - so try more-manual approach
- add
login
map tourls
to my ownlogin
class, have that render my local copy of thelogin.html
template. Result: yes, now get a nice login form. But submitting it does nothing. - smells like a slippery slope of copying and pasting huge amounts of stuff.
- add
- I guess I'll just have to start from scratch (and then steal tiny-bits-at-a-time from that code).
- I can pass an object or a dictionary to a template, but it seems like I can't use that in the base-layout template.
- When I tried passing a dictionary, then referencing
$content.user_block["user_name"]
inlayout.html
, I got error<type 'exceptions.TypeError'> at / - string indices must be integers
and found local varscontent = <TemplateResult: {'__body__': u'\n<h1>Welcome</h1>\n<p>Sorry</p>\n', 'user_block': u'{'user_name': 'Sorry'}', 'title': u'Personal Finance for Busy Parents'}>
- When I tried turning this into an object instead and referenced
$content.user.user_name
I got error<type 'exceptions.AttributeError'> at / - 'unicode' object has no attribute 'user_name'
with local vars<TemplateResult: {'__body__': u'\n<h1>Welcome</h1>\n<p>Sorry</p>\n', 'user': u'<code.User instance at 0x101e2e290>', 'title': u'Personal Finance for Busy Parents'}>
- but wiki example shows that base layout can have logic, so I don't have to be totally stupid about it.
- yes now have some toggling logic in the base layout.html (May16)
- When I tried passing a dictionary, then referencing
- so, to have the login/register logic bits in
layout.html
, it looks like (May17)- every view-generating method needs to
- be defined like
def GET(self, user_name = None):
(to set default user_name) - include {{{ if session.has_key("user_name"): user_name = session.user_name }}}
- be defined like
- the template must start with something like
$def with (form, user_name)
so it can pass that user_name to the baselayout.html
- the base
layout.html
then usescontent.user_name
- every view-generating method needs to
- or maybe I can simplify by copying this approach
- before doing all that decide to revive my use of Mercurial. Do init/add/commit of this directory. (May19)
- Move
if session.has_key("user_name")
to top level, moverender = web.template.render('templates/', base='layout', globals={'user_name': user_name})
right after that, take anydef(user_name)
out of templates, change base template to use$user_name
instead of$content.user_name
. Looks like it works! (May23)
- I can pass an object or a dictionary to a template, but it seems like I can't use that in the base-layout template.
- Playing with
register
form.- Now that I'm controlling this more directly, what should the user think of as his ID? Probably email address. (If I wanted to be fancy, I'd let a user associate multiple email/password pairs with his account, since users tend to forget what address they used...)
- realize that
dbauth
library didn't have the form/wrapper to call thecreateUser()
function, so start creating HttpPost target. - read some Jeff Atwood security notes (see OWASP page), decide to use BCrypt method from
dbauth
- made bunch of changes. Now when hit any page get
<type 'exceptions.TypeError'> at / - 'dict' object is not callable
triggered byapp = web.application(urls, globals())
- is that an issue related to setting aglobals
dictionary to be used by base layout?- hrm, like this guy, when I restarted the server the problem went away! (May24)
- except that every time I have some other little error, when I fix and then reload a page, I get that same error message and have to restart the server. Maybe something with session?
- hrm, like this guy, when I restarted the server the problem went away! (May24)
- progress - current status
- need to recheck whether I'm handling sessions right for reloading/debug mode.
- yes, I'm doing that fine. Though I was concerned about possible confusion between
web.config
used by sessions code vsconfig
used by dbauth stuff I copied, to I changed the latter toconfig_auth
. Still get that dict-not-callable error after fixing some other error.
- yes, I'm doing that fine. Though I was concerned about possible confusion between
- I know I'm not actually checking for the CSRF token.
- Urgh, first had to fix example to have form include straight param by name, not as function call.
<input type="hidden" name="csrf_token" value="$csrf_token"/>
- submits always getting rejected because
session.pop('csrf_token',None)
always gives None. - But if I take it out and just look for
session.csrf_token
then when there isn't one I get a<type 'exceptions.AttributeError'> at /register'Threaded Dict' object has no attribute 'csrf_token'
- so I definitely need a safe wrapper for that get, so I put back in thesession.pop
bit (behaving same as when it was there before) - decide to take the debug/reload bit out of the equation, so put in a
web.config.debug = False
- still getting same error!
- Urgh, first had to fix example to have form include straight param by name, not as function call.
- if try to insert dupe, get error msg - need to pass it to user
- even though (supposedly) adding user_name to session, it's not appearing anywhere. Is there a way to display the session data?
- need to recheck whether I'm handling sessions right for reloading/debug mode.
Feeling like solving too many generic problems here. Raise the idea of taking an existing sample app and adding lots of these cookbook pieces, hosted on GitHub for moving forward and sharing. (May28) So go on tangent: Extending Webpy Blog App With Cookbook Features.
- May30: hit wall with CSRF just like for my own app. Ask people for help over there.
So on this side, turn off the CSRF protection for the form. (Also have the session-with-reload stuff commented out, using debug=False instead.) Confirm can now do a register.
- May31 update: CSRF resolved - put back in.
But user_name still never showing up anywhere. Let's try something simpler: modifying the /hello/{user_name}
handler to set session.user_name
.
- playing around gets me toward thinking I'm assuming that the main body of code gets executed for every hit, but that's probably a big mistake....
- change from {{{ globals = {'user_name': user_name, 'csrf_token': csrf_token()} render = web.template.render('templates/', base='layout', globals=globals) }}}
- to {{{ render = web.template.render('templates/', base='layout', globals={'user_name': user_name, 'csrf_token': csrf_token()}) }}}
- this makes no difference
- (update: changed call again based on CSRF changes above - still no change in outcome)
- even simpler case: have a
/test
handler which outputs viareturn
without a template. Have that includesession.user_name
and it properly outputs the value I set with/hello/Bob
Jun13: integrating learnings from WebpyForSimplestThing re sessions, CSRF, etc.
Jun23: moving register/login logic forward
- hmm, user_password doesn't look like it has the salt stored in it
- Jul22: hmm even weirder it doesn't seem like the expected BCrypt function is even being called! (Something weird in the indirection being used from dbauth.) So call that
hashBcrypt()
function directly, and get an error atimport bcrypt
! So have to figure out where that is.- find bcrypt folder inside
cryptacular
directory, but it's possible I downloaded that last week - really not sure. But it's not getting found either way. - ah, looks like I got
cryptacular
with/because-of theshootout
demo app for WebPy. - clearly the dbauth code wasn't using cryptacular. So changing the stuff I stole to use cryptacular.
- find bcrypt folder inside
register
is now storing correct-looking hash value in db for user_password- and
login
is now working right. Also displaying error messages. But those messages aren't red, and it's not doing aback
to the form which would save my values. So no ideal, but ok for now. - next: back to actual app logic. (Getting this auth stuff put back into Extending Webpy Blog App With Cookbook Features will have to wait.)
- also need to work on some authorization code!
Jul24: app logic
db.select()
returns anIterBetter
object, which has nolen()
- if you just want to check where there are any rows returned or not, test
bool(rows)
- but if you want to check for some number of rows other than 0 or 1, then you need to convert with
rows = list(rows)
thennum = len(rows)
.
- if you just want to check where there are any rows returned or not, test
Jul30 - rendering some textarea/blog user-data with Mark Down, following notes here, though it looks like safemarkdown is in web.utils. Hitting a wall, [posted](https://groups.google.com/forum/?fromgroups#!topic/webpy/Qeg Ng Jy Iv7c) a question to the forum.
- update: got answer that works. You should use
$:safemarkdown(note.description)
-$foo
always escapes the value.$:foo
renders it verbatim.
Aug01: form.fill(record)
fills in current record values for an Edit form.
Aug02: by default, WebPy forms do an HttpPost back to the same page/form URL, because they have no action
specified. This becomes an issue when you want to have more than 1 form on a page, since they both end up posting to the same URL. But actually, this is just a convention, because the Form code doesn't generate the form container tag at all! So by convention people don't put any action
in the tag, so it falls back to the page URL. So you can easily manually add an action
pointing elsewhere for 1 of those forms.
(Have been working on a lot of app-domain-specific stuff, so nothing bloggable.)
Sept25: using an "ApplicationProcessor" to check for all my non-static URLs that user is logged in.
Sept28: generating/sending HtmlEmail
- make a
render_email()
variety that doesn't use the background template wrapper - remember that any links need an absolute reference
- very simple to use GMail to send the emails.
Oct12 - make forms look nice by modifying WebPy's /web/form.py
to use BootStrap styles.
Edited: | Tweet this! | Search Twitter for discussion