@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user