From 321489a41bc37f76e45430cd4cd3c2e151033dd0 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 6 Sep 2026 15:00:22 +1000 Subject: [PATCH] Add error message for missing TLD (#12789) * Add error message for missing TLD * add error code for missing TLD --- docs/docs/settings/error_codes.md | 8 +++++ src/backend/InvenTree/InvenTree/settings.py | 3 +- src/backend/InvenTree/InvenTree/validators.py | 35 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/docs/settings/error_codes.md b/docs/docs/settings/error_codes.md index e444125fdc..bfd72f9399 100644 --- a/docs/docs/settings/error_codes.md +++ b/docs/docs/settings/error_codes.md @@ -110,6 +110,14 @@ While using [invoke](../start/invoke.md), this can be overridden with the `--res An error occurred while rendering a component in the frontend. Typically this is caused by a browser caching issue, and can be resolved by clearing the browser cache and refreshing the page. If the issue persists, check the browser console for more information about the error. +#### INVE-E18 + +**Top Level Domain Required** + +The InvenTree server failed to start, because the configured server URL does not include a top-level domain (TLD). A valid TLD is required for proper operation. + +## Warning Codes + ### INVE-W (InvenTree Warning) Warnings - These are non-critical errors which should be addressed when possible. diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index cb691c2685..fe85764553 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -28,6 +28,7 @@ from InvenTree.cache import get_cache_config, is_global_cache_enabled from InvenTree.config import get_boolean_setting, get_oidc_private_key, get_setting from InvenTree.ready import isInMainThread, isRunningBackup from InvenTree.sentry import default_sentry_dsn, init_sentry +from InvenTree.validators import invalid_site_url_hint from InvenTree.version import checkMinPythonVersion, inventreeCommitHash from users.oauth2_scopes import oauth2_scopes @@ -771,7 +772,7 @@ if SITE_URL: validator = URLValidator() validator(SITE_URL) except Exception: - msg = f"Invalid SITE_URL value: '{SITE_URL}'. InvenTree server cannot start." + msg = f"Invalid SITE_URL value: '{SITE_URL}'. InvenTree server cannot start.{invalid_site_url_hint(SITE_URL)}" logger.error(msg) print(msg) sys.exit(-1) diff --git a/src/backend/InvenTree/InvenTree/validators.py b/src/backend/InvenTree/InvenTree/validators.py index ae4145a140..13ab702e07 100644 --- a/src/backend/InvenTree/InvenTree/validators.py +++ b/src/backend/InvenTree/InvenTree/validators.py @@ -1,6 +1,8 @@ """Custom field validators for InvenTree.""" +import ipaddress import tokenize +from urllib.parse import urlsplit from django.conf import settings from django.core import validators @@ -88,6 +90,39 @@ class AllowedURLValidator(validators.URLValidator): super().__call__(value) +def invalid_site_url_hint(site_url: str) -> str: + """Return an extra hint for *why* a SITE_URL value failed validation. + + Django's URLValidator rejects any hostname that isn't 'localhost', an IP + address, or a fully qualified (dotted) name - so a bare LAN hostname like + 'warehouse' fails with no indication of what's actually wrong. Returns an + empty string when nothing more specific than "invalid URL" applies. + """ + hostname = urlsplit(site_url).hostname + + if hostname is None and '//' not in site_url: + # No scheme was given at all, e.g. SITE_URL=warehouse + hostname = urlsplit(f'//{site_url}').hostname + + if not hostname or hostname == 'localhost': + return '' + + try: + ipaddress.ip_address(hostname) + return '' + except ValueError: + pass + + if '.' not in hostname: + return ( + f"INVE-E17: Top Level Domain Required.\ni'{hostname}' has no top-level domain. InvenTree requires a fully " + "qualified hostname (e.g. 'warehouse.local'), an IP address, or " + "'localhost' - a bare hostname is rejected by Django's URL validator." + ) + + return '' + + def validate_purchase_order_reference(value): """Validate the 'reference' field of a PurchaseOrder.""" from order.models import PurchaseOrder