Liasion Manager:

* Patch from Yaco to avoid resetting the From field when changing other items on the form
* Gave the secretariat the ability to find/approve any unapproved liaisons
* Changed all the email code to use ietf.mail.utils (and removed the fake-mail concept)

Charter documents and the Agenda pages:
* Added charter documents to iesg/agenda and iesg/agenda/documents
* Synced the ordering of drafts on iesg/agenda and iesg/agenda/documents
* Allow setting a responsible AD for charter documents
* Changed the UI of the charter page to use editlink for changing attributes and buttons for actions (to align with drafts and conflict-reviews)

Moderator package:
* Refactor: Simplified access to the current BallotDocEvent from a Document
* Added functions to BallotDocEvents? to faciliate access to BallotPositionDocEvents?, both for all positions, and current AD postions.
* Updated the moderator package to use the Documents from _agenda_data.
* Added a filter to assist with rendering the moderator package.
* Fixed a bug where different functions in idrfc/views_ballot were using log_state_changed expecting different implementations (a cleanup task should reconcile the _three_ implementations in the codebase of that function).

Cleanup from codesprint:
* Removed some duplication between doc/util and doc/models by moving things into doc/models
* Do not show non-empty discuss text when the ballot position is not blocking
* Added a migration to update non-blocking ballot positions that have non-empty discuss text


DEPLOYMENT NOTES
Please be aware that migration step will take a few minutes to complete.

Fixes bug 865
 - Legacy-Id: 4772
This commit is contained in:
Robert Sparks 2012-08-21 04:46:01 +00:00
commit e5c3a5adc5
33 changed files with 745 additions and 280 deletions

View file

@ -0,0 +1,372 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
from person.models import Person
from doc.models import BallotPositionDocEvent
import sys
class Migration(DataMigration):
def forwards(self, orm):
system_user = Person.objects.get(name="(System)")
change_count=0
progress=0
print "starting"
docset = set([e.doc for e in BallotPositionDocEvent.objects.exclude(pos__blocking=True).exclude(discuss="")])
print "updating non-blocking positions with non-empty discusses on",len(docset),"documents"
for doc in docset:
ballot_positions = BallotPositionDocEvent.objects.filter(doc=doc,type="changed_ballot_position")
for ad in set([e.ad for e in ballot_positions]):
e = ballot_positions.filter(ad=ad).order_by("-time")[0]
if e.discuss and not e.pos.blocking:
# time and discuss are intentionally not part of what update gets initialized with
update = BallotPositionDocEvent(doc=e.doc,type=e.type,by=e.by,ballot=e.ballot,ad=e.ad,pos=e.pos,discuss_time=e.discuss_time,comment=e.comment,comment_time=e.comment_time)
update.by=system_user
update.desc="post-migration administrative database adjustment to the "+e.pos.name+" position for "+e.ad.plain_name()
update.save()
change_count += 1
progress +=1
if not (progress % 100):
print str(progress)+",",
sys.stdout.flush()
print
print "Updated",change_count,"positions"
def backwards(self, orm):
pass
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

@ -259,29 +259,21 @@ class Document(DocumentInfo):
return self.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
return None
def active_ballot(self):
ballot = self.latest_event(BallotDocEvent, type="created_ballot")
e = self.latest_event(BallotDocEvent, ballot_type__slug=ballot.ballot_type.slug) if ballot else None
open = e and not e.type == "closed_ballot"
return ballot.ballot_type if open else None
# This, and several other ballot related functions 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 ballot_open(self, ballot_type_slug):
e = self.latest_event(BallotDocEvent, ballot_type__slug=ballot_type_slug)
return e and not e.type == "closed_ballot"
def active_ballot_positions(self):
"""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"))
res = {}
def active_ballot(self):
"""Returns the most recently created ballot if it isn't closed."""
ballot = self.latest_event(BallotDocEvent, type="created_ballot")
positions = BallotPositionDocEvent.objects.filter(doc=self, type="changed_ballot_position", ad__in=active_ads, ballot=ballot).select_related('ad', 'pos').order_by("-time", "-id")
for pos in positions:
if pos.ad not in res:
res[pos.ad] = pos
for ad in active_ads:
if ad not in res:
res[ad] = None
return res.values()
open = self.ballot_open(ballot.ballot_type.slug) if ballot else False
return ballot if open else None
def most_recent_ietflc(self):
"""Returns the most recent IETF LastCallDocEvent for this document"""
return self.latest_event(LastCallDocEvent,type="sent_last_call")
def displayname_with_link(self):
return '<a href="%s">%s-%s</a>' % (self.get_absolute_url(), self.name , self.rev)
@ -526,6 +518,59 @@ class BallotType(models.Model):
class BallotDocEvent(DocEvent):
ballot_type = models.ForeignKey(BallotType)
def active_ad_positions(self):
"""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"))
res = {}
if self.doc.latest_event(BallotDocEvent, type="created_ballot") == self:
positions = BallotPositionDocEvent.objects.filter(type="changed_ballot_position",ad__in=active_ads, ballot=self).select_related('ad', 'pos').order_by("-time", "-id")
for pos in positions:
if pos.ad not in res:
res[pos.ad] = pos
for ad in active_ads:
if ad not in res:
res[ad] = None
return res
def all_positions(self):
"""Return array holding the current and past positions per AD"""
positions = []
seen = {}
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active").distinct())
for e in BallotPositionDocEvent.objects.filter(type="changed_ballot_position", ballot=self).select_related('ad', 'pos').order_by("-time", '-id'):
if e.ad not in seen:
e.old_ad = e.ad not in active_ads
e.old_positions = []
positions.append(e)
seen[e.ad] = e
else:
latest = seen[e.ad]
if latest.old_positions:
prev = latest.old_positions[-1]
else:
prev = latest.pos
if e.pos != prev:
latest.old_positions.append(e.pos)
# add any missing ADs through fake No Record events
norecord = BallotPositionName.objects.get(slug="norecord")
for ad in active_ads:
if ad not in seen:
e = BallotPositionDocEvent(type="changed_ballot_position", doc=self.doc, ad=ad)
e.pos = norecord
e.old_ad = False
e.old_positions = []
positions.append(e)
positions.sort(key=lambda p: (p.old_ad, p.ad.last_name()))
return positions
class BallotPositionDocEvent(DocEvent):
ballot = models.ForeignKey(BallotDocEvent, null=True, default=None) # default=None is a temporary migration period fix, should be removed when charter branch is live
ad = models.ForeignKey(Person)

View file

@ -550,10 +550,9 @@ class InternetDraft(Document):
def active_positions(self):
"""Returns a list of dicts, with AD and Position tuples"""
from ietf.person.proxy import IESGLogin as IESGLoginProxy
from ietf.doc.utils import active_ballot_positions
res = []
for ad, pos in active_ballot_positions(self).iteritems():
for ad, pos in self.active_ballot().active_ad_positions().iteritems():
res.append(dict(ad=IESGLoginProxy().from_object(ad), pos=Position().from_object(pos) if pos else None))
res.sort(key=lambda x: x["ad"].last_name)

View file

@ -13,7 +13,7 @@ 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.utils import create_ballot_if_not_open
from ietf.doc.views_conflict_review import default_approval_text
from ietf.doc.models import Document,DocEvent,NewRevisionDocEvent,BallotPositionDocEvent,TelechatDocEvent,DocAlias,State
@ -105,7 +105,7 @@ class ConflictReviewTestCase(django.test.TestCase):
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))
self.assertFalse(review_doc.active_ballot())
# successful change to IESG Evaluation
iesgeval_pk = str(State.objects.get(slug='iesgeval',type__slug='conflrev').pk)
@ -114,7 +114,7 @@ class ConflictReviewTestCase(django.test.TestCase):
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.assertTrue(review_doc.active_ballot())
self.assertEquals(review_doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position").pos_id,'yes')
@ -234,7 +234,7 @@ class ConflictReviewTestCase(django.test.TestCase):
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.assertFalse(doc.ballot_open("conflrev"))
self.assertEquals(len(outbox), messages_before + 1)
self.assertTrue('Results of IETF-conflict review' in outbox[-1]['Subject'])

View file

@ -36,34 +36,6 @@ 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"))
res = {}
if not ballot:
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ad__in=active_ads, ballot=ballot).select_related('ad', 'pos').order_by("-time", "-id")
for pos in positions:
if pos.ad not in res:
res[pos.ad] = pos
for ad in active_ads:
if ad not in res:
res[ad] = None
return res
def needed_ballot_positions(doc, active_positions):
'''Returns text answering the question "what does this document
need to pass?". The return value is only useful if the document
@ -102,12 +74,8 @@ def needed_ballot_positions(doc, active_positions):
return " ".join(answer)
def ballot_open(doc, ballot_type_slug):
e = doc.latest_event(BallotDocEvent, ballot_type__slug=ballot_type_slug)
return e and not e.type == "closed_ballot"
def create_ballot_if_not_open(doc, by, ballot_type_slug):
if not ballot_open(doc, ballot_type_slug):
if not doc.ballot_open(ballot_type_slug):
e = BallotDocEvent(type="created_ballot", by=by, doc=doc)
e.ballot_type = BallotType.objects.get(doc_type=doc.type, slug=ballot_type_slug)
e.desc = u'Created "%s" ballot' % e.ballot_type.name
@ -115,7 +83,7 @@ def create_ballot_if_not_open(doc, by, ballot_type_slug):
def close_open_ballots(doc, by):
for t in BallotType.objects.filter(doc_type=doc.type_id):
if ballot_open(doc, t.slug):
if doc.ballot_open(t.slug):
e = BallotDocEvent(type="closed_ballot", doc=doc, by=by)
e.ballot_type = t
e.desc = 'Closed "%s" ballot' % t.name

View file

@ -37,7 +37,6 @@ 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
from ietf.doc.models import BallotDocEvent
register = template.Library()
@ -83,7 +82,7 @@ def render_ballot_icon(user, doc):
else:
return (1, pos.pos.order)
positions = list(active_ballot_positions(doc, ballot).items())
positions = list(doc.active_ballot().active_ad_positions().items())
positions.sort(key=sort_key)
cm = ""

View file

@ -38,7 +38,6 @@ 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
@ -90,7 +89,7 @@ def render_ballot_icon(user, doc):
else:
return (1, pos.pos.order)
positions = list(active_ballot_positions(doc, ballot).items())
positions = list(doc.active_ballot().active_ad_positions().items())
positions.sort(key=sort_key)
cm = ""
@ -144,7 +143,7 @@ def my_position(doc, user):
return None
if not in_group(user, "Area_Director"):
return None
ballot = active_ballot(doc)
ballot = doc.active_ballot()
pos = "No Record"
if ballot:
changed_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad__name=user_name, ballot=ballot)

View file

@ -35,7 +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
from ietf.doc.utils import log_state_changed as docutil_log_state_changed
from ietf.idrfc.utils import log_state_changed as idrfcutil_log_state_changed
BALLOT_CHOICES = (("yes", "Yes"),
("noobj", "No Objection"),
@ -431,7 +432,7 @@ def defer_ballotREDESIGN(request, name):
elif doc.type_id == 'conflrev':
doc.set_state(State.objects.get(type='conflrev', slug='defer'))
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
e = docutil_log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
doc.time = e.time
doc.save()
@ -481,7 +482,7 @@ def undefer_ballotREDESIGN(request, name):
elif doc.type_id == 'conflrev':
doc.set_state(State.objects.get(type='conflrev',slug='iesgeval'))
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
e = docutil_log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
doc.time = e.time
doc.save()
@ -557,7 +558,7 @@ def lastcalltext(request, name):
if "send_last_call_request" in request.POST:
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.LAST_CALL_REQUESTED), None)
change = log_state_changed(request, doc, login)
change = idrfcutil_log_state_changed(request, doc, login)
email_owner(request, doc, doc.idinternal.job_owner, login, change)
request_last_call(request, doc)
@ -646,7 +647,7 @@ def lastcalltextREDESIGN(request, name):
if prev_tag:
doc.tags.remove(prev_tag)
e = log_state_changed(request, doc, login, prev, prev_tag)
e = idrfcutil_log_state_changed(request, doc, login, prev, prev_tag)
doc.time = e.time
doc.save()
@ -1093,7 +1094,7 @@ def approve_ballotREDESIGN(request, name):
change_description = e.desc + " and state has been changed to %s" % doc.get_state("draft-iesg").name
e = log_state_changed(request, doc, login, prev, prev_tag)
e = idrfcutil_log_state_changed(request, doc, login, prev, prev_tag)
doc.time = e.time
doc.save()
@ -1153,7 +1154,7 @@ def make_last_call(request, name):
doc.idinternal.event_date = date.today()
doc.idinternal.save()
log_state_changed(request, doc, login)
idrfcutil_log_state_changed(request, doc, login)
doc.lc_sent_date = form.cleaned_data['last_call_sent_date']
doc.lc_expiration_date = form.cleaned_data['last_call_expiration_date']
@ -1216,7 +1217,7 @@ def make_last_callREDESIGN(request, name):
if prev_tag:
doc.tags.remove(prev_tag)
e = log_state_changed(request, doc, login, prev, prev_tag)
e = idrfcutil_log_state_changed(request, doc, login, prev, prev_tag)
doc.time = e.time
doc.save()

View file

@ -143,7 +143,7 @@ def document_main(request, name, rev=None):
ballot_summary = None
if doc.get_state_slug() in ("intrev", "iesgrev"):
ballot_summary = needed_ballot_positions(doc, active_ballot_positions(doc).values())
ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values())
return render_to_response("idrfc/document_charter.html",
dict(doc=doc,
@ -171,7 +171,7 @@ def document_main(request, name, rev=None):
ballot_summary = None
if doc.get_state_slug() in ("iesgeval"):
ballot_summary = needed_ballot_positions(doc, active_ballot_positions(doc).values())
ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values())
return render_to_response("idrfc/document_conflict_review.html",
dict(doc=doc,
@ -292,36 +292,7 @@ def document_ballot_content(request, doc, ballot_id, editable=True):
deferred = doc.active_defer_event()
# collect positions
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active").distinct())
positions = []
seen = {}
for e in BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ballot=ballot).select_related('ad', 'pos').order_by("-time", '-id'):
if e.ad not in seen:
e.old_ad = e.ad not in active_ads
e.old_positions = []
positions.append(e)
seen[e.ad] = e
else:
latest = seen[e.ad]
if latest.old_positions:
prev = latest.old_positions[-1]
else:
prev = latest.pos.name
if e.pos.name != prev:
latest.old_positions.append(e.pos.name)
# add any missing ADs through fake No Record events
norecord = BallotPositionName.objects.get(slug="norecord")
for ad in active_ads:
if ad not in seen:
e = BallotPositionDocEvent(type="changed_ballot_position", doc=doc, ad=ad)
e.pos = norecord
e.old_ad = False
e.old_positions = []
positions.append(e)
positions = doc.active_ballot().all_positions() if doc.active_ballot() else []
# put into position groups
position_groups = []

View file

@ -146,6 +146,15 @@ def square_brackets(value):
else:
return "[ ]"
@register.filter(name='bracketpos')
def bracketpos(pos,posslug):
if pos.pos.slug==posslug:
return "[ X ]"
elif posslug in [x.slug for x in pos.old_positions]:
return "[ . ]"
else:
return "[ ]"
@register.filter(name='fill')
def fill(text, width):
"""Wraps each paragraph in text (a string) so every line

View file

@ -207,6 +207,9 @@ def agenda_docs(date, next_agenda):
m.balloting_started = e.time if e else datetime.datetime.min
docmatches.append(m)
# Be careful to keep this the same as what's used in agenda_documents
docmatches.sort(key=lambda d: d.balloting_started)
res = dict(("s%s%s%s" % (i, j, k), []) for i in range(2, 5) for j in range (1, 4) for k in range(1, 4))
for id in docmatches:
@ -232,7 +235,8 @@ def agenda_wg_actions(date):
section_key = "s" + get_wg_section(c.group)
if section_key not in res:
res[section_key] = []
res[section_key].append({'obj': c.group})
# Cleanup - Older view code wants obj, newer wants doc. Older code should be moved forward
res[section_key].append({'obj': c.group, 'doc': c})
return res
def agenda_management_issues(date):
@ -370,6 +374,7 @@ def _agenda_data(request, date=None):
def agenda(request, date=None):
data = _agenda_data(request, date)
data['private'] = 'private' in request.REQUEST
data['settings'] = settings
return render_to_response("iesg/agenda.html", data, context_instance=RequestContext(request))
def agenda_txt(request):

View file

@ -2,7 +2,7 @@
# coding: latin-1
from types import ModuleType
import urls, models, views, forms, accounts, admin, utils, widgets, mail, decorators, sitemaps, feeds
import urls, models, views, forms, accounts, admin, utils, widgets, decorators, sitemaps, feeds
# These people will be sent a stack trace if there's an uncaught exception in
# code any of the modules imported above:

View file

@ -1,26 +0,0 @@
from django.core.mail import EmailMessage
# FIXME: this is not using the ietf mail stuff, which it really should
# - this code should be DELETED
class IETFEmailMessage(EmailMessage):
def __init__(self, *args, **kwargs):
cc = kwargs.pop('cc', [])
if cc:
assert isinstance(cc, (list, tuple)), '"cc" argument must be a list or tuple'
self.cc = list(cc)
else:
self.cc = []
super(IETFEmailMessage, self).__init__(*args, **kwargs)
def message(self):
msg = super(IETFEmailMessage, self).message()
if self.cc:
msg['Cc'] = ', '.join(self.cc)
if self.bcc:
msg['Bcc'] = ', '.join(self.bcc)
return msg
def recipients(self):
return self.to + self.cc + self.bcc

View file

@ -26,17 +26,6 @@ def send_liaison_by_email(request, liaison, fake=False):
url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.pk)),
referenced_url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None,
))
if fake:
# rather than this fake stuff, it's probably better to start a
# debug SMTP server as explained in the Django docs
from ietf.liaisons.mail import IETFEmailMessage
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
cc = cc,
bcc = bcc,
body = body)
return mail
send_mail_text(request, to_email, from_email, subject, body, cc=", ".join(cc), bcc=", ".join(bcc))
@ -56,12 +45,6 @@ def notify_pending_by_email(request, liaison, fake):
url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)),
referenced_url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None,
))
if fake:
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
body = body)
return mail
send_mail_text(request, to_email, from_email, subject, body)
def send_sdo_reminder(sdo):

View file

@ -6,7 +6,8 @@ from django.template.loader import render_to_string
from django.core.urlresolvers import reverse as urlreverse
from ietf.liaisons.models import LiaisonDetail
from ietf.liaisons.mail import IETFEmailMessage
#from ietf.liaisons.mail import IETFEmailMessage
from ietf.utils.mail import send_mail_text
PREVIOUS_DAYS = {
@ -44,17 +45,20 @@ class Command(BaseCommand):
'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)),
'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None,
})
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
cc=cc,
bcc=bcc,
body=body)
if not settings.DEBUG:
mail.send()
print 'Liaison %05s#: Deadline reminder Sent!' % liaison.pk
else:
print 'Liaison %05s#: Deadline reminder Not Sent because in DEBUG mode!' % liaison.pk
send_mail_text(context=None,to=to_email,frm=from_email,cc=cc,subject=subject,bcc=bcc,txt=body)
print 'Liaison %05s#: Deadline reminder Sent!' % liaison.pk
#mail = IETFEmailMessage(subject=subject,
# to=to_email,
# from_email=from_email,
# cc=cc,
# bcc=bcc,
# body=body)
#if not settings.DEBUG:
# mail.send()
# print 'Liaison %05s#: Deadline reminder Sent!' % liaison.pk
#else:
# print 'Liaison %05s#: Deadline reminder Not Sent because in DEBUG mode!' % liaison.pk
def handle(self, *args, **options):
today = datetime.date.today()

View file

@ -7,7 +7,8 @@ from django.template.loader import render_to_string
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse as urlreverse
from ietf.idtracker.models import Acronym, PersonOrOrgInfo, Area, IESGLogin
from ietf.liaisons.mail import IETFEmailMessage
#from ietf.liaisons.mail import IETFEmailMessage
from ietf.utils.mail import send_mail_text
from ietf.ietfauth.models import LegacyLiaisonUser
from ietf.utils.admin import admin_link
@ -147,13 +148,14 @@ class LiaisonDetail(models.Model):
'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=self.pk)),
'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.related_to.pk)) if self.related_to else None,
})
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
body = body)
if not fake:
mail.send()
return mail
send_mail_text(context=None, to=to_email, frm=from_email, subject=subject, txt = body)
#mail = IETFEmailMessage(subject=subject,
# to=to_email,
# from_email=from_email,
# body = body)
#if not fake:
# mail.send()
#return mail
def send_by_email(self, fake=False):
if self.is_pending():
@ -172,15 +174,16 @@ class LiaisonDetail(models.Model):
'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.pk)),
'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.related_to.pk)) if self.related_to else None,
})
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
cc = cc,
bcc = bcc,
body = body)
if not fake:
mail.send()
return mail
send_mail_text(context=None,to=to_email,frm=from_email,subject=subject,txt=body,cc=cc,bcc=bcc)
#mail = IETFEmailMessage(subject=subject,
# to=to_email,
# from_email=from_email,
# cc = cc,
# bcc = bcc,
# body = body)
#if not fake:
# mail.send()
#return mail
def is_pending(self):
return bool(self.approval and not self.approval.approved)

View file

@ -33,10 +33,7 @@ def add_liaison(request, liaison=None):
if form.is_valid():
liaison = form.save()
if request.POST.get('send', None):
if not settings.DEBUG:
liaison.send_by_email()
else:
return _fake_email_view(request, liaison)
liaison.send_by_email()
return HttpResponseRedirect(reverse('liaison_list'))
else:
form = liaison_form_factory(request, liaison=liaison)
@ -89,14 +86,6 @@ def get_info(request):
return HttpResponse(json_result, mimetype='text/javascript')
def _fake_email_view(request, liaison):
mail = liaison.send_by_email(fake=True)
return render_to_response('liaisons/liaison_mail_detail.html',
{'mail': mail,
'message': mail.message(),
'liaison': liaison},
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
def approvable_liaison_statements(group_codes):
# this is a bit complicated because IETFHM encodes the
@ -160,12 +149,15 @@ def liaison_list(request):
@can_submit_liaison
def liaison_approval_list(request):
person = get_person_for_user(request.user)
approval_codes = IETFHM.get_all_can_approve_codes(person)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
to_approve = approvable_liaison_statements(approval_codes).order_by("-submitted")
if is_secretariat(request.user):
to_approve = LiaisonDetail.objects.filter(approved=None).order_by("-submitted")
else:
to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date")
person = get_person_for_user(request.user)
approval_codes = IETFHM.get_all_can_approve_codes(person)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
to_approve = approvable_liaison_statements(approval_codes).order_by("-submitted")
else:
to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date")
return object_list(request, to_approve,
allow_empty=True,
@ -175,12 +167,15 @@ def liaison_approval_list(request):
@can_submit_liaison
def liaison_approval_detail(request, object_id):
person = get_person_for_user(request.user)
approval_codes = IETFHM.get_all_can_approve_codes(person)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
to_approve = approvable_liaison_statements(approval_codes).order_by("-submitted")
if is_secretariat(request.user):
to_approve = LiaisonDetail.objects.filter(approved=None).order_by("-submitted")
else:
to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date")
person = get_person_for_user(request.user)
approval_codes = IETFHM.get_all_can_approve_codes(person)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
to_approve = approvable_liaison_statements(approval_codes).order_by("-submitted")
else:
to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date")
if request.method=='POST' and request.POST.get('do_approval', False):
try:
@ -197,10 +192,7 @@ def liaison_approval_detail(request, object_id):
else:
approval.approved=True
approval.save()
if not settings.DEBUG:
liaison.send_by_email()
else:
return _fake_email_view(request, liaison)
liaison.send_by_email()
except LiaisonDetail.DoesNotExist:
pass
return HttpResponseRedirect(reverse('liaison_list'))

View file

@ -17,7 +17,6 @@ from ietf.ietfauth.decorators import has_role
from ietf.doc.models import *
from ietf.person.models import Person, Alias, Email
from ietf.doc.utils import active_ballot_positions
from ietf.message.models import Message
# Some useful states
@ -262,7 +261,7 @@ def announce_new_versionREDESIGN(request, submission, draft, state_change_msg):
if draft.ad:
to_email.append(draft.ad.role_email("ad").address)
for ad, pos in active_ballot_positions(draft).iteritems():
for ad, pos in draft.active_ballot().active_ad_positions().iteritems():
if pos and pos.pos_id == "discuss":
to_email.append(ad.role_email("ad").address)

View file

@ -57,7 +57,7 @@
{% for p in text_positions %}
<h2 id="{{ p.ad.plain_name|slugify }}" class="ad-ballot-comment">{% if p.old_ad %}[{% endif %}{{ p.ad.plain_name }}{% if p.old_ad %}]{% endif %}</h2>
{% if p.discuss %}
{% if p.pos.blocking and p.discuss %}
<p><b>{{ p.pos.name }} ({{ p.discuss_time|date:"Y-m-d" }})</b> <img src="/images/comment.png" width="14" height="12" alt=""/></p>
<pre>{{ p.discuss|wrap_text:80|escape }}</pre>
{% endif %}

View file

@ -1,4 +1,6 @@
{% extends "base.html" %}
{% extends "idrfc/doc_main.html" %}
{% comment extends "base.html" %}
{% endcomment %}
{% load ietf_filters %}
@ -37,37 +39,25 @@
<td><a href="/doc/help/state/charter/">Charter State</a>:</td>
<td>
<div>
<a title="{{ doc.get_state.desc }}"{% if not snapshot and user|has_role:"Area Director,Secretariat" %} href="{% url charter_change_state name=doc.name %}"{% endif %}>{{ doc.get_state.name }}</a>
{% if chartering == "initial" %}(Initial Chartering){% endif %}
{% if chartering == "rechartering" %}(Rechartering){% endif %}
<a title="{{ doc.get_state.desc }}"
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
class="editlink" href="{% url charter_change_state name=doc.name %}"
{% endif %}>
{{ doc.get_state.name }}
</a>
{% if chartering == "initial" %} - (Initial Chartering){% endif %}
{% if chartering == "rechartering" %} - (Rechartering){% endif %}
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
{% if chartering %}
- <a href="{% url charter_startstop_process name=doc.name option='abandon' %}">Abandon effort</a>
{% if request.user|has_role:"Secretariat" %}
- <a href="{% url charter_approve name=doc.name %}">Approve charter</a>
{% endif %}
{% else %}
{% if group.state_id == "proposed" or group.state_id == "bof" %}
- <a href="{% url charter_submit name=doc.name option='initcharter' %}">Start chartering</a>
{% else %}
- <a href="{% url charter_submit name=doc.name option='recharter' %}">Recharter</a>
{% endif %}
{% endif %}
{% endif %}
</div>
{% if not snapshot and chartering %}
<div class="telechat">
{% if not telechat %}Not on agenda of IESG telechat{% else %}On agenda of {{ telechat.telechat_date|date:"Y-m-d" }} IESG telechat{% endif %}
{% if user|has_role:"Area Director,Secretariat" %}
- <a href="{% url charter_telechat_date name=doc.name %}">Change</a>
{% endif %}
<a
{% if user|has_role:"Area Director,Secretariat" %}
class="editlink" href="{% url charter_telechat_date name=doc.name %}"
{% endif %}>
{% if not telechat %}Not on agenda of IESG telechat{% else %}On agenda of {{ telechat.telechat_date|date:"Y-m-d" }} IESG telechat{% endif %}
</a>
</div>
{% if ballot_summary %}
@ -88,14 +78,20 @@
</tr>
{% endif %}
<tr>
<td>Responsible AD:</td>
<td><a {% if request.user|has_role:"Area Director,Secretariat" %}class="editlink" href="{% url charter_edit_ad name=doc.name %}"{% endif %}>{{ doc.ad|default:"none" }}</a> </td>
</tr>
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
<tr>
<td>Send notices to:</td>
<td>{{ doc.notify|default:"none" }}
{% if user|has_role:"Area Director,Secretariat" %}
- <a href="{% url charter_edit_notify name=doc.name %}">Change</a>
{% endif %}
<td><a {% if user|has_role:"Area Director,Secretariat" %}
class="editlink" href="{% url charter_edit_notify name=doc.name %}"
{% endif %}>
{{ doc.notify|default:"none" }}
</a>
</td>
</tr>
@ -107,6 +103,26 @@
<div class="actions">
<a href="/feed/group-changes/{{ group.acronym }}/">Atom feed</a>
</div>
<div>
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
{% if chartering %}
<span id="charter_abandon_effort_button" class="yui-button yui-link-button" style="margin-left:2px;margin-top:2px">{% url charter_startstop_process name=doc.name option='abandon' as abandon_url %}{% if abandon_url %}<span class="first-child"><a href="{{abandon_url}}">Abandon Effort</a></span>{% endif %}</span>
{% if request.user|has_role:"Secretariat" %}
<span id="charter_approve_button" class="yui-button yui-link-button" style="margin-left:2px;margin-top:2px">{% url charter_approve name=doc.name as approve_url %}{% if approve_url %}<span class="first-child"><a href="{{approve_url}}">Approve Charter</a></span>{% endif %}</span>
{% endif %}
{% else %}
{% if group.state_id == "proposed" or group.state_id == "bof" %}
<span id="charter_start_button" class="yui-button yui-link-button" style="margin-left:2px;margin-top:2px">{% url charter_submit name=doc.name option='initcharter' as start_url %}{% if start_url %}<span class="first-child"><a href="{{start_url}}">Start Chartering</a></span>{% endif %}</span>
{% else %}
<span id="charter_recharter_button" class="yui-button yui-link-button" style="margin-left:2px;margin-top:2px">{% url charter_submit name=doc.name option='recharter' as recharter_url %}{% if recharter_url %}<span class="first-child"><a href="{{recharter_url}}">Recharter</a></span>{% endif %}</span>
{% endif %}
{% endif %}
{% endif %}
</div>
</div>
<p>Other versions: <a href="{{ txt_url }}">plain text</a></p>

View file

@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #}
.agenda blockquote { margin-left: 30px; width: 70ex; font-style:italic;}
table.agenda-doc { margin-left: 30px; margin-top:0.5em; margin-bottom: 0.5em; width: 95%; }
table.agenda-doc > tbody > tr { vertical-align:top; }
div.agenda-wg { margin-left: 30px; margin-top:0.5em; margin-bottom: 0.5em; width: 95%; }
{% endblock morecss %}
{% block pagehead %}

View file

@ -131,6 +131,22 @@ font-size:80%; font-style:italic;
{% 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%}
<tr class="header"><td colspan="6">4. Working Group Actions</td></tr>
{% if t.docs.s411 or t.docs.s412%}<tr class="header"><td colspan="6">4.1 WG Creation</td></tr>{% endif %}
{% if t.docs.s411 %}<tr class="header"><td colspan="6">4.1.1 Proposed for IETF Review</td></tr>{% endif %}
{% for doc in t.docs.s411 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s412 %}<tr class="header"><td colspan="6">4.1.2 Proposed for Approval</td></tr>{% endif %}
{% for doc in t.docs.s412 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s421 or t.docs.s422 %}<tr class="header"><td colspan="6">4.2 WG Rechartering</td></tr>{% endif %}
{% if t.docs.s421 %}<tr class="header"><td colspan="6">4.2.1 Under Evaluation for IETF Review</td></tr>{% endif %}
{% for doc in t.docs.s421 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% if t.docs.s422 %}<tr class="header"><td colspan="6">4.2.2 Proposed for Approval</td></tr>{% endif %}
{% for doc in t.docs.s422 %}{% include "iesg/agenda_documents_row_redesign.html" %}{%endfor%}
{% endfor %}
</table>

View file

@ -39,6 +39,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<tr
{% if user|in_group:"Area_Director" %}
{% if doc|my_position:user|equal:"Discuss" %}style="background:#ffa0a0;"{% endif %}
{% if doc|my_position:user|equal:"Block" %}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 %}

View file

@ -31,30 +31,24 @@ 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 %}
{% if title2_first %}{% if title1_first %}<h2>{{ title1 }}</h2>
{% endif %}
<h3>{{ title2 }}</h3>
{% endif %}<h4>{{ title3 }}</h4>
{% for wg in section_wgs %}
{% if forloop.first %}
<table>
<tr><th>Area</th><th>Date</th><th></th></tr>
{% endif %}
<tr>
<td>{{ wg.obj.parent.acronym|upper }}</td>
<td>{{ wg.obj.time|date:"M d"}}</td>
<td>
<a href="{{ wg.obj.txt_link }}">
{{ wg.obj.name|escape }} ({{wg.obj.acronym}})
</a>
</td>
</tr>
{% if forloop.last %}
</table>
{% endif %}
<div class="agenda-wg">
<span width="30%" style="float:right;">
{% ballot_icon wg.doc %}
</span>
<span width="30%">
<div> <a href="{{ wg.doc.get_absolute_url }}">{{ wg.doc.name}}-({{wg.doc.rev}})</a> <a href="{{ settings.CHARTER_TXT_URL }}{{ wg.doc.filename_with_rev }}">[txt]</a> </div>
<div>{{ wg.doc.group.name|escape }} ({{wg.doc.group.acronym}})</div>
<div>Area: {{ wg.doc.group.parent.acronym|upper }} ({{ wg.doc.ad|default:"Sponsoring AD not assigned" }})</div>
</span>
</div>
{% empty %}
<p>NONE</p>

View file

@ -40,19 +40,21 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{{ title2 }}<br>
{{ title3 }} ({{ forloop.counter }} of {{ section_docs|length }})</h3>
<p><b>{{doc.obj.document.filename}}{% if not doc.obj.rfc_flag %}-{{doc.obj.document.revision}}{% endif %}</b><br>
<i>({{ doc.obj.document.title|escape }})</i><br>
<b>Intended status: {{ doc.obj.document.intended_status }}<br>
Token: {{ doc.obj.token_name|escape }}<br>
Last call ends: {{ doc.obj.document.lc_expiration_date|default:"(none)" }}</b></p>
<p><b>{{doc.obj.name}}-{{doc.obj.rev}}</b><br>
<i>({{ doc.obj.title|escape }})</i><br>
<b>Intended status: {{ doc.obj.intended_std_level }}<br>
Token: {{ doc.obj.ad.plain_name|escape }}<br>
{% if doc.obj.type.slug == "draft" %}
Last call ends: {{ doc.obj.most_recent_ietflc.expires.date|default:"(none)" }}
{% if doc.obj.most_recent_ietflc.expires.date|timesince_days < 3 %}!!!{% endif %}
{% endif %}</b></p>
{% if doc.obj.ballot.active %}
{% filter linebreaks_crlf %}<small><pre>
Yes No-Objection Discuss Abstain
{% for curpos in doc.obj.ballot.active_positions %}{{ curpos.ad|ljust:"20" }} {{ curpos.pos.yes|bracket }} {{ curpos.pos.noobj|bracket }} {{ curpos.pos.discuss|bracket }} {{ curpos.pos.abstain_ind|bracket }}
{% if doc.obj.active_ballot %}
<small><pre>
Yes No-Objection Discuss Abstain Recuse
{% for pos in doc.obj.active_ballot.all_positions %}{% if pos.old_ad %}{{pos.ad.plain_name|bracket|ljust:"22"}}{%else%}{{pos.ad.plain_name|ljust:"22"}}{%endif%} {{pos|bracketpos:"yes"}} {{pos|bracketpos:"noobj"}} {{pos|bracketpos:"discuss"}} {{pos|bracketpos:"abstain"}} {{pos|bracketpos:"recuse"}}
{% endfor %}
{% for position in doc.obj.ballot.positions.all|dictsort:"ad.last_name" %}{% if not position.ad.is_current_ad %}{{ position.ad|ljust:"20" }} {{ position.yes|bracket }} {{ position.noobj|bracket }} {{ position.discuss|bracket }} {{ position.abstain_ind|bracket }}
{% endif %}{% endfor %}</pre></small>{% endfilter %}
</pre></small>
{% if title1|startswith:"2." %}
<p>____ open positions<br>
@ -67,16 +69,16 @@ Last call ends: {{ doc.obj.document.lc_expiration_date|default:"(none)" }}</b></
{% endif %}
{% if title2|startswith:"3.1" or title2|startswith:"3.2" %}
<p>Does anyone have an[y further] objection to this document being published as an {{ doc.obj.document.intended_status }} RFC?</p>
<p>Does anyone have an[y further] objection to this document being published as an {{ doc.obj.intended_std_level }} RFC?</p>
{% endif %}
{% if title3|startswith:"3.3.1" or title3|startswith:"3.3.2" %}
<p>Does anyone have an objection to the RFC Editor publishing this document as an {{ doc.obj.document.intended_status }} RFC?</p>
<p>Does anyone have an objection to the RFC Editor publishing this document as an {{ doc.obj.intended_std_level }} RFC?</p>
{% endif %}
{% if title3|startswith:"3.3.3" %}
<p>Who will do the review of this document?</p>
{% endif %}
<p>Current State: {{ doc.obj.cur_state }}, sub state: {{ doc.obj.cur_sub_state|default:"(none)" }}<br>
<p>Current State: {{ doc.obj.friendly_state }}<br>
Next State:<br>
Sub State: </p>

View file

@ -85,7 +85,7 @@ teleconference. The Secretariat will post them in the public archive.</p>
{{ minutes }}
</pre>{% endfilter %}
<h3>1. Administrivia<br>1.4 List of Remaining Action Items from Last Telehat</h3>
<h3>1. Administrivia<br>1.4 List of Remaining Action Items from Last Telechat</h3>
{% filter linebreaks_crlf %}<pre>
{{ action_items }}

View file

@ -65,7 +65,7 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{% if doc.obj.active_ballot %}
<br/><b>Discusses/comments</b> <a href="http://datatracker.ietf.org/idtracker/ballot/{{doc.obj.canonical_name}}/">[ballot]</a>:
<ul>
{% for p in doc.obj.active_ballot_positions %}
{% for p in doc.obj.active_ballot.active_ad_positions.values %}
{% if p.pos %}
{% if p.discuss %}
<li>

View file

@ -40,7 +40,7 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{% if doc.obj.active_ballot %}
<ul>
{% for p in doc.obj.active_ballot_positions %}
{% for p in doc.obj.active_ballot.active_ad_positions.values %}
{% if p.pos and p.discuss %}
<li>
<a name="{{doc.obj.name}}+{{p.ad|slugify}}+discuss">{{ p.ad }}: Discuss [{{ p.discuss_time }}]</a>:

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block morecss %}
.warning {
font-weight: bold;
color: #a00;
}
{% endblock %}
{% block title %}
Change the responsible AD for {{ charter.canonical_name }}-{{ charter.rev }}
{% endblock %}
{% block content %}
<h1>Change the responsible AD for {{ charter.canonical_name }}-{{ charter.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=charter.canonical_name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -132,6 +132,29 @@ class EditCharterTestCase(django.test.TestCase):
charter = Document.objects.get(name=charter.name)
self.assertEquals(charter.notify, "someone@example.com, someoneelse@example.com")
def test_edit_ad(self):
make_test_data()
charter = Group.objects.get(acronym="mars").charter
url = urlreverse('charter_edit_ad', kwargs=dict(name=charter.name))
login_testing_unauthorized(self, "secretary", 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)
# post
self.assertTrue(not charter.ad)
ad2 = Person.objects.get(name='Ad No2')
r = self.client.post(url,dict(ad=str(ad2.pk)))
self.assertEquals(r.status_code, 302)
charter = Document.objects.get(name=charter.name)
self.assertEquals(charter.ad, ad2)
def test_submit_charter(self):
make_test_data()
@ -224,7 +247,7 @@ class CharterApproveBallotTestCase(django.test.TestCase):
charter = Document.objects.get(name=charter.name)
self.assertEquals(charter.get_state_slug(), "approved")
self.assertTrue(not ballot_open(charter, "approve"))
self.assertTrue(not charter.ballot_open("approve"))
self.assertEquals(charter.rev, "01")
self.assertTrue(os.path.exists(os.path.join(self.charter_dir, "charter-ietf-%s-%s.txt" % (group.acronym, charter.rev))))

View file

@ -7,6 +7,7 @@ urlpatterns = patterns('',
url(r'^(?P<option>initcharter|recharter|abandon)/$', "ietf.wgcharter.views.change_state", name='charter_startstop_process'),
url(r'^telechat/$', "ietf.wgcharter.views.telechat_date", name='charter_telechat_date'),
url(r'^notify/$', "ietf.wgcharter.views.edit_notify", name='charter_edit_notify'),
url(r'^ad/$', "ietf.wgcharter.views.edit_ad", name='charter_edit_ad'),
url(r'^(?P<ann>action|review)/$', "ietf.wgcharter.views.announcement_text", name="charter_edit_announcement"),
url(r'^ballotwriteupnotes/$', "ietf.wgcharter.views.ballot_writeupnotes"),
url(r'^approve/$', "ietf.wgcharter.views.approve", name='charter_approve'),

View file

@ -267,6 +267,53 @@ def edit_notify(request, name):
login=login),
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="Responsible 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 responsible Area Director for this charter."""
charter = get_object_or_404(Document, type="charter", name=name)
login = request.user.get_profile()
if request.method == 'POST':
form = AdForm(request.POST)
if form.is_valid():
new_ad = form.cleaned_data['ad']
if new_ad != charter.ad:
save_document_in_history(charter)
e = DocEvent(doc=charter, by=login)
e.desc = "Responsible AD changed to %s" % new_ad.plain_name()
if charter.ad:
e.desc += " from %s" % charter.ad.plain_name()
e.type = "changed_document"
e.save()
charter.ad = new_ad
charter.time = e.time
charter.save()
return HttpResponseRedirect(reverse('doc_view', kwargs={'name': charter.name}))
else:
init = { "ad" : charter.ad_id }
form = AdForm(initial=init)
return render_to_response('wgcharter/change_ad.html',
{'form': form,
'charter': charter,
},
context_instance = RequestContext(request))
class UploadForm(forms.Form):
content = forms.CharField(widget=forms.Textarea, label="Charter text", help_text="Edit the charter text", required=False)

View file

@ -209,7 +209,7 @@
updateReplyTo();
};
var updateInfo = function(first_time) {
var updateInfo = function(first_time, sender) {
var entity = organization;
var to_entity = from;
if (!entity.is('select') || !to_entity.is('select')) {
@ -232,7 +232,9 @@
render_mails_into(poc, response.poc, true);
toggleApproval(response.needs_approval);
checkPostOnly(response.post_only);
userSelect(response.full_list);
if (sender == 'from') {
userSelect(response.full_list);
}
}
}
});
@ -330,7 +332,7 @@
var checkFrom = function(first_time) {
var reduce_options = form.find('.reducedToOptions');
if (!reduce_options.length) {
updateInfo(first_time);
updateInfo(first_time, 'from');
return;
}
var to_select = organization;
@ -351,7 +353,7 @@
to_select.find('optgroup').show();
to_select.find('option').show();
}
updateInfo(first_time);
updateInfo(first_time, 'from');
};
var checkSubmissionDate = function() {
@ -366,7 +368,7 @@
};
var initTriggers = function() {
organization.change(function() {updateInfo(false);});
organization.change(function() {updateInfo(false, 'to');});
organization.change(checkOtherSDO);
from.change(function() {checkFrom(false);});
reply.keyup(updateFrom);