This middleware routes requests to specific hosts to different URL schemes ("hostconf").
For example, if you own example.com
but want to serve specific content
at api.example.com
and beta.example.com
, add the following to your
hosts.py
:
from django_hosts import patterns, host host_patterns = patterns('path.to', host(r'api', 'api.urls', name='api'), host(r'beta', 'beta.urls', name='beta'), )
This causes requests to {api,beta}.example.com
to be routed to their
corresponding URLconf. You can use your urls.py
as a template for these
URLconfs.
Patterns are evaluated in order. If no pattern matches, the request is
processed in the usual way, ie. using settings.ROOT_URLCONF
.
Install the app with your favorite package manager, e.g.:
pip install django-hosts
Add
'django_hosts'
to yourINSTALLED_APPS
setting.Add
'django_hosts.middleware.HostsMiddleware'
to yourMIDDLEWARE_CLASSES
setting.Create a module containing your default host patterns, e.g. in the
hosts.py
file next to yoururls.py
.Set the
ROOT_HOSTCONF
setting to the dotted Python import path of the module containing your default host patterns, e.g.:ROOT_HOSTCONF = 'mysite.hosts'
Set the
DEFAULT_HOST
setting to the name of the host pattern you want to refer to as the default pattern. It'll be used if no other pattern matches or you don't give a name to thehost_url
template tag (see below).
The patterns on the left-hand side are regular expressions. For example,
the following settings.ROOT_HOSTCONF
will route foo.example.com
and bar.example.com
to the same URLconf.
from django_hosts import patterns, host host_patterns = patterns('', host(r'(foo|bar)', 'path.to.urls', name='foo-or-bar'), )
Patterns being regular expressions allows setups to feature dynamic (or "wildcard") host schemes:
from django.conf import settings from django_hosts import patterns, host host_patterns = patterns('', host(r'www', settings.ROOT_URLCONF, name='www'), host(r'(\w+)', 'path.to.custom_urls', name='wildcard'), )
Here, requests to www.example.com
will be routed as normal but a
request to lamby.example.com
is routed to path.to.custom_urls
.
As patterns are matched in order, we placed www
first as it otherwise
would have matched against \w+
and thus routed to the wrong
destination.
Alternatively, we could have used negative lookahead:
from django_hosts import patterns, host host_patterns = patterns('', host(r'(?!www)\w+', 'path.to.custom_urls', name='wildcard'), )
The previous section outlined using regular expressions to implement dynamic hosts.
However, inside every view referenced by the target URLconf we would have
to parse the host from request.get_host()
and lookup its corresponding
object instance, violating DRY. If these dynamic hosts had a lot of views
this would become particularly unwieldy.
To remedy this, you can optionally specify a callback method to be called if your host matches:
from django.conf import settings from django_hosts import patterns, host host_patterns = patterns('', host(r'www', settings.ROOT_URLCONF, name='www'), host(r'(?P<username>\w+)', 'path.to.custom_urls', callback='path.to.custom_fn', name='with-callback'), ) [..] from django.shortcuts import get_object_or_404 from django.contrib.auth.models import User def custom_fn(request, username): request.viewing_user = get_object_or_404(User, username=username)
This example avoids the duplicated work in every view by attaching a
viewing_user
instance to the request object. Views referenced by the
"dynamic" URLconf can now assume that this object exists.
The custom method is called with the request
object and any named
captured arguments, similar to regular Django url processing.
Callbacks may return either None
or an HttpResponse
object. If it
returns None
, the request continues to be processed and the appropriate
view is eventually called. If a callback returns an HttpResponse
object, that HttpResponse
is returned to the client without any further
processing.
Now if you want to actually refer to the full URLs in your templates
you can use the included host_url
template tag. So imagine having a
host pattern of:
from django.conf import settings from django_hosts import patterns, host host_patterns = patterns('', host(r'admin', settings.ROOT_URLCONF, name='our-admin'), )
and a ROOT_URLCONF
of:
from django.conf.urls.defaults import patterns, url urlpatterns = patterns('mysite.admin', url(r'^dashboard/$', 'dashboard', name='dashboard'), )
then this example will create a link to the admin dashboard:
{% load hosts %} <a href="{% host_url dashboard on our-admin %}">Admin dashboard</a>
which will be rendered as:
<a href="//admin/dashboard/">Admin dashboard</a>
Note
The double slash at the beginning of the href is an easy way
to not have to worry about which scheme (http or https) is used.
Your browser will automatically choose the currently used scheme.
If you're on https://mysite.com/
a link with an href
of //mysite.com/about/
would actually point to
https://mysite.com/about/
. For more information see the
The protocol-relative URL article by Paul Irish or the
appropriate section in RFC 3986.
In case you want to append a default domain name to the domain part of
the rendered URL you can simply set the PARENT_HOST
, e.g:
PARENT_HOST = 'example.com'
This would render the link above as:
<a href="//admin.example.com/dashboard/">Admin dashboard</a>
Alternatively -- in case you don't want to append this parent domain to all URLs you can also spell out the domain in the host pattern:
from django.conf import settings from django_hosts import patterns, host host_patterns = patterns('', host(r'admin\.example\.com', settings.ROOT_URLCONF, name='admin'), )
If your host pattern contains an argument (or key argument), like:
from django.conf import settings from django_hosts import patterns, host host_patterns = patterns('', host(r'www', settings.ROOT_URLCONF, name='homepage'), host(r'(\w+)', 'path.to.support_urls', name='wildcard'), host(r'(?P<username>\w+)', 'path.to.user_urls', name='user-area'), )
you can also easily pass arguments to the host_url
template tag:
{% load hosts %} <a href="{% host_url user-dashboard on user-area username='johndoe' %}">John's dashboard</a> <a href="{% host_url faq-index on wildcard 'help' %}">FAQ</a>
Which will be rendered (with a PARENT_HOST
of 'example.com'
) as:
<a href="//johndoe.example.com/">John's dashboard</a> <a href="//help.example.com/faq/">FAQ</a>
- When using dynamic hosts based on user input, ensure users cannot specify names that conflict with static subdomains such as "www" or their subdomain will not be accessible.
- Don't forget to add
handler404
andhandler500
entries for your custom URLconfs.
- Fixed issue related to the
PARENT_HOST
setting when used with empty host patterns. - Stopped automatically emulating hosts in debug mode.
BACKWARDS INCOMPATIBLE Renamed the package to
django_hosts
Please change your import from:
from hosts import patterns, hosts
to:
from django_hosts import patterns, hosts
BACKWARDS INCOMPATIBLE Changed the data type that the
django_hosts.patterns
function returns to be a list instead of a SortedDict to follow conventions of Django's URL patterns. You can use that for easy extension of the patterns, e.g.:from django_hosts import patterns, host from mytemplateproject.hosts import host_patterns host_patterns += patterns('', host('www2', 'mysite.urls.www2', name='www2') )
Extended tests to have full coverage.
Fixed prefix handling.
- Fixed docs issues.
- Use absolute imports where possible.
- Initial release with middleware, reverse and templatetags.
Many thanks to the folks at playfire for releasing their
django-dynamic-subdomains app, which was the inspiration for django-hosts
.