mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 15:15:42 +00:00 
			
		
		
		
	Base URL configuration options (#4749)
* Improve construct_absolute_url method - Look for hard-coded site URL if provided - Otherwise look for specified site URL - Otherwise look at the provided request object * Refactor existing code which used base URL setting * Update docs * Validate that a provided base URL is valid
This commit is contained in:
		@@ -17,6 +17,7 @@ from django.contrib.staticfiles.storage import StaticFilesStorage
 | 
				
			|||||||
from django.core.exceptions import FieldError, ValidationError
 | 
					from django.core.exceptions import FieldError, ValidationError
 | 
				
			||||||
from django.core.files.storage import default_storage
 | 
					from django.core.files.storage import default_storage
 | 
				
			||||||
from django.core.validators import URLValidator
 | 
					from django.core.validators import URLValidator
 | 
				
			||||||
 | 
					from django.db.utils import OperationalError, ProgrammingError
 | 
				
			||||||
from django.http import StreamingHttpResponse
 | 
					from django.http import StreamingHttpResponse
 | 
				
			||||||
from django.test import TestCase
 | 
					from django.test import TestCase
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
@@ -88,31 +89,57 @@ def getStaticUrl(filename):
 | 
				
			|||||||
    return os.path.join(STATIC_URL, str(filename))
 | 
					    return os.path.join(STATIC_URL, str(filename))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def construct_absolute_url(*arg):
 | 
					def construct_absolute_url(*arg, **kwargs):
 | 
				
			||||||
    """Construct (or attempt to construct) an absolute URL from a relative URL.
 | 
					    """Construct (or attempt to construct) an absolute URL from a relative URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    This is useful when (for example) sending an email to a user with a link
 | 
					    This is useful when (for example) sending an email to a user with a link
 | 
				
			||||||
    to something in the InvenTree web framework.
 | 
					    to something in the InvenTree web framework.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    This requires the BASE_URL configuration option to be set!
 | 
					    A URL is constructed in the following order:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    1. If setings.SITE_URL is set (e.g. in the Django settings), use that
 | 
				
			||||||
 | 
					    2. If the InvenTree setting INVENTREE_BASE_URL is set, use that
 | 
				
			||||||
 | 
					    3. Otherwise, use the current request URL (if available)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    base = str(common.models.InvenTreeSetting.get_setting('INVENTREE_BASE_URL'))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    url = '/'.join(arg)
 | 
					    relative_url = '/'.join(arg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not base:
 | 
					    # If a site URL is provided, use that
 | 
				
			||||||
        return url
 | 
					    site_url = getattr(settings, 'SITE_URL', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not site_url:
 | 
				
			||||||
 | 
					        # Otherwise, try to use the InvenTree setting
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            site_url = common.models.InvenTreeSetting.get_setting('INVENTREE_BASE_URL', create=False, cache=False)
 | 
				
			||||||
 | 
					        except ProgrammingError:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        except OperationalError:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not site_url:
 | 
				
			||||||
 | 
					        # Otherwise, try to use the current request
 | 
				
			||||||
 | 
					        request = kwargs.get('request', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if request:
 | 
				
			||||||
 | 
					            site_url = request.build_absolute_uri('/')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not site_url:
 | 
				
			||||||
 | 
					        # No site URL available, return the relative URL
 | 
				
			||||||
 | 
					        return relative_url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Strip trailing slash from base url
 | 
					    # Strip trailing slash from base url
 | 
				
			||||||
    if base.endswith('/'):
 | 
					    if site_url.endswith('/'):
 | 
				
			||||||
        base = base[:-1]
 | 
					        site_url = site_url[:-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if url.startswith('/'):
 | 
					    if relative_url.startswith('/'):
 | 
				
			||||||
        url = url[1:]
 | 
					        relative_url = relative_url[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    url = f"{base}/{url}"
 | 
					    return f"{site_url}/{relative_url}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return url
 | 
					
 | 
				
			||||||
 | 
					def get_base_url(**kwargs):
 | 
				
			||||||
 | 
					    """Return the base URL for the InvenTree server"""
 | 
				
			||||||
 | 
					    return construct_absolute_url('', **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def download_image_from_url(remote_url, timeout=2.5):
 | 
					def download_image_from_url(remote_url, timeout=2.5):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ from pathlib import Path
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import django.conf.locale
 | 
					import django.conf.locale
 | 
				
			||||||
import django.core.exceptions
 | 
					import django.core.exceptions
 | 
				
			||||||
 | 
					from django.core.validators import URLValidator
 | 
				
			||||||
from django.http import Http404
 | 
					from django.http import Http404
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -908,6 +909,16 @@ PLUGIN_TESTING_EVENTS = False
 | 
				
			|||||||
PLUGIN_RETRY = get_setting('INVENTREE_PLUGIN_RETRY', 'PLUGIN_RETRY', 5)                                 # How often should plugin loading be tried?
 | 
					PLUGIN_RETRY = get_setting('INVENTREE_PLUGIN_RETRY', 'PLUGIN_RETRY', 5)                                 # How often should plugin loading be tried?
 | 
				
			||||||
PLUGIN_FILE_CHECKED = False                                                                             # Was the plugin file checked?
 | 
					PLUGIN_FILE_CHECKED = False                                                                             # Was the plugin file checked?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Site URL can be specified statically, or via a run-time setting
 | 
				
			||||||
 | 
					SITE_URL = get_setting('INVENTREE_SITE_URL', 'site_url', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if SITE_URL:
 | 
				
			||||||
 | 
					    logger.info(f"Site URL: {SITE_URL}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Check that the site URL is valid
 | 
				
			||||||
 | 
					    validator = URLValidator()
 | 
				
			||||||
 | 
					    validator(SITE_URL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# User interface customization values
 | 
					# User interface customization values
 | 
				
			||||||
CUSTOM_LOGO = get_custom_file('INVENTREE_CUSTOM_LOGO', 'customize.logo', 'custom logo', lookup_media=True)
 | 
					CUSTOM_LOGO = get_custom_file('INVENTREE_CUSTOM_LOGO', 'customize.logo', 'custom logo', lookup_media=True)
 | 
				
			||||||
CUSTOM_SPLASH = get_custom_file('INVENTREE_CUSTOM_SPLASH', 'customize.splash', 'custom splash')
 | 
					CUSTOM_SPLASH = get_custom_file('INVENTREE_CUSTOM_SPLASH', 'customize.splash', 'custom splash')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,6 +71,10 @@ language: en-us
 | 
				
			|||||||
# Reference: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
 | 
					# Reference: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
 | 
				
			||||||
timezone: UTC
 | 
					timezone: UTC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Base URL for the InvenTree server
 | 
				
			||||||
 | 
					# Use the environment variable INVENTREE_BASE_URL
 | 
				
			||||||
 | 
					# base_url: 'http://localhost:8000'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Base currency code (or use env var INVENTREE_BASE_CURRENCY)
 | 
					# Base currency code (or use env var INVENTREE_BASE_CURRENCY)
 | 
				
			||||||
base_currency: USD
 | 
					base_currency: USD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,10 +13,9 @@ from django.template.loader import render_to_string
 | 
				
			|||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import common.models
 | 
					 | 
				
			||||||
import part.models
 | 
					import part.models
 | 
				
			||||||
import stock.models
 | 
					import stock.models
 | 
				
			||||||
from InvenTree.helpers import normalize, validateFilterString
 | 
					from InvenTree.helpers import get_base_url, normalize, validateFilterString
 | 
				
			||||||
from InvenTree.models import MetadataMixin
 | 
					from InvenTree.models import MetadataMixin
 | 
				
			||||||
from plugin.registry import registry
 | 
					from plugin.registry import registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -183,7 +182,7 @@ class LabelTemplate(MetadataMixin, models.Model):
 | 
				
			|||||||
        context = self.get_context_data(request)
 | 
					        context = self.get_context_data(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Add "basic" context data which gets passed to every label
 | 
					        # Add "basic" context data which gets passed to every label
 | 
				
			||||||
        context['base_url'] = common.models.InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
 | 
					        context['base_url'] = get_base_url(request=request)
 | 
				
			||||||
        context['date'] = datetime.datetime.now().date()
 | 
					        context['date'] = datetime.datetime.now().date()
 | 
				
			||||||
        context['datetime'] = datetime.datetime.now()
 | 
					        context['datetime'] = datetime.datetime.now()
 | 
				
			||||||
        context['request'] = request
 | 
					        context['request'] = request
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,6 @@
 | 
				
			|||||||
from django.contrib.auth import authenticate, login
 | 
					from django.contrib.auth import authenticate, login
 | 
				
			||||||
from django.db import transaction
 | 
					from django.db import transaction
 | 
				
			||||||
from django.db.models import F, Q
 | 
					from django.db.models import F, Q
 | 
				
			||||||
from django.db.utils import ProgrammingError
 | 
					 | 
				
			||||||
from django.http.response import JsonResponse
 | 
					from django.http.response import JsonResponse
 | 
				
			||||||
from django.urls import include, path, re_path
 | 
					from django.urls import include, path, re_path
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
@@ -20,7 +19,8 @@ from company.models import SupplierPart
 | 
				
			|||||||
from InvenTree.api import (APIDownloadMixin, AttachmentMixin,
 | 
					from InvenTree.api import (APIDownloadMixin, AttachmentMixin,
 | 
				
			||||||
                           ListCreateDestroyAPIView, MetadataView, StatusView)
 | 
					                           ListCreateDestroyAPIView, MetadataView, StatusView)
 | 
				
			||||||
from InvenTree.filters import SEARCH_ORDER_FILTER, SEARCH_ORDER_FILTER_ALIAS
 | 
					from InvenTree.filters import SEARCH_ORDER_FILTER, SEARCH_ORDER_FILTER_ALIAS
 | 
				
			||||||
from InvenTree.helpers import DownloadFile, str2bool
 | 
					from InvenTree.helpers import (DownloadFile, construct_absolute_url,
 | 
				
			||||||
 | 
					                               get_base_url, str2bool)
 | 
				
			||||||
from InvenTree.mixins import (CreateAPI, ListAPI, ListCreateAPI,
 | 
					from InvenTree.mixins import (CreateAPI, ListAPI, ListCreateAPI,
 | 
				
			||||||
                              RetrieveUpdateDestroyAPI)
 | 
					                              RetrieveUpdateDestroyAPI)
 | 
				
			||||||
from InvenTree.status_codes import (PurchaseOrderStatus, ReturnOrderLineStatus,
 | 
					from InvenTree.status_codes import (PurchaseOrderStatus, ReturnOrderLineStatus,
 | 
				
			||||||
@@ -1371,11 +1371,8 @@ class OrderCalendarExport(ICalFeed):
 | 
				
			|||||||
        whether or not to show completed orders. Defaults to false
 | 
					        whether or not to show completed orders. Defaults to false
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    instance_url = get_base_url()
 | 
				
			||||||
        instance_url = InvenTreeSetting.get_setting('INVENTREE_BASE_URL', create=False, cache=False)
 | 
					
 | 
				
			||||||
    except ProgrammingError:  # pragma: no cover
 | 
					 | 
				
			||||||
        # database is not initialized yet
 | 
					 | 
				
			||||||
        instance_url = ''
 | 
					 | 
				
			||||||
    instance_url = instance_url.replace("http://", "").replace("https://", "")
 | 
					    instance_url = instance_url.replace("http://", "").replace("https://", "")
 | 
				
			||||||
    timezone = settings.TIME_ZONE
 | 
					    timezone = settings.TIME_ZONE
 | 
				
			||||||
    file_name = "calendar.ics"
 | 
					    file_name = "calendar.ics"
 | 
				
			||||||
@@ -1507,9 +1504,7 @@ class OrderCalendarExport(ICalFeed):
 | 
				
			|||||||
    def item_link(self, item):
 | 
					    def item_link(self, item):
 | 
				
			||||||
        """Set the item link."""
 | 
					        """Set the item link."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Do not use instance_url as here, as the protocol needs to be included
 | 
					        return construct_absolute_url(item.get_absolute_url())
 | 
				
			||||||
        site_url = InvenTreeSetting.get_setting("INVENTREE_BASE_URL")
 | 
					 | 
				
			||||||
        return f'{site_url}{item.get_absolute_url()}'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
order_api_urls = [
 | 
					order_api_urls = [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -222,8 +222,8 @@ def inventree_splash(**kwargs):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@register.simple_tag()
 | 
					@register.simple_tag()
 | 
				
			||||||
def inventree_base_url(*args, **kwargs):
 | 
					def inventree_base_url(*args, **kwargs):
 | 
				
			||||||
    """Return the INVENTREE_BASE_URL setting."""
 | 
					    """Return the base URL of the InvenTree server"""
 | 
				
			||||||
    return InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
 | 
					    return InvenTree.helpers.get_base_url()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register.simple_tag()
 | 
					@register.simple_tag()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ import common.models
 | 
				
			|||||||
import order.models
 | 
					import order.models
 | 
				
			||||||
import part.models
 | 
					import part.models
 | 
				
			||||||
import stock.models
 | 
					import stock.models
 | 
				
			||||||
from InvenTree.helpers import validateFilterString
 | 
					from InvenTree.helpers import get_base_url, validateFilterString
 | 
				
			||||||
from InvenTree.models import MetadataMixin
 | 
					from InvenTree.models import MetadataMixin
 | 
				
			||||||
from plugin.registry import registry
 | 
					from plugin.registry import registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -203,7 +203,7 @@ class ReportTemplateBase(MetadataMixin, ReportBase):
 | 
				
			|||||||
        # Generate custom context data based on the particular report subclass
 | 
					        # Generate custom context data based on the particular report subclass
 | 
				
			||||||
        context = self.get_context_data(request)
 | 
					        context = self.get_context_data(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        context['base_url'] = common.models.InvenTreeSetting.get_setting('INVENTREE_BASE_URL')
 | 
					        context['base_url'] = get_base_url(request=request)
 | 
				
			||||||
        context['date'] = datetime.datetime.now().date()
 | 
					        context['date'] = datetime.datetime.now().date()
 | 
				
			||||||
        context['datetime'] = datetime.datetime.now()
 | 
					        context['datetime'] = datetime.datetime.now()
 | 
				
			||||||
        context['default_page_size'] = common.models.InvenTreeSetting.get_setting('REPORT_DEFAULT_PAGE_SIZE')
 | 
					        context['default_page_size'] = common.models.InvenTreeSetting.get_setting('REPORT_DEFAULT_PAGE_SIZE')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -205,10 +205,7 @@ def logo_image(**kwargs):
 | 
				
			|||||||
def internal_link(link, text):
 | 
					def internal_link(link, text):
 | 
				
			||||||
    """Make a <a></a> href which points to an InvenTree URL.
 | 
					    """Make a <a></a> href which points to an InvenTree URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Important Note: This only works if the INVENTREE_BASE_URL parameter is set!
 | 
					    Uses the InvenTree.helpers.construct_absolute_url function to build the URL.
 | 
				
			||||||
 | 
					 | 
				
			||||||
    If the INVENTREE_BASE_URL parameter is not configured,
 | 
					 | 
				
			||||||
    the text will be returned (unlinked)
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    text = str(text)
 | 
					    text = str(text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,6 +56,15 @@ The following basic options are available:
 | 
				
			|||||||
| INVENTREE_TIMZONE | timezome | Server timezone | UTC |
 | 
					| INVENTREE_TIMZONE | timezome | Server timezone | UTC |
 | 
				
			||||||
| ADMIN_URL | admin_url | URL for accessing [admin interface](../settings/admin.md) | admin |
 | 
					| ADMIN_URL | admin_url | URL for accessing [admin interface](../settings/admin.md) | admin |
 | 
				
			||||||
| INVENTREE_LANGUAGE | language | Default language | en-us |
 | 
					| INVENTREE_LANGUAGE | language | Default language | en-us |
 | 
				
			||||||
 | 
					| INVENTREE_BASE_URL | base_url | Server base URL | *Not specified* |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Base URL Configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The base URL of the InvenTree site is required for constructing absolute URLs in a number of circumstances. To construct a URL, the InvenTree iterates through the following options in decreasing order of importance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Static configuration (i.e. set using environment variable or configuration file as above)
 | 
				
			||||||
 | 
					2. Global settings (i.e. configured at run-time in the [global settings](../settings/global.md))
 | 
				
			||||||
 | 
					3. Using the hostname supplied by the user request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Administrator Account
 | 
					## Administrator Account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user