From 4ac99a9dc497926f11a87df86aa74e9e79afc32f Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 9 Oct 2015 14:20:27 +0000 Subject: [PATCH] Summary: Add a view for /doc/foo-bar-baz that tries to redirect helpfully instead of just throwing a 404 in case in an inexact match. Right now, it knows how to do prefix matches and handles revisions and extensions. If it can't find a unique match, it redirects to the search page. Branch ready for merge. - Legacy-Id: 10158 --- ietf/doc/tests.py | 43 +++++++++++++++++++++- ietf/doc/urls.py | 1 + ietf/doc/views_search.py | 78 +++++++++++++++++++++++++++++----------- 3 files changed, 100 insertions(+), 22 deletions(-) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 53b023bf9..58de6f0b9 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -10,6 +10,7 @@ else: from pyquery import PyQuery from tempfile import NamedTemporaryFile from Cookie import SimpleCookie +import urlparse from django.core.urlresolvers import reverse as urlreverse from django.conf import settings @@ -28,7 +29,7 @@ from ietf.utils.test_data import make_test_data from ietf.utils.test_utils import login_testing_unauthorized from ietf.utils.test_utils import TestCase -class SearchTestCase(TestCase): +class SearchTests(TestCase): def test_search(self): draft = make_test_data() @@ -104,6 +105,46 @@ class SearchTestCase(TestCase): self.assertEqual(r.status_code, 200) self.assertTrue(draft.title in r.content) + def test_search_for_name(self): + draft = make_test_data() + save_document_in_history(draft) + prev_rev = draft.rev + draft.rev = "%02d" % (int(prev_rev) + 1) + draft.save() + + # exact match + r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name))) + self.assertEqual(r.status_code, 302) + self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name))) + + # prefix match + r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name="-".join(draft.name.split("-")[:-1])))) + self.assertEqual(r.status_code, 302) + self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name))) + + # match with revision + r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name + "-" + prev_rev))) + self.assertEqual(r.status_code, 302) + self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name, rev=prev_rev))) + + # match with non-existing revision + r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name + "-09"))) + self.assertEqual(r.status_code, 302) + self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name))) + + # match with revision and extension + r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name + "-" + prev_rev + ".txt"))) + self.assertEqual(r.status_code, 302) + self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name, rev=prev_rev))) + + # no match + r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name="draft-ietf-doesnotexist-42"))) + self.assertEqual(r.status_code, 302) + + parsed = urlparse.urlparse(r["Location"]) + self.assertEqual(parsed.path, urlreverse("doc_search")) + self.assertEqual(urlparse.parse_qs(parsed.query)["name"][0], "draft-ietf-doesnotexist-42") + def test_frontpage(self): make_test_data() r = self.client.get("/") diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index d9d8489ec..7a699b35e 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -39,6 +39,7 @@ from ietf.doc import views_doc urlpatterns = patterns('', (r'^/?$', views_search.search), + url(r'^(?P[A-Za-z0-9\._\+\-]+)$', views_search.search_for_name, name="doc_search_for_name"), url(r'^search/$', views_search.search, name="doc_search"), url(r'^in-last-call/$', views_search.drafts_in_last_call, name="drafts_in_last_call"), url(r'^ad/(?P[A-Za-z0-9.-]+)/$', views_search.docs_for_ad, name="docs_for_ad"), diff --git a/ietf/doc/views_search.py b/ietf/doc/views_search.py index 732234d9f..f5c07664c 100644 --- a/ietf/doc/views_search.py +++ b/ietf/doc/views_search.py @@ -30,20 +30,20 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import datetime +import datetime, re from django import forms from django.core.exceptions import ObjectDoesNotExist -from django.shortcuts import render_to_response +from django.core.urlresolvers import reverse as urlreverse +from django.shortcuts import render from django.db.models import Q -from django.template import RequestContext -from django.http import Http404, HttpResponseBadRequest, HttpResponse +from django.http import Http404, HttpResponseBadRequest, HttpResponse, HttpResponseRedirect import debug # pyflakes:ignore from ietf.community.models import CommunityList -from ietf.doc.models import ( Document, DocAlias, State, RelatedDocument, DocEvent, - LastCallDocEvent, TelechatDocEvent, IESG_SUBSTATE_TAGS ) +from ietf.doc.models import ( Document, DocHistory, DocAlias, State, RelatedDocument, + DocEvent, LastCallDocEvent, TelechatDocEvent, IESG_SUBSTATE_TAGS ) from ietf.doc.expire import expirable_draft from ietf.doc.fields import select2_id_doc_name_json from ietf.group.models import Group @@ -384,13 +384,50 @@ def search(request): doc_is_tracked = get_doc_is_tracked(request, results) - return render_to_response('doc/search/search.html', - {'form':form, 'docs':results, 'doc_is_tracked':doc_is_tracked, 'meta':meta, }, - context_instance=RequestContext(request)) + return render(request, 'doc/search/search.html', { + 'form':form, 'docs':results, 'doc_is_tracked':doc_is_tracked, 'meta':meta, }, + ) def frontpage(request): form = SearchForm() - return render_to_response('doc/frontpage.html', {'form':form}, context_instance=RequestContext(request)) + return render(request, 'doc/frontpage.html', {'form':form}) + +def search_for_name(request, name): + def find_unique(n): + exact = DocAlias.objects.filter(name=n).first() + if exact: + return exact.name + + aliases = DocAlias.objects.filter(name__startswith=n)[:2] + if len(aliases) == 1: + return aliases[0].name + return None + + n = name + + # chop away extension + extension_split = re.search("^(.+)\.(txt|ps|pdf)$", n) + if extension_split: + n = extension_split.group(1) + + redirect_to = find_unique(name) + if redirect_to: + return HttpResponseRedirect(urlreverse("doc_view", kwargs={ "name": redirect_to })) + else: + # check for embedded rev - this may be ambigious, so don't + # chop it off if we don't find a match + rev_split = re.search("^(.+)-([0-9]{2})$", n) + if rev_split: + redirect_to = find_unique(rev_split.group(1)) + if redirect_to: + rev = rev_split.group(2) + # check if we can redirect directly to the rev + if DocHistory.objects.filter(doc__docalias__name=redirect_to, rev=rev).exists(): + return HttpResponseRedirect(urlreverse("doc_view", kwargs={ "name": redirect_to, "rev": rev })) + else: + return HttpResponseRedirect(urlreverse("doc_view", kwargs={ "name": redirect_to })) + + return HttpResponseRedirect(urlreverse("doc_search") + "?name=%s&rfcs=on&activedrafts=on" % n) def ad_dashboard_group(doc): @@ -500,18 +537,18 @@ def docs_for_ad(request, name): for d in results: d.search_heading = ad_dashboard_group(d) # - return render_to_response('doc/drafts_for_ad.html', - { 'form':form, 'docs':results, 'meta':meta, 'ad_name': ad.plain_name() }, - context_instance=RequestContext(request)) + return render(request, 'doc/drafts_for_ad.html', { + 'form':form, 'docs':results, 'meta':meta, 'ad_name': ad.plain_name() + }) def drafts_in_last_call(request): lc_state = State.objects.get(type="draft-iesg", slug="lc").pk form = SearchForm({'by':'state','state': lc_state, 'rfcs':'on', 'activedrafts':'on'}) results, meta = retrieve_search_results(form) - return render_to_response('doc/drafts_in_last_call.html', - { 'form':form, 'docs':results, 'meta':meta }, - context_instance=RequestContext(request)) + return render(request, 'doc/drafts_in_last_call.html', { + 'form':form, 'docs':results, 'meta':meta + }) def drafts_in_iesg_process(request, last_call_only=None): if last_call_only: @@ -535,11 +572,11 @@ def drafts_in_iesg_process(request, last_call_only=None): grouped_docs.append((s, docs)) - return render_to_response('doc/drafts_in_iesg_process.html', { + return render(request, 'doc/drafts_in_iesg_process.html', { "grouped_docs": grouped_docs, "title": title, "last_call_only": last_call_only, - }, context_instance=RequestContext(request)) + }) def index_all_drafts(request): # try to be efficient since this view returns a lot of data @@ -582,13 +619,12 @@ def index_all_drafts(request): len(names), "
".join(names) )) - return render_to_response('doc/index_all_drafts.html', { "categories": categories }, - context_instance=RequestContext(request)) + return render(request, 'doc/index_all_drafts.html', { "categories": categories }) def index_active_drafts(request): groups = active_drafts_index_by_group() - return render_to_response("doc/index_active_drafts.html", { 'groups': groups }, context_instance=RequestContext(request)) + return render(request, "doc/index_active_drafts.html", { 'groups': groups }) def ajax_select2_search_docs(request, model_name, doc_type): if model_name == "docalias":