Merged branch/iola/statesync up to @5118 to trunk.

- Legacy-Id: 5147
This commit is contained in:
Henrik Levkowetz 2012-12-20 20:32:03 +00:00
commit 1fbcd783f3
78 changed files with 4400 additions and 1468 deletions

View file

@ -0,0 +1,35 @@
#!/usr/bin/env python
import sys, os
import syslog
# boilerplate
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
from django.core import management
management.setup_environ(settings)
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-t", "--to", dest="to",
help="Email address to send report to", metavar="EMAIL")
options, args = parser.parse_args()
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
from ietf.sync.mails import email_discrepancies
receivers = ["iesg-secretary@ietf.org"]
if options.to:
receivers = [options.to]
email_discrepancies(receivers)
syslog.syslog("Emailed sync discrepancies to %s" % receivers)

67
ietf/bin/iana-changes-updates Executable file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env python
import os, sys, re, json, datetime, optparse
import syslog
# boilerplate
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
from django.core import management
management.setup_environ(settings)
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-f", "--from", dest="start",
help="Start time, defaults to a little less than 23 hours ago", metavar="YYYY-MM-DD HH:MM:SS")
parser.add_option("-t", "--to", dest="end",
help="End time, defaults to 23 hours later than from", metavar="YYYY-MM-DD HH:MM:SS")
parser.add_option("", "--no-email", dest="send_email", default=True, action="store_false",
help="Skip sending emails")
options, args = parser.parse_args()
# compensate to avoid we ask for something that happened now and then
# don't get it back because our request interval is slightly off
CLOCK_SKEW_COMPENSATION = 5 # seconds
# actually the interface accepts 24 hours, but then we get into
# trouble with daylights savings - meh
MAX_INTERVAL_ACCEPTED_BY_IANA = datetime.timedelta(hours=23)
start = datetime.datetime.now() - datetime.timedelta(hours=23) + datetime.timedelta(seconds=CLOCK_SKEW_COMPENSATION)
if options.start:
start = datetime.datetime.strptime(options.start, "%Y-%m-%d %H:%M:%S")
end = start + datetime.timedelta(hours=23)
if options.end:
end = datetime.datetime.strptime(options.end, "%Y-%m-%d %H:%M:%S")
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
from ietf.sync.iana import *
syslog.syslog("Updating history log with new changes from IANA from %s, period %s - %s" % (CHANGES_URL, start, end))
t = start
while t < end:
# the IANA server doesn't allow us to fetch more than a certain
# period, so loop over the requested period and make multiple
# requests if necessary
text = fetch_changes_json(CHANGES_URL, t, min(end, t + MAX_INTERVAL_ACCEPTED_BY_IANA))
changes = parse_changes_json(text)
added_events, warnings = update_history_with_changes(changes, send_email=options.send_email)
for e in added_events:
syslog.syslog("Added event for %s %s: %s" % (e.doc_id, e.time, e.desc))
for w in warnings:
syslog.syslog("WARNING: %s" % w)
t += MAX_INTERVAL_ACCEPTED_BY_IANA

34
ietf/bin/iana-protocols-updates Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env python
import os, sys, re, json, datetime
import syslog
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
# boilerplate
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
from django.core import management
management.setup_environ(settings)
from ietf.sync.iana import *
def chunks(l, n):
"""Split list l up in chunks of max size n."""
return (l[i:i+n] for i in xrange(0, len(l), n))
syslog.syslog("Updating history log with new RFC entries from IANA protocols page %s" % PROTOCOLS_URL)
# FIXME: this needs to be the date where this tool is first deployed
rfc_must_published_later_than = datetime.datetime(2012, 11, 26, 0, 0, 0)
text = fetch_protocol_page(PROTOCOLS_URL)
rfc_numbers = parse_protocol_page(text)
for chunk in chunks(rfc_numbers, 100):
updated = update_rfc_log_from_protocol_page(chunk, rfc_must_published_later_than)
for d in updated:
syslog.syslog("Added history entry for %s" % d.display_name())

33
ietf/bin/iana-review-email Executable file
View file

@ -0,0 +1,33 @@
#!/usr/bin/env python
import os, sys, re, json, datetime, optparse
import syslog
# boilerplate
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
from django.core import management
management.setup_environ(settings)
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
from ietf.sync.iana import *
from ietf.doc.models import Document
msg = sys.stdin.read()
doc_name, review_time, by, comment = parse_review_email(msg)
syslog.syslog(u"Read IANA review email for %s at %s by %s" % (doc_name, review_time, by))
if by.name == "(System)":
syslog.syslog("WARNING: person responsible for email does not have a IANA role")
try:
add_review_comment(doc_name, review_time, by, comment)
except Document.DoesNotExist:
syslog.syslog("ERROR: unknown document %s" % doc_name)

View file

@ -0,0 +1,42 @@
#!/usr/bin/env python
import os, sys, re, json, datetime
import syslog
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
# boilerplate
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
from django.core import management
management.setup_environ(settings)
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-d", dest="skip_date",
help="To speed up processing skip RFCs published before this date (default is one year ago)", metavar="YYYY-MM-DD")
options, args = parser.parse_args()
skip_date = datetime.date.today() - datetime.timedelta(days=365)
if options.skip_date:
skip_date = datetime.datetime.strptime(options.skip_date, "%Y-%m-%d").date()
from ietf.sync.rfceditor import *
syslog.syslog("Updating document metadata from RFC index from %s" % QUEUE_URL)
response = fetch_index_xml(INDEX_URL)
data = parse_index(response)
if len(data) < MIN_INDEX_RESULTS:
syslog.syslog("Not enough results, only %s" % len(data))
sys.exit(1)
changed = update_docs_from_rfc_index(data, skip_older_than_date=skip_date)
for c in changed:
syslog.syslog(c)

View file

@ -0,0 +1,35 @@
#!/usr/bin/env python
import os, sys, re, json, datetime
import syslog
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
# boilerplate
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
from django.core import management
management.setup_environ(settings)
from ietf.sync.rfceditor import *
syslog.syslog("Updating RFC Editor queue states from %s" % QUEUE_URL)
response = fetch_queue_xml(QUEUE_URL)
drafts, warnings = parse_queue(response)
for w in warnings:
syslog.syslog(u"WARNING: %s" % w)
if len(drafts) < MIN_QUEUE_RESULTS:
syslog.syslog("Not enough results, only %s" % len(drafts))
sys.exit(1)
changed, warnings = update_drafts_from_queue(drafts)
for w in warnings:
syslog.syslog(u"WARNING: %s" % w)
for c in changed:
syslog.syslog(u"Updated %s" % c)

View file

@ -142,6 +142,8 @@ class DocEventAdmin(admin.ModelAdmin):
admin.site.register(DocEvent, DocEventAdmin)
admin.site.register(NewRevisionDocEvent, DocEventAdmin)
admin.site.register(StateDocEvent, DocEventAdmin)
admin.site.register(ConsensusDocEvent, DocEventAdmin)
admin.site.register(BallotDocEvent, DocEventAdmin)
admin.site.register(WriteupDocEvent, DocEventAdmin)
admin.site.register(LastCallDocEvent, DocEventAdmin)

View file

@ -0,0 +1,377 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'StateDocEvent'
db.create_table('doc_statedocevent', (
('docevent_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['doc.DocEvent'], unique=True, primary_key=True)),
('state_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['doc.StateType'])),
('state', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['doc.State'], null=True, blank=True)),
))
db.send_create_signal('doc', ['StateDocEvent'])
# Adding model 'ConsensusDocEvent'
db.create_table('doc_consensusdocevent', (
('docevent_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['doc.DocEvent'], unique=True, primary_key=True)),
('consensus', self.gf('django.db.models.fields.BooleanField')(default=False)),
))
db.send_create_signal('doc', ['ConsensusDocEvent'])
def backwards(self, orm):
# Deleting model 'StateDocEvent'
db.delete_table('doc_statedocevent')
# Deleting model 'ConsensusDocEvent'
db.delete_table('doc_consensusdocevent')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.ballotdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotDocEvent', '_ormbases': ['doc.DocEvent']},
'ballot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.BallotType']"}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
},
'doc.ballotpositiondocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotPositionDocEvent', '_ormbases': ['doc.DocEvent']},
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'ballot': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['doc.BallotDocEvent']", 'null': 'True'}),
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'comment_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'discuss': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'discuss_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'pos': ('django.db.models.fields.related.ForeignKey', [], {'default': "'norecord'", 'to': "orm['name.BallotPositionName']"})
},
'doc.ballottype': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotType'},
'doc_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'positions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.BallotPositionName']", 'symmetrical': 'False', 'blank': 'True'}),
'question': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.consensusdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'ConsensusDocEvent', '_ormbases': ['doc.DocEvent']},
'consensus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.docevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'doc.dochistory': {
'Meta': {'object_name': 'DocHistory'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocHistoryAuthor']", 'blank': 'True'}),
'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['doc.Document']"}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.DocAlias']", 'symmetrical': 'False', 'through': "orm['doc.RelatedDocHistory']", 'blank': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.dochistoryauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {})
},
'doc.docreminder': {
'Meta': {'object_name': 'DocReminder'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'due': ('django.db.models.fields.DateTimeField', [], {}),
'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocEvent']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocReminderTypeName']"})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.initialreviewdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'doc.lastcalldocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'doc.newrevisiondocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'})
},
'doc.relateddochistory': {
'Meta': {'object_name': 'RelatedDocHistory'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': "orm['doc.DocAlias']"})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statedocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'StateDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']", 'null': 'True', 'blank': 'True'}),
'state_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'doc.telechatdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
},
'doc.writeupdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'name.ballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docrelationshipname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docremindertypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['doc']

View file

@ -0,0 +1,387 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
t = orm.StateType.objects.get_or_create(slug="draft-iana-action", label="IANA Action state")[0]
orm.State.objects.get_or_create(type=t, slug='newdoc', name='New Document', desc="A new document has been received by IANA, but no actions have been taken", order=1)
orm.State.objects.get_or_create(type=t, slug='inprog', name='In Progress', desc="IANA is currently processing the actions for this document", order=2)
orm.State.objects.get_or_create(type=t, slug='waitauth', name='Waiting on Authors', desc="IANA is waiting on the document's authors to respond", order=3)
orm.State.objects.get_or_create(type=t, slug='waitad', name='Waiting on ADs', desc="IANA is waiting on the IETF Area Directors to respond", order=4)
orm.State.objects.get_or_create(type=t, slug='waitwgc', name='Waiting on WGC', desc="IANA is waiting on the IETF Working Group Chairs to respond", order=5)
orm.State.objects.get_or_create(type=t, slug='waitrfc', name='Waiting on RFC Editor', desc="IANA has notified the RFC Editor that the actions have been completed", order=6)
orm.State.objects.get_or_create(type=t, slug='rfcedack', name='RFC-Ed-Ack', desc="Request completed. The RFC Editor has acknowledged receipt of IANA's message that the actions have been completed", order=7)
orm.State.objects.get_or_create(type=t, slug='onhold', name='On Hold', desc="IANA has suspended work on the document", order=8)
orm.State.objects.get_or_create(type=t, slug='noic', name='No IC', desc="Request completed. There were no IANA actions for this document", order=9)
t = orm.StateType.objects.get_or_create(slug="draft-iana-review", label="IANA Review state")[0]
orm.State.objects.get_or_create(type=t, slug="need-rev", name='IANA Review Needed', desc="Document has not yet been reviewed by IANA.", order=1)
orm.State.objects.get_or_create(type=t, slug="ok-act", name='IANA OK - Actions Needed', desc="Document requires IANA actions, and the IANA Considerations section indicates the details of the actions correctly.", order=2)
orm.State.objects.get_or_create(type=t, slug="ok-noact", name='IANA OK - No Actions Needed', desc="Document requires no IANA action, and the IANA Considerations section indicates this correctly.", order=3)
orm.State.objects.get_or_create(type=t, slug="not-ok", name='IANA Not OK', desc="IANA has issues with the text of the IANA Considerations section of the document.", order=4)
orm.State.objects.get_or_create(type=t, slug="changed", name='Version Changed - Review Needed', desc="Document revision has changed after review by IANA.", order=5)
# fixup RFC Editor states/tags
orm.State.objects.filter(type="draft-rfceditor", slug="edit").update(desc="Awaiting editing or being edited")
orm.State.objects.filter(type="draft-rfceditor", slug="iesg").update(desc="Awaiting IESG action")
orm.State.objects.filter(type="draft-rfceditor", slug="isr-auth").update(desc="Independent submission awaiting author action, or in discussion between author and ISE")
orm.State.objects.filter(type="draft-rfceditor", slug="iana-crd").update(slug="iana", desc="Document has been edited, but is holding for completion of IANA actions")
orm.State.objects.get_or_create(type_id="draft-rfceditor", slug="auth48-done", defaults=dict(name="AUTH48-DONE", desc="Final approvals are complete"))
orm["name.DocTagName"].objects.get_or_create(slug="iana", name="IANA", desc="The document has IANA actions that are not yet completed.")
for d in orm.Document.objects.filter(type="draft", tags="iana-crd"):
d.tags.remove("iana-crd")
d.tags.add("iana")
orm["name.DocTagName"].objects.filter(slug="iana-crd").delete()
def backwards(self, orm):
"Write your backwards methods here."
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.ballotdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotDocEvent', '_ormbases': ['doc.DocEvent']},
'ballot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.BallotType']"}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
},
'doc.ballotpositiondocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotPositionDocEvent', '_ormbases': ['doc.DocEvent']},
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'ballot': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['doc.BallotDocEvent']", 'null': 'True'}),
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'comment_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'discuss': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'discuss_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'pos': ('django.db.models.fields.related.ForeignKey', [], {'default': "'norecord'", 'to': "orm['name.BallotPositionName']"})
},
'doc.ballottype': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotType'},
'doc_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'positions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.BallotPositionName']", 'symmetrical': 'False', 'blank': 'True'}),
'question': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.consensusdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'ConsensusDocEvent', '_ormbases': ['doc.DocEvent']},
'consensus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.docevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'doc.dochistory': {
'Meta': {'object_name': 'DocHistory'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocHistoryAuthor']", 'blank': 'True'}),
'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['doc.Document']"}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.DocAlias']", 'symmetrical': 'False', 'through': "orm['doc.RelatedDocHistory']", 'blank': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.dochistoryauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {})
},
'doc.docreminder': {
'Meta': {'object_name': 'DocReminder'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'due': ('django.db.models.fields.DateTimeField', [], {}),
'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocEvent']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocReminderTypeName']"})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.initialreviewdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'doc.lastcalldocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'doc.newrevisiondocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'})
},
'doc.relateddochistory': {
'Meta': {'object_name': 'RelatedDocHistory'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': "orm['doc.DocAlias']"})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statedocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'StateDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']", 'null': 'True', 'blank': 'True'}),
'state_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'doc.telechatdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
},
'doc.writeupdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'name.ballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docrelationshipname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docremindertypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['doc']

View file

@ -0,0 +1,374 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
# IAB
iab = orm["group.Group"].objects.get(acronym="iab")
orm.Document.objects.filter(stream="iab").update(group=iab)
# IRTF
rgs = {}
for d in orm.Document.objects.filter(stream="irtf"):
acronym = d.name.split("-")[2]
if acronym not in rgs:
try:
rgs[acronym] = orm["group.Group"].objects.get(acronym=acronym, type="rg")
except orm["group.Group"].DoesNotExist:
rgs[acronym] = None
rg = rgs[acronym]
if rg:
d.group = rg
d.save()
def backwards(self, orm):
"Write your backwards methods here."
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.ballotdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotDocEvent', '_ormbases': ['doc.DocEvent']},
'ballot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.BallotType']"}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
},
'doc.ballotpositiondocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotPositionDocEvent', '_ormbases': ['doc.DocEvent']},
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'ballot': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['doc.BallotDocEvent']", 'null': 'True'}),
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'comment_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'discuss': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'discuss_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'pos': ('django.db.models.fields.related.ForeignKey', [], {'default': "'norecord'", 'to': "orm['name.BallotPositionName']"})
},
'doc.ballottype': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotType'},
'doc_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'positions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.BallotPositionName']", 'symmetrical': 'False', 'blank': 'True'}),
'question': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.consensusdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'ConsensusDocEvent', '_ormbases': ['doc.DocEvent']},
'consensus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.docevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'doc.dochistory': {
'Meta': {'object_name': 'DocHistory'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocHistoryAuthor']", 'blank': 'True'}),
'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['doc.Document']"}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.DocAlias']", 'symmetrical': 'False', 'through': "orm['doc.RelatedDocHistory']", 'blank': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.dochistoryauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {})
},
'doc.docreminder': {
'Meta': {'object_name': 'DocReminder'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'due': ('django.db.models.fields.DateTimeField', [], {}),
'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocEvent']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocReminderTypeName']"})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.initialreviewdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'doc.lastcalldocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'doc.newrevisiondocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'})
},
'doc.relateddochistory': {
'Meta': {'object_name': 'RelatedDocHistory'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': "orm['doc.DocAlias']"})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statedocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'StateDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']", 'null': 'True', 'blank': 'True'}),
'state_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'doc.telechatdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
},
'doc.writeupdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'name.ballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docrelationshipname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docremindertypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['doc']

View file

@ -0,0 +1,377 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'DeletedEvent'
db.create_table('doc_deletedevent', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
('json', self.gf('django.db.models.fields.TextField')()),
('by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Person'])),
('time', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
))
db.send_create_signal('doc', ['DeletedEvent'])
def backwards(self, orm):
# Deleting model 'DeletedEvent'
db.delete_table('doc_deletedevent')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.ballotdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotDocEvent', '_ormbases': ['doc.DocEvent']},
'ballot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.BallotType']"}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
},
'doc.ballotpositiondocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotPositionDocEvent', '_ormbases': ['doc.DocEvent']},
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'ballot': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['doc.BallotDocEvent']", 'null': 'True'}),
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'comment_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'discuss': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'discuss_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'pos': ('django.db.models.fields.related.ForeignKey', [], {'default': "'norecord'", 'to': "orm['name.BallotPositionName']"})
},
'doc.ballottype': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotType'},
'doc_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'positions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.BallotPositionName']", 'symmetrical': 'False', 'blank': 'True'}),
'question': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.consensusdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'ConsensusDocEvent', '_ormbases': ['doc.DocEvent']},
'consensus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
},
'doc.deletedevent': {
'Meta': {'object_name': 'DeletedEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'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'})
},
'doc.docalias': {
'Meta': {'object_name': 'DocAlias'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.docevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'},
'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}),
'desc': ('django.db.models.fields.TextField', [], {}),
'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'doc.dochistory': {
'Meta': {'object_name': 'DocHistory'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocHistoryAuthor']", 'blank': 'True'}),
'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['doc.Document']"}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.DocAlias']", 'symmetrical': 'False', 'through': "orm['doc.RelatedDocHistory']", 'blank': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.dochistoryauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {})
},
'doc.docreminder': {
'Meta': {'object_name': 'DocReminder'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'due': ('django.db.models.fields.DateTimeField', [], {}),
'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocEvent']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocReminderTypeName']"})
},
'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
'doc.initialreviewdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'doc.lastcalldocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
},
'doc.newrevisiondocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'})
},
'doc.relateddochistory': {
'Meta': {'object_name': 'RelatedDocHistory'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': "orm['doc.DocAlias']"})
},
'doc.relateddocument': {
'Meta': {'object_name': 'RelatedDocument'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}),
'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"})
},
'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': "orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'doc.statedocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'StateDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.State']", 'null': 'True', 'blank': 'True'}),
'state_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"})
},
'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
'doc.telechatdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
},
'doc.writeupdocevent': {
'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': ['doc.DocEvent']},
'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
},
'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
'name.ballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docrelationshipname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.docremindertypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['doc']

View file

@ -2,6 +2,7 @@
from django.db import models
from django.core.urlresolvers import reverse as urlreverse
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
from ietf.group.models import *
@ -26,7 +27,7 @@ class State(models.Model):
desc = models.TextField(blank=True)
order = models.IntegerField(default=0)
next_states = models.ManyToManyField('State', related_name="previous_states")
next_states = models.ManyToManyField('State', related_name="previous_states", blank=True)
def __unicode__(self):
return self.name
@ -450,12 +451,15 @@ EVENT_TYPES = [
("deleted", "Deleted document"),
("changed_state", "Changed state"),
# misc draft/RFC events
("changed_stream", "Changed document stream"),
("expired_document", "Expired document"),
("extended_expiry", "Extended expiry of document"),
("requested_resurrect", "Requested resurrect"),
("completed_resurrect", "Completed resurrect"),
("changed_consensus", "Changed consensus"),
("published_rfc", "Published RFC"),
# WG events
@ -481,13 +485,21 @@ EVENT_TYPES = [
("changed_last_call_text", "Changed last call text"),
("requested_last_call", "Requested last call"),
("sent_last_call", "Sent last call"),
("scheduled_for_telechat", "Scheduled for telechat"),
("iesg_approved", "IESG approved document (no problem)"),
("iesg_disapproved", "IESG disapproved document (do not publish)"),
("approved_in_minute", "Approved in minute"),
# IANA events
("iana_review", "IANA review comment"),
("rfc_in_iana_registry", "RFC is in IANA registry"),
# RFC Editor
("rfc_editor_received_announcement", "Announcement was received by RFC Editor"),
("requested_publication", "Publication at RFC Editor requested")
]
class DocEvent(models.Model):
@ -506,7 +518,14 @@ class DocEvent(models.Model):
class NewRevisionDocEvent(DocEvent):
rev = models.CharField(max_length=16)
class StateDocEvent(DocEvent):
state_type = models.ForeignKey(StateType)
state = models.ForeignKey(State, blank=True, null=True)
class ConsensusDocEvent(DocEvent):
consensus = models.BooleanField()
# IESG events
class BallotType(models.Model):
doc_type = models.ForeignKey(DocTypeName, blank=True, null=True)
@ -589,7 +608,7 @@ class BallotPositionDocEvent(DocEvent):
discuss_time = models.DateTimeField(help_text="Time discuss text was written", blank=True, null=True)
comment = models.TextField(help_text="Optional comment", blank=True)
comment_time = models.DateTimeField(help_text="Time optional comment was written", blank=True, null=True)
class WriteupDocEvent(DocEvent):
text = models.TextField(blank=True)
@ -603,3 +622,14 @@ class TelechatDocEvent(DocEvent):
# charter events
class InitialReviewDocEvent(DocEvent):
expires = models.DateTimeField(blank=True, null=True)
# dumping store for removed events
class DeletedEvent(models.Model):
content_type = models.ForeignKey(ContentType)
json = models.TextField(help_text="Deleted object in JSON format, with attribute names chosen to be suitable for passing into the relevant create method.")
by = models.ForeignKey(Person)
time = models.DateTimeField(default=datetime.datetime.now)
def __unicode__(self):
return u"%s by %s %s" % (self.content_type, self.by, self.time)

View file

@ -19,7 +19,8 @@ def get_state_types(doc):
res.append("draft-stream-%s" % doc.stream_id)
res.append("draft-iesg")
res.append("draft-iana")
res.append("draft-iana-review")
res.append("draft-iana-action")
res.append("draft-rfceditor")
return res
@ -108,8 +109,6 @@ def augment_with_start_time(docs):
if e.doc_id in seen:
continue
print e.time, e.doc_id
docs_dict[e.doc_id].start_time = e.time
seen.add(e.doc_id)
@ -167,11 +166,26 @@ def get_document_content(key, filename, split=True, markup=True):
return raw_content
def log_state_changed(request, doc, by, new_description, old_description):
from ietf.doc.models import DocEvent
e = DocEvent(doc=doc, by=by)
e.type = "changed_document"
e.desc = u"State changed to <b>%s</b> from %s" % (new_description, old_description)
e.save()
return e
def add_state_change_event(doc, by, prev_state, new_state, timestamp=None):
"""Add doc event to explain that state change just happened."""
if prev_state == new_state:
return None
e = StateDocEvent(doc=doc, by=by)
e.type = "changed_state"
e.state_type = (prev_state or new_state).type
e.state = new_state
e.desc = "%s changed to <b>%s</b>" % (e.state_type.label, new_state.name)
if prev_state:
e.desc += " from %s" % prev_state.name
if timestamp:
e.time = timestamp
e.save()
return e

View file

@ -24,6 +24,12 @@ class GroupInfo(models.Model):
def __unicode__(self):
return self.name
def name_with_acronym(self):
res = self.name
if self.type_id in ("wg", "rg", "area"):
res += " %s (%s)" % (self.type, self.acronym)
return res
class Meta:
abstract = True

View file

@ -34,11 +34,19 @@ def in_id_expire_freeze(when=None):
return second_cut_off <= when < ietf_monday
def expirable_documents():
# the general rule is that each active draft is expirable, unless
# it's in a state where we shouldn't touch it
d = Document.objects.filter(states__type="draft", states__slug="active").exclude(tags="rfc-rev")
# we need to get those that either don't have a state or have a
# state >= 42 (AD watching), unfortunately that doesn't appear to
# be possible to get to work directly in Django 1.1
return itertools.chain(d.exclude(states__type="draft-iesg").distinct(), d.filter(states__type="draft-iesg", states__slug__in=("watching", "dead")).distinct())
nonexpirable_states = []
# all IESG states except AD Watching and Dead block expiry
nonexpirable_states += list(State.objects.filter(type="draft-iesg").exclude(slug__in=("watching", "dead")))
# Sent to RFC Editor and RFC Published block expiry (the latter
# shouldn't be possible for an active draft, though)
nonexpirable_states += list(State.objects.filter(type__in=("draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"), slug__in=("rfc-edit", "pub")))
return d.exclude(states__in=nonexpirable_states).distinct()
def get_soon_to_expire_ids(days):
start_date = datetime.date.today() - datetime.timedelta(InternetDraft.DAYS_TO_EXPIRE - 1)

View file

@ -125,9 +125,9 @@ class IdWrapper:
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
s = self._draft.get_state("draft-rfceditor")
if s:
# extract possible extra states
tags = self._draft.tags.filter(slug__in=("iana-crd", "ref", "missref"))
return " ".join([s.name] + [t.slug.replace("-crd", "").upper() for t in tags])
# extract possible extra annotations
tags = self._draft.tags.filter(slug__in=("iana", "ref"))
return "*".join([s.name] + [t.slug.upper() for t in tags])
else:
return None

View file

@ -11,7 +11,8 @@ from django.core.urlresolvers import reverse as urlreverse
from ietf.utils.mail import send_mail, send_mail_text
from ietf.idtracker.models import *
from ietf.ipr.search import iprs_from_docs
from ietf.doc.models import WriteupDocEvent, BallotPositionDocEvent, LastCallDocEvent, DocAlias
#from ietf.doc.models import *
from ietf.doc.models import WriteupDocEvent, BallotPositionDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent
from ietf.person.models import Person
from ietf.group.models import Group
@ -62,8 +63,26 @@ def email_stream_changed(request, doc, old_stream, new_stream, text=""):
"idrfc/stream_changed_email.txt",
dict(text=text,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state):
send_mail(request, ["IANA <iana@iana.org>", "RFC Editor <rfc-editor@rfc-editor.org>"], None,
"%s changed state from %s to %s" % (doc.name, prev_state.name, next_state.name),
"idrfc/pulled_from_rfc_queue_email.txt",
dict(doc=doc,
prev_state=prev_state,
next_state=next_state,
comment=comment,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
extra=extra_automation_headers(doc))
def email_authors(request, doc, subject, text):
to = [x.strip() for x in doc.author_list().split(',')]
if not to:
return
send_mail_text(request, to, None, subject, text)
def html_to_text(html):
return strip_tags(html.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&").replace("<br>", "\n"))
@ -98,12 +117,15 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
def generate_ballot_writeup(request, doc):
e = doc.latest_event(type="iana_review")
iana = e.desc if e else ""
e = WriteupDocEvent()
e.type = "changed_ballot_writeup_text"
e.by = request.user.get_profile()
e.doc = doc
e.desc = u"Ballot writeup was generated"
e.text = unicode(render_to_string("idrfc/ballot_writeup.txt"))
e.text = unicode(render_to_string("idrfc/ballot_writeup.txt", {'iana': iana}))
e.save()
return e
@ -263,6 +285,30 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
generate_approval_mail = generate_approval_mailREDESIGN
generate_approval_mail_rfc_editor = generate_approval_mail_rfc_editorREDESIGN
def generate_publication_request(request, doc):
group_description = ""
if doc.group and doc.group.acronym != "none":
group_description = doc.group.name_with_acronym()
e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
consensus = e.consensus if e else None
if doc.stream_id == "irtf":
approving_body = "IRSG"
consensus_body = doc.group.acronym.upper()
else:
approving_body = str(doc.stream)
consensus_body = approving_body
return render_to_string("idrfc/publication_request.txt",
dict(doc=doc,
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
group_description=group_description,
approving_body=approving_body,
consensus_body=consensus_body,
consensus=consensus,
)
)
def send_last_call_request(request, doc, ballot):
to = "iesg-secretary@ietf.org"
@ -426,22 +472,6 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
generate_issue_ballot_mail = generate_issue_ballot_mailREDESIGN
def email_iana(request, doc, to, msg):
# fix up message and send message to IANA for each in ballot set
import email
parsed_msg = email.message_from_string(msg.encode("utf-8"))
for i in doc.idinternal.ballot_set():
extra = {}
extra["Reply-To"] = "noreply@ietf.org"
extra["X-IETF-Draft-string"] = i.document().filename
extra["X-IETF-Draft-revision"] = i.document().revision_display()
send_mail_text(request, "To: IANA <%s>" % to,
parsed_msg["From"], parsed_msg["Subject"],
parsed_msg.get_payload(),
extra=extra)
def email_ianaREDESIGN(request, doc, to, msg):
# fix up message and send it with extra info on doc in headers
import email
parsed_msg = email.message_from_string(msg.encode("utf-8"))
@ -456,8 +486,13 @@ def email_ianaREDESIGN(request, doc, to, msg):
parsed_msg.get_payload(),
extra=extra)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
email_iana = email_ianaREDESIGN
def extra_automation_headers(doc):
extra = {}
extra["Reply-To"] = "noreply@ietf.org"
extra["X-IETF-Draft-string"] = doc.name
extra["X-IETF-Draft-revision"] = doc.rev
return extra
def email_last_call_expired(doc):
text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.idinternal.cur_state.state

View file

@ -1,293 +0,0 @@
# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# * Neither the name of the Nokia Corporation and/or its
# subsidiary(-ies) nor the names of its contributors may be used
# to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from ietf import settings
from django.core import management
management.setup_environ(settings)
from django import db
from xml.dom import pulldom, Node
import re
import urllib2
from datetime import datetime
import socket
import sys
QUEUE_URL = "http://www.rfc-editor.org/queue2.xml"
TABLE = "rfc_editor_queue_mirror"
REF_TABLE = "rfc_editor_queue_mirror_refs"
log_data = ""
def log(line):
global log_data
if __name__ == '__main__' and len(sys.argv) > 1:
print line
else:
log_data += line + "\n"
def parse(response):
def getChildText(parentNode, tagName):
for node in parentNode.childNodes:
if node.nodeType == Node.ELEMENT_NODE and node.localName == tagName:
return node.firstChild.data
return None
events = pulldom.parse(response)
drafts = []
refs = []
for (event, node) in events:
if event == pulldom.START_ELEMENT and node.tagName == "entry":
events.expandNode(node)
node.normalize()
draft_name = getChildText(node, "draft").strip()
draft_name = re.sub("(-\d\d)?(.txt){1,2}$", "", draft_name)
date_received = getChildText(node, "date-received")
states = []
for child in node.childNodes:
if child.nodeType == Node.ELEMENT_NODE and child.localName == "state":
states.append(child.firstChild.data)
has_refs = False
for child in node.childNodes:
if child.nodeType == Node.ELEMENT_NODE and child.localName == "normRef":
ref_name = getChildText(child, "ref-name")
ref_state = getChildText(child, "ref-state")
in_queue = ref_state.startswith("IN-QUEUE")
refs.append([draft_name, ref_name, in_queue, True])
has_refs = True
if has_refs and not "MISSREF" in states:
states.append("REF")
if len(states) == 0:
state = "?"
else:
state = " ".join(states)
drafts.append([draft_name, date_received, state, stream])
elif event == pulldom.START_ELEMENT and node.tagName == "section":
name = node.getAttribute('name')
if name.startswith("IETF"):
stream = 1
elif name.startswith("IAB"):
stream = 2
elif name.startswith("IRTF"):
stream = 3
elif name.startswith("INDEPENDENT"):
stream = 4
else:
stream = 0
log("WARNING: unrecognized section "+name)
return (drafts, refs)
# Find set of all normative references (whether direct or via some
# other normative reference)
def find_indirect_refs(drafts, refs):
result = []
draft_names = set()
for draft in drafts:
draft_names.add(draft[0])
def recurse(draft_name, ref_set, level):
for (source, destination, in_queue, direct) in refs:
if source == draft_name:
if destination not in ref_set:
ref_set.add(destination)
recurse(destination, ref_set, level+1)
if level == 0:
# Remove self-reference
ref_set.remove(draft_name)
# Remove direct references
for (source, destination, in_queue, direct) in refs:
if source == draft_name:
if destination in ref_set:
ref_set.remove(destination)
# The rest are indirect references
for ref in ref_set:
if draft_name != ref:
result.append([draft_name, ref, ref in draft_names, False])
for draft_name in draft_names:
recurse(draft_name, set([draft_name]), 0)
return result
# Convert filenames to id_document_tags
def find_document_ids(cursor, drafts, refs):
draft_ids = {}
drafts2 = []
for draft in drafts:
cursor.execute("SELECT id_document_tag FROM internet_drafts WHERE filename=%s", [draft[0]])
row = cursor.fetchone()
if not row:
log("WARNING: cannot find id for "+draft[0])
else:
draft_ids[draft[0]] = row[0]
drafts2.append([row[0]]+draft[1:])
refs2 = []
for ref in refs:
if ref[0] in draft_ids:
refs2.append([draft_ids[ref[0]]]+ref[1:])
return (drafts2, refs2)
def parse_all(response):
log("parsing...")
(drafts, refs) = parse(response)
log("got "+ str(len(drafts)) + " drafts and "+str(len(refs))+" direct refs")
indirect_refs = find_indirect_refs(drafts, refs)
log("found " + str(len(indirect_refs)) + " indirect refs")
refs.extend(indirect_refs)
del(indirect_refs)
if settings.USE_DB_REDESIGN_PROXY_CLASSES: # note: return before id lookup
return (drafts, refs)
# convert filenames to id_document_tags
log("connecting to database...")
cursor = db.connection.cursor()
log("finding id_document_tags...")
(drafts, refs) = find_document_ids(cursor, drafts, refs)
cursor.close()
return (drafts, refs)
def insert_into_database(drafts, refs):
log("connecting to database...")
cursor = db.connection.cursor()
log("removing old data...")
cursor.execute("DELETE FROM "+TABLE)
cursor.execute("DELETE FROM "+REF_TABLE)
log("inserting new data...")
cursor.executemany("INSERT INTO "+TABLE+" (id_document_tag, date_received, state, stream) VALUES (%s, %s, %s, %s)", drafts)
cursor.execute("DELETE FROM "+REF_TABLE)
cursor.executemany("INSERT INTO "+REF_TABLE+" (source, destination, in_queue, direct) VALUES (%s, %s, %s, %s)", refs)
cursor.close()
db.connection._commit()
db.connection.close()
import django.db.transaction
def get_rfc_tag_mapping():
"""Return dict with RFC Editor state name -> DocTagName"""
from ietf.name.models import DocTagName
from ietf.name.utils import name
return {
'IANA': name(DocTagName, 'iana-crd', 'IANA coordination', "RFC-Editor/IANA Registration Coordination"),
'REF': name(DocTagName, 'ref', 'Holding for references', "Holding for normative reference"),
'MISSREF': name(DocTagName, 'missref', 'Missing references', "Awaiting missing normative reference"),
}
def get_rfc_state_mapping():
"""Return dict with RFC Editor state name -> State"""
from ietf.doc.models import State, StateType
t = StateType.objects.get(slug="draft-rfceditor")
return {
'AUTH': State.objects.get_or_create(type=t, slug='auth', name='AUTH', desc="Awaiting author action")[0],
'AUTH48': State.objects.get_or_create(type=t, slug='auth48', name="AUTH48", desc="Awaiting final author approval")[0],
'AUTH48-DONE': State.objects.get_or_create(type=t, slug='auth48done', name="AUTH48-DONE", desc="Final approvals are complete")[0],
'EDIT': State.objects.get_or_create(type=t, slug='edit', name='EDIT', desc="Approved by the stream manager (e.g., IESG, IAB, IRSG, ISE), awaiting processing and publishing")[0],
'IANA': State.objects.get_or_create(type=t, slug='iana-crd', name='IANA', desc="RFC-Editor/IANA Registration Coordination")[0],
'IESG': State.objects.get_or_create(type=t, slug='iesg', name='IESG', desc="Holding for IESG action")[0],
'ISR': State.objects.get_or_create(type=t, slug='isr', name='ISR', desc="Independent Submission Review by the ISE ")[0],
'ISR-AUTH': State.objects.get_or_create(type=t, slug='isr-auth', name='ISR-AUTH', desc="Independent Submission awaiting author update, or in discussion between author and ISE")[0],
'REF': State.objects.get_or_create(type=t, slug='ref', name='REF', desc="Holding for normative reference")[0],
'RFC-EDITOR': State.objects.get_or_create(type=t, slug='rfc-edit', name='RFC-EDITOR', desc="Awaiting final RFC Editor review before AUTH48")[0],
'TO': State.objects.get_or_create(type=t, slug='timeout', name='TO', desc="Time-out period during which the IESG reviews document for conflict/concurrence with other IETF working group work")[0],
'MISSREF': State.objects.get_or_create(type=t, slug='missref', name='MISSREF', desc="Awaiting missing normative reference")[0],
}
@django.db.transaction.commit_on_success
def insert_into_databaseREDESIGN(drafts, refs):
from ietf.doc.models import Document
from ietf.name.models import DocTagName
tags = get_rfc_tag_mapping()
state_map = get_rfc_state_mapping()
rfc_editor_tags = tags.values()
log("removing old data...")
for d in Document.objects.filter(states__type="draft-rfceditor").distinct():
d.tags.remove(*rfc_editor_tags)
d.unset_state("draft-rfceditor")
log("inserting new data...")
for name, date_received, state_info, stream_id in drafts:
try:
d = Document.objects.get(name=name)
except Document.DoesNotExist:
log("unknown document %s" % name)
continue
state_list = state_info.split(" ")
if state_list:
state = state_list[0]
# For now, ignore the '*R...' that's appeared for some states.
# FIXME : see if we need to add some refinement for this.
if '*' in state:
state = state.split("*")[0]
# first is state
d.set_state(state_map[state])
# remainding are tags
for x in state_list[1:]:
d.tags.add(tags[x])
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
insert_into_database = insert_into_databaseREDESIGN
if __name__ == '__main__':
try:
log("output from mirror_rfc_editor_queue.py:\n")
log("time: "+str(datetime.now()))
log("host: "+socket.gethostname())
log("url: "+QUEUE_URL)
log("downloading...")
socket.setdefaulttimeout(30)
response = urllib2.urlopen(QUEUE_URL)
(drafts, refs) = parse_all(response)
if len(drafts) < 10 or len(refs) < 10:
raise Exception('not enough data')
insert_into_database(drafts, refs)
log("all done!")
if log_data.find("WARNING") < 0:
log_data = ""
finally:
if len(log_data) > 0:
print log_data

View file

@ -1,365 +0,0 @@
# Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# * Neither the name of the Nokia Corporation and/or its
# subsidiary(-ies) nor the names of its contributors may be used
# to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from ietf import settings
from django.core import management
management.setup_environ(settings)
from django import db
from xml.dom import pulldom, Node
import re
import urllib2
from datetime import datetime, date, timedelta
import socket
import sys
INDEX_URL = "http://www.rfc-editor.org/rfc/rfc-index.xml"
TABLE = "rfc_index_mirror"
log_data = ""
def log(line):
global log_data
if __name__ == '__main__' and len(sys.argv) > 1:
print line
else:
log_data += line + "\n"
# python before 2.7 doesn't have the total_seconds method on datetime.timedelta.
def total_seconds(td):
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
def parse(response):
def getChildText(parentNode, tagName):
for node in parentNode.childNodes:
if node.nodeType == Node.ELEMENT_NODE and node.localName == tagName:
return node.firstChild.data
return None
def getDocList(parentNode, tagName):
l = []
for u in parentNode.getElementsByTagName(tagName):
for d in u.getElementsByTagName("doc-id"):
l.append(d.firstChild.data)
if len(l) == 0:
return None
else:
return ",".join(l)
also_list = {}
data = []
events = pulldom.parse(response)
for (event, node) in events:
if event == pulldom.START_ELEMENT and node.tagName in ["bcp-entry", "fyi-entry", "std-entry"]:
events.expandNode(node)
node.normalize()
bcpid = getChildText(node, "doc-id")
doclist = getDocList(node, "is-also")
if doclist:
for docid in doclist.split(","):
if docid in also_list:
also_list[docid].append(bcpid)
else:
also_list[docid] = [bcpid]
elif event == pulldom.START_ELEMENT and node.tagName == "rfc-entry":
events.expandNode(node)
node.normalize()
rfc_number = int(getChildText(node, "doc-id")[3:])
title = getChildText(node, "title")
l = []
for author in node.getElementsByTagName("author"):
l.append(getChildText(author, "name"))
authors = "; ".join(l)
d = node.getElementsByTagName("date")[0]
year = int(getChildText(d, "year"))
month = getChildText(d, "month")
month = ["January","February","March","April","May","June","July","August","September","October","November","December"].index(month)+1
rfc_published_date = ("%d-%02d-01" % (year, month))
current_status = getChildText(node, "current-status").title()
updates = getDocList(node, "updates")
updated_by = getDocList(node, "updated-by")
obsoletes = getDocList(node, "obsoletes")
obsoleted_by = getDocList(node, "obsoleted-by")
stream = getChildText(node, "stream")
wg = getChildText(node, "wg_acronym")
if wg and ((wg == "NON WORKING GROUP") or len(wg) > 15):
wg = None
l = []
for format in node.getElementsByTagName("format"):
l.append(getChildText(format, "file-format"))
file_formats = (",".join(l)).lower()
draft = getChildText(node, "draft")
if draft and re.search("-\d\d$", draft):
draft = draft[0:-3]
if len(node.getElementsByTagName("errata-url")) > 0:
has_errata = 1
else:
has_errata = 0
data.append([rfc_number,title,authors,rfc_published_date,current_status,updates,updated_by,obsoletes,obsoleted_by,None,draft,has_errata,stream,wg,file_formats])
for d in data:
k = "RFC%04d" % d[0]
if k in also_list:
d[9] = ",".join(also_list[k])
return data
def insert_to_database(data):
log("connecting to database...")
cursor = db.connection.cursor()
log("removing old data...")
cursor.execute("DELETE FROM "+TABLE)
log("inserting new data...")
cursor.executemany("INSERT INTO "+TABLE+" (rfc_number, title, authors, rfc_published_date, current_status,updates,updated_by,obsoletes,obsoleted_by,also,draft,has_errata,stream,wg,file_formats) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", data)
cursor.close()
db.connection._commit()
db.connection.close()
def get_std_level_mapping():
from ietf.name.models import StdLevelName
from ietf.name.utils import name
return {
"Standard": name(StdLevelName, "std", "Standard"),
"Draft Standard": name(StdLevelName, "ds", "Draft Standard"),
"Proposed Standard": name(StdLevelName, "ps", "Proposed Standard"),
"Informational": name(StdLevelName, "inf", "Informational"),
"Experimental": name(StdLevelName, "exp", "Experimental"),
"Best Current Practice": name(StdLevelName, "bcp", "Best Current Practice"),
"Historic": name(StdLevelName, "hist", "Historic"),
"Unknown": name(StdLevelName, "unkn", "Unknown"),
}
def get_stream_mapping():
from ietf.name.models import StreamName
from ietf.name.utils import name
return {
"IETF": name(StreamName, "ietf", "IETF", desc="IETF stream", order=1),
"INDEPENDENT": name(StreamName, "ise", "ISE", desc="Independent Submission Editor stream", order=2),
"IRTF": name(StreamName, "irtf", "IRTF", desc="Independent Submission Editor stream", order=3),
"IAB": name(StreamName, "iab", "IAB", desc="IAB stream", order=4),
"Legacy": name(StreamName, "legacy", "Legacy", desc="Legacy stream", order=5),
}
import django.db.transaction
@django.db.transaction.commit_on_success
def insert_to_databaseREDESIGN(data):
from ietf.person.models import Person
from ietf.doc.models import Document, DocAlias, DocEvent, RelatedDocument, State, save_document_in_history
from ietf.group.models import Group
from ietf.name.models import DocTagName, DocRelationshipName
from ietf.name.utils import name
system = Person.objects.get(name="(System)")
std_level_mapping = get_std_level_mapping()
stream_mapping = get_stream_mapping()
tag_has_errata = name(DocTagName, 'errata', "Has errata")
relationship_obsoletes = name(DocRelationshipName, "obs", "Obsoletes")
relationship_updates = name(DocRelationshipName, "updates", "Updates")
skip_older_than_date = (date.today() - timedelta(days=365)).strftime("%Y-%m-%d")
log("updating data...")
for d in data:
rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats = d
if rfc_published_date < skip_older_than_date:
# speed up the process by skipping old entries
continue
# we assume two things can happen: we get a new RFC, or an
# attribute has been updated at the RFC Editor (RFC Editor
# attributes currently take precedence over our local
# attributes)
# make sure we got the document and alias
created = False
doc = None
name = "rfc%s" % rfc_number
a = DocAlias.objects.filter(name=name)
if a:
doc = a[0].document
else:
if draft:
try:
doc = Document.objects.get(name=draft)
except Document.DoesNotExist:
pass
if not doc:
created = True
log("created document %s" % name)
doc = Document.objects.create(name=name)
# add alias
DocAlias.objects.create(name=name, document=doc)
if not created:
created = True
log("created alias %s to %s" % (name, doc.name))
# check attributes
changed_attributes = {}
changed_states = []
created_relations = []
other_changes = False
if title != doc.title:
changed_attributes["title"] = title
if std_level_mapping[current_status] != doc.std_level:
changed_attributes["std_level"] = std_level_mapping[current_status]
if doc.get_state_slug() != "rfc":
changed_states.append(State.objects.get(type="draft", slug="rfc"))
if doc.stream != stream_mapping[stream]:
changed_attributes["stream"] = stream_mapping[stream]
if not doc.group and wg:
changed_attributes["group"] = Group.objects.get(acronym=wg)
if not doc.latest_event(type="published_rfc"):
e = DocEvent(doc=doc, type="published_rfc")
pubdate = datetime.strptime(rfc_published_date, "%Y-%m-%d")
# unfortunately, pubdate doesn't include the correct day
# at the moment because the data only has month/year, so
# try to deduce it
synthesized = datetime.now()
if abs(pubdate - synthesized) > timedelta(days=60):
synthesized = pubdate
else:
direction = -1 if total_seconds(pubdate - synthesized) < 0 else +1
while synthesized.month != pubdate.month or synthesized.year != pubdate.year:
synthesized += timedelta(days=direction)
e.time = synthesized
e.by = system
e.desc = "RFC published"
e.save()
other_changes = True
if doc.get_state_slug("draft-iesg") == "rfcqueue":
changed_states.append(State.objects.get(type="draft-iesg", slug="pub"))
def parse_relation_list(s):
if not s:
return []
res = []
for x in s.split(","):
if x[:3] in ("NIC", "IEN", "STD", "RTR"):
# try translating this to RFCs that we can handle
# sensibly; otherwise we'll have to ignore them
l = DocAlias.objects.filter(name__startswith="rfc", document__docalias__name=x.lower())
else:
l = DocAlias.objects.filter(name=x.lower())
for a in l:
if a not in res:
res.append(a)
return res
for x in parse_relation_list(obsoletes):
if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_obsoletes):
created_relations.append(RelatedDocument(source=doc, target=x, relationship=relationship_obsoletes))
for x in parse_relation_list(updates):
if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_updates):
created_relations.append(RelatedDocument(source=doc, target=x, relationship=relationship_updates))
if also:
for a in also.lower().split(","):
if not DocAlias.objects.filter(name=a):
DocAlias.objects.create(name=a, document=doc)
other_changes = True
if has_errata:
if not doc.tags.filter(pk=tag_has_errata.pk):
changed_attributes["tags"] = list(doc.tags.all()) + [tag_has_errata]
else:
if doc.tags.filter(pk=tag_has_errata.pk):
changed_attributes["tags"] = set(doc.tags.all()) - set([tag_has_errata])
if changed_attributes or changed_states or created_relations or other_changes:
# apply changes
save_document_in_history(doc)
for k, v in changed_attributes.iteritems():
setattr(doc, k, v)
for s in changed_states:
doc.set_state(s)
for o in created_relations:
o.save()
doc.time = datetime.now()
doc.save()
if not created:
log("%s changed" % name)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
insert_to_database = insert_to_databaseREDESIGN
if __name__ == '__main__':
try:
log("output from mirror_rfc_index.py:\n")
log("time: "+str(datetime.now()))
log("host: "+socket.gethostname())
log("url: "+INDEX_URL)
log("downloading...")
socket.setdefaulttimeout(30)
response = urllib2.urlopen(INDEX_URL)
log("parsing...")
data = parse(response)
log("got " + str(len(data)) + " entries")
if len(data) < 5000:
raise Exception('not enough data')
insert_to_database(data)
log("all done!")
log_data = ""
finally:
if len(log_data) > 0:
print log_data

View file

@ -119,7 +119,61 @@ class ChangeStateTestCase(django.test.TestCase):
q = PyQuery(r.content)
self.assertEquals(len(q('.prev-state form input[name="state"]')), 1)
def test_pull_from_rfc_queue(self):
draft = make_test_data()
draft.set_state(State.objects.get(type="draft-iesg", slug="rfcqueue"))
url = urlreverse('doc_change_state', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "secretary", url)
# change state
mailbox_before = len(outbox)
r = self.client.post(url,
dict(state=State.objects.get(type="draft-iesg", slug="review-e").pk,
substate="",
comment="Test comment"))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
self.assertEquals(draft.get_state_slug("draft-iesg"), "review-e")
self.assertEquals(len(outbox), mailbox_before + 2 + 1)
self.assertTrue(draft.name in outbox[-1]['Subject'])
self.assertTrue("changed state" in outbox[-1]['Subject'])
self.assertTrue("is no longer" in str(outbox[-1]))
self.assertTrue("Test comment" in str(outbox[-1]))
def test_change_iana_state(self):
draft = make_test_data()
first_state = State.objects.get(type="draft-iana-review", slug="need-rev")
next_state = State.objects.get(type="draft-iana-review", slug="ok-noact")
draft.set_state(first_state)
url = urlreverse('doc_change_iana_state', kwargs=dict(name=draft.name, state_type="iana-review"))
login_testing_unauthorized(self, "iana", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form select[name=state]')), 1)
# faulty post
r = self.client.post(url, dict(state="foobarbaz"))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
draft = Document.objects.get(name=draft.name)
self.assertEquals(draft.get_state("draft-iana-review"), first_state)
# change state
r = self.client.post(url, dict(state=next_state.pk))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
self.assertEquals(draft.get_state("draft-iana-review"), next_state)
def test_request_last_call(self):
draft = make_test_data()
draft.set_state(State.objects.get(type="draft-iesg", slug="ad-eval"))
@ -202,7 +256,7 @@ class EditInfoTestCase(django.test.TestCase):
draft = Document.objects.get(name=draft.name)
self.assertEquals(draft.ad, new_ad)
self.assertEquals(draft.note, "New note")
self.assertTrue(not draft.latest_event(TelechatDocEvent, type="telechat_date"))
self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
self.assertEquals(draft.docevent_set.count(), events_before + 3)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue(draft.name in outbox[-1]['Subject'])
@ -221,14 +275,14 @@ class EditInfoTestCase(django.test.TestCase):
)
# add to telechat
self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
data["telechat_date"] = TelechatDate.objects.active()[0].date.isoformat()
r = self.client.post(url, data)
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
self.assertTrue(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
self.assertEquals(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[0].date)
self.assertTrue(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
self.assertEqual(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[0].date)
# change telechat
data["telechat_date"] = TelechatDate.objects.active()[1].date.isoformat()
@ -236,7 +290,7 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
self.assertEquals(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[1].date)
self.assertEqual(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[1].date)
# remove from agenda
data["telechat_date"] = ""
@ -244,7 +298,7 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date)
self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date)
def test_start_iesg_process_on_draft(self):
make_test_data()
@ -311,6 +365,18 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(events[-3].type, "started_iesg_process")
self.assertEquals(len(outbox), mailbox_before)
def test_edit_consensus(self):
draft = make_test_data()
url = urlreverse('doc_edit_consensus', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "secretary", url)
self.assertTrue(not draft.latest_event(ConsensusDocEvent, type="changed_consensus"))
r = self.client.post(url, dict(consensus="Yes"))
self.assertEquals(r.status_code, 302)
self.assertEqual(draft.latest_event(ConsensusDocEvent, type="changed_consensus").consensus, True)
class ResurrectTestCase(django.test.TestCase):
fixtures = ['names']
@ -406,6 +472,16 @@ class AddCommentTestCase(django.test.TestCase):
self.assertTrue("updated" in outbox[-1]['Subject'])
self.assertTrue(draft.name in outbox[-1]['Subject'])
# Make sure we can also do it as IANA
self.client.login(remote_user="iana")
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form textarea[name=comment]')), 1)
class EditPositionTestCase(django.test.TestCase):
fixtures = ['names']
@ -670,12 +746,21 @@ class BallotWriteupsTestCase(django.test.TestCase):
url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "secretary", url)
# add a IANA review note
draft.set_state(State.objects.get(type="draft-iana-review", slug="not-ok"))
DocEvent.objects.create(type="iana_review",
doc=draft,
by=Person.objects.get(user__username="iana"),
desc="IANA does not approve of this document, it does not make sense.",
)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('textarea[name=ballot_writeup]')), 1)
self.assertEquals(len(q('input[type=submit][value*="Save Ballot Writeup"]')), 1)
self.assertTrue("IANA does not" in r.content)
# save
r = self.client.post(url, dict(
@ -869,6 +954,44 @@ class MakeLastCallTestCase(django.test.TestCase):
self.assertTrue("Last Call" in outbox[-3]['Subject'])
self.assertTrue("Last Call" in draft.message_set.order_by("-time")[0].subject)
class RequestPublicationTestCase(django.test.TestCase):
fixtures = ['names']
def test_request_publication(self):
draft = make_test_data()
draft.stream = StreamName.objects.get(slug="iab")
draft.group = Group.objects.get(acronym="iab")
draft.intended_std_level = IntendedStdLevelName.objects.get(slug="inf")
draft.save()
draft.set_state(State.objects.get(type="draft-stream-iab", slug="approved"))
url = urlreverse('doc_request_publication', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "iabchair", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
subject = q('input#id_subject')[0].get("value")
self.assertTrue("Document Action" in subject)
body = q('.request-publication #id_body').text()
self.assertTrue("Informational" in body)
self.assertTrue("IAB" in body)
# approve
mailbox_before = len(outbox)
r = self.client.post(url, dict(subject=subject, body=body))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
self.assertEquals(draft.get_state_slug("draft-stream-iab"), "rfc-edit")
self.assertEquals(len(outbox), mailbox_before + 2)
self.assertTrue("Document Action" in outbox[-2]['Subject'])
self.assertTrue("Document Action" in draft.message_set.order_by("-time")[0].subject)
# the IANA copy
self.assertTrue("Document Action" in outbox[-1]['Subject'])
class ExpireIDsTestCase(django.test.TestCase):
fixtures = ['names']
@ -1099,341 +1222,6 @@ class ExpireLastCallTestCase(django.test.TestCase):
self.assertEquals(draft.docevent_set.count(), events_before + 1)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("Last Call Expired" in outbox[-1]["Subject"])
TEST_RFC_INDEX = '''<?xml version="1.0" encoding="UTF-8"?>
<rfc-index xmlns="http://www.rfc-editor.org/rfc-index"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.rfc-editor.org/rfc-index
http://www.rfc-editor.org/rfc-index.xsd">
<bcp-entry>
<doc-id>BCP0110</doc-id>
<is-also>
<doc-id>RFC4170</doc-id>
</is-also>
</bcp-entry>
<bcp-entry>
<doc-id>BCP0111</doc-id>
<is-also>
<doc-id>RFC4181</doc-id>
<doc-id>RFC4841</doc-id>
</is-also>
</bcp-entry>
<fyi-entry>
<doc-id>FYI0038</doc-id>
<is-also>
<doc-id>RFC3098</doc-id>
</is-also>
</fyi-entry>
<rfc-entry>
<doc-id>RFC1938</doc-id>
<title>A One-Time Password System</title>
<author>
<name>N. Haller</name>
</author>
<author>
<name>C. Metz</name>
</author>
<date>
<month>May</month>
<year>1996</year>
</date>
<format>
<file-format>ASCII</file-format>
<char-count>44844</char-count>
<page-count>18</page-count>
</format>
<keywords>
<kw>OTP</kw>
<kw>authentication</kw>
<kw>S/KEY</kw>
</keywords>
<abstract><p>This document describes a one-time password authentication system (OTP). [STANDARDS-TRACK]</p></abstract>
<obsoleted-by>
<doc-id>RFC2289</doc-id>
</obsoleted-by>
<current-status>PROPOSED STANDARD</current-status>
<publication-status>PROPOSED STANDARD</publication-status>
<stream>Legacy</stream>
</rfc-entry>
<rfc-entry>
<doc-id>RFC2289</doc-id>
<title>A One-Time Password System</title>
<author>
<name>N. Haller</name>
</author>
<author>
<name>C. Metz</name>
</author>
<author>
<name>P. Nesser</name>
</author>
<author>
<name>M. Straw</name>
</author>
<date>
<month>February</month>
<year>1998</year>
</date>
<format>
<file-format>ASCII</file-format>
<char-count>56495</char-count>
<page-count>25</page-count>
</format>
<keywords>
<kw>ONE-PASS</kw>
<kw>authentication</kw>
<kw>OTP</kw>
<kw>replay</kw>
<kw>attach</kw>
</keywords>
<abstract><p>This document describes a one-time password authentication system (OTP). The system provides authentication for system access (login) and other applications requiring authentication that is secure against passive attacks based on replaying captured reusable passwords. [STANDARDS- TRACK]</p></abstract>
<obsoletes>
<doc-id>RFC1938</doc-id>
</obsoletes>
<is-also>
<doc-id>STD0061</doc-id>
</is-also>
<current-status>STANDARD</current-status>
<publication-status>DRAFT STANDARD</publication-status>
<stream>Legacy</stream>
</rfc-entry>
<rfc-entry>
<doc-id>RFC3098</doc-id>
<title>How to Advertise Responsibly Using E-Mail and Newsgroups or - how NOT to $$$$$ MAKE ENEMIES FAST! $$$$$</title>
<author>
<name>T. Gavin</name>
</author>
<author>
<name>D. Eastlake 3rd</name>
</author>
<author>
<name>S. Hambridge</name>
</author>
<date>
<month>April</month>
<year>2001</year>
</date>
<format>
<file-format>ASCII</file-format>
<char-count>64687</char-count>
<page-count>28</page-count>
</format>
<keywords>
<kw>internet</kw>
<kw>marketing</kw>
<kw>users</kw>
<kw>service</kw>
<kw>providers</kw>
<kw>isps</kw>
</keywords>
<abstract><p>This memo offers useful suggestions for responsible advertising techniques that can be used via the internet in an environment where the advertiser, recipients, and the Internet Community can coexist in a productive and mutually respectful fashion. This memo provides information for the Internet community.</p></abstract>
<draft>draft-ietf-run-adverts-02</draft>
<is-also>
<doc-id>FYI0038</doc-id>
</is-also>
<current-status>INFORMATIONAL</current-status>
<publication-status>INFORMATIONAL</publication-status>
<stream>Legacy</stream>
</rfc-entry>
<rfc-entry>
<doc-id>RFC4170</doc-id>
<title>Tunneling Multiplexed Compressed RTP (TCRTP)</title>
<author>
<name>B. Thompson</name>
</author>
<author>
<name>T. Koren</name>
</author>
<author>
<name>D. Wing</name>
</author>
<date>
<month>November</month>
<year>2005</year>
</date>
<format>
<file-format>ASCII</file-format>
<char-count>48990</char-count>
<page-count>24</page-count>
</format>
<keywords>
<kw>real-time transport protocol</kw>
</keywords>
<abstract><p>This document describes a method to improve the bandwidth utilization of RTP streams over network paths that carry multiple Real-time Transport Protocol (RTP) streams in parallel between two endpoints, as in voice trunking. The method combines standard protocols that provide compression, multiplexing, and tunneling over a network path for the purpose of reducing the bandwidth used when multiple RTP streams are carried over that path. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</p></abstract>
<draft>draft-ietf-avt-tcrtp-08</draft>
<is-also>
<doc-id>BCP0110</doc-id>
</is-also>
<current-status>BEST CURRENT PRACTICE</current-status>
<publication-status>BEST CURRENT PRACTICE</publication-status>
<stream>IETF</stream>
<area>rai</area>
<wg_acronym>avt</wg_acronym>
</rfc-entry>
<rfc-entry>
<doc-id>RFC4181</doc-id>
<title>Guidelines for Authors and Reviewers of MIB Documents</title>
<author>
<name>C. Heard</name>
<title>Editor</title>
</author>
<date>
<month>September</month>
<year>2005</year>
</date>
<format>
<file-format>ASCII</file-format>
<char-count>102521</char-count>
<page-count>42</page-count>
</format>
<keywords>
<kw>standards-track specifications</kw>
<kw>management information base</kw>
<kw>review</kw>
</keywords>
<abstract><p>This memo provides guidelines for authors and reviewers of IETF standards-track specifications containing MIB modules. Applicable portions may be used as a basis for reviews of other MIB documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</p></abstract>
<draft>draft-ietf-ops-mib-review-guidelines-04</draft>
<updated-by>
<doc-id>RFC4841</doc-id>
</updated-by>
<is-also>
<doc-id>BCP0111</doc-id>
</is-also>
<current-status>BEST CURRENT PRACTICE</current-status>
<publication-status>BEST CURRENT PRACTICE</publication-status>
<stream>IETF</stream>
<area>rtg</area>
<wg_acronym>ospf</wg_acronym>
<errata-url>http://www.rfc-editor.org/errata_search.php?rfc=4181</errata-url>
</rfc-entry>
<rfc-entry>
<doc-id>RFC4841</doc-id>
<title>RFC 4181 Update to Recognize the IETF Trust</title>
<author>
<name>C. Heard</name>
<title>Editor</title>
</author>
<date>
<month>March</month>
<year>2007</year>
</date>
<format>
<file-format>ASCII</file-format>
<char-count>4414</char-count>
<page-count>3</page-count>
</format>
<keywords>
<kw>management information base</kw>
<kw> standards-track specifications</kw>
<kw>mib review</kw>
</keywords>
<abstract><p>This document updates RFC 4181, "Guidelines for Authors and Reviewers of MIB Documents", to recognize the creation of the IETF Trust. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</p></abstract>
<draft>draft-heard-rfc4181-update-00</draft>
<updates>
<doc-id>RFC4181</doc-id>
</updates>
<is-also>
<doc-id>BCP0111</doc-id>
</is-also>
<current-status>BEST CURRENT PRACTICE</current-status>
<publication-status>BEST CURRENT PRACTICE</publication-status>
<stream>IETF</stream>
<wg_acronym>NON WORKING GROUP</wg_acronym>
</rfc-entry>
<std-entry>
<doc-id>STD0061</doc-id>
<title>A One-Time Password System</title>
<is-also>
<doc-id>RFC2289</doc-id>
</is-also>
</std-entry>
</rfc-index>
'''
TEST_QUEUE = '''<rfc-editor-queue xmlns="http://www.rfc-editor.org/rfc-editor-queue">
<section name="IETF STREAM: WORKING GROUP STANDARDS TRACK">
<entry xml:id="draft-ietf-sipping-app-interaction-framework">
<draft>draft-ietf-sipping-app-interaction-framework-05.txt</draft>
<date-received>2005-10-17</date-received>
<state>EDIT</state>
<normRef>
<ref-name>draft-ietf-sip-gruu</ref-name>
<ref-state>IN-QUEUE</ref-state>
</normRef>
<authors>J. Rosenberg</authors>
<title>
A Framework for Application Interaction in the Session Initiation Protocol (SIP)
</title>
<bytes>94672</bytes>
<source>Session Initiation Proposal Investigation</source>
</entry>
</section>
<section name="IETF STREAM: NON-WORKING GROUP STANDARDS TRACK">
<entry xml:id="draft-ietf-sip-gruu">
<draft>draft-ietf-sip-gruu-15.txt</draft>
<date-received>2007-10-15</date-received>
<state>MISSREF</state>
<normRef>
<ref-name>draft-ietf-sip-outbound</ref-name>
<ref-state>NOT-RECEIVED</ref-state>
</normRef>
<authors>J. Rosenberg</authors>
<title>
Obtaining and Using Globally Routable User Agent (UA) URIs (GRUU) in the Session Initiation Protocol (SIP)
</title>
<bytes>95501</bytes>
<source>Session Initiation Protocol</source>
</entry>
</section>
<section name="IETF STREAM: WORKING GROUP INFORMATIONAL/EXPERIMENTAL/BCP">
</section>
<section name="IETF STREAM: NON-WORKING GROUP INFORMATIONAL/EXPERIMENTAL/BCP">
<entry xml:id="draft-thomson-beep-async">
<draft>draft-thomson-beep-async-02.txt</draft>
<date-received>2009-05-12</date-received>
<state>EDIT</state>
<state>IANA</state>
<authors>M. Thomson</authors>
<title>
Asynchronous Channels for the Blocks Extensible Exchange Protocol (BEEP)
</title>
<bytes>17237</bytes>
<source>IETF - NON WORKING GROUP</source>
</entry>
</section>
<section name="IAB STREAM">
</section>
<section name="IRTF STREAM">
</section>
<section name="INDEPENDENT SUBMISSIONS">
</section>
</rfc-editor-queue>
'''
class MirrorScriptTestCases(unittest.TestCase,RealDatabaseTest):
def setUp(self):
self.setUpRealDatabase()
def tearDown(self):
self.tearDownRealDatabase()
def testRfcIndex(self):
print " Testing rfc-index.xml parsing"
from ietf.idrfc.mirror_rfc_index import parse
data = parse(StringIO.StringIO(TEST_RFC_INDEX))
self.assertEquals(len(data), 6)
print "OK"
def testRfcEditorQueue(self):
print " Testing queue2.xml parsing"
from ietf.idrfc.mirror_rfc_editor_queue import parse_all
(drafts,refs) = parse_all(StringIO.StringIO(TEST_QUEUE))
self.assertEquals(len(drafts), 3)
self.assertEquals(len(refs), 3)
print "OK"
class IndividualInfoFormsTestCase(django.test.TestCase):

View file

@ -49,12 +49,13 @@ urlpatterns = patterns('',
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/(?P<ballot_id>[0-9]+)/emailposition/$', views_ballot.send_ballot_comment, name='doc_send_ballot_comment'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/(?P<ballot_id>[0-9]+)/$', views_doc.document_ballot, name="doc_ballot"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"),
(r'^(?P<name>[A-Za-z0-9._+-]+)/doc.json$', views_doc.document_debug),
(r'^(?P<name>[A-Za-z0-9._+-]+)/doc.json$', views_doc.document_json),
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballotpopup/$', views_doc.ballot_for_popup),
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot.tsv$', views_doc.ballot_tsv),
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot.json$', views_doc.ballot_json),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/state/$', views_edit.change_state, name='doc_change_state'), # IESG state
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/state/(?P<state_type>iana-action|iana-review)/$', views_edit.change_iana_state, name='doc_change_iana_state'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/info/$', views_edit.edit_info, name='doc_edit_info'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/requestresurrect/$', views_edit.request_resurrect, name='doc_request_resurrect'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/resurrect/$', views_edit.resurrect, name='doc_resurrect'),
@ -66,6 +67,8 @@ urlpatterns = patterns('',
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/telechat/$', views_edit.telechat_date, name='doc_change_telechat_date'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/iesgnote/$', views_edit.edit_iesg_note, name='doc_change_iesg_note'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/ad/$', views_edit.edit_ad, name='doc_change_ad'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/consensus/$', views_edit.edit_consensus, name='doc_edit_consensus'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/requestpublication/$', views_edit.request_publication, name='doc_request_publication'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/clearballot/$', views_ballot.clear_ballot, name='doc_clear_ballot'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/deferballot/$', views_ballot.defer_ballot, name='doc_defer_ballot'),

View file

@ -2,6 +2,7 @@ from django.conf import settings
from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin
from ietf.idrfc.mails import *
from ietf.ietfauth.decorators import has_role
def add_document_comment(request, doc, text, ballot=None):
if request:
@ -175,3 +176,24 @@ def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_i
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
update_telechat = update_telechatREDESIGN
def can_edit_intended_std_level(doc, user):
return user.is_authenticated() and (
has_role(user, ["Secretariat", "Area Director"]) or
doc.group.role_set.filter(name__in=("chair", "auth", "delegate"), person__user=user)
)
def can_edit_consensus(doc, user):
return user.is_authenticated() and (
has_role(user, ["Secretariat", "Area Director"]) or
doc.group.role_set.filter(name__in=("chair", "auth", "delegate"), person__user=user)
)
def nice_consensus(consensus):
mapping = {
None: "Unknown",
True: "Yes",
False: "No"
}
return mapping[consensus]

View file

@ -726,7 +726,7 @@ def ballot_writeupnotes(request, name):
msg = generate_issue_ballot_mail(request, doc)
send_mail_preformatted(request, msg)
email_iana(request, doc, 'drafts-eval@icann.org', msg)
# email_iana(request, doc, 'drafts-eval@icann.org', msg)
doc.b_sent_date = date.today()
doc.save()
@ -807,8 +807,8 @@ def ballot_writeupnotesREDESIGN(request, name):
msg = generate_issue_ballot_mail(request, doc, ballot)
send_mail_preformatted(request, msg)
email_iana(request, doc, 'drafts-eval@icann.org', msg)
send_mail_preformatted(request, msg, extra=extra_automation_headers(doc),
override={ "To": "IANA <drafts-eval@icann.org>" })
e = DocEvent(doc=doc, by=login)
e.by = login
@ -1043,7 +1043,8 @@ def approve_ballotREDESIGN(request, name):
send_mail_preformatted(request, announcement)
if action == "to_announcement_list":
email_iana(request, doc, "drafts-approval@icann.org", announcement)
send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
override={ "To": "IANA <drafts-approval@icann.org>" })
msg = infer_message(announcement)
msg.by = login
@ -1068,62 +1069,10 @@ class MakeLastCallForm(forms.Form):
@group_required('Secretariat')
def make_last_call(request, name):
"""Make last call for Internet Draft, sending out announcement."""
doc = get_object_or_404(InternetDraft, filename=name)
if not doc.idinternal:
raise Http404()
login = IESGLogin.objects.get(login_name=request.user.username)
ballot = doc.idinternal.ballot
docs = [i.document() for i in doc.idinternal.ballot_set()]
announcement = ballot.last_call_text
if request.method == 'POST':
form = MakeLastCallForm(request.POST)
if form.is_valid():
send_mail_preformatted(request, announcement)
email_iana(request, doc, "drafts-lastcall@icann.org", announcement)
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.IN_LAST_CALL), None)
doc.idinternal.event_date = date.today()
doc.idinternal.save()
idrfcutil_log_state_changed(request, doc, login)
doc.lc_sent_date = form.cleaned_data['last_call_sent_date']
doc.lc_expiration_date = form.cleaned_data['last_call_expiration_date']
doc.save()
comment = "Last call has been made for %s ballot and state has been changed to %s" % (doc.filename, doc.idinternal.cur_state.state)
email_owner(request, doc, doc.idinternal.job_owner, login, comment)
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
else:
initial = {}
initial["last_call_sent_date"] = date.today()
expire_days = 14
if doc.group_id == Acronym.INDIVIDUAL_SUBMITTER:
expire_days = 28
initial["last_call_expiration_date"] = date.today() + timedelta(days=expire_days)
form = MakeLastCallForm(initial=initial)
return render_to_response('idrfc/make_last_call.html',
dict(doc=doc,
docs=docs,
form=form),
context_instance=RequestContext(request))
@group_required('Secretariat')
def make_last_callREDESIGN(request, name):
"""Make last call for Internet Draft, sending out announcement."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
raise Http404
login = request.user.get_profile()
@ -1136,7 +1085,8 @@ def make_last_callREDESIGN(request, name):
form = MakeLastCallForm(request.POST)
if form.is_valid():
send_mail_preformatted(request, announcement)
email_iana(request, doc, "drafts-lastcall@icann.org", announcement)
send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
override={ "To": "IANA <drafts-lastcall@icann.org>" })
msg = infer_message(announcement)
msg.by = login
@ -1171,7 +1121,14 @@ def make_last_callREDESIGN(request, name):
e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time())
e.expires = form.cleaned_data['last_call_expiration_date']
e.save()
# update IANA Review state
prev_state = doc.get_state("draft-iana-review")
if not prev_state:
next_state = State.objects.get(type="draft-iana-review", slug="need-rev")
doc.set_state(next_state)
add_state_change_event(doc, login, prev_state, next_state)
return HttpResponseRedirect(doc.get_absolute_url())
else:
initial = {}
@ -1188,7 +1145,3 @@ def make_last_callREDESIGN(request, name):
dict(doc=doc,
form=form),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
make_last_call = make_last_callREDESIGN

View file

@ -46,6 +46,7 @@ from django.conf import settings
from ietf.idtracker.models import InternetDraft, IDInternal, BallotInfo, DocumentComment
from ietf.idtracker.templatetags.ietf_filters import format_textarea, fill
from ietf.idrfc import markup_txt
from ietf.idrfc.utils import *
from ietf.idrfc.models import RfcIndex, DraftVersions
from ietf.idrfc.idrfc_wrapper import BallotWrapper, IdWrapper, RfcWrapper
from ietf.ietfworkflows.utils import get_full_info_for_draft
@ -54,7 +55,6 @@ from ietf.doc.utils import *
from ietf.utils.history import find_history_active_at
from ietf.ietfauth.decorators import has_role
def render_document_top(request, doc, tab, name):
tabs = []
tabs.append(("Document", "document", urlreverse("ietf.idrfc.views_doc.document_main", kwargs=dict(name=name)), True))
@ -349,17 +349,51 @@ def document_ballot(request, name, ballot_id=None):
),
context_instance=RequestContext(request))
def document_debug(request, name):
r = re.compile("^rfc([1-9][0-9]*)$")
m = r.match(name)
if m:
rfc_number = int(m.group(1))
rfci = get_object_or_404(RfcIndex, rfc_number=rfc_number)
doc = RfcWrapper(rfci)
else:
id = get_object_or_404(InternetDraft, filename=name)
doc = IdWrapper(draft=id)
return HttpResponse(doc.to_json(), mimetype='text/plain')
def document_json(request, name):
doc = get_object_or_404(Document, docalias__name=name)
def extract_name(s):
return s.name if s else None
data = {}
data["name"] = doc.name
data["rev"] = doc.rev
data["time"] = doc.time.strftime("%Y-%m-%d %H:%M:%S")
data["group"] = None
if doc.group:
data["group"] = dict(
name=doc.group.name,
type=extract_name(doc.group.type),
acronym=doc.group.acronym)
data["expires"] = doc.expires.strftime("%Y-%m-%d %H:%M:%S") if doc.expires else None
data["title"] = doc.title
data["abstract"] = doc.abstract
data["aliases"] = list(doc.docalias_set.values_list("name", flat=True))
data["state"] = extract_name(doc.get_state())
data["intended_std_level"] = extract_name(doc.intended_std_level)
data["std_level"] = extract_name(doc.std_level)
data["authors"] = [
dict(name=e.person.name,
email=e.address,
affiliation=e.person.affiliation)
for e in Email.objects.filter(documentauthor__document=doc).select_related("person").order_by("documentauthor__order")
]
data["shepherd"] = doc.shepherd.formatted_email() if doc.shepherd else None
data["ad"] = doc.ad.role_email("ad").formatted_email() if doc.ad else None
if doc.type_id == "draft":
data["iesg_state"] = extract_name(doc.get_state("draft-iesg"))
data["rfceditor_state"] = extract_name(doc.get_state("draft-rfceditor"))
data["iana_review_state"] = extract_name(doc.get_state("draft-iana-review"))
data["iana_action_state"] = extract_name(doc.get_state("draft-iana-action"))
if doc.stream_id in ("ietf", "irtf", "iab"):
e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
data["consensus"] = e.consensus if e else None
data["stream"] = extract_name(doc.stream)
return HttpResponse(json.dumps(data, indent=2), mimetype='text/plain')
def _get_html(key, filename, split=True):
return get_document_content(key, filename, split=split, markup=True)
@ -415,7 +449,16 @@ def document_main_idrfc(request, name, tab):
info['is_rfc'] = False
info['conflict_reviews'] = [ rel.source for alias in id.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='conflrev') ]
info['rfc_editor_state'] = id.get_state("draft-rfceditor")
info['iana_review_state'] = id.get_state("draft-iana-review")
info['iana_action_state'] = id.get_state("draft-iana-action")
info["consensus"] = None
if id.stream_id in ("ietf", "irtf", "iab"):
e = id.latest_event(ConsensusDocEvent, type="changed_consensus")
info["consensus"] = nice_consensus(e and e.consensus)
info["can_edit_consensus"] = can_edit_consensus(id, request.user)
info["can_edit_intended_std_level"] = can_edit_intended_std_level(id, request.user)
(content1, content2) = _get_html(
str(name)+","+str(id.revision)+",html",
os.path.join(settings.INTERNET_DRAFT_PATH, name+"-"+id.revision+".txt"))

View file

@ -3,7 +3,7 @@
import re, os
from datetime import datetime, date, time, timedelta
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
from django.shortcuts import render_to_response, get_object_or_404
from django.core.urlresolvers import reverse as urlreverse
from django.template.loader import render_to_string
@ -13,10 +13,10 @@ from django.utils.html import strip_tags
from django.db.models import Max
from django.conf import settings
from ietf.utils.mail import send_mail_text
from ietf.utils.mail import send_mail_text, send_mail_message
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
from ietf.ietfauth.decorators import has_role
from ietf.ietfauth.decorators import has_role, role_required
from ietf.idtracker.models import *
from ietf.iesg.models import *
from ietf.idrfc.mails import *
@ -26,10 +26,13 @@ from ietf.idrfc.lastcall import request_last_call
from ietf.ietfworkflows.models import Stream
from ietf.ietfworkflows.utils import update_stream
from ietf.ietfworkflows.streams import get_stream_from_draft
from ietf.ietfworkflows.accounts import can_edit_state
from ietf.doc.models import *
from ietf.doc.utils import *
from ietf.name.models import IntendedStdLevelName, DocTagName, StreamName
from ietf.person.models import Person, Email
from ietf.message.models import Message
class ChangeStateForm(forms.Form):
pass
@ -58,20 +61,21 @@ def change_stateREDESIGN(request, name):
if request.method == 'POST':
form = ChangeStateForm(request.POST)
if form.is_valid():
state = form.cleaned_data['state']
next_state = form.cleaned_data['state']
prev_state = doc.get_state("draft-iesg")
tag = form.cleaned_data['substate']
comment = form.cleaned_data['comment'].strip()
prev = doc.get_state("draft-iesg")
# tag handling is a bit awkward since the UI still works
# as if IESG tags are a substate
prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))
prev_tag = prev_tag[0] if prev_tag else None
if state != prev or tag != prev_tag:
if next_state != prev_state or tag != prev_tag:
save_document_in_history(doc)
doc.set_state(state)
doc.set_state(next_state)
if prev_tag:
doc.tags.remove(prev_tag)
@ -79,7 +83,7 @@ def change_stateREDESIGN(request, name):
if tag:
doc.tags.add(tag)
e = log_state_changed(request, doc, login, prev, prev_tag)
e = log_state_changed(request, doc, login, prev_state, prev_tag)
if comment:
c = DocEvent(type="added_comment")
@ -96,7 +100,15 @@ def change_stateREDESIGN(request, name):
email_state_changed(request, doc, e.desc)
email_owner(request, doc, doc.ad, login, e.desc)
if state.slug == "lc-req":
if prev_state and prev_state.slug in ("ann", "rfcqueue") and next_state.slug not in ("rfcqueue", "pub"):
email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state)
if next_state.slug in ("iesg-eva", "lc"):
if not doc.get_state_slug("draft-iana-review"):
doc.set_state(State.objects.get(type="draft-iana-review", slug="rev-need"))
if next_state.slug == "lc-req":
request_last_call(request, doc)
return render_to_response('idrfc/last_call_requested.html',
@ -129,6 +141,7 @@ def change_stateREDESIGN(request, name):
return render_to_response('idrfc/change_stateREDESIGN.html',
dict(form=form,
doc=doc,
state=state,
prev_state=prev_state,
next_states=next_states,
to_iesg_eval=to_iesg_eval),
@ -138,6 +151,52 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
change_state = change_stateREDESIGN
ChangeStateForm = ChangeStateFormREDESIGN
class ChangeIanaStateForm(forms.Form):
state = forms.ModelChoiceField(State.objects.all(), required=False)
def __init__(self, state_type, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
choices = State.objects.filter(type=state_type).order_by("order").values_list("pk", "name")
self.fields['state'].choices = [("", "-------")] + list(choices)
@role_required('Secretariat', 'IANA')
def change_iana_state(request, name, state_type):
"""Change IANA review state of Internet Draft. Normally, this is done via
automatic sync, but this form allows one to set it manually."""
doc = get_object_or_404(Document, docalias__name=name)
state_type = doc.type_id + "-" + state_type
prev_state = doc.get_state(state_type)
if request.method == 'POST':
form = ChangeIanaStateForm(state_type, request.POST)
if form.is_valid():
next_state = form.cleaned_data['state']
if next_state != prev_state:
save_document_in_history(doc)
doc.set_state(next_state)
e = add_state_change_event(doc, request.user.get_profile(), prev_state, next_state)
doc.time = e.time
doc.save()
return HttpResponseRedirect(doc.get_absolute_url())
else:
form = ChangeIanaStateForm(state_type, initial=dict(state=prev_state.pk if prev_state else None))
return render_to_response('idrfc/change_iana_state.html',
dict(form=form,
doc=doc),
context_instance=RequestContext(request))
class ChangeStreamForm(forms.Form):
stream = forms.ModelChoiceField(StreamName.objects.exclude(slug="legacy"), required=False)
comment = forms.CharField(widget=forms.Textarea, required=False)
@ -197,13 +256,15 @@ class ChangeIntentionForm(forms.Form):
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status")
comment = forms.CharField(widget=forms.Textarea, required=False)
@group_required('Area_Director','Secretariat')
def change_intention(request, name):
"""Change the intended publication status of a Document of type 'draft' , notifying parties
as necessary and logging the change as a comment."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.type_id=='draft':
raise Http404()
if doc.type_id != 'draft':
raise Http404
if not can_edit_intended_std_level(doc, request.user):
return HttpResponseForbidden("You do not have the necessary permissions to view this page")
login = request.user.get_profile()
@ -617,7 +678,7 @@ def add_comment(request, name):
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
@group_required('Area_Director', 'Secretariat', 'IANA')
@group_required('Area_Director', 'Secretariat', 'IANA', 'RFC Editor')
def add_commentREDESIGN(request, name):
"""Add comment to history of document."""
doc = get_object_or_404(Document, docalias__name=name)
@ -827,3 +888,116 @@ def edit_ad(request, name):
},
context_instance = RequestContext(request))
class ConsensusForm(forms.Form):
consensus = forms.ChoiceField(choices=(("", "Unknown"), ("Yes", "Yes"), ("No", "No")), required=True)
def edit_consensus(request, name):
"""Change whether the draft is a consensus document or not."""
doc = get_object_or_404(Document, type="draft", name=name)
if not can_edit_consensus(doc, request.user):
return HttpResponseForbidden("You do not have the necessary permissions to view this page")
e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
prev_consensus = e and e.consensus
if request.method == 'POST':
form = ConsensusForm(request.POST)
if form.is_valid():
if form.cleaned_data["consensus"] != bool(prev_consensus):
e = ConsensusDocEvent(doc=doc, type="changed_consensus", by=request.user.get_profile())
e.consensus = form.cleaned_data["consensus"] == "Yes"
e.desc = "Changed consensus to <b>%s</b> from %s" % (nice_consensus(e.consensus),
nice_consensus(prev_consensus))
e.save()
return HttpResponseRedirect(urlreverse('doc_view', kwargs={'name': doc.name}))
else:
form = ConsensusForm(initial=dict(consensus=nice_consensus(prev_consensus).replace("Unknown", "")))
return render_to_response('idrfc/change_consensus.html',
{'form': form,
'doc': doc,
},
context_instance = RequestContext(request))
class PublicationForm(forms.Form):
subject = forms.CharField(max_length=200, required=True)
body = forms.CharField(widget=forms.Textarea, required=True)
def request_publication(request, name):
"""Request publication by RFC Editor for a document which hasn't
been through the IESG ballot process."""
doc = get_object_or_404(Document, type="draft", name=name, stream__in=("iab", "ise", "irtf"))
if not can_edit_state(request.user, doc):
return HttpResponseForbidden("You do not have the necessary permissions to view this page")
m = Message()
m.frm = request.user.get_profile().formatted_email()
m.to = "RFC Editor <rfc-editor@rfc-editor.org>"
m.by = request.user.get_profile()
next_state = State.objects.get(type="draft-stream-%s" % doc.stream.slug, slug="rfc-edit")
if request.method == 'POST' and not request.POST.get("reset"):
form = PublicationForm(request.POST)
if form.is_valid():
m.subject = form.cleaned_data["subject"]
m.body = form.cleaned_data["body"]
m.save()
if doc.group.acronym != "none":
m.related_groups = [doc.group]
m.related_docs = [doc]
send_mail_message(request, m)
# IANA copy
m.to = "IANA <drafts-approval@icann.org>"
send_mail_message(request, m, extra=extra_automation_headers(doc))
e = DocEvent(doc=doc, type="requested_publication", by=request.user.get_profile())
e.desc = "Sent request for publication to the RFC Editor"
e.save()
# change state
prev_state = doc.get_state(next_state.type)
doc.set_state(next_state)
e = add_state_change_event(doc, request.user.get_profile(), prev_state, next_state)
doc.time = e.time
doc.save()
return HttpResponseRedirect(urlreverse('doc_view', kwargs={'name': doc.name}))
else:
if doc.intended_std_level_id in ("std", "ds", "ps", "bcp"):
action = "Protocol Action"
else:
action = "Document Action"
from ietf.idrfc.templatetags.mail_filters import std_level_prompt
subject = "%s: '%s' to %s (%s-%s.txt)" % (action, doc.title, std_level_prompt(doc), doc.name, doc.rev)
body = generate_publication_request(request, doc)
form = PublicationForm(initial=dict(subject=subject,
body=body))
return render_to_response('idrfc/request_publication.html',
dict(form=form,
doc=doc,
message=m,
next_state=next_state,
),
context_instance = RequestContext(request))

View file

@ -22,4 +22,4 @@
# Test case for missing comment time (bug fixed in changeset 1733)
200 /feed/comments/draft-ietf-msec-newtype-keyid/
200,heavy /sitemap-idtracker.xml
#200,heavy /sitemap-idtracker.xml

View file

@ -54,7 +54,7 @@ from ietf.idrfc.utils import update_telechat
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
from ietf.ipr.models import IprDocAlias
from ietf.doc.models import Document, TelechatDocEvent
from ietf.doc.models import Document, TelechatDocEvent, LastCallDocEvent, ConsensusDocEvent
from ietf.group.models import Group
def date_threshold():
@ -139,7 +139,6 @@ def get_doc_section(id):
def get_doc_sectionREDESIGN(doc):
if doc.type_id == 'draft':
states = [16,17,18,19,20,21]
if doc.intended_std_level_id in ["bcp", "ds", "ps", "std"]:
s = "2"
else:
@ -152,7 +151,7 @@ def get_doc_sectionREDESIGN(doc):
s = s + "3"
else:
s = s + "2"
if not doc.get_state_slug=="rfc" and doc.get_state('draft-iesg').order not in states:
if not doc.get_state_slug=="rfc" and doc.get_state_slug('draft-iesg') not in ("lc", "writeupw", "goaheadw", "iesg-eva", "defer"):
s = s + "3"
elif doc.returning_item():
s = s + "2"
@ -191,23 +190,36 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
get_doc_section = get_doc_sectionREDESIGN
def agenda_docs(date, next_agenda):
from ietf.doc.models import TelechatDocEvent
matches = Document.objects.filter(docevent__telechatdocevent__telechat_date=date).distinct()
matches = Document.objects.filter(docevent__telechatdocevent__telechat_date=date).select_related("stream").distinct()
docmatches = []
for m in matches:
if m.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date != date:
for doc in matches:
if doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date != date:
continue
e = m.latest_event(type="started_iesg_process")
m.balloting_started = e.time if e else datetime.datetime.min
e = doc.latest_event(type="started_iesg_process")
doc.balloting_started = e.time if e else datetime.datetime.min
if m.type_id=='conflrev':
m.conflictdoc = m.relateddocument_set.get(relationship__slug='conflrev').target.document
if doc.type_id == "draft":
s = doc.get_state("draft-iana-review")
if s and s.slug in ("not-ok", "changed", "need-rev"):
doc.iana_review_state = str(s)
docmatches.append(m)
if doc.get_state_slug("draft-iesg") == "lc":
e = doc.latest_event(LastCallDocEvent, type="sent_last_call")
if e:
doc.lastcall_expires = e.expires
if doc.stream_id in ("ietf", "irtf", "iab"):
doc.consensus = "Unknown"
e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
if e:
doc.consensus = "Yes" if e.consensus else "No"
elif doc.type_id=='conflrev':
doc.conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
docmatches.append(doc)
# Be careful to keep this the same as what's used in agenda_documents
docmatches.sort(key=lambda d: d.balloting_started)
@ -313,19 +325,37 @@ def _agenda_json(request, date=None):
if defer:
docinfo['defer-by'] = defer.by.name
docinfo['defer-at'] = str(defer.time)
if d.type_id == 'conflrev':
td = d.relateddocument_set.get(relationship__slug='conflrev').target.document
if doc.type_id == "draft":
docinfo['intended-std-level'] = str(doc.intended_std_level)
if doc.rfc_number():
docinfo['rfc-number'] = doc.rfc_number()
else:
docinfo['rev'] = doc.rev
iana_state = doc.get_state("draft-iana-review")
if iana_state.slug in ("not-ok", "changed", "need-rev"):
docinfo['iana_review_state'] = str(iana_state)
if doc.get_state_slug("draft-iesg") == "lc":
e = doc.latest_event(LastCallDocEvent, type="sent_last_call")
if e:
docinfo['lastcall_expires'] = e.expires
docinfo['consensus'] = None
e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
if e:
docinfo['consensus'] = e.consensus
elif doc.type_id == 'conflrev':
td = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
docinfo['target-docname'] = td.canonical_name()
docinfo['target-title'] = td.title
docinfo['target-rev'] = td.rev
docinfo['intended-std-level'] = str(td.intended_std_level)
docinfo['stream'] = str(td.stream)
else:
docinfo['intended-std-level'] = str(d.intended_std_level)
if d.rfc_number():
docinfo['rfc-number'] = d.rfc_number()
else:
docinfo['rev'] = d.rev
else:
# XXX check this -- is there nothing to set for
# all other documents here?
pass
data['sections'][s]['docs'] += [docinfo, ]
wgs = agenda_wg_actions(date)

View file

@ -89,6 +89,7 @@ def has_role(user, role_names):
"Area Director": Q(person=person, name__in=("pre-ad", "ad"), group__type="area", group__state="active"),
"Secretariat": Q(person=person, name="secr", group__acronym="secretariat"),
"IANA": Q(person=person, name="auth", group__acronym="iana"),
"RFC Editor": Q(person=person, name="auth", group__acronym="rfceditor"),
"IAD": Q(person=person, name="admdir", group__acronym="ietf"),
"IETF Chair": Q(person=person, name="chair", group__acronym="ietf"),
"IAB Chair": Q(person=person, name="chair", group__acronym="iab"),

View file

@ -46,7 +46,7 @@ from django.utils.http import urlquote
from django.utils import simplejson as json
from django.utils.translation import ugettext as _
from ietf.ietfauth.forms import RegistrationForm, PasswordForm, RecoverPasswordForm
from ietf.ietfauth.forms import RegistrationForm, PasswordForm, RecoverPasswordForm, TestEmailForm
def index(request):
return render_to_response('registration/index.html', context_instance=RequestContext(request))

View file

@ -108,14 +108,6 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
def can_edit_state(user, draft):
streamed = get_streamed_draft(draft)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES and (not streamed or not streamed.stream):
person = get_person_for_user(user)
if not person:
return False
return (is_secretariat(user) or
is_wgchair(person) or
is_wgdelegate(person))
return (is_secretariat(user) or
is_authorized_in_draft_stream(user, draft))

View file

@ -74,6 +74,8 @@ def edit_actions(context, wrapper):
if can_edit_state(user, draft):
actions.append(("Change stream state", urlreverse('edit_state', kwargs=dict(name=doc.draft_name))))
if draft.stream_id in ("iab", "ise", "irtf"):
actions.append(("Request publication", urlreverse('doc_request_publication', kwargs=dict(name=doc.draft_name))))
if can_manage_shepherd_of_a_document(user, draft):
actions.append(("Change shepherd", urlreverse('doc_managing_shepherd', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))))

View file

@ -97,7 +97,7 @@
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="iana-crd" model="name.doctagname">
<object pk="iana" model="name.doctagname">
<field type="CharField" name="name">IANA coordination</field>
<field type="TextField" name="desc">RFC-Editor/IANA Registration Coordination</field>
<field type="BooleanField" name="used">True</field>
@ -325,36 +325,6 @@
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="no" model="name.groupballotpositionname">
<field type="CharField" name="name">No</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="yes" model="name.groupballotpositionname">
<field type="CharField" name="name">Yes</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="abstain" model="name.groupballotpositionname">
<field type="CharField" name="name">Abstain</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="block" model="name.groupballotpositionname">
<field type="CharField" name="name">Block</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="norecord" model="name.groupballotpositionname">
<field type="CharField" name="name">No record</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="bof" model="name.groupstatename">
<field type="CharField" name="name">BOF</field>
<field type="TextField" name="desc"></field>
@ -751,8 +721,11 @@
<object pk="draft-iesg" model="doc.statetype">
<field type="CharField" name="label">IESG state</field>
</object>
<object pk="draft-iana" model="doc.statetype">
<field type="CharField" name="label">IANA state</field>
<object pk="draft-iana-action" model="doc.statetype">
<field type="CharField" name="label">IANA Action state</field>
</object>
<object pk="draft-iana-review" model="doc.statetype">
<field type="CharField" name="label">IANA Review state</field>
</object>
<object pk="draft-rfceditor" model="doc.statetype">
<field type="CharField" name="label">RFC Editor state</field>
@ -1003,6 +976,141 @@
<field type="IntegerField" name="order">6</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="104" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">rfcedack</field>
<field type="CharField" name="name">RFC-Ed-Ack</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">Request completed. The RFC Editor has acknowledged receipt of IANA's message that the actions have been completed</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="103" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">waitrfc</field>
<field type="CharField" name="name">Waiting on RFC Editor</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">IANA has notified the RFC Editor that the actions have been completed</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="102" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">waitwgc</field>
<field type="CharField" name="name">Waiting on WGC</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">IANA is waiting on the IETF Working Group Chairs to respond</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="101" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">waitad</field>
<field type="CharField" name="name">Waiting on ADs</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">IANA is waiting on the IETF Area Directors to respond</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="100" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">waitauth</field>
<field type="CharField" name="name">Waiting on Authors</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">IANA is waiting on the document's authors to respond</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="99" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">inprog</field>
<field type="CharField" name="name">In Progress</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">IANA is currently processing the actions for this document</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="98" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">newdoc</field>
<field type="CharField" name="name">New Document</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">A new document has been received by IANA, but no actions have been taken</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="105" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">onhold</field>
<field type="CharField" name="name">On Hold</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">IANA has suspended work on the document</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="106" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-action</field>
<field type="SlugField" name="slug">noic</field>
<field type="CharField" name="name">No IC</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">Request completed. There were no IANA actions for this document</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="107" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-review</field>
<field type="SlugField" name="slug">need-rev</field>
<field type="CharField" name="name">IANA Review Needed</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc"></field>
<field type="IntegerField" name="order">1</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="108" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-review</field>
<field type="SlugField" name="slug">ok-act</field>
<field type="CharField" name="name">IANA OK - Actions Needed</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">Document requires IANA actions, and the IANA Considerations section indicates the details of the actions correctly.</field>
<field type="IntegerField" name="order">2</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="109" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-review</field>
<field type="SlugField" name="slug">ok-noact</field>
<field type="CharField" name="name">IANA OK - No Actions Needed</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">Document requires no IANA action, and the IANA Considerations section indicates this correctly.</field>
<field type="IntegerField" name="order">3</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="110" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-review</field>
<field type="SlugField" name="slug">not-ok</field>
<field type="CharField" name="name">IANA Not OK</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">IANA has issues with the text of the IANA Considerations section of the document.</field>
<field type="IntegerField" name="order">4</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="111" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iana-review</field>
<field type="SlugField" name="slug">changed</field>
<field type="CharField" name="name">Version Changed - Review Needed</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">Document revision has changed after review by IANA.</field>
<field type="IntegerField" name="order">5</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="112" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-rfceditor</field>
<field type="SlugField" name="slug">auth48-done</field>
<field type="CharField" name="name">AUTH48-DONE</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">Final approvals are complete</field>
<field type="IntegerField" name="order">0</field>
<field to="doc.state" name="next_states" rel="ManyToManyRel"></field>
</object>
<object pk="16" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-iesg</field>
<field type="SlugField" name="slug">pub-req</field>
@ -1185,7 +1293,7 @@
</object>
<object pk="27" model="doc.state">
<field to="doc.statetype" name="type" rel="ManyToOneRel">draft-rfceditor</field>
<field type="SlugField" name="slug">iana-crd</field>
<field type="SlugField" name="slug">iana</field>
<field type="CharField" name="name">IANA</field>
<field type="BooleanField" name="used">True</field>
<field type="TextField" name="desc">RFC-Editor/IANA Registration Coordination</field>
@ -1780,4 +1888,4 @@
<field type="IntegerField" name="order">3</field>
<field to="name.ballotpositionname" name="positions" rel="ManyToManyRel"><object pk="yes"></object><object pk="noobj"></object><object pk="block"></object><object pk="abstain"></object><object pk="norecord"></object></field>
</object>
</django-objects>
</django-objects>

View file

@ -165,6 +165,7 @@ INSTALLED_APPS = (
'ietf.ietfworkflows',
'ietf.wgchairs',
'ietf.wgcharter',
'ietf.sync',
'ietf.community',
)
@ -250,7 +251,8 @@ else:
IPR_EMAIL_TO = ['ietf-ipr@ietf.org', ]
DOC_APPROVAL_EMAIL_CC = ["RFC Editor <rfc-editor@rfc-editor.org>", ]
# Put real password in settings_local.py
IANA_SYNC_PASSWORD = "secret"
# Liaison Statement Tool settings
LIAISON_UNIVERSAL_FROM = 'Liaison Statement Management Tool <lsmt@' + IETF_DOMAIN + '>'

View file

@ -14,6 +14,7 @@ from django.core.urlresolvers import reverse as urlreverse
import debug
from ietf.group.models import Group
from ietf.idtracker.models import InternetDraft, IETFWG
from ietf.proceedings.models import Meeting
from ietf.submit.models import IdSubmissionDetail, TempIdAuthors, Preapproval
@ -226,27 +227,36 @@ class UploadForm(forms.Form):
self.idnits_message = p.stdout.read()
def get_working_group(self):
filename = self.draft.filename
existing_draft = InternetDraft.objects.filter(filename=filename)
name = self.draft.filename
existing_draft = InternetDraft.objects.filter(filename=name)
if existing_draft:
group = existing_draft[0].group and existing_draft[0].group.ietfwg or None
if group and group.pk != NONE_WG:
if settings.USE_DB_REDESIGN_PROXY_CLASSES and group.type_id == "area":
return None
if group and group.pk != NONE_WG and group.type_id != "area":
return group
else:
return None
else:
if filename.startswith('draft-ietf-'):
# Extra check for WG that contains dashes
for group in IETFWG.objects.filter(group_acronym__acronym__contains='-'):
if filename.startswith('draft-ietf-%s-' % group.group_acronym.acronym):
return group
group_acronym = filename.split('-')[2]
if name.startswith('draft-ietf-') or name.startswith("draft-irtf-"):
components = name.split("-")
if len(components) < 3:
raise forms.ValidationError("The draft name \"%s\" is missing a third part, please rename it")
if components[1] == "ietf":
group_type = "wg"
else:
group_type = "rg"
# first check groups with dashes
for g in Group.objects.filter(acronym__contains="-", type=group_type):
if name.startswith('draft-%s-%s-' % (components[1], g.acronym)):
return IETFWG().from_object(g)
try:
return IETFWG.objects.get(group_acronym__acronym=group_acronym)
except IETFWG.DoesNotExist:
raise forms.ValidationError('There is no active group with acronym \'%s\', please rename your draft' % group_acronym)
return IETFWG().from_object(Group.objects.get(acronym=components[2], type=group_type))
except Group.DoesNotExist:
raise forms.ValidationError('There is no active group with acronym \'%s\', please rename your draft' % components[2])
elif name.startswith("draft-iab-"):
return IETFWG().from_object(Group.objects.get(acronym="iab"))
else:
return None

View file

@ -16,7 +16,7 @@ from ietf.utils.mail import outbox
from ietf.person.models import Person, Email
from ietf.group.models import Group, Role
from ietf.doc.models import Document, BallotDocEvent, BallotPositionDocEvent
from ietf.doc.models import *
from ietf.submit.models import IdSubmissionDetail, Preapproval
class SubmitTestCase(django.test.TestCase):
@ -75,7 +75,6 @@ class SubmitTestCase(django.test.TestCase):
self.assertTrue(os.path.exists(os.path.join(self.staging_dir, u"%s-%s.txt" % (name, rev))))
self.assertEquals(IdSubmissionDetail.objects.filter(filename=name).count(), 1)
submission = IdSubmissionDetail.objects.get(filename=name)
self.assertEquals(submission.group_acronym.acronym, "mars")
self.assertEquals(submission.tempidauthors_set.count(), 1)
self.assertTrue(re.search('\s+Summary:\s+0\s+errors|No nits found', submission.idnits_message))
author = submission.tempidauthors_set.all()[0]
@ -139,6 +138,7 @@ class SubmitTestCase(django.test.TestCase):
draft = Document.objects.get(docalias__name=name)
self.assertEquals(draft.rev, rev)
new_revision = draft.latest_event()
self.assertEquals(draft.group.acronym, "mars")
self.assertEquals(new_revision.type, "new_revision")
self.assertEquals(new_revision.by.name, "Test Name")
self.assertTrue(not os.path.exists(os.path.join(self.staging_dir, u"%s-%s.txt" % (name, rev))))
@ -161,6 +161,16 @@ class SubmitTestCase(django.test.TestCase):
# submit new revision of existing -> supply submitter info -> confirm
draft = make_test_data()
# pretend IANA reviewed it
draft.set_state(State.objects.get(type="draft-iana-review", slug="not-ok"))
# pretend it was approved to check that we notify the RFC Editor
e = DocEvent(type="iesg_approved", doc=draft)
e.time = draft.time
e.by = Person.objects.get(name="(System)")
e.desc = "The IESG approved the document"
e.save()
# make a discuss to see if the AD gets an email
ballot_position = BallotPositionDocEvent()
ballot_position.ballot = draft.latest_event(BallotDocEvent, type="created_ballot")
@ -214,16 +224,17 @@ class SubmitTestCase(django.test.TestCase):
draft = Document.objects.get(docalias__name=name)
self.assertEquals(draft.rev, rev)
new_revision = draft.latest_event()
self.assertEquals(new_revision.type, "new_revision")
self.assertEquals(new_revision.by.name, "Test Name")
self.assertEquals(draft.group.acronym, name.split("-")[2])
self.assertEquals(draft.docevent_set.all()[1].type, "new_revision")
self.assertEquals(draft.docevent_set.all()[1].by.name, "Test Name")
self.assertTrue(not os.path.exists(os.path.join(self.repository_dir, "%s-%s.txt" % (name, old_rev))))
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "%s-%s.txt" % (name, old_rev))))
self.assertTrue(not os.path.exists(os.path.join(self.staging_dir, u"%s-%s.txt" % (name, rev))))
self.assertTrue(os.path.exists(os.path.join(self.repository_dir, u"%s-%s.txt" % (name, rev))))
self.assertEquals(draft.type_id, "draft")
self.assertEquals(draft.stream_id, "ietf")
self.assertEquals(draft.get_state("draft-stream-%s" % draft.stream_id).slug, "wg-doc")
self.assertEquals(draft.get_state_slug("draft-stream-%s" % draft.stream_id), "wg-doc")
self.assertEquals(draft.get_state_slug("draft-iana-review"), "changed")
self.assertEquals(draft.authors.count(), 1)
self.assertEquals(draft.authors.all()[0].get_name(), "Test Name")
self.assertEquals(draft.authors.all()[0].address, "testname@example.com")
@ -240,6 +251,38 @@ class SubmitTestCase(django.test.TestCase):
self.assertTrue(name in unicode(outbox[-1]))
self.assertTrue("mars" in unicode(outbox[-1]))
def test_submit_new_wg_with_dash(self):
draft = make_test_data()
group = Group.objects.create(acronym="mars-special", name="Mars Special", type_id="wg", state_id="active")
name = "draft-ietf-%s-testing-tests" % group.acronym
self.do_submission(name, "00")
self.assertEquals(IdSubmissionDetail.objects.get(filename=name).group_acronym.acronym, group.acronym)
def test_submit_new_irtf(self):
draft = make_test_data()
group = Group.objects.create(acronym="saturnrg", name="Saturn", type_id="rg", state_id="active")
name = "draft-irtf-%s-testing-tests" % group.acronym
self.do_submission(name, "00")
self.assertEquals(IdSubmissionDetail.objects.get(filename=name).group_acronym.acronym, group.acronym)
self.assertEquals(IdSubmissionDetail.objects.get(filename=name).group_acronym.type_id, group.type_id)
def test_submit_new_iab(self):
draft = make_test_data()
name = "draft-iab-testing-tests"
self.do_submission(name, "00")
self.assertEquals(IdSubmissionDetail.objects.get(filename=name).group_acronym.acronym, "iab")
def test_cancel_submission(self):
# submit -> cancel
draft = make_test_data()

View file

@ -17,6 +17,7 @@ from ietf.ietfauth.decorators import has_role
from ietf.doc.models import *
from ietf.person.models import Person, Alias, Email
from ietf.doc.utils import add_state_change_event
from ietf.message.models import Message
# Some useful states
@ -132,28 +133,34 @@ def perform_postREDESIGN(request, submission):
draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
draft.save()
draft.set_state(State.objects.get(type="draft", slug="active"))
if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00":
# automatically set state "WG Document"
draft.set_state(State.objects.get(type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
DocAlias.objects.get_or_create(name=submission.filename, document=draft)
update_authors(draft, submission)
# new revision event
a = submission.tempidauthors_set.filter(author_order=0)
if a:
submitter = ensure_person_email_info_exists(a[0]).person
else:
submitter = system
draft.set_state(State.objects.get(type="draft", slug="active"))
DocAlias.objects.get_or_create(name=submission.filename, document=draft)
update_authors(draft, submission)
# new revision event
e = NewRevisionDocEvent(type="new_revision", doc=draft, rev=draft.rev)
e.time = draft.time #submission.submission_date
e.by = submitter
e.desc = "New revision available"
e.save()
if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00":
# automatically set state "WG Document"
draft.set_state(State.objects.get(type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
if draft.get_state_slug("draft-iana-review") in ("ok-act", "ok-noact", "not-ok"):
prev_state = draft.get_state("draft-iana-review")
next_state = State.objects.get(type="draft-iana-review", slug="changed")
draft.set_state(next_state)
add_state_change_event(draft, submitter, prev_state, next_state)
# clean up old files
if prev_rev != draft.rev:
from ietf.idrfc.expire import move_draft_files_to_archive
@ -177,8 +184,7 @@ def perform_postREDESIGN(request, submission):
submission.status_id = POSTED
announce_to_lists(request, submission)
if draft.get_state("draft-iesg") != None and not was_rfc:
announce_new_version(request, submission, draft, state_change_msg)
announce_new_version(request, submission, draft, state_change_msg)
announce_to_authors(request, submission)
submission.save()
@ -261,17 +267,29 @@ def announce_new_versionREDESIGN(request, submission, draft, state_change_msg):
if draft.ad:
to_email.append(draft.ad.role_email("ad").address)
if draft.stream_id == "iab":
to_email.append("IAB Stream <iab-stream@iab.org>")
elif draft.stream_id == "ise":
to_email.append("Independent Submission Editor <rfc-ise@rfc-editor.org>")
elif draft.stream_id == "irtf":
to_email.append("IRSG <irsg@irtf.org>")
# if it has been sent to the RFC Editor, keep them in the loop
if draft.get_state_slug("draft-iesg") in ("ann", "rfcqueue"):
to_email.append("RFC Editor <rfc-editor@rfc-editor.org>")
active_ballot = draft.active_ballot()
if active_ballot:
for ad, pos in active_ballot.active_ad_positions().iteritems():
if pos and pos.pos_id == "discuss":
to_email.append(ad.role_email("ad").address)
subject = 'New Version Notification - %s-%s.txt' % (submission.filename, submission.revision)
from_email = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL
send_mail(request, to_email, from_email, subject, 'submit/announce_new_version.txt',
{'submission': submission,
'msg': state_change_msg})
if to_email:
subject = 'New Version Notification - %s-%s.txt' % (submission.filename, submission.revision)
from_email = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL
send_mail(request, to_email, from_email, subject, 'submit/announce_new_version.txt',
{'submission': submission,
'msg': state_change_msg})
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
announce_new_version = announce_new_versionREDESIGN
@ -563,7 +581,7 @@ class DraftValidation(object):
def validate_wg(self):
if self.wg and not self.wg.status_id == IETFWG.ACTIVE:
self.add_warning('group', 'Working Group exists but is not an active WG')
self.add_warning('group', 'Group exists but is not an active group')
def validate_abstract(self):
if not self.draft.abstract:

View file

@ -117,7 +117,7 @@ def draft_status(request, submission_id, submission_hash=None, message=None):
except Preapproval.DoesNotExist:
preapproval = None
if detail.revision == '00' and detail.group_acronym and not preapproval:
if detail.revision == '00' and detail.group_acronym and detail.group_acronym.type_id == "wg" and not preapproval:
detail.status_id = INITIAL_VERSION_APPROVAL_REQUESTED
detail.save()

0
ietf/sync/__init__.py Normal file
View file

View file

@ -0,0 +1,37 @@
from ietf.doc.models import *
def find_discrepancies():
res = []
title = "Drafts that have been sent to the RFC Editor but do not have an RFC Editor state"
docs = Document.objects.filter(states__in=list(State.objects.filter(type="draft-iesg", slug__in=("ann", "rfcqueue")))).exclude(states__in=list(State.objects.filter(type="draft-rfceditor")))
res.append((title, docs))
title = "Drafts that have the IANA Action state \"In Progress\" but do not have a \"IANA\" RFC-Editor state/tag"
docs = Document.objects.filter(states__in=list(State.objects.filter(type="draft-iana-action", slug__in=("inprog",)))).exclude(tags="iana").exclude(states__in=list(State.objects.filter(type="draft-rfceditor", slug="iana")))
res.append((title, docs))
title = "Drafts that have the IANA Action state \"Waiting on RFC Editor\" or \"RFC-Ed-Ack\" but are in the RFC Editor state \"IANA\"/tagged with \"IANA\""
docs = Document.objects.filter(states__in=list(State.objects.filter(type="draft-iana-action", slug__in=("waitrfc", "rfcedack")))).filter(models.Q(tags="iana") | models.Q(states__in=list(State.objects.filter(type="draft-rfceditor", slug="iana"))))
res.append((title, docs))
title = "Drafts that have a state other than \"RFC Ed Queue\", \"RFC Published\" or \"Sent to the RFC Editor\" and have an RFC Editor or IANA Action state"
docs = Document.objects.exclude(states__in=list(State.objects.filter(type="draft-iesg", slug__in=("rfcqueue", "pub"))) + list(State.objects.filter(type__in=("draft-stream-iab", "draft-stream-ise", "draft-stream-irtf"), slug="rfc-edit"))).filter(states__in=list(State.objects.filter(type__in=("draft-iana-action", "draft-rfceditor"))))
res.append((title, docs))
for _, docs in res:
for d in docs:
d.iesg_state = d.get_state("draft-iesg")
d.rfc_state = d.get_state("draft-rfceditor")
d.iana_action_state = d.get_state("draft-iana-action")
return res

266
ietf/sync/iana.py Normal file
View file

@ -0,0 +1,266 @@
import re, urllib2, json, email, base64
from django.utils.http import urlquote
from django.conf import settings
from ietf.doc.models import *
from ietf.doc.utils import add_state_change_event
from ietf.person.models import *
from ietf.idrfc.mails import email_owner, email_state_changed, email_authors
from ietf.utils.timezone import *
PROTOCOLS_URL = "http://www.iana.org/protocols/"
CHANGES_URL = "http://datatracker.dev.icann.org:8080/data-tracker/changes"
def fetch_protocol_page(url):
f = urllib2.urlopen(PROTOCOLS_URL)
text = f.read()
f.close()
return text
def parse_protocol_page(text):
"""Parse IANA protocols page to extract referenced RFCs (as
rfcXXXX document names)."""
matches = re.findall('RFC [0-9]+', text)
res = set()
for m in matches:
res.add("rfc" + m[len("RFC "):])
return list(res)
def update_rfc_log_from_protocol_page(rfc_names, rfc_must_published_later_than):
"""Add notices to RFC history log that IANA is now referencing the RFC."""
system = Person.objects.get(name="(System)")
updated = []
docs = Document.objects.filter(docalias__name__in=rfc_names).exclude(
docevent__type="rfc_in_iana_registry").filter(
# only take those that were published after cutoff since we
# have a big bunch of old RFCs that we unfortunately don't have data for
docevent__type="published_rfc", docevent__time__gte=rfc_must_published_later_than
).distinct()
for d in docs:
e = DocEvent(doc=d)
e.by = system
e.type = "rfc_in_iana_registry"
e.desc = "IANA registries were updated to include %s" % d.display_name()
e.save()
updated.append(d)
return updated
def fetch_changes_json(url, start, end):
url += "?start=%s&end=%s" % (urlquote(local_timezone_to_utc(start).strftime("%Y-%m-%d %H:%M:%S")),
urlquote(local_timezone_to_utc(end).strftime("%Y-%m-%d %H:%M:%S")))
request = urllib2.Request(url)
# HTTP basic auth
username = "ietfsync"
password = settings.IANA_SYNC_PASSWORD
request.add_header("Authorization", "Basic %s" % base64.encodestring("%s:%s" % (username, password)).replace("\n", ""))
f = urllib2.urlopen(request)
text = f.read()
f.close()
return text
def parse_changes_json(text):
response = json.loads(text)
if "error" in response:
raise Exception("IANA server returned error: %s" % response["error"])
changes = response["changes"]
# do some rudimentary validation
for i in changes:
for f in ['doc', 'type', 'time']:
if f not in i:
raise Exception('Error in response: Field %s missing in input: %s - %s' % (f, json.dumps(i), json.dumps(changes)))
# a little bit of cleaning
i["doc"] = i["doc"].strip()
if i["doc"].startswith("http://www.ietf.org/internet-drafts/"):
i["doc"] = i["doc"][len("http://www.ietf.org/internet-drafts/"):]
# make sure we process oldest entries first
changes.sort(key=lambda c: c["time"])
return changes
def update_history_with_changes(changes, send_email=True):
"""Take parsed changes from IANA and apply them. Note that we
expect to get these chronologically sorted, otherwise the change
descriptions generated may not be right."""
# build up state lookup
states = {}
slookup = dict((s.slug, s)
for s in State.objects.filter(type=StateType.objects.get(slug="draft-iana-action")))
states["action"] = {
"": slookup["newdoc"],
"In Progress": slookup["inprog"],
"Open": slookup["inprog"],
"pre-approval In Progress": slookup["inprog"],
"Waiting on Authors": slookup["waitauth"],
"Author": slookup["waitauth"],
"Waiting on ADs": slookup["waitad"],
"Waiting on AD": slookup["waitad"],
"AD": slookup["waitad"],
"Waiting on WGC": slookup["waitwgc"],
"WGC": slookup["waitwgc"],
"Waiting on RFC-Editor": slookup["waitrfc"],
"Waiting on RFC Editor": slookup["waitrfc"],
"RFC-Editor": slookup["waitrfc"],
"RFC-Ed-ACK": slookup["rfcedack"],
"RFC-Editor-ACK": slookup["rfcedack"],
"Completed": slookup["rfcedack"],
"On Hold": slookup["onhold"],
"No IC": slookup["noic"],
}
slookup = dict((s.slug, s)
for s in State.objects.filter(type=StateType.objects.get(slug="draft-iana-review")))
states["review"] = {
"IANA Review Needed": slookup["need-rev"],
"IANA OK - Actions Needed": slookup["ok-act"],
"IANA OK - No Actions Needed": slookup["ok-noact"],
"IANA Not OK": slookup["not-ok"],
"Version Changed - Review Needed": slookup["changed"],
}
# so it turns out IANA has made a mistake and are including some
# wrong states, we'll have to skip those
wrong_action_states = ("Waiting on Reviewer", "Review Complete", "Last Call",
"Last Call - Questions", "Evaluation", "Evaluation - Questions",
"With Reviewer", "IESG Notification Received", "Watiing on Last Call",
"IANA Comments Submitted", "Waiting on Last Call")
system = Person.objects.get(name="(System)")
added_events = []
warnings = []
for c in changes:
docname = c['doc']
timestamp = datetime.datetime.strptime(c["time"], "%Y-%m-%d %H:%M:%S")
timestamp = utc_to_local_timezone(timestamp) # timestamps are in UTC
if c['type'] in ("iana_state", "iana_review"):
if c['type'] == "iana_state":
kind = "action"
if c["state"] in wrong_action_states:
warnings.append("Wrong action state '%s' encountered in changes from IANA" % c["state"])
continue
else:
kind = "review"
if c["state"] not in states[kind]:
warnings.append("Unknown IANA %s state %s (%s)" % (kind, c["state"], timestamp))
continue
state = states[kind][c["state"]]
state_type = "draft-iana-%s" % kind
e = StateDocEvent.objects.filter(type="changed_state", time=timestamp,
state_type=state_type, state=state)
if not e:
try:
doc = Document.objects.get(docalias__name=docname)
except Document.DoesNotExist:
warnings.append("Document %s not found" % docname)
continue
# the naive way of extracting prev_state here means
# that we assume these changes are cronologically
# applied
prev_state = doc.get_state(state_type)
e = add_state_change_event(doc, system, prev_state, state, timestamp)
if e:
added_events.append(e)
if not StateDocEvent.objects.filter(doc=doc, time__gt=timestamp, state_type=state_type):
save_document_in_history(doc)
doc.set_state(state)
if send_email:
email_state_changed(None, doc, "IANA %s state changed to %s" % (kind, state.name))
email_owner(None, doc, doc.ad, system, "IANA %s state changed to %s" % (kind, state.name))
if doc.time < timestamp:
doc.time = timestamp
doc.save()
return added_events, warnings
def parse_review_email(text):
msg = email.message_from_string(text)
# doc
doc_name = ""
m = re.search(r"<([^>]+)>", msg["Subject"])
if m:
doc_name = m.group(1).lower()
if re.search(r"\.\w{3}$", doc_name): # strip off extension
doc_name = doc_name[:-4]
if re.search(r"-\d{2}$", doc_name): # strip off revision
doc_name = doc_name[:-3]
# date
review_time = datetime.datetime.now()
if "Date" in msg:
review_time = email_time_to_local_timezone(msg["Date"])
# by
by = None
m = re.search(r"\"(.*)\"", msg["From"])
if m:
name = m.group(1).strip()
if name.endswith(" via RT"):
name = name[:-len(" via RT")]
try:
by = Person.objects.get(alias__name=name, role__group__acronym="iana")
except Person.DoesNotExist:
pass
if not by:
by = Person.objects.get(name="(System)")
# comment
body = msg.get_payload().decode('quoted-printable').replace("\r", "")
b = body.find("(BEGIN IANA LAST CALL COMMENTS)")
e = body.find("(END IANA LAST CALL COMMENTS)")
comment = body[b + len("(BEGIN IANA LAST CALL COMMENTS)"):e].strip()
# strip leading IESG:
if comment.startswith("IESG:"):
comment = comment[len("IESG:"):].lstrip()
# strip ending Thanks, followed by signature
m = re.compile(r"^Thanks,\n\n", re.MULTILINE).search(comment)
if m:
comment = comment[:m.start()].rstrip()
return doc_name, review_time, by, comment
def add_review_comment(doc_name, review_time, by, comment):
try:
e = DocEvent.objects.get(doc__name=doc_name, time=review_time, type="iana_review")
except DocEvent.DoesNotExist:
doc = Document.objects.get(name=doc_name)
e = DocEvent(doc=doc, time=review_time, type="iana_review")
e.desc = comment
e.by = by
e.save()

21
ietf/sync/mails.py Normal file
View file

@ -0,0 +1,21 @@
from django.core.urlresolvers import reverse as urlreverse
from django.conf import settings
from ietf.utils.mail import send_mail
from ietf.sync.discrepancies import find_discrepancies
def email_discrepancies(receivers):
sections = find_discrepancies()
send_mail(None,
receivers,
None,
"Datatracker Sync Discrepancies Report",
"sync/discrepancies_report.txt",
dict(sections=sections,
url=settings.IDTRACKER_BASE_URL + urlreverse("ietf.sync.views.discrepancies"),
base_url=settings.IDTRACKER_BASE_URL,
))

0
ietf/sync/models.py Normal file
View file

446
ietf/sync/rfceditor.py Normal file
View file

@ -0,0 +1,446 @@
import re, urllib2, json, email, socket
from xml.dom import pulldom, Node
from django.utils.http import urlquote
from ietf.utils.mail import send_mail_text
from ietf.doc.models import *
from ietf.person.models import *
from ietf.name.models import *
from ietf.doc.utils import add_state_change_event
QUEUE_URL = "http://www.rfc-editor.org/queue2.xml"
INDEX_URL = "http://www.rfc-editor.org/rfc/rfc-index.xml"
MIN_QUEUE_RESULTS = 10
MIN_INDEX_RESULTS = 5000
# Python < 2.7 doesn't have the total_seconds method on datetime.timedelta.
def total_seconds(td):
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
def get_child_text(parent_node, tag_name):
for node in parent_node.childNodes:
if node.nodeType == Node.ELEMENT_NODE and node.localName == tag_name:
return node.firstChild.data
return None
def fetch_queue_xml(url):
socket.setdefaulttimeout(30)
return urllib2.urlopen(url)
def parse_queue(response):
events = pulldom.parse(response)
drafts = []
warnings = []
for event, node in events:
if event == pulldom.START_ELEMENT and node.tagName == "entry":
events.expandNode(node)
node.normalize()
draft_name = get_child_text(node, "draft").strip()
draft_name = re.sub("(-\d\d)?(.txt){1,2}$", "", draft_name)
date_received = get_child_text(node, "date-received")
state = ""
tags = []
missref_generation = ""
for child in node.childNodes:
if child.nodeType == Node.ELEMENT_NODE and child.localName == "state":
state = child.firstChild.data
# state has some extra annotations encoded, parse
# them out
if '*R' in state:
tags.append("ref")
state = state.replace("*R", "")
if '*A' in state:
tags.append("iana")
state = state.replace("*A", "")
m = re.search(r"\(([0-9]+)G\)", state)
if m:
missref_generation = m.group(1)
state = state.replace("(%sG)" % missref_generation, "")
# AUTH48 link
auth48 = ""
for child in node.childNodes:
if child.nodeType == Node.ELEMENT_NODE and child.localName == "auth48-url":
auth48 = child.firstChild.data
# cluster link (if it ever gets implemented)
cluster = ""
for child in node.childNodes:
if child.nodeType == Node.ELEMENT_NODE and child.localName == "cluster-url":
cluster = child.firstChild.data
refs = []
for child in node.childNodes:
if child.nodeType == Node.ELEMENT_NODE and child.localName == "normRef":
ref_name = get_child_text(child, "ref-name")
ref_state = get_child_text(child, "ref-state")
in_queue = ref_state.startswith("IN-QUEUE")
refs.append((ref_name, ref_state, in_queue))
drafts.append((draft_name, date_received, state, tags, missref_generation, stream, auth48, cluster, refs))
elif event == pulldom.START_ELEMENT and node.tagName == "section":
name = node.getAttribute('name')
if name.startswith("IETF"):
stream = "ietf"
elif name.startswith("IAB"):
stream = "iab"
elif name.startswith("IRTF"):
stream = "irtf"
elif name.startswith("INDEPENDENT"):
stream = "ise"
else:
stream = None
warnings.append("unrecognized section " + name)
return drafts, warnings
def update_drafts_from_queue(drafts):
tag_mapping = {
'IANA': DocTagName.objects.get(slug='iana'),
'REF': DocTagName.objects.get(slug='ref')
}
slookup = dict((s.slug, s)
for s in State.objects.filter(type=StateType.objects.get(slug="draft-rfceditor")))
state_mapping = {
'AUTH': slookup['auth'],
'AUTH48': slookup['auth48'],
'AUTH48-DONE': slookup['auth48-done'],
'EDIT': slookup['edit'],
'IANA': slookup['iana'],
'IESG': slookup['iesg'],
'ISR': slookup['isr'],
'ISR-AUTH': slookup['isr-auth'],
'REF': slookup['ref'],
'RFC-EDITOR': slookup['rfc-edit'],
'TO': slookup['timeout'],
'MISSREF': slookup['missref'],
}
system = Person.objects.get(name="(System)")
warnings = []
names = [t[0] for t in drafts]
drafts_in_db = dict((d.name, d)
for d in Document.objects.filter(type="draft", docalias__name__in=names))
changed = set()
for name, date_received, state, tags, missref_generation, stream, auth48, cluster, refs in drafts:
if name not in drafts_in_db:
warnings.append("unknown document %s" % name)
continue
if not state or state not in state_mapping:
warnings.append("unknown state '%s'" % state)
continue
d = drafts_in_db[name]
prev_state = d.get_state("draft-rfceditor")
next_state = state_mapping[state]
# check if we've noted it's been received
if d.get_state_slug("draft-iesg") == "ann" and not prev_state and not d.latest_event(DocEvent, type="rfc_editor_received_announcement"):
e = DocEvent(doc=d, by=system, type="rfc_editor_received_announcement")
e.desc = "Announcement was received by RFC Editor"
e.save()
send_mail_text(None, "iesg-secretary@ietf.org", None,
'%s in RFC Editor queue' % d.name,
'The announcement for %s has been received by the RFC Editor.' % d.name)
if prev_state != next_state:
save_document_in_history(d)
d.set_state(next_state)
e = add_state_change_event(d, system, prev_state, next_state)
if auth48:
e.desc = re.sub(r"(<b>.*</b>)", "<a href=\"%s\">\\1</a>" % auth48, e.desc)
e.save()
changed.add(name)
t = DocTagName.objects.filter(slug__in=tags)
if set(t) != set(d.tags.all()):
d.tags = t
changed.add(name)
# remove tags and states for those not in the queue anymore
for d in Document.objects.exclude(docalias__name__in=names).filter(states__type="draft-rfceditor").distinct():
d.tags.remove(*tag_mapping.values())
d.unset_state("draft-rfceditor")
# we do not add a history entry here - most likely we already
# have something that explains what happened
changed.add(name)
return changed, warnings
def fetch_index_xml(url):
socket.setdefaulttimeout(30)
return urllib2.urlopen(url)
def parse_index(response):
def getDocList(parentNode, tagName):
l = []
for u in parentNode.getElementsByTagName(tagName):
for d in u.getElementsByTagName("doc-id"):
l.append(d.firstChild.data)
return l
also_list = {}
data = []
events = pulldom.parse(response)
for event, node in events:
if event == pulldom.START_ELEMENT and node.tagName in ["bcp-entry", "fyi-entry", "std-entry"]:
events.expandNode(node)
node.normalize()
bcpid = get_child_text(node, "doc-id")
doclist = getDocList(node, "is-also")
for docid in doclist:
if docid in also_list:
also_list[docid].append(bcpid)
else:
also_list[docid] = [bcpid]
elif event == pulldom.START_ELEMENT and node.tagName == "rfc-entry":
events.expandNode(node)
node.normalize()
rfc_number = int(get_child_text(node, "doc-id")[3:])
title = get_child_text(node, "title")
authors = []
for author in node.getElementsByTagName("author"):
authors.append(get_child_text(author, "name"))
d = node.getElementsByTagName("date")[0]
year = int(get_child_text(d, "year"))
month = get_child_text(d, "month")
month = ["January","February","March","April","May","June","July","August","September","October","November","December"].index(month)+1
rfc_published_date = datetime.date(year, month, 1)
current_status = get_child_text(node, "current-status").title()
updates = getDocList(node, "updates")
updated_by = getDocList(node, "updated-by")
obsoletes = getDocList(node, "obsoletes")
obsoleted_by = getDocList(node, "obsoleted-by")
stream = get_child_text(node, "stream")
wg = get_child_text(node, "wg_acronym")
if wg and ((wg == "NON WORKING GROUP") or len(wg) > 15):
wg = None
l = []
pages = ""
for fmt in node.getElementsByTagName("format"):
l.append(get_child_text(fmt, "file-format"))
if get_child_text(fmt, "file-format") == "ASCII":
pages = get_child_text(fmt, "page-count")
file_formats = (",".join(l)).lower()
abstract = ""
for abstract in node.getElementsByTagName("abstract"):
abstract = get_child_text(abstract, "p")
draft = get_child_text(node, "draft")
if draft and re.search("-\d\d$", draft):
draft = draft[0:-3]
if len(node.getElementsByTagName("errata-url")) > 0:
has_errata = 1
else:
has_errata = 0
data.append((rfc_number,title,authors,rfc_published_date,current_status,updates,updated_by,obsoletes,obsoleted_by,[],draft,has_errata,stream,wg,file_formats,pages,abstract))
for d in data:
k = "RFC%04d" % d[0]
if k in also_list:
d[9].extend(also_list[k])
return data
def update_docs_from_rfc_index(data, skip_older_than_date=None):
std_level_mapping = {
"Standard": StdLevelName.objects.get(slug="std"),
"Draft Standard": StdLevelName.objects.get(slug="ds"),
"Proposed Standard": StdLevelName.objects.get(slug="ps"),
"Informational": StdLevelName.objects.get(slug="inf"),
"Experimental": StdLevelName.objects.get(slug="exp"),
"Best Current Practice": StdLevelName.objects.get(slug="bcp"),
"Historic": StdLevelName.objects.get(slug="hist"),
"Unknown": StdLevelName.objects.get(slug="unkn"),
}
stream_mapping = {
"IETF": StreamName.objects.get(slug="ietf"),
"INDEPENDENT": StreamName.objects.get(slug="ise"),
"IRTF": StreamName.objects.get(slug="irtf"),
"IAB": StreamName.objects.get(slug="iab"),
"Legacy": StreamName.objects.get(slug="legacy"),
}
tag_has_errata = DocTagName.objects.get(slug='errata')
relationship_obsoletes = DocRelationshipName.objects.get(slug="obs")
relationship_updates = DocRelationshipName.objects.get(slug="updates")
system = Person.objects.get(name="(System)")
results = []
for rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract in data:
if skip_older_than_date and rfc_published_date < skip_older_than_date:
# speed up the process by skipping old entries
continue
# we assume two things can happen: we get a new RFC, or an
# attribute has been updated at the RFC Editor (RFC Editor
# attributes take precedence over our local attributes)
# make sure we got the document and alias
created = False
doc = None
name = "rfc%s" % rfc_number
a = DocAlias.objects.filter(name=name).select_related("document")
if a:
doc = a[0].document
else:
if draft:
try:
doc = Document.objects.get(name=draft)
except Document.DoesNotExist:
pass
if not doc:
results.append("created document %s" % name)
doc = Document.objects.get_or_create(name=name)[0]
# add alias
DocAlias.objects.get_or_create(name=name, document=doc)
results.append("created alias %s to %s" % (name, doc.name))
created = True
# check attributes
changed_attributes = {}
changed_states = []
created_relations = []
other_changes = False
if title != doc.title:
changed_attributes["title"] = title
if abstract and abstract != doc.abstract:
changed_attributes["abstract"] = abstract
if pages and int(pages) != doc.pages:
changed_attributes["pages"] = int(pages)
if std_level_mapping[current_status] != doc.std_level:
changed_attributes["std_level"] = std_level_mapping[current_status]
if doc.get_state_slug() != "rfc":
changed_states.append(State.objects.get(type="draft", slug="rfc"))
if doc.stream != stream_mapping[stream]:
changed_attributes["stream"] = stream_mapping[stream]
if not doc.group and wg:
changed_attributes["group"] = Group.objects.get(acronym=wg)
if not doc.latest_event(type="published_rfc"):
e = DocEvent(doc=doc, type="published_rfc")
# unfortunately, rfc_published_date doesn't include the correct day
# at the moment because the data only has month/year, so
# try to deduce it
d = datetime.datetime.combine(rfc_published_date, datetime.time())
synthesized = datetime.datetime.now()
if abs(d - synthesized) > datetime.timedelta(days=60):
synthesized = d
else:
direction = -1 if total_seconds(d - synthesized) < 0 else +1
while synthesized.month != d.month or synthesized.year != d.year:
synthesized += datetime.timedelta(days=direction)
e.time = synthesized
e.by = system
e.desc = "RFC published"
e.save()
other_changes = True
results.append("Added RFC published event: %s" % e.time.strftime("%Y-%m-%d"))
for t in ("draft-iesg", "draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"):
slug = doc.get_state_slug(t)
if slug and slug != "pub":
changed_states.append(State.objects.get(type=t, slug="pub"))
def parse_relation_list(l):
res = []
for x in l:
if x[:3] in ("NIC", "IEN", "STD", "RTR"):
# try translating this to RFCs that we can handle
# sensibly; otherwise we'll have to ignore them
l = DocAlias.objects.filter(name__startswith="rfc", document__docalias__name=x.lower())
else:
l = DocAlias.objects.filter(name=x.lower())
for a in l:
if a not in res:
res.append(a)
return res
for x in parse_relation_list(obsoletes):
if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_obsoletes):
created_relations.append(RelatedDocument(source=doc, target=x, relationship=relationship_obsoletes))
for x in parse_relation_list(updates):
if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_updates):
created_relations.append(RelatedDocument(source=doc, target=x, relationship=relationship_updates))
if also:
for a in also:
a = a.lower()
if not DocAlias.objects.filter(name=a):
DocAlias.objects.create(name=a, document=doc)
other_changes = True
results.append("Created alias %s to %s" % (a, doc.name))
if has_errata:
if not doc.tags.filter(pk=tag_has_errata.pk):
changed_attributes["tags"] = list(doc.tags.all()) + [tag_has_errata]
else:
if doc.tags.filter(pk=tag_has_errata.pk):
changed_attributes["tags"] = set(doc.tags.all()) - set([tag_has_errata])
if changed_attributes or changed_states or created_relations or other_changes:
# apply changes
save_document_in_history(doc)
for k, v in changed_attributes.iteritems():
setattr(doc, k, v)
results.append("Changed %s to %s on %s" % (k, v, doc.name))
for s in changed_states:
doc.set_state(s)
results.append("Set state %s on %s" % (s, doc.name))
for o in created_relations:
o.save()
results.append("Created %s" % o)
doc.time = datetime.datetime.now()
doc.save()
return results

446
ietf/sync/tests.py Normal file
View file

@ -0,0 +1,446 @@
import unittest, re, json, datetime, StringIO
import django.test
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from ietf.utils.mail import outbox
from ietf.utils.test_data import make_test_data
from ietf.utils.test_utils import login_testing_unauthorized
from ietf.doc.models import *
from ietf.doc.utils import add_state_change_event
from ietf.person.models import *
from ietf.sync import iana, rfceditor
from pyquery import PyQuery
class IANASyncTestCase(django.test.TestCase):
fixtures = ['names']
def test_protocol_page_sync(self):
draft = make_test_data()
DocAlias.objects.create(name="rfc1234", document=draft)
DocEvent.objects.create(doc=draft, type="published_rfc", by=Person.objects.get(name="(System)"))
rfc_names = iana.parse_protocol_page('<html><a href="/go/rfc1234/">RFC 1234</a></html>')
self.assertEqual(len(rfc_names), 1)
self.assertEqual(rfc_names[0], "rfc1234")
iana.update_rfc_log_from_protocol_page(rfc_names, datetime.datetime.now() - datetime.timedelta(days=1))
self.assertEqual(DocEvent.objects.filter(doc=draft, type="rfc_in_iana_registry").count(), 1)
# make sure it doesn't create duplicates
iana.update_rfc_log_from_protocol_page(rfc_names, datetime.datetime.now() - datetime.timedelta(days=1))
self.assertEqual(DocEvent.objects.filter(doc=draft, type="rfc_in_iana_registry").count(), 1)
def test_changes_sync(self):
draft = make_test_data()
data = json.dumps({
"changes": [
{
"time": "2011-10-09 12:00:01",
"doc": draft.name,
"state": "IANA Not OK",
"type": "iana_review",
},
{
"time": "2011-10-09 12:00:00",
"doc": draft.name,
"state": "Waiting on RFC-Editor",
"type": "iana_state",
},
{
"time": "2011-10-09 11:00:00",
"doc": draft.name,
"state": "In Progress",
"type": "iana_state",
}
]
})
changes = iana.parse_changes_json(data)
# check sorting
self.assertEqual(changes[0]["time"], "2011-10-09 11:00:00")
mailbox_before = len(outbox)
added_events, warnings = iana.update_history_with_changes(changes)
self.assertEqual(len(added_events), 3)
self.assertEqual(len(warnings), 0)
self.assertEqual(draft.get_state_slug("draft-iana-review"), "not-ok")
self.assertEqual(draft.get_state_slug("draft-iana-action"), "waitrfc")
e = draft.latest_event(StateDocEvent, type="changed_state", state_type="draft-iana-action")
self.assertEqual(e.desc, "IANA Action state changed to <b>Waiting on RFC Editor</b> from In Progress")
# self.assertEqual(e.time, datetime.datetime(2011, 10, 9, 5, 0)) # check timezone handling
self.assertEqual(len(outbox), mailbox_before + 3 * 2)
# make sure it doesn't create duplicates
added_events, warnings = iana.update_history_with_changes(changes)
self.assertEqual(len(added_events), 0)
self.assertEqual(len(warnings), 0)
def test_changes_sync_errors(self):
draft = make_test_data()
# missing "type"
data = json.dumps({
"changes": [
{
"time": "2011-10-09 12:00:01",
"doc": draft.name,
"state": "IANA Not OK",
},
]
})
self.assertRaises(Exception, iana.parse_changes_json, data)
# error response
data = json.dumps({
"error": "I am in error."
})
self.assertRaises(Exception, iana.parse_changes_json, data)
# missing document from database
data = json.dumps({
"changes": [
{
"time": "2011-10-09 12:00:01",
"doc": "draft-this-does-not-exist",
"state": "IANA Not OK",
"type": "iana_review",
},
]
})
changes = iana.parse_changes_json(data)
added_events, warnings = iana.update_history_with_changes(changes)
self.assertEqual(len(added_events), 0)
self.assertEqual(len(warnings), 1)
def test_iana_review_mail(self):
draft = make_test_data()
msg = """From: "%(person)s via RT" <drafts-lastcall@iana.org>
Date: Thu, 10 May 2012 12:00:00 +0000
Subject: [IANA #12345] Last Call: <%(draft)s-%(rev)s.txt> (Long text) to Informational RFC
(BEGIN IANA LAST CALL COMMENTS)
IESG:
IANA has reviewed %(draft)s-%(rev)s, which is=20
currently in Last Call, and has the following comments:
IANA understands that, upon approval of this document, there are no=20
IANA Actions that need completion.
Thanks,
%(person)s
IANA Fake Test Person
ICANN
(END IANA LAST CALL COMMENTS)
"""
msg = msg % dict(person=Person.objects.get(user__username="iana").name,
draft=draft.name,
rev=draft.rev)
doc_name, review_time, by, comment = iana.parse_review_email(msg)
self.assertEqual(doc_name, draft.name)
# self.assertEqual(review_time, datetime.datetime(2012, 5, 10, 5, 0, 0))
self.assertEqual(by, Person.objects.get(user__username="iana"))
self.assertTrue("there are no IANA Actions" in comment.replace("\n", ""))
iana.add_review_comment(doc_name, review_time, by, comment)
e = draft.latest_event(type="iana_review")
self.assertTrue(e)
self.assertEqual(e.desc, comment)
self.assertEqual(e.by, by)
# make sure it doesn't create duplicates
iana.add_review_comment(doc_name, review_time, by, comment)
self.assertEqual(DocEvent.objects.filter(doc=draft, type="iana_review").count(), 1)
class RFCSyncTestCase(django.test.TestCase):
fixtures = ['names']
def test_rfc_index(self):
doc = make_test_data()
doc.set_state(State.objects.get(type="draft-iesg", slug="rfcqueue"))
# it's a bit strange to have this set when draft-iesg is set
# too, but for testing purposes ...
doc.set_state(State.objects.get(type="draft-stream-ise", slug="rfc-edit"))
updated_doc = Document.objects.create(name="draft-ietf-something")
DocAlias.objects.create(name=updated_doc.name, document=updated_doc)
DocAlias.objects.create(name="rfc123", document=updated_doc)
today = datetime.date.today()
t = '''<?xml version="1.0" encoding="UTF-8"?>
<rfc-index xmlns="http://www.rfc-editor.org/rfc-index"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.rfc-editor.org/rfc-index
http://www.rfc-editor.org/rfc-index.xsd">
<bcp-entry>
<doc-id>BCP0001</doc-id>
<is-also>
<doc-id>RFC1234</doc-id>
<doc-id>RFC2345</doc-id>
</is-also>
</bcp-entry>
<fyi-entry>
<doc-id>FYI0001</doc-id>
<is-also>
<doc-id>RFC1234</doc-id>
</is-also>
</fyi-entry>
<std-entry>
<doc-id>STD0001</doc-id>
<title>Test</title>
<is-also>
<doc-id>RFC1234</doc-id>
</is-also>
</std-entry>
<rfc-entry>
<doc-id>RFC1234</doc-id>
<title>A Testing RFC</title>
<author>
<name>A. Irector</name>
</author>
<date>
<month>%(month)s</month>
<year>%(year)s</year>
</date>
<format>
<file-format>ASCII</file-format>
<char-count>12345</char-count>
<page-count>42</page-count>
</format>
<keywords>
<kw>test</kw>
</keywords>
<abstract><p>This is some interesting text.</p></abstract>
<draft>%(name)s-%(rev)s</draft>
<updates>
<doc-id>RFC123</doc-id>
</updates>
<is-also>
<doc-id>BCP0001</doc-id>
</is-also>
<current-status>PROPOSED STANDARD</current-status>
<publication-status>PROPOSED STANDARD</publication-status>
<stream>IETF</stream>
<area>%(area)s</area>
<wg_acronym>%(group)s</wg_acronym>
<errata-url>http://www.rfc-editor.org/errata_search.php?rfc=1234</errata-url>
</rfc-entry>
</rfc-index>''' % dict(year=today.strftime("%Y"),
month=today.strftime("%B"),
name=doc.name,
rev=doc.rev,
area=doc.group.parent.acronym,
group=doc.group.acronym)
data = rfceditor.parse_index(StringIO.StringIO(t))
self.assertEqual(len(data), 1)
rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract = data[0]
# currently, we only check what we actually use
self.assertEqual(rfc_number, 1234)
self.assertEqual(title, "A Testing RFC")
self.assertEqual(rfc_published_date.year, today.year)
self.assertEqual(rfc_published_date.month, today.month)
self.assertEqual(current_status, "Proposed Standard")
self.assertEqual(updates, ["RFC123"])
self.assertEqual(set(also), set(["BCP0001", "FYI0001", "STD0001"]))
self.assertEqual(draft, doc.name)
self.assertEqual(wg, doc.group.acronym)
self.assertEqual(has_errata, True)
self.assertEqual(stream, "IETF")
self.assertEqual(pages, "42")
self.assertEqual(abstract, "This is some interesting text.")
mailbox_before = len(outbox)
changed = rfceditor.update_docs_from_rfc_index(data, today - datetime.timedelta(days=30))
doc = Document.objects.get(name=doc.name)
self.assertEqual(doc.docevent_set.all()[0].type, "published_rfc")
self.assertEqual(doc.docevent_set.all()[0].time.date(), today)
self.assertTrue("errata" in doc.tags.all().values_list("slug", flat=True))
self.assertTrue(DocAlias.objects.filter(name="rfc1234", document=doc))
self.assertTrue(DocAlias.objects.filter(name="bcp0001", document=doc))
self.assertTrue(DocAlias.objects.filter(name="fyi0001", document=doc))
self.assertTrue(DocAlias.objects.filter(name="std0001", document=doc))
self.assertTrue(RelatedDocument.objects.filter(source=doc, target__name="rfc123", relationship="updates"))
self.assertEqual(doc.title, "A Testing RFC")
self.assertEqual(doc.abstract, "This is some interesting text.")
self.assertEqual(doc.get_state_slug(), "rfc")
self.assertEqual(doc.get_state_slug("draft-iesg"), "pub")
self.assertEqual(doc.get_state_slug("draft-stream-ise"), "pub")
self.assertEqual(doc.std_level_id, "ps")
self.assertEqual(doc.pages, 42)
# make sure we can apply it again with no changes
changed = rfceditor.update_docs_from_rfc_index(data, today - datetime.timedelta(days=30))
self.assertEquals(len(changed), 0)
def test_rfc_queue(self):
draft = make_test_data()
draft.set_state(State.objects.get(type="draft-iesg", slug="ann"))
t = '''<rfc-editor-queue xmlns="http://www.rfc-editor.org/rfc-editor-queue">
<section name="IETF STREAM: WORKING GROUP STANDARDS TRACK">
<entry xml:id="%(name)s">
<draft>%(name)s-%(rev)s.txt</draft>
<date-received>2010-09-08</date-received>
<state>EDIT*R*A(1G)</state>
<auth48-url>http://www.rfc-editor.org/auth48/rfc1234</auth48-url>
<normRef>
<ref-name>%(ref)s</ref-name>
<ref-state>IN-QUEUE</ref-state>
</normRef>
<authors>A. Author</authors>
<title>
%(title)s
</title>
<bytes>10000000</bytes>
<source>%(group)s</source>
</entry>
</section>
</rfc-editor-queue>''' % dict(name=draft.name,
rev=draft.rev,
title=draft.title,
group=draft.group.name,
ref="draft-ietf-test")
drafts, warnings = rfceditor.parse_queue(StringIO.StringIO(t))
self.assertEqual(len(drafts), 1)
self.assertEqual(len(warnings), 0)
draft_name, date_received, state, tags, missref_generation, stream, auth48, cluster, refs = drafts[0]
# currently, we only check what we actually use
self.assertEqual(draft_name, draft.name)
self.assertEqual(state, "EDIT")
self.assertEqual(set(tags), set(["iana", "ref"]))
self.assertEqual(auth48, "http://www.rfc-editor.org/auth48/rfc1234")
mailbox_before = len(outbox)
changed, warnings = rfceditor.update_drafts_from_queue(drafts)
self.assertEqual(len(changed), 1)
self.assertEqual(len(warnings), 0)
self.assertEqual(draft.get_state_slug("draft-rfceditor"), "edit")
self.assertEqual(set(draft.tags.all()), set(DocTagName.objects.filter(slug__in=("iana", "ref"))))
self.assertEqual(draft.docevent_set.all()[0].type, "changed_state")
self.assertEqual(draft.docevent_set.all()[1].type, "rfc_editor_received_announcement")
self.assertEqual(len(outbox), mailbox_before + 1)
self.assertTrue("RFC Editor queue" in outbox[-1]["Subject"])
# make sure we can apply it again with no changes
changed, warnings = rfceditor.update_drafts_from_queue(drafts)
self.assertEquals(len(changed), 0)
self.assertEquals(len(warnings), 0)
class DiscrepanciesTestCase(django.test.TestCase):
fixtures = ['names']
def test_discrepancies(self):
make_test_data()
# draft approved but no RFC Editor state
doc = Document.objects.create(name="draft-ietf-test1", type_id="draft")
doc.set_state(State.objects.get(type="draft-iesg", slug="ann"))
r = self.client.get(urlreverse("ietf.sync.views.discrepancies"))
self.assertTrue(doc.name in r.content)
# draft with IANA state "In Progress" but RFC Editor state not IANA
doc = Document.objects.create(name="draft-ietf-test2", type_id="draft")
doc.set_state(State.objects.get(type="draft-iesg", slug="rfcqueue"))
doc.set_state(State.objects.get(type="draft-iana-action", slug="inprog"))
doc.set_state(State.objects.get(type="draft-rfceditor", slug="auth"))
r = self.client.get(urlreverse("ietf.sync.views.discrepancies"))
self.assertTrue(doc.name in r.content)
# draft with IANA state "Waiting on RFC Editor" or "RFC-Ed-Ack"
# but RFC Editor state is IANA
doc = Document.objects.create(name="draft-ietf-test3", type_id="draft")
doc.set_state(State.objects.get(type="draft-iesg", slug="rfcqueue"))
doc.set_state(State.objects.get(type="draft-iana-action", slug="waitrfc"))
doc.set_state(State.objects.get(type="draft-rfceditor", slug="iana"))
r = self.client.get(urlreverse("ietf.sync.views.discrepancies"))
self.assertTrue(doc.name in r.content)
# draft with state other than "RFC Ed Queue" or "RFC Published"
# that are in RFC Editor or IANA queues
doc = Document.objects.create(name="draft-ietf-test4", type_id="draft")
doc.set_state(State.objects.get(type="draft-iesg", slug="ann"))
doc.set_state(State.objects.get(type="draft-rfceditor", slug="auth"))
r = self.client.get(urlreverse("ietf.sync.views.discrepancies"))
self.assertTrue(doc.name in r.content)
class RFCEditorUndoTestCase(django.test.TestCase):
fixtures = ['names']
def test_rfceditor_undo(self):
draft = make_test_data()
e1 = add_state_change_event(draft, Person.objects.get(name="(System)"), None,
State.objects.get(type="draft-rfceditor", slug="auth"))
e1.desc = "First"
e1.save()
e2 = add_state_change_event(draft, Person.objects.get(name="(System)"), None,
State.objects.get(type="draft-rfceditor", slug="edit"))
e2.desc = "Second"
e2.save()
url = urlreverse('ietf.sync.views.rfceditor_undo')
login_testing_unauthorized(self, "rfc", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
self.assertTrue(e2.doc_id in r.content)
# delete e2
deleted_before = DeletedEvent.objects.count()
r = self.client.post(url, dict(event=e2.id))
self.assertEquals(r.status_code, 302)
self.assertEquals(StateDocEvent.objects.filter(id=e2.id).count(), 0)
self.assertEquals(draft.get_state("draft-rfceditor").slug, "auth")
self.assertEquals(DeletedEvent.objects.count(), deleted_before + 1)
# delete e1
r = self.client.post(url, dict(event=e1.id))
self.assertEquals(draft.get_state("draft-rfceditor"), None)
# let's just test we can recover
e = DeletedEvent.objects.all().order_by("-time")[0]
e.content_type.model_class().objects.create(**json.loads(e.json))
self.assertTrue(StateDocEvent.objects.filter(desc="First", doc=draft))

8
ietf/sync/urls.py Normal file
View file

@ -0,0 +1,8 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('',
url(r'^discrepancies/$', 'ietf.sync.views.discrepancies'),
url(r'^(?P<org>\w+)/notify/(?P<notification>\w+)/$', 'ietf.sync.views.notify'),
url(r'^rfceditor/undo/', 'ietf.sync.views.rfceditor_undo')
)

146
ietf/sync/views.py Normal file
View file

@ -0,0 +1,146 @@
import subprocess, os, json
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseServerError, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.template.loader import render_to_string
from django.conf import settings
from django import forms
from django.db.models import Q
from django.contrib.auth.models import User
from ietf.ietfauth.decorators import role_required, has_role
from ietf.doc.models import *
from ietf.sync import iana, rfceditor
from ietf.sync.discrepancies import find_discrepancies
from ietf.utils.serialize import object_as_shallow_dict
SYNC_BIN_PATH = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../bin"))
#@role_required('Secretariat', 'IANA', 'RFC Editor')
def discrepancies(request):
sections = find_discrepancies()
return render_to_response("sync/discrepancies.html",
dict(sections=sections),
context_instance=RequestContext(request))
def notify(request, org, notification):
"""Notify that something has changed at another site to trigger a
run of one of the sync scripts."""
known_orgs = {
"iana": "IANA",
"rfceditor": "RFC Editor",
}
if org not in known_orgs:
raise Http404
# handle auth, to make it easier for the other end, you can send
# the username/password as POST parameters instead of having to
# visit the login page
user = request.user
username = request.POST.get("username") or request.GET.get("username")
password = request.POST.get("password") or request.GET.get("password")
if username != None and password != None:
if settings.SERVER_MODE == "production" and not request.is_secure():
return HttpResponseForbidden("You must use HTTPS when sending username/password")
if not user.is_authenticated():
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return HttpResponse("Invalid username/password")
if not user.check_password(password):
return HttpResponse("Invalid username/password")
if not has_role(user, ("Secretariat", known_orgs[org])):
return HttpResponseForbidden("You do not have the necessary permissions to view this page")
known_notifications = {
"protocols": "an added reference to an RFC at <a href=\"%s\">the IANA protocols page</a>" % iana.PROTOCOLS_URL,
"changes": "new changes at <a href=\"%s\">the changes JSON dump</a>" % iana.CHANGES_URL,
"queue": "new changes to <a href=\"%s\">queue2.xml</a>" % rfceditor.QUEUE_URL,
"index": "new changes to <a href=\"%s\">rfc-index.xml</a>" % rfceditor.INDEX_URL,
}
if notification not in known_notifications:
raise Http404
if request.method == "POST":
def runscript(name):
p = subprocess.Popen(["python", os.path.join(SYNC_BIN_PATH, name)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out, _ = p.communicate()
return (p.returncode, out)
if notification == "protocols":
failed, out = runscript("iana-protocols-updates")
if notification == "changes":
failed, out = runscript("iana-changes-updates")
if notification == "queue":
failed, out = runscript("rfc-editor-queue-updates")
if notification == "index":
failed, out = runscript("rfc-editor-index-updates")
if failed:
return HttpResponseServerError("FAIL\n\n" + out, content_type="text/plain")
else:
return HttpResponse("OK", content_type="text/plain")
return render_to_response('sync/notify.html',
dict(org=known_orgs[org],
notification=notification,
help_text=known_notifications[notification],
),
context_instance=RequestContext(request))
@role_required('Secretariat', 'RFC Editor')
def rfceditor_undo(request):
"""Undo a DocEvent."""
events = StateDocEvent.objects.filter(state_type="draft-rfceditor",
time__gte=datetime.datetime.now() - datetime.timedelta(weeks=1)
).order_by("-time", "-id")
if request.method == "POST":
try:
eid = int(request.POST.get("event", ""))
except ValueError:
return HttpResponse("Could not parse event id")
try:
e = events.get(id=eid)
except StateDocEvent.DoesNotExist:
return HttpResponse("Event does not exist")
doc = e.doc
# possibly reset the state of the document
all_events = StateDocEvent.objects.filter(doc=doc, state_type="draft-rfceditor").order_by("-time", "-id")
if all_events and all_events[0] == e:
if len(all_events) > 1:
doc.set_state(all_events[1].state)
else:
doc.unset_state("draft-rfceditor")
dump = DeletedEvent()
dump.content_type = ContentType.objects.get_for_model(type(e))
dump.json = json.dumps(object_as_shallow_dict(e), indent=2)
dump.by = request.user.person
dump.save()
e.delete()
return HttpResponseRedirect(urlreverse("ietf.sync.views.rfceditor_undo"))
return render_to_response('sync/rfceditor_undo.html',
dict(events=events,
),
context_instance=RequestContext(request))

View file

@ -44,14 +44,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<li><a href="{% url ietf.iesg.views.agenda_documents %}">Next Telechat</a></li>
<li><a href="{% url ietf.iesg.views.discusses %}">Discusses</a></li>
<li><a href="{% url ietf.iesg.views.working_group_actions %}">Working Groups</a></li>
{# FIXME: wgcharter <li><a href="{% url wg_search_by_area name=user|ad_area %}">Working Groups</a></li> #}
{% endif %}
{% if user|in_group:"Secretariat" %}
<li class="sect first">Secretariat</li>
<li><a href="{% url ietf.iesg.views.telechat_dates %}">Telechat Dates</a></li>
<li><a href="/admin/iesg/telechatagendaitem/">Management Items</a></li>
<li><a href="{% url ietf.iesg.views.working_group_actions %}">Working Groups</a></li>
{# FIXME: wgcharter <li><a href="{% url wg_search_in_process %}">Working Groups</a></li> #}
<li><a href="{% url ietf.sync.views.discrepancies %}">Sync discrepancies</a>
{% endif %}
{% if user %}
{% get_user_managed_streams user as stream_list %}
@ -62,6 +61,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% endfor %}
{% endif %}
{% endif %}
{% if user|has_role:"IANA" %}
<li class="sect first">IANA</li>
<li><a href="{% url ietf.sync.views.discrepancies %}">Sync discrepancies</a></li>
{% endif %}
{% if user|has_role:"RFC Editor" %}
<li class="sect first">RFC Editor</li>
<li><a href="{% url ietf.sync.views.discrepancies %}">Sync discrepancies</a></li>
{% endif %}
<li class="sect{% if not user|in_group:"Area_Director,Secretariat" %} first{% endif %}">Working Groups</li>
<li style="padding-bottom:0;"><div id="wgs" class="yuimenu"><div class="bd" style="border:0;">

View file

@ -45,6 +45,8 @@ IESG Note
(Insert IESG Note here or remove section)
IANA Note
{% if iana %}
{% load ietf_filters %}{% filter wordwrap:"68"|indent:2 %}{{ iana }}{% endfilter %}
{% endif %}
(Insert IANA Note here or remove section)
{% endautoescape%}

View file

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block title %}Change whether {{ doc.name }}-{{ doc.rev }} is the result of a consensus process{% endblock %}
{% block content %}
<h1>Change whether {{ doc.name }}-{{ doc.rev }} is the result of a consensus process</h1>
<form action="" method="POST">
<table>
{{ form.as_table }}
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=doc.name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

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

View file

@ -3,9 +3,9 @@
{% block title %}Change state of {{ doc }}{% endblock %}
{% block morecss %}
form.change-state select {
width: 22em;
}
form.change-state select { width: 22em; }
form.change-state #id_comment { width: 30em; }
form.change-state .cancel-pub-note { width: 30em; color: #a00; }
form.change-state .actions {
text-align: right;
padding-top: 10px;
@ -29,6 +29,14 @@ form.change-state .actions {
<form class="change-state" action="" method="post">
<table>
{{ form.as_table }}
{% if state and state.slug == "rfcqueue" %}
<tr>
<td></td>
<td class="cancel-pub-note">Note: if you pull the draft out of the
{{ state.name }} state, the RFC Editor and IANA will be notified
by email with this comment so they can update their queues.</td>
</tr>
{% endif %}
<tr>
<td colspan="2" class="actions">
<a href="{{ doc.get_absolute_url }}">Back</a>

View file

@ -57,12 +57,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% with info.conflict_reviews as r %}{% if r %}<tr><td>IETF Conflict Review:</td><td> {% filter urlize_ietf_docs %}{{ r|join:","}}{% endfilter %}</td></tr>{% endif %} {% endwith %}
<tr><td>Intended RFC status:</td><td>
{% with user|in_group:"Area_Director,Secretariat" as add_link %}
{% if add_link %}<a class="editlink" href="{% url doc_change_intended_status name=doc.draft_name %}">{% endif %}
<a{% if info.can_edit_intended_std_level %} class="editlink" href="{% url doc_change_intended_status name=doc.draft_name %}"{% endif %}>
{{ doc.underlying_document.intended_std_level|default:"(None)" }}
{% if add_link %}</a>{% endif %}
{% endwith %}
</td></tr>
</a>
</td></tr>
<tr><td>Other versions:</td>
{% ifequal doc.draft_status "Active" %}
@ -119,6 +117,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% endif %}
<tr><td>Document shepherd:</td><td>{{ stream_info.shepherd|default:"" }}</td></tr>
{% if info.consensus %}
<tr><td>Consensus:</td><td><a title="Whether the document is the result of a community consensus process as defined in RFC 5741" {% if info.can_edit_consensus %}class="editlink" href="{% url doc_edit_consensus name=doc.draft_name %}"{% endif %}>{{ info.consensus }}</a></td></tr>
{% endif %}
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
@ -128,7 +130,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% else %}
{{ doc.friendly_state|safe }}
{% endif %}
{% if doc.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.draft_name}}">{{ doc.rfc_editor_state|escape }}</a>{% endif %}
{% if info.iana_review_state %}<br />IANA Review State: <a class="editlink" {% if user|in_group:"Secretariat,IANA" %}href="{% url doc_change_iana_state name=doc.draft_name state_type="iana-review" %}"{% endif %}>{{ info.iana_review_state.name|escape }}</a>{% endif %}
{% if info.iana_action_state %}<br />IANA Action State: <a class="editlink" {% if user|in_group:"Secretariat,IANA" %}href="{% url doc_change_iana_state name=doc.draft_name state_type="iana-action" %}"{% endif %}>{{ info.iana_action_state.name|escape }}</a>{% endif %}
{% if info.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{ doc.draft_name }}">{{ info.rfc_editor_state|escape }}</a>{% endif %}
{% ifequal doc.draft_status "Expired" %}
{% if doc.resurrect_requested_by %}(resurrect requested by {{ doc.resurrect_requested_by }}){% endif %}
{% endifequal %}

View file

@ -42,7 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% include "idrfc/doc_diffs.html" %}
{% endif %}
<h2 style="margin-top:1em;">Document history</h2>
{% if user|in_group:"Area_Director,Secretariat,IANA" and doc.in_ietf_process %}
{% if user|in_group:"Area_Director,Secretariat,IANA,RFC Editor" and doc.in_ietf_process %}
<div style="margin-bottom:8px" id="history_actions">
<span id="doc_add_comment_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url doc_add_comment name=doc.draft_name %}">Add comment</a></span></span>
</div>

View file

@ -136,7 +136,7 @@
{% if doc.rev %}
<div class="markup_draft">
{{ content|safe|linebreaksbr|keep_spacing|sanitize_html|safe }}
{{ content|safe|keep_spacing|sanitize_html|wordwrap:80|safe }}
</div>
{% endif %}

View file

@ -53,7 +53,7 @@
{% endif %}
<h2>Document history</h2>
{% if user|has_role:"Area Director,Secretariat,IANA" %}
{% if user|has_role:"Area Director,Secretariat,IANA,RFC Editor" %}
<div class="history-actions">
<a href="{% url doc_add_comment name=doc.name %}">Add comment</a>
</div>

View file

@ -22,7 +22,7 @@ with no "Discuss" positions, are needed for approval.
DISCUSSES AND COMMENTS
======================
{% filter wordwrap:79 %}{% for pos in ad_feedback %}{{ pos.ad.get_name }}:
{% filter wordwrap:79 %}{% for pos in ad_feedback %}{{ pos.ad }}:
{% if pos.discuss %}Discuss [{{ pos.discuss_time|date:"Y-m-d" }}]:
{{ pos.discuss }}

View file

@ -0,0 +1,20 @@
{% load mail_filters %}{% autoescape off %}{% filter wordwrap:73 %}
The {{ approving_body }} has approved the following document in the {{ doc.stream }} stream:
- "{{ doc.title }}" ({{ doc.name }}) as {{ doc|std_level_prompt }}.{% if group_description %}
This document is the product of the {{ group_description }}.{% endif %}{% endfilter %}
URL: {{ doc_url }}
{% filter wordwrap:73 %}{% if consensus != None %}The document {% if consensus %}represents{% else %}does not necessarily represent{% endif%} the consensus of the {{ consensus_body }}.
{% endif %}No IANA allocation in the document requires IETF Consensus or Standards Action.{% endfilter %}
[OPTIONAL: Include summary of related discussion of this document in an IETF WG or in the IESG.]
[OPTIONAL: Include statement of the purpose of publishing this document, its intended audience, its merits and significance.]
[OPTIONAL: Include suggested names and contact information for one or more competent and independent potential reviewers for the document (this can speed the review and approval process).]
{% endautoescape %}

View file

@ -0,0 +1,9 @@
{% autoescape off %}{{ doc.name }} is no longer in the {{ prev_state.name }} state.
{% if comment %}Explanation:
{{ comment }}
{% endif %}
URL: {{ url }}
{% endautoescape %}

View file

@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}Request publication for {{ doc }}{% endblock %}
{% block morecss %}
form.request-publication #id_subject,
form.request-publication #id_body { width: 50em; }
form.request-publication #id_body { height: 30em; }
{% endblock %}
{% block content %}
<h1>Request publication for {{ doc }}</h1>
<p>Send a publication request to the RFC Editor for {{ doc }} and move
it to the <em>{{ next_state.name }}</em> stream state.</p>
<p>Please edit the message and remove any parts in brackets you do not
fill in. For independent submissions, see the <a
href="http://www.rfc-editor.org/indsubs.html">guidelines</a>.</p>
<form class="request-publication" action="" method="POST">
<table>
<tr><td>From:</td> <td>{{ message.frm }}</td></tr>
<tr><td>To:</td> <td>{{ message.to }}</td></tr>
<tr><td>Subject:</td> <td>{{ form.subject }} {{ form.subject.errors }}</td></tr>
<tr><td style="vertical-align: top">Message:</td> <td>{{ form.body }} {{ form.body.errors }}</td></tr>
<tr class="actions"><td></td>
<td>
<a href="{% url doc_view name=doc.name %}">Back</a>
<input name="reset" type="submit" value="Reset"/>
<input type="submit" value="Send request to the RFC Editor"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

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

View file

@ -71,6 +71,8 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{% endif %}
{% endwith %}
<span class="stream">{% if doc.obj.stream %} - {{ doc.obj.stream }} stream{% endif %}</span>
<br/>{{ doc.obj.title|escape }} ({{ doc.obj.intended_std_level }})
@ -97,6 +99,19 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
<br/>Was deferred by {{defer.by}} on {{defer.time|date:"Y-m-d"}}
{% endif %}
{% endwith %}
{% if doc.obj.iana_review_state %}
<br/>IANA Review: {{ doc.obj.iana_review_state }}
{% endif %}
{% if doc.obj.consensus %}
<br/>Consensus: {{ doc.obj.consensus }}
{% endif %}
{% if doc.obj.lastcall_expires %}
<br/>Last call expires: {{ doc.obj.lastcall_expires|date:"Y-m-d" }}
{% endif %}
</td><td style="padding-left:20px; width: 50px;">
{% ballot_icon doc.obj %}
</td></tr></tbody></table>

View file

@ -39,10 +39,13 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
{{ title2 }}
{% endif %}{{ title3 }}
{% for doc in section_docs %}{% with doc.obj.rfc_number as rfc_number %}
o {{doc.obj.canonical_name}}{% if not rfc_number %}-{{doc.obj.rev}}{% endif %}{% endwith %}
o {{doc.obj.canonical_name}}{% if not rfc_number %}-{{doc.obj.rev}}{% endif %}{% endwith %}{% if doc.obj.stream %} - {{ doc.obj.stream }} stream{% endif %}
{% filter wordwrap:"68"|indent|indent %}{{ doc.obj.title }} ({{ doc.obj.intended_std_level }}){% endfilter %}
{% if doc.obj.note %}{# note: note is not escaped #} {% filter wordwrap:"68"|indent|indent %}Note: {{ doc.obj.note|striptags }}{% endfilter %}
{% endif %} Token: {{ doc.obj.ad }}
{% endif %} Token: {{ doc.obj.ad }}{% if doc.obj.iana_review_state %}
IANA Review: {{ doc.obj.iana_review_state }}{% endif %}{% if doc.obj.consensus %}
Consensus: {{ doc.obj.consensus }}{% endif %}{% if doc.obj.lastcall_expires %}
Last call expires: {{ doc.obj.lastcall_expires|date:"Y-m-d" }}{% endif %}
{% with doc.obj.active_defer_event as defer %}{% if defer %} Was deferred by {{defer.by}} on {{defer.time|date:"Y-m-d"}}{% endif %}{% endwith %}
{% empty %}
NONE

View file

@ -7,7 +7,7 @@ Filename: {{ submission.filename }}
Revision: {{ submission.revision }}
Title: {{ submission.id_document_name }}
Creation date: {{ submission.creation_date|date:"Y-m-d" }}
WG ID: {{ wg }}
Group: {{ wg }}
Number of pages: {{ submission.txt_page_count }}
URL: http://www.ietf.org/internet-drafts/{{ submission.filename }}-{{ submission.revision }}.txt
Status: http://datatracker.ietf.org/doc/{{ submission.filename }}

View file

@ -93,7 +93,7 @@ table.ietf-table span.field-error { display: block; color: red; }
{% show_submission_files detail %}
</td></tr>
<tr><th>Submission date</th><td>{{ detail.submission_date }}</td></tr>
<tr{% if validation.warnings.group %} class="warning"{% endif %}><th>WG</th><td>{{ validation.wg|default:"Individual Submission" }}
<tr{% if validation.warnings.group %} class="warning"{% endif %}><th>Group</th><td>{{ validation.wg|default:"Individual Submission" }}
{% if validation.warnings.group %}
<div class="warn_message">The secretariat will be notified that the working group is not active</div>
{% endif %}

View file

@ -149,7 +149,7 @@ returned to the submitter.
</td>
</tr>
<tr{% if validation.warnings.revision %} class="warning"{% endif %}><th>Revision</th><td>{{ detail.revision }}<div class="warn_message">{{ validation.warnings.revision }}{% if validation.warnings.revision %}<br /><a class="twopages_trigger" href="#">[View error]</a>{% endif %}</div></td></tr>
<tr{% if validation.warnings.group %} class="warning"{% endif %}><th>WG</th><td>{{ validation.wg|default:"Individual Submission" }}<div class="warn_message">{{ validation.warnings.group }}</div></td></tr>
<tr{% if validation.warnings.group %} class="warning"{% endif %}><th>Group</th><td>{{ validation.wg|default:"Individual Submission" }}<div class="warn_message">{{ validation.warnings.group }}</div></td></tr>
<tr{% if validation.warnings.creation_date %} class="warning"{% endif %}><th>Document date</th><td>{{ detail.creation_date }}<div class="warn_message">{{ validation.warnings.creation_date }}</div></td></tr>
<tr><th>Submission date</th><td>{{ detail.submission_date }}</td></tr>
<tr{% if validation.warnings.title %} class="warning"{% endif %}><th>Title</th><td>{{ detail.id_document_name|default:"" }}<div class="warn_message">{{ validation.warnings.title }}</div></td></tr>

View file

@ -9,7 +9,7 @@ I-D Submission Tool URL:
File name : {{ draft.filename }}
Version : {{ draft.revision }}
Submission date : {{ draft.submission_date }}
WG : {{ draft.group_acronym|default:"Individual Submission" }} {% if form.validation.warnings.group %}*Please note that this WG is not an active one*{% endif %}
Group : {{ draft.group_acronym|default:"Individual Submission" }} {% if form.validation.warnings.group %}*Please note that this group is not an active one*{% endif %}
Title : {{ draft.id_document_name }}
Document date : {{ draft.creation_date }}

View file

@ -9,7 +9,7 @@ To approve the draft, go to this URL (note: you need to login to be able to appr
File name : {{ draft.filename }}
Version : {{ draft.revision }}
Submission date : {{ draft.submission_date }}
WG : {{ draft.group_acronym|default:"Individual Submission" }}
Group : {{ draft.group_acronym|default:"Individual Submission" }}
Title : {{ draft.id_document_name }}
Document date : {{ draft.creation_date }}

View file

@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}Sync discrepancies{% endblock %}
{% block morecss %}
table.discrepancies td { padding-right: 0.2em; }
{% endblock %}
{% block content %}
<h1>Sync discrepancies</h1>
{% for title, docs in sections %}
<h3>{{ title }}</h3>
{% if docs %}
<table class="discrepancies">
<tr>
<td>Draft Name</td>
<td>IESG state</td>
<td>RFC Editor state</td>
<td>IANA Action state</td>
</tr>
{% for d in docs %}
<tr id="d{{ d.pk }}">
<td><a href="{{ d.get_absolute_url }}">{{ d.name }}</a></td>
<td>{{ d.iesg_state|default:"-" }}</td>
<td>{{ d.rfc_state|default:"-" }}</td>
<td>{{ d.iana_action_state|default:"-" }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>None found.</p>
{% endif %}
{% endfor %}
{% endblock %}

View file

@ -0,0 +1,18 @@
{% autoescape off %}
This is an automated report of current Datatracker sync discrepancies,
also available at:
{{ url }}
{% for title, docs in sections %}
{{ title|wordwrap:73 }}
{% if docs %}{% for d in docs %}
{{ d.name }}
IESG: {{ d.iesg_state|default:"-" }}
RFC Ed: {{ d.rfc_state|default:"-" }}
IANA: {{ d.iana_action_state|default:"-" }}
{{ base_url }}{{ d.get_absolute_url }}
{% endfor %}{% else %}
None found.
{% endif %}{% endfor %}
{% endautoescape %}

View file

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block title %}Trigger {{ notification }} sync for {{ org }}{% endblock %}
{% block content %}
<h1>Trigger {{ notification }} sync for {{ org }}</h1>
<p>Update the Datatracker with {{ help_text|safe }} at {{ org }}.</p>
<form class="sync-form" method="post">
<input type="submit" value="Trigger {{ notification }} sync"/>
</form>
{% endblock %}

View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}Undo RFC Editor state events{% endblock %}
{% block content %}
<h1>Undo RFC Editor state events</h1>
<table class="ietf-table">
<tr>
<th>Time</th>
<th>Document</th>
<th>Text</th>
<th>Undo</th>
</tr>
{% for e in events %}
<tr class="{% if forloop.counter|divisibleby:2 %}evenrow{% else %}oddrow{% endif %}">
<td>{{ e.time|date:"Y-m-d H:i:s"}}</td>
<td><a href="{% url doc_history e.doc_id %}">{{ e.doc_id }}</a></td>
<td>{{ e.desc|safe }}</td>
<td>
<form method="post">
<input type="hidden" name="event" value="{{ e.id }}"/>
<input type="submit" value="Remove"/>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -68,6 +68,7 @@ urlpatterns = patterns('',
(r'^submit/', include('ietf.submit.urls')),
(r'^(?P<path>public)/', include('ietf.redirects.urls')),
(r'^wg/', include('ietf.wginfo.urls')),
(r'^sync/', include('ietf.sync.urls')),
# Google webmaster tools verification url
(r'^googlea30ad1dacffb5e5b.html', 'django.views.generic.simple.direct_to_template', { 'template': 'googlea30ad1dacffb5e5b.html' }),

View file

@ -26,7 +26,7 @@ test_mode = False
outbox = []
def empty_outbox():
outbox[:] = []
outbox[:] = []
def add_headers(msg):
if not(msg.has_key('Message-ID')):
@ -187,16 +187,34 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F
msg['X-Tracker-Bcc']=bcc
copy_email(msg, copy_to)
def send_mail_preformatted(request, preformatted):
def send_mail_preformatted(request, preformatted, extra={}, override={}):
"""Parse preformatted string containing mail with From:, To:, ...,
and send it through the standard IETF mail interface (inserting
extra headers as needed)."""
msg = message_from_string(preformatted.encode("utf-8"))
extra = copy.copy(msg)
for key in ['To', 'From', 'Subject', ]:
del extra[key]
send_mail_text(request, msg['To'], msg["From"], msg["Subject"], msg.get_payload(), extra=extra)
def send_mail_message(request, message):
for k, v in override.iteritems():
if k in msg:
del msg[k]
msg[k] = v
headers = copy.copy(msg)
for key in ['To', 'From', 'Subject']:
del headers[key]
for k, v in extra.iteritems():
if k in headers:
del headers[k]
headers[k] = v
send_mail_text(request, msg['To'], msg["From"], msg["Subject"], msg.get_payload(), extra=headers)
def send_mail_message(request, message, extra={}):
"""Send a Message object."""
# note that this doesn't handle MIME messages at the moment
send_mail_text(request, message.to, message.frm, message.subject, message.body, cc=message.cc, bcc=message.bcc, extra={ 'Reply-to': message.reply_to })
e = extra.copy()
if message.reply_to:
e['Reply-to'] = message.reply_to
send_mail_text(request, message.to, message.frm, message.subject,
message.body, cc=message.cc, bcc=message.bcc, extra=e)

23
ietf/utils/serialize.py Normal file
View file

@ -0,0 +1,23 @@
from django.db import models
def object_as_shallow_dict(obj):
"""Turn a Django model object into a dict suitable for passing to
create and for serializing to JSON."""
d = {}
for f in obj._meta.fields:
n = f.name
if isinstance(f, models.ForeignKey):
n = f.name + "_id"
v = getattr(obj, n)
if isinstance(f, models.ManyToManyField):
v = list(v.values_list("pk", flat=True))
elif isinstance(f, models.DateTimeField):
v = v.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(f, models.DateField):
v = v.strftime('%Y-%m-%d')
d[n] = v
return d

View file

@ -114,6 +114,22 @@ def make_test_data():
ascii="(System)",
address="",
)
# IANA and RFC Editor groups
iana = Group.objects.create(
name="IANA",
acronym="iana",
state_id="active",
type_id="ietf",
parent=None,
)
rfc_editor = Group.objects.create(
name="RFC Editor",
acronym="rfceditor",
state_id="active",
type_id="ietf",
parent=None,
)
if system_person.id != 0: # work around bug in Django
Person.objects.filter(id=system_person.id).update(id=0)
@ -248,6 +264,42 @@ def make_test_data():
email=email,
)
# IANA user
u = User.objects.create(username="iana")
p = Person.objects.create(
name="Ina Iana",
ascii="Ina Iana",
user=u)
Alias.objects.create(
name=p.name,
person=p)
email = Email.objects.create(
address="iana@ia.na",
person=p)
Role.objects.create(
name_id="auth",
group=iana,
email=email,
person=p,
)
# RFC Editor user
u = User.objects.create(username="rfc")
p = Person.objects.create(
name="Rfc Editor",
ascii="Rfc Editor",
user=u)
email = Email.objects.create(
address="rfc@edit.or",
person=p)
Role.objects.create(
name_id="auth",
group=rfc_editor,
email=email,
person=p,
)
# draft
draft = Document.objects.create(
name="draft-ietf-mars-test",

View file

@ -112,7 +112,7 @@ class SimpleUrlTestCase(TestCase,RealDatabaseTest):
self.ref_prefix = os.environ.get("IETFDB_REF_PREFIX", "")
if self.ref_prefix.endswith("/"):
self.ref_prefix = self.ref_prefix[:-1]
self.skip_heavy_tests = os.environ.get("IETFDB_SKIP_HEAVY", False)
self.skip_heavy_tests = os.environ.get("IETFDB_SKIP_HEAVY", True)
def tearDown(self):
self.tearDownRealDatabase()

37
ietf/utils/timezone.py Normal file
View file

@ -0,0 +1,37 @@
import pytz
import email.utils
import datetime
from django.conf import settings
def local_timezone_to_utc(d):
"""Takes a naive datetime in the local timezone and returns a
naive datetime with the corresponding UTC time."""
local_timezone = pytz.timezone(settings.TIME_ZONE)
d = local_timezone.localize(d).astimezone(pytz.utc)
return d.replace(tzinfo=None)
def utc_to_local_timezone(d):
"""Takes a naive datetime UTC and returns a naive datetime in the
local time zone."""
local_timezone = pytz.timezone(settings.TIME_ZONE)
d = local_timezone.normalize(d.replace(tzinfo=pytz.utc).astimezone(local_timezone))
return d.replace(tzinfo=None)
def email_time_to_local_timezone(date_string):
"""Takes a time string from an email and returns a naive datetime
in the local time zone."""
t = email.utils.parsedate_tz(date_string)
d = datetime.datetime(*t[:6])
if t[7] != None:
d += datetime.timedelta(seconds=t[9])
return utc_to_local_timezone(d)

204
magic.py
View file

@ -1,204 +0,0 @@
#!/usr/bin/env python
'''
Python bindings for libmagic
'''
import ctypes
from ctypes import *
from ctypes.util import find_library
def _init():
"""
Loads the shared library through ctypes and returns a library
L{ctypes.CDLL} instance
"""
return ctypes.cdll.LoadLibrary(find_library('magic'))
_libraries = {}
_libraries['magic'] = _init()
# Flag constants for open and setflags
MAGIC_NONE = NONE = 0
MAGIC_DEBUG = DEBUG = 1
MAGIC_SYMLINK = SYMLINK = 2
MAGIC_COMPRESS = COMPRESS = 4
MAGIC_DEVICES = DEVICES = 8
MAGIC_MIME_TYPE = MIME_TYPE = 16
MAGIC_CONTINUE = CONTINUE = 32
MAGIC_CHECK = CHECK = 64
MAGIC_PRESERVE_ATIME = PRESERVE_ATIME = 128
MAGIC_RAW = RAW = 256
MAGIC_ERROR = ERROR = 512
MAGIC_MIME_ENCODING = MIME_ENCODING = 1024
MAGIC_MIME = MIME = 1040
MAGIC_APPLE = APPLE = 2048
MAGIC_NO_CHECK_COMPRESS = NO_CHECK_COMPRESS = 4096
MAGIC_NO_CHECK_TAR = NO_CHECK_TAR = 8192
MAGIC_NO_CHECK_SOFT = NO_CHECK_SOFT = 16384
MAGIC_NO_CHECK_APPTYPE = NO_CHECK_APPTYPE = 32768
MAGIC_NO_CHECK_ELF = NO_CHECK_ELF = 65536
MAGIC_NO_CHECK_TEXT = NO_CHECK_TEXT = 131072
MAGIC_NO_CHECK_CDF = NO_CHECK_CDF = 262144
MAGIC_NO_CHECK_TOKENS = NO_CHECK_TOKENS = 1048576
MAGIC_NO_CHECK_ENCODING = NO_CHECK_ENCODING = 2097152
MAGIC_NO_CHECK_BUILTIN = NO_CHECK_BUILTIN = 4173824
class magic_set(Structure):
pass
magic_set._fields_ = []
magic_t = POINTER(magic_set)
_open = _libraries['magic'].magic_open
_open.restype = magic_t
_open.argtypes = [c_int]
_close = _libraries['magic'].magic_close
_close.restype = None
_close.argtypes = [magic_t]
_file = _libraries['magic'].magic_file
_file.restype = c_char_p
_file.argtypes = [magic_t, c_char_p]
_descriptor = _libraries['magic'].magic_descriptor
_descriptor.restype = c_char_p
_descriptor.argtypes = [magic_t, c_int]
_buffer = _libraries['magic'].magic_buffer
_buffer.restype = c_char_p
_buffer.argtypes = [magic_t, c_void_p, c_size_t]
_error = _libraries['magic'].magic_error
_error.restype = c_char_p
_error.argtypes = [magic_t]
_setflags = _libraries['magic'].magic_setflags
_setflags.restype = c_int
_setflags.argtypes = [magic_t, c_int]
_load = _libraries['magic'].magic_load
_load.restype = c_int
_load.argtypes = [magic_t, c_char_p]
_compile = _libraries['magic'].magic_compile
_compile.restype = c_int
_compile.argtypes = [magic_t, c_char_p]
_check = _libraries['magic'].magic_check
_check.restype = c_int
_check.argtypes = [magic_t, c_char_p]
_list = _libraries['magic'].magic_list
_list.restype = c_int
_list.argtypes = [magic_t, c_char_p]
_errno = _libraries['magic'].magic_errno
_errno.restype = c_int
_errno.argtypes = [magic_t]
class Magic(object):
def __init__(self, ms):
self._magic_t = ms
def close(self):
"""
Closes the magic database and deallocates any resources used.
"""
_close(self._magic_t)
def file(self, file):
"""
Returns a textual description of the contents of the argument passed
as a filename or None if an error occurred and the MAGIC_ERROR flag
is set. A call to errno() will return the numeric error code.
"""
return _file(self._magic_t, file)
def descriptor(self, fd):
"""
Like the file method, but the argument is a file descriptor.
"""
return _descriptor(self._magic_t, fd)
def buffer(self, buf):
"""
Returns a textual description of the contents of the argument passed
as a buffer or None if an error occurred and the MAGIC_ERROR flag
is set. A call to errno() will return the numeric error code.
"""
return _buffer(self._magic_t, buf, len(buf))
def error(self):
"""
Returns a textual explanation of the last error or None
if there was no error.
"""
return _error(self._magic_t)
def setflags(self, flags):
"""
Set flags on the magic object which determine how magic checking behaves;
a bitwise OR of the flags described in libmagic(3), but without the MAGIC_
prefix.
Returns -1 on systems that don't support utime(2) or utimes(2)
when PRESERVE_ATIME is set.
"""
return _setflags(self._magic_t, flags)
def load(self, file=None):
"""
Must be called to load entries in the colon separated list of database files
passed as argument or the default database file if no argument before
any magic queries can be performed.
Returns 0 on success and -1 on failure.
"""
return _load(self._magic_t, file)
def compile(self, dbs):
"""
Compile entries in the colon separated list of database files
passed as argument or the default database file if no argument.
Returns 0 on success and -1 on failure.
The compiled files created are named from the basename(1) of each file
argument with ".mgc" appended to it.
"""
return _compile(self._magic_t, dbs)
def check(self, dbs):
"""
Check the validity of entries in the colon separated list of
database files passed as argument or the default database file
if no argument.
Returns 0 on success and -1 on failure.
"""
return _check(self._magic_t, dbs)
def list(self, dbs):
"""
Check the validity of entries in the colon separated list of
database files passed as argument or the default database file
if no argument.
Returns 0 on success and -1 on failure.
"""
return _list(self._magic_t, dbs)
def errno(self):
"""
Returns a numeric error code. If return value is 0, an internal
magic error occurred. If return value is non-zero, the value is
an OS error code. Use the errno module or os.strerror() can be used
to provide detailed error information.
"""
return _errno(self._magic_t)
def open(flags):
"""
Returns a magic object on success and None on failure.
Flags argument as for setflags.
"""
return Magic(_open(flags))