diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py index e2da852ebbe..89a5470baa8 100644 --- a/readthedocs/core/resolver.py +++ b/readthedocs/core/resolver.py @@ -136,7 +136,7 @@ def resolve_domain(self, project, private=None): return self._get_project_subdomain(canonical_project) return getattr(settings, 'PRODUCTION_DOMAIN') - def resolve(self, project, protocol='http', filename='', private=None, + def resolve(self, project, require_https=False, filename='', private=None, **kwargs): if private is None: version_slug = kwargs.get('version_slug') @@ -146,24 +146,27 @@ def resolve(self, project, protocol='http', filename='', private=None, canonical_project = self._get_canonical_project(project) custom_domain = self._get_project_custom_domain(canonical_project) + use_custom_domain = self._use_custom_domain(custom_domain) - # This duplication from resolve_domain is for performance purposes - # in order to check whether a custom domain should be HTTPS - if custom_domain: + if use_custom_domain: domain = custom_domain.domain elif self._use_subdomain(): domain = self._get_project_subdomain(canonical_project) else: domain = getattr(settings, 'PRODUCTION_DOMAIN') - if custom_domain: - protocol = 'https' if custom_domain.https else 'http' - else: - # Use HTTPS if settings specify - public_domain = getattr(settings, 'PUBLIC_DOMAIN', None) - use_https = getattr(settings, 'PUBLIC_DOMAIN_USES_HTTPS', False) - if use_https and public_domain and public_domain in domain: - protocol = 'https' + public_domain = getattr(settings, 'PUBLIC_DOMAIN', None) + use_https = getattr(settings, 'PUBLIC_DOMAIN_USES_HTTPS', False) + + use_https_protocol = any([ + # Rely on the ``Domain.https`` field + use_custom_domain and custom_domain.https, + # or force it if specified + require_https, + # or fallback to settings + use_https and public_domain and public_domain in domain, + ]) + protocol = 'https' if use_https_protocol else 'http' return '{protocol}://{domain}{path}'.format( protocol=protocol, @@ -249,6 +252,17 @@ def _fix_filename(self, project, filename): path = "" return path + def _use_custom_domain(self, custom_domain): + """ + Make decision about whether to use a custom domain to serve docs. + + Always use the custom domain if it exists. + + :param custom_domain: Domain instance or ``None`` + :type custom_domain: readthedocs.projects.models.Domain + """ + return True if custom_domain is not None else False + def _use_subdomain(self): """Make decision about whether to use a subdomain to serve docs.""" use_subdomain = getattr(settings, 'USE_SUBDOMAIN', False) diff --git a/readthedocs/rtd_tests/tests/test_project_symlinks.py b/readthedocs/rtd_tests/tests/test_project_symlinks.py index 05f6ed4bbdb..50e661f79b3 100644 --- a/readthedocs/rtd_tests/tests/test_project_symlinks.py +++ b/readthedocs/rtd_tests/tests/test_project_symlinks.py @@ -51,23 +51,26 @@ def get_filesystem(path, top_level_path=None): return fs -class TempSiterootCase(object): +TEMP_SITE_ROOT = tempfile.mkdtemp(suffix='siteroot') +TEMP_DOCROOT = os.path.join(TEMP_SITE_ROOT, 'user_builds') - """Override SITE_ROOT and patch necessary pieces to inspect symlink structure + +@override_settings( + SITE_ROOT=TEMP_SITE_ROOT, + DOCROOT=TEMP_DOCROOT, +) +class TempSiteRootTestCase(TestCase): + + """ + Override SITE_ROOT and patch necessary pieces to inspect symlink structure. This uses some patches and overidden settings to build out symlinking in a temporary path. Each test is therefore isolated, and cleanup will remove these paths after the test case wraps up. - - And subclasses that implement :py:class:`TestCase` should also make use of - :py:func:`override_settings`. """ def setUp(self): self.maxDiff = None - self.site_root = os.path.realpath(tempfile.mkdtemp(suffix='siteroot')) - settings.SITE_ROOT = self.site_root - settings.DOCROOT = os.path.join(settings.SITE_ROOT, 'user_builds') self.mocks = { 'PublicSymlinkBase.CNAME_ROOT': mock.patch( 'readthedocs.core.symlink.PublicSymlinkBase.CNAME_ROOT', @@ -115,16 +118,16 @@ def setUp(self): ) def tearDown(self): - shutil.rmtree(self.site_root) + shutil.rmtree(settings.SITE_ROOT) def assertFilesystem(self, filesystem): """ Creates a nested dictionary that represents the folder structure of rootdir """ - self.assertEqual(filesystem, get_filesystem(self.site_root)) + self.assertEqual(filesystem, get_filesystem(settings.SITE_ROOT)) -class BaseSymlinkCnames(TempSiterootCase): +class BaseSymlinkCnames(object): def setUp(self): super(BaseSymlinkCnames, self).setUp() @@ -290,19 +293,17 @@ def test_symlink_cname_dont_link_missing_domains(self): self.assertFilesystem(filesystem) -@override_settings() -class TestPublicSymlinkCnames(BaseSymlinkCnames, TestCase): +class TestPublicSymlinkCnames(BaseSymlinkCnames, TempSiteRootTestCase): privacy = 'public' symlink_class = PublicSymlink -@override_settings() -class TestPrivateSymlinkCnames(BaseSymlinkCnames, TestCase): +class TestPrivateSymlinkCnames(BaseSymlinkCnames, TempSiteRootTestCase): privacy = 'private' symlink_class = PrivateSymlink -class BaseSubprojects(TempSiterootCase): +class BaseSubprojects(object): def setUp(self): super(BaseSubprojects, self).setUp() @@ -503,19 +504,17 @@ def test_remove_subprojects(self): self.assertFilesystem(filesystem) -@override_settings() -class TestPublicSubprojects(BaseSubprojects, TestCase): +class TestPublicSubprojects(BaseSubprojects, TempSiteRootTestCase): privacy = 'public' symlink_class = PublicSymlink -@override_settings() -class TestPrivateSubprojects(BaseSubprojects, TestCase): +class TestPrivateSubprojects(BaseSubprojects, TempSiteRootTestCase): privacy = 'private' symlink_class = PrivateSymlink -class BaseSymlinkTranslations(TempSiterootCase): +class BaseSymlinkTranslations(object): def setUp(self): super(BaseSymlinkTranslations, self).setUp() @@ -757,19 +756,17 @@ def test_remove_language(self): self.assertFilesystem(filesystem) -@override_settings() -class TestPublicSymlinkTranslations(BaseSymlinkTranslations, TestCase): +class TestPublicSymlinkTranslations(BaseSymlinkTranslations, TempSiteRootTestCase): privacy = 'public' symlink_class = PublicSymlink -@override_settings() -class TestPrivateSymlinkTranslations(BaseSymlinkTranslations, TestCase): +class TestPrivateSymlinkTranslations(BaseSymlinkTranslations, TempSiteRootTestCase): privacy = 'private' symlink_class = PrivateSymlink -class BaseSymlinkSingleVersion(TempSiterootCase): +class BaseSymlinkSingleVersion(object): def setUp(self): super(BaseSymlinkSingleVersion, self).setUp() @@ -834,19 +831,17 @@ def test_symlink_single_version_missing(self): self.assertFilesystem(filesystem) -@override_settings() -class TestPublicSymlinkSingleVersion(BaseSymlinkSingleVersion, TestCase): +class TestPublicSymlinkSingleVersion(BaseSymlinkSingleVersion, TempSiteRootTestCase): privacy = 'public' symlink_class = PublicSymlink -@override_settings() -class TestPublicSymlinkSingleVersion(BaseSymlinkSingleVersion, TestCase): +class TestPublicSymlinkSingleVersion(BaseSymlinkSingleVersion, TempSiteRootTestCase): privacy = 'private' symlink_class = PrivateSymlink -class BaseSymlinkVersions(TempSiterootCase): +class BaseSymlinkVersions(object): def setUp(self): super(BaseSymlinkVersions, self).setUp() @@ -962,20 +957,17 @@ def test_symlink_other_versions(self): self.assertFilesystem(filesystem) -@override_settings() -class TestPublicSymlinkVersions(BaseSymlinkVersions, TestCase): +class TestPublicSymlinkVersions(BaseSymlinkVersions, TempSiteRootTestCase): privacy = 'public' symlink_class = PublicSymlink -@override_settings() -class TestPrivateSymlinkVersions(BaseSymlinkVersions, TestCase): +class TestPrivateSymlinkVersions(BaseSymlinkVersions, TempSiteRootTestCase): privacy = 'private' symlink_class = PrivateSymlink -@override_settings() -class TestPublicSymlinkUnicode(TempSiterootCase, TestCase): +class TestPublicSymlinkUnicode(TempSiteRootTestCase): def setUp(self): super(TestPublicSymlinkUnicode, self).setUp() @@ -1040,8 +1032,7 @@ def test_symlink_broadcast_calls_on_project_save(self): ) -@override_settings() -class TestPublicPrivateSymlink(TempSiterootCase, TestCase): +class TestPublicPrivateSymlink(TempSiteRootTestCase): def setUp(self): super(TestPublicPrivateSymlink, self).setUp()