mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05: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.files.storage import default_storage | ||||
| from django.core.validators import URLValidator | ||||
| from django.db.utils import OperationalError, ProgrammingError | ||||
| from django.http import StreamingHttpResponse | ||||
| from django.test import TestCase | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| @@ -88,31 +89,57 @@ def getStaticUrl(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. | ||||
|  | ||||
|     This is useful when (for example) sending an email to a user with a link | ||||
|     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: | ||||
|         return url | ||||
|     # If a site URL is provided, use that | ||||
|     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 | ||||
|     if base.endswith('/'): | ||||
|         base = base[:-1] | ||||
|     if site_url.endswith('/'): | ||||
|         site_url = site_url[:-1] | ||||
|  | ||||
|     if url.startswith('/'): | ||||
|         url = url[1:] | ||||
|     if relative_url.startswith('/'): | ||||
|         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): | ||||
|   | ||||
| @@ -17,6 +17,7 @@ from pathlib import Path | ||||
|  | ||||
| import django.conf.locale | ||||
| import django.core.exceptions | ||||
| from django.core.validators import URLValidator | ||||
| from django.http import Http404 | ||||
| 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_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 | ||||
| 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') | ||||
|   | ||||
| @@ -71,6 +71,10 @@ language: en-us | ||||
| # Reference: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | ||||
| 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: USD | ||||
|  | ||||
|   | ||||
| @@ -13,10 +13,9 @@ from django.template.loader import render_to_string | ||||
| from django.urls import reverse | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
| import common.models | ||||
| import part.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 plugin.registry import registry | ||||
|  | ||||
| @@ -183,7 +182,7 @@ class LabelTemplate(MetadataMixin, models.Model): | ||||
|         context = self.get_context_data(request) | ||||
|  | ||||
|         # 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['datetime'] = datetime.datetime.now() | ||||
|         context['request'] = request | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| from django.contrib.auth import authenticate, login | ||||
| from django.db import transaction | ||||
| from django.db.models import F, Q | ||||
| from django.db.utils import ProgrammingError | ||||
| from django.http.response import JsonResponse | ||||
| from django.urls import include, path, re_path | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| @@ -20,7 +19,8 @@ from company.models import SupplierPart | ||||
| from InvenTree.api import (APIDownloadMixin, AttachmentMixin, | ||||
|                            ListCreateDestroyAPIView, MetadataView, StatusView) | ||||
| 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, | ||||
|                               RetrieveUpdateDestroyAPI) | ||||
| from InvenTree.status_codes import (PurchaseOrderStatus, ReturnOrderLineStatus, | ||||
| @@ -1371,11 +1371,8 @@ class OrderCalendarExport(ICalFeed): | ||||
|         whether or not to show completed orders. Defaults to false | ||||
|     """ | ||||
|  | ||||
|     try: | ||||
|         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 = get_base_url() | ||||
|  | ||||
|     instance_url = instance_url.replace("http://", "").replace("https://", "") | ||||
|     timezone = settings.TIME_ZONE | ||||
|     file_name = "calendar.ics" | ||||
| @@ -1507,9 +1504,7 @@ class OrderCalendarExport(ICalFeed): | ||||
|     def item_link(self, item): | ||||
|         """Set the item link.""" | ||||
|  | ||||
|         # Do not use instance_url as here, as the protocol needs to be included | ||||
|         site_url = InvenTreeSetting.get_setting("INVENTREE_BASE_URL") | ||||
|         return f'{site_url}{item.get_absolute_url()}' | ||||
|         return construct_absolute_url(item.get_absolute_url()) | ||||
|  | ||||
|  | ||||
| order_api_urls = [ | ||||
|   | ||||
| @@ -222,8 +222,8 @@ def inventree_splash(**kwargs): | ||||
|  | ||||
| @register.simple_tag() | ||||
| def inventree_base_url(*args, **kwargs): | ||||
|     """Return the INVENTREE_BASE_URL setting.""" | ||||
|     return InvenTreeSetting.get_setting('INVENTREE_BASE_URL') | ||||
|     """Return the base URL of the InvenTree server""" | ||||
|     return InvenTree.helpers.get_base_url() | ||||
|  | ||||
|  | ||||
| @register.simple_tag() | ||||
|   | ||||
| @@ -20,7 +20,7 @@ import common.models | ||||
| import order.models | ||||
| import part.models | ||||
| import stock.models | ||||
| from InvenTree.helpers import validateFilterString | ||||
| from InvenTree.helpers import get_base_url, validateFilterString | ||||
| from InvenTree.models import MetadataMixin | ||||
| from plugin.registry import registry | ||||
|  | ||||
| @@ -203,7 +203,7 @@ class ReportTemplateBase(MetadataMixin, ReportBase): | ||||
|         # Generate custom context data based on the particular report subclass | ||||
|         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['datetime'] = datetime.datetime.now() | ||||
|         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): | ||||
|     """Make a <a></a> href which points to an InvenTree URL. | ||||
|  | ||||
|     Important Note: This only works if the INVENTREE_BASE_URL parameter is set! | ||||
|  | ||||
|     If the INVENTREE_BASE_URL parameter is not configured, | ||||
|     the text will be returned (unlinked) | ||||
|     Uses the InvenTree.helpers.construct_absolute_url function to build the URL. | ||||
|     """ | ||||
|     text = str(text) | ||||
|  | ||||
|   | ||||
| @@ -56,6 +56,15 @@ The following basic options are available: | ||||
| | INVENTREE_TIMZONE | timezome | Server timezone | UTC | | ||||
| | ADMIN_URL | admin_url | URL for accessing [admin interface](../settings/admin.md) | admin | | ||||
| | 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 | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user