* Adds a new document type for conflict reviews, with a ballot for the IESG 5742 response to a review request

* Integrated the new document type into the iESG agenda views (including RSS feeds)
* Removed the Edit and Add buttons from the document main view.
* Replaced Add with actions appropriate for the document type, such as "Begin IESG Processing" or "Begin IETF Conflict Review", and made most data directly editable on the document's main page, depending on access permissions.
* Removed a manual editing step that the secretariat had to perform when sending conflict review messages. The view now composes the message correctly given the stream.
* Added a pencil icon motif to differentiate fields that are editable.
* Generalized several views and helper functions to use Document instead of (e.g.) IdWrapper
* Generalized reading documents from the repository
* Added a way to get from IdWrapper  to the underlying Document to facilitate migrating way from the Wrapper classes
* Added many helpers to Document to assist with migrating off IdWrapper
* Minor fixes and other changes
  * Fixes to document main view to avoid (silent) template failures. 
  * Began removing some of the code that is no longer reachable post-migration
  * Corrected the behavior of the undefer code and added test cases for it
  * Improved initial population of notification lists and added the ability to regenerate the initial list
* Made the test code that scans for template coverage more robust
* Deployment notes:
  * new setting: CONFLICT_REVIEW_PATH. The associated directory will need to be created

This branch fixes bugs #805, #744 and #812
 - Legacy-Id: 4600
This commit is contained in:
Robert Sparks 2012-07-05 20:45:54 +00:00
commit 798e7a50e7
70 changed files with 3971 additions and 861 deletions

View file

@ -26,6 +26,11 @@ class DocAuthorInline(admin.TabularInline):
raw_id_fields = ['author', ]
extra = 1
class RelatedDocumentInline(admin.TabularInline):
model = RelatedDocument
raw_id_fields = ['target']
extra = 1
# document form for managing states in a less confusing way
class StatesWidget(forms.SelectMultiple):
@ -89,7 +94,7 @@ class DocumentAdmin(admin.ModelAdmin):
search_fields = ['name']
list_filter = ['type']
raw_id_fields = ['authors', 'related', 'group', 'shepherd', 'ad']
inlines = [DocAliasInline, DocAuthorInline, ]
inlines = [DocAliasInline, DocAuthorInline, RelatedDocumentInline, ]
form = DocumentForm
def state(self, instance):
@ -114,6 +119,11 @@ class DocAliasAdmin(admin.ModelAdmin):
raw_id_fields = ['document']
admin.site.register(DocAlias, DocAliasAdmin)
class RelatedDocumentAdmin(admin.ModelAdmin):
search_fields = ['source__name','target__name']
raw_id_fields = ['source','target']
admin.site.register(RelatedDocument,RelatedDocumentAdmin)
class BallotTypeAdmin(admin.ModelAdmin):
list_display = ["slug", "doc_type", "name", "question"]
admin.site.register(BallotType, BallotTypeAdmin)

View file

@ -0,0 +1,436 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
from doc.models import StateType, State, BallotType, DocTypeName
from name.models import BallotPositionName
class Migration(DataMigration):
#These slugs need to be more than 8 chars
def forwards(self, orm):
conflrev = StateType(slug="conflrev",label="Conflict Review State")
conflrev.save()
needshep = State(
type=conflrev,slug="needshep",
name="Needs Shepherd",used=True,order=1,
desc="A conflict review has been requested, but a shepherding AD has not yet been assigned")
needshep.save()
adrev = State(
type=conflrev,slug="adrev",
name="AD Review",used=True,order=2,
desc="The sponsoring AD is reviewing the document and preparing a proposed response")
adrev.save()
iesgeval = State(
type=conflrev,slug="iesgeval",
name="IESG Evaluation",used=True,order=3,
desc="The IESG is considering the proposed conflict review response")
iesgeval.save()
defer = State(
type=conflrev,slug="defer",
name="IESG Evaluation - Defer",used=True, order=4,
desc="The evaluation of the proposed conflict review response has been deferred to the next telechat")
defer.save()
appr_reqnopub_pend = State(
type=conflrev,slug="appr-reqnopub-pend",
name="Approved Request to Not Publish - announcement to be sent", used=True,order=5,
desc="The IESG has approved the conflict review response (a request to not publish), but the secretariat has not yet sent the response")
appr_reqnopub_pend.save()
appr_noprob_pend = State(
type=conflrev,slug="appr-noprob-pend",
name="Approved No Problem - announcement to be sent", used=True,order=6,
desc="The IESG has approved the conflict review response, but the secretariat has not yet sent the response")
appr_noprob_pend.save()
appr_reqnopub_sent = State(
type=conflrev,slug="appr-reqnopub-sent",
name="Approved Request to Not Publish - announcement sent",used=True,order=7,
desc="The secretariat has delivered the IESG's approved conflict review response (a request to not publish) to the requester")
appr_reqnopub_sent.save()
appr_noprob_sent = State(
type=conflrev,slug="appr-noprob-sent",
name="Approved No Problem - announcement sent",used=True,order=8,
desc="The secretariat has delivered the IESG's approved conflict review response to the requester")
appr_noprob_sent.save()
withdraw = State(
type=conflrev,slug="withdraw",
name="Withdrawn",used=True,order=9,
desc="The request for conflict review was withdrawn")
withdraw.save()
dead = State(
type=conflrev,slug="dead",
name="Dead",used=True,order=10,
desc="The conflict review has 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_reqnopub_pend,appr_noprob_pend,defer,withdraw,dead)
iesgeval.save()
defer.next_states.add(iesgeval,appr_reqnopub_pend,appr_noprob_pend,withdraw,dead)
defer.save()
appr_reqnopub_pend.next_states.add(appr_reqnopub_sent,withdraw)
appr_reqnopub_pend.save()
appr_noprob_pend.next_states.add(appr_noprob_sent,withdraw)
appr_noprob_pend.save()
withdraw.next_states.add(needshep)
withdraw.save()
dead.next_states.add(needshep)
dead.save()
conflict_ballot = BallotType(doc_type=DocTypeName.objects.get(slug='conflrev'),slug='conflrev',name="Approve",used=True,
question="Is this the correct conflict review response?")
conflict_ballot.save()
conflict_ballot.positions.add('yes','noobj','discuss','abstain','recuse','norecord')
conflict_ballot.save()
def backwards(self, orm):
State.objects.filter(type__slug='conflrev').delete()
StateType.objects.filter(slug='conflrev').delete()
BallotType.objects.filter(slug='conflrev').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']

View file

@ -205,6 +205,113 @@ class Document(DocumentInfo):
name = name.upper()
return name
#TODO can/should this be a function instead of a property? Currently a view uses it as a property
@property
def telechat_date(self):
e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
return e.telechat_date if e else None
def area_acronym(self):
g = self.group
if g:
if g.type_id == "area":
return g.acronym
elif g.type_id != "individ":
return g.parent.acronym
else:
return None
def group_acronym(self):
g = self.group
if g and g.type_id != "area":
return g.acronym
else:
return "none"
def on_upcoming_agenda(self):
e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
return bool(e and e.telechat_date and e.telechat_date >= datetime.date.today())
def returning_item(self):
e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
return e.returning_item if e else None
# This is brittle. Resist the temptation to make it more brittle by combining the search against those description
# strings to one command. It is coincidence that those states have the same description - one might change.
# Also, this needs further review - is it really the case that there would be no other changed_document events
# between when the state was changed to defer and when some bit of code wants to know if we are deferred? Why
# isn't this just returning whether the state is currently a defer state for that document type?
def active_defer_event(self):
if self.type_id == "draft" and self.get_state_slug("draft-iesg") == "defer":
return self.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
elif self.type_id == "conflrev" and self.get_state_slug("conflrev") == "defer":
return self.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
return None
def displayname_with_link(self):
return '<a href="%s">%s-%s</a>' % (self.get_absolute_url(), self.name , self.rev)
def rfc_number(self):
qs = self.docalias_set.filter(name__startswith='rfc')
return qs[0].name[3:] if qs else None
def replaced_by(self):
return [ rel.source for alias in self.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='replaces') ]
def friendly_state(self):
""" Return a concise text description of the document's current state """
if self.type_id=='draft':
# started_iesg_process is is how the redesigned database schema (as of May2012) captured what
# used to be "has an IDInternal", aka *Wrapper.in_ietf_process()=True
in_iesg_process = self.latest_event(type='started_iesg_process')
iesg_state_summary=None
if in_iesg_process:
iesg_state = self.states.get(type='draft-iesg')
# This knowledge about which tags are reportable IESG substate tags is duplicated in idrfc
IESG_SUBSTATE_TAGS = ('point', 'ad-f-up', 'need-rev', 'extpty')
iesg_substate = self.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
# There really shouldn't be more than one tag in iesg_substate, but this will do something sort-of-sensible if there is
iesg_state_summary = iesg_state.name
if iesg_substate:
iesg_state_summary = iesg_state_summary + "::"+"::".join(tag.name for tag in iesg_substate)
if self.get_state_slug() == "rfc":
return "<a href=\"%s\">RFC %d</a>" % (urlreverse('doc_view', args=['rfc%d' % self.rfc_number]), self.rfc_number)
elif self.get_state_slug() == "repl":
rs = self.replaced_by()
if rs:
return "Replaced by "+", ".join("<a href=\"%s\">%s</a>" % (urlreverse('doc_view', args=[name]),name) for name in rs)
else:
return "Replaced"
elif self.get_state_slug() == "active":
if in_iesg_process:
if iesg_state.slug == "dead":
# Many drafts in the draft-iesg "Dead" state are not dead
# in other state machines; they're just not currently under
# IESG processing. Show them as "I-D Exists (IESG: Dead)" instead...
return "I-D Exists (IESG: "+iesg_state_summary+")"
elif iesg_state.slug == "lc":
expiration_date = str(self.latest_event(LastCallDocEvent,type="sent_last_call").expires.date())
return iesg_state_summary + " (ends "+expiration_date+")"
else:
return iesg_state_summary
else:
return "I-D Exists"
else:
if in_iesg_process and iesg_state.slug == "dead":
return self.get_state().name +" (IESG: "+iesg_state_summary+")"
# Expired/Withdrawn by Submitter/IETF
return self.get_state().name
else:
return self.get_state().name
def ipr(self):
"""Returns the IPR disclosures against this document (as a queryset over IprDocAlias)."""
from ietf.ipr.models import IprDocAlias
return IprDocAlias.objects.filter(doc_alias__document=self)
class RelatedDocHistory(models.Model):
source = models.ForeignKey('DocHistory')
target = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set")

View file

View file

@ -0,0 +1,8 @@
from django import template
register = template.Library()
@register.filter
def rfc_editor_state(doc):
state = doc.states.filter(type='draft-stream-ise')
return state[0] if state else None

3
ietf/doc/tests.py Normal file
View file

@ -0,0 +1,3 @@
from ietf.doc.tests_conflict_review import *

View file

@ -0,0 +1,342 @@
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 active_ballot, create_ballot_if_not_open, ballot_open
from ietf.doc.views_conflict_review 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 ConflictReviewTestCase(django.test.TestCase):
fixtures = ['names']
def test_start_review(self):
doc = Document.objects.get(name='draft-imaginary-independent-submission')
url = urlreverse('conflict_review_start',kwargs=dict(name=doc.name))
login_testing_unauthorized(self, "secretary", url)
# can't start conflict reviews on documents not in the ise or irtf streams
r = self.client.get(url)
self.assertEquals(r.status_code, 404)
doc.stream=StreamName.objects.get(slug='ise')
doc.save()
# 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)
# faulty posts
r = self.client.post(url,dict(create_in_state=""))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertEquals(Document.objects.filter(name='conflict-review-imaginary-independent-submission').count() , 0)
r = self.client.post(url,dict(ad=""))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertEquals(Document.objects.filter(name='conflict-review-imaginary-independent-submission').count() , 0)
# successful review start
ad_strpk = str(Person.objects.get(name='Aread Irector').pk)
state_strpk = str(State.objects.get(slug='needshep',type__slug='conflrev').pk)
r = self.client.post(url,dict(ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org'))
self.assertEquals(r.status_code, 302)
review_doc = Document.objects.get(name='conflict-review-imaginary-independent-submission')
self.assertEquals(review_doc.get_state('conflrev').slug,'needshep')
self.assertEquals(review_doc.rev,u'00')
self.assertEquals(review_doc.ad.name,u'Aread Irector')
self.assertEquals(review_doc.notify,u'ipu@ietf.org')
doc = Document.objects.get(name='draft-imaginary-independent-submission')
self.assertTrue(doc in [x.target.document for x in review_doc.relateddocument_set.filter(relationship__slug='conflrev')])
self.assertTrue(review_doc.latest_event(DocEvent,type="added_comment").desc.startswith("IETF conflict review requested"))
self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith("IETF conflict review initiated"))
# verify you can't start a review when a review is already in progress
r = self.client.post(url,dict(ad="Aread Irector",create_in_state="Needs Shepherd",notify='ipu@ietf.org'))
self.assertEquals(r.status_code, 404)
def test_change_state(self):
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
url = urlreverse('conflict_review_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=review_state]')),1)
# faulty post
r = self.client.post(url,dict(review_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='conflrev').pk)
r = self.client.post(url,dict(review_state=adrev_pk,comment='RDNK84ZD'))
self.assertEquals(r.status_code, 302)
review_doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
self.assertEquals(review_doc.get_state('conflrev').slug,'adrev')
self.assertTrue(review_doc.latest_event(DocEvent,type="added_comment").desc.startswith('RDNK84ZD'))
self.assertFalse(active_ballot(review_doc))
# successful change to IESG Evaluation
iesgeval_pk = str(State.objects.get(slug='iesgeval',type__slug='conflrev').pk)
r = self.client.post(url,dict(review_state=iesgeval_pk,comment='TGmZtEjt'))
self.assertEquals(r.status_code, 302)
review_doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
self.assertEquals(review_doc.get_state('conflrev').slug,'iesgeval')
self.assertTrue(review_doc.latest_event(DocEvent,type="added_comment").desc.startswith('TGmZtEjt'))
self.assertTrue(active_ballot(review_doc))
self.assertEquals(review_doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position").pos_id,'yes')
def test_edit_notices(self):
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
url = urlreverse('conflict_review_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" <foo@bar.baz.com>'
r = self.client.post(url,dict(notify=newlist))
self.assertEquals(r.status_code,302)
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
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='conflict-review-imaginary-irtf-submission')
url = urlreverse('conflict_review_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='conflict-review-imaginary-irtf-submission')
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='conflict-review-imaginary-irtf-submission')
url = urlreverse('conflict_review_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='conflict-review-imaginary-irtf-submission')
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='conflict-review-imaginary-irtf-submission')
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='conflict-review-imaginary-irtf-submission')
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='conflict-review-imaginary-irtf-submission')
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)
self.assertEquals(doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,None)
def approve_test_helper(self,approve_type):
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
url = urlreverse('conflict_review_approve',kwargs=dict(name=doc.name))
login_testing_unauthorized(self, "secretary", url)
# Some additional setup
create_ballot_if_not_open(doc,Person.objects.get(name="Sec Retary"),"conflrev")
doc.set_state(State.objects.get(slug=approve_type+'-pend',type='conflrev'))
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)
if approve_type == 'appr-noprob':
self.assertTrue( 'IESG has no problem' in ''.join(wrap(r.content,2**16)))
else:
self.assertTrue( 'NOT be published' in ''.join(wrap(r.content,2**16)))
# submit
messages_before = len(outbox)
r = self.client.post(url,dict(announcement_text=default_approval_text(doc)))
self.assertEquals(r.status_code, 302)
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
self.assertEquals(doc.get_state_slug(),approve_type+'-sent')
self.assertFalse(ballot_open(doc, "conflrev"))
self.assertEquals(len(outbox), messages_before + 1)
self.assertTrue('Results of IETF-conflict review' in outbox[-1]['Subject'])
if approve_type == 'appr-noprob':
self.assertTrue( 'IESG has no problem' in ''.join(wrap(unicode(outbox[-1]),2**16)))
else:
self.assertTrue( 'NOT be published' in ''.join(wrap(unicode(outbox[-1]),2**16)))
def test_approve_reqnopub(self):
self.approve_test_helper('appr-reqnopub')
def test_approve_noprob(self):
self.approve_test_helper('appr-noprob')
def setUp(self):
make_test_data()
class ConflictReviewSubmitTestCase(django.test.TestCase):
fixtures = ['names']
def test_initial_submission(self):
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
url = urlreverse('conflict_review_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("[Edit this page"))
# 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.CONFLICT_REVIEW_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='conflict-review-imaginary-irtf-submission')
self.assertEquals(doc.rev,u'00')
with open(path) as f:
self.assertEquals(f.read(),"Some initial review text\n")
f.close()
self.assertTrue( "submission-00" in doc.latest_event(NewRevisionDocEvent).desc)
def test_subsequent_submission(self):
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
url = urlreverse('conflict_review_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.CONFLICT_REVIEW_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='conflict-review-imaginary-irtf-submission')
self.assertEquals(doc.rev,u'01')
path = os.path.join(settings.CONFLICT_REVIEW_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( "submission-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("[Edit this page"))
def setUp(self):
make_test_data()
self.test_dir = os.path.abspath("tmp-conflict-review-testdir")
os.mkdir(self.test_dir)
settings.CONFLICT_REVIEW_PATH = self.test_dir
def tearDown(self):
shutil.rmtree(self.test_dir)

View file

@ -0,0 +1,12 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('ietf.doc.views_conflict_review',
url(r'^state/$', "change_state", name='conflict_review_change_state'),
url(r'^submit/$', "submit", name='conflict_review_submit'),
url(r'^notices/$', "edit_notices", name='conflict_review_notices'),
url(r'^ad/$', "edit_ad", name='conflict_review_ad'),
url(r'^approve/$', "approve", name='conflict_review_approve'),
url(r'^start_conflict_review/$', "start_review", name='conflict_review_start'),
url(r'^telechat/$', "telechat_date", name='conflict_review_telechat_date'),
)

View file

@ -1,3 +1,9 @@
import os
from django.conf import settings
# Should this move from idrfc to doc?
from ietf.idrfc import markup_txt
from ietf.doc.models import *
def get_state_types(doc):
@ -30,6 +36,15 @@ def get_tags_for_stream_id(stream_id):
else:
return []
# This, and several other utilities here, assume that there is only one active ballot for a document at any point in time.
# If that assumption is violated, they will only expose the most recently created ballot
def active_ballot(doc):
"""Returns the most recently created ballot if it isn't closed."""
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
open = ballot_open(doc,ballot.ballot_type.slug) if ballot else False
return ballot if open else None
def active_ballot_positions(doc, ballot=None):
"""Return dict mapping each active AD to a current ballot position (or None if they haven't voted)."""
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active"))
@ -106,10 +121,6 @@ def close_open_ballots(doc, by):
e.desc = 'Closed "%s" ballot' % t.name
e.save()
def get_rfc_number(doc):
qs = doc.docalias_set.filter(name__startswith='rfc')
return qs[0].name[3:] if qs else None
def augment_with_start_time(docs):
"""Add a started_time attribute to each document with the time of
the first revision."""
@ -165,23 +176,31 @@ def augment_events_with_revision(doc, events):
e.rev = cur_rev
def augment_with_telechat_date(docs):
"""Add a telechat_date attribute to each document with the
scheduled telechat or None if it's not scheduled."""
docs = list(docs)
def get_document_content(key, filename, split=True, markup=True):
f = None
try:
f = open(filename, 'rb')
raw_content = f.read()
except IOError:
error = "Error; cannot read ("+key+")"
if split:
return (error, "")
else:
return error
finally:
if f:
f.close()
if markup:
return markup_txt.markup(raw_content,split)
else:
return raw_content
docs_dict = {}
for d in docs:
docs_dict[d.pk] = d
d.telechat_date = None
def log_state_changed(request, doc, by, new_description, old_description):
from ietf.doc.models import DocEvent
seen = set()
e = DocEvent(doc=doc, by=by)
e.type = "changed_document"
e.desc = u"State changed to <b>%s</b> from %s" % (new_description, old_description)
e.save()
return e
for e in TelechatDocEvent.objects.filter(type="scheduled_for_telechat", doc__in=docs).order_by('-time'):
if e.doc_id in seen:
continue
docs_dict[e.doc_id].telechat_date = e.telechat_date
seen.add(e.doc_id)
return docs

View file

@ -0,0 +1,498 @@
import datetime, os
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 Role, Group
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
and logging the change as a comment."""
review = get_object_or_404(Document, type="conflrev", name=name)
login = request.user.get_profile()
if request.method == 'POST':
form = ChangeStateForm(request.POST)
if form.is_valid():
clean = form.cleaned_data
review_state = clean['review_state']
comment = clean['comment'].rstrip()
if comment:
c = DocEvent(type="added_comment", doc=review, by=login)
c.desc = comment
c.save()
if review_state != review.get_state():
save_document_in_history(review)
old_description = review.friendly_state()
review.set_state(review_state)
new_description = review.friendly_state()
log_state_changed(request, review, login, new_description, old_description)
review.time = datetime.datetime.now()
review.save()
if review_state.slug == "iesgeval":
create_ballot_if_not_open(review, login, "conflrev")
ballot = review.latest_event(BallotDocEvent, type="created_ballot")
if has_role(request.user, "Area Director") and not review.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot, type="changed_ballot_position"):
# The AD putting a conflict review into iesgeval who doesn't already have a position is saying "yes"
pos = BallotPositionDocEvent(doc=review, 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()
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)
return render_to_response('doc/conflict_review/change_state.html',
dict(form=form,
doc=review,
login=login,
),
context_instance=RequestContext(request))
class UploadForm(forms.Form):
content = forms.CharField(widget=forms.Textarea, label="Conflict review response", help_text="Edit the conflict review response", 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, review):
filename = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (review.canonical_name(), review.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):
review = get_object_or_404(Document, type="conflrev", name=name)
login = request.user.get_profile()
path = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (review.canonical_name(), review.rev))
not_uploaded_yet = review.rev == "00" and not os.path.exists(path)
if not_uploaded_yet:
# this case is special - the conflict review text document doesn't actually exist yet
next_rev = review.rev
else:
next_rev = "%02d" % (int(review.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(review)
review.rev = next_rev
e = NewRevisionDocEvent(doc=review, by=login, type="new_revision")
e.desc = "New version available: <b>%s-%s.txt</b>" % (review.canonical_name(), review.rev)
e.rev = review.rev
e.save()
# Save file on disk
form.save(review)
review.time = datetime.datetime.now()
review.save()
return HttpResponseRedirect(reverse('doc_view', kwargs={'name': review.name}))
elif "reset_text" in request.POST:
init = { "content": render_to_string("doc/conflict_review/review_choices.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/conflict_review/review_choices.txt",
dict(),
)
else:
filename = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (review.canonical_name(), review.rev))
try:
with open(filename, 'r') as f:
init["content"] = f.read()
except IOError:
pass
form = UploadForm(initial=init)
return render_to_response('doc/conflict_review/submit.html',
{'form': form,
'next_rev': next_rev,
'review' : review,
'conflictdoc' : review.relateddocument_set.get(relationship__slug='conflrev').target.document,
},
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):
"""Change the set of email addresses document change notificaitions go to."""
review = get_object_or_404(Document, type="conflrev", name=name)
if request.method == 'POST':
form = NotifyForm(request.POST)
if form.is_valid():
review.notify = form.cleaned_data['notify']
review.save()
login = request.user.get_profile()
c = DocEvent(type="added_comment", doc=review, by=login)
c.desc = "Notification list changed to : "+review.notify
c.save()
return HttpResponseRedirect(reverse('doc_view', kwargs={'name': review.name}))
else:
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,
},
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."""
review = get_object_or_404(Document, type="conflrev", name=name)
if request.method == 'POST':
form = AdForm(request.POST)
if form.is_valid():
review.ad = form.cleaned_data['ad']
review.save()
login = request.user.get_profile()
c = DocEvent(type="added_comment", doc=review, by=login)
c.desc = "Shepherding AD changed to "+review.ad.name
c.save()
return HttpResponseRedirect(reverse('doc_view', kwargs={'name': review.name}))
else:
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,
},
context_instance = RequestContext(request))
def default_approval_text(review):
# Leaving these commented out lines for current_text to make it easy to put the actual review result into the approval email
# message should the stream owners decide that is a good thing.
#filename = "%s-%s.txt" % (review.canonical_name(), review.rev)
#current_text = get_document_content(filename, os.path.join(settings.CONFLICT_REVIEW_PATH, filename), split=False, markup=False)
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document
if conflictdoc.stream_id=='ise':
receiver = 'RFC-Editor'
elif conflictdoc.stream_id=='irtf':
receiver = 'IRTF'
else:
receiver = 'recipient'
text = render_to_string("doc/conflict_review/approval_text.txt",
dict(review=review,
review_url = settings.IDTRACKER_BASE_URL+review.get_absolute_url(),
conflictdoc = conflictdoc,
conflictdoc_url = settings.IDTRACKER_BASE_URL+conflictdoc.get_absolute_url(),
receiver=receiver,
#approved_review = current_text
)
)
return text
class AnnouncementForm(forms.Form):
announcement_text = forms.CharField(widget=forms.Textarea, label="IETF Conflict Review Announcement", help_text="Edit the announcement message", required=True)
@role_required("Secretariat")
def approve(request, name):
"""Approve this conflict review, setting the appropriate state and send the announcement to the right parties."""
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()
login = request.user.get_profile()
if request.method == 'POST':
form = AnnouncementForm(request.POST)
if form.is_valid():
new_state_slug = 'appr-reqnopub-sent' if review.get_state('conflrev').slug=='appr-reqnopub-pend' else 'appr-noprob-sent'
new_review_state = State.objects.get(type="conflrev", slug=new_state_slug)
save_document_in_history(review)
old_description = review.friendly_state()
review.set_state(new_review_state)
new_description = review.friendly_state()
log_state_changed(request, review, login, new_description, old_description)
close_open_ballots(review, login)
e = DocEvent(doc=review, by=login)
e.type = "iesg_approved"
e.desc = "IESG has approved the conflict review response"
e.save()
review.time = e.time
review.save()
# send announcement
send_mail_preformatted(request, form.cleaned_data['announcement_text'])
c = DocEvent(type="added_comment", doc=review, by=login)
c.desc = "The following approval message was sent\n"+form.cleaned_data['announcement_text']
c.save()
return HttpResponseRedirect(review.get_absolute_url())
else:
init = { "announcement_text" : default_approval_text(review) }
form = AnnouncementForm(initial=init)
return render_to_response('doc/conflict_review/approve.html',
dict(
review = review,
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document,
form = form,
),
context_instance=RequestContext(request))
class StartReviewForm(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)
create_in_state = forms.ModelChoiceField(State.objects.filter(type="conflrev", 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()'}))
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')]
#init = kwargs['initial']['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("Secretariat")
def start_review(request, name):
"""Start the conflict review process, setting the initial shepherding AD, and possibly putting the review on a telechat."""
doc_to_review = get_object_or_404(Document, type="draft", name=name)
if not doc_to_review.stream_id in ('ise','irtf'):
raise Http404
# sanity check that there's not already a conflict review document for this document
if [ rel.source for alias in doc_to_review.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='conflrev') ]:
raise Http404
login = request.user.get_profile()
if request.method == 'POST':
form = StartReviewForm(request.POST)
if form.is_valid():
if doc_to_review.name.startswith('draft-'):
review_name = 'conflict-review-'+doc_to_review.name[6:]
else:
# This is a failsafe - and might be treated better as an error
review_name = 'conflict-review-'+doc_to_review.name
iesg_group = Group.objects.get(acronym='iesg')
conflict_review=Document( type_id = "conflrev",
title = "IETF conflict review for %s" % doc_to_review.name,
name = review_name,
rev = "00",
ad = form.cleaned_data['ad'],
notify = form.cleaned_data['notify'],
stream_id = 'ietf',
group = iesg_group,
)
conflict_review.set_state(form.cleaned_data['create_in_state'])
conflict_review.save()
DocAlias.objects.create( name=review_name , document=conflict_review )
conflict_review.relateddocument_set.create(target=DocAlias.objects.get(name=doc_to_review.name),relationship_id='conflrev')
c = DocEvent(type="added_comment", doc=conflict_review, by=login)
c.desc = "IETF conflict review requested"
c.save()
c = DocEvent(type="added_comment", doc=doc_to_review, by=login)
# Is it really OK to put html tags into comment text?
c.desc = 'IETF conflict review initiated - see <a href="%s">%s</a>' % (reverse('doc_view', kwargs={'name':conflict_review.name}),conflict_review.name)
c.save()
tc_date = form.cleaned_data['telechat_date']
if tc_date:
update_telechat(request, conflict_review, login, tc_date)
return HttpResponseRedirect(conflict_review.get_absolute_url())
else:
# Take care to do the right thing during ietf chair and stream owner transitions
ietf_chair_id = Role.objects.filter(group__acronym='ietf',name='chair')[0].person.id
notify_addresses = []
notify_addresses.extend([x.person.formatted_email() for x in Role.objects.filter(group__acronym=doc_to_review.stream.slug,name='chair')])
notify_addresses.append("%s@%s" % (name, settings.TOOLS_SERVER))
init = {
"ad" : ietf_chair_id,
"notify" : u', '.join(notify_addresses),
}
form = StartReviewForm(initial=init)
return render_to_response('doc/conflict_review/start.html',
{'form': form,
'doc_to_review': doc_to_review,
},
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)
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/conflict_review/edit_telechat_date.html',
dict(doc=doc,
form=form,
user=request.user,
login=login),
context_instance=RequestContext(request))

View file

@ -272,6 +272,11 @@ class IdWrapper:
def displayname_with_link(self):
return '<a href="%s">%s</a>' % (self.get_absolute_url(), self.draft_name_and_revision())
def underlying_document(self):
""" Expose the Document object underneath the proxy """
from ietf.doc.models import Document
return Document.objects.get(docalias__name=self.draft_name)
def to_json(self):
result = jsonify_helper(self, ['draft_name', 'draft_status', 'latest_revision', 'rfc_number', 'title', 'tracker_id', 'publication_date','rfc_editor_state', 'replaced_by', 'replaces', 'in_ietf_process', 'file_types', 'group_acronym', 'stream_id','friendly_state', 'abstract', 'ad_name'])
if self.in_ietf_process():
@ -741,7 +746,7 @@ class BallotWrapper:
return
from ietf.person.models import Person
from ietf.doc.models import BallotPositionDocEvent, NewRevisionDocEvent
from ietf.doc.models import BallotPositionDocEvent, NewRevisionDocEvent, BallotDocEvent
active_ads = Person.objects.filter(role__name="ad", role__group__state="active").distinct()

View file

@ -38,6 +38,31 @@ def email_state_changedREDESIGN(request, doc, text):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
email_state_changed = email_state_changedREDESIGN
def email_stream_changed(request, doc, old_stream, new_stream, text=""):
"""Email the change text to the notify group and to the stream chairs"""
to = [x.strip() for x in doc.notify.replace(';', ',').split(',')]
from ietf.group.models import Role as RedesignRole
# These use comprehension to deal with conditions when there might be more than one chair listed for a stream
if old_stream:
to.extend([x.person.formatted_email() for x in RedesignRole.objects.filter(group__acronym=old_stream.slug,name='chair')])
if new_stream:
to.extend([x.person.formatted_email() for x in RedesignRole.objects.filter(group__acronym=new_stream.slug,name='chair')])
if not to:
return
if not text:
text = u"Stream changed to <b>%s</b> from %s"% (new_stream,old_stream)
text = strip_tags(text)
send_mail(request, to, None,
"ID Tracker Stream Change Notice: %s" % doc.file_tag(),
"idrfc/stream_changed_email.txt",
dict(text=text,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
def html_to_text(html):
return strip_tags(html.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&").replace("<br>", "\n"))

View file

@ -72,7 +72,7 @@ def render_ballot_icon(user, doc):
if not ballot:
return ""
edit_position_url = urlreverse('doc_edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk))
edit_position_url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk))
def sort_key(t):
_, pos = t

View file

@ -0,0 +1,219 @@
# Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# * Neither the name of the Nokia Corporation and/or its
# subsidiary(-ies) nor the names of its contributors may be used
# to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from django import template
from django.core.urlresolvers import reverse as urlreverse
from django.conf import settings
from django.db.models import Q
from ietf.idtracker.models import IDInternal, BallotInfo
from ietf.idrfc.idrfc_wrapper import position_to_string, BALLOT_ACTIVE_STATES
from ietf.idtracker.templatetags.ietf_filters import in_group, timesince_days
from ietf.ietfauth.decorators import has_role
from ietf.doc.utils import active_ballot_positions, active_ballot
from ietf.doc.models import BallotDocEvent, BallotPositionDocEvent
from datetime import date
register = template.Library()
def get_user_name(context):
if 'user' in context and context['user'].is_authenticated():
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.person.models import Person
try:
return context['user'].get_profile().plain_name()
except Person.DoesNotExist:
return None
person = context['user'].get_profile().person()
if person:
return str(person)
return None
def render_ballot_icon(user, doc):
if not doc:
return ""
if doc.type_id == "draft":
s = doc.get_state("draft-iesg")
if s and s.name not in BALLOT_ACTIVE_STATES:
return ""
elif doc.type_id == "charter":
if doc.get_state_slug() not in ("intrev", "iesgrev"):
return ""
elif doc.type_id == "conflrev":
if doc.get_state_slug() not in ("iesgeval","defer"):
return ""
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
if not ballot:
return ""
edit_position_url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk))
def sort_key(t):
_, pos = t
if not pos:
return (2, 0)
elif pos.pos.blocking:
return (0, pos.pos.order)
else:
return (1, pos.pos.order)
positions = list(active_ballot_positions(doc, ballot).items())
positions.sort(key=sort_key)
cm = ""
if has_role(user, "Area Director"):
cm = ' oncontextmenu="editBallot(\''+str(edit_position_url)+'\');return false;"'
res = ['<table class="ballot_icon" title="IESG Evaluation Record (click to show more, right-click to edit position)" onclick="showBallot(\'' + doc.name + '\',\'' + str(edit_position_url) + '\')"' + cm + '>']
res.append("<tr>")
for i, (ad, pos) in enumerate(positions):
if i > 0 and i % 5 == 0:
res.append("</tr>")
res.append("<tr>")
c = "position-%s" % (pos.pos.slug if pos else "norecord")
if hasattr(user, "get_profile") and ad == user.get_profile():
c += " my"
res.append('<td class="%s" />' % c)
res.append("</tr>")
res.append("</table>")
return "".join(res)
class BallotIconNode(template.Node):
def __init__(self, doc_var):
self.doc_var = doc_var
def render(self, context):
doc = template.resolve_variable(self.doc_var, context)
#if hasattr(doc, "_idinternal"):
# # hack for old schema
# doc = doc._idinternal
return render_ballot_icon(context.get("user"), doc)
def do_ballot_icon(parser, token):
try:
tagName, docName = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents.split()[0]
return BallotIconNode(docName)
register.tag('ballot_icon', do_ballot_icon)
@register.filter
def my_position(doc, user):
user_name = get_user_name({'user':user})
if not user_name:
return None
if not in_group(user, "Area_Director"):
return None
ballot = active_ballot(doc)
pos = "No Record"
if ballot:
changed_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad__name=user_name, ballot=ballot)
if changed_pos:
pos = changed_pos.pos.name;
return pos
@register.filter
def state_age_colored(doc):
if doc.type.slug == 'draft':
if not doc.latest_event(type='started_iesg_process'):
return ""
if not doc.get_state_slug() in ["active", "rfc"]:
# Don't show anything for expired/withdrawn/replaced drafts
return ""
main_state = doc.get_state('draft-iesg')
IESG_SUBSTATE_TAGS = ('point', 'ad-f-up', 'need-rev', 'extpty')
sub_states = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
if main_state.slug in ["dead","watching","pub"]:
return ""
try:
state_date = doc.docevent_set.filter(
Q(desc__istartswith="Draft Added by ")|
Q(desc__istartswith="Draft Added in state ")|
Q(desc__istartswith="Draft added in state ")|
Q(desc__istartswith="State changed to ")|
Q(desc__istartswith="State Changes to ")|
Q(desc__istartswith="Sub state has been changed to ")|
Q(desc__istartswith="State has been changed to ")|
Q(desc__istartswith="IESG has approved and state has been changed to")|
Q(desc__istartswith="IESG process started in state")
).order_by('-time')[0].time.date()
except IndexError:
state_date = date(1990,1,1)
days = timesince_days(state_date)
# loosely based on
# http://trac.tools.ietf.org/group/iesg/trac/wiki/PublishPath
if main_state.slug == "lc":
goal1 = 30
goal2 = 30
elif main_state.slug == "rfcqueue":
goal1 = 60
goal2 = 120
elif main_state.slug in ["lc-req", "ann"]:
goal1 = 4
goal2 = 7
elif 'need-rev' in [x.slug for x in sub_states]:
goal1 = 14
goal2 = 28
elif main_state.slug == "pub-req":
goal1 = 7
goal2 = 14
elif main_state.slug == "ad-eval":
goal1 = 14
goal2 = 28
else:
goal1 = 14
goal2 = 28
if days > goal2:
class_name = "ietf-small ietf-highlight-r"
elif days > goal1:
class_name = "ietf-small ietf-highlight-y"
else:
class_name = "ietf-small"
if days > goal1:
title = ' title="Goal is &lt;%d days"' % (goal1,)
else:
title = ''
return '<span class="%s"%s>(for&nbsp;%d&nbsp;day%s)</span>' % (class_name,title,days,('','s')[days != 1])
else:
return ""

View file

@ -293,7 +293,6 @@ class EditInfoTestCase(django.test.TestCase):
r = self.client.post(url,
dict(intended_std_level=str(draft.intended_std_level_id),
stream="ietf",
ad=ad.pk,
create_in_state=State.objects.get(type="draft-iesg", slug="watching").pk,
notify="test@example.com",
@ -307,9 +306,9 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(draft.ad, ad)
self.assertEquals(draft.note, "This is a note")
self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
self.assertEquals(draft.docevent_set.count(), events_before + 4)
self.assertEquals(draft.docevent_set.count(), events_before + 3)
events = list(draft.docevent_set.order_by('time', 'id'))
self.assertEquals(events[-4].type, "started_iesg_process")
self.assertEquals(events[-3].type, "started_iesg_process")
self.assertEquals(len(outbox), mailbox_before)
@ -412,7 +411,7 @@ class EditPositionTestCase(django.test.TestCase):
def test_edit_position(self):
draft = make_test_data()
url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name,
url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name,
ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk))
login_testing_unauthorized(self, "ad", url)
@ -474,7 +473,7 @@ class EditPositionTestCase(django.test.TestCase):
def test_edit_position_as_secretary(self):
draft = make_test_data()
url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name,
url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name,
ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk))
ad = Person.objects.get(name="Aread Irector")
url += "?ad=%s" % ad.pk
@ -499,8 +498,8 @@ class EditPositionTestCase(django.test.TestCase):
def test_cannot_edit_position_as_pre_ad(self):
draft = make_test_data()
url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name,
ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk))
url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name,
ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk))
# transform to pre-ad
ad_role = Role.objects.filter(name="ad")[0]
@ -1435,3 +1434,152 @@ class MirrorScriptTestCases(unittest.TestCase,RealDatabaseTest):
self.assertEquals(len(refs), 3)
print "OK"
class IndividualInfoFormsTestCase(django.test.TestCase):
fixtures = ['names']
def test_doc_change_stream(self):
url = urlreverse('doc_change_stream', kwargs=dict(name=self.docname))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code,200)
q = PyQuery(r.content)
self.assertEquals(len(q('form.change-stream')),1)
# shift to ISE stream
messages_before = len(outbox)
r = self.client.post(url,dict(stream="ise",comment="7gRMTjBM"))
self.assertEquals(r.status_code,302)
self.doc = Document.objects.get(name=self.docname)
self.assertEquals(self.doc.stream_id,'ise')
self.assertEquals(len(outbox),messages_before+1)
self.assertTrue('Stream Change Notice' in outbox[-1]['Subject'])
self.assertTrue('7gRMTjBM' in str(outbox[-1]))
self.assertTrue('7gRMTjBM' in self.doc.latest_event(DocEvent,type='added_comment').desc)
# Would be nice to test that the stream managers were in the To header...
# shift to an unknown stream (it must be possible to throw a document out of any stream)
r = self.client.post(url,dict(stream=""))
self.assertEquals(r.status_code,302)
self.doc = Document.objects.get(name=self.docname)
self.assertEquals(self.doc.stream,None)
def test_doc_change_notify(self):
url = urlreverse('doc_change_notify', kwargs=dict(name=self.docname))
login_testing_unauthorized(self, "secretary", url)
# 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)
# Provide a list
r = self.client.post(url,dict(notify="TJ2APh2P@ietf.org",save_addresses="1"))
self.assertEquals(r.status_code,302)
self.doc = Document.objects.get(name=self.docname)
self.assertEquals(self.doc.notify,'TJ2APh2P@ietf.org')
# Ask the form to regenerate the list
r = self.client.post(url,dict(regenerate_addresses="1"))
self.assertEquals(r.status_code,200)
self.doc = Document.objects.get(name=self.docname)
# Regenerate does not save!
self.assertEquals(self.doc.notify,'TJ2APh2P@ietf.org')
q = PyQuery(r.content)
self.assertTrue('TJ2Aph2P' not in q('form input[name=notify]')[0].value)
def test_doc_change_intended_status(self):
url = urlreverse('doc_change_intended_status', kwargs=dict(name=self.docname))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code,200)
q = PyQuery(r.content)
self.assertEquals(len(q('form.change-intended-status')),1)
# don't allow status level to be cleared
r = self.client.post(url,dict(intended_std_level=""))
self.assertEquals(r.status_code,200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
# change intended status level
messages_before = len(outbox)
r = self.client.post(url,dict(intended_std_level="bcp",comment="ZpyQFGmA"))
self.assertEquals(r.status_code,302)
self.doc = Document.objects.get(name=self.docname)
self.assertEquals(self.doc.intended_std_level_id,'bcp')
self.assertEquals(len(outbox),messages_before+1)
self.assertTrue('ZpyQFGmA' in str(outbox[-1]))
self.assertTrue('ZpyQFGmA' in self.doc.latest_event(DocEvent,type='added_comment').desc)
def test_doc_change_telechat_date(self):
url = urlreverse('doc_change_telechat_date', kwargs=dict(name=self.docname))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code,200)
q = PyQuery(r.content)
self.assertEquals(len(q('form.telechat-date')),1)
# set a date
self.assertFalse(self.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)
self.doc = Document.objects.get(name=self.docname)
self.assertEquals(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,telechat_date)
# Take the doc back off any telechat
r = self.client.post(url,dict(telechat_date=""))
self.assertEquals(r.status_code, 302)
self.assertEquals(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,None)
def test_doc_change_iesg_note(self):
url = urlreverse('doc_change_iesg_note', kwargs=dict(name=self.docname))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code,200)
q = PyQuery(r.content)
self.assertEquals(len(q('form.edit-iesg-note')),1)
# No validation code to test
# post - testing that the munge code exists in note.clean...
r = self.client.post(url,dict(note='ZpyQFGmA\nZpyQFGmA'))
self.assertEquals(r.status_code,302)
self.doc = Document.objects.get(name=self.docname)
self.assertEquals(self.doc.note,'ZpyQFGmA<br>ZpyQFGmA')
self.assertTrue('ZpyQFGmA' in self.doc.latest_event(DocEvent,type='added_comment').desc)
def test_doc_change_ad(self):
url = urlreverse('doc_change_ad', kwargs=dict(name=self.docname))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code,200)
q = PyQuery(r.content)
self.assertEquals(len(q('form 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)
self.doc = Document.objects.get(name=self.docname)
self.assertEquals(self.doc.ad,ad2)
self.assertTrue(self.doc.latest_event(DocEvent,type="added_comment").desc.startswith('Shepherding AD changed'))
def setUp(self):
make_test_data()
self.docname='draft-ietf-mars-test'
self.doc = Document.objects.get(name=self.docname)

View file

@ -54,11 +54,19 @@ urlpatterns = patterns('',
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot.tsv$', views_doc.ballot_tsv),
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot.json$', views_doc.ballot_json),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/state/$', views_edit.change_state, name='doc_change_state'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/state/$', views_edit.change_state, name='doc_change_state'), # IESG state
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/info/$', views_edit.edit_info, name='doc_edit_info'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/requestresurrect/$', views_edit.request_resurrect, name='doc_request_resurrect'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/resurrect/$', views_edit.resurrect, name='doc_resurrect'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/addcomment/$', views_edit.add_comment, name='doc_add_comment'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/stream/$', views_edit.change_stream, name='doc_change_stream'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/notify/$', views_edit.edit_notices, name='doc_change_notify'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/status/$', views_edit.change_intention, name='doc_change_intended_status'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/telechat/$', views_edit.telechat_date, name='doc_change_telechat_date'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/iesgnote/$', views_edit.edit_iesg_note, name='doc_change_iesg_note'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/ad/$', views_edit.edit_ad, name='doc_change_ad'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/clearballot/$', views_ballot.clear_ballot, name='doc_clear_ballot'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/deferballot/$', views_ballot.defer_ballot, name='doc_defer_ballot'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/undeferballot/$', views_ballot.undefer_ballot, name='doc_undefer_ballot'),
@ -68,11 +76,13 @@ urlpatterns = patterns('',
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/approveballot/$', views_ballot.approve_ballot, name='doc_approve_ballot'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/makelastcall/$', views_ballot.make_last_call, name='doc_make_last_call'),
(r'^(?P<name>charter-[A-Za-z0-9.-]+)/', include('ietf.wgcharter.urls')),
(r'^(?P<name>charter-[A-Za-z0-9._+-]+)/', include('ietf.wgcharter.urls')),
(r'^(?P<name>[A-Za-z0-9._+-]+)/conflict-review/', include('ietf.doc.urls_conflict_review')),
)
urlpatterns += patterns('django.views.generic.simple',
url(r'^help/state/charter/$', 'direct_to_template', { 'template': 'wgcharter/states.html', 'extra_context': { 'states': State.objects.filter(type="charter") } }, name='help_charter_states'),
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'),
)

View file

@ -167,9 +167,9 @@ def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_i
# we didn't reschedule but flipped returning item bit - let's
# just explain that
if returning:
e.desc = "Added as returning item on telechat"
e.desc = "Set telechat returning item indication"
else:
e.desc = "Removed as returning item on telechat"
e.desc = "Removed telechat returning item indication"
e.save()

View file

@ -35,6 +35,8 @@ from ietf.name.models import BallotPositionName
from ietf.message.utils import infer_message
from ietf.person.models import Person
from ietf.doc.utils import log_state_changed
BALLOT_CHOICES = (("yes", "Yes"),
("noobj", "No Objection"),
("discuss", "Discuss"),
@ -65,155 +67,11 @@ def get_ballot_info(ballot, area_director):
return (pos, discuss, comment)
class EditPositionForm(forms.Form):
position = forms.ChoiceField(choices=BALLOT_CHOICES, widget=forms.RadioSelect, required=False)
discuss_text = forms.CharField(required=False, widget=forms.Textarea)
comment_text = forms.CharField(required=False, widget=forms.Textarea)
return_to_url = forms.CharField(required=False, widget=forms.HiddenInput)
def clean_discuss_text(self):
entered_discuss = self.cleaned_data["discuss_text"]
entered_pos = self.cleaned_data["position"]
if entered_pos == "discuss" and not entered_discuss:
print "Raising discuss ",entered_pos," ",entered_discuss
raise forms.ValidationError("You must enter a non-empty discuss")
return entered_discuss
pass
@group_required('Area_Director','Secretariat')
def edit_position(request, name):
"""Vote and edit discuss and comment on Internet Draft as Area Director."""
doc = get_object_or_404(InternetDraft, filename=name)
if not doc.idinternal:
raise Http404()
ad = login = IESGLogin.objects.get(login_name=request.user.username)
if 'HTTP_REFERER' in request.META:
return_to_url = request.META['HTTP_REFERER']
else:
return_to_url = doc.idinternal.get_absolute_url()
# if we're in the Secretariat, we can select an AD to act as stand-in for
if not in_group(request.user, "Area_Director"):
ad_username = request.GET.get('ad')
if not ad_username:
raise Http404()
ad = get_object_or_404(IESGLogin, login_name=ad_username)
pos, discuss, comment = get_ballot_info(doc.idinternal.ballot, ad)
if request.method == 'POST':
form = EditPositionForm(request.POST)
if form.is_valid():
# save the vote
clean = form.cleaned_data
if clean['return_to_url']:
return_to_url = clean['return_to_url']
vote = clean['position']
if pos:
# mark discuss as cleared (quirk from old system)
if pos.discuss:
pos.discuss = -1
else:
pos = Position(ballot=doc.idinternal.ballot, ad=ad)
pos.discuss = 0
old_vote = position_to_ballot_choice(pos)
pos.yes = pos.noobj = pos.abstain = pos.recuse = 0
if vote:
setattr(pos, vote, 1)
if pos.id:
if vote:
pos.save()
else:
pos.delete()
if vote != old_vote:
add_document_comment(request, doc, "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad, position_label(vote), position_label(old_vote)))
elif vote:
pos.save()
add_document_comment(request, doc, "[Ballot Position Update] New position, %s, has been recorded" % position_label(vote))
# save discuss
if (discuss and clean['discuss_text'] != discuss.text) or (clean['discuss_text'] and not discuss):
if not discuss:
discuss = IESGDiscuss(ballot=doc.idinternal.ballot, ad=ad)
discuss.text = clean['discuss_text']
discuss.date = date.today()
discuss.revision = doc.revision_display()
discuss.active = True
discuss.save()
if discuss.text:
add_document_comment(request, doc, discuss.text,
ballot=DocumentComment.BALLOT_DISCUSS)
if pos.discuss < 1:
IESGDiscuss.objects.filter(ballot=doc.idinternal.ballot, ad=pos.ad).update(active=False)
# similar for comment (could share code with discuss, but
# it's maybe better to coalesce them in the model instead
# than doing a clever hack here)
if (comment and clean['comment_text'] != comment.text) or (clean['comment_text'] and not comment):
if not comment:
comment = IESGComment(ballot=doc.idinternal.ballot, ad=ad)
comment.text = clean['comment_text']
comment.date = date.today()
comment.revision = doc.revision_display()
comment.active = True
comment.save()
if comment.text:
add_document_comment(request, doc, comment.text,
ballot=DocumentComment.BALLOT_COMMENT)
doc.idinternal.event_date = date.today()
doc.idinternal.save()
if request.POST.get("send_mail"):
qstr = "?return_to_url=%s" % return_to_url
if request.GET.get('ad'):
qstr += "&ad=%s" % request.GET.get('ad')
return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.filename)) + qstr)
else:
if request.POST.get("Defer"):
return HttpResponseRedirect(urlreverse("doc_defer_ballot", kwargs=dict(name=doc)))
else:
if request.POST.get("Undefer"):
return HttpResponseRedirect(urlreverse("doc_undefer_ballot", kwargs=dict(name=doc)))
else:
return HttpResponseRedirect(return_to_url)
else:
initial = {}
if pos:
initial['position'] = position_to_ballot_choice(pos)
if discuss:
initial['discuss_text'] = discuss.text
if comment:
initial['comment_text'] = comment.text
if return_to_url:
initial['return_to_url'] = return_to_url
form = EditPositionForm(initial=initial)
return render_to_response('idrfc/edit_position.html',
dict(doc=doc,
form=form,
discuss=discuss,
comment=comment,
ad=ad,
return_to_url=return_to_url,
ballot=BallotWrapper(doc.idinternal)
),
context_instance=RequestContext(request))
pass
class EditPositionFormREDESIGN(forms.Form):
position = forms.ModelChoiceField(queryset=BallotPositionName.objects.all(), widget=forms.RadioSelect, initial="norecord", required=True)
@ -355,9 +213,7 @@ def edit_positionREDESIGN(request, name, ballot_id):
blocking_positions = dict((p.pk, p.name) for p in form.fields["position"].queryset.all() if p.blocking)
ballot_deferred = None
if doc.get_state_slug("%s-iesg" % doc.type_id) == "defer":
ballot_deferred = doc.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
ballot_deferred = doc.active_defer_event()
return render_to_response('idrfc/edit_positionREDESIGN.html',
dict(doc=doc,
@ -366,6 +222,7 @@ def edit_positionREDESIGN(request, name, ballot_id):
return_to_url=return_to_url,
old_pos=old_pos,
ballot_deferred=ballot_deferred,
ballot = ballot,
show_discuss_text=old_pos and old_pos.pos_id=="discuss",
blocking_positions=simplejson.dumps(blocking_positions),
),
@ -547,43 +404,15 @@ def clear_ballot(request, name):
@group_required('Area_Director','Secretariat')
def defer_ballot(request, name):
"""Signal post-pone of Internet Draft ballot, notifying relevant parties."""
doc = get_object_or_404(InternetDraft, filename=name)
if not doc.idinternal:
raise Http404()
login = IESGLogin.objects.get(login_name=request.user.username)
telechat_date = TelechatDates.objects.all()[0].date2
if request.method == 'POST':
doc.idinternal.ballot.defer = True
doc.idinternal.ballot.defer_by = login
doc.idinternal.ballot.defer_date = date.today()
doc.idinternal.ballot.save()
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.IESG_EVALUATION_DEFER), None)
doc.idinternal.agenda = True
doc.idinternal.telechat_date = telechat_date
doc.idinternal.event_date = date.today()
doc.idinternal.save()
email_ballot_deferred(request, doc, login, telechat_date)
log_state_changed(request, doc, login)
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
return render_to_response('idrfc/defer_ballot.html',
dict(doc=doc,
telechat_date=telechat_date,
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
pass
@group_required('Area_Director','Secretariat')
def defer_ballotREDESIGN(request, name):
"""Signal post-pone of Internet Draft ballot, notifying relevant parties."""
"""Signal post-pone of ballot, notifying relevant parties."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
if doc.type_id not in ('draft','conflrev'):
raise Http404()
if doc.type_id == 'draft' and not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
@ -592,15 +421,17 @@ def defer_ballotREDESIGN(request, name):
if request.method == 'POST':
save_document_in_history(doc)
prev = doc.get_state("draft-iesg")
doc.set_state(State.objects.get(type="draft-iesg", slug='defer'))
prev_state = doc.friendly_state()
if doc.type_id == 'draft':
doc.set_state(State.objects.get(type="draft-iesg", slug='defer'))
prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
elif doc.type_id == 'conflrev':
doc.set_state(State.objects.get(type='conflrev', slug='defer'))
prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
e = log_state_changed(request, doc, login, prev, prev_tag)
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
doc.time = e.time
doc.save()
@ -623,38 +454,15 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
@group_required('Area_Director','Secretariat')
def undefer_ballot(request, name):
"""Delete deferral of Internet Draft ballot."""
doc = get_object_or_404(InternetDraft, filename=name)
if not doc.idinternal:
raise Http404()
login = IESGLogin.objects.get(login_name=request.user.username)
telechat_date = TelechatDates.objects.all()[0].date1
if request.method == 'POST':
doc.idinternal.ballot.defer = False
doc.idinternal.ballot.save()
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.IESG_EVALUATION), None)
doc.idinternal.telechat_date = telechat_date
doc.idinternal.event_date = date.today()
doc.idinternal.save()
log_state_changed(request, doc, login)
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
return render_to_response('idrfc/undefer_ballot.html',
dict(doc=doc,
telechat_date=telechat_date,
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
pass
@group_required('Area_Director','Secretariat')
def undefer_ballotREDESIGN(request, name):
"""Delete deferral of Internet Draft ballot."""
"""undo deferral of ballot ballot."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
if doc.type_id not in ('draft','conflrev'):
raise Http404()
if doc.type_id == 'draft' and not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
@ -663,19 +471,22 @@ def undefer_ballotREDESIGN(request, name):
if request.method == 'POST':
save_document_in_history(doc)
prev = doc.get_state("draft-iesg")
doc.set_state(State.objects.get(type="draft-iesg", slug='iesg-eva'))
prev_state = doc.friendly_state()
if doc.type_id == 'draft':
doc.set_state(State.objects.get(type="draft-iesg", slug='iesg-eva'))
prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
elif doc.type_id == 'conflrev':
doc.set_state(State.objects.get(type='conflrev',slug='iesgeval'))
prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
e = log_state_changed(request, doc, login, prev, prev_tag)
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
doc.time = e.time
doc.save()
update_telechat(request, doc, login, telechat_date)
email_state_changed(request, doc, e.desc)
return HttpResponseRedirect(doc.get_absolute_url())

View file

@ -60,15 +60,17 @@ 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 == "draft":
if doc.type_id in ("draft","conflrev"):
# 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:
tabs.append(("IESG Writeups", "writeup", urlreverse("ietf.idrfc.views_doc.document_writeup", kwargs=dict(name=name)), True))
tabs.append(("History", "history", urlreverse("ietf.idrfc.views_doc.document_history", kwargs=dict(name=name)), True))
if doc.type_id != "conflrev":
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))
name = doc.canonical_name()
if name.startswith("rfc"):
@ -91,6 +93,8 @@ def document_main(request, name, rev=None):
doc = get_object_or_404(Document, docalias__name=name)
group = doc.group
if doc.type_id == 'conflrev':
conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
revisions = []
for h in doc.history_set.order_by("time", "id"):
@ -115,18 +119,23 @@ def document_main(request, name, rev=None):
if not snapshot:
return redirect('doc_view', name=name)
# find old group, too
gh = find_history_active_at(doc.group, doc.time)
if gh:
group = gh
if doc.type_id == "charter":
# find old group, too
gh = find_history_active_at(doc.group, doc.time)
if gh:
group = gh
top = render_document_top(request, doc, "document", name)
telechat = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
if telechat and not telechat.telechat_date:
telechat = None
if telechat and telechat.telechat_date < datetime.date.today():
telechat = None
if doc.type_id == "charter":
filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev)
@ -150,6 +159,33 @@ def document_main(request, name, rev=None):
),
context_instance=RequestContext(request))
if doc.type_id == "conflrev":
filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev)
pathname = os.path.join(settings.CONFLICT_REVIEW_PATH,filename)
if doc.rev == "00" and not os.path.isfile(pathname):
# This could move to a template
content = "A conflict review response 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, active_ballot_positions(doc).values())
return render_to_response("idrfc/document_conflict_review.html",
dict(doc=doc,
top=top,
content=content,
revisions=revisions,
snapshot=snapshot,
telechat=telechat,
conflictdoc=conflictdoc,
ballot_summary=ballot_summary,
approved_states=('appr-reqnopub-pend','appr-reqnopub-sent','appr-noprob-pend','appr-noprob-sent')
),
context_instance=RequestContext(request))
raise Http404()
@ -254,10 +290,7 @@ def document_ballot_content(request, doc, ballot_id, editable=True):
if not ballot:
raise Http404
deferred = None
if doc.type_id == "draft" and doc.get_state_slug("draft-iesg") == "defer":
# FIXME: fragile
deferred = doc.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
deferred = doc.active_defer_event()
# collect positions
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active").distinct())
@ -351,20 +384,7 @@ def document_debug(request, name):
return HttpResponse(doc.to_json(), mimetype='text/plain')
def _get_html(key, filename, split=True):
f = None
try:
f = open(filename, 'rb')
raw_content = f.read()
except IOError:
error = "Error; cannot read '%s'" % key
if split:
return (error, "")
else:
return error
finally:
if f:
f.close()
return markup_txt.markup(raw_content, split)
return get_document_content(key, filename, split=split, markup=True)
def include_text(request):
include_text = request.GET.get( 'include_text' )
@ -415,6 +435,8 @@ def document_main_idrfc(request, name, tab):
info = {}
info['has_pdf'] = (".pdf" in doc.file_types())
info['is_rfc'] = False
info['conflict_reviews'] = [ rel.source for alias in id.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='conflrev') ]
(content1, content2) = _get_html(
str(name)+","+str(id.revision)+",html",
@ -547,36 +569,39 @@ def get_ballot(name):
from ietf.doc.models import DocAlias
alias = get_object_or_404(DocAlias, name=name)
d = alias.document
id = get_object_or_404(InternetDraft, name=d.name)
try:
if not id.ballot.ballot_issued:
id = None
bw = None
dw = None
if (d.type_id=='draft'):
id = get_object_or_404(InternetDraft, name=d.name)
try:
if not id.ballot.ballot_issued:
raise Http404
except BallotInfo.DoesNotExist:
raise Http404
except BallotInfo.DoesNotExist:
raise Http404
bw = BallotWrapper(id) # XXX Fixme: Eliminate this as we go forward
# Python caches ~100 regex'es -- explicitly compiling it inside a method
# (where you then throw away the compiled version!) doesn't make sense at
# all.
if re.search("^rfc([1-9][0-9]*)$", name):
id.viewing_as_rfc = True
dw = RfcWrapper(id)
else:
dw = IdWrapper(id)
# XXX Fixme: Eliminate 'dw' as we go forward
try:
b = d.latest_event(BallotDocEvent, type="created_ballot")
except BallotDocEvent.DoesNotExist:
raise Http404
bw = BallotWrapper(id) # XXX Fixme: Eliminate this as we go forward
# Python caches ~100 regex'es -- explicitly compiling it inside a method
# (where you then throw away the compiled version!) doesn't make sense at
# all.
if re.search("^rfc([1-9][0-9]*)$", name):
id.viewing_as_rfc = True
dw = RfcWrapper(id)
else:
dw = IdWrapper(id)
# XXX Fixme: Eliminate 'dw' as we go forward
return (bw, dw, b, d)
def ballot_html(request, name):
bw, dw, ballot, doc = get_ballot(name)
return render_to_response('idrfc/doc_ballot.html', {'bw':bw, 'dw':dw, 'ballot':ballot, 'doc':doc}, context_instance=RequestContext(request))
content = document_ballot_content(request, doc, ballot.pk, editable=True)
return HttpResponse(content)
def ballot_tsv(request, name):
ballot, doc, b, d = get_ballot(name)

View file

@ -32,76 +32,11 @@ from ietf.name.models import IntendedStdLevelName, DocTagName, StreamName
from ietf.person.models import Person, Email
class ChangeStateForm(forms.Form):
state = forms.ModelChoiceField(IDState.objects.all(), empty_label=None, required=True)
substate = forms.ModelChoiceField(IDSubState.objects.all(), required=False)
note = forms.CharField(widget=forms.Textarea, label="Comment", required=False)
pass
@group_required('Area_Director','Secretariat')
def change_state(request, name):
"""Change state of Internet Draft, notifying parties as necessary
and logging the change as a comment."""
doc = get_object_or_404(InternetDraft, filename=name)
if not doc.idinternal or doc.status.status == "Expired":
raise Http404()
login = IESGLogin.objects.get(login_name=request.user.username)
if request.method == 'POST':
form = ChangeStateForm(request.POST)
if form.is_valid():
state = form.cleaned_data['state']
sub_state = form.cleaned_data['substate']
note = form.cleaned_data['note']
internal = doc.idinternal
if state != internal.cur_state or sub_state != internal.cur_sub_state:
internal.change_state(state, sub_state)
internal.event_date = date.today()
internal.mark_by = login
internal.save()
change = log_state_changed(request, doc, login, note=note)
email_owner(request, doc, internal.job_owner, login, change)
if internal.cur_state.document_state_id == IDState.LAST_CALL_REQUESTED:
request_last_call(request, doc)
return render_to_response('idrfc/last_call_requested.html',
dict(doc=doc,
url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
return HttpResponseRedirect(internal.get_absolute_url())
else:
init = dict(state=doc.idinternal.cur_state_id,
substate=doc.idinternal.cur_sub_state_id)
form = ChangeStateForm(initial=init)
next_states = IDNextState.objects.filter(cur_state=doc.idinternal.cur_state)
prev_state_formatted = format_document_state(doc.idinternal.prev_state,
doc.idinternal.prev_sub_state)
try:
ballot_issued = doc.idinternal.ballot.ballot_issued
except BallotInfo.DoesNotExist:
ballot_issued = False
to_iesg_eval = None
if not ballot_issued:
try:
to_iesg_eval = next_states.filter(next_state=IDState.IESG_EVALUATION)[0]
except IndexError:
pass
if to_iesg_eval:
next_states = next_states.exclude(next_state=IDState.IESG_EVALUATION)
return render_to_response('idrfc/change_state.html',
dict(form=form,
doc=doc,
prev_state_formatted=prev_state_formatted,
next_states=next_states,
to_iesg_eval=to_iesg_eval),
context_instance=RequestContext(request))
pass
IESG_SUBSTATE_TAGS = ('point', 'ad-f-up', 'need-rev', 'extpty')
@ -203,6 +138,115 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
change_state = change_stateREDESIGN
ChangeStateForm = ChangeStateFormREDESIGN
class ChangeStreamForm(forms.Form):
stream = forms.ModelChoiceField(StreamName.objects.exclude(slug="legacy"), required=False)
comment = forms.CharField(widget=forms.Textarea, required=False)
@group_required('Area_Director','Secretariat')
def change_stream(request, name):
"""Change the stream of a Document of type 'draft' , notifying parties as necessary
and logging the change as a comment."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.type_id=='draft':
raise Http404()
login = request.user.get_profile()
if request.method == 'POST':
form = ChangeStreamForm(request.POST)
if form.is_valid():
new_stream = form.cleaned_data['stream']
comment = form.cleaned_data['comment'].strip()
old_stream = doc.stream
if new_stream != old_stream:
save_document_in_history(doc)
doc.stream = new_stream
e = DocEvent(doc=doc,by=login,type='changed_document')
e.desc = u"Stream changed to <b>%s</b> from %s"% (new_stream,old_stream)
e.save()
email_desc = e.desc
if comment:
c = DocEvent(doc=doc,by=login,type="added_comment")
c.desc = comment
c.save()
email_desc += "\n"+c.desc
doc.time = e.time
doc.save()
email_stream_changed(request, doc, old_stream, new_stream, email_desc)
return HttpResponseRedirect(doc.get_absolute_url())
else:
stream = doc.stream
form = ChangeStreamForm(initial=dict(stream=stream))
return render_to_response('idrfc/change_stream.html',
dict(form=form,
doc=doc,
),
context_instance=RequestContext(request))
class ChangeIntentionForm(forms.Form):
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status")
comment = forms.CharField(widget=forms.Textarea, required=False)
@group_required('Area_Director','Secretariat')
def change_intention(request, name):
"""Change the intended publication status of a Document of type 'draft' , notifying parties
as necessary and logging the change as a comment."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.type_id=='draft':
raise Http404()
login = request.user.get_profile()
if request.method == 'POST':
form = ChangeIntentionForm(request.POST)
if form.is_valid():
new_level = form.cleaned_data['intended_std_level']
comment = form.cleaned_data['comment'].strip()
old_level = doc.intended_std_level
if new_level != old_level:
save_document_in_history(doc)
doc.intended_std_level = new_level
e = DocEvent(doc=doc,by=login,type='changed_document')
e.desc = u"Intended Status changed to <b>%s</b> from %s"% (new_level,old_level)
e.save()
email_desc = e.desc
if comment:
c = DocEvent(doc=doc,by=login,type="added_comment")
c.desc = comment
c.save()
email_desc += "\n"+c.desc
doc.time = e.time
doc.save()
email_owner(request, doc, doc.ad, login, email_desc)
return HttpResponseRedirect(doc.get_absolute_url())
else:
intended_std_level = doc.intended_std_level
form = ChangeIntentionForm(initial=dict(intended_std_level=intended_std_level))
return render_to_response('idrfc/change_intended_status.html',
dict(form=form,
doc=doc,
),
context_instance=RequestContext(request))
def dehtmlify_textarea_text(s):
return s.replace("<br>", "\n").replace("<b>", "").replace("</b>", "").replace(" ", " ")
@ -234,157 +278,10 @@ def get_new_ballot_id():
@group_required('Area_Director','Secretariat')
def edit_info(request, name):
"""Edit various Internet Draft attributes, notifying parties as
necessary and logging changes as document comments."""
doc = get_object_or_404(InternetDraft, filename=name)
if doc.status.status == "Expired":
raise Http404()
login = IESGLogin.objects.get(login_name=request.user.username)
new_document = False
if not doc.idinternal:
new_document = True
doc.idinternal = IDInternal(draft=doc,
rfc_flag=type(doc) == Rfc,
cur_state_id=IDState.PUBLICATION_REQUESTED,
prev_state_id=IDState.PUBLICATION_REQUESTED,
state_change_notice_to=get_initial_state_change_notice(doc),
primary_flag=1,
area_acronym_id=Acronym.INDIVIDUAL_SUBMITTER,
# would be better to use NULL to
# signify an empty ballot
ballot_id=get_new_ballot_id(),
)
if doc.idinternal.agenda:
initial_telechat_date = doc.idinternal.telechat_date
else:
initial_telechat_date = None
if request.method == 'POST':
form = EditInfoForm(request.POST,
#old_ads needs to be True here - sometimes the user needs to touch
#the information on an older document and the AD associated with it
#should remain the same - if th old ADs aren't offered, the form
#won't let the user proceed without doing the wrong thing
old_ads=True,
initial=dict(telechat_date=initial_telechat_date,
area_acronym=doc.idinternal.area_acronym_id))
if form.is_valid():
changes = []
r = form.cleaned_data
entry = "%s has been changed to <b>%s</b> from <b>%s</b>"
if new_document:
doc.idinternal.cur_state_id=r['create_in_state'].document_state_id
doc.idinternal.prev_state_id=r['create_in_state'].document_state_id
# Django barfs in the diff below because these fields
# can't be NULL
doc.idinternal.job_owner = r['job_owner']
if 'area_acronym' in r:
doc.idinternal.area_acronym = r['area_acronym']
replaces = doc.replaces_set.all()
if replaces and replaces[0].idinternal:
c = "Earlier history may be found in the Comment Log for <a href=\"%s\">%s</a>" % (replaces[0], replaces[0].idinternal.get_absolute_url())
add_document_comment(request, doc, c)
orig_job_owner = doc.idinternal.job_owner
# update the attributes, keeping track of what we're doing
# coalesce some of the changes into one comment, mail them below
def diff(obj, attr, name):
v = getattr(obj, attr)
if r[attr] != v:
changes.append(entry % (name, r[attr], v))
setattr(obj, attr, r[attr])
diff(doc, 'intended_status', "Intended Status")
if 'area_acronym' in r and r['area_acronym']:
diff(doc.idinternal, 'area_acronym', 'Area acronym')
diff(doc.idinternal, 'job_owner', 'Responsible AD')
diff(doc.idinternal, 'state_change_notice_to', "State Change Notice email list")
if changes and not new_document:
add_document_comment(request, doc, "<br>".join(changes))
# handle note (for some reason the old Perl code didn't
# include that in the changes)
if r['note'] != doc.idinternal.note:
if not r['note']:
if doc.idinternal.note:
add_document_comment(request, doc, "Note field has been cleared")
else:
if doc.idinternal.note:
add_document_comment(request, doc, "[Note]: changed to '%s'" % r['note'])
else:
add_document_comment(request, doc, "[Note]: '%s' added" % r['note'])
doc.idinternal.note = r['note']
update_telechat(request, doc.idinternal,
r['telechat_date'], r['returning_item'])
doc.idinternal.email_display = str(doc.idinternal.job_owner)
doc.idinternal.token_name = str(doc.idinternal.job_owner)
doc.idinternal.token_email = doc.idinternal.job_owner.person.email()[1]
doc.idinternal.mark_by = login
doc.idinternal.event_date = date.today()
doc.idinternal.status_date = date.today()
update_stream(request, doc,
"Setting stream while adding document to the tracker",
person=request.user.get_profile().person(),
to_stream = r['stream']
)
if changes and not new_document:
email_owner(request, doc, orig_job_owner, login, "\n".join(changes))
if new_document:
add_document_comment(request, doc, "Draft added in state %s" % doc.idinternal.cur_state.state)
doc.idinternal.save()
doc.save()
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
else:
stream=get_stream_from_draft(doc)
stream_id = stream.id if stream else None
init = dict(intended_status=doc.intended_status_id,
area_acronym=doc.idinternal.area_acronym_id,
job_owner=doc.idinternal.job_owner_id,
state_change_notice_to=doc.idinternal.state_change_notice_to,
note=dehtmlify_textarea_text(doc.idinternal.note),
telechat_date=initial_telechat_date,
returning_item=doc.idinternal.returning_item,
stream=stream_id
)
form = EditInfoForm(old_ads=False, initial=init)
if not new_document:
form.standard_fields = [x for x in form.standard_fields if x.name != "create_in_state"]
try:
ballot_issued = doc.idinternal.ballot.ballot_issued
except BallotInfo.DoesNotExist:
ballot_issued = False
return render_to_response('idrfc/edit_info.html',
dict(doc=doc,
form=form,
user=request.user,
login=login,
ballot_issued=ballot_issued),
context_instance=RequestContext(request))
pass
class EditInfoFormREDESIGN(forms.Form):
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status")
stream = forms.ModelChoiceField(StreamName.objects.all(), empty_label="(None)", required=True)
area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active"), empty_label="(None - individual submission)", required=False, label="Assigned to area")
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), label="Responsible AD", empty_label="(None)", required=True)
create_in_state = forms.ModelChoiceField(State.objects.filter(type="draft-iesg", slug__in=("pub-req", "watching")), empty_label=None, required=False)
@ -504,7 +401,6 @@ def edit_infoREDESIGN(request, name):
# update the attributes, keeping track of what we're doing
diff('intended_std_level', "Intended Status")
diff('ad', "Responsible AD")
diff('stream', "Stream")
diff('notify', "State Change Notice email list")
if r['note'] != doc.note:
@ -550,7 +446,6 @@ def edit_infoREDESIGN(request, name):
init = dict(intended_std_level=doc.intended_std_level_id,
area=doc.group_id,
ad=doc.ad_id,
stream=doc.stream_id,
notify=doc.notify,
note=dehtmlify_textarea_text(doc.note),
telechat_date=initial_telechat_date,
@ -753,3 +648,182 @@ def add_commentREDESIGN(request, name):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
add_comment = add_commentREDESIGN
class NotifyForm(forms.Form):
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False)
@group_required('Area_Director','Secretariat')
def edit_notices(request, name):
"""Change the set of email addresses document change notificaitions go to."""
doc = get_object_or_404(Document, type="draft", name=name)
if request.method == 'POST':
if "save_addresses" in request.POST:
form = NotifyForm(request.POST)
if form.is_valid():
doc.notify = form.cleaned_data['notify']
doc.save()
login = request.user.get_profile()
c = DocEvent(type="added_comment", doc=doc, by=login)
c.desc = "Notification list changed to : "+doc.notify
c.save()
return HttpResponseRedirect(urlreverse('doc_view', kwargs={'name': doc.name}))
elif "regenerate_addresses" in request.POST:
init = { "notify" : get_initial_notify(doc) }
form = NotifyForm(initial=init)
# Protect from handcrufted POST
else:
init = { "notify" : doc.notify }
form = NotifyForm(initial=init)
else:
init = { "notify" : doc.notify }
form = NotifyForm(initial=init)
return render_to_response('idrfc/change_notify.html',
{'form': form,
'doc': doc,
},
context_instance = RequestContext(request))
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]
@group_required("Area Director", "Secretariat")
def telechat_date(request, name):
doc = get_object_or_404(Document, type="draft", 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 HttpResponseRedirect(urlreverse('doc_view', kwargs={'name': doc.name}))
else:
form = TelechatForm(initial=initial)
return render_to_response('idrfc/edit_telechat_date.html',
dict(doc=doc,
form=form,
user=request.user,
login=login),
context_instance=RequestContext(request))
class IESGNoteForm(forms.Form):
note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False)
def clean_note(self):
# note is stored munged in the database
return self.cleaned_data['note'].replace('\n', '<br>').replace('\r', '').replace(' ', '&nbsp; ')
@group_required("Area Director", "Secretariat")
def edit_iesg_note(request, name):
doc = get_object_or_404(Document, type="draft", name=name)
login = request.user.get_profile()
initial = dict(note=dehtmlify_textarea_text(doc.note))
if request.method == "POST":
form = IESGNoteForm(request.POST, initial=initial)
if form.is_valid():
new_note = form.cleaned_data['note']
if new_note != doc.note:
if not new_note:
if doc.note:
log_message = "Note field has been cleared"
else:
if doc.note:
log_message = "Note changed to '%s'" % new_note
else:
log_message = "Note added '%s'" % new_note
doc.note = new_note
doc.save()
c = DocEvent(type="added_comment", doc=doc, by=login)
c.desc = log_message
c.save()
return HttpResponseRedirect(urlreverse('doc_view', kwargs={'name': doc.name}))
else:
form = IESGNoteForm(initial=initial)
return render_to_response('idrfc/edit_iesg_note.html',
dict(doc=doc,
form=form,
),
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())]
@group_required("Area Director", "Secretariat")
def edit_ad(request, name):
"""Change the shepherding Area Director for this draft."""
doc = get_object_or_404(Document, type="draft", name=name)
if request.method == 'POST':
form = AdForm(request.POST)
if form.is_valid():
doc.ad = form.cleaned_data['ad']
doc.save()
login = request.user.get_profile()
c = DocEvent(type="added_comment", doc=doc, by=login)
c.desc = "Shepherding AD changed to "+doc.ad.name
c.save()
return HttpResponseRedirect(urlreverse('doc_view', kwargs={'name': doc.name}))
else:
init = { "ad" : doc.ad_id }
form = AdForm(initial=init)
return render_to_response('idrfc/change_ad.html',
{'form': form,
'doc': doc,
},
context_instance = RequestContext(request))

View file

@ -210,6 +210,7 @@ def urlize_ietf_docs(string, autoescape=None):
string = re.sub("(?<!>)(STD ?)0{0,3}(\d+)", "<a href=\"http://tools.ietf.org/html/std\\2/\">\\1\\2</a>", string)
string = re.sub("(?<!>)(FYI ?)0{0,3}(\d+)", "<a href=\"http://tools.ietf.org/html/fyi\\2/\">\\1\\2</a>", string)
string = re.sub("(?<!>)(draft-[-0-9a-zA-Z._+]+)", "<a href=\"/doc/\\1/\">\\1</a>", string)
string = re.sub("(?<!>)(conflict-review-[-0-9a-zA-Z._+]+)", "<a href=\"/doc/\\1/\">\\1</a>", string)
return mark_safe(string)
urlize_ietf_docs.is_safe = True
urlize_ietf_docs.needs_autoescape = True

View file

@ -3,7 +3,7 @@
from django.conf import settings
from django.contrib.syndication.feeds import Feed
from django.utils.feedgenerator import Atom1Feed
from ietf.idtracker.models import IDInternal
from ietf.doc.models import Document
import datetime
class IESGAgenda(Feed):
@ -12,36 +12,23 @@ class IESGAgenda(Feed):
feed_type = Atom1Feed
def items(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.doc.models import TelechatDocEvent
drafts = IDInternal.objects.filter(docevent__telechatdocevent__telechat_date__gte=datetime.date.min).distinct()
for d in drafts:
d.latest_telechat_event = d.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
drafts = [d for d in drafts if d.latest_telechat_event.telechat_date]
drafts.sort(key=lambda d: d.latest_telechat_event.telechat_date)
return drafts
from ietf.doc.models import TelechatDocEvent
drafts = Document.objects.filter(docevent__telechatdocevent__telechat_date__gte=datetime.date.min).distinct()
for d in drafts:
d.latest_telechat_event = d.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
drafts = [d for d in drafts if d.latest_telechat_event.telechat_date]
drafts.sort(key=lambda d: d.latest_telechat_event.telechat_date)
return drafts
return IDInternal.objects.filter(agenda=1).order_by('telechat_date')
def item_categories(self, item):
return [ str(item.telechat_date) ]
def item_pubdate(self, item):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return item.latest_telechat_event.time
return item.latest_telechat_event.time
f = item.comments().filter(comment_text__startswith='Placed on agenda for telechat')
try:
comment = f[0]
date = comment.datetime()
except IndexError:
date = datetime.datetime.now() #XXX
return date
def item_author_name(self, item):
return str( item.job_owner )
return str( item.ad ) if item.ad else "None"
def item_author_email(self, item):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return item.ad.role_email("ad")
return item.job_owner.person.email()[1]
return str( item.ad.role_email("ad") ) if item.ad else ""

View file

@ -470,3 +470,107 @@ class IesgUrlTestCase(SimpleUrlTestCase):
else:
return content
#Tests added since database redesign that speak the new clases
from ietf.doc.models import Document,TelechatDocEvent,State
from ietf.group.models import Person
class DeferUndeferTestCase(django.test.TestCase):
fixtures=['names']
def helper_test_defer(self,name):
doc = Document.objects.get(name=name)
url = urlreverse('doc_defer_ballot',kwargs=dict(name=doc.name))
login_testing_unauthorized(self, "ad", url)
# some additional setup
dates = TelechatDate.objects.active().order_by("date")
first_date = dates[0].date
second_date = dates[1].date
e = TelechatDocEvent(type="scheduled_for_telechat",
doc = doc,
by = Person.objects.get(name="Aread Irector"),
telechat_date = first_date,
returning_item = False,
)
e.save()
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form.defer')),1)
# defer
self.assertEquals(doc.telechat_date,first_date)
r = self.client.post(url,dict())
self.assertEquals(r.status_code, 302)
doc = Document.objects.get(name=name)
self.assertEquals(doc.telechat_date,second_date)
self.assertTrue(doc.returning_item())
defer_states = dict(draft=['draft-iesg','defer'],conflrev=['conflrev','defer'])
if doc.type_id in defer_states:
self.assertEquals(doc.get_state(defer_states[doc.type_id][0]).slug,defer_states[doc.type_id][1])
def helper_test_undefer(self,name):
doc = Document.objects.get(name=name)
url = urlreverse('doc_undefer_ballot',kwargs=dict(name=doc.name))
login_testing_unauthorized(self, "ad", url)
# some additional setup
dates = TelechatDate.objects.active().order_by("date")
first_date = dates[0].date
second_date = dates[1].date
e = TelechatDocEvent(type="scheduled_for_telechat",
doc = doc,
by = Person.objects.get(name="Aread Irector"),
telechat_date = second_date,
returning_item = True,
)
e.save()
defer_states = dict(draft=['draft-iesg','defer'],conflrev=['conflrev','defer'])
if doc.type_id in defer_states:
doc.set_state(State.objects.get(type=defer_states[doc.type_id][0],slug=defer_states[doc.type_id][1]))
doc.save()
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form.undefer')),1)
# undefer
self.assertEquals(doc.telechat_date,second_date)
r = self.client.post(url,dict())
self.assertEquals(r.status_code, 302)
doc = Document.objects.get(name=name)
self.assertEquals(doc.telechat_date,first_date)
self.assertTrue(doc.returning_item())
undefer_states = dict(draft=['draft-iesg','iesg-eva'],conflrev=['conflrev','iesgeval'])
if doc.type_id in undefer_states:
self.assertEquals(doc.get_state(undefer_states[doc.type_id][0]).slug,undefer_states[doc.type_id][1])
def test_defer_draft(self):
self.helper_test_defer('draft-ietf-mars-test')
def test_defer_conflict_review(self):
self.helper_test_defer('conflict-review-imaginary-irtf-submission')
def test_undefer_draft(self):
self.helper_test_undefer('draft-ietf-mars-test')
def test_undefer_conflict_review(self):
self.helper_test_undefer('conflict-review-imaginary-irtf-submission')
# when charters support being deferred, be sure to test them here
def setUp(self):
from ietf.utils.test_data import make_test_data
make_test_data()

View file

@ -52,7 +52,7 @@ from ietf.idrfc.models import RfcIndex
from ietf.idrfc.utils import update_telechat
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
from ietf.ipr.models import IprRfc, IprDraft, IprDetail
from ietf.ipr.models import IprDocAlias
from ietf.doc.models import Document, TelechatDocEvent
from ietf.group.models import Group
@ -136,26 +136,37 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
def get_doc_section(id):
pass
def get_doc_sectionREDESIGN(id):
states = [16,17,18,19,20,21]
if id.intended_std_level_id in ["bcp", "ds", "ps", "std"]:
s = "2"
else:
s = "3"
def get_doc_sectionREDESIGN(doc):
if doc.type_id == 'draft':
states = [16,17,18,19,20,21]
if doc.intended_std_level_id in ["bcp", "ds", "ps", "std"]:
s = "2"
else:
s = "3"
g = doc.group_acronym()
if g and str(g) != 'none':
s = s + "1"
elif (s == "3") and doc.stream_id in ("ise","irtf"):
s = s + "3"
else:
s = s + "2"
if not doc.get_state_slug=="rfc" and doc.get_state('draft-iesg').order not in states:
s = s + "3"
elif doc.returning_item():
s = s + "2"
else:
s = s + "1"
elif doc.type_id == 'charter':
s = get_wg_section(doc.group)
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"
elif doc.returning_item():
s = "332"
else:
s = "331"
g = id.document().group_acronym()
if g and str(g) != 'none':
s = s + "1"
elif (s == "3") and id.stream in ("ISE","IRTF"):
s = s + "3"
else:
s = s + "2"
if not id.rfc_flag and id.cur_state.document_state_id not in states:
s = s + "3"
elif id.returning_item:
s = s + "2"
else:
s = s + "1"
return s
def get_wg_section(wg):
@ -179,42 +190,28 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
get_doc_section = get_doc_sectionREDESIGN
def agenda_docs(date, next_agenda):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.doc.models import TelechatDocEvent
from ietf.doc.models import TelechatDocEvent
matches = IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date).distinct()
matches = Document.objects.filter(docevent__telechatdocevent__telechat_date=date).distinct()
idmatches = []
rfcmatches = []
docmatches = []
for m in matches:
if m.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date != date:
continue
for m in matches:
if m.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date != date:
continue
e = m.latest_event(type="started_iesg_process")
m.balloting_started = e.time if e else datetime.datetime.min
e = m.latest_event(type="started_iesg_process")
m.balloting_started = e.time if e else datetime.datetime.min
if m.docalias_set.filter(name__startswith="rfc"):
rfcmatches.append(m)
else:
idmatches.append(m)
idmatches.sort(key=lambda d: d.balloting_started)
rfcmatches.sort(key=lambda d: d.balloting_started)
else:
if next_agenda:
matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)
else:
matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1)
idmatches = matches.filter(rfc_flag=0).order_by('ballot')
rfcmatches = matches.filter(rfc_flag=1).order_by('ballot')
docmatches.append(m)
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 id in list(idmatches)+list(rfcmatches):
for id in docmatches:
section_key = "s"+get_doc_section(id)
if section_key not in res:
res[section_key] = []
if id.note:
# TODO: Find out why this is _here_
id.note = id.note.replace(u"\240",u"&nbsp;")
res[section_key].append({'obj':id})
return res
@ -311,15 +308,12 @@ def agenda_documents_txt(request):
dates = TelechatDates.objects.all()[0].dates()
docs = []
for date in dates:
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.doc.models import TelechatDocEvent
for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date).distinct():
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date == date:
docs.append(d)
else:
docs.extend(IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1))
from ietf.doc.models import TelechatDocEvent
for d in Document.objects.filter(docevent__telechatdocevent__telechat_date=date).distinct():
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date == date:
docs.append(d)
t = loader.get_template('iesg/agenda_documents.txt')
c = Context({'docs':docs,'special_stream_list':['ISE','IRTF']})
c = Context({'docs':docs,'special_stream_list':['ise','irtf']})
return HttpResponse(t.render(c), mimetype='text/plain')
class RescheduleForm(forms.Form):
@ -342,83 +336,62 @@ class RescheduleForm(forms.Form):
self.fields['telechat_date'].choices = choices
def handle_reschedule_form(request, idinternal, dates):
def handle_reschedule_form(request, doc, dates):
initial = dict(
telechat_date=idinternal.telechat_date if idinternal.agenda else None)
telechat_date=doc.telechat_date if doc.on_upcoming_agenda() else None)
formargs = dict(telechat_dates=dates,
prefix="%s" % idinternal.draft_id,
prefix="%s" % doc.name,
initial=initial)
if request.method == 'POST':
form = RescheduleForm(request.POST, **formargs)
if form.is_valid():
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
login = request.user.get_profile()
update_telechat(request, idinternal, login,
form.cleaned_data['telechat_date'],
False if form.cleaned_data['clear_returning_item'] else None)
idinternal.time = datetime.datetime.now()
idinternal.save()
else:
update_telechat(request, idinternal,
form.cleaned_data['telechat_date'])
if form.cleaned_data['clear_returning_item']:
idinternal.returning_item = False
idinternal.event_date = datetime.date.today()
idinternal.save()
login = request.user.get_profile()
update_telechat(request, doc, login,
form.cleaned_data['telechat_date'],
False if form.cleaned_data['clear_returning_item'] else None)
doc.time = datetime.datetime.now()
doc.save()
else:
form = RescheduleForm(**formargs)
form.show_clear = idinternal.returning_item
form.show_clear = doc.returning_item()
return form
def agenda_documents(request):
dates = TelechatDates.objects.all()[0].dates()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.doc.models import TelechatDocEvent
idinternals = []
for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date__in=dates).distinct():
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date in dates:
idinternals.append(d)
from ietf.doc.models import TelechatDocEvent
docs = []
for d in Document.objects.filter(docevent__telechatdocevent__telechat_date__in=dates).distinct():
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date in dates:
docs.append(d)
e = d.latest_event(type="started_iesg_process")
d.balloting_started = e.time if e else datetime.datetime.min
idinternals.sort(key=lambda d: d.balloting_started)
else:
idinternals = list(IDInternal.objects.filter(telechat_date__in=dates,primary_flag=1,agenda=1).order_by('rfc_flag', 'ballot'))
for i in idinternals:
e = d.latest_event(type="started_iesg_process")
d.balloting_started = e.time if e else datetime.datetime.min
docs.sort(key=lambda d: d.balloting_started)
for i in docs:
i.reschedule_form = handle_reschedule_form(request, i, dates)
# some may have been taken off the schedule by the reschedule form
idinternals = filter(lambda x: x.agenda, idinternals)
docs = filter(lambda x: x.on_upcoming_agenda(), docs)
telechats = []
for date in dates:
matches = filter(lambda x: x.telechat_date == date, idinternals)
matches = filter(lambda x: x.telechat_date == date, docs)
res = {}
for i in matches:
section_key = "s" + get_doc_section(i)
if section_key not in res:
res[section_key] = []
# PM - add code to fill in IPR details. (Would be better to use IdRfc_Wrapper - but this breaks other code
if not i.rfc_flag:
w = IdWrapper(draft=i)
w.iprUrl = "/ipr/search?option=document_search&id_document_tag=" + str(w.id.tracker_id)
iprs = IprDraft.objects.filter(document=w.id.tracker_id)
else:
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
ri = i
if i.type_id=='draft':
if i.get_state_slug()!="rfc":
i.iprUrl = "/ipr/search?option=document_search&id_document_tag=" + str(i.name)
else:
ri = RfcIndex.objects.get(rfc_number=i.draft_id)
w = RfcWrapper(ri)
w.iprUrl = "/ipr/search?option=rfc_search&rfc_search=" + str(w.rfc.rfc_number)
iprs = IprRfc.objects.filter(document=w.rfc.rfc_number)
w.iprCount = len(iprs)
w.reschedule_form = i.reschedule_form
w.pages = i.pages
res[section_key].append(w)
i.iprUrl = "/ipr/search?option=rfc_search&rfc_search=" + str(i.rfc_number())
i.iprCount = len(i.ipr())
res[section_key].append(i)
telechats.append({'date':date, 'docs':res})
return direct_to_template(request, 'iesg/agenda_documents.html', {'telechats':telechats, 'hide_telechat_date':True})
return direct_to_template(request, 'iesg/agenda_documents_redesign.html', {'telechats':telechats, 'hide_telechat_date':True})
def telechat_docs_tarfile(request,year,month,day):
from tempfile import mkstemp

View file

@ -0,0 +1,112 @@
from django import template
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
from ietf.ietfworkflows.utils import (get_workflow_for_draft,
get_state_for_draft)
from ietf.wgchairs.accounts import (can_manage_shepherd_of_a_document,
can_manage_writeup_of_a_document)
from ietf.ietfworkflows.streams import get_stream_from_wrapper
from ietf.ietfworkflows.models import Stream
from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream,
is_chair_of_stream, can_adopt)
register = template.Library()
@register.inclusion_tag('ietfworkflows/stream_state_redesign.html', takes_context=True)
def stream_state(context, doc):
data = {}
stream = doc.stream
data.update({'stream': stream})
if not stream:
return data
if doc.type.slug != 'draft':
return data
state = get_state_for_draft(doc)
data.update({
'state': state,
'doc': doc,
})
return data
@register.inclusion_tag('ietfworkflows/workflow_history_entry.html', takes_context=True)
def workflow_history_entry(context, entry):
real_entry = entry.get_real_instance()
return {'entry': real_entry,
'entry_class': real_entry.__class__.__name__.lower()}
@register.inclusion_tag('ietfworkflows/edit_actions.html', takes_context=True)
def edit_actions(context, wrapper):
request = context.get('request', None)
user = request and request.user
if not user:
return {}
idwrapper = None
if isinstance(wrapper, IdRfcWrapper):
idwrapper = wrapper.id
elif isinstance(wrapper, IdWrapper):
idwrapper = wrapper
if not idwrapper:
return None
doc = wrapper
draft = wrapper._draft
actions = []
if can_adopt(user, draft):
actions.append(("Adopt in WG", urlreverse('edit_adopt', kwargs=dict(name=doc.draft_name))))
if can_edit_state(user, draft):
actions.append(("Change stream state", urlreverse('edit_state', kwargs=dict(name=doc.draft_name))))
if can_edit_stream(user, draft):
actions.append(("Change stream", urlreverse('edit_stream', kwargs=dict(name=doc.draft_name))))
if can_manage_shepherd_of_a_document(user, draft):
actions.append(("Change shepherd", urlreverse('doc_managing_shepherd', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))))
if can_manage_writeup_of_a_document(user, draft):
actions.append(("Change stream writeup", urlreverse('doc_managing_writeup', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))))
else:
actions.append(("View writeup", urlreverse('doc_managing_writeup', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))))
return dict(actions=actions)
class StreamListNode(template.Node):
def __init__(self, user, var_name):
self.user = user
self.var_name = var_name
def render(self, context):
user = self.user.resolve(context)
streams = []
for i in Stream.objects.all():
if "Legacy" in i.name:
continue
if is_chair_of_stream(user, i):
streams.append(i)
context.update({self.var_name: streams})
return ''
@register.tag
def get_user_managed_streams(parser, token):
firstbits = token.contents.split(None, 2)
if len(firstbits) != 3:
raise template.TemplateSyntaxError("'get_user_managed_streams' tag takes three arguments")
user = parser.compile_filter(firstbits[1])
lastbits_reversed = firstbits[2][::-1].split(None, 2)
if lastbits_reversed[1][::-1] != 'as':
raise template.TemplateSyntaxError("next-to-last argument to 'get_user_managed_stream' tag must"
" be 'as'")
var_name = lastbits_reversed[0][::-1]
return StreamListNode(user, var_name)

View file

@ -19,7 +19,7 @@
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">3</field>
<field type="BooleanField" name="blocking">False</field>
<field type="BooleanField" name="blocking">True</field>
</object>
<object pk="block" model="name.ballotpositionname">
<field type="CharField" name="name">Block</field>
@ -85,6 +85,12 @@
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="conflrev" model="name.docrelationshipname">
<field type="CharField" name="name">conflict reviews</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="stream-s" model="name.docremindertypename">
<field type="CharField" name="name">Stream state should change</field>
<field type="TextField" name="desc"></field>
@ -307,6 +313,18 @@
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="maturity" model="name.doctypename">
<field type="CharField" name="name">Maturity Change</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="conflrev" model="name.doctypename">
<field type="CharField" name="name">Conflict Review</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="no" model="name.groupballotpositionname">
<field type="CharField" name="name">No</field>
<field type="TextField" name="desc"></field>
@ -628,7 +646,7 @@
<object pk="ds" model="name.stdlevelname">
<field type="CharField" name="name">Draft Standard</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="BooleanField" name="used">False</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="ps" model="name.stdlevelname">
@ -766,6 +784,9 @@
<object pk="charter" model="doc.statetype">
<field type="CharField" name="label">State</field>
</object>
<object pk="conflrev" model="doc.statetype">
<field type="CharField" name="label">Conflict Review State</field>
</object>
<object pk="81" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">agenda</field>
<field type="SlugField" name="slug">active</field>
@ -838,6 +859,96 @@
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="89" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">needshep</field>
<field type="CharField" name="name">Needs Shepherd</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">A conflict review has been requested, but a shepherding AD has not yet been assigned</field>
<field type="IntegerField" name="order">1</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"><object pk="90"></object><object pk="97"></object><object pk="98"></object></field>
</object>
<object pk="90" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">adrev</field>
<field type="CharField" name="name">AD Review</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The sponsoring AD is reviewing the document and preparing a proposed response</field>
<field type="IntegerField" name="order">2</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"><object pk="91"></object><object pk="97"></object><object pk="98"></object></field>
</object>
<object pk="91" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">iesgeval</field>
<field type="CharField" name="name">IESG Evaluation</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The IESG is considering the proposed conflict review response</field>
<field type="IntegerField" name="order">3</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"><object pk="92"></object><object pk="93"></object><object pk="94"></object><object pk="97"></object><object pk="98"></object></field>
</object>
<object pk="92" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">defer</field>
<field type="CharField" name="name">IESG Evaluation - Defer</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The evaluation of the proposed conflict review response has been deferred to the next telechat</field>
<field type="IntegerField" name="order">4</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"><object pk="91"></object><object pk="93"></object><object pk="94"></object><object pk="97"></object><object pk="98"></object></field>
</object>
<object pk="93" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">appr-reqnopub-pend</field>
<field type="CharField" name="name">Approved Request to Not Publish - announcement to be sent</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The IESG has approved the conflict review response (a request to not publish), but the secretariat has not yet sent the response</field>
<field type="IntegerField" name="order">5</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"><object pk="95"></object><object pk="97"></object></field>
</object>
<object pk="94" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">appr-noprob-pend</field>
<field type="CharField" name="name">Approved No Problem - announcement to be sent</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The IESG has approved the conflict review response, but the secretariat has not yet sent the response</field>
<field type="IntegerField" name="order">6</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"><object pk="96"></object><object pk="97"></object></field>
</object>
<object pk="95" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">appr-reqnopub-sent</field>
<field type="CharField" name="name">Approved Request to Not Publish - announcement sent</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The secretariat has delivered the IESG's approved conflict review response (a request to not publish) to the requester</field>
<field type="IntegerField" name="order">7</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="96" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">appr-noprob-sent</field>
<field type="CharField" name="name">Approved No Problem - announcement sent</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The secretariat has delivered the IESG's approved conflict review response to the requester</field>
<field type="IntegerField" name="order">8</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="97" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">withdraw</field>
<field type="CharField" name="name">Withdrawn</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The request for conflict review was withdrawn</field>
<field type="IntegerField" name="order">9</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"><object pk="89"></object></field>
</object>
<object pk="98" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">dead</field>
<field type="CharField" name="name">Dead</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">The conflict review has been abandoned</field>
<field type="IntegerField" name="order">10</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"><object pk="89"></object></field>
</object>
<object pk="1" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft</field>
<field type="SlugField" name="slug">active</field>
@ -1624,31 +1735,49 @@
<field type="IntegerField" name="order">2</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="5" model="doc.ballottype">
<field to="name.doctypename" name="doc_type" rel="ManyToOneRel">conflrev</field>
<field type="SlugField" name="slug">conflrev</field>
<field type="CharField" name="name">Approve</field>
<field type="TextField" name="question">Is this the correct conflict review response?</field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
<field to="name.ballotpositionname" name="positions" rel="ManyToManyRel"><object pk="yes"></object><object pk="noobj"></object><object pk="discuss"></object><object pk="abstain"></object><object pk="recuse"></object><object pk="norecord"></object></field>
</object>
<object pk="1" model="doc.ballottype">
<field to="name.doctypename" name="doc_type" rel="ManyToOneRel">charter</field>
<field type="SlugField" name="slug">r-extrev</field>
<field type="CharField" name="name">Ready for external review</field>
<field type="TextField" name="question">Is this charter ready for external review?</field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
<field type="IntegerField" name="order">1</field>
<field to="name.ballotpositionname" name="positions" rel="ManyToManyRel"><object pk="yes"></object><object pk="noobj"></object><object pk="block"></object><object pk="abstain"></object><object pk="norecord"></object></field>
</object>
<object pk="2" model="doc.ballottype">
<field to="name.doctypename" name="doc_type" rel="ManyToOneRel">charter</field>
<field type="SlugField" name="slug">approve</field>
<field type="CharField" name="name">Approve</field>
<field type="TextField" name="question">Do we approve of this charter?</field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
<field to="name.ballotpositionname" name="positions" rel="ManyToManyRel"><object pk="yes"></object><object pk="noobj"></object><object pk="block"></object><object pk="abstain"></object><object pk="norecord"></object></field>
</object>
<object pk="3" model="doc.ballottype">
<object pk="4" model="doc.ballottype">
<field to="name.doctypename" name="doc_type" rel="ManyToOneRel">draft</field>
<field type="SlugField" name="slug">approve</field>
<field type="CharField" name="name">Approve</field>
<field type="TextField" name="question"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
<field type="IntegerField" name="order">1</field>
<field to="name.ballotpositionname" name="positions" rel="ManyToManyRel"><object pk="yes"></object><object pk="noobj"></object><object pk="discuss"></object><object pk="abstain"></object><object pk="recuse"></object><object pk="norecord"></object></field>
</object>
<object pk="2" model="doc.ballottype">
<field to="name.doctypename" name="doc_type" rel="ManyToOneRel">charter</field>
<field type="SlugField" name="slug">r-wo-ext</field>
<field type="CharField" name="name">Ready w/o external review</field>
<field type="TextField" name="question">Is this charter ready for external review? Is this charter ready for approval without external review?</field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">2</field>
<field to="name.ballotpositionname" name="positions" rel="ManyToManyRel"><object pk="yes"></object><object pk="noobj"></object><object pk="block"></object><object pk="abstain"></object><object pk="norecord"></object></field>
</object>
<object pk="3" model="doc.ballottype">
<field to="name.doctypename" name="doc_type" rel="ManyToOneRel">charter</field>
<field type="SlugField" name="slug">approve</field>
<field type="CharField" name="name">Approve</field>
<field type="TextField" name="question">Do we approve of this charter?</field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">3</field>
<field to="name.ballotpositionname" name="positions" rel="ManyToManyRel"><object pk="yes"></object><object pk="noobj"></object><object pk="block"></object><object pk="abstain"></object><object pk="norecord"></object></field>
</object>
</django-objects>

View file

@ -0,0 +1,161 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
from name.models import DocTypeName
from name.models import DocRelationshipName
class Migration(DataMigration):
def forwards(self, orm):
DocTypeName(slug='conflrev',name='Conflict Review',used=True).save()
DocRelationshipName(slug='conflrev',name='conflict reviews',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']

View file

@ -161,17 +161,19 @@ class MainUrlTestCase(SimpleUrlTestCase):
def get_templates():
templates = set()
for root, dirs, files in os.walk(os.path.join(settings.BASE_DIR,"templates")):
# Shoud we teach this to use TEMPLATE_DIRS?
templatepath = os.path.join(settings.BASE_DIR,"templates")
for root, dirs, files in os.walk(templatepath):
if ".svn" in dirs:
dirs.remove(".svn")
last_dir = os.path.split(root)[1]
relative_path = root[len(templatepath)+1:]
for file in files:
if file.endswith("~") or file.startswith("#"):
continue
if last_dir == "templates":
if relative_path == "":
templates.add(file)
else:
templates.add(os.path.join(last_dir, file))
templates.add(os.path.join(relative_path, file))
return templates
class TemplateCoverageTestCase(unittest.TestCase):

View file

@ -195,6 +195,7 @@ INTERNET_DRAFT_PDF_PATH = '/a/www/ietf-datatracker/pdf/'
RFC_PATH = '/a/www/ietf-ftp/rfc/'
CHARTER_PATH = '/a/www/ietf-ftp/charters/'
CHARTER_TXT_URL = 'http://www.ietf.org/charter/'
CONFLICT_REVIEW_PATH = '/a/www/ietf-ftp/conflict_reviews'
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'

View file

@ -0,0 +1,30 @@
{% load mail_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
To: {{ review.notify }}
Cc: The IESG <iesg@ietf.org>, <iana@iana.org>, <ietf-announce@ietf.org>
Subject: Results of IETF-conflict review for {{conflictdoc.canonical_name}}-{{conflictdoc.rev}}
{% filter wordwrap:73 %}The IESG has completed a review of {{conflictdoc.canonical_name}}-{{conflictdoc.rev}} consistent with RFC5742.
{% if review.get_state_slug == 'appr-reqnopub-pend' %}
The IESG recommends that '{{ conflictdoc.title }}' {{ conflictdoc.file_tag|safe }} NOT be published as {{ conflictdoc|std_level_prompt_with_article }}.
{% else %}
The IESG has no problem with the publication of '{{ conflictdoc.title }}' {{ conflictdoc.file_tag|safe }} as {{ conflictdoc|std_level_prompt_with_article }}.
{% endif %}
The IESG would also like the {{receiver}} to review the comments in the datatracker related to this document and determine whether or not they merit incorporation into the document. Comments may exist in both the ballot and the history log.
The IESG review is documented at:
{{review_url}}
A URL of the reviewed Internet Draft is:
{{conflictdoc_url}}
The process for such documents is described at
http://www.rfc-editor.org/indsubs.html
Thank you,
The IESG Secretary
{% endfilter %}
{% endautoescape %}

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block title %}Approve {{ review.canonical_name }}{% endblock %}
{% block morecss %}
form #id_announcement_text {
overflow-x: auto;
overflow-y: scroll;
width: 800px;
height: 400px;
border: 1px solid #bbb;
}
{% endblock %}
{% block content %}
<h1>Approve {{ review.canonical_name }}</h1>
<form class="approve" action="" method="POST">
<table>
{% for field in form.visible_fields %}
<tr>
<td>
<div>{{ field.label_tag }}:</div>
{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td class="actions">
<a href="{% url doc_view name=review.name %}">Back</a>
<input type="submit" value="Send out the announcement and close the ballot"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block morecss %}
.warning {
font-weight: bold;
color: #a00;
}
{% endblock %}
{% block title %}
Change the shepherding AD for the conflict review of {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}
{% endblock %}
{% block content %}
<h1>Change the shepherding AD for the conflict review of {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}</h1>
<form class="edit-info" action="" enctype="multipart/form-data" method="POST">
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<td>
{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=review.canonical_name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Change State: {{doc.title}}{% endblock %}
{% block morecss %}
form.change-state select {
width: 22em;
}
#id_message, #id_comment {
width: 40em;
}
form.change-state .actions {
text-align: right;
padding-top: 10px;
}
{% endblock %}
{% block content %}
<h1>Change State: {{doc.title}}</h1>
<p class="helptext">For help on the states, see the <a href="{% url help_conflict_review_states %}">state table</a>.</p>
<form class="change-state" action="" method="post">
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<td>{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td colspan="2" class="actions">
<a href="{% url doc_view name=doc.name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}
Set Telechat Date for {{ doc.name }}
{% endblock %}
{% block morecss %}
form.telechat-date td.actions {
padding-top: 1em;
}
{% endblock %}
{% block content %}
{% load ietf_filters %}
<h1>Set Telechat Date for {{ doc.name }}</h1>
<form class="telechat-date" action="" method="POST">
<table>
{{ form.as_table }}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=doc.name %}">Back</a>
<input type="submit" value="Save"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block morecss %}
form.edit-info #id_notify {
width: 600px;
}
.warning {
font-weight: bold;
color: #a00;
}
{% endblock %}
{% block title %}
Edit notification addresses for the conflict review of {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}
{% endblock %}
{% block content %}
<h1>Edit notification addresses for the conflict review of {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}</h1>
<form class="edit-info" action="" enctype="multipart/form-data" method="POST">
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<td>
{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=review.canonical_name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,17 @@
[Edit this page to chose one of the following five responses, adding the information requested in <> as necessary. Delete these instructions.]
The IESG has concluded that there is no conflict between this document and IETF work.
The IESG has concluded that this work is related to IETF work done in WG <X>, but this relationship does not prevent publishing.
The IESG has concluded that publication could potentially disrupt the IETF work done in WG <X> and recommends not publishing the document at this time.
The IESG has concluded that this document violates IETF procedures for <Y> and should therefore not be published without IETF review and IESG approval.
The IESG has concluded that this document extends an IETF protocol in a way that requires IETF review and should therefore not be published without IETF review and IESG approval.
[In the exceptional case that an IESG note is needed, add the following section (otherwise delete it): Delete these instructions.]
Additionally, the IESG requests the following note be added to the document if it is published:
<IESG NOTE GOES HERE>

View file

@ -0,0 +1,44 @@
{% extends "base.html" %}
{% block title %}Begin IETF conflict review : {{doc_to_review.canonical_name}}-{{doc_to_review.rev}}{% endblock %}
{% block morecss %}
form.start-conflict-review #id_notify {
width: 600px;
}
form.start-conflict-review .actions {
padding-top: 20px;
}
.warning {
font-weight: bold;
color: #a00;
}
{% endblock %}
{% block content %}
<h1>Begin IETF conflict review for {{doc_to_review.canonical_name}}-{{doc_to_review.rev}}</h1>
<p class="helptext">For help on the initial state choice, see the <a href="{% url help_conflict_review_states %}">state table</a>.</p>
<form class="start-conflict-review" action="" method="post">
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<td>{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td colspan="2" class="actions">
<a href="{% url doc_view name=doc_to_review.name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block morecss %}
form #id_content {
width: 40em;
height: 450px;
}
{% endblock %}
{% block title %}
Edit conflict review for {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}
{% endblock %}
{% block content %}
<h1>Edit conflict review for {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}</h1>
<p>The text will be submitted as <strong>{{ review.canonical_name }}-{{ next_rev }}</strong></p>
<form class="edit-info" action="" enctype="multipart/form-data" method="POST">
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<td>
{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=review.canonical_name %}">Back</a>
<input type="submit" name="reset_text" value="Reset to Template Text"/>
<input type="submit" name="submit_response" value="Submit"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -1,7 +1,6 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2011, All Rights Reserved #}
{% block title %}Charter States{% endblock %}
{% block title %}{{ title }} States{% endblock %}
{% block morecss %}
.state_column {
@ -11,7 +10,7 @@
{% block content %}
<h1>Charter States</h1>
<h1>{{ title }} States</h1>
<table class="ietf-table">

View file

@ -0,0 +1,54 @@
{% comment %}
Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the Nokia Corporation and/or its
subsidiary(-ies) nor the names of its contributors may be used
to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% endcomment %}
{% load ietf_filters ietf_streams_redesign %}{% load ballot_icon_redesign %}{% load doc_states %}
<td class="status">
{{ doc.friendly_state|safe }} {% if not doc.rfc %}{{ doc|state_age_colored|safe }}{% endif %}
{% if not hide_telechat_date %}{% if doc.telechat_date %}<br/>IESG Telechat: {{ doc.telechat_date }}{% endif %}{% endif %}
{% block extra_status %}{% endblock %}
{% if doc.rfc %}
{%comment%}
TODO: Port this block to Document when something that shows RFCs uses this template
{% if doc.rfc.obsoleted_by %}<br />Obsoleted by {{ doc.rfc.obsoleted_by|urlize_ietf_docs }}{%endif %}
{% if doc.rfc.updated_by %}<br />Updated by {{ doc.rfc.updated_by|urlize_ietf_docs }}{%endif %}
{% if doc.rfc.has_errata %}<br /><a href="http://www.rfc-editor.org/errata_search.php?rfc={{doc.rfc.rfc_number}}" rel="nofollow">Errata</a>{% endif %}
{%endcomment%}
{% else %}{# not rfc #}
{% if doc|rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.name}}">{{ doc|rfc_editor_state|escape }}</a>{% endif %}
{% stream_state doc %}
{% endif %}
</td>
<td class="ballot">
{% ballot_icon doc %}
</td>

View file

@ -1,3 +1,3 @@
{# Copyright The IETF Trust 2008, All Rights Reserved #}
{{ obj.document.title|escape }}<br>
{{ obj.document.abstract|escape|truncatewords:50 }}
{{ obj.title|escape }}<br>
{{ obj.abstract|escape|truncatewords:50 }}

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block morecss %}
.warning {
font-weight: bold;
color: #a00;
}
{% endblock %}
{% block title %}
Change the shepherding AD for {{ doc.name }}-{{ doc.rev }}
{% endblock %}
{% block content %}
<h1>Change the shepherding AD for {{ doc.name }}-{{ doc.rev }}</h1>
<form class="edit-info" action="" enctype="multipart/form-data" method="POST">
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<td>
{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=doc.name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}Change intended status for {{ doc }}{% endblock %}
{% block morecss %}
form.change-intended-status select {
width: 22em;
}
form.change-intended-status .actions {
text-align: right;
padding-top: 10px;
}
{% endblock %}
{% block content %}
<h1>Change intended status for {{ doc }}</h1>
<form class="change-intended-status" action="" method="post">
<table>
{{ form.as_table }}
<tr>
<td colspan="2" class="actions">
<a href="{{ doc.get_absolute_url }}">Back</a>
<input type="submit" value="Save"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block morecss %}
form.edit-info #id_notify {
width: 600px;
}
.warning {
font-weight: bold;
color: #a00;
}
{% endblock %}
{% block title %}
Edit notification addresses for {{ doc.canonical_name }}-{{ doc.rev }}
{% endblock %}
{% block content %}
<h1>Edit notification addresses for {{ doc.canonical_name }}-{{ doc.rev }}</h1>
<form class="edit-info" action="" enctype="multipart/form-data" method="POST">
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<td>
{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=doc.canonical_name %}">Back</a>
<input type="submit" name="save_addresses" value="Save" />
<input type="submit" name="regenerate_addresses" value="Regenerate Address List" />
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}Change stream for {{ doc }}{% endblock %}
{% block morecss %}
form.change-stream select {
width: 22em;
}
form.change-stream .actions {
text-align: right;
padding-top: 10px;
}
{% endblock %}
{% block content %}
<h1>Change stream for {{ doc }}</h1>
<form class="change-stream" action="" method="post">
<table>
{{ form.as_table }}
<tr>
<td colspan="2" class="actions">
<a href="{{ doc.get_absolute_url }}">Back</a>
<input type="submit" value="Save"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -5,7 +5,7 @@
{% block content %}
<h1>Defer ballot for {{ doc }}</h1>
<form action="" method="POST">
<form class="defer" action="" method="POST">
<p>Defer the ballot for {{ doc.file_tag }}?</p>
<p>The ballot will then be on the IESG agenda of {{ telechat_date }}.</p>

View file

@ -33,15 +33,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->{% endcomment %}
{% load ietf_filters %}
<table class="ietf-ballot"><tr valign="top"><td class="left">
{% if doc_ballot_edit_button and user|in_group:"Area_Director,Secretariat" %}
{% if user|in_group:"Area_Director" %}
<div style="margin-top:8px; margin-bottom:8px;"><span id="doc_ballot_button" class="yui-button yui-link-button"><span class="first-child"><a href="{% url idrfc.views_ballot.edit_position name=doc.name,ballot_id=ballot.pk %}">Edit position</a></span></span></div>
{% endif %}
{% if bw.was_deferred %}
{% if doc.active_defer_event %}
<div style="margin-top:8px; margin-bottom:8px;"><span id="doc_undefer_ballot_button" class="yui-button yui-link-button"><span class="first-child"><a href="{% url doc_undefer_ballot name=doc.name,ballot_id=ballot.pk %}">Undefer ballot</a></span></span></div>
<div style="margin-top:8px; margin-bottom:8px;">Ballot deferred by {{ bw.deferred_by }} on {{ bw.deferred_date }}.</div>
<div style="margin-top:8px; margin-bottom:8px;">Ballot deferred by {{ doc.active_defer_event.by }} on {{ doc.active_defer_event.time|date:"Y-m-d" }}.</div>
{% else %}
<div style="margin-top:8px; margin-bottom:8px;"><span id="doc_defer_ballot_button" class="yui-button yui-link-button"><span class="first-child"><a href="{% url doc_defer_ballot name=doc.name,ballot_id=ballot.pk %}">Defer ballot</a></span></span></div>
{% endif %}

View file

@ -49,6 +49,21 @@ div.diffTool { padding: 8px 4px; margin: 8px 0;}
.m_hdr, .m_ftr { color: #808080; }
.m_ftr { border-bottom: 1px solid #a0a0a0; }
.m_h { font-family: arial; font-weight:bold;}
a.editlink {
background-image: url("/images/pencil.png");
background-size:10px;
background-position: right top;
background-attachment: scroll;
background-repeat: no-repeat;
padding-right: 12px;
}
a.editlink:link {text-decoration:none; color:inherit;}
a.editlink:visited {text-decoration:none; color:inherit;}
a.editlink:hover {text-decoration:underline;}
a.editlink:active {text-decoration:underline;}
{% endblock %}
{% block pagehead %}

View file

@ -1,143 +0,0 @@
{% extends "idrfc/doc_main.html" %}
{% comment %}
Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the Nokia Corporation and/or its
subsidiary(-ies) nor the names of its contributors may be used
to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% endcomment %}
{% load ietf_filters ietf_streams %}
{% block title %}{{ doc.draft_name_and_revision }}{% endblock %}
{% block doc_meta_description %}{{ doc.title }} ({{info.type}}; {{doc.publication_date|date:"Y"}}){% endblock %}
{% block doc_h1 %}{{ doc.title|escape }}<br/>{{ doc.draft_name_and_revision }}{% endblock %}
{% block doc_metatable %}
<tr><td>Document Stream:</td><td> {{ stream_info.stream.name|default:"No stream defined" }}</td></tr>
<tr><td>I-D availability status:</td><td> {{ doc.draft_status }}
{% ifequal doc.draft_status "Expired" %}
{% if doc.resurrect_requested_by %}(resurrect requested by {{ doc.resurrect_requested_by }}){% endif %}
{% endifequal %}
{% with doc.replaces as r %}{% if r %}<br />Replaces {% filter urlize_ietf_docs %}{{ r|join:", "}}{% endfilter %}{% endif %}{% endwith %}
</td></tr>
<tr><td>Last updated:</td><td> {{ doc.publication_date|default:"(data missing)" }}</td></tr>
<tr><td>IETF WG state:</td><td>{{ stream_info.state.name }} ({{ stream_info.streamed.get_group }})
{% if stream_info.tags %}<br /><i>{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}
</td></tr>
<tr><td>Intended RFC status:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.intended_maturity_level|default:"-" }}{% else %}-{%endif%}</td></tr>
<tr><td>Document shepherd:</td><td>{{ stream_info.shepherd }}</td></tr>
<tr><td><a href="/idtracker/help/state/">IESG State</>:</td><td> {{ doc.friendly_state|safe }}
{% if doc.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.draft_name}}">{{ doc.rfc_editor_state|escape }}</a>{% endif %}
{% if doc.in_ietf_process %}{% if doc.ietf_process.telechat_date %}<br/>On agenda of {{ doc.ietf_process.telechat_date }} IESG telechat {% if doc.ietf_process.telechat_returning_item %} (returning item){%endif%}{%endif%}{% if doc.ietf_process.has_active_iesg_ballot %}<br/><i>({{ doc.ietf_process.iesg_ballot_needed }})</i>{%endif%}{%endif%}
</td></tr>
<tr><td>Submission:</td><td>{{ doc.submission }}</td></tr>
<tr><td>Responsible AD:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.ad_name|default:"-"|escape }}{%else%}-{%endif%}</td></tr>
{% if doc.in_ietf_process and doc.ietf_process.iesg_note %}<tr><td>IESG Note:</td><td>{{ doc.ietf_process.iesg_note|format_textarea|safe }}</td></tr>{% endif %}
{% if user|in_group:"Area_Director,Secretariat" %}
{% if doc.in_ietf_process %}<tr><td>Send notices to:</td><td>{{ doc.ietf_process.state_change_notice_to}}</td></tr>{% endif %}
{% endif %}{# if user|in_group:... #}
{% ifequal doc.draft_status "Active" %}
<tr><td>Other versions:</td><td>
<a href="http://www.ietf.org/id/{{doc.draft_name_and_revision}}.txt">plain text</a>,
{% for ext in doc.file_types %}
{% ifnotequal ext ".txt" %}
<a href="http://www.ietf.org/id/{{doc.draft_name_and_revision}}{{ext}}">{{ext|cut:"."}}</a>,
{% endifnotequal %}
{% endfor %}
{% if not info.has_pdf %}
<a href="http://tools.ietf.org/pdf/{{doc.draft_name_and_revision}}.pdf">pdf</a>,
{% endif %}
<a href="http://tools.ietf.org/html/{{doc.draft_name_and_revision}}">html</a>
</td></tr>
{% endifequal %}
{% endblock doc_metatable %}
{% block doc_metalinks %}
{% edit_actions doc %}
<div>
<a href="mailto:{{doc.draft_name}}@tools.ietf.org?subject=Mail%20regarding%20{{doc.draft_name}}" rel="nofollow">Email Authors</a>
| <a href="/ipr/search/?option=document_search&amp;id_document_tag={{doc.tracker_id}}" rel="nofollow">IPR Disclosures</a>
| <a href="http://www.fenron.net/~fenner/ietf/deps/index.cgi?dep={{doc.draft_name}}" rel="nofollow">Dependencies to this draft</a>
| <a href="http://tools.ietf.org/idnits?url=http://tools.ietf.org/id/{{doc.draft_name_and_revision}}.txt" rel="nofollow" target="_blank">Check nits</a>
{% if doc.in_ietf_process %}| <a href="/feed/comments/{% if info.is_rfc %}rfc{{doc.rfc_number}}{% else %}{{doc.draft_name}}{% endif %}/">Comments feed</a>{% endif %}
| <a href="http://www.google.com/search?as_q={{doc.draft_name}}&as_sitesearch={{ doc.search_archive }}" rel="nofollow" target="_blank">Search Mailing Lists</a>
{% if user|in_group:"Area_Director" %}
| <a href="https://www.iesg.org/bin/c5i?mid=6&rid=77&target={{doc.draft_name}}" rel="nofollow" target="_blank">Search Mailing Lists (ARO)</a>
{% endif %}
</div>
{% if user|in_group:"Area_Director,Secretariat" %}
<div>
{% if doc.in_ietf_process %}
<a href="{% url doc_ballot_lastcall name=doc.draft_name %}">Last Call Text</a>
| <a href="{% url doc_ballot_writeupnotes name=doc.draft_name %}">Ballot Text</a>
| <a href="{% url doc_ballot_approvaltext name=doc.draft_name %}">Announcement Text</a>
{% endif %}
</div>
{% endif %}
{% endblock %}
{% block doc_text1 %}
{% ifequal doc.draft_status "Active" %}
<div class="markup_draft">
{{ content1|safe }}
</div>
{% else %}
<p>This Internet-Draft is no longer active. Unofficial copies of old Internet-Drafts can be found here:<br/>
<a href="http://tools.ietf.org/id/{{doc.draft_name}}">http://tools.ietf.org/id/{{doc.draft_name}}</a>.</p>
<p style="max-width: 400px;"><b>Abstract:</b><br/> {{ doc.abstract|escape }}</p>
<p><b>Authors:</b><br/>
{% for author in doc.authors.all %}
{% if author.email %}
<a href="mailto:{{ author.email }}">{{ author.person }} &lt;{{author.email}}&gt;</a>
{% else %}
{% if author.person %}
{{ author.person }}
{% else %}
Missing author info #{{ author.person_id }}
{% endif %}
{% endif %}<br />
{% endfor %}</p>
<p>(Note: The e-mail addresses provided for the authors of this Internet-Draft may no longer be valid)</p>
{% endifequal %}
{% endblock %}{# doc_text1 #}
{% block doc_text2 %}
{% ifequal doc.draft_status "Active" %}
<div class="markup_draft">
{{ content2|safe }}
</div>
{% endifequal %}
{% endblock %} {# doc_text2 #}

View file

@ -56,10 +56,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<span id="doc_resurrect_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url doc_resurrect name=doc.draft_name %}">Resurrect</a></span></span>
{% endif %}
{% else %}
{% if doc.in_ietf_process %}
<span id="doc_edit_info_button" class="yui-button yui-link-button" style="margin-left:2px;">{% url doc_edit_info name=doc.draft_name as doc_edit_url %}{% if doc_edit_url %}<span class="first-child"><a href="{{doc_edit_url}}">Edit</a></span>{% endif %}</span>
{% if stream_info.stream.name == 'ISE' or stream_info.stream.name == 'IRTF' %}
{% if user|in_group:"Secretariat" and not info.conflict_reviews %}
<span id="doc_conflict_review_button" class="yui-button yui-link-button" style="margin-left:2px;">{% url conflict_review_start name=doc.draft_name as start_review_url %}{% if start_review_url %}<span class="first-child"><a href="{{start_review_url}}">Begin IETF Conflict Review</a></span>{% endif %}</span>
{% endif %}
{% else %}
<span id="doc_add_button" class="yui-button yui-link-button" style="margin-left:2px;">{% url doc_edit_info name=doc.draft_name as doc_edit_url %}{% if doc_edit_url %}<span class="first-child"><a href="{{doc_edit_url}}">Add</a></span>{% endif %}</span>
{% if stream_info.stream.name == 'IETF'%}{%if not doc.in_ietf_process %}
<span id="doc_add_button" class="yui-button yui-link-button" style="margin-left:2px;">{% url doc_edit_info name=doc.draft_name as doc_edit_url %}{% if doc_edit_url %}<span class="first-child"><a href="{{doc_edit_url}}">Begin IESG Processing</a></span>{% endif %}</span>
{% endif %}{% endif %}
{% endif %}
{% endifequal %}

View file

@ -41,13 +41,27 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% if doc.resurrect_requested_by %}(resurrect requested by {{ doc.resurrect_requested_by }}){% endif %}
{% endifequal %} </td>
<tr><td>Document Stream:</td><td> {{ stream_info.stream.name|default:"No stream defined" }}</td></tr>
<tr><td>Document Stream:</td><td>
{% with user|in_group:"Area_Director,Secretariat" as add_link %}
{% if add_link %}<a class="editlink" href="{% url doc_change_stream name=doc.draft_name %}">{% endif %}
{% if stream_info.stream %}{{ stream_info.stream.name|default:"No stream defined" }}{% else %}No stream defined{% endif %}
{% if add_link %}</a>{% endif %}
{% endwith %}
</td></tr>
<tr><td>Last updated:</td><td> {{ doc.publication_date|default:"(data missing)" }}</td></tr>
{% with doc.replaces as r %}{% if r %}<tr><td>Replaces:</td><td> {% filter urlize_ietf_docs %}{{ r|join:", "}}{% endfilter %}</td></tr>{% endif %}{% endwith %}
<tr><td>Intended RFC status:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.intended_maturity_level|default:"-" }}{% else %}-{%endif%}</td></tr>
{% with info.conflict_reviews as r %}{% if r %}<tr><td>IETF Conflict Review:</td><td> {% filter urlize_ietf_docs %}{{ r|join:","}}{% endfilter %}</td></tr>{% endif %} {% endwith %}
<tr><td>Intended RFC status:</td><td>
{% with user|in_group:"Area_Director,Secretariat" as add_link %}
{% if add_link %}<a class="editlink" href="{% url doc_change_intended_status name=doc.draft_name %}">{% endif %}
{{ doc.underlying_document.intended_std_level|default:"-" }}
{% if add_link %}</a>{% endif %}
{% endwith %}
</td></tr>
{% ifequal doc.draft_status "Active" %}
<tr><td>Other versions:</td><td>
@ -64,7 +78,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</td></tr>
{% endifequal %}
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
{% if stream_info.state %}
@ -94,7 +107,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<tr><td><a href="/idtracker/help/state/">IESG State</>:</td><td>
{% if doc.in_ietf_process and user|in_group:"Area_Director,Secretariat" %}
<a href="{% url doc_change_state name=doc.draft_name %}"> {{ doc.friendly_state|safe }} </a>
<a class="editlink" href="{% url doc_change_state name=doc.draft_name %}"> {{ doc.friendly_state|safe }} </a>
{% else %}
{{ doc.friendly_state|safe }}
{% endif %}
@ -102,14 +115,36 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% ifequal doc.draft_status "Expired" %}
{% if doc.resurrect_requested_by %}(resurrect requested by {{ doc.resurrect_requested_by }}){% endif %}
{% endifequal %}
{% if doc.in_ietf_process %}{% if doc.ietf_process.telechat_date %}<br/>On agenda of {{ doc.ietf_process.telechat_date }} IESG telechat {% if doc.ietf_process.telechat_returning_item %} (returning item){%endif%}{%endif%}{% if doc.ietf_process.has_active_iesg_ballot %}<br/><i>({{ doc.ietf_process.iesg_ballot_needed }})</i>{%endif%}{%endif%}
{% with user|in_group:"Area_Director,Secretariat" as add_link %}
{% if add_link %}<a class="editlink" href="{% url doc_change_telechat_date name=doc.draft_name %}">{% endif %}
{% if doc.underlying_document.telechat_date %}<br/>On agenda of {{ doc.underlying_document.telechat_date }} IESG telechat {% if doc.underlying_document.returning_item %} (returning item){% endif %}{% else %}{%if add_link %}<br/>Not on an upcoming telechat agenda{% endif %}{% endif %}
{% if add_link %}</a>{% endif %}
{% if doc.ietf_process.has_active_iesg_ballot %}<br/><i>({{ doc.ietf_process.iesg_ballot_needed }})</i>{%endif%}
{% endwith %}
</td></tr>
{# <tr><td>Submission:</td><td>{{ doc.submission|safe }}</td></tr> #}
<tr><td>Responsible AD:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.ad_name|default:"-"|escape }}{%else%}-{%endif%}</td></tr>
{% if doc.in_ietf_process and doc.ietf_process.iesg_note %}<tr><td>IESG Note:</td><td>{{ doc.ietf_process.iesg_note|format_textarea|safe }}</td></tr>{% endif %}
{% if user|in_group:"Area_Director,Secretariat" %}
{% if doc.in_ietf_process %}<tr><td>Send notices to:</td><td>{{ doc.ietf_process.state_change_notice_to}}</td></tr>{% endif %}
{% endif %}{# if user|in_group:... #}
<tr><td>Responsible AD:</td><td>
{% with user|in_group:"Area_Director,Secretariat" as add_link %}
{% if add_link %}<a class="editlink" href="{% url doc_change_ad name=doc.draft_name %}">{% endif %}
{% if doc.in_ietf_process %}{{ doc.ietf_process.ad_name|default:"-"|escape }}{%else%}-{%endif%}
{% if add_link %}</a>{% endif %}
{% endwith %}
</td></tr>
{% if doc.in_ietf_process and doc.ietf_process.iesg_note %}<tr><td>
IESG Note:</td><td>
{% with user|in_group:"Area_Director,Secretariat" as add_link %}
{% if add_link %}<a class="editlink" href="{% url doc_change_iesg_note name=doc.draft_name %}">{% endif %}
{{ doc.ietf_process.iesg_note|format_textarea|safe }}
{% if add_link %}</a>{% endif %}
{% endwith %}
</td></tr>{% endif %}
<tr><td>Send notices to:</td><td>
{% with user|in_group:"Area_Director,Secretariat" as add_link %}
{% if add_link %}<a class="editlink" href="{% url doc_change_notify name=doc.draft_name %}">{% endif %}
{{ doc.underlying_document.notify|default:"No addresses provided"}}
{% if add_link %}</a>{% endif %}
{% endwith %}
</td></tr>
<tr><td colspan='2'><hr size='1' noshade /></td></tr>

View file

@ -7,7 +7,7 @@
<div class="action"><a href="{% url idrfc.views_ballot.edit_position name=doc.name,ballot_id=ballot.pk %}">Edit position</a></div>
{% endif %}
{% if doc.type_id == "draft" %}
{% if doc.type_id == "draft" or doc.type_id == "conflrev" %}
<div class="action">
{% if deferred %}
<a href="{% url doc_undefer_ballot name=doc.name %}">Undefer ballot</a>

View file

@ -0,0 +1,116 @@
{% extends "idrfc/doc_main.html" %}
{% load ietf_filters %}
{% block title %}{{ doc.canonical_name }}-{{ doc.rev }}{% endblock %}
{% block pagehead %}
<link rel="stylesheet" type="text/css" href="/css/doc.css"></link>
{% endblock %}
{% block content %}
{{ top|safe }}
<div class="snapshots">
Versions:
<span class="revisions">
{% for rev in revisions %}
<a {% if rev != doc.rev %}href="{% url doc_view name=doc.name %}{% if not forloop.last %}{{ rev }}/{% endif %}"{% endif %}>{{ rev }}</a>
{% endfor %}
</span>
</div>
<div class="ietf-box metabox">
<div>
{% if snapshot %}Snapshot of{% endif %}
{% if doc.get_state_slug not in approved_states %}Proposed{% endif %}
IESG Conflict Review for the {{conflictdoc.stream}} document: <a href="{% url doc_view name=conflictdoc.canonical_name %}">{{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}</a>
</div>
<table id="metatable" width="100%">
<tr>
<td><a href="/doc/help/state/conflict-review/">Conflict Review State</a>:</td>
<td>
<div>
<a title="{{ doc.get_state.desc }}"{% if not snapshot and user|has_role:"Area Director,Secretariat" %} class="editlink" href="{% url conflict_review_change_state name=doc.name %}"{% endif %}>{{ doc.get_state.name }}</a>
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
{% if request.user|has_role:"Secretariat" %}{% if doc.get_state_slug = 'appr-reqnopub-pend' or doc.get_state_slug = 'appr-noprob-pend' %}
- <a href="{% url conflict_review_approve name=doc.name %}">Approve conflict review</a>
{% endif %}{% endif %}
{% endif %}
</div>
{% if not snapshot %}
<div class="telechat">
<a {% if not snapshot and user|has_role:"Area Director,Secretariat" and doc.get_state_slug not in approved_states %}
class="editlink" href="{% url conflict_review_telechat_date name=doc.name %}"
{%endif%} >
{% 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 %}
</a>
</div>
{% if ballot_summary %}
<div class="ballot-summary">
({{ ballot_summary }})
</div>
{% endif %}
{% endif %}
</td>
</tr>
<tr>
<td>Shepherding AD:</td>
<td>
<a {% if not snapshot and user|has_role:"Area Director,Secretariat" and doc.get_state_slug not in approved_states %}
class="editlink" href="{% url conflict_review_ad name=doc.name %}"
{% endif %}
>
{{doc.ad}}
</a>
</td>
</tr>
<tr>
<td>Send notices to:</td>
<td>
<a {% if not snapshot and user|has_role:"Area Director,Secretariat" and doc.get_state_slug not in approved_states %}
class="editlink" href="{% url conflict_review_notices name=doc.name %}"
{% endif %}
>
{{doc.notify}}
</a>
</td>
</tr>
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
<tr><td>Last updated:</td><td> {{ doc.time|date:"Y-m-d" }}</td></tr>
{% comment %}
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
{% endcomment %}
</table>
</div>
<h3>Conflict Review for {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}
{% if not snapshot and user|has_role:"Area Director,Secretariat" and doc.get_state_slug != 'apprsent' %}
<a class="edit" href="{% url conflict_review_submit name=doc.name %}">Change conflict review text</a>
{% endif %}
</h3>
{% if doc.rev %}
<div class="markup_draft">
{{ content|fill:"80"|safe|linebreaksbr|keep_spacing|sanitize_html|safe }}
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block title %}Edit IESG note for {{ doc.name }}{% endblock %}
{% block morecss %}
form.edit-iesg-note #id_note {
width: 600px;
height: 150px;
}
{% endblock %}
{% block content %}
<h1>Edit IESG note for {{ doc.name }}</h1>
<form class="edit-iesg-note" action="" method="POST">
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<td>{{ field }}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}</td>
</tr>
{% endfor %}
<tr>
<td></td>
<td class="actions">
<a href="{{ doc.get_absolute_url }}">Back</a>
<input type="submit" value="Save"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -30,10 +30,17 @@ form.position-form #id_comment {
form.position-form .comment-text {
margin-top: 20px;
}
div.question {
font-size: 173%;
padding-left: 5px;
padding-bottom: 10px;
}
{% endblock %}
{% block content %}
<h1>Change position for {{ ad.plain_name }} {{ doc }}</h1>
<h1>Change position for {{ ad.plain_name }} on {{ doc }}</h1>
<div class="question">{{ ballot.ballot_type.question }}</div>
{% if ballot_deferred %}
<div class="ballot-deferred">Ballot deferred by {{ ballot_deferred.by }} on {{ ballot_deferred.time|date:"Y-m-d" }}.</div>
@ -45,7 +52,7 @@ form.position-form .comment-text {
<span class="actions">
<input type="submit" name="send_mail" value="Save and send email"/>
<input type="submit" value="Save"/>
{% if doc.type_id == "draft" %}
{% if doc.type_id == "draft" or doc.type_id == "conflrev" %}
{% if ballot_deferred %}<input type="submit" name="Undefer" value="Undefer"/>{% else %}<input type="submit" name="Defer" value="Defer"/>{% endif %}
{% endif %}
</span>

View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}
Set Telechat Date for {{ doc.name }}
{% endblock %}
{% block morecss %}
form.telechat-date td.actions {
padding-top: 1em;
}
{% endblock %}
{% block content %}
{% load ietf_filters %}
<h1>Set Telechat Date for {{ doc.name }}</h1>
<form class="telechat-date" action="" method="POST">
<table>
{{ form.as_table }}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=doc.name %}">Back</a>
<input type="submit" value="Save"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -0,0 +1,3 @@
{% autoescape off %}{{ text }}
ID Tracker URL: {{ url }}
{% endautoescape %}

View file

@ -5,7 +5,7 @@
{% block content %}
<h1>Undefer ballot for {{ doc }}</h1>
<form action="" method="POST">
<form class="undefer" action="" method="POST">
<p>Undefer the ballot for {{ doc.file_tag }}?</p>
<p>The ballot will then be on the IESG agenda of {{ telechat_date }}.</p>

View file

@ -34,7 +34,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% comment %}
Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{% endcomment %}
{% load ietf_filters %}{% load ballot_icon %}
{% load ietf_filters %}{% load ballot_icon_redesign %}
{% if title2_first %}{% if title1_first %}<h2>{{ title1 }}</h2>
{% if title1|startswith:"2." %}
@ -69,6 +69,7 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
review and should therefore not be published without IETF review
and IESG approval.<br/>
<br />
<b>(Old instructions)</b>
The document shepherd must propose one of these responses in the
document write-up in the Data Tracker, and the document shepherd
may supply text for an IESG Note in the write-up. The Area
@ -76,6 +77,13 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
proposed by the document shepherd and agreement that the IESG
should request inclusion of the IESG Note.<br/>
<br />
<b>(New instructions)</b>
The document shepherd must propose one of these responses in the
conflict-review document, and the document shepherd may supply text
for an IESG Note in that document. The Area Director ballot positions
indicate consensus with the response proposed by the document shepherd
and agreement that the IESG should request inclusion of the IESG Note.<br/>
<br />
Other matters may be recorded in comments, and the comments will
be passed on to the RFC Editor as community review of the document.
</blockquote>
@ -89,26 +97,27 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
<table class="agenda-doc">
<tbody>
<tr><td>
{% if doc.obj.rfc_flag %}
<a href="/doc/rfc{{doc.obj.document.rfc_number}}">{{doc.obj.document.filename}}</a>
<a href="http://www.rfc-editor.org/rfc/rfc{{doc.obj.document.rfc_number}}/">[txt]</a>
<a href="{% url doc_view name=doc.obj.canonical_name %}">{{doc.obj.canonical_name}}</a>
{% with doc.obj.rfc_number as rfc_number %}
{% if rfc_number %}
<a href="http://www.rfc-editor.org/rfc/rfc{{rfc_number}}/">[txt]</a>
{% else %}
<a href="/doc/{{doc.obj.document.filename}}/">{{doc.obj.document.displayname|safe}}</a>
<a href="http://www.ietf.org/id/{{doc.obj.document.filename}}-{{doc.obj.document.revision}}.txt">[txt]</a>
<a href="http://www.ietf.org/id/{{doc.obj.name}}-{{doc.obj.rev}}.txt">[txt]</a>
{% endif %}
{% endwith %}
<br/>{{ doc.obj.document.title|escape }} ({{ doc.obj.document.intended_status }})
<br/>{{ doc.obj.title|escape }} ({{ doc.obj.intended_std_level }})
{% if doc.obj.note %}
<br/>Note: {{ doc.obj.note|unescape }}
{% endif %}
{% if doc.obj.draft.ipr.all %}
{% if doc.obj.ipr %}
<br />
<h5>IPR:</h5>
<ul>
{% for ipr in doc.obj.draft.ipr.all %}
{% for ipr in doc.obj.ipr %}
{% ifequal ipr.ipr.status 1 %}
<li><a href="/ipr/{{ ipr.ipr.ipr_id }}/">{{ ipr.ipr.title|escape }}</a></li>
{% endifequal %}
@ -117,10 +126,12 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{% endif %}
<br/>Token: {{ doc.obj.token_name|escape }} ({{doc.obj.area_acronym}} area)
{% if doc.obj.ballot.defer %}
<br/>Was deferred by {{doc.obj.ballot.defer_by}} on {{doc.obj.ballot.defer_date}}
<br/>Token: {{ doc.obj.ad }} ({{doc.obj.area_acronym}} area)
{% with doc.obj.active_defer_event as defer %}
{% if defer %}
<br/>Was deferred by {{defer.by}} on {{defer.time|date:"Y-m-d"}}
{% endif %}
{% endwith %}
</td><td style="padding-left:20px; width: 50px;">
{% ballot_icon doc.obj %}
</td></tr></tbody></table>

View file

@ -38,12 +38,12 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{% if title2_first %}{% if title1_first %}{{ title1 }}{% endif %}
{{ title2 }}
{% endif %}{{ title3 }}
{% for doc in section_docs %}
o {{doc.obj.document.filename}}{% if not doc.obj.rfc_flag %}-{{doc.obj.document.revision}}{% endif %}
{% filter wordwrap:"68"|indent|indent %}{{ doc.obj.document.title }} ({{ doc.obj.document.intended_status }}){% endfilter %}
{% for doc in section_docs %}{% with doc.obj.rfc_number as rfc_number %}
o {{doc.obj.canonical_name}}{% if not rfc_number %}-{{doc.obj.rev}}{% endif %}{% endwith %}
{% filter wordwrap:"68"|indent|indent %}{{ doc.obj.title }} ({{ doc.obj.intended_std_level }}){% endfilter %}
{% if doc.obj.note %}{# note: note is not escaped #} {% filter wordwrap:"68"|indent|indent %}Note: {{ doc.obj.note|striptags }}{% endfilter %}
{% endif %} Token: {{ doc.obj.token_name }}
{% if doc.obj.ballot.defer %} Was deferred by {{doc.obj.ballot.defer_by}} on {{doc.obj.ballot.defer_date}}{% endif %}
{% endif %} Token: {{ doc.obj.ad }}
{% with doc.obj.active_defer_event as defer %}{% if defer %} Was deferred by {{defer.by}} on {{defer.time|date:"Y-m-d"}}{% endif %}{% endwith %}
{% empty %}
NONE
{% endfor %}

View file

@ -31,4 +31,4 @@ 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 %}# Fields: telechat date, filename (draft-foo-bar or rfc1234), intended status, rfc editor submission flag (0=no, 1=yes), area acronym, AD name, version{% for doc in docs %}
{{ doc.telechat_date }} {{ doc.document.filename }} {{ doc.document.intended_status }} {% if doc.stream in special_stream_list %}1{% else %}0{% endif %} {{doc.document.revision}}{% endfor %}
{{ doc.telechat_date }} {{ doc.name }} {{ doc.intended_std_level }} {% if doc.stream.slug in special_stream_list %}1{% else %}0{% endif %} {{doc.area_acronym|lower}} {% with doc.ad as ad %}{% if ad %}{{ ad.plain_name }}{% else %}None Assigned{% endif %}{% endwith %} {{doc.rev}}{% endfor %}

View file

@ -0,0 +1,143 @@
{% extends "base.html" %}
{% comment %}
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
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_redesign %}
{% 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 %}
<link rel="alternate" type="application/atom+xml" href="/feed/iesg-agenda/" />
{% endblock %}
{% block content %}
<h1>Documents on Future IESG Telechat Agendas</h1>
<form action="" method="POST">
{% if user|in_group:"Secretariat" %}
<div class="secretariat-actions">
<button id="clear-all-on-schedule">Set all to not on agenda</button>
<input type="submit" value="Save"/>
</div>
{% endif %}
<table class="ietf-table ietf-doctable agenda_docs">
{% for t in telechats %}
{% if not forloop.first %}
<tr class="header"><td colspan="6">&nbsp;</td></tr>
{% endif %}
<tr class="header telechat_date"><td colspan="6">IESG telechat {{t.date}}</td></tr>
{% if forloop.first %}
<tr class="header"><td colspan="6"><a href="/iesg/agenda/">Full IESG Agenda</a></td></tr>
{% endif %}
<tr class="header"><td colspan="6"><a href="/iesg/agenda/telechat-{{t.date|date:"Y"}}-{{t.date|date:"m"}}-{{t.date|date:"d"}}-docs.tgz">Download Documents</a></td></tr>
<tr class="header"><td colspan="6">2. Protocol Actions</td></tr>
<tr class="header"><td colspan="6">2.1 WG Submissions</td></tr>
{% for doc in t.docs.s211 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s212 %}<tr class="header"><td colspan="6">2.1.2 Returning Item</td></tr>{% endif %}
{% for doc in t.docs.s212 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s213 %}<tr class="header"><td colspan="6">2.1.3 For Action</td></tr>{% endif %}
{% for doc in t.docs.s213 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
<tr class="header"><td colspan="6">2.2 Individual Submissions</td></tr>
{% for doc in t.docs.s221 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s222 %}<tr class="header"><td colspan="6">2.2.2 Returning Item</td></tr>{% endif %}
{% for doc in t.docs.s222 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s223 %}<tr class="header"><td colspan="6">2.2.3 For Action</td></tr>{% endif %}
{% for doc in t.docs.s223 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
<tr class="header"><td colspan="6">3. Document Actions</td></tr>
<tr class="header"><td colspan="6">3.1 WG Submissions</td></tr>
{% for doc in t.docs.s311 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s312 %}<tr class="header"><td colspan="6">3.1.2 Returning Item</td></tr>{% endif %}
{% for doc in t.docs.s312 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s313 %}<tr class="header"><td colspan="6">3.1.3 For Action</td></tr>{% endif %}
{% for doc in t.docs.s313 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
<tr class="header"><td colspan="6">3.2 Individual Submissions Via AD</td></tr>
{% for doc in t.docs.s321 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s322 %}<tr class="header"><td colspan="6">3.2.2 Returning Item</td></tr>{% endif %}
{% for doc in t.docs.s322 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s323 %}<tr class="header"><td colspan="6">3.2.3 For Action</td></tr>{% endif %}
{% for doc in t.docs.s323 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
<tr class="header"><td colspan="6">3.3 IRTF and Independent Submission Stream Documents</td></tr>
{% for doc in t.docs.s331 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s332 %}<tr class="header"><td colspan="6">3.3.2 Returning Item</td></tr>{% endif %}
{% for doc in t.docs.s332 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s333 %}<tr class="header"><td colspan="6">3.3.3 For Action</td></tr>{% endif %}
{% for doc in t.docs.s333 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% endfor %}
</table>
</form>
{% endblock content %}
{% block content_end %}
<script type="text/javascript" src="/js/agenda-documents.js"></script>
{% endblock %}

View file

@ -54,7 +54,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% endif %}
{% endif %}
</td>
<td class="title">{{ doc.title }}<span class="doc_pages"> ({{doc.pages}} pp)</span></td>
<td class="title">{{ doc.title }}
{% with doc.pages as pagecount %}{% if pagecount %}<span class="doc_pages">({{doc.pages}} pp)</span>{% endif %}{% endwith %}
</td>
{% include "iesg/agenda_documents_row_status.html" %}
{% include "idrfc/ipr_column_with_label.html" %}
<td class="ad">{{ doc.ad_name|default:"" }}</td>

View file

@ -0,0 +1,63 @@
{% comment %}
Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
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_redesign %}
{% load ietf_filters %}
<tr
{% if user|in_group:"Area_Director" %}
{% if doc|my_position:user|equal:"Discuss" %}style="background:#ffa0a0;"{% endif %}
{% if doc|my_position:user|equal:"Abstain" %}style="background:#ffff00;"{% endif %}
{% if doc|my_position:user|equal:"Yes" or doc|my_position:user|slugify|equal:"no-objection" %}style="background:#a0ffa0;"{% endif %}
{% if doc|my_position:user|equal:"Recuse" %}style="background:#c0c0c0;"{% endif %}
{% endif %}
>
<td class="doc">
<div>{{ doc.displayname_with_link|safe }}</div>
{% with doc.active_defer_event as defer %}{% if defer %}
<div><b title="deferred by {{ defer.by }}">(deferred on {{ defer.time|date:"Y-m-d" }})</b></div>
{% endif %}{% endwith %}
{% if user|in_group:"Secretariat" %}
<div class="reschedule"><label>Reschedule: {{ doc.reschedule_form.telechat_date }}</label></div>
{% if doc.reschedule_form.show_clear %}
<div class="clear-returning-item"><label>{{ doc.reschedule_form.clear_returning_item }} Clear returning item</label></div>
{% endif %}
{% endif %}
</td>
<td class="title">{{ doc.title }}
{% with doc.pages as pagecount %}{% if pagecount %}<span class="doc_pages">({{doc.pages}} pp)</span>{% endif %}{% endwith %}
</td>
{% include "iesg/agenda_documents_row_status_redesign.html" %}
{% include "idrfc/ipr_column_with_label.html" %}
<td class="ad">{{ doc.ad.name|default:"" }}</td>
</tr>

View file

@ -0,0 +1,6 @@
{% extends "doc/status_columns.html" %}
{% block extra_status %}
{% if doc.type.slug == 'draft' %}
<br/>Intended status: {{doc.intended_std_level}}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,10 @@
{% if doc %}
<div class="stream_state">
<div class="stream_state_more" style="float: left; margin: 1px 0.5em 0 0;"><a href="{% url stream_history doc.name %}" class="show_stream_info" title="Stream information for {{ doc.name }}" style="text-decoration: none; color:transparent; margin: 0; padding: 0; border: 0;"><img src="/images/plus.png" style="margin: 0; padding: 0; border:0;"><img></a></div>
{% if stream %}
{% if state %}{{ state.name }}{% else %}{{ stream }}{% endif %}
{% else %}
No stream assigned
{% endif %}
</div>
{% endif %}

View file

@ -32,7 +32,7 @@ def make_test_data():
state_id="active",
type_id="ietf",
parent=None)
for x in ["irtf", "iab", "ise"]:
for x in ["irtf", "iab", "ise", "iesg"]:
Group.objects.create(
name=x.upper(),
acronym=x,
@ -178,6 +178,25 @@ def make_test_data():
group=areahist,
person=p,
email=email)
# stream chairs
for stream in ['ietf','irtf','iab','iesg']:
u = User.objects.create( username= ("%schair"%stream) )
p = Person.objects.create(
name="%s chair"%stream,
ascii="%s chair"%stream,
user = u,
)
chairmail = Email.objects.create(
address="%schair@ietf.org"%stream,
person = p,
)
Role.objects.create(
name_id = "chair",
group = Group.objects.get(acronym=stream),
person = p,
email = chairmail,
)
# group chair
u = User.objects.create(username="marschairman")
@ -313,5 +332,20 @@ def make_test_data():
break_area="Lounge",
reg_area="Lobby",
)
# an independent submission before review
doc = Document.objects.create(name='draft-imaginary-independent-submission',type_id='draft')
DocAlias.objects.create( name='draft-imaginary-independent-submission',document=doc)
# an irtf submission mid review
doc = Document.objects.create( name='draft-imaginary-irtf-submission',type_id='draft')
docalias = DocAlias.objects.create(name='draft-imaginary-irtf-submission',document=doc)
doc.stream = StreamName.objects.get(slug='irtf')
doc.save()
crdoc = Document.objects.create(name='conflict-review-imaginary-irtf-submission',type_id='conflrev',rev='00',notify="fsm@ietf.org")
DocAlias.objects.create( name='conflict-review-imaginary-irtf-submission',document=crdoc)
crdoc.set_state(State.objects.get(name='Needs Shepherd',type__slug='conflrev'))
crdoc.save()
crdoc.relateddocument_set.create(target=docalias,relationship_id='conflrev')
return draft

View file

@ -43,7 +43,7 @@ from ietf.idrfc.idrfc_wrapper import IdRfcWrapper
from ietf.ipr.models import IprDetail
from ietf.group.models import Group
from ietf.doc.models import State
from ietf.doc.utils import get_chartering_type, augment_with_telechat_date
from ietf.doc.utils import get_chartering_type
def fill_in_charter_info(wg, include_drafts=False):
@ -126,7 +126,6 @@ def chartering_wgs(request):
charter_states = State.objects.filter(type="charter").exclude(slug__in=("approved", "notrev"))
groups = Group.objects.filter(type="wg", charter__states__in=charter_states).select_related("state", "charter")
augment_with_telechat_date([g.charter for g in groups])
for g in groups:
g.chartering_type = get_chartering_type(g.charter)

BIN
static/images/pencil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -46,8 +46,9 @@ function showBallot(draftName, editPositionUrl) {
document.getElementById("ietf-extras").appendChild(el);
var buttons = [{text:"Close", handler:handleClose, isDefault:true}];
if (("Area_Director" in IETF.user_groups) ||
("Secretariat" in IETF.user_groups)) {
// if (("Area_Director" in IETF.user_groups) ||
// ("Secretariat" in IETF.user_groups)) {
if ("Area_Director" in IETF.user_groups) {
buttons.unshift({text:"Edit Position", handler:handleEditPosition});
}
var kl = [new YAHOO.util.KeyListener(document, {keys:27}, handleClose)]