auth.login() from inside a component in a dialog

415 views
Skip to first unread message

weheh

unread,
Mar 22, 2012, 3:36:00 AM3/22/12
to web2py-users
I'm trying to do the modal login thing. I'm LOADing the auth login
form into a div, which is id'd as a dialog for jquery ui. Should be a
snap, right? Wrong. It's not working at all.

First, I ran into the problem I describe in another thread, which is
that I'm forced to do this:

session._auth_next = auth.settings.login_next = URL(c='user',
f='login')

because otherwise, session._auth_next will drive me to the default
index.

The other problem I have is that I can't figure out how to get the
flow of the form submission to recognize response.js and execute the
ajax call after login is completed. I've tried putting response.js
into a function in my 0_db.py file, like this:

def myonaccept(form):
response.js = ...

auth.settings.login_onaccept = [myonaccept]

but that doesn't seem to do anything.

I've also tried changing the login function to something like this:


def login():
auth.settings.captcha = None
login_form = auth.login()
if login_form.accepts(request):
response.flash = 'yo dude'
response.js = util.clean_str(
'ajax("%s",[],":eval");' % URL(c='user',
f='cb_after_login'))

return dict(login_form=login_form)

But that also doesn't work. I get tied up with a nasty ticket:
...
if login_form.accepts(request):
File "N:\web2py\gluon\sqlhtml.py", line 1267, in accepts
self.vars.id = self.table.insert(**fields)
File "N:\web2py\gluon\dal.py", line 5597, in insert
return self._db._adapter.insert(self,self._listify(fields))
File "N:\web2py\gluon\dal.py", line 914, in insert
raise e
IntegrityError: duplicate key value violates unique constraint
"auth_user_email_key"


Any help or working examples would be appreciated. On, and I have
multiple forms going on the page, so that might be an issue, too.

Anthony

unread,
Mar 22, 2012, 8:59:57 AM3/22/12
to web...@googlegroups.com
First, I ran into the problem I describe in another thread, which is
that I'm forced to do this:

session._auth_next = auth.settings.login_next = URL(c='user',
f='login')

because otherwise, session._auth_next will drive me to the default
index.

I believe auth.settings.login_next only has an effect when there is no session._auth_next (i.e., when the user goes directly to the login URL rather than getting there via an internal link or redirect). The idea is that when the user gets to the login via a redirect (i.e., trying to access a URL that requires login) or an internal link, they should be redirected back to their original page right after login. auth.settings.login_next is only intended as a default post-login redirect for other cases (I guess primarily if the user simply goes directly to the login URL itself).
 
The other problem I have is that I can't figure out how to get the
flow of the form submission to recognize response.js and execute the
ajax call after login is completed. I've tried putting response.js
into a function in my 0_db.py file, like this:

def myonaccept(form):
    response.js = ...

auth.settings.login_onaccept = [myonaccept]

The problem is that the auth.login() action does a redirect after running the onaccept callback, so I think your response.js is getting lost. You can probably solve both this problem and the session._auth_next redirect problem by having your onaccept callback itself do a redirect (to an action that then sets response.js) or even raise its own HTTP() response directly.

def login():
    auth.settings.captcha = None
    login_form = auth.login()
    if login_form.accepts(request):
        response.flash = 'yo dude'
        response.js = util.clean_str(
            'ajax("%s",[],":eval");' % URL(c='user',
f='cb_after_login')) 

That won't work because auth.login() does its own form.accepts(), and in the case of a successful login, it does a redirect, which will prevent the rest of your code from executing.

Anthony 

weheh

unread,
Mar 22, 2012, 9:14:33 AM3/22/12
to web2py-users
Hi Anthony, again, many thanks for your valuable contributions to this
group.

> I believe auth.settings.login_next only has an effect when there is no
> session._auth_next (i.e., when the user goes directly to the login URL
> rather than getting there via an internal link or redirect).

Your logic is sound, but I'm afraid this is not the case. I am
directly going to myapp/login and then getting redirected via
session._auth_next to default/index.

> > The other problem I have is that I can't figure out how to get the
> > flow of the form submission to recognize response.js and execute the
> > ajax call after login is completed. I've tried putting response.js
> > into a function in my 0_db.py file, like this:
>
> > def myonaccept(form):
> >     response.js = ...
>
> > auth.settings.login_onaccept = [myonaccept]
>
> The problem is that the auth.login() action does a redirect after running
> the onaccept callback, so I think your response.js is getting lost. You can
> probably solve both this problem and the session._auth_next redirect
> problem by having your onaccept callback itself do a redirect (to an action
> that then sets response.js) or even raise its own HTTP() response directly.
>
> def login():
>
> >     auth.settings.captcha = None
> >     login_form = auth.login()
> >     if login_form.accepts(request):
> >         response.flash = 'yo dude'
> >         response.js = util.clean_str(
> >             'ajax("%s",[],":eval");' % URL(c='user',
> > f='cb_after_login'))
>
> That won't work because auth.login() does its own form.accepts(), and in
> the case of a successful login, it does a redirect, which will prevent the
> rest of your code from executing.
>
> Anthony

I thought this is what the auth.settings.login_onaccept = [myonaccept]
was supposed to do (see my example stated earlier). That was my first
attempt as it seemed the most logical. Am I doing something wrong
there? The second attempt, which is the if login_form.accepts(...) was
an act of desperation :-o

Anthony

unread,
Mar 22, 2012, 9:33:30 AM3/22/12
to web...@googlegroups.com
> I believe auth.settings.login_next only has an effect when there is no
> session._auth_next (i.e., when the user goes directly to the login URL
> rather than getting there via an internal link or redirect).

Your logic is sound, but I'm afraid this is not the case. I am
directly going to myapp/login and then getting redirected via
session._auth_next to default/index.

Puzzling. Could be a bug.
 
I thought this is what the auth.settings.login_onaccept = [myonaccept]
was supposed to do (see my example stated earlier). That was my first
attempt as it seemed the most logical. Am I doing something wrong
there?

auth.settings.login_onaccept gets called after a successful login, but it's not the very last thing that happens in auth.login() -- after the callback, auth.login() still proceeds to do its usual redirect. The redirect starts a whole new request, so if you set response.js before the redirect, it will get lost. Here's why -- when you set response.js, it adds the JS code to a special web2py-component-command header. However, because of the redirect, that header will arrive at the browser as part of a 303 redirect response. When the browser receives a redirect response, it immediately issues the redirect request without further processing the XHR object, so the client-side web2py JS code never gets to process the web2py-component-command header. Instead, you have to set response.js *after* the redirect, or simply prevent the redirect altogether (hence my suggestion to have your callback raise an HTTP() response).

Anthony

Anthony

unread,
Mar 22, 2012, 9:38:26 AM3/22/12
to web...@googlegroups.com
When the browser receives a redirect response, it immediately issues the redirect request without further processing the XHR object, so the client-side web2py JS code never gets to process the web2py-component-command header.

Note, this is not a web2py issue -- this is how XHR works.

tsvim

unread,
Mar 22, 2012, 10:02:09 AM3/22/12
to web...@googlegroups.com
I think this actually is a bug. I haven't looked at it more, but I changed my default controller/function to home/index
When I login (regular login except for the following lines in my model) it returns me to default/index (my old controller still exists there until I finished refactoring the code).

def get_last_opened(form):
    if auth.has_membership(auth.user.last_opened):
        session.table_token = auth.user.last_opened
    else:
        redirect(URL('error','index'))

auth.settings.login_onaccept = get_last_opened

If I have more time next week and the issue is not solved, I'll take a better look at it.

pbreit

unread,
Mar 22, 2012, 2:13:20 PM3/22/12
to web...@googlegroups.com
<mini rant to others considering this type of thing>

Why waste so much time and energy overcomplicating something that works perfectly out-of-the-box? 99% of projects do not benefit from a modal login. Modal log-ins can actually be worse since they don't always work everywhere and they don't always have a unique URL where you can send people. Sure Twitter does it, but look who doesn't: Facebook, Apple, Google, Ebay, etc.

</mini rant>

weheh

unread,
Mar 22, 2012, 10:11:20 PM3/22/12
to web2py-users
@anothony: I'm not sure how to do the raise HTTP() and get it to
execute the response.js. I looked in the doc but it's pretty thin on
the subject. I'm going to look at the source to get an idea of how
web2py does it (I've always been curious about this), but in the mean
time, if you could shed some light as to how and where to do this, I
would appreciate it much. Thanks.

@pbreit: I don't know. Maybe my project is the 1% :-) It just seemed
like the logical evolution of my site to collapse a lot of pages down
to one page. My site is audio-centric, and there didn't seem to be a
good reason to stop the audio just because the user wanted to login,
which is what would have happened if I redirected to a login page
while listening to audio. So modal login seemed the right way to go.
But it definitely requires some contortions in web2py. It ain't over
yet, but when I do get it figured out, I don't think it will be all
that hard to implement.

weheh

unread,
Mar 22, 2012, 10:31:39 PM3/22/12
to web2py-users
@anthony: actually, on closer inspection, the doc does go into some
details:
http://web2py.com/books/default/chapter/29/4#HTTP-and-redirect
but I'm still experimenting with how to get it to execute a script.

weheh

unread,
Mar 22, 2012, 11:02:02 PM3/22/12
to web2py-users
Thank you Anthony. You're my hero. Wasn't so hard after all. For those
following, I did it by putting the response.js in myonaccept(form)
function and then, the last thing to do in that function is

HTTP(303,SCRIPT(response.js))

That did the trick.

@pbreit: After it's all said and done, I am more convinced than ever
that modal login is the right thing for my website, making it
considerably more user friendly.

weheh

unread,
Mar 22, 2012, 11:03:04 PM3/22/12
to web2py-users
.

Anthony

unread,
Mar 22, 2012, 11:08:51 PM3/22/12
to web...@googlegroups.com
Something like:

def myonaccept(form):
    [do stuff]
    response.js = 'some JS code'
    raise HTTP(200, response.render())

In that case, raise HTTP() will immediately return a response without proceeding through the remainder of auth.login() (thus avoiding the subsequent redirect). As long as the original request was made via an Ajax component, setting response.js before calling raise HTTP() will result in web2py adding the JS code as a response header before returning the response.

Note, response.render() can take a view argument and a context argument (i.e., a dict) if needed. HTTP() can also take arbitrary keyword arguments, which will be converted to response headers.

Anthony
 

Anthony

unread,
Mar 22, 2012, 11:12:15 PM3/22/12
to web...@googlegroups.com
Thank you Anthony. You're my hero. Wasn't so hard after all. For those
following, I did it by putting the response.js in myonaccept(form)
function and then, the last thing to do in that function is

HTTP(303,SCRIPT(response.js))

Typically, a 303 response would also set a "Location" header to tell the browser where to redirect. In this case, you probably just want to return a 200 status code (or is it supposed to redirect somewhere?).

Anthony 

weheh

unread,
Mar 22, 2012, 11:53:39 PM3/22/12
to web2py-users
No, in fact it's not going anywhere in particular. I tried 200 then
decided 303. Probably will return to 200. Thanks again.

weheh

unread,
Mar 23, 2012, 12:54:00 AM3/23/12
to web2py-users
Anthony, I tried your
raise HTTP(200, response.render())
vs. my
raise HTTP(200, SCRIPT(response.js))

I ran into a ticket complaining about my form not being defined, so
I'm back to my SCRIPT().

Traceback (most recent call last):
File "N:\web2py\gluon\restricted.py", line 204, in restricted
exec ccode in environment
File "N:\web2py\applications\myapp\views\user/login.load", line 9,
in <module>
}}
NameError: name 'login_form' is not defined

Sebastien Stormacq

unread,
Mar 23, 2012, 7:30:56 AM3/23/12
to web...@googlegroups.com
Hello Anthony,

Could you share your code for modal login ?
I am beginning with web2py and would love to learn from that example.

Thanks

Seb

weheh

unread,
Mar 23, 2012, 7:46:13 AM3/23/12
to web2py-users
Here's what I'm using:

# controller
def login():


On Mar 23, 7:30 pm, Sebastien Stormacq <sebastien.storm...@gmail.com>
wrote:
> Hello Anthony,
>
> Could you share your code for modal login ?
> I am beginning with web2py and would love to learn from that example.
>
> Thanks
>
> Seb
>
>
>
>
>
>
>
> On Friday, March 23, 2012 4:08:51 AM UTC+1, Anthony wrote:
>
> > @anthony: actually, on closer inspection, the doc does go into some
> >> details:
> >>http://web2py.com/books/​​default/chapter/29/4#HTTP-and-​​redirect<http://web2py.com/books/default/chapter/29/4#HTTP-and-redirect>

weheh

unread,
Mar 23, 2012, 7:57:10 AM3/23/12
to web2py-users
Sorry, last message got mangled somehow. Here goes again.

Sebastian, here's what I'm using:

# model
auth = Auth(db)
...
def complete_login(form):
from gluon import HTTP
# do some stuff with form as necessary
response.flash = 'You are now logged in'
response.js = 'ajax("%s",[],":eval:);' % URL(
c='user', f='cb_after_login', extension=False)
# note: Anthony does this
# raise HTTP(200, response.render())
raise HTTP(200, SCRIPT(response.js))

auth.settings.login_onaccept = [complete_login]

# controller
def login():
return dict(form=auth.login())

def cb_after_login():
return 'jQuery("#login-dialog").dialog("close");'

# view
<div id='login-dialog'>
{{=form}}
</div>

<script>
jQuery("#login-dialog").dialog({
autoOpen: false
, width: 'auto'
, height: 'auto'
, modal: true
, draggable: false
, closeOnEscape: true
});
</script>

# note: this method uses the jquery ui dialog box, so you need to
# install and load that javascript before running any of this

This has been transcribed from original code, so there may be a typo
in here. No warranty ;-) But I think it should be good to go.

Good luck. Note - you *WILL* get burned, as stated by pbreit, when you
try to access code that has been decorated by @auth.requires_login().
That's because you will be directed to your login page, which no
longer exists. So you will have to figure out what to do about that.
I'm in the process of figuring that out.

Sebastien Stormacq

unread,
Mar 23, 2012, 8:28:20 AM3/23/12
to web...@googlegroups.com
Thanks !

Anthony

unread,
Mar 23, 2012, 10:13:07 AM3/23/12
to web...@googlegroups.com
I mentioned this in another thread (https://groups.google.com/d/msg/web2py/XaHiLQHQ9X0/N8J1PqUxK6YJ), but an entirely different approach is to skip auth.login() altogether, write all the login logic yourself, and use auth.login_bare() just to check the submitted credentials (in addition to returning True or False, it also updates the session and auth.user in case of successful login).

Anthony

Anthony

unread,
Mar 23, 2012, 10:25:00 AM3/23/12
to web...@googlegroups.com
On Friday, March 23, 2012 12:54:00 AM UTC-4, weheh wrote:
Anthony, I tried your
    raise HTTP(200, response.render())
vs. my
    raise HTTP(200, SCRIPT(response.js))

I ran into a ticket complaining about my form not being defined, so
I'm back to my SCRIPT().

Well, by default response.render() renders the current view (which in this case is login.load). Instead, you can tell it to render a different view (either change  response.view, or just pass the name of the view as the first arg to response.render()). But if you don't actually need to render any view, then there's no need to include response.render() at all. In that case, just do:

raise HTTP(200)

Your response.js code will get added to the response headers, and the web2py JS code on the client will execute the response.js code when the Ajax response is received (this assumes the Ajax request was made via a web2py component). Note, when the web2py component receives the Ajax response, it replaces the component contents with the returned response, which in this case will be emtpy -- so your component will end up empty. Is that what you want? Shouldn't the response include some kind of "You are now logged in" message?

Anthony

weheh

unread,
Mar 23, 2012, 10:53:00 AM3/23/12
to web2py-users
raise HTTP(200) works great and is cleaner. Thanks.
Reply all
Reply to author
Forward
0 new messages