Skip to content

Commit 083abf7

Browse files
committed
Merged in [10158] from olau@iola.dk:
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 - Legacy-Id: 10253 Note: SVN reference [10158] has been migrated to Git commit 4ac99a9
2 parents bd4cf33 + 4ac99a9 commit 083abf7

3 files changed

Lines changed: 100 additions & 22 deletions

File tree

ietf/doc/tests.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pyquery import PyQuery
1111
from tempfile import NamedTemporaryFile
1212
from Cookie import SimpleCookie
13+
import urlparse
1314

1415
from django.core.urlresolvers import reverse as urlreverse
1516
from django.conf import settings
@@ -28,7 +29,7 @@
2829
from ietf.utils.test_utils import login_testing_unauthorized
2930
from ietf.utils.test_utils import TestCase
3031

31-
class SearchTestCase(TestCase):
32+
class SearchTests(TestCase):
3233
def test_search(self):
3334
draft = make_test_data()
3435

@@ -104,6 +105,46 @@ def test_search(self):
104105
self.assertEqual(r.status_code, 200)
105106
self.assertTrue(draft.title in r.content)
106107

108+
def test_search_for_name(self):
109+
draft = make_test_data()
110+
save_document_in_history(draft)
111+
prev_rev = draft.rev
112+
draft.rev = "%02d" % (int(prev_rev) + 1)
113+
draft.save()
114+
115+
# exact match
116+
r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name)))
117+
self.assertEqual(r.status_code, 302)
118+
self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name)))
119+
120+
# prefix match
121+
r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name="-".join(draft.name.split("-")[:-1]))))
122+
self.assertEqual(r.status_code, 302)
123+
self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name)))
124+
125+
# match with revision
126+
r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name + "-" + prev_rev)))
127+
self.assertEqual(r.status_code, 302)
128+
self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name, rev=prev_rev)))
129+
130+
# match with non-existing revision
131+
r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name + "-09")))
132+
self.assertEqual(r.status_code, 302)
133+
self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name)))
134+
135+
# match with revision and extension
136+
r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name + "-" + prev_rev + ".txt")))
137+
self.assertEqual(r.status_code, 302)
138+
self.assertEqual(urlparse.urlparse(r["Location"]).path, urlreverse("doc_view", kwargs=dict(name=draft.name, rev=prev_rev)))
139+
140+
# no match
141+
r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name="draft-ietf-doesnotexist-42")))
142+
self.assertEqual(r.status_code, 302)
143+
144+
parsed = urlparse.urlparse(r["Location"])
145+
self.assertEqual(parsed.path, urlreverse("doc_search"))
146+
self.assertEqual(urlparse.parse_qs(parsed.query)["name"][0], "draft-ietf-doesnotexist-42")
147+
107148
def test_frontpage(self):
108149
make_test_data()
109150
r = self.client.get("/")

ietf/doc/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
urlpatterns = patterns('',
4141
(r'^/?$', views_search.search),
42+
url(r'^(?P<name>[A-Za-z0-9\._\+\-]+)$', views_search.search_for_name, name="doc_search_for_name"),
4243
url(r'^search/$', views_search.search, name="doc_search"),
4344
url(r'^in-last-call/$', views_search.drafts_in_last_call, name="drafts_in_last_call"),
4445
url(r'^ad/(?P<name>[A-Za-z0-9.-]+)/$', views_search.docs_for_ad, name="docs_for_ad"),

ietf/doc/views_search.py

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,20 @@
3030
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3131
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3232

33-
import datetime
33+
import datetime, re
3434

3535
from django import forms
3636
from django.core.exceptions import ObjectDoesNotExist
37-
from django.shortcuts import render_to_response
37+
from django.core.urlresolvers import reverse as urlreverse
38+
from django.shortcuts import render
3839
from django.db.models import Q
39-
from django.template import RequestContext
40-
from django.http import Http404, HttpResponseBadRequest, HttpResponse
40+
from django.http import Http404, HttpResponseBadRequest, HttpResponse, HttpResponseRedirect
4141

4242
import debug # pyflakes:ignore
4343

4444
from ietf.community.models import CommunityList
45-
from ietf.doc.models import ( Document, DocAlias, State, RelatedDocument, DocEvent,
46-
LastCallDocEvent, TelechatDocEvent, IESG_SUBSTATE_TAGS )
45+
from ietf.doc.models import ( Document, DocHistory, DocAlias, State, RelatedDocument,
46+
DocEvent, LastCallDocEvent, TelechatDocEvent, IESG_SUBSTATE_TAGS )
4747
from ietf.doc.expire import expirable_draft
4848
from ietf.doc.fields import select2_id_doc_name_json
4949
from ietf.group.models import Group
@@ -384,13 +384,50 @@ def search(request):
384384

385385
doc_is_tracked = get_doc_is_tracked(request, results)
386386

387-
return render_to_response('doc/search/search.html',
388-
{'form':form, 'docs':results, 'doc_is_tracked':doc_is_tracked, 'meta':meta, },
389-
context_instance=RequestContext(request))
387+
return render(request, 'doc/search/search.html', {
388+
'form':form, 'docs':results, 'doc_is_tracked':doc_is_tracked, 'meta':meta, },
389+
)
390390

391391
def frontpage(request):
392392
form = SearchForm()
393-
return render_to_response('doc/frontpage.html', {'form':form}, context_instance=RequestContext(request))
393+
return render(request, 'doc/frontpage.html', {'form':form})
394+
395+
def search_for_name(request, name):
396+
def find_unique(n):
397+
exact = DocAlias.objects.filter(name=n).first()
398+
if exact:
399+
return exact.name
400+
401+
aliases = DocAlias.objects.filter(name__startswith=n)[:2]
402+
if len(aliases) == 1:
403+
return aliases[0].name
404+
return None
405+
406+
n = name
407+
408+
# chop away extension
409+
extension_split = re.search("^(.+)\.(txt|ps|pdf)$", n)
410+
if extension_split:
411+
n = extension_split.group(1)
412+
413+
redirect_to = find_unique(name)
414+
if redirect_to:
415+
return HttpResponseRedirect(urlreverse("doc_view", kwargs={ "name": redirect_to }))
416+
else:
417+
# check for embedded rev - this may be ambigious, so don't
418+
# chop it off if we don't find a match
419+
rev_split = re.search("^(.+)-([0-9]{2})$", n)
420+
if rev_split:
421+
redirect_to = find_unique(rev_split.group(1))
422+
if redirect_to:
423+
rev = rev_split.group(2)
424+
# check if we can redirect directly to the rev
425+
if DocHistory.objects.filter(doc__docalias__name=redirect_to, rev=rev).exists():
426+
return HttpResponseRedirect(urlreverse("doc_view", kwargs={ "name": redirect_to, "rev": rev }))
427+
else:
428+
return HttpResponseRedirect(urlreverse("doc_view", kwargs={ "name": redirect_to }))
429+
430+
return HttpResponseRedirect(urlreverse("doc_search") + "?name=%s&rfcs=on&activedrafts=on" % n)
394431

395432
def ad_dashboard_group(doc):
396433

@@ -500,18 +537,18 @@ def docs_for_ad(request, name):
500537
for d in results:
501538
d.search_heading = ad_dashboard_group(d)
502539
#
503-
return render_to_response('doc/drafts_for_ad.html',
504-
{ 'form':form, 'docs':results, 'meta':meta, 'ad_name': ad.plain_name() },
505-
context_instance=RequestContext(request))
540+
return render(request, 'doc/drafts_for_ad.html', {
541+
'form':form, 'docs':results, 'meta':meta, 'ad_name': ad.plain_name()
542+
})
506543

507544
def drafts_in_last_call(request):
508545
lc_state = State.objects.get(type="draft-iesg", slug="lc").pk
509546
form = SearchForm({'by':'state','state': lc_state, 'rfcs':'on', 'activedrafts':'on'})
510547
results, meta = retrieve_search_results(form)
511548

512-
return render_to_response('doc/drafts_in_last_call.html',
513-
{ 'form':form, 'docs':results, 'meta':meta },
514-
context_instance=RequestContext(request))
549+
return render(request, 'doc/drafts_in_last_call.html', {
550+
'form':form, 'docs':results, 'meta':meta
551+
})
515552

516553
def drafts_in_iesg_process(request, last_call_only=None):
517554
if last_call_only:
@@ -535,11 +572,11 @@ def drafts_in_iesg_process(request, last_call_only=None):
535572

536573
grouped_docs.append((s, docs))
537574

538-
return render_to_response('doc/drafts_in_iesg_process.html', {
575+
return render(request, 'doc/drafts_in_iesg_process.html', {
539576
"grouped_docs": grouped_docs,
540577
"title": title,
541578
"last_call_only": last_call_only,
542-
}, context_instance=RequestContext(request))
579+
})
543580

544581
def index_all_drafts(request):
545582
# try to be efficient since this view returns a lot of data
@@ -582,13 +619,12 @@ def index_all_drafts(request):
582619
len(names),
583620
"<br>".join(names)
584621
))
585-
return render_to_response('doc/index_all_drafts.html', { "categories": categories },
586-
context_instance=RequestContext(request))
622+
return render(request, 'doc/index_all_drafts.html', { "categories": categories })
587623

588624
def index_active_drafts(request):
589625
groups = active_drafts_index_by_group()
590626

591-
return render_to_response("doc/index_active_drafts.html", { 'groups': groups }, context_instance=RequestContext(request))
627+
return render(request, "doc/index_active_drafts.html", { 'groups': groups })
592628

593629
def ajax_select2_search_docs(request, model_name, doc_type):
594630
if model_name == "docalias":

0 commit comments

Comments
 (0)