"""Map tiles, served from our own origin. The origin-zone map is drawn from a stored polygon, but the background imagery comes from a public tile server. That makes the map the only part of the admin page that depends on a *third-party* domain, and it is the part that breaks: a viewer whose network, proxy or browser filter refuses tile.openstreetmap.org (the refusal arrives as a 403) gets a map that never draws, even though the page, the Leaflet bundle and the polygon data are all fine and same-origin. So the browser asks us for tiles and we fetch them: browser -> /tiles/{z}/{x}/{y}.png -> Two things this buys. The map stops depending on the viewer's network path to a third party, and tiles are cached on this server, so a page that is opened repeatedly does not re-fetch them. The upstream stays configurable (``LEAFLET_TILE_URL``) because OpenStreetMap's tile policy deliberately does not cover heavy commercial use — the same setting points the map at a paid provider or a self-hosted tile server without further code changes. """ import urllib.error import urllib.request from pathlib import Path from django.conf import settings from django.http import HttpResponse, HttpResponseForbidden, HttpResponseNotFound # How long a browser may reuse a tile. Tiles are effectively static. BROWSER_CACHE_SECONDS = 60 * 60 * 24 * 7 MAX_ZOOM = 19 FETCH_TIMEOUT_SECONDS = 5 # Identify the application to the upstream, as tile providers require. UPSTREAM_USER_AGENT = ( "IXG-Platform/1.0 (internal origin-zone map; contact the platform administrator)" ) def browser_tile_url(): """The tile URL template the *browser* should use. With the proxy on (the default) this is a same-origin path; with it off, the browser talks to the upstream directly, as it did before. """ if settings.LEAFLET_TILE_PROXY_ENABLED: return "/tiles/{z}/{x}/{y}.png" return settings.LEAFLET_TILE_URL def cache_dir(): return Path(settings.LEAFLET_TILE_CACHE_DIR) def upstream_url(z, x, y): """Format the configured upstream template for one tile.""" url = settings.LEAFLET_TILE_URL # Some providers use a {s} subdomain placeholder; any label works. url = url.replace("{s}", "a") return ( url.replace("{z}", str(z)).replace("{x}", str(x)).replace("{y}", str(y)) ) def valid_tile(z, x, y): """Reject anything that is not a real tile address for the zoom level.""" if not 0 <= z <= MAX_ZOOM: return False span = 2 ** z return 0 <= x < span and 0 <= y < span def tile_view(request, z, x, y): """Serve one tile, from cache when possible, otherwise from the upstream.""" if not request.user.is_authenticated: return HttpResponseForbidden("Authentication required") if not valid_tile(z, x, y): return HttpResponseNotFound("No such tile") cached = cache_dir() / str(z) / str(x) / f"{y}.png" if cached.is_file(): return serve(cached.read_bytes()) try: request_obj = urllib.request.Request( upstream_url(z, x, y), headers={"User-Agent": UPSTREAM_USER_AGENT} ) with urllib.request.urlopen(request_obj, timeout=FETCH_TIMEOUT_SECONDS) as resp: data = resp.read() except (urllib.error.URLError, urllib.error.HTTPError, OSError, ValueError): # The upstream refused or is unreachable. Answer with an empty tile rather # than an error page: the map keeps its polygon and its controls, and the # viewer sees a blank background instead of a broken grid. return HttpResponse(status=204) try: cached.parent.mkdir(parents=True, exist_ok=True) cached.write_bytes(data) except OSError: # A read-only or full cache directory must not break the map. pass return serve(data) def serve(data): response = HttpResponse(data, content_type="image/png") response["Cache-Control"] = f"public, max-age={BROWSER_CACHE_SECONDS}" return response