django-hosts¶
This Django app routes requests for specific hosts to different URL schemes defined in modules called “hostconfs”.
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 a hosts.py file:
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 hostconfs.
Patterns are evaluated in order. If no pattern matches, the request is processed in the usual way, ie. using the standard ROOT_URLCONF.
The patterns on the left-hand side are regular expressions. For example, the following ROOT_HOSTCONF setting 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'),
)
Installation¶
First, install the app with your favorite package manager, e.g.:
pip install django-hosts
Alternatively, use the repository on Github.
Then configure your Django site to use the app:
Add 'django_hosts' to your INSTALLED_APPS setting.
Add 'django_hosts.middleware.HostsMiddleware' to your MIDDLEWARE_CLASSES setting.
Create a module containing your default host patterns, e.g. in the hosts.py file next to your urls.py.
Set the ROOT_HOSTCONF setting to the dotted Python import path of the module containing your 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 the host_url template tag.
django-hosts uses versiontools to manage version numbers following PEP 386.
Usage¶
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 admin.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, given the value of the ROOT_URLCONF setting:
from django_hosts import patterns, host
host_patterns = patterns('',
host(r'(?!www)\w+', 'path.to.custom_urls', name='wildcard'),
)
Settings¶
- django.conf.settings.ROOT_HOSTCONF(required)¶
The dotted Python import path of the module containing your host patterns. Similar to ROOT_URLCONF.
- django.conf.settings.DEFAULT_HOST(required)¶
The name of the host pattern you want to refer to as the default pattern. Used if no other host pattern matches or no host name is passed to the host_url() template tag.
- django.conf.settings.PARENT_HOST(optional)¶
The parent domain name to be appended to the reversed domain (e.g. using the host_url() template tag).
- django.conf.settings.HOST_SITE_TIMEOUT(optional)¶
The time to cache the host in the default cache backend, in seconds, when using the cached_host_site() callback. Defaults to 3600.
Contents¶
Per-host callbacks¶
Parsing the host from request.get_host() and lookup its corresponding object instance (e.g. site) in every view violates 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.
Simply define a callback function:
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)
..and pass it as the callback paramter to the host object:
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'),
)
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.
Note
There are a few things to keep in mind when using the callbacks:
- Callbacks are executed with the URLconf set to the second argument in the host_patterns list. For example, in the example above, the callback will be executed with the URLconf as path.to.custom_urls and not the default URLconf.
- This can cause problems when reversing URLs within your callback as they may not be “visible” to django.core.urlresolvers.reverse() as they are specified in (eg.) the default URLconf.
- To remedy this, specify the urlconf parameter when calling reverse().
- 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 and handler500 entries for your custom URLconfs.
Included callbacks¶
django-hosts includes the following callbacks:
- django_hosts.callbacks.host_site(request, *args, **kwargs)¶
A callback function which uses the django.contrib.sites contrib app included in Django to match a host to a Site instance, setting a request.site attribute on success.
Parameters: - request – the request object passed from the middleware
- *args – the parameters as matched by the host patterns
- **kwargs – the keyed parameters as matched by the host patterns
It’s important to note that this uses reverse_host() behind the scenes to reverse the host with the given arguments and keyed arguments to enable a flexible configuration of what will be used to retrieve the Site instance – in the end the callback will use a domain__iexact lookup to get it.
For example, imagine a host conf with a username parameter:
from django.conf import settings from django_hosts import patterns, host settings.PARENT_HOST = 'example.com' host_patterns = patterns('', host(r'www', settings.ROOT_URLCONF, name='www'), host(r'(?P<username>\w+)', 'path.to.custom_urls', callback='django_hosts.callbacks.host_site', name='user-sites'), )
When requesting this website with the host jezdez.example.com, the callback will act as if you’d do:
request.site = Site.objects.get(domain__iexact='jezdez.example.com')
..since the result of calling reverse_host() with the username 'jezdez' is 'jezdez.example.com'.
Later, in your views, you can nicely refer to the current site as request.site for further site-specific functionality.
- django_hosts.callbacks.cached_host_site(request, *args, **kwargs)¶
A callback function similar to host_site() which caches the resulting Site instance in the default cache backend for the time specfified as HOST_SITE_TIMEOUT.
Parameters: - request – the request object passed from the middleware
- *args – the parameters as matched by the host patterns
- **kwargs – the keyed parameters as matched by the host patterns
Template tags¶
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.
Setting a context variable¶
New in version 0.4.0.
If you’d like to retrieve a URL without displaying it, you can save the result of the template tag in a template variable and use it later on, e.g.:
{% load hosts %}
{% host_url homepage as homepage_url %}
<a href="{{ homepage_url }}" title="Go back to {{ homepage_url }}">Home</a>
Fully qualified domain names (FQDN)¶
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 of the previous section 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'),
)
Host and URL parameters¶
If your host pattern contains an parameter (or keyed parameter), 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 parameters 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>
Reference¶
hosts and patterns¶
- django_hosts.defaults.patterns(prefix, *args)¶
The function to define the list of hosts (aka host confs), e.g.:
from django_hosts import patterns host_patterns = patterns('path.to', (r'www', 'urls.default', 'default'), (r'api', 'urls.api', 'api'), )
Parameters: - prefix (str) – the URLconf prefix to pass to the host object
- *args – a list of hosts instances or an iterable thereof
- class django_hosts.defaults.host(regex, urlconf, name, callback=None, prefix='')¶
The host object used in host conf together with the django_hosts.defaults.patterns() function, e.g.:
from django_hosts import patterns, host host_patterns = patterns('path.to', host(r'www', 'urls.default', name='default'), host(r'api', 'urls.api', name='api'), )
Parameters: - regex (str) – a regular expression to be used to match the request’s host.
- urlconf (str) – the dotted path of a URLconf module of the host
- callback (callable or str) – a callable or the dotted path of a callable to be used when matching has happened
- prefix (str) – the prefix to apply to the urlconf parameter
- add_prefix(prefix='')¶
Adds the prefix string to a string-based urlconf.
Reversal with reverse_host and reverse_full¶
- django_hosts.reverse.reverse_full(host, view, host_args=None, host_kwargs=None, view_args=None, view_kwargs=None)¶
Given the host and view name and the appropriate parameters, reverses the fully qualified URL, e.g.:
>>> from django.conf import settings >>> settings.ROOT_HOSTCONF = 'mysite.hosts' >>> settings.PARENT_HOST = 'example.com' >>> from django_hosts.reverse import reverse_full >>> reverse_full('www', 'about') '//www.example.com/about/'
Parameters: - host – the name of the host
- view – the name of the view
Host_args: the host arguments
Host_kwargs: the host keyed arguments
View_args: the arguments of the view
View_kwargs: the keyed arguments of the view
Return type: fully qualified URL with path
- django_hosts.reverse.reverse_host(host, args=None, kwargs=None)¶
Given the host name and the appropriate parameters, reverses the host, e.g.:
>>> from django.conf import settings >>> settings.ROOT_HOSTCONF = 'mysite.hosts' >>> settings.PARENT_HOST = 'example.com' >>> from django_hosts.reverse import reverse_host >>> reverse_host('with_username', 'jezdez') 'jezdez.example.com'
Parameters: name – the name of the host as specified in the hostconf Args: the host arguments to use to find a matching entry in the hostconf Kwargs: similar to args but key value arguments Raises django.core.urlresolvers.NoReverseMatch: if no host matches Return type: reversed hostname
HostSiteManager model manager¶
- class django_hosts.managers.HostSiteManager(field_name=None, select_related=True)¶
A model manager to limit objects to those associated with a site.
Parameters: - field_name – the name of the related field pointing at the Site model, or a series of relations using the field1__field2__field3 notation. Falls back to looking for ‘site’ and ‘sites’ fields.
- select_related – a boolean specifying whether to use select_related() when querying the database
Define a manager instance in your model class with one of the following notations:
on_site = HostSiteManager() # automatically looks for site and sites on_site = HostSiteManager("author__site") on_site = HostSiteManager("author__blog__site") on_site = HostSiteManager("author__blog__site", select_related=False)
Then query against it with one of the manager methods:
def home_page(request): posts = BlogPost.on_site.by_request(request).all() return render(request, 'home_page.html', {'posts': posts})
- by_id(site_id=None)¶
Returns a queryset matching the given site id. If not given this falls back to the SITE_ID setting.
Parameters: site_id – the ID of the site Return type: QuerySet
- by_request(request)¶
Returns a queryset matching the given request’s site attribute.
Parameters: request (HttpRequest) – the current request Return type: QuerySet
Changelog¶
0.4.1 (2011/12/23)¶
- Added cached_host_site() callback which stores the matching Site instance in the default cache backend (also see new HOST_SITE_TIMEOUT setting).
- Throw warning if django-debug-toolbar is used together with the django_hosts and the order of the MIDDLEWARE_CLASSES setting isn’t correct.
- Added CI server at https://jenkins.shiningpanda.com/enn.io/job/django-hosts/
0.4 (2011/11/04)¶
- Added ability to save the result of host_url() template tag in a template context variable.
0.3 (2011/09/30)¶
- Consolidated reversal internals.
- Removed unfinished support for the Django Debug Toolbar.
- Added a custom callback which uses Django’s sites app to retrieve a Site instance matching the current host, setting request.site.
- Extended tests dramatically (100% coverage).
- Added docs at http://django-hosts.rtfd.org
- Stopped preventing the name ‘default’ for hosts.
0.2.1 (2011/05/31)¶
- Fixed issue related to the PARENT_HOST setting when used with empty host patterns.
- Stopped automatically emulating hosts in debug mode.
0.2 (2011/05/31)¶
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.
0.1.1 (2011/05/30)¶
- Fixed docs issues.
- Use absolute imports where possible.
0.1 (2011/05/29)¶
- Initial release with middleware, reverse and templatetags.
Issues¶
For any bug reports and feature requests, please use the Github issue tracker.
Thanks¶
Many thanks to the folks at playfire for releasing their django-dynamic-subdomains app, which was the inspiration for this app.