ICVOSS DJANGO PACKAGE REGISTRY

The package index django-hostmap Link across hosts in templates and Python

Link across hosts in templates and Python

Documentation

Goal

Understand what {% url %} and reverse() return once hostmap is installed, so you can rely on them the same way you always have, in templates and in Python, without an explicit host at any call site.

Prerequisites

Steps

1. Use {% url %} exactly as you always have

No template changes, no new tag, no host= argument:

{# templates/nav.html #}
<a href="{% url 'blog-index' %}">Blog</a>
<a href="{% url 'api:user-detail' request.user.pk %}">My API profile</a>

If blog-index resolves on the active host, you get a relative path (/blog/), byte-identical to what stock Django would render. If api:user-detail only resolves on the api host, you get an absolute URL (https://api.example.com/users/7/). The template author does not need to know, or care, which case applies.

2. Use reverse() exactly as you always have in Python

from django.urls import reverse

reverse("blog-index")               # '/blog/'
reverse("api:user-detail", args=[7])  # 'https://api.example.com/users/7/'

This is stock django.urls.reverse, not a hostmap wrapper. It works identically inside your own views and inside third-party apps you have not touched, because hostmap hooks resolver acquisition rather than rebinding reverse itself. See how host-aware reversing works for the mechanism.

3. Know the silent-failure contract still holds

{% url ... as var %} keeps Django's usual silent-failure behaviour through the cross-host fallback: if the name resolves on no host at all, the template assigns nothing to var rather than raising.

{% url 'maybe-missing' as maybe_link %}
{% if maybe_link %}<a href="{{ maybe_link }}">Link</a>{% endif %}

Without as, a name that resolves on no configured host raises NoReverseMatch, naming every host that was searched, exactly like stock Django names the URLconf it searched.

4. Know when a result is absolute versus relative

Anything downstream that already tolerates an absolute URL from reverse() continues to work unmodified: HttpResponseRedirect, the redirect() shortcut, resolve_url(), and request.build_absolute_uri() all accept absolute input. If you have code that assumes reverse() always returns a path (for example, string concatenation like "/prefix" + reverse(...)), that assumption was already fragile; hostmap surfaces it rather than causing it.

Verify it worked

import pytest
from django.urls import reverse

from hostmap import context


@pytest.mark.django_db
def test_same_host_reverse_is_relative(settings):
    assert reverse("blog-index") == "/blog/"


@pytest.mark.django_db
def test_cross_host_reverse_is_absolute(settings):
    url = reverse("api:user-detail", args=[7])
    assert url.startswith("https://api.")

Use host_client() to exercise this through a real request against a specific host.

Common pitfalls