diff --git a/bin/mkrelease b/bin/mkrelease index ad70bd5e8..b99c85441 100755 --- a/bin/mkrelease +++ b/bin/mkrelease @@ -231,7 +231,7 @@ Regards, Henrik (via the mkrelease script) " > ~/src/db/release-mail-v$VER.txt -cat ~/src/db/release-mail-v$VER.txt | $do mail -s "New datatracker release: v$VER" -c henrik@levkowetz.com -c glen@amsl.com -c fenner@fenron.net -c rjs@nostrum.com -c housley@vigilsec.com -c cmorgan@amsl.com -c avezza@amsl.com -c amorris@amsl.com -c smccammon@amsl.com -c kmoreland@amsl.com -c stevey@amsl.com -c wchen@amsl.com -c olau@iola.dk $contributors codesprints@ietf.org +cat ~/src/db/release-mail-v$VER.txt | $do mail -s "New datatracker release: v$VER" -c henrik@levkowetz.com -c glen@amsl.com -c fenner@fenron.net -c rjs@nostrum.com -c housley@vigilsec.com -c cmorgan@amsl.com -c avezza@amsl.com -c amorris@amsl.com -c smccammon@amsl.com -c kmoreland@amsl.com -c stevey@amsl.com -c olau@iola.dk $contributors codesprints@ietf.org $do toolsfeed control changelog /www/tools.ietf.org/tools/atomfeed.xml $do toolpush /www/tools.ietf.org/tools/atomfeed.xml diff --git a/ietf/bin/expire-ids b/ietf/bin/expire-ids index 0437bb148..44f913bd1 100755 --- a/ietf/bin/expire-ids +++ b/ietf/bin/expire-ids @@ -11,10 +11,11 @@ syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_USER) from ietf.idrfc.expire import * -if not in_id_expire_freeze(): - for doc in get_expired_ids(): - send_expire_notice_for_id(doc) - expire_id(doc) - syslog.syslog("Expired %s (id=%s)%s" % (doc.file_tag(), doc.pk, " in the ID Tracker" if doc.latest_event(type="started_iesg_process") else "")) +if not in_draft_expire_freeze(): + for doc in get_expired_drafts(): + send_expire_notice_for_draft(doc) + expire_draft(doc) + syslog.syslog("Expired draft %s-%s" % (doc.name, doc.rev)) -clean_up_id_files() +syslog.syslog("Cleaning up draft files") +clean_up_draft_files() diff --git a/ietf/bin/notify-expirations b/ietf/bin/notify-expirations index 030d0611c..71de766f6 100755 --- a/ietf/bin/notify-expirations +++ b/ietf/bin/notify-expirations @@ -6,11 +6,11 @@ from ietf import settings from django.core import management management.setup_environ(settings) -from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id +from ietf.idrfc.expire import get_soon_to_expire_drafts, send_expire_warning_for_draft # notify about documents that expire within the next 2 weeks -notify_days = 14 +notify_days = 14 -for doc in get_soon_to_expire_ids(notify_days): - send_expire_warning_for_id(doc) +for doc in get_soon_to_expire_drafts(notify_days): + send_expire_warning_for_draft(doc) diff --git a/ietf/bin/test-crawl b/ietf/bin/test-crawl index 395ca1eb7..f48c9abc3 100755 --- a/ietf/bin/test-crawl +++ b/ietf/bin/test-crawl @@ -24,6 +24,12 @@ connection.queries = DontSaveQueries() MAX_URL_LENGTH = 500 SLOW_THRESHOLD = 1.0 +initial = ["/doc/all/", "/doc/in-last-call/"] + +visited = set() +urls = {} # url -> referrer + + def strip_url(url): if url.startswith("http://testserver"): url = url[len("http://testserver"):] @@ -40,15 +46,13 @@ def extract_html_urls(content): yield url - -visited = set() -blacklist = set() -urls = set(["/doc/all/"]) - client = django.test.Client() +for url in initial: + urls[url] = "[initial]" + while urls: - url = urls.pop() + url, referrer = urls.popitem() visited.add(url) @@ -62,7 +66,7 @@ while urls: except: print "FAIL", url print "=============" - traceback.print_exc() + print traceback.format_exc() print "=============" else: tags = [] @@ -70,7 +74,7 @@ while urls: if r.status_code in (301, 302): u = strip_url(r["Location"]) if u not in visited and u not in urls: - urls.add(u) + urls[u] = referrer # referrer is original referrer, not redirected url elif r.status_code == 200: ctype = r["Content-Type"] @@ -80,9 +84,9 @@ while urls: if ctype == "text/html": for u in extract_html_urls(r.content): if u not in visited and u not in urls: - urls.add(u) + urls[u] = url else: - tags.append("FAIL") + tags.append(u"FAIL (from %s)" % referrer) if elapsed.total_seconds() > SLOW_THRESHOLD: tags.append("SLOW") diff --git a/ietf/doc/feeds.py b/ietf/doc/feeds.py new file mode 100644 index 000000000..9fec1a4b5 --- /dev/null +++ b/ietf/doc/feeds.py @@ -0,0 +1,86 @@ +# Copyright The IETF Trust 2007, All Rights Reserved + +import datetime, re + +from django.conf import settings +from django.contrib.syndication.feeds import Feed, FeedDoesNotExist +from django.utils.feedgenerator import Atom1Feed +from django.core.urlresolvers import reverse as urlreverse +from django.template.defaultfilters import truncatewords_html, date as datefilter, linebreaks +from django.utils.html import strip_tags +from django.utils.text import truncate_words + +from ietf.doc.models import * +from ietf.doc.utils import augment_events_with_revision +from ietf.idtracker.templatetags.ietf_filters import format_textarea + +class DocumentChanges(Feed): + feed_type = Atom1Feed + + def get_object(self, bits): + if len(bits) != 1: + raise Document.DoesNotExist + + return Document.objects.get(docalias__name=bits[0]) + + def title(self, obj): + return "Changes for %s" % obj.display_name() + + def link(self, obj): + if obj is None: + raise FeedDoesNotExist + if not hasattr(self, "cached_link"): + self.cached_link = urlreverse("doc_history", kwargs=dict(name=obj.canonical_name())) + return self.cached_link + + def subtitle(self, obj): + return "History of change entries for %s." % obj.display_name() + + def items(self, obj): + events = obj.docevent_set.all().order_by("-time","-id") + augment_events_with_revision(obj, events) + return events + + def item_title(self, item): + return u"[%s] %s [rev. %s]" % (item.by, truncate_words(strip_tags(item.desc), 15), item.rev) + + def item_description(self, item): + return truncatewords_html(format_textarea(item.desc), 20) + + def item_pubdate(self, item): + return item.time + + def item_author_name(self, item): + return unicode(item.by) + + def item_link(self, item): + return self.cached_link + "#history-%s" % item.pk + +class InLastCall(Feed): + title = "Documents in Last Call" + subtitle = "Announcements for documents in last call." + feed_type = Atom1Feed + author_name = 'IESG Secretary' + link = "/doc/iesg/last-call/" + + def items(self): + docs = list(Document.objects.filter(type="draft", states=State.objects.get(type="draft-iesg", slug="lc"))) + for d in docs: + d.lc_event = d.latest_event(LastCallDocEvent, type="sent_last_call") + + docs = [d for d in docs if d.lc_event] + docs.sort(key=lambda d: d.lc_event.expires) + + return docs + + def item_title(self, item): + return u"%s (%s - %s)" % (item.name, + datefilter(item.lc_event.time, "F j"), + datefilter(item.lc_event.expires, "F j, Y")) + + def item_description(self, item): + return linebreaks(item.lc_event.desc) + + def item_pubdate(self, item): + return item.lc_event.time + diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 08dfe7c9e..47ae26d64 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -37,6 +37,9 @@ class State(models.Model): class Meta: ordering = ["type", "order"] +IESG_BALLOT_ACTIVE_STATES = ("lc", "writeupw", "goaheadw", "iesg-eva", "defer") +IESG_SUBSTATE_TAGS = ('point', 'ad-f-up', 'need-rev', 'extpty') + class DocumentInfo(models.Model): """Any kind of document. Draft, RFC, Charter, IPR Statement, Liaison Statement""" time = models.DateTimeField(default=datetime.datetime.now) # should probably have auto_now=True @@ -186,9 +189,7 @@ class Document(DocumentInfo): def get_absolute_url(self): name = self.name if self.type_id == "draft" and self.get_state_slug() == "rfc": - aliases = self.docalias_set.filter(name__startswith="rfc") - if aliases: - name = aliases[0].name + name = self.canonical_name() elif self.type_id in ('slides','agenda','minutes'): session = self.session_set.all()[0] meeting = session.meeting @@ -260,11 +261,10 @@ class Document(DocumentInfo): raise TypeError("Expected a string, tuple or list, received %s" % type(relationship)) return DocAlias.objects.filter(relateddocument__source=self, relateddocument__relationship__in=relationship) - #TODO can/should this be a function instead of a property? Currently a view uses it as a property - @property - def telechat_date(self): - e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat") - return e.telechat_date if e else None + def telechat_date(self, e=None): + if not e: + e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat") + return e.telechat_date if e and e.telechat_date and e.telechat_date >= datetime.date.today() else None def area_acronym(self): g = self.group @@ -283,10 +283,6 @@ class Document(DocumentInfo): else: return "none" - def on_upcoming_agenda(self): - e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat") - return bool(e and e.telechat_date and e.telechat_date >= datetime.date.today()) - def returning_item(self): e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat") return e.returning_item if e else None @@ -311,56 +307,56 @@ class Document(DocumentInfo): return '%s-%s' % (self.get_absolute_url(), self.name , self.rev) def rfc_number(self): - qs = self.docalias_set.filter(name__startswith='rfc') - return qs[0].name[3:] if qs else None + n = self.canonical_name() + return n[3:] if n.startswith("rfc") else None def friendly_state(self): - """ Return a concise text description of the document's current state """ - if self.type_id=='draft': - # started_iesg_process is is how the redesigned database schema (as of May2012) captured what - # used to be "has an IDInternal", aka *Wrapper.in_ietf_process()=True - in_iesg_process = self.latest_event(type='started_iesg_process') - iesg_state_summary=None - if in_iesg_process: - iesg_state = self.states.get(type='draft-iesg') + """ Return a concise text description of the document's current state.""" + state = self.get_state() + if not state: + return "Unknown state" + + if self.type_id == 'draft': + iesg_state = self.get_state("draft-iesg") + iesg_state_summary = None + if iesg_state: # This knowledge about which tags are reportable IESG substate tags is duplicated in idrfc - IESG_SUBSTATE_TAGS = ('point', 'ad-f-up', 'need-rev', 'extpty') iesg_substate = self.tags.filter(slug__in=IESG_SUBSTATE_TAGS) # There really shouldn't be more than one tag in iesg_substate, but this will do something sort-of-sensible if there is iesg_state_summary = iesg_state.name if iesg_substate: iesg_state_summary = iesg_state_summary + "::"+"::".join(tag.name for tag in iesg_substate) - if self.get_state_slug() == "rfc": - n = self.rfc_number() - return "RFC %s" % (urlreverse('doc_view', kwargs=dict(name='rfc%s' % n)), n) - elif self.get_state_slug() == "repl": + if state.slug == "rfc": + return "RFC %s (%s)" % (self.rfc_number(), self.std_level) + elif state.slug == "repl": rs = self.related_that("replaces") if rs: - return mark_safe("Replaced by " + ", ".join("%s" % (urlreverse('doc_view', args=[name]), name) for name in rs)) + return mark_safe("Replaced by " + ", ".join("%s" % (urlreverse('doc_view', kwargs=dict(name=name)), name) for name in rs)) else: return "Replaced" - elif self.get_state_slug() == "active": - if in_iesg_process: + elif state.slug == "active": + if iesg_state: if iesg_state.slug == "dead": # Many drafts in the draft-iesg "Dead" state are not dead # in other state machines; they're just not currently under # IESG processing. Show them as "I-D Exists (IESG: Dead)" instead... - return "I-D Exists (IESG: "+iesg_state_summary+")" + return "I-D Exists (IESG: %s)" % iesg_state_summary elif iesg_state.slug == "lc": - expiration_date = str(self.latest_event(LastCallDocEvent,type="sent_last_call").expires.date()) - return iesg_state_summary + " (ends "+expiration_date+")" - else: - return iesg_state_summary + e = self.latest_event(LastCallDocEvent, type="sent_last_call") + if e: + return iesg_state_summary + " (ends %s)" % e.expires.date().isoformat() + + return iesg_state_summary else: return "I-D Exists" else: - if in_iesg_process and iesg_state.slug == "dead": - return self.get_state().name +" (IESG: "+iesg_state_summary+")" + if iesg_state and iesg_state.slug == "dead": + return state.name + " (IESG: %s)" % iesg_state_summary # Expired/Withdrawn by Submitter/IETF - return self.get_state().name + return state.name else: - return self.get_state().name + return state.name def ipr(self): """Returns the IPR disclosures against this document (as a queryset over IprDocAlias).""" diff --git a/ietf/doc/redirect_drafts_urls.py b/ietf/doc/redirect_drafts_urls.py new file mode 100644 index 000000000..fa920a461 --- /dev/null +++ b/ietf/doc/redirect_drafts_urls.py @@ -0,0 +1,23 @@ +# Copyright The IETF Trust 2007, All Rights Reserved + +from django.conf import settings +from django.conf.urls.defaults import patterns + + +from django.http import HttpResponsePermanentRedirect +from django.shortcuts import get_object_or_404 + +from ietf.group.models import Group + +urlpatterns = patterns('', + (r'^$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/'}), + (r'^all/$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/all/'}), + (r'^rfc/$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/all/#rfc'}), + (r'^dead/$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/all/#expired'}), + (r'^current/$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/active/'}), + (r'^(?P\d+)/(related/)?$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/' }), + (r'^(?P[^/]+)/(related/)?$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/%(name)s/' }), + (r'^wgid/(?P\d+)/$', lambda request, id: HttpResponsePermanentRedirect("/wg/%s/" % get_object_or_404(Group, id=id).acronym)), + (r'^wg/(?P[^/]+)/$', 'django.views.generic.simple.redirect_to', { 'url': '/wg/%(acronym)s/' }), + (r'^all_id(?:_txt)?.html$', 'django.views.generic.simple.redirect_to', { 'url': 'http://www.ietf.org/id/all_id.txt' }), +) diff --git a/ietf/doc/redirect_idtracker_urls.py b/ietf/doc/redirect_idtracker_urls.py new file mode 100644 index 000000000..ac3eff633 --- /dev/null +++ b/ietf/doc/redirect_idtracker_urls.py @@ -0,0 +1,13 @@ +from django.conf.urls.defaults import patterns, url +from django.views.generic.simple import redirect_to + +urlpatterns = patterns('', + (r'^help/(?:sub)?state/(?:\d+/)?$', redirect_to, {'url': '/doc/help/state/draft-iesg/' }), + (r'^help/evaluation/$', redirect_to, {'url':'http://www.ietf.org/iesg/voting-procedures.html' }), + (r'^status/$', redirect_to, {'url':'/doc/iesg/' }), + (r'^status/last-call/$', redirect_to, {'url':'/doc/iesg/last-call/' }), + (r'^rfc0*(?P\d+)/$', redirect_to, {'url':'/doc/rfc%(rfc_number)s/' }), + (r'^(?P[^/]+)/$', redirect_to, {'url':'/doc/%(name)s/' }), + (r'^(?P[^/]+)/comment/\d+/$', redirect_to, {'url':'/doc/%(name)s/history/' }), + (r'^$', redirect_to, { 'url': '/doc/'}), +) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index a4a7cff17..43b0e9315 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -2,7 +2,6 @@ import os, shutil, datetime import django.test from django.core.urlresolvers import reverse as urlreverse -from django.conf import settings from pyquery import PyQuery @@ -21,6 +20,108 @@ from ietf.iesg.models import TelechatDate from ietf.doc.tests_conflict_review import * +class SearchTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_search(self): + draft = make_test_data() + + base_url = urlreverse("doc_search") + + # only show form, no search yet + r = self.client.get(base_url) + self.assertEqual(r.status_code, 200) + + # no match + r = self.client.get(base_url + "?activedrafts=on&name=thisisnotadocumentname") + self.assertEqual(r.status_code, 200) + self.assertTrue("no documents match" in str(r.content).lower()) + + r = self.client.get(base_url + "?rfcs=on&name=xyzzy") + self.assertEqual(r.status_code, 200) + self.assertTrue("no documents match" in r.content.lower()) + + r = self.client.get(base_url + "?olddrafts=on&name=") + self.assertEqual(r.status_code, 200) + self.assertTrue("no documents match" in r.content.lower()) + + # find by rfc/active/inactive + draft.set_state(State.objects.get(type="draft", slug="rfc")) + r = self.client.get(base_url + "?rfcs=on&name=%s" % draft.name) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + draft.set_state(State.objects.get(type="draft", slug="active")) + r = self.client.get(base_url + "?activedrafts=on&name=%s" % draft.name) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + draft.set_state(State.objects.get(type="draft", slug="expired")) + r = self.client.get(base_url + "?olddrafts=on&name=%s" % draft.name) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + draft.set_state(State.objects.get(type="draft", slug="active")) + + # find by title + r = self.client.get(base_url + "?activedrafts=on&name=%s" % draft.title.split()[0]) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + # find by author + r = self.client.get(base_url + "?activedrafts=on&by=author&author=%s" % draft.authors.all()[0].person.name_parts()[1]) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + # find by group + r = self.client.get(base_url + "?activedrafts=on&by=group&group=%s" % draft.group.acronym) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + # find by area + r = self.client.get(base_url + "?activedrafts=on&by=area&area=%s" % draft.group.parent_id) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + # find by area + r = self.client.get(base_url + "?activedrafts=on&by=area&area=%s" % draft.group.parent_id) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + # find by AD + r = self.client.get(base_url + "?activedrafts=on&by=ad&ad=%s" % draft.ad_id) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + # find by IESG state + r = self.client.get(base_url + "?activedrafts=on&by=state&state=%s&substate=" % draft.get_state("draft-iesg").pk) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + def test_drafts_pages(self): + draft = make_test_data() + + r = self.client.get(urlreverse("drafts_for_ad", kwargs=dict(name=draft.ad.full_name_as_key()))) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + draft.set_state(State.objects.get(type="draft-iesg", slug="lc")) + r = self.client.get(urlreverse("drafts_in_last_call")) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + def test_indexes(self): + draft = make_test_data() + + r = self.client.get(urlreverse("index_all_drafts")) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.name in r.content) + + r = self.client.get(urlreverse("index_active_drafts")) + self.assertEqual(r.status_code, 200) + self.assertTrue(draft.title in r.content) + + class DocTestCase(django.test.TestCase): fixtures = ['names'] @@ -134,7 +235,7 @@ class DocTestCase(django.test.TestCase): self.assertEqual(r.status_code, 200) # test popup too while we're at it - r = self.client.get(urlreverse("ietf.doc.views_doc.ballot_for_popup", kwargs=dict(name=doc.name))) + r = self.client.get(urlreverse("ietf.doc.views_doc.ballot_popup", kwargs=dict(name=doc.name, ballot_id=ballot.pk))) self.assertEqual(r.status_code, 200) def test_document_json(self): diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 9724acd68..7303e7deb 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -222,7 +222,7 @@ def document_main(request, name, rev=None): # ballot ballot_summary = None - if iesg_state and iesg_state.slug in ("lc", "writeupw", "goaheadw", "iesg-eva", "defer"): + if iesg_state and iesg_state.slug in IESG_BALLOT_ACTIVE_STATES: active_ballot = doc.active_ballot() if active_ballot: ballot_summary = needed_ballot_positions(doc, active_ballot.active_ad_positions().values()) @@ -653,6 +653,17 @@ def document_ballot(request, name, ballot_id=None): ), context_instance=RequestContext(request)) +def ballot_popup(request, name, ballot_id): + doc = get_object_or_404(Document, docalias__name=name) + c = document_ballot_content(request, doc, ballot_id=ballot_id, editable=False) + return render_to_response("doc/ballot_popup.html", + dict(doc=doc, + ballot_content=c, + ballot_id=ballot_id, + ), + context_instance=RequestContext(request)) + + def document_json(request, name): doc = get_object_or_404(Document, docalias__name=name) @@ -699,11 +710,6 @@ def document_json(request, name): return HttpResponse(json.dumps(data, indent=2), mimetype='text/plain') -def ballot_for_popup(request, name): - doc = get_object_or_404(Document, docalias__name=name) - return HttpResponse(document_ballot_content(request, doc, ballot_id=None, editable=False)) - - def ballot_json(request, name): # REDESIGN: this view needs to be deleted or updated def get_ballot(name): diff --git a/ietf/doc/views_help.py b/ietf/doc/views_help.py new file mode 100644 index 000000000..d6548a2d8 --- /dev/null +++ b/ietf/doc/views_help.py @@ -0,0 +1,45 @@ +from django import forms +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext + +from ietf.doc.models import * + +def state_help(request, type): + slug, title = { + "draft-iesg": ("draft-iesg", "IESG States For Internet-Drafts"), + "draft-rfceditor": ("draft-rfceditor", "RFC Editor States For Internet-Drafts"), + "draft-iana-action": ("draft-iana-action", "IANA Action States For Internet-Drafts"), + "charter": ("charter", "Charter States"), + "conflict-review": ("conflrev", "Conflict Review States") + }.get(type, "") + state_type = get_object_or_404(StateType, slug=slug) + + states = State.objects.filter(type=state_type).order_by("order") + + has_next_states = False + for state in states: + if state.next_states.all(): + has_next_states = True + break + + tags = [] + + if state_type.slug == "draft-iesg": + # legacy hack + states = list(states) + fake_state = dict(name="I-D Exists", + desc="Initial (default) state for all internet drafts. Such documents are not being tracked by the IESG as no request has been made of the IESG to do anything with the document.", + next_states=dict(all=State.objects.filter(type="draft-iesg", slug__in=("watching", "pub-req"))) + ) + states.insert(0, fake_state) + + tags = DocTagName.objects.filter(slug__in=IESG_SUBSTATE_TAGS) + + return render_to_response("doc/state_help.html", { + "title": title, + "state_type": state_type, + "states": states, + "has_next_states": has_next_states, + "tags": tags, + }, + context_instance=RequestContext(request)) diff --git a/ietf/idindex/generate_all_id2_txt.py b/ietf/idindex/generate_all_id2_txt.py index bbebdcb08..f2ca9c229 100644 --- a/ietf/idindex/generate_all_id2_txt.py +++ b/ietf/idindex/generate_all_id2_txt.py @@ -34,5 +34,5 @@ from ietf import settings from django.core import management management.setup_environ(settings) -from ietf.idindex.views import all_id2_txt +from ietf.idindex.index import all_id2_txt print all_id2_txt().encode('utf-8'), diff --git a/ietf/idindex/generate_all_id_txt.py b/ietf/idindex/generate_all_id_txt.py index f427bb185..1f72bc428 100644 --- a/ietf/idindex/generate_all_id_txt.py +++ b/ietf/idindex/generate_all_id_txt.py @@ -34,5 +34,5 @@ from ietf import settings from django.core import management management.setup_environ(settings) -from ietf.idindex.views import all_id_txt -print all_id_txt(), +from ietf.idindex.index import all_id_txt +print all_id_txt().encode("utf-8"), diff --git a/ietf/idindex/generate_id_abstracts_txt.py b/ietf/idindex/generate_id_abstracts_txt.py index 6ea4bd40a..9a35b8b21 100644 --- a/ietf/idindex/generate_id_abstracts_txt.py +++ b/ietf/idindex/generate_id_abstracts_txt.py @@ -34,9 +34,5 @@ from ietf import settings from django.core import management management.setup_environ(settings) -from ietf.idindex.views import id_abstracts_txt -x = id_abstracts_txt() -if isinstance(x, unicode): - print x.encode('utf-8'), -else: - print x, +from ietf.idindex.index import id_index_txt +print id_index_txt(with_abstracts=True).encode('utf-8'), diff --git a/ietf/idindex/generate_id_index_txt.py b/ietf/idindex/generate_id_index_txt.py index 1b53117d5..ad25d5f4c 100644 --- a/ietf/idindex/generate_id_index_txt.py +++ b/ietf/idindex/generate_id_index_txt.py @@ -34,9 +34,5 @@ from ietf import settings from django.core import management management.setup_environ(settings) -from ietf.idindex.views import id_index_txt -x = id_index_txt() -if isinstance(x, unicode): - print x.encode('utf-8'), -else: - print x, +from ietf.idindex.index import id_index_txt +print id_index_txt().encode('utf-8'), diff --git a/ietf/idindex/index.py b/ietf/idindex/index.py new file mode 100644 index 000000000..6b686936c --- /dev/null +++ b/ietf/idindex/index.py @@ -0,0 +1,275 @@ +# code to generate plain-text index files that are placed on +# www.ietf.org in the same directory as the I-Ds + +import datetime, os + +import pytz + +from django.conf import settings +from django.template.loader import render_to_string + +from ietf.idtracker.templatetags.ietf_filters import clean_whitespace +from ietf.doc.models import * + +def all_id_txt(): + # this returns a lot of data so try to be efficient + + # precalculations + revision_time = dict(NewRevisionDocEvent.objects.filter(type="new_revision", doc__name__startswith="draft-").order_by('time').values_list("doc_id", "time")) + + def formatted_rev_date(name): + t = revision_time.get(name) + return t.strftime("%Y-%m-%d") if t else "" + + rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", + document__states=State.objects.get(type="draft", slug="rfc")).values_list("document_id", "name")) + + replacements = dict(RelatedDocument.objects.filter(target__document__states=State.objects.get(type="draft", slug="repl"), + relationship="replaces").values_list("target__document_id", "source")) + + + # we need a distinct to prevent the queries below from multiplying the result + all_ids = Document.objects.filter(type="draft").order_by('name').exclude(name__startswith="rfc").distinct() + + res = ["\nInternet-Drafts Status Summary\n"] + + def add_line(f1, f2, f3, f4): + # each line must have exactly 4 tab-separated fields + res.append(f1 + "\t" + f2 + "\t" + f3 + "\t" + f4) + + + inactive_states = ["pub", "watching", "dead"] + + in_iesg_process = all_ids.exclude(states=State.objects.get(type="draft", slug="rfc")).filter(states__in=list(State.objects.filter(type="draft-iesg").exclude(slug__in=inactive_states))).only("name", "rev") + + # handle those actively in the IESG process + for d in in_iesg_process: + state = d.get_state("draft-iesg").name + tags = d.tags.filter(slug__in=IESG_SUBSTATE_TAGS).values_list("name", flat=True) + if tags: + state += "::" + "::".join(tags) + add_line(d.name + "-" + d.rev, + formatted_rev_date(d.name), + "In IESG processing - ID Tracker state <" + state + ">", + "", + ) + + + # handle the rest + + not_in_process = all_ids.exclude(pk__in=[d.name for d in in_iesg_process]) + + for s in State.objects.filter(type="draft").order_by("order"): + for name, rev in not_in_process.filter(states=s).values_list("name", "rev"): + state = s.name + last_field = "" + + if s.slug == "rfc": + a = rfc_aliases.get(name) + if a: + last_field = a[3:] + elif s.slug == "repl": + state += " replaced by " + replacements.get(name, "0") + + add_line(name + "-" + rev, + formatted_rev_date(name), + state, + last_field, + ) + + return u"\n".join(res) + "\n" + +def file_types_for_drafts(): + """Look in the draft directory and return file types found as dict (name + rev -> [t1, t2, ...]).""" + file_types = {} + for filename in os.listdir(settings.INTERNET_DRAFT_PATH): + if filename.startswith("draft-"): + base, ext = os.path.splitext(filename) + if ext: + if base not in file_types: + file_types[base] = [ext] + else: + file_types[base].append(ext) + + return file_types + +def all_id2_txt(): + # this returns a lot of data so try to be efficient + + drafts = Document.objects.filter(type="draft").exclude(name__startswith="rfc").order_by('name').select_related('group', 'group__parent', 'ad', 'ad__email', 'intended_std_level', 'shepherd', 'shepherd__email') + + rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", + document__states=State.objects.get(type="draft", slug="rfc")).values_list("document_id", "name")) + + replacements = dict(RelatedDocument.objects.filter(target__document__states=State.objects.get(type="draft", slug="repl"), + relationship="replaces").values_list("target__document_id", "source")) + + revision_time = dict(DocEvent.objects.filter(type="new_revision", doc__name__startswith="draft-").order_by('time').values_list("doc_id", "time")) + + file_types = file_types_for_drafts() + + authors = {} + for a in DocumentAuthor.objects.filter(document__name__startswith="draft-").order_by("order").select_related("author", "author__person").iterator(): + if a.document_id not in authors: + l = authors[a.document_id] = [] + else: + l = authors[a.document_id] + if "@" in a.author.address: + l.append(u'%s <%s>' % (a.author.person.plain_name().replace("@", ""), a.author.address.replace(",", ""))) + else: + l.append(a.author.person.plain_name()) + + res = [] + for d in drafts: + state = d.get_state_slug() + iesg_state = d.get_state("draft-iesg") + + fields = [] + # 0 + fields.append(d.name + "-" + d.rev) + # 1 + fields.append("-1") # used to be internal numeric identifier, we don't have that anymore + # 2 + fields.append(d.get_state().name if state else "") + # 3 + if state == "active": + s = "I-D Exists" + if iesg_state: + s = iesg_state.name + tags = d.tags.filter(slug__in=IESG_SUBSTATE_TAGS).values_list("name", flat=True) + if tags: + s += "::" + "::".join(tags) + fields.append(s) + else: + fields.append("") + # 4 + rfc_number = "" + if state == "rfc": + a = rfc_aliases.get(d.name) + if a: + rfc_number = a[3:] + fields.append(rfc_number) + # 5 + repl = "" + if state == "repl": + repl = replacements.get(d.name, "") + fields.append(repl) + # 6 + t = revision_time.get(d.name) + fields.append(t.strftime("%Y-%m-%d") if t else "") + # 7 + group_acronym = "" + if d.group and d.group.type_id != "area" and d.group.acronym != "none": + group_acronym = d.group.acronym + fields.append(group_acronym) + # 8 + area = "" + if d.group: + if d.group.type_id == "area": + area = d.group.acronym + elif d.group.type_id == "wg" and d.group.parent and d.group.parent.type_id == "area": + area = d.group.parent.acronym + fields.append(area) + # 9 responsible AD name + fields.append(unicode(d.ad) if d.ad else "") + # 10 + fields.append(d.intended_std_level.name if d.intended_std_level else "") + # 11 + lc_expires = "" + if iesg_state and iesg_state.slug == "lc": + e = d.latest_event(LastCallDocEvent, type="sent_last_call") + if e: + lc_expires = e.expires.strftime("%Y-%m-%d") + fields.append(lc_expires) + # 12 + doc_file_types = file_types.get(d.name + "-" + d.rev, []) + doc_file_types.sort() # make the order consistent (and the result testable) + fields.append(",".join(doc_file_types) if state == "active" else "") + # 13 + fields.append(clean_whitespace(d.title)) # FIXME: we should make sure this is okay in the database and in submit + # 14 + fields.append(u", ".join(authors.get(d.name, []))) + # 15 + fields.append(d.shepherd.formatted_email().replace('"', '') if d.shepherd else "") + # 16 Responsible AD name and email + fields.append(d.ad.formatted_email().replace('"', '') if d.ad else "") + + # + res.append(u"\t".join(fields)) + + return render_to_string("idindex/all_id2.txt", {'data': u"\n".join(res) }) + +def active_drafts_index_by_group(extra_values=()): + """Return active drafts grouped into their corresponding + associated group, for spitting out draft index.""" + + # this returns a lot of data so try to be efficient + + active_state = State.objects.get(type="draft", slug="active") + + groups_dict = dict((g.id, g) for g in Group.objects.all()) + + extracted_values = ("name", "rev", "title", "group_id") + extra_values + + docs_dict = dict((d["name"], d) + for d in Document.objects.filter(states=active_state).values(*extracted_values)) + + # add initial and latest revision time + for time, doc_id in NewRevisionDocEvent.objects.filter(type="new_revision", doc__states=active_state).order_by('-time').values_list("time", "doc_id"): + d = docs_dict.get(doc_id) + if d: + if "rev_time" not in d: + d["rev_time"] = time + d["initial_rev_time"] = time + + # add authors + for a in DocumentAuthor.objects.filter(document__states=active_state).order_by("order").select_related("author__person"): + d = docs_dict.get(a.document_id) + if d: + if "authors" not in d: + d["authors"] = [] + d["authors"].append(unicode(a.author.person)) + + # put docs into groups + for d in docs_dict.itervalues(): + g = groups_dict.get(d["group_id"]) + if not g: + continue + + if not hasattr(g, "active_drafts"): + g.active_drafts = [] + + g.active_drafts.append(d) + + groups = [g for g in groups_dict.itervalues() if hasattr(g, "active_drafts")] + groups.sort(key=lambda g: g.acronym) + + fallback_time = datetime.datetime(1950, 1, 1) + for g in groups: + g.active_drafts.sort(key=lambda d: d.get("initial_rev_time", fallback_time)) + + return groups + +def id_index_txt(with_abstracts=False): + extra_values = () + if with_abstracts: + extra_values = ("abstract",) + groups = active_drafts_index_by_group(extra_values) + + file_types = file_types_for_drafts() + for g in groups: + for d in g.active_drafts: + # we need to output a multiple extension thing + types = file_types.get(d["name"] + "-" + d["rev"], "") + exts = ".txt" + if ".ps" in types: + exts += ",.ps" + if ".pdf" in types: + exts += ",.pdf" + d["exts"] = exts + + return render_to_string("idindex/id_index.txt", { + 'groups': groups, + 'time': datetime.datetime.now(pytz.UTC).strftime("%Y-%m-%d %H:%M:%S %Z"), + 'with_abstracts': with_abstracts, + }) diff --git a/ietf/idindex/models.py b/ietf/idindex/models.py index 7f1df9760..e69de29bb 100644 --- a/ietf/idindex/models.py +++ b/ietf/idindex/models.py @@ -1 +0,0 @@ -# Copyright The IETF Trust 2007, All Rights Reserved diff --git a/ietf/idindex/tests.py b/ietf/idindex/tests.py index b21d781cc..54e71ba17 100644 --- a/ietf/idindex/tests.py +++ b/ietf/idindex/tests.py @@ -1,72 +1,146 @@ -# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). -# All rights reserved. Contact: Pasi Eronen -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# -# * Neither the name of the Nokia Corporation and/or its -# subsidiary(-ies) nor the names of its contributors may be used -# to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (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, shutil -import unittest -import re -from django.test.client import Client -from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest +import django.test +from django.core.urlresolvers import reverse as urlreverse -class IdIndexUrlTestCase(SimpleUrlTestCase): - def testUrls(self): - self.doTestUrls(__file__) +from ietf.utils.test_data import make_test_data -# class IndexTestCase(unittest.TestCase, RealDatabaseTest): -# def setUp(self): -# self.setUpRealDatabase() -# def tearDown(self): -# self.tearDownRealDatabase() +from ietf.doc.models import * +from ietf.idindex.index import * -# def testAllId(self): -# print " Testing all_id.txt generation" -# c = Client() -# response = c.get('/drafts/_test/all_id.txt') -# self.assertEquals(response.status_code, 200) -# content = response.content -# # Test that correct version number is shown for couple of old drafts -# self.assert_(content.find("draft-ietf-tls-psk-09") >= 0) -# self.assert_(content.find("draft-eronen-eap-sim-aka-80211-00") >= 0) -# # Since all_id.txt contains all old drafts, it should never shrink -# lines = content.split("\n") -# self.assert_(len(lines) > 18000) -# # Test that the lines look OK and have correct number of tabs -# r = re.compile(r'^(draft-\S*-\d\d)\t(\d\d\d\d-\d\d-\d\d)\t([^\t]+)\t([^\t]*)$') -# for line in lines: -# if ((line == "") or -# (line == "Internet-Drafts Status Summary") or -# (line == "Web version is available at") or -# (line == "https://datatracker.ietf.org/public/idindex.cgi")): -# pass -# elif r.match(line): -# pass -# else: -# self.fail("Unexpected line \""+line+"\"") -# print "OK (all_id.txt)" + +class IndexTestCase(django.test.TestCase): + fixtures = ['names'] + + def setUp(self): + self.id_dir = os.path.abspath("tmp-id-dir") + os.mkdir(self.id_dir) + settings.INTERNET_DRAFT_PATH = self.id_dir + + def tearDown(self): + shutil.rmtree(self.id_dir) + + def write_draft_file(self, name, size): + with open(os.path.join(self.id_dir, name), 'w') as f: + f.write("a" * size) + + def test_all_id_txt(self): + draft = make_test_data() + + # active in IESG process + draft.set_state(State.objects.get(type="draft", slug="active")) + draft.set_state(State.objects.get(type="draft-iesg", slug="lc")) + + txt = all_id_txt() + + self.assertTrue(draft.name + "-" + draft.rev in txt) + self.assertTrue(draft.get_state("draft-iesg").name in txt) + + # not active in IESG process + draft.unset_state("draft-iesg") + + txt = all_id_txt() + self.assertTrue(draft.name + "-" + draft.rev in txt) + self.assertTrue("Active" in txt) + + # published + draft.set_state(State.objects.get(type="draft", slug="rfc")) + DocAlias.objects.create(name="rfc1234", document=draft) + + txt = all_id_txt() + self.assertTrue(draft.name + "-" + draft.rev in txt) + self.assertTrue("RFC\t1234" in txt) + + # replaced + draft.set_state(State.objects.get(type="draft", slug="repl")) + + RelatedDocument.objects.create( + relationship=DocRelationshipName.objects.get(slug="replaces"), + source=Document.objects.create(type_id="draft", rev="00", name="draft-test-replacement"), + target=draft.docalias_set.get(name__startswith="draft")) + + txt = all_id_txt() + self.assertTrue(draft.name + "-" + draft.rev in txt) + self.assertTrue("Replaced replaced by draft-test-replacement" in txt) + + def test_all_id2_txt(self): + draft = make_test_data() + + def get_fields(content): + self.assertTrue(draft.name + "-" + draft.rev in content) + + for line in content.splitlines(): + if line.startswith(draft.name + "-" + draft.rev): + return line.split("\t") + # test Active + draft.set_state(State.objects.get(type="draft", slug="active")) + draft.set_state(State.objects.get(type="draft-iesg", slug="review-e")) + + NewRevisionDocEvent.objects.create(doc=draft, type="new_revision", rev=draft.rev, by=draft.ad) + + self.write_draft_file("%s-%s.txt" % (draft.name, draft.rev), 5000) + self.write_draft_file("%s-%s.pdf" % (draft.name, draft.rev), 5000) + + t = get_fields(all_id2_txt()) + self.assertEqual(t[0], draft.name + "-" + draft.rev) + self.assertEqual(t[1], "-1") + self.assertEqual(t[2], "Active") + self.assertEqual(t[3], "Expert Review") + self.assertEqual(t[4], "") + self.assertEqual(t[5], "") + self.assertEqual(t[6], draft.latest_event(type="new_revision").time.strftime("%Y-%m-%d")) + self.assertEqual(t[7], draft.group.acronym) + self.assertEqual(t[8], draft.group.parent.acronym) + self.assertEqual(t[9], unicode(draft.ad)) + self.assertEqual(t[10], draft.intended_std_level.name) + self.assertEqual(t[11], "") + self.assertEqual(t[12], ".pdf,.txt") + self.assertEqual(t[13], draft.title) + author = draft.documentauthor_set.order_by("order").get() + self.assertEqual(t[14], "%s <%s>" % (author.author.person.name, author.author.address)) + self.assertEqual(t[15], "%s <%s>" % (draft.shepherd, draft.shepherd.email_address())) + self.assertEqual(t[16], "%s <%s>" % (draft.ad, draft.ad.email_address())) + + + # test RFC + draft.set_state(State.objects.get(type="draft", slug="rfc")) + DocAlias.objects.create(name="rfc1234", document=draft) + t = get_fields(all_id2_txt()) + self.assertEqual(t[4], "1234") + + # test Replaced + draft.set_state(State.objects.get(type="draft", slug="repl")) + RelatedDocument.objects.create( + relationship=DocRelationshipName.objects.get(slug="replaces"), + source=Document.objects.create(type_id="draft", rev="00", name="draft-test-replacement"), + target=draft.docalias_set.get(name__startswith="draft")) + + t = get_fields(all_id2_txt()) + self.assertEqual(t[5], "draft-test-replacement") + + # test Last Call + draft.set_state(State.objects.get(type="draft", slug="active")) + draft.set_state(State.objects.get(type="draft-iesg", slug="lc")) + + e = LastCallDocEvent.objects.create(doc=draft, type="sent_last_call", expires=datetime.datetime.now() + datetime.timedelta(days=14), by=draft.ad) + + DocAlias.objects.create(name="rfc1234", document=draft) + t = get_fields(all_id2_txt()) + self.assertEqual(t[11], e.expires.strftime("%Y-%m-%d")) + + + def test_id_index_txt(self): + draft = make_test_data() + + draft.set_state(State.objects.get(type="draft", slug="active")) + + txt = id_index_txt() + + self.assertTrue(draft.name + "-" + draft.rev in txt) + self.assertTrue(draft.title in txt) + + self.assertTrue(draft.abstract[:20] not in txt) + + txt = id_index_txt(with_abstracts=True) + + self.assertTrue(draft.abstract[:20] in txt) diff --git a/ietf/idindex/testurl.list b/ietf/idindex/testurl.list deleted file mode 100644 index 5ba17e942..000000000 --- a/ietf/idindex/testurl.list +++ /dev/null @@ -1,20 +0,0 @@ -301 /drafts/wgid/1041/ -404 /drafts/wgid/987654/ -301 /drafts/wg/idr/ -301 /drafts/rfc/ -301 /drafts/current/ -301 /drafts/all/ -301 /drafts/dead/ -#301 /drafts/9574/related/ -#301 /drafts/9574/ -301 /drafts/draft-ietf-dnsext-dnssec-protocol/related/ -301 /drafts/draft-ietf-dnsext-dnssec-protocol/ -#404 /drafts/987654/ -301 /drafts/all_id_txt.html -301 /drafts/all_id.html -301 /drafts/ -#200,heavy /drafts/_test/all_id.txt -# this takes 3 minutes, so disabled by default -#200,heavy /drafts/_test/all_id2.txt -#200,heavy /drafts/_test/id_index.txt -#200,heavy /drafts/_test/id_abstracts.txt diff --git a/ietf/idindex/urls.py b/ietf/idindex/urls.py deleted file mode 100644 index 70f14777a..000000000 --- a/ietf/idindex/urls.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright The IETF Trust 2007, All Rights Reserved - -from django.conf import settings -from django.conf.urls.defaults import patterns -from ietf.idindex import views - -urlpatterns = patterns('', - (r'^$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/'}), - (r'^all/$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/all/'}), - (r'^rfc/$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/all/#rfc'}), - (r'^dead/$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/all/#dead'}), - (r'^current/$', 'django.views.generic.simple.redirect_to', { 'url': '/doc/active/'}), - (r'^(?P\d+)/(related/)?$', views.redirect_id), - (r'^(?P[^/]+)/(related/)?$', views.redirect_filename), - (r'^wgid/(?P\d+)/$', views.wgdocs_redirect_id), - (r'^wg/(?P[^/]+)/$', views.wgdocs_redirect_acronym), - (r'^all_id(?:_txt)?.html$', 'django.views.generic.simple.redirect_to', { 'url': 'http://www.ietf.org/id/all_id.txt' }), -) - -if settings.SERVER_MODE != 'production': - # these haven't been ported - urlpatterns += patterns('', - (r'^_test/all_id.txt$', views.test_all_id_txt), - (r'^_test/all_id2.txt$', views.test_all_id2_txt), - (r'^_test/id_index.txt$', views.test_id_index_txt), - (r'^_test/id_abstracts.txt$', views.test_id_abstracts_txt) - ) diff --git a/ietf/idindex/views.py b/ietf/idindex/views.py deleted file mode 100644 index 584693733..000000000 --- a/ietf/idindex/views.py +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright The IETF Trust 2007, All Rights Reserved - -# Portions Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -# All rights reserved. Contact: Pasi Eronen -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# -# * Neither the name of the Nokia Corporation and/or its -# subsidiary(-ies) nor the names of its contributors may be used -# to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from django.http import HttpResponse, HttpResponsePermanentRedirect -from django.template import loader -from django.shortcuts import get_object_or_404 -from django.conf import settings - -from ietf.idtracker.models import Acronym, IETFWG, InternetDraft, IDInternal,PersonOrOrgInfo, Area -from ietf.idtracker.templatetags.ietf_filters import clean_whitespace -import re -import sys -from datetime import datetime as Datetime -import pytz - -def all_id_txt(): - # we need a distinct to prevent the queries below from multiplying the result - all_ids = InternetDraft.objects.order_by('name').exclude(name__startswith="rfc").distinct() - - inactive_states = ["pub", "watching", "dead"] - - in_track_ids = all_ids.exclude(states__type="draft", states__slug="rfc").filter(states__type="draft-iesg").exclude(states__type="draft-iesg", states__slug__in=inactive_states) - not_in_track = all_ids.filter(states__type="draft", states__slug="rfc") | all_ids.exclude(states__type="draft-iesg") | all_ids.filter(states__type="draft-iesg", states__slug__in=inactive_states) - - active = not_in_track.filter(states__type="draft", states__slug="active") - published = not_in_track.filter(states__type="draft", states__slug="rfc") - expired = not_in_track.filter(states__type="draft", states__slug="expired") - withdrawn_submitter = not_in_track.filter(states__type="draft", states__slug="auth-rm") - withdrawn_ietf = not_in_track.filter(states__type="draft", states__slug="ietf-rm") - replaced = not_in_track.filter(states__type="draft", states__slug="repl") - - return loader.render_to_string("idindex/all_ids.txt", - { 'in_track_ids':in_track_ids, - 'active':active, - 'published':published, - 'expired':expired, - 'withdrawn_submitter':withdrawn_submitter, - 'withdrawn_ietf':withdrawn_ietf, - 'replaced':replaced}) - -def all_id2_entry(id): - fields = [] - # 0 - fields.append(id.filename+"-"+id.revision_display()) - # 1 - fields.append(-1) # this used to be id.id_document_tag, we don't have this identifier anymore - # 2 - status = str(id.get_state()) - fields.append(status) - # 3 - iesgstate = id.idstate() if status=="Active" else "" - fields.append(iesgstate) - # 4 - fields.append(id.rfc_number if status=="RFC" else "") - # 5 - try: - fields.append(id.replaced_by.filename) - except (AttributeError, InternetDraft.DoesNotExist): - fields.append("") - # 6 - fields.append(id.revision_date) - # 7 - group_acronym = "" if id.group.type_id == "area" else id.group.acronym - if group_acronym == "none": - group_acronym = "" - fields.append(group_acronym) - # 8 - area = "" - if id.group.type_id == "area": - area = id.group.acronym - elif id.group.type_id == "wg" and id.group.parent: - area = id.group.parent.acronym - fields.append(area) - # 9 responsible AD name - fields.append(id.idinternal.job_owner if id.idinternal else "") - # 10 - s = id.intended_status - if s and str(s) not in ("None","Request"): - fields.append(str(s)) - else: - fields.append("") - # 11 - if (iesgstate=="In Last Call") or iesgstate.startswith("In Last Call::"): - fields.append(id.lc_expiration_date) - else: - fields.append("") - # 12 - fields.append(id.file_type if status=="Active" else "") - # 13 - fields.append(clean_whitespace(id.title)) - # 14 - authors = [] - for author in sorted(id.authors.all(), key=lambda x: x.final_author_order()): - try: - realname = unicode(author.person) - email = author.email() or "" - name = re.sub(u"[<>@,]", u"", realname) + u" <"+re.sub(u"[<>,]", u"", email).strip()+u">" - authors.append(clean_whitespace(name)) - except PersonOrOrgInfo.DoesNotExist: - pass - fields.append(u", ".join(authors)) - # 15 - if id.shepherd: - shepherd = id.shepherd - realname = unicode(shepherd) - email = shepherd.email_address() - name = re.sub(u"[<>@,]", u"", realname) + u" <"+re.sub(u"[<>,]", u"", email).strip()+u">" - else: - name = u"" - fields.append(name) - # 16 Responsible AD name and email - if id.ad: - ad = id.ad - realname = unicode(ad) - email = ad.email_address() - name = re.sub(u"[<>@,]", u"", realname) + u" <"+re.sub(u"[<>,]", u"", email).strip()+u">" - else: - name = u"" - fields.append(name) - # - return "\t".join([unicode(x) for x in fields]) - -def all_id2_txt(): - all_ids = InternetDraft.objects.order_by('name').exclude(name__startswith="rfc").select_related('group', 'group__parent', 'ad') - data = "\n".join(all_id2_entry(id) for id in all_ids) - - return loader.render_to_string("idindex/all_id2.txt",{'data':data}) - -def id_index_txt(): - groups = IETFWG.objects.all() - return loader.render_to_string("idindex/id_index.txt", {'groups':groups}) - -def id_abstracts_txt(): - groups = IETFWG.objects.all() - return loader.render_to_string("idindex/id_abstracts.txt", {'groups':groups, 'time':Datetime.now(pytz.UTC).strftime("%Y-%m-%d %H:%M:%S %Z")}) - -def test_all_id_txt(request): - return HttpResponse(all_id_txt(), mimetype='text/plain') -def test_all_id2_txt(request): - return HttpResponse(all_id2_txt(), mimetype='text/plain') -def test_id_index_txt(request): - return HttpResponse(id_index_txt(), mimetype='text/plain') -def test_id_abstracts_txt(request): - return HttpResponse(id_abstracts_txt(), mimetype='text/plain') - -def redirect_id(request, object_id): - '''Redirect from historical document ID to preferred filename url.''' - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - return HttpResponsePermanentRedirect("/doc/") - - doc = get_object_or_404(InternetDraft, id_document_tag=object_id) - return HttpResponsePermanentRedirect("/doc/"+doc.filename+"/") - -def redirect_filename(request, filename): - return HttpResponsePermanentRedirect("/doc/"+filename+"/") - -def wgdocs_redirect_id(request, id): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.group.models import Group - group = get_object_or_404(Group, id=id) - return HttpResponsePermanentRedirect("/wg/%s/" % group.acronym) - - group = get_object_or_404(Acronym, acronym_id=id) - return HttpResponsePermanentRedirect("/wg/"+group.acronym+"/") - -def wgdocs_redirect_acronym(request, acronym): - return HttpResponsePermanentRedirect("/wg/"+acronym+"/") - diff --git a/ietf/idrfc/expire.py b/ietf/idrfc/expire.py index 5428a5fc3..126d6f76b 100644 --- a/ietf/idrfc/expire.py +++ b/ietf/idrfc/expire.py @@ -6,105 +6,64 @@ from django.db.models import Q import datetime, os, shutil, glob, re, itertools -from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment, IDAuthor, WGChair from ietf.utils.mail import send_mail, send_mail_subj -from ietf.idrfc.utils import log_state_changed, add_document_comment -from ietf.doc.models import Document, DocEvent, save_document_in_history, State -from ietf.name.models import DocTagName +from ietf.idrfc.utils import log_state_changed +from ietf.doc.models import Document, DocEvent, State, save_document_in_history, IESG_SUBSTATE_TAGS from ietf.person.models import Person, Email from ietf.meeting.models import Meeting -def in_id_expire_freeze(when=None): - if when == None: - when = datetime.datetime.now() +def expirable_draft(draft): + """Return whether draft is in an expirable state or not. This is + the single draft version of the logic in expirable_drafts. These + two functions need to be kept in sync.""" + return (draft.expires and draft.get_state_slug() == "active" + and draft.get_state_slug("draft-iesg") in (None, "watching", "dead") + and draft.get_state_slug("draft-stream-%s" % draft.stream_id) not in ("rfc-edit", "pub") + and not draft.tags.filter(slug="rfc-rev")) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - d = Meeting.get_second_cut_off() - else: - d = IDDates.objects.get(id=IDDates.SECOND_CUT_OFF).date - # for some reason, the old Perl code started at 9 am - second_cut_off = datetime.datetime.combine(d, datetime.time(9, 0)) - - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - d = Meeting.get_ietf_monday() - else: - d = IDDates.objects.get(id=IDDates.IETF_MONDAY).date - ietf_monday = datetime.datetime.combine(d, datetime.time(0, 0)) - - return second_cut_off <= when < ietf_monday - -def expirable_documents(): +def expirable_drafts(): + """Return a queryset with expirable drafts.""" # the general rule is that each active draft is expirable, unless # it's in a state where we shouldn't touch it - - d = Document.objects.filter(states__type="draft", states__slug="active").exclude(tags="rfc-rev") + d = Document.objects.filter(states__type="draft", states__slug="active").exclude(expires=None) nonexpirable_states = [] # all IESG states except AD Watching and Dead block expiry nonexpirable_states += list(State.objects.filter(used=True, type="draft-iesg").exclude(slug__in=("watching", "dead"))) - # Sent to RFC Editor and RFC Published block expiry (the latter + # sent to RFC Editor and RFC Published block expiry (the latter # shouldn't be possible for an active draft, though) nonexpirable_states += list(State.objects.filter(used=True, type__in=("draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"), slug__in=("rfc-edit", "pub"))) - return d.exclude(states__in=nonexpirable_states).distinct() + d = d.exclude(states__in=nonexpirable_states) -def get_soon_to_expire_ids(days): - start_date = datetime.date.today() - datetime.timedelta(InternetDraft.DAYS_TO_EXPIRE - 1) - end_date = start_date + datetime.timedelta(days - 1) - - for d in InternetDraft.objects.filter(revision_date__gte=start_date,revision_date__lte=end_date,status__status='Active'): - if d.can_expire(): - yield d + # under review by the RFC Editor blocks expiry + d = d.exclude(tags="rfc-rev") -def get_soon_to_expire_idsREDESIGN(days): + return d.distinct() + +def get_soon_to_expire_drafts(days_of_warning): start_date = datetime.date.today() - datetime.timedelta(1) - end_date = start_date + datetime.timedelta(days - 1) + end_date = start_date + datetime.timedelta(days_of_warning) + + return expirable_drafts().filter(expires__gte=start_date, expires__lt=end_date) + +def get_expired_drafts(): + return expirable_drafts().filter(expires__lt=datetime.date.today() + datetime.timedelta(1)) + +def in_draft_expire_freeze(when=None): + if when == None: + when = datetime.datetime.now() + + d = Meeting.get_second_cut_off() + # for some reason, the old Perl code started at 9 am + second_cut_off = datetime.datetime.combine(d, datetime.time(9, 0)) - for d in expirable_documents(): - if d.expires and start_date <= d.expires.date() <= end_date: - yield d - -def get_expired_ids(): - cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE) - - return InternetDraft.objects.filter( - revision_date__lte=cut_off, - status__status="Active", - review_by_rfc_editor=0).filter( - Q(idinternal=None) | Q(idinternal__cur_state__document_state_id__gte=42)) - -def get_expired_idsREDESIGN(): - today = datetime.date.today() - - for d in expirable_documents(): - if d.expires and d.expires.date() <= today: - yield d - -def send_expire_warning_for_id(doc): - expiration = doc.expiration() - # Todo: - #second_cutoff = IDDates.objects.get(date_id=2) - #ietf_monday = IDDates.objects.get(date_id=3) - #freeze_delta = ietf_monday - second_cutoff - # # The I-D expiration job doesn't run while submissions are frozen. - # if ietf_monday > expiration > second_cutoff: - # expiration += freeze_delta + d = Meeting.get_ietf_monday() + ietf_monday = datetime.datetime.combine(d, datetime.time(0, 0)) - authors = doc.authors.all() - to_addrs = [author.email() for author in authors if author.email()] - cc_addrs = None - if doc.group.acronym != 'none': - cc_addrs = [chair.person.email() for chair in WGChair.objects.filter(group_acronym=doc.group)] + return second_cut_off <= when < ietf_monday - if to_addrs or cc_addrs: - send_mail_subj(None, to_addrs, None, 'notify_expirations/subject.txt', 'notify_expirations/body.txt', - { - 'draft':doc, - 'expiration':expiration, - }, - cc_addrs) - -def send_expire_warning_for_idREDESIGN(doc): +def send_expire_warning_for_draft(doc): if doc.get_state_slug("draft-iesg") == "dead": return # don't warn about dead documents @@ -130,23 +89,7 @@ def send_expire_warning_for_idREDESIGN(doc): ), cc=cc) -def send_expire_notice_for_id(doc): - doc.dunn_sent_date = datetime.date.today() - doc.save() - - if not doc.idinternal: - return - - request = None - to = u"%s <%s>" % doc.idinternal.job_owner.person.email() - send_mail(request, to, - "I-D Expiring System ", - u"I-D was expired %s" % doc.file_tag(), - "idrfc/id_expired_email.txt", - dict(doc=doc, - state=doc.idstate())) - -def send_expire_notice_for_idREDESIGN(doc): +def send_expire_notice_for_draft(doc): if not doc.ad or doc.get_state_slug("draft-iesg") == "dead": return @@ -163,42 +106,6 @@ def send_expire_notice_for_idREDESIGN(doc): state=state, )) -def expire_id(doc): - def move_file(f): - src = os.path.join(settings.INTERNET_DRAFT_PATH, f) - dst = os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, f) - - if os.path.exists(src): - shutil.move(src, dst) - - move_file("%s-%s.txt" % (doc.filename, doc.revision_display())) - move_file("%s-%s.txt.p7s" % (doc.filename, doc.revision_display())) - move_file("%s-%s.ps" % (doc.filename, doc.revision_display())) - move_file("%s-%s.pdf" % (doc.filename, doc.revision_display())) - - new_revision = "%02d" % (int(doc.revision) + 1) - - new_file = open(os.path.join(settings.INTERNET_DRAFT_PATH, "%s-%s.txt" % (doc.filename, new_revision)), 'w') - txt = render_to_string("idrfc/expire_text.txt", - dict(doc=doc, - authors=[a.person.email() for a in doc.authors.all()], - expire_days=InternetDraft.DAYS_TO_EXPIRE)) - new_file.write(txt) - new_file.close() - - doc.revision = new_revision - doc.expiration_date = datetime.date.today() - doc.last_modified_date = datetime.date.today() - doc.status = IDStatus.objects.get(status="Expired") - doc.save() - - if doc.idinternal: - if doc.idinternal.cur_state_id != IDState.DEAD: - doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.DEAD), None) - log_state_changed(None, doc, "system") - - add_document_comment(None, doc, "Document is expired by system") - def move_draft_files_to_archive(doc, rev): def move_file(f): src = os.path.join(settings.INTERNET_DRAFT_PATH, f) @@ -211,7 +118,7 @@ def move_draft_files_to_archive(doc, rev): for t in file_types: move_file("%s-%s.%s" % (doc.name, rev, t)) -def expire_idREDESIGN(doc): +def expire_draft(doc): # clean up files move_draft_files_to_archive(doc, doc.rev) @@ -222,7 +129,7 @@ def expire_idREDESIGN(doc): if doc.latest_event(type='started_iesg_process'): dead_state = State.objects.get(used=True, type="draft-iesg", slug="dead") prev = doc.get_state("draft-iesg") - prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty')) + prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if prev != dead_state: doc.set_state(dead_state) @@ -239,67 +146,7 @@ def expire_idREDESIGN(doc): doc.time = datetime.datetime.now() doc.save() -def clean_up_id_files(): - """Move unidentified and old files out of the Internet Draft directory.""" - cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE) - - pattern = os.path.join(settings.INTERNET_DRAFT_PATH, "draft-*.*") - files = [] - filename_re = re.compile('^(.*)-(\d\d)$') - - def splitext(fn): - """ - Split the pathname path into a pair (root, ext) such that root + ext - == path, and ext is empty or begins with a period and contains all - periods in the last path component. - - This differs from os.path.splitext in the number of periods in the ext - parts when the final path component containt more than one period. - """ - s = fn.rfind("/") - if s == -1: - s = 0 - i = fn[s:].find(".") - if i == -1: - return fn, '' - else: - return fn[:s+i], fn[s+i:] - - for path in glob.glob(pattern): - basename = os.path.basename(path) - stem, ext = splitext(basename) - match = filename_re.search(stem) - if not match: - filename, revision = ("UNKNOWN", "00") - else: - filename, revision = match.groups() - - def move_file_to(subdir): - shutil.move(path, - os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, subdir, basename)) - - try: - doc = InternetDraft.objects.get(filename=filename, revision=revision) - - if doc.status_id == 3: # RFC - if ext != ".txt": - move_file_to("unknown_ids") - elif doc.status_id in (2, 4, 5, 6) and doc.expiration_date and doc.expiration_date < cut_off: - # Expired, Withdrawn by Auth, Replaced, Withdrawn by IETF, - # and expired more than DAYS_TO_EXPIRE ago - if os.path.getsize(path) < 1500: - move_file_to("deleted_tombstones") - # revert version after having deleted tombstone - doc.revision = "%02d" % (int(revision) - 1) - doc.expired_tombstone = True - doc.save() - else: - move_file_to("expired_without_tombstone") - - except InternetDraft.DoesNotExist: - move_file_to("unknown_ids") - -def clean_up_id_filesREDESIGN(): +def clean_up_draft_files(): """Move unidentified and old files out of the Internet Draft directory.""" cut_off = datetime.date.today() @@ -314,7 +161,7 @@ def clean_up_id_filesREDESIGN(): periods in the last path component. This differs from os.path.splitext in the number of periods in the ext - parts when the final path component containt more than one period. + parts when the final path component contains more than one period. """ s = fn.rfind("/") if s == -1: @@ -357,11 +204,3 @@ def clean_up_id_filesREDESIGN(): except Document.DoesNotExist: move_file_to("unknown_ids") - -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - get_soon_to_expire_ids = get_soon_to_expire_idsREDESIGN - get_expired_ids = get_expired_idsREDESIGN - send_expire_warning_for_id = send_expire_warning_for_idREDESIGN - send_expire_notice_for_id = send_expire_notice_for_idREDESIGN - expire_id = expire_idREDESIGN - clean_up_id_files = clean_up_id_filesREDESIGN diff --git a/ietf/idrfc/lastcall.py b/ietf/idrfc/lastcall.py index 2a6a52ac2..7ff40284c 100644 --- a/ietf/idrfc/lastcall.py +++ b/ietf/idrfc/lastcall.py @@ -8,7 +8,7 @@ from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo from ietf.idrfc.mails import * from ietf.idrfc.utils import * -from ietf.doc.models import Document, DocEvent, LastCallDocEvent, WriteupDocEvent, save_document_in_history, State +from ietf.doc.models import * from ietf.person.models import Person def request_last_call(request, doc): @@ -83,7 +83,7 @@ def expire_last_callREDESIGN(doc): prev = doc.get_state("draft-iesg") doc.set_state(state) - prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty')) + prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if prev_tag: doc.tags.remove(prev_tag) diff --git a/ietf/idrfc/templatetags/ballot_icon_redesign.py b/ietf/idrfc/templatetags/ballot_icon_redesign.py index 12cc93315..29f0b79e7 100644 --- a/ietf/idrfc/templatetags/ballot_icon_redesign.py +++ b/ietf/idrfc/templatetags/ballot_icon_redesign.py @@ -30,42 +30,31 @@ # (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 + from django import template from django.core.urlresolvers import reverse as urlreverse from django.conf import settings from django.db.models import Q -from ietf.idtracker.models import IDInternal, BallotInfo -from ietf.idrfc.idrfc_wrapper import position_to_string, BALLOT_ACTIVE_STATES -from ietf.idtracker.templatetags.ietf_filters import in_group, timesince_days -from ietf.ietfauth.decorators import has_role -from ietf.doc.models import BallotDocEvent, BallotPositionDocEvent +from django.utils.safestring import mark_safe -from datetime import date +from ietf.ietfauth.utils import user_is_person, has_role +from ietf.doc.models import BallotDocEvent, BallotPositionDocEvent, IESG_BALLOT_ACTIVE_STATES, IESG_SUBSTATE_TAGS register = template.Library() -def get_user_name(context): - if 'user' in context and context['user'].is_authenticated(): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.person.models import Person - try: - return context['user'].get_profile().plain_name() - except Person.DoesNotExist: - return None - - person = context['user'].get_profile().person() - if person: - return str(person) - return None - def render_ballot_icon(user, doc): if not doc: return "" + # FIXME: temporary backwards-compatibility hack + from ietf.doc.models import Document + if not isinstance(doc, Document): + doc = doc._draft + if doc.type_id == "draft": - s = doc.get_state("draft-iesg") - if s and s.name not in BALLOT_ACTIVE_STATES: + if doc.get_state_slug("draft-iesg") not in IESG_BALLOT_ACTIVE_STATES: return "" elif doc.type_id == "charter": if doc.get_state_slug() not in ("intrev", "iesgrev"): @@ -77,12 +66,10 @@ def render_ballot_icon(user, doc): if doc.get_state_slug() not in ("iesgeval","defer"): return "" - ballot = doc.latest_event(BallotDocEvent, type="created_ballot") + ballot = doc.active_ballot() if not ballot: return "" - edit_position_url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk)) - def sort_key(t): _, pos = t if not pos: @@ -95,11 +82,18 @@ def render_ballot_icon(user, doc): positions = list(doc.active_ballot().active_ad_positions().items()) positions.sort(key=sort_key) - cm = "" + edit_position_url = "" if has_role(user, "Area Director"): - cm = ' oncontextmenu="editBallot(\''+str(edit_position_url)+'\');return false;"' + edit_position_url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk)) - res = [''] + title = "IESG positions (click to show more%s)" % (", right-click to edit position" if edit_position_url else "") + + res = ['
' % ( + urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot_id=ballot.pk)), + urlreverse("ietf.doc.views_doc.ballot_popup", kwargs=dict(name=doc.name, ballot_id=ballot.pk)), + edit_position_url, + title + )] res.append("") @@ -110,13 +104,13 @@ def render_ballot_icon(user, doc): c = "position-%s" % (pos.pos.slug if pos else "norecord") - if hasattr(user, "get_profile") and ad == user.get_profile(): + if user_is_person(user, ad): c += " my" res.append('") - res.append("
' % c) res.append("
") + res.append("") return "".join(res) @@ -125,48 +119,51 @@ class BallotIconNode(template.Node): self.doc_var = doc_var def render(self, context): doc = template.resolve_variable(self.doc_var, context) - #if hasattr(doc, "_idinternal"): - # # hack for old schema - # doc = doc._idinternal return render_ballot_icon(context.get("user"), doc) def do_ballot_icon(parser, token): try: - tagName, docName = token.split_contents() + tag_name, doc_name = token.split_contents() except ValueError: raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents.split()[0] - return BallotIconNode(docName) + return BallotIconNode(doc_name) register.tag('ballot_icon', do_ballot_icon) + @register.filter def my_position(doc, user): - user_name = get_user_name({'user':user}) - if not user_name: - return None - if not in_group(user, "Area_Director"): + if not has_role(user, "Area Director"): return None + # FIXME: temporary backwards-compatibility hack + from ietf.doc.models import Document + if not isinstance(doc, Document): + doc = doc._draft + ballot = doc.active_ballot() pos = "No Record" if ballot: - changed_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad__name=user_name, ballot=ballot) - if changed_pos: - pos = changed_pos.pos.name; + changed_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad__user=user, ballot=ballot) + if changed_pos: + pos = changed_pos.pos.name; return pos -@register.filter +@register.filter() def state_age_colored(doc): - if doc.type.slug == 'draft': - if not doc.latest_event(type='started_iesg_process'): - return "" + # FIXME: temporary backwards-compatibility hack + from ietf.doc.models import Document + if not isinstance(doc, Document): + doc = doc._draft + + if doc.type_id == 'draft': if not doc.get_state_slug() in ["active", "rfc"]: # Don't show anything for expired/withdrawn/replaced drafts return "" - main_state = doc.get_state('draft-iesg') - IESG_SUBSTATE_TAGS = ('point', 'ad-f-up', 'need-rev', 'extpty') - sub_states = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) + main_state = doc.get_state_slug('draft-iesg') + if not main_state: + return "" - if main_state.slug in ["dead","watching","pub"]: + if main_state in ["dead", "watching", "pub"]: return "" try: state_date = doc.docevent_set.filter( @@ -181,26 +178,26 @@ def state_age_colored(doc): Q(desc__istartswith="IESG process started in state") ).order_by('-time')[0].time.date() except IndexError: - state_date = date(1990,1,1) - days = timesince_days(state_date) + state_date = datetime.date(1990,1,1) + days = (datetime.date.today() - state_date).days # loosely based on # http://trac.tools.ietf.org/group/iesg/trac/wiki/PublishPath - if main_state.slug == "lc": + if main_state == "lc": goal1 = 30 goal2 = 30 - elif main_state.slug == "rfcqueue": + elif main_state == "rfcqueue": goal1 = 60 goal2 = 120 - elif main_state.slug in ["lc-req", "ann"]: + elif main_state in ["lc-req", "ann"]: goal1 = 4 goal2 = 7 - elif 'need-rev' in [x.slug for x in sub_states]: + elif 'need-rev' in [x.slug for x in doc.tags.all()]: goal1 = 14 goal2 = 28 - elif main_state.slug == "pub-req": + elif main_state == "pub-req": goal1 = 7 goal2 = 14 - elif main_state.slug == "ad-eval": + elif main_state == "ad-eval": goal1 = 14 goal2 = 28 else: @@ -216,6 +213,7 @@ def state_age_colored(doc): title = ' title="Goal is <%d days"' % (goal1,) else: title = '' - return '(for %d day%s)' % (class_name,title,days,('','s')[days != 1]) + return mark_safe('(for %d day%s)' % ( + class_name, title, days, 's' if days != 1 else '')) else: return "" diff --git a/ietf/idrfc/tests.py b/ietf/idrfc/tests.py index 9903aa59c..5ec9ddc5b 100644 --- a/ietf/idrfc/tests.py +++ b/ietf/idrfc/tests.py @@ -1013,13 +1013,13 @@ class ExpireIDsTestCase(django.test.TestCase): shutil.rmtree(self.id_dir) shutil.rmtree(self.archive_dir) - def write_id_file(self, name, size): + def write_draft_file(self, name, size): f = open(os.path.join(self.id_dir, name), 'w') f.write("a" * size) f.close() - def test_in_id_expire_freeze(self): - from ietf.idrfc.expire import in_id_expire_freeze + def test_in_draft_expire_freeze(self): + from ietf.idrfc.expire import in_draft_expire_freeze Meeting.objects.create(number="123", type=MeetingTypeName.objects.get(slug="ietf"), @@ -1027,70 +1027,70 @@ class ExpireIDsTestCase(django.test.TestCase): second_cut_off = Meeting.get_second_cut_off() ietf_monday = Meeting.get_ietf_monday() - self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(second_cut_off - datetime.timedelta(days=7), time(0, 0, 0)))) - self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(second_cut_off, time(0, 0, 0)))) - self.assertTrue(in_id_expire_freeze(datetime.datetime.combine(second_cut_off + datetime.timedelta(days=7), time(0, 0, 0)))) - self.assertTrue(in_id_expire_freeze(datetime.datetime.combine(ietf_monday - datetime.timedelta(days=1), time(0, 0, 0)))) - self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(ietf_monday, time(0, 0, 0)))) + self.assertTrue(not in_draft_expire_freeze(datetime.datetime.combine(second_cut_off - datetime.timedelta(days=7), time(0, 0, 0)))) + self.assertTrue(not in_draft_expire_freeze(datetime.datetime.combine(second_cut_off, time(0, 0, 0)))) + self.assertTrue(in_draft_expire_freeze(datetime.datetime.combine(second_cut_off + datetime.timedelta(days=7), time(0, 0, 0)))) + self.assertTrue(in_draft_expire_freeze(datetime.datetime.combine(ietf_monday - datetime.timedelta(days=1), time(0, 0, 0)))) + self.assertTrue(not in_draft_expire_freeze(datetime.datetime.combine(ietf_monday, time(0, 0, 0)))) - def test_warn_expirable_ids(self): - from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id + def test_warn_expirable_drafts(self): + from ietf.idrfc.expire import get_soon_to_expire_drafts, send_expire_warning_for_draft draft = make_test_data() - self.assertEquals(len(list(get_soon_to_expire_ids(14))), 0) + self.assertEquals(len(list(get_soon_to_expire_drafts(14))), 0) # hack into expirable state draft.unset_state("draft-iesg") draft.expires = datetime.datetime.now() + datetime.timedelta(days=10) draft.save() - self.assertEquals(len(list(get_soon_to_expire_ids(14))), 1) + self.assertEquals(len(list(get_soon_to_expire_drafts(14))), 1) # test send warning mailbox_before = len(outbox) - send_expire_warning_for_id(draft) + send_expire_warning_for_draft(draft) self.assertEquals(len(outbox), mailbox_before + 1) self.assertTrue("aread@ietf.org" in str(outbox[-1])) # author self.assertTrue("wgchairman@ietf.org" in str(outbox[-1])) - def test_expire_ids(self): - from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id + def test_expire_drafts(self): + from ietf.idrfc.expire import get_expired_drafts, send_expire_notice_for_draft, expire_draft draft = make_test_data() - self.assertEquals(len(list(get_expired_ids())), 0) + self.assertEquals(len(list(get_expired_drafts())), 0) # hack into expirable state draft.unset_state("draft-iesg") draft.expires = datetime.datetime.now() draft.save() - self.assertEquals(len(list(get_expired_ids())), 1) + self.assertEquals(len(list(get_expired_drafts())), 1) draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="watching")) - self.assertEquals(len(list(get_expired_ids())), 1) + self.assertEquals(len(list(get_expired_drafts())), 1) draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) - self.assertEquals(len(list(get_expired_ids())), 0) + self.assertEquals(len(list(get_expired_drafts())), 0) # test notice mailbox_before = len(outbox) - send_expire_notice_for_id(draft) + send_expire_notice_for_draft(draft) self.assertEquals(len(outbox), mailbox_before + 1) self.assertTrue("expired" in outbox[-1]["Subject"]) # test expiry txt = "%s-%s.txt" % (draft.name, draft.rev) - self.write_id_file(txt, 5000) + self.write_draft_file(txt, 5000) - expire_id(draft) + expire_draft(draft) draft = Document.objects.get(name=draft.name) self.assertEquals(draft.get_state_slug(), "expired") @@ -1099,16 +1099,16 @@ class ExpireIDsTestCase(django.test.TestCase): self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) self.assertTrue(os.path.exists(os.path.join(self.archive_dir, txt))) - def test_clean_up_id_files(self): + def test_clean_up_draft_files(self): draft = make_test_data() - from ietf.idrfc.expire import clean_up_id_files + from ietf.idrfc.expire import clean_up_draft_files # put unknown file unknown = "draft-i-am-unknown-01.txt" - self.write_id_file(unknown, 5000) + self.write_draft_file(unknown, 5000) - clean_up_id_files() + clean_up_draft_files() self.assertTrue(not os.path.exists(os.path.join(self.id_dir, unknown))) self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", unknown))) @@ -1116,9 +1116,9 @@ class ExpireIDsTestCase(django.test.TestCase): # put file with malformed name (no revision) malformed = draft.name + ".txt" - self.write_id_file(malformed, 5000) + self.write_draft_file(malformed, 5000) - clean_up_id_files() + clean_up_draft_files() self.assertTrue(not os.path.exists(os.path.join(self.id_dir, malformed))) self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", malformed))) @@ -1129,11 +1129,11 @@ class ExpireIDsTestCase(django.test.TestCase): draft.save() txt = "%s-%s.txt" % (draft.name, draft.rev) - self.write_id_file(txt, 5000) + self.write_draft_file(txt, 5000) pdf = "%s-%s.pdf" % (draft.name, draft.rev) - self.write_id_file(pdf, 5000) + self.write_draft_file(pdf, 5000) - clean_up_id_files() + clean_up_draft_files() # txt files shouldn't be moved (for some reason) self.assertTrue(os.path.exists(os.path.join(self.id_dir, txt))) @@ -1157,9 +1157,9 @@ class ExpireIDsTestCase(django.test.TestCase): # expired without tombstone txt = "%s-%s.txt" % (draft.name, draft.rev) - self.write_id_file(txt, 5000) + self.write_draft_file(txt, 5000) - clean_up_id_files() + clean_up_draft_files() self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "expired_without_tombstone", txt))) @@ -1169,9 +1169,9 @@ class ExpireIDsTestCase(django.test.TestCase): revision_before = draft.rev txt = "%s-%s.txt" % (draft.name, draft.rev) - self.write_id_file(txt, 1000) # < 1500 means tombstone + self.write_draft_file(txt, 1000) # < 1500 means tombstone - clean_up_id_files() + clean_up_draft_files() self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "deleted_tombstones", txt))) diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py index 71cabcb9a..32e5415b5 100644 --- a/ietf/idrfc/urls.py +++ b/ietf/idrfc/urls.py @@ -37,15 +37,20 @@ from ietf.doc import views_status_change from ietf.doc import views_doc urlpatterns = patterns('', - (r'^/?$', views_search.search_main), - (r'^search/$', views_search.search_results), - (r'^all/$', views_search.all), - (r'^active/$', views_search.active), - (r'^in-last-call/$', views_search.in_last_call), - url(r'^rfc-status-changes/$', views_status_change.rfc_status_changes, name='rfc_status_changes'), - url(r'^start-rfc-status-change/(?P[A-Za-z0-9._+-]*)$', views_status_change.start_rfc_status_change, name='start_rfc_status_change'), - url(r'^ad/(?P[A-Za-z0-9.-]+)/$', views_search.by_ad, name="doc_search_by_ad"), - url(r'^ad2/(?P[A-Za-z0-9.-]+)/$', views_search.by_ad2, name="doc_search_by_ad2"), + (r'^/?$', views_search.search), + 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.drafts_for_ad, name="drafts_for_ad"), +# (r'^all/$', views_search.all), # XXX CHECK MERGE +# (r'^active/$', views_search.active), # XXX CHECK MERGE + url(r'^rfc-status-changes/$', views_status_change.rfc_status_changes, name='rfc_status_changes'), + url(r'^start-rfc-status-change/(?P[A-Za-z0-9._+-]*)$', views_status_change.start_rfc_status_change, name='start_rfc_status_change'), + url(r'^iesg/(?P[A-Za-z0-9.-]+/)?$', views_search.drafts_in_iesg_process, name="drafts_in_iesg_process"), + +# url(r'^ad2/(?P[A-Za-z0-9.-]+)/$', views_search.by_ad2, name="doc_search_by_ad2"), + + url(r'^all/$', views_search.index_all_drafts, name="index_all_drafts"), + url(r'^active/$', views_search.index_active_drafts, name="index_active_drafts"), url(r'^(?P[A-Za-z0-9._+-]+)/((?P[0-9-]+)/)?$', views_doc.document_main, name="doc_view"), url(r'^(?P[A-Za-z0-9._+-]+)/history/$', views_doc.document_history, name="doc_history"), @@ -56,7 +61,7 @@ urlpatterns = patterns('', url(r'^(?P[A-Za-z0-9._+-]+)/ballot/(?P[0-9]+)/$', views_doc.document_ballot, name="doc_ballot"), url(r'^(?P[A-Za-z0-9._+-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"), (r'^(?P[A-Za-z0-9._+-]+)/doc.json$', views_doc.document_json), - (r'^(?P[A-Za-z0-9._+-]+)/ballotpopup/$', views_doc.ballot_for_popup), + (r'^(?P[A-Za-z0-9._+-]+)/ballotpopup/(?P[0-9]+)/$', views_doc.ballot_popup), #(r'^(?P[A-Za-z0-9._+-]+)/ballot.json$', views_doc.ballot_json), # legacy view url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/$', views_edit.change_state, name='doc_change_state'), # IESG state @@ -86,6 +91,8 @@ urlpatterns = patterns('', url(r'^(?P[A-Za-z0-9._+-]+)/edit/approveballot/$', views_ballot.approve_ballot, name='doc_approve_ballot'), url(r'^(?P[A-Za-z0-9._+-]+)/edit/makelastcall/$', views_ballot.make_last_call, name='doc_make_last_call'), + url(r'^help/state/(?P[\w-]+)/$', 'ietf.doc.views_help.state_help', name="state_help"), + (r'^(?Pcharter-[A-Za-z0-9._+-]+)/', include('ietf.wgcharter.urls')), (r'^(?P[A-Za-z0-9._+-]+)/conflict-review/', include('ietf.doc.urls_conflict_review')), (r'^(?P[A-Za-z0-9._+-]+)/status-change/', include('ietf.doc.urls_status_change')), @@ -97,4 +104,3 @@ urlpatterns += patterns('django.views.generic.simple', url(r'^help/state/status-change/$', 'direct_to_template', { 'template': 'doc/states.html', 'extra_context': { 'states': State.objects.filter(type="statchg").order_by("order"),'title':"RFC Status Change" } }, name='help_status_change_states'), ) - diff --git a/ietf/idrfc/utils.py b/ietf/idrfc/utils.py index e0fababef..4cc8df32b 100644 --- a/ietf/idrfc/utils.py +++ b/ietf/idrfc/utils.py @@ -64,12 +64,12 @@ def log_state_changed(request, doc, by, email_watch_list=True, note=''): return change def log_state_changedREDESIGN(request, doc, by, prev_iesg_state, prev_iesg_tag): - from ietf.doc.models import DocEvent + from ietf.doc.models import DocEvent, IESG_SUBSTATE_TAGS state = doc.get_state("draft-iesg") state_name = state.name - tags = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty')) + tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) if tags: state_name += "::" + tags[0].name diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index fce68359f..3dfefe5b8 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -453,7 +453,7 @@ def defer_ballotREDESIGN(request, name): prev_state = doc.friendly_state() if doc.type_id == 'draft': doc.set_state(State.objects.get(used=True, type="draft-iesg", slug='defer')) - prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty')) + prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if prev_tag: doc.tags.remove(prev_tag) @@ -641,7 +641,7 @@ def lastcalltextREDESIGN(request, name): prev = doc.get_state("draft-iesg") doc.set_state(State.objects.get(used=True, type="draft-iesg", slug='lc-req')) - prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty')) + prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if prev_tag: doc.tags.remove(prev_tag) @@ -1035,7 +1035,7 @@ def approve_ballotREDESIGN(request, name): prev = doc.get_state("draft-iesg") doc.set_state(new_state) - prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty')) + prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if prev_tag: doc.tags.remove(prev_tag) diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index e6e781c83..a663f916d 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -47,8 +47,6 @@ class ChangeStateForm(forms.Form): def change_state(request, name): pass -IESG_SUBSTATE_TAGS = ('point', 'ad-f-up', 'need-rev', 'extpty') - class ChangeStateFormREDESIGN(forms.Form): state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label=None, required=True) substate = forms.ModelChoiceField(DocTagName.objects.filter(slug__in=IESG_SUBSTATE_TAGS), required=False) @@ -93,7 +91,7 @@ def change_stateREDESIGN(request, name): # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate - prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty')) + prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if next_state != prev_state or tag != prev_tag: @@ -144,7 +142,7 @@ def change_stateREDESIGN(request, name): else: state = doc.get_state("draft-iesg") - t = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty')) + t = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) form = ChangeStateForm(initial=dict(state=state.pk if state else None, substate=t[0].pk if t else None)) form.docname=name diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index 8863545ab..9c5b47857 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -31,29 +31,26 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import re, datetime + from django import forms from django.shortcuts import render_to_response from django.db.models import Q from django.template import RequestContext -from django.views.decorators.cache import cache_page -from ietf.idtracker.models import IDState, IESGLogin, IDSubState, Area, InternetDraft, Rfc, IDInternal, IETFWG -from ietf.idrfc.models import RfcIndex -from ietf.ipr.models import IprDraft -from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect -from ietf.idrfc.idrfc_wrapper import IdWrapper,RfcWrapper,IdRfcWrapper -from ietf.utils import normalize_draftname -from django.conf import settings +from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect +from ietf.idrfc.expire import expirable_draft +from ietf.utils import normalize_draftname from ietf.doc.models import * from ietf.person.models import * from ietf.group.models import * +from ietf.ipr.models import IprDocAlias +from ietf.idindex.index import active_drafts_index_by_group class SearchForm(forms.Form): name = forms.CharField(required=False) - rfcs = forms.BooleanField(required=False,initial=True) - activeDrafts = forms.BooleanField(required=False,initial=True) - oldDrafts = forms.BooleanField(required=False,initial=False) - lucky = forms.BooleanField(required=False,initial=False) + rfcs = forms.BooleanField(required=False, initial=True) + activedrafts = forms.BooleanField(required=False, initial=True) + olddrafts = forms.BooleanField(required=False, initial=False) by = forms.ChoiceField(choices=[(x,x) for x in ('author','group','area','ad','state')], required=False, initial='wg', label='Foobar') author = forms.CharField(required=False) @@ -61,7 +58,9 @@ class SearchForm(forms.Form): area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), empty_label="any area", required=False) ad = forms.ChoiceField(choices=(), required=False) state = forms.ModelChoiceField(State.objects.filter(type="draft-iesg"), empty_label="any state", required=False) - subState = forms.ChoiceField(choices=(), required=False) + substate = forms.ChoiceField(choices=(), required=False) + + sort = forms.ChoiceField(choices=(("document", "Document"), ("title", "Title"), ("date", "Date"), ("status", "Status"), ("ipr", "Ipr"), ("ad", "AD")), required=False, widget=forms.HiddenInput) def __init__(self, *args, **kwargs): super(SearchForm, self).__init__(*args, **kwargs) @@ -78,54 +77,138 @@ class SearchForm(forms.Form): inactive_ads.sort(key=extract_last_name) self.fields['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.plain_name()) for ad in active_ads] + [('', '------------------')] + [(ad.pk, ad.name) for ad in inactive_ads] - self.fields['subState'].choices = [('', 'any substate'), ('0', 'no substate')] + [(n.slug, n.name) for n in DocTagName.objects.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))] + self.fields['substate'].choices = [('', 'any substate'), ('0', 'no substate')] + [(n.slug, n.name) for n in DocTagName.objects.filter(slug__in=IESG_SUBSTATE_TAGS)] + def clean_name(self): value = self.cleaned_data.get('name','') return normalize_draftname(value) + def clean(self): q = self.cleaned_data # Reset query['by'] if needed - if 'by' not in q: - q['by'] = None - else: - for k in ('author','group','area','ad'): - if (q['by'] == k) and (k not in q or not q[k]): + if 'by' in q: + for k in ('author', 'group', 'area', 'ad'): + if q['by'] == k and not q.get(k): q['by'] = None - if (q['by'] == 'state') and (not 'state' in q or not 'subState' in q or not (q['state'] or q['subState'])): + if q['by'] == 'state' and not (q.get("state") or q.get('substate')): q['by'] = None # Reset other fields - for k in ('author','group','area','ad'): - if q['by'] != k: - self.data[k] = "" + for k in ('author','group', 'area', 'ad'): + if k != q['by']: q[k] = "" if q['by'] != 'state': - self.data['state'] = "" - self.data['subState'] = "" - q['state'] = "" - q['subState'] = "" + q['state'] = q['substate'] = None return q -def search_query(query_original, sort_by=None): - query = dict(query_original.items()) - drafts = query['activeDrafts'] or query['oldDrafts'] - if (not drafts) and (not query['rfcs']): +def wrap_value(v): + return lambda: v + +def fill_in_search_attributes(docs): + # fill in some attributes for the search results to save some + # hairy template code and avoid repeated SQL queries - remaining + # queries we don't handle here are mostly implicit many-to-many + # relations for which there is poor support in Django 1.2 + + docs_dict = dict((d.pk, d) for d in docs) + doc_ids = docs_dict.keys() + + rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=doc_ids).values_list("document_id", "name")) + + # latest event cache + event_types = ("published_rfc", + "changed_ballot_position", + "started_iesg_process", + "new_revision") + for d in docs: + d.latest_event_cache = dict() + for e in event_types: + d.latest_event_cache[e] = None + + for e in DocEvent.objects.filter(doc__in=doc_ids, type__in=event_types).order_by('time'): + docs_dict[e.doc_id].latest_event_cache[e.type] = e + + # IPR + for d in docs: + d.iprs = [] + + ipr_docaliases = IprDocAlias.objects.filter(doc_alias__document__in=doc_ids).select_related('doc_alias') + for a in ipr_docaliases: + docs_dict[a.doc_alias.document_id].iprs.append(a) + + # telechat date, can't do this with above query as we need to get TelechatDocEvents out + seen = set() + for e in TelechatDocEvent.objects.filter(doc__in=doc_ids, type="scheduled_for_telechat").order_by('-time'): + if e.doc_id not in seen: + d = docs_dict[e.doc_id] + d.telechat_date = wrap_value(d.telechat_date(e)) + seen.add(e.doc_id) + + # misc + for d in docs: + # emulate canonical name which is used by a lot of the utils + d.canonical_name = wrap_value(rfc_aliases[d.pk] if d.pk in rfc_aliases else d.name) + + if d.rfc_number() != None and d.latest_event_cache["published_rfc"]: + d.latest_revision_date = d.latest_event_cache["published_rfc"].time + elif d.latest_event_cache["new_revision"]: + d.latest_revision_date = d.latest_event_cache["new_revision"].time + else: + d.latest_revision_date = d.time + + if d.get_state_slug() == "rfc": + d.search_heading = "RFCs" + elif d.get_state_slug() == "active": + d.search_heading = "Active Internet-Drafts" + else: + d.search_heading = "Old Internet-Drafts" + + d.expirable = expirable_draft(d) + + if d.get_state_slug() != "rfc": + d.milestones = d.groupmilestone_set.filter(state="active").order_by("time").select_related("group") + + + + # RFCs + + # errata + erratas = set(Document.objects.filter(tags="errata", name__in=rfc_aliases.keys()).distinct().values_list("name", flat=True)) + for d in docs: + d.has_errata = d.name in erratas + + # obsoleted/updated by + for a in rfc_aliases: + d = docs_dict[a] + d.obsoleted_by_list = [] + d.updated_by_list = [] + + xed_by = RelatedDocument.objects.filter(target__name__in=rfc_aliases.values(), + relationship__in=("obs", "updates")).select_related('target__document_id') + rel_rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", + document__in=[rel.source_id for rel in xed_by]).values_list('document_id', 'name')) + for rel in xed_by: + d = docs_dict[rel.target.document_id] + if rel.relationship_id == "obs": + l = d.obsoleted_by_list + elif rel.relationship_id == "updates": + l = d.updated_by_list + l.append(rel_rfc_aliases[rel.source_id].upper()) + l.sort() + + +def retrieve_search_results(form): + """Takes a validated SearchForm and return the results.""" + if not form.is_valid(): + raise ValueError("SearchForm doesn't validate: %s" % form.errors) + + query = form.cleaned_data + + if not (query['activedrafts'] or query['olddrafts'] or query['rfcs']): return ([], {}) - # Non-ASCII strings don't match anything; this check - # is currently needed to avoid complaints from MySQL. - # FIXME: this should be fixed with MySQL if it's still a problem? - for k in ['name','author','group']: - try: - tmp = str(query.get(k, '')) - except: - query[k] = '*NOSUCH*' - - # Start by search InternetDrafts - idresults = [] - rfcresults = [] MAX = 500 - docs = InternetDraft.objects.all() + docs = Document.objects.filter(type="draft") # name if query["name"]: @@ -136,9 +219,9 @@ def search_query(query_original, sort_by=None): allowed_states = [] if query["rfcs"]: allowed_states.append("rfc") - if query["activeDrafts"]: + if query["activedrafts"]: allowed_states.append("active") - if query["oldDrafts"]: + if query["olddrafts"]: allowed_states.extend(['repl', 'expired', 'auth-rm', 'ietf-rm']) docs = docs.filter(states__type="draft", states__slug__in=allowed_states) @@ -146,7 +229,6 @@ def search_query(query_original, sort_by=None): # radio choices by = query["by"] if by == "author": - # FIXME: this is full name, not last name as hinted in the HTML docs = docs.filter(authors__person__name__icontains=query["author"]) elif by == "group": docs = docs.filter(group__acronym=query["group"]) @@ -158,66 +240,20 @@ def search_query(query_original, sort_by=None): elif by == "state": if query["state"]: docs = docs.filter(states=query["state"]) - if query["subState"]: - docs = docs.filter(tags=query["subState"]) + if query["substate"]: + docs = docs.filter(tags=query["substate"]) - # evaluate and fill in values with aggregate queries to avoid - # too many individual queries + # evaluate and fill in attribute results immediately to cut down + # the number of queries results = list(docs.select_related("states", "ad", "ad__person", "std_level", "intended_std_level", "group", "stream")[:MAX]) - rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[r.pk for r in results]).values_list("document_id", "name")) - # canonical name - for r in results: - if r.pk in rfc_aliases: - # lambda weirdness works around lambda binding in local for loop scope - r.canonical_name = (lambda x: lambda: x)(rfc_aliases[r.pk]) - else: - r.canonical_name = (lambda x: lambda: x)(r.name) - - result_map = dict((r.pk, r) for r in results) - - # events - event_types = ("published_rfc", - "changed_ballot_position", - "started_iesg_process", - "new_revision") - for d in rfc_aliases.keys(): - for e in event_types: - setattr(result_map[d], e, None) - - for e in DocEvent.objects.filter(doc__in=rfc_aliases.keys(), type__in=event_types).order_by('-time'): - r = result_map[e.doc_id] - if not getattr(r, e.type): - # sets e.g. r.published_date = e for use in proxy wrapper - setattr(r, e.type, e) - - # obsoleted/updated by - for d in rfc_aliases: - r = result_map[d] - r.obsoleted_by_list = [] - r.updated_by_list = [] - - xed_by = RelatedDocument.objects.filter(target__name__in=rfc_aliases.values(), relationship__in=("obs", "updates")).select_related('target__document_id') - rel_rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[rel.source_id for rel in xed_by]).values_list('document_id', 'name')) - for rel in xed_by: - r = result_map[rel.target.document_id] - if rel.relationship_id == "obs": - attr = "obsoleted_by_list" - else: - attr = "updated_by_list" - - getattr(r, attr).append(int(rel_rfc_aliases[rel.source_id][3:])) - + fill_in_search_attributes(results) # sort def sort_key(d): res = [] - canonical = d.canonical_name() - if canonical.startswith('rfc'): - rfc_num = int(canonical[3:]) - else: - rfc_num = None + rfc_num = d.rfc_number() if rfc_num != None: res.append(2) @@ -226,20 +262,20 @@ def search_query(query_original, sort_by=None): else: res.append(3) - if sort_by == "title": + if query["sort"] == "title": res.append(d.title) - elif sort_by == "date": - res.append(str(d.revision_date or datetime.date(1990, 1, 1))) - elif sort_by == "status": + elif query["sort"] == "date": + res.append(str(d.latest_revision_date)) + elif query["sort"] == "status": if rfc_num != None: - res.append(rfc_num) + res.append(int(rfc_num)) else: - res.append(d.get_state().order) - elif sort_by == "ipr": - res.append(d.name) - elif sort_by == "ad": + res.append(d.get_state().order if d.get_state() else None) + elif query["sort"] == "ipr": + res.append(len(d.iprs)) + elif query["sort"] == "ad": if rfc_num != None: - res.append(rfc_num) + res.append(int(rfc_num)) elif d.get_state_slug() == "active": if d.get_state("draft-iesg"): res.append(d.get_state("draft-iesg").order) @@ -247,286 +283,177 @@ def search_query(query_original, sort_by=None): res.append(0) else: if rfc_num != None: - res.append(rfc_num) + res.append(int(rfc_num)) else: - res.append(canonical) + res.append(d.canonical_name()) return res results.sort(key=sort_key) + # fill in a meta dict with some information for rendering the result table meta = {} - if len(docs) == MAX: + if len(results) == MAX: meta['max'] = MAX - if query['by']: - meta['advanced'] = True + meta['by'] = query['by'] + meta['advanced'] = bool(query['by']) - # finally wrap in old wrappers + meta['headers'] = [{'title': 'Document', 'key':'document'}, + {'title': 'Title', 'key':'title'}, + {'title': 'Date', 'key':'date'}, + {'title': 'Status', 'key':'status', 'colspan':'2'}, + {'title': 'IPR', 'key':'ipr'}, + {'title': 'Area Director', 'key':'ad'}] - wrapped_results = [] - for r in results: - draft = None - rfc = None - if not r.name.startswith('rfc'): - draft = IdWrapper(r) - if r.name.startswith('rfc') or r.pk in rfc_aliases: - rfc = RfcWrapper(r) - wrapped_results.append(IdRfcWrapper(draft, rfc)) + if hasattr(form.data, "urlencode"): # form was fed a Django QueryDict, not local plain dict + d = form.data.copy() + for h in meta['headers']: + d["sort"] = h["key"] + h["sort_url"] = "?" + d.urlencode() + if h['key'] == query.get('sort'): + h['sorted'] = True - return (wrapped_results, meta) - + return (results, meta) -def generate_query_string(request, ignore_list): - """Recreates the parameter string from the given request, and - returns it as a string. - Any parameter names present in ignore_list shall not be put - in the result string. - """ - params = [] - for i in request.GET: - if not i in ignore_list: - params.append(i + "=" + request.GET[i]) - return "?" + "&".join(params) -def search_results(request): - if len(request.REQUEST.items()) == 0: - return search_main(request) - form = SearchForm(dict(request.REQUEST.items())) - if not form.is_valid(): - return HttpResponseBadRequest("form not valid?", mimetype="text/plain") +def search(request): + if request.GET: + # backwards compatibility + get_params = request.GET.copy() + if 'activeDrafts' in request.GET: + get_params['activedrafts'] = request.GET['activeDrafts'] + if 'oldDrafts' in request.GET: + get_params['olddrafts'] = request.GET['oldDrafts'] + if 'subState' in request.GET: + get_params['substate'] = request.GET['subState'] - sort_by = None - if "sortBy" in request.GET: - sort_by = request.GET["sortBy"] + form = SearchForm(get_params) + if not form.is_valid(): + return HttpResponseBadRequest("form not valid: %s" % form.errors) - (results,meta) = search_query(form.cleaned_data, sort_by) - - meta['searching'] = True - meta['by'] = form.cleaned_data['by'] - meta['rqps'] = generate_query_string(request, ['sortBy']) - # With a later Django we can do this from the template (incude with tag) - # Pass the headers and their sort key names - meta['hdrs'] = [{'htitle': 'Document', 'htype':'doc'}, - {'htitle': 'Title', 'htype':'title'}, - {'htitle': 'Date', 'htype':'date'}, - {'htitle': 'Status', 'htype':'status', 'colspan':'2'}, - {'htitle': 'IPR', 'htype':'ipr'}, - {'htitle': 'Ad/Shepherd', 'htype':'ad'}] - - # Make sure we know which one is selected (for visibility later) - if sort_by: - for hdr in meta['hdrs']: - if hdr['htype'] == sort_by: - hdr['selected'] = True - - if 'ajax' in request.REQUEST and request.REQUEST['ajax']: - return render_to_response('idrfc/search_results.html', {'docs':results, 'meta':meta}, context_instance=RequestContext(request)) - elif form.cleaned_data['lucky'] and len(results)==1: - doc = results[0] - if doc.id: - return HttpResponsePermanentRedirect(doc.id.get_absolute_url()) - else: - return HttpResponsePermanentRedirect(doc.rfc.get_absolute_url()) + results, meta = retrieve_search_results(form) + meta['searching'] = True else: - return render_to_response('idrfc/search_main.html', {'form':form, 'docs':results,'meta':meta}, context_instance=RequestContext(request)) - + form = SearchForm() + results = [] + meta = { 'by': None, 'advanced': False, 'searching': False } -def search_main(request): - form = SearchForm() - return render_to_response('idrfc/search_main.html', {'form':form}, context_instance=RequestContext(request)) + return render_to_response('doc/search.html', + {'form':form, 'docs':results, 'meta':meta, 'show_add_to_list': True }, + context_instance=RequestContext(request)) -def by_ad(request, name): - ad_id = None - ad_name = None - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - responsible = Document.objects.values_list('ad', flat=True).distinct() - for p in Person.objects.filter(Q(role__name__in=("pre-ad", "ad"), - role__group__type="area", - role__group__state="active") - | Q(pk__in=responsible)).distinct(): - if name == p.full_name_as_key(): - ad_id = p.id - ad_name = p.plain_name() - break - else: - for i in IESGLogin.objects.filter(user_level__in=[1,2]): - iname = str(i).lower().replace(' ','.') - if name == iname: - ad_id = i.id - ad_name = str(i) - break - if not ad_id: - raise Http404 - form = SearchForm({'by':'ad','ad':ad_id, - 'rfcs':'on', 'activeDrafts':'on', 'oldDrafts':'on'}) - if not form.is_valid(): - raise ValueError("form did not validate") - (results,meta) = search_query(form.cleaned_data) - results.sort(key=lambda obj: obj.view_sort_key_byad()) - return render_to_response('idrfc/by_ad.html', {'form':form, 'docs':results,'meta':meta, 'ad_name':ad_name}, context_instance=RequestContext(request)) - -def ad_dashboard_group(doc): - - if doc.type.slug=='draft': - if doc.get_state_slug('draft') == 'rfc': - return 'RFC' - elif doc.get_state_slug('draft') == 'active' and doc.get_state_slug('draft-iesg'): - return '%s Internet-Draft' % doc.get_state('draft-iesg').name - else: - return '%s Internet-Draft' % doc.get_state('draft').name - elif doc.type.slug=='conflrev': - if doc.get_state_slug('conflrev') in ('appr-reqnopub-sent','appr-noprob-sent'): - return 'Approved Conflict Review' - elif doc.get_state_slug('conflrev') in ('appr-reqnopub-pend','appr-noprob-pend','appr-reqnopub-pr','appr-noprob-pr'): - return "%s Conflict Review" % State.objects.get(type__slug='draft-iesg',slug='approved') - else: - return '%s Conflict Review' % doc.get_state('conflrev') - elif doc.type.slug=='statchg': - if doc.get_state_slug('statchg') in ('appr-sent',): - return 'Approved Status Change' - if doc.get_state_slug('statchg') in ('appr-pend','appr-pr'): - return '%s Status Change' % State.objects.get(type__slug='draft-iesg',slug='approved') - else: - return '%s Status Change' % doc.get_state('statchg') - elif doc.type.slug=='charter': - if doc.get_state_slug('charter') == 'approved': - return "Approved Charter" - else: - return '%s Charter' % doc.get_state('charter') - else: - return "Document" - -def ad_dashboard_sort_key(doc): - - if doc.type.slug=='draft' and doc.get_state_slug('draft') == 'rfc': - return "21%04d" % int(doc.rfc_number()) - if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'appr-sent': - return "22%d" % 0 # TODO - get the date of the transition into this state here - if doc.type.slug=='conflrev' and doc.get_state_slug('conflrev') in ('appr-reqnopub-sent','appr-noprob-sent'): - return "23%d" % 0 # TODO - get the date of the transition into this state here - if doc.type.slug=='charter' and doc.get_state_slug('charter') == 'approved': - return "24%d" % 0 # TODO - get the date of the transition into this state here - - seed = ad_dashboard_group(doc) - - if doc.type.slug=='conflrev' and doc.get_state_slug('conflrev') == 'adrev': - state = State.objects.get(type__slug='draft-iesg',slug='ad-eval') - return "1%d%s" % (state.order,seed) - - if doc.type.slug=='charter': - if doc.get_state_slug('charter') in ('notrev','infrev'): - return "100%s" % seed - elif doc.get_state_slug('charter') == 'intrev': - state = State.objects.get(type__slug='draft-iesg',slug='ad-eval') - return "1%d%s" % (state.order,seed) - elif doc.get_state_slug('charter') == 'extrev': - state = State.objects.get(type__slug='draft-iesg',slug='lc') - return "1%d%s" % (state.order,seed) - elif doc.get_state_slug('charter') == 'iesgrev': - state = State.objects.get(type__slug='draft-iesg',slug='iesg-eva') - return "1%d%s" % (state.order,seed) - - if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'adrev': - state = State.objects.get(type__slug='draft-iesg',slug='ad-eval') - return "1%d%s" % (state.order,seed) - - if seed.startswith('Needs Shepherd'): - return "100%s" % seed - if seed.endswith(' Document'): - seed = seed[:-9] - elif seed.endswith(' Internet-Draft'): - seed = seed[:-15] - elif seed.endswith(' Conflict Review'): - seed = seed[:-16] - elif seed.endswith(' Status Change'): - seed = seed[:-14] - state = State.objects.filter(type__slug='draft-iesg',name=seed) - if state: - ageseconds = 0 - changetime= doc.latest_event(type='changed_document') - if changetime: - ad = (datetime.datetime.now()-doc.latest_event(type='changed_document').time) - ageseconds = (ad.microseconds + (ad.seconds + ad.days * 24 * 3600) * 10**6) / 10**6 - return "1%d%s%s%010d" % (state[0].order,seed,doc.type.slug,ageseconds) - - return "3%s" % seed - -def by_ad2(request, name): +def drafts_for_ad(request, name): + ad = None responsible = Document.objects.values_list('ad', flat=True).distinct() - ad_id = None for p in Person.objects.filter(Q(role__name__in=("pre-ad", "ad"), role__group__type="area", role__group__state="active") | Q(pk__in=responsible)).distinct(): if name == p.full_name_as_key(): - ad_id = p.id - ad_name = p.plain_name() + ad = p break - - if not ad_id: + if not ad: raise Http404 + form = SearchForm({'by':'ad','ad': ad.id, + 'rfcs':'on', 'activedrafts':'on', 'olddrafts':'on', + 'sort': 'status'}) + results, meta = retrieve_search_results(form) - docqueryset = Document.objects.filter(ad__id=ad_id) - docs=[] - for doc in docqueryset: - doc.ad_dashboard_sort_key = ad_dashboard_sort_key(doc) - doc.ad_dashboard_group = ad_dashboard_group(doc) - if doc.get_state_slug() == 'rfc': - doc.display_date = doc.latest_event(type='published_rfc').time - else: - revision = doc.latest_event(type='new_revision') - if revision: - doc.display_date = revision.time - # This might be better handled as something Documents know about themselves - now = datetime.datetime.now() - doc.can_expire = (doc.type.slug=='draft' and doc.get_state_slug('draft')=='active' and ( not doc.get_state('draft-iesg') or doc.get_state('draft-iesg').order >= 42) and doc.expires>now) - if doc.get_state_slug('draft') == 'rfc': - doc.obsoleted_by = ", ".join([ 'RFC %04d' % int(rel.source.rfc_number()) for alias in doc.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='obsoletes') ] ) - doc.updated_by = ", ".join([ 'RFC %04d' % int(rel.source.rfc_number()) for alias in doc.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='updates') ] ) - doc.has_errata = bool(doc.tags.filter(slug="errata")) - else: - s = doc.get_state("draft-rfceditor") - if s: - # extract possible extra annotations - tags = doc.tags.filter(slug__in=("iana", "ref")) - doc.rfc_editor_state = "*".join([s.name] + [t.slug.upper() for t in tags]) - if doc.type.slug == 'draft': - doc.iprCount = IprDraft.objects.filter(document=doc, ipr__status__in=[1,3]).count() - doc.iprUrl = "/ipr/search?option=document_search&id_document_tag=%s" % doc.name - docs.append(doc) - docs.sort(key=ad_dashboard_sort_key) - return render_to_response('idrfc/by_ad2.html',{'docs':docs,'ad_name':ad_name}, context_instance=RequestContext(request)) + for d in results: + if d.get_state_slug() == "active": + iesg_state = d.get_state("draft-iesg") + if iesg_state: + if iesg_state.slug == "dead": + d.search_heading = "IESG Dead Internet-Drafts" + else: + d.search_heading = "%s Internet-Drafts" % iesg_state.name + return render_to_response('doc/drafts_for_ad.html', + { 'form':form, 'docs':results, 'meta':meta, 'ad_name': ad.plain_name() }, + context_instance=RequestContext(request)) -@cache_page(15*60) # 15 minutes -def all(request): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - active = (dict(filename=n) for n in InternetDraft.objects.filter(states__type="draft", states__slug="active").order_by("name").values_list('name', flat=True)) - rfc1 = (dict(filename=d, rfc_number=int(n[3:])) for d, n in DocAlias.objects.filter(document__states__type="draft", document__states__slug="rfc", name__startswith="rfc").exclude(document__name__startswith="rfc").order_by("document__name").values_list('document__name','name').distinct()) - rfc2 = (dict(rfc_number=r, draft=None) for r in sorted(int(n[3:]) for n in Document.objects.filter(type="draft", name__startswith="rfc").values_list('name', flat=True))) - dead = InternetDraft.objects.exclude(states__type="draft", states__slug__in=("active", "rfc")).select_related("states").order_by("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)) + +def drafts_in_iesg_process(request, last_call_only=None): + if last_call_only: + states = State.objects.filter(type="draft-iesg", slug__in=("lc", "writeupw", "goaheadw")) + title = "Documents in Last Call" else: - active = InternetDraft.objects.all().filter(status=1).order_by("filename").values('filename') - rfc1 = InternetDraft.objects.all().filter(status=3).order_by("filename").values('filename','rfc_number') - rfc_numbers1 = InternetDraft.objects.all().filter(status=3).values_list('rfc_number', flat=True) - rfc2 = RfcIndex.objects.all().exclude(rfc_number__in=rfc_numbers1).order_by('rfc_number').values('rfc_number','draft') - dead = InternetDraft.objects.all().exclude(status__in=[1,3]).order_by("filename").select_related('status__status') - return render_to_response('idrfc/all.html', {'active':active, 'rfc1':rfc1, 'rfc2':rfc2, 'dead':dead}, context_instance=RequestContext(request)) + states = State.objects.filter(type="draft-iesg").exclude(slug__in=('pub', 'dead', 'watching', 'rfcqueue')) + title = "Documents in IESG process" -@cache_page(15*60) # 15 minutes -def active(request): - groups = IETFWG.objects.exclude(group_acronym=1027) - individual = IETFWG.objects.get(group_acronym=1027) - return render_to_response("idrfc/active.html", {'groups':groups,'individual':individual}, context_instance=RequestContext(request)) + grouped_docs = [] -def in_last_call(request): - - lcdocs = [] + for s in states.order_by("order"): + docs = Document.objects.filter(type="draft", states=s).distinct().order_by("time").select_related("ad", "group", "group__parent") + if docs: + if s.slug == "lc": + for d in docs: + e = d.latest_event(LastCallDocEvent, type="sent_last_call") + d.lc_expires = e.expires if e else datetime.datetime.min + docs = list(docs) + docs.sort(key=lambda d: d.lc_expires) - for p in InternetDraft.objects.all().filter(idinternal__primary_flag=1).filter(idinternal__cur_state__state='In Last Call'): - if (p.idinternal.rfc_flag): - lcdocs.append(IdRfcWrapper(None,RfcWrapper(p))) - else: - lcdocs.append(IdRfcWrapper(IdWrapper(p),None)) + grouped_docs.append((s, docs)) - return render_to_response("idrfc/in_last_call.html", {'lcdocs':lcdocs}, context_instance=RequestContext(request)) + return render_to_response('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 + categories = [] + + for s in ("active", "rfc", "expired", "repl", "auth-rm", "ietf-rm"): + state = State.objects.get(type="draft", slug=s) + + if state.slug == "rfc": + heading = "RFCs" + elif state.slug in ("ietf-rm", "auth-rm"): + heading = "Internet-Drafts %s" % state.name + else: + heading = "%s Internet-Drafts" % state.name + + draft_names = DocAlias.objects.filter(document__states=state).values_list("name", "document") + + names = [] + names_to_skip = set() + for name, doc in draft_names: + sort_key = name + if name != doc: + if not name.startswith("rfc"): + name, doc = doc, name + names_to_skip.add(doc) + + if name.startswith("rfc"): + name = name.upper() + sort_key = -int(name[3:]) + + names.append((name, sort_key)) + + names.sort(key=lambda t: t[1]) + + names = ['' + name +'' + for name, _ in names if name not in names_to_skip] + + categories.append((state, + heading, + len(names), + "
".join(names) + )) + return render_to_response('doc/index_all_drafts.html', { "categories": categories }, + context_instance=RequestContext(request)) + +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)) diff --git a/ietf/idtracker/README b/ietf/idtracker/README deleted file mode 100644 index 815328d30..000000000 --- a/ietf/idtracker/README +++ /dev/null @@ -1,56 +0,0 @@ -form_for_model(IDInternal) will autogenerate form elemnts to edit the model - -class ImageAddForm(BaseForm): - - def __init__(self, *args, name=None, **kwargs): - super(ImageAddForm, self).__init__(*args, **kwargs) - self.fields['category'].choices=(('a','a'),) - # create filter based on name= - -ImageForm = form_for_model(Image, form=ImageAddForm) -foo = ImageForm(name='foo') - - - -To get from draft to author list: - ->>> d = a[5] ->>> print d -draft-fenner-zinin-rtg-standard-reqts ->>> print d.authors.all() -[, ] ->>> l=d.authors.all() ->>> print l[0].person -Bill Fenner ->>> print l[0].person.emailaddresses_set.filter(priority=d.id_document_tag) -[] ->>> print l[0].person.emailaddresses_set.filter(priority=d.id_document_tag)[0].ail_address -fenner@research.att.com - -IDAuthors should have an auxilliary function for this. -It's the one that has the person linkage and knows the document. - - --- -we should use a variant of django-registration. -http://www.stonemind.net/blog/2007/04/13/django-registration-for-newbies/ - -1. verify email address with round trip -2. if there's a row in iesg_login, use that username - (? liaison tool logins ?) - otherwise, force the email address -3. get a password and create the user - (this is almost the same as resetting the password) -4. find the person_or_org_info row, associate that with - the user row - --- - - Both the regular and date-base object_detail can take either an -+object_id, or a slug/slug_field combo. - So use the username as the "slug" and specify 'username' as the -+'slug_field' argument. - -http://www.b-list.org/weblog/2006/11/16/django-tips-get-most-out-generic-views - -newly learned: 'slug_field' just gets passed so can be otherdb__username diff --git a/ietf/idtracker/feeds.py b/ietf/idtracker/feeds.py deleted file mode 100644 index 4b4a5f678..000000000 --- a/ietf/idtracker/feeds.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright The IETF Trust 2007, All Rights Reserved - -from django.conf import settings -from django.contrib.syndication.feeds import Feed, FeedDoesNotExist -from django.utils.feedgenerator import Atom1Feed -from ietf.idtracker.models import IDInternal -import datetime -import re - -class DocumentComments(Feed): - feed_type = Atom1Feed - def get_object(self, bits): - if len(bits) != 1: - raise IDInternal.DoesNotExist - rfc = re.match('rfc(\d+)', bits[0]) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - return IDInternal.objects.get(docalias__name=bits[0]) - - if rfc: - return IDInternal.objects.get(draft=int(rfc.group(1)), rfc_flag=1) - else: - return IDInternal.objects.get(draft__filename=bits[0], rfc_flag=0) - - def title(self, obj): - # filename is a function for RFCs and an attribute for I-Ds. - # This works transparently for templates but is not transparent - # for python. - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - return "I-D Tracker comments for %s" % obj.filename - - if obj.rfc_flag: - filename = obj.document().filename() - else: - filename = obj.document().filename - return "I-D Tracker comments for %s" % filename - - def link(self, obj): - if obj is None: - raise FeedDoesNotExist - return obj.get_absolute_url() - - def description(self, obj): - return self.title(obj) - - def items(self, obj): - return obj.public_comments().order_by("-date","-id") - - def item_pubdate(self, item): - return item.datetime() - - def item_author_name(self, item): - return item.get_author() - -class InLastCall(Feed): - title = "Documents in Last Call" - feed_type = Atom1Feed - author_name = 'IESG Secretary' - link = "/idtracker/status/last-call/" - - def items(self): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - ret = list(IDInternal.objects.filter(states__type="draft-iesg", states__slug="lc")) - else: - ret = list(IDInternal.objects.filter(primary_flag=1).filter(cur_state__state='In Last Call')) - ret.sort(key=lambda item: (item.document().lc_expiration_date or datetime.date.today())) - return ret - - def item_pubdate(self, item): - # this method needs to return a datetime instance, even - # though the database has only date, not time - return datetime.datetime.combine((item.document().lc_sent_date or datetime.datetime.now().date()), datetime.time(0,0,0)) - diff --git a/ietf/idtracker/fixtures/.gitignore b/ietf/idtracker/fixtures/.gitignore deleted file mode 100644 index a74b07aee..000000000 --- a/ietf/idtracker/fixtures/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*.pyc diff --git a/ietf/idtracker/fixtures/wgtest.xml b/ietf/idtracker/fixtures/wgtest.xml deleted file mode 100644 index 5c7bd6174..000000000 --- a/ietf/idtracker/fixtures/wgtest.xml +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - Hermey - HERMEY - - - Elf - ELF - - 2007-07-02 - - 2007-07-02 - - - - - - - Kris - KRIS - - - Kringle - KRINGLE - - 2007-07-02 - - 2007-07-02 - - - - - - - Snow - SNOW - - - Miser - MISER - - 2007-07-02 - - 2007-07-02 - - - - - - - Rudolph - RUDOLPH - - - Reindeer - REINDEER - - 2007-07-02 - - 2007-07-02 - - - - - 1 - 1 - - - 2 - 2 - - - 1 - - - - - 1 - - - - - - - - 2007-07-02 - - - - 3 - - - - - 1 - - - - - - - - 2007-07-02 - - - - xmas - Christmas - CHRISTMAS - - - snow - Silly New Operational Work - SILLY NEW OPERATIONAL WORK - - - WG - - - PWG - - - BOF - - - AG - - - TEAM - - - Active - - - Dormant - - - Concluded - - - 1 - 3 - - - 1 - 4 - - diff --git a/ietf/idtracker/sitemaps.py b/ietf/idtracker/sitemaps.py deleted file mode 100644 index 650a4a94a..000000000 --- a/ietf/idtracker/sitemaps.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright The IETF Trust 2007, All Rights Reserved -# -from django.contrib.sitemaps import Sitemap -from django.conf import settings -from ietf.idtracker.models import IDInternal, InternetDraft - -class IDTrackerMap(Sitemap): - changefreq = "always" - def items(self): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - return IDInternal.objects.all() - else: - return IDInternal.objects.exclude(draft=999999) - -class DraftMap(Sitemap): - changefreq = "always" - def items(self): - return InternetDraft.objects.all() - def location(self, obj): - return "/drafts/%s/" % obj.filename - def lastmod(self, obj): - return obj.last_modified_date diff --git a/ietf/idtracker/templatetags/ietf_filters.py b/ietf/idtracker/templatetags/ietf_filters.py index 77ea5f946..3afffac2a 100644 --- a/ietf/idtracker/templatetags/ietf_filters.py +++ b/ietf/idtracker/templatetags/ietf_filters.py @@ -31,9 +31,18 @@ def expand_comma(value): def format_charter(value): return value.replace("\n\n", "

").replace("\n","
\n") -@register.filter(name='indent') -def indent(value,numspaces=2): - return value.replace("\n", "\n"+" "*int(numspaces)); +@register.filter +def indent(value, numspaces=2): + replacement = "\n" + " " * int(numspaces) + res = value.replace("\n", replacement) + if res.endswith(replacement): + res = res[:-int(numspaces)] # fix up superfluous spaces + return res + +@register.filter +def unindent(value): + """Remove indentation from string.""" + return re.sub("\n +", "\n", value) @register.filter(name='parse_email_list') def parse_email_list(value): @@ -241,6 +250,11 @@ def dashify(string): """ return re.sub('.', '-', string) +@register.filter +def underline(string): + """Return string with an extra line underneath of dashes, for plain text underlining.""" + return string + "\n" + ("-" * len(string)) + @register.filter(name='lstrip') def lstripw(string, chars): """Strip matching leading characters from words in string""" @@ -320,23 +334,6 @@ def wrap_text(text, width=72): prev_indent = indent return "\n".join(filled) -@register.filter(name="id_index_file_types") -def id_index_file_types(text): - r = ".txt" - if text.find("txt") < 0: - return r - if text.find("ps") >= 0: - r = r + ",.ps" - if text.find("pdf") >= 0: - r = r + ",.pdf" - return r - -@register.filter(name="id_index_wrap") -def id_index_wrap(text): - x = wordwrap(text, 72) - x = x.replace("\n", "\n ") - return " "+x.strip() - @register.filter(name="compress_empty_lines") def compress_empty_lines(text): text = re.sub("( *\n){3,}", "\n\n", text) @@ -408,14 +405,6 @@ def expires_soon(x,request): days = 14 return x > -days -@register.filter(name='greater_than') -def greater_than(x, y): - return x > int(y) - -@register.filter(name='less_than') -def less_than(x, y): - return x < int(y) - @register.filter(name='equal') def equal(x, y): return str(x)==str(y) @@ -470,32 +459,22 @@ def format_history_text(text): full = mark_safe(keep_spacing(linebreaksbr(urlize(sanitize_html(full))))) snippet = truncate_html_words(full, 25) if snippet != full: - return mark_safe(u'

%s[show all]
' % (snippet, full)) + return mark_safe(u'
%s[show all]
' % (snippet, full)) return full -@register.filter -def user_roles_json(user): - roles = {} - if not isinstance(user, basestring) and user.is_authenticated(): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.group.models import Role - for r in Role.objects.filter(person__user=user).select_related(depth=1): - if r.name_id == "secr" and r.group.acronym == "secretariat": - roles["Secretariat"] = True - elif r.name_id == "ad" and r.group.type_id == "area" and r.group.state_id == "active": - roles["Area Director"] = roles["Area_Director"] = True - else: - for g in user.groups.all(): - roles[g.name] = True - return mark_safe(simplejson.dumps(roles)) - @register.filter def textify(text): text = re.sub("", "*", text) text = re.sub("", "/", text) # There are probably additional conversions we should apply here return text - + +@register.filter +def state(doc, slug): + if slug == "stream": # convenient shorthand + slug = "%s-stream-%s" % (doc.type_id, doc.stream_id) + return doc.get_state(slug) + def _test(): import doctest doctest.testmod() diff --git a/ietf/idtracker/templatetags/versiontags.py b/ietf/idtracker/templatetags/versiontags.py deleted file mode 100644 index 893e4b407..000000000 --- a/ietf/idtracker/templatetags/versiontags.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright The IETF Trust 2007, All Rights Reserved - -from django import template -from ietf import __date__, __rev__, __version__, __id__ - - -register = template.Library() - - -@register.simple_tag -def revision_time(): - return __date__[7:32] - -@register.simple_tag -def revision_date(): - return __date__[34:-3] - -@register.simple_tag -def revision_num(): - return __rev__[6:-2] - -@register.simple_tag -def revision_id(): - return __id__[5:-2] - -@register.simple_tag -def version_num(): - return __version__ - diff --git a/ietf/idtracker/tests.py b/ietf/idtracker/tests.py index d9b63ba7a..7c2c3aaa7 100644 --- a/ietf/idtracker/tests.py +++ b/ietf/idtracker/tests.py @@ -1,42 +1,10 @@ # Copyright The IETF Trust 2007, All Rights Reserved # -import doctest +import doctest, unittest + from ietf.idtracker.templatetags import ietf_filters -import unittest -from ietf.utils.test_utils import SimpleUrlTestCase, canonicalize_feed, canonicalize_sitemap -import django.test class TemplateTagTest(unittest.TestCase): - def testTemplateTags(self): - print " Testing ietf_filters" - #doctest.testmod(ietf_filters,verbose=True) - (failures, tests) = doctest.testmod(ietf_filters) + def test_template_tags(self): + failures, tests = doctest.testmod(ietf_filters) self.assertEqual(failures, 0) - print "OK (ietf_filters)" - -class IdTrackerUrlTestCase(SimpleUrlTestCase): - def testUrls(self): - self.doTestUrls(__file__) - def doCanonicalize(self, url, content): - if url.startswith("/feed/"): - return canonicalize_feed(content) - elif url.startswith("/sitemap"): - return canonicalize_sitemap(content) - else: - return content - -# class WGRoleTest(django.test.TestCase): -# fixtures = ['wgtest'] -# -# def setUp(self): -# from ietf.idtracker.models import IETFWG -# self.xmas = IETFWG.objects.get(group_acronym__acronym='xmas') -# self.snow = IETFWG.objects.get(group_acronym__acronym='snow') -# -# def test_roles(self): -# print " Testing WG roles" -# self.assertEquals(self.xmas.wgchair_set.all()[0].role(), 'xmas WG Chair') -# self.assertEquals(self.snow.wgchair_set.all()[0].role(), 'snow BOF Chair') -# self.assertEquals(self.xmas.wgsecretary_set.all()[0].role(), 'xmas WG Secretary') -# self.assertEquals(self.xmas.wgtechadvisor_set.all()[0].role(), 'xmas Technical Advisor') -# print "OK" diff --git a/ietf/idtracker/testurl.list b/ietf/idtracker/testurl.list deleted file mode 100644 index 0b28acb1f..000000000 --- a/ietf/idtracker/testurl.list +++ /dev/null @@ -1,25 +0,0 @@ -200 /idtracker/help/state/ -200 /idtracker/help/state/12/ -200 /idtracker/help/substate/1/ -301 /idtracker/help/evaluation/ -200 /idtracker/status/ -200 /idtracker/status/last-call/ - -301 /idtracker/rfc3847/ -301 /idtracker/draft-ietf-isis-link-attr/ -301 /idtracker/draft-eronen-tls-psk/ # no IESG information -301 /idtracker/ -200 /feed/comments/draft-ietf-isis-link-attr/ -200 /feed/comments/rfc3373/ -200 /feed/last-call/ - -# An RFC with no matching value in InternetDrafts. This tests -# subtle cases of using the draft relation when it's not appropriate. -# See ticket #218. -301 /idtracker/rfc2444/ -200 /feed/comments/rfc2444/ - -# Test case for missing comment time (bug fixed in changeset 1733) -200 /feed/comments/draft-ietf-msec-newtype-keyid/ - -#200,heavy /sitemap-idtracker.xml diff --git a/ietf/idtracker/urls.py b/ietf/idtracker/urls.py deleted file mode 100644 index 300c43614..000000000 --- a/ietf/idtracker/urls.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright The IETF Trust 2007, All Rights Reserved - -from django.conf.urls.defaults import patterns, url -from ietf.idtracker.models import IDState, IDSubState -from ietf.idtracker import views -from django.views.generic.simple import redirect_to - -urlpatterns = patterns('django.views.generic.simple', - url(r'^help/state/$', 'direct_to_template', { 'template': 'idtracker/states.html', 'extra_context': { 'states': IDState.objects.all(), 'substates': IDSubState.objects.all() } }, name="help_states"), - (r'^help/evaluation/$', redirect_to, {'url':'http://www.ietf.org/iesg/voting-procedures.html' }), -) -urlpatterns += patterns('', - (r'^status/$', views.status), - (r'^status/last-call/$', views.last_call), -) -urlpatterns += patterns('', - (r'^rfc0*(?P\d+)/$', views.redirect_rfc), - (r'^(?P\d+)/$', views.redirect_id), - (r'^(?P[^/]+)/$', views.redirect_filename), - (r'^comment/(?P\d+)/$', views.redirect_comment), - (r'^ballot/(?P\d+)/$', views.redirect_ballot), - (r'^([^/]+)/comment/(?P\d+)/$', views.redirect_comment), - (r'^help/state/(?P\d+)/$', views.state_desc), - (r'^help/substate/(?P\d+)/$', views.state_desc, { 'is_substate': 1 }), - (r'^$', redirect_to, { 'url': '/doc/'}), -) diff --git a/ietf/idtracker/views.py b/ietf/idtracker/views.py deleted file mode 100644 index a61411f05..000000000 --- a/ietf/idtracker/views.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright The IETF Trust 2007, All Rights Reserved - -# Create your views here. -from django.http import HttpResponsePermanentRedirect, Http404 -from django.conf import settings -from django.template import RequestContext -from django.shortcuts import get_object_or_404, render_to_response -from django.views.generic.list_detail import object_detail, object_list -from ietf.idtracker.models import InternetDraft, IDInternal, IDState, IDSubState, BallotInfo, DocumentComment -import re, datetime - -def state_desc(request, state, is_substate=0): - if int(state) == 100: - object = { - 'state': 'I-D Exists', - 'description': """ -Initial (default) state for all internet drafts. Such documents are -not being tracked by the IESG as no request has been made of the -IESG to do anything with the document. -""" - } - elif is_substate: - sub = get_object_or_404(IDSubState, pk=state) - object = { 'state': sub.sub_state, 'description': sub.description } - else: - object = get_object_or_404(IDState, pk=state) - return render_to_response('idtracker/state_desc.html', {'state': object}, - context_instance=RequestContext(request)) - -def status(request): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - drafts = IDInternal.objects.filter(states__type="draft-iesg").exclude(states__type="draft-iesg", states__slug__in=('pub', 'dead', 'watching', 'rfcqueue')).distinct().order_by('states__order') - drafts = [ d for d in drafts if not d.replaced_by ] - drafts.sort(key=lambda d: (d.cur_state_id, d.status_date or datetime.date.min, d.b_sent_date or datetime.date.min)) - # sadly we can't use the generic view because it only works with a queryset... - return render_to_response('idtracker/status_of_items.html', dict(object_list=drafts, title="IESG Status of Items"), context_instance=RequestContext(request)) - - queryset = IDInternal.objects.filter(primary_flag=1).exclude(cur_state__state__in=('RFC Ed Queue', 'RFC Published', 'AD is watching', 'Dead')).order_by('cur_state', 'status_date', 'ballot') - return object_list(request, template_name="idtracker/status_of_items.html", queryset=queryset, extra_context={'title': 'IESG Status of Items'}) - -def last_call(request): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - drafts = list(IDInternal.objects.filter(states__type="draft-iesg", states__slug__in=('lc', 'writeupw', 'goaheadw')).distinct().order_by('states__order')) - drafts.sort(key=lambda d: (d.cur_state_id, d.status_date or datetime.date.min, d.b_sent_date or datetime.date.min)) - # sadly we can't use the generic view because it only works with a queryset... - return render_to_response('idtracker/status_of_items.html', dict(object_list=drafts, title="Documents in Last Call", lastcall=1), context_instance=RequestContext(request)) - - queryset = IDInternal.objects.filter(primary_flag=1).filter(cur_state__state__in=('In Last Call', 'Waiting for Writeup', 'Waiting for AD Go-Ahead')).order_by('cur_state', 'status_date', 'ballot') - return object_list(request, template_name="idtracker/status_of_items.html", queryset=queryset, extra_context={'title': 'Documents in Last Call', 'lastcall': 1}) - -def redirect_id(request, object_id): - '''Redirect from historical document ID to preferred filename url.''' - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - raise Http404() # we don't store the numbers anymore - - doc = get_object_or_404(InternetDraft, id_document_tag=object_id) - return HttpResponsePermanentRedirect("/doc/"+doc.filename+"/") - -def redirect_rfc(request, rfc_number): - return HttpResponsePermanentRedirect("/doc/rfc"+rfc_number+"/") - -def redirect_filename(request, filename): - return HttpResponsePermanentRedirect("/doc/"+filename+"/") - -def redirect_ballot(request, object_id): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - raise Http404() # we don't store the numbers anymore - - ballot = get_object_or_404(BallotInfo, pk=object_id) - ids = ballot.drafts.filter(primary_flag=1) - if len(ids) == 0: - raise Http404("Ballot does not correspond to any document") - id = ids[0] - if id.rfc_flag: - return HttpResponsePermanentRedirect("/doc/rfc"+str(id.draft_id)+"/#ballot") - else: - return HttpResponsePermanentRedirect("/doc/"+id.draft.filename+"/#ballot") - -def redirect_comment(request, object_id): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - raise Http404() # we don't store the numbers anymore - - comment = get_object_or_404(DocumentComment, pk=object_id) - id = comment.document - if id.rfc_flag: - return HttpResponsePermanentRedirect("/doc/rfc"+str(id.draft_id)+"/#history-"+str(object_id)) - else: - return HttpResponsePermanentRedirect("/doc/"+id.draft.filename+"/#history-"+str(object_id)) - diff --git a/ietf/iesg/tests.py b/ietf/iesg/tests.py index 83a55151d..406bdb234 100644 --- a/ietf/iesg/tests.py +++ b/ietf/iesg/tests.py @@ -505,11 +505,11 @@ class DeferUndeferTestCase(django.test.TestCase): self.assertEquals(len(q('form.defer')),1) # defer - self.assertEquals(doc.telechat_date,first_date) + self.assertEquals(doc.telechat_date(), first_date) r = self.client.post(url,dict()) self.assertEquals(r.status_code, 302) doc = Document.objects.get(name=name) - self.assertEquals(doc.telechat_date,second_date) + self.assertEquals(doc.telechat_date(), second_date) self.assertTrue(doc.returning_item()) defer_states = dict(draft=['draft-iesg','defer'],conflrev=['conflrev','defer']) if doc.type_id in defer_states: @@ -547,11 +547,11 @@ class DeferUndeferTestCase(django.test.TestCase): self.assertEquals(len(q('form.undefer')),1) # undefer - self.assertEquals(doc.telechat_date,second_date) + self.assertEquals(doc.telechat_date(), second_date) r = self.client.post(url,dict()) self.assertEquals(r.status_code, 302) doc = Document.objects.get(name=name) - self.assertEquals(doc.telechat_date,first_date) + self.assertEquals(doc.telechat_date(), first_date) self.assertTrue(doc.returning_item()) undefer_states = dict(draft=['draft-iesg','iesg-eva'],conflrev=['conflrev','iesgeval']) if doc.type_id in undefer_states: diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index bd995abfb..024221151 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -515,8 +515,7 @@ class RescheduleForm(forms.Form): self.fields['telechat_date'].choices = choices def handle_reschedule_form(request, doc, dates): - initial = dict( - telechat_date=doc.telechat_date if doc.on_upcoming_agenda() else None) + initial = dict(telechat_date=doc.telechat_date()) formargs = dict(telechat_dates=dates, prefix="%s" % doc.name, @@ -551,11 +550,11 @@ def agenda_documents(request): i.reschedule_form = handle_reschedule_form(request, i, dates) # some may have been taken off the schedule by the reschedule form - docs = filter(lambda x: x.on_upcoming_agenda(), docs) - + docs = [d for d in docs if d.telechat_date()] + telechats = [] for date in dates: - matches = filter(lambda x: x.telechat_date == date, docs) + matches = filter(lambda x: x.telechat_date() == date, docs) res = {} for i in matches: section_key = "s" + get_doc_section(i) diff --git a/ietf/ipr/models.py b/ietf/ipr/models.py index 65ec0d41d..9b84a3a24 100644 --- a/ietf/ipr/models.py +++ b/ietf/ipr/models.py @@ -2,9 +2,6 @@ from django.db import models from django.conf import settings -#from django import newforms as forms -from ietf.idtracker.views import InternetDraft -from ietf.idtracker.models import Rfc from ietf.utils.lazy import reverse_lazy # ------------------------------------------------------------------------ @@ -139,9 +136,6 @@ class IprDetail(models.Model): return None except IprContact.MultipleObjectsReturned: return self.contact.filter(contact_type=3)[0] - class Meta: - if not settings.USE_DB_REDESIGN_PROXY_CLASSES: - db_table = 'ipr_detail' class IprContact(models.Model): TYPE_CHOICES = ( @@ -162,20 +156,8 @@ class IprContact(models.Model): email = models.EmailField(max_length=255) def __str__(self): return self.name or '' - class Meta: - if not settings.USE_DB_REDESIGN_PROXY_CLASSES: - db_table = 'ipr_contacts' -class IprDraft(models.Model): - ipr = models.ForeignKey(IprDetail, related_name='drafts_old' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'drafts') - document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name="ipr_draft_old" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "ipr") - revision = models.CharField(max_length=2) - def __str__(self): - return "%s which applies to %s-%s" % ( self.ipr, self.document, self.revision ) - class Meta: - db_table = 'ipr_ids' - class IprNotification(models.Model): ipr = models.ForeignKey(IprDetail) notification = models.TextField(blank=True) @@ -183,94 +165,70 @@ class IprNotification(models.Model): time_sent = models.CharField(blank=True, max_length=25) def __str__(self): return "IPR notification for %s sent %s %s" % (self.ipr, self.date_sent, self.time_sent) - class Meta: - if not settings.USE_DB_REDESIGN_PROXY_CLASSES: - db_table = 'ipr_notifications' - -class IprRfc(models.Model): - ipr = models.ForeignKey(IprDetail, related_name='rfcs_old' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'rfcs') - document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr_rfc_old" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "ipr") - def __str__(self): - return "%s applies to RFC%04d" % ( self.ipr, self.document_id ) - class Meta: - db_table = 'ipr_rfcs' class IprUpdate(models.Model): ipr = models.ForeignKey(IprDetail, related_name='updates') updated = models.ForeignKey(IprDetail, db_column='updated', related_name='updated_by') status_to_be = models.IntegerField(null=True, blank=True) processed = models.IntegerField(null=True, blank=True) + + +from ietf.doc.models import DocAlias + +class IprDocAlias(models.Model): + ipr = models.ForeignKey(IprDetail, related_name='documents') + doc_alias = models.ForeignKey(DocAlias) + rev = models.CharField(max_length=2, blank=True) + def __unicode__(self): + if self.rev: + return u"%s which applies to %s-%s" % (self.ipr, self.doc_alias.name, self.rev) + else: + return u"%s which applies to %s" % (self.ipr, self.doc_alias.name) + class Meta: - if not settings.USE_DB_REDESIGN_PROXY_CLASSES: - db_table = 'ipr_updates' + verbose_name = "IPR document alias" + verbose_name_plural = "IPR document aliases" +# proxy stuff -if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_IPR"): - from ietf.doc.models import DocAlias - - class IprDocAlias(models.Model): - ipr = models.ForeignKey(IprDetail, related_name='documents') - doc_alias = models.ForeignKey(DocAlias) - rev = models.CharField(max_length=2, blank=True) - def __unicode__(self): - if self.rev: - return u"%s which applies to %s-%s" % (self.ipr, self.doc_alias.name, self.rev) - else: - return u"%s which applies to %s" % (self.ipr, self.doc_alias.name) +from ietf.utils.proxy import TranslatingManager - class Meta: - verbose_name = "IPR document alias" - verbose_name_plural = "IPR document aliases" +class IprDraftProxy(IprDocAlias): + objects = TranslatingManager(dict(document="doc_alias__name")) - # proxy stuff - IprDraftOld = IprDraft - IprRfcOld = IprRfc + # document = models.ForeignKey(InternetDraft, db_column='id_document_tag', "ipr") + # document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr") + @property + def document(self): + from ietf.doc.proxy import DraftLikeDocAlias + return DraftLikeDocAlias.objects.get(pk=self.doc_alias_id) - from ietf.utils.proxy import TranslatingManager - - class IprDraftProxy(IprDocAlias): - objects = TranslatingManager(dict(document="doc_alias__name")) - - # document = models.ForeignKey(InternetDraft, db_column='id_document_tag', "ipr") - # document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr") - @property - def document(self): - from ietf.doc.proxy import DraftLikeDocAlias - return DraftLikeDocAlias.objects.get(pk=self.doc_alias_id) - - #revision = models.CharField(max_length=2) - @property - def revision(self): - return self.rev - - class Meta: - proxy = True + #revision = models.CharField(max_length=2) + @property + def revision(self): + return self.rev - IprDraft = IprDraftProxy + class Meta: + proxy = True - class IprRfcProxy(IprDocAlias): - objects = TranslatingManager(dict(document=lambda v: ("doc_alias__name", "rfc%s" % v))) - - # document = models.ForeignKey(InternetDraft, db_column='id_document_tag', "ipr") - # document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr") - @property - def document(self): - from ietf.doc.proxy import DraftLikeDocAlias - return DraftLikeDocAlias.objects.get(pk=self.doc_alias_id) - - #revision = models.CharField(max_length=2) - @property - def revision(self): - return self.rev - - class Meta: - proxy = True +IprDraft = IprDraftProxy - IprRfc = IprRfcProxy - +class IprRfcProxy(IprDocAlias): + objects = TranslatingManager(dict(document=lambda v: ("doc_alias__name", "rfc%s" % v))) + # document = models.ForeignKey(InternetDraft, db_column='id_document_tag', "ipr") + # document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr") + @property + def document(self): + from ietf.doc.proxy import DraftLikeDocAlias + return DraftLikeDocAlias.objects.get(pk=self.doc_alias_id) -# changes done by convert-096.py:changed maxlength to max_length -# removed core -# removed edit_inline -# removed raw_id_admin + #revision = models.CharField(max_length=2) + @property + def revision(self): + return self.rev + + class Meta: + proxy = True + +IprRfc = IprRfcProxy diff --git a/ietf/ipr/urls.py b/ietf/ipr/urls.py index 77a00f9a1..43ad3749f 100644 --- a/ietf/ipr/urls.py +++ b/ietf/ipr/urls.py @@ -15,7 +15,7 @@ urlpatterns = patterns('', (r'^new-(?Pspecific)/$', new.new), (r'^new-(?Pgeneric)/$', new.new), (r'^new-(?Pthird-party)/$', new.new), - (r'^search/$', search.search), + url(r'^search/$', search.search, name="ipr_search"), ) diff --git a/ietf/templates/base.html b/ietf/templates/base.html index 0ed8ba14b..9ec643469 100644 --- a/ietf/templates/base.html +++ b/ietf/templates/base.html @@ -45,10 +45,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% block morecss %}{% endblock %} {% block pagehead %}{% endblock %} - {% ifnotequal server_mode "production" %} {% else %} @@ -107,7 +103,6 @@ YAHOO.util.Event.onContentReady("wgs", function () { {% endblock %} //]]> - {% block js %}{% endblock %} {% block content_end %} diff --git a/ietf/templates/base_leftmenu.html b/ietf/templates/base_leftmenu.html index d4b0462ce..7c08aecaf 100644 --- a/ietf/templates/base_leftmenu.html +++ b/ietf/templates/base_leftmenu.html @@ -38,17 +38,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  • Accounts
  • {% if request.user.is_authenticated %}Manage Account{% else %}New Account{% endif %}
  • -{% if user|in_group:"Area_Director" %} +{% if user|has_role:"Area Director" %}
  • AD Dashboard
  • -
  • My Documents (old)
  • -
  • My Documents (new)
  • +
  • My Documents
  • Next Telechat
  • Discusses
  • Milestones
  • {# FIXME: this link should be removed when the old WG Actions are completely dead #}
  • Working Groups
  • {% endif %} -{% if user|in_group:"Secretariat" %} +{% if user|has_role:"Secretariat" %}
  • Secretariat
  • Telechat Dates
  • Management Items
  • @@ -74,7 +73,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  • RFC Editor
  • Sync discrepancies
  • {% endif %} -
  • Working Groups
  • +
  • Working Groups
    • @@ -93,7 +92,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    • Drafts & RFCs
    • Document search:
    • -
    • +
    • Submit a draft
    • {% if user|in_group:"WG Chair" %}
    • Approve a draft
    • diff --git a/ietf/templates/doc/ballot_popup.html b/ietf/templates/doc/ballot_popup.html new file mode 100644 index 000000000..16faf4d1a --- /dev/null +++ b/ietf/templates/doc/ballot_popup.html @@ -0,0 +1,14 @@ +{% load ietf_filters %} +
      +
      + {{ ballot_content }} +
      +
      + + {% if request.user|has_role:"Area Director" %} + Edit Position + {% endif %} + + Close +
      +
      diff --git a/ietf/templates/doc/conflict_review/start.html b/ietf/templates/doc/conflict_review/start.html index e8e002947..c62f4a665 100644 --- a/ietf/templates/doc/conflict_review/start.html +++ b/ietf/templates/doc/conflict_review/start.html @@ -18,7 +18,7 @@ form.start-conflict-review .actions { {% block content %}

      Begin IETF conflict review for {{doc_to_review.canonical_name}}-{{doc_to_review.rev}}

      -

      For help on the initial state choice, see the state table.

      +

      For help on the initial state choice, see the state table.

      diff --git a/ietf/templates/doc/document_draft.html b/ietf/templates/doc/document_draft.html index 785374ba9..1c12ea712 100644 --- a/ietf/templates/doc/document_draft.html +++ b/ietf/templates/doc/document_draft.html @@ -226,10 +226,10 @@
      + + + + + + {% for doc in docs %} + + + + + + + + + + + + + + + + + + + + {% if doc.note %} + + + + + + {% endif %} + + {% endfor %} +
      Area{% if state.slug == "lc" %}Expires at{% else %}Date{% endif %}
      {% if doc.area_acronym %}{{ doc.area_acronym.upper }}{% endif %} + {% if state.slug == "lc" %} + {% if doc.lc_expires %}{{ doc.lc_expires|date:"M j, Y" }}{% endif %} + {% else %} + {{ doc.time|date:"M j, Y" }} + {% endif %} + {{ doc.title }} ({{ doc.intended_std_level.name }})
      {{ doc.name }}
      AD:{{ doc.ad.plain_name }}
      Note:{{ doc.note|linebreaksbr|urlize }}
      +{% endfor %} + +{% endblock %} diff --git a/ietf/templates/doc/drafts_in_last_call.html b/ietf/templates/doc/drafts_in_last_call.html new file mode 100644 index 000000000..50465eb00 --- /dev/null +++ b/ietf/templates/doc/drafts_in_last_call.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %}Internet-Drafts in IETF Last Call{% endblock %} + +{% block content %} +

      Internet-Drafts in IETF Last Call

      + +{% include "idrfc/search_results.html" %} + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/doc/index_active_drafts.html b/ietf/templates/doc/index_active_drafts.html new file mode 100644 index 000000000..3c65c2ffa --- /dev/null +++ b/ietf/templates/doc/index_active_drafts.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block morecss %} +.contents { max-width: 50em; } +.contents a { float: left; width: 7em; margin-right: 2em; } +{% endblock %} + +{% block title %}Active Internet-Drafts{% endblock %} + +{% block content %} +

      Active Internet-Drafts

      + +

      This page lists all active Internet-Drafts, grouped by responsible +group. For normal use, it is recommended to use the +search page.

      + +

      There is also an index of all +Internet-Drafts (that page also lists some machine-readable files +for download).

      + +

      +{% for group in groups %}{{ group.acronym }} {% endfor %} +

      + +
      + +{% for group in groups %} +

      {{ group.name }} ({{ group.acronym }})

      +{% for d in group.active_drafts %} +

      {{ d.title }}
      +{% for a in d.authors %}{{ a }}{% if not forloop.last %}, {% endif %}{% endfor %}
      +{{ d.name }}-{{ d.rev }} ({{ d.rev_time|date:"Y-m-d" }})

      +{% endfor %} +{% endfor %} + +{% endblock %} diff --git a/ietf/templates/doc/index_all_drafts.html b/ietf/templates/doc/index_all_drafts.html new file mode 100644 index 000000000..da8f8a7a0 --- /dev/null +++ b/ietf/templates/doc/index_all_drafts.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% block title %}Index of all Internet-Drafts and RFCs{% endblock %} + +{% block content %} +

      Index of all Internet-Drafts and RFCs

      + +

      This page lists all Internet-Drafts and RFCs. The main purpose of +this page is to ensure all pages can be found by search engines. For +normal use, it is recommended to use the search +page.

      + +

      There's also an index of +active Internet-Drafts with more information.

      + +

      In addition, the following files are available for download:

      + + +

      Contents

      + +

      The documents are listed in the following categories:

      + + + +{% for state, heading, count, links in categories %} +

      {{ heading }} ({{ count }})

      + + +{% endfor %} + +{% endblock %} diff --git a/ietf/templates/doc/search.html b/ietf/templates/doc/search.html new file mode 100644 index 000000000..ee60fa559 --- /dev/null +++ b/ietf/templates/doc/search.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block title %}Search Internet-Drafts and RFCs{% endblock %} + +{% block content %} +

      Search Internet-Drafts and RFCs

      + +
      +{% include "idrfc/search_form.html" %} +
      + +{% if meta.searching %}{% include "idrfc/search_results.html" %}{% endif %} + +{% endblock content %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/doc/state_help.html b/ietf/templates/doc/state_help.html new file mode 100644 index 000000000..5c512e2df --- /dev/null +++ b/ietf/templates/doc/state_help.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} + +{% block title %}{{ title }}{% endblock %} + +{% block morecss %} +.ietf-table .name { white-space: nowrap; padding-right: 1em; } +.ietf-table .desc { max-width: 35em; } +{% endblock %} + +{% block content %} +

      {{ title }}

      + +{% if state_type.slug == "draft-iesg" %} +

      View Diagram

      +{% endif %} + + + + + + {% if has_next_states %}{% endif %} + + + {% for state in states %} + + + + {% if has_next_states %} + + {% endif %} + + {% endfor %} +
      StateDescriptionNext State
      {{ state.name }}{{ state.desc|linebreaksbr }} + {% for s in state.next_states.all %} + {{ s.name }}
      + {% endfor %} +
      + +{% if tags %} +

      Tags

      + + + + + + + + {% for tag in tags %} + + + + + {% endfor %} +
      TagDescription
      {{ tag.name }}{{ tag.desc|linebreaksbr }}
      +{% endif %} + +{% endblock %} diff --git a/ietf/templates/doc/states.html b/ietf/templates/doc/states.html deleted file mode 100644 index 6bc967a8d..000000000 --- a/ietf/templates/doc/states.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ title }} States{% endblock %} - -{% block morecss %} - .state_column { - width: 13em; - } -{% endblock %} - -{% block content %} - -

      {{ title }} States

      - - - - - - - - - - {% for state in states %} - - - - - {% endfor %} -
      StateDescription
      {{ state.name|escape }}{{ state.desc|escape }}
      - -{% endblock %} diff --git a/ietf/templates/doc/status_columns.html b/ietf/templates/doc/status_columns.html index d194fd573..95328dc0b 100644 --- a/ietf/templates/doc/status_columns.html +++ b/ietf/templates/doc/status_columns.html @@ -1,54 +1,46 @@ -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} -{% load ietf_filters ietf_streams_redesign %}{% load ballot_icon_redesign %}{% load doc_states %} +{% load ietf_filters %}{% load ballot_icon_redesign %} -{{ doc.friendly_state|safe }} {% if not doc.rfc %}{{ doc|state_age_colored|safe }}{% endif %} -{% if not hide_telechat_date %}{% if doc.telechat_date %}
      IESG Telechat: {{ doc.telechat_date }}{% endif %}{% endif %} + {{ doc.friendly_state|safe }} {% if not doc.get_state_slug == "rfc" %}{{ doc|state_age_colored }}{% endif %} -{% block extra_status %}{% endblock %} -{% if doc.rfc %} -{%comment%} -TODO: Port this block to Document when something that shows RFCs uses this template -{% if doc.rfc.obsoleted_by %}
      Obsoleted by {{ doc.rfc.obsoleted_by|urlize_ietf_docs }}{%endif %} -{% if doc.rfc.updated_by %}
      Updated by {{ doc.rfc.updated_by|urlize_ietf_docs }}{%endif %} -{% if doc.rfc.has_errata %}
      Errata{% endif %} -{%endcomment%} -{% else %}{# not rfc #} -{% if doc|rfc_editor_state %}
      RFC Editor State: {{ doc|rfc_editor_state|escape }}{% endif %} -{% stream_state doc %} -{% endif %} + {% if not hide_telechat_date and doc.telechat_date %} +
      IESG Telechat: {{ doc.telechat_date }} + {% endif %} + + {% block extra_status %}{% endblock %} + + + {% if doc.get_state_slug != "rfc" %}{# I-D #} + + {% if doc|state:"draft-rfceditor" %} +
      RFC Editor State: {{ doc|state:"draft-rfceditor" }} + {% endif %} + + {% if doc.stream %} +
      + {% if doc|state:"stream" %}{{ doc|state:"stream" }}{% else %}{{ doc.stream }}{% endif %} + + {% if doc.milestones %} + {% for m in doc.milestones %}{{ m.due|date:"M Y" }}{% endfor %} + {% endif %} + {% endif %} + + {% else %}{# RFC #} + + {% if doc.obsoleted_by_list %} +
      Obsoleted by {{ doc.obsoleted_by_list|join:", "|urlize_ietf_docs }}
      + {% endif %} + + {% if doc.updated_by_list %} +
      Updated by {{ doc.updated_by_list|join:", "|urlize_ietf_docs }}
      + {% endif %} + + {% if doc.has_errata %} + + {% endif %} + + {% endif %} + -{% ballot_icon doc %} + {% ballot_icon doc %} diff --git a/ietf/templates/feeds/comments_description.html b/ietf/templates/feeds/comments_description.html deleted file mode 100644 index acf097d19..000000000 --- a/ietf/templates/feeds/comments_description.html +++ /dev/null @@ -1,3 +0,0 @@ -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{% load ietf_filters %} -{{ obj.comment_text|format_textarea|safe|truncatewords_html:"20" }} diff --git a/ietf/templates/feeds/comments_title.html b/ietf/templates/feeds/comments_title.html deleted file mode 100644 index daf7e329f..000000000 --- a/ietf/templates/feeds/comments_title.html +++ /dev/null @@ -1,5 +0,0 @@ -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{% load ietf_filters %} -[{{ obj.get_username }}] {{ obj.comment_text|removetags:"b"|truncatewords:"15"|safe }} -{% if obj.ballot %}[[ IESG {{ obj.get_ballot_display.upper }} ]]{% endif %} -{% if not obj.document.rfc_flag %}[ version {{ obj.version }} ]{% endif %} diff --git a/ietf/templates/feeds/last-call_description.html b/ietf/templates/feeds/last-call_description.html deleted file mode 100644 index 9c620a045..000000000 --- a/ietf/templates/feeds/last-call_description.html +++ /dev/null @@ -1,3 +0,0 @@ -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{% load ietf_filters %} -{{ obj.ballot.last_call_text|escape|linebreaks }} diff --git a/ietf/templates/feeds/last-call_title.html b/ietf/templates/feeds/last-call_title.html deleted file mode 100644 index d12a663d7..000000000 --- a/ietf/templates/feeds/last-call_title.html +++ /dev/null @@ -1,2 +0,0 @@ -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{{ obj }} ({{ obj.document.lc_sent_date|date:"F j" }}-{{ obj.document.lc_expiration_date|date:"F j, Y" }}) diff --git a/ietf/templates/idindex/all_id2.txt b/ietf/templates/idindex/all_id2.txt index 3addadb74..5d810ab96 100644 --- a/ietf/templates/idindex/all_id2.txt +++ b/ietf/templates/idindex/all_id2.txt @@ -4,8 +4,7 @@ # # Description of fields: # 0 draft name and latest revision -# 1 id_document_tag (internal database identifier; avoid using -# unless you really need it) +# 1 always -1 (was internal numeric database id in earlier schema) # 2 one of "Active", "Expired", "RFC", "Withdrawn by Submitter", # "Replaced", or "Withdrawn by IETF" # 3 if #2 is "Active", the IESG state for the document (such as diff --git a/ietf/templates/idindex/all_ids.txt b/ietf/templates/idindex/all_ids.txt deleted file mode 100644 index 0daf8710c..000000000 --- a/ietf/templates/idindex/all_ids.txt +++ /dev/null @@ -1,17 +0,0 @@ - -Internet-Drafts Status Summary - -{% for item in in_track_ids %}{{ item.filename }}-{{ item.revision_display }} {{ item.revision_date|default_if_none:"" }} In IESG processing - ID Tracker state <{{ item.idstate }}> {# that last tab is on purpose #} -{% endfor %}{%comment%} -{%endcomment%}{% for item in active %}{{ item.filename }}-{{ item.revision_display }} {{ item.revision_date|default_if_none:"" }} {{ item.status.status }} {# keep that last tab #} -{% endfor %}{%comment%} -{%endcomment%}{% for item in published %}{{ item.filename }}-{{ item.revision_display }} {{ item.revision_date|default_if_none:"" }} {{ item.status.status }} {{ item.rfc_number }} -{% endfor %}{%comment%} -{%endcomment%}{% for item in expired %}{{ item.filename }}-{{ item.revision_display }} {{ item.revision_date|default_if_none:"" }} {{ item.status.status }} {# keep that last tab #} -{% endfor %}{%comment%} -{%endcomment%}{% for item in withdrawn_submitter %}{{ item.filename }}-{{ item.revision_display }} {{ item.revision_date|default_if_none:"" }} {{ item.status.status }} {# keep that last tab #} -{% endfor %}{%comment%} -{%endcomment%}{% for item in withdrawn_ietf %}{{ item.filename }}-{{ item.revision_display }} {{ item.revision_date|default_if_none:"" }} {{ item.status.status }} {# keep that last tab #} -{% endfor %}{%comment%} -{%endcomment%}{% for item in replaced %}{{ item.filename }}-{{ item.revision_display }} {{ item.revision_date|default_if_none:"" }} {{ item.status.status }} replaced by {% if item.replaced_by_id %}{{ item.replaced_by.filename }}{% else %}0{% endif %} {# and this one needs the trailing tab as well #} -{% endfor %} diff --git a/ietf/templates/idindex/id_abstracts.txt b/ietf/templates/idindex/id_abstracts.txt deleted file mode 100644 index d225369f5..000000000 --- a/ietf/templates/idindex/id_abstracts.txt +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "idindex/id_index.txt" %}{% load ietf_filters %} {% block intro %} Current Internet-Drafts - - This summary sheet provides a short synopsis of each Internet-Draft -available within the "internet-drafts" directory at the shadow -sites directory. These drafts are listed alphabetically by working -group acronym and start date. Generated {{ time }} -{% endblock %}{% block abstract %} - - {{ draft.clean_abstract|indent|indent|safe }}{% endblock %} diff --git a/ietf/templates/idindex/id_index.txt b/ietf/templates/idindex/id_index.txt index eca586397..2c18708d9 100644 --- a/ietf/templates/idindex/id_index.txt +++ b/ietf/templates/idindex/id_index.txt @@ -1,13 +1,12 @@ -{% autoescape off %}{% load ietf_filters %}{% block intro %} Current Internet-Drafts - This summary sheet provides an index of each Internet-Draft -These drafts are listed alphabetically by Working Group acronym and -initial post date. -{% endblock %} -{% for group in groups|dictsort:"group_acronym.acronym" %}{% if group.active_drafts %} -{{ group.group_acronym.name }} ({{ group.group_acronym.acronym}}) -{% filter dashify %}{{ group.group_acronym.name }} ({{ group.group_acronym.acronym}}){% endfilter %} -{% for draft in group.active_drafts|stable_dictsort:"filename"|stable_dictsort:"start_date" %} -{% filter id_index_wrap %} -"{{draft.title.strip|clean_whitespace}}", {% for author in draft.authors.all|dictsort:"final_author_order" %}{{author.person}}, {% endfor %}{{draft.revision_date|date:"j-M-y"}}, <{{draft.filename}}-{{draft.revision}}{{draft.file_type|id_index_file_types}}> -{% endfilter %}{% block abstract %}{% endblock %} -{% endfor %}{%endif %}{% endfor %}{% endautoescape %} +{% autoescape off %}{% load ietf_filters %} Current Internet-Drafts +This summary sheet provides an index of each Internet-Draft. These +drafts are listed alphabetically by Working Group acronym and initial +post date. Generated {{ time }}. + +{% for group in groups %} +{% filter underline %}{{ group.name }} ({{ group.acronym }}){% endfilter %} +{% for d in group.active_drafts %} + {% filter wordwrap:72|indent:2 %}"{{ d.title|clean_whitespace }}", {% for a in d.authors %}{{ a }}, {% endfor %}{{ d.rev_time|date:"Y-m-d"}}, <{{ d.name }}-{{ d.rev }}{{ d.exts }}> +{% endfilter %}{% if with_abstracts %} + + {{ d.abstract.strip|unindent|fill:72|indent:6 }}{% endif %}{% endfor %}{% endfor %}{% endautoescape %} diff --git a/ietf/templates/idrfc/all.html b/ietf/templates/idrfc/all.html index ed7672731..aa83ba90c 100644 --- a/ietf/templates/idrfc/all.html +++ b/ietf/templates/idrfc/all.html @@ -58,7 +58,7 @@ the search page instead.

      More information about active Internet-Drafts

      -{% for doc in active %}{{ doc.filename }}
      +{% for doc in active %}{{ doc.filename }}
      {% endfor %}

      diff --git a/ietf/templates/idrfc/change_stateREDESIGN.html b/ietf/templates/idrfc/change_stateREDESIGN.html index 4cc755bb1..05dec79eb 100644 --- a/ietf/templates/idrfc/change_stateREDESIGN.html +++ b/ietf/templates/idrfc/change_stateREDESIGN.html @@ -24,7 +24,7 @@ form.change-state .actions { {% block content %}

      Change state of {{ doc }}

      -

      For help on the states, see the state table.

      +

      For help on the states, see the state table.

      diff --git a/ietf/templates/idrfc/main.html b/ietf/templates/idrfc/main.html index 15c0a4b19..d3be2cf1f 100644 --- a/ietf/templates/idrfc/main.html +++ b/ietf/templates/idrfc/main.html @@ -61,7 +61,7 @@ td.ietf-main-intro { width:200px; background:#fff5df; padding:8px; border:1px so The IETF Datatracker is the IETF's web system for managing information about:
      - -
      -{% if docs %} -{% include "idrfc/search_results.html" %} -{% endif %} -
      {% endblock content %} {% block js %} + {% endblock %} diff --git a/ietf/templates/idrfc/search_form.html b/ietf/templates/idrfc/search_form.html index 9592ce186..69a24b089 100644 --- a/ietf/templates/idrfc/search_form.html +++ b/ietf/templates/idrfc/search_form.html @@ -32,7 +32,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% endcomment %} - +
      {{ form.name }} @@ -42,18 +42,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - + +
      +{{ form.sort }} {# hidden field #} + Advanced
      Additional search criteria:
      - {{ form.author }} + {{ form.author }}
      {{ form.group }} @@ -65,7 +67,7 @@ Additional search criteria: {{ form.ad }}
      - {{ form.state }} :: {{ form.subState }} + {{ form.state }} :: {{ form.substate }}
      diff --git a/ietf/templates/idrfc/search_main.html b/ietf/templates/idrfc/search_main.html index 9c235d03a..5a2d869b1 100644 --- a/ietf/templates/idrfc/search_main.html +++ b/ietf/templates/idrfc/search_main.html @@ -42,13 +42,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% include "idrfc/search_form.html" %}
    -
    +
    {% if meta.searching %} -{% include "idrfc/search_results.html" %} +{% include "idrfc/search-results.html" %} {% endif %}
    {% endblock content %} {% block js %} + {% endblock %} diff --git a/ietf/templates/idrfc/search_result_row.html b/ietf/templates/idrfc/search_result_row.html index 097e73180..0d3f72def 100644 --- a/ietf/templates/idrfc/search_result_row.html +++ b/ietf/templates/idrfc/search_result_row.html @@ -37,30 +37,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% if show_add_to_list and user.is_authenticated %} -{% if doc.id %} - Add to your personal ID list -{% else %} - Add to your personal ID list -{% endif %} + Add to your personal ID list {% endif %} -{% if doc.rfc %}{{ doc.rfc.displayname_with_link|safe }} -{% if doc.id %}
    ({{doc.id.draft_name}}){%endif%} -{% else %}{{ doc.id.displayname_with_link|safe }} -{% endif %} + {% if doc.get_state_slug == "rfc" %}RFC {{ doc.rfc_number }}{% else %}{{ doc.name }}-{{ doc.rev }}{% endif %} + + {% if doc.get_state_slug == "rfc" and "draft" in doc.name %} +
    (was {{ doc.name }}) + {% endif %} + {{ doc.title }} -{% if not doc.rfc %}{{ doc.publication_date }}{% else %}{{ doc.publication_date|date:"Y-m" }}{% endif %} -{% if doc.publication_date|timesince_days|new_enough:request %}
    {% if not doc.rfc%}new{%else%}new{%endif%}{%endif%} -{% if doc.id and doc.id.expected_expiration_date and doc.id.expected_expiration_date|timesince_days|expires_soon:request %}
    expires soon{%endif%} + + {% if doc.get_state_slug == "rfc" %}{{ doc.latest_revision_date|date:"Y-m" }}{% else %}{{ doc.latest_revision_date|date:"Y-m-d" }}{% endif %} + + {% if doc.latest_revision_date|timesince_days|new_enough:request %} +
    new
    + {% endif %} + + {% if doc.get_state_slug == "active" and doc.expirable and doc.expires|timesince_days|expires_soon:request %} +
    expires soon
    + {% endif %} -{% include "idrfc/status_columns.html" %} -{% include "idrfc/ipr_column.html" %} -{# {% if doc.ad_name %}{{ doc.ad_name }}{% else %} {% endif %} #} -{{ doc.ad_name|default:"" }} -{% if doc.id.underlying_document.shepherd %}
    {{ doc.id.underlying_document.shepherd.plain_name|default:""}}{% endif %} +{% include "doc/status_columns.html" %} + + + {% if doc.iprs %} + {{ doc.iprs|length }} + {% endif %} + +{{ doc.ad|default:"" }} + diff --git a/ietf/templates/idrfc/search_results.html b/ietf/templates/idrfc/search_results.html index ae7918688..17c9ce28c 100644 --- a/ietf/templates/idrfc/search_results.html +++ b/ietf/templates/idrfc/search_results.html @@ -32,36 +32,42 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% endcomment %} -{% if meta.max %} -

    Too many documents match the query! Returning partial result only.

    -{% endif %} +
    + {% if not docs %} -

    No documents match your query.

    +

    No documents match your query.

    {% else %} +{% if meta.max %} +

    Too many documents match the query! Returning partial result only.

    +{% endif %} + - {% if user.is_authenticated %}{% endif %} + {% if show_add_to_list and user.is_authenticated %}{% endif %} - {% for hdr in meta.hdrs %} - {% endfor %} - -{% regroup docs by view_sort_group as grouped_docs %} + +{% regroup docs by search_heading as grouped_docs %} + {% for doc_group in grouped_docs %} - - -{% with 1 as show_add_to_list %} -{% for doc in doc_group.list %} - {% include "idrfc/search_result_row.html" %} -{% endfor %} -{% endwith %} + + {% for doc in doc_group.list %} + {% include "idrfc/search_result_row.html" %} + {% endfor %} {% endfor %}
    - {{ hdr.htitle }} - - + {% for h in meta.headers %} + + {% if "sort_url" in h %} + {{ h.title }} + + {% else %} + {{ h.title }} + {% endif %}
    {{ doc_group.grouper }}s
    {{ doc_group.grouper }}
    {% endif %} + +
    diff --git a/ietf/templates/idrfc/status_columns.html b/ietf/templates/idrfc/status_columns.html index 00491018e..f9dc48d3b 100644 --- a/ietf/templates/idrfc/status_columns.html +++ b/ietf/templates/idrfc/status_columns.html @@ -31,7 +31,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% endcomment %} -{% load ietf_filters ietf_streams %}{% load ballot_icon %} +{% load ietf_filters ietf_streams %}{% load ballot_icon_redesign %} {{ doc.friendly_state|safe }} {% if not doc.rfc %}{{ doc.id|state_age_colored|safe }}{% endif %} {% if not hide_telechat_date %}{% if doc.telechat_date %}
    IESG Telechat: {{ doc.telechat_date }}{% endif %}{% endif %} diff --git a/ietf/templates/idtracker/.gitignore b/ietf/templates/idtracker/.gitignore deleted file mode 100644 index a74b07aee..000000000 --- a/ietf/templates/idtracker/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*.pyc diff --git a/ietf/templates/idtracker/document_entry.html b/ietf/templates/idtracker/document_entry.html deleted file mode 100644 index d7b8f5f6c..000000000 --- a/ietf/templates/idtracker/document_entry.html +++ /dev/null @@ -1,25 +0,0 @@ -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{% load ietf_filters %} -{% if doc.primary_flag %} - {{ doc.area_acronym.area_acronym.acronym.upper }} - - {% ifequal doc.cur_state.state "In Last Call" %} - {% if doc.document.lc_expiration_date %} - {{ doc.document.lc_expiration_date|date:"M j" }}, {{ doc.document.lc_expiration_date|date:"Y" }} - {% endif %} - {% else %} - {% if doc.status_date %} - {{ doc.status_date|date:"M j" }}, {{ doc.status_date|date:"Y" }} - {% endif %} - {% endifequal %} -{% else %} - -{% endif %} -{{ doc.document.title|escape }} ({{ doc.document.intended_status|escape }}) -{{ doc.document.displayname|safe }} -{% if doc.primary_flag %} -Token:{{ doc.token_name }} -{% if doc.note %} -Note:{{ doc.note|linebreaksbr|urlize }} -{% endif %} -{% endif %} diff --git a/ietf/templates/idtracker/state_desc.html b/ietf/templates/idtracker/state_desc.html deleted file mode 100644 index 977e6340e..000000000 --- a/ietf/templates/idtracker/state_desc.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "base.html" %} -{# Copyright The IETF Trust 2007, All Rights Reserved #} - -{% block content %} -

    {{ state.state }}

    -{{ state.description|escape }} - - - -{% endblock%} diff --git a/ietf/templates/idtracker/states.html b/ietf/templates/idtracker/states.html deleted file mode 100644 index f9d8fd20f..000000000 --- a/ietf/templates/idtracker/states.html +++ /dev/null @@ -1,61 +0,0 @@ -{% extends "base.html" %} -{# Copyright The IETF Trust 2007, All Rights Reserved #} - -{% block title %}Internet Draft States{% endblock %} - -{% block content %} - -

    Main I-D States

    - -

    View Diagram

    - - - - - - - - - -{# XXX I-D Exists should be added to the database #} - - - - - - -{% for state in states %} - - - - - -{% endfor %} -
    StateDescriptionNext State(s)
    I-D ExistsInitial (default) state for all internet drafts. Such documents are -not being tracked by the IESG as no request has been made of the -IESG to do anything with the document. -
      -
    • AD is watching -
    • Publication Requested
    -
    {{ state.state|escape }}{{ state.description|escape }} -
      - {% for next in state.nextstate.all %} -
    • {{ next.next_state.state }} - {% endfor %} -
    -
    - -

    Sub States

    - - - - -{% for substate in substates %} - - - - -{% endfor %} -
    Sub State NameDescription
    {{ substate.sub_state|escape }}{{ substate.description|escape }}
    - -{% endblock %} diff --git a/ietf/templates/idtracker/status_of_items.html b/ietf/templates/idtracker/status_of_items.html deleted file mode 100644 index 363bd0140..000000000 --- a/ietf/templates/idtracker/status_of_items.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "base.html" %} -{# Copyright The IETF Trust 2007, All Rights Reserved #} - -{% block pagehead %}{% if lastcall %} - -{% endif %}{% endblock %} - -{% block title %}{{ title }}{% endblock %} - -{% block content %} -

    {{ title }}

    - -{% regroup object_list by cur_state_id as grouped %} -{% for state in grouped %} -

    {{ state.list.0.cur_state }}

    - - -{% for doc in state.list %} -{% include "idtracker/document_entry.html" %} -{% if doc.ballot_others %} -{% for doc in doc.ballot_others %} -{% include "idtracker/document_entry.html" %} -{% endfor %} -{% endif %} -{% endfor %} -
    Area{% ifequal state.list.0.cur_state.state "In Last Call" %}Expires at{% else %}Date{% endifequal %}
    -{% endfor %} -{% endblock %} diff --git a/ietf/templates/iesg/agenda.html b/ietf/templates/iesg/agenda.html index fbe53c862..c83339a50 100644 --- a/ietf/templates/iesg/agenda.html +++ b/ietf/templates/iesg/agenda.html @@ -110,3 +110,8 @@ div.agenda-wg { margin-left: 30px; margin-top:0.5em; margin-bottom: 0.5em; width
    {% endblock content %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/iesg/agenda_documents_redesign.html b/ietf/templates/iesg/agenda_documents_redesign.html index ebc6d7e04..ad6e261df 100644 --- a/ietf/templates/iesg/agenda_documents_redesign.html +++ b/ietf/templates/iesg/agenda_documents_redesign.html @@ -170,5 +170,7 @@ font-size:80%; font-style:italic; {% endblock content %} {% block content_end %} + + {% endblock %} diff --git a/ietf/templates/iesg/discusses.html b/ietf/templates/iesg/discusses.html index 49b4439f8..99e7578fe 100644 --- a/ietf/templates/iesg/discusses.html +++ b/ietf/templates/iesg/discusses.html @@ -112,3 +112,8 @@ if (url[1] == 'byme') { } {% endif %}{# user in_group #} {% endblock scripts %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/wgcharter/change_state.html b/ietf/templates/wgcharter/change_state.html index 4ab8bad5e..44518317e 100644 --- a/ietf/templates/wgcharter/change_state.html +++ b/ietf/templates/wgcharter/change_state.html @@ -21,7 +21,7 @@ form.change-state .actions {

    {{ title }}

    {% if not option %} -

    For help on the states, see the state table.

    +

    For help on the states, see the state table.

    {% endif %}
    diff --git a/ietf/templates/wginfo/bofs.html b/ietf/templates/wginfo/bofs.html index bfd4f5baf..60df23342 100644 --- a/ietf/templates/wginfo/bofs.html +++ b/ietf/templates/wginfo/bofs.html @@ -4,7 +4,6 @@ {% block content %} {% load ietf_filters %} -{% load ballot_icon %}

    Bofs

    diff --git a/ietf/templates/wginfo/chartering_wgs.html b/ietf/templates/wginfo/chartering_wgs.html index bea3edbeb..72c81340c 100644 --- a/ietf/templates/wginfo/chartering_wgs.html +++ b/ietf/templates/wginfo/chartering_wgs.html @@ -4,7 +4,7 @@ {% block content %} {% load ietf_filters %} -{% load ballot_icon %} +{% load ballot_icon_redesign %}

    Chartering or Re-Chartering Working Groups

    @@ -51,3 +51,8 @@ {% endif %} {% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/wginfo/wg_documents.html b/ietf/templates/wginfo/wg_documents.html index 39e8bc572..b748bdfeb 100644 --- a/ietf/templates/wginfo/wg_documents.html +++ b/ietf/templates/wginfo/wg_documents.html @@ -35,72 +35,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% block wg_titledetail %}Documents{% endblock %} {% block wg_content %} +
    -{% regroup docs by view_sort_group as grouped_docs %} +{% include "idrfc/search_results.html" %} - - - {% if user.is_authenticated %}{% endif %} - - -{% for doc_group in grouped_docs %} - - -{% with 1 as show_add_to_list %} -{% for doc in doc_group.list %} -{% include "idrfc/search_result_row.html" %} -{% endfor %} -{% endwith %} - -{% endfor %} -
    DocumentTitleDateStatusiprAD / Shepherd
    {{doc_group.grouper}}s
    - -{% regroup docs_related by view_sort_group as grouped_docs_related %} - - - - {% if user.is_authenticated %}{% endif %} - - -{% for doc_group in grouped_docs_related %} - - -{% with 1 as show_add_to_list %} -{% for doc in doc_group.list %} - {% include "idrfc/search_result_row.html" %} -{% endfor %} -{% endwith %} - -{% endfor %} -
    Related DocumentsTitleDateStatusiprAD / Shepherd
    {{doc_group.grouper}}s
    +{% with docs_related as docs %}{% include "idrfc/search_results.html" %}{% endwith %} +
    {% endblock wg_content %} -{% block scripts %} -YAHOO.util.Event.onContentReady("search_submit_button", function () { - var oButton = new YAHOO.widget.Button("search_submit_button", {}); -}); -(function ($) { - $(document).ready(function () { - $('.addtolist a').click(function() { - var trigger = $(this); - $.ajax({ - url: trigger.attr('href'), - type: 'GET', - cache: false, - dataType: 'json', - success: function(response){ - if (response.success) { - trigger.replaceWith('added'); - } - } - }); - return false; - }); - }); -})(jQuery); -{% endblock scripts %} - {% block js %} + {% endblock %} diff --git a/ietf/urls.py b/ietf/urls.py index 80f2d5d5d..fa1531483 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -5,13 +5,12 @@ from django.conf.urls.defaults import patterns, include, handler404, handler500 from django.contrib import admin from ietf.iesg.feeds import IESGAgenda -from ietf.idtracker.feeds import DocumentComments, InLastCall +from ietf.doc.feeds import DocumentChanges, InLastCall from ietf.ipr.feeds import LatestIprDisclosures from ietf.proceedings.feeds import LatestWgProceedingsActivity from ietf.liaisons.feeds import Liaisons from ietf.wgcharter.feeds import GroupChanges -from ietf.idtracker.sitemaps import IDTrackerMap, DraftMap from ietf.liaisons.sitemaps import LiaisonMap from ietf.ipr.sitemaps import IPRMap from ietf.announcements.sitemaps import NOMCOMAnnouncementsMap @@ -25,7 +24,7 @@ admin.site.disable_action('delete_selected') feeds = { 'iesg-agenda': IESGAgenda, 'last-call': InLastCall, - 'comments': DocumentComments, + 'document-changes': DocumentChanges, 'group-changes': GroupChanges, 'ipr': LatestIprDisclosures, 'liaison': Liaisons, @@ -33,16 +32,11 @@ feeds = { } sitemaps = { - 'idtracker': IDTrackerMap, - 'drafts': DraftMap, 'liaison': LiaisonMap, 'ipr': IPRMap, 'nomcom-announcements': NOMCOMAnnouncementsMap, } -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - del sitemaps['drafts'] # not needed, overlaps sitemaps['idtracker'] - urlpatterns = patterns('', (r'^$', 'ietf.idrfc.views.main'), (r'^accounts/', include('ietf.ietfauth.urls')), @@ -52,10 +46,11 @@ urlpatterns = patterns('', (r'^community/', include('ietf.community.urls')), (r'^cookies/', include('ietf.cookies.urls')), (r'^doc/', include('ietf.idrfc.urls')), - (r'^drafts/', include('ietf.idindex.urls')), + (r'^drafts/', include('ietf.doc.redirect_drafts_urls')), + (r'^feed/comments/(?P.*)/$', 'django.views.generic.simple.redirect_to', { 'url': '/feed/document-changes/%(remainder)s/'}), (r'^feed/(?P.*)/$', 'django.contrib.syndication.views.feed', { 'feed_dict': feeds}), (r'^help/', include('ietf.help.urls')), - (r'^idtracker/', include('ietf.idtracker.urls')), + (r'^idtracker/', include('ietf.doc.redirect_idtracker_urls')), (r'^iesg/', include('ietf.iesg.urls')), (r'^ipr/', include('ietf.ipr.urls')), (r'^liaison/', include('ietf.liaisons.urls')), diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 46c425297..1e9ffc83c 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -418,6 +418,7 @@ def make_test_data(): docalias = DocAlias.objects.create(name=doc.name, document=doc) doc.stream = StreamName.objects.get(slug='irtf') doc.save() + doc.set_state(State.objects.get(type="draft", slug="active")) crdoc = Document.objects.create(name='conflict-review-imaginary-irtf-submission', type_id='conflrev', rev='00', notify="fsm@ietf.org") DocAlias.objects.create(name=crdoc.name, document=crdoc) crdoc.set_state(State.objects.get(name='Needs Shepherd', type__slug='conflrev')) diff --git a/ietf/wginfo/views.py b/ietf/wginfo/views.py index a0dd53071..fc4ad9797 100644 --- a/ietf/wginfo/views.py +++ b/ietf/wginfo/views.py @@ -38,7 +38,7 @@ from django.http import HttpResponse from django.conf import settings from django.core.urlresolvers import reverse as urlreverse from ietf.idtracker.models import Area, IETFWG -from ietf.idrfc.views_search import SearchForm, search_query +from ietf.idrfc.views_search import SearchForm, retrieve_search_results from ietf.idrfc.idrfc_wrapper import IdRfcWrapper from ietf.ipr.models import IprDetail from ietf.group.models import Group @@ -146,39 +146,34 @@ def wg_documents(request, acronym): concluded = wg.status_id in [ 2, 3, ] proposed = (wg.status_id == 4) form = SearchForm({'by':'group', 'group':str(wg.group_acronym.acronym), - 'rfcs':'on', 'activeDrafts':'on'}) - if not form.is_valid(): - raise ValueError("form did not validate") - (docs,meta) = search_query(form.cleaned_data) + 'rfcs':'on', 'activedrafts':'on'}) + docs, meta = retrieve_search_results(form) # get the related docs - form_related = SearchForm({'by':'group', 'name':'-'+str(wg.group_acronym.acronym)+'-', 'activeDrafts':'on'}) - if not form_related.is_valid(): - raise ValueError("form_related did not validate") - (docs_related,meta_related) = search_query(form_related.cleaned_data) + form_related = SearchForm({'by':'group', 'name':'-'+str(wg.group_acronym.acronym)+'-', 'activedrafts':'on'}) + docs_related, meta_related = retrieve_search_results(form_related) docs_related_pruned = [] for d in docs_related: - parts = d.id.draft_name.split("-", 2); + parts = d.name.split("-", 2); # canonical form draft--wg-etc - if ( len(parts) >= 3): - if parts[1] != "ietf" and parts[2].startswith(wg.group_acronym.acronym+"-"): + if len(parts) >= 3 and parts[1] != "ietf" and parts[2].startswith(wg.group_acronym.acronym + "-"): docs_related_pruned.append(d) docs_related = docs_related_pruned # move call for WG adoption to related cleaned_docs = [] - related_doc_names = set(d.id.draft_name for d in docs_related) + docs_related_names = set(d.name for d in docs_related) for d in docs: - if d.id and d.id._draft and d.id._draft.stream_id == "ietf" and d.id._draft.get_state_slug("draft-stream-ietf") == "c-adopt": - if d.id.draft_name not in related_doc_names: + if d.stream_id == "ietf" and d.get_state_slug("draft-stream-ietf") == "c-adopt": + if d.name not in docs_related_names: docs_related.append(d) else: cleaned_docs.append(d) docs = cleaned_docs - docs_related.sort(key=lambda d: d.id.draft_name) + docs_related.sort(key=lambda d: d.name) return wg, concluded, proposed, docs, meta, docs_related, meta_related diff --git a/static/css/base2.css b/static/css/base2.css index 7b8bddf2a..73167b1cb 100644 --- a/static/css/base2.css +++ b/static/css/base2.css @@ -33,6 +33,9 @@ */ body { margin: 0; } + +a img { border: 0; } + .yui-skin-sam h1 {margin: 0.5em 0; font-size: 167%;} .yui-skin-sam .yui-navset .yui-content { background: white; @@ -114,7 +117,8 @@ table.ietf-table { border-collapse:collapse; border:1px solid #7f7f7f; } .ietf-doctable tr.header { border-top: 1px solid #7f7f7f; border-bottom: 1px solid #7f7f7f; border-left: 1px solid white; border-right:2px solid white;} .ietf-doctable tr.header td {padding: 6px 6px; font-weight: bold; } .ietf-doctable table { max-width: 1200px; } -.ietf-doctable th { cursor: pointer; white-space: nowrap; } +.ietf-doctable th { white-space: nowrap; } +.ietf-doctable th a { text-decoration: none; color: #fff; } .ietf-doctable th img { border-style: none; vertical-align: top; } .ietf-doctable th.doc, .ietf-doctable td.doc { min-width:20em; max-width: 35em; } .ietf-doctable th.title, .ietf-doctable td.title { min-width: 20em; max-width: 35em; } @@ -123,10 +127,11 @@ table.ietf-table { border-collapse:collapse; border:1px solid #7f7f7f; } .ietf-doctable th.ipr { font-variant: small-caps; } .ietf-doctable th.ad, .ietf-doctable td.ad { white-space:nowrap; min-width: 6em; } .ietf-doctable td.ballot { border-left: hidden; min-width: 37px; } +.ietf-doctable td .updated-by { max-width: 20em; } /* some RFCs have really long lists */ -table.ballot_icon { empty-cells: show; padding: 0; border-spacing: 0; border: 1px solid black; border-collapse: collapse; table-layout:fixed; min-width:35px; background:white; } -table.ballot_icon td { border: 1px solid black; height: 7px; width: 6px; padding: 0;} -table.ballot_icon td.my { border: 3px outset black;} +.ietf-doctable .status .milestone, +.ietf-box .stream-state .milestone +{ display: inline-block; font-size: smaller; background-color: #d5dde6; padding: 0 0.2em; margin-left: 0.3em; } .ietf-small { font-size:85%; } .ietf-tiny { font-size:70%; } @@ -134,7 +139,8 @@ table.ballot_icon td.my { border: 3px outset black;} .ietf-highlight-r { padding:0 2px;background:#ffa0a0;} .ietf-divider { background: #2647a0; color: white; font-size:116%; padding:0.5em 1em; } -table.history .snippet .showAll { color: blue; cursor: pointer; } + +table.history .snippet .show-all { color: blue; cursor: pointer; } .error-text { font-size: 1.095em; @@ -175,14 +181,18 @@ form table .help { .warning { color: #a00; } .position-discuss, -.position-block { background-color: #c00000;} -.position-yes { background-color: #80ff80;} -.position-noobj { background-color: #80ff80;} -.position-abstain { background-color: #ffff00;} -.position-recuse { background-color: #c0c0c0;} -.position-norecord { background-color: #ffffff;} +.position-block { background-color: #c00;} +.position-yes { background-color: #0d0;} +.position-noobj { background-color: #0d0;} +.position-abstain { background-color: #ff0;} +.position-recuse { background-color: #bbb;} +.position-norecord { background-color: #fff;} -.ballot-sidebar { width: 160px; float: left; margin-top: 0.4em; margin-right: 1em; padding: 0.5em; background: #edf5ff; } +.ballot-icon table { empty-cells: show; padding: 0; border-spacing: 0; border: 1px solid #666; border-collapse: collapse; table-layout: fixed; min-width: 35px; background: #fff; } +.ballot-icon table td { border: 1px solid #666; height: 7px; width: 6px; padding: 0;} +.ballot-icon table td.my { border: 3px solid #000;} + +.ballot-sidebar { width: 160px; float: left; margin-top: 0.4em; padding: 5px; background: #edf5ff; } .ballot-sidebar .action { margin-bottom: 1em; } .ballot-sidebar .position-group { margin-bottom: 1em; } .ballot-sidebar .position-group .heading { font-weight: bold; } @@ -190,15 +200,14 @@ form table .help { .ballot-sidebar .position-group .was { padding-left: 10px; font-size:85%; } .ballot-sidebar .position-group:last-child { margin-bottom: 0; } -.ballot-content { margin-left: 180px; } +.ballot-content { margin-left: 180px; } /* this positions the content to the right of the sidebar */ .ballot-content .other-ballots { margin: 1em 0 2em 0; } .ballot-content .other-ballots a { display: inline-block; margin-right: 0.5em; } - -#ballot_dialog_body { background-color: #fff; } -#ballot_dialog_body .ballot-sidebar { margin-top: 0; } - .ballot-content h2.ad-ballot-comment { background: #2647A0; color: #fff; padding: 2px 4px; font-size: 108%; margin-top: 0;} +.ballot-popup > .content { width: 860px; height: 500px; overflow: auto; background: #fff; border: 1px solid #ccc; } +.ballot-popup .ballot-sidebar { margin-top: 0; } + ul.messages { border: solid black 1px; margin: 0.4em 0; padding: 1em; } li.debug { margin: 0.5em; background-color: #ccf; } li.info { margin: 0.5em; background-color: #ff8; } @@ -208,12 +217,17 @@ li.error { margin: 0.5em; background-color: #f44; } .errorlist { background: red; color: white; padding: 0.2ex 0.2ex 0.2ex 0.5ex; border: 0px; margin: 0px; font-family: Arial, sans-serif; } +.group-documents .search-results { margin-top: 1.5em; } + table.milestones td.due { vertical-align: top; width: 80px; } table.milestones .doc { display: block; padding-left: 1em; } -.stream-state .milestone { display: inline-block; font-size: smaller; background-color: #d5dde6; padding: 0 0.2em; margin-left: 0.3em; } -.button, .button:hover:disabled { +#modal-box { background: #f0f0f0; border-radius: 2px; padding: 10px; box-shadow: 0 0 4px rgba(0, 0, 0, 0.8); } + +#modal-box .actions { padding-top: 1em; text-align: right; } + +.button { display: inline-block; padding: 4px 12px; margin-right: 0.3em; color: #222; font-weight: normal; text-align: center; text-decoration: none; outline: none; cursor: pointer; background: #eee; background: linear-gradient(#fff, #e0e0e0); background: -webkit-linear-gradient(#fff, #e0e0e0); background: -moz-linear-gradient(#fff, #e0e0e0); @@ -222,4 +236,4 @@ table.milestones .doc { display: block; padding-left: 1em; } } .button:hover { color: #111; background: #ddd; background: linear-gradient(#eee, #ccc); background: -webkit-linear-gradient(#eee, #ccc); background: -moz-linear-gradient(#eee, #ccc); } .button:active { color: #000; background: #ccc; } -.button:disabled, .button:hover:disabled { color: #999; cursor: default; } +.button:disabled, .button:hover:disabled { color: #999; cursor: default; background: #eee; background: linear-gradient(#fff, #e0e0e0); background: -webkit-linear-gradient(#fff, #e0e0e0); background: -moz-linear-gradient(#fff, #e0e0e0); } diff --git a/static/images/state_diagram.png b/static/images/iesg-draft-state-diagram.png similarity index 100% rename from static/images/state_diagram.png rename to static/images/iesg-draft-state-diagram.png diff --git a/static/js/datatracker-search.xml b/static/js/datatracker-search.xml index 0d8855e20..95a1f6d4c 100644 --- a/static/js/datatracker-search.xml +++ b/static/js/datatracker-search.xml @@ -4,7 +4,7 @@ Use datatracker.ietf.org to search for Internet-Drafts and RFCs Requests For Comments + template="http://datatracker.ietf.org/doc/search/?name={searchTerms}&activedrafts=on&rfcs=on"/> datatracker.ietf.org RFC and Internet-Draft Search http://datatracker.ietf.org/images/ietf-icon-blue.bmp Tony Hansen diff --git a/static/js/doc-search.js b/static/js/doc-search.js index 5f999f38c..9a3b874f7 100644 --- a/static/js/doc-search.js +++ b/static/js/doc-search.js @@ -1,21 +1,23 @@ $(function () { + // search form var form = jQuery("#search_form"); - // we want to disable our submit button if we have no search text, - // and we have no advanced options selected - function toggleSubmit() { - var nameSearch = $.trim($("#id_name").val()); - - var noAdvanced = true; + function anyAdvancedActive() { + var advanced = false; var by = form.find("input[name=by]:checked"); if (by.length > 0) by.closest(".search_field").find("input,select").not("input[name=by]").each(function () { if ($.trim(this.value)) - noAdvanced = false; + advanced = true; }); - form.find("input[type=submit]").get(0).disabled = !nameSearch && noAdvanced; + return advanced; + } + + function toggleSubmit() { + var nameSearch = $.trim($("#id_name").val()); + form.find("input[type=submit]").get(0).disabled = !nameSearch && !anyAdvancedActive(); } function togglePlusMinus(toggler, toggled) { @@ -29,7 +31,7 @@ $(function () { } } - function updateBy() { + function updateAdvanced() { form.find("input[name=by]:checked").closest(".search_field").find("input,select").not("input[name=by]").each(function () { this.disabled = false; }); @@ -41,25 +43,24 @@ $(function () { toggleSubmit(); } - form.find(".search_field input[name=by]").closest("label").click(updateBy); + if (form.length > 0) { + form.find(".search_field input[name=by]").closest("label").click(updateAdvanced); - form.find(".search_field input,select") - .change(toggleSubmit).click(toggleSubmit).keyup(toggleSubmit); + form.find(".search_field input,select") + .change(toggleSubmit).click(toggleSubmit).keyup(toggleSubmit); - form.find(".toggle_advanced").click(function () { - var advanced = $(this).next(); - advanced.find('.search_field input[type="radio"]').attr("checked", false); - togglePlusMinus($(this), advanced); - updateBy(); - }); + form.find(".toggle_advanced").click(function () { + var advanced = $(this).next(); + advanced.find('.search_field input[type="radio"]').attr("checked", false); + togglePlusMinus($(this), advanced); + updateAdvanced(); + }); - updateBy(); + updateAdvanced(); + } - $("#search_results th").click(function (e) { - window.location = $(this).find("a").attr("href"); - }) - - $('#search_results .addtolist a').click(function(e) { + // search results + $('.search-results .addtolist a').click(function(e) { e.preventDefault(); var trigger = $(this); $.ajax({ @@ -74,4 +75,27 @@ $(function () { } }); }); + + $("a.ballot-icon").click(function (e) { + e.preventDefault(); + + $.ajax({ + url: $(this).data("popup"), + success: function (data) { + showModalBox(data); + }, + error: function () { + showModalBox("
    Error retrieving popup content
    "); + } + }); + }).each(function () { + // bind right-click shortcut + var editPositionUrl = $(this).data("edit"); + if (editPositionUrl) { + $(this).bind("contextmenu", function (e) { + e.preventDefault(); + window.location = editPositionUrl; + }); + } + }); }); diff --git a/static/js/history.js b/static/js/history.js index eaae6ca6f..3b44400e6 100644 --- a/static/js/history.js +++ b/static/js/history.js @@ -1,5 +1,5 @@ jQuery(function () { - jQuery("table.history .snippet .showAll").click(function () { + jQuery("table.history .snippet .show-all").click(function () { jQuery(this).parents(".snippet").hide().siblings(".full").show(); }); }); diff --git a/static/js/utils.js b/static/js/utils.js new file mode 100644 index 000000000..a84c9bc6b --- /dev/null +++ b/static/js/utils.js @@ -0,0 +1,43 @@ +function showModalBox(content, callback) { + content = $(content); + + // make sure previous one is gone + $("#modal-overlay").remove(); + + // the url(data:...) part is backwards compatibility for non-rgba + // supporting browsers (IE 8) - it's a 2 pixel black PNG with + // opacity 50% + var overlay = $(''); + var box = $(''); + + box.append(content); + overlay.append(box); + + box.click(function (e) { + e.stopPropagation(); + }); + overlay.click(closeModalBox); + box.find(".button.close").click(function (e) { + e.preventDefault(); + closeModalBox(); + }); + overlay.keydown(function (e) { + if (e.which == 27) + closeModalBox(); + }); + + $("body").append(overlay); + + var w = content.outerWidth() || 400; + var h = content.outerHeight() || 300; + box.css({ "margin-left": -parseInt(w/2), "margin-top": -parseInt(h/2) }); + + content.focus(); + + if (callback) + callback(); +} + +function closeModalBox() { + $("#modal-overlay").remove(); +}