Skip to content

Show suggestion on the 404 page #678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 26, 2014
17 changes: 17 additions & 0 deletions readthedocs/core/templatetags/core_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,20 @@ def gravatar(email, size=48):
})
return ('<img src="%s" width="%s" height="%s" alt="gravatar" '
'class="gravatar" border="0" />' % (url, size, size))

@register.simple_tag(name="doc_url")
def make_document_url(project, version=None, page=None):
if project.main_language_project:
base_url = project.get_translation_url(version)
else:
base_url = project.get_docs_url(version)
if page and page != "index":
if project.documentation_type == "sphinx_htmldir":
path = page + "/"
elif project.documentation_type == "sphinx_singlehtml":
path = "index.html#document-" + page
else:
path = page + ".html"
else:
path = ""
return base_url + path
117 changes: 114 additions & 3 deletions readthedocs/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import os
import logging
import redis
import re

log = logging.getLogger(__name__)
pc_log = logging.getLogger(__name__+'.post_commit')
Expand Down Expand Up @@ -366,9 +367,14 @@ def redirect_page_with_filename(request, filename, project_slug=None):
def serve_docs(request, lang_slug, version_slug, filename, project_slug=None):
if not project_slug:
project_slug = request.slug
proj = get_object_or_404(Project, slug=project_slug)
ver = get_object_or_404(Version, project__slug=project_slug,
slug=version_slug)
try:
proj = Project.objects.get(slug=project_slug)
ver = Version.objects.get(project__slug=project_slug, slug=version_slug)
except (Project.DoesNotExist, Version.DoesNotExist):
proj = None
ver = None
if not proj or not ver:
return server_helpful_404(request, project_slug, lang_slug, version_slug, filename)

# Auth checks
if ver not in proj.versions.public(request.user, proj, only_active=False):
Expand Down Expand Up @@ -451,6 +457,111 @@ def server_error_404(request, template_name='404.html'):
return r


def server_helpful_404(request, project_slug=None, lang_slug=None, version_slug=None, filename=None, template_name='404.html'):
pagename = re.sub(r'/index$', r'', re.sub(r'\.html$', r'', re.sub(r'/$', r'', filename)))
suggestion = get_suggestion(project_slug, lang_slug, version_slug, pagename, request.user)
r = render_to_response(template_name,
{'suggestion': suggestion},
context_instance=RequestContext(request))
r.status_code = 404
return r


def get_suggestion(project_slug, lang_slug, version_slug, pagename, user):
"""
| # | project | version | language | What to show |
| 1 | 0 | 0 | 0 | Error message |
| 2 | 0 | 0 | 1 | Error message (Can't happen) |
| 3 | 0 | 1 | 0 | Error message (Can't happen) |
| 4 | 0 | 1 | 1 | Error message (Can't happen) |
| 5 | 1 | 0 | 0 | A link to top-level page of default version |
| 6 | 1 | 0 | 1 | Available versions on the translation project |
| 7 | 1 | 1 | 0 | Available translations of requested version |
| 8 | 1 | 1 | 1 | A link to top-level page of requested version |
"""

suggestion = {}
if project_slug:
try:
proj = Project.objects.get(slug=project_slug)
if not lang_slug:
lang_slug = proj.language
try:
ver = Version.objects.get(project__slug=project_slug, slug=version_slug)
except Version.DoesNotExist:
ver = None

if ver: # if requested version is available on main project
if lang_slug != proj.language:
try:
translations = proj.translations.filter(language=lang_slug)
if translations:
ver = Version.objects.get(project__slug=translations[0].slug, slug=version_slug)
else:
ver = None
except Version.DoesNotExist:
ver = None
if ver: #if requested version is available on translation project too
# Case #8: Show a link to top-level page of the version
suggestion['type'] = 'top'
suggestion['message'] = "What are you looking for?"
suggestion['href'] = proj.get_docs_url(ver.slug, lang_slug)
else: # requested version is available but not in requested language
# Case #7: Show available translations of the version
suggestion['type'] = 'list'
suggestion['message'] = "Requested page seems not to be translated in requested language. But it's available in these languages."
suggestion['list'] = []
suggestion['list'].append({
'label':proj.language,
'project': proj,
'version_slug': version_slug,
'pagename': pagename
})
for t in proj.translations.all():
try:
Version.objects.get(project__slug=t.slug, slug=version_slug)
suggestion['list'].append({
'label':t.language,
'project': t,
'version_slug': version_slug,
'pagename': pagename
})
except Version.DoesNotExist:
pass
else: # requested version does not exist on main project
if lang_slug == proj.language:
trans = proj
else:
translations = proj.translations.filter(language=lang_slug)
trans = translations[0] if translations else None
if trans: # requested language is available
# Case #6: Show available versions of the translation
suggestion['type'] = 'list'
suggestion['message'] = "Requested version seems not to have been built yet. But these versions are available."
suggestion['list'] = []
for v in Version.objects.public(user, trans, True):
suggestion['list'].append({
'label': v.slug,
'project': trans,
'version_slug': v.slug,
'pagename': pagename
})
else: # requested project exists but requested version and language are not available.
# Case #5: Show a link to top-level page of default version of main project
suggestion['type'] = 'top'
suggestion['message'] = 'What are you looking for??'
suggestion['href'] = proj.get_docs_url()
except Project.DoesNotExist:
# Case #1-4: Show error mssage
suggestion['type'] = 'none'
suggestion['message'] = "What are you looking for???"
else:
suggestion['type'] = 'none'
suggestion['message'] = "What are you looking for????"

return suggestion


def divide_by_zero(request):
return 1 / 0

Expand Down
65 changes: 65 additions & 0 deletions readthedocs/rtd_tests/tests/test_404.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import lxml.html

from django.test import TestCase

from builds.models import Version
from projects.models import Project


class Testmaker(TestCase):
fixtures = ["eric", "test_data"]

def setUp(self):
self.client.login(username='eric', password='test')
self.pip = Project.objects.get(slug='pip')
self.latest = Version.objects.create(project=self.pip, identifier='latest',
verbose_name='latest', slug='latest',
active=True)
self.pip_es = Project.objects.create(name="PIP-ES", slug='pip-es', language='es', main_language_project=self.pip)
self.latest_es = Version.objects.create(project=self.pip_es, identifier='latest',
verbose_name='latest', slug='latest',
active=True)

def test_project_does_not_exist(self):
# Case 1-4: Project doesn't exist
r = self.client.get('/docs/nonexistent_proj/en/nonexistent_dir/subdir/bogus.html')
self.assertContains(r, '<p>What are you looking for???</p>', status_code=404, html=False)

def test_only_project_exist(self):
# Case 5: Project exists but both of version and language are not available
r = self.client.get('/docs/pip/fr/nonexistent_ver/nonexistent_dir/bogus.html', {})
self.assertContains(r, '<p>What are you looking for??</p>', status_code=404, html=False)

def test_not_built_in_main_language(self):
# Case 6: Project exists and main language is available but the version is not available
r = self.client.get('/docs/pip/en/nonexistent_ver/nonexistent_dir/bogus.html', {})
self.assertContains(r, '<p>Requested version seems not to have been built yet.', status_code=404, html=False)

def test_not_built_in_other_language(self):
# Case 6: Project exists and translation is available but the version is not available
r = self.client.get('/docs/pip/es/nonexistent_ver/nonexistent_dir/bogus.html', {})
self.assertContains(r, '<p>Requested version seems not to have been built yet.', status_code=404, html=False)

def test_not_translated(self):
# Case 7: Project exists and the version is available but not in the language
r = self.client.get('/docs/pip/fr/latest/nonexistent_dir/bogus.html', {})
self.assertEqual(r.status_code, 200)
self.assertEqual(r['X-Accel-Redirect'], '/user_builds/pip/translations/fr/latest/nonexistent_dir/bogus.html')
r = self.client.get('/user_builds/pip/translations/fr/latest/nonexistent_dir/bogus.html', {})
self.assertContains(r, '<p>Requested page seems not to be translated in requested language.', status_code=404, html=False)

def test_no_dir_or_file_in_main_language(self):
# Case 8: Everything is OK but sub-dir or file doesn't exist
r = self.client.get('/docs/pip/en/latest/nonexistent_dir/bogus.html', {})
self.assertEqual(r.status_code, 200)
self.assertEqual(r['X-Accel-Redirect'], '/user_builds/pip/rtd-builds/latest/nonexistent_dir/bogus.html')
r = self.client.get('/user_builds/pip/rtd-builds/latest/nonexistent_dir/bogus.html', {})
self.assertContains(r, '<p>What are you looking for?</p>', status_code=404, html=False)

def test_no_dir_or_file_in_other_language(self):
# Case 8: Everything is OK but sub-dir or file doesn't exist
r = self.client.get('/docs/pip/es/latest/nonexistent_dir/bogus.html', {})
self.assertEqual(r.status_code, 200)
self.assertEqual(r['X-Accel-Redirect'], '/user_builds/pip/translations/es/latest/nonexistent_dir/bogus.html')
r = self.client.get('/user_builds/pip/translations/es/latest/nonexistent_dir/bogus.html', {})
self.assertContains(r, '<p>What are you looking for?</p>', status_code=404, html=False)
157 changes: 157 additions & 0 deletions readthedocs/rtd_tests/tests/test_core_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from django.test import TestCase
from projects.models import Project
from builds.models import Version
from core.templatetags import core_tags

class CoreTagsTests(TestCase):
fixtures = ["eric", "test_data"]

def setUp(self):
self.client.login(username='eric', password='test')
self.pip = Project.objects.get(slug='pip')
self.latest = Version.objects.create(project=self.pip, identifier='latest',
verbose_name='latest', slug='latest',
active=True)
self.pip_fr = Project.objects.create(name="PIP-FR", slug='pip-fr', language='fr', main_language_project=self.pip)
self.latest_fr = Version.objects.create(project=self.pip_fr, identifier='latest',
verbose_name='latest', slug='latest',
active=True)

def test_project_only(self):
proj = Project.objects.get(slug='pip')
url = core_tags.make_document_url(proj)
self.assertEqual(url, '/docs/pip/en/latest/')
url = core_tags.make_document_url(proj, '')
self.assertEqual(url, '/docs/pip/en/latest/')

def test_project_only_htmldir(self):
proj = Project.objects.get(slug='pip')
proj.documentation_type = 'sphinx_htmldir'
url = core_tags.make_document_url(proj)
self.assertEqual(url, '/docs/pip/en/latest/')
url = core_tags.make_document_url(proj, '')
self.assertEqual(url, '/docs/pip/en/latest/')

def test_project_only_singlehtml(self):
proj = Project.objects.get(slug='pip')
proj.documentation_type = 'sphinx_singlehtml'
url = core_tags.make_document_url(proj)
self.assertEqual(url, '/docs/pip/en/latest/')
url = core_tags.make_document_url(proj, '')
self.assertEqual(url, '/docs/pip/en/latest/')

def test_translation_project_only(self):
proj = Project.objects.get(slug='pip-fr')
url = core_tags.make_document_url(proj)
self.assertEqual(url, '/docs/pip/fr/latest/')
url = core_tags.make_document_url(proj, '')
self.assertEqual(url, '/docs/pip/fr/latest/')

def test_translation_project_only_htmldir(self):
proj = Project.objects.get(slug='pip-fr')
proj.documentation_type = 'sphinx_htmldir'
url = core_tags.make_document_url(proj)
self.assertEqual(url, '/docs/pip/fr/latest/')
url = core_tags.make_document_url(proj, '')
self.assertEqual(url, '/docs/pip/fr/latest/')

def test_translation_project_only_singlehtml(self):
proj = Project.objects.get(slug='pip-fr')
proj.documentation_type = 'sphinx_singlehtml'
url = core_tags.make_document_url(proj)
self.assertEqual(url, '/docs/pip/fr/latest/')
url = core_tags.make_document_url(proj, '')
self.assertEqual(url, '/docs/pip/fr/latest/')

def test_project_and_version(self):
proj = Project.objects.get(slug='pip')
url = core_tags.make_document_url(proj, 'abc')
self.assertEqual(url, '/docs/pip/en/abc/')
url = core_tags.make_document_url(proj, 'abc', '')
self.assertEqual(url, '/docs/pip/en/abc/')

def test_project_and_version_htmldir(self):
proj = Project.objects.get(slug='pip')
proj.documentation_type = 'sphinx_htmldir'
url = core_tags.make_document_url(proj, 'abc')
self.assertEqual(url, '/docs/pip/en/abc/')
url = core_tags.make_document_url(proj, 'abc', '')
self.assertEqual(url, '/docs/pip/en/abc/')

def test_project_and_version_singlehtml(self):
proj = Project.objects.get(slug='pip')
proj.documentation_type = 'sphinx_singlehtml'
url = core_tags.make_document_url(proj, 'abc')
self.assertEqual(url, '/docs/pip/en/abc/')
url = core_tags.make_document_url(proj, 'abc', '')
self.assertEqual(url, '/docs/pip/en/abc/')

def test_translation_project_and_version(self):
proj = Project.objects.get(slug='pip-fr')
url = core_tags.make_document_url(proj, 'abc')
self.assertEqual(url, '/docs/pip/fr/abc/')
url = core_tags.make_document_url(proj, 'abc', '')
self.assertEqual(url, '/docs/pip/fr/abc/')

def test_translation_project_and_version_htmldir(self):
proj = Project.objects.get(slug='pip-fr')
proj.documentation_type = 'sphinx_htmldir'
url = core_tags.make_document_url(proj, 'abc')
self.assertEqual(url, '/docs/pip/fr/abc/')
url = core_tags.make_document_url(proj, 'abc', '')
self.assertEqual(url, '/docs/pip/fr/abc/')

def test_translation_project_and_version_singlehtml(self):
proj = Project.objects.get(slug='pip-fr')
proj.documentation_type = 'sphinx_singlehtml'
url = core_tags.make_document_url(proj, 'abc')
self.assertEqual(url, '/docs/pip/fr/abc/')
url = core_tags.make_document_url(proj, 'abc', '')
self.assertEqual(url, '/docs/pip/fr/abc/')

def test_project_and_version_and_page(self):
proj = Project.objects.get(slug='pip')
url = core_tags.make_document_url(proj, 'abc', 'xyz')
self.assertEqual(url, '/docs/pip/en/abc/xyz.html')
url = core_tags.make_document_url(proj, 'abc', 'index')
self.assertEqual(url, '/docs/pip/en/abc/')

def test_project_and_version_and_page_htmldir(self):
proj = Project.objects.get(slug='pip')
proj.documentation_type = 'sphinx_htmldir'
url = core_tags.make_document_url(proj, 'abc', 'xyz')
self.assertEqual(url, '/docs/pip/en/abc/xyz/')
url = core_tags.make_document_url(proj, 'abc', 'index')
self.assertEqual(url, '/docs/pip/en/abc/')

def test_project_and_version_and_page_signlehtml(self):
proj = Project.objects.get(slug='pip')
proj.documentation_type = 'sphinx_singlehtml'
url = core_tags.make_document_url(proj, 'abc', 'xyz')
self.assertEqual(url, '/docs/pip/en/abc/index.html#document-xyz')
url = core_tags.make_document_url(proj, 'abc', 'index')
self.assertEqual(url, '/docs/pip/en/abc/')

def test_translation_project_and_version_and_page(self):
proj = Project.objects.get(slug='pip-fr')
url = core_tags.make_document_url(proj, 'abc', 'xyz')
self.assertEqual(url, '/docs/pip/fr/abc/xyz.html')
url = core_tags.make_document_url(proj, 'abc', 'index')
self.assertEqual(url, '/docs/pip/fr/abc/')

def test_translation_project_and_version_and_page_htmldir(self):
proj = Project.objects.get(slug='pip-fr')
proj.documentation_type = 'sphinx_htmldir'
url = core_tags.make_document_url(proj, 'abc', 'xyz')
self.assertEqual(url, '/docs/pip/fr/abc/xyz/')
url = core_tags.make_document_url(proj, 'abc', 'index')
self.assertEqual(url, '/docs/pip/fr/abc/')

def test_translation_project_and_version_and_page_singlehtml(self):
proj = Project.objects.get(slug='pip-fr')
proj.documentation_type = 'sphinx_singlehtml'
url = core_tags.make_document_url(proj, 'abc', 'xyz')
self.assertEqual(url, '/docs/pip/fr/abc/index.html#document-xyz')
url = core_tags.make_document_url(proj, 'abc', 'index')
self.assertEqual(url, '/docs/pip/fr/abc/')

Loading