Skip to content

Commit 814e131

Browse files
committed
Added a new document tab for some documents, showing htmlized drafts and RFCs
using the htmlization code previously developed for tools.ietf.org. As the generation of the htmlized page is a bit too costly to do on the fly for often-referenced drafts and RFCs, the part of each page which contains the htmlized document is cached on file with a cache time of 2 weeks. Changed all links which pointed to the htmlized version on tools to instead point at the datatracker htmlized document. Tweaked some URLs which didn't permit retrieval of intermediate-rev-charters. Narrowed the pattern for document names to disallow dots in names, and instead explicitly enumerated the few historical draftw with dots in the name. Added a file-system cache for the htmlized documents, and specified a max_entries value for caches, overriding the default 300 entries. Tweaked the code for new author email entries to provide a time if missing in an updated entry. Changed links in various email templates which pointed at tools.ietf.org pages to instead point at datatracker pages, where appropriate. Changed the search result rows to provide links to both the current meta- information document pages (with a (i) info symbol) and to the new htmlized document pages. - Legacy-Id: 13040
1 parent a67e9dd commit 814e131

20 files changed

Lines changed: 444 additions & 33 deletions

ietf/doc/templatetags/ietf_filters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def rfclink(string):
197197
URL for that RFC.
198198
"""
199199
string = str(string);
200-
return "https://tools.ietf.org/html/rfc" + string;
200+
return "/doc/html/rfc" + string;
201201

202202
@register.filter(name='urlize_ietf_docs', is_safe=True, needs_autoescape=True)
203203
def urlize_ietf_docs(string, autoescape=None):

ietf/doc/tests.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,15 @@ def test_document_draft(self):
493493
self.assertTrue("Show full document text" in unicontent(r))
494494
self.assertFalse("Deimos street" in unicontent(r))
495495

496+
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=draft.name)))
497+
self.assertEqual(r.status_code, 200)
498+
self.assertTrue("Versions:" in unicontent(r))
499+
self.assertTrue("Deimos street" in unicontent(r))
500+
q = PyQuery(r.content)
501+
self.assertEqual(len(q('.rfcmarkup pre')), 4)
502+
self.assertEqual(len(q('span.h1')), 2)
503+
self.assertEqual(len(q('a[href]')), 116)
504+
496505
# expired draft
497506
draft.set_state(State.objects.get(type="draft", slug="expired"))
498507

ietf/doc/urls.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,14 @@
5959
url(r'^stats/newrevisiondocevent/data/?$', views_stats.chart_data_newrevisiondocevent),
6060
url(r'^stats/person/(?P<id>[0-9]+)/drafts/conf/?$', views_stats.chart_conf_person_drafts),
6161
url(r'^stats/person/(?P<id>[0-9]+)/drafts/data/?$', views_stats.chart_data_person_drafts),
62+
url(r'^html/%(name)s(?:-%(rev)s)?(\.txt|\.html)?$' % settings.URL_REGEXPS, views_doc.document_html),
6263

6364
url(r'^all/$', views_search.index_all_drafts),
6465
url(r'^active/$', views_search.index_active_drafts),
6566
url(r'^select2search/(?P<model_name>(document|docalias))/(?P<doc_type>draft)/$', views_search.ajax_select2_search_docs),
6667

67-
url(r'^%(name)s/(?:%(rev)s/)?$' % settings.URL_REGEXPS, views_doc.document_main),
68-
url(r'^%(name)s/(?:%(rev)s/)?bibtex/$' % settings.URL_REGEXPS, views_doc.document_bibtex),
68+
url(r'^%(name)s(?:/%(rev)s)?/$' % settings.URL_REGEXPS, views_doc.document_main),
69+
url(r'^%(name)s(?:/%(rev)s)?/bibtex/$' % settings.URL_REGEXPS, views_doc.document_bibtex),
6970
url(r'^%(name)s/history/$' % settings.URL_REGEXPS, views_doc.document_history),
7071
url(r'^%(name)s/writeup/$' % settings.URL_REGEXPS, views_doc.document_writeup),
7172
url(r'^%(name)s/email/$' % settings.URL_REGEXPS, views_doc.document_email),

ietf/doc/urls_charter.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# Copyright The IETF Trust 2011, All Rights Reserved
22

3-
from django.conf import settings
4-
53
from ietf.doc import views_charter, views_doc
64
from ietf.utils.urls import url
75

@@ -17,5 +15,5 @@
1715
url(r'^ballotwriteupnotes/$', views_charter.ballot_writeupnotes),
1816
url(r'^approve/$', views_charter.approve),
1917
url(r'^submit/(?:(?P<option>initcharter|recharter)/)?$', views_charter.submit),
20-
url(r'^withmilestones-%(rev)s.txt$' % settings.URL_REGEXPS, views_charter.charter_with_milestones_txt),
18+
url(r'^withmilestones-(?P<rev>[0-9-]{2,5}).txt$', views_charter.charter_with_milestones_txt),
2119
]

ietf/doc/utils.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from django.utils.html import escape
1313
from django.core.urlresolvers import reverse as urlreverse
1414

15+
import debug # pyflakes:ignore
16+
1517
from ietf.doc.models import Document, DocHistory, State, DocumentAuthor, DocHistoryAuthor
1618
from ietf.doc.models import DocAlias, RelatedDocument, RelatedDocHistory, BallotType, DocReminder
1719
from ietf.doc.models import DocEvent, ConsensusDocEvent, BallotDocEvent, NewRevisionDocEvent, StateDocEvent
@@ -705,3 +707,130 @@ def get_search_cache_key(params):
705707
key = "doc:document:search:" + hashlib.sha512(json.dumps(kwargs, sort_keys=True)).hexdigest()
706708
return key
707709

710+
def label_wrap(label, items, joiner=',', max=50):
711+
lines = []
712+
if not items:
713+
return lines
714+
line = '%s: %s' % (label, items[0])
715+
for item in items[1:]:
716+
if len(line)+len(joiner+' ')+len(item) > max:
717+
lines.append(line+joiner)
718+
line = ' '*(len(label)+len(': ')) + item
719+
else:
720+
line += joiner+' '+item
721+
if line:
722+
lines.append(line)
723+
return lines
724+
725+
def join_justified(left, right, width=72):
726+
count = max(len(left), len(right))
727+
left = left + ['']*(count-len(left))
728+
right = right + ['']*(count-len(right))
729+
lines = []
730+
i = 0
731+
while True:
732+
l = left[i]
733+
r = right[i]
734+
if len(l)+1+len(r) > width:
735+
left = left + ['']
736+
right = right[:i] + [''] + right[i:]
737+
r = right[i]
738+
count += 1
739+
lines.append( l + ' ' + r.rjust(width-len(l)-1) )
740+
i += 1
741+
if i >= count:
742+
break
743+
return lines
744+
745+
def build_doc_meta_block(doc, path):
746+
def add_markup(path, doc, lines):
747+
is_hst = doc.is_dochistory()
748+
rev = doc.rev
749+
if is_hst:
750+
doc = doc.doc
751+
name = doc.name
752+
rfcnum = doc.rfc_number()
753+
errata_url = settings.RFC_EDITOR_ERRATA_URL.format(rfc_number=rfcnum) if not is_hst else ""
754+
ipr_url = "%s?submit=draft&amp;id=%s" % (urlreverse('ietf.ipr.views.search'), name)
755+
for i, line in enumerate(lines):
756+
# add draft links
757+
line = re.sub(r'\b(draft-[-a-z0-9]+)\b', '<a href="%s/\g<1>">\g<1></a>'%(path, ), line)
758+
# add rfcXXXX to RFC links
759+
line = re.sub(r' (rfc[0-9]+)\b', ' <a href="%s/\g<1>">\g<1></a>'%(path, ), line)
760+
# add XXXX to RFC links
761+
line = re.sub(r' ([0-9]{3,5})\b', ' <a href="%s/rfc\g<1>">\g<1></a>'%(path, ), line)
762+
# add draft revision links
763+
line = re.sub(r' ([0-9]{2})\b', ' <a href="%s/%s-\g<1>">\g<1></a>'%(path, name, ), line)
764+
if rfcnum:
765+
# add errata link
766+
line = re.sub(r'Errata exist', '<a class="text-warning" href="%s">Errata exist</a>'%(errata_url, ), line)
767+
if is_hst or not rfcnum:
768+
# make current draft rev bold
769+
line = re.sub(r'>(%s)<'%rev, '><b>\g<1></b><', line)
770+
line = re.sub(r'IPR declarations', '<a class="text-warning" href="%s">IPR declarations</a>'%(ipr_url, ), line)
771+
line = line.replace(r'[txt]', '[<a href="%s">txt</a>]' % doc.href())
772+
lines[i] = line
773+
return lines
774+
#
775+
now = datetime.datetime.now()
776+
draft_state = doc.get_state('draft')
777+
block = ''
778+
meta = {}
779+
if doc.type_id == 'draft':
780+
revisions = []
781+
ipr = doc.related_ipr()
782+
if ipr:
783+
meta['ipr'] = [ "IPR declarations" ]
784+
if doc.is_rfc() and not doc.is_dochistory():
785+
if not doc.name.startswith('rfc'):
786+
meta['from'] = [ "%s-%s"%(doc.name, doc.rev) ]
787+
meta['errata'] = [ "Errata exist" ] if doc.tags.filter(slug='errata').exists() else []
788+
meta['obsoletedby'] = [ alias.document.rfc_number() for alias in doc.related_that('obs') ]
789+
meta['obsoletedby'].sort()
790+
meta['updatedby'] = [ alias.document.rfc_number() for alias in doc.related_that('updates') ]
791+
meta['updatedby'].sort()
792+
meta['stdstatus'] = [ doc.std_level.name ]
793+
else:
794+
dd = doc.doc if doc.is_dochistory() else doc
795+
revisions += [ '(%s)%s'%(d.name, ' '*(2-((len(d.name)-1)%3))) for d in dd.replaces() ]
796+
revisions += doc.revisions()
797+
if doc.is_dochistory() and doc.doc.is_rfc():
798+
revisions += [ doc.doc.canonical_name() ]
799+
else:
800+
revisions += [ d.name for d in doc.replaced_by() ]
801+
meta['versions'] = revisions
802+
if not doc.is_dochistory and draft_state.slug == 'active' and now > doc.expires:
803+
# Active past expiration date
804+
meta['active'] = [ 'Document is active' ]
805+
meta['state' ] = [ doc.friendly_state() ]
806+
intended_std = doc.intended_std_level if doc.intended_std_level else None
807+
if intended_std:
808+
if intended_std.slug in ['ps', 'ds', 'std']:
809+
meta['stdstatus'] = [ "Standards Track" ]
810+
else:
811+
meta['stdstatus'] = [ intended_std.name ]
812+
elif doc.type_id == 'charter':
813+
meta['versions'] = doc.revisions()
814+
#
815+
# Add markup to items that needs it.
816+
if 'versions' in meta:
817+
meta['versions'] = label_wrap('Versions', meta['versions'], joiner="")
818+
for label in ['Obsoleted by', 'Updated by', 'From' ]:
819+
item = label.replace(' ','').lower()
820+
if item in meta and meta[item]:
821+
meta[item] = label_wrap(label, meta[item])
822+
#
823+
left = []
824+
right = []
825+
#right = [ '[txt]']
826+
for item in [ 'from', 'versions', 'obsoletedby', 'updatedby', ]:
827+
if item in meta and meta[item]:
828+
left += meta[item]
829+
for item in ['stdstatus', 'active', 'state', 'ipr', 'errata', ]:
830+
if item in meta and meta[item]:
831+
right += meta[item]
832+
lines = join_justified(left, right)
833+
block = '\n'.join(add_markup(path, doc, lines))
834+
#
835+
return block
836+

ietf/doc/views_doc.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
can_adopt_draft, get_chartering_type, get_document_content, get_tags_for_stream_id,
5151
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
5252
get_initial_notify, make_notify_changed_event, crawl_history, default_consensus,
53-
add_events_message_info, get_unicode_document_content)
53+
add_events_message_info, get_unicode_document_content, build_doc_meta_block)
5454
from ietf.community.utils import augment_docs_with_tracking_info
5555
from ietf.group.models import Role
5656
from ietf.group.utils import can_manage_group_type, can_manage_materials
@@ -67,9 +67,13 @@
6767
from ietf.review.utils import can_request_review_of_doc, review_requests_to_list_for_docs
6868
from ietf.review.utils import no_review_from_teams_on_doc
6969

70+
7071
def render_document_top(request, doc, tab, name):
7172
tabs = []
72-
tabs.append(("Document", "document", urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=name)), True, None))
73+
tabs.append(("Status", "status", urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=name)), True, None))
74+
75+
if doc.type_id in ["draft", "charter", ]:
76+
tabs.append(("Document", "document", urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=name)), True, None))
7377

7478
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
7579
if doc.type_id in ("draft","conflrev", "statchg"):
@@ -141,7 +145,7 @@ def document_main(request, name, rev=None):
141145
# set this after we've found the right doc instance
142146
group = doc.group
143147

144-
top = render_document_top(request, doc, "document", name)
148+
top = render_document_top(request, doc, "status", name)
145149

146150

147151
telechat = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
@@ -597,8 +601,38 @@ def document_main(request, name, rev=None):
597601
other_reviews=other_reviews,
598602
))
599603

600-
raise Http404
604+
raise Http404("Document not found: %s" % (name + ("-%s"%rev if rev else "")))
605+
606+
607+
def document_html(request, name, rev=None):
608+
if name.startswith('rfc0'):
609+
name = "rfc" + name[3:].lstrip('0')
610+
if name.startswith('review-') and re.search('-\d\d\d\d-\d\d$', name):
611+
name = "%s-%s" % (name, rev)
612+
docs = Document.objects.filter(docalias__name=name)
613+
if not docs.exists():
614+
# handle some special cases, like draft-ietf-tsvwg-ieee-802-11
615+
name = '%s-%s' % (name, rev)
616+
rev=None
617+
docs = Document.objects.filter(docalias__name=name)
618+
doc = docs.get()
619+
620+
if not os.path.exists(doc.get_file_name()):
621+
raise Http404("Document not found: %s" % doc.get_base_name())
622+
623+
top = render_document_top(request, doc, "document", name)
624+
if not rev and not name.startswith('rfc'):
625+
rev = doc.rev
626+
if rev:
627+
docs = DocHistory.objects.filter(doc=doc, rev=rev)
628+
if docs.exists():
629+
doc = docs.first()
630+
else:
631+
doc = doc.fake_history_obj(rev)
632+
if doc.type_id in ['draft',]:
633+
doc.meta = build_doc_meta_block(doc, settings.HTMLIZER_URL_PREFIX)
601634

635+
return render(request, "doc/document_html.html", {"doc":doc, "top":top, "navbar_mode":"navbar-static-top", })
602636

603637
def check_doc_email_aliases():
604638
pattern = re.compile('^expand-(.*?)(\..*?)?@.*? +(.*)$')

ietf/settings.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
TOOLS_SERVER = 'tools.' + IETF_DOMAIN
6262
TOOLS_SERVER_URL = 'https://' + TOOLS_SERVER
6363
TOOLS_ID_PDF_URL = TOOLS_SERVER_URL + '/pdf/'
64-
TOOLS_ID_HTML_URL = TOOLS_SERVER_URL + '/html/'
64+
TOOLS_ID_HTML_URL = '/doc/html/'
6565

6666
# Override this in the settings_local.py file:
6767
SERVER_EMAIL = 'Django Server <django-project@' + TOOLS_SERVER + '>'
@@ -524,12 +524,14 @@ def skip_unreadable_post(record):
524524
DATE_FORMAT = "Y-m-d"
525525
DATETIME_FORMAT = "Y-m-d H:i T"
526526

527+
528+
DRAFT_NAMES_WITH_DOT = "(draft-[a-z-]+-(ion-sig-uni4.0|pilc-2.5g3g|trade-iotp-v1.0-[a-z]+|msword-template-v2.0))"
527529
URL_REGEXPS = {
528530
"acronym": r"(?P<acronym>[-a-z0-9]+)",
529531
"charter": r"(?P<name>charter-[-a-z0-9]+)",
530532
"date": r"(?P<date>\d{4}-\d{2}-\d{2})",
531-
"name": r"(?P<name>[A-Za-z0-9._+-]+)",
532-
"rev": r"(?P<rev>[0-9-]+)",
533+
"name": r"(?P<name>([A-Za-z0-9_+-]+?|%s))" % DRAFT_NAMES_WITH_DOT,
534+
"rev": r"(?P<rev>[0-9-]{2})",
533535
"owner": r"(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)",
534536
"schedule_name": r"(?P<name>[A-Za-z0-9-:_]+)",
535537
}
@@ -595,9 +597,22 @@ def skip_unreadable_post(record):
595597
'default': {
596598
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
597599
'LOCATION': '127.0.0.1:11211',
598-
}
600+
'OPTIONS': {
601+
'MAX_ENTRIES': 10000, # 10,000
602+
},
603+
},
604+
'htmlized': {
605+
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
606+
'LOCATION': '/var/cache/datatracker/htmlized',
607+
'OPTIONS': {
608+
'MAX_ENTRIES': 100000, # 100,000
609+
},
610+
},
599611
}
600612

613+
HTMLIZER_VERSION = 1
614+
HTMLIZER_URL_PREFIX = "/doc/html"
615+
601616
IPR_EMAIL_FROM = 'ietf-ipr@ietf.org'
602617
AUDIO_IMPORT_EMAIL = ['agenda@ietf.org']
603618
IANA_EVAL_EMAIL = "drafts-eval@icann.org"
@@ -613,6 +628,7 @@ def skip_unreadable_post(record):
613628
RFC_EDITOR_SYNC_NOTIFICATION_URL = "https://www.rfc-editor.org/parser/parser.php"
614629
RFC_EDITOR_QUEUE_URL = "https://www.rfc-editor.org/queue2.xml"
615630
RFC_EDITOR_INDEX_URL = "https://www.rfc-editor.org/rfc/rfc-index.xml"
631+
RFC_EDITOR_ERRATA_URL = "https://www.rfc-editor.org/errata_search.php?rfc={rfc_number}&amp;rec_status=0"
616632

617633
# NomCom Tool settings
618634
ROLODEX_URL = ""
@@ -889,7 +905,15 @@ def skip_unreadable_post(record):
889905
CACHES = {
890906
'default': {
891907
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
892-
}
908+
},
909+
'htmlized': {
910+
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
911+
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
912+
'LOCATION': '/var/cache/datatracker/htmlized',
913+
'OPTIONS': {
914+
'MAX_ENTRIES': 1000,
915+
},
916+
}
893917
}
894918
SESSION_ENGINE = "django.contrib.sessions.backends.db"
895919

ietf/settings_testcrawl.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,18 @@
1919
CACHES = {
2020
'default': {
2121
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
22-
}
22+
'OPTIONS': {
23+
'MAX_ENTRIES': 10000,
24+
},
25+
},
26+
'htmlized': {
27+
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
28+
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
29+
'LOCATION': '/var/cache/datatracker/htmlized',
30+
'OPTIONS': {
31+
'MAX_ENTRIES': 100000,
32+
},
33+
},
2334
}
2435

2536
PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.MD5PasswordHasher', )

0 commit comments

Comments
 (0)