What do people think about the get_absolute_url proposal?

16 views
Skip to first unread message

Simon Willison

unread,
Dec 7, 2009, 5:43:52 PM12/7/09
to Django developers
This made it to the 1.2 feature list:

http://code.djangoproject.com/wiki/ReplacingGetAbsoluteUrl

If we want this in 1.2, it could be as simple as merging the get_url /
get_url_path methods in to the base Model class, rolling a few unit
tests and writing some documentation. It feels like we should discuss
it a bit first though - the proposal hasn't really seen much
discussion since it was originally put together back in September last
year.

Cheers,

Simon

Rick Yazwinski

unread,
Dec 7, 2009, 10:23:06 PM12/7/09
to django-d...@googlegroups.com
I think that this may be too simplified:
protocol = getattr(settings, "PROTOCOL", "http")
domain = Site.objects.get_current().domain
port = getattr(settings, "PORT", "")

Many sites put load balancers and https hardward acceleration in front
of their web interfaces. This would obscure the protocol and port
info from Django.

At the very least we'd need something that would allow an override
table and/or manual setting...
> --
>
> You received this message because you are subscribed to the Google Groups "Django developers" group.
> To post to this group, send email to django-d...@googlegroups.com.
> To unsubscribe from this group, send email to django-develop...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
>
>
>

Rick Yazwinski

unread,
Dec 7, 2009, 10:28:46 PM12/7/09
to django-d...@googlegroups.com
BTW, generally I like this idea... :)

Dave Jeffery

unread,
Dec 8, 2009, 8:51:38 AM12/8/09
to django-d...@googlegroups.com
I brought this up during the 1.1 dev schedule and if I remember correctly the response was "let's have it painted red" and it seemed like it wasn't considered a practical problem.

The reason I think this is an issue is the same reason that I get frustrated whenever I see a pull-style handle on a door that you need to push. I understand that people will 'get it' eventually but I don't think it's acceptable to ask the user to refer to documentation (or read the 'push' label) when the function is so simple and *seems* obvious.

To me it seems like an obvious fix which has no obvious downsides. I'm assuming that get_absolute_url will continue to be available through the lifecycle of 1.x., so I can't think of a reason not to do it.

Dave


Mike Malone

unread,
Dec 8, 2009, 2:40:59 PM12/8/09
to django-d...@googlegroups.com
I'd much rather have this information come from the current request
vs. coming from settings. Relying on the Site is particularly
annoying. I like the implementation of build_absolute_uri() in
django.http.HttpRequest. The hard part is getting the request object
to a place where it's usable by models without introduction global
state.

Mike

Russell Keith-Magee

unread,
Dec 8, 2009, 10:52:24 PM12/8/09
to django-d...@googlegroups.com
On Tue, Dec 8, 2009 at 6:43 AM, Simon Willison <si...@simonwillison.net> wrote:
> This made it to the 1.2 feature list:
>
> http://code.djangoproject.com/wiki/ReplacingGetAbsoluteUrl
>
> If we want this in 1.2, it could be as simple as merging the get_url /
> get_url_path methods in to the base Model class, rolling a few unit
> tests and writing some documentation.

It could be, but I don't think it is :-)

> It feels like we should discuss
> it a bit first though - the proposal hasn't really seen much
> discussion since it was originally put together back in September last
> year.

I'm +1 to addressing this problem. Looking over the wiki and past
discussions on django-dev, I see some suggestions that the wiki
doesn't mention or discount, and I can see some additional issues that
the wiki doesn't address:

1. The wiki identifies existing usage, but doesn't address the
migration path. The admin currently has (documented) behavior for
objects that define get_absolute_url(). ABSOLUTE_URL_OVERRIDES is a
documented setting with influence over get_absolute_url. Sitemaps rely
on obj.get_absolute_url. What is the migration path for objects that
define get_absolute_url?

2. For that matter - which method (get_url or get_url_path) is used
preferentially if the user defines both?

3. There is the potential for conflicting implementations because the
methods have overlapping return content. For example:

def get_url(self):
return 'http://example.com/foo/'

def get_url_path(self):
return '/bar/'

Which one is correct? Both methods provide for the definition of the
url path. In this case, the exact order of inspection from (2) becomes
even more important.

4. I share Mike's concern about using settings.SITE_ID to determine
the current host, but I'm not sure I have any suggestions on how we
could practically use request, short of encouraging the use of a
template tag like {% obj_url %} that can use data from the request
context if it is available.

5. The most recent discussion [1] had some talk about using
urlparse.ParseResult to make it easier to use the output of get_url.
Jacob was +1 to the idea at the time, and I'm inclined to agree with
him, although it's unclear how that would affect the rest of the
proposal.

[1] http://groups.google.com/group/django-developers/browse_thread/thread/7e69c39c23ec1079

Yours,
Russ Magee %-)

Mike Malone

unread,
Dec 9, 2009, 2:37:52 PM12/9/09
to django-d...@googlegroups.com
On Tue, Dec 8, 2009 at 7:52 PM, Russell Keith-Magee
<freakb...@gmail.com> wrote:
>  4. I share Mike's concern about using settings.SITE_ID to determine
> the current host, but I'm not sure I have any suggestions on how we
> could practically use request, short of encouraging the use of a
> template tag like {% obj_url %} that can use data from the request
> context if it is available.

It's not exactly pretty, but: http://paste.pocoo.org/show/155827/

Burus

unread,
Dec 10, 2009, 5:03:06 AM12/10/09
to Django developers
+1 to get_url

Ivan Sagalaev

unread,
Dec 12, 2009, 6:58:13 PM12/12/09
to django-d...@googlegroups.com
All these are variations of the same thing: making request object
global. Do we really want it?

For one thing it breaks what Django has always got right: being able to
work in a script outside of a web request loop. So relying on
contrib.Site may be inconvenient but it's way better than a global
request. What I take from code like this:

protocol = getattr(settings, "PROTOCOL", "http")
domain = Site.objects.get_current().domain
port = getattr(settings, "PORT", "")

... is not that we should get all these from a request but that we
should add "protocol" and "port" fields to the Site model.

Mike Malone

unread,
Dec 14, 2009, 1:13:52 PM12/14/09
to django-d...@googlegroups.com
On Sat, Dec 12, 2009 at 3:58 PM, Ivan Sagalaev
<man...@softwaremaniacs.org> wrote:
> Mike Malone wrote:
>> On Tue, Dec 8, 2009 at 7:52 PM, Russell Keith-Magee
>> <freakb...@gmail.com> wrote:
>>>  4. I share Mike's concern about using settings.SITE_ID to determine
>>> the current host, but I'm not sure I have any suggestions on how we
>>> could practically use request, short of encouraging the use of a
>>> template tag like {% obj_url %} that can use data from the request
>>> context if it is available.
>>
>> It's not exactly pretty, but: http://paste.pocoo.org/show/155827/
>
> All these are variations of the same thing: making request object
> global. Do we really want it?
>
> For one thing it breaks what Django has always got right: being able to
> work in a script outside of a web request loop. So relying on
> contrib.Site may be inconvenient but it's way better than a global
> request. What I take from code like this:

Well, not really. It's making a way to generate a URL based on the
request object global. I agree that it's not ideal, but it's not the
same as just making the request object global. It'd be much easier to
patch around this, for example, than it would be to work around a
global request object.

You wouldn't have any trouble in a standalone script unless you tried
to call the get_absolute_url() function.

>     protocol = getattr(settings, "PROTOCOL", "http")
>     domain = Site.objects.get_current().domain
>     port = getattr(settings, "PORT", "")
>
> ... is not that we should get all these from a request but that we
> should add "protocol" and "port" fields to the Site model.

I will reiterate that, in practice, this is a huge pain in the ass.

Sucks that we have to choose the lesser of two evils. Maybe we could
make a RequestSite object that does it the global way and then
get_absolute_url can try to use that and fall back on Site (or vice
versa?) or something? Not sure if that's the best of both worlds or
the worst...

Mike

Ivan Sagalaev

unread,
Dec 15, 2009, 7:35:12 AM12/15/09
to django-d...@googlegroups.com
Mike Malone wrote:
> Well, not really. It's making a way to generate a URL based on the
> request object global. I agree that it's not ideal, but it's not the
> same as just making the request object global.

My main gripe is not globalness of a request object itself (I agree with
"not ideal" here) but the very idea of constructing a URL from request.

> You wouldn't have any trouble in a standalone script unless you tried
> to call the get_absolute_url() function.

But I kinda want that. Here's two more usecases where using current
request for creating URLs is broken:

- If I have an API part of the service and human-readable part of the
service on different hosts and I want to construct a reference to API
when serving user pages.
- If have several machines behind a load-balancing proxy that's not
under my control and that's not telling me its hostname I don't want to
construct URLs with internal hostnames of individual machines in cluster.

In other words there are legitimate real-world cases when "current"
requests has nothing to do the URL I want to cnstruct.

> I will reiterate that, in practice, this is a huge pain in the ass.

Can you provide an example? My experience doesn't match this. My only
minor complaint is that I sometimes forget to update default
"example.com" generated for Site model on new installation (which should
be fixed differently, IMO).

Michael Richardson

unread,
Dec 15, 2009, 3:23:22 PM12/15/09
to Django developers


On Dec 15, 4:35 am, Ivan Sagalaev <man...@softwaremaniacs.org> wrote:
> Mike Malone wrote:
> > Well, not really. It's making a way to generate a URL based on the
> > request object global. I agree that it's not ideal, but it's not the
> > same as just making the request object global.
>
> My main gripe is not globalness of a request object itself (I agree with
> "not ideal" here) but the very idea of constructing a URL from request.
>
> > You wouldn't have any trouble in a standalone script unless you tried
> > to call the get_absolute_url() function.
>
> But I kinda want that. Here's two more usecases where using current
> request for creating URLs is broken:
>
> - If I have an API part of the service and human-readable part of the
> service on different hosts and I want to construct a reference to API
> when serving user pages.
> - If have several machines behind a load-balancing proxy that's not
> under my control and that's not telling me its hostname I don't want to
> construct URLs with internal hostnames of individual machines in cluster.
>
> In other words there are legitimate real-world cases when "current"
> requests has nothing to do the URL I want to cnstruct.

With these cases (the former more so than the latter) you are creating
URLs for an entirely separate project, not your own, essentially. You
aren't creating URLs for that particular project, which is where a
request-aware URL creation resource is intensely useful.

We pass around request objects all over the place in order to get this
functionality. request.build_absolute_uri is great, but it does
require that request object and passing it around - which sucks.

I am 100% with Mike on this.

Waylan Limberg

unread,
Dec 15, 2009, 4:07:29 PM12/15/09
to django-d...@googlegroups.com
On Tue, Dec 15, 2009 at 3:23 PM, Michael Richardson
<richardson...@gmail.com> wrote:
>
>
> On Dec 15, 4:35 am, Ivan Sagalaev <man...@softwaremaniacs.org> wrote:
>> Mike Malone wrote:
>> > Well, not really. It's making a way to generate a URL based on the
>> > request object global. I agree that it's not ideal, but it's not the
>> > same as just making the request object global.
>>
>> My main gripe is not globalness of a request object itself (I agree with
>> "not ideal" here) but the very idea of constructing a URL from request.
>>
>> > You wouldn't have any trouble in a standalone script unless you tried
>> > to call the get_absolute_url() function.
>>
>> But I kinda want that. Here's two more usecases where using current
>> request for creating URLs is broken:
>>
[snip]
>
> With these cases (the former more so than the latter) you are creating
> URLs for an entirely separate project, not your own, essentially.  You
> aren't creating URLs for that particular project, which is where a
> request-aware URL creation resource is intensely useful.
>
> We pass around request objects all over the place in order to get this
> functionality.  request.build_absolute_uri is great, but it does
> require that request object and passing it around - which sucks.
>
> I am 100% with Mike on this.
>

The way i see it (which may be wrong), this is not a proposal to make
the request object global or replace/refactor the contrib.site app. In
fact, some of the use cases mentioned strike me as things that would
require overriding the default get_absolute_url method anyway. It
seems to me like everyone is arguing over things outside the scope of
this proposal.

Sure, a better solution than the current site app would be nice, but
that's not on the table right now. So given the existing framework,
and knowing you can override the default very easily, is seems that
Simon's proposal fits the bill.

I was going to suggest that the method could accept a request object
as an optional argument and fall back to using the site app if no
request is provided. But if you have the request, you can get the
domain yourself and call get_url on the model. Besides, I'm not really
sure how that would translate to use from within the template system.
But wouldn't that really come under a future proposal for a
better/replacement site app? Or should the current proposal be set
aside until that is solved first?

Personally, I'd prefer this proposal to land now and hope for a better
solution to the limitations of the site app later. That way the API is
set and I can start using it. If the stuff behind the scenes changes
later that's fine.

--
----
\X/ /-\ `/ |_ /-\ |\|
Waylan Limberg

Mike Malone

unread,
Dec 16, 2009, 2:02:14 PM12/16/09
to django-d...@googlegroups.com
> The way i see it (which may be wrong), this is not a proposal to make
> the request object global or replace/refactor the contrib.site app. In
> fact, some of the use cases mentioned strike me as things that would
> require overriding the default get_absolute_url method anyway. It
> seems to me like everyone is arguing over things outside the scope of
> this proposal.

Actually the fundamental disagreement (as far as I can tell) is over
whether the absolute URL should be built using information pulled from
various application settings (Site module, settings file, etc) or
information pulled from the currently active request.

In my opinion, using the Site module and settings files is damn
annoying. I never use the Site module, and in my experience having to
change the "FRONTEND_URL" of your app every time you push to a
different environment is tedious and a frequent source of subtle
problems. Moreover, the request information in your current request
_should_ always be correct. If someone requests a non-canonical URL
you should redirect (CNAME, 301, etc) to the canonical URL. If your
load balancer isn't sending the correct headers then the load balancer
is broken, not Django.

That said, it sounds like there are a number of special cases where it
would be useful to override these settings. So maybe the best corse of
action is to try to use the configured Site information and fall back
on "RequestSite", which uses information from the currently active
request.

Thoughts?

Mike

Alex Gaynor

unread,
Dec 16, 2009, 2:10:19 PM12/16/09
to django-d...@googlegroups.com
> --
>
> You received this message because you are subscribed to the Google Groups "Django developers" group.
> To post to this group, send email to django-d...@googlegroups.com.
> To unsubscribe from this group, send email to django-develop...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
>
>
>

I am very strongly against making the request a thread local. We have
used thread locals in a few places (urlconf and i18n are the obvious
ones), and while they don't put a smile on my face they do serve a
very narrow, well defined purpose, in places where it would often be
impossible to get the appropriate context in.

However, I think making the entire request object (or even just the
domain + SSL state) is an incredibly open ended solution that is rife
with potential for abuse.

However, I come bearing a solution!

Instead of having get_url() or whatever the method is named return a
string, have it return a URL object, specifically instantiated with
REQUEST_HOST for it's host value. Then the caller can pass this value
around and when it gets returned to the appropriate place where the
request is in scope it can interpolate it's values into the URL object
appropriately. This may necessitate adding a template filter ({{
obj.get_url|interpolate_request:request }}).

Alex

--
"I disapprove of what you say, but I will defend to the death your
right to say it." -- Voltaire
"The people's good is the highest law." -- Cicero
"Code can always be simpler than you think, but never as simple as you
want" -- Me

Mike Malone

unread,
Dec 16, 2009, 2:53:25 PM12/16/09
to django-d...@googlegroups.com
How's that different than the current situation, where we return an
absolute URL reference that can be converted into an absolute URL
using request.build_absolute_uri?

Mike

>
> Alex
>
> --
> "I disapprove of what you say, but I will defend to the death your
> right to say it." -- Voltaire
> "The people's good is the highest law." -- Cicero
> "Code can always be simpler than you think, but never as simple as you
> want" -- Me
>

Alex Gaynor

unread,
Dec 16, 2009, 2:57:50 PM12/16/09
to django-d...@googlegroups.com
It allows an object to return a URL that already has it's domain
defined (as might happen for a SaaS site with custom subdomains).

SmileyChris

unread,
Dec 16, 2009, 4:51:24 PM12/16/09
to Django developers


On Dec 17, 8:57 am, Alex Gaynor <alex.gay...@gmail.com> wrote:
> On Wed, Dec 16, 2009 at 2:53 PM, Mike Malone <mjmal...@gmail.com> wrote:
> > How's that different than the current situation, where we return an
> > absolute URL reference that can be converted into an absolute URL
> > using request.build_absolute_uri?
>
> It allows an object to return a URL that already has it's domain
> defined (as might happen for a SaaS site with custom subdomains).
>

build_absolute_uri allows that already.

>>> r = HttpRequest()
>>> r.META['SERVER_NAME'] = 'test.com'
>>> r.META['SERVER_PORT'] = '80'
>>> r.build_absolute_uri('fish')
'http://test.com/fish'
>>> r.build_absolute_uri('//fish.com/fish')
'http://fish.com/fish'
>>> r.build_absolute_uri('https://fish.com/fish')
'https://fish.com/fish'

Alex Gaynor

unread,
Dec 16, 2009, 4:53:24 PM12/16/09
to django-d...@googlegroups.com
> --
>
> You received this message because you are subscribed to the Google Groups "Django developers" group.
> To post to this group, send email to django-d...@googlegroups.com.
> To unsubscribe from this group, send email to django-develop...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
>
>
>

My mistake. I have to admit I'm rather confused about what the
discussion is at this point :/

Ivan Sagalaev

unread,
Dec 17, 2009, 8:24:36 AM12/17/09
to django-d...@googlegroups.com
Michael Richardson wrote:
> With these cases (the former more so than the latter) you are creating
> URLs for an entirely separate project, not your own, essentially.

Not "entirely separate". These are projects sharing much of the
codebase, just having different SITE_ID in settings.

Anyway there's still my first use-case of constructing URLs from
standalone scripts.

> We pass around request objects all over the place in order to get this
> functionality. request.build_absolute_uri is great, but it does
> require that request object and passing it around - which sucks.

request.build_absolute_uri works for you because you don't have those
use-cases. This is why we see this way differently: you think that
having a request universally available makes things more convenient
while in my view it doesn't fix URL construction at all[*] and just adds
a bit to the mess in thread-locals. And I kind of disagree that my
use-cases very exotic.

[*] URL construction is really broken right now because I am forced to
hard-code 'http://' or 'https://' in my script that sends mail by cron.

Ivan Sagalaev

unread,
Dec 17, 2009, 8:46:05 AM12/17/09
to django-d...@googlegroups.com
Mike Malone wrote:
> In my opinion, using the Site module and settings files is damn
> annoying.

My point is that we better fix these annoyances than leave this way of
doing things altogether.

> I never use the Site module

This is by itself is not the reason why it might be annoying. Django
actually recommends its usage in documentation and if you don't follow
it then, why, some level of annoyance is expected.

>, and in my experience having to
> change the "FRONTEND_URL" of your app every time you push to a
> different environment is tedious and a frequent source of subtle
> problems.

Indeed. This is why there's Site model. It's better than a setting
because it's per-database, not per-installation.

I agree that having to maintain a record in Site is also not ideal. But
it can be made less error-prone:

- syncdb could interactively ask for scheme/host/port instead of using
"example.com" (that would fix all *my* problems with SIte)
- correct set of sites can live in initial_data fixture

> Moreover, the request information in your current request
> _should_ always be correct.

Agreed. But it's not the whole point I'm arguing.

> That said, it sounds like there are a number of special cases where it
> would be useful to override these settings. So maybe the best corse of
> action is to try to use the configured Site information and fall back
> on "RequestSite", which uses information from the currently active
> request.

This might work, I think. Anyway I can't bash this idea with anything
from the top of my head :-). I'll think of it a bit more.

Simon Willison

unread,
Dec 21, 2009, 7:18:30 PM12/21/09
to Django developers
On Dec 8, 3:23 am, Rick Yazwinski <rick.yazwin...@gmail.com> wrote:
> I think that this may be too simplified:
>         protocol = getattr(settings, "PROTOCOL", "http")
>         domain = Site.objects.get_current().domain
>         port = getattr(settings, "PORT", "")
>
> Many sites put load balancers and https hardward acceleration in front
> of their web interfaces.  This would obscure the protocol and port
> info from Django.

Aha! I was pretty confused by this thread, since I didn't remember
writing the above code. It turns out that's the problem with making
proposals like this on a wiki...

http://code.djangoproject.com/wiki/ReplacingGetAbsoluteUrl?action=diff&version=10

My original proposal can still be seen here:

http://code.google.com/p/django-urls/source/browse/trunk/django_urls/base.py

It's basically this:

def get_url(self):
if hasattr(self.get_url_path, 'dont_recurse'):
raise NotImplemented
try:
path = self.get_url_path()
except NotImplemented:
raise
prefix = getattr(settings, 'DEFAULT_URL_PREFIX', 'http://
localhost')
return prefix + path
get_url.dont_recurse = True

So for the common case it invents a new DEFAULT_URL_PREFIX setting
which can be used to ensure get_url uses the correct domain. If you
want to use a threadlocal request for this instead that's fine - just
define your own get_url() method that does that.

Cheers,

Simon

Reply all
Reply to author
Forward
0 new messages