diff --git a/changelog b/changelog index 4f8952e8c..9a7673daf 100644 --- a/changelog +++ b/changelog @@ -15,7 +15,7 @@ ietfdb (5.7.0) ietf; urgency=medium correct Person/Email combination, and where the correct email address to use for an association has been unavailable previously. - -- Henrik Levkowetz 25 Oct 2014 13:53:37 -0700 + -- Henrik Levkowetz 25 Oct 2014 13:49:49 -0200 ietfdb (5.6.5) ietf; urgency=medium diff --git a/ietf/community/rules.py b/ietf/community/rules.py index 69f94e454..95c6c3099 100644 --- a/ietf/community/rules.py +++ b/ietf/community/rules.py @@ -89,7 +89,7 @@ class ShepherdRule(RuleManager): description = 'All I-Ds with a particular document shepherd' def get_documents(self): - return Document.objects.filter(type='draft', states__slug='active').filter(shepherd__name__icontains=self.value).distinct() + return Document.objects.filter(type='draft', states__slug='active').filter(shepherd__person__name__icontains=self.value).distinct() # class ReferenceToRFCRule(RuleManager): diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py index 9bd844452..03ea137b7 100644 --- a/ietf/doc/mails.py +++ b/ietf/doc/mails.py @@ -32,9 +32,9 @@ def email_stream_changed(request, doc, old_stream, new_stream, text=""): # These use comprehension to deal with conditions when there might be more than one chair listed for a stream if old_stream: - to.extend([x.person.formatted_email() for x in Role.objects.filter(group__acronym=old_stream.slug,name='chair')]) + to.extend([r.formatted_email() for r in Role.objects.filter(group__acronym=old_stream.slug, name='chair')]) if new_stream: - to.extend([x.person.formatted_email() for x in Role.objects.filter(group__acronym=new_stream.slug,name='chair')]) + to.extend([r.formatted_email() for r in Role.objects.filter(group__acronym=new_stream.slug, name='chair')]) if not to: return @@ -443,10 +443,10 @@ def stream_state_email_recipients(doc, extra_recipients=[]): res.append(email.formatted_email()) persons.add(email.person) - for p in extra_recipients: - if not p in persons: - res.append(p.formatted_email()) - persons.add(p) + for e in extra_recipients: + if e.person not in persons: + res.append(e.formatted_email()) + persons.add(e.person) return res diff --git a/ietf/doc/migrations/0021_auto__add_field_document_shepherd_email__add_field_dochistory_shepherd.py b/ietf/doc/migrations/0021_auto__add_field_document_shepherd_email__add_field_dochistory_shepherd.py new file mode 100644 index 000000000..044748d35 --- /dev/null +++ b/ietf/doc/migrations/0021_auto__add_field_document_shepherd_email__add_field_dochistory_shepherd.py @@ -0,0 +1,380 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Document.shepherd_email' + db.add_column(u'doc_document', 'shepherd_email', + self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='shepherd_document_set', null=True, to=orm['person.Email']), + keep_default=False) + + # Adding field 'DocHistory.shepherd_email' + db.add_column(u'doc_dochistory', 'shepherd_email', + self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='shepherd_dochistory_set', null=True, to=orm['person.Email']), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Document.shepherd_email' + db.delete_column(u'doc_document', 'shepherd_email_id') + + # Deleting field 'DocHistory.shepherd_email' + db.delete_column(u'doc_dochistory', 'shepherd_email_id') + + + 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']"}), + 'shepherd_email': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_dochistory_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.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']"}), + 'shepherd_email': ('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.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'] diff --git a/ietf/doc/migrations/0022_fill_in_shepherd_email.py b/ietf/doc/migrations/0022_fill_in_shepherd_email.py new file mode 100644 index 000000000..7fab47855 --- /dev/null +++ b/ietf/doc/migrations/0022_fill_in_shepherd_email.py @@ -0,0 +1,377 @@ +# -*- coding: utf-8 -*- +from south.v2 import DataMigration + +class Migration(DataMigration): + + def forwards(self, orm): + # figure out person -> email mapping + shepherds = set(orm.Document.objects.values_list("shepherd", flat=True).distinct()) + shepherds.update(orm.DocHistory.objects.values_list("shepherd", flat=True).distinct()) + + for person_id in shepherds: + emails = orm['person.Email'].objects.filter(person=person_id).order_by("-active", "-time")[:1] + if not emails: + print "ERROR: no emails for", person_id + + email = emails[0] + + orm.Document.objects.filter(shepherd=person_id, shepherd_email=None).update(shepherd_email=email) + orm.DocHistory.objects.filter(shepherd=person_id, shepherd_email=None).update(shepherd_email=email) + + 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.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']"}), + 'shepherd_email': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_dochistory_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.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']"}), + 'shepherd_email': ('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.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/migrations/0023_auto__del_field_document_shepherd__rename_shepherd_email.py b/ietf/doc/migrations/0023_auto__del_field_document_shepherd__rename_shepherd_email.py new file mode 100644 index 000000000..9fe2da3e6 --- /dev/null +++ b/ietf/doc/migrations/0023_auto__del_field_document_shepherd__rename_shepherd_email.py @@ -0,0 +1,375 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.delete_column(u'doc_document', 'shepherd_id') + db.rename_column(u'doc_document', 'shepherd_email_id', 'shepherd_id') + db.delete_column(u'doc_dochistory', 'shepherd_id') + db.rename_column(u'doc_dochistory', 'shepherd_email_id', 'shepherd_id') + + def backwards(self, orm): + db.rename_column(u'doc_document', 'shepherd_id', 'shepherd_email_id') + db.add_column(u'doc_document', 'shepherd', + self.gf('django.db.models.fields.related.ForeignKey')(related_name='shepherd_document_set', null=True, to=orm['person.Person'], blank=True), + keep_default=False) + + db.rename_column(u'doc_dochistory', 'shepherd_id', 'shepherd_email_id') + db.add_column(u'doc_dochistory', 'shepherd', + self.gf('django.db.models.fields.related.ForeignKey')(related_name='shepherd_document_set', null=True, to=orm['person.Person'], blank=True), + keep_default=False) + + 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.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.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.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.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'] diff --git a/ietf/doc/models.py b/ietf/doc/models.py index abb01ea5e..3125e22cd 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -64,7 +64,7 @@ class DocumentInfo(models.Model): intended_std_level = models.ForeignKey(IntendedStdLevelName, verbose_name="Intended standardization level", blank=True, null=True) std_level = models.ForeignKey(StdLevelName, verbose_name="Standardization level", blank=True, null=True) ad = models.ForeignKey(Person, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True) - shepherd = models.ForeignKey(Person, related_name='shepherd_%(class)s_set', blank=True, null=True) + shepherd = models.ForeignKey(Email, related_name='shepherd_%(class)s_set', blank=True, null=True) expires = models.DateTimeField(blank=True, null=True) notify = models.CharField(max_length=255, blank=True) external_url = models.URLField(blank=True) # Should be set for documents with type 'External'. diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py index 4e33a1514..e70f0aa0e 100644 --- a/ietf/doc/tests_draft.py +++ b/ietf/doc/tests_draft.py @@ -819,6 +819,9 @@ class IndividualInfoFormsTests(TestCase): self.assertTrue(self.doc.latest_event(DocEvent,type="added_comment").desc.startswith('Shepherding AD changed')) def test_doc_change_shepherd(self): + self.doc.shepherd = None + self.doc.save() + url = urlreverse('doc_edit_shepherd',kwargs=dict(name=self.docname)) login_testing_unauthorized(self, "plain", url) @@ -835,23 +838,51 @@ class IndividualInfoFormsTests(TestCase): self.assertEqual(len(q('form input[id=id_shepherd]')),1) # change the shepherd - plain = Person.objects.get(name='Plain Man') - plain_email = plain.email_set.all()[0] - r = self.client.post(url,dict(shepherd=plain_email)) + plain_email = Email.objects.get(person__name="Plain Man") + r = self.client.post(url, dict(shepherd=plain_email.pk)) self.assertEqual(r.status_code,302) self.doc = Document.objects.get(name=self.docname) - self.assertEqual(self.doc.shepherd,plain) + self.assertEqual(self.doc.shepherd, plain_email) comments = '::'.join([x.desc for x in self.doc.docevent_set.filter(time=self.doc.time,type="added_comment")]) self.assertTrue('Document shepherd changed to Plain Man' in comments) self.assertTrue('Notification list changed' in comments) + # test buggy change ad = Person.objects.get(name='Aread Irector') two_answers = "%s,%s" % (plain_email, ad.email_set.all()[0]) - r = self.client.post(url,(dict(shepherd=two_answers))) - self.assertEqual(r.status_code,200) + r = self.client.post(url, dict(shepherd=two_answers)) + self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(len(q('form ul.errorlist')) > 0) + def test_doc_change_shepherd_email(self): + self.doc.shepherd = None + self.doc.save() + + url = urlreverse('doc_change_shepherd_email',kwargs=dict(name=self.docname)) + r = self.client.get(url) + self.assertEqual(r.status_code, 404) + + self.doc.shepherd = Email.objects.get(person__user__username="ad1") + self.doc.save() + + login_testing_unauthorized(self, "plain", url) + + self.doc.shepherd = Email.objects.get(person__user__username="plain") + self.doc.save() + + new_email = Email.objects.create(address="anotheremail@example.com", person=self.doc.shepherd.person) + + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + + # change the shepherd email + r = self.client.post(url, dict(shepherd=new_email)) + self.assertEqual(r.status_code, 302) + self.doc = Document.objects.get(name=self.docname) + self.assertEqual(self.doc.shepherd, new_email) + self.assertTrue(self.doc.latest_event(DocEvent, type="added_comment").desc.startswith('Document shepherd email changed')) + def test_doc_view_shepherd_writeup(self): url = urlreverse('doc_shepherd_writeup',kwargs=dict(name=self.docname)) diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index c203c764a..8997b570a 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -81,6 +81,7 @@ urlpatterns = patterns('', url(r'^(?P[A-Za-z0-9._+-]+)/edit/ad/$', views_draft.edit_ad, name='doc_change_ad'), url(r'^(?P[A-Za-z0-9._+-]+)/edit/consensus/$', views_draft.edit_consensus, name='doc_edit_consensus'), url(r'^(?P[A-Za-z0-9._+-]+)/edit/shepherd/$', views_draft.edit_shepherd, name='doc_edit_shepherd'), + url(r'^(?P[A-Za-z0-9._+-]+)/edit/shepherdemail/$', views_draft.change_shepherd_email, name='doc_change_shepherd_email'), url(r'^(?P[A-Za-z0-9._+-]+)/edit/shepherdwriteup/$', views_draft.edit_shepherd_writeup, name='doc_edit_shepherd_writeup'), url(r'^(?P[A-Za-z0-9._+-]+)/edit/requestpublication/$', views_draft.request_publication, name='doc_request_publication'), url(r'^(?P[A-Za-z0-9._+-]+)/edit/adopt/$', views_draft.adopt_draft, name='doc_adopt_draft'), diff --git a/ietf/doc/views_conflict_review.py b/ietf/doc/views_conflict_review.py index 3f6425d9d..b372c0d8c 100644 --- a/ietf/doc/views_conflict_review.py +++ b/ietf/doc/views_conflict_review.py @@ -366,7 +366,7 @@ def start_review_sanity_check(request, name): def build_notify_addresses(doc_to_review): # Take care to do the right thing during ietf chair and stream owner transitions notify_addresses = [] - notify_addresses.extend([x.person.formatted_email() for x in Role.objects.filter(group__acronym=doc_to_review.stream.slug,name='chair')]) + notify_addresses.extend([r.formatted_email() for r in Role.objects.filter(group__acronym=doc_to_review.stream.slug, name='chair')]) notify_addresses.append("%s@%s" % (doc_to_review.name, settings.TOOLS_SERVER)) return notify_addresses diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index a7957d2fd..5194f3b79 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -41,6 +41,8 @@ from django.core.urlresolvers import reverse as urlreverse from django.conf import settings from django import forms +import debug # pyflakes:ignore + from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDocEvent, ConsensusDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_BALLOT_ACTIVE_STATES, STATUSCHANGE_RELATIONS ) @@ -263,7 +265,7 @@ def document_main(request, name, rev=None): shepherd_writeup = doc.latest_event(WriteupDocEvent, type="changed_protocol_writeup") can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) - can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd) or has_role(request.user, ["Area Director"]) + can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd and doc.shepherd.person) or has_role(request.user, ["Area Director"]) can_edit_consensus = False consensus = None @@ -351,6 +353,7 @@ def document_main(request, name, rev=None): can_edit=can_edit, can_change_stream=can_change_stream, can_edit_stream_info=can_edit_stream_info, + is_shepherd = user_is_person(request.user, doc.shepherd and doc.shepherd.person), can_edit_shepherd_writeup=can_edit_shepherd_writeup, can_edit_iana_state=can_edit_iana_state, can_edit_consensus=can_edit_consensus, @@ -659,7 +662,7 @@ def document_shepherd_writeup(request, name): writeup_text = "(There is no shepherd's writeup available for this document)" can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) - can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd) or has_role(request.user, ["Area Director"]) + can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd and doc.shepherd.person) or has_role(request.user, ["Area Director"]) return render_to_response("doc/shepherd_writeup.html", dict(doc=doc, diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py index 64f106fd0..516c78169 100644 --- a/ietf/doc/views_draft.py +++ b/ietf/doc/views_draft.py @@ -4,7 +4,7 @@ import datetime, json from django import forms from django.http import HttpResponseRedirect, HttpResponseForbidden, Http404 -from django.shortcuts import render_to_response, get_object_or_404, redirect +from django.shortcuts import render_to_response, get_object_or_404, redirect, render from django.template.loader import render_to_string from django.template import RequestContext from django.conf import settings @@ -29,8 +29,8 @@ from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_p from ietf.ietfauth.utils import role_required from ietf.message.models import Message from ietf.name.models import IntendedStdLevelName, DocTagName, StreamName -from ietf.person.forms import EmailsField -from ietf.person.models import Person +from ietf.person.fields import AutocompletedEmailField +from ietf.person.models import Person, Email from ietf.secr.lib.template import jsonapi from ietf.utils.mail import send_mail, send_mail_message from ietf.utils.textupload import get_cleaned_text_file_content @@ -301,10 +301,8 @@ def collect_email_addresses(emails, doc): for role in doc.group.parent.role_set.filter(name='chair'): if role.email.address not in emails: emails[role.email.address] = '"%s"' % (role.person.name) - if doc.shepherd: - address = doc.shepherd.email_address(); - if address not in emails: - emails[address] = '"%s"' % (doc.shepherd.name) + if doc.shepherd and doc.shepherd.address not in emails: + emails[doc.shepherd.address] = u'"%s"' % (doc.shepherd.person.name or "") return emails class ReplacesForm(forms.Form): @@ -941,13 +939,7 @@ def edit_shepherd_writeup(request, name): context_instance=RequestContext(request)) class ShepherdForm(forms.Form): - shepherd = EmailsField(label="Shepherd", required=False) - - def clean_shepherd(self): - data = self.cleaned_data['shepherd'] - if len(data)>1: - raise forms.ValidationError("Please choose at most one shepherd.") - return data + shepherd = AutocompletedEmailField(required=False, only_users=True) def edit_shepherd(request, name): """Change the shepherd for a Document""" @@ -955,30 +947,29 @@ def edit_shepherd(request, name): doc = get_object_or_404(Document, type="draft", name=name) can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) - if not can_edit_stream_info: return HttpResponseForbidden("You do not have the necessary permissions to view this page") if request.method == 'POST': form = ShepherdForm(request.POST) if form.is_valid(): + save_document_in_history(doc) - if form.cleaned_data['shepherd']: - doc.shepherd = form.cleaned_data['shepherd'][0].person - else: - doc.shepherd = None + if form.cleaned_data['shepherd'] != doc.shepherd: + doc.shepherd = form.cleaned_data['shepherd'] + doc.save() - login = request.user.person - c = DocEvent(type="added_comment", doc=doc, by=login) - c.desc = "Document shepherd changed to "+ (doc.shepherd.name if doc.shepherd else "(None)") - c.save() + c = DocEvent(type="added_comment", doc=doc, by=request.user.person) + c.desc = "Document shepherd changed to "+ (doc.shepherd.person.name if doc.shepherd else "(None)") + c.save() if doc.shepherd.formatted_email() not in doc.notify: + login = request.user.person addrs = doc.notify if addrs: addrs += ', ' addrs += doc.shepherd.formatted_email() - e = make_notify_changed_event(request, doc, login, addrs, c.time) + make_notify_changed_event(request, doc, login, addrs, c.time) doc.notify = addrs doc.time = c.time @@ -987,19 +978,55 @@ def edit_shepherd(request, name): return redirect('doc_view', name=doc.name) else: - current_shepherd = None - if doc.shepherd: - e = doc.shepherd.email_set.order_by("-active", "-time") - if e: - current_shepherd=e[0].pk - init = { "shepherd": current_shepherd} - form = ShepherdForm(initial=init) + form = ShepherdForm(initial={ "shepherd": doc.shepherd_id }) - return render_to_response('doc/change_shepherd.html', - {'form': form, - 'doc': doc, - }, - context_instance = RequestContext(request)) + return render(request, 'doc/change_shepherd.html', { + 'form': form, + 'doc': doc, + }) + +class ChangeShepherdEmailForm(forms.Form): + shepherd = forms.ModelChoiceField(queryset=Email.objects.all(), label="Shepherd email", empty_label=None) + + def __init__(self, *args, **kwargs): + super(ChangeShepherdEmailForm, self).__init__(*args, **kwargs) + self.fields["shepherd"].queryset = self.fields["shepherd"].queryset.filter(person__email=self.initial["shepherd"]).distinct() + +def change_shepherd_email(request, name): + """Change the shepherd email address for a Document""" + doc = get_object_or_404(Document, name=name) + + if not doc.shepherd: + raise Http404 + + can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) + is_shepherd = user_is_person(request.user, doc.shepherd and doc.shepherd.person) + if not can_edit_stream_info and not is_shepherd: + return HttpResponseForbidden("You do not have the necessary permissions to view this page") + + initial = { "shepherd": doc.shepherd_id } + if request.method == 'POST': + form = ChangeShepherdEmailForm(request.POST, initial=initial) + if form.is_valid(): + if form.cleaned_data['shepherd'] != doc.shepherd: + save_document_in_history(doc) + + doc.shepherd = form.cleaned_data['shepherd'] + doc.save() + + c = DocEvent(type="added_comment", doc=doc, by=request.user.person) + c.desc = "Document shepherd email changed" + c.save() + + return redirect('doc_view', name=doc.name) + + else: + form = ChangeShepherdEmailForm(initial=initial) + + return render(request, 'doc/change_shepherd_email.html', { + 'form': form, + 'doc': doc, + }) class AdForm(forms.Form): ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), diff --git a/ietf/group/edit.py b/ietf/group/edit.py index a65372c63..2d9ce0942 100644 --- a/ietf/group/edit.py +++ b/ietf/group/edit.py @@ -19,7 +19,7 @@ from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStat from ietf.group.utils import save_group_in_history, can_manage_group_type from ietf.group.utils import get_group_or_404 from ietf.ietfauth.utils import has_role -from ietf.person.forms import EmailsField +from ietf.person.fields import AutocompletedEmailsField from ietf.person.models import Person, Email from ietf.group.mails import email_iesg_secretary_re_charter @@ -29,10 +29,11 @@ class GroupForm(forms.Form): name = forms.CharField(max_length=255, label="Name", required=True) acronym = forms.CharField(max_length=10, label="Acronym", required=True) state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True) - chairs = EmailsField(label="Chairs", required=False) - secretaries = EmailsField(label="Secretaries", required=False) - techadv = EmailsField(label="Technical Advisors", required=False) - delegates = EmailsField(label="Delegates", required=False, help_text=mark_safe("Type in name to search for person
Chairs can delegate the authority to update the state of group documents - max %s persons at a given time" % MAX_GROUP_DELEGATES)) + chairs = AutocompletedEmailsField(required=False, only_users=True) + secretaries = AutocompletedEmailsField(required=False, only_users=True) + techadv = AutocompletedEmailsField(label="Technical Advisors", required=False, only_users=True) + delegates = AutocompletedEmailsField(required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES, + help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - max %s persons at a given time" % MAX_GROUP_DELEGATES)) ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), label="Shepherding AD", empty_label="(None)", required=False) parent = forms.ModelChoiceField(Group.objects.filter(state="active").order_by('name'), empty_label="(None)", required=False) list_email = forms.CharField(max_length=64, required=False) diff --git a/ietf/group/views_stream.py b/ietf/group/views_stream.py index 26a452a90..48790beb3 100644 --- a/ietf/group/views_stream.py +++ b/ietf/group/views_stream.py @@ -10,7 +10,7 @@ from ietf.group.models import Group, GroupEvent, Role from ietf.group.utils import save_group_in_history from ietf.ietfauth.utils import has_role from ietf.name.models import StreamName -from ietf.person.forms import EmailsField +from ietf.person.fields import AutocompletedEmailsField from ietf.person.models import Email import debug # pyflakes:ignore @@ -31,7 +31,7 @@ def stream_documents(request, acronym): return render_to_response('group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta }, context_instance=RequestContext(request)) class StreamEditForm(forms.Form): - delegates = EmailsField(label="Delegates", required=False, help_text=u"Type in name to search for person") + delegates = AutocompletedEmailsField(required=False, only_users=True) def stream_edit(request, acronym): group = get_object_or_404(Group, acronym=acronym) diff --git a/ietf/idindex/index.py b/ietf/idindex/index.py index bfb5b9d58..e0cda9adc 100644 --- a/ietf/idindex/index.py +++ b/ietf/idindex/index.py @@ -13,7 +13,7 @@ from ietf.doc.models import Document, DocEvent, DocumentAuthor, RelatedDocument, from ietf.doc.models import LastCallDocEvent, NewRevisionDocEvent from ietf.doc.models import IESG_SUBSTATE_TAGS from ietf.group.models import Group -from ietf.person.models import Person +from ietf.person.models import Person, Email def all_id_txt(): # this returns a lot of data so try to be efficient @@ -125,8 +125,8 @@ def all_id2_txt(): else: l.append(a.author.person.plain_name()) - shepherds = dict((p.pk, p.formatted_email().replace('"', '')) - for p in Person.objects.filter(shepherd_document_set__type="draft").distinct()) + shepherds = dict((e.pk, e.formatted_email().replace('"', '')) + for e in Email.objects.filter(shepherd_document_set__type="draft").select_related("person").distinct()) ads = dict((p.pk, p.formatted_email().replace('"', '')) for p in Person.objects.filter(ad_document_set__type="draft").distinct()) diff --git a/ietf/idindex/tests.py b/ietf/idindex/tests.py index 485dc9978..602a45ef9 100644 --- a/ietf/idindex/tests.py +++ b/ietf/idindex/tests.py @@ -97,7 +97,7 @@ class IndexTests(TestCase): self.assertEqual(t[13], draft.title) author = draft.documentauthor_set.order_by("order").get() self.assertEqual(t[14], "%s <%s>" % (author.author.person.name, author.author.address)) - self.assertEqual(t[15], "%s <%s>" % (draft.shepherd, draft.shepherd.email_address())) + self.assertEqual(t[15], "%s <%s>" % (draft.shepherd.person.name, draft.shepherd.address)) self.assertEqual(t[16], "%s <%s>" % (draft.ad, draft.ad.email_address())) diff --git a/ietf/nomcom/forms.py b/ietf/nomcom/forms.py index 304d784e8..f6d7e3880 100644 --- a/ietf/nomcom/forms.py +++ b/ietf/nomcom/forms.py @@ -17,7 +17,8 @@ from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEM get_user_email, validate_private_key, validate_public_key, get_or_create_nominee, create_feedback_email) from ietf.person.models import Email -from ietf.utils import fields as custom_fields +from ietf.person.fields import AutocompletedEmailField +from ietf.utils.fields import MultiEmailField from ietf.utils.mail import send_mail @@ -31,10 +32,6 @@ def get_nomcom_group_or_404(year): nomcom__isnull=False) -def get_list(string): - return map(unicode.strip, string.replace('\r\n', '').split(',')) - - class PositionNomineeField(forms.ChoiceField): def __init__(self, *args, **kwargs): @@ -109,7 +106,7 @@ class BaseNomcomForm(object): class EditMembersForm(BaseNomcomForm, forms.Form): - members = custom_fields.MultiEmailField(label="Members email", required=False) + members = MultiEmailField(label="Members email", required=False, widget=forms.Textarea) fieldsets = [('Members', ('members',))] @@ -133,7 +130,7 @@ class EditMembersFormPreview(FormPreview): def preview_get(self, request): "Displays the form" - f = self.form(auto_id=AUTO_ID) + f = self.form(auto_id=self.get_auto_id(), initial=self.get_initial(request)) return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), @@ -143,14 +140,14 @@ class EditMembersFormPreview(FormPreview): 'selected': 'edit_members'}, context_instance=RequestContext(request)) - def parse_params(self, *args, **kwargs): + def get_initial(self, request): members = self.group.role_set.filter(name__slug='member') - if members: - self.form.base_fields['members'].initial = ',\r\n'.join([role.email.address for role in members]) + return { "members": ",\r\n".join(role.email.address for role in members) } + return {} def process_preview(self, request, form, context): - members_email = get_list(form.cleaned_data['members']) + members_email = form.cleaned_data['members'] members_info = [] emails_not_found = [] @@ -233,10 +230,11 @@ class EditChairFormPreview(FormPreview): return super(EditChairFormPreview, self).__call__(request, *args, **kwargs) - def parse_params(self, *args, **kwargs): + def get_initial(self, request): chair = self.group.get_chair() if chair: - self.form.base_fields['chair'].initial = chair.email.address + return { "chair": chair.email.address } + return {} def process_preview(self, request, form, context): chair_email = form.cleaned_data['chair'] @@ -297,8 +295,8 @@ class EditNomcomForm(BaseNomcomForm, forms.ModelForm): class MergeForm(BaseNomcomForm, forms.Form): - secondary_emails = custom_fields.MultiEmailField(label="Secondary email addresses", - help_text="Provide a comma separated list of email addresses. Nominations already received with any of these email address will be moved to show under the primary address") + secondary_emails = MultiEmailField(label="Secondary email addresses", + help_text="Provide a comma separated list of email addresses. Nominations already received with any of these email address will be moved to show under the primary address", widget=forms.Textarea) primary_email = forms.EmailField(label="Primary email address", widget=forms.TextInput(attrs={'size': '40'})) @@ -312,22 +310,21 @@ class MergeForm(BaseNomcomForm, forms.Form): email = self.cleaned_data['primary_email'] nominees = Nominee.objects.get_by_nomcom(self.nomcom).not_duplicated().filter(email__address=email) if not nominees: - msg = "Does not exist a nomiee with this email" + msg = "No nominee with this email exists" self._errors["primary_email"] = self.error_class([msg]) return email def clean_secondary_emails(self): - data = self.cleaned_data['secondary_emails'] - emails = get_list(data) + emails = self.cleaned_data['secondary_emails'] for email in emails: nominees = Nominee.objects.get_by_nomcom(self.nomcom).not_duplicated().filter(email__address=email) if not nominees: - msg = "Does not exist a nomiee with email %s" % email + msg = "No nominee with email %s exists" % email self._errors["primary_email"] = self.error_class([msg]) break - return data + return emails def clean(self): primary_email = self.cleaned_data.get("primary_email") @@ -340,7 +337,7 @@ class MergeForm(BaseNomcomForm, forms.Form): def save(self): primary_email = self.cleaned_data.get("primary_email") - secondary_emails = get_list(self.cleaned_data.get("secondary_emails")) + secondary_emails = self.cleaned_data.get("secondary_emails") primary_nominee = Nominee.objects.get_by_nomcom(self.nomcom).get(email__address=primary_email) while primary_nominee.duplicated: @@ -659,6 +656,8 @@ class PositionForm(BaseNomcomForm, forms.ModelForm): fieldsets = [('Position', ('name', 'description', 'is_open', 'incumbent'))] + incumbent = AutocompletedEmailField(required=False) + class Meta: model = Position fields = ('name', 'description', 'is_open', 'incumbent') diff --git a/ietf/person/fields.py b/ietf/person/fields.py new file mode 100644 index 000000000..11462bc41 --- /dev/null +++ b/ietf/person/fields.py @@ -0,0 +1,123 @@ +import json + +from django.utils.html import escape +from django import forms +from django.core.urlresolvers import reverse as urlreverse + +import debug # pyflakes:ignore + +from ietf.person.models import Email, Person + +def tokeninput_id_name_json(objs): + def format_email(e): + return escape(u"%s <%s>" % (e.person.name, e.address)) + def format_person(p): + return escape(p.name) + + formatter = format_email if objs and isinstance(objs[0], Email) else format_person + + return json.dumps([{ "id": o.pk, "name": formatter(o) } for o in objs]) + +class AutocompletedPersonsField(forms.CharField): + """Tokenizing autocompleted multi-select field for choosing + persons/emails or just persons using jquery.tokeninput.js. + + The field operates on either Email or Person models. In the case + of Email models, the person name is shown next to the email address. + + The field uses a comma-separated list of primary keys in a + CharField element as its API, the tokeninput Javascript adds some + selection magic on top of this so we have to pass it a JSON + representation of ids and user-understandable labels.""" + + def __init__(self, + max_entries=None, # max number of selected objs + only_users=False, # only select persons who also have a user + model=Person, # or Email + hint_text="Type in name to search for person", + *args, **kwargs): + kwargs["max_length"] = 1000 + self.max_entries = max_entries + self.only_users = only_users + self.model = model + + super(AutocompletedPersonsField, self).__init__(*args, **kwargs) + + self.widget.attrs["class"] = "tokenized-field" + self.widget.attrs["data-hint-text"] = hint_text + if self.max_entries != None: + self.widget.attrs["data-max-entries"] = self.max_entries + + def parse_tokenized_value(self, value): + return [x.strip() for x in value.split(",") if x.strip()] + + def prepare_value(self, value): + if not value: + value = "" + if isinstance(value, basestring): + pks = self.parse_tokenized_value(value) + value = self.model.objects.filter(pk__in=pks).select_related("person") + if isinstance(value, self.model): + value = [value] + + self.widget.attrs["data-pre"] = tokeninput_id_name_json(value) + + # doing this in the constructor is difficult because the URL + # patterns may not have been fully constructed there yet + self.widget.attrs["data-ajax-url"] = urlreverse("ajax_tokeninput_search", kwargs={ "model_name": self.model.__name__.lower() }) + if self.only_users: + self.widget.attrs["data-ajax-url"] += "?user=1" # require a Datatracker account + + return ",".join(e.address for e in value) + + def clean(self, value): + value = super(AutocompletedPersonsField, self).clean(value) + pks = self.parse_tokenized_value(value) + + objs = self.model.objects.filter(pk__in=pks) + if self.model == Email: + objs = objs.exclude(person=None).select_related("person") + + # there are still a couple of active roles without accounts so don't disallow those yet + #if self.only_users: + # objs = objs.exclude(person__user=None) + + found_pks = [str(o.pk) for o in objs] + failed_pks = [x for x in pks if x not in found_pks] + if failed_pks: + raise forms.ValidationError(u"Could not recognize the following {model_name}s: {pks}. You can only input {model_name}s already registered in the Datatracker.".format(pks=", ".join(failed_pks), model_name=self.model.__name__.lower())) + + if self.max_entries != None and len(objs) > self.max_entries: + raise forms.ValidationError(u"You can select at most %s entries only." % self.max_entries) + + return objs + +class AutocompletedPersonField(AutocompletedPersonsField): + """Version of AutocompletedPersonsField specialized to a single object.""" + + def __init__(self, *args, **kwargs): + kwargs["max_entries"] = 1 + super(AutocompletedPersonField, self).__init__(*args, **kwargs) + + def clean(self, value): + return super(AutocompletedPersonField, self).clean(value).first() + + +class AutocompletedEmailsField(AutocompletedPersonsField): + """Version of AutocompletedPersonsField with the defaults right for Emails.""" + + def __init__(self, model=Email, hint_text="Type in name or email to search for person and email address", + *args, **kwargs): + super(AutocompletedEmailsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs) + +class AutocompletedEmailField(AutocompletedEmailsField): + """Version of AutocompletedEmailsField specialized to a single object.""" + + def __init__(self, *args, **kwargs): + kwargs["max_entries"] = 1 + super(AutocompletedEmailField, self).__init__(*args, **kwargs) + + def clean(self, value): + return super(AutocompletedEmailField, self).clean(value).first() + + diff --git a/ietf/person/forms.py b/ietf/person/forms.py deleted file mode 100644 index f4a1c9d3e..000000000 --- a/ietf/person/forms.py +++ /dev/null @@ -1,44 +0,0 @@ -import json - -from django.utils.html import escape -from django.utils.functional import lazy -from django import forms -from django.core.urlresolvers import reverse as urlreverse - -import debug # pyflakes:ignore - -from ietf.person.models import Email - -def json_emails(emails): - if isinstance(emails, basestring): - emails = Email.objects.filter(address__in=[x.strip() for x in emails.split(",") if x.strip()]).select_related("person") - return json.dumps([{"id": e.address + "", "name": escape(u"%s <%s>" % (e.person.name, e.address))} for e in emails]) - -class EmailsField(forms.CharField): - """Multi-select field using jquery.tokeninput.js. Since the API of - tokeninput" is asymmetric, we have to pass it a JSON - representation on the way out and parse the ids coming back as a - comma-separated list on the way in.""" - - def __init__(self, *args, **kwargs): - kwargs["max_length"] = 1000 - if not "help_text" in kwargs: - kwargs["help_text"] = "Type in name to search for person" - super(EmailsField, self).__init__(*args, **kwargs) - self.widget.attrs["class"] = "tokenized-field" - self.widget.attrs["data-ajax-url"] = lazy(urlreverse, str)("ajax_search_emails") # make this lazy to prevent initialization problem - - def parse_tokenized_value(self, value): - return Email.objects.filter(address__in=[x.strip() for x in value.split(",") if x.strip()]).select_related("person") - - def prepare_value(self, value): - if not value: - return "" - if isinstance(value, str) or isinstance(value, unicode): - value = self.parse_tokenized_value(value) - return json_emails(value) - - def clean(self, value): - value = super(EmailsField, self).clean(value) - return self.parse_tokenized_value(value) - diff --git a/ietf/person/models.py b/ietf/person/models.py index 60b50d5a9..9842d9297 100644 --- a/ietf/person/models.py +++ b/ietf/person/models.py @@ -56,15 +56,15 @@ class PersonInfo(models.Model): return e[0] return None def email_address(self): - e = self.email_set.filter(active=True).order_by("-time") + e = self.email_set.filter(active=True).order_by("-time").first() if e: - return e[0].address + return e.address else: return "" def formatted_email(self): - e = self.email_set.order_by("-active", "-time") + e = self.email_set.order_by("-active", "-time").first() if e: - return e[0].formatted_email() + return e.formatted_email() else: return "" def full_name_as_key(self): diff --git a/ietf/person/tests.py b/ietf/person/tests.py index 91cea95d1..d044d45f5 100644 --- a/ietf/person/tests.py +++ b/ietf/person/tests.py @@ -11,7 +11,7 @@ class PersonTests(TestCase): draft = make_test_data() person = draft.ad - r = self.client.get(urlreverse("ietf.person.views.ajax_search_emails"), dict(q=person.name)) + r = self.client.get(urlreverse("ietf.person.views.ajax_tokeninput_search", kwargs={ "model_name": "email"}), dict(q=person.name)) self.assertEqual(r.status_code, 200) data = json.loads(r.content) self.assertEqual(data[0]["id"], person.email_address()) diff --git a/ietf/person/urls.py b/ietf/person/urls.py index 27fbe9c20..8392fb51d 100644 --- a/ietf/person/urls.py +++ b/ietf/person/urls.py @@ -2,6 +2,6 @@ from django.conf.urls import patterns from ietf.person import ajax urlpatterns = patterns('', - (r'^search/$', "ietf.person.views.ajax_search_emails", None, 'ajax_search_emails'), + (r'^search/(?P(person|email))/$', "ietf.person.views.ajax_tokeninput_search", None, 'ajax_tokeninput_search'), (r'^(?P[a-z0-9]+).json$', ajax.person_json), ) diff --git a/ietf/person/views.py b/ietf/person/views.py index e4d13a4f5..7e61e727a 100644 --- a/ietf/person/views.py +++ b/ietf/person/views.py @@ -1,8 +1,44 @@ from django.http import HttpResponse +from django.db.models import Q -from ietf.person.models import Email -from ietf.person.forms import json_emails +from ietf.person.models import Email, Person +from ietf.person.fields import tokeninput_id_name_json -def ajax_search_emails(request): - emails = Email.objects.filter(person__alias__name__icontains=request.GET.get('q','')).filter(active='true').order_by('person__name').distinct()[:10] - return HttpResponse(json_emails(emails), content_type='application/json') +def ajax_tokeninput_search(request, model_name): + if model_name == "email": + model = Email + else: + model = Person + + q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()] + + if not q: + objs = model.objects.none() + else: + query = Q() + for t in q: + if model == Email: + query &= Q(person__alias__name__icontains=t) | Q(address__icontains=t) + elif model == Person: + if "@" in t: # allow searching email address if there's a @ in the search term + query &= Q(alias__name__icontains=t) | Q(email__address__icontains=t) + else: + query &= Q(alias__name__icontains=t) + + objs = model.objects.filter(query) + + # require an account at the Datatracker + only_users = request.GET.get("user") == "1" + + if model == Email: + objs = objs.filter(active=True).order_by('person__name').exclude(person=None) + if only_users: + objs = objs.exclude(person__user=None) + elif model == Person: + objs = objs.order_by("name") + if only_users: + objs = objs.exclude(user=None) + + objs = objs.distinct()[:10] + + return HttpResponse(tokeninput_id_name_json(objs), content_type='application/json') diff --git a/ietf/secr/announcement/forms.py b/ietf/secr/announcement/forms.py index 95c499efb..29c49c08c 100644 --- a/ietf/secr/announcement/forms.py +++ b/ietf/secr/announcement/forms.py @@ -1,10 +1,10 @@ from django import forms -from django.core.validators import validate_email from ietf.group.models import Group, Role from ietf.ietfauth.utils import has_role from ietf.message.models import Message from ietf.secr.utils.group import current_nomcom +from ietf.utils.fields import MultiEmailField # --------------------------------------------- # Globals @@ -42,33 +42,6 @@ TO_LIST = ('IETF Announcement List ', 'Working Group Chairs ', 'BoF Chairs ', 'Other...') -# --------------------------------------------- -# Custom Fields -# --------------------------------------------- - -class MultiEmailField(forms.Field): - def to_python(self, value): - "Normalize data to a list of strings." - - # Return an empty list if no input was given. - if not value: - return [] - - import types - if isinstance(value, types.StringTypes): - values = value.split(',') - return [ x.strip() for x in values ] - else: - return value - - def validate(self, value): - "Check if value consists only of valid emails." - - # Use the parent's handling of required fields, etc. - super(MultiEmailField, self).validate(value) - - for email in value: - validate_email(email) # --------------------------------------------- # Helper Functions diff --git a/ietf/secr/drafts/email.py b/ietf/secr/drafts/email.py index 96e33a8f6..a06b3aa62 100644 --- a/ietf/secr/drafts/email.py +++ b/ietf/secr/drafts/email.py @@ -154,7 +154,7 @@ def get_fullcc_list(draft): # add sheperd if draft.shepherd: - emails[draft.shepherd.email_address()] = '"%s"' % (draft.shepherd.name) + emails[draft.shepherd.address] = '"%s"' % (draft.shepherd.person.name) # use sort so we get consistently ordered lists result_list = [] diff --git a/ietf/secr/drafts/forms.py b/ietf/secr/drafts/forms.py index c4060e7c6..58d5a6ed6 100644 --- a/ietf/secr/drafts/forms.py +++ b/ietf/secr/drafts/forms.py @@ -8,6 +8,7 @@ from ietf.doc.models import Document, DocAlias, State from ietf.name.models import IntendedStdLevelName, DocRelationshipName from ietf.group.models import Group from ietf.person.models import Person, Email +from ietf.person.fields import AutocompletedEmailField from ietf.secr.groups.forms import get_person @@ -131,7 +132,7 @@ class EditModelForm(forms.ModelForm): iesg_state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft-iesg'),required=False) group = GroupModelChoiceField(required=True) review_by_rfc_editor = forms.BooleanField(required=False) - shepherd = forms.CharField(max_length=100,widget=forms.TextInput(attrs={'class':'name-autocomplete'}),help_text="To see a list of people type the first name, or last name, or both.",required=False) + shepherd = AutocompletedEmailField(required=False, only_users=True) class Meta: model = Document @@ -148,8 +149,6 @@ class EditModelForm(forms.ModelForm): self.initial['state'] = self.instance.get_state().pk if self.instance.get_state('draft-iesg'): self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk - if self.instance.shepherd: - self.initial['shepherd'] = "%s - (%s)" % (self.instance.shepherd.name, self.instance.shepherd.id) # setup special fields if self.instance: @@ -191,16 +190,6 @@ class EditModelForm(forms.ModelForm): raise forms.ValidationError("ERROR: Draft does not exist") return name - # check for id within parenthesis to ensure name was selected from the list - def clean_shepherd(self): - person = self.cleaned_data.get('shepherd', '') - m = re.search(r'(\d+)', person) - if person and not m: - raise forms.ValidationError("You must select an entry from the list!") - - # return person object - return get_person(person) - def clean(self): super(EditModelForm, self).clean() cleaned_data = self.cleaned_data diff --git a/ietf/secr/drafts/views.py b/ietf/secr/drafts/views.py index b6e388abb..ce99d03bd 100644 --- a/ietf/secr/drafts/views.py +++ b/ietf/secr/drafts/views.py @@ -7,7 +7,7 @@ from django.conf import settings from django.contrib import messages from django.db.models import Max from django.forms.formsets import formset_factory -from django.shortcuts import render_to_response, get_object_or_404, redirect +from django.shortcuts import render_to_response, get_object_or_404, redirect, render from django.template import RequestContext from django.template.loader import render_to_string @@ -799,10 +799,9 @@ def edit(request, id): else: form = EditModelForm(instance=draft) - return render_to_response('drafts/edit.html', { + return render(request, 'drafts/edit.html', { 'form': form, 'draft': draft}, - RequestContext(request, {}), ) def email(request, id): diff --git a/ietf/secr/sreq/forms.py b/ietf/secr/sreq/forms.py index 3db0ec3ce..20cf544c3 100644 --- a/ietf/secr/sreq/forms.py +++ b/ietf/secr/sreq/forms.py @@ -2,7 +2,7 @@ from django import forms from ietf.group.models import Group from ietf.meeting.models import ResourceAssociation -from ietf.person.forms import EmailsField +from ietf.person.fields import AutocompletedPersonsField # ------------------------------------------------- @@ -67,7 +67,7 @@ class SessionForm(forms.Form): wg_selector3 = forms.ChoiceField(choices=WG_CHOICES,required=False) third_session = forms.BooleanField(required=False) resources = forms.MultipleChoiceField(choices=[(x.pk,x.desc) for x in ResourceAssociation.objects.all()], widget=forms.CheckboxSelectMultiple,required=False) - bethere = EmailsField(label="Must be present", required=False) + bethere = AutocompletedPersonsField(label="Must be present", required=False) def __init__(self, *args, **kwargs): super(SessionForm, self).__init__(*args, **kwargs) diff --git a/ietf/secr/sreq/views.py b/ietf/secr/sreq/views.py index 9c4c1b75a..db411d90b 100644 --- a/ietf/secr/sreq/views.py +++ b/ietf/secr/sreq/views.py @@ -16,7 +16,7 @@ from ietf.secr.utils.decorators import check_permissions, sec_only from ietf.secr.utils.group import groups_by_session from ietf.secr.utils.mail import get_ad_email_list, get_chair_email_list, get_cc_list from ietf.utils.mail import send_mail -from ietf.person.models import Email +from ietf.person.models import Person # ------------------------------------------------- # Globals @@ -48,15 +48,8 @@ def get_initial_session(sessions): group = sessions[0].group conflicts = group.constraint_source_set.filter(meeting=meeting) - bethere_people = [x.person for x in sessions[0].constraints().filter(name='bethere')] - bethere_email = [] - for person in bethere_people: - e = person.email_set.order_by("-active","-time").first() - if e: - bethere_email.append(e) - # even if there are three sessions requested, the old form has 2 in this field - initial['num_session'] = sessions.count() if sessions.count() <= 2 else 2 + initial['num_session'] = min(sessions.count(), 2) # accessing these foreign key fields throw errors if they are unset so we # need to catch these @@ -72,7 +65,7 @@ def get_initial_session(sessions): initial['conflict3'] = ' '.join([ c.target.acronym for c in conflicts.filter(name__slug='conflic3') ]) initial['comments'] = sessions[0].comments initial['resources'] = sessions[0].resources.all() - initial['bethere'] = bethere_email + initial['bethere'] = [x.person for x in sessions[0].constraints().filter(name='bethere').select_related("person")] return initial def get_lock_message(meeting=None): @@ -234,6 +227,7 @@ def confirm(request, acronym): This view displays details of the new session that has been requested for the user to confirm for submission. ''' + # FIXME: this should be using form.is_valid/form.cleaned_data - invalid input will make it crash querydict = request.session.get('session_form',None) if not querydict: raise Http404 @@ -241,7 +235,7 @@ def confirm(request, acronym): if 'resources' in form: form['resources'] = [ ResourceAssociation.objects.get(pk=pk) for pk in form['resources'].split(',')] if 'bethere' in form: - form['bethere'] = [Email.objects.get(address=addr) for addr in form['bethere'].split(',')] + form['bethere'] = Person.objects.filter(pk__in=form['bethere'].split(',')) meeting = get_meeting() group = get_object_or_404(Group,acronym=acronym) login = request.user.person @@ -285,8 +279,8 @@ def confirm(request, acronym): if 'bethere' in form: bethere_cn = ConstraintName.objects.get(slug='bethere') - for email in form['bethere']: - Constraint.objects.create(name=bethere_cn,source=group,person=email.person,meeting=new_session.meeting) + for p in form['bethere']: + Constraint.objects.create(name=bethere_cn, source=group, person=p, meeting=new_session.meeting) # deprecated in new schema # log activity @@ -318,14 +312,8 @@ def add_essential_people(group,initial): people = set() if 'bethere' in initial: people.update(initial['bethere']) - for role in group.role_set.filter(name='chair'): - e = role.person.email_set.order_by("-active","-time").first() - if e: - people.add(e) - if group.ad: - e = group.ad.email_set.order_by("-active","-time").first() - if e: - people.add(e) + people.update(Person.objects.filter(role__group=group, role__name='chair')) + people.add(group.ad) initial['bethere'] = list(people) @@ -447,8 +435,8 @@ def edit_mtg(request, num, acronym): if 'bethere' in form.changed_data and set(form.cleaned_data['bethere'])!=set(initial['bethere']): session.constraints().filter(name='bethere').delete() bethere_cn = ConstraintName.objects.get(slug='bethere') - for email in form.cleaned_data['bethere']: - Constraint.objects.create(name=bethere_cn,source=group,person=email.person,meeting=session.meeting) + for p in form.cleaned_data['bethere']: + Constraint.objects.create(name=bethere_cn, source=group, person=p, meeting=session.meeting) # deprecated # log activity diff --git a/ietf/secr/templates/announcement/confirm.html b/ietf/secr/templates/announcement/confirm.html index b65caa6ec..4a3b4cdd7 100644 --- a/ietf/secr/templates/announcement/confirm.html +++ b/ietf/secr/templates/announcement/confirm.html @@ -21,7 +21,7 @@ To: {{ to }} From: {{ message.frm }} Cc: {{ message.cc }} -Bcc: {{ message.bccc }} +Bcc: {{ message.bcc }} Reply To: {{ message.reply_to }} Subject: {{ message.subject }} diff --git a/ietf/secr/templates/drafts/edit.html b/ietf/secr/templates/drafts/edit.html index 9ccb60e10..1c29810f0 100644 --- a/ietf/secr/templates/drafts/edit.html +++ b/ietf/secr/templates/drafts/edit.html @@ -7,6 +7,9 @@ + + + {% endblock %} {% block breadcrumbs %}{{ block.super }} diff --git a/ietf/secr/templates/drafts/view.html b/ietf/secr/templates/drafts/view.html index 256e69ff9..eea7ac7c0 100644 --- a/ietf/secr/templates/drafts/view.html +++ b/ietf/secr/templates/drafts/view.html @@ -23,7 +23,7 @@ Area:{% if draft.group.parent %}{{ draft.group.parent }}{% endif %} Group:{% if draft.group %}{{ draft.group.acronym }}{% endif %} Area Director:{{ draft.ad }} - Shepherd:{{ draft.shepherd }} + Shepherd:{% if draft.shepherd %}{{ draft.shepherd.person }} <{{ draft.shepherd.address }}>{% else %}None{% endif %} Notify:{{ draft.notify }} Document State:{{ draft.get_state }} IESG State:{{ draft.iesg_state }} diff --git a/ietf/secr/templates/includes/sessions_request_view.html b/ietf/secr/templates/includes/sessions_request_view.html index 54e08719b..90d26a3f8 100644 --- a/ietf/secr/templates/includes/sessions_request_view.html +++ b/ietf/secr/templates/includes/sessions_request_view.html @@ -32,7 +32,7 @@ People who must be present: - {% if session.bethere %}
    {% for email in session.bethere %}
  • {{ email.person }}
  • {% endfor %}
{% else %}None{% endif %} + {% if session.bethere %}
    {% for person in session.bethere %}
  • {{ person }}
  • {% endfor %}
{% else %}None{% endif %} {% autoescape off %} Special Requests:{{ session.comments }} {% endautoescape %} diff --git a/ietf/templates/community/public/atom.xml b/ietf/templates/community/public/atom.xml index c6614f4c4..b527bac37 100644 --- a/ietf/templates/community/public/atom.xml +++ b/ietf/templates/community/public/atom.xml @@ -33,8 +33,8 @@ {{ entry.type }} {% if entry.doc.stream.slug %}{{ entry.doc.stream.slug }}{% endif %} {{ entry.doc.group.acronym }} - {% if entry.doc.shepherd.name %}{{entry.doc.shepherd.name }}{% endif %} - {% if entry.doc.ad.name %}{{entry.doc.ad.name}}{% endif %} + {% if entry.doc.shepherd %}{{ entry.doc.shepherd.person.name }}{% endif %} + {% if entry.doc.ad %}{{entry.doc.ad.name}}{% endif %} {% for state in entry.doc.states.all %} {{ state.slug }} {% endfor %} diff --git a/ietf/templates/doc/change_shepherd.html b/ietf/templates/doc/change_shepherd.html index 4499801b9..e0559cd29 100644 --- a/ietf/templates/doc/change_shepherd.html +++ b/ietf/templates/doc/change_shepherd.html @@ -18,7 +18,10 @@ Change the document shepherd for {{ doc.name }}-{{ doc.rev }} {% block content %}

Change the document shepherd for {{ doc.name }}-{{ doc.rev }}

-
{% csrf_token %} +

The shepherd needs to have a Datatracker account. A new account can be + created here.

+ +{% csrf_token %} {% for field in form.visible_fields %} @@ -33,8 +36,8 @@ Change the document shepherd for {{ doc.name }}-{{ doc.rev }}
- Back - + Cancel +
diff --git a/ietf/templates/doc/change_shepherd_email.html b/ietf/templates/doc/change_shepherd_email.html new file mode 100644 index 000000000..b671466bc --- /dev/null +++ b/ietf/templates/doc/change_shepherd_email.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block title %} +Change the document shepherd email for {{ doc.name }}-{{ doc.rev }} +{% endblock %} + +{% block pagehead %} + +{% endblock %} + +{% block content %} +

Change the document shepherd email for {{ doc.name }}-{{ doc.rev }}

+ +{% csrf_token %} + + {{ form.as_table }} + + + + +
+ Cancel + +
+
+{% endblock %} diff --git a/ietf/templates/doc/document_draft.html b/ietf/templates/doc/document_draft.html index 0985e3302..349f845c4 100644 --- a/ietf/templates/doc/document_draft.html +++ b/ietf/templates/doc/document_draft.html @@ -131,8 +131,8 @@ Document shepherd: - - {{ doc.shepherd|default:"No shepherd assigned" }} + + {% if doc.shepherd %}{{ doc.shepherd.person }}{% else %}No shepherd assigned{% endif %} diff --git a/ietf/templates/doc/search/search_result_row.html b/ietf/templates/doc/search/search_result_row.html index 02c2d1cc3..ebd86c31d 100644 --- a/ietf/templates/doc/search/search_result_row.html +++ b/ietf/templates/doc/search/search_result_row.html @@ -78,7 +78,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% if ad_name == None or ad_name != doc.ad.plain_name %} -{{ doc.ad|default:"" }}
{{doc.shepherd|default:""}}
+{{ doc.ad|default:"" }}
{% if doc.shepherd %}{{ doc.shepherd.person }}{% endif %}
{% endif %} diff --git a/ietf/templates/doc/submit_to_iesg.html b/ietf/templates/doc/submit_to_iesg.html index d5be56dc7..564769f2a 100644 --- a/ietf/templates/doc/submit_to_iesg.html +++ b/ietf/templates/doc/submit_to_iesg.html @@ -29,7 +29,7 @@ Please verify the following information: Document Shepherd: {% if warn.shepherd %}{% endif %} - {{doc.shepherd}} + {% if doc.shepherd %}{{ doc.shepherd.person }}{% endif %} diff --git a/ietf/templates/nomcom/edit_position.html b/ietf/templates/nomcom/edit_position.html index be1ed7ce5..3add94e61 100644 --- a/ietf/templates/nomcom/edit_position.html +++ b/ietf/templates/nomcom/edit_position.html @@ -1,5 +1,14 @@ {% extends "nomcom/nomcom_private_base.html" %} +{% block pagehead %} + +{% endblock %} + +{% block content_end %} + + +{% endblock %} + {% block nomcom_content %}

{% if position %}Edit{% else %}Add{% endif %} position

diff --git a/ietf/templates/nomcom/list_positions.html b/ietf/templates/nomcom/list_positions.html index 398a57fe7..d6a193415 100644 --- a/ietf/templates/nomcom/list_positions.html +++ b/ietf/templates/nomcom/list_positions.html @@ -11,7 +11,7 @@
Description:
{{ position.description }}
Incumbent:
-
{{ position.incumbent }}
+
{% if position.incumbent %}{{ position.incumbent.person }} <{{ position.incumbent.address }}>{% else %}None{% endif %}
Is open:
{{ position.is_open }}
Templates:
diff --git a/ietf/utils/fields.py b/ietf/utils/fields.py index 9508bd31e..e5c0dfeb2 100644 --- a/ietf/utils/fields.py +++ b/ietf/utils/fields.py @@ -1,23 +1,25 @@ from django import forms -from django.core.validators import validate_email, ValidationError +from django.core.validators import validate_email +class MultiEmailField(forms.Field): + def to_python(self, value): + "Normalize data to a list of strings." -class MultiEmailField(forms.CharField): - widget = forms.widgets.Textarea - - def clean(self, value): - super(MultiEmailField, self).clean(value) + # Return an empty list if no input was given. if not value: + return [] + + if isinstance(value, basestring): + values = value.split(',') + return [ x.strip() for x in values if x.strip() ] + else: return value - if value.endswith(','): - value = value[:-1] - emails = [v.strip() for v in value.split(',') if v.strip()] + def validate(self, value): + "Check if value consists only of valid emails." - for email in emails: - try: - validate_email(email) - except ValidationError: - raise ValidationError("This is not a valid comma separated email list.") + # Use the parent's handling of required fields, etc. + super(MultiEmailField, self).validate(value) - return value + for email in value: + validate_email(email) diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index f06f9ba7c..73b405f3a 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -172,7 +172,7 @@ def make_test_data(): u.set_password("plain+password") u.save() plainman = Person.objects.create(name="Plain Man", ascii="Plain Man", user=u) - email = Email.objects.create(address="plain@example.com", person=plainman) # pyflakes:ignore + email = Email.objects.create(address="plain@example.com", person=plainman) # group personnel create_person(mars_wg, "chair", name="WG Chair Man", username="marschairman") @@ -194,7 +194,7 @@ def make_test_data(): rev="01", pages=2, intended_std_level_id="ps", - shepherd=plainman, + shepherd=email, ad=ad, expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), notify="aliens@example.mars", diff --git a/static/css/base2.css b/static/css/base2.css index ebcca78d8..eb224e81e 100644 --- a/static/css/base2.css +++ b/static/css/base2.css @@ -310,6 +310,10 @@ div.prompt { border: 1px dashed red; background-color: #ffeeaa; padding: 1em 2em margin-left: 150px; } +.baseform .fieldWidget ul.token-input-list { + clear: none; +} + #feedbackformset .fieldWidget { margin-left: 0px; } diff --git a/static/js/lib/jquery.tokeninput.js b/static/js/lib/jquery.tokeninput.js index 4d01f392f..87641a57a 100644 --- a/static/js/lib/jquery.tokeninput.js +++ b/static/js/lib/jquery.tokeninput.js @@ -1,6 +1,6 @@ /* * jQuery Plugin: Tokenizing Autocomplete Text Entry - * Version 1.5.0 + * Version 1.6.0 * * Copyright (c) 2009 James Smith (http://loopj.com) * Licensed jointly under the GPL and MIT licenses, @@ -11,26 +11,46 @@ (function ($) { // Default settings var DEFAULT_SETTINGS = { + // Search settings + method: "GET", + contentType: "json", + queryParam: "q", + searchDelay: 300, + minChars: 1, + propertyToSearch: "name", + jsonContainer: null, + + // Display settings hintText: "Type in a search term", noResultsText: "No results", searchingText: "Searching...", deleteText: "×", - searchDelay: 300, - minChars: 1, + animateDropdown: true, + + // Tokenization settings tokenLimit: null, - jsonContainer: null, - method: "GET", - contentType: "json", - queryParam: "q", tokenDelimiter: ",", preventDuplicates: false, + + // Output settings + tokenValue: "id", + + // Prepopulation settings prePopulate: null, processPrePopulate: false, - animateDropdown: true, + + // Manipulation settings + idPrefix: "token-input-", + + // Formatters + resultsFormatter: function(item){ return "
  • " + item[this.propertyToSearch]+ "
  • " }, + tokenFormatter: function(item) { return "
  • " + item[this.propertyToSearch] + "

  • " }, + + // Callbacks onResult: null, onAdd: null, onDelete: null, - idPrefix: "token-input-" + onReady: null }; // Default classes to use when theming @@ -93,7 +113,10 @@ var methods = { remove: function(item) { this.data("tokenInputObject").remove(item); return this; - } + }, + get: function() { + return this.data("tokenInputObject").getTokens(); + } } // Expose the .tokenInput function to jQuery as a plugin @@ -113,16 +136,19 @@ $.TokenList = function (input, url_or_data, settings) { // // Configure the data source - if(typeof(url_or_data) === "string") { + if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") { // Set the url to query against settings.url = url_or_data; + // If the URL is a function, evaluate it here to do our initalization work + var url = computeURL(); + // Make a smart guess about cross-domain if it wasn't explicitly specified if(settings.crossDomain === undefined) { - if(settings.url.indexOf("://") === -1) { + if(url.indexOf("://") === -1) { settings.crossDomain = false; } else { - settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]); + settings.crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]); } } } else if(typeof(url_or_data) === "object") { @@ -223,6 +249,7 @@ $.TokenList = function (input, url_or_data, settings) { if(!$(this).val().length) { if(selected_token) { delete_token($(selected_token)); + hidden_input.change(); } else if(previous_token.length) { select_token($(previous_token.get(0))); } @@ -242,6 +269,7 @@ $.TokenList = function (input, url_or_data, settings) { case KEY.COMMA: if(selected_dropdown_item) { add_token($(selected_dropdown_item).data("tokeninput")); + hidden_input.change(); return false; } break; @@ -346,6 +374,10 @@ $.TokenList = function (input, url_or_data, settings) { }); } + // Initialization is done + if($.isFunction(settings.onReady)) { + settings.onReady.call(); + } // // Public functions @@ -380,6 +412,10 @@ $.TokenList = function (input, url_or_data, settings) { } }); } + + this.getTokens = function() { + return saved_tokens; + } // // Private functions @@ -390,8 +426,6 @@ $.TokenList = function (input, url_or_data, settings) { input_box.hide(); hide_dropdown(); return; - } else { - input_box.focus(); } } @@ -413,7 +447,8 @@ $.TokenList = function (input, url_or_data, settings) { // Inner function to a token to the list function insert_token(item) { - var this_token = $("
  • "+ item.name +"

  • ") + var this_token = settings.tokenFormatter(item); + this_token = $(this_token) .addClass(settings.classes.token) .insertBefore(input_token); @@ -423,11 +458,13 @@ $.TokenList = function (input, url_or_data, settings) { .appendTo(this_token) .click(function () { delete_token($(this).parent()); + hidden_input.change(); return false; }); // Store data on the token - var token_data = {"id": item.id, "name": item.name}; + var token_data = {"id": item.id}; + token_data[settings.propertyToSearch] = item[settings.propertyToSearch]; $.data(this_token.get(0), "tokeninput", item); // Save this token for duplicate checking @@ -435,13 +472,16 @@ $.TokenList = function (input, url_or_data, settings) { selected_token_index++; // Update the hidden input - var token_ids = $.map(saved_tokens, function (el) { - return el.id; - }); - hidden_input.val(token_ids.join(settings.tokenDelimiter)); + update_hidden_input(saved_tokens, hidden_input); token_count += 1; + // Check the token limit + if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) { + input_box.hide(); + hide_dropdown(); + } + return this_token; } @@ -470,8 +510,10 @@ $.TokenList = function (input, url_or_data, settings) { } // Insert the new tokens - insert_token(item); - checkTokenLimit(); + if(settings.tokenLimit == null || token_count < settings.tokenLimit) { + insert_token(item); + checkTokenLimit(); + } // Clear input box input_box.val(""); @@ -553,10 +595,7 @@ $.TokenList = function (input, url_or_data, settings) { if(index < selected_token_index) selected_token_index--; // Update the hidden input - var token_ids = $.map(saved_tokens, function (el) { - return el.id; - }); - hidden_input.val(token_ids.join(settings.tokenDelimiter)); + update_hidden_input(saved_tokens, hidden_input); token_count -= 1; @@ -573,6 +612,15 @@ $.TokenList = function (input, url_or_data, settings) { } } + // Update the hidden input box value + function update_hidden_input(saved_tokens, hidden_input) { + var token_values = $.map(saved_tokens, function (el) { + return el[settings.tokenValue]; + }); + hidden_input.val(token_values.join(settings.tokenDelimiter)); + + } + // Hide and clear the results dropdown function hide_dropdown () { dropdown.hide().empty(); @@ -608,6 +656,10 @@ $.TokenList = function (input, url_or_data, settings) { function highlight_term(value, term) { return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); } + + function find_value_and_highlight_term(template, value, term) { + return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + value + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term)); + } // Populate the results dropdown with some results function populate_dropdown (query, results) { @@ -620,14 +672,18 @@ $.TokenList = function (input, url_or_data, settings) { }) .mousedown(function (event) { add_token($(event.target).closest("li").data("tokeninput")); + hidden_input.change(); return false; }) .hide(); $.each(results, function(index, value) { - var this_li = $("
  • " + highlight_term(value.name, query) + "
  • ") - .appendTo(dropdown_ul); - + var this_li = settings.resultsFormatter(value); + + this_li = find_value_and_highlight_term(this_li ,value[settings.propertyToSearch], query); + + this_li = $(this_li).appendTo(dropdown_ul); + if(index % 2) { this_li.addClass(settings.classes.dropdownItem); } else { @@ -699,17 +755,19 @@ $.TokenList = function (input, url_or_data, settings) { // Do the actual search function run_search(query) { - var cached_results = cache.get(query); + var cache_key = query + computeURL(); + var cached_results = cache.get(cache_key); if(cached_results) { populate_dropdown(query, cached_results); } else { // Are we doing an ajax search or local data search? if(settings.url) { + var url = computeURL(); // Extract exisiting get params var ajax_params = {}; ajax_params.data = {}; - if(settings.url.indexOf("?") > -1) { - var parts = settings.url.split("?"); + if(url.indexOf("?") > -1) { + var parts = url.split("?"); ajax_params.url = parts[0]; var param_array = parts[1].split("&"); @@ -718,7 +776,7 @@ $.TokenList = function (input, url_or_data, settings) { ajax_params.data[kv[0]] = kv[1]; }); } else { - ajax_params.url = settings.url; + ajax_params.url = url; } // Prepare the request @@ -734,7 +792,7 @@ $.TokenList = function (input, url_or_data, settings) { if($.isFunction(settings.onResult)) { results = settings.onResult.call(hidden_input, results); } - cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results); + cache.add(cache_key, settings.jsonContainer ? results[settings.jsonContainer] : results); // only populate the dropdown if the results are associated with the active search query if(input_box.val().toLowerCase() === query) { @@ -747,17 +805,26 @@ $.TokenList = function (input, url_or_data, settings) { } else if(settings.local_data) { // Do the search through local data var results = $.grep(settings.local_data, function (row) { - return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1; + return row[settings.propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1; }); if($.isFunction(settings.onResult)) { results = settings.onResult.call(hidden_input, results); } - cache.add(query, results); + cache.add(cache_key, results); populate_dropdown(query, results); } } } + + // compute the dynamic URL + function computeURL() { + var url = settings.url; + if(typeof settings.url == 'function') { + url = settings.url.call(); + } + return url; + } }; // Really basic cache for the results diff --git a/static/js/tokenized-field.js b/static/js/tokenized-field.js index b2040ecc5..5d206b1ed 100644 --- a/static/js/tokenized-field.js +++ b/static/js/tokenized-field.js @@ -3,15 +3,15 @@ function setupTokenizedField(field) { return; // don't tokenize hidden template snippets var pre = []; - if (field.val()) - pre = JSON.parse((field.val() || "").replace(/"/g, '"')); - else if (field.data("pre")) + if (field.attr("data-pre")) pre = JSON.parse((field.attr("data-pre") || "").replace(/"/g, '"')); field.tokenInput(field.attr("data-ajax-url"), { - hintText: "", preventDuplicates: true, - prePopulate: pre + prePopulate: pre, + tokenLimit: field.attr("data-max-entries"), + noResultsText: "No results - cannot use this entry", + hintText: field.attr("data-hint-text") }); } diff --git a/static/secretariat/js/utils.js b/static/secretariat/js/utils.js index f4cddb84f..8be94c45d 100644 --- a/static/secretariat/js/utils.js +++ b/static/secretariat/js/utils.js @@ -211,8 +211,8 @@ $(document).ready(function() { minLength: 3, select: function(event, ui) { //match number inside paren and then strip paren - id = ui.item.label.match(/\(\d+\)/); - val = id[0].replace(/[\(\)]/g, ""); + var id = ui.item.label.match(/\(\d+\)/); + var val = id[0].replace(/[\(\)]/g, ""); //alert(id,val); //alert(id.match(/\d+/)); $.getJSON('/secr/areas/getemails/',{"id":val},function(data) {