From 75b94bd114589b858470be389be7e3acb36e5cf4 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Tue, 9 Sep 2014 21:56:33 +0000
Subject: [PATCH 01/18] sketch that uses a through table, preserving most, but
 not all, of the current interface  - Legacy-Id: 8302

---
 .../migrations/0019_version_materials.py      | 340 ++++++++++++++++++
 .../0020_snapshot_material_revisions.py       | 338 +++++++++++++++++
 ietf/meeting/models.py                        |  15 +-
 ietf/meeting/test_data.py                     |   8 +-
 ietf/secr/proceedings/views.py                |   6 +-
 5 files changed, 698 insertions(+), 9 deletions(-)
 create mode 100644 ietf/meeting/migrations/0019_version_materials.py
 create mode 100644 ietf/meeting/migrations/0020_snapshot_material_revisions.py

diff --git a/ietf/meeting/migrations/0019_version_materials.py b/ietf/meeting/migrations/0019_version_materials.py
new file mode 100644
index 000000000..c53ef7c8c
--- /dev/null
+++ b/ietf/meeting/migrations/0019_version_materials.py
@@ -0,0 +1,340 @@
+# -*- coding: utf-8 -*-
+from south.v2 import SchemaMigration
+from south.db import db
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        db.add_column('meeting_session_materials', 'rev', 
+                      self.gf('django.db.models.fields.CharField')(max_length=16, blank=True)
+                     )
+
+
+    def backwards(self, orm):
+        db.delete_column('meeting_session_materials', 'rev' )
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        u'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'}),
+            u'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'})
+        },
+        u'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': u"orm['person.Person']"}),
+            'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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'}),
+            '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': u"orm['person.Person']"}),
+            'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"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': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
+        },
+        u'doc.documentauthor': {
+            'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+        },
+        u'doc.state': {
+            'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
+            'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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'})
+        },
+        u'group.group': {
+            'Meta': {'object_name': 'Group'},
+            'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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': u"orm['doc.Document']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
+            'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'meeting.constraint': {
+            'Meta': {'object_name': 'Constraint'},
+            'day': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
+            'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.ConstraintName']"}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
+            'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'constraint_source_set'", 'to': u"orm['group.Group']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'constraint_target_set'", 'null': 'True', 'to': u"orm['group.Group']"})
+        },
+        u'meeting.meeting': {
+            'Meta': {'ordering': "['-date']", 'object_name': 'Meeting'},
+            'agenda': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': u"orm['meeting.Schedule']"}),
+            'agenda_note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'break_area': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}),
+            'date': ('django.db.models.fields.DateField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'number': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+            'reg_area': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'time_zone': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.MeetingTypeName']"}),
+            'venue_addr': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'venue_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
+        },
+        u'meeting.resourceassociation': {
+            'Meta': {'object_name': 'ResourceAssociation'},
+            'desc': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'icon': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.RoomResourceName']"})
+        },
+        u'meeting.room': {
+            'Meta': {'object_name': 'Room'},
+            'capacity': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'resources': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['meeting.ResourceAssociation']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'meeting.schedule': {
+            'Meta': {'object_name': 'Schedule'},
+            'badness': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'meeting.scheduledsession': {
+            'Meta': {'ordering': "['timeslot__time', 'session__group__parent__name', 'session__group__acronym', 'session__name']", 'object_name': 'ScheduledSession'},
+            'badness': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
+            'extendedfrom': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['meeting.ScheduledSession']", 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'pinned': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'schedule': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'assignments'", 'to': u"orm['meeting.Schedule']"}),
+            'session': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['meeting.Session']", 'null': 'True'}),
+            'timeslot': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.TimeSlot']"})
+        },
+        u'meeting.session': {
+            'Meta': {'object_name': 'Session'},
+            'agenda_note': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'attendees': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'materials': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'through': u"orm['meeting.VersionedMaterials']", 'blank': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'requested': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'requested_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+            'requested_duration': ('ietf.meeting.timedeltafield.TimedeltaField', [], {'default': '0'}),
+            'resources': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['meeting.ResourceAssociation']", 'symmetrical': 'False'}),
+            'scheduled': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.SessionStatusName']"})
+        },
+        u'meeting.timeslot': {
+            'Meta': {'object_name': 'TimeSlot'},
+            'duration': ('ietf.meeting.timedeltafield.TimedeltaField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Room']", 'null': 'True', 'blank': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'sessions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'slots'", 'to': u"orm['meeting.Session']", 'through': u"orm['meeting.ScheduledSession']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True'}),
+            'show_location': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.TimeSlotTypeName']"})
+        },
+        u'meeting.versionedmaterials': {
+            'Meta': {'object_name': 'VersionedMaterials', 'db_table': "'meeting_session_materials'"},
+            'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
+            'session': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Session']"})
+        },
+        u'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'}),
+            'penalty': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'name.roomresourcename': {
+            'Meta': {'ordering': "['order']", 'object_name': 'RoomResourceName'},
+            '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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': u"orm['person.Person']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        u'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'}),
+            u'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': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['meeting']
diff --git a/ietf/meeting/migrations/0020_snapshot_material_revisions.py b/ietf/meeting/migrations/0020_snapshot_material_revisions.py
new file mode 100644
index 000000000..088e6a8ac
--- /dev/null
+++ b/ietf/meeting/migrations/0020_snapshot_material_revisions.py
@@ -0,0 +1,338 @@
+# -*- coding: utf-8 -*-
+from south.v2 import DataMigration
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        for vm in orm['meeting.VersionedMaterials'].objects.all():
+            vm.rev = vm.document.rev
+            vm.save()
+
+    def backwards(self, orm):
+        pass
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        u'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'}),
+            u'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'})
+        },
+        u'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': u"orm['person.Person']"}),
+            'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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'}),
+            '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': u"orm['person.Person']"}),
+            'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"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': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
+        },
+        u'doc.documentauthor': {
+            'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+        },
+        u'doc.state': {
+            'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
+            'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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'})
+        },
+        u'group.group': {
+            'Meta': {'object_name': 'Group'},
+            'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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': u"orm['doc.Document']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
+            'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'meeting.constraint': {
+            'Meta': {'object_name': 'Constraint'},
+            'day': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
+            'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.ConstraintName']"}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
+            'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'constraint_source_set'", 'to': u"orm['group.Group']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'constraint_target_set'", 'null': 'True', 'to': u"orm['group.Group']"})
+        },
+        u'meeting.meeting': {
+            'Meta': {'ordering': "['-date']", 'object_name': 'Meeting'},
+            'agenda': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': u"orm['meeting.Schedule']"}),
+            'agenda_note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'break_area': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}),
+            'date': ('django.db.models.fields.DateField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'number': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+            'reg_area': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'time_zone': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.MeetingTypeName']"}),
+            'venue_addr': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'venue_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
+        },
+        u'meeting.resourceassociation': {
+            'Meta': {'object_name': 'ResourceAssociation'},
+            'desc': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'icon': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.RoomResourceName']"})
+        },
+        u'meeting.room': {
+            'Meta': {'object_name': 'Room'},
+            'capacity': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'resources': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['meeting.ResourceAssociation']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'meeting.schedule': {
+            'Meta': {'object_name': 'Schedule'},
+            'badness': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'meeting.scheduledsession': {
+            'Meta': {'ordering': "['timeslot__time', 'session__group__parent__name', 'session__group__acronym', 'session__name']", 'object_name': 'ScheduledSession'},
+            'badness': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
+            'extendedfrom': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['meeting.ScheduledSession']", 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'pinned': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'schedule': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'assignments'", 'to': u"orm['meeting.Schedule']"}),
+            'session': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['meeting.Session']", 'null': 'True'}),
+            'timeslot': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.TimeSlot']"})
+        },
+        u'meeting.session': {
+            'Meta': {'object_name': 'Session'},
+            'agenda_note': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'attendees': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'materials': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'through': u"orm['meeting.VersionedMaterials']", 'blank': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'requested': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'requested_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+            'requested_duration': ('ietf.meeting.timedeltafield.TimedeltaField', [], {'default': '0'}),
+            'resources': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['meeting.ResourceAssociation']", 'symmetrical': 'False'}),
+            'scheduled': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
+            'status': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.SessionStatusName']"})
+        },
+        u'meeting.timeslot': {
+            'Meta': {'object_name': 'TimeSlot'},
+            'duration': ('ietf.meeting.timedeltafield.TimedeltaField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Room']", 'null': 'True', 'blank': 'True'}),
+            'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'sessions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'slots'", 'to': u"orm['meeting.Session']", 'through': u"orm['meeting.ScheduledSession']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True'}),
+            'show_location': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.TimeSlotTypeName']"})
+        },
+        u'meeting.versionedmaterials': {
+            'Meta': {'object_name': 'VersionedMaterials', 'db_table': "'meeting_session_materials'"},
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
+            'session': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Session']"})
+        },
+        u'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'}),
+            'penalty': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'name.roomresourcename': {
+            'Meta': {'ordering': "['order']", 'object_name': 'RoomResourceName'},
+            '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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': u"orm['person.Person']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        u'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'}),
+            u'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': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['meeting']
+    symmetrical = True
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index 02300c04b..bb62596a0 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -776,6 +776,18 @@ class Constraint(models.Model):
         ct1['meeting_href'] = urljoin(host_scheme, self.meeting.json_url())
         return ct1
 
+
+class VersionedMaterials(models.Model):
+    session = models.ForeignKey('Session')
+    document = models.ForeignKey(Document)
+    rev = models.CharField(verbose_name="revision", max_length=16, blank=True)
+
+    class Meta:
+        db_table = 'meeting_session_materials'
+
+    def __unicode__(self):
+        return u"%s -> %s-%s" % (self.session, self.document.name, self.rev)
+
 constraint_cache_uses = 0
 constraint_cache_initials = 0
 
@@ -799,7 +811,7 @@ class Session(models.Model):
     scheduled = models.DateTimeField(null=True, blank=True)
     modified = models.DateTimeField(default=datetime.datetime.now)
 
-    materials = models.ManyToManyField(Document, blank=True)
+    materials = models.ManyToManyField(Document, through=VersionedMaterials, blank=True)
     resources = models.ManyToManyField(ResourceAssociation)
 
     unique_constraints_dict = None
@@ -1171,4 +1183,3 @@ class Session(models.Model):
         if self.badness_test(1):
             self.badness_log(1, "badgroup: %s badness = %u\n" % (self.group.acronym, badness))
         return badness
-
diff --git a/ietf/meeting/test_data.py b/ietf/meeting/test_data.py
index 17225f046..c568cbe21 100644
--- a/ietf/meeting/test_data.py
+++ b/ietf/meeting/test_data.py
@@ -2,7 +2,7 @@ import datetime
 
 from ietf.doc.models import Document, State
 from ietf.group.models import Group
-from ietf.meeting.models import Meeting, Room, TimeSlot, Session, Schedule, ScheduledSession, ResourceAssociation
+from ietf.meeting.models import Meeting, Room, TimeSlot, Session, Schedule, ScheduledSession, ResourceAssociation, VersionedMaterials
 from ietf.name.models import RoomResourceName
 from ietf.person.models import Person
 from ietf.utils.test_data import make_test_data
@@ -46,15 +46,15 @@ def make_meeting_test_data():
 
     doc = Document.objects.create(name='agenda-mars-ietf-42', type_id='agenda', title="Agenda", external_url="agenda-mars")
     doc.set_state(State.objects.get(type=doc.type_id, slug="active"))
-    mars_session.materials.add(doc)
+    mars_session.versionedmaterials_set.add(VersionedMaterials(session=mars_session,document=doc,rev=doc.rev))
 
     doc = Document.objects.create(name='minutes-mars-ietf-42', type_id='minutes', title="Minutes", external_url="minutes-mars")
     doc.set_state(State.objects.get(type=doc.type_id, slug="active"))
-    mars_session.materials.add(doc)
+    mars_session.versionedmaterials_set.add(VersionedMaterials(session=mars_session,document=doc,rev=doc.rev))
 
     doc = Document.objects.create(name='slides-mars-ietf-42', type_id='slides', title="Slideshow", external_url="slides-mars")
     doc.set_state(State.objects.get(type=doc.type_id, slug="active"))
-    mars_session.materials.add(doc)
+    mars_session.versionedmaterials_set.add(VersionedMaterials(session=mars_session,document=doc,rev=doc.rev))
     
     return meeting
 
diff --git a/ietf/secr/proceedings/views.py b/ietf/secr/proceedings/views.py
index f4a42430b..58636c00d 100644
--- a/ietf/secr/proceedings/views.py
+++ b/ietf/secr/proceedings/views.py
@@ -23,7 +23,7 @@ from ietf.secr.utils.meeting import get_upload_root, get_material, get_timeslot
 from ietf.doc.models import Document, DocAlias, DocEvent, State, NewRevisionDocEvent
 from ietf.group.models import Group
 from ietf.ietfauth.utils import has_role
-from ietf.meeting.models import Meeting, Session, TimeSlot, ScheduledSession
+from ietf.meeting.models import Meeting, Session, TimeSlot, ScheduledSession, VersionedMaterials
 from ietf.secr.proceedings.forms import EditSlideForm, InterimMeetingForm, ReplaceSlideForm, UnifiedUploadForm
 from ietf.secr.proceedings.proc_utils import ( gen_acknowledgement, gen_agenda, gen_areas, gen_attendees,
     gen_group_pages, gen_index, gen_irtf, gen_overview, gen_plenaries, gen_progress, gen_research,
@@ -842,9 +842,9 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
             # for the current meeting (until tools support different materials for diff sessions)
             if sessions:
                 for s in sessions:
-                    s.materials.add(doc)
+                    s.materials.versionedmaterials_set.add(VersionedMaterials(session=s,document=doc,rev=doc.rev))
             else:
-                session.materials.add(doc)
+                session.materials.versionedmaterials_set.add(VersionedMaterials(session=session,document=doc,rev=doc.rev))
 
             # create NewRevisionDocEvent instead of uploaded, per Ole
             NewRevisionDocEvent.objects.create(type='new_revision',

From c3f460cbe902da19cf2f9219f2a15399938f5131 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Fri, 19 Sep 2014 21:33:30 +0000
Subject: [PATCH 02/18] Refactored history snippets for general use Moved from
 VersionedMaterials to SessionPresentation for the through table name Added
 the abstract and most recent presentation to the documents on the group
 materials page Working snapshot before building historic edu-team documents 
 - Legacy-Id: 8349

---
 ietf/doc/templatetags/ietf_filters.py                 |  6 +++++-
 ietf/group/info.py                                    |  5 ++++-
 ietf/meeting/migrations/0019_version_materials.py     |  6 +++---
 .../migrations/0020_snapshot_material_revisions.py    |  8 ++++----
 ietf/meeting/models.py                                |  4 ++--
 ietf/meeting/test_data.py                             |  8 ++++----
 ietf/secr/proceedings/views.py                        |  6 +++---
 ietf/templates/doc/document_history.html              |  2 +-
 ietf/templates/group/history.html                     |  2 +-
 ietf/templates/group/materials.html                   | 11 ++++++++++-
 static/css/base2.css                                  |  2 +-
 static/js/{history.js => snippet.js}                  |  2 +-
 12 files changed, 39 insertions(+), 23 deletions(-)
 rename static/js/{history.js => snippet.js} (61%)

diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py
index 2d03d55f4..fd9bb2df7 100644
--- a/ietf/doc/templatetags/ietf_filters.py
+++ b/ietf/doc/templatetags/ietf_filters.py
@@ -447,7 +447,11 @@ def format_history_text(text):
     if text.startswith("This was part of a ballot set with:"):
         full = urlize_ietf_docs(full)
 
-    full = mark_safe(keep_spacing(linebreaksbr(urlize(sanitize_html(full)))))
+    return format_snippet(full)
+
+@register.filter
+def format_snippet(text): 
+    full = mark_safe(keep_spacing(linebreaksbr(urlize(sanitize_html(text)))))
     snippet = truncatewords_html(full, 25)
     if snippet != full:
         return mark_safe(u'<div class="snippet">%s<span class="show-all">[show all]</span></div><div style="display:none" class="full">%s</div>' % (snippet, full))
diff --git a/ietf/group/info.py b/ietf/group/info.py
index cff40b125..4beacd9e2 100644
--- a/ietf/group/info.py
+++ b/ietf/group/info.py
@@ -267,7 +267,8 @@ def concluded_groups(request):
                   dict(group_types=group_types))
 
 def get_group_materials(group):
-    return Document.objects.filter(group=group, type__in=group.features.material_types, session=None).exclude(states__slug="deleted")
+#   return Document.objects.filter(group=group, type__in=group.features.material_types, session=None).exclude(states__slug="deleted")
+    return Document.objects.filter(group=group, type__in=group.features.material_types).exclude(states__slug="deleted")
 
 def construct_group_menu_context(request, group, selected, group_type, others):
     """Return context with info for the group menu filled in."""
@@ -450,6 +451,8 @@ def materials(request, acronym, group_type=None):
         if d.type not in doc_types:
             doc_types[d.type] = []
         doc_types[d.type].append(d)
+        # This needs to be better - probably looking at ScheduledSession, and perhaps ignoring future Sessions
+        d.last_presented = d.sessionpresentation_set.order_by('-session__meeting__date').first()
 
     return render(request, 'group/materials.html',
                   construct_group_menu_context(request, group, "materials", group_type, {
diff --git a/ietf/meeting/migrations/0019_version_materials.py b/ietf/meeting/migrations/0019_version_materials.py
index c53ef7c8c..f699c6847 100644
--- a/ietf/meeting/migrations/0019_version_materials.py
+++ b/ietf/meeting/migrations/0019_version_materials.py
@@ -188,7 +188,7 @@ class Migration(SchemaMigration):
             'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
             'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
             u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'materials': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'through': u"orm['meeting.VersionedMaterials']", 'blank': 'True'}),
+            'materials': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'through': u"orm['meeting.SessionPresentation']", 'blank': 'True'}),
             'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
             'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
             'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
@@ -213,8 +213,8 @@ class Migration(SchemaMigration):
             'time': ('django.db.models.fields.DateTimeField', [], {}),
             'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.TimeSlotTypeName']"})
         },
-        u'meeting.versionedmaterials': {
-            'Meta': {'object_name': 'VersionedMaterials', 'db_table': "'meeting_session_materials'"},
+        u'meeting.sessionpresentation': {
+            'Meta': {'object_name': 'SessionPresentation', 'db_table': "'meeting_session_materials'"},
             'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
             u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
             'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
diff --git a/ietf/meeting/migrations/0020_snapshot_material_revisions.py b/ietf/meeting/migrations/0020_snapshot_material_revisions.py
index 088e6a8ac..ee26a3902 100644
--- a/ietf/meeting/migrations/0020_snapshot_material_revisions.py
+++ b/ietf/meeting/migrations/0020_snapshot_material_revisions.py
@@ -4,7 +4,7 @@ from south.v2 import DataMigration
 class Migration(DataMigration):
 
     def forwards(self, orm):
-        for vm in orm['meeting.VersionedMaterials'].objects.all():
+        for vm in orm['meeting.SessionPresentation'].objects.all():
             vm.rev = vm.document.rev
             vm.save()
 
@@ -185,7 +185,7 @@ class Migration(DataMigration):
             'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
             'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
             u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'materials': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'through': u"orm['meeting.VersionedMaterials']", 'blank': 'True'}),
+            'materials': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'through': u"orm['meeting.SessionPresentation']", 'blank': 'True'}),
             'meeting': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['meeting.Meeting']"}),
             'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
             'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
@@ -210,8 +210,8 @@ class Migration(DataMigration):
             'time': ('django.db.models.fields.DateTimeField', [], {}),
             'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.TimeSlotTypeName']"})
         },
-        u'meeting.versionedmaterials': {
-            'Meta': {'object_name': 'VersionedMaterials', 'db_table': "'meeting_session_materials'"},
+        u'meeting.sessionpresentation': {
+            'Meta': {'object_name': 'SessionPresentation', 'db_table': "'meeting_session_materials'"},
             'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
             u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
             'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index bb62596a0..6ac604027 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -777,7 +777,7 @@ class Constraint(models.Model):
         return ct1
 
 
-class VersionedMaterials(models.Model):
+class SessionPresentation(models.Model):
     session = models.ForeignKey('Session')
     document = models.ForeignKey(Document)
     rev = models.CharField(verbose_name="revision", max_length=16, blank=True)
@@ -811,7 +811,7 @@ class Session(models.Model):
     scheduled = models.DateTimeField(null=True, blank=True)
     modified = models.DateTimeField(default=datetime.datetime.now)
 
-    materials = models.ManyToManyField(Document, through=VersionedMaterials, blank=True)
+    materials = models.ManyToManyField(Document, through=SessionPresentation, blank=True)
     resources = models.ManyToManyField(ResourceAssociation)
 
     unique_constraints_dict = None
diff --git a/ietf/meeting/test_data.py b/ietf/meeting/test_data.py
index c568cbe21..e7a877f76 100644
--- a/ietf/meeting/test_data.py
+++ b/ietf/meeting/test_data.py
@@ -2,7 +2,7 @@ import datetime
 
 from ietf.doc.models import Document, State
 from ietf.group.models import Group
-from ietf.meeting.models import Meeting, Room, TimeSlot, Session, Schedule, ScheduledSession, ResourceAssociation, VersionedMaterials
+from ietf.meeting.models import Meeting, Room, TimeSlot, Session, Schedule, ScheduledSession, ResourceAssociation, SessionPresentation
 from ietf.name.models import RoomResourceName
 from ietf.person.models import Person
 from ietf.utils.test_data import make_test_data
@@ -46,15 +46,15 @@ def make_meeting_test_data():
 
     doc = Document.objects.create(name='agenda-mars-ietf-42', type_id='agenda', title="Agenda", external_url="agenda-mars")
     doc.set_state(State.objects.get(type=doc.type_id, slug="active"))
-    mars_session.versionedmaterials_set.add(VersionedMaterials(session=mars_session,document=doc,rev=doc.rev))
+    mars_session.sessionpresentation_set.add(SessionPresentation(session=mars_session,document=doc,rev=doc.rev))
 
     doc = Document.objects.create(name='minutes-mars-ietf-42', type_id='minutes', title="Minutes", external_url="minutes-mars")
     doc.set_state(State.objects.get(type=doc.type_id, slug="active"))
-    mars_session.versionedmaterials_set.add(VersionedMaterials(session=mars_session,document=doc,rev=doc.rev))
+    mars_session.sessionpresentation_set.add(SessionPresentation(session=mars_session,document=doc,rev=doc.rev))
 
     doc = Document.objects.create(name='slides-mars-ietf-42', type_id='slides', title="Slideshow", external_url="slides-mars")
     doc.set_state(State.objects.get(type=doc.type_id, slug="active"))
-    mars_session.versionedmaterials_set.add(VersionedMaterials(session=mars_session,document=doc,rev=doc.rev))
+    mars_session.sessionpresentation_set.add(SessionPresentation(session=mars_session,document=doc,rev=doc.rev))
     
     return meeting
 
diff --git a/ietf/secr/proceedings/views.py b/ietf/secr/proceedings/views.py
index 58636c00d..3afce1325 100644
--- a/ietf/secr/proceedings/views.py
+++ b/ietf/secr/proceedings/views.py
@@ -23,7 +23,7 @@ from ietf.secr.utils.meeting import get_upload_root, get_material, get_timeslot
 from ietf.doc.models import Document, DocAlias, DocEvent, State, NewRevisionDocEvent
 from ietf.group.models import Group
 from ietf.ietfauth.utils import has_role
-from ietf.meeting.models import Meeting, Session, TimeSlot, ScheduledSession, VersionedMaterials
+from ietf.meeting.models import Meeting, Session, TimeSlot, ScheduledSession, SessionPresentation
 from ietf.secr.proceedings.forms import EditSlideForm, InterimMeetingForm, ReplaceSlideForm, UnifiedUploadForm
 from ietf.secr.proceedings.proc_utils import ( gen_acknowledgement, gen_agenda, gen_areas, gen_attendees,
     gen_group_pages, gen_index, gen_irtf, gen_overview, gen_plenaries, gen_progress, gen_research,
@@ -842,9 +842,9 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
             # for the current meeting (until tools support different materials for diff sessions)
             if sessions:
                 for s in sessions:
-                    s.materials.versionedmaterials_set.add(VersionedMaterials(session=s,document=doc,rev=doc.rev))
+                    s.materials.sessionpresentation_set.add(SessionPresentation(session=s,document=doc,rev=doc.rev))
             else:
-                session.materials.versionedmaterials_set.add(VersionedMaterials(session=session,document=doc,rev=doc.rev))
+                session.materials.sessionpresentation_set.add(SessionPresentation(session=session,document=doc,rev=doc.rev))
 
             # create NewRevisionDocEvent instead of uploaded, per Ole
             NewRevisionDocEvent.objects.create(type='new_revision',
diff --git a/ietf/templates/doc/document_history.html b/ietf/templates/doc/document_history.html
index ec50b9cee..c2de18b59 100644
--- a/ietf/templates/doc/document_history.html
+++ b/ietf/templates/doc/document_history.html
@@ -77,5 +77,5 @@
 {% endblock content %}
 
 {% block content_end %}
-<script type="text/javascript" src="/js/history.js"></script>
+<script type="text/javascript" src="/js/snippet.js"></script>
 {% endblock content_end %}
diff --git a/ietf/templates/group/history.html b/ietf/templates/group/history.html
index 66d2b0a8d..b59871d53 100644
--- a/ietf/templates/group/history.html
+++ b/ietf/templates/group/history.html
@@ -22,5 +22,5 @@
 {% endblock %}
 
 {% block content_end %}
-<script src="/js/history.js" type="text/javascript"></script>
+<script src="/js/snippet.js" type="text/javascript"></script>
 {% endblock %} 
diff --git a/ietf/templates/group/materials.html b/ietf/templates/group/materials.html
index 221e00e1f..afa04ff3d 100644
--- a/ietf/templates/group/materials.html
+++ b/ietf/templates/group/materials.html
@@ -6,6 +6,7 @@
 {% block morecss %}
   {{ block.super }}
   .materials .edit-options { float: right; margin-left: 2em; font-style: italic; }
+  .materials .snippable { width:25em; }
 {% endblock %}
 
 {% block group_content %}
@@ -19,15 +20,19 @@
 
         <tr>
           <th>Title</th>
-          <th>Rev.</th>
+          <th>Abstract</th>
+          <th>Curr. Rev.</th>
           <th>Date</th>
+          <th>Last Presented</th>
         </tr>
 
         {% for d in docs %}
           <tr class="{% cycle "evenrow" "oddrow" %}">
             <td><a class="title-link" href="{% url "doc_view" name=d.name %}">{{ d.title }}</a></td>
+            <td class='snippable'>{{ d.abstract|format_snippet }} </td>
             <td>{{ d.rev }}</td>
             <td>{{ d.time|date:"Y-m-d" }}</td>
+            <td>{% if d.last_presented %}-{{ d.last_presented.rev }} at {{d.last_presented.session.meeting}}{% endif %}</td>
           </tr>
         {% endfor %}
       </table>
@@ -39,3 +44,7 @@
   {% endif %}
 
 {% endblock %}
+
+{% block content_end %}
+<script src="/js/snippet.js" type="text/javascript"></script>
+{% endblock %}
diff --git a/static/css/base2.css b/static/css/base2.css
index 1bd01acd8..1824c97d0 100644
--- a/static/css/base2.css
+++ b/static/css/base2.css
@@ -154,7 +154,7 @@ table.ietf-table { border-collapse:collapse; border:1px solid #7f7f7f; }
 .ietf-divider { background: #2647a0; color: white; font-size:116%; padding:0.5em 1em; }
 
 
-table.history .snippet .show-all { color: blue; cursor: pointer; }
+table .snippet .show-all { color: blue; cursor: pointer; }
 
 .error-text {
 	font-size: 1.095em;
diff --git a/static/js/history.js b/static/js/snippet.js
similarity index 61%
rename from static/js/history.js
rename to static/js/snippet.js
index 3b44400e6..411af9144 100644
--- a/static/js/history.js
+++ b/static/js/snippet.js
@@ -1,5 +1,5 @@
 jQuery(function () {
-    jQuery("table.history .snippet .show-all").click(function () {
+    jQuery("table .snippet .show-all").click(function () {
         jQuery(this).parents(".snippet").hide().siblings(".full").show();
     });
 });

From 81119d6f37a3e42259eef5817ea971faa8fce9ee Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Tue, 23 Sep 2014 15:34:10 +0000
Subject: [PATCH 03/18] checkpoint before migrating forward to 5.6.4-dev0  -
 Legacy-Id: 8352

---
 ietf/doc/migrations/0020_archive_slides.py | 398 +++++++++++++++++++++
 ietf/doc/models.py                         |   9 +
 2 files changed, 407 insertions(+)
 create mode 100644 ietf/doc/migrations/0020_archive_slides.py

diff --git a/ietf/doc/migrations/0020_archive_slides.py b/ietf/doc/migrations/0020_archive_slides.py
new file mode 100644
index 000000000..110b3c6fd
--- /dev/null
+++ b/ietf/doc/migrations/0020_archive_slides.py
@@ -0,0 +1,398 @@
+# -*- coding: utf-8 -*-
+from south.v2 import DataMigration
+
+class Migration(DataMigration):
+
+    def set_state(self, doc, state):
+        ''' snapshot of DocumentInfo.set_state '''
+        already_set = doc.states.filter(type=state.type)
+        others = [s for s in already_set if s != state]
+        if others:
+            doc.states.remove(*others)
+        if state not in already_set:
+            doc.states.add(state)
+        doc.state_cache = None 
+    
+    def forwards(self, orm):
+
+        orm.State.objects.filter(type='slides',slug='deleted').update(order=4)
+
+        slidetype = orm.StateType.objects.get(slug='slides')
+
+        orm.State.objects.create(type=slidetype,
+                                 slug='sessonly',
+                                 name='Session Only',
+                                 used=True,
+                                 desc='This document is active only for the sessions at an upcoming meeting',
+                                 order=2) 
+
+        arch = orm.State.objects.create(type=slidetype,
+                                        slug='archived',
+                                        name='Archived',
+                                        used=True,
+                                        desc='This document is not active, but is available in the archives',
+                                        order=3) 
+
+        for doc in orm.Document.objects.filter(type='slides').exclude(group__type='team'):
+            self.set_state(doc,arch)
+
+    def backwards(self, orm):
+
+        active = orm.State.objects.get(type='slides',slug='active')
+        for doc in orm.Document.objects.filter(type='slides',states__slug='archived').exclude(group__type='team'):
+            self.set_state(doc,active)
+        orm.State.objects.filter(type='slides',slug='deleted').update(order=2)
+        orm.State.objects.filter(type='slides',slug__in=['sessonly','archived']).delete()
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        u'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'}),
+            u'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'})
+        },
+        u'doc.ballotdocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotDocEvent', '_ormbases': [u'doc.DocEvent']},
+            'ballot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.BallotType']"}),
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        u'doc.ballotpositiondocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotPositionDocEvent', '_ormbases': [u'doc.DocEvent']},
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+            'ballot': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"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'}),
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+            'pos': ('django.db.models.fields.related.ForeignKey', [], {'default': "'norecord'", 'to': u"orm['name.BallotPositionName']"})
+        },
+        u'doc.ballottype': {
+            'Meta': {'ordering': "['order']", 'object_name': 'BallotType'},
+            'doc_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}),
+            u'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': u"orm['name.BallotPositionName']", 'symmetrical': 'False', 'blank': 'True'}),
+            'question': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'doc.consensusdocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'ConsensusDocEvent', '_ormbases': [u'doc.DocEvent']},
+            'consensus': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        u'doc.deletedevent': {
+            'Meta': {'object_name': 'DeletedEvent'},
+            'by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'json': ('django.db.models.fields.TextField', [], {}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
+        },
+        u'doc.docalias': {
+            'Meta': {'object_name': 'DocAlias'},
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
+        },
+        u'doc.docevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'},
+            'by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+            'desc': ('django.db.models.fields.TextField', [], {}),
+            'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'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': u"orm['person.Person']"}),
+            'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"orm['doc.DocHistoryAuthor']", 'blank': 'True'}),
+            'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': u"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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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': u"orm['doc.DocAlias']", 'symmetrical': 'False', 'through': u"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': u"orm['person.Person']"}),
+            'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"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': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
+        },
+        u'doc.dochistoryauthor': {
+            'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocHistory']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'order': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'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': u"orm['doc.DocEvent']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocReminderTypeName']"})
+        },
+        u'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': u"orm['person.Person']"}),
+            'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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'}),
+            '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': u"orm['person.Person']"}),
+            'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"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': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
+        },
+        u'doc.documentauthor': {
+            'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+        },
+        u'doc.initialreviewdocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': [u'doc.DocEvent']},
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+            'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        u'doc.lastcalldocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': [u'doc.DocEvent']},
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+            'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        u'doc.newrevisiondocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': [u'doc.DocEvent']},
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+            'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'})
+        },
+        u'doc.relateddochistory': {
+            'Meta': {'object_name': 'RelatedDocHistory'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocRelationshipName']"}),
+            'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocHistory']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': u"orm['doc.DocAlias']"})
+        },
+        u'doc.relateddocument': {
+            'Meta': {'object_name': 'RelatedDocument'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocRelationshipName']"}),
+            'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocAlias']"})
+        },
+        u'doc.state': {
+            'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
+            'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'doc.statedocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'StateDocEvent', '_ormbases': [u'doc.DocEvent']},
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.State']", 'null': 'True', 'blank': 'True'}),
+            'state_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"})
+        },
+        u'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'})
+        },
+        u'doc.telechatdocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': [u'doc.DocEvent']},
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"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'})
+        },
+        u'doc.writeupdocevent': {
+            'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': [u'doc.DocEvent']},
+            u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+            'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'group.group': {
+            'Meta': {'object_name': 'Group'},
+            'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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': u"orm['doc.Document']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
+            'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'name.docrelationshipname': {
+            'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
+            'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': u"orm['person.Person']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        u'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'}),
+            u'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': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['doc']
+    symmetrical = True
diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index 8fd884866..5ff1c6fa8 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -171,6 +171,15 @@ class DocumentInfo(models.Model):
             return None
 
     def meeting_related(self):
+        if self.type_id in ("agenda","minutes",):
+            return (self.name.split("-")[1] == "interim"
+                   or (self.session_set.exists() if isinstance(self, Document) else self.doc.session_set.exists()))
+        elif self.type_id in ("slides",):
+            return (self.name.split("-")[1] == "interim"
+                   or (self.get_state('slides') in ("sessonly","archived") ))
+        else:
+            return False
+
         return(self.type_id in ("agenda", "minutes", "slides") and (
             self.name.split("-")[1] == "interim"
             or (self.session_set.exists() if isinstance(self, Document) else self.doc.session_set.exists())))

From 754ee8001655765f0643107a0aab66825807a8df Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Fri, 26 Sep 2014 20:47:13 +0000
Subject: [PATCH 04/18] snapshot before SIPit - have a working session
 presentation edit form  - Legacy-Id: 8358

---
 ietf/doc/models.py                            | 18 +++++++
 ietf/doc/urls_material.py                     |  1 +
 ietf/doc/views_doc.py                         |  4 ++
 ietf/doc/views_material.py                    | 54 +++++++++++++++++++
 ietf/group/info.py                            |  4 +-
 ietf/templates/doc/document_material.html     | 25 +++++++++
 .../doc/material/material_presentations.html  | 23 ++++++++
 ietf/templates/group/materials.html           |  4 +-
 8 files changed, 129 insertions(+), 4 deletions(-)
 create mode 100644 ietf/templates/doc/material/material_presentations.html

diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index 5ff1c6fa8..d80feaf8c 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -184,6 +184,24 @@ class DocumentInfo(models.Model):
             self.name.split("-")[1] == "interim"
             or (self.session_set.exists() if isinstance(self, Document) else self.doc.session_set.exists())))
 
+    def future_presentations(self):
+        """ returns related SessionPresentation objects for meetings that
+            have not yet ended. This implementation allows for 2 week meetings """
+        candidate_presentations = self.sessionpresentation_set.filter(session__meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
+        return [pres for pres in candidate_presentations if pres.session.meeting.end_date()>=datetime.date.today()]
+
+    def last_presented(self):
+        """ returns related SessionPresentation objects for the most recent meeting in the past"""
+        # Assumes no two meetings have the same start date - if the assumption is violated, one will be chosen arbitrariy
+        candidate_presentations = self.sessionpresentation_set.filter(session__meeting__date__lte=datetime.date.today())
+        candidate_meetings = set([p.session.meeting for p in candidate_presentations if p.session.meeting.end_date()<datetime.date.today()])
+        if candidate_meetings:
+            mtg = sorted(list(candidate_meetings),key=lambda x:x.date,reverse=True)[0]
+            return self.sessionpresentation_set.filter(session__meeting=mtg)
+        else:
+            return None
+
+
     class Meta:
         abstract = True
 
diff --git a/ietf/doc/urls_material.py b/ietf/doc/urls_material.py
index 01aa922fd..32272cbd5 100644
--- a/ietf/doc/urls_material.py
+++ b/ietf/doc/urls_material.py
@@ -2,5 +2,6 @@ from django.conf.urls import patterns, url
 
 urlpatterns = patterns('ietf.doc.views_material',
     url(r'^(?P<action>state|title|revise)/$', "edit_material", name="material_edit"),
+    url(r'^sessions/$', "material_presentations", name="material_presentations"),
 )
 
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index d3756c733..dd163cc29 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -493,6 +493,9 @@ def document_main(request, name, rev=None):
 
     if doc.type_id in ("slides", "agenda", "minutes"):
         can_manage_material = can_manage_materials(request.user, doc.group)
+        presentations = None
+        if doc.type_id=='slides' and doc.get_state_slug('slides') in ['sessonly','active']:
+            presentations = doc.future_presentations()
         if doc.meeting_related():
             # disallow editing meeting-related stuff through this
             # interface for the time being
@@ -530,6 +533,7 @@ def document_main(request, name, rev=None):
                                        snapshot=snapshot,
                                        can_manage_material=can_manage_material,
                                        other_types=other_types,
+                                       presentations=presentations,
                                        ),
                                   context_instance=RequestContext(request))
 
diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index d7e75c587..ac71c0cc9 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -17,6 +17,7 @@ from ietf.doc.models import NewRevisionDocEvent, save_document_in_history
 from ietf.doc.utils import add_state_change_event, check_common_doc_name_rules
 from ietf.group.models import Group
 from ietf.group.utils import can_manage_materials
+from ietf.meeting.models import Session
 
 @login_required
 def choose_material_type(request, acronym):
@@ -168,3 +169,56 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
         'document_type': document_type,
         'doc_name': doc.name if doc else "",
     })
+
+class MaterialPresentationForm(forms.Form):
+
+    sesspres = forms.MultipleChoiceField(required=False,widget=forms.CheckboxSelectMultiple,label='Place this document on the agenda for the selected sessions')
+
+    def __init__(self,*args,**kwargs):
+        choices = kwargs.pop('choices')
+        super(MaterialPresentationForm,self).__init__(*args,**kwargs)
+        self.fields['sesspres'].choices=choices
+
+@login_required
+def material_presentations(request, name):
+
+    doc = get_object_or_404(Document, name=name)
+    if not (doc.type_id=='slides' and doc.get_state('slides').slug=='active'):
+        raise Http404
+
+    group = doc.group
+    if not (group.features.has_materials and can_manage_materials(request.user,group)):
+        raise Http404
+
+    # Find all the sessions for meetings that haven't ended that the user could affect
+    # This motif is also in Document.future_presentations - it would be nice to consolodate it somehow
+    candidate_sessions = Session.objects.filter(meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
+    refined_candidates = [ sess for sess in candidate_sessions if sess.meeting.end_date()>=datetime.date.today()]
+    changeable_sessions = [ sess for sess in refined_candidates if can_manage_materials(request.user, sess.group) ]
+    for sess in changeable_sessions:
+        sess.has_presentation = sess.sessionpresentation_set.filter(document=doc)
+    sorted_sessions = sorted(changeable_sessions,key=lambda x:'%s%s%s'%('0' if x.has_presentation else '1',x.meeting,x.short_name))
+
+    choices=[(sess.pk,'%s: %s'%(sess.meeting,sess.short_name)) for sess in sorted_sessions]
+    initial = {'sesspres': [sess.pk for sess in sorted_sessions if sess.has_presentation]}
+
+    if request.method == 'POST':
+        form = MaterialPresentationForm(request.POST,choices=choices)
+        if form.is_valid():
+            print "STUFF",request.POST.get("action","nothing to be gotten")
+            if request.POST.get("action", "") == "Save":
+                new_selections = form.cleaned_data['sesspres']
+                doc.sessionpresentation_set.filter(session_id__in=(set(initial['sesspres'])-set(new_selections))).delete()
+                for sess_pk in set(new_selections)-set(initial['sesspres']):
+                    doc.sessionpresentation_set.create(session_id=sess_pk,rev=doc.rev)
+            return redirect('doc_view',name=doc.name)
+    else:
+        form = MaterialPresentationForm(choices=choices,
+                                        initial=initial,
+                                       )
+
+    return render(request, 'doc/material/material_presentations.html', {
+        'sessions' : sorted_sessions,
+        'doc': doc,
+        'form': form,
+    })
diff --git a/ietf/group/info.py b/ietf/group/info.py
index 4beacd9e2..8030a1af5 100644
--- a/ietf/group/info.py
+++ b/ietf/group/info.py
@@ -268,7 +268,7 @@ def concluded_groups(request):
 
 def get_group_materials(group):
 #   return Document.objects.filter(group=group, type__in=group.features.material_types, session=None).exclude(states__slug="deleted")
-    return Document.objects.filter(group=group, type__in=group.features.material_types).exclude(states__slug="deleted")
+    return Document.objects.filter(group=group, type__in=group.features.material_types).exclude(states__slug__in=['deleted','archived'])
 
 def construct_group_menu_context(request, group, selected, group_type, others):
     """Return context with info for the group menu filled in."""
@@ -451,8 +451,6 @@ def materials(request, acronym, group_type=None):
         if d.type not in doc_types:
             doc_types[d.type] = []
         doc_types[d.type].append(d)
-        # This needs to be better - probably looking at ScheduledSession, and perhaps ignoring future Sessions
-        d.last_presented = d.sessionpresentation_set.order_by('-session__meeting__date').first()
 
     return render(request, 'group/materials.html',
                   construct_group_menu_context(request, group, "materials", group_type, {
diff --git a/ietf/templates/doc/document_material.html b/ietf/templates/doc/document_material.html
index e1d13b40c..5c27972d5 100644
--- a/ietf/templates/doc/document_material.html
+++ b/ietf/templates/doc/document_material.html
@@ -26,6 +26,13 @@
       </td>
     </tr>
 
+    {% if doc.abstract %}
+    <tr>
+     <td>Abstract:</td>
+     <td> {{ doc.abstract | format_snippet }} </td>
+    </tr>
+    {% endif %}
+
     <tr>
       <td>State:</td>
       <td>
@@ -44,6 +51,21 @@
     </tr>
     {% endif %}
 
+    {% if presentations or can_manage_material %}
+    <tr>
+      <td>On Agenda:</td>
+      <td>
+         <a {% if not snapshot and can_manage_material %} class="editlink" href="{% url "material_presentations" name=doc.name %}"{%endif%}>
+         {% if presentations %}
+           {% for pres in presentations %}{{pres.session.short_name}} at {{pres.session.meeting}} {% if pres.rev != doc.rev %}(version -{{pres.rev}}){% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}
+        {% else %}
+           None
+        {% endif %}
+        </a>
+      </td>        
+    </tr>
+    {% endif %}
+
     <tr>
       <td>Last updated:</td>
       <td>{{ doc.time|date:"Y-m-d" }}</td>
@@ -74,3 +96,6 @@
 
 {% endblock %}
 
+{% block content_end %}
+<script src="/js/snippet.js" type="text/javascript"></script>
+{% endblock %}
diff --git a/ietf/templates/doc/material/material_presentations.html b/ietf/templates/doc/material/material_presentations.html
new file mode 100644
index 000000000..e7196819e
--- /dev/null
+++ b/ietf/templates/doc/material/material_presentations.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}
+Edit Upcoming Presentations
+{% endblock %}
+
+{% block morecss %}
+{{ block.super }}
+ul#id_sesspres { list-style-type: none; padding: 0px; margin: 0px; }
+{% endblock %}
+
+{% block content %}
+{% load ietf_filters %}
+
+<h1>Edit Upcoming Presentations of<br/>{{doc.title}}<br/>{{doc.name}}</h1>
+
+<form class="session-presentations" action="" method="post">{% csrf_token %}
+{{form.as_p}}
+<input style="button" type="submit" name="action" value="Save">
+<input style="button" type="submit" name="action" value="Cancel">
+</form>
+
+{% endblock content %}
diff --git a/ietf/templates/group/materials.html b/ietf/templates/group/materials.html
index afa04ff3d..1c3f709c6 100644
--- a/ietf/templates/group/materials.html
+++ b/ietf/templates/group/materials.html
@@ -24,6 +24,7 @@
           <th>Curr. Rev.</th>
           <th>Date</th>
           <th>Last Presented</th>
+          <th>On Agenda</th>
         </tr>
 
         {% for d in docs %}
@@ -32,7 +33,8 @@
             <td class='snippable'>{{ d.abstract|format_snippet }} </td>
             <td>{{ d.rev }}</td>
             <td>{{ d.time|date:"Y-m-d" }}</td>
-            <td>{% if d.last_presented %}-{{ d.last_presented.rev }} at {{d.last_presented.session.meeting}}{% endif %}</td>
+            <td>{% for p in  d.last_presented %}{{p.session.meeting}}{% if d.rev != p.rev %} (-{{ p.rev }}){% endif %}{%if not forloop.last%}, {% endif %}{% endfor %}</td>
+            <td>{% for p in d.future_presentations %}{{p.session.meeting}}{% if d.rev != p.rev %} (-{{p.rev}}){%endif%}{%if not forloop.last%}, {%endif%}{%endfor%}</td>
           </tr>
         {% endfor %}
       </table>

From b86d5c1d09966c1e3a20981a73cb7e0f9f3857b7 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Thu, 16 Oct 2014 16:01:16 +0000
Subject: [PATCH 05/18] renumber migration to resolve prefix collision after
 merge  - Legacy-Id: 8433

---
 .../migrations/{0020_archive_slides.py => 0021_archive_slides.py} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename ietf/doc/migrations/{0020_archive_slides.py => 0021_archive_slides.py} (100%)

diff --git a/ietf/doc/migrations/0020_archive_slides.py b/ietf/doc/migrations/0021_archive_slides.py
similarity index 100%
rename from ietf/doc/migrations/0020_archive_slides.py
rename to ietf/doc/migrations/0021_archive_slides.py

From 2f56de622386ccbcc9de3a6faecef8b701fa490f Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Thu, 16 Oct 2014 16:27:51 +0000
Subject: [PATCH 06/18] added abstract to the upload form  - Legacy-Id: 8434

---
 ietf/doc/views_material.py                     | 4 ++++
 ietf/templates/doc/material/edit_material.html | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index ac71c0cc9..26b031b14 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -33,6 +33,7 @@ def choose_material_type(request, acronym):
 class UploadMaterialForm(forms.Form):
     title = forms.CharField(max_length=Document._meta.get_field("title").max_length)
     name = forms.CharField(max_length=Document._meta.get_field("name").max_length)
+    abstract = forms.CharField(max_length=Document._meta.get_field("abstract").max_length,widget=forms.Textarea)
     state = forms.ModelChoiceField(State.objects.all(), empty_label=None)
     material = forms.FileField(label='File', help_text="PDF or text file (ASCII/UTF-8)")
 
@@ -121,6 +122,9 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
             if "title" in form.cleaned_data:
                 doc.title = form.cleaned_data["title"]
 
+            if "abstract" in form.cleaned_data:
+                doc.abstract = form.cleaned_data["abstract"]
+
             doc.time = datetime.datetime.now()
 
             if "material" in form.fields:
diff --git a/ietf/templates/doc/material/edit_material.html b/ietf/templates/doc/material/edit_material.html
index 735761e50..cd20331ca 100644
--- a/ietf/templates/doc/material/edit_material.html
+++ b/ietf/templates/doc/material/edit_material.html
@@ -5,7 +5,7 @@
 {% block morecss %}
 {{ block.super }}
 form.upload-material td { padding-bottom: 0.6em; }
-form.upload-material #id_title, form.upload-material #id_name { width: 30em; }
+form.upload-material #id_title, form.upload-material #id_name, form.upload-material #id_abstract { width: 30em; }
 form.upload-material .submit-row td { padding-top: 1em; text-align: right; }
 {% endblock %}
 

From 31f687b343b9eb2014d916bfb4016090ab168dad Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Thu, 16 Oct 2014 16:46:38 +0000
Subject: [PATCH 07/18] allow editing of abstract for existing material  -
 Legacy-Id: 8435

---
 ietf/doc/urls_material.py                 |  2 +-
 ietf/doc/views_material.py                | 10 ++++------
 ietf/templates/doc/document_material.html |  6 +++++-
 3 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/ietf/doc/urls_material.py b/ietf/doc/urls_material.py
index 32272cbd5..22cf0de6c 100644
--- a/ietf/doc/urls_material.py
+++ b/ietf/doc/urls_material.py
@@ -1,7 +1,7 @@
 from django.conf.urls import patterns, url
 
 urlpatterns = patterns('ietf.doc.views_material',
-    url(r'^(?P<action>state|title|revise)/$', "edit_material", name="material_edit"),
+    url(r'^(?P<action>state|title|abstract|revise)/$', "edit_material", name="material_edit"),
     url(r'^sessions/$', "material_presentations", name="material_presentations"),
 )
 
diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index 26b031b14..9ddcf6b0e 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -55,16 +55,14 @@ class UploadMaterialForm(forms.Form):
             del self.fields["name"]
 
             self.fields["title"].initial = doc.title
+            self.fields["abstract"].initial = doc.abstract
             self.fields["state"].initial = doc.get_state().pk if doc.get_state() else None
             if doc.get_state_slug() == "deleted":
                 self.fields["state"].help_text = "Note: If you wish to revise this document, you may wish to change the state so it's not deleted."
 
-            if action == "title":
-                del self.fields["state"]
-                del self.fields["material"]
-            elif action == "state":
-                del self.fields["title"]
-                del self.fields["material"]
+            for fieldname in ["title","state","material","abstract"]: 
+                if fieldname != action:
+                    del self.fields[fieldname]
 
     def clean_name(self):
         name = self.cleaned_data["name"].strip().rstrip("-")
diff --git a/ietf/templates/doc/document_material.html b/ietf/templates/doc/document_material.html
index 5c27972d5..be36ed394 100644
--- a/ietf/templates/doc/document_material.html
+++ b/ietf/templates/doc/document_material.html
@@ -29,7 +29,11 @@
     {% if doc.abstract %}
     <tr>
      <td>Abstract:</td>
-     <td> {{ doc.abstract | format_snippet }} </td>
+     <td> 
+        <a {% if not snapshot and can_manage_material %} class="editlink" href="{% url "material_edit" name=doc.name action="abstract" name=doc.name %}"{% endif %}>
+          {{ doc.abstract | format_snippet }} 
+        </a>
+     </td>
     </tr>
     {% endif %}
 

From 7e60f910d4ee89267fecf51ec9bbbae3f460586f Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Thu, 16 Oct 2014 17:39:52 +0000
Subject: [PATCH 08/18] rework filters to avoid conflict in editing vs showing
 all  - Legacy-Id: 8436

---
 ietf/doc/templatetags/ietf_filters.py     | 13 +++++++++++++
 ietf/templates/doc/document_material.html |  7 +++++--
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py
index da342e785..404e73313 100644
--- a/ietf/doc/templatetags/ietf_filters.py
+++ b/ietf/doc/templatetags/ietf_filters.py
@@ -458,6 +458,19 @@ def format_snippet(text):
         return mark_safe(u'<div class="snippet">%s<span class="show-all">[show all]</span></div><div style="display:none" class="full">%s</div>' % (snippet, full))
     return full
 
+@register.filter
+def format_editable_snippet(text,link): 
+    full = mark_safe(keep_spacing(linebreaksbr(urlize(sanitize_html(text)))))
+    snippet = truncatewords_html(full, 25)
+    if snippet != full:
+        return mark_safe(u'<div class="snippet">%s<span class="show-all">[show all]</span></div><div style="display:none" class="full">%s' % (format_editable(snippet,link),format_editable(full,link)) )
+    else:
+        return format_editable(full,link)
+
+@register.filter
+def format_editable(text,link): 
+    return mark_safe(u'<a class="editlink" href="%s">%s</a>' % (link,text))
+
 @register.filter
 def textify(text):
     text = re.sub("</?b>", "*", text)
diff --git a/ietf/templates/doc/document_material.html b/ietf/templates/doc/document_material.html
index be36ed394..d1b87c340 100644
--- a/ietf/templates/doc/document_material.html
+++ b/ietf/templates/doc/document_material.html
@@ -30,9 +30,12 @@
     <tr>
      <td>Abstract:</td>
      <td> 
-        <a {% if not snapshot and can_manage_material %} class="editlink" href="{% url "material_edit" name=doc.name action="abstract" name=doc.name %}"{% endif %}>
+        {% if not snapshot and can_manage_material %}
+          {% url "material_edit" name=doc.name action="abstract" as editurl %}
+          {{ doc.abstract | format_editable_snippet:editurl }} 
+        {% else %}
           {{ doc.abstract | format_snippet }} 
-        </a>
+        {% endif %}
      </td>
     </tr>
     {% endif %}

From 0db5b308c92ddb4aaa20d807a919dded6bbfb49a Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Thu, 16 Oct 2014 17:58:45 +0000
Subject: [PATCH 09/18] take advantage of new filters to simplify page a little
  - Legacy-Id: 8437

---
 ietf/templates/doc/document_material.html | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/ietf/templates/doc/document_material.html b/ietf/templates/doc/document_material.html
index d1b87c340..0b7b070f9 100644
--- a/ietf/templates/doc/document_material.html
+++ b/ietf/templates/doc/document_material.html
@@ -22,7 +22,12 @@
     <tr>
       <td>Title:</td>
       <td>
-        <a {% if not snapshot and can_manage_material %} class="editlink" href="{% url "material_edit" name=doc.name action="title" name=doc.name %}"{% endif %}>{{ doc.title }}</a>
+        {% if not snapshot and can_manage_material %}
+            {% url "material_edit" name=doc.name action="title" as editurl %}
+            {{ doc.title | format_editable:editurl }} 
+        {% else %}
+            {{ doc.title }}
+        {% endif %}
       </td>
     </tr>
 
@@ -43,7 +48,12 @@
     <tr>
       <td>State:</td>
       <td>
-        <a title="{{ doc.get_state.desc }}"{% if not snapshot and can_manage_material %} class="editlink" href="{% url "material_edit" name=doc.name action="state" %}"{% endif %}>{{ doc.get_state.name }}</a>
+        {% if not snapshot and can_manage_material %}
+          {% url "material_edit" name=doc.name action="state" as editurl %}
+          {{ doc.get_state.name | format_editable:editurl }}
+        {% else %}
+          {{ doc.get_state.name }}
+        {% endif %}
       </td>
     </tr>
 
@@ -65,9 +75,9 @@
          <a {% if not snapshot and can_manage_material %} class="editlink" href="{% url "material_presentations" name=doc.name %}"{%endif%}>
          {% if presentations %}
            {% for pres in presentations %}{{pres.session.short_name}} at {{pres.session.meeting}} {% if pres.rev != doc.rev %}(version -{{pres.rev}}){% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}
-        {% else %}
+         {% else %}
            None
-        {% endif %}
+         {% endif %}
         </a>
       </td>        
     </tr>

From 9a46f471685804515f19104f29e03f914cc61d0c Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Thu, 16 Oct 2014 18:58:13 +0000
Subject: [PATCH 10/18] fix diffable bug, improve form grooming, remove too
 restrictive help text from form  - Legacy-Id: 8439

---
 ietf/doc/views_doc.py      | 2 +-
 ietf/doc/views_material.py | 9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index 1586aabc5..afefe11b8 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -549,7 +549,7 @@ def document_history(request, name):
     diff_revisions = []
 
     diffable = [ name.startswith(prefix) for prefix in ["rfc", "draft", "charter", "conflict-review", "status-change", ]]
-    if diffable:
+    if any(diffable):
         diff_documents = [ doc ]
         diff_documents.extend(Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces"))
 
diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index 9ddcf6b0e..4ccd1d175 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -35,7 +35,7 @@ class UploadMaterialForm(forms.Form):
     name = forms.CharField(max_length=Document._meta.get_field("name").max_length)
     abstract = forms.CharField(max_length=Document._meta.get_field("abstract").max_length,widget=forms.Textarea)
     state = forms.ModelChoiceField(State.objects.all(), empty_label=None)
-    material = forms.FileField(label='File', help_text="PDF or text file (ASCII/UTF-8)")
+    material = forms.FileField(label='File')
 
     def __init__(self, doc_type, action, group, doc, *args, **kwargs):
         super(UploadMaterialForm, self).__init__(*args, **kwargs)
@@ -60,9 +60,10 @@ class UploadMaterialForm(forms.Form):
             if doc.get_state_slug() == "deleted":
                 self.fields["state"].help_text = "Note: If you wish to revise this document, you may wish to change the state so it's not deleted."
 
-            for fieldname in ["title","state","material","abstract"]: 
-                if fieldname != action:
-                    del self.fields[fieldname]
+            if action in ["title","state","abstract"]:
+                for fieldname in ["title","state","material","abstract"]: 
+                    if fieldname != action:
+                        del self.fields[fieldname]
 
     def clean_name(self):
         name = self.cleaned_data["name"].strip().rstrip("-")

From 2f6799ece6b3f1928a0ce825533d30aa1f692cfc Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Tue, 21 Oct 2014 21:16:27 +0000
Subject: [PATCH 11/18] Have working selectors for sessions related to a
 meeting and sessions related to a document, views for listing such sections
 that can be edited, and an edit form for changing the presented version.
 Checkpointing before big refactor to move the URI components around  -
 Legacy-Id: 8453

---
 ietf/doc/tests_material.py                    |   2 +
 ietf/doc/urls_material.py                     |   6 +
 ietf/doc/views_material.py                    | 121 +++++++++++++-----
 ietf/meeting/models.py                        |   6 +-
 ietf/meeting/urls.py                          |   7 +
 ietf/meeting/views.py                         |  63 ++++++++-
 .../doc/material/material_presentations.html  |  29 +++--
 7 files changed, 189 insertions(+), 45 deletions(-)

diff --git a/ietf/doc/tests_material.py b/ietf/doc/tests_material.py
index 7fe5216ad..07072ea93 100644
--- a/ietf/doc/tests_material.py
+++ b/ietf/doc/tests_material.py
@@ -66,6 +66,7 @@ class GroupMaterialTests(TestCase):
 
         # post
         r = self.client.post(url, dict(title="Test File - with fancy title",
+                                       abstract = "Test Abstract",
                                        name="slides-%s-test-file" % group.acronym,
                                        state=State.objects.get(type="slides", slug="active").pk,
                                        material=test_file))
@@ -125,6 +126,7 @@ class GroupMaterialTests(TestCase):
 
         # post
         r = self.client.post(url, dict(title="New title",
+                                       abstract="New abstract",
                                        state=State.objects.get(type="slides", slug="active").pk,
                                        material=test_file))
         self.assertEqual(r.status_code, 302)
diff --git a/ietf/doc/urls_material.py b/ietf/doc/urls_material.py
index 22cf0de6c..09e2645bd 100644
--- a/ietf/doc/urls_material.py
+++ b/ietf/doc/urls_material.py
@@ -3,5 +3,11 @@ from django.conf.urls import patterns, url
 urlpatterns = patterns('ietf.doc.views_material',
     url(r'^(?P<action>state|title|abstract|revise)/$', "edit_material", name="material_edit"),
     url(r'^sessions/$', "material_presentations", name="material_presentations"),
+    (r'^sessions/(?P<seq>\d+)/$',  "material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/$',  "material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<seq>\d+)/$',  "material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/$',  "material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/$',  "material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P<seq>\d+)/$',  "material_presentations"),
 )
 
diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index 4ccd1d175..10a73130d 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -173,17 +173,18 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
         'doc_name': doc.name if doc else "",
     })
 
-class MaterialPresentationForm(forms.Form):
+class MaterialVersionForm(forms.Form):
 
-    sesspres = forms.MultipleChoiceField(required=False,widget=forms.CheckboxSelectMultiple,label='Place this document on the agenda for the selected sessions')
+    version = forms.ChoiceField(required=False,
+                                label='Which version of this document will be presented at this session')
 
-    def __init__(self,*args,**kwargs):
+    def __init__(self, *args, **kwargs):
         choices = kwargs.pop('choices')
-        super(MaterialPresentationForm,self).__init__(*args,**kwargs)
-        self.fields['sesspres'].choices=choices
+        super(MaterialVersionForm,self).__init__(*args,**kwargs)
+        self.fields['version'].choices = choices
 
 @login_required
-def material_presentations(request, name):
+def material_presentations(request, name, acronym=None, date=None, seq=None, week_day=None):
 
     doc = get_object_or_404(Document, name=name)
     if not (doc.type_id=='slides' and doc.get_state('slides').slug=='active'):
@@ -197,31 +198,93 @@ def material_presentations(request, name):
     # This motif is also in Document.future_presentations - it would be nice to consolodate it somehow
     candidate_sessions = Session.objects.filter(meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
     refined_candidates = [ sess for sess in candidate_sessions if sess.meeting.end_date()>=datetime.date.today()]
+
+    if acronym:
+        refined_candidates = [ sess for sess in refined_candidates if sess.group.acronym==acronym]
+
+    if date:
+        if len(date)==15:
+            start = datetime.datetime.strptime(date,"%Y-%m-%d-%H%M")
+            refined_candidates = [ sess for sess in refined_candidates if sess.scheduledsession_set.filter(schedule=sess.meeting.agenda,timeslot__time=start) ]
+        else:
+            start = datetime.datetime.strptime(date,"%Y-%m-%d").date()
+            end = start+datetime.timedelta(days=1)
+            refined_candidates = [ sess for sess in refined_candidates if sess.scheduledsession_set.filter(schedule=sess.meeting.agenda,timeslot__time__range=(start,end)) ]
+
+    if week_day:
+        try:
+            dow = ['sun','mon','tue','wed','thu','fri','sat'].index(week_day.lower()[:3]) + 1
+        except ValueError:
+            raise Http404
+        refined_candidates = [ sess for sess in refined_candidates if sess.scheduledsession_set.filter(schedule=sess.meeting.agenda,timeslot__time__week_day=dow) ]
+
     changeable_sessions = [ sess for sess in refined_candidates if can_manage_materials(request.user, sess.group) ]
+
+    if not changeable_sessions:
+        raise Http404
+
     for sess in changeable_sessions:
-        sess.has_presentation = sess.sessionpresentation_set.filter(document=doc)
-    sorted_sessions = sorted(changeable_sessions,key=lambda x:'%s%s%s'%('0' if x.has_presentation else '1',x.meeting,x.short_name))
+        sess.has_presentation = bool(sess.sessionpresentation_set.filter(document=doc))
+        if sess.has_presentation:
+            sess.version = sess.sessionpresentation_set.get(document=doc).rev
 
-    choices=[(sess.pk,'%s: %s'%(sess.meeting,sess.short_name)) for sess in sorted_sessions]
-    initial = {'sesspres': [sess.pk for sess in sorted_sessions if sess.has_presentation]}
+    # Since Python 2.2 sorts are stable, so this series results in a list sorted first by whether
+    # the session has any presentations, then by the meeting 'number', then by session's group 
+    # acronym, then by scheduled time (or the time of the session request if the session isn't 
+    # scheduled).
+    
+    def time_sort_key(session):
+        official_sessions = session.scheduledsession_set.filter(schedule=session.meeting.agenda)
+        if official_sessions:
+            return official_sessions.first().timeslot.time
+        else:
+            return session.requested
+
+    time_sorted = sorted(changeable_sessions,key=time_sort_key)
+    acronym_sorted = sorted(time_sorted,key=lambda x: x.group.acronym)
+    meeting_sorted = sorted(acronym_sorted,key=lambda x: x.meeting.number)
+    sorted_sessions = sorted(meeting_sorted,key=lambda x: '0' if x.has_presentation else '1')
+    
+    if seq:
+        iseq = int(seq) - 1
+        if not iseq in range(0,len(sorted_sessions)):
+            raise Http404
+        else:
+            sorted_sessions = [sorted_sessions[iseq]]
+
+    for index,session in enumerate(sorted_sessions):
+        session.sequence = index+1
+
+    if len(sorted_sessions)==1:
+        session = sorted_sessions[0]
+        choices = [('notpresented','Not Presented')]
+        choices.extend([(x,x) for x in doc.docevent_set.filter(type='new_revision').values_list('newrevisiondocevent__rev',flat=True)])
+        initial = {'version' : session.version if hasattr(session,'version') else 'notpresented'}
+
+        if request.method == 'POST':
+            form = MaterialVersionForm(request.POST,choices=choices)
+            if form.is_valid():
+                if request.POST.get("action", "") == "Save":
+                    new_selection = form.cleaned_data['version']
+                    if initial['version'] != new_selection:
+                        if initial['version'] == 'notpresented':
+                            doc.sessionpresentation_set.create(session=session,rev=new_selection)
+                        elif new_selection == 'notpresented':
+                            doc.sessionpresentation_set.filter(session=session).delete()
+                        else:
+                            doc.sessionpresentation_set.filter(session=session).update(rev=new_selection)
+                return redirect('doc_view',name=doc.name)
+        else:
+            form = MaterialVersionForm(choices=choices,initial=initial)
+
+        return render(request, 'doc/material/edit_material_presentations.html', {
+            'session': session,
+            'doc': doc,
+            'form': form,
+            })
 
-    if request.method == 'POST':
-        form = MaterialPresentationForm(request.POST,choices=choices)
-        if form.is_valid():
-            print "STUFF",request.POST.get("action","nothing to be gotten")
-            if request.POST.get("action", "") == "Save":
-                new_selections = form.cleaned_data['sesspres']
-                doc.sessionpresentation_set.filter(session_id__in=(set(initial['sesspres'])-set(new_selections))).delete()
-                for sess_pk in set(new_selections)-set(initial['sesspres']):
-                    doc.sessionpresentation_set.create(session_id=sess_pk,rev=doc.rev)
-            return redirect('doc_view',name=doc.name)
     else:
-        form = MaterialPresentationForm(choices=choices,
-                                        initial=initial,
-                                       )
-
-    return render(request, 'doc/material/material_presentations.html', {
-        'sessions' : sorted_sessions,
-        'doc': doc,
-        'form': form,
-    })
+        return render(request, 'doc/material/material_presentations.html', {
+            'sessions' : sorted_sessions,
+            'doc': doc,
+        })
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index 1c6f035c5..5fe9adbbe 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -839,10 +839,10 @@ class Session(models.Model):
             return self.meeting.number
 
         ss0name = "(unscheduled)"
-        ss = self.scheduledsession_set.order_by('timeslot__time')
+        ss = self.scheduledsession_set.filter(schedule=self.meeting.agenda).order_by('timeslot__time')
         if ss:
-            ss0name = ss[0].timeslot.time.strftime("%H%M")
-        return u"%s: %s %s[%u]" % (self.meeting, self.group.acronym, ss0name, self.pk)
+            ss0name = ','.join([x.timeslot.time.strftime("%a-%H%M") for x in ss])
+        return u"%s: %s %s %s" % (self.meeting, self.group.acronym, self.name, ss0name)
 
     def is_bof(self):
         return self.group.is_bof();
diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py
index a4443f1fe..f8cce53f7 100644
--- a/ietf/meeting/urls.py
+++ b/ietf/meeting/urls.py
@@ -53,6 +53,13 @@ urlpatterns = patterns('',
     (r'^(?P<num>\d+)/sessions.json',                               ajax.sessions_json),
     (r'^(?P<num>\d+)/session/(?P<sessionid>\d+).json',             ajax.session_json),
     (r'^(?P<num>\d+)/session/(?P<sessionid>\d+)/constraints.json', ajax.session_constraints),
+
+    (r'^(?P<num>\d+)/session/(?P<acronym>[A-Za-z0-9_\-\+]+)/$',  views.session_details),
+    (r'^(?P<num>\d+)/session/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<seq>\d+)/$',  views.session_details),
+    (r'^(?P<num>\d+)/session/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/$',  views.session_details),
+    (r'^(?P<num>\d+)/session/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/$',  views.session_details),
+    (r'^(?P<num>\d+)/session/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P<seq>\d+)/$',  views.session_details),
+
     (r'^(?P<num>\d+)/constraint/(?P<constraintid>\d+).json',       ajax.constraint_json),
     (r'^(?P<num>\d+).json$',                               ajax.meeting_json),
     (r'^$', views.current_materials),
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
index ceaae58b8..556b34e27 100644
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -10,7 +10,7 @@ from tempfile import mkstemp
 import debug                            # pyflakes:ignore
 
 from django import forms
-from django.shortcuts import render_to_response, redirect
+from django.shortcuts import render, render_to_response, redirect
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
 from django.core.urlresolvers import reverse
 from django.db.models import Q
@@ -616,3 +616,64 @@ def meeting_requests(request, num=None) :
          "groups_not_meeting": groups_not_meeting},
         context_instance=RequestContext(request))
 
+def session_details(request, num, acronym, date=None, week_day=None, seq=None) :
+    meeting = get_meeting(num)
+    sessions = Session.objects.filter(meeting=meeting,group__acronym=acronym)
+
+    if not sessions:
+        sessions = Session.objects.filter(meeting=meeting,short=acronym) 
+
+    if date:
+        if len(date)==15:
+            start = datetime.datetime.strptime(date,"%Y-%m-%d-%H%M")
+            sessions = sessions.filter(scheduledsession__schedule=meeting.agenda,scheduledsession__timeslot__time=start)
+        else:
+            start = datetime.datetime.strptime(date,"%Y-%m-%d").date()
+            end = start+datetime.timedelta(days=1)
+            sessions = sessions.filter(scheduledsession__schedule=meeting.agenda,scheduledsession__timeslot__time__range=(start,end))
+
+    if week_day:
+        try:
+            dow = ['sun','mon','tue','wed','thu','fri','sat'].index(week_day.lower()[:3]) + 1
+        except ValueError:
+            raise Http404
+        sessions = sessions.filter(scheduledsession__schedule=meeting.agenda,scheduledsession__timeslot__time__week_day=dow)
+        
+
+    def sort_key(session):
+        official_sessions = session.scheduledsession_set.filter(schedule=session.meeting.agenda)
+        if official_sessions:
+            return official_sessions.first().timeslot.time
+        else:
+            return session.requested
+
+    sessions = sorted(sessions,key=sort_key)
+
+    if seq:
+        iseq = int(seq) - 1
+        if not iseq in range(0,len(sessions)):
+            raise Http404
+        else:
+            sessions= [sessions[iseq]]
+
+    if not sessions:
+        raise Http404
+
+    if len(sessions)==1:
+        session = sessions[0]
+        scheduled_time = "Not yet scheduled"
+        ss = session.scheduledsession_set.filter(schedule=meeting.agenda).order_by('timeslot__time')
+        if ss:
+            scheduled_time = ','.join([x.timeslot.time.strftime("%A %b-%d %H%M") for x in ss])
+        return render(request, "meeting/session_details.html",
+                      { 'session':sessions[0] ,
+                        'meeting' :meeting ,
+                        'acronym' :acronym,
+                        'time': scheduled_time,
+                      })
+    else:
+        return render(request, "meeting/session_list.html",
+                      { 'sessions':sessions ,
+                        'meeting' :meeting ,
+                        'acronym' :acronym,
+                      })
diff --git a/ietf/templates/doc/material/material_presentations.html b/ietf/templates/doc/material/material_presentations.html
index e7196819e..f84f14013 100644
--- a/ietf/templates/doc/material/material_presentations.html
+++ b/ietf/templates/doc/material/material_presentations.html
@@ -1,23 +1,28 @@
 {% extends "base.html" %}
 
 {% block title %}
-Edit Upcoming Presentations
-{% endblock %}
-
-{% block morecss %}
-{{ block.super }}
-ul#id_sesspres { list-style-type: none; padding: 0px; margin: 0px; }
+Upcoming Presentations
 {% endblock %}
 
 {% block content %}
 {% load ietf_filters %}
 
-<h1>Edit Upcoming Presentations of<br/>{{doc.title}}<br/>{{doc.name}}</h1>
+<h1>Upcoming Presentations of<br/>{{doc.title}}<br/>{{doc.name}}</h1>
 
-<form class="session-presentations" action="" method="post">{% csrf_token %}
-{{form.as_p}}
-<input style="button" type="submit" name="action" value="Save">
-<input style="button" type="submit" name="action" value="Cancel">
-</form>
+<ul>
+{% regroup sessions by has_presentation as is_scheduled_list %}
+  {% for is_scheduled in is_scheduled_list %}
+    <li> {{ is_scheduled.grouper|yesno:"Presentation Scheduled,Presentation Not Scheduled"}}
+      <ul>
+        {% for session in is_scheduled.list %}
+          <li> 
+               <a href="{{session.sequence}}">{{ session }}</a>
+               {% if session.versions %} (version{{session.versions|pluralize}} {{session.versions|join:','}}) {% endif %}
+          </li>
+        {% endfor %}
+      </ul>
+    </li>
+  {% endfor %}
+</ul>
 
 {% endblock content %}

From c05d734cf33dc8d5e6d076b428a0eabee7eb1972 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Tue, 21 Oct 2014 22:38:06 +0000
Subject: [PATCH 12/18] one more tweak before refactoring  - Legacy-Id: 8455

---
 ietf/doc/views_material.py |  3 ++-
 ietf/meeting/models.py     | 11 +++++++----
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index 10a73130d..faf9a158a 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -196,7 +196,8 @@ def material_presentations(request, name, acronym=None, date=None, seq=None, wee
 
     # Find all the sessions for meetings that haven't ended that the user could affect
     # This motif is also in Document.future_presentations - it would be nice to consolodate it somehow
-    candidate_sessions = Session.objects.filter(meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
+
+    candidate_sessions = Session.objects.exclude(status__in=['canceled','disappr','notmeet','deleted']).filter(meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
     refined_candidates = [ sess for sess in candidate_sessions if sess.meeting.end_date()>=datetime.date.today()]
 
     if acronym:
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index 5fe9adbbe..1fdd1c238 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -838,10 +838,13 @@ class Session(models.Model):
         if self.meeting.type_id == "interim":
             return self.meeting.number
 
-        ss0name = "(unscheduled)"
-        ss = self.scheduledsession_set.filter(schedule=self.meeting.agenda).order_by('timeslot__time')
-        if ss:
-            ss0name = ','.join([x.timeslot.time.strftime("%a-%H%M") for x in ss])
+        if self.status.slug in ('canceled','disappr','notmeet','deleted'):
+            ss0name = "(%s)" % self.status.name
+        else:
+            ss0name = "(unscheduled)"
+            ss = self.scheduledsession_set.filter(schedule=self.meeting.agenda).order_by('timeslot__time')
+            if ss:
+                ss0name = ','.join([x.timeslot.time.strftime("%a-%H%M") for x in ss])
         return u"%s: %s %s %s" % (self.meeting, self.group.acronym, self.name, ss0name)
 
     def is_bof(self):

From b7d8ed8491d97eed4a6739714b035e9189198332 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Wed, 5 Nov 2014 21:49:44 +0000
Subject: [PATCH 13/18] add missing templates  - Legacy-Id: 8590

---
 .../material/edit_material_presentations.html | 23 +++++++++++++++++
 ietf/templates/meeting/session_details.html   | 25 +++++++++++++++++++
 ietf/templates/meeting/session_list.html      | 13 ++++++++++
 3 files changed, 61 insertions(+)
 create mode 100644 ietf/templates/doc/material/edit_material_presentations.html
 create mode 100644 ietf/templates/meeting/session_details.html
 create mode 100644 ietf/templates/meeting/session_list.html

diff --git a/ietf/templates/doc/material/edit_material_presentations.html b/ietf/templates/doc/material/edit_material_presentations.html
new file mode 100644
index 000000000..657a7cfad
--- /dev/null
+++ b/ietf/templates/doc/material/edit_material_presentations.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}
+Edit Upcoming Presentations
+{% endblock %}
+
+{% block morecss %}
+{{ block.super }}
+ul#id_versions { list-style-type: none; padding: 0px; margin: 0px; }
+{% endblock %}
+
+{% block content %}
+{% load ietf_filters %}
+
+<h1>Edit Upcoming Presentations of<br/>{{doc.title}}<br/>{{doc.name}}<br/>at {{session}}</h1>
+
+<form method="post">{% csrf_token %}
+{{form.as_p}}
+<input style="button" type="submit" name="action" value="Save"/>
+<input style="button" type="submit" name="action" value="Cancel"/>
+</form>
+
+{% endblock content %}
diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html
new file mode 100644
index 000000000..dc1e8028e
--- /dev/null
+++ b/ietf/templates/meeting/session_details.html
@@ -0,0 +1,25 @@
+{% extends "base.html" %}
+
+{% block title %} {{meeting}} : {{acronym}} {% endblock %}
+
+{% block morecss %}
+.timeheader {font-size:120%;}
+{% endblock %}
+
+{% block content %}
+<h1> {{ meeting }} : {{ acronym }} : {{ time }} </h1>
+{% if session.name %}
+<h2> {{session.name}} </h2>
+{% endif %}
+
+{% if session.sessionpresentation_set.all.count %}
+<p>Materials:
+<ul>
+{% for pres in session.sessionpresentation_set.all %}
+<li><a href="{% url 'doc_view' name=pres.document.name rev=pres.rev%}">{{pres.document.name}}-{{pres.rev}}</a></li>
+{% endfor %}
+</ul>
+</p>
+{% endif %}
+
+{% endblock %}
diff --git a/ietf/templates/meeting/session_list.html b/ietf/templates/meeting/session_list.html
new file mode 100644
index 000000000..222127acf
--- /dev/null
+++ b/ietf/templates/meeting/session_list.html
@@ -0,0 +1,13 @@
+{% extends "base.html" %}
+
+{% block title %} {{meeting}} : {{acronym}} {% endblock %}
+
+{% block content %}
+<h1> {{ meeting }} : {{acronym}} </h1>
+
+<ul>
+{% for session in sessions %}
+  <li> <a href="{{forloop.counter}}">{{session}}</a></li>
+{% endfor %}
+</ul>
+{% endblock %}

From 5f1663530c398a39a43cfb4602aee6b0c5c6038e Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Mon, 24 Nov 2014 19:54:25 +0000
Subject: [PATCH 14/18] Added comment docevents when changing
 SessionPresentation objects. Reworked group materials page to be more helpful
 when there are multiple SessionPresentations withing a given Meeting  -
 Legacy-Id: 8705

---
 ietf/doc/models.py                  |  2 +-
 ietf/doc/views_material.py          |  9 +++++++++
 ietf/templates/group/materials.html | 31 +++++++++++++++++++++++++++--
 3 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index cfb3e2c2f..fb89f643e 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -203,7 +203,7 @@ class DocumentInfo(models.Model):
         """ returns related SessionPresentation objects for meetings that
             have not yet ended. This implementation allows for 2 week meetings """
         candidate_presentations = self.sessionpresentation_set.filter(session__meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
-        return [pres for pres in candidate_presentations if pres.session.meeting.end_date()>=datetime.date.today()]
+        return sorted([pres for pres in candidate_presentations if pres.session.meeting.end_date()>=datetime.date.today()], key=lambda x:x.session.meeting.date)
 
     def last_presented(self):
         """ returns related SessionPresentation objects for the most recent meeting in the past"""
diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index faf9a158a..f0c655f12 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -270,10 +270,19 @@ def material_presentations(request, name, acronym=None, date=None, seq=None, wee
                     if initial['version'] != new_selection:
                         if initial['version'] == 'notpresented':
                             doc.sessionpresentation_set.create(session=session,rev=new_selection)
+                            c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
+                            c.desc = "Added version %s to session: %s" % (new_selection,session)
+                            c.save()
                         elif new_selection == 'notpresented':
                             doc.sessionpresentation_set.filter(session=session).delete()
+                            c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
+                            c.desc = "Removed from session: %s" % (session)
+                            c.save()
                         else:
                             doc.sessionpresentation_set.filter(session=session).update(rev=new_selection)
+                            c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
+                            c.desc = "Revision for session %s changed to  %s" % (session,new_selection)
+                            c.save()
                 return redirect('doc_view',name=doc.name)
         else:
             form = MaterialVersionForm(choices=choices,initial=initial)
diff --git a/ietf/templates/group/materials.html b/ietf/templates/group/materials.html
index 1c3f709c6..c8742eacc 100644
--- a/ietf/templates/group/materials.html
+++ b/ietf/templates/group/materials.html
@@ -33,8 +33,35 @@
             <td class='snippable'>{{ d.abstract|format_snippet }} </td>
             <td>{{ d.rev }}</td>
             <td>{{ d.time|date:"Y-m-d" }}</td>
-            <td>{% for p in  d.last_presented %}{{p.session.meeting}}{% if d.rev != p.rev %} (-{{ p.rev }}){% endif %}{%if not forloop.last%}, {% endif %}{% endfor %}</td>
-            <td>{% for p in d.future_presentations %}{{p.session.meeting}}{% if d.rev != p.rev %} (-{{p.rev}}){%endif%}{%if not forloop.last%}, {%endif%}{%endfor%}</td>
+            <td>{% regroup d.last_presented by session.meeting as past_pres_list %}
+                {% for p in past_pres_list %}
+                  {{ p.grouper }}
+                   {% if p.list|length > 1 %}
+                       ( {{ p.list|length }} sessions )
+                   {% else %}
+                       {% for pr in p.list %}
+                         {% if pr.rev != d.rev %}
+                           (-{{ pr.rev }})
+                         {% endif %}
+                       {% endfor %}
+                   {% endif %}
+                   {% if not forloop.last %}, {% endif %}
+               {% endfor %}
+            <td>{% regroup d.future_presentations by session.meeting as meeting_pres_list %} 
+                {% for p in meeting_pres_list %}
+                   {{ p.grouper }}
+                   {% if p.list|length > 1 %}
+                       ( {{ p.list|length }} sessions )
+                   {% else %}
+                       {% for pr in p.list %}
+                         {% if pr.rev != d.rev %}
+                           (-{{ pr.rev }})
+                         {% endif %}
+                       {% endfor %}
+                   {% endif %}
+                   {% if not forloop.last %}, {% endif %}
+                {% endfor %}
+            </td>
           </tr>
         {% endfor %}
       </table>

From 2d0eb1e0115c494a3ba5bc35aec870f48eba7038 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Mon, 24 Nov 2014 20:53:36 +0000
Subject: [PATCH 15/18] Reworked URLs to explicitly invoke edit. Build list of
 possible sessions using group acronym and sequence variant of urls, not just
 sequence alone  - Legacy-Id: 8706

---
 ietf/doc/urls_material.py                     |   6 +
 ietf/doc/views_material.py                    | 120 ++++++++++--------
 .../doc/material/material_presentations.html  |  13 +-
 3 files changed, 84 insertions(+), 55 deletions(-)

diff --git a/ietf/doc/urls_material.py b/ietf/doc/urls_material.py
index 09e2645bd..d61b5faaf 100644
--- a/ietf/doc/urls_material.py
+++ b/ietf/doc/urls_material.py
@@ -3,6 +3,12 @@ from django.conf.urls import patterns, url
 urlpatterns = patterns('ietf.doc.views_material',
     url(r'^(?P<action>state|title|abstract|revise)/$', "edit_material", name="material_edit"),
     url(r'^sessions/$', "material_presentations", name="material_presentations"),
+    (r'^sessions/(?P<seq>\d+)/edit/$',  "edit_material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/edit/$',  "edit_material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<seq>\d+)/edit/$',  "edit_material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/edit/$',  "edit_material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/edit/$',  "edit_material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P<seq>\d+)/edit/$',  "edit_material_presentations"),
     (r'^sessions/(?P<seq>\d+)/$',  "material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/$',  "material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<seq>\d+)/$',  "material_presentations"),
diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index f0c655f12..29ed7e48c 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -183,16 +183,7 @@ class MaterialVersionForm(forms.Form):
         super(MaterialVersionForm,self).__init__(*args,**kwargs)
         self.fields['version'].choices = choices
 
-@login_required
-def material_presentations(request, name, acronym=None, date=None, seq=None, week_day=None):
-
-    doc = get_object_or_404(Document, name=name)
-    if not (doc.type_id=='slides' and doc.get_state('slides').slug=='active'):
-        raise Http404
-
-    group = doc.group
-    if not (group.features.has_materials and can_manage_materials(request.user,group)):
-        raise Http404
+def get_upcoming_manageable_sessions(user, doc, acronym=None, date=None, seq=None, week_day = None):
 
     # Find all the sessions for meetings that haven't ended that the user could affect
     # This motif is also in Document.future_presentations - it would be nice to consolodate it somehow
@@ -219,7 +210,7 @@ def material_presentations(request, name, acronym=None, date=None, seq=None, wee
             raise Http404
         refined_candidates = [ sess for sess in refined_candidates if sess.scheduledsession_set.filter(schedule=sess.meeting.agenda,timeslot__time__week_day=dow) ]
 
-    changeable_sessions = [ sess for sess in refined_candidates if can_manage_materials(request.user, sess.group) ]
+    changeable_sessions = [ sess for sess in refined_candidates if can_manage_materials(user, sess.group) ]
 
     if not changeable_sessions:
         raise Http404
@@ -253,48 +244,77 @@ def material_presentations(request, name, acronym=None, date=None, seq=None, wee
         else:
             sorted_sessions = [sorted_sessions[iseq]]
 
-    for index,session in enumerate(sorted_sessions):
-        session.sequence = index+1
+    return sorted_sessions
 
-    if len(sorted_sessions)==1:
-        session = sorted_sessions[0]
-        choices = [('notpresented','Not Presented')]
-        choices.extend([(x,x) for x in doc.docevent_set.filter(type='new_revision').values_list('newrevisiondocevent__rev',flat=True)])
-        initial = {'version' : session.version if hasattr(session,'version') else 'notpresented'}
+@login_required
+def edit_material_presentations(request, name, acronym=None, date=None, seq=None, week_day=None):
 
-        if request.method == 'POST':
-            form = MaterialVersionForm(request.POST,choices=choices)
-            if form.is_valid():
-                if request.POST.get("action", "") == "Save":
-                    new_selection = form.cleaned_data['version']
-                    if initial['version'] != new_selection:
-                        if initial['version'] == 'notpresented':
-                            doc.sessionpresentation_set.create(session=session,rev=new_selection)
-                            c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
-                            c.desc = "Added version %s to session: %s" % (new_selection,session)
-                            c.save()
-                        elif new_selection == 'notpresented':
-                            doc.sessionpresentation_set.filter(session=session).delete()
-                            c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
-                            c.desc = "Removed from session: %s" % (session)
-                            c.save()
-                        else:
-                            doc.sessionpresentation_set.filter(session=session).update(rev=new_selection)
-                            c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
-                            c.desc = "Revision for session %s changed to  %s" % (session,new_selection)
-                            c.save()
-                return redirect('doc_view',name=doc.name)
-        else:
-            form = MaterialVersionForm(choices=choices,initial=initial)
+    doc = get_object_or_404(Document, name=name)
+    if not (doc.type_id=='slides' and doc.get_state('slides').slug=='active'):
+        raise Http404
 
-        return render(request, 'doc/material/edit_material_presentations.html', {
-            'session': session,
-            'doc': doc,
-            'form': form,
-            })
+    group = doc.group
+    if not (group.features.has_materials and can_manage_materials(request.user,group)):
+        raise Http404
 
+    sorted_sessions = get_upcoming_manageable_sessions(request.user, doc, acronym, date, seq, week_day)
+
+    if len(sorted_sessions)!=1:
+        raise Http404
+
+    session = sorted_sessions[0]
+    choices = [('notpresented','Not Presented')]
+    choices.extend([(x,x) for x in doc.docevent_set.filter(type='new_revision').values_list('newrevisiondocevent__rev',flat=True)])
+    initial = {'version' : session.version if hasattr(session,'version') else 'notpresented'}
+
+    if request.method == 'POST':
+        form = MaterialVersionForm(request.POST,choices=choices)
+        if form.is_valid():
+            if request.POST.get("action", "") == "Save":
+                new_selection = form.cleaned_data['version']
+                if initial['version'] != new_selection:
+                    if initial['version'] == 'notpresented':
+                        doc.sessionpresentation_set.create(session=session,rev=new_selection)
+                        c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
+                        c.desc = "Added version %s to session: %s" % (new_selection,session)
+                        c.save()
+                    elif new_selection == 'notpresented':
+                        doc.sessionpresentation_set.filter(session=session).delete()
+                        c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
+                        c.desc = "Removed from session: %s" % (session)
+                        c.save()
+                    else:
+                        doc.sessionpresentation_set.filter(session=session).update(rev=new_selection)
+                        c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
+                        c.desc = "Revision for session %s changed to  %s" % (session,new_selection)
+                        c.save()
+            return redirect('doc_view',name=doc.name)
     else:
-        return render(request, 'doc/material/material_presentations.html', {
-            'sessions' : sorted_sessions,
-            'doc': doc,
+        form = MaterialVersionForm(choices=choices,initial=initial)
+
+    return render(request, 'doc/material/edit_material_presentations.html', {
+        'session': session,
+        'doc': doc,
+        'form': form,
+        })
+
+@login_required
+def material_presentations(request, name, acronym=None, date=None, seq=None, week_day=None):
+
+    doc = get_object_or_404(Document, name=name)
+    if not (doc.type_id=='slides' and doc.get_state('slides').slug=='active'):
+        raise Http404
+
+    group = doc.group
+    if not (group.features.has_materials and can_manage_materials(request.user,group)):
+        raise Http404
+
+    sorted_sessions = get_upcoming_manageable_sessions(request.user, doc, acronym, date, seq, week_day)
+
+    #for index,session in enumerate(sorted_sessions):
+    #    session.sequence = index+1
+
+    return render(request, 'doc/material/material_presentations.html', {
+        'sessions' : sorted_sessions,
+        'doc': doc,
         })
diff --git a/ietf/templates/doc/material/material_presentations.html b/ietf/templates/doc/material/material_presentations.html
index f84f14013..fd093e269 100644
--- a/ietf/templates/doc/material/material_presentations.html
+++ b/ietf/templates/doc/material/material_presentations.html
@@ -14,11 +14,14 @@ Upcoming Presentations
   {% for is_scheduled in is_scheduled_list %}
     <li> {{ is_scheduled.grouper|yesno:"Presentation Scheduled,Presentation Not Scheduled"}}
       <ul>
-        {% for session in is_scheduled.list %}
-          <li> 
-               <a href="{{session.sequence}}">{{ session }}</a>
-               {% if session.versions %} (version{{session.versions|pluralize}} {{session.versions|join:','}}) {% endif %}
-          </li>
+        {% regroup is_scheduled.list by group as group_list %}
+        {% for group in group_list %}
+            {% for session in group.list %}
+              <li> 
+                   <a href="{% url 'ietf.doc.views_material.edit_material_presentations' name=doc.name acronym=group.grouper.acronym seq=forloop.counter %}">{{ session }}</a>
+                   {% if session.versions %} (version{{session.versions|pluralize}} {{session.versions|join:','}}) {% endif %}
+              </li>
+            {% endfor %}
         {% endfor %}
       </ul>
     </li>

From 986768bf73910aec3d714154d3ce971c3831acf9 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Mon, 24 Nov 2014 21:06:58 +0000
Subject: [PATCH 16/18] Cleaned up issues with date and weekday variants of the
 session views  - Legacy-Id: 8707

---
 ietf/doc/urls_material.py                               | 2 ++
 ietf/doc/views_material.py                              | 2 ++
 ietf/templates/doc/material/material_presentations.html | 8 +++++++-
 3 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/ietf/doc/urls_material.py b/ietf/doc/urls_material.py
index d61b5faaf..40fb7fd6c 100644
--- a/ietf/doc/urls_material.py
+++ b/ietf/doc/urls_material.py
@@ -7,12 +7,14 @@ urlpatterns = patterns('ietf.doc.views_material',
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/edit/$',  "edit_material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<seq>\d+)/edit/$',  "edit_material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/edit/$',  "edit_material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/(?P<seq>\d+)/edit/$',  "edit_material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/edit/$',  "edit_material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P<seq>\d+)/edit/$',  "edit_material_presentations"),
     (r'^sessions/(?P<seq>\d+)/$',  "material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/$',  "material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<seq>\d+)/$',  "material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/$',  "material_presentations"),
+    (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/(?P<seq>\d+)/$',  "material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/$',  "material_presentations"),
     (r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P<seq>\d+)/$',  "material_presentations"),
 )
diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py
index 29ed7e48c..0f2c6b374 100644
--- a/ietf/doc/views_material.py
+++ b/ietf/doc/views_material.py
@@ -317,4 +317,6 @@ def material_presentations(request, name, acronym=None, date=None, seq=None, wee
     return render(request, 'doc/material/material_presentations.html', {
         'sessions' : sorted_sessions,
         'doc': doc,
+        'date': date,
+        'week_day': week_day,
         })
diff --git a/ietf/templates/doc/material/material_presentations.html b/ietf/templates/doc/material/material_presentations.html
index fd093e269..02cb1cb1d 100644
--- a/ietf/templates/doc/material/material_presentations.html
+++ b/ietf/templates/doc/material/material_presentations.html
@@ -18,7 +18,13 @@ Upcoming Presentations
         {% for group in group_list %}
             {% for session in group.list %}
               <li> 
-                   <a href="{% url 'ietf.doc.views_material.edit_material_presentations' name=doc.name acronym=group.grouper.acronym seq=forloop.counter %}">{{ session }}</a>
+                   {% if week_day %}
+                     <a href="{% url 'ietf.doc.views_material.edit_material_presentations' name=doc.name acronym=group.grouper.acronym seq=forloop.counter week_day=week_day %}">{{ session }}</a>
+                   {% elif date %}
+                     <a href="{% url 'ietf.doc.views_material.edit_material_presentations' name=doc.name acronym=group.grouper.acronym seq=forloop.counter date=date %}">{{ session }}</a>
+                   {% else %}
+                     <a href="{% url 'ietf.doc.views_material.edit_material_presentations' name=doc.name acronym=group.grouper.acronym seq=forloop.counter %}">{{ session }}</a>
+                   {% endif %}
                    {% if session.versions %} (version{{session.versions|pluralize}} {{session.versions|join:','}}) {% endif %}
               </li>
             {% endfor %}

From e5e6b1337d7cc6de3dd1486aa65939049c17bd78 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Mon, 24 Nov 2014 21:40:49 +0000
Subject: [PATCH 17/18] Added a materials manager (matman) Role, and populated
 it for the edu team  - Legacy-Id: 8708

---
 .../migrations/0008_materials_managers.py     | 306 ++++++++++++++++++
 ietf/group/utils.py                           |   2 +-
 2 files changed, 307 insertions(+), 1 deletion(-)
 create mode 100644 ietf/group/migrations/0008_materials_managers.py

diff --git a/ietf/group/migrations/0008_materials_managers.py b/ietf/group/migrations/0008_materials_managers.py
new file mode 100644
index 000000000..09887a4f7
--- /dev/null
+++ b/ietf/group/migrations/0008_materials_managers.py
@@ -0,0 +1,306 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        matman = orm['name.RoleName'].objects.create(slug='matman', name='Materials Manager', used=True, order=13)
+        edu = orm.Group.objects.get(acronym='edu')
+        for existing_role in orm.Role.objects.filter(group=edu,name__in=['member','chair']):
+            orm.Role.objects.create(name=matman, group=edu, person=existing_role.person, email=existing_role.email)
+
+    def backwards(self, orm):
+        orm['name.RoleName'].objects.filter(slug='matman').delete()
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        u'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'}),
+            u'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'})
+        },
+        u'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': u"orm['person.Person']"}),
+            'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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'}),
+            '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': u"orm['person.Email']"}),
+            'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
+            'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"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': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
+        },
+        u'doc.documentauthor': {
+            'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+            'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+        },
+        u'doc.state': {
+            'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
+            'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'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', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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'})
+        },
+        u'group.changestategroupevent': {
+            'Meta': {'ordering': "['-time', 'id']", 'object_name': 'ChangeStateGroupEvent', '_ormbases': [u'group.GroupEvent']},
+            u'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']"})
+        },
+        u'group.group': {
+            'Meta': {'object_name': 'Group'},
+            'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"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': u"orm['doc.Document']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
+            'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'group.groupevent': {
+            'Meta': {'ordering': "['-time', 'id']", 'object_name': 'GroupEvent'},
+            'by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+            'desc': ('django.db.models.fields.TextField', [], {}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+            u'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'})
+        },
+        u'group.grouphistory': {
+            'Meta': {'object_name': 'GroupHistory'},
+            'acronym': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': u"orm['group.Group']"}),
+            u'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': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
+            'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+            'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'group.groupmilestone': {
+            'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestone'},
+            'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+            'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
+            'due': ('django.db.models.fields.DateField', [], {}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupMilestoneStateName']"}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+        },
+        u'group.groupmilestonehistory': {
+            'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestoneHistory'},
+            'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+            'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
+            'due': ('django.db.models.fields.DateField', [], {}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'milestone': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': u"orm['group.GroupMilestone']"}),
+            'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupMilestoneStateName']"}),
+            'time': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        u'group.groupstatetransitions': {
+            'Meta': {'object_name': 'GroupStateTransitions'},
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_groupstatetransitions_states'", 'symmetrical': 'False', 'to': u"orm['doc.State']"}),
+            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.State']"})
+        },
+        u'group.groupurl': {
+            'Meta': {'object_name': 'GroupURL'},
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
+        },
+        u'group.milestonegroupevent': {
+            'Meta': {'ordering': "['-time', 'id']", 'object_name': 'MilestoneGroupEvent', '_ormbases': [u'group.GroupEvent']},
+            u'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
+            'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.GroupMilestone']"})
+        },
+        u'group.role': {
+            'Meta': {'object_name': 'Role'},
+            'email': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.RoleName']"}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"})
+        },
+        u'group.rolehistory': {
+            'Meta': {'object_name': 'RoleHistory'},
+            'email': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.GroupHistory']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.RoleName']"}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'name.groupmilestonestatename': {
+            'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
+            '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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': '32', 'primary_key': 'True'}),
+            'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+        },
+        u'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': u"orm['person.Person']", 'null': 'True'}),
+            'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        u'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'}),
+            u'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': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['group']
+    symmetrical = True
diff --git a/ietf/group/utils.py b/ietf/group/utils.py
index 28086e103..b9edb5d99 100644
--- a/ietf/group/utils.py
+++ b/ietf/group/utils.py
@@ -108,7 +108,7 @@ def milestone_reviewer_for_group_type(group_type):
         return "Area Director"
 
 def can_manage_materials(user, group):
-    return has_role(user, 'Secretariat') or group.has_role(user, ("chair", "delegate", "secr"))
+    return has_role(user, 'Secretariat') or group.has_role(user, ("chair", "delegate", "secr", "matman"))
 
 def get_group_or_404(acronym, group_type):
     """Helper to overcome the schism between group-type prefixed URLs and generic."""

From 8a8da7a3977fc5a3db6698c200d88bae40705952 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Mon, 24 Nov 2014 21:51:51 +0000
Subject: [PATCH 18/18] cleaned out boilerplate migration imports that arent
 actually used  - Legacy-Id: 8709

---
 ietf/group/migrations/0008_materials_managers.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/ietf/group/migrations/0008_materials_managers.py b/ietf/group/migrations/0008_materials_managers.py
index 09887a4f7..ea3924eeb 100644
--- a/ietf/group/migrations/0008_materials_managers.py
+++ b/ietf/group/migrations/0008_materials_managers.py
@@ -1,8 +1,5 @@
 # -*- coding: utf-8 -*-
-from south.utils import datetime_utils as datetime
-from south.db import db
 from south.v2 import DataMigration
-from django.db import models
 
 class Migration(DataMigration):