Opened 18 years ago

Closed 18 years ago

#672 closed enhancement (fixed)

get_absolute_url isn't nice

Reported by: ian@… Owned by: Adrian Holovaty
Component: contrib.syndication Version: dev
Severity: minor Keywords:
Cc: gary.wilson@… Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX: no

Description

now by this I mean the application should not be required to know where it is installed.
get_absolute_url forces the object to know the URL path it is coming in on.

what might be nicer would be a get_relative_url which would return the part of the URL that the app is resposible for.

so take this URL for an example
http://www.lawrence.com/news/2005/oct/15/serenity/
I'm guessing that there is a 'news' app configured here with a 'story' table inside of it.
what would be nice is if the 'story' could return '/2005/oct/15/serenity/' and have the framework 'know' that it should be prefixing that with '/news'. It could get this information out of the URL pattern couldn't it?

regards
and thanks for writing django
Ian

Change History (27)

comment:1 by eugene@…, 18 years ago

We need some consistent way to work around this problem. get_absolute_url() harms portability and reusability of Django apps. One way is to use urls.py module to do reverse translation. It is trivial in most cases --- just take the url regex and view parameters and replace () with these parameters in the same order. Complex cases can be tricky --- let's provide a way to do reverse mapping for them.

Of course simplified hadling can be used. E.g., most apps use prefixes, which can be stored and reapplied. But I favor some form of reversal.

comment:2 by hugo, 18 years ago

I don't think that get_absolute_url should run along the urlpatterns - because the urlpatterns might have several different ways to reach an object or a list of object or stuff that isn't an object at all. I think get_absolute_url should produce an URL independed of the urlpatterns (I even have a situation where get_absolute_url retuns an URL that isn't in the urlpatterns at all - because it's on a different server). So the base mechanism of get_absolute_url should stay in, I think - allowing users to do something that might be incompatible with moving apps, but might be needed.

But I think there should be some kind of app registry where appliations can hook into and request the site base url from. So that the standard case where your project defers url matching to the application via the include() mechanism will work - because in those cases the get_absolute_url can know everything about an object url, besides the base path used in the project urlpattern.

This app registry would allow you to define base paths and matching appliation urlpattern includes, just like you do now - but will internally do the registry for get_absolute_url questions. Some code how it could look like:

# this is in PROJECT/url.py
from django.conf.urls.default import include, register

urlpatterns = patterns('',
    # this just hooks up application patterns. register
    # returns the (regexpstring, urlpatterninclude) tuple
    register('/something', 'something.urls'),
)

# this is in something.urls
from django.conf.urls.default import *

urlpatterns = patterns('something.views',
    ('^object/(?<P>slug)/$', 'show'),
)

# this is in something.models.something.Something
def get_absolute_url(self):
    from django.conf.urls.default import prefix
    return '%s/%s/' % (prefix('something.urls'), self.slug)

A sample implementation of register and prefix could be:

appregistry = {}

def register(prefix, urlmodule):
    appregistry[urlmodule] = prefix
    return ('^'+prefix, include(urlmodule))

def prefix(urlmodule):
    return appregistry[urlmodule]

So this would allow urlpattern modules to hook into a global registry and store a prefix path with it's name for later inclusion in the get_absolute_url call of the object. Very simple, but it solves the main problem with moving apps between django projects without giving up on the flexibility of the django urlpattern matching. And it allows apps that need two different prefixes - the project author just needs to hook both includes in there (and the app will just have two different url modules).

comment:3 by Sune Kirkeby <sune.kirkeby@…>, 18 years ago

Good idea, hugo. And, I completely agree that trying to extract URLs from the url-configuration is a Bad Thing(tm).

But, it'd be nice if multi-level includes just worked with the registry, for example I have something along the lines of: urls.devel includes urls.admin which includes apps.yeehaw.urls.admin. Although I could probably live with manually munging around in the registry, if making that work is much trouble.

comment:4 by hugo, 18 years ago

I think hierarchical structure of the registry should be doable and would be a worthwile thing to have, as projects can be more complexe like being constructed of different subprojects that bring in a whole bunch of applications (I might envision my CMS stuff to be something like that one day).

Oh, and just to give a real world example for why register() should work with the urlpattern module name: in my gallery I have paths like /images/hugo/ and /calendar/hugo/ - it's the same object, but different structure. The /images/ structure is folder based while the /calendar/ structure is blog oriented. The user will be able to switch by setting some boolean on the picturefolder object what structure should be used. So get_absolute_url would look something like this:

def get_absolute_url(self):
    if self.is_calendar:
        return '%s/%s/' % (prefix('picturefolder.calendar_urls', self.slug)
    else:
        return '%s/%s/' % (prefix('picturefolder.images_urls', self.slug)

And the urlpatterns would be stored in either calendar_urls (for all calendar related stuff) and images_urls (for all folder related stuff).

If this gallery project would be used as a sub project, we would get exactly the above situation where hierarchical registry would be needed. Maybe this could be done with some registersubproject function like this:

import copy

projectregistry = {}
appregistry = {}

def register_subproject(prefix, urlmodule):
    global appregistry, projectregistry
    projectregistry[urlmodule] = prefix
    saved_appregistry = copy.copy(appregistry)
    res = ('^'+prefix, include(urlmodule))
    for k in appregistry.keys():
        if k not in saved_appregistry:
            projectregistry[k] = prefix
    return res

def register(prefix, urlmodule):
    global appregistry, projectregistry
    appregistry[urlmodule] = prefix
    return ('^'+prefix, include(urlmodule))

def prefix(urlmodule):
    if projectregistry.has_key(urlmodule):
        return projectregistry[urlmodule]+appregistry[urlmodule]
    else:
        return appregistry[urlmodule]

I didn't test this, it's just the idea of what to do - if you are in a project registry, just check what apps where registered new with this call (by the subsequent register() calls) and store the relations of urlmodules to their main projects in the global projectregistry. This only handles project-subproject-app, though - a more complex solution might be needed if we want that to be fully hierarchical (in those cases the registries will need to keep track of intermittent paths and modify the stored prefix based on that - should be rather simple, too, only we would need some place where to keep track - but because urlpatterns are loaded up front, they could just keep track in a global variable).

comment:5 by eugene@…, 18 years ago

It looks way too complex for simple things: registration mechanism, subprojects, extra code to write even for simple apps... Of course, you can "have several different ways to reach an object" (and so on), but how common is that? Ultimately the over-engineering is The Bad Thing (tm), which trumps a lot of other Bad Thingies. Simple things should be simple, complex things should be possible.

I still think that primitive url-derived back-translator will cover 95% of real world apps. For the rest 5% I wouldn't mind to write a method, which will do back-translation.

From your example '^object/(?<P>slug)/$' --- if your view can get the URL pattern, which was used to invoke it, it can trivially substitute (?<P>slug) with actual value, and off you go. It is easy to automate --- Django knows the regex and extracted parameters before calling views. I wish we leveraged it.

BTW, about "several different ways to reach an object...". I was under impression that the only thing you can reach is a view function. Consequently it is The Bad Thing (tm) to make objects involved in presentation layer, which is represented by views. This is what I don't like about get_absolute_url(). Request routing is an application level or even web site level functionality. Models have no business in it. Obviously it is possible to have different views for the same object (or objects) but is it an object's business to know your web site?

Ultimately it would be a simple regex, which will route requests. So far I didn't feel a need for complex regexes, which require some arcane back-translations. I can imaging that some convoluted legacy url structure may need it. Should we worry about it?

The whole idea of REST is to make this mapping as simple as possible. Do you really think we have to create some complex mechanism with registration and subprojects to cover the basics?

comment:6 by hugo, 18 years ago

Uh - for a simple case you just use what is there or just use register('prefix', 'urlmodule') - there is no code to write. The code to write would be in django.conf.urls.default, not in your application. In simple cases your urlpatterns will just look like this:

urlpatterns = patterns('',
    register('/prefix', 'project.apps.application.urls'),
)

I don't think that the simple case of an app that brings it's own url mappings can be anything simpler than that :-)

URL driven back-translators _can't_ work - my URLs are only 5% object urls, the rest is view stuff like day views, month views, calendars, admin forms, whatever. And the get_absolute_url content isn't a function of the view that happens to render the output - it's a function of the model, where an object itself decides what it's absolute url is. Only that way it is guaranteed that any object only has one absolute url. Of course your objects might be reacheable by different ways, but the object should decide how to reach it primarily. Because those URLs are what google and friends will spider and you want to make damn sure that there is only one URl for google to see for one object ;-)

Of course objects _need_ to be interwoven with the view layer at exactly one point: the get_absolute_url method. Because that one is the mediator between the object layer and the view layer (the other is the DB API, that is the mediator between the view layer and the object layer).

And yes, we should worry about "arcane url pattern" because that's one of the niceties of Django: the urlpatterns are _not_ necessarily structured by some app internal structure, but are highly flexible. There are very good reasons to keep it that way. For example with Django url patterns it is dead easy to override some urlpatterns of a standard app by just providing your own urlpattern that matches part of that other urlpattern and route it to your own view function.

An example: I port over a CMS from one django based CMS to a new one. I keep around the old app and the content in it's tables and start anew with some other app. It's dead easy to put in patterns for the archive urls of the old app with constant values for the years and months it was active and route all other archive urls to the new app. That way I have a common URL space with common format, but two different applications to provide content. URL based back-translation won't cut it here, as that would have to happen based on the same knowledge of when the switch was - but that can't be done in the root urlconf. But model-object based get_absolute_url will still work, because old-app objects still exist in their original place!

comment:7 by eugene@…, 18 years ago

Uh - for a simple case you just use what is there or just use register('prefix', 'urlmodule') - there is no code to write.

In your previous examples you had some code in applications, namely get_absolute_url(), which is simple, yet repetitive, and goes contrary to DRY principles. I say, if code repetitive and looks similar in majority of cases, let Django handle it for you.

And the get_absolute_url content isn't a function of the view that happens to render the output - it's a function of the model, where an object itself decides what it's absolute url is.

This is exactly why it is bad. I understand your 'convoluted legacy url pattern' argument but bad url schemas should be fixed, not propagated. In your specific case it makes more sense to do one-way translation from legacy urls to new urls and be done with it.

Data objects should not meddle in presentation, which can depend on many things. For starters it makes difficult to reuse apps in same web site, e.g., different categories/tags for different things. From your examples I see that data objects should know where they are used (your 1st example) and how many times they are reused (your 2nd example). Your data object should be acutely aware that I changed a url pattern and now it should use a different view. It doesn't sound right. It is wrong wrong wrong. :)

BTW, are we still talking about making reusable apps easy? I hope it is not off the agenda.

comment:8 by hugo, 18 years ago

The code I provided does exactly that: make apps easily reuseable without changing Django semantics. That's exactly what it is about.

Sure, there might be reasons why one would like to change the semantics of Django, but I don't think that's needed here - we can get easily shareable applications where the project user can just shuffle apps around in the URL space without needing to change one line of code in the applications.

If given the choice of wether I can accomplish a solution for a given problem (here: "make apps easy reuseable without the need of users to change app sources") by not changing anything in djangos semantics, I am happy to do exactly that.

comment:9 by eugene@…, 18 years ago

I think that the present semantics of get_absolute_url() in data models prevents writing truly portable apps. Your solution helps a bit without solving the problem. Probably I am missing something. Could you help me to understand your point?

def get_absolute_url(self):
    from django.conf.urls.default import prefix
    return '%s/%s/' % (prefix('something.urls'), self.slug)

As far as I can tell this object assumes that the url is exactly prefix/slug/. It cannot be prefix?object=slug&view=abbr or whatever I use on my site. Additional assumption that prefix is a constant string, which doesn't depend on other parameters, like a view mode, and so on.

def get_absolute_url(self):
    if self.is_calendar:
        return '%s/%s/' % (prefix('picturefolder.calendar_urls', self.slug)
    else:
        return '%s/%s/' % (prefix('picturefolder.images_urls', self.slug)

And this object makes all assumptions of the previous one and it assumes that it may be in a calendar or in pictures (?). What if I decided to use it in a different place? In more than 2 places? It looks to me that apps using this code are hardly reusable.

comment:10 by hugo, 18 years ago

Those places are all part of the same application. So it's no contradiction - the get_absolute_url of the picturefolder object is part of the same application as the urlpatterns. The same application should indeed know about both sides of the URL. You will reuse the _whole_ app - if your tear an application appart into different parts, you are bound to have problems.

Outside knowledge is limited to sticking a prefix before the application urlspace - that's what the register() call does. It just allows a project to use an application and put some prefix in front of it. It won't change the apps url structure as that is only known to the app, not to the project.

And inside knowledge of the outside is limited to a way to pull out that prefix that is sticked in front of the URLs - that's what the prefix() call does. The app doesn't need more information than just _where_ it is located in the projects urlspace. It already knows all about how it's own local urlspace is structured.

comment:11 by eugene@…, 18 years ago

You didn't comment on specific examples. Am I right assuming that Calendar and Pictures are one application? Really?

Let me give you an example. I want to have a gallery of pictures, which have comments, RSS/Atom feed, and links to related articles from my blog. Imagine that I have Hugo's excellent gallery app, which doesn't do comments, I have Sune's great comments, I have my own blog, and Django's RSS feed generator. Now I want to keep them all together using this pretty url schema:

  • site/pictures/slug/view/ - close up of the picture
  • site/pictures/slug/comments/ - comments
  • site/pictures/slug/rss/ - RSS feed of changes and comments
  • site/pictures/slug/articles/ - list of related articles

Can I do it now? Of course! Instead of using supplied url mappers, I'll write my own, which will call your views. (I hope customization of templates is a solved problem.) Can I combine them in one view? Yes! I can write my own wrapper view, which will call your views, and combine results in the single view any way I like. Custom templates will help me to do that. What if my web site templates require some extra parameters? No problems! I can do it too - it is a part of url module. This is what I like about Django - nice separation of concerns.

This sunny picture breaks immediately by get_absolute_url() because it is produced by objects directly. Prefixes don't help much. I can do a bolt on translation from assumed urls to my pretty urls :) using mod_rewrite or something. But using mod_rewrite defeats the purpose.

I assume that something like that was the starting point of ticket #672 ("get_absolute_url isn't nice"). :) The whole point of the ticket is to get rid of it and improve Django. That's why it is categorized as an enhancement.

comment:12 by hugo, 18 years ago

Nope, the ticket is about what the original author writes:

"what might be nicer would be a get_relative_url which would return the part of the URL that the app is resposible for."

That's exactly what the register/prefix thing does - it reworks the responsibility of the application for exactly the thing that it is responsible for, the relative part of it's own urlspace. That's the way django currently works: by using include() you pass on responsibility for url patterns that are _below_ some prefix. The only thing that my current proposal doesn't do is to make that prefix a full regexp, as it is possible with the current include() approach - but that can be easily solved.

Sure, there might be the wish to get rid of get_absolute_url and the whole urlpattern responsibility thingy, but that would be a completely different thing. Current state of affairs is that Django has a concept of urlspaces - which are bundles of urlpatterns matched for a given application. And Django has a concept of prefix patterns that tell when to send a request over to some applications urlspace. My proposal just gives a way to get from the application back to the surrounding urlspace to find out what to put in front of the application URL to produce a full absolute path.

If you stay within the current way Django structures url spaces and add something like my proposal, you can happily push applications around in your main urlspace and move them from one place to another place, without the need of changing the code in the application.

It's about that and only about that. It's not out to rescue the world, solve Fermats last theorem or do other silly things.

Of course there might be different ways to do things - but nobody showed any code that will do it. And no, just passing on the urlpattern that triggered an object wouldn't cut it: if I have a list view, there are lot's of objects on that page that all need to give out their own URL, but the urlpattern that triggered the list view tells you zilch about how the object url would be constructed. That's knowledge that is only in the application itself and the model IS PART OF THE APPLICATION. So I really don't see a problem if the application model has a method that returns a URL that's completely under control of the application - we only need to solve the "prefix from outside" problem.

But as I said, if somebody shows working code (or at least enough code fragments so that we can see what it will look like and how it might be implemented), I am open to discussion. But just repeating "get_absolute_url is bad" won't cut it.

comment:13 by hugo, 18 years ago

Oh, and the last word on this is with Jacob and Adrian, anyway :-)

comment:14 by eugene@…, 18 years ago

Nope, the ticket is about what the original author writes: "what might be nicer would be a get_relative_url which would return the part of the URL that the app is resposible for."

Okay, I'll bite. Where does it say about prefix? ;)

I suggested to go beyond get_absolute_url() and related get_relative_url(). I don't recall proposals to get rid of "the whole urlpattern responsibility thingy". I proposed to reuse "the thingy" for simplified automatic implementation. Django does have a concept of prefix patterns, which can be easily avoided, if one decided to do so. It is optional and can be used in parallel with custom url patterns. get_absolute_url() breaks it.

if I have a list view, there are lot's of objects on that page that all need to give out their own URL

Here it is: you assume that _objects_ should provide their own url. They don't. Objects are responsible for their identification. Period. Urls should be constructed by external means. In your particular example I assume it would be the list view. Different list view may create different urls. Views are called by urls, let them work with urls.

but nobody showed any code that will do it

This is an excellent technical argument, which states that any code is better than no code. :) I prefer to think before coding. That's why I like to discuss it first before investing time and efforts into making a patch. I explained why I didn't like your solution (I am sorry for that) and asked specific questions about the code. I didn't get answers. Maybe your solution fits the bill and I overlooked hidden gems but so far it is unexplained.

That's knowledge that is only in the application itself and the model IS PART OF THE APPLICATION.

Hmm. I say that if we want to combine reusable components (==applications in our case), "that's knowledge is only in the web site itself and any application is part of the web site", and it should play nice with other apps. You assume that application is totally isolated chunk, which owns its fiefdom, which is separated from all neighbors by the prefix. I think it is wrong because it doesn't give a lot of value for portable apps. If apps are not interacting, it would be more productive to use WordPress, Flickr, and other existing services instead of making new web sites.

we only need to solve the "prefix from outside" problem

...and that's exactly what I am talking about.

comment:15 by Sune Kirkeby <sune.kirkeby@…>, 18 years ago

Eugene, it sounds like you want to have reusable views, not reusable apps. The way things work now is simple, easy to unserstand, actually implemented and Good Enough(tm) (it works for LJ-World).

Also, if you want to create a franken-app there's nothing stopping you, just ignore get_absolute_url, write your own URL-space and write your own templates; this should work 99% of the time (objects doing redirects is the only thing I can think of which wouldn't.)

And, if you really want that last 1% of cases to work, you could implement a MODEL_URL_OVERRIDES setting, which would map model-classnames to get_absolute_url-override-functions. This would play well with hugo's prefix and it would not be a performance hit, since the work would be done when django.core.meta does it's magic.

comment:16 by eugene@…, 18 years ago

You lost me here. What are reusable views? Generic views?

I assume that an app consists of model (which is usually backed by db in Django), views, url mapping, optional template tags, optional templates. Imagine that you packed your app (as an egg) and gave it to me. I want to be able to use it on my web site. In order to do that you have to provide a reasonable customization.

  1. Model is the main part here. It cannot be redefined. The only thing I may want to change is the names of tables it resides in. It is a must, if I use your app in more than one places ot have a name clash with existing tables. Simple unique prefix would work for me.
  2. Your views are tailored to render your data. It doesn't make a lot of sense replacing them. In this assertion I assume that your views are doing just that - rendering your data and nothing more.
  3. I may want to change your url mapping by adding more ways to reach your views, prohibiting certain views, or redefining url mapping.
  4. Template tags are custom tailored to your model and essential to it. They are similar to views in this respect.
  5. Templates - I would definitely redefine those. :)

IMHO, out of these 5, the most serious is #3. get_absolute_url is used in a number of places inside Django, most notably in the admin. If it is used in 3rd-party views/templates/template tags it renders #3 impossible. Prefixes may be a solution but they look more like a band-aid: the proposed code had them hardcoded, which prevents reuse of the application inside one web site. Of course it prevents rewriting url schema.

Given that templates may create a link to a view, they should be aware of url mapping somehow. It means that it should be reachable through Context. Right now it is done by passing an object (which is needed anyway), which defines an infamous get_absolute_url or something like that (e.g., get_relative_url). This is the only reason to have get_absolute_url - extreme simplicity. I don't see other reasons. It makes the whole system very rigid and prompts hardcoding of urls in more then one place. It prevents customization of urls.

The only solution I see is to add extra parameters to a view, which can be passed on Context. These parameters should be optional. If parameters are not given, view, and related chain, use the defaults.

There are many ways to do it. Two possible ways are:

  • non-invasive: reusable app provides views, which can take extra parameters in url definition (like generic views). These parameters are described in app's documentation, and can be supplied by user. The views can use these parameters and propagate them to a context making them available to templates.
  • invasive: these extra parameters are bound to a request object by Django. Context (e.g., DjangoContext) can extract them from the request automatically and add them to itself.

One possible example of such parameter can be something like that (pseudo-code):

{
    # stuff
    'get_object_url': lambda info: '%s/%s/%s/%s/' % (info['site'], info['app_name'], info['module_name'], info['slug']),
    # more stuff
}

Obviously view/templates/template tags should be aware of existance and purpose of such parameters. E.g., if view uses 2 different objects, it should understand more than one parameter.

This is one more example for some fictious data_base-like set of views to demonstrate flexibility (pseudo-code):

info_dict = {
    # some stuff may go here
    
    # view defaults
    #'get_year_url':   lambda info: '%s/' % info['year'],
    #'get_month_url':  lambda info: '%s/%s/' % (info['year'], info['month']),
    #'get_day_url':    lambda info: '%s/%s/%s/' % (info['year'], info['month'], info['day']),
    #'get_object_url': lambda info: '%s/%s/%s/%s/' % (info['year'], info['month'], info['day'], info['slug']),

    # redefinition of all 4 parameters
    'get_year_url':   lambda info: '%s/' % info['year'],
    'get_month_url':  lambda info: '%s%s/' % (info['month'], info['month']),
    'get_day_url':    lambda info: '%s%s%s/' % (info['day'], info['month'], info['year']),
    'get_object_url': lambda info: 'docs/%s/' % info['slug'],
}

With these parameters all templates used by these views, can generate links between each other, and to some custom detailed view of the object.

A little bit more extravaganzas (not sure if it is actually needed):

info = {
    'year': '\d{4}',
    'month': '[a-z]{3}',
    'day': '\d{1,2}',
    'slug': '\w+',
}

This dictionary applied to get_* parameters in my previous example would produce urls I want to match. I can rewrite my example like this (pseudo-code):

info_dict = {
    'app_label': 'blog',
    'module_name': 'documents',
    'date_field': 'pub_date',
    'slug_field': 'slug',
    
    # defaults
    
    'year': '\d{4}',
    'month': '[a-z]{3}',
    'day': '\d{1,2}',
    'slug': '\w+',
    
    # defaults
    
    #'get_year_url':   lambda info: '%s/' % info['year'],
    #'get_month_url':  lambda info: '%s/%s/' % (info['year'], info['month']),
    #'get_day_url':    lambda info: '%s/%s/%s/' % (info['year'], info['month'], info['day']),
    #'get_object_url': lambda info: '%s/%s/%s/%s/' % (info['year'], info['month'], info['day'], info['slug']),

    # redefinition of 1 parameter
    'get_object_url': lambda info: 'docs/%s/' % info['slug'],
}

urlpatterns = patterns('django.views.fictional.date_based',
    (r'^/?$',               'archive_index', info_dict), 
    (object_url(info_dict), 'object_detail', info_dict),
    (day_url(info_dict),    'archive_day',   info_dict),
    (month_url(info_dict),  'archive_month', info_dict),
    (year_url(info_dict),   'archive_year',  info_dict),
)

I hope you get the idea. I think that something like this is more flexible than prefixes, more generic, and not overly complex. Obviously it doesn't come free --- it requires some cooperation from view/template/template tag writers. It may require a modification of Django to ease the pain. One possible place to extend Django without major breaking is to add one extra parameter to {{include}} function:

urlpatterns = patterns('',
    # urls

    (r'^my_app/', include('my_app.urls')),
    (r'^super_app/', include('super_app.urls', info_dict_from_above_which_redefines_urls)),

    # more urls
)

...or something like that. I hope people, who waited to see some code, can put on the critics cap and swamp me in constructive criticism. :)

comment:17 by rjwittams, 18 years ago

Eugene,
I've got to say I found your code a bit confusing, but I think I understood what you were driving at in the end.
I still don't get what you couldn't acheive with ABSOLUTE_URL_OVERRIDES. From http://www.djangoproject.com/documentation/settings/


ABSOLUTE_URL_OVERRIDES

Default: {} (Empty dictionary)

A dictionary mapping "app_label.module_name" strings to functions that take a model object and return its URL. This is a way of overriding get_absolute_url() methods on a per-installation basis. Example:

ABSOLUTE_URL_OVERRIDES = {

'blogs.blogs': lambda o: "/blogs/%s/" % o.slug,
'news.stories': lambda o: "/stories/%s/%s/" % (o.pub_year, o.slug),

}


Is it that you end up defining the url in two places?

comment:18 by ian@…, 18 years ago

The ABSOLUTE_URL_OVERRIDES are good.
but shouldn't these also set at the app level? and just removed from the model class altogether?
(why does the model need to know the URL?

comment:19 by eugene@…, 18 years ago

Sorry for confusing code --- it was a pseudo-code with lots of faults. I provided it to continue a discussion, because some people think better in code. I don't think that this is a perfect solution, but I hope we will come up with the perfect solution after discussion. :)

I think that defining the url in two (or more) places is a minor problem (but it counts!). :) The bigger problem is ABSOLUTE_URL_OVERRIDES/get_absolute_url() family provides us with 1-to-1 mapping, which has a problem spotted by Hugo: "...because the urlpatterns might have several different ways to reach an object..." It's quite possible to have several views to present an object in different context. Please read his 2nd post for a code example, where he branches in get_absolute_url() to alleviate the problem. IMHO it is not up to object to decided which representation should be used in a particular context.

I may use the same application twice (or more) in a single web site. I have two sets of url patterns, parameters, and templates, which change look and feel of a single app (e.g., a category app). The problem is it takes a lot of boiler plate code to propagate everything down to templates and template tags, which goes contrary to DRY. Additionally it is non-portable --- other writers would implement it differently, when confronted by a problem. IMHO it makes sense to do it in a less hackish more organized way by providing appropriate helpers.

The lack of url customization is not a big deal for a single web site, where all code is yours. Hugo's solution is good for it --- it does go against the principle of separation of concerns (already violated by Django with get_absolute_url()), but it is a simple and practical solution, which Just Works (tm).

Anyway the lack of url customization is a problem for:

  1. portable applications:
    • I don't want to spend time studying sources of potentially complex 3rd-party applications modifying them just to tailor their urls patterns for my needs. I want a single place of control, if possible.
  2. ongoing maintenance and enhancements:
    • If I decided to reuse an existing app, I have to hunt the code for all possible places, which define/redefine urls with get_absolute_url() or in views and template tags. ABSOLUTE_URL_OVERRIDES doesn't help in this case.

I can tell you how these two problems are solved in The Real Life (tm) now:

  1. A portable app? Copy its source and start hacking.
  2. Application reuse? Copy its source, rename appropriately, and start hacking.

Does it sound familiar? :-( There must be a better way to do it!

comment:20 by hugo, 18 years ago

see also #682 and #66

comment:21 by anonymous, 18 years ago

Summary: get_absolute_url isn't niceget_absolute_url isn't nice$

comment:22 by anonymous, 18 years ago

Summary: get_absolute_url isn't nice$get_absolute_url isn't nice

comment:23 by doyoupython-01@…, 18 years ago

This is how I'm solving the problem:

  1. Adding two parameters to $PROJECT_DIR/settings.py
PRJ_ROOT_URL = 'http://localhost:8000'
APP_ROOT_URLS = { 'polls': 'polls', 'tasks': 'tasks' }
  1. Adding $PROJECT_DIR/context_processors/processors.py
import urlparse
from django.conf.settings import PRJ_ROOT_URL, APP_ROOT_URLS
def aru(request):
    for u in APP_ROOT_URLS.itervalues():
        url, urlpath = get_app_urls( u )
        if request.path.startswith(urlpath): return { 'aru': url }
    else: return { 'aru': PRJ_ROOT_URL }
def get_app_urls( tail ):
    url = urlparse.urljoin( PRJ_ROOT_URL, tail )
    if url.endswith('/'): url = url[:-1]
    urlpath = urlparse.urlsplit(url)[2]
    return ( url, urlpath )
  1. Add the custom processor to $PROJECT_DIR/settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
    "django.core.context_processors.auth", "django.core.context_processors.debug",
    "django.core.context_processors.i18n", "myproject.context_processors.processors.aru" )
  1. In custom views, call return render_to_response() as such, import DjangoContext of course:
        return render_to_response('polls/polls_detail', {
            'object': p,
            'error_message': "You didn't make a choice." },
            context_instance = DjangoContext(request) )
  1. Generic views automatically get it for free
  1. In templates, just use {{ aru }} when you need to access the application URL
  1. Anywhere else (most likely URLConf or views, for redirects and such), use:
from myproject.context_processors.processors import get_app_urls
from django.conf.settings import APP_ROOT_URLS
myurl = get_app_urls( APP_ROOT_URL['tasks'] )[1]

or

myurl = get_app_urls( APP_ROOT_URL['tasks'] )[0]

depending on what you need.

  1. By having $PROJECT_DIR/URLConf (wherever yours may be) handle only app level urlpatterns:
from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^polls/', include('myproject.apps.polls.settings.urls.main')),
    (r'^tasks/', include('myproject.apps.tasks.settings.urls.main')),
    (r'^admin/', include('django.contrib.admin.urls.admin')) )
  1. Have $PROJECT_DIR/$APP_DIR/URLConf handle the relative urlpatterns:
from django.conf.urls.defaults import *
info_dict = { 'app_label': 'polls', 'module_name': 'polls' }
generics = patterns('',
    (r'^$', 'django.views.generic.list_detail.object_list', info_dict),
    (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
    (r'^(?P<object_id>\d+)/result/$',
     'django.views.generic.list_detail.object_detail',
     dict(info_dict, template_name='polls/polls_result')) )
poll_urls = patterns('myproject.apps.polls.views',
    (r'^(?P<poll_id>\d+)/vote/$', 'vote.vote'),
)
urlpatterns = generics + poll_urls

This is fairly similar to Hugo's idea of a registry, I think.

comment:24 by jbowtie@…, 18 years ago

Wouldn't the patch for #66 address this issue without having to change anything??

comment:25 by Luke Plant, 18 years ago

jbowtie: No, that only makes the app root available from the request object, which model instances and methods (such as get_absolute_url) don't have access to, so it only fixes a small subset of the problem.

comment:26 by anonymous, 18 years ago

Cc: gary.wilson@… added

comment:27 by Adrian Holovaty, 18 years ago

Resolution: fixed
Status: newclosed

Closing this now that we have django.core.urlresolvers.reverse() and django.db.models.permalink. Documentation is forthcoming.

Note: See TracTickets for help on using tickets.
Back to Top