Get a fully-qualified URL for the current Django site

NOTE: This post is out of date and I’m not sure it was a good solution to begin with, but I’ll leave it here FWIW. –DCS, 18 Nov 2011

You need to generate a fully-qualified URL to a Django page, in particular outside of a web request context (in which you would have access to server variables), such as an automated process that generates e-mail with links.  You may be able to generate a root-relative URL from a reverse lookup; there’s also get_absolute_url() of course, but it’s provided on a per-model basis, and in any case shouldn’t be coupled with URL elements such as protocol and host name.  You can get the domain part of the host name from the current site object, but Django currently (as of version 1.0.2) provides no means for reliably generating a fully-qualified URL (including protocol and port) outside of a web request context.  In the function current_site_url(), below, I have used two custom settings, MY_SITE_PROTOCOL and MY_SITE_PORT.  (My current practice is to prefix custom settings with MY_,  place them in a parallel module in the project called my_settings.py, and import the custom settings into the project settings module.)

def current_site_url():
    """Returns fully qualified URL (no trailing slash) for the current site."""
    from django.contrib.sites.models import Site
    current_site = Site.objects.get_current()
    protocol = getattr(settings, 'MY_SITE_PROTOCOL', 'http')
    port     = getattr(settings, 'MY_SITE_PORT', '')
    url = '%s://%s' % (protocol, current_site.domain)
    if port:
        url += ':%s' % port
    return url

Now, I still don’t really have enough information to construct a fully-qualified URL for the most general case, because in taking advantage of the django.root setting, my code no longer “knows” what Django’s root path is.  That was good for decoupling the URLconf from the web server conf, but again, I need to generate fully-qualified URLs outside of a web request context, so I don’t have access the django.root setting.  My solution has been to add another custom setting, MY_DJANGO_URL_PATH, which corresponds to the django.root setting (a comment Django’s mod_python handler module indicates that the handler must be called before importing any settings in order for os.environ to be set up correctly with respect to settings).  With that, I can get my Django root URL with this function:

def django_root_url(fq=False):
    """Returns base URL (no trailing slash) for the current project.

    Setting fq parameter to a true value will prepend the base URL
    of the current site to create a fully qualified URL.

    The name django_root_url is used in favor of alternatives
    (such as project_url) because it corresponds to the mod_python
    PythonOption django.root setting used in Apache.
    """
    url = getattr(settings, 'MY_DJANGO_URL_PATH', '')
    if fq:
        url = current_site_url() + url
    return url

With these functions and Django’s reverse URL lookup, I can construct fully-qualified URLs.

Advertisements

, ,

  1. #1 by Graham Dumpleton on February 25, 2009 - 3:59 am

    If you used mod_wsgi then you could just get all the information from the WSGI environment and use the documented code in the WSGI PEP for reconstructing the URL. See:

    http://www.python.org/dev/peps/pep-0333/#url-reconstruction

  2. #2 by David Chandek-Stark on February 25, 2009 - 10:18 am

    The problem is constructing a URL without respect to a web request, so mod_wsgi is no more helpful than mod_python in that regard. The thing is, Django already has almost everything needed — between the URLconf and reverse() and the current_site from the sites framework — except protocol and port. That information you can obviously get from a web request, but not when using the API directly.

  3. #3 by name on July 29, 2009 - 3:36 am

    Good Job,

  4. #4 by Letailer on August 6, 2009 - 4:13 pm

    If you’re in method, which handles request, you can use request.get_host() – it returns domain+port (no protocol)

  5. #5 by Andrew Rowls on October 31, 2010 - 9:07 pm

    An old post, but apparently the problem still exists in 1.2.

    If you have access to a request object, you’re set: just use request.build_absolute_uri(‘/absolute/path/on/site/’), and it’ll fill on all the blanks concerning scheme (http/s), domain, and port.

    If you want to depend on the sites app, you can get the domain and port, but only if the user has set it up properly (and the scheme is still in question).

    While it’s trivial to require the user to set up the sites app for your app to work properly, I’d much rather not have to depend on it, but afaik django doesn’t yet make it possible to get the necessary information outside of a request context or without dependency on the sites app.

    Suggestions and insights on the subject are appreciated.

    ~ eternicode
    @eternicode

  6. #6 by IN.MG Domain on January 9, 2011 - 12:55 pm

    Now, I still don’t really have enough

  7. #7 by Rolo on September 28, 2011 - 9:55 am

    Hi. Also worth noting that certain requests, eg, HttpResponseRedirect, or those behind multiple proxies, won’t have the headers for get_host() to work. ( https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.get_host )