diff --git a/CHANGELOG b/CHANGELOG
index e64487e9ff..032506905c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,5 @@
+* Add is_secure, host and http_host to url module
+* Add __href__ to URLMixin
* Add validate attribute to wizard's Button
* Return 400 instead of 500 for Tryton exception
* Fill the reverse field in the One2Many setter
diff --git a/trytond/res/email_reset_password.html b/trytond/res/email_reset_password.html
index ec48b3e81d..f1a3ffb360 100644
--- a/trytond/res/email_reset_password.html
+++ b/trytond/res/email_reset_password.html
@@ -10,8 +10,8 @@
The password for your account, ${record.login}, has been reset.
You must set a new one from the user's preferences.
You can connect with this temporary password ${record.password_reset} to
- tryton://${hostname}/${database}
- https://${hostname}/#${database}
+ tryton://${host}/${database}
+ ${http_host}/#${database}
diff --git a/trytond/res/user.py b/trytond/res/user.py
index eb7c503668..e39a1f7e6b 100644
--- a/trytond/res/user.py
+++ b/trytond/res/user.py
@@ -51,7 +51,7 @@ from ..rpc import RPC
from ..exceptions import LoginException, RateLimitException
from trytond.report import Report, get_email
from trytond.sendmail import sendmail_transactional
-from trytond.url import HOSTNAME
+from trytond.url import host, http_host
__all__ = [
'User', 'LoginAttempt', 'UserAction', 'UserGroup', 'Warning_',
@@ -973,7 +973,8 @@ class EmailResetPassword(Report):
Lang = pool.get('ir.lang')
context = super(EmailResetPassword, cls).get_context(records, data)
lang = Lang.get()
- context['hostname'] = HOSTNAME
+ context['host'] = host()
+ context['http_host'] = http_host()
context['database'] = Transaction().database.name
context['expire'] = lang.strftime(
records[0].password_reset_expire,
diff --git a/trytond/tests/test_mixins.py b/trytond/tests/test_mixins.py
index abaef90253..eaf891e6bd 100644
--- a/trytond/tests/test_mixins.py
+++ b/trytond/tests/test_mixins.py
@@ -9,7 +9,7 @@ from trytond.tests.test_tryton import activate_module, with_transaction
from trytond.transaction import Transaction
from trytond.model import ModelView
from trytond.pool import Pool
-from trytond.url import HOSTNAME
+from trytond.url import http_host, HOSTNAME
from .mixin import TestMixin, TestSecondMixin, NotMixin
@@ -36,6 +36,21 @@ class UrlTestCase(unittest.TestCase):
'tryton://%s/%s/model/test.urlobject/1' % (
HOSTNAME, urllib.parse.quote(db_name)))
+ @with_transaction()
+ def testModelHref(self):
+ "Test model href"
+ pool = Pool()
+ UrlObject = pool.get('test.urlobject')
+ db_name = Transaction().database.name
+
+ self.assertEqual(UrlObject.__href__,
+ '%s/#%s/model/test.urlobject' % (
+ http_host(), urllib.parse.quote(db_name)))
+
+ self.assertEqual(UrlObject(1).__href__,
+ '%s/#%s/model/test.urlobject/1' % (
+ http_host(), urllib.parse.quote(db_name)))
+
@with_transaction()
def testWizardURL(self):
"Test wizard URLs"
@@ -47,6 +62,17 @@ class UrlTestCase(unittest.TestCase):
'tryton://%s/%s/wizard/test.test_wizard' % (
HOSTNAME, urllib.parse.quote(db_name)))
+ @with_transaction()
+ def testWizardHref(self):
+ "Test wizard href"
+ pool = Pool()
+ UrlWizard = pool.get('test.test_wizard', type='wizard')
+ db_name = Transaction().database.name
+
+ self.assertEqual(UrlWizard.__href__,
+ '%s/#%s/wizard/test.test_wizard' % (
+ http_host(), urllib.parse.quote(db_name)))
+
class MixinTestCase(unittest.TestCase):
"Test Mixin"
diff --git a/trytond/url.py b/trytond/url.py
index 7f8c034405..27ef789d2f 100644
--- a/trytond/url.py
+++ b/trytond/url.py
@@ -8,7 +8,7 @@ import urllib.parse
from trytond.config import config
from trytond.transaction import Transaction
-__all__ = ['URLMixin', 'HOSTNAME']
+__all__ = ['URLMixin', 'is_secure', 'host', 'http_host']
HOSTNAME = (config.get('web', 'hostname')
or socket.getfqdn())
@@ -17,7 +17,46 @@ HOSTNAME = '.'.join(encodings.idna.ToASCII(part).decode('ascii')
class URLAccessor(object):
- __slots__ = ()
+ __slots__ = ('_protocol',)
+
+ def __init__(self, protocol='tryton'):
+ self._protocol = protocol
+
+ @classmethod
+ def is_secure(cls):
+ context = Transaction().context
+ if context:
+ request = context.get('_request')
+ if request:
+ return request['is_secure']
+ return bool(config.get('ssl', 'certificate'))
+
+ @classmethod
+ def host(cls):
+ context = Transaction().context
+ if context:
+ request = context.get('_request')
+ if request:
+ return request['http_host']
+ return HOSTNAME
+
+ @classmethod
+ def http_host(cls):
+ return urllib.parse.urlunsplit((
+ 'http' + ('s' if cls.is_secure() else ''),
+ cls.host(), '', '', ''))
+
+ @property
+ def protocol(self):
+ if self._protocol == 'http':
+ return 'http' + ('s' if self.is_secure() else '')
+ return self._protocol
+
+ @property
+ def separator(self):
+ if self._protocol == 'http':
+ return '#'
+ return ''
def __get__(self, inst, cls):
from trytond.model import Model
@@ -37,12 +76,20 @@ class URLAccessor(object):
url_part['name'] = cls.__name__
url_part['database'] = Transaction().database.name
- local_part = urllib.parse.quote('%(database)s/%(type)s/%(name)s' % url_part)
+ local_part = urllib.parse.quote(
+ '%(database)s/%(type)s/%(name)s' % url_part)
if isinstance(inst, Model) and inst.id:
local_part += '/%d' % inst.id
- return 'tryton://%s/%s' % (HOSTNAME, local_part)
+ return '%s://%s/%s%s' % (
+ self.protocol, self.host(), self.separator, local_part)
-class URLMixin(object):
+is_secure = URLAccessor.is_secure
+host = URLAccessor.host
+http_host = URLAccessor.http_host
+
+
+class URLMixin:
__slots__ = ()
__url__ = URLAccessor()
+ __href__ = URLAccessor('http')