Support mounting application under a prefix

Closes #9239
This commit is contained in:
Cédric Krier
2023-11-12 23:44:43 +01:00
parent 05b4e9a6e9
commit 84c47a0433
11 changed files with 47 additions and 12 deletions
@@ -30,7 +30,7 @@ from trytond.tools import pairwise_longest
from trytond.tools.chart import sparkline
from trytond.tools.email_ import format_address, has_rcpt, set_from_header
from trytond.transaction import Transaction
from trytond.url import http_host
from trytond.url import http_base
from trytond.wsgi import Base64Converter
from .exceptions import ConditionError, DomainError, TemplateError
@@ -636,7 +636,7 @@ class Activity(
Email = pool.get('ir.email')
record = record_activity.record
url_base = config.get(
'marketing', 'automation_base', default=http_host())
'marketing', 'automation_base', default=http_base())
url_open = urljoin(url_base, '/m/empty.gif')
with Transaction().set_context(language=record.language):
+2 -2
View File
@@ -26,7 +26,7 @@ from trytond.tools.email_ import (
EmailNotValidError, format_address, normalize_email, set_from_header,
validate_email)
from trytond.transaction import Transaction, inactive_records
from trytond.url import http_host
from trytond.url import http_base
from trytond.wizard import Button, StateTransition, StateView, Wizard
from .exceptions import EMailValidationError, TemplateError
@@ -430,7 +430,7 @@ class Message(Workflow, ModelSQL, ModelView):
spy_pixel = config.getboolean(
'marketing', 'email_spy_pixel', default=False)
url_base = config.get('marketing', 'email_base', default=http_host())
url_base = config.get('marketing', 'email_base', default=http_base())
url_open = urljoin(url_base, '/m/empty.gif')
@lru_cache(None)
+2 -2
View File
@@ -16,7 +16,7 @@ from trytond.pool import PoolMeta
from trytond.pyson import Bool, Eval, If
from trytond.tools import slugify
from trytond.transaction import Transaction
from trytond.url import http_host
from trytond.url import http_base
from trytond.wsgi import Base64Converter
from .exceptions import ImageValidationError
@@ -77,7 +77,7 @@ class ImageURLMixin:
url_base = config.get(
'product', 'image_base', default='')
url_external_base = config.get(
'product', 'image_base', default=http_host())
'product', 'image_base', default=http_base())
return self._image_url(
url_external_base if _external else url_base, **args)
+2 -2
View File
@@ -12,7 +12,7 @@ import trytond.config as config
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.url import http_host
from trytond.url import http_base
from trytond.wsgi import Base64Converter
ALPHABET = string.digits + string.ascii_lowercase
@@ -37,7 +37,7 @@ class ShortenedURL(ModelSQL, ModelView):
'database': Base64Converter(None).to_url(
Transaction().database.name),
}
url_base = config.get('web', 'shortener_base', default=http_host())
url_base = config.get('web', 'shortener_base', default=http_base())
for shortened in shortened_urls:
url_parts['short_id'] = cls._shorten(shortened.id)
urls[shortened.id] = urljoin(
+1
View File
@@ -1,3 +1,4 @@
* Support mounting application under a prefix
* Send emails on chat messages
* Add path attribute on XML fields to specfiy a relative path value
* Allow to include subdirectories in tryton.cfg
+10
View File
@@ -66,6 +66,16 @@ hostname
Defines the hostname to use when generating a URL when there is no request
context available, for example during a cron job.
.. _config-web.root_path:
root_path
~~~~~~~~~
Defines the prefix that the WSGI application is mounted under (with trailing slash).
This must be the same as the ``SCRIPT_NAME`` set by the WSGI server.
The default value is: ``/``.
.. _config-web.root:
root
+1
View File
@@ -55,6 +55,7 @@ class TrytonConfigParser(configparser.ConfigParser):
super().__init__(interpolation=None)
self.add_section('web')
self.set('web', 'listen', 'localhost:8000')
self.set('web', 'root_path', '/')
self.set('web', 'root', os.path.join(os.path.expanduser('~'), 'www'))
self.set('web', 'num_proxies', '0')
self.set('web', 'cache_timeout', str(60 * 60 * 12))
+1 -1
View File
@@ -90,7 +90,7 @@ class Avatar(ImageMixin, ResourceMixin, ModelSQL):
if self.image_id or self.image:
url_base = config.get('web', 'avatar_base', default='')
return urljoin(
url_base, quote('/avatar/%(database)s/%(uuid)s' % {
url_base, quote('avatar/%(database)s/%(uuid)s' % {
'database': Base64Converter(None).to_url(
Transaction().database.name),
'uuid': self.uuid,
+1
View File
@@ -200,6 +200,7 @@ class Request(_Request):
'http_host': self.environ.get('HTTP_HOST'),
'scheme': self.scheme,
'is_secure': self.is_secure,
'root_path': self.root_path,
}
+1 -1
View File
@@ -35,7 +35,7 @@ class ResTestCase(ModuleTestCase):
self.assertEqual(len(user.avatars), 1)
self.assertIsNotNone(user.avatar)
self.assertRegex(user.avatar_url, r'/avatar/.*/([0-9a-fA-F]{12})')
self.assertRegex(user.avatar_url, r'avatar/.*/([0-9a-fA-F]{12})')
@with_transaction()
def test_user_warning(self):
+24 -2
View File
@@ -14,6 +14,7 @@ HOSTNAME = (config.get('web', 'hostname')
or socket.getfqdn())
HOSTNAME = '.'.join(encodings.idna.ToASCII(part).decode('ascii')
if part else '' for part in HOSTNAME.split('.'))
ROOT_PATH = config.get('web', 'root_path')
class URLAccessor(object):
@@ -48,12 +49,31 @@ class URLAccessor(object):
'http' + ('s' if cls.is_secure() else ''),
cls.host(), '', '', ''))
@classmethod
def http_root_path(cls):
context = Transaction().context
if context:
request = context.get('_request')
if request:
return request['root_path']
return ROOT_PATH
@classmethod
def http_base(cls):
return urllib.parse.urljoin(cls.http_host(), cls.http_root_path())
@property
def protocol(self):
if self._protocol == 'http':
return 'http' + ('s' if self.is_secure() else '')
return self._protocol
@property
def root_path(self):
if self._protocol == 'http':
return self.http_root_path()
return '/'
@property
def separator(self):
if self._protocol == 'http':
@@ -82,13 +102,15 @@ class URLAccessor(object):
'%(database)s/%(type)s/%(name)s' % url_part)
if isinstance(inst, Model) and inst.id:
local_part += '/%d' % inst.id
return '%s://%s/%s%s' % (
self.protocol, self.host(), self.separator, local_part)
return (
f'{self.protocol}://{self.host()}{self.root_path}'
f'{self.separator}{local_part}')
is_secure = URLAccessor.is_secure
host = URLAccessor.host
http_host = URLAccessor.http_host
http_base = URLAccessor.http_base
class URLMixin: