diff --git a/bin/test-crawl b/bin/test-crawl
index 2c4fce706..204df4744 100755
--- a/bin/test-crawl
+++ b/bin/test-crawl
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-import os, sys, re, datetime, argparse, traceback, tempfile, json, subprocess
+import os, sys, re, datetime, argparse, traceback, json, subprocess
import html5lib
import random
diff --git a/ietf/bin/rfc-editor-index-updates b/ietf/bin/rfc-editor-index-updates
index 430f1fe1a..4387805f0 100755
--- a/ietf/bin/rfc-editor-index-updates
+++ b/ietf/bin/rfc-editor-index-updates
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-import os, sys, re, json, datetime
+import os, sys, datetime
import syslog
import traceback
@@ -29,22 +29,28 @@ if options.skip_date:
skip_date = datetime.datetime.strptime(options.skip_date, "%Y-%m-%d").date()
from ietf.utils.pipe import pipe
-from ietf.sync.rfceditor import *
from ietf.doc.utils import rebuild_reference_relations
+import ietf.sync.rfceditor
syslog.syslog("Updating document metadata from RFC index from %s" % settings.RFC_EDITOR_QUEUE_URL)
-response = fetch_index_xml(settings.RFC_EDITOR_INDEX_URL)
-data = parse_index(response)
+response = ietf.sync.rfceditor.fetch_index_xml(settings.RFC_EDITOR_INDEX_URL)
+data = ietf.sync.rfceditor.parse_index(response)
-if len(data) < MIN_INDEX_RESULTS:
+if len(data) < ietf.sync.rfceditor.MIN_INDEX_RESULTS:
syslog.syslog("Not enough results, only %s" % len(data))
sys.exit(1)
-changed, new_rfcs = update_docs_from_rfc_index(data, skip_older_than_date=skip_date)
+new_rfcs = []
+for changes, doc, rfc_published in ietf.sync.rfceditor.update_docs_from_rfc_index(data, skip_older_than_date=skip_date):
+ if rfc_published:
+ new_rfcs.append(doc)
-for c in changed:
- syslog.syslog(c)
+ for c in changes:
+ syslog.syslog("%s: %s" % (doc.name, c))
+ print "%s: %s" % (doc.name, c)
+
+sys.exit(0)
# This can be called while processing a notifying POST from the RFC Editor
# Spawn a child to sync the rfcs and calculate new reference relationships
diff --git a/ietf/doc/expire.py b/ietf/doc/expire.py
index 4e215abc6..8a833ea53 100644
--- a/ietf/doc/expire.py
+++ b/ietf/doc/expire.py
@@ -6,7 +6,7 @@ import datetime, os, shutil, glob, re
from pathlib import Path
from ietf.utils.mail import send_mail
-from ietf.doc.models import Document, DocEvent, State, save_document_in_history, IESG_SUBSTATE_TAGS
+from ietf.doc.models import Document, DocEvent, State, IESG_SUBSTATE_TAGS
from ietf.person.models import Person
from ietf.meeting.models import Meeting
from ietf.doc.utils import add_state_change_event
@@ -129,8 +129,9 @@ def expire_draft(doc):
system = Person.objects.get(name="(System)")
+ events = []
+
# change the state
- save_document_in_history(doc)
if doc.latest_event(type='started_iesg_process'):
new_state = State.objects.get(used=True, type="draft-iesg", slug="dead")
prev_state = doc.get_state(new_state.type_id)
@@ -139,15 +140,13 @@ def expire_draft(doc):
doc.set_state(new_state)
doc.tags.remove(*prev_tags)
e = add_state_change_event(doc, system, prev_state, new_state, prev_tags=prev_tags, new_tags=[])
+ if e:
+ events.append(e)
- e = DocEvent(doc=doc, by=system)
- e.type = "expired_document"
- e.desc = "Document has expired"
- e.save()
+ events.append(DocEvent.objects.create(doc=doc, by=system, type="expired_document", desc="Document has expired"))
doc.set_state(State.objects.get(used=True, type="draft", slug="expired"))
- doc.time = datetime.datetime.now()
- doc.save()
+ doc.save_with_history(events)
def clean_up_draft_files():
"""Move unidentified and old files out of the Internet Draft directory."""
diff --git a/ietf/doc/lastcall.py b/ietf/doc/lastcall.py
index 6310de4a4..86cae3832 100644
--- a/ietf/doc/lastcall.py
+++ b/ietf/doc/lastcall.py
@@ -5,7 +5,6 @@ import datetime
from django.db.models import Q
from ietf.doc.models import Document, State, DocEvent, LastCallDocEvent, WriteupDocEvent
-from ietf.doc.models import save_document_in_history
from ietf.doc.models import IESG_SUBSTATE_TAGS
from ietf.person.models import Person
from ietf.doc.utils import add_state_change_event
@@ -14,12 +13,15 @@ from ietf.doc.mails import send_last_call_request, email_last_call_expired
def request_last_call(request, doc):
if not doc.latest_event(type="changed_ballot_writeup_text"):
- generate_ballot_writeup(request, doc)
+ e = generate_ballot_writeup(request, doc)
+ e.save()
if not doc.latest_event(type="changed_ballot_approval_text"):
- generate_approval_mail(request, doc)
+ e = generate_approval_mail(request, doc)
+ e.save()
if not doc.latest_event(type="changed_last_call_text"):
- generate_last_call_announcement(request, doc)
-
+ e = generate_last_call_announcement(request, doc)
+ e.save()
+
send_last_call_request(request, doc)
e = DocEvent()
@@ -50,8 +52,6 @@ def expire_last_call(doc):
else:
raise ValueError("Unexpected document type to expire_last_call(): %s" % doc.type)
- save_document_in_history(doc)
-
prev_state = doc.get_state(new_state.type_id)
doc.set_state(new_state)
@@ -60,8 +60,7 @@ def expire_last_call(doc):
system = Person.objects.get(name="(System)")
e = add_state_change_event(doc, system, prev_state, new_state, prev_tags=prev_tags, new_tags=[])
-
- doc.time = (e and e.time) or datetime.datetime.now()
- doc.save()
+ if e:
+ doc.save_with_history([e])
email_last_call_expired(doc)
diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py
index ab7d336c6..2b07cfedc 100644
--- a/ietf/doc/mails.py
+++ b/ietf/doc/mails.py
@@ -78,6 +78,21 @@ def email_iesg_processing_document(request, doc, changes):
def html_to_text(html):
return strip_tags(html.replace("<", "<").replace(">", ">").replace("&", "&").replace(" ", "\n"))
+def email_update_telechat(request, doc, text):
+ (to, cc) = gather_address_lists('doc_telechat_details_changed',doc=doc)
+
+ if not to:
+ return
+
+ text = strip_tags(text)
+ send_mail(request, to, None,
+ "Telechat update notice: %s" % doc.file_tag(),
+ "doc/mail/update_telechat.txt",
+ dict(text=text,
+ url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
+ cc=cc)
+
+
def generate_ballot_writeup(request, doc):
e = doc.latest_event(type="iana_review")
iana = e.desc if e else ""
@@ -88,8 +103,8 @@ def generate_ballot_writeup(request, doc):
e.doc = doc
e.desc = u"Ballot writeup was generated"
e.text = unicode(render_to_string("doc/mail/ballot_writeup.txt", {'iana': iana}))
- e.save()
-
+
+ # caller is responsible for saving, if necessary
return e
def generate_last_call_announcement(request, doc):
@@ -130,8 +145,8 @@ def generate_last_call_announcement(request, doc):
e.doc = doc
e.desc = u"Last call announcement was generated"
e.text = unicode(mail)
- e.save()
+ # caller is responsible for saving, if necessary
return e
@@ -149,8 +164,8 @@ def generate_approval_mail(request, doc):
e.doc = doc
e.desc = u"Ballot approval text was generated"
e.text = unicode(mail)
- e.save()
+ # caller is responsible for saving, if necessary
return e
def generate_approval_mail_approved(request, doc):
diff --git a/ietf/doc/migrations/0011_auto_20151027_1127.py b/ietf/doc/migrations/0011_auto_20151027_1127.py
new file mode 100644
index 000000000..1039d0678
--- /dev/null
+++ b/ietf/doc/migrations/0011_auto_20151027_1127.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+def fix_buggy_author_foreignkey(apps, schema_editor):
+ DocumentAuthor = apps.get_model("doc", "DocumentAuthor")
+ # apparently, we have a buggy key in the DB, fix it
+ DocumentAuthor.objects.filter(author="[]").update(author="d3e3e3@gmail.com")
+
+def save_all_documents_in_history(apps, schema_editor):
+ State = apps.get_model("doc", "State")
+ Document = apps.get_model("doc", "Document")
+ DocHistory = apps.get_model("doc", "DocHistory")
+ RelatedDocument = apps.get_model("doc", "RelatedDocument")
+ RelatedDocHistory = apps.get_model("doc", "RelatedDocHistory")
+ DocumentAuthor = apps.get_model("doc", "DocumentAuthor")
+ DocHistoryAuthor = apps.get_model("doc", "DocHistoryAuthor")
+
+ def canonical_name(self):
+ name = self.name
+ state = State.objects.filter(document=self, type_id=self.type_id).first()
+ if self.type_id == "draft" and state.slug == "rfc":
+ a = self.docalias_set.filter(name__startswith="rfc")
+ if a:
+ name = a[0].name
+ elif self.type_id == "charter":
+ return charter_name_for_group(self.chartered_group)
+ return name
+
+ def charter_name_for_group(group):
+ if group.type_id == "rg":
+ top_org = "irtf"
+ else:
+ top_org = "ietf"
+
+ return "charter-%s-%s" % (top_org, group.acronym)
+
+ def save_document_in_history(doc):
+ """Save a snapshot of document and related objects in the database."""
+ def get_model_fields_as_dict(obj):
+ return dict((field.name, getattr(obj, field.name))
+ for field in obj._meta.fields
+ if field is not obj._meta.pk)
+
+ # copy fields
+ fields = get_model_fields_as_dict(doc)
+ fields["doc"] = doc
+ fields["name"] = canonical_name(doc)
+
+ dochist = DocHistory(**fields)
+ dochist.save()
+
+ # copy many to many
+ for field in doc._meta.many_to_many:
+ if field.rel.through and field.rel.through._meta.auto_created:
+ setattr(dochist, field.name, getattr(doc, field.name).all())
+
+ # copy remaining tricky many to many
+ def transfer_fields(obj, HistModel):
+ mfields = get_model_fields_as_dict(item)
+ # map doc -> dochist
+ for k, v in mfields.iteritems():
+ if v == doc:
+ mfields[k] = dochist
+ HistModel.objects.create(**mfields)
+
+ for item in RelatedDocument.objects.filter(source=doc):
+ transfer_fields(item, RelatedDocHistory)
+
+ for item in DocumentAuthor.objects.filter(document=doc):
+ transfer_fields(item, DocHistoryAuthor)
+
+ return dochist
+
+ from django.conf import settings
+ settings.DEBUG = False # prevent out-of-memory problems
+
+ for d in Document.objects.iterator():
+ save_document_in_history(d)
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('doc', '0010_auto_20150930_0251'),
+ ]
+
+ operations = [
+ migrations.RunPython(fix_buggy_author_foreignkey),
+ migrations.RunPython(save_all_documents_in_history)
+ ]
diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index ddc20b297..d6133fc50 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -18,7 +18,6 @@ from ietf.name.models import ( DocTypeName, DocTagName, StreamName, IntendedStdL
from ietf.person.models import Email, Person
from ietf.utils.admin import admin_link
-
class StateType(models.Model):
slug = models.CharField(primary_key=True, max_length=30) # draft, draft-iesg, charter, ...
label = models.CharField(max_length=255, help_text="Label that should be used (e.g. in admin) for state drop-down for this type of state") # State, IESG state, WG state, ...
@@ -432,6 +431,27 @@ class Document(DocumentInfo):
name = name.upper()
return name
+ def save_with_history(self, events):
+ """Save document and put a snapshot in the history models where they
+ can be retrieved later. You must pass in at least one event
+ with a description of what happened."""
+
+ assert events, "You must always add at least one event to describe the changes in the history log"
+ self.time = max(self.time, events[0].time)
+
+ self._has_an_event_so_saving_is_allowed = True
+ self.save()
+ del self._has_an_event_so_saving_is_allowed
+
+ from ietf.doc.utils import save_document_in_history
+ save_document_in_history(self)
+
+ def save(self, *args, **kwargs):
+ # if there's no primary key yet, we can allow the save to go
+ # through to break the cycle between the document and any
+ # events
+ assert kwargs.get("force_insert", False) or getattr(self, "_has_an_event_so_saving_is_allowed", None), "Use .save_with_history to save documents"
+ super(Document, self).save(*args, **kwargs)
def telechat_date(self, e=None):
if not e:
@@ -568,50 +588,6 @@ class DocHistory(DocumentInfo):
verbose_name = "document history"
verbose_name_plural = "document histories"
-def save_document_in_history(doc):
- """This should be called before saving changes to a Document instance,
- so that the DocHistory entries contain all previous states, while
- the Group entry contain the current state. XXX TODO: Call this
- directly from Document.save(), and add event listeners for save()
- on related objects so we can save as needed when they change, too.
- """
- def get_model_fields_as_dict(obj):
- return dict((field.name, getattr(obj, field.name))
- for field in obj._meta.fields
- if field is not obj._meta.pk)
-
- # copy fields
- fields = get_model_fields_as_dict(doc)
- fields["doc"] = doc
- fields["name"] = doc.canonical_name()
-
- dochist = DocHistory(**fields)
- dochist.save()
-
- # copy many to many
- for field in doc._meta.many_to_many:
- if field.rel.through and field.rel.through._meta.auto_created:
- setattr(dochist, field.name, getattr(doc, field.name).all())
-
- # copy remaining tricky many to many
- def transfer_fields(obj, HistModel):
- mfields = get_model_fields_as_dict(item)
- # map doc -> dochist
- for k, v in mfields.iteritems():
- if v == doc:
- mfields[k] = dochist
- HistModel.objects.create(**mfields)
-
- for item in RelatedDocument.objects.filter(source=doc):
- transfer_fields(item, RelatedDocHistory)
-
- for item in DocumentAuthor.objects.filter(document=doc):
- transfer_fields(item, DocHistoryAuthor)
-
- return dochist
-
-
-
class DocAlias(models.Model):
"""This is used for documents that may appear under multiple names,
and in particular for RFCs, which for continuity still keep the
@@ -693,7 +669,8 @@ EVENT_TYPES = [
# RFC Editor
("rfc_editor_received_announcement", "Announcement was received by RFC Editor"),
- ("requested_publication", "Publication at RFC Editor requested")
+ ("requested_publication", "Publication at RFC Editor requested"),
+ ("sync_from_rfc_editor", "Received updated information from RFC Editor"),
]
class DocEvent(models.Model):
@@ -705,10 +682,11 @@ class DocEvent(models.Model):
desc = models.TextField()
def for_current_revision(self):
- return self.time >= self.doc.latest_event(NewRevisionDocEvent,type='new_revision').time
+ e = self.doc.latest_event(NewRevisionDocEvent,type='new_revision')
+ return not e or (self.time, self.pk) >= (e.time, e.pk)
def get_dochistory(self):
- return DocHistory.objects.filter(time__lte=self.time,doc__name=self.doc.name).order_by('-time').first()
+ return DocHistory.objects.filter(time__lte=self.time,doc__name=self.doc.name).order_by('-time', '-pk').first()
def __unicode__(self):
return u"%s %s by %s at %s" % (self.doc.name, self.get_type_display().lower(), self.by.plain_name(), self.time)
diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py
index 382f95447..5017bbf53 100644
--- a/ietf/doc/tests.py
+++ b/ietf/doc/tests.py
@@ -18,8 +18,7 @@ from django.conf import settings
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocAlias, DocRelationshipName, RelatedDocument, State,
- DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, NewRevisionDocEvent,
- save_document_in_history )
+ DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, NewRevisionDocEvent )
from ietf.group.models import Group
from ietf.meeting.models import Meeting, Session, SessionPresentation
from ietf.name.models import SessionStatusName
@@ -111,10 +110,11 @@ class SearchTests(TestCase):
def test_search_for_name(self):
draft = make_test_data()
- save_document_in_history(draft)
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
+
prev_rev = draft.rev
draft.rev = "%02d" % (int(prev_rev) + 1)
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
# exact match
r = self.client.get(urlreverse("doc_search_for_name", kwargs=dict(name=draft.name)))
@@ -513,9 +513,8 @@ Man Expires September 22, 2015 [Page 3]
# draft published as RFC
draft.set_state(State.objects.get(type="draft", slug="rfc"))
draft.std_level_id = "bcp"
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="published_rfc", by=Person.objects.get(name="(System)"))])
- DocEvent.objects.create(doc=draft, type="published_rfc", by=Person.objects.get(name="(System)"))
rfc_alias = DocAlias.objects.create(name="rfc123456", document=draft)
bcp_alias = DocAlias.objects.create(name="bcp123456", document=draft)
@@ -561,9 +560,10 @@ Man Expires September 22, 2015 [Page 3]
]:
doc = Document.objects.get(name=docname)
# give it some history
- save_document_in_history(doc)
- doc.rev="01"
- doc.save()
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
+
+ doc.rev = "01"
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
@@ -620,7 +620,8 @@ class DocTestCase(TestCase):
doc = make_test_data()
ballot = doc.active_ballot()
- save_document_in_history(doc)
+ # make sure we have some history
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
pos = BallotPositionDocEvent.objects.create(
doc=doc,
@@ -648,9 +649,8 @@ class DocTestCase(TestCase):
# Now simulate a new revision and make sure positions on older revisions are marked as such
oldrev = doc.rev
e = NewRevisionDocEvent.objects.create(doc=doc,rev='%02d'%(int(doc.rev)+1),type='new_revision',by=Person.objects.get(name="(System)"))
- save_document_in_history(doc)
doc.rev = e.rev
- doc.save()
+ doc.save_with_history([e])
r = self.client.get(urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
self.assertTrue( '(%s for -%s)' % (pos.comment_time.strftime('%Y-%m-%d'), oldrev) in r.content)
diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py
index 1589df22c..cb14c3c2e 100644
--- a/ietf/doc/tests_ballot.py
+++ b/ietf/doc/tests_ballot.py
@@ -125,7 +125,7 @@ class EditPositionTests(TestCase):
def test_send_ballot_comment(self):
draft = make_test_data()
draft.notify = "somebody@example.com"
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
ad = Person.objects.get(name="Aread Irector")
@@ -217,8 +217,8 @@ class BallotWriteupsTests(TestCase):
regenerate_last_call_text="1"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- draft = Document.objects.get(name=draft.name)
- self.assertTrue("Subject: Last Call" in draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text)
+ text = q("[name=last_call_text]").text()
+ self.assertTrue("Subject: Last Call" in text)
def test_request_last_call(self):
@@ -229,12 +229,14 @@ class BallotWriteupsTests(TestCase):
# give us an announcement to send
r = self.client.post(url, dict(regenerate_last_call_text="1"))
self.assertEqual(r.status_code, 200)
-
+ q = PyQuery(r.content)
+ text = q("[name=last_call_text]").text()
+
mailbox_before = len(outbox)
# send
r = self.client.post(url, dict(
- last_call_text=draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text,
+ last_call_text=text,
send_last_call_request="1"))
draft = Document.objects.get(name=draft.name)
self.assertEqual(draft.get_state_slug("draft-iesg"), "lc-req")
@@ -318,7 +320,6 @@ class BallotWriteupsTests(TestCase):
# test regenerate
r = self.client.post(url, dict(regenerate_approval_text="1"))
self.assertEqual(r.status_code, 200)
- draft = Document.objects.get(name=draft.name)
self.assertTrue("Subject: Protocol Action" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text)
# test regenerate when it's a disapprove
@@ -326,18 +327,16 @@ class BallotWriteupsTests(TestCase):
r = self.client.post(url, dict(regenerate_approval_text="1"))
self.assertEqual(r.status_code, 200)
- draft = Document.objects.get(name=draft.name)
self.assertTrue("NOT be published" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text)
# test regenerate when it's a conflict review
draft.group = Group.objects.get(type="individ")
draft.stream_id = "irtf"
- draft.save()
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva"))
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.post(url, dict(regenerate_approval_text="1"))
self.assertEqual(r.status_code, 200)
- draft = Document.objects.get(name=draft.name)
self.assertTrue("Subject: Results of IETF-conflict review" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text)
@@ -519,7 +518,6 @@ class DeferUndeferTestCase(TestCase):
defer_states = dict(draft=['draft-iesg','defer'],conflrev=['conflrev','defer'],statchg=['statchg','defer'])
if doc.type_id in defer_states:
doc.set_state(State.objects.get(used=True, type=defer_states[doc.type_id][0],slug=defer_states[doc.type_id][1]))
- doc.save()
# get
r = self.client.get(url)
diff --git a/ietf/doc/tests_charter.py b/ietf/doc/tests_charter.py
index 2ea147f36..e429c6d8a 100644
--- a/ietf/doc/tests_charter.py
+++ b/ietf/doc/tests_charter.py
@@ -9,7 +9,8 @@ from django.core.urlresolvers import reverse as urlreverse
from ietf.doc.models import ( Document, State, BallotDocEvent, BallotType, NewRevisionDocEvent,
TelechatDocEvent, WriteupDocEvent )
-from ietf.doc.utils_charter import next_revision, default_review_text, default_action_text
+from ietf.doc.utils_charter import ( next_revision, default_review_text, default_action_text,
+ charter_name_for_group )
from ietf.group.models import Group, GroupMilestone
from ietf.iesg.models import TelechatDate
from ietf.person.models import Person
@@ -27,6 +28,10 @@ class EditCharterTests(TestCase):
def tearDown(self):
shutil.rmtree(self.charter_dir)
+ def write_charter_file(self, charter):
+ with open(os.path.join(self.charter_dir, "%s-%s.txt" % (charter.canonical_name(), charter.rev)), "w") as f:
+ f.write("This is a charter.")
+
def test_startstop_process(self):
make_test_data()
@@ -43,6 +48,8 @@ class EditCharterTests(TestCase):
self.assertEqual(r.status_code, 200)
# post
+ self.write_charter_file(charter)
+
r = self.client.post(url, dict(message="test message"))
self.assertEqual(r.status_code, 302)
if option == "abandon":
@@ -273,6 +280,40 @@ class EditCharterTests(TestCase):
self.assertEqual(f.read(),
"Windows line\nMac line\nUnix line\n" + utf_8_snippet)
+ def test_submit_initial_charter(self):
+ make_test_data()
+
+ group = Group.objects.get(acronym="mars")
+ # get rid of existing charter
+ charter = group.charter
+ group.charter = None
+ group.save()
+ charter.delete()
+ charter = None
+
+ url = urlreverse('charter_submit', kwargs=dict(name=charter_name_for_group(group)))
+ login_testing_unauthorized(self, "secretary", url)
+
+ # normal get
+ r = self.client.get(url)
+ self.assertEqual(r.status_code, 200)
+ q = PyQuery(r.content)
+ self.assertEqual(len(q('form input[name=txt]')), 1)
+
+ # create charter
+ test_file = StringIO("Simple test")
+ test_file.name = "unnamed"
+
+ r = self.client.post(url, dict(txt=test_file))
+ self.assertEqual(r.status_code, 302)
+
+ charter = Document.objects.get(name="charter-ietf-%s" % group.acronym)
+ self.assertEqual(charter.rev, "00-00")
+ self.assertTrue("new_revision" in charter.latest_event().type)
+
+ group = Group.objects.get(pk=group.pk)
+ self.assertEqual(group.charter, charter)
+
def test_edit_review_announcement_text(self):
draft = make_test_data()
charter = draft.group.charter
@@ -385,7 +426,8 @@ class EditCharterTests(TestCase):
url = urlreverse('ietf.doc.views_charter.ballot_writeupnotes', kwargs=dict(name=charter.name))
login_testing_unauthorized(self, "secretary", url)
- default_action_text(draft.group, charter, by)
+ e = default_action_text(draft.group, charter, by)
+ e.save()
# normal get
r = self.client.get(url)
@@ -417,8 +459,7 @@ class EditCharterTests(TestCase):
url = urlreverse('charter_approve', kwargs=dict(name=charter.name))
login_testing_unauthorized(self, "secretary", url)
- with open(os.path.join(self.charter_dir, "%s-%s.txt" % (charter.canonical_name(), charter.rev)), "w") as f:
- f.write("This is a charter.")
+ self.write_charter_file(charter)
p = Person.objects.get(name="Aread Irector")
diff --git a/ietf/doc/tests_conflict_review.py b/ietf/doc/tests_conflict_review.py
index 34edd19d8..f3e2cd0cf 100644
--- a/ietf/doc/tests_conflict_review.py
+++ b/ietf/doc/tests_conflict_review.py
@@ -32,8 +32,8 @@ class ConflictReviewTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 404)
- doc.stream=StreamName.objects.get(slug='ise')
- doc.save()
+ doc.stream = StreamName.objects.get(slug='ise')
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
# normal get should succeed and get a reasonable form
r = self.client.get(url)
@@ -89,14 +89,14 @@ class ConflictReviewTests(TestCase):
# can't start conflict reviews on documents in some other stream
- doc.stream=StreamName.objects.get(slug='irtf')
- doc.save()
+ doc.stream = StreamName.objects.get(slug='irtf')
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.get(url)
self.assertEquals(r.status_code, 404)
# successful get
- doc.stream=StreamName.objects.get(slug='ise')
- doc.save()
+ doc.stream = StreamName.objects.get(slug='ise')
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
@@ -269,7 +269,6 @@ class ConflictReviewTests(TestCase):
# Some additional setup
create_ballot_if_not_open(doc,Person.objects.get(name="Sec Retary"),"conflrev")
doc.set_state(State.objects.get(used=True, slug=approve_type+'-pend',type='conflrev'))
- doc.save()
# get
r = self.client.get(url)
diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py
index 12094d341..f4f248123 100644
--- a/ietf/doc/tests_draft.py
+++ b/ietf/doc/tests_draft.py
@@ -370,9 +370,9 @@ class EditInfoTests(TestCase):
self.assertEqual(draft.ad, ad)
self.assertEqual(draft.note, "This is a note")
self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
- self.assertEqual(draft.docevent_set.count(), events_before + 3)
+ self.assertEqual(draft.docevent_set.count(), events_before + 4)
events = list(draft.docevent_set.order_by('time', 'id'))
- self.assertEqual(events[-3].type, "started_iesg_process")
+ self.assertEqual(events[-4].type, "started_iesg_process")
self.assertEqual(len(outbox), mailbox_before+1)
self.assertTrue('IESG processing' in outbox[-1]['Subject'])
self.assertTrue('draft-ietf-mars-test2@' in outbox[-1]['To'])
@@ -381,7 +381,7 @@ class EditInfoTests(TestCase):
draft.unset_state('draft-iesg')
draft.set_state(State.objects.get(type='draft-stream-ietf',slug='writeupw'))
draft.stream = StreamName.objects.get(slug='ietf')
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.post(url,
dict(intended_std_level=str(draft.intended_std_level_id),
ad=ad.pk,
@@ -537,7 +537,7 @@ class ExpireIDsTests(TestCase):
# hack into expirable state
draft.unset_state("draft-iesg")
draft.expires = datetime.datetime.now() + datetime.timedelta(days=10)
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
self.assertEqual(len(list(get_soon_to_expire_drafts(14))), 1)
@@ -561,7 +561,7 @@ class ExpireIDsTests(TestCase):
# hack into expirable state
draft.unset_state("draft-iesg")
draft.expires = datetime.datetime.now()
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
self.assertEqual(len(list(get_expired_drafts())), 1)
@@ -624,7 +624,6 @@ class ExpireIDsTests(TestCase):
# RFC draft
draft.set_state(State.objects.get(used=True, type="draft", slug="rfc"))
- draft.save()
txt = "%s-%s.txt" % (draft.name, draft.rev)
self.write_draft_file(txt, 5000)
@@ -642,7 +641,7 @@ class ExpireIDsTests(TestCase):
# expire draft
draft.set_state(State.objects.get(used=True, type="draft", slug="expired"))
draft.expires = datetime.datetime.now() - datetime.timedelta(days=1)
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
e = DocEvent()
e.doc = draft
@@ -862,7 +861,7 @@ class IndividualInfoFormsTests(TestCase):
def test_doc_change_shepherd(self):
self.doc.shepherd = None
- self.doc.save()
+ self.doc.save_with_history([DocEvent.objects.create(doc=self.doc, type="changed_shepherd", by=Person.objects.get(user__username="secretary"), desc="Test")])
url = urlreverse('doc_edit_shepherd',kwargs=dict(name=self.docname))
@@ -914,19 +913,19 @@ class IndividualInfoFormsTests(TestCase):
def test_doc_change_shepherd_email(self):
self.doc.shepherd = None
- self.doc.save()
+ self.doc.save_with_history([DocEvent.objects.create(doc=self.doc, type="changed_shepherd", by=Person.objects.get(user__username="secretary"), desc="Test")])
url = urlreverse('doc_change_shepherd_email',kwargs=dict(name=self.docname))
r = self.client.get(url)
self.assertEqual(r.status_code, 404)
self.doc.shepherd = Email.objects.get(person__user__username="ad1")
- self.doc.save()
+ self.doc.save_with_history([DocEvent.objects.create(doc=self.doc, type="changed_shepherd", by=Person.objects.get(user__username="secretary"), desc="Test")])
login_testing_unauthorized(self, "plain", url)
self.doc.shepherd = Email.objects.get(person__user__username="plain")
- self.doc.save()
+ self.doc.save_with_history([DocEvent.objects.create(doc=self.doc, type="changed_shepherd", by=Person.objects.get(user__username="secretary"), desc="Test")])
new_email = Email.objects.create(address="anotheremail@example.com", person=self.doc.shepherd.person)
@@ -961,7 +960,7 @@ class IndividualInfoFormsTests(TestCase):
# Try again when no longer a shepherd.
self.doc.shepherd = None
- self.doc.save()
+ self.doc.save_with_history([DocEvent.objects.create(doc=self.doc, type="changed_shepherd", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
@@ -1070,7 +1069,7 @@ class RequestPublicationTests(TestCase):
draft.stream = StreamName.objects.get(slug="iab")
draft.group = Group.objects.get(acronym="iab")
draft.intended_std_level = IntendedStdLevelName.objects.get(slug="inf")
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
draft.set_state(State.objects.get(used=True, type="draft-stream-iab", slug="approved"))
url = urlreverse('doc_request_publication', kwargs=dict(name=draft.name))
@@ -1110,8 +1109,8 @@ class AdoptDraftTests(TestCase):
draft = make_test_data()
draft.stream = None
draft.group = Group.objects.get(type="individ")
- draft.save()
draft.unset_state("draft-stream-ietf")
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
url = urlreverse('doc_adopt_draft', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "marschairman", url)
diff --git a/ietf/doc/tests_status_change.py b/ietf/doc/tests_status_change.py
index f9a564549..491779e34 100644
--- a/ietf/doc/tests_status_change.py
+++ b/ietf/doc/tests_status_change.py
@@ -23,7 +23,7 @@ from ietf.utils.test_utils import login_testing_unauthorized
class StatusChangeTests(TestCase):
def test_start_review(self):
- url = urlreverse('start_rfc_status_change',kwargs=dict(name=""))
+ url = urlreverse('start_rfc_status_change')
login_testing_unauthorized(self, "secretary", url)
# normal get should succeed and get a reasonable form
@@ -104,7 +104,7 @@ class StatusChangeTests(TestCase):
# successful change to Last Call Requested
messages_before = len(outbox)
doc.ad = Person.objects.get(user__username='ad')
- doc.save()
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
lc_req_pk = str(State.objects.get(slug='lc-req',type__slug='statchg').pk)
r = self.client.post(url,dict(new_state=lc_req_pk))
self.assertEquals(r.status_code, 200)
@@ -251,8 +251,8 @@ class StatusChangeTests(TestCase):
# additional setup
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
- doc.ad = Person.objects.get(name='Ad No2')
- doc.save()
+ doc.ad = Person.objects.get(name='Ad No2')
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
# get
r = self.client.get(url)
@@ -295,9 +295,8 @@ class StatusChangeTests(TestCase):
# Some additional setup
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
- create_ballot_if_not_open(doc,Person.objects.get(name="Sec Retary"),"statchg")
+ create_ballot_if_not_open(doc,Person.objects.get(user__username="secretary"),"statchg")
doc.set_state(State.objects.get(slug='appr-pend',type='statchg'))
- doc.save()
# get
r = self.client.get(url)
diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py
index 49cbc3d38..aab6d594d 100644
--- a/ietf/doc/urls.py
+++ b/ietf/doc/urls.py
@@ -32,6 +32,7 @@
from django.conf.urls import patterns, url, include
from django.views.generic import RedirectView
+from django.conf import settings
from ietf.doc import views_search, views_draft, views_ballot
from ietf.doc import views_status_change
@@ -45,7 +46,7 @@ urlpatterns = patterns('',
url(r'^ad/(?P[A-Za-z0-9.-]+)/$', views_search.docs_for_ad, name="docs_for_ad"),
(r'^ad2/(?P[A-Za-z0-9.-]+)/$', RedirectView.as_view(url='/doc/ad/%(name)s/', permanent=True)),
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'^start-rfc-status-change/(?:%(name)s/)?$' % settings.URL_REGEXPS, 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'^email-aliases/$', views_doc.email_aliases),
@@ -53,62 +54,62 @@ urlpatterns = patterns('',
url(r'^active/$', views_search.index_active_drafts, name="index_active_drafts"),
url(r'^select2search/(?P(document|docalias))/(?Pdraft)/$', views_search.ajax_select2_search_docs, name="ajax_select2_search_docs"),
- 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"),
- url(r'^(?P[A-Za-z0-9._+-]+)/writeup/$', views_doc.document_writeup, name="doc_writeup"),
- url(r'^(?P[A-Za-z0-9._+-]+)/email/$', views_doc.document_email, name="doc_email"),
- url(r'^(?P[A-Za-z0-9._+-]+)/shepherdwriteup/$', views_doc.document_shepherd_writeup, name="doc_shepherd_writeup"),
- url(r'^(?P[A-Za-z0-9._+-]+)/references/$', views_doc.document_references, name="doc_references"),
- url(r'^(?P[A-Za-z0-9._+-]+)/referencedby/$', views_doc.document_referenced_by, name="doc_referenced_by"),
- url(r'^(?P[A-Za-z0-9._+-]+)/ballot/(?P[0-9]+)/position/$', views_ballot.edit_position),
- url(r'^(?P[A-Za-z0-9._+-]+)/ballot/(?P[0-9]+)/emailposition/$', views_ballot.send_ballot_comment, name='doc_send_ballot_comment'),
- 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/(?P[0-9]+)/$', views_doc.ballot_popup),
+ url(r'^%(name)s/(?:%(rev)s/)?$' % settings.URL_REGEXPS, views_doc.document_main, name="doc_view"),
+ url(r'^%(name)s/history/$' % settings.URL_REGEXPS, views_doc.document_history, name="doc_history"),
+ url(r'^%(name)s/writeup/$' % settings.URL_REGEXPS, views_doc.document_writeup, name="doc_writeup"),
+ url(r'^%(name)s/email/$' % settings.URL_REGEXPS, views_doc.document_email, name="doc_email"),
+ url(r'^%(name)s/shepherdwriteup/$' % settings.URL_REGEXPS, views_doc.document_shepherd_writeup, name="doc_shepherd_writeup"),
+ url(r'^%(name)s/references/$' % settings.URL_REGEXPS, views_doc.document_references, name="doc_references"),
+ url(r'^%(name)s/referencedby/$' % settings.URL_REGEXPS, views_doc.document_referenced_by, name="doc_referenced_by"),
+ url(r'^%(name)s/ballot/(?P[0-9]+)/position/$' % settings.URL_REGEXPS, views_ballot.edit_position),
+ url(r'^%(name)s/ballot/(?P[0-9]+)/emailposition/$' % settings.URL_REGEXPS, views_ballot.send_ballot_comment, name='doc_send_ballot_comment'),
+ url(r'^%(name)s/ballot/(?P[0-9]+)/$' % settings.URL_REGEXPS, views_doc.document_ballot, name="doc_ballot"),
+ url(r'^%(name)s/ballot/$' % settings.URL_REGEXPS, views_doc.document_ballot, name="doc_ballot"),
+ (r'^%(name)s/doc.json$' % settings.URL_REGEXPS, views_doc.document_json),
+ (r'^%(name)s/ballotpopup/(?P[0-9]+)/$' % settings.URL_REGEXPS, views_doc.ballot_popup),
- url(r'^(?P[A-Za-z0-9._+-]+)/email-aliases/$', RedirectView.as_view(pattern_name='doc_email', permanent=False),name='doc_specific_email_aliases'),
+ url(r'^%(name)s/email-aliases/$' % settings.URL_REGEXPS, RedirectView.as_view(pattern_name='doc_email', permanent=False),name='doc_specific_email_aliases'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/$', views_draft.change_state, name='doc_change_state'), # IESG state
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/(?Piana-action|iana-review)/$', views_draft.change_iana_state, name='doc_change_iana_state'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/info/$', views_draft.edit_info, name='doc_edit_info'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/requestresurrect/$', views_draft.request_resurrect, name='doc_request_resurrect'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/submit-to-iesg/$', views_draft.to_iesg, name='doc_to_iesg'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/resurrect/$', views_draft.resurrect, name='doc_resurrect'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/addcomment/$', views_doc.add_comment, name='doc_add_comment'),
+ url(r'^%(name)s/edit/state/$' % settings.URL_REGEXPS, views_draft.change_state, name='doc_change_state'), # IESG state
+ url(r'^%(name)s/edit/state/(?Piana-action|iana-review)/$' % settings.URL_REGEXPS, views_draft.change_iana_state, name='doc_change_iana_state'),
+ url(r'^%(name)s/edit/info/$' % settings.URL_REGEXPS, views_draft.edit_info, name='doc_edit_info'),
+ url(r'^%(name)s/edit/requestresurrect/$' % settings.URL_REGEXPS, views_draft.request_resurrect, name='doc_request_resurrect'),
+ url(r'^%(name)s/edit/submit-to-iesg/$' % settings.URL_REGEXPS, views_draft.to_iesg, name='doc_to_iesg'),
+ url(r'^%(name)s/edit/resurrect/$' % settings.URL_REGEXPS, views_draft.resurrect, name='doc_resurrect'),
+ url(r'^%(name)s/edit/addcomment/$' % settings.URL_REGEXPS, views_doc.add_comment, name='doc_add_comment'),
url(r'^ajax/internet_draft/?$', views_draft.doc_ajax_internet_draft, name="doc_ajax_internet_draft"),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/stream/$', views_draft.change_stream, name='doc_change_stream'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/replaces/$', views_draft.replaces, name='doc_change_replaces'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/notify/$', views_doc.edit_notify, name='doc_change_notify'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/suggested-replaces/$', views_draft.review_possibly_replaces, name='doc_review_possibly_replaces'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/status/$', views_draft.change_intention, name='doc_change_intended_status'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/telechat/$', views_doc.telechat_date, name='doc_change_telechat_date'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/iesgnote/$', views_draft.edit_iesg_note, name='doc_change_iesg_note'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/ad/$', views_draft.edit_ad, name='doc_change_ad'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/consensus/$', views_draft.edit_consensus, name='doc_edit_consensus'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/shepherd/$', views_draft.edit_shepherd, name='doc_edit_shepherd'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/shepherdemail/$', views_draft.change_shepherd_email, name='doc_change_shepherd_email'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/shepherdwriteup/$', views_draft.edit_shepherd_writeup, name='doc_edit_shepherd_writeup'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/requestpublication/$', views_draft.request_publication, name='doc_request_publication'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/adopt/$', views_draft.adopt_draft, name='doc_adopt_draft'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/(?Pdraft-stream-[a-z]+)/$', views_draft.change_stream_state, name='doc_change_stream_state'),
+ url(r'^%(name)s/edit/stream/$' % settings.URL_REGEXPS, views_draft.change_stream, name='doc_change_stream'),
+ url(r'^%(name)s/edit/replaces/$' % settings.URL_REGEXPS, views_draft.replaces, name='doc_change_replaces'),
+ url(r'^%(name)s/edit/notify/$' % settings.URL_REGEXPS, views_doc.edit_notify, name='doc_change_notify'),
+ url(r'^%(name)s/edit/suggested-replaces/$' % settings.URL_REGEXPS, views_draft.review_possibly_replaces, name='doc_review_possibly_replaces'),
+ url(r'^%(name)s/edit/status/$' % settings.URL_REGEXPS, views_draft.change_intention, name='doc_change_intended_status'),
+ url(r'^%(name)s/edit/telechat/$' % settings.URL_REGEXPS, views_doc.telechat_date, name='doc_change_telechat_date'),
+ url(r'^%(name)s/edit/iesgnote/$' % settings.URL_REGEXPS, views_draft.edit_iesg_note, name='doc_change_iesg_note'),
+ url(r'^%(name)s/edit/ad/$' % settings.URL_REGEXPS, views_draft.edit_ad, name='doc_change_ad'),
+ url(r'^%(name)s/edit/consensus/$' % settings.URL_REGEXPS, views_draft.edit_consensus, name='doc_edit_consensus'),
+ url(r'^%(name)s/edit/shepherd/$' % settings.URL_REGEXPS, views_draft.edit_shepherd, name='doc_edit_shepherd'),
+ url(r'^%(name)s/edit/shepherdemail/$' % settings.URL_REGEXPS, views_draft.change_shepherd_email, name='doc_change_shepherd_email'),
+ url(r'^%(name)s/edit/shepherdwriteup/$' % settings.URL_REGEXPS, views_draft.edit_shepherd_writeup, name='doc_edit_shepherd_writeup'),
+ url(r'^%(name)s/edit/requestpublication/$' % settings.URL_REGEXPS, views_draft.request_publication, name='doc_request_publication'),
+ url(r'^%(name)s/edit/adopt/$' % settings.URL_REGEXPS, views_draft.adopt_draft, name='doc_adopt_draft'),
+ url(r'^%(name)s/edit/state/(?Pdraft-stream-[a-z]+)/$' % settings.URL_REGEXPS, views_draft.change_stream_state, name='doc_change_stream_state'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/clearballot/$', views_ballot.clear_ballot, name='doc_clear_ballot'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/deferballot/$', views_ballot.defer_ballot, name='doc_defer_ballot'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/undeferballot/$', views_ballot.undefer_ballot, name='doc_undefer_ballot'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/lastcalltext/$', views_ballot.lastcalltext, name='doc_ballot_lastcall'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/ballotwriteupnotes/$', views_ballot.ballot_writeupnotes, name='doc_ballot_writeupnotes'),
- url(r'^(?P[A-Za-z0-9._+-]+)/edit/approvaltext/$', views_ballot.ballot_approvaltext, name='doc_ballot_approvaltext'),
- 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'^%(name)s/edit/clearballot/$' % settings.URL_REGEXPS, views_ballot.clear_ballot, name='doc_clear_ballot'),
+ url(r'^%(name)s/edit/deferballot/$' % settings.URL_REGEXPS, views_ballot.defer_ballot, name='doc_defer_ballot'),
+ url(r'^%(name)s/edit/undeferballot/$' % settings.URL_REGEXPS, views_ballot.undefer_ballot, name='doc_undefer_ballot'),
+ url(r'^%(name)s/edit/lastcalltext/$' % settings.URL_REGEXPS, views_ballot.lastcalltext, name='doc_ballot_lastcall'),
+ url(r'^%(name)s/edit/ballotwriteupnotes/$' % settings.URL_REGEXPS, views_ballot.ballot_writeupnotes, name='doc_ballot_writeupnotes'),
+ url(r'^%(name)s/edit/approvaltext/$' % settings.URL_REGEXPS, views_ballot.ballot_approvaltext, name='doc_ballot_approvaltext'),
+ url(r'^%(name)s/edit/approveballot/$' % settings.URL_REGEXPS, views_ballot.approve_ballot, name='doc_approve_ballot'),
+ url(r'^%(name)s/edit/makelastcall/$' % settings.URL_REGEXPS, 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"),
url(r'^help/relationships/$', 'ietf.doc.views_help.relationship_help', name="relationship_help"),
url(r'^help/relationships/(?P\w+)/$', 'ietf.doc.views_help.relationship_help', name="relationship_subset_help"),
(r'^(?Pcharter-[A-Za-z0-9._+-]+)/', include('ietf.doc.urls_charter')),
- (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')),
- (r'^(?P[A-Za-z0-9._+-]+)/material/', include('ietf.doc.urls_material')),
+ (r'^%(name)s/conflict-review/' % settings.URL_REGEXPS, include('ietf.doc.urls_conflict_review')),
+ (r'^%(name)s/status-change/' % settings.URL_REGEXPS, include('ietf.doc.urls_status_change')),
+ (r'^%(name)s/material/' % settings.URL_REGEXPS, include('ietf.doc.urls_material')),
)
diff --git a/ietf/doc/urls_charter.py b/ietf/doc/urls_charter.py
index 5bc52770f..4b708c52b 100644
--- a/ietf/doc/urls_charter.py
+++ b/ietf/doc/urls_charter.py
@@ -1,6 +1,7 @@
# Copyright The IETF Trust 2011, All Rights Reserved
from django.conf.urls import patterns, url
+from django.conf import settings
urlpatterns = patterns('',
url(r'^state/$', "ietf.doc.views_charter.change_state", name='charter_change_state'),
@@ -14,5 +15,5 @@ urlpatterns = patterns('',
url(r'^ballotwriteupnotes/$', "ietf.doc.views_charter.ballot_writeupnotes"),
url(r'^approve/$', "ietf.doc.views_charter.approve", name='charter_approve'),
url(r'^submit/(?:(?P
";
+ var ajax_load = '
Generating Proceedings. This may take a few minutes.';
var url = window.location.pathname;
var parts = url.split("/");
var loadUrl = "/secr/proceedings/ajax/generate-proceedings/" + parts[3] + "/";
diff --git a/ietf/secr/templates/proceedings/wait.html b/ietf/secr/templates/proceedings/wait.html
index 9440221d8..8fa4b49cd 100644
--- a/ietf/secr/templates/proceedings/wait.html
+++ b/ietf/secr/templates/proceedings/wait.html
@@ -16,7 +16,7 @@
Proceedings
{{ message }}
-
+
{% endblock %}
diff --git a/ietf/secr/templates/roles/main.html b/ietf/secr/templates/roles/main.html
index dc5cc636d..b61d8d5a6 100755
--- a/ietf/secr/templates/roles/main.html
+++ b/ietf/secr/templates/roles/main.html
@@ -16,7 +16,7 @@
$('#id_group').change(function(){
var loadUrl = "/secr/roles/ajax/get-roles/" + $(this).val() + "/";
- var ajax_load = "";
+ var ajax_load = '';
var text = $(this).val();
$("#id_group_acronym").val(text);
$("#roles-list").html(ajax_load).load(loadUrl);
diff --git a/ietf/settings.py b/ietf/settings.py
index 1f10b97e6..e96215ab4 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -365,6 +365,13 @@ MAX_WG_DELEGATES = 3
DATE_FORMAT = "Y-m-d"
DATETIME_FORMAT = "Y-m-d H:i T"
+URL_REGEXPS = {
+ "acronym": r"(?P[a-zA-Z0-9-]+)",
+ "date": r"(?P\d{4}-\d{2}-\d{2})",
+ "name": r"(?P[A-Za-z0-9._\+\-]+)",
+ "rev": r"(?P[0-9-]+)",
+}
+
# Override this in settings_local.py if needed
# *_PATH variables ends with a slash/ .
DOCUMENT_PATH_PATTERN = '/a/www/ietf-ftp/{doc.type_id}/'
diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py
index 939d1b185..0a3461335 100644
--- a/ietf/submit/utils.py
+++ b/ietf/submit/utils.py
@@ -5,7 +5,7 @@ import datetime
from django.conf import settings
from ietf.doc.models import Document, State, DocAlias, DocEvent, DocumentAuthor
-from ietf.doc.models import NewRevisionDocEvent, save_document_in_history
+from ietf.doc.models import NewRevisionDocEvent
from ietf.doc.models import RelatedDocument, DocRelationshipName
from ietf.doc.utils import add_state_change_event, rebuild_reference_relations
from ietf.doc.utils import set_replaces_for_document
@@ -126,19 +126,23 @@ def create_submission_event(request, submission, desc):
def post_submission(request, submission):
+ # find out who did it
system = Person.objects.get(name="(System)")
+ submitter_parsed = submission.submitter_parsed()
+ if submitter_parsed["name"] and submitter_parsed["email"]:
+ submitter = ensure_person_email_info_exists(submitter_parsed["name"], submitter_parsed["email"]).person
+ else:
+ submitter = system
+ # update draft attributes
try:
draft = Document.objects.get(name=submission.name)
- save_document_in_history(draft)
except Document.DoesNotExist:
- draft = Document(name=submission.name)
- draft.intended_std_level = None
+ draft = Document.objects.create(name=submission.name, type_id="draft")
prev_rev = draft.rev
draft.type_id = "draft"
- draft.time = datetime.datetime.now()
draft.title = submission.title
group = submission.group or Group.objects.get(type="individ")
if not (group.type_id == "individ" and draft.group and draft.group.type_id == "area"):
@@ -163,16 +167,23 @@ def post_submission(request, submission):
draft.stream = StreamName.objects.get(slug=stream_slug)
draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
- draft.save()
- submitter_parsed = submission.submitter_parsed()
- if submitter_parsed["name"] and submitter_parsed["email"]:
- submitter = ensure_person_email_info_exists(submitter_parsed["name"], submitter_parsed["email"]).person
- else:
- submitter = system
+ events = []
+
+ # new revision event
+ e = NewRevisionDocEvent.objects.create(
+ type="new_revision",
+ doc=draft,
+ rev=draft.rev,
+ by=submitter,
+ desc="New version available: %s-%s.txt" % (draft.name, draft.rev),
+ )
+ events.append(e)
+
+ # update related objects
+ DocAlias.objects.get_or_create(name=submission.name, document=draft)
draft.set_state(State.objects.get(used=True, type="draft", slug="active"))
- DocAlias.objects.get_or_create(name=submission.name, document=draft)
update_authors(draft, submission)
@@ -180,29 +191,15 @@ def post_submission(request, submission):
if trouble:
log('Rebuild_reference_relations trouble: %s'%trouble)
- # new revision event
- e = NewRevisionDocEvent(type="new_revision", doc=draft, rev=draft.rev)
- e.time = draft.time #submission.submission_date
- e.by = submitter
- e.desc = "New version available: %s-%s.txt" % (draft.name, draft.rev)
- e.save()
-
- if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00":
- # automatically set state "WG Document"
- draft.set_state(State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
-
+ # automatic state changes
if draft.get_state_slug("draft-iana-review") in ("ok-act", "ok-noact", "not-ok"):
prev_state = draft.get_state("draft-iana-review")
next_state = State.objects.get(used=True, type="draft-iana-review", slug="changed")
draft.set_state(next_state)
- add_state_change_event(draft, submitter, prev_state, next_state)
+ e = add_state_change_event(draft, submitter, prev_state, next_state)
+ if e:
+ events.append(e)
- # clean up old files
- if prev_rev != draft.rev:
- from ietf.doc.expire import move_draft_files_to_archive
- move_draft_files_to_archive(draft, prev_rev)
-
- # automatic state changes
state_change_msg = ""
if not was_rfc and draft.tags.filter(slug="need-rev"):
@@ -213,9 +210,22 @@ def post_submission(request, submission):
e.desc = "Sub state has been changed to AD Followup from Revised ID Needed"
e.by = system
e.save()
+ events.append(e)
state_change_msg = e.desc
+ if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00":
+ # automatically set state "WG Document"
+ draft.set_state(State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
+
+ # save history now that we're done with changes to the draft itself
+ draft.save_with_history(events)
+
+ # clean up old files
+ if prev_rev != draft.rev:
+ from ietf.doc.expire import move_draft_files_to_archive
+ move_draft_files_to_archive(draft, prev_rev)
+
move_files_to_repository(submission)
submission.state = DraftSubmissionStateName.objects.get(slug="posted")
diff --git a/ietf/sync/iana.py b/ietf/sync/iana.py
index b611aedb4..efcaea3ec 100644
--- a/ietf/sync/iana.py
+++ b/ietf/sync/iana.py
@@ -9,7 +9,7 @@ from django.utils.http import urlquote
from django.conf import settings
from ietf.doc.mails import email_state_changed
-from ietf.doc.models import Document, DocEvent, State, StateDocEvent, StateType, save_document_in_history
+from ietf.doc.models import Document, DocEvent, State, StateDocEvent, StateType
from ietf.doc.utils import add_state_change_event
from ietf.person.models import Person
from ietf.utils.timezone import local_timezone_to_utc, email_time_to_local_timezone, utc_to_local_timezone
@@ -201,15 +201,14 @@ def update_history_with_changes(changes, send_email=True):
added_events.append(e)
if not StateDocEvent.objects.filter(doc=doc, time__gt=timestamp, state_type=state_type):
- save_document_in_history(doc)
doc.set_state(state)
+ if e:
+ doc.save_with_history([e])
+
if send_email and (state != prev_state):
email_state_changed(None, doc, "IANA %s state changed to %s" % (kind, state.name),'doc_iana_state_changed')
- if doc.time < timestamp:
- doc.time = timestamp
- doc.save()
return added_events, warnings
diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 36aa929d9..6b41032e8 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -9,9 +9,9 @@ from xml.dom import pulldom, Node
from django.conf import settings
from ietf.doc.models import ( Document, DocAlias, State, StateType, DocEvent, DocRelationshipName,
- DocTagName, DocTypeName, RelatedDocument, save_document_in_history )
+ DocTagName, DocTypeName, RelatedDocument )
from ietf.doc.expire import move_draft_files_to_archive
-from ietf.doc.utils import add_state_change_event
+from ietf.doc.utils import add_state_change_event, prettify_std_name
from ietf.group.models import Group
from ietf.name.models import StdLevelName, StreamName
from ietf.person.models import Person
@@ -37,6 +37,8 @@ def fetch_queue_xml(url):
return urllib2.urlopen(url)
def parse_queue(response):
+ """Parse RFC Editor queue XML into a bunch of tuples + warnings."""
+
events = pulldom.parse(response)
drafts = []
warnings = []
@@ -108,6 +110,9 @@ def parse_queue(response):
return drafts, warnings
def update_drafts_from_queue(drafts):
+ """Given a list of parsed drafts from the RFC Editor queue, update the
+ documents in the database. Return those that were changed."""
+
tag_mapping = {
'IANA': DocTagName.objects.get(slug='iana'),
'REF': DocTagName.objects.get(slug='ref')
@@ -154,6 +159,7 @@ def update_drafts_from_queue(drafts):
prev_state = d.get_state("draft-rfceditor")
next_state = state_mapping[state]
+ events = []
# check if we've noted it's been received
if d.get_state_slug("draft-iesg") == "ann" and not prev_state and not d.latest_event(DocEvent, type="rfc_editor_received_announcement"):
@@ -166,15 +172,15 @@ def update_drafts_from_queue(drafts):
# change draft-iesg state to RFC Ed Queue
prev_iesg_state = State.objects.get(used=True, type="draft-iesg", slug="ann")
next_iesg_state = State.objects.get(used=True, type="draft-iesg", slug="rfcqueue")
- save_document_in_history(d)
+
d.set_state(next_iesg_state)
- add_state_change_event(d, system, prev_iesg_state, next_iesg_state)
+ e = add_state_change_event(d, system, prev_iesg_state, next_iesg_state)
+ if e:
+ events.append(e)
changed.add(name)
# check draft-rfceditor state
if prev_state != next_state:
- save_document_in_history(d)
-
d.set_state(next_state)
e = add_state_change_event(d, system, prev_state, next_state)
@@ -183,6 +189,9 @@ def update_drafts_from_queue(drafts):
e.desc = re.sub(r"(.*)", "\\1" % auth48, e.desc)
e.save()
+ if e:
+ events.append(e)
+
changed.add(name)
t = DocTagName.objects.filter(slug__in=tags)
@@ -190,6 +199,9 @@ def update_drafts_from_queue(drafts):
d.tags = t
changed.add(name)
+ if events:
+ d.save_with_history(events)
+
# remove tags and states for those not in the queue anymore
for d in Document.objects.exclude(docalias__name__in=names).filter(states__type="draft-rfceditor").distinct():
@@ -207,6 +219,8 @@ def fetch_index_xml(url):
return urllib2.urlopen(url)
def parse_index(response):
+ """Parse RFC Editor index XML into a bunch of tuples."""
+
def normalize_std_name(std_name):
# remove zero padding
prefix = std_name[:3]
@@ -297,6 +311,10 @@ def parse_index(response):
def update_docs_from_rfc_index(data, skip_older_than_date=None):
+ """Given parsed data from the RFC Editor index, update the documents
+ in the database. Yields a list of change descriptions for each
+ document, if any."""
+
std_level_mapping = {
"Standard": StdLevelName.objects.get(slug="std"),
"Internet Standard": StdLevelName.objects.get(slug="std"),
@@ -323,9 +341,6 @@ def update_docs_from_rfc_index(data, skip_older_than_date=None):
system = Person.objects.get(name="(System)")
- results = []
- new_rfcs = []
-
for rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract in data:
if skip_older_than_date and rfc_published_date < skip_older_than_date:
@@ -335,6 +350,9 @@ def update_docs_from_rfc_index(data, skip_older_than_date=None):
# we assume two things can happen: we get a new RFC, or an
# attribute has been updated at the RFC Editor (RFC Editor
# attributes take precedence over our local attributes)
+ events = []
+ changes = []
+ rfc_published = False
# make sure we got the document and alias
doc = None
@@ -350,42 +368,45 @@ def update_docs_from_rfc_index(data, skip_older_than_date=None):
pass
if not doc:
- results.append("created document %s" % name)
+ changes.append("created document %s" % prettify_std_name(name))
doc = Document.objects.create(name=name, type=DocTypeName.objects.get(slug="draft"))
# add alias
DocAlias.objects.get_or_create(name=name, document=doc)
- results.append("created alias %s to %s" % (name, doc.name))
+ changes.append("created alias %s" % prettify_std_name(name))
# check attributes
- changed_attributes = {}
- changed_states = []
- created_relations = []
- other_changes = False
if title != doc.title:
- changed_attributes["title"] = title
+ doc.title = title
+ changes.append("changed title to '%s'" % doc.title)
if abstract and abstract != doc.abstract:
- changed_attributes["abstract"] = abstract
+ doc.abstract = abstract
+ changes.append("changed abstract to '%s'" % doc.abstract)
if pages and int(pages) != doc.pages:
- changed_attributes["pages"] = int(pages)
+ doc.pages = int(pages)
+ changes.append("changed pages to %s" % doc.pages)
if std_level_mapping[current_status] != doc.std_level:
- changed_attributes["std_level"] = std_level_mapping[current_status]
+ doc.std_level = std_level_mapping[current_status]
+ changes.append("changed standardization level to %s" % doc.std_level)
if doc.get_state_slug() != "rfc":
- changed_states.append(State.objects.get(used=True, type="draft", slug="rfc"))
+ doc.set_state(State.objects.get(used=True, type="draft", slug="rfc"))
move_draft_files_to_archive(doc, doc.rev)
+ changes.append("changed state to %s" % doc.get_state())
if doc.stream != stream_mapping[stream]:
- changed_attributes["stream"] = stream_mapping[stream]
+ doc.stream = stream_mapping[stream]
+ changes.append("changed stream to %s" % doc.stream)
if not doc.group: # if we have no group assigned, check if RFC Editor has a suggestion
if wg:
- changed_attributes["group"] = Group.objects.get(acronym=wg)
+ doc.group = Group.objects.get(acronym=wg)
+ changes.append("set group to %s" % doc.group)
else:
- changed_attributes["group"] = Group.objects.get(type="individ")
+ doc.group = Group.objects.get(type="individ") # fallback for newly created doc
if not doc.latest_event(type="published_rfc"):
e = DocEvent(doc=doc, type="published_rfc")
@@ -404,15 +425,17 @@ def update_docs_from_rfc_index(data, skip_older_than_date=None):
e.by = system
e.desc = "RFC published"
e.save()
- other_changes = True
+ events.append(e)
- results.append("Added RFC published event: %s" % e.time.strftime("%Y-%m-%d"))
- new_rfcs.append(doc)
+ changes.append("added RFC published event at %s" % e.time.strftime("%Y-%m-%d"))
+ rfc_published = True
for t in ("draft-iesg", "draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"):
slug = doc.get_state_slug(t)
if slug and slug != "pub":
- changed_states.append(State.objects.get(used=True, type=t, slug="pub"))
+ new_state = State.objects.select_related("type").get(used=True, type=t, slug="pub")
+ doc.set_state(new_state)
+ changes.append("changed %s to %s" % (new_state.type.label, new_state))
def parse_relation_list(l):
res = []
@@ -431,46 +454,42 @@ def update_docs_from_rfc_index(data, skip_older_than_date=None):
for x in parse_relation_list(obsoletes):
if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_obsoletes):
- created_relations.append(RelatedDocument(source=doc, target=x, relationship=relationship_obsoletes))
+ r = RelatedDocument.objects.create(RelatedDocument(source=doc, target=x, relationship=relationship_obsoletes))
+ changes.append("created %s relation between %s and %s" % (r.relationship.name.lower(), prettify_std_name(r.source.name), prettify_std_name(r.target.name)))
for x in parse_relation_list(updates):
if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_updates):
- created_relations.append(RelatedDocument(source=doc, target=x, relationship=relationship_updates))
+ r = RelatedDocument.objects.create(source=doc, target=x, relationship=relationship_updates)
+ changes.append("created %s relation between %s and %s" % (r.relationship.name.lower(), prettify_std_name(r.source.name), prettify_std_name(r.target.name)))
if also:
for a in also:
a = a.lower()
if not DocAlias.objects.filter(name=a):
DocAlias.objects.create(name=a, document=doc)
- other_changes = True
- results.append("Created alias %s to %s" % (a, doc.name))
+ changes.append("created alias %s" % prettify_std_name(a))
if has_errata:
if not doc.tags.filter(pk=tag_has_errata.pk):
- changed_attributes["tags"] = list(doc.tags.all()) + [tag_has_errata]
+ doc.tags.add(tag_has_errata)
+ changes.append("added Errata tag")
else:
if doc.tags.filter(pk=tag_has_errata.pk):
- changed_attributes["tags"] = set(doc.tags.all()) - set([tag_has_errata])
+ doc.tags.remove(tag_has_errata)
+ changes.append("removed Errata tag")
- if changed_attributes or changed_states or created_relations or other_changes:
- # apply changes
- save_document_in_history(doc)
- for k, v in changed_attributes.iteritems():
- setattr(doc, k, v)
- results.append("Changed %s to %s on %s" % (k, v, doc.name))
+ if changes:
+ events.append(DocEvent.objects.create(
+ doc=doc,
+ by=system,
+ type="sync_from_rfc_editor",
+ desc=u"Received changes through RFC Editor sync (%s)" % u", ".join(changes),
+ ))
- for s in changed_states:
- doc.set_state(s)
- results.append("Set state %s on %s" % (s, doc.name))
+ doc.save_with_history(events)
- for o in created_relations:
- o.save()
- results.append("Created %s" % o)
-
- doc.time = datetime.datetime.now()
- doc.save()
-
- return results, new_rfcs
+ if changes:
+ yield changes, doc, rfc_published
def post_approved_draft(url, name):
diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index c90e5b0a0..b4ae5398d 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -327,12 +327,15 @@ class RFCSyncTests(TestCase):
draft_filename = "%s-%s.txt" % (doc.name, doc.rev)
self.write_draft_file(draft_filename, 5000)
- changed,_ = rfceditor.update_docs_from_rfc_index(data, today - datetime.timedelta(days=30))
+ changes = []
+ for cs, d, rfc_published in rfceditor.update_docs_from_rfc_index(data, today - datetime.timedelta(days=30)):
+ changes.append(cs)
doc = Document.objects.get(name=doc.name)
- self.assertEqual(doc.docevent_set.all()[0].type, "published_rfc")
- self.assertEqual(doc.docevent_set.all()[0].time.date(), today)
+ self.assertEqual(doc.docevent_set.all()[0].type, "sync_from_rfc_editor")
+ self.assertEqual(doc.docevent_set.all()[1].type, "published_rfc")
+ self.assertEqual(doc.docevent_set.all()[1].time.date(), today)
self.assertTrue("errata" in doc.tags.all().values_list("slug", flat=True))
self.assertTrue(DocAlias.objects.filter(name="rfc1234", document=doc))
self.assertTrue(DocAlias.objects.filter(name="bcp1", document=doc))
@@ -350,7 +353,7 @@ class RFCSyncTests(TestCase):
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, draft_filename)))
# make sure we can apply it again with no changes
- changed,_ = rfceditor.update_docs_from_rfc_index(data, today - datetime.timedelta(days=30))
+ changed = list(rfceditor.update_docs_from_rfc_index(data, today - datetime.timedelta(days=30)))
self.assertEqual(len(changed), 0)
diff --git a/ietf/sync/views.py b/ietf/sync/views.py
index ac46eebf2..c6a9ae0e3 100644
--- a/ietf/sync/views.py
+++ b/ietf/sync/views.py
@@ -4,14 +4,13 @@ import os
import json
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseServerError, HttpResponseRedirect, Http404
-from django.shortcuts import render_to_response
-from django.template import RequestContext
+from django.shortcuts import render
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.views.decorators.csrf import csrf_exempt
-from ietf.doc.models import DeletedEvent, StateDocEvent
+from ietf.doc.models import DeletedEvent, StateDocEvent, DocEvent
from ietf.ietfauth.utils import role_required, has_role
from ietf.sync.discrepancies import find_discrepancies
from ietf.utils.serialize import object_as_shallow_dict
@@ -22,9 +21,7 @@ SYNC_BIN_PATH = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__f
def discrepancies(request):
sections = find_discrepancies()
- return render_to_response("sync/discrepancies.html",
- dict(sections=sections),
- context_instance=RequestContext(request))
+ return render(request, "sync/discrepancies.html", dict(sections=sections))
@csrf_exempt # external API so we can't expect the other end to have a token
def notify(request, org, notification):
@@ -100,19 +97,27 @@ def notify(request, org, notification):
else:
return HttpResponse("OK", content_type="text/plain; charset=%s"%settings.DEFAULT_CHARSET)
- return render_to_response('sync/notify.html',
- dict(org=known_orgs[org],
- notification=notification,
- help_text=known_notifications[notification],
- ),
- context_instance=RequestContext(request))
+ return render(request, 'sync/notify.html',
+ dict(org=known_orgs[org],
+ notification=notification,
+ help_text=known_notifications[notification],
+ ))
@role_required('Secretariat', 'RFC Editor')
def rfceditor_undo(request):
"""Undo a DocEvent."""
- events = StateDocEvent.objects.filter(state_type="draft-rfceditor",
- time__gte=datetime.datetime.now() - datetime.timedelta(weeks=1)
- ).order_by("-time", "-id")
+ events = []
+ events.extend(StateDocEvent.objects.filter(
+ state_type="draft-rfceditor",
+ time__gte=datetime.datetime.now() - datetime.timedelta(weeks=1)
+ ).order_by("-time", "-id"))
+
+ events.extend(DocEvent.objects.filter(
+ type="sync_from_rfc_editor",
+ time__gte=datetime.datetime.now() - datetime.timedelta(weeks=1)
+ ).order_by("-time", "-id"))
+
+ events.sort(key=lambda e: (e.time, e.id), reverse=True)
if request.method == "POST":
try:
@@ -120,9 +125,10 @@ def rfceditor_undo(request):
except ValueError:
return HttpResponse("Could not parse event id")
- try:
- e = events.get(id=eid)
- except StateDocEvent.DoesNotExist:
+ for e in events:
+ if e.id == eid:
+ break
+ else:
return HttpResponse("Event does not exist")
doc = e.doc
@@ -145,7 +151,4 @@ def rfceditor_undo(request):
return HttpResponseRedirect("")
- return render_to_response('sync/rfceditor_undo.html',
- dict(events=events,
- ),
- context_instance=RequestContext(request))
+ return render(request, 'sync/rfceditor_undo.html', dict(events=events))
diff --git a/ietf/templates/doc/charter/submit.html b/ietf/templates/doc/charter/submit.html
index 767f00472..9ddca9481 100644
--- a/ietf/templates/doc/charter/submit.html
+++ b/ietf/templates/doc/charter/submit.html
@@ -14,7 +14,7 @@
{% bootstrap_messages %}
-
The text will be submitted as charter-ietf-{{ name }}-{{ next_rev }}.
+
The text will be submitted as {{ name }}-{{ next_rev }}.
- {% if can_edit_stream_info %}
+ {% if can_edit_stream_info and not snapshot %}
Edit
{% endif %}
@@ -106,7 +106,7 @@
Possibly Replaces
- {% if can_edit_replaces %}
+ {% if can_edit_replaces and not snapshot %}
Edit
{% endif %}
@@ -120,7 +120,7 @@
Possibly Replaced By
- {% if can_edit_replaces %}
+ {% if can_edit_replaces and not snapshot %}
{% comment %}Edit{% endcomment %}
{% endif %}
@@ -135,7 +135,7 @@
Stream
- {% if can_change_stream %}
+ {% if can_change_stream and not snapshot %}
Edit
{% endif %}
@@ -149,8 +149,8 @@
Intended RFC status
- {% if can_edit or can_edit_stream_info %}
- Edit
+ {% if can_edit_stream_info and not snapshot %}
+ Edit
{% endif %}
@@ -201,7 +201,7 @@
{% endif %}
- {% if doc.stream and can_edit_stream_info %}
+ {% if doc.stream and can_edit_stream_info and doc.stream.slug != "legacy" and not snapshot %}
Edit
{% endif %}
@@ -228,7 +228,7 @@
Consensus
- {% if can_edit or can_edit_stream_info %}
+ {% if can_edit_stream_info and not snapshot %}
Edit
{% endif %}
- {% if can_edit_shepherd_writeup %}
+ {% if can_edit_shepherd_writeup and not snapshot %}
{% url "doc_edit_shepherd_writeup" name=doc.name as doc_edit_url %}
{% if doc_edit_url %}
Edit
@@ -289,7 +291,7 @@
{% endif %}
{% regroup docs by get_state as state_groups %}
diff --git a/ietf/templates/doc/submit_to_iesg_email.txt b/ietf/templates/doc/submit_to_iesg_email.txt
index 807cdd351..dd62f1762 100644
--- a/ietf/templates/doc/submit_to_iesg_email.txt
+++ b/ietf/templates/doc/submit_to_iesg_email.txt
@@ -1,4 +1,4 @@
-{{login}} has requested publication of {{doc.name}}-{{doc.rev}} as {{doc.intended_std_level}} on behalf of the {{doc.group.acronym|upper}} working group.
+{{by}} has requested publication of {{doc.name}}-{{doc.rev}} as {{doc.intended_std_level}} on behalf of the {{doc.group.acronym|upper}} working group.
Please verify the document's state at {{url}}
diff --git a/ietf/templates/group/group_about.html b/ietf/templates/group/group_about.html
index 567543b88..0910b6df9 100644
--- a/ietf/templates/group/group_about.html
+++ b/ietf/templates/group/group_about.html
@@ -57,7 +57,7 @@
{% else %}
(None)
{% if user|has_role:"Area Director,Secretariat" %}
- Submit charter
+ Submit charter
{% endif %}
{% endif %}
diff --git a/ietf/templates/liaisons/feed_item_description.html b/ietf/templates/liaisons/feed_item_description.html
index 832414003..ce03d8cf5 100644
--- a/ietf/templates/liaisons/feed_item_description.html
+++ b/ietf/templates/liaisons/feed_item_description.html
@@ -1,14 +1,12 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}{% load origin %}{% origin %}
{{ obj.body|truncatewords:"30"|wordwrap:"71"|linebreaksbr }}
-{% with obj.attachments.all as attachments %}
-