Using the @login_required() decorator with #hash url's

703 views
Skip to first unread message

Jumpfroggy

unread,
Jul 16, 2009, 10:58:15 AM7/16/09
to Django users
Currently, the @login_required() decorator does not preserve any #hash
part of the URL. So if I go to /page#section1 without being logged
in, I'm redirected to /login?next=/page#section1. After logging in,
I'm redirected to /page (without the hash part).

I read in another thread here that the hash is not sent as part of the
request headers, so the django server doesn't have this information.
However, I still need to be able to preserve the hash part of URL's
when using the login feature, since the hash can alter the page in
AJAX apps.

My question: does anyone have any ideas how to go about implementing
this? My first thought is to modify the @login_required() decorator,
so that it spits out some javascript to 1) capture the hash of the url
and then 2) redirect to the login page. This may break the back
button in browsers.

Does anyone else have ideas of how to do something like this? Thanks!

Javier Guerra

unread,
Jul 16, 2009, 11:05:52 AM7/16/09
to django...@googlegroups.com
On Thu, Jul 16, 2009 at 2:58 PM, Jumpfroggy<rocket...@gmail.com> wrote:
>
> Currently, the @login_required() decorator does not preserve any #hash
> part of the URL.  So if I go to /page#section1 without being logged
> in, I'm redirected to /login?next=/page#section1.  After logging in,
> I'm redirected to /page (without the hash part).

> Does anyone else have ideas of how to do something like this?  Thanks!

maybe urlencode()'ing the '/page#section1' parameter?

--
Javier

Jumpfroggy

unread,
Jul 16, 2009, 11:18:00 AM7/16/09
to Django users
On Jul 16, 11:05 am, Javier Guerra <jav...@guerrag.com> wrote:
> maybe urlencode()'ing the '/page#section1' parameter?

The first problem is that the django server only receives the '/page'
part of the URL. The browser itself holds onto the '#hash' part and
doesn't transmit that to the django server at all, so the
login_required() decorator calls request.get_full_path() and gets '/
page', so that's what it uses. It doesn't look like there's a simple
way to get the '#hash' from django.

Once I get it, yes I can use urlencode(full_path_with_hash) and use
that instead.

Michael

unread,
Jul 16, 2009, 11:51:52 AM7/16/09
to django...@googlegroups.com
There is a better way to get around this, although not perfect. As long as the posts and gets have the hash (fragment identifier or named anchor), the browser will carry the information through the redirects. Where it is currently lost is with the post to login. So all you need to do is put the hash on the post url, then when the user is redirected it remains. You can do this with a pretty basic javascript on the form:

form.action = form.action + location.hash 

// I think that works across all browsers, but I am being lazy and not looking

Then when a user submits the post and the browser gets a redirect, the fragment identifier or named anchor stays with the browser. This information isn't transmitted to the server in anyway so short of what you described above, Django can't really solve your issue. 

I hope that helps,

Michael

Javier Guerra

unread,
Jul 16, 2009, 12:00:16 PM7/16/09
to django...@googlegroups.com
On Thu, Jul 16, 2009 at 3:51 PM, Michael<newma...@gmail.com> wrote:
> This information isn't transmitted to the server in anyway so short of what
> you described above, Django can't really solve your issue.

but where is it getting lost? i mean, what does the html looks like?
does it have the #section1 in the action attribute? i imagine three
answers:

1: no #section1 in the HTML => Django is losing it, should be fixed

2: #section1 present, but not urlencoded => Django should encode it,
since it's a parameter for the view wrapper.

3: #section1 present and urlencoded => browser's fault, should be
worked around with JavaScript

--
Javier

Tom Evans

unread,
Jul 16, 2009, 12:08:11 PM7/16/09
to django...@googlegroups.com
On Thu, 2009-07-16 at 16:00 +0000, Javier Guerra wrote:
> On Thu, Jul 16, 2009 at 3:51 PM, Michael<newma...@gmail.com> wrote:
> > This information isn't transmitted to the server in anyway so short of what
> > you described above, Django can't really solve your issue.
>
> but where is it getting lost? i mean, what does the html looks like?
> does it have the #section1 in the action attribute? i imagine three
> answers:
>
> 1: no #section1 in the HTML => Django is losing it, should be fixed

Re-read the thread. The #part of the URL is not something ever
transmitted beyond the browser. No web application ever sees the #part
of a URI.

>
> 2: #section1 present, but not urlencoded => Django should encode it,
> since it's a parameter for the view wrapper.
>
> 3: #section1 present and urlencoded => browser's fault, should be
> worked around with JavaScript
>

Cheers

Tom

Michael

unread,
Jul 16, 2009, 12:12:37 PM7/16/09
to django...@googlegroups.com
The fragment identifier is not transmitted to the server from the browser:


GET / HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache


Check your server logs if you don't believe me. The browser carries the fragment identifier through redirects, so if it is passed through the form's post it will appear in the last redirect with the JS I mentioned earlier.

Javier Guerra

unread,
Jul 16, 2009, 12:13:13 PM7/16/09
to django...@googlegroups.com
On Thu, Jul 16, 2009 at 4:08 PM, Tom Evans<teva...@googlemail.com> wrote:
>> 1: no #section1 in the HTML => Django is losing it, should be fixed
>
> Re-read the thread. The #part of the URL is not something ever
> transmitted beyond the browser. No web application ever sees the #part
> of a URI.

as i understand it, the #part doesn't get _back_ to the server; but is
it getting to the browser in the first place? not in the 'current
url', but in the 'action' attribute of the form, (or 'href' of a
link?)

--
Javier

Tim Chase

unread,
Jul 16, 2009, 12:16:23 PM7/16/09
to django...@googlegroups.com
> The first problem is that the django server only receives the '/page'
> part of the URL. The browser itself holds onto the '#hash' part and
> doesn't transmit that to the django server at all, so the
> login_required() decorator calls request.get_full_path() and gets '/
> page', so that's what it uses. It doesn't look like there's a simple
> way to get the '#hash' from django.

The only way I know around this involves leaning on JavaScript to
get the URL, split off the #hash bit from it, and sneak it in as
a hidden element on the login form. It breaks for those of us
that use the Firefox NoScript plugin or browse from a
non-JS-enabled browser (such as Lynx or Dillo which I use
regularly) but it merely provides a convenience the user is
opting to forgo.

So if you execute something like

function snag_loc() {
var loc = window.location.href;
var bits = loc.split('#');
var fragment = "";
if (bits.length > 1) {
fragment = bits[1];
}
document.forms['myloginform'
].elements['hidden_fragment_input'
].value = fragment;
}

on loading, it will set the value of your "hidden_fragment_input"
field to the fragment it found in the URL, which will then make
it available to the login POST.

-tim


Jumpfroggy

unread,
Jul 29, 2009, 12:29:29 AM7/29/09
to Django users
> The only way I know around this involves leaning on JavaScript to
> get the URL, split off the #hash bit from it, and sneak it in as
> a hidden element on the login form.

Yeah, I ended up doing this on the /login page. Basically:

<script>
$(function() {
$('#url-hash').attr('value', window.location.hash);
});
</script>

<form action="..." method="post">
<input type="hidden" id="url-hash" name="url-hash" value="" />
...
</form>

This works for firefox. The bigger problem? Internet Explorer does
not preserve the hash when following redirects!!! So in this case,
you have *no opportunity* to save the hash via javascript on the login
page. So instead of sending the 'location' header to redirect, you'd
have to send something like this HTML page:

<script>
window.location = '/login?next=' + escape(window.location);
</script>

Besides being totally annoying and hackish, it breaks the back
button. You can't save the hash with javascript on the login form
with IE, because to get to the login form you had to use a redirect,
and when you use a redirect the hash is lost... this means the login
page never sees the hash in any way when using a redirect. It's
amazing that something seemingly simple and fundamental ends up being
such a hack.

So besides my horrible back-button-breaking hack, does anyone else see
a way to do this?
Reply all
Reply to author
Forward
0 new messages