From 589ff698ee958a2ef271c84f65d9c9da1a3eb8fd Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 24 Jan 2013 21:28:27 +0000 Subject: [PATCH 1/6] Merge forward complete - Legacy-Id: 5328 --- ietf/doc/forms.py | 37 ++ ietf/doc/migrations/0005_add_statchg.py | 427 +++++++++++++ ietf/doc/models.py | 3 +- ietf/doc/tests.py | 1 + ietf/doc/tests_status_change.py | 392 ++++++++++++ ietf/doc/urls_status_change.py | 12 + ietf/doc/views_conflict_review.py | 82 +-- ietf/doc/views_status_change.py | 596 ++++++++++++++++++ ietf/idrfc/urls.py | 5 + ietf/idrfc/views_doc.py | 31 +- ietf/name/admin.py | 5 +- ietf/name/fixtures/names.xml | 246 +++++++- ietf/name/migrations/0005_add_newstat.py | 157 +++++ .../migrations/0006_add_revname_column.py | 161 +++++ .../migrations/0007_reverse_relation_names.py | 177 ++++++ ietf/name/models.py | 3 + ietf/settings.py | 2 + ietf/templates/base_leftmenu.html | 3 + .../doc/{conflict_review => }/change_ad.html | 6 +- .../{conflict_review => }/change_state.html | 2 +- .../edit_telechat_date.html | 0 .../doc/{conflict_review => }/eval_email.txt | 4 +- .../doc/{conflict_review => }/notify.html | 6 +- .../doc/status_change/approval_text.txt | 25 + ietf/templates/doc/status_change/approve.html | 41 ++ .../doc/status_change/edit_relations.html | 73 +++ .../doc/status_change/initial_template.txt | 17 + ietf/templates/doc/status_change/start.html | 89 +++ .../status-change-edit-relations-js.html | 78 +++ .../doc/status_change/status_changes.html | 23 + ietf/templates/doc/status_change/submit.html | 41 ++ .../idrfc/document_conflict_review.html | 13 +- .../idrfc/document_status_change.html | 135 ++++ ietf/templates/registration/edit_profile.html | 2 +- ietf/utils/test_data.py | 18 + 35 files changed, 2803 insertions(+), 110 deletions(-) create mode 100644 ietf/doc/forms.py create mode 100644 ietf/doc/migrations/0005_add_statchg.py create mode 100644 ietf/doc/tests_status_change.py create mode 100644 ietf/doc/urls_status_change.py create mode 100644 ietf/doc/views_status_change.py create mode 100644 ietf/name/migrations/0005_add_newstat.py create mode 100644 ietf/name/migrations/0006_add_revname_column.py create mode 100644 ietf/name/migrations/0007_reverse_relation_names.py rename ietf/templates/doc/{conflict_review => }/change_ad.html (69%) rename ietf/templates/doc/{conflict_review => }/change_state.html (87%) rename ietf/templates/doc/{conflict_review => }/edit_telechat_date.html (100%) rename ietf/templates/doc/{conflict_review => }/eval_email.txt (67%) rename ietf/templates/doc/{conflict_review => }/notify.html (70%) create mode 100644 ietf/templates/doc/status_change/approval_text.txt create mode 100644 ietf/templates/doc/status_change/approve.html create mode 100644 ietf/templates/doc/status_change/edit_relations.html create mode 100644 ietf/templates/doc/status_change/initial_template.txt create mode 100644 ietf/templates/doc/status_change/start.html create mode 100644 ietf/templates/doc/status_change/status-change-edit-relations-js.html create mode 100644 ietf/templates/doc/status_change/status_changes.html create mode 100644 ietf/templates/doc/status_change/submit.html create mode 100644 ietf/templates/idrfc/document_status_change.html diff --git a/ietf/doc/forms.py b/ietf/doc/forms.py new file mode 100644 index 000000000..2058d86bd --- /dev/null +++ b/ietf/doc/forms.py @@ -0,0 +1,37 @@ +import datetime + +from django import forms + +from ietf.iesg.models import TelechatDate + +class TelechatForm(forms.Form): + telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False) + returning_item = forms.BooleanField(required=False) + + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + dates = [d.date for d in TelechatDate.objects.active().order_by('date')] + init = kwargs['initial'].get("telechat_date") + if init and init not in dates: + dates.insert(0, init) + + self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates] + +from ietf.person.models import Person + +class AdForm(forms.Form): + ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), + label="Shepherding AD", empty_label="(None)", required=True) + + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + # if previous AD is now ex-AD, append that person to the list + ad_pk = self.initial.get('ad') + choices = self.fields['ad'].choices + if ad_pk and ad_pk not in [pk for pk, name in choices]: + self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())] + +class NotifyForm(forms.Form): + notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False) diff --git a/ietf/doc/migrations/0005_add_statchg.py b/ietf/doc/migrations/0005_add_statchg.py new file mode 100644 index 000000000..93d59a634 --- /dev/null +++ b/ietf/doc/migrations/0005_add_statchg.py @@ -0,0 +1,427 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +from ietf.doc.models import StateType, State, BallotType, DocTypeName +from ietf.name.models import BallotPositionName + +class Migration(DataMigration): + + def forwards(self, orm): + statchg = StateType(slug='statchg',label='RFC Status Change') + statchg.save() + + needshep = State( + type=statchg,slug="needshep", + name="Needs Shepherd",used=True,order=1, + desc="An RFC status change has been requested, but a shepherding AD has not yet been assigned") + needshep.save() + + adrev = State( + type=statchg,slug="adrev", + name="AD Review",used=True,order=2, + desc="The sponsoring AD is preparing an RFC status change document") + adrev.save() + + iesgeval = State( + type=statchg,slug="iesgeval", + name="IESG Evaluation",used=True,order=3, + desc="The IESG is considering the proposed RFC status changes") + iesgeval.save() + + defer = State( + type=statchg,slug="defer", + name="IESG Evaluation - Defer",used=True, order=4, + desc="The evaluation of the proposed RFC status changes have been deferred to the next telechat") + defer.save() + + appr_pr = State( + type=statchg,slug="appr-pr", + name="Approved - point raised", used=True,order=5, + desc="The IESG has approved the RFC status changes, but a point has been raised that should be cleared before proceeding to announcement to be sent") + appr_pr.save() + + appr_pend = State( + type=statchg,slug="appr-pend", + name="Approved - announcement to be sent", used=True,order=6, + desc="The IESG has approved the RFC status changes, but the secretariat has not yet sent the announcement") + appr_pend.save() + + appr_sent = State( + type=statchg,slug="appr-sent", + name="Approved - announcement sent",used=True,order=7, + desc="The secretariat has announced the IESG's approved RFC status changes") + appr_sent.save() + + withdraw = State( + type=statchg,slug="withdraw", + name="Withdrawn",used=True,order=8, + desc="The request for RFC status changes was withdrawn") + withdraw.save() + + dead = State( + type=statchg,slug="dead", + name="Dead",used=True,order=9, + desc="The RFC status changes have been abandoned") + dead.save() + + needshep.next_states.add(adrev,withdraw,dead) + needshep.save() + adrev.next_states.add(iesgeval,withdraw,dead) + adrev.save() + iesgeval.next_states.add(appr_pr,appr_pend,defer,withdraw,dead) + iesgeval.save() + defer.next_states.add(iesgeval,appr_pend,appr_pr,withdraw,dead) + defer.save() + appr_pend.next_states.add(appr_sent,withdraw) + appr_pend.save() + withdraw.next_states.add(needshep) + withdraw.save() + dead.next_states.add(needshep) + dead.save() + + statchg_ballot = BallotType(doc_type=DocTypeName.objects.get(slug='statchg'), + slug='statchg',name="Approve",used=True, + question="Do we approve these RFC status changes?") + statchg_ballot.save() + statchg_ballot.positions.add('yes','noobj','discuss','abstain','recuse','norecord') + statchg_ballot.save() + + def backwards(self, orm): + StateType.objects.filter(slug='statchg').delete() + StateType.objects.filter(slug='statchg').delete() + BallotType.objects.filter(slug='statchg').delete() + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'doc.ballotdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotDocEvent', '_ormbases': ['doc.DocEvent']}, + 'ballot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.BallotType']"}), + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'doc.ballotpositiondocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotPositionDocEvent', '_ormbases': ['doc.DocEvent']}, + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}), + 'ballot': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['doc.BallotDocEvent']", 'null': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'comment_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'discuss': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'discuss_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'pos': ('django.db.models.fields.related.ForeignKey', [], {'default': "'norecord'", 'to': "orm['name.BallotPositionName']"}) + }, + 'doc.ballottype': { + 'Meta': {'ordering': "['order']", 'object_name': 'BallotType'}, + 'doc_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'positions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.BallotPositionName']", 'symmetrical': 'False', 'blank': 'True'}), + 'question': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'doc.docalias': { + 'Meta': {'object_name': 'DocAlias'}, + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'doc.docevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'}, + 'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}), + 'desc': ('django.db.models.fields.TextField', [], {}), + 'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'doc.dochistory': { + 'Meta': {'object_name': 'DocHistory'}, + 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocHistoryAuthor']", 'blank': 'True'}), + 'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['doc.Document']"}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}), + 'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}), + 'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.DocAlias']", 'symmetrical': 'False', 'through': "orm['doc.RelatedDocHistory']", 'blank': 'True'}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}) + }, + 'doc.dochistoryauthor': { + 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {}) + }, + 'doc.docreminder': { + 'Meta': {'object_name': 'DocReminder'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'due': ('django.db.models.fields.DateTimeField', [], {}), + 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocEvent']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocReminderTypeName']"}) + }, + 'doc.document': { + 'Meta': {'object_name': 'Document'}, + 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}), + 'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}), + 'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}) + }, + 'doc.documentauthor': { + 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}) + }, + 'doc.initialreviewdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.lastcalldocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.newrevisiondocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'}) + }, + 'doc.relateddochistory': { + 'Meta': {'object_name': 'RelatedDocHistory'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': "orm['doc.DocAlias']"}) + }, + 'doc.relateddocument': { + 'Meta': {'object_name': 'RelatedDocument'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"}) + }, + 'doc.state': { + 'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'doc.statetype': { + 'Meta': {'object_name': 'StateType'}, + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}) + }, + 'doc.telechatdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.writeupdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'group.group': { + 'Meta': {'object_name': 'Group'}, + 'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}), + 'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}), + 'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}), + 'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'name.ballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'}, + 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docrelationshipname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docremindertypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'person.email': { + 'Meta': {'object_name': 'Email'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'person.person': { + 'Meta': {'object_name': 'Person'}, + 'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['doc'] diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 18c937ac4..1cefeda24 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -150,8 +150,7 @@ class RelatedDocument(models.Model): def action(self): return self.relationship.name def inverse_action(): - infinitive = self.relationship.name[:-1] - return u"%sd by" % infinitive + return self.relationship.revname def __unicode__(self): return u"%s %s %s" % (self.source.name, self.relationship.name.lower(), self.target.name) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 6ffbe2985..c527fa6c4 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -1,3 +1,4 @@ from ietf.doc.tests_conflict_review import * +from ietf.doc.tests_status_change import * diff --git a/ietf/doc/tests_status_change.py b/ietf/doc/tests_status_change.py new file mode 100644 index 000000000..9e7ae64fe --- /dev/null +++ b/ietf/doc/tests_status_change.py @@ -0,0 +1,392 @@ +import os +import shutil + +from pyquery import PyQuery +from StringIO import StringIO +from textwrap import wrap + + +import django.test +from django.conf import settings +from django.core.urlresolvers import reverse as urlreverse + +from ietf.utils.test_utils import login_testing_unauthorized +from ietf.utils.test_data import make_test_data +from ietf.utils.mail import outbox +from ietf.doc.utils import create_ballot_if_not_open +from ietf.doc.views_status_change import default_approval_text + +from ietf.doc.models import Document,DocEvent,NewRevisionDocEvent,BallotPositionDocEvent,TelechatDocEvent,DocAlias,State +from ietf.name.models import StreamName +from ietf.group.models import Person +from ietf.iesg.models import TelechatDate + + +class StatusChangeTestCase(django.test.TestCase): + + fixtures = ['names'] + + def test_start_review(self): + + url = urlreverse('start_rfc_status_change') + login_testing_unauthorized(self, "secretary", url) + + # normal get should succeed and get a reasonable form + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form select[name=create_in_state]')),1) + + ad_strpk = str(Person.objects.get(name='Aread Irector').pk) + state_strpk = str(State.objects.get(slug='adrev',type__slug='statchg').pk) + + # faulty posts + + ## Must set a responsible AD + r = self.client.post(url,dict(document_name="bogus",title="Bogus Title",ad="",create_in_state=state_strpk,notify='ipu@ietf.org')) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + ## Must set a name + r = self.client.post(url,dict(document_name="",title="Bogus Title",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org')) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + ## Must not choose a document name that already exists + r = self.client.post(url,dict(document_name="imaginary-mid-review",title="Bogus Title",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org')) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + ## Must set a title + r = self.client.post(url,dict(document_name="bogus",title="",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org')) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + # successful status change start + r = self.client.post(url,dict(document_name="imaginary-new",title="A new imaginary status change",ad=ad_strpk, + create_in_state=state_strpk,notify='ipu@ietf.org',new_relation_row_blah="rfc9999", + statchg_relation_row_blah="tois")) + self.assertEquals(r.status_code, 302) + status_change = Document.objects.get(name='status-change-imaginary-new') + self.assertEquals(status_change.get_state('statchg').slug,'adrev') + self.assertEquals(status_change.rev,u'00') + self.assertEquals(status_change.ad.name,u'Aread Irector') + self.assertEquals(status_change.notify,u'ipu@ietf.org') + self.assertTrue(status_change.relateddocument_set.filter(relationship__slug='tois',target__document__name='draft-ietf-random-thing')) + + def test_change_state(self): + + doc = Document.objects.get(name='status-change-imaginary-mid-review') + url = urlreverse('status_change_change_state',kwargs=dict(name=doc.name)) + + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form select[name=new_state]')),1) + + # faulty post + r = self.client.post(url,dict(new_state="")) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + # successful change to AD Review + adrev_pk = str(State.objects.get(slug='adrev',type__slug='statchg').pk) + r = self.client.post(url,dict(new_state=adrev_pk,comment='RDNK84ZD')) + self.assertEquals(r.status_code, 302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.get_state('statchg').slug,'adrev') + self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('RDNK84ZD')) + self.assertFalse(doc.active_ballot()) + + # successful change to IESG Evaluation + iesgeval_pk = str(State.objects.get(slug='iesgeval',type__slug='statchg').pk) + r = self.client.post(url,dict(new_state=iesgeval_pk,comment='TGmZtEjt')) + self.assertEquals(r.status_code, 302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.get_state('statchg').slug,'iesgeval') + self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('TGmZtEjt')) + self.assertTrue(doc.active_ballot()) + self.assertEquals(doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position").pos_id,'yes') + + def test_edit_notices(self): + doc = Document.objects.get(name='status-change-imaginary-mid-review') + url = urlreverse('status_change_notices',kwargs=dict(name=doc.name)) + + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form input[name=notify]')),1) + self.assertEquals(doc.notify,q('form input[name=notify]')[0].value) + + # change notice list + newlist = '"Foo Bar" ' + r = self.client.post(url,dict(notify=newlist)) + self.assertEquals(r.status_code,302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.notify,newlist) + self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('Notification list changed')) + + def test_edit_ad(self): + doc = Document.objects.get(name='status-change-imaginary-mid-review') + url = urlreverse('status_change_ad',kwargs=dict(name=doc.name)) + + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('select[name=ad]')),1) + + # change ads + ad2 = Person.objects.get(name='Ad No2') + r = self.client.post(url,dict(ad=str(ad2.pk))) + self.assertEquals(r.status_code,302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.ad,ad2) + self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('Shepherding AD changed')) + + def test_edit_telechat_date(self): + doc = Document.objects.get(name='status-change-imaginary-mid-review') + url = urlreverse('status_change_telechat_date',kwargs=dict(name=doc.name)) + + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('select[name=telechat_date]')),1) + + # set a date + self.assertFalse(doc.latest_event(TelechatDocEvent, "scheduled_for_telechat")) + telechat_date = TelechatDate.objects.active().order_by('date')[0].date + r = self.client.post(url,dict(telechat_date=telechat_date.isoformat())) + self.assertEquals(r.status_code,302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,telechat_date) + + # move it forward a telechat (this should set the returning item bit) + telechat_date = TelechatDate.objects.active().order_by('date')[1].date + r = self.client.post(url,dict(telechat_date=telechat_date.isoformat())) + self.assertEquals(r.status_code,302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertTrue(doc.returning_item()) + + # clear the returning item bit + r = self.client.post(url,dict(telechat_date=telechat_date.isoformat())) + self.assertEquals(r.status_code,302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertFalse(doc.returning_item()) + + # set the returning item bit without changing the date + r = self.client.post(url,dict(telechat_date=telechat_date.isoformat(),returning_item="on")) + self.assertEquals(r.status_code,302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertTrue(doc.returning_item()) + + # Take the doc back off any telechat + r = self.client.post(url,dict(telechat_date="")) + self.assertEquals(r.status_code, 302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,None) + + def test_approve(self): + doc = Document.objects.get(name='status-change-imaginary-mid-review') + url = urlreverse('status_change_approve',kwargs=dict(name=doc.name)) + + login_testing_unauthorized(self, "secretary", url) + + # 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") + doc.set_state(State.objects.get(slug='appr-pend',type='statchg')) + doc.save() + + # get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form.approve')),1) + # There should be two messages to edit + self.assertEquals(q('input#id_form-TOTAL_FORMS').val(),'2') + self.assertTrue( '(rfc9999) to Internet Standard' in ''.join(wrap(r.content,2**16))) + self.assertTrue( '(rfc9998) to Historic' in ''.join(wrap(r.content,2**16))) + + # submit + messages_before = len(outbox) + msg0=default_approval_text(doc,doc.relateddocument_set.all()[0]) + msg1=default_approval_text(doc,doc.relateddocument_set.all()[1]) + r = self.client.post(url,{'form-0-announcement_text':msg0,'form-1-announcement_text':msg1,'form-TOTAL_FORMS':'2','form-INITIAL_FORMS':'2','form-MAX_NUM_FORMS':''}) + self.assertEquals(r.status_code, 302) + + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.get_state_slug(),'appr-sent') + self.assertFalse(doc.ballot_open("statchg")) + + self.assertEquals(len(outbox), messages_before + 2) + self.assertTrue('Action:' in outbox[-1]['Subject']) + self.assertTrue('(rfc9999) to Internet Standard' in ''.join(wrap(unicode(outbox[-1])+unicode(outbox[-2]),2**16))) + self.assertTrue('(rfc9998) to Historic' in ''.join(wrap(unicode(outbox[-1])+unicode(outbox[-2]),2**16))) + + self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('The following approval message was sent')) + + def test_edit_relations(self): + doc = Document.objects.get(name='status-change-imaginary-mid-review') + url = urlreverse('status_change_relations',kwargs=dict(name=doc.name)) + + login_testing_unauthorized(self, "secretary", url) + + # 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') + + # get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form.edit-status-change-rfcs')),1) + # There should be three rows on the form + self.assertEquals(len(q('tr[id^=relation_row]')),3) + + # Try to add a relation to an RFC that doesn't exist + r = self.client.post(url,dict(new_relation_row_blah="rfc9997", + statchg_relation_row_blah="tois")) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + # Try to add a relation leaving the relation type blank + r = self.client.post(url,dict(new_relation_row_blah="rfc9999", + statchg_relation_row_blah="")) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + # Try to add a relation with an unknown relationship type + r = self.client.post(url,dict(new_relation_row_blah="rfc9999", + statchg_relation_row_blah="badslug")) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + # Successful change of relations + r = self.client.post(url,dict(new_relation_row_blah="rfc9999", + statchg_relation_row_blah="toexp", + new_relation_row_foo="rfc9998", + statchg_relation_row_foo="tobcp")) + self.assertEquals(r.status_code, 302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.relateddocument_set.count(),2) + verify9999 = doc.relateddocument_set.filter(target__name='rfc9999') + self.assertTrue(verify9999) + self.assertEquals(verify9999.count(),1) + self.assertEquals(verify9999[0].relationship.slug,'toexp') + verify9998 = doc.relateddocument_set.filter(target__name='rfc9998') + self.assertTrue(verify9998) + self.assertEquals(verify9998.count(),1) + self.assertEquals(verify9998[0].relationship.slug,'tobcp') + self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('Affected RFC list changed.')) + + def setUp(self): + make_test_data() + + +class StatusChangeSubmitTestCase(django.test.TestCase): + + fixtures = ['names'] + + def test_initial_submission(self): + doc = Document.objects.get(name='status-change-imaginary-mid-review') + url = urlreverse('status_change_submit',kwargs=dict(name=doc.name)) + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertTrue(q('textarea')[0].text.startswith("Provide a description")) + + # Faulty posts using textbox + # Right now, nothing to test - we let people put whatever the web browser will let them put into that textbox + + # sane post using textbox + path = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev)) + self.assertEquals(doc.rev,u'00') + self.assertFalse(os.path.exists(path)) + r = self.client.post(url,dict(content="Some initial review text\n",submit_response="1")) + self.assertEquals(r.status_code,302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.rev,u'00') + with open(path) as f: + self.assertEquals(f.read(),"Some initial review text\n") + f.close() + self.assertTrue( "mid-review-00" in doc.latest_event(NewRevisionDocEvent).desc) + + def test_subsequent_submission(self): + doc = Document.objects.get(name='status-change-imaginary-mid-review') + url = urlreverse('status_change_submit',kwargs=dict(name=doc.name)) + login_testing_unauthorized(self, "ad", url) + + # A little additional setup + # doc.rev is u'00' per the test setup - double-checking that here - if it fails, the breakage is in setUp + self.assertEquals(doc.rev,u'00') + path = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev)) + with open(path,'w') as f: + f.write('This is the old proposal.') + f.close() + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertTrue(q('textarea')[0].text.startswith("This is the old proposal.")) + + # faulty posts trying to use file upload + # Copied from wgtracker tests - is this really testing the server code, or is it testing + # how client.post populates Content-Type? + test_file = StringIO("\x10\x11\x12") # post binary file + test_file.name = "unnamed" + r = self.client.post(url, dict(txt=test_file,submit_response="1")) + self.assertEquals(r.status_code, 200) + self.assertTrue("does not appear to be a text file" in r.content) + + # sane post uploading a file + test_file = StringIO("This is a new proposal.") + test_file.name = "unnamed" + r = self.client.post(url,dict(txt=test_file,submit_response="1")) + self.assertEquals(r.status_code, 302) + doc = Document.objects.get(name='status-change-imaginary-mid-review') + self.assertEquals(doc.rev,u'01') + path = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev)) + with open(path) as f: + self.assertEquals(f.read(),"This is a new proposal.") + f.close() + self.assertTrue( "mid-review-01" in doc.latest_event(NewRevisionDocEvent).desc) + + # verify reset text button works + r = self.client.post(url,dict(reset_text="1")) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(q('textarea')[0].text.startswith("Provide a description")) + + def setUp(self): + make_test_data() + self.test_dir = os.path.abspath("tmp-status-change-testdir") + os.mkdir(self.test_dir) + settings.STATUS_CHANGE_PATH = self.test_dir + + def tearDown(self): + shutil.rmtree(self.test_dir) diff --git a/ietf/doc/urls_status_change.py b/ietf/doc/urls_status_change.py new file mode 100644 index 000000000..91bbea9bb --- /dev/null +++ b/ietf/doc/urls_status_change.py @@ -0,0 +1,12 @@ +from django.conf.urls.defaults import patterns, url + +urlpatterns = patterns('ietf.doc.views_status_change', + url(r'^state/$', "change_state", name='status_change_change_state'), + url(r'^submit/$', "submit", name='status_change_submit'), + url(r'^notices/$', "edit_notices", name='status_change_notices'), + url(r'^ad/$', "edit_ad", name='status_change_ad'), + url(r'^approve/$', "approve", name='status_change_approve'), + url(r'^telechat/$', "telechat_date", name='status_change_telechat_date'), + url(r'^relations/$', "edit_relations", name='status_change_relations'), +) + diff --git a/ietf/doc/views_conflict_review.py b/ietf/doc/views_conflict_review.py index 34f99a540..f5d42fb52 100644 --- a/ietf/doc/views_conflict_review.py +++ b/ietf/doc/views_conflict_review.py @@ -25,20 +25,15 @@ from ietf.person.models import Person from ietf.iesg.models import TelechatDate from ietf.group.models import Role, Group +from ietf.doc.forms import TelechatForm, AdForm, NotifyForm + class ChangeStateForm(forms.Form): review_state = forms.ModelChoiceField(State.objects.filter(type="conflrev", used=True), label="Conflict review state", empty_label=None, required=True) comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the review history", required=False) - def __init__(self, *args, **kwargs): - self.hide = kwargs.pop('hide', None) - super(ChangeStateForm, self).__init__(*args, **kwargs) - # hide requested fields - if self.hide: - for f in self.hide: - self.fields[f].widget = forms.HiddenInput @role_required("Area Director", "Secretariat") def change_state(request, name, option=None): - """Change state of and IESG review for IETF conflicts in other stream's documents, notifying parties as necessary + """Change state of an IESG review for IETF conflicts in other stream's documents, notifying parties as necessary and logging the change as a comment.""" review = get_object_or_404(Document, type="conflrev", name=name) @@ -86,22 +81,22 @@ def change_state(request, name, option=None): return redirect('doc_view', name=review.name) else: - hide = [] s = review.get_state() init = dict(review_state=s.pk if s else None) - form = ChangeStateForm(hide=hide, initial=init) + form = ChangeStateForm(initial=init) - return render_to_response('doc/conflict_review/change_state.html', + return render_to_response('doc/change_state.html', dict(form=form, doc=review, login=login, + help_url=reverse('help_conflict_review_states'), ), context_instance=RequestContext(request)) def send_conflict_eval_email(request,review): - msg = render_to_string("doc/conflict_review/eval_email.txt", - dict(review=review, - review_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(), + msg = render_to_string("doc/eval_email.txt", + dict(doc=review, + doc_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(), ) ) send_mail_preformatted(request,msg) @@ -202,9 +197,6 @@ def submit(request, name): }, context_instance=RequestContext(request)) -class NotifyForm(forms.Form): - notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False) - @role_required("Area Director", "Secretariat") def edit_notices(request, name): @@ -231,28 +223,15 @@ def edit_notices(request, name): init = { "notify" : review.notify } form = NotifyForm(initial=init) - return render_to_response('doc/conflict_review/notify.html', - {'form': form, - 'review': review, - 'conflictdoc' : review.relateddocument_set.get(relationship__slug='conflrev').target.document, + conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document + titletext = 'the conflict review of %s-%s' % (conflictdoc.canonical_name(),conflictdoc.rev) + return render_to_response('doc/notify.html', + {'form': form, + 'doc': review, + 'titletext' : titletext }, context_instance = RequestContext(request)) -class AdForm(forms.Form): - ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), - label="Shepherding AD", empty_label="(None)", required=True) - - def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) - - # if previous AD is now ex-AD, append that person to the list - ad_pk = self.initial.get('ad') - choices = self.fields['ad'].choices - if ad_pk and ad_pk not in [pk for pk, name in choices]: - self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())] - - - @role_required("Area Director", "Secretariat") def edit_ad(request, name): """Change the shepherding Area Director for this review.""" @@ -277,10 +256,13 @@ def edit_ad(request, name): init = { "ad" : review.ad_id } form = AdForm(initial=init) - return render_to_response('doc/conflict_review/change_ad.html', - {'form': form, - 'review': review, - 'conflictdoc' : review.relateddocument_set.get(relationship__slug='conflrev').target.document, + + conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document + titletext = 'the conflict review of %s-%s' % (conflictdoc.canonical_name(),conflictdoc.rev) + return render_to_response('doc/change_ad.html', + {'form': form, + 'doc': review, + 'titletext': titletext }, context_instance = RequestContext(request)) @@ -318,7 +300,7 @@ def approve(request, name): review = get_object_or_404(Document, type="conflrev", name=name) if review.get_state('conflrev').slug not in ('appr-reqnopub-pend','appr-noprob-pend'): - return Http404() + raise Http404 login = request.user.get_profile() @@ -465,22 +447,6 @@ def start_review(request, name): context_instance = RequestContext(request)) -# There should really only be one of these living in Doc instead of it being spread between idrfc,charter, and here -class TelechatForm(forms.Form): - telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False) - returning_item = forms.BooleanField(required=False) - - def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) - - dates = [d.date for d in TelechatDate.objects.active().order_by('date')] - init = kwargs['initial'].get("telechat_date") - if init and init not in dates: - dates.insert(0, init) - - self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates] - - @role_required("Area Director", "Secretariat") def telechat_date(request, name): doc = get_object_or_404(Document, type="conflrev", name=name) @@ -501,7 +467,7 @@ def telechat_date(request, name): else: form = TelechatForm(initial=initial) - return render_to_response('doc/conflict_review/edit_telechat_date.html', + return render_to_response('doc/edit_telechat_date.html', dict(doc=doc, form=form, user=request.user, diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py new file mode 100644 index 000000000..ad053dfba --- /dev/null +++ b/ietf/doc/views_status_change.py @@ -0,0 +1,596 @@ +import datetime, os, re + +from django import forms +from django.shortcuts import render_to_response, get_object_or_404, redirect +from django.http import HttpResponseRedirect, Http404 +from django.core.urlresolvers import reverse +from django.template import RequestContext +from django.template.loader import render_to_string +from django.conf import settings + +from ietf.idrfc.utils import update_telechat +from ietf.doc.utils import log_state_changed +from ietf.doc.models import save_document_in_history + +from ietf.doc.utils import create_ballot_if_not_open, close_open_ballots, get_document_content +from ietf.ietfauth.decorators import has_role, role_required +from ietf.utils.textupload import get_cleaned_text_file_content +from ietf.utils.mail import send_mail_preformatted +from ietf.doc.models import State, Document, DocHistory, DocAlias +from ietf.doc.models import DocEvent, NewRevisionDocEvent, WriteupDocEvent, TelechatDocEvent, BallotDocEvent, BallotPositionDocEvent +from ietf.person.models import Person +from ietf.iesg.models import TelechatDate +from ietf.group.models import Group +from ietf.name.models import DocRelationshipName, StdLevelName + +from ietf.doc.forms import TelechatForm, AdForm, NotifyForm + +class ChangeStateForm(forms.Form): + new_state = forms.ModelChoiceField(State.objects.filter(type="statchg", used=True), label="Status Change Evaluation State", empty_label=None, required=True) + comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the review history", required=False) + + +@role_required("Area Director", "Secretariat") +def change_state(request, name, option=None): + """Change state of an status-change document, notifying parties as necessary + and logging the change as a comment.""" + status_change = get_object_or_404(Document, type="statchg", name=name) + + login = request.user.get_profile() + + if request.method == 'POST': + form = ChangeStateForm(request.POST) + if form.is_valid(): + clean = form.cleaned_data + new_state = clean['new_state'] + comment = clean['comment'].rstrip() + + if comment: + c = DocEvent(type="added_comment", doc=status_change, by=login) + c.desc = comment + c.save() + + if new_state != status_change.get_state(): + save_document_in_history(status_change) + + old_description = status_change.friendly_state() + status_change.set_state(new_state) + new_description = status_change.friendly_state() + + log_state_changed(request, status_change, login, new_description, old_description) + + status_change.time = datetime.datetime.now() + status_change.save() + + if new_state.slug == "iesgeval": + create_ballot_if_not_open(status_change, login, "statchg") + 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"): + + # The AD putting a status change into iesgeval who doesn't already have a position is saying "yes" + pos = BallotPositionDocEvent(doc=status_change, by=login) + pos.ballot = ballot + pos.type = "changed_ballot_position" + pos.ad = login + 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() + + send_status_change_eval_email(request,status_change) + + + return redirect('doc_view', name=status_change.name) + else: + s = status_change.get_state() + init = dict(new_state=s.pk if s else None, + type='statchg', + label='Status Change Evaluation State', + ) + form = ChangeStateForm(initial=init) + + return render_to_response('doc/change_state.html', + dict(form=form, + doc=status_change, + login=login, + help_url=reverse('help_status_change_states') + ), + context_instance=RequestContext(request)) + +def send_status_change_eval_email(request,doc): + msg = render_to_string("doc/eval_email.txt", + dict(doc=doc, + doc_url = settings.IDTRACKER_BASE_URL+doc.get_absolute_url(), + ) + ) + send_mail_preformatted(request,msg) + +class UploadForm(forms.Form): + content = forms.CharField(widget=forms.Textarea, label="Status change text", help_text="Edit the status change text", required=False) + txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file", required=False) + + def clean_content(self): + return self.cleaned_data["content"].replace("\r", "") + + def clean_txt(self): + return get_cleaned_text_file_content(self.cleaned_data["txt"]) + + def save(self, doc): + filename = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev)) + with open(filename, 'wb') as destination: + if self.cleaned_data['txt']: + destination.write(self.cleaned_data['txt']) + else: + destination.write(self.cleaned_data['content']) + +#This is very close to submit on charter - can we get better reuse? +@role_required('Area Director','Secretariat') +def submit(request, name): + doc = get_object_or_404(Document, type="statchg", name=name) + + login = request.user.get_profile() + + path = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev)) + not_uploaded_yet = doc.rev == "00" and not os.path.exists(path) + + if not_uploaded_yet: + # this case is special - the status change text document doesn't actually exist yet + next_rev = doc.rev + else: + next_rev = "%02d" % (int(doc.rev)+1) + + if request.method == 'POST': + 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 + + 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() + + # Save file on disk + form.save(doc) + + doc.time = datetime.datetime.now() + doc.save() + + return HttpResponseRedirect(reverse('doc_view', kwargs={'name': doc.name})) + + elif "reset_text" in request.POST: + + init = { "content": render_to_string("doc/status_change/initial_template.txt",dict())} + form = UploadForm(initial=init) + + # Protect against handcrufted malicious posts + else: + form = None + + else: + form = None + + if not form: + init = { "content": ""} + + if not_uploaded_yet: + init["content"] = render_to_string("doc/status_change/initial_template.txt", + dict(), + ) + else: + filename = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev)) + try: + with open(filename, 'r') as f: + init["content"] = f.read() + except IOError: + pass + + form = UploadForm(initial=init) + + return render_to_response('doc/status_change/submit.html', + {'form': form, + 'next_rev': next_rev, + 'doc' : doc, + }, + context_instance=RequestContext(request)) + +@role_required("Area Director", "Secretariat") +def edit_notices(request, name): + """Change the set of email addresses document change notificaitions go to.""" + + status_change = get_object_or_404(Document, type="statchg", name=name) + + if request.method == 'POST': + form = NotifyForm(request.POST) + if form.is_valid(): + + status_change.notify = form.cleaned_data['notify'] + status_change.save() + + login = request.user.get_profile() + c = DocEvent(type="added_comment", doc=status_change, by=login) + c.desc = "Notification list changed to : "+status_change.notify + c.save() + + return HttpResponseRedirect(reverse('doc_view', kwargs={'name': status_change.name})) + + else: + + init = { "notify" : status_change.notify } + form = NotifyForm(initial=init) + + return render_to_response('doc/notify.html', + {'form': form, + 'doc': status_change, + 'titletext' : '%s-%s.txt' % (status_change.canonical_name(),status_change.rev) + }, + context_instance = RequestContext(request)) + +@role_required("Area Director", "Secretariat") +def edit_ad(request, name): + """Change the shepherding Area Director for this status_change.""" + + status_change = get_object_or_404(Document, type="statchg", name=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.get_profile() + c = DocEvent(type="added_comment", doc=status_change, by=login) + c.desc = "Shepherding AD changed to "+status_change.ad.name + c.save() + + return redirect("doc_view", name=status_change.name) + + else: + init = { "ad" : status_change.ad_id } + form = AdForm(initial=init) + + titletext = '%s-%s.txt' % (status_change.canonical_name(),status_change.rev) + return render_to_response('doc/change_ad.html', + {'form': form, + 'doc': status_change, + 'titletext' : titletext, + }, + context_instance = RequestContext(request)) + +def newstatus(relateddoc): + + level_map = { + 'tops' : 'ps', + 'tois' : 'std', + 'tohist' : 'hist', + 'toinf' : 'inf', + 'tobcp' : 'bcp', + 'toexp' : 'exp', + } + + return StdLevelName.objects.get(slug=level_map[relateddoc.relationship.slug]) + +def default_approval_text(status_change,relateddoc): + + filename = "%s-%s.txt" % (status_change.canonical_name(), status_change.rev) + current_text = get_document_content(filename, os.path.join(settings.STATUS_CHANGE_PATH, filename), split=False, markup=False) + + if relateddoc.target.document.std_level.slug in ('std','ps','ds','bcp',): + action = "Protocol Action" + else: + action = "Document Action" + + + text = render_to_string("doc/status_change/approval_text.txt", + dict(status_change=status_change, + status_change_url = settings.IDTRACKER_BASE_URL+status_change.get_absolute_url(), + relateddoc= relateddoc, + relateddoc_url = settings.IDTRACKER_BASE_URL+relateddoc.target.document.get_absolute_url(), + approved_text = current_text, + action=action, + newstatus=newstatus(relateddoc), + ) + ) + + return text + +from django.forms.formsets import formset_factory + +class AnnouncementForm(forms.Form): + announcement_text = forms.CharField(widget=forms.Textarea, label="Status Change Announcement", help_text="Edit the announcement message", required=True) + label = None + + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self.label = self.initial.get('label') + +@role_required("Secretariat") +def approve(request, name): + """Approve this status change, setting the appropriate state and send the announcements to the right parties.""" + status_change = get_object_or_404(Document, type="statchg", name=name) + + if status_change.get_state('statchg').slug not in ('appr-pend'): + raise Http404 + + login = request.user.get_profile() + + AnnouncementFormSet = formset_factory(AnnouncementForm,extra=0) + + if request.method == 'POST': + + formset = AnnouncementFormSet(request.POST) + + if formset.is_valid(): + + save_document_in_history(status_change) + + old_description = status_change.friendly_state() + status_change.set_state(State.objects.get(type='statchg', slug='appr-sent')) + new_description = status_change.friendly_state() + log_state_changed(request, status_change, login, new_description, old_description) + + close_open_ballots(status_change, login) + + e = DocEvent(doc=status_change, by=login) + e.type = "iesg_approved" + e.desc = "IESG has approved the status change" + e.save() + + status_change.time = e.time + status_change.save() + + + for form in formset.forms: + + send_mail_preformatted(request,form.cleaned_data['announcement_text']) + + c = DocEvent(type="added_comment", doc=status_change, by=login) + c.desc = "The following approval message was sent\n"+form.cleaned_data['announcement_text'] + c.save() + + for rel in status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS): + # Add a document event to each target + c = DocEvent(type="added_comment", doc=rel.target.document, by=login) + c.desc = "New status of %s approved by the IESG\n%s%s" % (newstatus(rel), settings.IDTRACKER_BASE_URL,reverse('doc_view', kwargs={'name': status_change.name})) + c.save() + + return HttpResponseRedirect(status_change.get_absolute_url()) + + else: + + init = [] + for rel in status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS): + init.append({"announcement_text" : default_approval_text(status_change,rel), + "label": "Announcement text for %s to %s"%(rel.target.document.canonical_name(),newstatus(rel)), + }) + formset = AnnouncementFormSet(initial=init) + for form in formset.forms: + form.fields['announcement_text'].label=form.label + + return render_to_response('doc/status_change/approve.html', + dict( + doc = status_change, + formset = formset, + ), + context_instance=RequestContext(request)) + +RELATION_SLUGS = ('tops','tois','tohist','toinf','tobcp','toexp') + +def clean_helper(form, formtype): + cleaned_data = super(formtype, form).clean() + + new_relations = {} + rfc_fields = {} + status_fields={} + for k in sorted(form.data.iterkeys()): + v = form.data[k] + if k.startswith('new_relation_row'): + if re.match('\d{4}',v): + v = 'rfc'+v + rfc_fields[k[17:]]=v + elif k.startswith('statchg_relation_row'): + status_fields[k[21:]]=v + for key in rfc_fields: + if rfc_fields[key]!="": + new_relations[rfc_fields[key]]=status_fields[key] + + form.relations = new_relations + + errors=[] + for key in new_relations: + + if not re.match('(?i)rfc\d{4}',key): + errors.append(key+" is not a valid RFC - please use the form RFCxxxx\n") + elif not DocAlias.objects.filter(name=key): + errors.append(key+" does not exist\n") + + if new_relations[key] not in RELATION_SLUGS: + errors.append("Please choose a new status level for "+key+"\n") + + if errors: + raise forms.ValidationError(errors) + + cleaned_data['relations']=new_relations + + return cleaned_data + +class EditStatusChangeForm(forms.Form): + relations={} + + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self.relations = self.initial.get('relations') + + def clean(self): + return clean_helper(self,EditStatusChangeForm) + +class StartStatusChangeForm(forms.Form): + document_name = forms.CharField(max_length=255, label="Document name", help_text="A descriptive name such as status-change-md2-to-historic is better than status-change-rfc1319", required=True) + title = forms.CharField(max_length=255, label="Title", required=True) + ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), + label="Shepherding AD", empty_label="(None)", required=True) + create_in_state = forms.ModelChoiceField(State.objects.filter(type="statchg", slug__in=("needshep", "adrev")), empty_label=None, required=False) + notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False) + telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'})) + relations={} + + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + # telechat choices + dates = [d.date for d in TelechatDate.objects.active().order_by('date')] + self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates] + + def clean_document_name(self): + name = self.cleaned_data['document_name'] + if Document.objects.filter(name='status-change-%s'%name): + raise forms.ValidationError("status-change-%s already exists"%name) + return name + + def clean(self): + return clean_helper(self,StartStatusChangeForm) + +#TODO - cleaned data, especially on document_name + +def rfc_status_changes(request): + """Show the rfc status changes that are under consideration, and those that are completed.""" + + docs=Document.objects.filter(type__slug='statchg') + doclist=[x for x in docs] + doclist.sort(key=lambda obj: obj.get_state().order) + return render_to_response('doc/status_change/status_changes.html', + {'docs' : doclist, + }, + context_instance = RequestContext(request)) + +@role_required("Area Director","Secretariat") +def start_rfc_status_change(request): + """Start the RFC status change review process, setting the initial shepherding AD, and possibly putting the review on a telechat.""" + + login = request.user.get_profile() + + relation_slugs = DocRelationshipName.objects.filter(slug__in=RELATION_SLUGS) + + if request.method == 'POST': + form = StartStatusChangeForm(request.POST) + if form.is_valid(): + + 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.set_state(form.cleaned_data['create_in_state']) + + status_change.save() + + DocAlias.objects.create( name= 'status-change-'+form.cleaned_data['document_name'], document=status_change ) + + for key in form.cleaned_data['relations']: + status_change.relateddocument_set.create(target=DocAlias.objects.get(name=key), + relationship_id=form.cleaned_data['relations'][key]) + + + tc_date = form.cleaned_data['telechat_date'] + if tc_date: + update_telechat(request, status_change, login, tc_date) + + return HttpResponseRedirect(status_change.get_absolute_url()) + else: + init = { + } + form = StartStatusChangeForm(initial=init) + + return render_to_response('doc/status_change/start.html', + {'form': form, + 'relation_slugs': relation_slugs, + }, + context_instance = RequestContext(request)) + + +@role_required("Area Director", "Secretariat") +def telechat_date(request, name): + doc = get_object_or_404(Document, type="statchg", name=name) + login = request.user.get_profile() + + e = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat") + initial_returning_item = bool(e and e.returning_item) + + initial = dict(telechat_date=e.telechat_date if e else None, + returning_item = initial_returning_item, + ) + if request.method == "POST": + form = TelechatForm(request.POST, initial=initial) + + if form.is_valid(): + update_telechat(request, doc, login, form.cleaned_data['telechat_date'], form.cleaned_data['returning_item']) + return redirect("doc_view", name=doc.name) + else: + form = TelechatForm(initial=initial) + + return render_to_response('doc/edit_telechat_date.html', + dict(doc=doc, + form=form, + user=request.user, + login=login), + context_instance=RequestContext(request)) + +@role_required("Area Director", "Secretariat") +def edit_relations(request, name): + """Change the affected set of RFCs""" + + status_change = get_object_or_404(Document, type="statchg", name=name) + + login = request.user.get_profile() + + relation_slugs = DocRelationshipName.objects.filter(slug__in=RELATION_SLUGS) + + if request.method == 'POST': + form = EditStatusChangeForm(request.POST) + if form.is_valid(): + + old_relations={} + for rel in status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS): + old_relations[rel.target.document.canonical_name()]=rel.relationship.slug + new_relations=form.cleaned_data['relations'] + status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS).delete() + for key in new_relations: + status_change.relateddocument_set.create(target=DocAlias.objects.get(name=key), + relationship_id=new_relations[key]) + c = DocEvent(type="added_comment", doc=status_change, by=login) + c.desc = "Affected RFC list changed.\nOLD:" + for relname,relslug in (set(old_relations.items())-set(new_relations.items())): + c.desc += "\n "+relname+": "+DocRelationshipName.objects.get(slug=relslug).name + c.desc += "\nNEW:" + for relname,relslug in (set(new_relations.items())-set(old_relations.items())): + c.desc += "\n "+relname+": "+DocRelationshipName.objects.get(slug=relslug).name + #for rel in status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS): + # c.desc +="\n"+rel.relationship.name+": "+rel.target.document.canonical_name() + c.desc += "\n" + c.save() + + return HttpResponseRedirect(status_change.get_absolute_url()) + + else: + relations={} + for rel in status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS): + relations[rel.target.document.canonical_name()]=rel.relationship.slug + init = { "relations":relations, + } + form = EditStatusChangeForm(initial=init) + + return render_to_response('doc/status_change/edit_relations.html', + { + 'doc': status_change, + 'form': form, + 'relation_slugs': relation_slugs, + }, + context_instance = RequestContext(request)) diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py index 2a0477be8..4e2e83b04 100644 --- a/ietf/idrfc/urls.py +++ b/ietf/idrfc/urls.py @@ -33,6 +33,7 @@ from django.conf.urls.defaults import patterns, url, include from ietf.idrfc import views_doc, views_search, views_edit, views_ballot, views from ietf.doc.models import State +from ietf.doc import views_status_change urlpatterns = patterns('', (r'^/?$', views_search.search_main), @@ -40,6 +41,8 @@ urlpatterns = patterns('', (r'^all/$', views_search.all), (r'^active/$', views_search.active), (r'^in-last-call/$', views_search.in_last_call), + url(r'^rfc-status-changes/$', views_status_change.rfc_status_changes, name='rfc_status_changes'), + url(r'^start-rfc-status-change/$', views_status_change.start_rfc_status_change, name='start_rfc_status_change'), url(r'^ad/(?P[A-Za-z0-9.-]+)/$', views_search.by_ad, name="doc_search_by_ad"), url(r'^(?P[A-Za-z0-9._+-]+)/((?P[0-9-]+)/)?$', views_doc.document_main, name="doc_view"), @@ -81,11 +84,13 @@ urlpatterns = patterns('', (r'^(?Pcharter-[A-Za-z0-9._+-]+)/', include('ietf.wgcharter.urls')), (r'^(?P[A-Za-z0-9._+-]+)/conflict-review/', include('ietf.doc.urls_conflict_review')), + (r'^(?P[A-Za-z0-9._+-]+)/status-change/', include('ietf.doc.urls_status_change')), ) urlpatterns += patterns('django.views.generic.simple', url(r'^help/state/charter/$', 'direct_to_template', { 'template': 'doc/states.html', 'extra_context': { 'states': State.objects.filter(type="charter"),'title':"Charter" } }, name='help_charter_states'), url(r'^help/state/conflict-review/$', 'direct_to_template', { 'template': 'doc/states.html', 'extra_context': { 'states': State.objects.filter(type="conflrev").order_by("order"),'title':"Conflict Review" } }, name='help_conflict_review_states'), + url(r'^help/state/status-change/$', 'direct_to_template', { 'template': 'doc/states.html', 'extra_context': { 'states': State.objects.filter(type="statchg").order_by("order"),'title':"RFC Status Change" } }, name='help_status_change_states'), ) diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index 7fc7372e7..6f5a76541 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -60,14 +60,14 @@ def render_document_top(request, doc, tab, name): tabs.append(("Document", "document", urlreverse("ietf.idrfc.views_doc.document_main", kwargs=dict(name=name)), True)) ballot = doc.latest_event(BallotDocEvent, type="created_ballot") - if doc.type_id in ("draft","conflrev"): + if doc.type_id in ("draft","conflrev","statchg"): # if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot: tabs.append(("IESG Evaluation Record", "ballot", urlreverse("ietf.idrfc.views_doc.document_ballot", kwargs=dict(name=name)), ballot)) elif doc.type_id == "charter": tabs.append(("IESG Review", "ballot", urlreverse("ietf.idrfc.views_doc.document_ballot", kwargs=dict(name=name)), ballot)) # FIXME: if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot: - if doc.type_id != "conflrev": + if doc.type_id not in ("conflrev","statchg"): tabs.append(("IESG Writeups", "writeup", urlreverse("ietf.idrfc.views_doc.document_writeup", kwargs=dict(name=doc.name)), True)) tabs.append(("History", "history", urlreverse("ietf.idrfc.views_doc.document_history", kwargs=dict(name=doc.name)), True)) @@ -190,6 +190,33 @@ def document_main(request, name, rev=None): ), context_instance=RequestContext(request)) + if doc.type_id == "statchg": + filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev) + pathname = os.path.join(settings.STATUS_CHANGE_PATH,filename) + + if doc.rev == "00" and not os.path.isfile(pathname): + # This could move to a template + content = "Status change text has not yet been proposed." + else: + content = _get_html(filename, pathname, split=False) + + ballot_summary = None + if doc.get_state_slug() in ("iesgeval"): + ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values()) + + return render_to_response("idrfc/document_status_change.html", + dict(doc=doc, + top=top, + content=content, + revisions=revisions, + snapshot=snapshot, + telechat=telechat, + ballot_summary=ballot_summary, + approved_states=('appr-pend','appr-sent'), + sorted_relations=doc.relateddocument_set.all().order_by('relationship__name'), + ), + context_instance=RequestContext(request)) + raise Http404() diff --git a/ietf/name/admin.py b/ietf/name/admin.py index 60b1b952a..7e09d4dab 100644 --- a/ietf/name/admin.py +++ b/ietf/name/admin.py @@ -4,12 +4,15 @@ from models import * class NameAdmin(admin.ModelAdmin): list_display = ["slug", "name", "desc", "used"] prepopulate_from = { "slug": ("name",) } + +class DocRelationshipNameAdmin(NameAdmin): + list_display = ["slug", "name", "revname", "desc", "used"] admin.site.register(GroupTypeName, NameAdmin) admin.site.register(GroupStateName, NameAdmin) admin.site.register(RoleName, NameAdmin) admin.site.register(StreamName, NameAdmin) -admin.site.register(DocRelationshipName, NameAdmin) +admin.site.register(DocRelationshipName, DocRelationshipNameAdmin) admin.site.register(DocTypeName, NameAdmin) admin.site.register(DocTagName, NameAdmin) admin.site.register(StdLevelName, NameAdmin) diff --git a/ietf/name/fixtures/names.xml b/ietf/name/fixtures/names.xml index a979ab819..9c3a0206a 100644 --- a/ietf/name/fixtures/names.xml +++ b/ietf/name/fixtures/names.xml @@ -72,24 +72,70 @@ True 0 + Obsoleted by Updates True 0 + Updated by Replaces True 0 + Replaced by conflict reviews True 0 + Conflict reviewed by + + + Moves to Proposed Standard + + True + 0 + Moved to Proposed Standard by + + + Moves to Internet Standard + + True + 0 + Moved to Internet Standard by + + + Moves to Historic + + True + 0 + Moved to Historic by + + + Moves to Informational + + True + 0 + Moved to Informational by + + + Moves to BCP + + True + 0 + Moved to BCP by + + + Moves to Experimental + + True + 0 + Moved to Experimental by Stream state should change @@ -134,7 +180,7 @@ 0 - Approved in minute + Approved in minutes True 0 @@ -313,14 +359,14 @@ True 0 - - Maturity Change + + Conflict Review True 0 - - Conflict Review + + Status Change True 0 @@ -361,6 +407,12 @@ True 0 + + Abandonded + Formation of the group (most likely a BoF or Proposed WG) was abandoned + True + 0 + IETF @@ -415,6 +467,12 @@ True 0 + + RFC Editor + + True + 0 + Proposed Standard @@ -559,6 +617,12 @@ True 0 + + At Large Member + + True + 0 + Waiting for Scheduling @@ -760,6 +824,9 @@ Conflict Review State + + RFC Status Change + agenda active @@ -832,95 +899,113 @@ 0 - + conflrev needshep Needs Shepherd True A conflict review has been requested, but a shepherding AD has not yet been assigned 1 - + - + conflrev adrev AD Review True The sponsoring AD is reviewing the document and preparing a proposed response 2 - + - + conflrev iesgeval IESG Evaluation True The IESG is considering the proposed conflict review response 3 - + - + conflrev defer IESG Evaluation - Defer True The evaluation of the proposed conflict review response has been deferred to the next telechat 4 - + - + + conflrev + appr-reqnopub-pr + Approved Request to Not Publish - point raised + True + The IESG has approved the conflict review response (a request to not publish), but a point has been raised that should be cleared before moving to announcement to be sent + 5 + + + + conflrev + appr-noprob-pr + Approved No Problem - point raised + True + The IESG has approved the conflict review response, but a point has been raised that should be cleared before proceeding to announcement to be sent + 6 + + + conflrev appr-reqnopub-pend Approved Request to Not Publish - announcement to be sent True The IESG has approved the conflict review response (a request to not publish), but the secretariat has not yet sent the response - 5 - + 7 + - + conflrev appr-noprob-pend Approved No Problem - announcement to be sent True The IESG has approved the conflict review response, but the secretariat has not yet sent the response - 6 - + 8 + - + conflrev appr-reqnopub-sent Approved Request to Not Publish - announcement sent True The secretariat has delivered the IESG's approved conflict review response (a request to not publish) to the requester - 7 - + 9 + - + conflrev appr-noprob-sent Approved No Problem - announcement sent True The secretariat has delivered the IESG's approved conflict review response to the requester - 8 - + 10 + - + conflrev withdraw Withdrawn True The request for conflict review was withdrawn - 9 - + 11 + - + conflrev dead Dead True The conflict review has been abandoned - 10 - + 12 + draft @@ -1363,6 +1448,15 @@ 0 + + draft-rfceditor + auth48done + AUTH48-DONE + True + Final approvals are complete + 0 + + draft-stream-iab candidat @@ -1843,6 +1937,87 @@ 2 + + statchg + needshep + Needs Shepherd + True + An RFC status change has been requested, but a shepherding AD has not yet been assigned + 1 + + + + statchg + adrev + AD Review + True + The sponsoring AD is preparing an RFC status change document + 2 + + + + statchg + iesgeval + IESG Evaluation + True + The IESG is considering the proposed RFC status changes + 3 + + + + statchg + defer + IESG Evaluation - Defer + True + The evaluation of the proposed RFC status changes have been deferred to the next telechat + 4 + + + + statchg + appr-pr + Approved - point raised + True + The IESG has approved the RFC status changes, but a point has been raised that should be cleared before proceeding to announcement to be sent + 5 + + + + statchg + appr-pend + Approved - announcement to be sent + True + The IESG has approved the RFC status changes, but the secretariat has not yet sent the announcement + 6 + + + + statchg + appr-sent + Approved - announcement sent + True + The secretariat has announced the IESG's approved RFC status changes + 7 + + + + statchg + withdraw + Withdrawn + True + The request for RFC status changes was withdrawn + 8 + + + + statchg + dead + Dead + True + The RFC status changes have been abandoned + 9 + + conflrev conflrev @@ -1852,6 +2027,15 @@ 0 + + statchg + statchg + Approve + Do we approve these RFC status changes? + True + 0 + + charter r-extrev diff --git a/ietf/name/migrations/0005_add_newstat.py b/ietf/name/migrations/0005_add_newstat.py new file mode 100644 index 000000000..aabf93f67 --- /dev/null +++ b/ietf/name/migrations/0005_add_newstat.py @@ -0,0 +1,157 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +from ietf.name.models import DocTypeName, DocRelationshipName + +class Migration(DataMigration): + + def forwards(self, orm): + DocTypeName(slug='statchg',name='Status Change',used=True).save() + + def backwards(self, orm): + pass + + models = { + 'name.ballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'}, + 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.constraintname': { + 'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docrelationshipname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docremindertypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupBallotPositionName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.liaisonstatementpurposename': { + 'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.meetingtypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.rolename': { + 'Meta': {'ordering': "['order']", 'object_name': 'RoleName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.sessionstatusname': { + 'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.timeslottypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + } + } + + complete_apps = ['name'] diff --git a/ietf/name/migrations/0006_add_revname_column.py b/ietf/name/migrations/0006_add_revname_column.py new file mode 100644 index 000000000..98a832082 --- /dev/null +++ b/ietf/name/migrations/0006_add_revname_column.py @@ -0,0 +1,161 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'DocRelationshipName.revname' + db.add_column('name_docrelationshipname', 'revname', self.gf('django.db.models.fields.CharField')(default='fixme', max_length=255), keep_default=False) + + def backwards(self, orm): + + # Deleting field 'DocRelationshipName.revname' + db.delete_column('name_docrelationshipname', 'revname') + + + models = { + 'name.ballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'}, + 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.constraintname': { + 'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docrelationshipname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docremindertypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupBallotPositionName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.liaisonstatementpurposename': { + 'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.meetingtypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.rolename': { + 'Meta': {'ordering': "['order']", 'object_name': 'RoleName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.sessionstatusname': { + 'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.timeslottypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + } + } + + complete_apps = ['name'] diff --git a/ietf/name/migrations/0007_reverse_relation_names.py b/ietf/name/migrations/0007_reverse_relation_names.py new file mode 100644 index 000000000..d81e734a2 --- /dev/null +++ b/ietf/name/migrations/0007_reverse_relation_names.py @@ -0,0 +1,177 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +from ietf.name.models import DocRelationshipName + +class Migration(DataMigration): + + def update_reverse_name(self,slug,revname): + relation = DocRelationshipName.objects.get(slug=slug) + relation.revname = revname + relation.save() + + def forwards(self, orm): + + revnames = { 'obs' : 'Obsoleted by', + 'updates' : 'Updated by', + 'replaces': 'Replaced by', + 'conflrev': 'Conflict reviewed by', + } + for key in revnames: + self.update_reverse_name(key,revnames[key]) + + DocRelationshipName(slug='tops', name='Moves to Proposed Standard', revname='Moved to Proposed Standard by', used=True).save() + DocRelationshipName(slug='tois', name='Moves to Internet Standard', revname='Moved to Internet Standard by', used=True).save() + DocRelationshipName(slug='tohist', name='Moves to Historic', revname='Moved to Historic by', used=True).save() + DocRelationshipName(slug='toinf', name='Moves to Informational', revname='Moved to Informational by', used=True).save() + DocRelationshipName(slug='tobcp', name='Moves to BCP', revname='Moved to BCP by', used=True).save() + DocRelationshipName(slug='toexp', name='Moves to Experimental', revname='Moved to Experimental by', used=True).save() + + def backwards(self, orm): + pass + + models = { + 'name.ballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'}, + 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.constraintname': { + 'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docrelationshipname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docremindertypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupBallotPositionName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.liaisonstatementpurposename': { + 'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.meetingtypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.rolename': { + 'Meta': {'ordering': "['order']", 'object_name': 'RoleName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.sessionstatusname': { + 'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.timeslottypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + } + } + + complete_apps = ['name'] diff --git a/ietf/name/models.py b/ietf/name/models.py index 9568ed078..e3d1e8fce 100644 --- a/ietf/name/models.py +++ b/ietf/name/models.py @@ -24,9 +24,12 @@ class RoleName(NameModel): """AD, Chair""" class StreamName(NameModel): """IETF, IAB, IRTF, ISE, Legacy""" + class DocRelationshipName(NameModel): """Updates, Replaces, Obsoletes, Reviews, ... The relationship is always recorded in one direction.""" + revname = models.CharField(max_length=255) + class DocTypeName(NameModel): """Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email, Review, Issue, Wiki""" diff --git a/ietf/settings.py b/ietf/settings.py index 2ffde5d0c..89c6447e1 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -206,6 +206,8 @@ CHARTER_PATH = '/a/www/ietf-ftp/charters/' CHARTER_TXT_URL = 'http://www.ietf.org/charter/' CONFLICT_REVIEW_PATH = '/a/www/ietf-ftp/conflict-reviews' CONFLICT_REVIEW_TXT_URL = 'http://www.ietf.org/cr/' +STATUS_CHANGE_PATH = '/a/www/ietf-ftp/status-changes' +STATUS_CHANGE_TXT_URL = 'http://www.ietf.org/sc/' AGENDA_PATH = '/a/www/www6s/proceedings/' AGENDA_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/agenda/%(wg)s.%(ext)s' MINUTES_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/minutes/%(wg)s.%(ext)s' diff --git a/ietf/templates/base_leftmenu.html b/ietf/templates/base_leftmenu.html index cba8d1370..fce7514fd 100644 --- a/ietf/templates/base_leftmenu.html +++ b/ietf/templates/base_leftmenu.html @@ -102,6 +102,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% else %}
  • Sign in to track drafts
  • {% endif %} +{% if user|in_group:"Area_Director,Secretariat" %} +
  • RFC status changes
  • +{% endif %}
  • Meetings
  • diff --git a/ietf/templates/doc/conflict_review/change_ad.html b/ietf/templates/doc/change_ad.html similarity index 69% rename from ietf/templates/doc/conflict_review/change_ad.html rename to ietf/templates/doc/change_ad.html index 233b01f01..8fab6800f 100644 --- a/ietf/templates/doc/conflict_review/change_ad.html +++ b/ietf/templates/doc/change_ad.html @@ -8,11 +8,11 @@ {% endblock %} {% block title %} -Change the shepherding AD for the conflict review of {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }} +Change the shepherding AD for {{titletext}} {% endblock %} {% block content %} -

    Change the shepherding AD for the conflict review of {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}

    +

    Change the shepherding AD for {{titletext}}

    @@ -29,7 +29,7 @@ Change the shepherding AD for the conflict review of {{ conflictdoc.canonical_na diff --git a/ietf/templates/doc/conflict_review/change_state.html b/ietf/templates/doc/change_state.html similarity index 87% rename from ietf/templates/doc/conflict_review/change_state.html rename to ietf/templates/doc/change_state.html index a6633e371..424699d7a 100644 --- a/ietf/templates/doc/conflict_review/change_state.html +++ b/ietf/templates/doc/change_state.html @@ -20,7 +20,7 @@ form.change-state .actions { {% block content %}

    Change State: {{doc.title}}

    -

    For help on the states, see the state table.

    +

    For help on the states, see the state table.

    - Back + Back
    diff --git a/ietf/templates/doc/conflict_review/edit_telechat_date.html b/ietf/templates/doc/edit_telechat_date.html similarity index 100% rename from ietf/templates/doc/conflict_review/edit_telechat_date.html rename to ietf/templates/doc/edit_telechat_date.html diff --git a/ietf/templates/doc/conflict_review/eval_email.txt b/ietf/templates/doc/eval_email.txt similarity index 67% rename from ietf/templates/doc/conflict_review/eval_email.txt rename to ietf/templates/doc/eval_email.txt index c0009bc3f..9c15e9638 100644 --- a/ietf/templates/doc/conflict_review/eval_email.txt +++ b/ietf/templates/doc/eval_email.txt @@ -1,8 +1,8 @@ {% load mail_filters %}{% autoescape off %}To: Internet Engineering Steering Group From: IESG Secretary Reply-To: IESG Secretary -Subject: Evaluation: {{review.title}} +Subject: Evaluation: {{doc.title}} -Evaluation for {{ review.title }} can be found at <{{ review_url }}> +Evaluation for {{ doc.title }} can be found at <{{ doc_url }}> {% endautoescape%} diff --git a/ietf/templates/doc/conflict_review/notify.html b/ietf/templates/doc/notify.html similarity index 70% rename from ietf/templates/doc/conflict_review/notify.html rename to ietf/templates/doc/notify.html index 14aad867c..0dc7d8d59 100644 --- a/ietf/templates/doc/conflict_review/notify.html +++ b/ietf/templates/doc/notify.html @@ -11,11 +11,11 @@ form.edit-info #id_notify { {% endblock %} {% block title %} -Edit notification addresses for the conflict review of {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }} +Edit notification addresses for {{titletext}} {% endblock %} {% block content %} -

    Edit notification addresses for the conflict review of {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}

    +

    Edit notification addresses for {{titletext}}

    @@ -32,7 +32,7 @@ Edit notification addresses for the conflict review of {{ conflictdoc.canonical_ diff --git a/ietf/templates/doc/status_change/approval_text.txt b/ietf/templates/doc/status_change/approval_text.txt new file mode 100644 index 000000000..25310852e --- /dev/null +++ b/ietf/templates/doc/status_change/approval_text.txt @@ -0,0 +1,25 @@ +{% load mail_filters %}{% autoescape off %}From: The IESG +To: IETF-Announce +Cc: RFC Editor , {{status_change.notify}} +Subject: {{action}}: {{relateddoc.target.document.title}} to {{newstatus}} + +{% filter wordwrap:73 %}The IESG has approved changing the status of the following document: +- {{relateddoc.target.document.title }} + ({{relateddoc.target.document.canonical_name }}) to {{ newstatus }} + +This {{action|lower}} is documented at: +{{status_change_url}} + +A URL of the affected document is: +{{relateddoc_url}} + +Status Change Details: + +{{ approved_text }} + +Personnel + + {{status_change.ad.plain_name}} is the responsible Area Director. + +{% endfilter %} +{% endautoescape %} diff --git a/ietf/templates/doc/status_change/approve.html b/ietf/templates/doc/status_change/approve.html new file mode 100644 index 000000000..5c4e97ead --- /dev/null +++ b/ietf/templates/doc/status_change/approve.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% block title %}Approve {{doc.canonical_name }}{% endblock %} + +{% block morecss %} +textarea[id^="id_form-"][id$="-announcement_text"] { + overflow-x: auto; + overflow-y: scroll; + width: 800px; + height: 400px; + border: 1px solid #bbb; +} +{% endblock %} + +{% block content %} +

    Approve {{ doc.canonical_name }}

    + + + {{formset.management_form}} +
    - Back + Back
    + {% for form in formset.forms %} + {% for field in form.visible_fields %} + + + + {% endfor %} + {% endfor %} + + + +
    +
    {{ field.label_tag }}:
    + {{ field }} + {% if field.help_text %}
    {{ field.help_text }}
    {% endif %} + {{ field.errors }} +
    + Back + +
    +
    +{% endblock %} diff --git a/ietf/templates/doc/status_change/edit_relations.html b/ietf/templates/doc/status_change/edit_relations.html new file mode 100644 index 000000000..f83d3e648 --- /dev/null +++ b/ietf/templates/doc/status_change/edit_relations.html @@ -0,0 +1,73 @@ +{% extends "base.html" %} + +{% block title %}Edit List of RFCs Affected By Status Change{% endblock %} + +{% block morecss %} +form.start-rfc-status-change-review #id_notify { + width: 600px; +} +form.start-rfc-status-change-review #id_document_name { + width: 510px; +} +form.start-rfc-status-change-review .actions { + padding-top: 20px; +} +.warning { + font-weight: bold; + color: #a00; +} +{% endblock %} + +{% block pagehead %} +{% include "doc/status_change/status-change-edit-relations-js.html" %} +{% endblock %} + +{% block content %} + +

    Edit List of RFCs Affected By Status Change

    + +
    + + + + + + + +
    Affects RFCs: + {% for rfc,choice_slug in form.relations.items %} + + + + {% endfor %} + + + +
    + + + + +
    + +
    +
    Enter one of the affected RFC as RFCXXXX
    + {{ form.non_field_errors }} +
    + +
    +
    +{% endblock %} diff --git a/ietf/templates/doc/status_change/initial_template.txt b/ietf/templates/doc/status_change/initial_template.txt new file mode 100644 index 000000000..a66d68f8a --- /dev/null +++ b/ietf/templates/doc/status_change/initial_template.txt @@ -0,0 +1,17 @@ +Provide a description of what RFCs status are changed and any necessary rational for the change. + +This is a good place to document how the RFC6410 criteria for advancing to Internet Standard are met: + + (1) There are at least two independent interoperating implementations + with widespread deployment and successful operational experience. + + (2) There are no errata against the specification that would cause a + new implementation to fail to interoperate with deployed ones. + + (3) There are no unused features in the specification that greatly + increase implementation complexity. + + (4) If the technology required to implement the specification + requires patented or otherwise controlled technology, then the + set of implementations must demonstrate at least two independent, + separate and successful uses of the licensing process. diff --git a/ietf/templates/doc/status_change/start.html b/ietf/templates/doc/status_change/start.html new file mode 100644 index 000000000..07fb2f6f3 --- /dev/null +++ b/ietf/templates/doc/status_change/start.html @@ -0,0 +1,89 @@ +{% extends "base.html" %} + +{% block title %}Begin RFC status change review {% endblock %} + +{% block morecss %} +form.start-rfc-status-change-review #id_notify { + width: 600px; +} +form.start-rfc-status-change-review #id_document_name { + width: 510px; +} +form.start-rfc-status-change-review .actions { + padding-top: 20px; +} +.warning { + font-weight: bold; + color: #a00; +} +{% endblock %} + +{% block pagehead %} +{% include "doc/status_change/status-change-edit-relations-js.html" %} +{% endblock %} + +{% block content %} +

    Begin RFC status change review

    + +

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

    + +
    + + {% for field in form.visible_fields %} + + + + + {% if field.label == "Document name" %} + + + + + {% endif %} + {% endfor %} + + + +
    {{ field.label_tag }}: + {% if field.label == "Document name" %}status-change-{% endif %} + {{ field }} + {% if field.help_text %}
    {{ field.help_text }}
    {% endif %} + + {{ field.errors }} +
    Affects RFCs: + {% for rfc,choice_slug in form.relations.items %} + + + + {% endfor %} + + + +
    + + + + +
    + +
    +
    Enter one of the affected RFC as RFCXXXX
    + {{ form.non_field_errors }} +
    + +
    +
    +{% endblock %} diff --git a/ietf/templates/doc/status_change/status-change-edit-relations-js.html b/ietf/templates/doc/status_change/status-change-edit-relations-js.html new file mode 100644 index 000000000..99b08e31b --- /dev/null +++ b/ietf/templates/doc/status_change/status-change-edit-relations-js.html @@ -0,0 +1,78 @@ + diff --git a/ietf/templates/doc/status_change/status_changes.html b/ietf/templates/doc/status_change/status_changes.html new file mode 100644 index 000000000..8106fecb0 --- /dev/null +++ b/ietf/templates/doc/status_change/status_changes.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% load ietf_filters %} +{% block title %}RFC Status Changes{% endblock %} +{% block content %} +

    RFC Status Changes

    + +{% if user|in_group:"Area_Director,Secretariat" %} +

    Start new RFC status change document

    +{% endif %} +{% regroup docs by get_state as state_groups %} + + +{% for state in state_groups %} + +{% for doc in state.list %} + + + + +{% endfor %} +{% endfor %} +{% endblock content %} + diff --git a/ietf/templates/doc/status_change/submit.html b/ietf/templates/doc/status_change/submit.html new file mode 100644 index 000000000..f2e77b128 --- /dev/null +++ b/ietf/templates/doc/status_change/submit.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% block morecss %} +form #id_content { + width: 40em; + height: 450px; +} +{% endblock %} + +{% block title %} +Edit status change text for {{doc.title}} +{% endblock %} + +{% block content %} +

    Edit status change text for {{doc.title}}

    + +

    The text will be submitted as {{ doc.canonical_name }}-{{ next_rev }}

    + +
    DocumentTitle
    {{state.grouper}}
    {{ doc.displayname_with_link|safe }}{{ doc.title }}
    + {% for field in form.visible_fields %} + + + + + {% endfor %} + + + + +
    {{ field.label_tag }}: + {{ field }} + {% if field.help_text %}
    {{ field.help_text }}
    {% endif %} + {{ field.errors }} +
    + Back + + +
    + + +{% endblock %} diff --git a/ietf/templates/idrfc/document_conflict_review.html b/ietf/templates/idrfc/document_conflict_review.html index 0cb182033..41d437f6c 100644 --- a/ietf/templates/idrfc/document_conflict_review.html +++ b/ietf/templates/idrfc/document_conflict_review.html @@ -42,25 +42,26 @@ {% endif %} - - {% if not snapshot %} - - {% if ballot_summary %}
    ({{ ballot_summary }})
    {% endif %} - {% endif %} + {% endif %} Shepherding AD: diff --git a/ietf/templates/idrfc/document_status_change.html b/ietf/templates/idrfc/document_status_change.html new file mode 100644 index 000000000..c45f7ae25 --- /dev/null +++ b/ietf/templates/idrfc/document_status_change.html @@ -0,0 +1,135 @@ +{% extends "idrfc/doc_main.html" %} + +{% load ietf_filters %} + +{% block title %}{{ doc.canonical_name }}-{{ doc.rev }}{% endblock %} + +{% block pagehead %} + +{% endblock %} + +{% block content %} +{{ top|safe }} + +
    + Versions: + + {% for rev in revisions %} + {{ rev }} + {% endfor %} + +
    + +
    +
    + {% if snapshot %}Snapshot of{% endif %} + {% if doc.get_state_slug not in approved_states %}Proposed{% endif %} + Status change : {{ doc.title }} +
    + + + {% regroup sorted_relations by relationship.name as relation_groups %} + {% for relation_group in relation_groups %} + + + + + {% endfor %} + + + + + + + + + + + + + + + + + + + + + + + + + {% if not snapshot and user|has_role:"Area Director,Secretariat" and doc.get_state_slug not in approved_states %} + + {% endif %} + + {% comment %} + + {% endcomment %} + +
    {{relation_group.grouper}}:{% for rel in relation_group.list %}{{rel.target.document.canonical_name|upper|urlize_ietf_docs}}{% if not forloop.last %}, {% endif %}{% endfor %}
    Review State: +
    + {{ doc.get_state.name }} + + {% if not snapshot and user|has_role:"Area Director,Secretariat" %} + + {% if request.user|has_role:"Secretariat" %}{% if doc.get_state_slug = 'appr-pend' %} + - Approve RFC status changes + {% endif %}{% endif %} + + {% endif %} +
    +
    Telechat Date: + {% if not snapshot %} + + {% if not telechat %}Not on agenda of an IESG telechat{% else %}On agenda of {{ telechat.telechat_date|date:"Y-m-d" }} IESG telechat{% if doc.returning_item %} (returning item){% endif %}{% endif %} + + + {% if ballot_summary %} +
    + ({{ ballot_summary }}) +
    + {% endif %} + + {% endif %} +
    Shepherding AD: + + {{doc.ad}} + +
    Send notices to: + + {{doc.notify}} + +

    Last updated: {{ doc.time|date:"Y-m-d" }}
    + + + Edit Affected RFC List + + +

    + +
    + +

    RFC Status Change : {{ doc.title }} + +{% if not snapshot and user|has_role:"Area Director,Secretariat" and doc.get_state_slug != 'apprsent' %} +Change status change text +{% endif %} +

    + +{% if doc.rev %} +
    +{{ content|fill:"80"|safe|linebreaksbr|keep_spacing|sanitize_html|safe }} +
    +{% endif %} + +{% endblock %} + diff --git a/ietf/templates/registration/edit_profile.html b/ietf/templates/registration/edit_profile.html index d28efb9eb..e14f60566 100644 --- a/ietf/templates/registration/edit_profile.html +++ b/ietf/templates/registration/edit_profile.html @@ -126,7 +126,7 @@ th { {{person_form.ascii_short.label}}: - {{person_form.ascii_short}} - Short form, if any, of your name as renedered in ASCII (blank is okay) + {{person_form.ascii_short}} - Short form, if any, of your name as rendered in ASCII (blank is okay) {{person_form.affiliation.label}}: diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 3a8e61bdf..336677090 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -400,4 +400,22 @@ def make_test_data(): crdoc.save() crdoc.relateddocument_set.create(target=docalias,relationship_id='conflrev') + # A status change mid review + doc = Document.objects.create(name='status-change-imaginary-mid-review',type_id='statchg', rev='00', notify="fsm@ietf.org") + 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 + target_rfc = Document.objects.create(name='draft-ietf-random-thing', type_id='draft', std_level_id='ps') + target_rfc.set_state(State.objects.get(slug='rfc',type__slug='draft')) + target_rfc.save() + docalias = DocAlias.objects.create(name='draft-ietf-random-thing',document=target_rfc) + docalias = DocAlias.objects.create(name='rfc9999',document=target_rfc) + target_rfc = Document.objects.create(name='draft-ietf-random-otherthing', type_id='draft', std_level_id='inf') + target_rfc.set_state(State.objects.get(slug='rfc',type__slug='draft')) + target_rfc.save() + docalias = DocAlias.objects.create(name='draft-ietf-random-otherthing',document=target_rfc) + docalias = DocAlias.objects.create(name='rfc9998',document=target_rfc) + return draft From c67661311f478900b0f509d71b34b68dd68b9b72 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 28 Jan 2013 16:25:42 +0000 Subject: [PATCH 2/6] Improved validaiton of status change document name added the ability to start a status change when looking at an RFC (populating the start from accordingly) Made changes and proposed changes show on the RFC pages - Legacy-Id: 5330 --- ietf/doc/views_status_change.py | 45 +++++++++++++++--- ietf/idrfc/idrfc_wrapper.py | 5 ++ ietf/idrfc/urls.py | 2 +- ietf/idrfc/views_doc.py | 14 +++++- ietf/idtracker/templatetags/ietf_filters.py | 1 + ietf/iesg/views.py | 8 ++++ ietf/templates/doc/status_change/start.html | 27 ++++++----- .../doc/status_change/status_changes.html | 2 +- ietf/templates/idrfc/doc_tab_document.html | 47 ++++++++++--------- .../templates/idrfc/doc_tab_document_rfc.html | 2 + 10 files changed, 109 insertions(+), 44 deletions(-) diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py index ad053dfba..560f2e9fe 100644 --- a/ietf/doc/views_status_change.py +++ b/ietf/doc/views_status_change.py @@ -438,6 +438,7 @@ class StartStatusChangeForm(forms.Form): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) + self.relations = self.initial.get('relations') # telechat choices dates = [d.date for d in TelechatDate.objects.active().order_by('date')] @@ -445,15 +446,37 @@ class StartStatusChangeForm(forms.Form): def clean_document_name(self): name = self.cleaned_data['document_name'] + errors=[] + if re.search("[^a-z0-9-]", name): + errors.append("The name of the document may only contain digits, lowercase letters and dashes") + if re.search("--", name): + errors.append("Please do not put more than one hyphen between any two words in the name") + if name.startswith('status-change'): + errors.append("status-change- will be added automatically as a prefix") + if name.startswith('-'): + errors.append("status-change- will be added automatically as a prefix, starting with a - will result in status-change-%s"%name) + if re.search("-[0-9]{2}$", name): + errors.append("This name looks like ends in a version number. -00 will be added automatically. Please adjust the end of the name.") if Document.objects.filter(name='status-change-%s'%name): - raise forms.ValidationError("status-change-%s already exists"%name) + errors.append("status-change-%s already exists"%name) + if name.endswith('CHANGETHIS'): + errors.append("Please change CHANGETHIS to reflect the intent of this status change") + if errors: + raise forms.ValidationError(errors) return name + def clean_title(self): + title = self.cleaned_data['title'] + errors=[] + if title.endswith('CHANGETHIS'): + errors.append("Please change CHANGETHIS to reflect the intent of this status change") + if errors: + raise forms.ValidationError(errors) + return title + def clean(self): return clean_helper(self,StartStatusChangeForm) -#TODO - cleaned data, especially on document_name - def rfc_status_changes(request): """Show the rfc status changes that are under consideration, and those that are completed.""" @@ -466,9 +489,14 @@ def rfc_status_changes(request): context_instance = RequestContext(request)) @role_required("Area Director","Secretariat") -def start_rfc_status_change(request): +def start_rfc_status_change(request,name): """Start the RFC status change review process, setting the initial shepherding AD, and possibly putting the review on a telechat.""" + if name: + if not re.match("(?i)rfc[0-9]{4}",name): + raise Http404 + seed_rfc = get_object_or_404(Document, type="draft", docalias__name=name) + login = request.user.get_profile() relation_slugs = DocRelationshipName.objects.filter(slug__in=RELATION_SLUGS) @@ -505,8 +533,13 @@ def start_rfc_status_change(request): return HttpResponseRedirect(status_change.get_absolute_url()) else: - init = { - } + init = {} + if name: + init['title'] = "%s to CHANGETHIS" % seed_rfc.title + init['document_name'] = "%s-to-CHANGETHIS" % seed_rfc.canonical_name() + relations={} + relations[seed_rfc.canonical_name()]=None + init['relations'] = relations form = StartStatusChangeForm(initial=init) return render_to_response('doc/status_change/start.html', diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py index b9e1c7dd3..3d1e5c93d 100644 --- a/ietf/idrfc/idrfc_wrapper.py +++ b/ietf/idrfc/idrfc_wrapper.py @@ -405,6 +405,11 @@ class RfcWrapper: result['ietf_process'] = self.ietf_process.dict() return json.dumps(result, indent=2) + def underlying_document(self): + """ Expose the Document object underneath the proxy """ + from ietf.doc.models import Document + return Document.objects.get(docalias__name='rfc%04d'%self.rfc_number) + # --------------------------------------------------------------------------- class IetfProcessData: diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py index 4e2e83b04..d3acd53e2 100644 --- a/ietf/idrfc/urls.py +++ b/ietf/idrfc/urls.py @@ -42,7 +42,7 @@ urlpatterns = patterns('', (r'^active/$', views_search.active), (r'^in-last-call/$', views_search.in_last_call), url(r'^rfc-status-changes/$', views_status_change.rfc_status_changes, name='rfc_status_changes'), - url(r'^start-rfc-status-change/$', views_status_change.start_rfc_status_change, name='start_rfc_status_change'), + url(r'^start-rfc-status-change/(?P[A-Za-z0-9._+-]*)$', views_status_change.start_rfc_status_change, name='start_rfc_status_change'), url(r'^ad/(?P[A-Za-z0-9.-]+)/$', views_search.by_ad, name="doc_search_by_ad"), url(r'^(?P[A-Za-z0-9._+-]+)/((?P[0-9-]+)/)?$', views_doc.document_main, name="doc_view"), diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index 6f5a76541..6936e447d 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -54,6 +54,7 @@ from ietf.doc.models import * from ietf.doc.utils import * from ietf.utils.history import find_history_active_at from ietf.ietfauth.decorators import has_role +from ietf.doc.views_status_change import RELATION_SLUGS as status_change_relationships def render_document_top(request, doc, tab, name): tabs = [] @@ -236,7 +237,7 @@ def document_history(request, name): diff_revisions = [] seen = set() - diffable = name.startswith("draft") or name.startswith("charter") or name.startswith("conflict-review") + diffable = name.startswith("draft") or name.startswith("charter") or name.startswith("conflict-review") or name.startswith("status-change") if diffable: for e in NewRevisionDocEvent.objects.filter(type="new_revision", doc__in=diff_documents).select_related('doc').order_by("-time", "-id"): @@ -250,6 +251,9 @@ def document_history(request, name): elif name.startswith("conflict-review"): h = find_history_active_at(e.doc, e.time) url = settings.CONFLICT_REVIEW_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev)) + elif name.startswith("status-change"): + h = find_history_active_at(e.doc, e.time) + url = settings.STATUS_CHANGE_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev)) elif name.startswith("draft"): # rfcdiff tool has special support for IDs url = e.doc.name + "-" + e.rev @@ -450,6 +454,14 @@ def document_main_rfc(request, rfc_number, tab): content1 = "" content2 = "" + info['status_changes'] = ', '.join([ rel.source.canonical_name() for rel in RelatedDocument.objects.filter(relationship__in=status_change_relationships,target__document=doc.underlying_document()) if rel.source.get_state_slug() in ('appr-sent','appr-pend')]) + info['proposed_status_changes'] = ', '.join([ rel.source.canonical_name() for rel in RelatedDocument.objects.filter(relationship__in=status_change_relationships,target__document=doc.underlying_document()) if rel.source.get_state_slug() in ('needshep','adrev','iesgeval','defer','appr-pr')]) + + print "DEBUGGING" + print doc.underlying_document() + print "status_changes",info['status_changes'] + print "proposed_status_changes",info['proposed_status_changes'] + history = _get_history(doc, None) template = "idrfc/doc_tab_%s" % tab diff --git a/ietf/idtracker/templatetags/ietf_filters.py b/ietf/idtracker/templatetags/ietf_filters.py index f48a30a8a..b55f88aac 100644 --- a/ietf/idtracker/templatetags/ietf_filters.py +++ b/ietf/idtracker/templatetags/ietf_filters.py @@ -226,6 +226,7 @@ def urlize_ietf_docs(string, autoescape=None): string = re.sub("(?)(FYI ?)0{0,3}(\d+)", "\\1\\2", string) string = re.sub("(?)(draft-[-0-9a-zA-Z._+]+)", "\\1", string) string = re.sub("(?)(conflict-review-[-0-9a-zA-Z._+]+)", "\\1", string) + string = re.sub("(?)(status-change-[-0-9a-zA-Z._+]+)", "\\1", string) return mark_safe(string) urlize_ietf_docs.is_safe = True urlize_ietf_docs.needs_autoescape = True diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index 9b8da1bf7..a0ab5aa05 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -166,6 +166,14 @@ def get_doc_sectionREDESIGN(doc): s = "332" else: s = "331" + elif doc.type_id == 'statchg': + # TODO This is WRONG + s="211" + #protocol_action = False + #for relation in doc.relateddocument_set.filter(relationship__in="('tops','tois','tohist','toinf','tobcp,'toexp')"): + # if relation.relationship.slug in ('tops','tois') or relation.target.document.std_level.slug in ('std','ds','ps'): + # protocol_action = True + #if protocol_action: return s diff --git a/ietf/templates/doc/status_change/start.html b/ietf/templates/doc/status_change/start.html index 07fb2f6f3..0e15d969a 100644 --- a/ietf/templates/doc/status_change/start.html +++ b/ietf/templates/doc/status_change/start.html @@ -6,6 +6,9 @@ form.start-rfc-status-change-review #id_notify { width: 600px; } +form.start-rfc-status-change-review #id_title { + width: 600px; +} form.start-rfc-status-change-review #id_document_name { width: 510px; } @@ -29,18 +32,6 @@ form.start-rfc-status-change-review .actions {
    - {% for field in form.visible_fields %} - - - - - {% if field.label == "Document name" %}
    {{ field.label_tag }}: - {% if field.label == "Document name" %}status-change-{% endif %} - {{ field }} - {% if field.help_text %}
    {{ field.help_text }}
    {% endif %} - - {{ field.errors }} -
    Affects RFCs: @@ -77,7 +68,17 @@ form.start-rfc-status-change-review .actions { {{ form.non_field_errors }} - {% endif %} + {% for field in form.visible_fields %} + + + + {% endfor %} {% endif %} {% for doc in t.docs.s223 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} + + + {% for doc in t.docs.s231 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} + {% if t.docs.s222 %}{% endif %} + {% for doc in t.docs.s232 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} + {% if t.docs.s223 %}{% endif %} + {% for doc in t.docs.s233 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} + @@ -123,7 +131,7 @@ font-size:80%; font-style:italic; {% if t.docs.s323 %}{% endif %} {% for doc in t.docs.s323 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} - + {% for doc in t.docs.s331 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} {% if t.docs.s332 %}{% endif %} @@ -131,6 +139,14 @@ font-size:80%; font-style:italic; {% if t.docs.s333 %}{% endif %} {% for doc in t.docs.s333 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} + + + {% for doc in t.docs.s341 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} + {% if t.docs.s342 %}{% endif %} + {% for doc in t.docs.s342 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} + {% if t.docs.s343 %}{% endif %} + {% for doc in t.docs.s343 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%} + {% if t.docs.s411 or t.docs.s412%}{% endif %} diff --git a/ietf/templates/iesg/agenda_outline_23.html b/ietf/templates/iesg/agenda_outline_23.html index 176b274df..9311c9e25 100644 --- a/ietf/templates/iesg/agenda_outline_23.html +++ b/ietf/templates/iesg/agenda_outline_23.html @@ -73,8 +73,29 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% endwith %}{# title2 #} +{% with "2.3 Status Changes" as title2 %} +{% with 1 as title2_first %} + +{% with "2.3.1 New Items" as title3 %} +{% with docs.s231 as section_docs %}{% include doc_template %}{% endwith %} +{% endwith %} +{% endwith %}{# title2_first #} + +{% with "2.3.2 Returning Items" as title3 %} +{% with docs.s232 as section_docs %}{% include doc_template %}{% endwith %} +{% endwith %} + +{% if docs.s233 %} +{% with "2.3.3 For Action" as title3 %} +{% with docs.s233 as section_docs %}{% include doc_template %}{% endwith %} +{% endwith %} +{% endif %} + +{% endwith %}{# title2 #} + {% endwith %}{# title1 #} + {% with "3. Document Actions" as title1 %}{% with 1 as title1_first %} {% with "3.1 WG Submissions" as title2 %} @@ -119,21 +140,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% endwith %}{# title2 #} -{% with "3.3 IRTF and Independent Submission Stream Documents" as title2 %} +{% with "3.3 Status Changes" as title2 %} {% with 1 as title2_first %} {% with "3.3.1 New Items" as title3 %} -{% with docs.s331 as section_docs %}{% include doc_conflict_template %}{% endwith %} +{% with docs.s331 as section_docs %}{% include doc_template %}{% endwith %} {% endwith %} {% endwith %}{# title2_first #} {% with "3.3.2 Returning Items" as title3 %} -{% with docs.s332 as section_docs %}{% include doc_conflict_template %}{% endwith %} +{% with docs.s332 as section_docs %}{% include doc_template %}{% endwith %} {% endwith %} {% if docs.s333 %} {% with "3.3.3 For Action" as title3 %} -{% with docs.s333 as section_docs %}{% include doc_conflict_template %}{% endwith %} +{% with docs.s333 as section_docs %}{% include doc_template %}{% endwith %} +{% endwith %} +{% endif %} + +{% endwith %} {# title2 #} + +{% with "3.4 IRTF and Independent Submission Stream Documents" as title2 %} +{% with 1 as title2_first %} + +{% with "3.4.1 New Items" as title3 %} +{% with docs.s341 as section_docs %}{% include doc_conflict_template %}{% endwith %} +{% endwith %} +{% endwith %}{# title2_first #} + +{% with "3.4.2 Returning Items" as title3 %} +{% with docs.s342 as section_docs %}{% include doc_conflict_template %}{% endwith %} +{% endwith %} + +{% if docs.s343 %} +{% with "3.4.3 For Action" as title3 %} +{% with docs.s343 as section_docs %}{% include doc_conflict_template %}{% endwith %} {% endwith %} {% endif %} diff --git a/ietf/templates/iesg/moderator_conflict_doc.html b/ietf/templates/iesg/moderator_conflict_doc.html index e96466f56..7dc96539c 100644 --- a/ietf/templates/iesg/moderator_conflict_doc.html +++ b/ietf/templates/iesg/moderator_conflict_doc.html @@ -60,11 +60,6 @@ Last call ends: {{ doc.obj.most_recent_ietflc.expires.date|default:"(none)" }} {% endfor %} -{% if title1|startswith:"2." %} -

    ____ open positions
    -[   ] would you like to record a position?

    -{% endif %} -

    ____ active discusses
    [   ] there are [#] discusses in the tracker on this document. Do we need to talk about them now?

    @@ -72,13 +67,10 @@ Last call ends: {{ doc.obj.most_recent_ietflc.expires.date|default:"(none)" }}

    (Ballot not issued)

    {% endif %} -{% if title2|startswith:"3.1" or title2|startswith:"3.2" %} -

    Does anyone have an[y further] objection to this document being published as an {{ doc.obj.intended_std_level }} RFC?

    -{% endif %} -{% if title3|startswith:"3.3.1" or title3|startswith:"3.3.2" %} +{% if title3|startswith:"3.4.1" or title3|startswith:"3.4.2" %}

    Does anyone have an objection to the RFC Editor publishing this document as an {{ doc.obj.intended_std_level }} RFC?

    {% endif %} -{% if title3|startswith:"3.3.3" %} +{% if title3|startswith:"3.4.3" %}

    Who will do the review of this document?

    {% endif %} @@ -86,45 +78,7 @@ Last call ends: {{ doc.obj.most_recent_ietflc.expires.date|default:"(none)" }} Next State:
    Sub State:

    -{% if title3|startswith:"2.1.1" or title3|startswith:"2.1.2" %} -

    If APPROVED - The Secretariat will send a working group - submission, Protocol Action Announcement.

    - -

    If APPROVED with caveats - The Secretariat will send a working - group submission, Protocol Action Announcement that includes the - [RFC Editor Note, IESG Note, etc.] to be drafted by [Name that - AD].

    -{% endif %} - -{% if title3|startswith:"2.2.1" or title3|startswith:"2.2.2" %} -

    If APPROVED - The Secretariat will send an individual submission, - Protocol Action Announcement.

    - -

    If APPROVED with caveats - The Secretariat will send an - individual submission, Protocol Action Announcement that includes - the [RFC Editor Note, IESG Note, etc.] to be drafted by [Name that - AD].

    -{% endif %} - -{% if title3|startswith:"3.1.1" or title3|startswith:"3.1.2" %} -

    If APPROVED - The Secretariat will send a working group submission - Document Action Announcement.

    - -

    If APPROVED with caveats - The Secretariat will send a working - group submission Document Action announcement that includes the [RFC - Ed. Note, IESG, note, etc.] from [Name that AD].

    -{% endif %} - -{% if title3|startswith:"3.2.1" or title3|startswith:"3.2.2" %} -

    If APPROVED - The Secretariat will send an individual submission - Document Action Announcement.

    - -

    If APPROVED with caveats - The Secretariat will send an - individual submission Document Action announcement that includes the - [RFC Ed. Note, IESG, note, etc.] from [Name that AD].

    -{% endif %} - -{% if title3|startswith:"3.3.1" or title3|startswith:"3.3.2" %} +{% if title3|startswith:"3.4.1" or title3|startswith:"3.4.2" %}

    If APPROVED - The Secretariat will send a standard no problem message to the RFC Editor. [Name of AD] will you supply the text for the IESG Note?

    diff --git a/ietf/templates/iesg/moderator_doc.html b/ietf/templates/iesg/moderator_doc.html index a20dfd9a5..d4b6a82c7 100644 --- a/ietf/templates/iesg/moderator_doc.html +++ b/ietf/templates/iesg/moderator_doc.html @@ -102,6 +102,16 @@ Sub State:

    AD].

    {% endif %} +{% if title3|startswith:"2.3.1" or title3|startswith:"2.3.2" %} +

    If APPROVED - The Secretariat will send the associated status change + Protocol Action Announcements.

    + +

    If APPROVED with caveats - The Secretariat will send the + associated status change Protocol Action Announcements that includes the + [RFC Editor Note, IESG Note, etc.] to be drafted by [Name that + AD].

    +{% endif %} + {% if title3|startswith:"3.1.1" or title3|startswith:"3.1.2" %}

    If APPROVED - The Secretariat will send a working group submission Document Action Announcement.

    @@ -121,6 +131,15 @@ Sub State:

    {% endif %} {% if title3|startswith:"3.3.1" or title3|startswith:"3.3.2" %} +

    If APPROVED - The Secretariat will send the associated status change + Document Action Announcements.

    + +

    If APPROVED with caveats - The Secretariat will send the associated + status change Document Action announcements that includes the [RFC + Ed. Note, IESG, note, etc.] from [Name that AD].

    +{% endif %} + +{% if title3|startswith:"3.4.1" or title3|startswith:"3.4.2" %}

    If APPROVED - The Secretariat will send a standard no problem message to the RFC Editor. [Name of AD] will you supply the text for the IESG Note?

    diff --git a/ietf/templates/iesg/scribe_template.html b/ietf/templates/iesg/scribe_template.html index 9a779ab6f..78c97dd1b 100644 --- a/ietf/templates/iesg/scribe_template.html +++ b/ietf/templates/iesg/scribe_template.html @@ -60,6 +60,9 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. {% for doc in docs.s221 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} {% for doc in docs.s222 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} {% for doc in docs.s223 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} +{% for doc in docs.s231 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} +{% for doc in docs.s232 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} +{% for doc in docs.s233 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} {% for doc in docs.s311 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} {% for doc in docs.s312 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} {% for doc in docs.s313 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} @@ -69,6 +72,9 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. {% for doc in docs.s331 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} {% for doc in docs.s332 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} {% for doc in docs.s333 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} +{% for doc in docs.s341 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} +{% for doc in docs.s342 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} +{% for doc in docs.s343 %}{% include "iesg/scribe_doc2.html" %}{%endfor%} {% endfilter %} From 8a7c5ac977f7f51c03d3a2c2fc268c87727126f8 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 4 Feb 2013 02:24:14 +0000 Subject: [PATCH 4/6] All documents show on the AD status page now - Legacy-Id: 5422 --- ietf/doc/models.py | 3 +- ietf/idrfc/urls.py | 1 + ietf/idrfc/views_search.py | 124 ++++++++++++++++++++++++++++++ ietf/templates/base_leftmenu.html | 3 +- ietf/templates/idrfc/by_ad2.html | 88 +++++++++++++++++++++ 5 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 ietf/templates/idrfc/by_ad2.html diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 1cefeda24..a64753d0e 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -305,7 +305,8 @@ class Document(DocumentInfo): iesg_state_summary = iesg_state_summary + "::"+"::".join(tag.name for tag in iesg_substate) if self.get_state_slug() == "rfc": - return "RFC %d" % (urlreverse('doc_view', args=['rfc%d' % self.rfc_number]), self.rfc_number) + #return "RFC %d" % (urlreverse('doc_view', args=['rfc%d' % int(self.rfc_number())]), int(self.rfc_number())) + return "RFC %d (%s)" % (int(self.rfc_number()), self.std_level) elif self.get_state_slug() == "repl": rs = self.replaced_by() if rs: diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py index d3acd53e2..91866bbac 100644 --- a/ietf/idrfc/urls.py +++ b/ietf/idrfc/urls.py @@ -44,6 +44,7 @@ urlpatterns = patterns('', url(r'^rfc-status-changes/$', views_status_change.rfc_status_changes, name='rfc_status_changes'), url(r'^start-rfc-status-change/(?P[A-Za-z0-9._+-]*)$', views_status_change.start_rfc_status_change, name='start_rfc_status_change'), url(r'^ad/(?P[A-Za-z0-9.-]+)/$', views_search.by_ad, name="doc_search_by_ad"), + url(r'^ad2/(?P[A-Za-z0-9.-]+)/$', views_search.by_ad2, name="doc_search_by_ad2"), url(r'^(?P[A-Za-z0-9._+-]+)/((?P[0-9-]+)/)?$', views_doc.document_main, name="doc_view"), url(r'^(?P[A-Za-z0-9._+-]+)/history/$', views_doc.document_history, name="doc_history"), diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index de2e8b789..bd0f65f09 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -38,6 +38,7 @@ from django.template import RequestContext from django.views.decorators.cache import cache_page from ietf.idtracker.models import IDState, IESGLogin, IDSubState, Area, InternetDraft, Rfc, IDInternal, IETFWG from ietf.idrfc.models import RfcIndex +from ietf.ipr.models import IprDraft from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect from ietf.idrfc.idrfc_wrapper import IdWrapper,RfcWrapper,IdRfcWrapper from ietf.utils import normalize_draftname @@ -579,6 +580,129 @@ def by_ad(request, name): results.sort(key=lambda obj: obj.view_sort_key_byad()) return render_to_response('idrfc/by_ad.html', {'form':form, 'docs':results,'meta':meta, 'ad_name':ad_name}, context_instance=RequestContext(request)) +def ad_dashboard_group(doc): + + if doc.type.slug=='draft': + if doc.get_state_slug('draft') == 'rfc': + return 'RFC' + elif doc.get_state_slug('draft') == 'active' and doc.get_state_slug('draft-iesg'): + return '%s Internet-Draft' % doc.get_state('draft-iesg').name + else: + return '%s Internet-Draft' % doc.get_state('draft').name + elif doc.type.slug=='conflrev': + if doc.get_state_slug('conflrev') in ('appr-reqnopub-sent','appr-noprob-sent'): + return 'Approved Conflict Review' + elif doc.get_state_slug('conflrev') in ('appr-reqnopub-pend','appr-noprob-pend','appr-reqnopub-pr','appr-noprob-pr'): + return "%s Conflict Review" % State.objects.get(type__slug='draft-iesg',slug='approved') + else: + return '%s Conflict Review' % doc.get_state('conflrev') + elif doc.type.slug=='statchg': + if doc.get_state_slug('statchg') in ('appr-sent',): + return 'Approved Status Change' + if doc.get_state_slug('statchg') in ('appr-pend','appr-pr'): + return '%s Status Change' % State.objects.get(type__slug='draft-iesg',slug='approved') + else: + return '%s Status Change' % doc.get_state('statchg') + elif doc.type.slug=='charter': + if doc.get_state_slug('charter') == 'approved': + return "Approved Charter" + else: + return '%s Charter' % doc.get_state('charter') + else: + return "Document" + +def ad_dashboard_sort_key(doc): + + if doc.type.slug=='draft' and doc.get_state_slug('draft') == 'rfc': + return "21%04d" % int(doc.rfc_number()) + if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'appr-sent': + return "22%d" % 0 # TODO - get the date of the transition into this state here + if doc.type.slug=='conflrev' and doc.get_state_slug('conflrev') in ('appr-reqnopub-sent','appr-noprob-sent'): + return "23%d" % 0 # TODO - get the date of the transition into this state here + if doc.type.slug=='charter' and doc.get_state_slug('charter') == 'approved': + return "24%d" % 0 # TODO - get the date of the transition into this state here + + seed = ad_dashboard_group(doc) + + if doc.type.slug=='conflrev' and doc.get_state_slug('conflrev') == 'adrev': + state = State.objects.get(type__slug='draft-iesg',slug='ad-eval') + return "1%d%s" % (state.order,seed) + + if doc.type.slug=='charter': + if doc.get_state_slug('charter') in ('notrev','infrev'): + return "100%s" % seed + elif doc.get_state_slug('charter') == 'intrev': + state = State.objects.get(type__slug='draft-iesg',slug='ad-eval') + return "1%d%s" % (state.order,seed) + elif doc.get_state_slug('charter') == 'extrev': + state = State.objects.get(type__slug='draft-iesg',slug='lc') + return "1%d%s" % (state.order,seed) + elif doc.get_state_slug('charter') == 'iesgrev': + state = State.objects.get(type__slug='draft-iesg',slug='iesg-eva') + return "1%d%s" % (state.order,seed) + + if seed.startswith('Needs Shepherd'): + return "100%s" % seed + if seed.endswith(' Document'): + seed = seed[:-9] + elif seed.endswith(' Internet-Draft'): + seed = seed[:-15] + elif seed.endswith(' Conflict Review'): + seed = seed[:-16] + elif seed.endswith(' Status Change'): + seed = seed[:-14] + state = State.objects.filter(type__slug='draft-iesg',name=seed) + if state: + ageseconds = 0 + changetime= doc.latest_event(type='changed_document') + if changetime: + ageseconds = (datetime.datetime.now()-doc.latest_event(type='changed_document').time).total_seconds() + return "1%d%s%010d" % (state[0].order,seed,ageseconds) + + return "3%s" % seed + +def by_ad2(request, name): + responsible = Document.objects.values_list('ad', flat=True).distinct() + for p in Person.objects.filter(Q(role__name__in=("pre-ad", "ad"), + role__group__type="area", + role__group__state="active") + | Q(pk__in=responsible)).distinct(): + if name == p.full_name_as_key(): + ad_id = p.id + ad_name = p.plain_name() + break + docqueryset = Document.objects.filter(ad__id=ad_id) + docs=[] + for doc in docqueryset: + doc.ad_dashboard_sort_key = ad_dashboard_sort_key(doc) + doc.ad_dashboard_group = ad_dashboard_group(doc) + if doc.get_state_slug() == 'rfc': + doc.display_date = doc.latest_event(type='published_rfc').time + else: + revision = doc.latest_event(type='new_revision') + if revision: + doc.display_date = revision.time + # This might be better handled as something Documents know about themselves + now = datetime.datetime.now() + doc.can_expire = (doc.type.slug=='draft' and doc.get_state_slug('draft')=='active' and ( not doc.get_state('draft-iesg') or doc.get_state('draft-iesg').order >= 42) and doc.expires>now) + if doc.get_state_slug('draft') == 'rfc': + doc.obsoleted_by = ", ".join([ 'RFC %04d' % int(rel.source.rfc_number()) for alias in doc.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='obsoletes') ] ) + doc.updated_by = ", ".join([ 'RFC %04d' % int(rel.source.rfc_number()) for alias in doc.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='updates') ] ) + doc.has_errata = bool(doc.tags.filter(slug="errata")) + else: + s = doc.get_state("draft-rfceditor") + if s: + # extract possible extra annotations + tags = doc.tags.filter(slug__in=("iana", "ref")) + doc.rfc_editor_state = "*".join([s.name] + [t.slug.upper() for t in tags]) + if doc.type.slug == 'draft': + doc.iprCount = IprDraft.objects.filter(document=doc, ipr__status__in=[1,3]).count() + doc.iprUrl = "/ipr/search?option=document_search&id_document_tag=%s" % doc.name + docs.append(doc) + docs.sort(key=ad_dashboard_sort_key) + return render_to_response('idrfc/by_ad2.html',{'docs':docs,'ad_name':ad_name}, context_instance=RequestContext(request)) + + @cache_page(15*60) # 15 minutes def all(request): if settings.USE_DB_REDESIGN_PROXY_CLASSES: diff --git a/ietf/templates/base_leftmenu.html b/ietf/templates/base_leftmenu.html index fce7514fd..9e1f255ee 100644 --- a/ietf/templates/base_leftmenu.html +++ b/ietf/templates/base_leftmenu.html @@ -40,7 +40,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  • {% if request.user.is_authenticated %}Manage Account{% else %}New Account{% endif %}
  • {% if user|in_group:"Area_Director" %}
  • AD Dashboard
  • -
  • My Documents
  • +
  • My Documents (old)
  • +
  • My Documents (new)
  • Next Telechat
  • Discusses
  • Working Groups
  • diff --git a/ietf/templates/idrfc/by_ad2.html b/ietf/templates/idrfc/by_ad2.html new file mode 100644 index 000000000..05b9618d0 --- /dev/null +++ b/ietf/templates/idrfc/by_ad2.html @@ -0,0 +1,88 @@ +{% extends "base.html" %} + +{% load ietf_filters %} +{% load ietf_streams_redesign %} +{% load ballot_icon_redesign %} + +{% block title %}Documents for {{ ad_name }}{% endblock %} + +{% block content %} +

    Documents for {{ ad_name }}

    + +{% regroup docs by ad_dashboard_group as grouped_docs %} + +
    {{ field.label_tag }}: + {% if field.label == "Document name" %}status-change-{% endif %} + {{ field }} + {% if field.help_text %}
    {{ field.help_text }}
    {% endif %} + + {{ field.errors }} +
    diff --git a/ietf/templates/doc/status_change/status_changes.html b/ietf/templates/doc/status_change/status_changes.html index 8106fecb0..5b84dd4ab 100644 --- a/ietf/templates/doc/status_change/status_changes.html +++ b/ietf/templates/doc/status_change/status_changes.html @@ -5,7 +5,7 @@

    RFC Status Changes

    {% if user|in_group:"Area_Director,Secretariat" %} -

    Start new RFC status change document

    +

    Start new RFC status change document

    {% endif %} {% regroup docs by get_state as state_groups %} diff --git a/ietf/templates/idrfc/doc_tab_document.html b/ietf/templates/idrfc/doc_tab_document.html index e4f91e1bd..7ec5f7bc8 100644 --- a/ietf/templates/idrfc/doc_tab_document.html +++ b/ietf/templates/idrfc/doc_tab_document.html @@ -47,29 +47,32 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% block doc_metabuttons %} {% if user|in_group:"Area_Director,Secretariat" %} -
    -{% ifequal doc.draft_status "Expired" %} -{% if not doc.resurrect_requested_by %} -Request resurrect -{% endif %} -{% if user|in_group:"Secretariat" %} -Resurrect -{% endif %} -{% else %} -{% if stream_info.stream.name == 'ISE' or stream_info.stream.name == 'IRTF' %} -{% if user|in_group:"Secretariat" and not info.conflict_reviews %} -{% url conflict_review_start name=doc.draft_name as start_review_url %}{% if start_review_url %}Begin IETF Conflict Review {% if not doc.underlying_document.intended_std_level %}(note that intended status is not set){% endif %}{% endif %} -{% endif %} -{% else %} - {% if stream_info.stream.name == 'IETF'%}{%if not doc.in_ietf_process %} - {% url doc_edit_info name=doc.draft_name as doc_edit_url %}{% if doc_edit_url %}Begin IESG Processing{% endif %} - {% endif %}{% endif %} -{% endif %} -{% endifequal %} - -
    +
    + {% ifequal doc.draft_status "Expired" %} + {% if not doc.resurrect_requested_by and user|in_group:"Area_Director" %} + Request resurrect + {% endif %} + {% if user|in_group:"Secretariat" %} + Resurrect + {% endif %} + {% else %} {# not expired #} + {% if stream_info.stream.name == 'ISE' or stream_info.stream.name == 'IRTF' %} + {% if user|in_group:"Secretariat" and not info.conflict_reviews %} + {% url conflict_review_start name=doc.draft_name as start_review_url %}{% if start_review_url %}Begin IETF Conflict Review {% if not doc.underlying_document.intended_std_level %}(note that intended status is not set){% endif %}{% endif %} + {% endif %} + {% else %} {# Not a candidate for conflict review #} + {% if stream_info.stream.name == 'IETF' and not doc.in_ietf_process %} + {% url doc_edit_info name=doc.draft_name as doc_edit_url %}{% if doc_edit_url %}Begin IESG Processing{% endif %} + {% else %} + {% if doc.underlying_document.get_state_slug == 'rfc' %} + {% url start_rfc_status_change name=doc.underlying_document.canonical_name as start_url %}{% if start_url %}Start {% if info.proposed_status_changes %}An Additional {% endif %}Status Change{% endif %} + {% endif %} + {% endif %} + {% endif %} + {% endifequal %} +
    {% endif %}{# if user in group #} -{% endblock doc_metabuttons%} +{% endblock doc_metabuttons %}
    diff --git a/ietf/templates/idrfc/doc_tab_document_rfc.html b/ietf/templates/idrfc/doc_tab_document_rfc.html index dab62087a..8d595dd64 100644 --- a/ietf/templates/idrfc/doc_tab_document_rfc.html +++ b/ietf/templates/idrfc/doc_tab_document_rfc.html @@ -41,6 +41,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% if doc.updated_by %}
    Updated by {{ doc.updated_by|urlize_ietf_docs }}{%endif %} {% if doc.obsoletes %}
    Obsoletes {{ doc.obsoletes|urlize_ietf_docs }}{%endif %} {% if doc.updates %}
    Updates {{ doc.updates|urlize_ietf_docs }}{%endif %} +{% if info.status_changes %}
    Status changed by {{ info.status_changes|urlize_ietf_docs }}{% endif %} +{% if info.proposed_status_changes %}
    Proposed status changes by {{ info.proposed_status_changes|urlize_ietf_docs }}{% endif %} {% if doc.also %}
    Also Known As {{ doc.also|urlize_ietf_docs }}{%endif %} {% if doc.draft_name %}
    Was {{doc.draft_name}}{% endif %} {% if doc.has_errata %}
    Errata{% endif %} From 1cff6187f580d334c7f88dd203ac10520ec84f38 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 28 Jan 2013 20:16:17 +0000 Subject: [PATCH 3/6] Put status changes on the agenda, moderator, and package pages - Legacy-Id: 5331 --- ietf/iesg/views.py | 39 +++-- ietf/templates/iesg/agenda_conflict_doc.html | 2 +- ietf/templates/iesg/agenda_documents.html | 143 ------------------ .../iesg/agenda_documents_redesign.html | 18 ++- ietf/templates/iesg/agenda_outline_23.html | 49 +++++- .../iesg/moderator_conflict_doc.html | 52 +------ ietf/templates/iesg/moderator_doc.html | 19 +++ ietf/templates/iesg/scribe_template.html | 6 + 8 files changed, 118 insertions(+), 210 deletions(-) delete mode 100644 ietf/templates/iesg/agenda_documents.html diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index a0ab5aa05..28e283906 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -159,21 +159,28 @@ def get_doc_sectionREDESIGN(doc): s = s + "1" elif doc.type_id == 'charter': s = get_wg_section(doc.group) + elif doc.type_id == 'statchg': + protocol_action = False + for relation in doc.relateddocument_set.filter(relationship__slug__in=('tops','tois','tohist','toinf','tobcp','toexp')): + if relation.relationship.slug in ('tops','tois') or relation.target.document.std_level.slug in ('std','ds','ps'): + protocol_action = True + if protocol_action: + s="23" + else: + s="33" + if doc.get_state_slug() not in ("iesgeval", "defer", "appr-pr", "appr-pend", "appr-sent"): + s = s + "3" + elif doc.returning_item(): + s = s + "2" + else: + s = s + "1" elif doc.type_id == 'conflrev': if doc.get_state('conflrev').slug not in ('adrev','iesgeval','appr-reqnopub-pend','appr-reqnopub-sent','appr-noprob-pend','appr-noprob-sent','defer'): - s = "333" + s = "343" elif doc.returning_item(): - s = "332" + s = "342" else: - s = "331" - elif doc.type_id == 'statchg': - # TODO This is WRONG - s="211" - #protocol_action = False - #for relation in doc.relateddocument_set.filter(relationship__in="('tops','tois','tohist','toinf','tobcp,'toexp')"): - # if relation.relationship.slug in ('tops','tois') or relation.target.document.std_level.slug in ('std','ds','ps'): - # protocol_action = True - #if protocol_action: + s = "341" return s @@ -233,6 +240,8 @@ def agenda_docs(date, next_agenda): docmatches.sort(key=lambda d: d.balloting_started) res = dict(("s%s%s%s" % (i, j, k), []) for i in range(2, 5) for j in range (1, 4) for k in range(1, 4)) + for k in range(1,4): + res['s34%d'%k]=[] for id in docmatches: section_key = "s"+get_doc_section(id) if section_key not in res: @@ -287,6 +296,9 @@ def _agenda_json(request, date=None): data['sections']['2.2'] = {'title':"Individual Submissions"} data['sections']['2.2.1'] = {'title':"New Items", 'docs':[]} data['sections']['2.2.2'] = {'title':"Returning Items", 'docs':[]} + data['sections']['2.3'] = {'title':"Individual Submissions"} + data['sections']['2.3.1'] = {'title':"New Items", 'docs':[]} + data['sections']['2.3.2'] = {'title':"Returning Items", 'docs':[]} data['sections']['3'] = {'title':"Document Actions"} data['sections']['3.1'] = {'title':"WG Submissions"} data['sections']['3.1.1'] = {'title':"New Items", 'docs':[]} @@ -294,9 +306,12 @@ def _agenda_json(request, date=None): data['sections']['3.2'] = {'title':"Individual Submissions Via AD"} data['sections']['3.2.1'] = {'title':"New Items", 'docs':[]} data['sections']['3.2.2'] = {'title':"Returning Items", 'docs':[]} - data['sections']['3.3'] = {'title':"IRTF and Independent Submission Stream Documents"} + data['sections']['3.3'] = {'title':"Status Changes"} data['sections']['3.3.1'] = {'title':"New Items", 'docs':[]} data['sections']['3.3.2'] = {'title':"Returning Items", 'docs':[]} + data['sections']['3.4'] = {'title':"IRTF and Independent Submission Stream Documents"} + data['sections']['3.4.1'] = {'title':"New Items", 'docs':[]} + data['sections']['3.4.2'] = {'title':"Returning Items", 'docs':[]} data['sections']['4'] = {'title':"Working Group Actions"} data['sections']['4.1'] = {'title':"WG Creation"} data['sections']['4.1.1'] = {'title':"Proposed for IETF Review", 'wgs':[]} diff --git a/ietf/templates/iesg/agenda_conflict_doc.html b/ietf/templates/iesg/agenda_conflict_doc.html index 43d3c1187..ff9afbbe9 100644 --- a/ietf/templates/iesg/agenda_conflict_doc.html +++ b/ietf/templates/iesg/agenda_conflict_doc.html @@ -39,7 +39,7 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. {% if title2_first %}{% if title1_first %}

    {{ title1 }}

    {% endif %}

    {{ title2 }}

    -{% if title2|startswith:"3.3" %} +{% if title2|startswith:"3.4" %}
    The IESG will use RFC 5742 responses: 1) The IESG has concluded that there is no conflict between this document and IETF work; 2) diff --git a/ietf/templates/iesg/agenda_documents.html b/ietf/templates/iesg/agenda_documents.html deleted file mode 100644 index 39bd29aaa..000000000 --- a/ietf/templates/iesg/agenda_documents.html +++ /dev/null @@ -1,143 +0,0 @@ -{% extends "base.html" %} -{% comment %} -Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} -{% load ballot_icon %} -{% load ietf_filters %} - -{% block title %}Documents on Future IESG Telechat Agendas{% endblock %} - -{% block morecss %} -.agenda_docs tr.oddrow {background-color: #EDF5FF; } -.agenda_docs tr.header.telechat_date { margin-top:10px; background:#2647A0; color: white;} -.agenda_docs tr.header.telechat_date td { font-size: 125%; } -.agenda_docs tr.header + tr.header { border-top: 2px solid white;} -.agenda_docs tr td { - vertical-align: top; -} -.agenda_docs tr .reschedule, -.agenda_docs tr .clear-returning-item { - font-size: 11px; -} -.agenda_docs tr .doc_pages { -font-size:80%; font-style:italic; -} -.secretariat-actions { - margin-bottom: 10px; -} -{% endblock %} - -{% block pagehead %} - -{% endblock %} - -{% block content %} -

    Documents on Future IESG Telechat Agendas

    - - -{% if user|in_group:"Secretariat" %} -
    - - -
    -{% endif %} - -
    -{% for t in telechats %} - -{% if not forloop.first %} - -{% endif %} - - - -{% if forloop.first %} - -{% endif %} - - - - - - - - {% for doc in t.docs.s211 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s212 %}{% endif %} - {% for doc in t.docs.s212 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s213 %}{% endif %} - {% for doc in t.docs.s213 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - - - - {% for doc in t.docs.s221 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s222 %}{% endif %} - {% for doc in t.docs.s222 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s223 %}{% endif %} - {% for doc in t.docs.s223 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - - - - - - {% for doc in t.docs.s311 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s312 %}{% endif %} - {% for doc in t.docs.s312 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s313 %}{% endif %} - {% for doc in t.docs.s313 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - - - - {% for doc in t.docs.s321 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s322 %}{% endif %} - {% for doc in t.docs.s322 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s323 %}{% endif %} - {% for doc in t.docs.s323 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - - - - {% for doc in t.docs.s331 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s332 %}{% endif %} - {% for doc in t.docs.s332 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - {% if t.docs.s333 %}{% endif %} - {% for doc in t.docs.s333 %}{% include "iesg/agenda_documents_row.html" %}{%endfor%} - -{% endfor %} - -
     
    IESG telechat {{t.date}}
    Full IESG Agenda
    Download Documents
    2. Protocol Actions
    2.1 WG Submissions
    2.1.2 Returning Item
    2.1.3 For Action
    2.2 Individual Submissions
    2.2.2 Returning Item
    2.2.3 For Action
    3. Document Actions
    3.1 WG Submissions
    3.1.2 Returning Item
    3.1.3 For Action
    3.2 Individual Submissions Via AD
    3.2.2 Returning Item
    3.2.3 For Action
    3.3 IRTF and Independent Submission Stream Documents
    3.3.2 Returning Item
    3.3.3 For Action
    - - -{% endblock content %} - -{% block content_end %} - -{% endblock %} diff --git a/ietf/templates/iesg/agenda_documents_redesign.html b/ietf/templates/iesg/agenda_documents_redesign.html index 2c086e6e2..167ff60b7 100644 --- a/ietf/templates/iesg/agenda_documents_redesign.html +++ b/ietf/templates/iesg/agenda_documents_redesign.html @@ -105,6 +105,14 @@ font-size:80%; font-style:italic; {% if t.docs.s223 %}
    2.2.3 For Action
    2.3 Status Changes
    2.3.2 Returning Item
    2.3.3 For Action
    3. Document Actions
    3.1 WG Submissions
    3.2.3 For Action
    3.3 IRTF and Independent Submission Stream Documents
    3.3 Status Changes
    3.3.2 Returning Item
    3.3.3 For Action
    3.4 IRTF and Independent Submission Stream Documents
    3.4.2 Returning Item
    3.4.3 For Action
    4. Working Group Actions
    4.1 WG Creation
    + +{% for doc_group in grouped_docs %} + + +{% for doc in doc_group.list %} + + + + + + + + + + + + + + + + + +{% comment %} +{% include "idrfc/ipr_column.html" %} +{% endcomment %} + + +{% endfor %} + +{% endfor %} +
    DocumentTitleDateStatusipr
    {{doc_group.grouper}}s
    +{#{{doc.ad_dashboard_sort_key}}
    #} +{% if doc.get_state_slug == 'rfc' %} +{{ doc.canonical_name|upper|rfcspace|urlize_ietf_docs }}
    {{ doc.name|urlize_ietf_docs }} +{% else %} +{{ doc.name|urlize_ietf_docs }} +{% endif %} +
    {{ doc.title }} +{% if doc.display_date %} + {% if doc.get_state_slug == 'rfc' %} + {{ doc.display_date|date:"Y-m"}} + {% else %} + {{ doc.display_date|date:"Y-m-d" }} + {% endif %} + {% if doc.display_date|timesince_days|new_enough:request %} +
    + {% if not doc.type.slug == 'draft' or doc.get_state_slug == 'rfc' %} + new + {% else %} + new + {% endif %} + + {% endif %} + {% if doc.can_expire and doc.expires and doc.expires|timesince_days|expires_soon:request %} +
    expires soon + {%endif%} +{% endif %} +
    +{{ doc.friendly_state|safe }} {% if not doc.get_state_slug == 'rfc' %}{{ doc|state_age_colored|safe }}{% endif %} +{% if doc.on_upcoming_agenda %}
    IESG Telechat: {{ doc.telechat_date }}{% endif %} +{% if doc.get_state_slug == 'rfc' %} +{% if doc.obsoleted_by %}
    Obsoleted by {{ doc.obsoleted_by|urlize_ietf_docs }}{%endif %} +{% if doc.updated_by %}
    Updated by {{ doc.updated_by|urlize_ietf_docs }}{%endif %} +{% if doc.has_errata %}
    Errata{% endif %} +{% else %}{# not rfc #} +{% if doc.rfc_editor_state %}
    RFC Editor State: {{ doc.rfc_editor_state|escape }}{% endif %} +{% stream_state doc %} +{% endif %} +
    +{% ballot_icon doc %} + +{% if doc.iprCount %} {{ doc.iprCount }} {% endif %} +
    +{% endblock content %} From 33e2a0766e53c2940b59a7767b078d85183b3e68 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 4 Feb 2013 02:34:01 +0000 Subject: [PATCH 5/6] Repairing migration merge conflict - Legacy-Id: 5423 --- ietf/doc/migrations/{0005_add_statchg.py => 0009_add_statchg.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ietf/doc/migrations/{0005_add_statchg.py => 0009_add_statchg.py} (100%) diff --git a/ietf/doc/migrations/0005_add_statchg.py b/ietf/doc/migrations/0009_add_statchg.py similarity index 100% rename from ietf/doc/migrations/0005_add_statchg.py rename to ietf/doc/migrations/0009_add_statchg.py From 6f611530c63fbfd30bbf6a0a08352623b89175df Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 12 Feb 2013 22:00:07 +0000 Subject: [PATCH 6/6] Fixed bug in display of conflict review targets after they become RFCs. Fixed views of previous version of status changes - Legacy-Id: 5429 --- ietf/idrfc/views_doc.py | 9 ++++++++- ietf/templates/idrfc/document_conflict_review.html | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index 6936e447d..bbb42a287 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -204,6 +204,13 @@ def document_main(request, name, rev=None): ballot_summary = None if doc.get_state_slug() in ("iesgeval"): ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values()) + + if isinstance(doc,Document): + sorted_relations=doc.relateddocument_set.all().order_by('relationship__name') + elif isinstance(doc,DocHistory): + sorted_relations=doc.relateddochistory_set.all().order_by('relationship__name') + else: + sorted_relations=None return render_to_response("idrfc/document_status_change.html", dict(doc=doc, @@ -214,7 +221,7 @@ def document_main(request, name, rev=None): telechat=telechat, ballot_summary=ballot_summary, approved_states=('appr-pend','appr-sent'), - sorted_relations=doc.relateddocument_set.all().order_by('relationship__name'), + sorted_relations=sorted_relations, ), context_instance=RequestContext(request)) diff --git a/ietf/templates/idrfc/document_conflict_review.html b/ietf/templates/idrfc/document_conflict_review.html index 41d437f6c..4b6669c9d 100644 --- a/ietf/templates/idrfc/document_conflict_review.html +++ b/ietf/templates/idrfc/document_conflict_review.html @@ -2,7 +2,7 @@ {% load ietf_filters %} -{% block title %}{{ doc.canonical_name }}-{{ doc.rev }}{% endblock %} +{% block title %}{{ doc.name }}-{{ doc.rev }}{% endblock %} {% block pagehead %} @@ -24,7 +24,7 @@
    {% if snapshot %}Snapshot of{% endif %} {% if doc.get_state_slug not in approved_states %}Proposed{% endif %} - IESG Conflict Review for the {{conflictdoc.stream}} document: {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }} + IESG Conflict Review for the {{conflictdoc.stream}} document: {{ conflictdoc.canonical_name }}{% if conflictdoc.get_state_slug != 'rfc' %}-{{ conflictdoc.rev }}{% endif %}
    @@ -100,7 +100,7 @@ -

    Conflict Review for {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }} +

    Conflict Review for {{ conflictdoc.name }}-{{ conflictdoc.rev }} {% if not snapshot and user|has_role:"Area Director,Secretariat" and doc.get_state_slug != 'apprsent' %} Change conflict review text