From 99a9cb5569b157f143d3095fb4b75371bbc082a6 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Wed, 23 Sep 2015 09:49:48 +0000
Subject: [PATCH 01/30] Summary: Switch to a much faster password hasher when
running unit tests, the default, more secure one took ~20% of the test time
- Legacy-Id: 10097
---
ietf/utils/test_runner.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py
index 5ff91138c..6e87775fc 100644
--- a/ietf/utils/test_runner.py
+++ b/ietf/utils/test_runner.py
@@ -322,6 +322,8 @@ class IetfTestRunner(DiscoverRunner):
ietf.utils.mail.test_mode = True
ietf.utils.mail.SMTP_ADDR['ip4'] = '127.0.0.1'
ietf.utils.mail.SMTP_ADDR['port'] = 2025
+ # switch to a much faster hasher
+ settings.PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.MD5PasswordHasher', )
#
if self.check_coverage:
with codecs.open(self.coverage_file, encoding='utf-8') as file:
From e1f0917659d44ab88b4d88fe97fb7ae04da902c7 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 28 Sep 2015 14:01:03 +0000
Subject: [PATCH 02/30] Summary: Add new document saving API,
Document.save_with_history(events).
The new API requires at least one event and will automatically save a
snapshot of the document and related state. Document.save() will now
throw an exception if called directly, as the new API is intended to
ensure that documents are saved with both an appropriate snapsnot and
relevant history log, both of which are easily defeated by just
calling .save() directly.
To simplify things, the snapshot is generated after the changes to a
document have been made (in anticipation of coming changes), instead
of before as was usual.
While revising the existing code to work with this API, a couple of
missing events was discovered:
- In draft expiry, a "Document has expired" event was only generated
in case an IESG process had started on the document - now it's
always generated, as the document changes its state in any case
- Synchronization updates like title and abstract amendmends from the
RFC Editor were silently (except for RFC publication) applied and
not accompanied by a descriptive event - they now are
- do_replace in the Secretariat tools now adds an event
- Proceedings post_process in the Secretariat tools now adds an event
- do_withdraw in the Secretariat tools now adds an event
A migration is needed for snapshotting all documents, takes a while to
run. It turns out that a single document had a bad foreign key so the
migration fixes that too.
- Legacy-Id: 10101
---
ietf/bin/rfc-editor-index-updates | 22 +-
ietf/doc/expire.py | 15 +-
ietf/doc/lastcall.py | 8 +-
ietf/doc/mails.py | 14 +
.../doc/migrations/0006_auto_20150924_0800.py | 91 +++++++
ietf/doc/models.py | 69 ++---
ietf/doc/tests.py | 17 +-
ietf/doc/tests_ballot.py | 9 +-
ietf/doc/tests_charter.py | 9 +-
ietf/doc/tests_conflict_review.py | 7 +-
ietf/doc/tests_draft.py | 27 +-
ietf/doc/tests_status_change.py | 7 +-
ietf/doc/utils.py | 91 ++++---
ietf/doc/utils_charter.py | 45 +++-
ietf/doc/views_ballot.py | 98 +++----
ietf/doc/views_charter.py | 197 ++++++--------
ietf/doc/views_conflict_review.py | 54 ++--
ietf/doc/views_doc.py | 3 +-
ietf/doc/views_draft.py | 246 +++++++++---------
ietf/doc/views_material.py | 20 +-
ietf/doc/views_status_change.py | 80 +++---
ietf/group/edit.py | 3 +-
ietf/group/tests.py | 4 +-
ietf/iesg/tests.py | 18 +-
ietf/iesg/views.py | 10 +-
ietf/secr/drafts/forms.py | 9 +-
ietf/secr/drafts/views.py | 157 +++++------
ietf/secr/proceedings/views.py | 59 +++--
ietf/secr/telechat/views.py | 8 +-
ietf/submit/utils.py | 70 ++---
ietf/sync/iana.py | 9 +-
ietf/sync/rfceditor.py | 119 +++++----
ietf/sync/tests.py | 11 +-
ietf/sync/views.py | 47 ++--
ietf/templates/doc/submit_to_iesg_email.txt | 2 +-
ietf/utils/test_data.py | 9 +-
36 files changed, 920 insertions(+), 744 deletions(-)
create mode 100644 ietf/doc/migrations/0006_auto_20150924_0800.py
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 b2a62ff91..a57ca6203 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, Email
from ietf.meeting.models import Meeting
from ietf.doc.utils import add_state_change_event
@@ -131,8 +131,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)
@@ -141,15 +142,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..016b85e41 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
@@ -50,8 +49,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 +57,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 6184458d8..2fdea8310 100644
--- a/ietf/doc/mails.py
+++ b/ietf/doc/mails.py
@@ -87,6 +87,20 @@ def email_ad(request, doc, ad, changed_by, text, subject=None):
doc=doc,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
+def email_update_telechat(request, doc, text):
+ to = set(['iesg@ietf.org','iesg-secretary@ietf.org'])
+ to.update(set([x.strip() for x in doc.notify.replace(';', ',').split(',')]))
+
+ if not to:
+ return
+
+ text = strip_tags(text)
+ send_mail(request, list(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()))
+
def generate_ballot_writeup(request, doc):
e = doc.latest_event(type="iana_review")
diff --git a/ietf/doc/migrations/0006_auto_20150924_0800.py b/ietf/doc/migrations/0006_auto_20150924_0800.py
new file mode 100644
index 000000000..2440cf776
--- /dev/null
+++ b/ietf/doc/migrations/0006_auto_20150924_0800.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', '0005_auto_20150721_0230'),
+ ]
+
+ 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 70bbe2142..5083c8fa8 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, ...
@@ -433,6 +432,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:
@@ -570,50 +590,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
@@ -695,7 +671,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):
diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py
index 53b023bf9..2df6ce63c 100644
--- a/ietf/doc/tests.py
+++ b/ietf/doc/tests.py
@@ -17,8 +17,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
@@ -432,9 +431,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)
@@ -480,9 +478,10 @@ Man Expires September 22, 2015 [Page 3]
]:
doc = Document.objects.get(name=docname)
# give it some history
- save_document_in_history(doc)
+ doc.save_with_history([DocEvent(doc=doc)])
+
doc.rev="01"
- doc.save()
+ doc.save_with_history([DocEvent(doc=doc)])
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
@@ -539,7 +538,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="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
pos = BallotPositionDocEvent.objects.create(
doc=doc,
@@ -567,9 +567,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 5ba9fa867..75551e76b 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="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
ad = Person.objects.get(name="Aread Irector")
@@ -355,8 +355,8 @@ class BallotWriteupsTests(TestCase):
# 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="added_comment", 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)
@@ -492,8 +492,8 @@ class DeferUndeferTestCase(TestCase):
self.assertEqual(doc.get_state(defer_states[doc.type_id][0]).slug,defer_states[doc.type_id][1])
self.assertTrue(doc.active_defer_event())
self.assertEqual(len(outbox), mailbox_before + 3)
- self.assertTrue("State Update" in outbox[-3]['Subject'])
- self.assertTrue("Telechat update" in outbox[-2]['Subject'])
+ self.assertTrue("Telechat update" in outbox[-3]['Subject'])
+ self.assertTrue("State Update" in outbox[-2]['Subject'])
self.assertTrue("Deferred" in outbox[-1]['Subject'])
self.assertTrue(doc.file_tag() in outbox[-1]['Subject'])
@@ -526,7 +526,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 ac99b7552..912d788db 100644
--- a/ietf/doc/tests_charter.py
+++ b/ietf/doc/tests_charter.py
@@ -27,6 +27,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 +47,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":
@@ -349,8 +355,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 6de5ac13f..5d6847832 100644
--- a/ietf/doc/tests_conflict_review.py
+++ b/ietf/doc/tests_conflict_review.py
@@ -33,7 +33,7 @@ class ConflictReviewTests(TestCase):
self.assertEqual(r.status_code, 404)
doc.stream=StreamName.objects.get(slug='ise')
- doc.save()
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
# normal get should succeed and get a reasonable form
r = self.client.get(url)
@@ -91,13 +91,13 @@ class ConflictReviewTests(TestCase):
# can't start conflict reviews on documents in some other stream
doc.stream=StreamName.objects.get(slug='irtf')
- doc.save()
+ doc.save_with_history([DocEvent.objects.create(doc=doc, type="added_comment", 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.save_with_history([DocEvent.objects.create(doc=doc, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
@@ -268,7 +268,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 44a9b0b61..987c9eefb 100644
--- a/ietf/doc/tests_draft.py
+++ b/ietf/doc/tests_draft.py
@@ -353,16 +353,16 @@ 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)
# Redo, starting in publication requested to make sure WG state is also set
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="added_comment", 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,
@@ -514,7 +514,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="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
self.assertEqual(len(list(get_soon_to_expire_drafts(14))), 1)
@@ -537,7 +537,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="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
self.assertEqual(len(list(get_expired_drafts())), 1)
@@ -597,7 +597,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)
@@ -615,7 +614,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="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
e = DocEvent()
e.doc = draft
@@ -822,7 +821,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="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
url = urlreverse('doc_edit_shepherd',kwargs=dict(name=self.docname))
@@ -874,19 +873,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="added_comment", 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="added_comment", 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="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
new_email = Email.objects.create(address="anotheremail@example.com", person=self.doc.shepherd.person)
@@ -921,7 +920,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="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
@@ -1028,7 +1027,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="added_comment", 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))
@@ -1064,8 +1063,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="added_comment", 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 b22cd9f31..792a83b70 100644
--- a/ietf/doc/tests_status_change.py
+++ b/ietf/doc/tests_status_change.py
@@ -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="added_comment", 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)
@@ -255,7 +255,7 @@ class StatusChangeTests(TestCase):
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.save_with_history([DocEvent.objects.create(doc=doc, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
# get
r = self.client.get(url)
@@ -299,9 +299,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/utils.py b/ietf/doc/utils.py
index bd0ef5c96..37ee196bf 100644
--- a/ietf/doc/utils.py
+++ b/ietf/doc/utils.py
@@ -8,12 +8,13 @@ from django.conf import settings
from django.db.models import Q
from django.db.models.query import EmptyQuerySet
from django.forms import ValidationError
-from django.utils.html import strip_tags, escape
+from django.utils.html import escape
-from ietf.doc.models import Document, DocHistory, State
-from ietf.doc.models import DocAlias, RelatedDocument, BallotType, DocReminder
+from ietf.doc.models import Document, DocHistory, State, DocumentAuthor, DocHistoryAuthor
+from ietf.doc.models import DocAlias, RelatedDocument, RelatedDocHistory, BallotType, DocReminder
from ietf.doc.models import DocEvent, BallotDocEvent, NewRevisionDocEvent, StateDocEvent
-from ietf.doc.models import save_document_in_history, STATUSCHANGE_RELATIONS
+from ietf.doc.models import TelechatDocEvent
+from ietf.doc.models import STATUSCHANGE_RELATIONS
from ietf.name.models import DocReminderTypeName, DocRelationshipName
from ietf.group.models import Role
from ietf.person.models import Email
@@ -21,21 +22,43 @@ from ietf.ietfauth.utils import has_role
from ietf.utils import draft, markup_txt
from ietf.utils.mail import send_mail
-#FIXME - it would be better if this lived in ietf/doc/mails.py, but there's
-# an import order issue to work out.
-def email_update_telechat(request, doc, text):
- to = set(['iesg@ietf.org','iesg-secretary@ietf.org'])
- to.update(set([x.strip() for x in doc.notify.replace(';', ',').split(',')]))
+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)
- if not to:
- return
+ # copy fields
+ fields = get_model_fields_as_dict(doc)
+ fields["doc"] = doc
+ fields["name"] = doc.canonical_name()
- text = strip_tags(text)
- send_mail(request, list(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()))
+ 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
+
def get_state_types(doc):
res = []
@@ -344,7 +367,6 @@ def make_notify_changed_event(request, doc, by, new_notify, time=None):
# events to match
if doc.type.slug=='charter':
event_type = 'changed_document'
- save_document_in_history(doc)
else:
event_type = 'added_comment'
@@ -359,8 +381,6 @@ def make_notify_changed_event(request, doc, by, new_notify, time=None):
return e
def update_telechat(request, doc, by, new_telechat_date, new_returning_item=None):
- from ietf.doc.models import TelechatDocEvent
-
on_agenda = bool(new_telechat_date)
prev = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
@@ -411,8 +431,12 @@ def update_telechat(request, doc, by, new_telechat_date, new_returning_item=None
e.desc = "Removed telechat returning item indication"
e.save()
+
+ from ietf.doc.mails import email_update_telechat
email_update_telechat(request, doc, e.desc)
+ return e
+
def rebuild_reference_relations(doc,filename=None):
if doc.type.slug != 'draft':
return None
@@ -477,13 +501,26 @@ def collect_email_addresses(emails, doc):
if doc.shepherd and doc.shepherd.address not in emails:
emails[doc.shepherd.address] = u'"%s"' % (doc.shepherd.person.name or "")
-def set_replaces_for_document(request, doc, new_replaces, by, email_subject, email_comment=""):
+def set_replaces_for_document(request, doc, new_replaces, by, email_subject, comment=""):
emails = {}
collect_email_addresses(emails, doc)
relationship = DocRelationshipName.objects.get(slug='replaces')
old_replaces = doc.related_that_doc("replaces")
+ events = []
+
+ e = DocEvent(doc=doc, by=by, type='changed_document')
+ new_replaces_names = u", ".join(d.name for d in new_replaces) or u"None"
+ old_replaces_names = u", ".join(d.name for d in old_replaces) or u"None"
+ e.desc = u"This document now replaces %s instead of %s" % (new_replaces_names, old_replaces_names)
+ e.save()
+
+ events.append(e)
+
+ if comment:
+ events.append(DocEvent.objects.create(doc=doc, by=by, type="added_comment", desc=comment))
+
for d in old_replaces:
if d not in new_replaces:
collect_email_addresses(emails, d.document)
@@ -498,19 +535,13 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, ema
RelatedDocument.objects.create(source=doc, target=d, relationship=relationship)
d.document.set_state(State.objects.get(type='draft', slug='repl'))
- e = DocEvent(doc=doc, by=by, type='changed_document')
- new_replaces_names = u", ".join(d.name for d in new_replaces) or u"None"
- old_replaces_names = u", ".join(d.name for d in old_replaces) or u"None"
- e.desc = u"This document now replaces %s instead of %s" % (new_replaces_names, old_replaces_names)
- e.save()
-
# make sure there are no lingering suggestions duplicating new replacements
RelatedDocument.objects.filter(source=doc, target__in=new_replaces, relationship="possibly-replaces").delete()
email_desc = e.desc.replace(", ", "\n ")
- if email_comment:
- email_desc += "\n" + email_comment
+ if comment:
+ email_desc += "\n" + comment
to = [
u'%s <%s>' % (emails[email], email) if emails[email] else u'<%s>' % email
@@ -527,6 +558,8 @@ def set_replaces_for_document(request, doc, new_replaces, by, email_subject, ema
doc=doc,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
+ return events
+
def check_common_doc_name_rules(name):
"""Check common rules for document names for use in forms, throws
ValidationError in case there's a problem."""
diff --git a/ietf/doc/utils_charter.py b/ietf/doc/utils_charter.py
index b3812b11c..02cea0872 100644
--- a/ietf/doc/utils_charter.py
+++ b/ietf/doc/utils_charter.py
@@ -1,4 +1,4 @@
-import re, datetime, os
+import re, datetime, os, shutil
from django.template.loader import render_to_string
from django.utils.html import strip_tags
@@ -6,8 +6,12 @@ from django.conf import settings
from ietf.doc.models import NewRevisionDocEvent, WriteupDocEvent, BallotPositionDocEvent
from ietf.person.models import Person
+from ietf.group.models import ChangeStateGroupEvent
+from ietf.name.models import GroupStateName
from ietf.utils.history import find_history_active_at
from ietf.utils.mail import send_mail_text
+from ietf.utils.log import log
+from ietf.group.utils import save_group_in_history
def charter_name_for_group(group):
if group.type_id == "rg":
@@ -46,6 +50,45 @@ def read_charter_text(doc):
except IOError:
return "Error: couldn't read charter text"
+def change_group_state_after_charter_approval(group, by):
+ new_state = GroupStateName.objects.get(slug="active")
+ if group.state == new_state:
+ return None
+
+ save_group_in_history(group)
+ group.state = new_state
+ group.time = datetime.datetime.now()
+ group.save()
+
+ # create an event for the group state change, too
+ e = ChangeStateGroupEvent(group=group, type="changed_state")
+ e.time = group.time
+ e.by = by
+ e.state_id = "active"
+ e.desc = "Charter approved, group active"
+ e.save()
+
+ return e
+
+def fix_charter_revision_after_approval(charter, by):
+ # according to spec, 00-02 becomes 01, so copy file and record new revision
+ try:
+ old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), charter.rev))
+ new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
+ shutil.copy(old, new)
+ except IOError:
+ log("There was an error copying %s to %s" % (old, new))
+
+ events = []
+ e = NewRevisionDocEvent(doc=charter, by=by, type="new_revision")
+ e.rev = next_approved_revision(charter.rev)
+ e.desc = "New version available: %s-%s.txt" % (charter.canonical_name(), e.rev)
+ e.save()
+ events.append(e)
+
+ charter.rev = e.rev
+ charter.save_with_history(events)
+
def historic_milestones_for_charter(charter, rev):
"""Return GroupMilestone/GroupMilestoneHistory objects for charter
document at rev by looking through the history."""
diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py
index 79f73cca5..e614c51fe 100644
--- a/ietf/doc/views_ballot.py
+++ b/ietf/doc/views_ballot.py
@@ -4,7 +4,7 @@
import datetime, json
from django.http import HttpResponseForbidden, HttpResponseRedirect, Http404
-from django.shortcuts import render_to_response, get_object_or_404, redirect
+from django.shortcuts import render, render_to_response, get_object_or_404, redirect
from django.core.urlresolvers import reverse as urlreverse
from django.template.loader import render_to_string
from django.template import RequestContext
@@ -14,7 +14,7 @@ from django.conf import settings
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, State, DocEvent, BallotDocEvent, BallotPositionDocEvent,
- BallotType, LastCallDocEvent, WriteupDocEvent, save_document_in_history, IESG_SUBSTATE_TAGS )
+ BallotType, LastCallDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS )
from ietf.doc.utils import ( add_state_change_event, close_ballot, close_open_ballots,
create_ballot_if_not_open, update_telechat )
from ietf.doc.mails import ( email_ad, email_ballot_deferred, email_ballot_undeferred,
@@ -44,12 +44,12 @@ def do_undefer_ballot(request, doc):
Helper function to perform undefer of ballot. Takes the Request object, for use in
logging, and the Document object.
'''
- login = request.user.person
+ by = request.user.person
telechat_date = TelechatDate.objects.active().order_by("date")[0].date
- save_document_in_history(doc)
new_state = doc.get_state()
- prev_tags = new_tags = []
+ prev_tags = []
+ new_tags = []
if doc.type_id == 'draft':
new_state = State.objects.get(used=True, type="draft-iesg", slug='iesg-eva')
@@ -62,15 +62,21 @@ def do_undefer_ballot(request, doc):
doc.set_state(new_state)
doc.tags.remove(*prev_tags)
- e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags)
-
- doc.time = (e and e.time) or datetime.datetime.now()
- doc.save()
+ events = []
+ state_change_event = add_state_change_event(doc, by, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags)
+ if state_change_event:
+ events.append(state_change_event)
- update_telechat(request, doc, login, telechat_date)
+ e = update_telechat(request, doc, by, telechat_date)
if e:
- email_state_changed(request, doc, e.desc)
- email_ballot_undeferred(request, doc, login.plain_name(), telechat_date)
+ events.append(e)
+
+ if events:
+ doc.save_with_history(events)
+
+ if state_change_event:
+ email_state_changed(request, doc, state_change_event.desc)
+ email_ballot_undeferred(request, doc, by.plain_name(), telechat_date)
def position_to_ballot_choice(position):
for v, label in BALLOT_CHOICES:
@@ -342,10 +348,9 @@ def defer_ballot(request, name):
telechat_date = TelechatDate.objects.active().order_by("date")[1].date
if request.method == 'POST':
- save_document_in_history(doc)
-
new_state = doc.get_state()
- prev_tags = new_tags = []
+ prev_tags = []
+ new_tags = []
if doc.type_id == 'draft':
new_state = State.objects.get(used=True, type="draft-iesg", slug='defer')
@@ -358,15 +363,20 @@ def defer_ballot(request, name):
doc.set_state(new_state)
doc.tags.remove(*prev_tags)
- e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags)
-
- doc.time = (e and e.time) or datetime.datetime.now()
- doc.save()
+ events = []
+ state_change_event = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags)
+ if state_change_event:
+ events.append(state_change_event)
+
+ e = update_telechat(request, doc, login, telechat_date)
if e:
- email_state_changed(request, doc, e.desc)
+ events.append(e)
- update_telechat(request, doc, login, telechat_date)
+ doc.save_with_history(events)
+
+ if state_change_event:
+ email_state_changed(request, doc, e.desc)
email_ballot_deferred(request, doc, login.plain_name(), telechat_date)
return HttpResponseRedirect(doc.get_absolute_url())
@@ -443,8 +453,6 @@ def lastcalltext(request, name):
e.save()
if "send_last_call_request" in request.POST:
- save_document_in_history(doc)
-
prev_state = doc.get_state("draft-iesg")
new_state = State.objects.get(used=True, type="draft-iesg", slug='lc-req')
@@ -455,10 +463,9 @@ def lastcalltext(request, name):
e = add_state_change_event(doc, login, 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_state_changed(request, doc, e.desc)
email_ad(request, doc, doc.ad, login, e.desc)
@@ -666,19 +673,17 @@ def approve_ballot(request, name):
prev_state = doc.get_state("draft-iesg")
prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
+ events = []
if new_state.slug == "ann" and new_state.slug != prev_state.slug and not request.REQUEST.get("skiprfceditorpost"):
# start by notifying the RFC Editor
import ietf.sync.rfceditor
response, error = ietf.sync.rfceditor.post_approved_draft(settings.RFC_EDITOR_SYNC_NOTIFICATION_URL, doc.name)
if error:
- return render_to_response('doc/draft/rfceditor_post_approved_draft_failed.html',
- dict(name=doc.name,
- response=response,
- error=error),
- context_instance=RequestContext(request))
-
- save_document_in_history(doc)
+ return render(request, 'doc/draft/rfceditor_post_approved_draft_failed.html',
+ dict(name=doc.name,
+ response=response,
+ error=error))
doc.set_state(new_state)
doc.tags.remove(*prev_tags)
@@ -693,15 +698,17 @@ def approve_ballot(request, name):
else:
e.type = "iesg_approved"
e.desc = "IESG has approved the document"
-
e.save()
+ events.append(e)
change_description = e.desc + " and state has been changed to %s" % doc.get_state("draft-iesg").name
e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=[])
- doc.time = (e and e.time) or datetime.datetime.now()
- doc.save()
+ if e:
+ events.append(e)
+
+ doc.save_with_history(events)
email_state_changed(request, doc, change_description)
email_ad(request, doc, doc.ad, login, change_description)
@@ -761,10 +768,10 @@ def make_last_call(request, name):
msg.save()
msg.related_docs.add(doc)
- save_document_in_history(doc)
-
new_state = doc.get_state()
- prev_tags = new_tags = []
+ prev_tags = []
+ new_tags = []
+ events = []
if doc.type.slug == 'draft':
new_state = State.objects.get(used=True, type="draft-iesg", slug='lc')
@@ -778,10 +785,8 @@ def make_last_call(request, name):
doc.tags.remove(*prev_tags)
e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags)
-
- doc.time = (e and e.time) or datetime.datetime.now()
- doc.save()
-
+ if e:
+ events.append(e)
change_description = "Last call has been made for %s and state has been changed to %s" % (doc.name, new_state.name)
email_state_changed(request, doc, change_description)
@@ -796,6 +801,7 @@ def make_last_call(request, name):
e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time())
e.expires = form.cleaned_data['last_call_expiration_date']
e.save()
+ events.append(e)
# update IANA Review state
if doc.type.slug == 'draft':
@@ -803,7 +809,11 @@ def make_last_call(request, name):
if not prev_state:
next_state = State.objects.get(used=True, type="draft-iana-review", slug="need-rev")
doc.set_state(next_state)
- add_state_change_event(doc, login, prev_state, next_state)
+ e = add_state_change_event(doc, login, prev_state, next_state)
+ if e:
+ events.append(e)
+
+ doc.save_with_history(events)
return HttpResponseRedirect(doc.get_absolute_url())
else:
diff --git a/ietf/doc/views_charter.py b/ietf/doc/views_charter.py
index fe6380481..01767577d 100644
--- a/ietf/doc/views_charter.py
+++ b/ietf/doc/views_charter.py
@@ -1,6 +1,6 @@
-import os, datetime, shutil, textwrap, json
+import os, datetime, textwrap, json
-from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, HttpResponseForbidden, Http404
+from django.http import HttpResponseRedirect, HttpResponseNotFound, HttpResponseForbidden, Http404
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.core.urlresolvers import reverse as urlreverse
from django.template import RequestContext
@@ -14,12 +14,13 @@ import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocHistory, State, DocEvent, BallotDocEvent,
BallotPositionDocEvent, InitialReviewDocEvent, NewRevisionDocEvent,
- WriteupDocEvent, save_document_in_history )
+ WriteupDocEvent )
from ietf.doc.utils import ( add_state_change_event, close_open_ballots,
create_ballot_if_not_open, get_chartering_type )
from ietf.doc.utils_charter import ( historic_milestones_for_charter,
approved_revision, default_review_text, default_action_text, email_state_changed,
- generate_ballot_writeup, generate_issue_ballot_mail, next_approved_revision, next_revision )
+ generate_ballot_writeup, generate_issue_ballot_mail, next_revision,
+ change_group_state_after_charter_approval, fix_charter_revision_after_approval)
from ietf.group.models import ChangeStateGroupEvent, MilestoneGroupEvent
from ietf.group.utils import save_group_in_history, save_milestone_in_history, can_manage_group_type
from ietf.ietfauth.utils import has_role, role_required
@@ -65,7 +66,7 @@ def change_state(request, name, option=None):
if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < datetime.datetime.now()) or chartering_type == "rechartering":
initial_review = None
- login = request.user.person
+ by = request.user.person
if request.method == 'POST':
form = ChangeStateForm(request.POST, group=group)
@@ -97,7 +98,7 @@ def change_state(request, name, option=None):
group.save()
e = ChangeStateGroupEvent(group=group, type="changed_state")
e.time = group.time
- e.by = login
+ e.by = by
e.state_id = group.state.slug
e.desc = "Group state changed to %s from %s" % (group.state, oldstate)
e.save()
@@ -112,32 +113,30 @@ def change_state(request, name, option=None):
message = clean['message']
if charter_state != charter.get_state():
- # Charter state changed
- save_document_in_history(charter)
-
+ events = []
prev_state = charter.get_state()
new_state = charter_state
charter.set_state(new_state)
charter.rev = charter_rev
if option != "abandon":
- add_state_change_event(charter, login, prev_state, new_state)
+ e = add_state_change_event(charter, by, prev_state, new_state)
+ if e:
+ events.append(e)
else:
# kill hanging ballots
- close_open_ballots(charter, login)
+ close_open_ballots(charter, by)
# Special log for abandoned efforts
- e = DocEvent(type="changed_document", doc=charter, by=login)
+ e = DocEvent(type="changed_document", doc=charter, by=by)
e.desc = "IESG has abandoned the chartering effort"
e.save()
+ events.append(e)
if comment:
- c = DocEvent(type="added_comment", doc=charter, by=login)
- c.desc = comment
- c.save()
+ events.append(DocEvent.objects.create(type="added_comment", doc=charter, by=by, desc=comment))
- charter.time = datetime.datetime.now()
- charter.save()
+ charter.save_with_history(events)
if message or charter_state.slug == "intrev" or charter_state.slug == "extrev":
email_iesg_secretary_re_charter(request, group, "Charter state changed to %s" % charter_state.name, message)
@@ -146,19 +145,19 @@ def change_state(request, name, option=None):
if charter_state.slug == "intrev" and group.type_id == "wg":
if request.POST.get("ballot_wo_extern"):
- create_ballot_if_not_open(charter, login, "r-wo-ext")
+ create_ballot_if_not_open(charter, by, "r-wo-ext")
else:
- create_ballot_if_not_open(charter, login, "r-extrev")
- default_review_text(group, charter, login)
- default_action_text(group, charter, login)
+ create_ballot_if_not_open(charter, by, "r-extrev")
+ default_review_text(group, charter, by)
+ default_action_text(group, charter, by)
elif charter_state.slug == "iesgrev":
- create_ballot_if_not_open(charter, login, "approve")
+ create_ballot_if_not_open(charter, by, "approve")
elif charter_state.slug == "approved":
- change_group_state_after_charter_approval(group, login)
- fix_charter_revision_after_approval(charter, login)
+ change_group_state_after_charter_approval(group, by)
+ fix_charter_revision_after_approval(charter, by)
if charter_state.slug == "infrev" and clean["initial_time"] and clean["initial_time"] != 0:
- e = InitialReviewDocEvent(type="initial_review", by=login, doc=charter)
+ e = InitialReviewDocEvent(type="initial_review", by=by, doc=charter)
e.expires = datetime.datetime.now() + datetime.timedelta(weeks=clean["initial_time"])
e.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d")
e.save()
@@ -178,10 +177,10 @@ def change_state(request, name, option=None):
init = dict()
elif option == "initcharter":
hide = ['charter_state']
- init = dict(initial_time=1, message='%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
+ init = dict(initial_time=1, message='%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (by.plain_name(), group.type.name, group.name, group.acronym))
elif option == "abandon":
hide = ['initial_time', 'charter_state']
- init = dict(message='%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
+ init = dict(message='%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (by.plain_name(), group.type.name, group.name, group.acronym))
form = ChangeStateForm(hide=hide, initial=init, group=group)
prev_charter_state = None
@@ -202,9 +201,9 @@ def change_state(request, name, option=None):
info_msg = {}
if group.type_id == "wg":
- info_msg[state_pk("infrev")] = 'The %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name())
- info_msg[state_pk("intrev")] = 'The %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (group.type.name, group.name, group.acronym, login.plain_name())
- info_msg[state_pk("extrev")] = 'The %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name())
+ info_msg[state_pk("infrev")] = 'The %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, by.plain_name())
+ info_msg[state_pk("intrev")] = 'The %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (group.type.name, group.name, group.acronym, by.plain_name())
+ info_msg[state_pk("extrev")] = 'The %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, by.plain_name())
states_for_ballot_wo_extern = State.objects.none()
if group.type_id == "wg":
@@ -213,7 +212,6 @@ def change_state(request, name, option=None):
return render_to_response('doc/charter/change_state.html',
dict(form=form,
doc=group.charter,
- login=login,
option=option,
prev_charter_state=prev_charter_state,
title=title,
@@ -242,7 +240,7 @@ def change_title(request, name, option=None):
group = charter.group
if not can_manage_group_type(request.user, group.type_id):
return HttpResponseForbidden("You don't have permission to access this view")
- login = request.user.person
+ by = request.user.person
if request.method == 'POST':
form = ChangeTitleForm(request.POST, charter=charter)
if form.is_valid():
@@ -253,17 +251,19 @@ def change_title(request, name, option=None):
message = clean['message']
prev_title = charter.title
if new_title != prev_title:
- # Charter title changed
- save_document_in_history(charter)
- charter.title=new_title
+ events = []
+ charter.title = new_title
charter.rev = charter_rev
+
if not comment:
comment = "Changed charter title from '%s' to '%s'." % (prev_title, new_title)
- event = DocEvent(type="added_comment", doc=charter, by=login)
- event.desc = comment
- event.save()
- charter.time = datetime.datetime.now()
- charter.save()
+ e = DocEvent(type="added_comment", doc=charter, by=by)
+ e.desc = comment
+ e.save()
+ events.append(e)
+
+ charter.save_with_history(events)
+
if message:
email_iesg_secretary_re_charter(request, group, "Charter title changed to %s" % new_title, message)
email_state_changed(request, charter, "Title changed to %s." % new_title)
@@ -274,7 +274,6 @@ def change_title(request, name, option=None):
return render_to_response('doc/charter/change_title.html',
dict(form=form,
doc=group.charter,
- login=login,
title=title,
),
context_instance=RequestContext(request))
@@ -297,23 +296,24 @@ def edit_ad(request, name):
"""Change the responsible Area Director for this charter."""
charter = get_object_or_404(Document, type="charter", name=name)
- login = request.user.person
+ by = request.user.person
if request.method == 'POST':
form = AdForm(request.POST)
if form.is_valid():
new_ad = form.cleaned_data['ad']
if new_ad != charter.ad:
- save_document_in_history(charter)
- e = DocEvent(doc=charter, by=login)
+ events = []
+ e = DocEvent(doc=charter, by=by)
e.desc = "Responsible AD changed to %s" % new_ad.plain_name()
if charter.ad:
e.desc += " from %s" % charter.ad.plain_name()
e.type = "changed_document"
e.save()
+ events.append(e)
+
charter.ad = new_ad
- charter.time = e.time
- charter.save()
+ charter.save_with_history(events)
return redirect('doc_view', name=charter.name)
else:
@@ -372,16 +372,17 @@ def submit(request, name=None, option=None):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
- save_document_in_history(charter)
# Also save group history so we can search for it
save_group_in_history(group)
charter.rev = next_rev
+ events = []
e = NewRevisionDocEvent(doc=charter, by=request.user.person, type="new_revision")
e.desc = "New version available: %s-%s.txt" % (charter.canonical_name(), charter.rev)
e.rev = charter.rev
e.save()
+ events.append(e)
# Save file on disk
form.save(group, charter.rev)
@@ -389,8 +390,7 @@ def submit(request, name=None, option=None):
if option in ['initcharter','recharter'] and charter.ad == None:
charter.ad = getattr(group.ad_role(),'person',None)
- charter.time = datetime.datetime.now()
- charter.save()
+ charter.save_with_history(events)
if option:
return redirect('charter_startstop_process', name=charter.name, option=option)
@@ -434,15 +434,15 @@ def announcement_text(request, name, ann):
charter = get_object_or_404(Document, type="charter", name=name)
group = charter.group
- login = request.user.person
+ by = request.user.person
if ann in ("action", "review"):
existing = charter.latest_event(WriteupDocEvent, type="changed_%s_announcement" % ann)
if not existing:
if ann == "action":
- existing = default_action_text(group, charter, login)
+ existing = default_action_text(group, charter, by)
elif ann == "review":
- existing = default_review_text(group, charter, login)
+ existing = default_review_text(group, charter, by)
if not existing:
raise Http404
@@ -454,15 +454,14 @@ def announcement_text(request, name, ann):
if "save_text" in request.POST and form.is_valid():
t = form.cleaned_data['announcement_text']
if t != existing.text:
- e = WriteupDocEvent(doc=charter, by=login)
- e.by = login
+ e = WriteupDocEvent(doc=charter, by=by)
+ e.by = by
e.type = "changed_%s_announcement" % ann
e.desc = "%s %s text was changed" % (group.type.name, ann)
e.text = t
e.save()
- charter.time = e.time
- charter.save()
+ charter.save_with_history([e])
if request.GET.get("next", "") == "approve":
return redirect('charter_approve', name=charter.canonical_name())
@@ -471,9 +470,9 @@ def announcement_text(request, name, ann):
if "regenerate_text" in request.POST:
if ann == "action":
- e = default_action_text(group, charter, login)
+ e = default_action_text(group, charter, by)
elif ann == "review":
- e = default_review_text(group, charter, login)
+ e = default_review_text(group, charter, by)
# make sure form has the updated text
form = AnnouncementTextForm(initial=dict(announcement_text=e.text))
@@ -505,7 +504,7 @@ def ballot_writeupnotes(request, name):
if not ballot:
raise Http404
- login = request.user.person
+ by = request.user.person
approval = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
@@ -522,8 +521,7 @@ def ballot_writeupnotes(request, name):
if form.is_valid():
t = form.cleaned_data["ballot_writeup"]
if t != existing.text:
- e = WriteupDocEvent(doc=charter, by=login)
- e.by = login
+ e = WriteupDocEvent(doc=charter, by=by)
e.type = "changed_ballot_writeup_text"
e.desc = "Ballot writeup was changed"
e.text = t
@@ -532,11 +530,11 @@ def ballot_writeupnotes(request, name):
existing = e
if "send_ballot" in request.POST and approval:
- if has_role(request.user, "Area Director") and not charter.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=login, ballot=ballot):
+ if has_role(request.user, "Area Director") and not charter.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=by, ballot=ballot):
# sending the ballot counts as a yes
- pos = BallotPositionDocEvent(doc=charter, by=login)
+ pos = BallotPositionDocEvent(doc=charter, by=by)
pos.type = "changed_ballot_position"
- pos.ad = login
+ pos.ad = by
pos.pos_id = "yes"
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
pos.save()
@@ -544,8 +542,8 @@ def ballot_writeupnotes(request, name):
msg = generate_issue_ballot_mail(request, charter, ballot)
send_mail_preformatted(request, msg)
- e = DocEvent(doc=charter, by=login)
- e.by = login
+ e = DocEvent(doc=charter, by=by)
+ e.by = by
e.type = "sent_ballot_announcement"
e.desc = "Ballot has been sent"
e.save()
@@ -564,57 +562,17 @@ def ballot_writeupnotes(request, name):
),
context_instance=RequestContext(request))
-def change_group_state_after_charter_approval(group, by):
- new_state = GroupStateName.objects.get(slug="active")
- if group.state == new_state:
- return None
-
- save_group_in_history(group)
- group.state = new_state
- group.time = datetime.datetime.now()
- group.save()
-
- # create an event for the group state change, too
- e = ChangeStateGroupEvent(group=group, type="changed_state")
- e.time = group.time
- e.by = by
- e.state_id = "active"
- e.desc = "Charter approved, group active"
- e.save()
-
- return e
-
-def fix_charter_revision_after_approval(charter, by):
- # according to spec, 00-02 becomes 01, so copy file and record new revision
- try:
- old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), charter.rev))
- new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
- shutil.copy(old, new)
- except IOError:
- return HttpResponse("There was an error copying %s to %s" %
- ('%s-%s.txt' % (charter.canonical_name(), charter.rev),
- '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev))))
-
- e = NewRevisionDocEvent(doc=charter, by=by, type="new_revision")
- e.rev = next_approved_revision(charter.rev)
- e.desc = "New version available: %s-%s.txt" % (charter.canonical_name(), e.rev)
- e.save()
-
- charter.rev = e.rev
- charter.time = e.time
- charter.save()
-
@role_required("Secretariat")
def approve(request, name):
"""Approve charter, changing state, fixing revision, copying file to final location."""
charter = get_object_or_404(Document, type="charter", name=name)
group = charter.group
- login = request.user.person
+ by = request.user.person
e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
if not e:
- announcement = default_action_text(group, charter, login).text
+ announcement = default_action_text(group, charter, by).text
else:
announcement = e.text
@@ -622,26 +580,31 @@ def approve(request, name):
new_charter_state = State.objects.get(used=True, type="charter", slug="approved")
prev_charter_state = charter.get_state()
- save_document_in_history(charter)
charter.set_state(new_charter_state)
- close_open_ballots(charter, login)
+ close_open_ballots(charter, by)
+ events = []
# approve
- e = DocEvent(doc=charter, by=login)
+ e = DocEvent(doc=charter, by=by)
e.type = "iesg_approved"
e.desc = "IESG has approved the charter"
e.save()
+ events.append(e)
change_description = e.desc
- group_state_change_event = change_group_state_after_charter_approval(group, login)
+ group_state_change_event = change_group_state_after_charter_approval(group, by)
if group_state_change_event:
change_description += " and group state has been changed to %s" % group.state.name
- add_state_change_event(charter, login, prev_charter_state, new_charter_state)
+ e = add_state_change_event(charter, by, prev_charter_state, new_charter_state)
+ if e:
+ events.append(e)
- fix_charter_revision_after_approval(charter, login)
+ fix_charter_revision_after_approval(charter, by)
+
+ charter.save_with_history(events)
email_iesg_secretary_re_charter(request, group, "Charter state changed to %s" % new_charter_state.name, change_description)
@@ -664,7 +627,7 @@ def approve(request, name):
o.state_id = "active"
o.save()
MilestoneGroupEvent.objects.create(
- group=group, type="changed_milestone", by=login,
+ group=group, type="changed_milestone", by=by,
desc="Changed milestone \"%s\", set state to active from review" % o.desc,
milestone=o)
@@ -681,7 +644,7 @@ def approve(request, name):
m.save()
MilestoneGroupEvent.objects.create(
- group=group, type="changed_milestone", by=login,
+ group=group, type="changed_milestone", by=by,
desc="Added milestone \"%s\", due %s, from approved charter" % (m.desc, m.due),
milestone=m)
@@ -691,7 +654,7 @@ def approve(request, name):
m.save()
MilestoneGroupEvent.objects.create(
- group=group, type="changed_milestone", by=login,
+ group=group, type="changed_milestone", by=by,
desc="Deleted milestone \"%s\", not present in approved charter" % m.desc,
milestone=m)
diff --git a/ietf/doc/views_conflict_review.py b/ietf/doc/views_conflict_review.py
index 23de7310f..1beca11fe 100644
--- a/ietf/doc/views_conflict_review.py
+++ b/ietf/doc/views_conflict_review.py
@@ -9,7 +9,7 @@ from django.template.loader import render_to_string
from django.conf import settings
from ietf.doc.models import ( BallotDocEvent, BallotPositionDocEvent, DocAlias, DocEvent,
- Document, NewRevisionDocEvent, State, save_document_in_history )
+ Document, NewRevisionDocEvent, State )
from ietf.doc.utils import ( add_state_change_event, close_open_ballots,
create_ballot_if_not_open, get_document_content, update_telechat )
from ietf.doc.mails import email_iana
@@ -47,13 +47,12 @@ def change_state(request, name, option=None):
prev_state = review.get_state()
if new_state != prev_state:
- save_document_in_history(review)
+ events = []
review.set_state(new_state)
- add_state_change_event(review, login, prev_state, new_state)
+ events.append(add_state_change_event(review, login, prev_state, new_state))
- review.time = datetime.datetime.now()
- review.save()
+ review.save_with_history(events)
if new_state.slug == "iesgeval":
create_ballot_if_not_open(review, login, "conflrev")
@@ -151,20 +150,19 @@ def submit(request, name):
if "submit_response" in request.POST:
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
- save_document_in_history(review)
-
review.rev = next_rev
+ events = []
e = NewRevisionDocEvent(doc=review, by=login, type="new_revision")
e.desc = "New version available: %s-%s.txt" % (review.canonical_name(), review.rev)
e.rev = review.rev
e.save()
+ events.append(e)
# Save file on disk
form.save(review)
- review.time = datetime.datetime.now()
- review.save()
+ review.save_with_history(events)
return redirect('doc_view', name=review.name)
@@ -214,15 +212,14 @@ def edit_ad(request, name):
if request.method == 'POST':
form = AdForm(request.POST)
if form.is_valid():
-
review.ad = form.cleaned_data['ad']
- review.save()
-
- login = request.user.person
- c = DocEvent(type="added_comment", doc=review, by=login)
+
+ c = DocEvent(type="added_comment", doc=review, by=request.user.person)
c.desc = "Shepherding AD changed to "+review.ad.name
c.save()
+ review.save_with_history([c])
+
return redirect('doc_view', name=review.name)
else:
@@ -283,13 +280,14 @@ def approve(request, name):
if form.is_valid():
prev_state = review.get_state()
+ events = []
new_state_slug = 'appr-reqnopub-sent' if prev_state.slug == 'appr-reqnopub-pend' else 'appr-noprob-sent'
new_state = State.objects.get(used=True, type="conflrev", slug=new_state_slug)
- save_document_in_history(review)
review.set_state(new_state)
- add_state_change_event(review, login, prev_state, new_state)
+ e = add_state_change_event(review, login, prev_state, new_state)
+ events.append(e)
close_open_ballots(review, login)
@@ -297,9 +295,9 @@ def approve(request, name):
e.type = "iesg_approved"
e.desc = "IESG has approved the conflict review response"
e.save()
+ events.append(e)
- review.time = e.time
- review.save()
+ review.save_with_history(events)
# send announcement
send_mail_preformatted(request, form.cleaned_data['announcement_text'])
@@ -379,16 +377,16 @@ def build_conflict_review_document(login, doc_to_review, ad, notify, create_in_s
iesg_group = Group.objects.get(acronym='iesg')
- conflict_review=Document( type_id = "conflrev",
- title = "IETF conflict review for %s" % doc_to_review.name,
- name = review_name,
- rev = "00",
- ad = ad,
- notify = notify,
- stream_id = 'ietf',
- group = iesg_group,
- )
- conflict_review.save()
+ conflict_review = Document.objects.create(
+ type_id="conflrev",
+ title="IETF conflict review for %s" % doc_to_review.name,
+ name=review_name,
+ rev="00",
+ ad=ad,
+ notify=notify,
+ stream_id='ietf',
+ group=iesg_group,
+ )
conflict_review.set_state(create_in_state)
DocAlias.objects.create( name=review_name , document=conflict_review )
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index d18221f11..0ac328612 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -953,8 +953,7 @@ def edit_notify(request, name):
if set(new_notify.split(',')) != set(doc.notify.split(',')):
e = make_notify_changed_event(request, doc, login.person, new_notify)
doc.notify = new_notify
- doc.time = e.time
- doc.save()
+ doc.save_with_history([e])
return redirect('doc_view', name=doc.name)
elif "regenerate_addresses" in request.POST:
diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py
index 8e3d4dd66..6402c4592 100644
--- a/ietf/doc/views_draft.py
+++ b/ietf/doc/views_draft.py
@@ -16,8 +16,7 @@ from django.contrib import messages
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State,
- StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS,
- save_document_in_history )
+ StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS)
from ietf.doc.mails import ( email_ad, email_pulled_from_rfc_queue, email_resurrect_requested,
email_resurrection_completed, email_state_changed, email_stream_changed,
email_stream_state_changed, email_stream_tags_changed, extra_automation_headers,
@@ -87,17 +86,17 @@ def change_state(request, name):
prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
new_tags = [tag] if tag else []
if new_state != prev_state or set(new_tags) != set(prev_tags):
- save_document_in_history(doc)
-
doc.set_state(new_state)
doc.tags.remove(*prev_tags)
doc.tags.add(*new_tags)
+ events = []
+
e = add_state_change_event(doc, login, prev_state, new_state,
prev_tags=prev_tags, new_tags=new_tags)
- msg = e.desc
+ events.append(e)
if comment:
c = DocEvent(type="added_comment")
@@ -106,10 +105,11 @@ def change_state(request, name):
c.desc = comment
c.save()
- msg += "\n" + comment
-
- doc.time = e.time
- doc.save()
+ events.append(c)
+
+ doc.save_with_history(events)
+
+ msg = u"\n".join(e.desc for e in events)
email_state_changed(request, doc, msg)
email_ad(request, doc, doc.ad, login, msg)
@@ -187,14 +187,11 @@ def change_iana_state(request, name, state_type):
new_state = form.cleaned_data['state']
if new_state != prev_state:
- save_document_in_history(doc)
-
doc.set_state(new_state)
- e = add_state_change_event(doc, request.user.person, prev_state, new_state)
+ events = [add_state_change_event(doc, request.user.person, prev_state, new_state)]
- doc.time = e.time
- doc.save()
+ doc.save_with_history(events)
return HttpResponseRedirect(doc.get_absolute_url())
@@ -243,27 +240,28 @@ def change_stream(request, name):
elif "irsg@irtf.org" not in doc.notify:
doc.notify += ", irsg@irtf.org"
- save_document_in_history(doc)
-
doc.stream = new_stream
doc.group = Group.objects.get(type="individ")
+ events = []
+
e = DocEvent(doc=doc,by=login,type='changed_document')
e.desc = u"Stream changed to %s from %s"% (new_stream, old_stream or "None")
e.save()
- email_desc = e.desc
+ events.append(e)
if comment:
c = DocEvent(doc=doc,by=login,type="added_comment")
c.desc = comment
c.save()
- email_desc += "\n"+c.desc
-
- doc.time = e.time
- doc.save()
+ events.append(c)
- email_stream_changed(request, doc, old_stream, new_stream, email_desc)
+ doc.save_with_history(events)
+
+ msg = u"\n".join(e.desc for e in events)
+
+ email_stream_changed(request, doc, old_stream, new_stream, msg)
return HttpResponseRedirect(doc.get_absolute_url())
@@ -326,16 +324,11 @@ def replaces(request, name):
by = request.user.person
if new_replaces != old_replaces:
- save_document_in_history(doc)
- doc.time = datetime.datetime.now()
- doc.save()
+ events = set_replaces_for_document(request, doc, new_replaces, by=by,
+ email_subject="%s replacement status updated by %s" % (doc.name, by),
+ comment=comment)
- set_replaces_for_document(request, doc, new_replaces, by=by,
- email_subject="%s replacement status updated by %s" % (doc.name, by),
- email_comment=comment)
-
- if comment:
- DocEvent.objects.create(doc=doc, by=by, type="added_comment", desc=comment)
+ doc.save_with_history(events)
return HttpResponseRedirect(doc.get_absolute_url())
else:
@@ -380,22 +373,22 @@ def review_possibly_replaces(request, name):
comment = form.cleaned_data['comment'].strip()
by = request.user.person
- save_document_in_history(doc)
- doc.time = datetime.datetime.now()
- doc.save()
+ events = []
# all suggestions reviewed, so get rid of them
- DocEvent.objects.create(doc=doc, by=by, type="reviewed_suggested_replaces",
- desc="Reviewed suggested replacement relationships: %s" % ", ".join(d.name for d in suggested))
+ events.append(DocEvent.objects.create(doc=doc, by=by, type="reviewed_suggested_replaces",
+ desc="Reviewed suggested replacement relationships: %s" % ", ".join(d.name for d in suggested)))
RelatedDocument.objects.filter(source=doc, target__in=suggested,relationship__slug='possibly-replaces').delete()
if new_replaces != old_replaces:
- set_replaces_for_document(request, doc, new_replaces, by=by,
- email_subject="%s replacement status updated by %s" % (doc.name, by),
- email_comment=comment)
+ events.extend(set_replaces_for_document(request, doc, new_replaces, by=by,
+ email_subject="%s replacement status updated by %s" % (doc.name, by),
+ comment=comment))
if comment:
- DocEvent.objects.create(doc=doc, by=by, type="added_comment", desc=comment)
+ events.append(DocEvent.objects.create(doc=doc, by=by, type="added_comment", desc=comment))
+
+ doc.save_with_history(events)
return HttpResponseRedirect(doc.get_absolute_url())
else:
@@ -432,26 +425,25 @@ def change_intention(request, name):
old_level = doc.intended_std_level
if new_level != old_level:
- save_document_in_history(doc)
-
doc.intended_std_level = new_level
+ events = []
e = DocEvent(doc=doc,by=login,type='changed_document')
e.desc = u"Intended Status changed to %s from %s"% (new_level,old_level)
e.save()
-
- email_desc = e.desc
+ events.append(e)
if comment:
c = DocEvent(doc=doc,by=login,type="added_comment")
c.desc = comment
c.save()
- email_desc += "\n"+c.desc
-
- doc.time = e.time
- doc.save()
+ events.append(c)
- email_ad(request, doc, doc.ad, login, email_desc)
+ doc.save_with_history(events)
+
+ msg = u"\n".join(e.desc for e in events)
+
+ email_ad(request, doc, doc.ad, login, msg)
return HttpResponseRedirect(doc.get_absolute_url())
@@ -538,27 +530,27 @@ def to_iesg(request,name):
if request.POST.get("confirm", ""):
- save_document_in_history(doc)
+ by = request.user.person
- login = request.user.person
+ events = []
changes = []
if not doc.get_state("draft-iesg"):
-
e = DocEvent()
e.type = "started_iesg_process"
- e.by = login
+ e.by = by
e.doc = doc
e.desc = "IESG process started in state %s" % target_state['iesg'].name
e.save()
+ events.append(e)
for state_type in ['draft-iesg','draft-stream-ietf']:
prev_state=doc.get_state(state_type)
new_state = target_state[target_map[state_type]]
if not prev_state==new_state:
doc.set_state(new_state)
- add_state_change_event(doc=doc,by=login,prev_state=prev_state,new_state=new_state)
+ events.append(add_state_change_event(doc=doc,by=by,prev_state=prev_state,new_state=new_state))
if not doc.ad == ad :
doc.ad = ad
@@ -574,23 +566,22 @@ def to_iesg(request,name):
changes.append(previous_writeup.text)
for c in changes:
- e = DocEvent(doc=doc, by=login)
+ e = DocEvent(doc=doc, by=by)
e.desc = c
e.type = "changed_document"
e.save()
+ events.append(e)
- doc.time = datetime.datetime.now()
-
- doc.save()
+ doc.save_with_history(events)
extra = {}
extra['Cc'] = "%s-chairs@ietf.org, iesg-secretary@ietf.org, %s" % (doc.group.acronym,doc.notify)
send_mail(request=request,
to = doc.ad.email_address(),
- frm = login.formatted_email(),
+ frm = by.formatted_email(),
subject = "Publication has been requested for %s-%s" % (doc.name,doc.rev),
template = "doc/submit_to_iesg_email.txt",
- context = dict(doc=doc,login=login,url="%s%s"%(settings.IDTRACKER_BASE_URL,doc.get_absolute_url()),),
+ context = dict(doc=doc,by=by,url="%s%s"%(settings.IDTRACKER_BASE_URL,doc.get_absolute_url()),),
extra = extra)
return HttpResponseRedirect(doc.get_absolute_url())
@@ -614,8 +605,6 @@ def edit_info(request, name):
if doc.get_state_slug() == "expired":
raise Http404
- login = request.user.person
-
new_document = False
if not doc.get_state("draft-iesg"): # FIXME: should probably receive "new document" as argument to view instead of this
new_document = True
@@ -630,27 +619,26 @@ def edit_info(request, name):
initial=dict(ad=doc.ad_id,
telechat_date=initial_telechat_date))
if form.is_valid():
- save_document_in_history(doc)
-
+ by = request.user.person
+
r = form.cleaned_data
+ events = []
+
if new_document:
doc.set_state(r['create_in_state'])
# Is setting the WG state here too much of a hidden side-effect?
if r['create_in_state'].slug=='pub-req':
- if doc.stream and ( doc.stream.slug=='ietf' ) and doc.group and ( doc.group.type.name=='WG'):
+ if doc.stream and doc.stream.slug=='ietf' and doc.group and doc.group.type_id == 'wg':
submitted_state = State.objects.get(type='draft-stream-ietf',slug='sub-pub')
doc.set_state(submitted_state)
e = DocEvent()
e.type = "changed_document"
- e.by = login
+ e.by = by
e.doc = doc
e.desc = "Working group state set to %s" % submitted_state.name
e.save()
-
- # fix so Django doesn't barf in the diff below because these
- # fields can't be NULL
- doc.ad = r['ad']
+ events.append(e)
replaces = Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces")
if replaces:
@@ -662,14 +650,16 @@ def edit_info(request, name):
e.doc = doc
e.desc = "Earlier history may be found in the Comment Log for %s" % (replaces[0], replaces[0].get_absolute_url())
e.save()
+ events.append(e)
e = DocEvent()
e.type = "started_iesg_process"
- e.by = login
+ e.by = by
e.doc = doc
e.desc = "IESG process started in state %s" % doc.get_state("draft-iesg").name
e.save()
-
+ events.append(e)
+
orig_ad = doc.ad
changes = []
@@ -716,20 +706,18 @@ def edit_info(request, name):
doc.group = r["area"]
for c in changes:
- e = DocEvent(doc=doc, by=login)
- e.desc = c
- e.type = "changed_document"
- e.save()
+ events.append(DocEvent.objects.create(doc=doc, by=by, desc=c, type="changed_document"))
- update_telechat(request, doc, login,
- r['telechat_date'], r['returning_item'])
+ e = update_telechat(request, doc, by,
+ r['telechat_date'], r['returning_item'])
+ if e:
+ events.append(e)
- doc.time = datetime.datetime.now()
+ doc.save_with_history(events)
if changes and not new_document:
- email_ad(request, doc, orig_ad, login, "\n".join(changes))
-
- doc.save()
+ email_ad(request, doc, orig_ad, by, "\n".join(changes))
+
return HttpResponseRedirect(doc.get_absolute_url())
else:
init = dict(intended_std_level=doc.intended_std_level_id,
@@ -753,7 +741,6 @@ def edit_info(request, name):
dict(doc=doc,
form=form,
user=request.user,
- login=login,
ballot_issued=doc.latest_event(type="sent_ballot_announcement")),
context_instance=RequestContext(request))
@@ -764,12 +751,12 @@ def request_resurrect(request, name):
if doc.get_state_slug() != "expired":
raise Http404
- login = request.user.person
-
if request.method == 'POST':
- email_resurrect_requested(request, doc, login)
+ by = request.user.person
+
+ email_resurrect_requested(request, doc, by)
- e = DocEvent(doc=doc, by=login)
+ e = DocEvent(doc=doc, by=by)
e.type = "requested_resurrect"
e.desc = "Resurrection was requested"
e.save()
@@ -788,24 +775,22 @@ def resurrect(request, name):
if doc.get_state_slug() != "expired":
raise Http404
- login = request.user.person
-
if request.method == 'POST':
- save_document_in_history(doc)
-
e = doc.latest_event(type__in=('requested_resurrect', "completed_resurrect"))
if e and e.type == 'requested_resurrect':
email_resurrection_completed(request, doc, requester=e.by)
- e = DocEvent(doc=doc, by=login)
+ events = []
+ e = DocEvent(doc=doc, by=request.user.person)
e.type = "completed_resurrect"
e.desc = "Resurrection was completed"
e.save()
-
+ events.append(e)
+
doc.set_state(State.objects.get(used=True, type="draft", slug="active"))
doc.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
- doc.time = datetime.datetime.now()
- doc.save()
+ doc.save_with_history(events)
+
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('doc/draft/resurrect.html',
@@ -817,7 +802,7 @@ class IESGNoteForm(forms.Form):
note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False)
def clean_note(self):
- # not muning the database content to use html line breaks --
+ # not munging the database content to use html line breaks --
# that has caused a lot of pain in the past.
return self.cleaned_data['note'].replace('\r', '').strip()
@@ -843,13 +828,13 @@ def edit_iesg_note(request, name):
else:
log_message = "Note added '%s'" % new_note
- doc.note = new_note
- doc.save()
-
c = DocEvent(type="added_comment", doc=doc, by=login)
c.desc = log_message
c.save()
+ doc.note = new_note
+ doc.save_with_history([c])
+
return redirect('doc_view', name=doc.name)
else:
form = IESGNoteForm(initial=initial)
@@ -956,27 +941,24 @@ def edit_shepherd(request, name):
if form.is_valid():
if form.cleaned_data['shepherd'] != doc.shepherd:
+ events = []
- save_document_in_history(doc)
-
doc.shepherd = form.cleaned_data['shepherd']
- doc.save()
-
+
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
c.desc = "Document shepherd changed to "+ (doc.shepherd.person.name if doc.shepherd else "(None)")
c.save()
+ events.append(c)
if doc.shepherd and (doc.shepherd.formatted_email() not in doc.notify):
- login = request.user.person
addrs = doc.notify
if addrs:
addrs += ', '
addrs += doc.shepherd.formatted_email()
- make_notify_changed_event(request, doc, login, addrs, c.time)
+ events.append(make_notify_changed_event(request, doc, request.user.person, addrs, c.time))
doc.notify = addrs
- doc.time = c.time
- doc.save()
+ doc.save_with_history(events)
else:
messages.info(request,"The selected shepherd was already assigned - no changes have been made.")
@@ -1015,14 +997,15 @@ def change_shepherd_email(request, name):
form = ChangeShepherdEmailForm(request.POST, initial=initial)
if form.is_valid():
if form.cleaned_data['shepherd'] != doc.shepherd:
- save_document_in_history(doc)
-
doc.shepherd = form.cleaned_data['shepherd']
- doc.save()
-
+
+ events = []
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
c.desc = "Document shepherd email changed"
c.save()
+ events.append(c)
+
+ doc.save_with_history(events)
else:
messages.info(request,"The selected shepherd address was already assigned - no changes have been made.")
@@ -1058,15 +1041,14 @@ def edit_ad(request, name):
if request.method == 'POST':
form = AdForm(request.POST)
if form.is_valid():
-
doc.ad = form.cleaned_data['ad']
- doc.save()
-
- login = request.user.person
- c = DocEvent(type="added_comment", doc=doc, by=login)
+
+ c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
c.desc = "Shepherding AD changed to "+doc.ad.name
c.save()
+ doc.save_with_history([c])
+
return redirect('doc_view', name=doc.name)
else:
@@ -1142,6 +1124,8 @@ def request_publication(request, name):
if request.method == 'POST' and not request.POST.get("reset"):
form = PublicationForm(request.POST)
if form.is_valid():
+ events = []
+
if not request.REQUEST.get("skiprfceditorpost"):
# start by notifying the RFC Editor
import ietf.sync.rfceditor
@@ -1170,14 +1154,16 @@ def request_publication(request, name):
e = DocEvent(doc=doc, type="requested_publication", by=request.user.person)
e.desc = "Sent request for publication to the RFC Editor"
e.save()
+ events.append(e)
# change state
prev_state = doc.get_state(next_state.type_id)
if next_state != prev_state:
doc.set_state(next_state)
e = add_state_change_event(doc, request.user.person, prev_state, next_state)
- doc.time = e.time
- doc.save()
+ if e:
+ events.append(e)
+ doc.save_with_history(events)
return redirect('doc_view', name=doc.name)
@@ -1246,10 +1232,7 @@ def adopt_draft(request, name):
if form.is_valid():
# adopt
by = request.user.person
-
- save_document_in_history(doc)
-
- doc.time = datetime.datetime.now()
+ events = []
group = form.cleaned_data["group"]
if group.type.slug == "rg":
@@ -1266,6 +1249,7 @@ def adopt_draft(request, name):
if doc.stream:
e.desc += u" from %s" % doc.stream.name
e.save()
+ events.append(e)
old_stream = doc.stream
doc.stream = new_stream
if old_stream != None:
@@ -1278,14 +1262,13 @@ def adopt_draft(request, name):
if doc.group.type_id != "individ":
e.desc += " from %s (%s)" % (doc.group.name, doc.group.acronym.upper())
e.save()
+ events.append(e)
doc.group = group
new_notify = get_initial_notify(doc,extra=doc.notify)
- make_notify_changed_event(request, doc, by, new_notify, doc.time)
+ events.append(make_notify_changed_event(request, doc, by, new_notify, doc.time))
doc.notify = new_notify
- doc.save()
-
comment = form.cleaned_data["comment"].strip()
# state
@@ -1293,6 +1276,7 @@ def adopt_draft(request, name):
if new_state != prev_state:
doc.set_state(new_state)
e = add_state_change_event(doc, by, prev_state, new_state, timestamp=doc.time)
+ events.append(e)
due_date = None
if form.cleaned_data["weeks"] != None:
@@ -1307,6 +1291,9 @@ def adopt_draft(request, name):
e = DocEvent(type="added_comment", time=doc.time, by=by, doc=doc)
e.desc = comment
e.save()
+ events.append(e)
+
+ doc.save_with_history(events)
return HttpResponseRedirect(doc.get_absolute_url())
else:
@@ -1382,10 +1369,8 @@ def change_stream_state(request, name, state_type):
form = ChangeStreamStateForm(request.POST, doc=doc, state_type=state_type)
if form.is_valid():
by = request.user.person
+ events = []
- save_document_in_history(doc)
-
- doc.time = datetime.datetime.now()
comment = form.cleaned_data["comment"].strip()
# state
@@ -1393,6 +1378,7 @@ def change_stream_state(request, name, state_type):
if new_state != prev_state:
doc.set_state(new_state)
e = add_state_change_event(doc, by, prev_state, new_state, timestamp=doc.time)
+ events.append(e)
due_date = None
if form.cleaned_data["weeks"] != None:
@@ -1419,6 +1405,7 @@ def change_stream_state(request, name, state_type):
l.append(u"Tag%s %s cleared." % (pluralize(removed_tags), ", ".join(t.name for t in removed_tags)))
e.desc = " ".join(l)
e.save()
+ events.append(e)
email_stream_tags_changed(request, doc, added_tags, removed_tags, by, comment)
@@ -1427,6 +1414,9 @@ def change_stream_state(request, name, state_type):
e = DocEvent(type="added_comment", time=doc.time, by=by, doc=doc)
e.desc = comment
e.save()
+ events.append(e)
+
+ doc.save_with_history(events)
return HttpResponseRedirect(doc.get_absolute_url())
else:
diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index c1c5f6c3c..0c8fadbaf 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse as urlreverse
import debug # pyflakes:ignore
from ietf.doc.models import Document, DocAlias, DocTypeName, DocEvent, State
-from ietf.doc.models import NewRevisionDocEvent, save_document_in_history
+from ietf.doc.models import NewRevisionDocEvent
from ietf.doc.utils import add_state_change_event, check_common_doc_name_rules
from ietf.group.models import Group
from ietf.group.utils import can_manage_materials
@@ -104,15 +104,17 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
form = UploadMaterialForm(document_type, action, group, doc, request.POST, request.FILES)
if form.is_valid():
+ events = []
+
if action == "new":
doc = Document()
doc.type = document_type
doc.group = group
doc.rev = "00"
doc.name = form.cleaned_data["name"]
+
prev_rev = None
else:
- save_document_in_history(doc)
prev_rev = doc.rev
prev_title = doc.title
@@ -124,8 +126,6 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
if "abstract" in form.cleaned_data:
doc.abstract = form.cleaned_data["abstract"]
- doc.time = datetime.datetime.now()
-
if "material" in form.fields:
if action != "new":
doc.rev = "%02d" % (int(doc.rev) + 1)
@@ -137,8 +137,6 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
for chunk in f.chunks():
dest.write(chunk)
- doc.save()
-
if action == "new":
DocAlias.objects.get_or_create(name=doc.name, document=doc)
@@ -148,7 +146,8 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
e.by = request.user.person
e.desc = "New version available: %s-%s" % (doc.name, doc.rev)
e.save()
-
+ events.append(e)
+
if prev_title != doc.title:
e = DocEvent(doc=doc, by=request.user.person, type='changed_document')
e.desc = u"Changed title to %s" % doc.title
@@ -156,10 +155,15 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
e.desc += u" from %s" % prev_title
e.time = doc.time
e.save()
+ events.append(e)
if "state" in form.cleaned_data and form.cleaned_data["state"] != prev_state:
doc.set_state(form.cleaned_data["state"])
- add_state_change_event(doc, request.user.person, prev_state, form.cleaned_data["state"])
+ e = add_state_change_event(doc, request.user.person, prev_state, form.cleaned_data["state"])
+ events.append(e)
+
+ if events:
+ doc.save_with_history(events)
return redirect("doc_view", name=doc.name)
else:
diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py
index 39bab4214..bfc3a324a 100644
--- a/ietf/doc/views_status_change.py
+++ b/ietf/doc/views_status_change.py
@@ -9,8 +9,7 @@ from django.template.loader import render_to_string
from django.conf import settings
from ietf.doc.models import ( Document, DocAlias, State, DocEvent, BallotDocEvent,
- BallotPositionDocEvent, NewRevisionDocEvent, WriteupDocEvent,
- save_document_in_history, STATUSCHANGE_RELATIONS )
+ BallotPositionDocEvent, NewRevisionDocEvent, WriteupDocEvent, STATUSCHANGE_RELATIONS )
from ietf.doc.forms import AdForm
from ietf.doc.lastcall import request_last_call
from ietf.doc.utils import get_document_content, add_state_change_event, update_telechat, close_open_ballots, create_ballot_if_not_open
@@ -50,16 +49,13 @@ def change_state(request, name, option=None):
prev_state = status_change.get_state()
if new_state != prev_state:
- save_document_in_history(status_change)
-
status_change.set_state(new_state)
- e = add_state_change_event(status_change, login, prev_state, new_state)
-
- status_change.time = e.time
- status_change.save()
+ events = []
+ events.append(add_state_change_event(status_change, login, prev_state, new_state))
+ status_change.save_with_history(events)
if new_state.slug == "iesgeval":
- create_ballot_if_not_open(status_change, login, "statchg", e.time)
+ create_ballot_if_not_open(status_change, login, "statchg", status_change.time)
ballot = status_change.latest_event(BallotDocEvent, type="created_ballot")
if has_role(request.user, "Area Director") and not status_change.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot, type="changed_ballot_position"):
@@ -146,20 +142,19 @@ def submit(request, name):
if "submit_response" in request.POST:
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
- save_document_in_history(doc)
-
doc.rev = next_rev
+ events = []
e = NewRevisionDocEvent(doc=doc, by=login, type="new_revision")
e.desc = "New version available: %s-%s.txt" % (doc.canonical_name(), doc.rev)
e.rev = doc.rev
e.save()
+ events.append(e)
# Save file on disk
form.save(doc)
- doc.time = datetime.datetime.now()
- doc.save()
+ doc.save_with_history(events)
return redirect('doc_view', name=doc.name)
@@ -213,13 +208,13 @@ def edit_title(request, name):
if form.is_valid():
status_change.title = form.cleaned_data['title']
- status_change.save()
-
- login = request.user.person
- c = DocEvent(type="added_comment", doc=status_change, by=login)
+
+ c = DocEvent(type="added_comment", doc=status_change, by=request.user.person)
c.desc = "Title changed to '%s'"%status_change.title
c.save()
+ status_change.save_with_history([c])
+
return redirect("doc_view", name=status_change.name)
else:
@@ -243,15 +238,14 @@ def edit_ad(request, name):
if request.method == 'POST':
form = AdForm(request.POST)
if form.is_valid():
-
status_change.ad = form.cleaned_data['ad']
- status_change.save()
-
- login = request.user.person
- c = DocEvent(type="added_comment", doc=status_change, by=login)
+
+ c = DocEvent(type="added_comment", doc=status_change, by=request.user.person)
c.desc = "Shepherding AD changed to "+status_change.ad.name
c.save()
+ status_change.save_with_history([c])
+
return redirect("doc_view", name=status_change.name)
else:
@@ -331,13 +325,13 @@ def approve(request, name):
if formset.is_valid():
- save_document_in_history(status_change)
-
prev_state = status_change.get_state()
new_state = State.objects.get(type='statchg', slug='appr-sent')
status_change.set_state(new_state)
- add_state_change_event(status_change, login, prev_state, new_state)
+
+ events = []
+ events.append(add_state_change_event(status_change, login, prev_state, new_state))
close_open_ballots(status_change, login)
@@ -345,9 +339,9 @@ def approve(request, name):
e.type = "iesg_approved"
e.desc = "IESG has approved the status change"
e.save()
+ events.append(e)
- status_change.time = e.time
- status_change.save()
+ status_change.save_with_history(events)
for form in formset.forms:
@@ -516,16 +510,16 @@ def start_rfc_status_change(request,name):
iesg_group = Group.objects.get(acronym='iesg')
- status_change=Document( type_id = "statchg",
- name = 'status-change-'+form.cleaned_data['document_name'],
- title = form.cleaned_data['title'],
- rev = "00",
- ad = form.cleaned_data['ad'],
- notify = form.cleaned_data['notify'],
- stream_id = 'ietf',
- group = iesg_group,
- )
- status_change.save()
+ status_change = Document.objects.create(
+ type_id="statchg",
+ name='status-change-'+form.cleaned_data['document_name'],
+ title=form.cleaned_data['title'],
+ rev="00",
+ ad=form.cleaned_data['ad'],
+ notify=form.cleaned_data['notify'],
+ stream_id='ietf',
+ group=iesg_group,
+ )
status_change.set_state(form.cleaned_data['create_in_state'])
DocAlias.objects.create( name= 'status-change-'+form.cleaned_data['document_name'], document=status_change )
@@ -654,6 +648,8 @@ def last_call(request, name):
if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST:
form = LastCallTextForm(request.POST)
if form.is_valid():
+ events = []
+
t = form.cleaned_data['last_call_text']
if t != last_call_event.text:
e = WriteupDocEvent(doc=status_change, by=login)
@@ -663,17 +659,19 @@ def last_call(request, name):
e.text = t
e.save()
- if "send_last_call_request" in request.POST:
- save_document_in_history(status_change)
+ events.append(e)
+ if "send_last_call_request" in request.POST:
prev_state = status_change.get_state()
new_state = State.objects.get(type='statchg', slug='lc-req')
status_change.set_state(new_state)
e = add_state_change_event(status_change, login, prev_state, new_state)
+ if e:
+ events.append(e)
- status_change.time = (e and e.time) or datetime.datetime.now()
- status_change.save()
+ if events:
+ status_change.save_with_history(events)
request_last_call(request, status_change)
diff --git a/ietf/group/edit.py b/ietf/group/edit.py
index e4a40aea9..5133c3d4d 100644
--- a/ietf/group/edit.py
+++ b/ietf/group/edit.py
@@ -150,7 +150,7 @@ def get_or_create_initial_charter(group, group_type):
try:
charter = Document.objects.get(docalias__name=charter_name)
except Document.DoesNotExist:
- charter = Document(
+ charter = Document.objects.create(
name=charter_name,
type_id="charter",
title=group.name,
@@ -158,7 +158,6 @@ def get_or_create_initial_charter(group, group_type):
abstract=group.name,
rev="00-00",
)
- charter.save()
charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
# Create an alias as well
diff --git a/ietf/group/tests.py b/ietf/group/tests.py
index 36f9fdd48..d7bd00615 100644
--- a/ietf/group/tests.py
+++ b/ietf/group/tests.py
@@ -6,7 +6,9 @@ from django.core.urlresolvers import reverse as urlreverse
from django.db.models import Q
from django.test import Client
+from ietf.doc.models import DocEvent
from ietf.group.models import Role, Group
+from ietf.person.models import Person
from ietf.utils.test_data import make_test_data
from ietf.utils.test_utils import login_testing_unauthorized, TestCase
@@ -33,7 +35,7 @@ class StreamTests(TestCase):
def test_stream_documents(self):
draft = make_test_data()
draft.stream_id = "iab"
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.get(urlreverse("ietf.group.views_stream.stream_documents", kwargs=dict(acronym="iab")))
self.assertEqual(r.status_code, 200)
diff --git a/ietf/iesg/tests.py b/ietf/iesg/tests.py
index 295c00c67..a9d573714 100644
--- a/ietf/iesg/tests.py
+++ b/ietf/iesg/tests.py
@@ -71,7 +71,7 @@ class IESGAgendaTests(TestCase):
ise_draft = Document.objects.get(name="draft-imaginary-independent-submission")
ise_draft.stream = StreamName.objects.get(slug="ise")
- ise_draft.save()
+ ise_draft.save_with_history([DocEvent.objects.create(doc=ise_draft, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
self.telechat_docs = {
"ietf_draft": Document.objects.get(name="draft-ietf-mars-test"),
@@ -120,7 +120,7 @@ class IESGAgendaTests(TestCase):
# 2.1 protocol WG submissions
draft.intended_std_level_id = "ps"
draft.group = Group.objects.get(acronym="mars")
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
draft.set_state(State.objects.get(type="draft-iesg", slug="iesg-eva"))
self.assertTrue(draft in agenda_data(date_str)["sections"]["2.1.1"]["docs"])
@@ -135,7 +135,7 @@ class IESGAgendaTests(TestCase):
# 2.2 protocol individual submissions
draft.group = Group.objects.get(type="individ")
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
draft.set_state(State.objects.get(type="draft-iesg", slug="iesg-eva"))
self.assertTrue(draft in agenda_data(date_str)["sections"]["2.2.1"]["docs"])
@@ -151,7 +151,7 @@ class IESGAgendaTests(TestCase):
# 3.1 document WG submissions
draft.intended_std_level_id = "inf"
draft.group = Group.objects.get(acronym="mars")
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
draft.set_state(State.objects.get(type="draft-iesg", slug="iesg-eva"))
self.assertTrue(draft in agenda_data(date_str)["sections"]["3.1.1"]["docs"])
@@ -166,7 +166,7 @@ class IESGAgendaTests(TestCase):
# 3.2 document individual submissions
draft.group = Group.objects.get(type="individ")
- draft.save()
+ draft.save_with_history([DocEvent.objects.create(doc=draft, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
draft.set_state(State.objects.get(type="draft-iesg", slug="iesg-eva"))
self.assertTrue(draft in agenda_data(date_str)["sections"]["3.2.1"]["docs"])
@@ -189,7 +189,7 @@ class IESGAgendaTests(TestCase):
relationship_id="tohist")
statchg.group = Group.objects.get(acronym="mars")
- statchg.save()
+ statchg.save_with_history([DocEvent.objects.create(doc=statchg, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
statchg.set_state(State.objects.get(type="statchg", slug="iesgeval"))
self.assertTrue(statchg in agenda_data(date_str)["sections"]["2.3.1"]["docs"])
@@ -207,7 +207,7 @@ class IESGAgendaTests(TestCase):
relation.save()
statchg.group = Group.objects.get(acronym="mars")
- statchg.save()
+ statchg.save_with_history([DocEvent.objects.create(doc=statchg, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
statchg.set_state(State.objects.get(type="statchg", slug="iesgeval"))
self.assertTrue(statchg in agenda_data(date_str)["sections"]["3.3.1"]["docs"])
@@ -225,7 +225,7 @@ class IESGAgendaTests(TestCase):
telechat_event.save()
conflrev.group = Group.objects.get(acronym="mars")
- conflrev.save()
+ conflrev.save_with_history([DocEvent.objects.create(doc=conflrev, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
conflrev.set_state(State.objects.get(type="conflrev", slug="iesgeval"))
self.assertTrue(conflrev in agenda_data(date_str)["sections"]["3.4.1"]["docs"])
@@ -244,7 +244,7 @@ class IESGAgendaTests(TestCase):
telechat_event.save()
charter.group = Group.objects.get(acronym="mars")
- charter.save()
+ charter.save_with_history([DocEvent.objects.create(doc=charter, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
charter.group.state_id = "bof"
charter.group.save()
diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py
index 6fbd07cc2..97496e46d 100644
--- a/ietf/iesg/views.py
+++ b/ietf/iesg/views.py
@@ -327,11 +327,11 @@ def handle_reschedule_form(request, doc, dates, status):
if request.method == 'POST':
form = RescheduleForm(request.POST, **formargs)
if form.is_valid():
- update_telechat(request, doc, request.user.person,
- form.cleaned_data['telechat_date'],
- False if form.cleaned_data['clear_returning_item'] else None)
- doc.time = datetime.datetime.now()
- doc.save()
+ e = update_telechat(request, doc, request.user.person,
+ form.cleaned_data['telechat_date'],
+ False if form.cleaned_data['clear_returning_item'] else None)
+ if e:
+ doc.save_with_history([e])
status["changed"] = True
else:
diff --git a/ietf/secr/drafts/forms.py b/ietf/secr/drafts/forms.py
index 5ba5dd247..228f98bfb 100644
--- a/ietf/secr/drafts/forms.py
+++ b/ietf/secr/drafts/forms.py
@@ -155,7 +155,7 @@ class EditModelForm(forms.ModelForm):
# setup replaced
self.fields['review_by_rfc_editor'].initial = bool(self.instance.tags.filter(slug='rfc-rev'))
- def save(self, force_insert=False, force_update=False, commit=True):
+ def save(self, commit=False):
m = super(EditModelForm, self).save(commit=False)
state = self.cleaned_data['state']
iesg_state = self.cleaned_data['iesg_state']
@@ -176,11 +176,8 @@ class EditModelForm(forms.ModelForm):
else:
m.tags.remove('rfc-rev')
- m.time = datetime.datetime.now()
# handle replaced by
- if commit:
- m.save()
return m
# field must contain filename of existing draft
@@ -274,14 +271,12 @@ class RfcModelForm(forms.ModelForm):
self.fields['title'].widget = forms.Textarea()
self.fields['std_level'].required = True
- def save(self, force_insert=False, force_update=False, commit=True):
+ def save(self, force_insert=False, force_update=False, commit=False):
obj = super(RfcModelForm, self).save(commit=False)
# create DocAlias
DocAlias.objects.create(document=self.instance,name="rfc%d" % self.cleaned_data['rfc_number'])
- if commit:
- obj.save()
return obj
def clean_rfc_number(self):
diff --git a/ietf/secr/drafts/views.py b/ietf/secr/drafts/views.py
index ee0b9c146..39f0d621b 100644
--- a/ietf/secr/drafts/views.py
+++ b/ietf/secr/drafts/views.py
@@ -14,7 +14,7 @@ from django.template.loader import render_to_string
#from email import *
from ietf.doc.models import Document, DocumentAuthor, DocAlias, DocRelationshipName, RelatedDocument, State
from ietf.doc.models import DocEvent, NewRevisionDocEvent
-from ietf.doc.models import save_document_in_history
+from ietf.doc.utils import add_state_change_event
from ietf.ietfauth.utils import role_required
from ietf.meeting.models import Meeting
from ietf.meeting.helpers import get_meeting
@@ -176,18 +176,17 @@ def do_extend(draft, request):
- update revision_date
- set extension_date
'''
- save_document_in_history(draft)
+ e = DocEvent.objects.create(
+ type='changed_document',
+ by=request.user.person,
+ doc=draft,
+ time=draft.time,
+ desc='Extended expiry',
+ )
draft.expires = request.session['data']['expiration_date']
- draft.time = datetime.datetime.now()
- draft.save()
-
- DocEvent.objects.create(type='changed_document',
- by=request.user.person,
- doc=draft,
- time=draft.time,
- desc='extend_expiry')
-
+ draft.save_with_history([e])
+
# save scheduled announcement
announcement_from_form(request.session['email'],by=request.user.person)
@@ -196,24 +195,28 @@ def do_extend(draft, request):
def do_replace(draft, request):
'Perform document replace'
- save_document_in_history(draft)
-
replaced = request.session['data']['replaced'] # a DocAlias
replaced_by = request.session['data']['replaced_by'] # a Document
- # change state and update last modified
- draft.set_state(State.objects.get(type="draft", slug="repl"))
- draft.time = datetime.datetime.now()
- draft.save()
-
# create relationship
RelatedDocument.objects.create(source=replaced_by,
target=replaced,
relationship=DocRelationshipName.objects.get(slug='replaces'))
- # create DocEvent
- # no replace DocEvent at this time, Jan 2012
-
+
+
+ draft.set_state(State.objects.get(type="draft", slug="repl"))
+
+ e = DocEvent.objects.create(
+ type='changed_document',
+ by=request.user.person,
+ doc=replaced_by,
+ time=draft.time,
+ desc='This document now replaces %s' % request.session['data']['replaced'],
+ )
+
+ draft.save_with_history([e])
+
# move replaced document to archive
archive_draft_files(replaced.document.name + '-' + replaced.document.rev)
@@ -243,16 +246,16 @@ def do_resurrect(draft, request):
# set expires
draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
- draft.time = datetime.datetime.now()
- draft.save()
# create DocEvent
- NewRevisionDocEvent.objects.create(type='completed_resurrect',
- by=request.user.person,
- doc=draft,
- rev=draft.rev,
- time=draft.time)
+ e = NewRevisionDocEvent.objects.create(type='completed_resurrect',
+ by=request.user.person,
+ doc=draft,
+ rev=draft.rev,
+ time=draft.time)
+ draft.save_with_history([e])
+
# send announcement
announcement_from_form(request.session['email'],by=request.user.person)
@@ -278,12 +281,10 @@ def do_revision(draft, request):
# TODO this behavior may change with archive strategy
archive_draft_files(draft.name + '-' + draft.rev)
- save_document_in_history(draft)
-
# save form data
form = BaseRevisionModelForm(request.session['data'],instance=draft)
if form.is_valid():
- new_draft = form.save()
+ new_draft = form.save(commit=False)
else:
raise Exception(form.errors)
raise Exception('Problem with input data %s' % form.data)
@@ -291,16 +292,16 @@ def do_revision(draft, request):
# set revision and expires
new_draft.rev = request.session['filename'][-2:]
new_draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
- new_draft.time = datetime.datetime.now()
- new_draft.save()
-
+
# create DocEvent
- NewRevisionDocEvent.objects.create(type='new_revision',
- by=request.user.person,
- doc=draft,
- rev=new_draft.rev,
- desc='New revision available',
- time=draft.time)
+ e = NewRevisionDocEvent.objects.create(type='new_revision',
+ by=request.user.person,
+ doc=draft,
+ rev=new_draft.rev,
+ desc='New revision available',
+ time=draft.time)
+
+ new_draft.save_with_history([e])
handle_substate(new_draft)
@@ -325,12 +326,10 @@ def do_update(draft,request):
- do substate check
- change state to Active
'''
- save_document_in_history(draft)
-
# save form data
form = BaseRevisionModelForm(request.session['data'],instance=draft)
if form.is_valid():
- new_draft = form.save()
+ new_draft = form.save(commit=False)
else:
raise Exception('Problem with input data %s' % form.data)
@@ -339,19 +338,19 @@ def do_update(draft,request):
# update draft record
new_draft.rev = os.path.splitext(request.session['data']['filename'])[0][-2:]
new_draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
- new_draft.time = datetime.datetime.now()
- new_draft.save()
-
+
new_draft.set_state(State.objects.get(type="draft", slug="active"))
# create DocEvent
- NewRevisionDocEvent.objects.create(type='new_revision',
- by=request.user.person,
- doc=new_draft,
- rev=new_draft.rev,
- desc='New revision available',
- time=new_draft.time)
+ e = NewRevisionDocEvent.objects.create(type='new_revision',
+ by=request.user.person,
+ doc=new_draft,
+ rev=new_draft.rev,
+ desc='New revision available',
+ time=new_draft.time)
+ new_draft.save_with_history([e])
+
# move uploaded files to production directory
promote_files(new_draft, request.session['file_type'])
@@ -370,15 +369,22 @@ def do_withdraw(draft,request):
- TODO move file to archive
'''
withdraw_type = request.session['data']['type']
+
+ prev_state = draft.get_state("draft")
+ new_state = None
if withdraw_type == 'ietf':
- draft.set_state(State.objects.get(type="draft", slug="ietf-rm"))
+ new_state = State.objects.get(type="draft", slug="ietf-rm")
elif withdraw_type == 'author':
- draft.set_state(State.objects.get(type="draft", slug="auth-rm"))
-
- draft.time = datetime.datetime.now()
- draft.save()
-
- # no DocEvent ?
+ new_state = State.objects.get(type="draft", slug="auth-rm")
+
+ if not new_state:
+ return
+
+ draft.set_state(new_state)
+
+ e = add_state_change_event(draft, request.user.person, prev_state, new_state)
+ if e:
+ draft.save_with_history([e])
# send announcement
announcement_from_form(request.session['email'],by=request.user.person)
@@ -538,8 +544,7 @@ def add(request):
draft.rev = revision
draft.name = name
draft.type_id = 'draft'
- draft.time = datetime.datetime.now()
-
+
# set stream based on document name
if not draft.stream:
stream_slug = None
@@ -556,7 +561,7 @@ def add(request):
# set expires
draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
- draft.save()
+ draft.save(force_insert=True)
# set state
draft.set_state(State.objects.get(type="draft", slug="active"))
@@ -792,13 +797,13 @@ def edit(request, id):
form = EditModelForm(request.POST, instance=draft)
if form.is_valid():
if form.changed_data:
- save_document_in_history(draft)
- DocEvent.objects.create(type='changed_document',
- by=request.user.person,
- doc=draft,
- desc='Changed field(s): %s' % ','.join(form.changed_data))
+ e = DocEvent.objects.create(type='changed_document',
+ by=request.user.person,
+ doc=draft,
+ desc='Changed field(s): %s' % ','.join(form.changed_data))
# see EditModelForm.save() for detailed logic
- form.save()
+ form.save(commit=False)
+ draft.save_with_history([e])
messages.success(request, 'Draft modified successfully!')
@@ -924,16 +929,16 @@ def makerfc(request, id):
if form.is_valid() and obs_formset.is_valid():
# TODO
- save_document_in_history(draft)
archive_draft_files(draft.name + '-' + draft.rev)
- rfc = form.save()
+ rfc = form.save(commit=False)
# create DocEvent
- DocEvent.objects.create(type='published_rfc',
- by=request.user.person,
- doc=rfc)
-
+ e = DocEvent.objects.create(type='published_rfc',
+ by=request.user.person,
+ doc=rfc,
+ desc="Published RFC")
+
# change state
draft.set_state(State.objects.get(type="draft", slug="rfc"))
@@ -950,7 +955,9 @@ def makerfc(request, id):
RelatedDocument.objects.create(source=draft,
target=target,
relationship=DocRelationshipName.objects.get(slug=relation))
-
+
+ rfc.save_with_history([e])
+
messages.success(request, 'RFC created successfully!')
return redirect('drafts_view', id=id)
else:
diff --git a/ietf/secr/proceedings/views.py b/ietf/secr/proceedings/views.py
index d25bc5a2f..78bc44002 100644
--- a/ietf/secr/proceedings/views.py
+++ b/ietf/secr/proceedings/views.py
@@ -25,6 +25,7 @@ from ietf.secr.utils.group import get_my_groups, groups_by_session
from ietf.secr.utils.meeting import get_upload_root, get_materials, get_timeslot, get_proceedings_path, get_proceedings_url
from ietf.doc.models import Document, DocAlias, DocEvent, State, NewRevisionDocEvent
from ietf.group.models import Group
+from ietf.person.models import Person
from ietf.ietfauth.utils import has_role, role_required
from ietf.meeting.models import Meeting, Session, TimeSlot, ScheduledSession
from ietf.secr.proceedings.forms import EditSlideForm, InterimMeetingForm, RecordingForm, RecordingEditForm, ReplaceSlideForm, UnifiedUploadForm
@@ -212,7 +213,14 @@ def post_process(doc):
# change extension
base,ext = os.path.splitext(doc.external_url)
doc.external_url = base + '.pdf'
- doc.save()
+
+ e = DocEvent.objects.create(
+ type='changed_document',
+ by=Person.objects.get(name="(System)"),
+ doc=doc,
+ desc='Converted document to PDF',
+ )
+ doc.save_with_history([e])
# -------------------------------------------------
# AJAX Functions
@@ -362,7 +370,8 @@ def delete_material(request,slide_id):
# create deleted_document
DocEvent.objects.create(doc=doc,
by=request.user.person,
- type='deleted')
+ type='deleted',
+ desc="State set to deleted")
create_proceedings(meeting,group)
@@ -582,7 +591,13 @@ def process_pdfs(request, meeting_num):
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting_num,'slides',pdf_file)
if os.path.exists(path):
doc.external_url = pdf_file
- doc.save()
+ e = DocEvent.objects.create(
+ type='changed_document',
+ by=Person.objects.get(name="(System)"),
+ doc=doc,
+ desc='Set URL to PDF version',
+ )
+ doc.save_with_history([e])
count += 1
else:
warn_count += 1
@@ -658,7 +673,15 @@ def recording_edit(request, meeting_num, name):
form = RecordingEditForm(request.POST, instance=recording)
if form.is_valid():
# save record and rebuild proceedings
- form.save()
+ form.save(commit=False)
+ e = DocEvent.objects.create(
+ type='changed_document',
+ by=request.user.person,
+ doc=recording,
+ desc=u'Changed URL to %s' % recording.external_url,
+ )
+ recording.save_with_history([e])
+
create_proceedings(meeting,recording.group)
messages.success(request,'Recording saved')
return redirect('proceedings_recording', meeting_num=meeting_num)
@@ -704,13 +727,15 @@ def replace_slide(request, slide_id):
handle_upload_file(file,disk_filename,meeting,'slides')
new_slide.external_url = disk_filename
- new_slide.save()
- post_process(new_slide)
-
+
# create DocEvent uploaded
- DocEvent.objects.create(doc=slide,
- by=request.user.person,
- type='uploaded')
+ e = DocEvent.objects.create(doc=slide,
+ by=request.user.person,
+ type='uploaded',
+ desc="Uploaded")
+ new_slide.save_with_history([e])
+
+ post_process(new_slide)
# rebuild proceedings.html
create_proceedings(meeting,group)
@@ -936,7 +961,6 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
doc.title = doc.name
else:
doc.title = '%s for %s at %s' % (material_type.slug.capitalize(), group.acronym.upper(), meeting)
- doc.save()
DocAlias.objects.get_or_create(name=doc.name, document=doc)
@@ -960,12 +984,13 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
s.sessionpresentation_set.create(document=doc,rev=doc.rev)
# create NewRevisionDocEvent instead of uploaded, per Ole
- NewRevisionDocEvent.objects.create(type='new_revision',
- by=request.user.person,
- doc=doc,
- rev=doc.rev,
- desc='New revision available',
- time=now)
+ e = NewRevisionDocEvent.objects.create(type='new_revision',
+ by=request.user.person,
+ doc=doc,
+ rev=doc.rev,
+ desc='New revision available')
+
+ doc.save_with_history([e])
post_process(doc)
create_proceedings(meeting,group)
diff --git a/ietf/secr/telechat/views.py b/ietf/secr/telechat/views.py
index c3f2be7df..750daae51 100644
--- a/ietf/secr/telechat/views.py
+++ b/ietf/secr/telechat/views.py
@@ -6,7 +6,7 @@ from django.forms.formsets import formset_factory
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template import RequestContext
-from ietf.doc.models import DocEvent, Document, BallotDocEvent, BallotPositionDocEvent, WriteupDocEvent, save_document_in_history
+from ietf.doc.models import DocEvent, Document, BallotDocEvent, BallotPositionDocEvent, WriteupDocEvent
from ietf.doc.utils import get_document_content, add_state_change_event
from ietf.person.models import Person
from ietf.doc.lastcall import request_last_call
@@ -247,8 +247,6 @@ def doc_detail(request, date, name):
new_tags = [tag] if tag else []
if state_form.changed_data:
- save_document_in_history(doc)
-
if 'state' in state_form.changed_data:
doc.set_state(new_state)
@@ -258,8 +256,8 @@ def doc_detail(request, date, name):
e = add_state_change_event(doc, login, prev_state, new_state,
prev_tags=prev_tags, new_tags=new_tags)
- doc.time = (e and e.time) or datetime.datetime.now()
- doc.save()
+ if e:
+ doc.save_with_history([e])
email_state_changed(request, doc, e.desc)
email_ad(request, doc, doc.ad, login, e.desc)
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 3772ecfca..72548a8fd 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_ad, 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,16 +201,15 @@ 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))
email_ad(None, doc, doc.ad, system, "IANA %s state changed to %s" % (kind, state.name))
- 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 32ecc3838..0772996dc 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 c84a04109..108af7457 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -325,12 +325,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))
@@ -348,7 +351,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/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/utils/test_data.py b/ietf/utils/test_data.py
index 4a5790765..20dce0c1a 100644
--- a/ietf/utils/test_data.py
+++ b/ietf/utils/test_data.py
@@ -288,10 +288,8 @@ def make_test_data():
DocAlias.objects.create(name=doc.name, document=doc)
# an irtf submission mid review
- doc = Document.objects.create(name='draft-imaginary-irtf-submission', type_id='draft',rev='00')
+ doc = Document.objects.create(name='draft-imaginary-irtf-submission', type_id='draft',rev='00', stream=StreamName.objects.get(slug='irtf'))
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)
@@ -302,15 +300,12 @@ def make_test_data():
iesg = Group.objects.get(acronym='iesg')
doc = Document.objects.create(name='status-change-imaginary-mid-review',type_id='statchg', rev='00', notify="fsm@ietf.org",group=iesg)
doc.set_state(State.objects.get(slug='needshep',type__slug='statchg'))
- doc.save()
docalias = DocAlias.objects.create(name='status-change-imaginary-mid-review',document=doc)
# Some things for a status change to affect
def rfc_for_status_change_test_factory(name,rfc_num,std_level_id):
- target_rfc = Document.objects.create(name=name, type_id='draft', std_level_id=std_level_id)
+ target_rfc = Document.objects.create(name=name, type_id='draft', std_level_id=std_level_id, notify="%s@ietf.org"%name)
target_rfc.set_state(State.objects.get(slug='rfc',type__slug='draft'))
- target_rfc.notify = "%s@ietf.org"%name
- target_rfc.save()
docalias = DocAlias.objects.create(name=name,document=target_rfc)
docalias = DocAlias.objects.create(name='rfc%d'%rfc_num,document=target_rfc) # pyflakes:ignore
return target_rfc
From c39c4ae5ccaabea3886cd568744dc2f68aad41c1 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 29 Sep 2015 07:58:28 +0000
Subject: [PATCH 03/30] Summary: Use double-quotes " instead of ' in src attrs
to not confuse the test crawler (which is slightly dumb) - Legacy-Id: 10103
---
ietf/secr/templates/proceedings/select.html | 2 +-
ietf/secr/templates/proceedings/wait.html | 2 +-
ietf/secr/templates/roles/main.html | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/ietf/secr/templates/proceedings/select.html b/ietf/secr/templates/proceedings/select.html
index a56a16aa2..a22e66eaa 100755
--- a/ietf/secr/templates/proceedings/select.html
+++ b/ietf/secr/templates/proceedings/select.html
@@ -13,7 +13,7 @@
});
$('#generate-button').click(function(){
- var ajax_load = "
Generating Proceedings. This may take a few minutes.
";
+ 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);
From fd87b22030ef645e9f9857b3740690220bac6501 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 29 Sep 2015 08:51:42 +0000
Subject: [PATCH 04/30] Summary: Fix missing u in front of a format string
(causes Unicode woes) - Legacy-Id: 10104
---
ietf/ipr/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py
index 0058ca6c5..a3aeca35b 100644
--- a/ietf/ipr/views.py
+++ b/ietf/ipr/views.py
@@ -380,7 +380,7 @@ def email(request, id):
initial = {
'to': ipr.submitter_email,
'frm': settings.IPR_EMAIL_TO,
- 'subject': 'Regarding {}'.format(ipr.title),
+ 'subject': u'Regarding {}'.format(ipr.title),
'reply_to': reply_to,
}
form = MessageModelForm(initial=initial)
From 07f867e6220a8015fce9dc0d8b5e1bb7451333eb Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 29 Sep 2015 10:00:53 +0000
Subject: [PATCH 05/30] Summary: Enable the state help page for IRTF stream
states (was causing 404s) - Legacy-Id: 10105
---
ietf/doc/views_help.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/ietf/doc/views_help.py b/ietf/doc/views_help.py
index 1a8d8cb34..224ab158c 100644
--- a/ietf/doc/views_help.py
+++ b/ietf/doc/views_help.py
@@ -12,6 +12,7 @@ def state_help(request, type):
"draft-rfceditor": ("draft-rfceditor", "RFC Editor States for Internet-Drafts"),
"draft-iana-action": ("draft-iana-action", "IANA Action States for Internet-Drafts"),
"draft-stream-ietf": ("draft-stream-ietf", "IETF Stream States for Internet-Drafts"),
+ "draft-stream-irtf": ("draft-stream-irtf", "IRTF Stream States for Internet-Drafts"),
"charter": ("charter", "Charter States"),
"conflict-review": ("conflrev", "Conflict Review States"),
"status-change": ("statchg", "RFC Status Change States"),
From e91c97e7ff3f79daaeb39d206ed3268e61b459fd Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 29 Sep 2015 12:55:54 +0000
Subject: [PATCH 06/30] Summary: Save doc state in history in delete_material
- Legacy-Id: 10106
---
ietf/secr/proceedings/views.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/ietf/secr/proceedings/views.py b/ietf/secr/proceedings/views.py
index 78bc44002..048ab4b47 100644
--- a/ietf/secr/proceedings/views.py
+++ b/ietf/secr/proceedings/views.py
@@ -368,10 +368,12 @@ def delete_material(request,slide_id):
doc.set_state(state)
# create deleted_document
- DocEvent.objects.create(doc=doc,
- by=request.user.person,
- type='deleted',
- desc="State set to deleted")
+ e = DocEvent.objects.create(doc=doc,
+ by=request.user.person,
+ type='deleted',
+ desc="State set to deleted")
+
+ doc.save_with_history([e])
create_proceedings(meeting,group)
From 5e4645d7d2a93c265434bb554e72d6468fc538a4 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 29 Sep 2015 13:21:24 +0000
Subject: [PATCH 07/30] Summary: Trim the test-crawl imports - Legacy-Id:
10107
---
bin/test-crawl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bin/test-crawl b/bin/test-crawl
index b4cea6417..3d5ba5a81 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
From 51e96e3c128c582ec03f4bdda7db3eac4da185ef Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Wed, 30 Sep 2015 12:34:06 +0000
Subject: [PATCH 08/30] Summary: Add rel="nofollow" to logout link in
Secretariat tools - Legacy-Id: 10110
---
ietf/secr/templates/base_site.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ietf/secr/templates/base_site.html b/ietf/secr/templates/base_site.html
index 207ade7b6..189269793 100644
--- a/ietf/secr/templates/base_site.html
+++ b/ietf/secr/templates/base_site.html
@@ -13,7 +13,7 @@
- {% if user|has_role:"Secretariat" %}Secretariat {% endif %}Logged in: {{ user }} | Log out
+ {% if user|has_role:"Secretariat" %}Secretariat {% endif %}Logged in: {{ user }} | Log out