diff --git a/django/test/testcases.py b/django/test/testcases.py index 8dcbf019d..1c43bfe23 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -494,6 +494,19 @@ class TransactionTestCase(unittest.TestCase): def assertQuerysetEqual(self, qs, values, transform=repr): return self.assertEqual(map(transform, qs), values) + # some things from newer (python 2.7) unittests + def assertIsNone(self, obj, msg=None): + """Same as self.assertTrue(obj is None), with a nicer default message.""" + if obj is not None: + standardMsg = '%s is not None' % (safe_repr(obj),) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNotNone(self, obj, msg=None): + """Included for symmetry with assertIsNone.""" + if obj is None: + standardMsg = 'unexpectedly None' + self.fail(self._formatMessage(msg, standardMsg)) + def connections_support_transactions(): """ Returns True if all connections support transactions. This is messy diff --git a/ietf/bin/test-crawl b/ietf/bin/test-crawl new file mode 100755 index 000000000..395ca1eb7 --- /dev/null +++ b/ietf/bin/test-crawl @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +import os, sys, re, datetime, optparse, traceback +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) + +import django.test +from django.conf import settings + +# prevent memory from leaking when settings.DEBUG=True +from django.db import connection +class DontSaveQueries(object): + def append(self, x): + pass +connection.queries = DontSaveQueries() + +MAX_URL_LENGTH = 500 +SLOW_THRESHOLD = 1.0 + +def strip_url(url): + if url.startswith("http://testserver"): + url = url[len("http://testserver"):] + return url + +def extract_html_urls(content): + for m in re.finditer(r'', content): + url = strip_url(m.group(1)) + if len(url) > MAX_URL_LENGTH: + continue # avoid infinite GET parameter appendages + + if not url.startswith("/"): + continue + + yield url + + +visited = set() +blacklist = set() +urls = set(["/doc/all/"]) + +client = django.test.Client() + +while urls: + url = urls.pop() + + visited.add(url) + + try: + timestamp = datetime.datetime.now() + r = client.get(url) + elapsed = datetime.datetime.now() - timestamp + except KeyboardInterrupt: + print "was fetching", url + sys.exit(1) + except: + print "FAIL", url + print "=============" + traceback.print_exc() + print "=============" + else: + tags = [] + + if r.status_code in (301, 302): + u = strip_url(r["Location"]) + if u not in visited and u not in urls: + urls.add(u) + + elif r.status_code == 200: + ctype = r["Content-Type"] + if ";" in ctype: + ctype = ctype[:ctype.index(";")] + + if ctype == "text/html": + for u in extract_html_urls(r.content): + if u not in visited and u not in urls: + urls.add(u) + else: + tags.append("FAIL") + + if elapsed.total_seconds() > SLOW_THRESHOLD: + tags.append("SLOW") + + print r.status_code, "%.3fs" % elapsed.total_seconds(), url, " ".join(tags) + diff --git a/ietf/doc/admin.py b/ietf/doc/admin.py index 1c007f228..fca46fd3d 100644 --- a/ietf/doc/admin.py +++ b/ietf/doc/admin.py @@ -93,7 +93,7 @@ class DocumentAdmin(admin.ModelAdmin): list_display = ['name', 'rev', 'group', 'pages', 'intended_std_level', 'author_list', 'time'] search_fields = ['name'] list_filter = ['type'] - raw_id_fields = ['authors', 'related', 'group', 'shepherd', 'ad'] + raw_id_fields = ['authors', 'group', 'shepherd', 'ad'] inlines = [DocAliasInline, DocAuthorInline, RelatedDocumentInline, ] form = DocumentForm diff --git a/ietf/doc/migrations/0011_cleanup_new_revision_docevents.py b/ietf/doc/migrations/0011_cleanup_new_revision_docevents.py new file mode 100644 index 000000000..4d069781c --- /dev/null +++ b/ietf/doc/migrations/0011_cleanup_new_revision_docevents.py @@ -0,0 +1,351 @@ +# 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): + for e in orm.NewRevisionDocEvent.objects.filter(type="new_revision").iterator(): + # fix the format + if (e.doc_id.startswith("draft-") or e.doc_id.startswith("charter-")) and e.desc in ("Added new revision", "New revision available"): + desc = "New version available: %s-%s.txt" % (e.doc_id, e.rev) + orm.DocEvent.objects.filter(id=e.docevent_ptr_id).update(desc=desc) + + + 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.docalias': { + 'Meta': {'object_name': 'DocAlias'}, + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'doc.docevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'}, + 'by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']"}), + 'desc': ('django.db.models.fields.TextField', [], {}), + 'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'doc.dochistory': { + 'Meta': {'object_name': 'DocHistory'}, + 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocHistoryAuthor']", 'blank': 'True'}), + 'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': "orm['doc.Document']"}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}), + 'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}), + 'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.DocAlias']", 'symmetrical': 'False', 'through': "orm['doc.RelatedDocHistory']", 'blank': 'True'}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_dochistory_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}) + }, + 'doc.dochistoryauthor': { + 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {}) + }, + 'doc.docreminder': { + 'Meta': {'object_name': 'DocReminder'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'due': ('django.db.models.fields.DateTimeField', [], {}), + 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocEvent']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocReminderTypeName']"}) + }, + 'doc.document': { + 'Meta': {'object_name': 'Document'}, + 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}), + 'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}), + 'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'related': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'reversely_related_document_set'", 'blank': 'True', 'through': "orm['doc.RelatedDocument']", 'to': "orm['doc.DocAlias']"}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}) + }, + 'doc.documentauthor': { + 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}) + }, + 'doc.initialreviewdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.lastcalldocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.newrevisiondocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'}) + }, + 'doc.relateddochistory': { + 'Meta': {'object_name': 'RelatedDocHistory'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': "orm['doc.DocAlias']"}) + }, + 'doc.relateddocument': { + 'Meta': {'object_name': 'RelatedDocument'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"}) + }, + 'doc.state': { + 'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'doc.statetype': { + 'Meta': {'object_name': 'StateType'}, + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}) + }, + 'doc.telechatdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.writeupdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'group.group': { + 'Meta': {'object_name': 'Group'}, + 'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}), + 'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}), + 'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}), + 'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'name.ballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'}, + 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docrelationshipname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docremindertypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'person.email': { + 'Meta': {'object_name': 'Email'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'person.person': { + 'Meta': {'object_name': 'Person'}, + 'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['doc'] diff --git a/ietf/doc/migrations/0012_cleanup_doc_notes.py b/ietf/doc/migrations/0012_cleanup_doc_notes.py new file mode 100644 index 000000000..533dad298 --- /dev/null +++ b/ietf/doc/migrations/0012_cleanup_doc_notes.py @@ -0,0 +1,363 @@ +# encoding: utf-8 +import datetime, re +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + for d in orm.Document.objects.filter(type="draft").exclude(note="").iterator(): + note = d.note + + # get rid of HTML garbage + note = note.replace("\r", "") + note = note.replace("
", "\n") + note = note.replace(" ", " ") + note = note.strip() + + # whack obsolete RFC/BCP/STD 1234 notes (obsolete now that + # we got RFC alias links set up properly), + # there are a couple that match without a + # corresponding RFC DocAlias, but upon manual + # inspection these all turn out to be mistakes in the + # note (most are off-by-one in the RFC number) + note = re.sub("^((STD|BCP|RFC)\s*#?\d+[\s,;]*)+", "", note).strip() + + if note != d.note: + orm.Document.objects.filter(name=d.name).update(note=note) + + 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.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'}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}) + }, + 'doc.documentauthor': { + 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}) + }, + 'doc.initialreviewdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.lastcalldocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.newrevisiondocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'}) + }, + 'doc.relateddochistory': { + 'Meta': {'object_name': 'RelatedDocHistory'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocHistory']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': "orm['doc.DocAlias']"}) + }, + 'doc.relateddocument': { + 'Meta': {'object_name': 'RelatedDocument'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocRelationshipName']"}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.DocAlias']"}) + }, + 'doc.state': { + 'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_states'", 'symmetrical': 'False', 'to': "orm['doc.State']"}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'doc.statetype': { + 'Meta': {'object_name': 'StateType'}, + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}) + }, + 'doc.telechatdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.writeupdocevent': { + 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': ['doc.DocEvent']}, + 'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'group.group': { + 'Meta': {'object_name': 'Group'}, + 'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}), + 'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}), + 'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}), + 'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'name.ballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'}, + 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docrelationshipname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docremindertypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'person.email': { + 'Meta': {'object_name': 'Email'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'person.person': { + 'Meta': {'object_name': 'Person'}, + 'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['doc'] diff --git a/ietf/doc/migrations/0013_fixup_broken_rfc_docs.py b/ietf/doc/migrations/0013_fixup_broken_rfc_docs.py new file mode 100644 index 000000000..1d8f17bee --- /dev/null +++ b/ietf/doc/migrations/0013_fixup_broken_rfc_docs.py @@ -0,0 +1,379 @@ +# 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): + # first get rid of unconnected redundant RFCs + for d in orm.Document.objects.filter(name__startswith="rfc"): + if d.docalias_set.count() == 0 and orm.DocAlias.objects.filter(name=d.name): + d.delete() # this doc is unreachable + + # fix up some standalone RFCs that didn't get type="draft" set + orm.Document.objects.filter(name__startswith="rfc", type=None).update(type="draft") + + # fix RFCs with group=None, set them to the individual + # submitter "none" group - this may in some historic cases be + # wrong, but in many of those cases we don't have a group to + # connect the document to anyway (a later history fixing round + # can find these documents as + # Document.objects.filter(name__startswith="rfc", group__type="individ")) + orm.Document.objects.filter(name__startswith="rfc", group=None).update(group=orm["group.Group"].objects.get(type="individ")) + + + 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.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'}), + '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'] diff --git a/ietf/doc/migrations/0014_fix_rfc_aliases.py b/ietf/doc/migrations/0014_fix_rfc_aliases.py new file mode 100644 index 000000000..14a128bf2 --- /dev/null +++ b/ietf/doc/migrations/0014_fix_rfc_aliases.py @@ -0,0 +1,369 @@ +# 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): + for alias in orm.DocAlias.objects.filter(models.Q(name__startswith="bcp") + | models.Q(name__startswith="std") + | models.Q(name__startswith="fyi")): + # remove left-padded zeros + alias.name = alias.name[:3] + str(int(alias.name[3:])) + alias.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.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'}), + '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'] diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 31d6a35f6..474025963 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -4,6 +4,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 django.utils.html import mark_safe from ietf.group.models import * from ietf.name.models import * @@ -99,20 +100,26 @@ class DocumentInfo(models.Model): self.states.remove(*others) if state not in already_set: self.states.add(state) + self.state_cache = None # invalidate cache def unset_state(self, state_type): """Unset state of type so no state of that type is any longer set.""" self.states.remove(*self.states.filter(type=state_type)) + self.state_cache = None # invalidate cache def get_state(self, state_type=None): - """Get state of type, or default state for document type if not specified.""" + """Get state of type, or default state for document type if + not specified. Uses a local cache to speed multiple state + reads up.""" if state_type == None: state_type = self.type_id - try: - return self.states.get(type=state_type) - except State.DoesNotExist: - return None + if not hasattr(self, "state_cache") or self.state_cache == None: + self.state_cache = {} + for s in self.states.all().select_related(): + self.state_cache[s.type_id] = s + + return self.state_cache.get(state_type, None) def get_state_slug(self, state_type=None): """Get state of type, or default if not specified, returning @@ -137,8 +144,10 @@ class DocumentInfo(models.Model): def active_ballot(self): """Returns the most recently created ballot if it isn't closed.""" ballot = self.latest_event(BallotDocEvent, type="created_ballot") - open = self.ballot_open(ballot.ballot_type.slug) if ballot else False - return ballot if open else None + if ballot and self.ballot_open(ballot.ballot_type.slug): + return ballot + else: + return None class Meta: abstract = True @@ -167,7 +176,7 @@ class DocumentAuthor(models.Model): class Document(DocumentInfo): name = models.CharField(max_length=255, primary_key=True) # immutable - related = models.ManyToManyField('DocAlias', through=RelatedDocument, blank=True, related_name="reversely_related_document_set") + #related = models.ManyToManyField('DocAlias', through=RelatedDocument, blank=True, related_name="reversely_related_document_set") authors = models.ManyToManyField(Email, through=DocumentAuthor, blank=True) def __unicode__(self): @@ -230,6 +239,14 @@ class Document(DocumentInfo): name = name.upper() return name + def related_that(self, relationship): + """Return the documents that are source of relationship targeting self.""" + return Document.objects.filter(relateddocument__target__document=self, relateddocument__relationship=relationship) + + def related_that_doc(self, relationship): + """Return the doc aliases that are target of relationship originating from self.""" + return DocAlias.objects.filter(relateddocument__source=self, relateddocument__relationship=relationship) + #TODO can/should this be a function instead of a property? Currently a view uses it as a property @property def telechat_date(self): @@ -284,9 +301,6 @@ class Document(DocumentInfo): qs = self.docalias_set.filter(name__startswith='rfc') return qs[0].name[3:] if qs else None - def replaced_by(self): - return [ rel.source for alias in self.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='replaces') ] - def friendly_state(self): """ Return a concise text description of the document's current state """ if self.type_id=='draft': @@ -305,12 +319,12 @@ class Document(DocumentInfo): iesg_state_summary = iesg_state_summary + "::"+"::".join(tag.name for tag in iesg_substate) if self.get_state_slug() == "rfc": - #return "RFC %d" % (urlreverse('doc_view', args=['rfc%d' % int(self.rfc_number())]), int(self.rfc_number())) - return "RFC %d (%s)" % (int(self.rfc_number()), self.std_level) + n = self.rfc_number() + return "RFC %s" % (urlreverse('doc_view', kwargs=dict(name='rfc%s' % n)), n) elif self.get_state_slug() == "repl": - rs = self.replaced_by() + rs = self.related_that("replaces") if rs: - return "Replaced by "+", ".join("%s" % (urlreverse('doc_view', args=[name]),name) for name in rs) + return mark_safe("Replaced by " + ", ".join("%s" % (urlreverse('doc_view', args=[name]), name) for name in rs)) else: return "Replaced" elif self.get_state_slug() == "active": @@ -585,7 +599,13 @@ class BallotDocEvent(DocEvent): if e.pos != prev: latest.old_positions.append(e.pos) - + + # get rid of trailling "No record" positions, some old ballots + # have plenty of these + for p in positions: + while p.old_positions and p.old_positions[-1].slug == "norecord": + p.old_positions.pop() + # add any missing ADs through fake No Record events if self.doc.active_ballot() == self: norecord = BallotPositionName.objects.get(slug="norecord") diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index c527fa6c4..a4a7cff17 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -1,4 +1,144 @@ +import os, shutil, datetime +import django.test +from django.core.urlresolvers import reverse as urlreverse +from django.conf import settings +from pyquery import PyQuery + +from ietf.utils.mail import outbox +from ietf.utils.test_utils import login_testing_unauthorized +from ietf.utils.test_data import make_test_data + +from ietf.doc.models import * +from ietf.name.models import * +from ietf.group.models import * +from ietf.person.models import * +from ietf.meeting.models import Meeting, MeetingTypeName +from ietf.iesg.models import TelechatDate + +# extra tests from ietf.doc.tests_conflict_review import * -from ietf.doc.tests_status_change import * + + +class DocTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_document_draft(self): + draft = make_test_data() + + # these tests aren't testing all attributes yet, feel free to + # expand them + + + # active draft + draft.set_state(State.objects.get(type="draft", slug="active")) + + r = self.client.get(urlreverse("doc_view", kwargs=dict(name=draft.name))) + self.assertEqual(r.status_code, 200) + self.assertTrue("Active Internet-Draft" in r.content) + + # expired draft + draft.set_state(State.objects.get(type="draft", slug="expired")) + + r = self.client.get(urlreverse("doc_view", kwargs=dict(name=draft.name))) + self.assertEqual(r.status_code, 200) + self.assertTrue("Expired Internet-Draft" in r.content) + + # replaced draft + draft.set_state(State.objects.get(type="draft", slug="repl")) + + replacement = Document.objects.create( + name="draft-ietf-replacement", + time=datetime.datetime.now(), + type_id="draft", + title="Replacement Draft", + stream_id=draft.stream_id, group_id=draft.group_id, abstract=draft.stream, rev=draft.rev, + pages=draft.pages, intended_std_level_id=draft.intended_std_level_id, + shepherd_id=draft.shepherd_id, ad_id=draft.ad_id, expires=draft.expires, + notify=draft.notify, note=draft.note) + DocAlias.objects.create(name=replacement.name, document=replacement) + rel = RelatedDocument.objects.create(source=replacement, + target=draft.docalias_set.get(name__startswith="draft"), + relationship_id="replaces") + + r = self.client.get(urlreverse("doc_view", kwargs=dict(name=draft.name))) + self.assertEqual(r.status_code, 200) + self.assertTrue("Replaced Internet-Draft" in r.content) + self.assertTrue(replacement.name in r.content) + rel.delete() + + # draft published as RFC + draft.set_state(State.objects.get(type="draft", slug="rfc")) + draft.std_level_id = "bcp" + draft.save() + + DocEvent.objects.create(doc=draft, type="published_rfc", by=Person.objects.get(name="(System)")) + + rfc_alias = DocAlias.objects.create(name="rfc123456", document=draft) + bcp_alias = DocAlias.objects.create(name="bcp123456", document=draft) + + r = self.client.get(urlreverse("doc_view", kwargs=dict(name=draft.name))) + self.assertEqual(r.status_code, 302) + r = self.client.get(urlreverse("doc_view", kwargs=dict(name=bcp_alias.name))) + self.assertEqual(r.status_code, 302) + + r = self.client.get(urlreverse("doc_view", kwargs=dict(name=rfc_alias.name))) + self.assertEqual(r.status_code, 200) + self.assertTrue("RFC 123456" in r.content) + self.assertTrue(draft.name in r.content) + + # naked RFC + rfc = Document.objects.create( + name="rfc1234567", + type_id="draft", + title="RFC without a Draft", + stream_id="ise", + group=Group.objects.get(type="individ"), + std_level_id="ps") + DocAlias.objects.create(name=rfc.name, document=rfc) + r = self.client.get(urlreverse("doc_view", kwargs=dict(name=rfc.name))) + self.assertEqual(r.status_code, 200) + self.assertTrue("RFC 1234567" in r.content) + + # unknown draft + r = self.client.get(urlreverse("doc_view", kwargs=dict(name="draft-xyz123"))) + self.assertEqual(r.status_code, 404) + + def test_document_charter(self): + make_test_data() + + r = self.client.get(urlreverse("doc_view", kwargs=dict(name="charter-ietf-mars"))) + self.assertEqual(r.status_code, 200) + + def test_document_conflict_review(self): + make_test_data() + + r = self.client.get(urlreverse("doc_view", kwargs=dict(name='conflict-review-imaginary-irtf-submission'))) + self.assertEqual(r.status_code, 200) + + def test_document_ballot(self): + doc = make_test_data() + ballot = doc.active_ballot() + + BallotPositionDocEvent.objects.create( + doc=doc, + type="changed_ballot_position", + pos_id="yes", + comment="Looks fine to me", + comment_time=datetime.datetime.now(), + ad=Person.objects.get(user__username="ad"), + by=Person.objects.get(name="(System)")) + + r = self.client.get(urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name))) + self.assertEqual(r.status_code, 200) + + # test popup too while we're at it + r = self.client.get(urlreverse("ietf.doc.views_doc.ballot_for_popup", kwargs=dict(name=doc.name))) + self.assertEqual(r.status_code, 200) + + def test_document_json(self): + doc = make_test_data() + + r = self.client.get(urlreverse("ietf.doc.views_doc.document_json", kwargs=dict(name=doc.name))) + self.assertEqual(r.status_code, 200) diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index 9f5b7d7ef..dbab6f70b 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -1,4 +1,4 @@ -import os +import os, re, urllib import math from django.conf import settings @@ -138,9 +138,12 @@ def augment_events_with_revision(doc, events): event_revisions = list(NewRevisionDocEvent.objects.filter(doc=doc).order_by('time', 'id').values('id', 'rev', 'time')) - cur_rev = doc.rev - if doc.get_state_slug() == "rfc": - cur_rev = "RFC" + if doc.type_id == "draft" and doc.get_state_slug() == "rfc": + # add fake "RFC" revision + e = doc.latest_event(type="published_rfc") + if e: + event_revisions.append(dict(id=e.id, time=e.time, rev="RFC")) + event_revisions.sort(key=lambda x: (x["time"], x["id"])) for e in sorted(events, key=lambda e: (e.time, e.id), reverse=True): while event_revisions and (e.time, e.id) < (event_revisions[-1]["time"], event_revisions[-1]["id"]): @@ -153,6 +156,37 @@ def augment_events_with_revision(doc, events): e.rev = cur_rev +def add_links_in_new_revision_events(doc, events, diff_revisions): + """Add direct .txt links and diff links to new_revision events.""" + prev = None + + diff_urls = dict(((name, revision), url) for name, revision, time, url in diff_revisions) + + for e in sorted(events, key=lambda e: (e.time, e.id)): + if not e.type == "new_revision": + continue + + if not (e.doc.name, e.rev) in diff_urls: + continue + + full_url = diff_url = diff_urls[(e.doc.name, e.rev)] + + if doc.type_id in "draft": # work around special diff url for drafts + full_url = "http://tools.ietf.org/id/" + diff_url + ".txt" + + # build links + links = r'\1' % full_url + if prev: + links += "" + + if prev != None: + links += ' (diff from previous)' % (settings.RFCDIFF_PREFIX, urllib.quote(diff_url, safe="~"), urllib.quote(prev, safe="~")) + + # replace the bold filename part + e.desc = re.sub(r"(.+-[0-9][0-9].txt)", links, e.desc) + + prev = diff_url + def get_document_content(key, filename, split=True, markup=True): f = None @@ -161,15 +195,12 @@ def get_document_content(key, filename, split=True, markup=True): raw_content = f.read() except IOError: error = "Error; cannot read ("+key+")" - if split: - return (error, "") - else: - return error + return error finally: if f: f.close() if markup: - return markup_txt.markup(raw_content,split) + return markup_txt.markup(raw_content, split) else: return raw_content @@ -197,3 +228,16 @@ def add_state_change_event(doc, by, prev_state, new_state, timestamp=None): e.save() return e +def prettify_std_name(n): + if re.match(r"(rfc|bcp|fyi|std)[0-9]+", n): + return n[:3].upper() + " " + n[3:] + else: + return n + +def nice_consensus(consensus): + mapping = { + None: "Unknown", + True: "Yes", + False: "No" + } + return mapping[consensus] diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py new file mode 100644 index 000000000..bee19c160 --- /dev/null +++ b/ietf/doc/views_doc.py @@ -0,0 +1,701 @@ +# Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. Contact: Pasi Eronen +# +# 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. + +import re, os, datetime, urllib + +from django.http import HttpResponse, Http404 +from django.shortcuts import render_to_response, get_object_or_404, redirect +from django.template import RequestContext +from django.template.loader import render_to_string +from django.template.defaultfilters import truncatewords_html +from django.utils import simplejson as json +from django.utils.decorators import decorator_from_middleware +from django.middleware.gzip import GZipMiddleware +from django.core.urlresolvers import reverse as urlreverse, NoReverseMatch +from django.conf import settings + +from ietf.doc.models import * +from ietf.doc.utils import * +from ietf.utils.history import find_history_active_at +from ietf.ietfauth.utils import * + +def render_document_top(request, doc, tab, name): + tabs = [] + tabs.append(("Document", "document", urlreverse("doc_view", kwargs=dict(name=name)), True)) + + ballot = doc.latest_event(BallotDocEvent, type="created_ballot") + if doc.type_id in ("draft","conflrev"): + # if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot: + tabs.append(("IESG Evaluation Record", "ballot", urlreverse("doc_ballot", kwargs=dict(name=name)), ballot)) + elif doc.type_id == "charter": + tabs.append(("IESG Review", "ballot", urlreverse("doc_ballot", kwargs=dict(name=name)), ballot)) + + # FIXME: if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot: + if doc.type_id != "conflrev": + tabs.append(("IESG Writeups", "writeup", urlreverse("doc_writeup", kwargs=dict(name=name)), True)) + + tabs.append(("History", "history", urlreverse("doc_history", kwargs=dict(name=name)), True)) + + if name.startswith("rfc"): + name = "RFC %s" % name[3:] + else: + name += "-" + doc.rev + + return render_to_string("doc/document_top.html", + dict(doc=doc, + tabs=tabs, + selected=tab, + name=name)) + + +@decorator_from_middleware(GZipMiddleware) +def document_main(request, name, rev=None): + doc = get_object_or_404(Document.objects.select_related(), docalias__name=name) + + # take care of possible redirections + aliases = DocAlias.objects.filter(document=doc).values_list("name", flat=True) + if doc.type_id == "draft" and not name.startswith("rfc"): + for a in aliases: + if a.startswith("rfc"): + return redirect("doc_view", name=a) + + group = doc.group + if doc.type_id == 'conflrev': + conflictdoc = doc.related_that_doc('conflrev').get().document + + revisions = [] + for h in doc.history_set.order_by("time", "id"): + if h.rev and not h.rev in revisions: + revisions.append(h.rev) + if not doc.rev in revisions: + revisions.append(doc.rev) + + snapshot = False + + if rev != None: + if rev == doc.rev: + return redirect('doc_view', name=name) + + # find the entry in the history + for h in doc.history_set.order_by("-time"): + if rev == h.rev: + snapshot = True + doc = h + break + + if not snapshot: + return redirect('doc_view', name=name) + + if doc.type_id == "charter": + # find old group, too + gh = find_history_active_at(doc.group, doc.time) + if gh: + group = gh + + top = render_document_top(request, doc, "document", name) + + + telechat = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat") + if telechat and (not telechat.telechat_date or telechat.telechat_date < datetime.date.today()): + telechat = None + + + # specific document types + if doc.type_id == "draft": + split_content = not request.GET.get('include_text') + if request.COOKIES.get("full_draft", "") == "on": + split = False + + iesg_state = doc.get_state("draft-iesg") + + can_edit = has_role(request.user, ("Area Director", "Secretariat")) + stream_slugs = StreamName.objects.values_list("slug", flat=True) + can_change_stream = bool(can_edit or ( + request.user.is_authenticated() and + Role.objects.filter(name__in=("chair", "auth"), + group__acronym__in=stream_slugs, + person__user=request.user))) + can_edit_iana_state = has_role(request.user, ("Secretariat", "IANA")) + + rfc_number = name[3:] if name.startswith("") else None + draft_name = None + for a in aliases: + if a.startswith("draft"): + draft_name = a + + rfc_aliases = [prettify_std_name(a) for a in aliases + if a.startswith("fyi") or a.startswith("std") or a.startswith("bcp")] + + latest_revision = None + + if doc.get_state_slug() == "rfc": + # content + filename = name + ".txt" + + content = get_document_content(filename, os.path.join(settings.RFC_PATH, filename), + split_content, markup=True) + + # file types + base_path = os.path.join(settings.RFC_PATH, name + ".") + possible_types = ["txt", "pdf", "ps"] + found_types = [t for t in possible_types if os.path.exists(base_path + t)] + + base = "http://www.rfc-editor.org/rfc/" + + file_urls = [] + for t in found_types: + label = "plain text" if t == "txt" else t + file_urls.append((label, base + name + "." + t)) + + if "pdf" not in found_types and "txt" in found_types: + file_urls.append(("pdf", base + "pdfrfc/" + name + ".txt.pdf")) + + if "txt" in found_types: + file_urls.append(("html", "http://tools.ietf.org/html/" + name)) + + if not found_types: + content = "This RFC is not currently available online." + split_content = False + elif "txt" not in found_types: + content = "This RFC is not available in plain text format." + split_content = False + else: + filename = "%s-%s.txt" % (draft_name, doc.rev) + + content = get_document_content(filename, os.path.join(settings.INTERNET_DRAFT_PATH, filename), + split_content, markup=True) + + # file types + base_path = os.path.join(settings.INTERNET_DRAFT_PATH, doc.name + "-" + doc.rev + ".") + possible_types = ["pdf", "xml", "ps"] + found_types = ["txt"] + [t for t in possible_types if os.path.exists(base_path + t)] + + tools_base = "http://tools.ietf.org/" + + if doc.get_state_slug() == "active": + base = "http://www.ietf.org/id/" + else: + base = tools_base + "id/" + + file_urls = [] + for t in found_types: + label = "plain text" if t == "txt" else t + file_urls.append((label, base + doc.name + "-" + doc.rev + "." + t)) + + if "pdf" not in found_types: + file_urls.append(("pdf", tools_base + "pdf/" + doc.name + "-" + doc.rev + ".pdf")) + file_urls.append(("html", tools_base + "html/" + doc.name + "-" + doc.rev)) + + # latest revision + latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision") + + # ballot + ballot_summary = None + if iesg_state and iesg_state.slug in ("lc", "writeupw", "goaheadw", "iesg-eva", "defer"): + active_ballot = doc.active_ballot() + if active_ballot: + ballot_summary = needed_ballot_positions(doc, active_ballot.active_ad_positions().values()) + + # submission + submission = "" + if group.type_id == "individ": + submission = "individual" + elif group.type_id == "area" and doc.stream_id == "ietf": + submission = "individual in %s area" % group.acronym + elif group.type_id in ("rg", "wg"): + submission = "%s %s" % (group.acronym, group.type) + if group.type_id == "wg": + submission = "%s" % (urlreverse("wg_docs", kwargs=dict(acronym=doc.group.acronym)), submission) + if doc.stream_id and doc.get_state_slug("draft-stream-%s" % doc.stream_id) == "c-adopt": + submission = "candidate for %s" % submission + + # resurrection + resurrected_by = None + if doc.get_state_slug() == "expired": + e = doc.latest_event(type__in=("requested_resurrect", "completed_resurrect")) + if e and e.type == "requested_resurrect": + resurrected_by = e.by + + # stream info + stream_state = None + if doc.stream: + stream_state = doc.get_state("draft-stream-%s" % doc.stream_id) + stream_tags = doc.tags.filter(slug__in=get_tags_for_stream_id(doc.stream_id)) + + shepherd_writeup = doc.latest_event(WriteupDocEvent, type="changed_protocol_writeup") + + can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) + can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd) or has_role(request.user, ["Area Director"]) + + consensus = None + if doc.stream_id in ("ietf", "irtf", "iab"): + e = doc.latest_event(ConsensusDocEvent, type="changed_consensus") + consensus = nice_consensus(e and e.consensus) + + # mailing list search archive + search_archive = "www.ietf.org/mail-archive/web/" + if doc.stream_id == "ietf" and group.type_id == "wg" and group.list_archive: + search_archive = group.list_archive + + search_archive = urllib.quote(search_archive, safe="~") + + # conflict reviews + conflict_reviews = [d.name for d in doc.related_that("conflrev")] + + # remaining actions + actions = [] + + if ((not doc.stream_id or doc.stream_id in ("ietf", "irtf")) and group.type_id == "individ" and + (request.user.is_authenticated() and + Role.objects.filter(person__user=request.user, name__in=("chair", "delegate"), + group__type__in=("wg",), + group__state="active") + or has_role(request.user, "Secretariat"))): + actions.append(("Adopt in Group", urlreverse('edit_adopt', kwargs=dict(name=doc.name)))) + + if doc.get_state_slug() == "expired" and not resurrected_by and can_edit: + actions.append(("Request Resurrect", urlreverse('doc_request_resurrect', kwargs=dict(name=doc.name)))) + + if doc.get_state_slug() == "expired" and has_role(request.user, ("Secretariat",)): + actions.append(("Resurrect", urlreverse('doc_resurrect', kwargs=dict(name=doc.name)))) + + if (doc.get_state_slug() != "expired" and doc.stream_id in ("ise", "irtf") + and has_role(request.user, ("Secretariat",)) and not conflict_reviews): + label = "Begin IETF Conflict Review" + if not doc.intended_std_level: + label += " (note that intended status is not set)" + actions.append((label, urlreverse('conflict_review_start', kwargs=dict(name=doc.name)))) + + if doc.get_state_slug() != "expired" and doc.stream_id in ("ietf",) and can_edit and not iesg_state: + actions.append(("Begin IESG Processing", urlreverse('doc_edit_info', kwargs=dict(name=doc.name)) + "?new=1")) + + return render_to_response("doc/document_draft.html", + dict(doc=doc, + group=group, + top=top, + name=name, + content=content, + split_content=split_content, + revisions=revisions, + snapshot=snapshot, + latest_revision=latest_revision, + + can_edit=can_edit, + can_change_stream=can_change_stream, + can_edit_stream_info=can_edit_stream_info, + can_edit_shepherd_writeup=can_edit_shepherd_writeup, + can_edit_iana_state=can_edit_iana_state, + + rfc_number=rfc_number, + draft_name=draft_name, + telechat=telechat, + ballot_summary=ballot_summary, + submission=submission, + resurrected_by=resurrected_by, + + replaces=[d.name for d in doc.related_that_doc("replaces")], + replaced_by=[d.name for d in doc.related_that("replaces")], + updates=[prettify_std_name(d.name) for d in doc.related_that_doc("updates")], + updated_by=[prettify_std_name(d.canonical_name()) for d in doc.related_that("updates")], + obsoletes=[prettify_std_name(d.name) for d in doc.related_that_doc("obs")], + obsoleted_by=[prettify_std_name(d.canonical_name()) for d in doc.related_that("obs")], + conflict_reviews=conflict_reviews, + rfc_aliases=rfc_aliases, + has_errata=doc.tags.filter(slug="errata"), + published=doc.latest_event(type="published_rfc"), + file_urls=file_urls, + stream_state=stream_state, + stream_tags=stream_tags, + milestones=doc.groupmilestone_set.filter(state="active"), + consensus=consensus, + iesg_state=iesg_state, + rfc_editor_state=doc.get_state("draft-rfceditor"), + iana_review_state=doc.get_state("draft-iana-review"), + iana_action_state=doc.get_state("draft-iana-action"), + started_iesg_process=doc.latest_event(type="started_iesg_process"), + shepherd_writeup=shepherd_writeup, + search_archive=search_archive, + actions=actions, + ), + context_instance=RequestContext(request)) + + if doc.type_id == "charter": + filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev) + + content = get_document_content(filename, os.path.join(settings.CHARTER_PATH, filename), split=False, markup=True) + + ballot_summary = None + if doc.get_state_slug() in ("intrev", "iesgrev"): + active_ballot = doc.active_ballot() + if active_ballot: + ballot_summary = needed_ballot_positions(doc, active_ballot.active_ad_positions().values()) + else: + ballot_summary = "No active ballot found." + + chartering = get_chartering_type(doc) + + # inject milestones from group + milestones = None + if chartering and not snapshot: + milestones = doc.group.groupmilestone_set.filter(state="charter") + + return render_to_response("doc/document_charter.html", + dict(doc=doc, + top=top, + chartering=chartering, + content=content, + txt_url=settings.CHARTER_TXT_URL + filename, + revisions=revisions, + snapshot=snapshot, + telechat=telechat, + ballot_summary=ballot_summary, + group=group, + milestones=milestones, + ), + context_instance=RequestContext(request)) + + if doc.type_id == "conflrev": + filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev) + pathname = os.path.join(settings.CONFLICT_REVIEW_PATH,filename) + + if doc.rev == "00" and not os.path.isfile(pathname): + # This could move to a template + content = "A conflict review response has not yet been proposed." + else: + content = get_document_content(filename, pathname, split=False, markup=True) + + ballot_summary = None + if doc.get_state_slug() in ("iesgeval"): + ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values()) + + return render_to_response("doc/document_conflict_review.html", + dict(doc=doc, + top=top, + content=content, + revisions=revisions, + snapshot=snapshot, + telechat=telechat, + conflictdoc=conflictdoc, + ballot_summary=ballot_summary, + approved_states=('appr-reqnopub-pend','appr-reqnopub-sent','appr-noprob-pend','appr-noprob-sent') + ), + context_instance=RequestContext(request)) + + raise Http404 + + +def document_history(request, name): + doc = get_object_or_404(Document, docalias__name=name) + top = render_document_top(request, doc, "history", name) + + # pick up revisions from events + diff_revisions = [] + + diffable = name.startswith("draft") or name.startswith("charter") or name.startswith("conflict-review") + if diffable: + diff_documents = [ doc ] + diff_documents.extend(Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces")) + + seen = set() + for e in NewRevisionDocEvent.objects.filter(type="new_revision", doc__in=diff_documents).select_related('doc').order_by("-time", "-id"): + if (e.doc.name, e.rev) in seen: + continue + + seen.add((e.doc.name, e.rev)) + + url = "" + if name.startswith("charter"): + url = request.build_absolute_uri(urlreverse("charter_with_milestones_txt", kwargs=dict(name=e.doc.name, rev=e.rev))) + elif name.startswith("conflict-review"): + h = find_history_active_at(e.doc, e.time) + url = settings.CONFLICT_REVIEW_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev)) + elif name.startswith("draft"): + # rfcdiff tool has special support for IDs + url = e.doc.name + "-" + e.rev + + diff_revisions.append((e.doc.name, e.rev, e.time, url)) + + # grab event history + events = doc.docevent_set.all().order_by("-time", "-id").select_related("by") + + augment_events_with_revision(doc, events) + add_links_in_new_revision_events(doc, events, diff_revisions) + + return render_to_response("doc/document_history.html", + dict(doc=doc, + top=top, + diff_revisions=diff_revisions, + events=events, + ), + context_instance=RequestContext(request)) + +def document_writeup(request, name): + doc = get_object_or_404(Document, docalias__name=name) + top = render_document_top(request, doc, "writeup", name) + + def text_from_writeup(event_type): + e = doc.latest_event(WriteupDocEvent, type=event_type) + if e: + return e.text + else: + return "" + + sections = [] + if doc.type_id == "draft": + writeups = [] + sections.append(("Approval Announcement", + "Draft of message to be sent after approval:", + writeups)) + + writeups.append(("Announcement", + text_from_writeup("changed_ballot_approval_text"), + urlreverse("doc_ballot_approvaltext", kwargs=dict(name=doc.name)))) + + writeups.append(("Ballot Text", + text_from_writeup("changed_ballot_writeup_text"), + urlreverse("doc_ballot_writeupnotes", kwargs=dict(name=doc.name)))) + + elif doc.type_id == "charter": + sections.append(("WG Review Announcement", + "", + [("WG Review Announcement", + text_from_writeup("changed_review_announcement"), + urlreverse("ietf.wgcharter.views.announcement_text", kwargs=dict(name=doc.name, ann="review")))] + )) + + sections.append(("WG Action Announcement", + "", + [("WG Action Announcement", + text_from_writeup("changed_action_announcement"), + urlreverse("ietf.wgcharter.views.announcement_text", kwargs=dict(name=doc.name, ann="action")))] + )) + + if doc.latest_event(BallotDocEvent, type="created_ballot"): + sections.append(("Ballot Announcement", + "", + [("Ballot Announcement", + text_from_writeup("changed_ballot_writeup_text"), + urlreverse("ietf.wgcharter.views.ballot_writeupnotes", kwargs=dict(name=doc.name)))] + )) + + if not sections: + raise Http404() + + return render_to_response("doc/document_writeup.html", + dict(doc=doc, + top=top, + sections=sections, + can_edit=has_role(request.user, ("Area Director", "Secretariat")), + ), + context_instance=RequestContext(request)) + +def document_shepherd_writeup(request, name): + doc = get_object_or_404(Document, docalias__name=name) + lastwriteup = doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup") + if lastwriteup: + writeup_text = lastwriteup.text + else: + writeup_text = "(There is no shepherd's writeup available for this document)" + + can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) + can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd) or has_role(request.user, ["Area Director"]) + + return render_to_response("doc/shepherd_writeup.html", + dict(doc=doc, + writeup=writeup_text, + can_edit=can_edit_shepherd_writeup + ), + context_instance=RequestContext(request)) + +def document_ballot_content(request, doc, ballot_id, editable=True): + """Render HTML string with content of ballot page.""" + all_ballots = list(BallotDocEvent.objects.filter(doc=doc, type="created_ballot").order_by("time")) + augment_events_with_revision(doc, all_ballots) + + ballot = None + if ballot_id != None: + ballot_id = int(ballot_id) + for b in all_ballots: + if b.id == ballot_id: + ballot = b + break + elif all_ballots: + ballot = all_ballots[-1] + + if not ballot: + raise Http404 + + deferred = doc.active_defer_event() + + positions = ballot.all_positions() + + # put into position groups + position_groups = [] + for n in BallotPositionName.objects.filter(slug__in=[p.pos_id for p in positions]).order_by('order'): + g = (n, [p for p in positions if p.pos_id == n.slug]) + g[1].sort(key=lambda p: (p.old_ad, p.ad.plain_name())) + if n.blocking: + position_groups.insert(0, g) + else: + position_groups.append(g) + + summary = needed_ballot_positions(doc, [p for p in positions if not p.old_ad]) + + text_positions = [p for p in positions if p.discuss or p.comment] + text_positions.sort(key=lambda p: (p.old_ad, p.ad.plain_name())) + + ballot_open = not BallotDocEvent.objects.filter(doc=doc, + type__in=("closed_ballot", "created_ballot"), + time__gt=ballot.time, + ballot_type=ballot.ballot_type) + if not ballot_open: + editable = False + + return render_to_string("doc/document_ballot_content.html", + dict(doc=doc, + ballot=ballot, + position_groups=position_groups, + text_positions=text_positions, + editable=editable, + ballot_open=ballot_open, + deferred=deferred, + summary=summary, + all_ballots=all_ballots, + ), + context_instance=RequestContext(request)) + +def document_ballot(request, name, ballot_id=None): + doc = get_object_or_404(Document, docalias__name=name) + top = render_document_top(request, doc, "ballot", name) + + c = document_ballot_content(request, doc, ballot_id, editable=True) + + return render_to_response("doc/document_ballot.html", + dict(doc=doc, + top=top, + ballot_content=c, + ), + context_instance=RequestContext(request)) + +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 ballot_for_popup(request, name): + doc = get_object_or_404(Document, docalias__name=name) + return HttpResponse(document_ballot_content(request, doc, ballot_id=None, editable=False)) + + +def ballot_json(request, name): + # REDESIGN: this view needs to be deleted or updated + def get_ballot(name): + from ietf.doc.models import DocAlias + alias = get_object_or_404(DocAlias, name=name) + d = alias.document + from ietf.idtracker.models import InternetDraft, BallotInfo + from ietf.idrfc.idrfc_wrapper import BallotWrapper, IdWrapper, RfcWrapper + id = None + bw = None + dw = None + if (d.type_id=='draft'): + id = get_object_or_404(InternetDraft, name=d.name) + try: + if not id.ballot.ballot_issued: + raise Http404 + except BallotInfo.DoesNotExist: + raise Http404 + + bw = BallotWrapper(id) # XXX Fixme: Eliminate this as we go forward + # Python caches ~100 regex'es -- explicitly compiling it inside a method + # (where you then throw away the compiled version!) doesn't make sense at + # all. + if re.search("^rfc([1-9][0-9]*)$", name): + id.viewing_as_rfc = True + dw = RfcWrapper(id) + else: + dw = IdWrapper(id) + # XXX Fixme: Eliminate 'dw' as we go forward + + try: + b = d.latest_event(BallotDocEvent, type="created_ballot") + except BallotDocEvent.DoesNotExist: + raise Http404 + + return (bw, dw, b, d) + + ballot, doc, b, d = get_ballot(name) + response = HttpResponse(mimetype='text/plain') + response.write(json.dumps(ballot.dict(), indent=2)) + return response + diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py index 854c53cd8..feb57d199 100644 --- a/ietf/idrfc/idrfc_wrapper.py +++ b/ietf/idrfc/idrfc_wrapper.py @@ -395,7 +395,7 @@ class RfcWrapper: @models.permalink def get_absolute_url(self): - return ('ietf.idrfc.views_doc.document_main', ['rfc%s' % (str(self.rfc_number))]) + return ('ietf.doc.views_doc.document_main', ['rfc%s' % (str(self.rfc_number))]) def displayname_with_link(self): return 'RFC %d' % (self.get_absolute_url(), self.rfc_number) diff --git a/ietf/idrfc/markup_txt.py b/ietf/idrfc/markup_txt.py index b4bb5d6f0..3094ba98b 100644 --- a/ietf/idrfc/markup_txt.py +++ b/ietf/idrfc/markup_txt.py @@ -66,7 +66,8 @@ def markup(content, split=True): if split: n = content.find("\n", 5000) content1 = "
"+content[:n+1]+"
\n" - content2 = "
"+content[n+1:]+"
\n" - return (content1, content2) + return content1 + #content2 = "
"+content[n+1:]+"
\n" + #return (content1, content2) else: return "
" + content + "
\n" diff --git a/ietf/idrfc/tests.py b/ietf/idrfc/tests.py index a2d5d94f0..9903aa59c 100644 --- a/ietf/idrfc/tests.py +++ b/ietf/idrfc/tests.py @@ -33,337 +33,1424 @@ import unittest import StringIO import os, shutil -from datetime import date, timedelta +from datetime import date, timedelta, time import django.test from django.core.urlresolvers import reverse as urlreverse from django.conf import settings from pyquery import PyQuery +import debug -from ietf.idrfc.models import * -from ietf.idtracker.models import * +from ietf.doc.models import * +from ietf.name.models import * +from ietf.group.models import * +from ietf.person.models import * +from ietf.meeting.models import Meeting, MeetingTypeName +from ietf.iesg.models import TelechatDate from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest, login_testing_unauthorized +from ietf.utils.test_data import make_test_data from ietf.utils.mail import outbox -from ietf.ietfworkflows.models import Stream -from django.contrib.auth.models import User +class IdRfcUrlTestCase(SimpleUrlTestCase): + def testUrls(self): + #self.doTestUrls(__file__) + self.doTestUrls(os.path.join(os.path.dirname(os.path.abspath(__file__)), "testurlREDESIGN.list")) -# REMOVED ALL THE CLASS DEFINITIONS FROM THIS FILE -# AS A FIRST STEP TOWARDS TRANSITION CLEANUP. ALL -# OF WHAT WAS HERE IS REDEFINED BY THE INCLUDE -# BELOW + +class ChangeStateTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_change_state(self): + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="ad-eval")) + + url = urlreverse('doc_change_state', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + first_state = draft.get_state("draft-iesg") + next_states = first_state.next_states + + # 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) -TEST_RFC_INDEX = ''' - - - BCP0110 - - RFC4170 - - - - BCP0111 - - RFC4181 - RFC4841 - - - - FYI0038 - - RFC3098 - - - - RFC1938 - A One-Time Password System - - N. Haller - - - C. Metz - - - May - 1996 - - - ASCII - 44844 - 18 - - - OTP - authentication - S/KEY - -

This document describes a one-time password authentication system (OTP). [STANDARDS-TRACK]

- - RFC2289 - - PROPOSED STANDARD - PROPOSED STANDARD - Legacy -
- - RFC2289 - A One-Time Password System - - N. Haller - - - C. Metz - - - P. Nesser - - - M. Straw - - - February - 1998 - - - ASCII - 56495 - 25 - - - ONE-PASS - authentication - OTP - replay - attach - -

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]

- - RFC1938 - - - STD0061 - - STANDARD - DRAFT STANDARD - Legacy -
- - RFC3098 - How to Advertise Responsibly Using E-Mail and Newsgroups or - how NOT to $$$$$ MAKE ENEMIES FAST! $$$$$ - - T. Gavin - - - D. Eastlake 3rd - - - S. Hambridge - - - April - 2001 - - - ASCII - 64687 - 28 - - - internet - marketing - users - service - providers - isps - -

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.

- draft-ietf-run-adverts-02 - - FYI0038 - - INFORMATIONAL - INFORMATIONAL - Legacy -
- - RFC4170 - Tunneling Multiplexed Compressed RTP (TCRTP) - - B. Thompson - - - T. Koren - - - D. Wing - - - November - 2005 - - - ASCII - 48990 - 24 - - - real-time transport protocol - -

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.

- draft-ietf-avt-tcrtp-08 - - BCP0110 - - BEST CURRENT PRACTICE - BEST CURRENT PRACTICE - IETF - rai - avt -
- - RFC4181 - Guidelines for Authors and Reviewers of MIB Documents - - C. Heard - Editor - - - September - 2005 - - - ASCII - 102521 - 42 - - - standards-track specifications - management information base - review - -

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.

- draft-ietf-ops-mib-review-guidelines-04 - - RFC4841 - - - BCP0111 - - BEST CURRENT PRACTICE - BEST CURRENT PRACTICE - IETF - rtg - ospf - http://www.rfc-editor.org/errata_search.php?rfc=4181 -
- - RFC4841 - RFC 4181 Update to Recognize the IETF Trust - - C. Heard - Editor - - - March - 2007 - - - ASCII - 4414 - 3 - - - management information base - standards-track specifications - mib review - -

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.

- draft-heard-rfc4181-update-00 - - RFC4181 - - - BCP0111 - - BEST CURRENT PRACTICE - BEST CURRENT PRACTICE - IETF - NON WORKING GROUP -
- - STD0061 - A One-Time Password System - - RFC2289 - - -
-''' + if next_states: + self.assertTrue(len(q('.next-states form input[type=hidden]')) > 0) -TEST_QUEUE = ''' -
- -draft-ietf-sipping-app-interaction-framework-05.txt -2005-10-17 -EDIT - -draft-ietf-sip-gruu -IN-QUEUE - -J. Rosenberg - -A Framework for Application Interaction in the Session Initiation Protocol (SIP) - -94672 -Session Initiation Proposal Investigation - -
-
- -draft-ietf-sip-gruu-15.txt -2007-10-15 -MISSREF - -draft-ietf-sip-outbound -NOT-RECEIVED - -J. Rosenberg - -Obtaining and Using Globally Routable User Agent (UA) URIs (GRUU) in the Session Initiation Protocol (SIP) - -95501 -Session Initiation Protocol - -
-
-
-
- -draft-thomson-beep-async-02.txt -2009-05-12 -EDIT -IANA -M. Thomson - -Asynchronous Channels for the Blocks Extensible Exchange Protocol (BEEP) - -17237 -IETF - NON WORKING GROUP - -
-
-
-
-
-
-
-
-''' + + # faulty post + r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft", slug="active").pk)) + 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-iesg"), first_state) + + + # change state + events_before = draft.docevent_set.count() + mailbox_before = len(outbox) + draft.tags.add("ad-f-up") + + r = self.client.post(url, + dict(state=State.objects.get(used=True, type="draft-iesg", slug="review-e").pk, + substate="point", + 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.assertTrue(not draft.tags.filter(slug="ad-f-up")) + self.assertTrue(draft.tags.filter(slug="point")) + self.assertEquals(draft.docevent_set.count(), events_before + 2) + self.assertTrue("Test comment" in draft.docevent_set.all()[0].desc) + self.assertTrue("State changed" in draft.docevent_set.all()[1].desc) + self.assertEquals(len(outbox), mailbox_before + 2) + self.assertTrue("State Update Notice" in outbox[-2]['Subject']) + self.assertTrue(draft.name in outbox[-1]['Subject']) + + + # check that we got a previous state now + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + 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(used=True, 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(used=True, 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(used=True, type="draft-iana-review", slug="need-rev") + next_state = State.objects.get(used=True, 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(used=True, type="draft-iesg", slug="ad-eval")) + + self.client.login(remote_user="secretary") + url = urlreverse('doc_change_state', kwargs=dict(name=draft.name)) + + mailbox_before = len(outbox) + + self.assertTrue(not draft.latest_event(type="changed_ballot_writeup_text")) + r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft-iesg", slug="lc-req").pk)) + self.assertContains(r, "Your request to issue the Last Call") + + # last call text + e = draft.latest_event(WriteupDocEvent, type="changed_last_call_text") + self.assertTrue(e) + self.assertTrue("The IESG has received" in e.text) + self.assertTrue(draft.title in e.text) + self.assertTrue(draft.get_absolute_url() in e.text) + + # approval text + e = draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") + self.assertTrue(e) + self.assertTrue("The IESG has approved" in e.text) + self.assertTrue(draft.title in e.text) + self.assertTrue(draft.get_absolute_url() in e.text) + + # ballot writeup + e = draft.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") + self.assertTrue(e) + self.assertTrue("Technical Summary" in e.text) + + # mail notice + self.assertTrue(len(outbox) > mailbox_before) + self.assertTrue("Last Call:" in outbox[-1]['Subject']) + + # comment + self.assertTrue("Last call was requested" in draft.latest_event().desc) + + +class EditInfoTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_edit_info(self): + draft = make_test_data() + url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form select[name=intended_std_level]')), 1) + + prev_ad = draft.ad + # faulty post + r = self.client.post(url, dict(ad="123456789")) + 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.ad, prev_ad) + + # edit info + events_before = draft.docevent_set.count() + mailbox_before = len(outbox) + + new_ad = Person.objects.get(name="Ad No1") + + r = self.client.post(url, + dict(intended_std_level=str(draft.intended_std_level.pk), + stream=draft.stream_id, + ad=str(new_ad.pk), + notify="test@example.com", + note="New note", + telechat_date="", + )) + self.assertEquals(r.status_code, 302) + + 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="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']) + + def test_edit_telechat_date(self): + draft = make_test_data() + + url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + data = dict(intended_std_level=str(draft.intended_std_level_id), + stream=draft.stream_id, + ad=str(draft.ad_id), + notify="test@example.com", + note="", + ) + + # add to 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, 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() + r = self.client.post(url, data) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEqual(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[1].date) + + # remove from agenda + data["telechat_date"] = "" + r = self.client.post(url, data) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date) + + def test_start_iesg_process_on_draft(self): + make_test_data() + + draft = Document.objects.create( + name="draft-ietf-mars-test2", + time=datetime.datetime.now(), + type_id="draft", + title="Testing adding a draft", + stream=None, + group=Group.objects.get(acronym="mars"), + abstract="Test test test.", + rev="01", + pages=2, + intended_std_level_id="ps", + shepherd=None, + ad=None, + expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), + ) + doc_alias = DocAlias.objects.create( + document=draft, + name=draft.name, + ) + + DocumentAuthor.objects.create( + document=draft, + author=Email.objects.get(address="aread@ietf.org"), + order=1 + ) + + url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form select[name=intended_std_level]')), 1) + self.assertTrue('@' in q('form input[name=notify]')[0].get('value')) + + # add + events_before = draft.docevent_set.count() + mailbox_before = len(outbox) + + ad = Person.objects.get(name="Aread Irector") + + r = self.client.post(url, + dict(intended_std_level=str(draft.intended_std_level_id), + ad=ad.pk, + create_in_state=State.objects.get(used=True, type="draft-iesg", slug="watching").pk, + notify="test@example.com", + note="This is a note", + telechat_date="", + )) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug("draft-iesg"), "watching") + self.assertEquals(draft.ad, ad) + self.assertEquals(draft.note, "This is a note") + self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat")) + self.assertEquals(draft.docevent_set.count(), events_before + 3) + events = list(draft.docevent_set.order_by('time', 'id')) + 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) -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from testsREDESIGN import * +class ResurrectTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_request_resurrect(self): + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) + + url = urlreverse('doc_request_resurrect', kwargs=dict(name=draft.name)) + + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form input[type=submit]')), 1) + + + # request resurrect + events_before = draft.docevent_set.count() + mailbox_before = len(outbox) + + r = self.client.post(url, dict()) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.docevent_set.count(), events_before + 1) + e = draft.latest_event(type="requested_resurrect") + self.assertTrue(e) + self.assertEquals(e.by, Person.objects.get(name="Aread Irector")) + self.assertTrue("Resurrection" in e.desc) + self.assertEquals(len(outbox), mailbox_before + 1) + self.assertTrue("Resurrection" in outbox[-1]['Subject']) + + def test_resurrect(self): + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) + + DocEvent.objects.create(doc=draft, + type="requested_resurrect", + by=Person.objects.get(name="Aread Irector")) + + url = urlreverse('doc_resurrect', kwargs=dict(name=draft.name)) + + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form input[type=submit]')), 1) + + # complete resurrect + events_before = draft.docevent_set.count() + mailbox_before = len(outbox) + + r = self.client.post(url, dict()) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.docevent_set.count(), events_before + 1) + self.assertEquals(draft.latest_event().type, "completed_resurrect") + self.assertEquals(draft.get_state_slug(), "active") + self.assertTrue(draft.expires >= datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1)) + self.assertEquals(len(outbox), mailbox_before + 1) + +class AddCommentTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_add_comment(self): + draft = make_test_data() + url = urlreverse('doc_add_comment', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form textarea[name=comment]')), 1) + + # request resurrect + events_before = draft.docevent_set.count() + mailbox_before = len(outbox) + + r = self.client.post(url, dict(comment="This is a test.")) + self.assertEquals(r.status_code, 302) + + self.assertEquals(draft.docevent_set.count(), events_before + 1) + self.assertEquals("This is a test.", draft.latest_event().desc) + self.assertEquals("added_comment", draft.latest_event().type) + self.assertEquals(len(outbox), mailbox_before + 1) + 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'] + + def test_edit_position(self): + draft = make_test_data() + url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name, + ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk)) + login_testing_unauthorized(self, "ad", url) + + ad = Person.objects.get(name="Aread Irector") + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form input[name=position]')) > 0) + self.assertEquals(len(q('form textarea[name=comment]')), 1) + + # vote + events_before = draft.docevent_set.count() + + r = self.client.post(url, dict(position="discuss", + discuss=" This is a discussion test. \n ", + comment=" This is a test. \n ")) + self.assertEquals(r.status_code, 302) + + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) + self.assertEquals(pos.pos.slug, "discuss") + self.assertTrue(" This is a discussion test." in pos.discuss) + self.assertTrue(pos.discuss_time != None) + self.assertTrue(" This is a test." in pos.comment) + self.assertTrue(pos.comment_time != None) + self.assertTrue("New position" in pos.desc) + self.assertEquals(draft.docevent_set.count(), events_before + 3) + + # recast vote + events_before = draft.docevent_set.count() + r = self.client.post(url, dict(position="noobj")) + self.assertEquals(r.status_code, 302) + + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) + self.assertEquals(pos.pos.slug, "noobj") + self.assertEquals(draft.docevent_set.count(), events_before + 1) + self.assertTrue("Position for" in pos.desc) + + # clear vote + events_before = draft.docevent_set.count() + r = self.client.post(url, dict(position="norecord")) + self.assertEquals(r.status_code, 302) + + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) + self.assertEquals(pos.pos.slug, "norecord") + self.assertEquals(draft.docevent_set.count(), events_before + 1) + self.assertTrue("Position for" in pos.desc) + + # change comment + events_before = draft.docevent_set.count() + r = self.client.post(url, dict(position="norecord", comment="New comment.")) + self.assertEquals(r.status_code, 302) + + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) + self.assertEquals(pos.pos.slug, "norecord") + self.assertEquals(draft.docevent_set.count(), events_before + 2) + self.assertTrue("Ballot comment text updated" in pos.desc) + + def test_edit_position_as_secretary(self): + draft = make_test_data() + url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name, + ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk)) + ad = Person.objects.get(name="Aread Irector") + url += "?ad=%s" % ad.pk + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form input[name=position]')) > 0) + + # vote on behalf of AD + events_before = draft.docevent_set.count() + r = self.client.post(url, dict(position="discuss", discuss="Test discuss text")) + self.assertEquals(r.status_code, 302) + + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) + self.assertEquals(pos.pos.slug, "discuss") + self.assertEquals(pos.discuss, "Test discuss text") + self.assertTrue("New position" in pos.desc) + self.assertTrue("by Sec" in pos.desc) + + def test_cannot_edit_position_as_pre_ad(self): + draft = make_test_data() + url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name, + ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk)) + + # transform to pre-ad + ad_role = Role.objects.filter(name="ad")[0] + ad_role.name_id = "pre-ad" + ad_role.save() + + # we can see + login_testing_unauthorized(self, ad_role.person.user.username, url) + + # but not touch + r = self.client.post(url, dict(position="discuss", discuss="Test discuss text")) + self.assertEquals(r.status_code, 403) + + def test_send_ballot_comment(self): + draft = make_test_data() + draft.notify = "somebody@example.com" + draft.save() + + ad = Person.objects.get(name="Aread Irector") + + ballot = draft.latest_event(BallotDocEvent, type="created_ballot") + + BallotPositionDocEvent.objects.create( + doc=draft, type="changed_ballot_position", + by=ad, ad=ad, ballot=ballot, pos=BallotPositionName.objects.get(slug="discuss"), + discuss="This draft seems to be lacking a clearer title?", + discuss_time=datetime.datetime.now(), + comment="Test!", + comment_time=datetime.datetime.now()) + + url = urlreverse('doc_send_ballot_comment', kwargs=dict(name=draft.name, + ballot_id=ballot.pk)) + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('form input[name="cc"]')) > 0) + + # send + mailbox_before = len(outbox) + + r = self.client.post(url, dict(cc="test@example.com", cc_state_change="1")) + self.assertEquals(r.status_code, 302) + + self.assertEquals(len(outbox), mailbox_before + 1) + m = outbox[-1] + self.assertTrue("COMMENT" in m['Subject']) + self.assertTrue("DISCUSS" in m['Subject']) + self.assertTrue(draft.name in m['Subject']) + self.assertTrue("clearer title" in str(m)) + self.assertTrue("Test!" in str(m)) + + +class DeferBallotTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_defer_ballot(self): + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) + + url = urlreverse('doc_defer_ballot', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + + # defer + mailbox_before = len(outbox) + + r = self.client.post(url, dict()) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug("draft-iesg"), "defer") + + self.assertEquals(len(outbox), mailbox_before + 2) + self.assertTrue("State Update" in outbox[-2]['Subject']) + self.assertTrue("Deferred" in outbox[-1]['Subject']) + self.assertTrue(draft.file_tag() in outbox[-1]['Subject']) + + def test_undefer_ballot(self): + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="defer")) + + url = urlreverse('doc_undefer_ballot', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + + # undefer + r = self.client.post(url, dict()) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug("draft-iesg"), "iesg-eva") + +class BallotWriteupsTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_edit_last_call_text(self): + draft = make_test_data() + url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('textarea[name=last_call_text]')), 1) + self.assertEquals(len(q('input[type=submit][value*="Save Last Call"]')), 1) + # we're secretariat, so we got The Link + self.assertEquals(len(q('a:contains("Make Last Call")')), 1) + + # subject error + r = self.client.post(url, dict( + last_call_text="Subject: test\r\nhello\r\n\r\n", + save_last_call_text="1")) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(len(q('ul.errorlist')) > 0) + + # save + r = self.client.post(url, dict( + last_call_text="This is a simple test.", + save_last_call_text="1")) + self.assertEquals(r.status_code, 200) + draft = Document.objects.get(name=draft.name) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text) + + # test regenerate + r = self.client.post(url, dict( + last_call_text="This is a simple test.", + regenerate_last_call_text="1")) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + draft = Document.objects.get(name=draft.name) + self.assertTrue("Subject: Last Call" in draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text) + + + def test_request_last_call(self): + draft = make_test_data() + url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # give us an announcement to send + r = self.client.post(url, dict(regenerate_last_call_text="1")) + self.assertEquals(r.status_code, 200) + + mailbox_before = len(outbox) + + # send + r = self.client.post(url, dict( + last_call_text=draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text, + send_last_call_request="1")) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug("draft-iesg"), "lc-req") + self.assertEquals(len(outbox), mailbox_before + 3) + self.assertTrue("Last Call" in outbox[-1]['Subject']) + self.assertTrue(draft.name in outbox[-1]['Subject']) + + def test_edit_ballot_writeup(self): + draft = make_test_data() + 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(used=True, 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( + ballot_writeup="This is a simple test.", + save_ballot_writeup="1")) + self.assertEquals(r.status_code, 200) + draft = Document.objects.get(name=draft.name) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text").text) + + def test_issue_ballot(self): + draft = make_test_data() + url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "ad", url) + + ballot = draft.latest_event(BallotDocEvent, type="created_ballot") + + def create_pos(num, vote, comment="", discuss=""): + ad = Person.objects.get(name="Ad No%s" % num) + e = BallotPositionDocEvent() + e.doc = draft + e.ballot = ballot + e.by = ad + e.ad = ad + e.pos = BallotPositionName.objects.get(slug=vote) + e.type = "changed_ballot_position" + e.comment = comment + if e.comment: + e.comment_time = datetime.datetime.now() + e.discuss = discuss + if e.discuss: + e.discuss_time = datetime.datetime.now() + e.save() + + # active + create_pos(1, "yes", discuss="discuss1 " * 20) + create_pos(2, "noobj", comment="comment2 " * 20) + create_pos(3, "discuss", discuss="discuss3 " * 20, comment="comment3 " * 20) + create_pos(4, "abstain") + create_pos(5, "recuse") + + # inactive + create_pos(9, "yes") + + mailbox_before = len(outbox) + + r = self.client.post(url, dict( + ballot_writeup="This is a test.", + issue_ballot="1")) + self.assertEquals(r.status_code, 200) + draft = Document.objects.get(name=draft.name) + + self.assertTrue(draft.latest_event(type="sent_ballot_announcement")) + self.assertEquals(len(outbox), mailbox_before + 2) + issue_email = outbox[-2] + self.assertTrue("Evaluation:" in issue_email['Subject']) + self.assertTrue("comment1" not in str(issue_email)) + self.assertTrue("comment2" in str(issue_email)) + self.assertTrue("comment3" in str(issue_email)) + self.assertTrue("discuss3" in str(issue_email)) + self.assertTrue("This is a test" in str(issue_email)) + self.assertTrue("The IESG has approved" in str(issue_email)) + + def test_edit_approval_text(self): + draft = make_test_data() + url = urlreverse('doc_ballot_approvaltext', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('textarea[name=approval_text]')), 1) + self.assertEquals(len(q('input[type=submit][value*="Save Approval"]')), 1) + + # save + r = self.client.post(url, dict( + approval_text="This is a simple test.", + save_approval_text="1")) + self.assertEquals(r.status_code, 200) + draft = Document.objects.get(name=draft.name) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) + + # test regenerate + r = self.client.post(url, dict(regenerate_approval_text="1")) + self.assertEquals(r.status_code, 200) + draft = Document.objects.get(name=draft.name) + self.assertTrue("Subject: Protocol Action" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) + + # test regenerate when it's a disapprove + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="nopubadw")) + + r = self.client.post(url, dict(regenerate_approval_text="1")) + self.assertEquals(r.status_code, 200) + draft = Document.objects.get(name=draft.name) + self.assertTrue("NOT be published" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) + + # test regenerate when it's a conflict review + draft.group = Group.objects.get(type="individ") + draft.stream_id = "irtf" + draft.save() + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) + + r = self.client.post(url, dict(regenerate_approval_text="1")) + self.assertEquals(r.status_code, 200) + draft = Document.objects.get(name=draft.name) + self.assertTrue("Subject: Results of IETF-conflict review" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) + +class ApproveBallotTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_approve_ballot(self): + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) # make sure it's approvable + + url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue("send out the announcement" in q('.actions input[type=submit]')[0].get('value').lower()) + self.assertEquals(len(q('.announcement pre:contains("Subject: Protocol Action")')), 1) + + # approve + mailbox_before = len(outbox) + + r = self.client.post(url, dict(skiprfceditorpost="1")) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug("draft-iesg"), "ann") + self.assertEquals(len(outbox), mailbox_before + 4) + self.assertTrue("Protocol Action" in outbox[-2]['Subject']) + # the IANA copy + self.assertTrue("Protocol Action" in outbox[-1]['Subject']) + self.assertTrue(not outbox[-1]['CC']) + self.assertTrue("Protocol Action" in draft.message_set.order_by("-time")[0].subject) + + def test_disapprove_ballot(self): + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="nopubadw")) + + url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # disapprove (the Martians aren't going to be happy) + mailbox_before = len(outbox) + + r = self.client.post(url, dict()) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug("draft-iesg"), "dead") + self.assertEquals(len(outbox), mailbox_before + 3) + self.assertTrue("NOT be published" in str(outbox[-1])) + +class MakeLastCallTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_make_last_call(self): + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="lc-req")) + + url = urlreverse('doc_make_last_call', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('input[name=last_call_sent_date]')), 1) + + # make last call + mailbox_before = len(outbox) + + expire_date = q('input[name=last_call_expiration_date]')[0].get("value") + + r = self.client.post(url, + dict(last_call_sent_date=q('input[name=last_call_sent_date]')[0].get("value"), + last_call_expiration_date=expire_date + )) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug("draft-iesg"), "lc") + self.assertEquals(draft.latest_event(LastCallDocEvent, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date) + self.assertEquals(len(outbox), mailbox_before + 4) + + self.assertTrue("Last Call" in outbox[-4]['Subject']) + # the IANA copy + 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(used=True, 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, skiprfceditorpost="1")) + 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']) + self.assertTrue(not outbox[-1]['CC']) + +class ExpireIDsTestCase(django.test.TestCase): + fixtures = ['names'] + + def setUp(self): + self.id_dir = os.path.abspath("tmp-id-dir") + self.archive_dir = os.path.abspath("tmp-id-archive") + os.mkdir(self.id_dir) + os.mkdir(self.archive_dir) + os.mkdir(os.path.join(self.archive_dir, "unknown_ids")) + os.mkdir(os.path.join(self.archive_dir, "deleted_tombstones")) + os.mkdir(os.path.join(self.archive_dir, "expired_without_tombstone")) + + settings.INTERNET_DRAFT_PATH = self.id_dir + settings.INTERNET_DRAFT_ARCHIVE_DIR = self.archive_dir + + def tearDown(self): + shutil.rmtree(self.id_dir) + shutil.rmtree(self.archive_dir) + + def write_id_file(self, name, size): + f = open(os.path.join(self.id_dir, name), 'w') + f.write("a" * size) + f.close() + + def test_in_id_expire_freeze(self): + from ietf.idrfc.expire import in_id_expire_freeze + + Meeting.objects.create(number="123", + type=MeetingTypeName.objects.get(slug="ietf"), + date=date.today()) + second_cut_off = Meeting.get_second_cut_off() + ietf_monday = Meeting.get_ietf_monday() + + self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(second_cut_off - datetime.timedelta(days=7), time(0, 0, 0)))) + self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(second_cut_off, time(0, 0, 0)))) + self.assertTrue(in_id_expire_freeze(datetime.datetime.combine(second_cut_off + datetime.timedelta(days=7), time(0, 0, 0)))) + self.assertTrue(in_id_expire_freeze(datetime.datetime.combine(ietf_monday - datetime.timedelta(days=1), time(0, 0, 0)))) + self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(ietf_monday, time(0, 0, 0)))) + + def test_warn_expirable_ids(self): + from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id + + draft = make_test_data() + + self.assertEquals(len(list(get_soon_to_expire_ids(14))), 0) + + # hack into expirable state + draft.unset_state("draft-iesg") + draft.expires = datetime.datetime.now() + datetime.timedelta(days=10) + draft.save() + + self.assertEquals(len(list(get_soon_to_expire_ids(14))), 1) + + # test send warning + mailbox_before = len(outbox) + + send_expire_warning_for_id(draft) + + self.assertEquals(len(outbox), mailbox_before + 1) + self.assertTrue("aread@ietf.org" in str(outbox[-1])) # author + self.assertTrue("wgchairman@ietf.org" in str(outbox[-1])) + + def test_expire_ids(self): + from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id + + draft = make_test_data() + + self.assertEquals(len(list(get_expired_ids())), 0) + + # hack into expirable state + draft.unset_state("draft-iesg") + draft.expires = datetime.datetime.now() + draft.save() + + self.assertEquals(len(list(get_expired_ids())), 1) + + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="watching")) + + self.assertEquals(len(list(get_expired_ids())), 1) + + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) + + self.assertEquals(len(list(get_expired_ids())), 0) + + # test notice + mailbox_before = len(outbox) + + send_expire_notice_for_id(draft) + + self.assertEquals(len(outbox), mailbox_before + 1) + self.assertTrue("expired" in outbox[-1]["Subject"]) + + # test expiry + txt = "%s-%s.txt" % (draft.name, draft.rev) + self.write_id_file(txt, 5000) + + expire_id(draft) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug(), "expired") + self.assertEquals(draft.get_state_slug("draft-iesg"), "dead") + self.assertTrue(draft.latest_event(type="expired_document")) + self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) + self.assertTrue(os.path.exists(os.path.join(self.archive_dir, txt))) + + def test_clean_up_id_files(self): + draft = make_test_data() + + from ietf.idrfc.expire import clean_up_id_files + + # put unknown file + unknown = "draft-i-am-unknown-01.txt" + self.write_id_file(unknown, 5000) + + clean_up_id_files() + + self.assertTrue(not os.path.exists(os.path.join(self.id_dir, unknown))) + self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", unknown))) + + + # put file with malformed name (no revision) + malformed = draft.name + ".txt" + self.write_id_file(malformed, 5000) + + clean_up_id_files() + + self.assertTrue(not os.path.exists(os.path.join(self.id_dir, malformed))) + self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", malformed))) + + + # RFC draft + draft.set_state(State.objects.get(used=True, type="draft", slug="rfc")) + draft.save() + + txt = "%s-%s.txt" % (draft.name, draft.rev) + self.write_id_file(txt, 5000) + pdf = "%s-%s.pdf" % (draft.name, draft.rev) + self.write_id_file(pdf, 5000) + + clean_up_id_files() + + # txt files shouldn't be moved (for some reason) + self.assertTrue(os.path.exists(os.path.join(self.id_dir, txt))) + + self.assertTrue(not os.path.exists(os.path.join(self.id_dir, pdf))) + self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", pdf))) + + + # expire draft + draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) + draft.expires = datetime.datetime.now() - datetime.timedelta(days=1) + draft.save() + + e = DocEvent() + e.doc = draft + e.by = Person.objects.get(name="(System)") + e.type = "expired_document" + e.text = "Document has expired" + e.time = draft.expires + e.save() + + # expired without tombstone + txt = "%s-%s.txt" % (draft.name, draft.rev) + self.write_id_file(txt, 5000) + + clean_up_id_files() + + self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) + self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "expired_without_tombstone", txt))) + + + # expired with tombstone + revision_before = draft.rev + + txt = "%s-%s.txt" % (draft.name, draft.rev) + self.write_id_file(txt, 1000) # < 1500 means tombstone + + clean_up_id_files() + + self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) + self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "deleted_tombstones", txt))) + +class ExpireLastCallTestCase(django.test.TestCase): + fixtures = ['names'] + + def test_expire_last_call(self): + from ietf.idrfc.lastcall import get_expired_last_calls, expire_last_call + + # check that non-expirable drafts aren't expired + + draft = make_test_data() + draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="lc")) + + secretary = Person.objects.get(name="Sec Retary") + + self.assertEquals(len(list(get_expired_last_calls())), 0) + + e = LastCallDocEvent() + e.doc = draft + e.by = secretary + e.type = "sent_last_call" + e.text = "Last call sent" + e.expires = datetime.datetime.now() + datetime.timedelta(days=14) + e.save() + + self.assertEquals(len(list(get_expired_last_calls())), 0) + + # test expired + e = LastCallDocEvent() + e.doc = draft + e.by = secretary + e.type = "sent_last_call" + e.text = "Last call sent" + e.expires = datetime.datetime.now() + e.save() + + drafts = list(get_expired_last_calls()) + self.assertEquals(len(drafts), 1) + + # expire it + mailbox_before = len(outbox) + events_before = draft.docevent_set.count() + + expire_last_call(drafts[0]) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.get_state_slug("draft-iesg"), "writeupw") + 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"]) + +class IndividualInfoFormsTestCase(django.test.TestCase): + + fixtures = ['names'] + + def test_doc_change_stream(self): + url = urlreverse('doc_change_stream', kwargs=dict(name=self.docname)) + login_testing_unauthorized(self, "secretary", url) + + # get + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('form.change-stream')),1) + + # shift to ISE stream + messages_before = len(outbox) + r = self.client.post(url,dict(stream="ise",comment="7gRMTjBM")) + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertEquals(self.doc.stream_id,'ise') + self.assertEquals(len(outbox),messages_before+1) + self.assertTrue('Stream Change Notice' in outbox[-1]['Subject']) + self.assertTrue('7gRMTjBM' in str(outbox[-1])) + self.assertTrue('7gRMTjBM' in self.doc.latest_event(DocEvent,type='added_comment').desc) + # Would be nice to test that the stream managers were in the To header... + + # shift to an unknown stream (it must be possible to throw a document out of any stream) + r = self.client.post(url,dict(stream="")) + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertEquals(self.doc.stream,None) + + def test_doc_change_notify(self): + url = urlreverse('doc_change_notify', kwargs=dict(name=self.docname)) + login_testing_unauthorized(self, "secretary", url) + + # get + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('form input[name=notify]')),1) + + # Provide a list + r = self.client.post(url,dict(notify="TJ2APh2P@ietf.org",save_addresses="1")) + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertEquals(self.doc.notify,'TJ2APh2P@ietf.org') + + # Ask the form to regenerate the list + r = self.client.post(url,dict(regenerate_addresses="1")) + self.assertEquals(r.status_code,200) + self.doc = Document.objects.get(name=self.docname) + # Regenerate does not save! + self.assertEquals(self.doc.notify,'TJ2APh2P@ietf.org') + q = PyQuery(r.content) + self.assertTrue('TJ2Aph2P' not in q('form input[name=notify]')[0].value) + + def test_doc_change_intended_status(self): + url = urlreverse('doc_change_intended_status', kwargs=dict(name=self.docname)) + login_testing_unauthorized(self, "secretary", url) + + # get + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('form.change-intended-status')),1) + + # don't allow status level to be cleared + r = self.client.post(url,dict(intended_std_level="")) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + # change intended status level + messages_before = len(outbox) + r = self.client.post(url,dict(intended_std_level="bcp",comment="ZpyQFGmA")) + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertEquals(self.doc.intended_std_level_id,'bcp') + self.assertEquals(len(outbox),messages_before+1) + self.assertTrue('ZpyQFGmA' in str(outbox[-1])) + self.assertTrue('ZpyQFGmA' in self.doc.latest_event(DocEvent,type='added_comment').desc) + + def test_doc_change_telechat_date(self): + url = urlreverse('doc_change_telechat_date', kwargs=dict(name=self.docname)) + login_testing_unauthorized(self, "secretary", url) + + # get + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('form.telechat-date')),1) + + # set a date + self.assertFalse(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat")) + telechat_date = TelechatDate.objects.active().order_by('date')[0].date + r = self.client.post(url,dict(telechat_date=telechat_date.isoformat())) + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertEquals(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,telechat_date) + + # Take the doc back off any telechat + r = self.client.post(url,dict(telechat_date="")) + self.assertEquals(r.status_code, 302) + self.assertEquals(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,None) + + def test_doc_change_iesg_note(self): + url = urlreverse('doc_change_iesg_note', kwargs=dict(name=self.docname)) + login_testing_unauthorized(self, "secretary", url) + + # get + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('form.edit-iesg-note')),1) + + # post + r = self.client.post(url,dict(note='ZpyQFGmA\r\nZpyQFGmA')) + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertEquals(self.doc.note,'ZpyQFGmA\nZpyQFGmA') + self.assertTrue('ZpyQFGmA' in self.doc.latest_event(DocEvent,type='added_comment').desc) + + def test_doc_change_ad(self): + url = urlreverse('doc_change_ad', kwargs=dict(name=self.docname)) + login_testing_unauthorized(self, "secretary", url) + + # get + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('form select[name=ad]')),1) + + # change ads + ad2 = Person.objects.get(name='Ad No2') + r = self.client.post(url,dict(ad=str(ad2.pk))) + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertEquals(self.doc.ad,ad2) + self.assertTrue(self.doc.latest_event(DocEvent,type="added_comment").desc.startswith('Shepherding AD changed')) + + def test_doc_change_shepherd(self): + url = urlreverse('doc_edit_shepherd',kwargs=dict(name=self.docname)) + + login_testing_unauthorized(self, "plain", url) + + r = self.client.get(url) + self.assertEquals(r.status_code,403) + + # get as the secretariat (and remain secretariat) + login_testing_unauthorized(self, "secretary", url) + + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('form input[id=id_shepherd]')),1) + + # change the shepherd + plain = Person.objects.get(name='Plain Man') + plain_email = plain.email_set.all()[0] + r = self.client.post(url,dict(shepherd=plain_email)) + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertEquals(self.doc.shepherd,plain) + self.assertTrue(self.doc.latest_event(DocEvent,type="added_comment").desc.startswith('Document shepherd changed to Plain Man')) + + ad = Person.objects.get(name='Aread Irector') + two_answers = "%s,%s" % (plain_email, ad.email_set.all()[0]) + r = self.client.post(url,(dict(shepherd=two_answers))) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertTrue(len(q('form ul.errorlist')) > 0) + + def test_doc_view_shepherd_writeup(self): + url = urlreverse('doc_shepherd_writeup',kwargs=dict(name=self.docname)) + + # get as a shepherd + self.client.login(remote_user="plain") + + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('span[id=doc_edit_shepherd_writeup]')),1) + + # Try again when no longer a shepherd. + + self.doc.shepherd = None + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('span[id=doc_edit_shepherd_writeup]')),1) + + def test_doc_change_shepherd_writeup(self): + url = urlreverse('doc_edit_shepherd_writeup',kwargs=dict(name=self.docname)) + + # get + login_testing_unauthorized(self, "secretary", url) + + r = self.client.get(url) + self.assertEquals(r.status_code,200) + q = PyQuery(r.content) + self.assertEquals(len(q('form textarea[id=id_content]')),1) + + # direct edit + r = self.client.post(url,dict(content='here is a new writeup',submit_response="1")) + print r.content + self.assertEquals(r.status_code,302) + self.doc = Document.objects.get(name=self.docname) + self.assertTrue(self.doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup").text.startswith('here is a new writeup')) + + # file upload + test_file = StringIO.StringIO("This is a different writeup.") + test_file.name = "unnamed" + r = self.client.post(url,dict(txt=test_file,submit_response="1")) + self.assertEquals(r.status_code, 302) + doc = Document.objects.get(name=self.docname) + self.assertTrue(self.doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup").text.startswith('This is a different writeup.')) + + # template reset + r = self.client.post(url,dict(txt=test_file,reset_text="1")) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(q('textarea')[0].text.startswith("As required by RFC 4858")) + + def setUp(self): + make_test_data() + self.docname='draft-ietf-mars-test' + self.doc = Document.objects.get(name=self.docname) + diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py deleted file mode 100644 index 4b136c666..000000000 --- a/ietf/idrfc/testsREDESIGN.py +++ /dev/null @@ -1,1458 +0,0 @@ -# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). -# All rights reserved. Contact: Pasi Eronen -# -# 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. - -import unittest -import StringIO -import os, shutil -from datetime import date, timedelta, time - -import django.test -from django.core.urlresolvers import reverse as urlreverse -from django.conf import settings - -from pyquery import PyQuery -import debug - -from ietf.doc.models import * -from ietf.name.models import * -from ietf.group.models import * -from ietf.person.models import * -from ietf.meeting.models import Meeting, MeetingTypeName -from ietf.iesg.models import TelechatDate -from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest, login_testing_unauthorized -from ietf.utils.test_data import make_test_data -from ietf.utils.mail import outbox - -class IdRfcUrlTestCase(SimpleUrlTestCase): - def testUrls(self): - #self.doTestUrls(__file__) - self.doTestUrls(os.path.join(os.path.dirname(os.path.abspath(__file__)), "testurlREDESIGN.list")) - - -class ChangeStateTestCase(django.test.TestCase): - fixtures = ['names'] - - def test_change_state(self): - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="ad-eval")) - - url = urlreverse('doc_change_state', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - first_state = draft.get_state("draft-iesg") - next_states = first_state.next_states - - # 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) - - if next_states: - self.assertTrue(len(q('.next-states form input[type=hidden]')) > 0) - - - # faulty post - r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft", slug="active").pk)) - 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-iesg"), first_state) - - - # change state - events_before = draft.docevent_set.count() - mailbox_before = len(outbox) - draft.tags.add("ad-f-up") - - r = self.client.post(url, - dict(state=State.objects.get(used=True, type="draft-iesg", slug="review-e").pk, - substate="point", - 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.assertTrue(not draft.tags.filter(slug="ad-f-up")) - self.assertTrue(draft.tags.filter(slug="point")) - self.assertEquals(draft.docevent_set.count(), events_before + 2) - self.assertTrue("Test comment" in draft.docevent_set.all()[0].desc) - self.assertTrue("State changed" in draft.docevent_set.all()[1].desc) - self.assertEquals(len(outbox), mailbox_before + 2) - self.assertTrue("State Update Notice" in outbox[-2]['Subject']) - self.assertTrue(draft.name in outbox[-1]['Subject']) - - - # check that we got a previous state now - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - 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(used=True, 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(used=True, 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(used=True, type="draft-iana-review", slug="need-rev") - next_state = State.objects.get(used=True, 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(used=True, type="draft-iesg", slug="ad-eval")) - - self.client.login(remote_user="secretary") - url = urlreverse('doc_change_state', kwargs=dict(name=draft.name)) - - mailbox_before = len(outbox) - - self.assertTrue(not draft.latest_event(type="changed_ballot_writeup_text")) - r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft-iesg", slug="lc-req").pk)) - self.assertContains(r, "Your request to issue the Last Call") - - # last call text - e = draft.latest_event(WriteupDocEvent, type="changed_last_call_text") - self.assertTrue(e) - self.assertTrue("The IESG has received" in e.text) - self.assertTrue(draft.title in e.text) - self.assertTrue(draft.get_absolute_url() in e.text) - - # approval text - e = draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") - self.assertTrue(e) - self.assertTrue("The IESG has approved" in e.text) - self.assertTrue(draft.title in e.text) - self.assertTrue(draft.get_absolute_url() in e.text) - - # ballot writeup - e = draft.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") - self.assertTrue(e) - self.assertTrue("Technical Summary" in e.text) - - # mail notice - self.assertTrue(len(outbox) > mailbox_before) - self.assertTrue("Last Call:" in outbox[-1]['Subject']) - - # comment - self.assertTrue("Last call was requested" in draft.latest_event().desc) - - -class EditInfoTestCase(django.test.TestCase): - fixtures = ['names'] - - def test_edit_info(self): - draft = make_test_data() - url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertEquals(len(q('form select[name=intended_std_level]')), 1) - - prev_ad = draft.ad - # faulty post - r = self.client.post(url, dict(ad="123456789")) - 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.ad, prev_ad) - - # edit info - events_before = draft.docevent_set.count() - mailbox_before = len(outbox) - - new_ad = Person.objects.get(name="Ad No1") - - r = self.client.post(url, - dict(intended_std_level=str(draft.intended_std_level.pk), - stream=draft.stream_id, - ad=str(new_ad.pk), - notify="test@example.com", - note="New note", - telechat_date="", - )) - self.assertEquals(r.status_code, 302) - - 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="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']) - - def test_edit_telechat_date(self): - draft = make_test_data() - - url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - data = dict(intended_std_level=str(draft.intended_std_level_id), - stream=draft.stream_id, - ad=str(draft.ad_id), - notify="test@example.com", - note="", - ) - - # add to 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, 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() - r = self.client.post(url, data) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEqual(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[1].date) - - # remove from agenda - data["telechat_date"] = "" - r = self.client.post(url, data) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date) - - def test_start_iesg_process_on_draft(self): - make_test_data() - - draft = Document.objects.create( - name="draft-ietf-mars-test2", - time=datetime.datetime.now(), - type_id="draft", - title="Testing adding a draft", - stream=None, - group=Group.objects.get(acronym="mars"), - abstract="Test test test.", - rev="01", - pages=2, - intended_std_level_id="ps", - shepherd=None, - ad=None, - expires=datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE), - ) - doc_alias = DocAlias.objects.create( - document=draft, - name=draft.name, - ) - - DocumentAuthor.objects.create( - document=draft, - author=Email.objects.get(address="aread@ietf.org"), - order=1 - ) - - url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertEquals(len(q('form select[name=intended_std_level]')), 1) - self.assertTrue('@' in q('form input[name=notify]')[0].get('value')) - - # add - events_before = draft.docevent_set.count() - mailbox_before = len(outbox) - - ad = Person.objects.get(name="Aread Irector") - - r = self.client.post(url, - dict(intended_std_level=str(draft.intended_std_level_id), - ad=ad.pk, - create_in_state=State.objects.get(used=True, type="draft-iesg", slug="watching").pk, - notify="test@example.com", - note="This is a note", - telechat_date="", - )) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug("draft-iesg"), "watching") - self.assertEquals(draft.ad, ad) - self.assertEquals(draft.note, "This is a note") - self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat")) - self.assertEquals(draft.docevent_set.count(), events_before + 3) - events = list(draft.docevent_set.order_by('time', 'id')) - 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'] - - def test_request_resurrect(self): - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) - - url = urlreverse('doc_request_resurrect', kwargs=dict(name=draft.name)) - - login_testing_unauthorized(self, "ad", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertEquals(len(q('form input[type=submit]')), 1) - - - # request resurrect - events_before = draft.docevent_set.count() - mailbox_before = len(outbox) - - r = self.client.post(url, dict()) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.docevent_set.count(), events_before + 1) - e = draft.latest_event(type="requested_resurrect") - self.assertTrue(e) - self.assertEquals(e.by, Person.objects.get(name="Aread Irector")) - self.assertTrue("Resurrection" in e.desc) - self.assertEquals(len(outbox), mailbox_before + 1) - self.assertTrue("Resurrection" in outbox[-1]['Subject']) - - def test_resurrect(self): - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) - - DocEvent.objects.create(doc=draft, - type="requested_resurrect", - by=Person.objects.get(name="Aread Irector")) - - url = urlreverse('doc_resurrect', kwargs=dict(name=draft.name)) - - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertEquals(len(q('form input[type=submit]')), 1) - - # complete resurrect - events_before = draft.docevent_set.count() - mailbox_before = len(outbox) - - r = self.client.post(url, dict()) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.docevent_set.count(), events_before + 1) - self.assertEquals(draft.latest_event().type, "completed_resurrect") - self.assertEquals(draft.get_state_slug(), "active") - self.assertTrue(draft.expires >= datetime.datetime.now() + datetime.timedelta(days=settings.INTERNET_DRAFT_DAYS_TO_EXPIRE - 1)) - self.assertEquals(len(outbox), mailbox_before + 1) - -class AddCommentTestCase(django.test.TestCase): - fixtures = ['names'] - - def test_add_comment(self): - draft = make_test_data() - url = urlreverse('doc_add_comment', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertEquals(len(q('form textarea[name=comment]')), 1) - - # request resurrect - events_before = draft.docevent_set.count() - mailbox_before = len(outbox) - - r = self.client.post(url, dict(comment="This is a test.")) - self.assertEquals(r.status_code, 302) - - self.assertEquals(draft.docevent_set.count(), events_before + 1) - self.assertEquals("This is a test.", draft.latest_event().desc) - self.assertEquals("added_comment", draft.latest_event().type) - self.assertEquals(len(outbox), mailbox_before + 1) - 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'] - - def test_edit_position(self): - draft = make_test_data() - url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name, - ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk)) - login_testing_unauthorized(self, "ad", url) - - ad = Person.objects.get(name="Aread Irector") - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertTrue(len(q('form input[name=position]')) > 0) - self.assertEquals(len(q('form textarea[name=comment]')), 1) - - # vote - events_before = draft.docevent_set.count() - - r = self.client.post(url, dict(position="discuss", - discuss=" This is a discussion test. \n ", - comment=" This is a test. \n ")) - self.assertEquals(r.status_code, 302) - - pos = draft.latest_event(BallotPositionDocEvent, ad=ad) - self.assertEquals(pos.pos.slug, "discuss") - self.assertTrue(" This is a discussion test." in pos.discuss) - self.assertTrue(pos.discuss_time != None) - self.assertTrue(" This is a test." in pos.comment) - self.assertTrue(pos.comment_time != None) - self.assertTrue("New position" in pos.desc) - self.assertEquals(draft.docevent_set.count(), events_before + 3) - - # recast vote - events_before = draft.docevent_set.count() - r = self.client.post(url, dict(position="noobj")) - self.assertEquals(r.status_code, 302) - - pos = draft.latest_event(BallotPositionDocEvent, ad=ad) - self.assertEquals(pos.pos.slug, "noobj") - self.assertEquals(draft.docevent_set.count(), events_before + 1) - self.assertTrue("Position for" in pos.desc) - - # clear vote - events_before = draft.docevent_set.count() - r = self.client.post(url, dict(position="norecord")) - self.assertEquals(r.status_code, 302) - - pos = draft.latest_event(BallotPositionDocEvent, ad=ad) - self.assertEquals(pos.pos.slug, "norecord") - self.assertEquals(draft.docevent_set.count(), events_before + 1) - self.assertTrue("Position for" in pos.desc) - - # change comment - events_before = draft.docevent_set.count() - r = self.client.post(url, dict(position="norecord", comment="New comment.")) - self.assertEquals(r.status_code, 302) - - pos = draft.latest_event(BallotPositionDocEvent, ad=ad) - self.assertEquals(pos.pos.slug, "norecord") - self.assertEquals(draft.docevent_set.count(), events_before + 2) - self.assertTrue("Ballot comment text updated" in pos.desc) - - def test_edit_position_as_secretary(self): - draft = make_test_data() - url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name, - ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk)) - ad = Person.objects.get(name="Aread Irector") - url += "?ad=%s" % ad.pk - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertTrue(len(q('form input[name=position]')) > 0) - - # vote on behalf of AD - events_before = draft.docevent_set.count() - r = self.client.post(url, dict(position="discuss", discuss="Test discuss text")) - self.assertEquals(r.status_code, 302) - - pos = draft.latest_event(BallotPositionDocEvent, ad=ad) - self.assertEquals(pos.pos.slug, "discuss") - self.assertEquals(pos.discuss, "Test discuss text") - self.assertTrue("New position" in pos.desc) - self.assertTrue("by Sec" in pos.desc) - - def test_cannot_edit_position_as_pre_ad(self): - draft = make_test_data() - url = urlreverse('ietf.idrfc.views_ballot.edit_position', kwargs=dict(name=draft.name, - ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk)) - - # transform to pre-ad - ad_role = Role.objects.filter(name="ad")[0] - ad_role.name_id = "pre-ad" - ad_role.save() - - # we can see - login_testing_unauthorized(self, ad_role.person.user.username, url) - - # but not touch - r = self.client.post(url, dict(position="discuss", discuss="Test discuss text")) - self.assertEquals(r.status_code, 403) - - def test_send_ballot_comment(self): - draft = make_test_data() - draft.notify = "somebody@example.com" - draft.save() - - ad = Person.objects.get(name="Aread Irector") - - ballot = draft.latest_event(BallotDocEvent, type="created_ballot") - - BallotPositionDocEvent.objects.create( - doc=draft, type="changed_ballot_position", - by=ad, ad=ad, ballot=ballot, pos=BallotPositionName.objects.get(slug="discuss"), - discuss="This draft seems to be lacking a clearer title?", - discuss_time=datetime.datetime.now(), - comment="Test!", - comment_time=datetime.datetime.now()) - - url = urlreverse('doc_send_ballot_comment', kwargs=dict(name=draft.name, - ballot_id=ballot.pk)) - login_testing_unauthorized(self, "ad", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertTrue(len(q('form input[name="cc"]')) > 0) - - # send - mailbox_before = len(outbox) - - r = self.client.post(url, dict(cc="test@example.com", cc_state_change="1")) - self.assertEquals(r.status_code, 302) - - self.assertEquals(len(outbox), mailbox_before + 1) - m = outbox[-1] - self.assertTrue("COMMENT" in m['Subject']) - self.assertTrue("DISCUSS" in m['Subject']) - self.assertTrue(draft.name in m['Subject']) - self.assertTrue("clearer title" in str(m)) - self.assertTrue("Test!" in str(m)) - - -class DeferBallotTestCase(django.test.TestCase): - fixtures = ['names'] - - def test_defer_ballot(self): - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) - - url = urlreverse('doc_defer_ballot', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "ad", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - - # defer - mailbox_before = len(outbox) - - r = self.client.post(url, dict()) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug("draft-iesg"), "defer") - - self.assertEquals(len(outbox), mailbox_before + 2) - self.assertTrue("State Update" in outbox[-2]['Subject']) - self.assertTrue("Deferred" in outbox[-1]['Subject']) - self.assertTrue(draft.file_tag() in outbox[-1]['Subject']) - - def test_undefer_ballot(self): - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="defer")) - - url = urlreverse('doc_undefer_ballot', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "ad", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - - # undefer - r = self.client.post(url, dict()) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug("draft-iesg"), "iesg-eva") - -class BallotWriteupsTestCase(django.test.TestCase): - fixtures = ['names'] - - def test_edit_last_call_text(self): - draft = make_test_data() - url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertEquals(len(q('textarea[name=last_call_text]')), 1) - self.assertEquals(len(q('input[type=submit][value*="Save Last Call"]')), 1) - # we're secretariat, so we got The Link - self.assertEquals(len(q('a:contains("Make Last Call")')), 1) - - # subject error - r = self.client.post(url, dict( - last_call_text="Subject: test\r\nhello\r\n\r\n", - save_last_call_text="1")) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertTrue(len(q('ul.errorlist')) > 0) - - # save - r = self.client.post(url, dict( - last_call_text="This is a simple test.", - save_last_call_text="1")) - self.assertEquals(r.status_code, 200) - draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text) - - # test regenerate - r = self.client.post(url, dict( - last_call_text="This is a simple test.", - regenerate_last_call_text="1")) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - draft = Document.objects.get(name=draft.name) - self.assertTrue("Subject: Last Call" in draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text) - - - def test_request_last_call(self): - draft = make_test_data() - url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # give us an announcement to send - r = self.client.post(url, dict(regenerate_last_call_text="1")) - self.assertEquals(r.status_code, 200) - - mailbox_before = len(outbox) - - # send - r = self.client.post(url, dict( - last_call_text=draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text, - send_last_call_request="1")) - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug("draft-iesg"), "lc-req") - self.assertEquals(len(outbox), mailbox_before + 3) - self.assertTrue("Last Call" in outbox[-1]['Subject']) - self.assertTrue(draft.name in outbox[-1]['Subject']) - - def test_edit_ballot_writeup(self): - draft = make_test_data() - 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(used=True, 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( - ballot_writeup="This is a simple test.", - save_ballot_writeup="1")) - self.assertEquals(r.status_code, 200) - draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text").text) - - def test_issue_ballot(self): - draft = make_test_data() - url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "ad", url) - - ballot = draft.latest_event(BallotDocEvent, type="created_ballot") - - def create_pos(num, vote, comment="", discuss=""): - ad = Person.objects.get(name="Ad No%s" % num) - e = BallotPositionDocEvent() - e.doc = draft - e.ballot = ballot - e.by = ad - e.ad = ad - e.pos = BallotPositionName.objects.get(slug=vote) - e.type = "changed_ballot_position" - e.comment = comment - if e.comment: - e.comment_time = datetime.datetime.now() - e.discuss = discuss - if e.discuss: - e.discuss_time = datetime.datetime.now() - e.save() - - # active - create_pos(1, "yes", discuss="discuss1 " * 20) - create_pos(2, "noobj", comment="comment2 " * 20) - create_pos(3, "discuss", discuss="discuss3 " * 20, comment="comment3 " * 20) - create_pos(4, "abstain") - create_pos(5, "recuse") - - # inactive - create_pos(9, "yes") - - mailbox_before = len(outbox) - - r = self.client.post(url, dict( - ballot_writeup="This is a test.", - issue_ballot="1")) - self.assertEquals(r.status_code, 200) - draft = Document.objects.get(name=draft.name) - - self.assertTrue(draft.latest_event(type="sent_ballot_announcement")) - self.assertEquals(len(outbox), mailbox_before + 2) - issue_email = outbox[-2] - self.assertTrue("Evaluation:" in issue_email['Subject']) - self.assertTrue("comment1" not in str(issue_email)) - self.assertTrue("comment2" in str(issue_email)) - self.assertTrue("comment3" in str(issue_email)) - self.assertTrue("discuss3" in str(issue_email)) - self.assertTrue("This is a test" in str(issue_email)) - self.assertTrue("The IESG has approved" in str(issue_email)) - - def test_edit_approval_text(self): - draft = make_test_data() - url = urlreverse('doc_ballot_approvaltext', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertEquals(len(q('textarea[name=approval_text]')), 1) - self.assertEquals(len(q('input[type=submit][value*="Save Approval"]')), 1) - - # save - r = self.client.post(url, dict( - approval_text="This is a simple test.", - save_approval_text="1")) - self.assertEquals(r.status_code, 200) - draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) - - # test regenerate - r = self.client.post(url, dict(regenerate_approval_text="1")) - self.assertEquals(r.status_code, 200) - draft = Document.objects.get(name=draft.name) - self.assertTrue("Subject: Protocol Action" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) - - # test regenerate when it's a disapprove - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="nopubadw")) - - r = self.client.post(url, dict(regenerate_approval_text="1")) - self.assertEquals(r.status_code, 200) - draft = Document.objects.get(name=draft.name) - self.assertTrue("NOT be published" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) - - # test regenerate when it's a conflict review - draft.group = Group.objects.get(type="individ") - draft.stream_id = "irtf" - draft.save() - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) - - r = self.client.post(url, dict(regenerate_approval_text="1")) - self.assertEquals(r.status_code, 200) - draft = Document.objects.get(name=draft.name) - self.assertTrue("Subject: Results of IETF-conflict review" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) - -class ApproveBallotTestCase(django.test.TestCase): - fixtures = ['names'] - - def test_approve_ballot(self): - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) # make sure it's approvable - - url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertTrue("send out the announcement" in q('.actions input[type=submit]')[0].get('value').lower()) - self.assertEquals(len(q('.announcement pre:contains("Subject: Protocol Action")')), 1) - - # approve - mailbox_before = len(outbox) - - r = self.client.post(url, dict(skiprfceditorpost="1")) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug("draft-iesg"), "ann") - self.assertEquals(len(outbox), mailbox_before + 4) - self.assertTrue("Protocol Action" in outbox[-2]['Subject']) - # the IANA copy - self.assertTrue("Protocol Action" in outbox[-1]['Subject']) - self.assertTrue(not outbox[-1]['CC']) - self.assertTrue("Protocol Action" in draft.message_set.order_by("-time")[0].subject) - - def test_disapprove_ballot(self): - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="nopubadw")) - - url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # disapprove (the Martians aren't going to be happy) - mailbox_before = len(outbox) - - r = self.client.post(url, dict()) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug("draft-iesg"), "dead") - self.assertEquals(len(outbox), mailbox_before + 3) - self.assertTrue("NOT be published" in str(outbox[-1])) - -class MakeLastCallTestCase(django.test.TestCase): - fixtures = ['names'] - - def test_make_last_call(self): - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="lc-req")) - - url = urlreverse('doc_make_last_call', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "secretary", url) - - # normal get - r = self.client.get(url) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertEquals(len(q('input[name=last_call_sent_date]')), 1) - - # make last call - mailbox_before = len(outbox) - - expire_date = q('input[name=last_call_expiration_date]')[0].get("value") - - r = self.client.post(url, - dict(last_call_sent_date=q('input[name=last_call_sent_date]')[0].get("value"), - last_call_expiration_date=expire_date - )) - self.assertEquals(r.status_code, 302) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug("draft-iesg"), "lc") - self.assertEquals(draft.latest_event(LastCallDocEvent, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date) - self.assertEquals(len(outbox), mailbox_before + 4) - - self.assertTrue("Last Call" in outbox[-4]['Subject']) - # the IANA copy - 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(used=True, 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, skiprfceditorpost="1")) - 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']) - self.assertTrue(not outbox[-1]['CC']) - -class ExpireIDsTestCase(django.test.TestCase): - fixtures = ['names'] - - def setUp(self): - self.id_dir = os.path.abspath("tmp-id-dir") - self.archive_dir = os.path.abspath("tmp-id-archive") - os.mkdir(self.id_dir) - os.mkdir(self.archive_dir) - os.mkdir(os.path.join(self.archive_dir, "unknown_ids")) - os.mkdir(os.path.join(self.archive_dir, "deleted_tombstones")) - os.mkdir(os.path.join(self.archive_dir, "expired_without_tombstone")) - - settings.INTERNET_DRAFT_PATH = self.id_dir - settings.INTERNET_DRAFT_ARCHIVE_DIR = self.archive_dir - - def tearDown(self): - shutil.rmtree(self.id_dir) - shutil.rmtree(self.archive_dir) - - def write_id_file(self, name, size): - f = open(os.path.join(self.id_dir, name), 'w') - f.write("a" * size) - f.close() - - def test_in_id_expire_freeze(self): - from ietf.idrfc.expire import in_id_expire_freeze - - Meeting.objects.create(number="123", - type=MeetingTypeName.objects.get(slug="ietf"), - date=date.today()) - second_cut_off = Meeting.get_second_cut_off() - ietf_monday = Meeting.get_ietf_monday() - - self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(second_cut_off - datetime.timedelta(days=7), time(0, 0, 0)))) - self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(second_cut_off, time(0, 0, 0)))) - self.assertTrue(in_id_expire_freeze(datetime.datetime.combine(second_cut_off + datetime.timedelta(days=7), time(0, 0, 0)))) - self.assertTrue(in_id_expire_freeze(datetime.datetime.combine(ietf_monday - datetime.timedelta(days=1), time(0, 0, 0)))) - self.assertTrue(not in_id_expire_freeze(datetime.datetime.combine(ietf_monday, time(0, 0, 0)))) - - def test_warn_expirable_ids(self): - from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id - - draft = make_test_data() - - self.assertEquals(len(list(get_soon_to_expire_ids(14))), 0) - - # hack into expirable state - draft.unset_state("draft-iesg") - draft.expires = datetime.datetime.now() + datetime.timedelta(days=10) - draft.save() - - self.assertEquals(len(list(get_soon_to_expire_ids(14))), 1) - - # test send warning - mailbox_before = len(outbox) - - send_expire_warning_for_id(draft) - - self.assertEquals(len(outbox), mailbox_before + 1) - self.assertTrue("aread@ietf.org" in str(outbox[-1])) # author - self.assertTrue("wgchairman@ietf.org" in str(outbox[-1])) - - def test_expire_ids(self): - from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id - - draft = make_test_data() - - self.assertEquals(len(list(get_expired_ids())), 0) - - # hack into expirable state - draft.unset_state("draft-iesg") - draft.expires = datetime.datetime.now() - draft.save() - - self.assertEquals(len(list(get_expired_ids())), 1) - - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="watching")) - - self.assertEquals(len(list(get_expired_ids())), 1) - - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva")) - - self.assertEquals(len(list(get_expired_ids())), 0) - - # test notice - mailbox_before = len(outbox) - - send_expire_notice_for_id(draft) - - self.assertEquals(len(outbox), mailbox_before + 1) - self.assertTrue("expired" in outbox[-1]["Subject"]) - - # test expiry - txt = "%s-%s.txt" % (draft.name, draft.rev) - self.write_id_file(txt, 5000) - - expire_id(draft) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug(), "expired") - self.assertEquals(draft.get_state_slug("draft-iesg"), "dead") - self.assertTrue(draft.latest_event(type="expired_document")) - self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) - self.assertTrue(os.path.exists(os.path.join(self.archive_dir, txt))) - - def test_clean_up_id_files(self): - draft = make_test_data() - - from ietf.idrfc.expire import clean_up_id_files - - # put unknown file - unknown = "draft-i-am-unknown-01.txt" - self.write_id_file(unknown, 5000) - - clean_up_id_files() - - self.assertTrue(not os.path.exists(os.path.join(self.id_dir, unknown))) - self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", unknown))) - - - # put file with malformed name (no revision) - malformed = draft.name + ".txt" - self.write_id_file(malformed, 5000) - - clean_up_id_files() - - self.assertTrue(not os.path.exists(os.path.join(self.id_dir, malformed))) - self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", malformed))) - - - # RFC draft - draft.set_state(State.objects.get(used=True, type="draft", slug="rfc")) - draft.save() - - txt = "%s-%s.txt" % (draft.name, draft.rev) - self.write_id_file(txt, 5000) - pdf = "%s-%s.pdf" % (draft.name, draft.rev) - self.write_id_file(pdf, 5000) - - clean_up_id_files() - - # txt files shouldn't be moved (for some reason) - self.assertTrue(os.path.exists(os.path.join(self.id_dir, txt))) - - self.assertTrue(not os.path.exists(os.path.join(self.id_dir, pdf))) - self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "unknown_ids", pdf))) - - - # expire draft - draft.set_state(State.objects.get(used=True, type="draft", slug="expired")) - draft.expires = datetime.datetime.now() - datetime.timedelta(days=1) - draft.save() - - e = DocEvent() - e.doc = draft - e.by = Person.objects.get(name="(System)") - e.type = "expired_document" - e.text = "Document has expired" - e.time = draft.expires - e.save() - - # expired without tombstone - txt = "%s-%s.txt" % (draft.name, draft.rev) - self.write_id_file(txt, 5000) - - clean_up_id_files() - - self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) - self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "expired_without_tombstone", txt))) - - - # expired with tombstone - revision_before = draft.rev - - txt = "%s-%s.txt" % (draft.name, draft.rev) - self.write_id_file(txt, 1000) # < 1500 means tombstone - - clean_up_id_files() - - self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) - self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "deleted_tombstones", txt))) - -class ExpireLastCallTestCase(django.test.TestCase): - fixtures = ['names'] - - def test_expire_last_call(self): - from ietf.idrfc.lastcall import get_expired_last_calls, expire_last_call - - # check that non-expirable drafts aren't expired - - draft = make_test_data() - draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="lc")) - - secretary = Person.objects.get(name="Sec Retary") - - self.assertEquals(len(list(get_expired_last_calls())), 0) - - e = LastCallDocEvent() - e.doc = draft - e.by = secretary - e.type = "sent_last_call" - e.text = "Last call sent" - e.expires = datetime.datetime.now() + datetime.timedelta(days=14) - e.save() - - self.assertEquals(len(list(get_expired_last_calls())), 0) - - # test expired - e = LastCallDocEvent() - e.doc = draft - e.by = secretary - e.type = "sent_last_call" - e.text = "Last call sent" - e.expires = datetime.datetime.now() - e.save() - - drafts = list(get_expired_last_calls()) - self.assertEquals(len(drafts), 1) - - # expire it - mailbox_before = len(outbox) - events_before = draft.docevent_set.count() - - expire_last_call(drafts[0]) - - draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.get_state_slug("draft-iesg"), "writeupw") - 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"]) - -class IndividualInfoFormsTestCase(django.test.TestCase): - - fixtures = ['names'] - - def test_doc_change_stream(self): - url = urlreverse('doc_change_stream', kwargs=dict(name=self.docname)) - login_testing_unauthorized(self, "secretary", url) - - # get - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('form.change-stream')),1) - - # shift to ISE stream - messages_before = len(outbox) - r = self.client.post(url,dict(stream="ise",comment="7gRMTjBM")) - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertEquals(self.doc.stream_id,'ise') - self.assertEquals(len(outbox),messages_before+1) - self.assertTrue('Stream Change Notice' in outbox[-1]['Subject']) - self.assertTrue('7gRMTjBM' in str(outbox[-1])) - self.assertTrue('7gRMTjBM' in self.doc.latest_event(DocEvent,type='added_comment').desc) - # Would be nice to test that the stream managers were in the To header... - - # shift to an unknown stream (it must be possible to throw a document out of any stream) - r = self.client.post(url,dict(stream="")) - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertEquals(self.doc.stream,None) - - def test_doc_change_notify(self): - url = urlreverse('doc_change_notify', kwargs=dict(name=self.docname)) - login_testing_unauthorized(self, "secretary", url) - - # get - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('form input[name=notify]')),1) - - # Provide a list - r = self.client.post(url,dict(notify="TJ2APh2P@ietf.org",save_addresses="1")) - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertEquals(self.doc.notify,'TJ2APh2P@ietf.org') - - # Ask the form to regenerate the list - r = self.client.post(url,dict(regenerate_addresses="1")) - self.assertEquals(r.status_code,200) - self.doc = Document.objects.get(name=self.docname) - # Regenerate does not save! - self.assertEquals(self.doc.notify,'TJ2APh2P@ietf.org') - q = PyQuery(r.content) - self.assertTrue('TJ2Aph2P' not in q('form input[name=notify]')[0].value) - - def test_doc_change_intended_status(self): - url = urlreverse('doc_change_intended_status', kwargs=dict(name=self.docname)) - login_testing_unauthorized(self, "secretary", url) - - # get - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('form.change-intended-status')),1) - - # don't allow status level to be cleared - r = self.client.post(url,dict(intended_std_level="")) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertTrue(len(q('form ul.errorlist')) > 0) - - # change intended status level - messages_before = len(outbox) - r = self.client.post(url,dict(intended_std_level="bcp",comment="ZpyQFGmA")) - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertEquals(self.doc.intended_std_level_id,'bcp') - self.assertEquals(len(outbox),messages_before+1) - self.assertTrue('ZpyQFGmA' in str(outbox[-1])) - self.assertTrue('ZpyQFGmA' in self.doc.latest_event(DocEvent,type='added_comment').desc) - - def test_doc_change_telechat_date(self): - url = urlreverse('doc_change_telechat_date', kwargs=dict(name=self.docname)) - login_testing_unauthorized(self, "secretary", url) - - # get - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('form.telechat-date')),1) - - # set a date - self.assertFalse(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat")) - telechat_date = TelechatDate.objects.active().order_by('date')[0].date - r = self.client.post(url,dict(telechat_date=telechat_date.isoformat())) - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertEquals(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,telechat_date) - - # Take the doc back off any telechat - r = self.client.post(url,dict(telechat_date="")) - self.assertEquals(r.status_code, 302) - self.assertEquals(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,None) - - def test_doc_change_iesg_note(self): - url = urlreverse('doc_change_iesg_note', kwargs=dict(name=self.docname)) - login_testing_unauthorized(self, "secretary", url) - - # get - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('form.edit-iesg-note')),1) - - # No validation code to test - - # post - testing that the munge code exists in note.clean... - r = self.client.post(url,dict(note='ZpyQFGmA\nZpyQFGmA')) - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertEquals(self.doc.note,'ZpyQFGmA
ZpyQFGmA') - self.assertTrue('ZpyQFGmA' in self.doc.latest_event(DocEvent,type='added_comment').desc) - - def test_doc_change_ad(self): - url = urlreverse('doc_change_ad', kwargs=dict(name=self.docname)) - login_testing_unauthorized(self, "secretary", url) - - # get - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('form select[name=ad]')),1) - - # change ads - ad2 = Person.objects.get(name='Ad No2') - r = self.client.post(url,dict(ad=str(ad2.pk))) - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertEquals(self.doc.ad,ad2) - self.assertTrue(self.doc.latest_event(DocEvent,type="added_comment").desc.startswith('Shepherding AD changed')) - - def test_doc_change_shepherd(self): - url = urlreverse('doc_edit_shepherd',kwargs=dict(name=self.docname)) - - login_testing_unauthorized(self, "plain", url) - - r = self.client.get(url) - self.assertEquals(r.status_code,403) - - # get as the secretariat (and remain secretariat) - login_testing_unauthorized(self, "secretary", url) - - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('form input[id=id_shepherd]')),1) - - # change the shepherd - plain = Person.objects.get(name='Plain Man') - plain_email = plain.email_set.all()[0] - r = self.client.post(url,dict(shepherd=plain_email)) - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertEquals(self.doc.shepherd,plain) - self.assertTrue(self.doc.latest_event(DocEvent,type="added_comment").desc.startswith('Document shepherd changed to Plain Man')) - - ad = Person.objects.get(name='Aread Irector') - two_answers = "%s,%s" % (plain_email, ad.email_set.all()[0]) - r = self.client.post(url,(dict(shepherd=two_answers))) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertTrue(len(q('form ul.errorlist')) > 0) - - def test_doc_view_shepherd_writeup(self): - url = urlreverse('doc_shepherd_writeup',kwargs=dict(name=self.docname)) - - # get as a shepherd - self.client.login(remote_user="plain") - - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('span[id=doc_edit_shepherd_writeup]')),1) - - # Try again when no longer a shepherd. - - self.doc.shepherd = None - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('span[id=doc_edit_shepherd_writeup]')),1) - - def test_doc_change_shepherd_writeup(self): - url = urlreverse('doc_edit_shepherd_writeup',kwargs=dict(name=self.docname)) - - # get - login_testing_unauthorized(self, "secretary", url) - - r = self.client.get(url) - self.assertEquals(r.status_code,200) - q = PyQuery(r.content) - self.assertEquals(len(q('form textarea[id=id_content]')),1) - - # direct edit - r = self.client.post(url,dict(content='here is a new writeup',submit_response="1")) - print r.content - self.assertEquals(r.status_code,302) - self.doc = Document.objects.get(name=self.docname) - self.assertTrue(self.doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup").text.startswith('here is a new writeup')) - - # file upload - test_file = StringIO.StringIO("This is a different writeup.") - test_file.name = "unnamed" - r = self.client.post(url,dict(txt=test_file,submit_response="1")) - self.assertEquals(r.status_code, 302) - doc = Document.objects.get(name=self.docname) - self.assertTrue(self.doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup").text.startswith('This is a different writeup.')) - - # template reset - r = self.client.post(url,dict(txt=test_file,reset_text="1")) - self.assertEquals(r.status_code, 200) - q = PyQuery(r.content) - self.assertTrue(q('textarea')[0].text.startswith("As required by RFC 4858")) - - def setUp(self): - make_test_data() - self.docname='draft-ietf-mars-test' - self.doc = Document.objects.get(name=self.docname) - diff --git a/ietf/idrfc/testurl.list b/ietf/idrfc/testurl.list deleted file mode 100644 index 60bf3e81f..000000000 --- a/ietf/idrfc/testurl.list +++ /dev/null @@ -1,88 +0,0 @@ -200 / -200 /doc/ -200,heavy /doc/all/ -200,heavy /doc/active/ - -# draft that's now RFC -200 /doc/draft-ietf-avt-rtp-atrac-family/ -200 /doc/draft-ietf-avt-rtp-atrac-family/doc.json -200 /doc/draft-ietf-avt-rtp-atrac-family/ballot.json -200 /doc/draft-ietf-avt-rtp-atrac-family/_ballot.data - -# replaced draft, never went to IESG -200 /doc/draft-eronen-mobike-mopo/ -404 /doc/draft-eronen-mobike-mopo/ballot.json -404 /doc/draft-eronen-mobike-mopo/_ballot.data - -# expired draft -200 /doc/draft-eronen-eap-sim-aka-80211/ - -# Normal RFC -200 /doc/rfc4739/ -200 /doc/rfc4739/doc.json -404 /doc/rfc4739/ballot.json -404 /doc/rfc4739/_ballot.data - -# RFC that's evaluated in IESG -200 /doc/rfc3852/ -200 /doc/rfc3852/doc.json -200 /doc/rfc3852/ballot.json -200 /doc/rfc3852/_ballot.data - -# old RFC -200 /doc/rfc822/ -200 /doc/rfc822/doc.json - -# ballot sets -200 /doc/rfc3550/ballot.json -200 /doc/rfc3550/_ballot.data -200 /doc/rfc3551/ballot.json -200 /doc/rfc3551/_ballot.data -200 /doc/draft-irtf-dtnrg-ltp/ballot.json -200 /doc/draft-irtf-dtnrg-ltp/_ballot.data - -# file formats -200 /doc/rfc9/ # PDF only -200 /doc/rfc2490/ # TXT+PDF+PS -200 /doc/rfc500/ # not online - -404 /doc/draft-no-such-draft/ -404 /doc/rfc4637/ - -200 /doc/rfc2444/doc.json # foreignkey problem with Django 1.x - -# current AD -- needs to be updated at some point -200 /doc/ad/robert.sparks/ -# former AD -200 /doc/ad/sam.hartman/ -404 /doc/ad/no.body/ - -# ballot exists, but it's not issued -404 /doc/draft-ietf-aaa-diameter-api/ballot.json -404 /doc/draft-ietf-aaa-diameter-api/_ballot.data -# ballot does not exist -404 /doc/draft-zeilenga-cldap/ballot.json -404 /doc/draft-zeilenga-cldap/_ballot.data -# comment with created_by=999 -200 /doc/draft-ietf-l3vpn-2547bis-mcast-bgp/ -# comment with created_by=0 (and no idinternal entry) -200 /doc/draft-ietf-proto-wgdocument-states/ - -200 /doc/search/ -200 /doc/search/?rfcs=on&name=snmp -200 /doc/search/?rfcs=on&name=nfs&by=ad&ad=53 -200 /doc/search/?activeDrafts=on&name=sipping -200 /doc/search/?oldDrafts=on&name=tls -200 /doc/search/?activeDrafts=on&oldDrafts=on&ad=53&by=ad -200 /doc/search/?activeDrafts=on&state=20&by=state -200 /doc/search/?activeDrafts=on&oldDrafts=on&subState=5&by=state -200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&ad=53&name=nfs&by=ad -200 /doc/search/?rfcs=on&group=tls&by=group -200 /doc/search/?activeDrafts=on&group=tls&by=group -200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&author=eronen&by=author -200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&area=934&name=ldap&by=area -200 /doc/search/?activeDrafts=on&name=asdfsadfsdfasdf -200 /doc/search/?activeDrafts=on&name=%EF%BD%8C #non-ASCII - -# Test case for missing publication date -200 /doc/search/?oldDrafts=on&name=ppvpn diff --git a/ietf/idrfc/testurlREDESIGN.list b/ietf/idrfc/testurlREDESIGN.list index 5b9b6a930..b44c197d0 100644 --- a/ietf/idrfc/testurlREDESIGN.list +++ b/ietf/idrfc/testurlREDESIGN.list @@ -3,71 +3,12 @@ 200,heavy /doc/all/ 200,heavy /doc/active/ -# draft that's now RFC -200 /doc/draft-ietf-avt-rtp-atrac-family/ -200 /doc/draft-ietf-avt-rtp-atrac-family/doc.json -200 /doc/draft-ietf-avt-rtp-atrac-family/ballot.json -200 /doc/draft-ietf-avt-rtp-atrac-family/ballotpopup/ - -# replaced draft, never went to IESG -200 /doc/draft-eronen-mobike-mopo/ -404 /doc/draft-eronen-mobike-mopo/ballot.json -404 /doc/draft-eronen-mobike-mopo/ballotpopup/ - -# expired draft -200 /doc/draft-eronen-eap-sim-aka-80211/ - -# Normal RFC -200 /doc/rfc4739/ -200 /doc/rfc4739/doc.json -200 /doc/rfc4739/ballot.json # has ballot from I-D -200 /doc/rfc4739/ballotpopup/ - -# RFC that's evaluated in IESG -200 /doc/rfc3852/ -200 /doc/rfc3852/doc.json -200 /doc/rfc3852/ballot.json -200 /doc/rfc3852/ballotpopup/ - -# old RFC -200 /doc/rfc822/ -200 /doc/rfc822/doc.json - -# ballot sets -200 /doc/rfc3550/ballot.json -200 /doc/rfc3550/ballotpopup/ -200 /doc/rfc3551/ballot.json -200 /doc/rfc3551/ballotpopup/ -200 /doc/draft-irtf-dtnrg-ltp/ballot.json -200 /doc/draft-irtf-dtnrg-ltp/ballotpopup/ - -# file formats -200 /doc/rfc9/ # PDF only -200 /doc/rfc2490/ # TXT+PDF+PS -200 /doc/rfc500/ # not online - -404 /doc/draft-no-such-draft/ -404 /doc/rfc4637/ - -200 /doc/rfc2444/doc.json # foreignkey problem with Django 1.x - # current AD -- needs to be updated at some point 200 /doc/ad/robert.sparks/ # former AD 200 /doc/ad/sam.hartman/ 404 /doc/ad/no.body/ -# ballot exists, but it's not issued -404 /doc/draft-ietf-aaa-diameter-api/ballot.json -404 /doc/draft-ietf-aaa-diameter-api/ballotpopup/ -# ballot does not exist -404 /doc/draft-zeilenga-cldap/ballot.json -404 /doc/draft-zeilenga-cldap/ballotpopup/ -# comment with created_by=999 -200 /doc/draft-ietf-l3vpn-2547bis-mcast-bgp/ -# comment with created_by=0 (and no idinternal entry) -200 /doc/draft-ietf-proto-wgdocument-states/ - 200 /doc/search/ 200 /doc/search/?rfcs=on&name=snmp 200 /doc/search/?rfcs=on&name=nfs&by=ad&ad=112773 diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py index 3e628a39d..71cabcb9a 100644 --- a/ietf/idrfc/urls.py +++ b/ietf/idrfc/urls.py @@ -31,9 +31,10 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from django.conf.urls.defaults import patterns, url, include -from ietf.idrfc import views_doc, views_search, views_edit, views_ballot, views +from ietf.idrfc import views_search, views_edit, views_ballot, views from ietf.doc.models import State from ietf.doc import views_status_change +from ietf.doc import views_doc urlpatterns = patterns('', (r'^/?$', views_search.search_main), @@ -56,8 +57,7 @@ urlpatterns = patterns('', url(r'^(?P[A-Za-z0-9._+-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"), (r'^(?P[A-Za-z0-9._+-]+)/doc.json$', views_doc.document_json), (r'^(?P[A-Za-z0-9._+-]+)/ballotpopup/$', views_doc.ballot_for_popup), - (r'^(?P[A-Za-z0-9._+-]+)/ballot.tsv$', views_doc.ballot_tsv), - (r'^(?P[A-Za-z0-9._+-]+)/ballot.json$', views_doc.ballot_json), + #(r'^(?P[A-Za-z0-9._+-]+)/ballot.json$', views_doc.ballot_json), # legacy view url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/$', views_edit.change_state, name='doc_change_state'), # IESG state url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/(?Piana-action|iana-review)/$', views_edit.change_iana_state, name='doc_change_iana_state'), diff --git a/ietf/idrfc/utils.py b/ietf/idrfc/utils.py index b9143bf39..e0fababef 100644 --- a/ietf/idrfc/utils.py +++ b/ietf/idrfc/utils.py @@ -2,7 +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 +from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream def add_document_comment(request, doc, text, ballot=None): if request: @@ -177,27 +177,3 @@ 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_base(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) - ) - -can_edit_intended_std_level = can_edit_base -can_edit_consensus = can_edit_base -can_edit_shepherd = can_edit_base - -def can_edit_shepherd_writeup(doc, user): - if user.is_authenticated(): - if Person.objects.filter(user=user).count(): - return can_edit_base(doc,user) or (doc.shepherd==user.person) - return False - -def nice_consensus(consensus): - mapping = { - None: "Unknown", - True: "Yes", - False: "No" - } - return mapping[consensus] - diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 86dccd70e..fce68359f 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -26,7 +26,6 @@ from ietf.ipr.search import iprs_from_docs from ietf.idrfc.mails import * from ietf.idrfc.utils import * from ietf.idrfc.lastcall import request_last_call -from ietf.idrfc.idrfc_wrapper import BallotWrapper from ietf.doc.utils import * from ietf.doc.models import * diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py deleted file mode 100644 index 6f921f0c5..000000000 --- a/ietf/idrfc/views_doc.py +++ /dev/null @@ -1,709 +0,0 @@ -# Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -# All rights reserved. Contact: Pasi Eronen -# -# 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. - -import re, os, datetime - -from django.http import HttpResponse, Http404 -from django.shortcuts import render_to_response, get_object_or_404, redirect -from django.template import RequestContext -from django.template.loader import render_to_string -from django.template.defaultfilters import truncatewords_html -from django.utils import simplejson as json -from django.utils.decorators import decorator_from_middleware -from django.middleware.gzip import GZipMiddleware -from django.core.urlresolvers import reverse as urlreverse, NoReverseMatch -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 -from ietf.doc.models import * -from ietf.doc.utils import * -from ietf.utils.history import find_history_active_at -from ietf.ietfauth.decorators import has_role -from ietf.doc.views_status_change import RELATION_SLUGS as status_change_relationships -from ietf.wgcharter.utils import historic_milestones_for_charter - -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)) - - ballot = doc.latest_event(BallotDocEvent, type="created_ballot") - if doc.type_id in ("draft","conflrev","statchg"): - # if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot: - tabs.append(("IESG Evaluation Record", "ballot", urlreverse("ietf.idrfc.views_doc.document_ballot", kwargs=dict(name=name)), ballot)) - elif doc.type_id == "charter": - tabs.append(("IESG Review", "ballot", urlreverse("ietf.idrfc.views_doc.document_ballot", kwargs=dict(name=name)), ballot)) - - # FIXME: if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot: - if doc.type_id not in ("conflrev","statchg"): - tabs.append(("IESG Writeups", "writeup", urlreverse("ietf.idrfc.views_doc.document_writeup", kwargs=dict(name=doc.name)), True)) - - tabs.append(("History", "history", urlreverse("ietf.idrfc.views_doc.document_history", kwargs=dict(name=doc.name)), True)) - - name = doc.canonical_name() - if name.startswith("rfc"): - name = "RFC %s" % name[3:] - else: - name += "-" + doc.rev - - return render_to_string("idrfc/document_top.html", - dict(doc=doc, - tabs=tabs, - selected=tab, - name=name)) - - -def document_main(request, name, rev=None): - if name.lower().startswith("draft") or name.lower().startswith("rfc"): - if rev != None: # no support for old revisions at the moment - raise Http404() - return document_main_idrfc(request, name, tab="document") - - orig_doc = doc = get_object_or_404(Document, docalias__name=name) - group = doc.group - if doc.type_id == 'conflrev': - conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document - - revisions = [] - for h in doc.history_set.order_by("time", "id"): - if h.rev and not h.rev in revisions: - revisions.append(h.rev) - if not doc.rev in revisions: - revisions.append(doc.rev) - - snapshot = False - - if rev != None: - if rev == doc.rev: - return redirect('doc_view', name=name) - - # find the entry in the history - for h in doc.history_set.order_by("-time"): - if rev == h.rev: - snapshot = True - doc = h - break - - if not snapshot: - return redirect('doc_view', name=name) - - if doc.type_id == "charter": - # find old group, too - gh = find_history_active_at(doc.group, doc.time) - if gh: - group = gh - - top = render_document_top(request, doc, "document", name) - - - - telechat = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat") - if telechat and not telechat.telechat_date: - telechat = None - if telechat and telechat.telechat_date < datetime.date.today(): - telechat = None - - - if doc.type_id == "charter": - filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev) - - content = _get_html(filename, os.path.join(settings.CHARTER_PATH, filename), split=False) - - ballot_summary = None - if doc.get_state_slug() in ("intrev", "iesgrev"): - active_ballot = doc.active_ballot() - if active_ballot: - ballot_summary = needed_ballot_positions(doc, active_ballot.active_ad_positions().values()) - else: - ballot_summary = "No active ballot found." - - chartering = get_chartering_type(doc) - - # inject milestones from group - milestones = historic_milestones_for_charter(orig_doc, doc.rev) - - return render_to_response("idrfc/document_charter.html", - dict(doc=doc, - top=top, - chartering=chartering, - content=content, - txt_url=settings.CHARTER_TXT_URL + filename, - revisions=revisions, - snapshot=snapshot, - telechat=telechat, - ballot_summary=ballot_summary, - group=group, - milestones=milestones, - ), - context_instance=RequestContext(request)) - - if doc.type_id == "conflrev": - filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev) - pathname = os.path.join(settings.CONFLICT_REVIEW_PATH,filename) - - if doc.rev == "00" and not os.path.isfile(pathname): - # This could move to a template - content = "A conflict review response has not yet been proposed." - else: - content = _get_html(filename, pathname, split=False) - - ballot_summary = None - if doc.get_state_slug() in ("iesgeval"): - ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values()) - - return render_to_response("idrfc/document_conflict_review.html", - dict(doc=doc, - top=top, - content=content, - revisions=revisions, - snapshot=snapshot, - telechat=telechat, - conflictdoc=conflictdoc, - ballot_summary=ballot_summary, - approved_states=('appr-reqnopub-pend','appr-reqnopub-sent','appr-noprob-pend','appr-noprob-sent') - ), - context_instance=RequestContext(request)) - - if doc.type_id == "statchg": - filename = "%s-%s.txt" % (doc.canonical_name(), doc.rev) - pathname = os.path.join(settings.STATUS_CHANGE_PATH,filename) - - if doc.rev == "00" and not os.path.isfile(pathname): - # This could move to a template - content = "Status change text has not yet been proposed." - else: - content = _get_html(filename, pathname, split=False) - - ballot_summary = None - if doc.get_state_slug() in ("iesgeval"): - ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values()) - - if isinstance(doc,Document): - sorted_relations=doc.relateddocument_set.all().order_by('relationship__name') - elif isinstance(doc,DocHistory): - sorted_relations=doc.relateddochistory_set.all().order_by('relationship__name') - else: - sorted_relations=None - - return render_to_response("idrfc/document_status_change.html", - dict(doc=doc, - top=top, - content=content, - revisions=revisions, - snapshot=snapshot, - telechat=telechat, - ballot_summary=ballot_summary, - approved_states=('appr-pend','appr-sent'), - sorted_relations=sorted_relations, - ), - context_instance=RequestContext(request)) - - raise Http404() - - -def document_history(request, name): - # todo: remove need for specific handling of drafts by porting the - # two event text hacks - if name.lower().startswith("draft") or name.lower().startswith("rfc"): - return document_main_idrfc(request, name, "history") - - doc = get_object_or_404(Document, docalias__name=name) - top = render_document_top(request, doc, "history", name) - - # pick up revisions from events - diff_revisions = [] - - diffable = name.startswith("draft") or name.startswith("charter") or name.startswith("conflict-review") or name.startswith("status-change") - if diffable: - diff_documents = [ doc ] - diff_documents.extend(Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces")) - - seen = set() - for e in NewRevisionDocEvent.objects.filter(type="new_revision", doc__in=diff_documents).select_related('doc').order_by("-time", "-id"): - if (e.doc.name, e.rev) in seen: - continue - - seen.add((e.doc.name, e.rev)) - - url = "" - if name.startswith("charter"): - h = find_history_active_at(e.doc, e.time) - url = settings.CHARTER_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev)) - elif name.startswith("conflict-review"): - h = find_history_active_at(e.doc, e.time) - url = settings.CONFLICT_REVIEW_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev)) - elif name.startswith("status-change"): - h = find_history_active_at(e.doc, e.time) - url = settings.STATUS_CHANGE_TXT_URL + ("%s-%s.txt" % ((h or doc).canonical_name(), e.rev)) - elif name.startswith("draft"): - # rfcdiff tool has special support for IDs - url = e.doc.name + "-" + e.rev - - diff_revisions.append((e.doc.name, e.rev, e.time, url)) - - # grab event history - events = doc.docevent_set.all().order_by("-time", "-id").select_related("by") - - augment_events_with_revision(doc, events) - - return render_to_response("idrfc/document_history.html", - dict(doc=doc, - top=top, - diff_revisions=diff_revisions, - events=events, - ), - context_instance=RequestContext(request)) - -def document_writeup(request, name): - if name.lower().startswith("draft") or name.lower().startswith("rfc"): - # todo: migrate idrfc to pattern below - return document_main_idrfc(request, name, "writeup") - - doc = get_object_or_404(Document, docalias__name=name) - top = render_document_top(request, doc, "writeup", name) - - writeups = [] - if doc.type_id == "charter": - e = doc.latest_event(WriteupDocEvent, type="changed_review_announcement") - writeups.append(("WG Review Announcement", - e.text if e else "", - urlreverse("ietf.wgcharter.views.announcement_text", kwargs=dict(name=doc.name, ann="review")))) - - e = doc.latest_event(WriteupDocEvent, type="changed_action_announcement") - writeups.append(("WG Action Announcement", - e.text if e else "", - urlreverse("ietf.wgcharter.views.announcement_text", kwargs=dict(name=doc.name, ann="action")))) - - if doc.latest_event(BallotDocEvent, type="created_ballot"): - e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") - writeups.append(("Ballot Announcement", - e.text if e else "", - urlreverse("ietf.wgcharter.views.ballot_writeupnotes", kwargs=dict(name=doc.name)))) - - if not writeups: - raise Http404() - - return render_to_response("idrfc/document_writeup.html", - dict(doc=doc, - top=top, - writeups=writeups, - can_edit=has_role(request.user, ("Area Director", "Secretariat")), - ), - context_instance=RequestContext(request)) - -def document_shepherd_writeup(request, name): - doc = get_object_or_404(Document, docalias__name=name) - lastwriteup = doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup") - if lastwriteup: - writeup_text = lastwriteup.text - else: - writeup_text = "(There is no shepherd's writeup available for this document)" - return render_to_response("idrfc/shepherd_writeup.html", - dict(doc=doc, - writeup=writeup_text, - can_edit=can_edit_shepherd_writeup(doc,request.user) - ), - context_instance=RequestContext(request)) - - -def document_ballot_content(request, doc, ballot_id, editable=True): - """Render HTML string with content of ballot page.""" - all_ballots = list(BallotDocEvent.objects.filter(doc=doc, type="created_ballot").order_by("time")) - augment_events_with_revision(doc, all_ballots) - - ballot = None - if ballot_id != None: - ballot_id = int(ballot_id) - for b in all_ballots: - if b.id == ballot_id: - ballot = b - break - elif all_ballots: - ballot = all_ballots[-1] - - if not ballot: - raise Http404 - - deferred = doc.active_defer_event() - - positions = ballot.all_positions() - - # put into position groups - position_groups = [] - for n in BallotPositionName.objects.filter(slug__in=[p.pos_id for p in positions]).order_by('order'): - g = (n, [p for p in positions if p.pos_id == n.slug]) - g[1].sort(key=lambda p: (p.old_ad, p.ad.plain_name())) - if n.blocking: - position_groups.insert(0, g) - else: - position_groups.append(g) - - summary = needed_ballot_positions(doc, [p for p in positions if not p.old_ad]) - - text_positions = [p for p in positions if p.discuss or p.comment] - text_positions.sort(key=lambda p: (p.old_ad, p.ad.plain_name())) - - ballot_open = not BallotDocEvent.objects.filter(doc=doc, - type__in=("closed_ballot", "created_ballot"), - time__gt=ballot.time, - ballot_type=ballot.ballot_type) - if not ballot_open: - editable = False - - return render_to_string("idrfc/document_ballot_content.html", - dict(doc=doc, - ballot=ballot, - position_groups=position_groups, - text_positions=text_positions, - editable=editable, - ballot_open=ballot_open, - deferred=deferred, - summary=summary, - all_ballots=all_ballots, - ), - context_instance=RequestContext(request)) - -def document_ballot(request, name, ballot_id=None): - doc = get_object_or_404(Document, docalias__name=name) - top = render_document_top(request, doc, "ballot", name) - - c = document_ballot_content(request, doc, ballot_id, editable=True) - - return render_to_response("idrfc/document_ballot.html", - dict(doc=doc, - top=top, - ballot_content=c, - ), - context_instance=RequestContext(request)) - -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) - -def include_text(request): - include_text = request.GET.get( 'include_text' ) - if "full_draft" in request.COOKIES: - if request.COOKIES["full_draft"] == "on": - include_text = 1 - return include_text - -def document_main_rfc(request, rfc_number, tab): - rfci = get_object_or_404(RfcIndex, rfc_number=rfc_number, states__type="draft", states__slug="rfc") - rfci.viewing_as_rfc = True - doc = RfcWrapper(rfci) - - info = {} - info['is_rfc'] = True - info['has_pdf'] = (".pdf" in doc.file_types()) - info['has_txt'] = (".txt" in doc.file_types()) - info['has_ps'] = (".ps" in doc.file_types()) - if info['has_txt']: - (content1, content2) = _get_html( - "rfc"+str(rfc_number)+",html", - os.path.join(settings.RFC_PATH, "rfc"+str(rfc_number)+".txt")) - else: - content1 = "" - content2 = "" - - underlying_document = doc.underlying_document() - if underlying_document: - info['status_changes'] = ', '.join([ rel.source.canonical_name() for rel in RelatedDocument.objects.filter(relationship__in=status_change_relationships,target__document=underlying_document) if rel.source.get_state_slug() in ('appr-sent','appr-pend')]) - info['proposed_status_changes'] = ', '.join([ rel.source.canonical_name() for rel in RelatedDocument.objects.filter(relationship__in=status_change_relationships,target__document=underlying_document) if rel.source.get_state_slug() in ('needshep','adrev','iesgeval','defer','appr-pr')]) - - history = _get_history(doc, None) - - template = "idrfc/doc_tab_%s" % tab - if tab == "document": - template += "_rfc" - return render_to_response(template + ".html", - {'content1':content1, 'content2':content2, - 'doc':doc, 'info':info, 'tab':tab, - 'include_text':include_text(request), - 'history':history}, - context_instance=RequestContext(request)); - -@decorator_from_middleware(GZipMiddleware) -def document_main_idrfc(request, name, tab): - r = re.compile("^rfc([1-9][0-9]*)$") - m = r.match(name) - if m: - return document_main_rfc(request, int(m.group(1)), tab) - id = get_object_or_404(InternetDraft, filename=name) - doc = IdWrapper(id) - - info = {} - info['has_pdf'] = (".pdf" in doc.file_types()) - info['is_rfc'] = False - - info['conflict_reviews'] = [ rel.source for alias in id.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='conflrev') ] - 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) - info["can_edit_shepherd"] = can_edit_shepherd(id, request.user) - - (content1, content2) = _get_html( - str(name)+","+str(id.revision)+",html", - os.path.join(settings.INTERNET_DRAFT_PATH, name+"-"+id.revision+".txt")) - - versions = _get_versions(id) - history = _get_history(doc, versions) - - template = "idrfc/doc_tab_%s" % tab - if tab == "document": - template += "_id" - return render_to_response(template + ".html", - {'content1':content1, 'content2':content2, - 'doc':doc, 'info':info, 'tab':tab, - 'include_text':include_text(request), - 'stream_info': get_full_info_for_draft(id), - 'milestones': id.groupmilestone_set.filter(state="active"), - 'versions':versions, 'history':history}, - context_instance=RequestContext(request)); - -# doc is either IdWrapper or RfcWrapper -def _get_history(doc, versions): - results = [] - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - versions = [] # clear versions - event_holder = doc._draft if hasattr(doc, "_draft") else doc._rfcindex - for e in event_holder.docevent_set.all().select_related('by').order_by('-time', 'id'): - info = {} - if e.type == "new_revision": - filename = u"%s-%s" % (e.doc.name, e.newrevisiondocevent.rev) - e.desc = 'New version available: %s' % (filename, filename) - if int(e.newrevisiondocevent.rev) != 0: - e.desc += ' (diff from -%02d)' % (settings.RFCDIFF_PREFIX, filename, int(e.newrevisiondocevent.rev) - 1) - info["dontmolest"] = True - - multiset_ballot_text = "This was part of a ballot set with: " - if e.desc.startswith(multiset_ballot_text): - names = [ n.strip() for n in e.desc[len(multiset_ballot_text):].split(",") ] - e.desc = multiset_ballot_text + ", ".join(u'%s' % (urlreverse("doc_view", kwargs={'name': n }), n) for n in names) - info["dontmolest"] = True - - info['text'] = e.desc - info['by'] = e.by.plain_name() - info['textSnippet'] = truncatewords_html(info['text'], 25).replace('
',' ') - info['snipped'] = info['textSnippet'][-3:] == "..." and e.type != "new_revision" - results.append({'comment':e, 'info':info, 'date':e.time, 'is_com':True}) - - prev_rev = "00" - # actually, we're already sorted and this ruins the sort from - # the ids which is sometimes needed, so the function should be - # rewritten to not rely on a resort - results.sort(key=lambda x: x['date']) - for o in results: - e = o["comment"] - if e.type == "new_revision": - e.version = e.newrevisiondocevent.rev - else: - e.version = prev_rev - prev_rev = e.version - else: - if doc.is_id_wrapper: - comments = DocumentComment.objects.filter(document=doc.tracker_id).exclude(rfc_flag=1) - else: - comments = DocumentComment.objects.filter(document=doc.rfc_number,rfc_flag=1) - if len(comments) > 0: - # also include rfc_flag=NULL, but only if at least one - # comment with rfc_flag=1 exists (usually NULL means same as 0) - comments = DocumentComment.objects.filter(document=doc.rfc_number).exclude(rfc_flag=0) - for comment in comments.order_by('-date','-time','-id').filter(public_flag=1).select_related('created_by'): - info = {} - info['text'] = comment.comment_text - info['by'] = comment.get_fullname() - info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25) - info['snipped'] = info['textSnippet'][-3:] == "..." - results.append({'comment':comment, 'info':info, 'date':comment.datetime(), 'is_com':True}) - - if doc.is_id_wrapper and versions: - for v in versions: - if v['draft_name'] == doc.draft_name: - v = dict(v) # copy it, since we're modifying datetimes later - v['is_rev'] = True - results.insert(0, v) - if not settings.USE_DB_REDESIGN_PROXY_CLASSES and doc.is_id_wrapper and doc.draft_status == "Expired" and doc._draft.expiration_date: - results.append({'is_text':True, 'date':doc._draft.expiration_date, 'text':'Draft expired'}) - if not settings.USE_DB_REDESIGN_PROXY_CLASSES and doc.is_rfc_wrapper: - text = 'RFC Published' - if doc.draft_name: - try: - text = 'RFC Published (see %s for earlier history)' % (urlreverse('doc_view', args=[doc.draft_name]),doc.draft_name) - except NoReverseMatch: - pass - results.append({'is_text':True, 'date':doc.publication_date, 'text':text}) - - # convert plain dates to datetimes (required for sorting) - for x in results: - if not isinstance(x['date'], datetime.datetime): - if x['date']: - x['date'] = datetime.datetime.combine(x['date'], datetime.time(0,0,0)) - else: - x['date'] = datetime.datetime(1970,1,1) - - results.sort(key=lambda x: x['date']) - results.reverse() - return results - -# takes InternetDraft instance -def _get_versions(draft, include_replaced=True): - ov = [] - ov.append({"draft_name":draft.filename, "revision":draft.revision_display(), "date":draft.revision_date}) - if include_replaced: - draft_list = [draft]+list(draft.replaces_set.all()) - else: - draft_list = [draft] - - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.doc.models import NewRevisionDocEvent - for e in NewRevisionDocEvent.objects.filter(type="new_revision", doc__in=draft_list).select_related('doc').order_by("-time", "-id"): - if not (e.doc.name == draft.name and e.rev == draft.rev): - ov.append(dict(draft_name=e.doc.name, revision=e.rev, date=e.time.date())) - - return ov - - for d in draft_list: - for v in DraftVersions.objects.filter(filename=d.filename).order_by('-revision'): - if (d.filename == draft.filename) and (draft.revision_display() == v.revision): - continue - ov.append({"draft_name":d.filename, "revision":v.revision, "date":v.revision_date}) - return ov - -def get_ballot(name): - from ietf.doc.models import DocAlias - alias = get_object_or_404(DocAlias, name=name) - d = alias.document - id = None - bw = None - dw = None - if (d.type_id=='draft'): - id = get_object_or_404(InternetDraft, name=d.name) - try: - if not id.ballot.ballot_issued: - raise Http404 - except BallotInfo.DoesNotExist: - raise Http404 - - bw = BallotWrapper(id) # XXX Fixme: Eliminate this as we go forward - # Python caches ~100 regex'es -- explicitly compiling it inside a method - # (where you then throw away the compiled version!) doesn't make sense at - # all. - if re.search("^rfc([1-9][0-9]*)$", name): - id.viewing_as_rfc = True - dw = RfcWrapper(id) - else: - dw = IdWrapper(id) - # XXX Fixme: Eliminate 'dw' as we go forward - - try: - b = d.latest_event(BallotDocEvent, type="created_ballot") - except BallotDocEvent.DoesNotExist: - raise Http404 - - return (bw, dw, b, d) - - -def ballot_for_popup(request, name): - doc = get_object_or_404(Document, docalias__name=name) - return HttpResponse(document_ballot_content(request, doc, ballot_id=None, editable=False)) - -def ballot_html(request, name): - bw, dw, ballot, doc = get_ballot(name) - content = document_ballot_content(request, doc, ballot.pk, editable=True) - return HttpResponse(content) - -def ballot_tsv(request, name): - ballot, doc, b, d = get_ballot(name) - return HttpResponse(render_to_string('idrfc/ballot.tsv', {'ballot':ballot}, RequestContext(request)), content_type="text/plain") - -def ballot_json(request, name): - ballot, doc, b, d = get_ballot(name) - response = HttpResponse(mimetype='text/plain') - response.write(json.dumps(ballot.dict(), indent=2)) - return response - diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index c5c87d371..e6e781c83 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -13,11 +13,12 @@ from django.utils.html import strip_tags from django.db.models import Max from django.conf import settings from django.forms.util import ErrorList +from django.contrib.auth.decorators import login_required from ietf.utils.mail import send_mail_text, send_mail_message -from ietf.ietfauth.decorators import group_required +from ietf.ietfauth.decorators import group_required, has_role, role_required +from ietf.ietfauth.utils import user_is_person from ietf.idtracker.templatetags.ietf_filters import in_group -from ietf.ietfauth.decorators import has_role, role_required from ietf.idtracker.models import * from ietf.iesg.models import * from ietf.idrfc.mails import * @@ -225,14 +226,21 @@ class ChangeStreamForm(forms.Form): stream = forms.ModelChoiceField(StreamName.objects.exclude(slug="legacy"), required=False) comment = forms.CharField(widget=forms.Textarea, required=False) -@group_required('Area_Director','Secretariat') +@login_required def change_stream(request, name): - """Change the stream of a Document of type 'draft' , notifying parties as necessary + """Change the stream of a Document of type 'draft', notifying parties as necessary and logging the change as a comment.""" doc = get_object_or_404(Document, docalias__name=name) if not doc.type_id=='draft': raise Http404() + if not (has_role(request.user, ("Area Director", "Secretariat")) or + (request.user.is_authenticated() and + Role.objects.filter(name="chair", + group__acronym__in=StreamName.objects.values_list("slug", flat=True), + person__user=request.user))): + return HttpResponseForbidden("You do not have permission to view this page") + login = request.user.get_profile() if request.method == 'POST': @@ -248,7 +256,7 @@ def change_stream(request, name): doc.stream = new_stream e = DocEvent(doc=doc,by=login,type='changed_document') - e.desc = u"Stream changed to %s from %s"% (new_stream,old_stream) + e.desc = u"Stream changed to %s from %s"% (new_stream, old_stream or "None") e.save() email_desc = e.desc @@ -287,7 +295,8 @@ def change_intention(request, name): if doc.type_id != 'draft': raise Http404 - if not can_edit_intended_std_level(doc, request.user): + if not (has_role(request.user, ("Secretariat", "Area Director")) + or is_authorized_in_doc_stream(request.user, doc)): return HttpResponseForbidden("You do not have the necessary permissions to view this page") login = request.user.get_profile() @@ -396,8 +405,7 @@ class EditInfoFormREDESIGN(forms.Form): self.standard_fields = [x for x in self.visible_fields() if x.name not in ('returning_item',)] def clean_note(self): - # note is stored munged in the database - return self.cleaned_data['note'].replace('\n', '
').replace('\r', '').replace(' ', '  ') + return self.cleaned_data['note'].replace('\r', '').strip() def get_initial_notify(doc): @@ -532,7 +540,7 @@ def edit_infoREDESIGN(request, name): area=doc.group_id, ad=doc.ad_id, notify=doc.notify, - note=dehtmlify_textarea_text(doc.note), + note=doc.note, telechat_date=initial_telechat_date, returning_item=initial_returning_item, ) @@ -827,15 +835,16 @@ class IESGNoteForm(forms.Form): note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False) def clean_note(self): - # note is stored munged in the database - return self.cleaned_data['note'].replace('\n', '
').replace('\r', '').replace(' ', '  ') + # not muning the database content to use html line breaks -- + # that has caused a lot of pain in the past. + return self.cleaned_data['note'].replace('\r', '').strip() @group_required("Area Director", "Secretariat") def edit_iesg_note(request, name): doc = get_object_or_404(Document, type="draft", name=name) login = request.user.get_profile() - initial = dict(note=dehtmlify_textarea_text(doc.note)) + initial = dict(note=doc.note) if request.method == "POST": form = IESGNoteForm(request.POST, initial=initial) @@ -883,7 +892,10 @@ def edit_shepherd_writeup(request, name): """Change this document's shepherd writeup""" doc = get_object_or_404(Document, type="draft", name=name) - if not can_edit_shepherd_writeup(doc, request.user): + can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) + can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd) or has_role(request.user, ["Area Director"]) + + if not can_edit_shepherd_writeup: return HttpResponseForbidden("You do not have the necessary permissions to view this page") login = request.user.get_profile() @@ -950,7 +962,9 @@ def edit_shepherd(request, name): # TODO - this shouldn't be type="draft" specific doc = get_object_or_404(Document, type="draft", name=name) - if not can_edit_shepherd(doc, request.user): + can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc) + + if not can_edit_stream_info: return HttpResponseForbidden("You do not have the necessary permissions to view this page") if request.method == 'POST': @@ -1036,7 +1050,8 @@ def edit_consensus(request, name): doc = get_object_or_404(Document, type="draft", name=name) - if not can_edit_consensus(doc, request.user): + if not (has_role(request.user, ("Secretariat", "Area Director")) + or is_authorized_in_doc_stream(request.user, doc)): return HttpResponseForbidden("You do not have the necessary permissions to view this page") e = doc.latest_event(ConsensusDocEvent, type="changed_consensus") diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index 7acc084ce..8863545ab 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -44,6 +44,10 @@ from ietf.idrfc.idrfc_wrapper import IdWrapper,RfcWrapper,IdRfcWrapper from ietf.utils import normalize_draftname from django.conf import settings +from ietf.doc.models import * +from ietf.person.models import * +from ietf.group.models import * + class SearchForm(forms.Form): name = forms.CharField(required=False) rfcs = forms.BooleanField(required=False,initial=True) @@ -54,15 +58,27 @@ class SearchForm(forms.Form): by = forms.ChoiceField(choices=[(x,x) for x in ('author','group','area','ad','state')], required=False, initial='wg', label='Foobar') author = forms.CharField(required=False) group = forms.CharField(required=False) - area = forms.ModelChoiceField(Area.active_areas(), empty_label="any area", required=False) + area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), empty_label="any area", required=False) ad = forms.ChoiceField(choices=(), required=False) - state = forms.ModelChoiceField(IDState.objects.all(), empty_label="any state", required=False) + state = forms.ModelChoiceField(State.objects.filter(type="draft-iesg"), empty_label="any state", required=False) subState = forms.ChoiceField(choices=(), required=False) def __init__(self, *args, **kwargs): super(SearchForm, self).__init__(*args, **kwargs) - self.fields['ad'].choices = [('', 'any AD')] + [(ad.id, "%s %s" % (ad.first_name, ad.last_name)) for ad in IESGLogin.objects.filter(user_level=1).order_by('last_name')] + [('-99', '------------------')] + [(ad.id, "%s %s" % (ad.first_name, ad.last_name)) for ad in IESGLogin.objects.filter(user_level=2).order_by('last_name')] - self.fields['subState'].choices = [('', 'any substate'), ('0', 'no substate')] + [(state.sub_state_id, state.sub_state) for state in IDSubState.objects.all()] + responsible = Document.objects.values_list('ad', flat=True).distinct() + active_ads = list(Person.objects.filter(role__name="ad", + role__group__type="area", + role__group__state="active").distinct()) + inactive_ads = list(((Person.objects.filter(pk__in=responsible) | Person.objects.filter(role__name="pre-ad", + role__group__type="area", + role__group__state="active")).distinct()) + .exclude(pk__in=[x.pk for x in active_ads])) + extract_last_name = lambda x: x.name_parts()[3] + active_ads.sort(key=extract_last_name) + inactive_ads.sort(key=extract_last_name) + + self.fields['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.plain_name()) for ad in active_ads] + [('', '------------------')] + [(ad.pk, ad.name) for ad in inactive_ads] + self.fields['subState'].choices = [('', 'any substate'), ('0', 'no substate')] + [(n.slug, n.name) for n in DocTagName.objects.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))] def clean_name(self): value = self.cleaned_data.get('name','') return normalize_draftname(value) @@ -73,9 +89,9 @@ class SearchForm(forms.Form): q['by'] = None else: for k in ('author','group','area','ad'): - if (q['by'] == k) and not q[k]: + if (q['by'] == k) and (k not in q or not q[k]): q['by'] = None - if (q['by'] == 'state') and not (q['state'] or q['subState']): + if (q['by'] == 'state') and (not 'state' in q or not 'subState' in q or not (q['state'] or q['subState'])): q['by'] = None # Reset other fields for k in ('author','group','area','ad'): @@ -88,7 +104,7 @@ class SearchForm(forms.Form): q['state'] = "" q['subState'] = "" return q - + def search_query(query_original, sort_by=None): query = dict(query_original.items()) drafts = query['activeDrafts'] or query['oldDrafts'] @@ -97,6 +113,7 @@ def search_query(query_original, sort_by=None): # Non-ASCII strings don't match anything; this check # is currently needed to avoid complaints from MySQL. + # FIXME: this should be fixed with MySQL if it's still a problem? for k in ['name','author','group']: try: tmp = str(query.get(k, '')) @@ -107,388 +124,156 @@ def search_query(query_original, sort_by=None): idresults = [] rfcresults = [] MAX = 500 - maxReached = False - prefix = "" - q_objs = [] - if query['by'] in ('ad','state'): - prefix = "draft__" - if query['name']: - q_objs.append(Q(**{prefix+"filename__icontains":query['name']})|Q(**{prefix+"title__icontains":query['name']})) + docs = InternetDraft.objects.all() - if query['by'] == 'author': - q_objs.append(Q(**{prefix+"authors__person__last_name__icontains":query['author']})) - elif query['by'] == 'group': - q_objs.append(Q(**{prefix+"group__acronym":query['group']})) - elif query['by'] == 'area': - q_objs.append(Q(**{prefix+"group__ietfwg__areagroup__area":query['area']})) - elif query['by'] == 'ad': - q_objs.append(Q(job_owner=query['ad'])) - elif query['by'] == 'state': - if query['state']: - q_objs.append(Q(cur_state=query['state'])) - if query['subState']: - q_objs.append(Q(cur_sub_state=query['subState'])) - if (not query['rfcs']) and query['activeDrafts'] and (not query['oldDrafts']): - q_objs.append(Q(**{prefix+"status":1})) - elif query['rfcs'] and query['activeDrafts'] and (not query['oldDrafts']): - q_objs.append(Q(**{prefix+"status":1})|Q(**{prefix+"status":3})) - elif query['rfcs'] and (not drafts): - q_objs.append(Q(**{prefix+"status":3})) - if prefix: - q_objs.append(Q(rfc_flag=0)) - matches = IDInternal.objects.filter(*q_objs) - else: - matches = InternetDraft.objects.filter(*q_objs) - if not query['activeDrafts']: - matches = matches.exclude(Q(**{prefix+"status":1})) - if not query['rfcs']: - matches = matches.exclude(Q(**{prefix+"status":3})) - if prefix: - matches = [id.draft for id in matches[:MAX]] - else: - matches = matches[:MAX] - if len(matches) == MAX: - maxReached = True - for id in matches: - if id.status.status == 'RFC': - rfcresults.append([id.rfc_number, id, None, None]) + # name + if query["name"]: + docs = docs.filter(Q(docalias__name__icontains=query["name"]) | + Q(title__icontains=query["name"])).distinct() + + # rfc/active/old check buttons + allowed_states = [] + if query["rfcs"]: + allowed_states.append("rfc") + if query["activeDrafts"]: + allowed_states.append("active") + if query["oldDrafts"]: + allowed_states.extend(['repl', 'expired', 'auth-rm', 'ietf-rm']) + + docs = docs.filter(states__type="draft", states__slug__in=allowed_states) + + # radio choices + by = query["by"] + if by == "author": + # FIXME: this is full name, not last name as hinted in the HTML + docs = docs.filter(authors__person__name__icontains=query["author"]) + elif by == "group": + docs = docs.filter(group__acronym=query["group"]) + elif by == "area": + docs = docs.filter(Q(group__type="wg", group__parent=query["area"]) | + Q(group=query["area"])).distinct() + elif by == "ad": + docs = docs.filter(ad=query["ad"]) + elif by == "state": + if query["state"]: + docs = docs.filter(states=query["state"]) + if query["subState"]: + docs = docs.filter(tags=query["subState"]) + + # evaluate and fill in values with aggregate queries to avoid + # too many individual queries + results = list(docs.select_related("states", "ad", "ad__person", "std_level", "intended_std_level", "group", "stream")[:MAX]) + + rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[r.pk for r in results]).values_list("document_id", "name")) + # canonical name + for r in results: + if r.pk in rfc_aliases: + # lambda weirdness works around lambda binding in local for loop scope + r.canonical_name = (lambda x: lambda: x)(rfc_aliases[r.pk]) else: - idresults.append([id]) + r.canonical_name = (lambda x: lambda: x)(r.name) - # Next, search RFCs - if query['rfcs']: - q_objs = [] - searchRfcIndex = True - if query['name']: - r = re.compile("^\s*(?:RFC)?\s*(\d+)\s*$", re.IGNORECASE) - m = r.match(query['name']) - if m: - q_objs.append(Q(rfc_number__contains=m.group(1))|Q(title__icontains=query['name'])) + result_map = dict((r.pk, r) for r in results) + + # events + event_types = ("published_rfc", + "changed_ballot_position", + "started_iesg_process", + "new_revision") + for d in rfc_aliases.keys(): + for e in event_types: + setattr(result_map[d], e, None) + + for e in DocEvent.objects.filter(doc__in=rfc_aliases.keys(), type__in=event_types).order_by('-time'): + r = result_map[e.doc_id] + if not getattr(r, e.type): + # sets e.g. r.published_date = e for use in proxy wrapper + setattr(r, e.type, e) + + # obsoleted/updated by + for d in rfc_aliases: + r = result_map[d] + r.obsoleted_by_list = [] + r.updated_by_list = [] + + xed_by = RelatedDocument.objects.filter(target__name__in=rfc_aliases.values(), relationship__in=("obs", "updates")).select_related('target__document_id') + rel_rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[rel.source_id for rel in xed_by]).values_list('document_id', 'name')) + for rel in xed_by: + r = result_map[rel.target.document_id] + if rel.relationship_id == "obs": + attr = "obsoleted_by_list" + else: + attr = "updated_by_list" + + getattr(r, attr).append(int(rel_rfc_aliases[rel.source_id][3:])) + + + # sort + def sort_key(d): + res = [] + + canonical = d.canonical_name() + if canonical.startswith('rfc'): + rfc_num = int(canonical[3:]) + else: + rfc_num = None + + if rfc_num != None: + res.append(2) + elif d.get_state_slug() == "active": + res.append(1) + else: + res.append(3) + + if sort_by == "title": + res.append(d.title) + elif sort_by == "date": + res.append(str(d.revision_date or datetime.date(1990, 1, 1))) + elif sort_by == "status": + if rfc_num != None: + res.append(rfc_num) else: - q_objs.append(Q(title__icontains=query['name'])) - if query['by'] == 'author': - q_objs.append(Q(authors__icontains=query['author'])) - elif query['by'] == 'group': - # We prefer searching RfcIndex, but it doesn't have group info - searchRfcIndex = False - q_objs.append(Q(group_acronym=query['group'])) - elif query['by'] == 'area': - # Ditto for area - searchRfcIndex = False - q_objs.append(Q(area_acronym=query['area'])) - elif query['by'] == 'ad': - numbers = IDInternal.objects.filter(rfc_flag=1,job_owner=query['ad']).values_list('draft_id',flat=True) - q_objs.append(Q(rfc_number__in=numbers)) - elif query['by'] == 'state': - numbers_q = [Q(rfc_flag=1)] - if query['state']: - numbers_q.append(Q(cur_state=query['state'])) - if query['subState']: - numbers_q.append(Q(cur_state=query['subState'])) - numbers = IDInternal.objects.filter(*numbers_q).values_list('draft_id',flat=True) - q_objs.append(Q(rfc_number__in=numbers)) - - if searchRfcIndex: - matches = RfcIndex.objects.filter(*q_objs)[:MAX] - else: - matches = Rfc.objects.filter(*q_objs)[:MAX] - if len(matches) == MAX: - maxReached = True - for rfc in matches: - found = False - for r2 in rfcresults: - if r2[0] == rfc.rfc_number: - if searchRfcIndex: - r2[3] = rfc - else: - r2[2] = rfc - found = True - if not found: - if searchRfcIndex: - rfcresults.append([rfc.rfc_number, None, None, rfc]) + res.append(d.get_state().order) + elif sort_by == "ipr": + res.append(d.name) + elif sort_by == "ad": + if rfc_num != None: + res.append(rfc_num) + elif d.get_state_slug() == "active": + if d.get_state("draft-iesg"): + res.append(d.get_state("draft-iesg").order) else: - rfcresults.append([rfc.rfc_number, None, rfc, None]) - - # Find missing InternetDraft objects - for r in rfcresults: - if not r[1]: - ids = InternetDraft.objects.filter(rfc_number=r[0]) - if len(ids) >= 1: - r[1] = ids[0] - if not r[1] and r[3] and r[3].draft: - ids = InternetDraft.objects.filter(filename=r[3].draft) - if len(ids) >= 1: - r[1] = ids[0] - - # Finally, find missing RFC objects - for r in rfcresults: - if not r[2]: - rfcs = Rfc.objects.filter(rfc_number=r[0]) - if len(rfcs) >= 1: - r[2] = rfcs[0] - if not r[3]: - rfcs = RfcIndex.objects.filter(rfc_number=r[0]) - if len(rfcs) >= 1: - r[3] = rfcs[0] - - # TODO: require that RfcIndex is present? - - results = [] - for res in idresults+rfcresults: - if len(res)==1: - doc = IdRfcWrapper(IdWrapper(res[0]), None) - results.append(doc) + res.append(0) else: - d = None - r = None - if res[1]: - d = IdWrapper(res[1]) - if res[3]: - r = RfcWrapper(res[3]) - if d or r: - doc = IdRfcWrapper(d, r) - results.append(doc) - results.sort(key=lambda obj: obj.view_sort_key(sort_by)) - + if rfc_num != None: + res.append(rfc_num) + else: + res.append(canonical) + + return res + + results.sort(key=sort_key) + meta = {} - if maxReached: + if len(docs) == MAX: meta['max'] = MAX if query['by']: meta['advanced'] = True - return (results,meta) -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.doc.models import * - from ietf.person.models import * - from ietf.group.models import * + # finally wrap in old wrappers - class SearchForm(forms.Form): - name = forms.CharField(required=False) - rfcs = forms.BooleanField(required=False,initial=True) - activeDrafts = forms.BooleanField(required=False,initial=True) - oldDrafts = forms.BooleanField(required=False,initial=False) - lucky = forms.BooleanField(required=False,initial=False) + wrapped_results = [] + for r in results: + draft = None + rfc = None + if not r.name.startswith('rfc'): + draft = IdWrapper(r) + if r.name.startswith('rfc') or r.pk in rfc_aliases: + rfc = RfcWrapper(r) + wrapped_results.append(IdRfcWrapper(draft, rfc)) - by = forms.ChoiceField(choices=[(x,x) for x in ('author','group','area','ad','state')], required=False, initial='wg', label='Foobar') - author = forms.CharField(required=False) - group = forms.CharField(required=False) - area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), empty_label="any area", required=False) - ad = forms.ChoiceField(choices=(), required=False) - state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg"), empty_label="any state", required=False) - subState = forms.ChoiceField(choices=(), required=False) - - def __init__(self, *args, **kwargs): - super(SearchForm, self).__init__(*args, **kwargs) - responsible = Document.objects.values_list('ad', flat=True).distinct() - active_ads = list(Person.objects.filter(role__name="ad", - role__group__type="area", - role__group__state="active").distinct()) - inactive_ads = list(((Person.objects.filter(pk__in=responsible) | Person.objects.filter(role__name="pre-ad", - role__group__type="area", - role__group__state="active")).distinct()) - .exclude(pk__in=[x.pk for x in active_ads])) - extract_last_name = lambda x: x.name_parts()[3] - active_ads.sort(key=extract_last_name) - inactive_ads.sort(key=extract_last_name) - - self.fields['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.plain_name()) for ad in active_ads] + [('', '------------------')] + [(ad.pk, ad.name) for ad in inactive_ads] - self.fields['subState'].choices = [('', 'any substate'), ('0', 'no substate')] + [(n.slug, n.name) for n in DocTagName.objects.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))] - def clean_name(self): - value = self.cleaned_data.get('name','') - return normalize_draftname(value) - def clean(self): - q = self.cleaned_data - # Reset query['by'] if needed - if 'by' not in q: - q['by'] = None - else: - for k in ('author','group','area','ad'): - if (q['by'] == k) and (k not in q or not q[k]): - q['by'] = None - if (q['by'] == 'state') and (not 'state' in q or not 'subState' in q or not (q['state'] or q['subState'])): - q['by'] = None - # Reset other fields - for k in ('author','group','area','ad'): - if q['by'] != k: - self.data[k] = "" - q[k] = "" - if q['by'] != 'state': - self.data['state'] = "" - self.data['subState'] = "" - q['state'] = "" - q['subState'] = "" - return q - - def search_query(query_original, sort_by=None): - query = dict(query_original.items()) - drafts = query['activeDrafts'] or query['oldDrafts'] - if (not drafts) and (not query['rfcs']): - return ([], {}) - - # Non-ASCII strings don't match anything; this check - # is currently needed to avoid complaints from MySQL. - # FIXME: this should be fixed with MySQL if it's still a problem? - for k in ['name','author','group']: - try: - tmp = str(query.get(k, '')) - except: - query[k] = '*NOSUCH*' - - # Start by search InternetDrafts - idresults = [] - rfcresults = [] - MAX = 500 - - docs = InternetDraft.objects.all() - - # name - if query["name"]: - docs = docs.filter(Q(docalias__name__icontains=query["name"]) | - Q(title__icontains=query["name"])).distinct() - - # rfc/active/old check buttons - allowed_states = [] - if query["rfcs"]: - allowed_states.append("rfc") - if query["activeDrafts"]: - allowed_states.append("active") - if query["oldDrafts"]: - allowed_states.extend(['repl', 'expired', 'auth-rm', 'ietf-rm']) - - docs = docs.filter(states__type="draft", states__slug__in=allowed_states) - - # radio choices - by = query["by"] - if by == "author": - # FIXME: this is full name, not last name as hinted in the HTML - docs = docs.filter(authors__person__name__icontains=query["author"]) - elif by == "group": - docs = docs.filter(group__acronym=query["group"]) - elif by == "area": - docs = docs.filter(Q(group__type="wg", group__parent=query["area"]) | - Q(group=query["area"])).distinct() - elif by == "ad": - docs = docs.filter(ad=query["ad"]) - elif by == "state": - if query["state"]: - docs = docs.filter(states=query["state"]) - if query["subState"]: - docs = docs.filter(tags=query["subState"]) - - # evaluate and fill in values with aggregate queries to avoid - # too many individual queries - results = list(docs.select_related("states", "ad", "ad__person", "std_level", "intended_std_level", "group", "stream")[:MAX]) - - rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[r.pk for r in results]).values_list("document_id", "name")) - # canonical name - for r in results: - if r.pk in rfc_aliases: - # lambda weirdness works around lambda binding in local for loop scope - r.canonical_name = (lambda x: lambda: x)(rfc_aliases[r.pk]) - else: - r.canonical_name = (lambda x: lambda: x)(r.name) - - result_map = dict((r.pk, r) for r in results) - - # events - event_types = ("published_rfc", - "changed_ballot_position", - "started_iesg_process", - "new_revision") - for d in rfc_aliases.keys(): - for e in event_types: - setattr(result_map[d], e, None) - - for e in DocEvent.objects.filter(doc__in=rfc_aliases.keys(), type__in=event_types).order_by('-time'): - r = result_map[e.doc_id] - if not getattr(r, e.type): - # sets e.g. r.published_date = e for use in proxy wrapper - setattr(r, e.type, e) - - # obsoleted/updated by - for d in rfc_aliases: - r = result_map[d] - r.obsoleted_by_list = [] - r.updated_by_list = [] - - xed_by = RelatedDocument.objects.filter(target__name__in=rfc_aliases.values(), relationship__in=("obs", "updates")).select_related('target__document_id') - rel_rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[rel.source_id for rel in xed_by]).values_list('document_id', 'name')) - for rel in xed_by: - r = result_map[rel.target.document_id] - if rel.relationship_id == "obs": - attr = "obsoleted_by_list" - else: - attr = "updated_by_list" - - getattr(r, attr).append(int(rel_rfc_aliases[rel.source_id][3:])) - - - # sort - def sort_key(d): - res = [] - - canonical = d.canonical_name() - if canonical.startswith('rfc'): - rfc_num = int(canonical[3:]) - else: - rfc_num = None - - if rfc_num != None: - res.append(2) - elif d.get_state_slug() == "active": - res.append(1) - else: - res.append(3) - - if sort_by == "title": - res.append(d.title) - elif sort_by == "date": - res.append(str(d.revision_date or datetime.date(1990, 1, 1))) - elif sort_by == "status": - if rfc_num != None: - res.append(rfc_num) - else: - res.append(d.get_state().order) - elif sort_by == "ipr": - res.append(d.name) - elif sort_by == "ad": - if rfc_num != None: - res.append(rfc_num) - elif d.get_state_slug() == "active": - if d.get_state("draft-iesg"): - res.append(d.get_state("draft-iesg").order) - else: - res.append(0) - else: - if rfc_num != None: - res.append(rfc_num) - else: - res.append(canonical) - - return res - - results.sort(key=sort_key) - - meta = {} - if len(docs) == MAX: - meta['max'] = MAX - if query['by']: - meta['advanced'] = True - - # finally wrap in old wrappers - - wrapped_results = [] - for r in results: - draft = None - rfc = None - if not r.name.startswith('rfc'): - draft = IdWrapper(r) - if r.name.startswith('rfc') or r.pk in rfc_aliases: - rfc = RfcWrapper(r) - wrapped_results.append(IdRfcWrapper(draft, rfc)) - - return (wrapped_results, meta) + return (wrapped_results, meta) def generate_query_string(request, ignore_list): diff --git a/ietf/idtracker/templatetags/ietf_filters.py b/ietf/idtracker/templatetags/ietf_filters.py index b55f88aac..77ea5f946 100644 --- a/ietf/idtracker/templatetags/ietf_filters.py +++ b/ietf/idtracker/templatetags/ietf_filters.py @@ -4,7 +4,8 @@ import textwrap from django import template from django.conf import settings from django.utils.html import escape, fix_ampersands -from django.template.defaultfilters import linebreaksbr, wordwrap, stringfilter, urlize, truncatewords_html +from django.utils.text import truncate_html_words +from django.template.defaultfilters import linebreaksbr, wordwrap, stringfilter, urlize from django.template import resolve_variable from django.utils.safestring import mark_safe, SafeData from django.utils import simplejson @@ -221,9 +222,9 @@ def urlize_ietf_docs(string, autoescape=None): if autoescape and not isinstance(string, SafeData): string = escape(string) string = re.sub("(?)(RFC ?)0{0,3}(\d+)", "\\1\\2", string) - string = re.sub("(?)(BCP ?)0{0,3}(\d+)", "\\1\\2", string) - string = re.sub("(?)(STD ?)0{0,3}(\d+)", "\\1\\2", string) - string = re.sub("(?)(FYI ?)0{0,3}(\d+)", "\\1\\2", string) + string = re.sub("(?)(BCP ?)0{0,3}(\d+)", "\\1\\2", string) + string = re.sub("(?)(STD ?)0{0,3}(\d+)", "\\1\\2", string) + string = re.sub("(?)(FYI ?)0{0,3}(\d+)", "\\1\\2", string) string = re.sub("(?)(draft-[-0-9a-zA-Z._+]+)", "\\1", string) string = re.sub("(?)(conflict-review-[-0-9a-zA-Z._+]+)", "\\1", string) string = re.sub("(?)(status-change-[-0-9a-zA-Z._+]+)", "\\1", string) @@ -461,9 +462,14 @@ def ad_area(user): @register.filter def format_history_text(text): """Run history text through some cleaning and add ellipsis if it's too long.""" - full = mark_safe(sanitize_html(keep_spacing(linebreaksbr(urlize(mark_safe(text)))))) - snippet = truncatewords_html(format_textarea(text), 25) - if snippet[-3:] == "...": + full = mark_safe(text) + + if text.startswith("This was part of a ballot set with:"): + full = urlize_ietf_docs(full) + + full = mark_safe(keep_spacing(linebreaksbr(urlize(sanitize_html(full))))) + snippet = truncate_html_words(full, 25) + if snippet != full: return mark_safe(u'
%s[show all]
' % (snippet, full)) return full diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index 6294eb2ce..bd995abfb 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -246,9 +246,6 @@ def agenda_docs(date, next_agenda): section_key = "s"+get_doc_section(id) if section_key not in res: res[section_key] = [] - if id.note: - # TODO: Find out why this is _here_ - id.note = id.note.replace(u"\240",u" ") res[section_key].append({'obj':id}) return res diff --git a/ietf/ietfauth/decorators.py b/ietf/ietfauth/decorators.py index 973362c5b..a30cbb01b 100644 --- a/ietf/ietfauth/decorators.py +++ b/ietf/ietfauth/decorators.py @@ -30,87 +30,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from django.utils.http import urlquote -from django.conf import settings -from django.db.models import Q -from django.http import HttpResponseRedirect, HttpResponseForbidden -from django.contrib.auth import REDIRECT_FIELD_NAME - -def passes_test_decorator(test_func, message): - """ - Decorator creator that creates a decorator for checking that user - passes the test, redirecting to login or returning a 403 - error. The test function should be on the form fn(user) -> - true/false. - """ - def decorate(view_func): - def inner(request, *args, **kwargs): - if not request.user.is_authenticated(): - return HttpResponseRedirect('%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, urlquote(request.get_full_path()))) - elif test_func(request.user): - return view_func(request, *args, **kwargs) - else: - return HttpResponseForbidden(message) - return inner - return decorate - -def group_required(*group_names): - """Decorator for views that checks that the user is logged in, - and belongs to (at least) one of the listed groups.""" - return passes_test_decorator(lambda u: u.groups.filter(name__in=group_names), - "Restricted to group%s %s" % ("s" if len(group_names) != 1 else "", ",".join(group_names))) - - -def has_role(user, role_names): - """Determines whether user has any of the given standard roles - given. Role names must be a list or, in case of a single value, a - string.""" - if isinstance(role_names, str) or isinstance(role_names, unicode): - role_names = [ role_names ] - - if not user or not user.is_authenticated(): - return False - - if not hasattr(user, "roles_check_cache"): - user.roles_check_cache = {} - - key = frozenset(role_names) - if key not in user.roles_check_cache: - - from ietf.person.models import Person - from ietf.group.models import Role - - try: - person = user.get_profile() - except Person.DoesNotExist: - return False - - role_qs = { - "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"), - "WG Chair": Q(person=person,name="chair", group__type="wg", group__state="active"), - "WG Secretary": Q(person=person,name="secr", group__type="wg", group__state="active"), - } - - filter_expr = Q() - for r in role_names: - filter_expr |= role_qs[r] - - user.roles_check_cache[key] = bool(Role.objects.filter(filter_expr)[:1]) - - return user.roles_check_cache[key] - -def role_required(*role_names): - """View decorator for checking that the user is logged in and - has one of the listed roles.""" - return passes_test_decorator(lambda u: has_role(u, role_names), - "Restricted to role%s %s" % ("s" if len(role_names) != 1 else "", ", ".join(role_names))) - -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - # overwrite group_required - group_required = lambda *group_names: role_required(*[n.replace("Area_Director", "Area Director") for n in group_names]) +# REDESIGN: backwards compatibility, to be deleted +from ietf.ietfauth.utils import role_required, has_role, passes_test_decorator +group_required = lambda *group_names: role_required(*[n.replace("Area_Director", "Area Director") for n in group_names]) diff --git a/ietf/ietfauth/utils.py b/ietf/ietfauth/utils.py new file mode 100644 index 000000000..678e1a1f5 --- /dev/null +++ b/ietf/ietfauth/utils.py @@ -0,0 +1,116 @@ +# various authentication and authorization utilities + +from django.utils.http import urlquote +from django.conf import settings +from django.db.models import Q +from django.http import HttpResponseRedirect, HttpResponseForbidden +from django.contrib.auth import REDIRECT_FIELD_NAME + +from ietf.doc.models import Document +from ietf.person.models import Person +from ietf.group.models import Role + +def user_is_person(user, person): + """Test whether user is associated with person.""" + if not user.is_authenticated() or not person: + return False + + if person.user_id == None: + return False + + return person.user_id == user.id + +def has_role(user, role_names): + """Determines whether user has any of the given standard roles + given. Role names must be a list or, in case of a single value, a + string.""" + if isinstance(role_names, str) or isinstance(role_names, unicode): + role_names = [ role_names ] + + if not user or not user.is_authenticated(): + return False + + # use cache to avoid checking the same permissions again and again + if not hasattr(user, "roles_check_cache"): + user.roles_check_cache = {} + + key = frozenset(role_names) + if key not in user.roles_check_cache: + try: + person = user.get_profile() + except Person.DoesNotExist: + return False + + role_qs = { + "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"), + "WG Chair": Q(person=person,name="chair", group__type="wg", group__state="active"), + "WG Secretary": Q(person=person,name="secr", group__type="wg", group__state="active"), + } + + filter_expr = Q() + for r in role_names: + filter_expr |= role_qs[r] + + user.roles_check_cache[key] = bool(Role.objects.filter(filter_expr)[:1]) + + return user.roles_check_cache[key] + + +# convenient decorator + +def passes_test_decorator(test_func, message): + """Decorator creator that creates a decorator for checking that + user passes the test, redirecting to login or returning a 403 + error. The test function should be on the form fn(user) -> + true/false.""" + def decorate(view_func): + def inner(request, *args, **kwargs): + if not request.user.is_authenticated(): + return HttpResponseRedirect('%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, urlquote(request.get_full_path()))) + elif test_func(request.user): + return view_func(request, *args, **kwargs) + else: + return HttpResponseForbidden(message) + return inner + return decorate + +def role_required(*role_names): + """View decorator for checking that the user is logged in and + has one of the listed roles.""" + return passes_test_decorator(lambda u: has_role(u, role_names), + "Restricted to role%s %s" % ("s" if len(role_names) != 1 else "", ", ".join(role_names))) + +# specific permissions + +def is_authorized_in_doc_stream(user, doc): + """Return whether user is authorized to perform stream duties on + document.""" + if has_role(user, ["Secretariat"]): + return True + + if not doc.stream or not user.is_authenticated(): + return False + + # must be authorized in the stream or group + + group_req = None + + if doc.stream.slug == "ietf": + if not doc.group.type == "individ": + group_req = Q(group=doc.group) + elif doc.stream.slug == "irtf": + group_req = Q(group__acronym=doc.stream.slug) | Q(group=doc.group) + elif doc.stream.slug in ("iab", "ise"): + group_req = Q(group__acronym=doc.stream.slug) + + if not group_req: + return False + + return bool(Role.objects.filter(Q(name__in=("chair", "delegate", "auth"), person__user=user) & group_req)) + diff --git a/ietf/ietfworkflows/urls.py b/ietf/ietfworkflows/urls.py index ae339e45c..af7a885d4 100644 --- a/ietf/ietfworkflows/urls.py +++ b/ietf/ietfworkflows/urls.py @@ -6,6 +6,7 @@ from django.views.generic.simple import redirect_to urlpatterns = patterns('ietf.ietfworkflows.views', url(r'^(?P[^/]+)/history/$', 'stream_history', name='stream_history'), url(r'^(?P[^/]+)/edit/adopt/$', 'edit_adopt', name='edit_adopt'), + # FIXME: the name edit_state is far too generic url(r'^(?P[^/]+)/edit/state/$', 'edit_state', name='edit_state'), url(r'^(?P[^/]+)/edit/stream/$', redirect_to, { 'url': '/doc/%(name)s/edit/info/'}) , url(r'^delegates/(?P[^/]+)/$', 'stream_delegates', name='stream_delegates'), diff --git a/ietf/person/models.py b/ietf/person/models.py index d292019e5..28bafc5c3 100644 --- a/ietf/person/models.py +++ b/ietf/person/models.py @@ -111,4 +111,7 @@ class Email(models.Model): return u'"%s" <%s>' % (self.person.ascii, self.address) else: return self.address - + + def invalid_address(self): + # we have some legacy authors with unknown email addresses + return self.address.startswith("unknown-email") and "@" not in self.address diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py index 6e787bf58..8bd45b598 100644 --- a/ietf/submit/utils.py +++ b/ietf/submit/utils.py @@ -148,7 +148,7 @@ def perform_postREDESIGN(request, submission): 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.desc = "New version available: %s-%s.txt" % (draft.name, draft.rev) e.save() if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00": diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py index 6ac13611d..8bb29eb5f 100644 --- a/ietf/sync/rfceditor.py +++ b/ietf/sync/rfceditor.py @@ -195,11 +195,21 @@ def fetch_index_xml(url): return urllib2.urlopen(url) def parse_index(response): - def getDocList(parentNode, tagName): + def normalize_std_name(std_name): + # remove zero padding + prefix = std_name[:3] + if prefix in ("RFC", "FYI", "BCP", "STD"): + try: + return prefix + str(int(std_name[3:])) + except ValueError: + pass + return std_name + + def extract_doc_list(parentNode, tagName): l = [] for u in parentNode.getElementsByTagName(tagName): for d in u.getElementsByTagName("doc-id"): - l.append(d.firstChild.data) + l.append(normalize_std_name(d.firstChild.data)) return l also_list = {} @@ -209,8 +219,8 @@ def parse_index(response): 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") + bcpid = normalize_std_name(get_child_text(node, "doc-id")) + doclist = extract_doc_list(node, "is-also") for docid in doclist: if docid in also_list: also_list[docid].append(bcpid) @@ -235,10 +245,10 @@ def parse_index(response): 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") + updates = extract_doc_list(node, "updates") + updated_by = extract_doc_list(node, "updated-by") + obsoletes = extract_doc_list(node, "obsoletes") + obsoleted_by = extract_doc_list(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): @@ -329,7 +339,7 @@ def update_docs_from_rfc_index(data, skip_older_than_date=None): if not doc: results.append("created document %s" % name) - doc = Document.objects.get_or_create(name=name)[0] + doc = Document.objects.create(name=name, type=DocTypeName.objects.get(slug="draft")) # add alias DocAlias.objects.get_or_create(name=name, document=doc) @@ -360,8 +370,11 @@ def update_docs_from_rfc_index(data, skip_older_than_date=None): 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.group: # if we have no group assigned, check if RFC Editor has a suggestion + if wg: + changed_attributes["group"] = Group.objects.get(acronym=wg) + else: + changed_attributes["group"] = Group.objects.get(type="individ") if not doc.latest_event(type="published_rfc"): e = DocEvent(doc=doc, type="published_rfc") diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py index 1b066dc8c..1814586ae 100644 --- a/ietf/sync/tests.py +++ b/ietf/sync/tests.py @@ -269,7 +269,7 @@ class RFCSyncTestCase(django.test.TestCase): 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(set(also), set(["BCP1", "FYI1", "STD1"])) self.assertEqual(draft, doc.name) self.assertEqual(wg, doc.group.acronym) self.assertEqual(has_errata, True) @@ -288,9 +288,9 @@ class RFCSyncTestCase(django.test.TestCase): 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(DocAlias.objects.filter(name="bcp1", document=doc)) + self.assertTrue(DocAlias.objects.filter(name="fyi1", document=doc)) + self.assertTrue(DocAlias.objects.filter(name="std1", 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.") @@ -443,6 +443,7 @@ class RFCEditorUndoTestCase(django.test.TestCase): self.assertEquals(DeletedEvent.objects.count(), deleted_before + 1) # delete e1 + draft.state_cache = None r = self.client.post(url, dict(event=e1.id)) self.assertEquals(draft.get_state("draft-rfceditor"), None) diff --git a/ietf/templates/idrfc/document_ballot.html b/ietf/templates/doc/document_ballot.html similarity index 100% rename from ietf/templates/idrfc/document_ballot.html rename to ietf/templates/doc/document_ballot.html diff --git a/ietf/templates/idrfc/document_ballot_content.html b/ietf/templates/doc/document_ballot_content.html similarity index 89% rename from ietf/templates/idrfc/document_ballot_content.html rename to ietf/templates/doc/document_ballot_content.html index da52897a0..1fc600111 100644 --- a/ietf/templates/idrfc/document_ballot_content.html +++ b/ietf/templates/doc/document_ballot_content.html @@ -10,13 +10,10 @@ {% if doc.type_id == "draft" or doc.type_id == "conflrev" %}
{% if deferred %} - + Undefer ballot
Ballot deferred by {{ deferred.by }} on {{ deferred.time|date:"Y-m-d" }}.
{% else %} - -{% endif %} -{% if user|has_role:"Secretariat" %} - + Defer ballot {% endif %}
{% endif %} diff --git a/ietf/templates/doc/document_charter.html b/ietf/templates/doc/document_charter.html new file mode 100644 index 000000000..df88883b8 --- /dev/null +++ b/ietf/templates/doc/document_charter.html @@ -0,0 +1,155 @@ +{% extends "base.html" %} + +{% load ietf_filters %} + +{% block title %}{{ doc.canonical_name }}-{{ doc.rev }}{% endblock %} + +{% block pagehead %} + +{% endblock %} + +{% block content %} +{{ top|safe }} + +
+ Snapshots: + + {% for rev in revisions %} + {{ rev }} + {% endfor %} + +
+ +
+
+ {% if snapshot %}Snapshot of{% endif %} + {% if doc.get_state_slug != "approved" %}Proposed{% endif %} + Charter for "{{ group.name }}" + ({{ group.acronym }}) {{ group.type.name }} +
+ + + + + + + + + + + + {% if chartering and group.comments %} + + {% if chartering == "initial" %}{% endif %} + {% if chartering == "rechartering" %}{% endif %} + + + {% endif %} + + + + + + + + + + + + + + + + +
WG State:{{ group.state.name }}
Charter State: +
+ + {{ doc.get_state.name }} + + {% if chartering == "initial" %} - (Initial Chartering){% endif %} + {% if chartering == "rechartering" %} - (Rechartering){% endif %} +
+ + {% if not snapshot and chartering %} + + + {% if ballot_summary %} +
+ ({{ ballot_summary }}) +
+ {% endif %} + + {% endif %} +
Reason for chartering:Reason for rechartering:{{ group.comments }}
Responsible AD:{{ doc.ad|default:"none" }}

Send notices to: + {{ doc.notify|default:"none" }} + +
Last updated: {{ doc.time|date:"Y-m-d" }}

+ + + +
+ {% if not snapshot and user|has_role:"Area Director,Secretariat" %} + {% if chartering %} + {% url charter_startstop_process name=doc.name option='abandon' as abandon_url %}{% if abandon_url %}Abandon Effort{% endif %} + + {% if request.user|has_role:"Secretariat" %} + {% url charter_approve name=doc.name as approve_url %}{% if approve_url %}Approve Charter{% endif %} + {% endif %} + + {% else %} + {% if group.state_id == "proposed" or group.state_id == "bof" %} + {% url charter_submit name=doc.name option='initcharter' as start_url %}{% if start_url %}Start Chartering{% endif %} + {% else %} + {% url charter_submit name=doc.name option='recharter' as recharter_url %}{% if recharter_url %}Recharter{% endif %} + {% endif %} + {% endif %} + + {% endif %} +
+
+ +

Other versions: plain text

+ +

Charter {{ doc.canonical_name }}-{{ doc.rev }} + +{% if not snapshot and user|has_role:"Area Director,Secretariat" and chartering and group.state_id != "conclude" %} +Change charter text +{% endif %} +

+ +{% if doc.rev != "" %} +
+ {{ content|safe|keep_spacing|sanitize_html|wordwrap:80|safe }} +
+{% endif %} + +{% if not snapshot and chartering %} +

Proposed Milestones +{% if user|has_role:"Area Director,Secretariat" %} +Edit charter milestones +{% endif %} +

+ +{% if milestones %} +{% include "wginfo/milestones.html" %} +{% else %} +

No milestones for charter found.

+{% endif %} +{% endif %} + +{% endblock %} + diff --git a/ietf/templates/idrfc/document_conflict_review.html b/ietf/templates/doc/document_conflict_review.html similarity index 89% rename from ietf/templates/idrfc/document_conflict_review.html rename to ietf/templates/doc/document_conflict_review.html index 4b6669c9d..cdcfcddba 100644 --- a/ietf/templates/idrfc/document_conflict_review.html +++ b/ietf/templates/doc/document_conflict_review.html @@ -1,8 +1,8 @@ -{% extends "idrfc/doc_main.html" %} +{% extends "base.html" %} {% load ietf_filters %} -{% block title %}{{ doc.name }}-{{ doc.rev }}{% endblock %} +{% block title %}{{ doc.canonical_name }}-{{ doc.rev }}{% endblock %} {% block pagehead %} @@ -24,7 +24,7 @@
{% if snapshot %}Snapshot of{% endif %} {% if doc.get_state_slug not in approved_states %}Proposed{% endif %} - IESG Conflict Review for the {{conflictdoc.stream}} document: {{ conflictdoc.canonical_name }}{% if conflictdoc.get_state_slug != 'rfc' %}-{{ conflictdoc.rev }}{% endif %} + IESG Conflict Review for the {{conflictdoc.stream}} document: {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }}
@@ -42,26 +42,25 @@ {% endif %} - - - {% if not snapshot %} - - - - {% endif %} @@ -100,7 +99,7 @@ -

Conflict Review for {{ conflictdoc.name }}-{{ conflictdoc.rev }} +

Conflict Review for {{ conflictdoc.canonical_name }}-{{ conflictdoc.rev }} {% if not snapshot and user|has_role:"Area Director,Secretariat" and doc.get_state_slug != 'apprsent' %} Change conflict review text @@ -108,7 +107,7 @@

{% if doc.rev %} -
+
{{ content|fill:"80"|safe|linebreaksbr|keep_spacing|sanitize_html|safe }}
{% endif %} diff --git a/ietf/templates/doc/document_draft.html b/ietf/templates/doc/document_draft.html new file mode 100644 index 000000000..878e71d2b --- /dev/null +++ b/ietf/templates/doc/document_draft.html @@ -0,0 +1,284 @@ +{% extends "base.html" %} + +{% load ietf_filters %} + +{% block title %}{% if doc.get_state_slug == "rfc" %}RFC {{ rfc_number }}{% else %}{{ name }}-{{ doc.rev }}{% endif %}{% endblock %} + +{% block pagehead %} + + +{% endblock %} + +{% block content %} +{{ top|safe }} + +
+
Telechat Date: + + {% if not snapshot %} + + {% if ballot_summary %}
({{ ballot_summary }})
{% endif %} + {% endif %}
Shepherding AD:
+ + + + + + + + + + + + + + + + {% if doc.get_state_slug != "rfc" %} + + + + + {% endif %} + + + + + + + {% if conflict_reviews %} + + + + + {% endif %} + + + + + + + + + {% if consensus %} + + + + + {% endif %} + + + + + + + {# the shepherd write up page only works for WGs at the moment ... #} + {% if group.type_id == "wg" %} + {% if shepherd_writeup or can_edit_shepherd_writeup %} + + + + + {% endif %} + {% endif %} + + + + {% if published and started_iesg_process and published.time < started_iesg_process.time %} + + + + {% endif %} + + + + + + + + + + + + {% if iesg_state %} + {% if doc.note or can_edit %} + + + + + {% endif %} + {% endif %} + + + + + + + +
Document type: + {% if doc.get_state_slug == "rfc" %} + RFC - {{ doc.std_level }} + ({% if published %}{{ published.time|date:"F Y" }}{% else %}publication date unknown{% endif %}{% if has_errata %}; Errata{% endif %}) + + {% if obsoleted_by %}
Obsoleted by {{ obsoleted_by|join:", "|urlize_ietf_docs }}
{% endif %} + {% if updated_by %}
Updated by {{ updated_by|join:", "|urlize_ietf_docs }}
{% endif %} + {% if obsoletes %}
Obsoletes {{ obsoletes|join:", "|urlize_ietf_docs }}
{% endif %} + {% if updates %}
Updates {{ updates|join:", "|urlize_ietf_docs }}
{% endif %} + {% if rfc_aliases %}
Also Known As {{ rfc_aliases|join:", "|urlize_ietf_docs }}
{% endif %} + {% if draft_name %}
Was {{ draft_name }} {% if submission %}({{ submission|safe }}){% endif %}
{% endif %} + {% else %} + {{ doc.get_state }} Internet-Draft {% if submission %}({{ submission|safe }}){% endif %} + + {% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %} + {% endif %} + + + {% if replaces %}
Replaces: {{ replaces|join:", "|urlize_ietf_docs }}
{% endif %} + {% if replaced_by %}
Replaced by: {{ replaced_by|join:", "|urlize_ietf_docs }}
{% endif %} +
Document stream: + + {{ doc.stream|default:"No stream defined" }} + +
Last updated: + {{ doc.time|date:"Y-m-d" }} + {% if latest_revision and latest_revision.time.date != doc.time.date %} + (latest revision {{ latest_revision.time|date:"Y-m-d" }}) + {% endif %} +
Intended RFC status: + + {{ doc.intended_std_level|default:"Unknown" }} +
Other versions: + {% if doc.get_state_slug != "active" and doc.get_state_slug != "rfc" %}(expired, archived):{% endif %} + {% if file_urls %} + {% for label, url in file_urls %}{{ label}}{% if not forloop.last%}, {% endif %}{% endfor %} + {% else %} + (not online) + {% endif %} +
IETF Conflict Review:{{ conflict_reviews|join:", "|urlize_ietf_docs }}

{{ doc.stream }} State: + + {{ stream_state|default:"(None)" }} + + + {% for m in milestones %} + {{ m.due|date:"M Y" }} + {% endfor %} + + {% if stream_tags %} +
{% for tag in stream_tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
+ {% endif %} +
Consensus: + + {{ consensus }} + +
Document shepherd: + {# the shepherd edit page only works for WGs at the moment ... #} + + {{ doc.shepherd|default:"No shepherd assigned" }} + +
Shepherd Write-Up: + + {% if shepherd_writeup %}Last changed {{ shepherd_writeup.time|date:"Y-m-d"}}{% else %}(None){% endif %} + +

This information refers to IESG processing after the RFC was initially published:
IESG State: + + {{ iesg_state|default:"I-D Exists" }} + + {% if iana_review_state %} +
IANA Review State: + {{ iana_review_state }} +
+ {% endif %} + + {% if iana_review_state %} +
IANA Action State: + {{ iana_action_state }} +
+ {% endif %} + + {% if rfc_editor_state %} +
+ RFC Editor State: + {{ rfc_editor_state }}
+ {% endif %} + + + + {% if ballot_summary %}
({{ ballot_summary }})
{% endif %} +
Responsible AD: + + {{ doc.ad|default:"(None)" }} + +
IESG Note: + + {{ doc.note|default:"(None)"|linebreaksbr }} + +
Send notices to: + + {{ doc.notify|default:"No addresses provided"}} + +

+ + + + {% if can_edit and iesg_state %} + + {% endif %} + + {% if actions %} +
+ {% for label, url in actions %}{{ label }} {% endfor %} +
+ {% endif %} + + +{% if doc.get_state_slug == "active" or doc.get_state_slug == "rfc" %} + +
+{{ content|safe }} +
+ +{% if split_content %} +

[include full document text]

+{% endif %} + +{% else %} + +

This Internet-Draft is no longer active. Unofficial copies of old Internet-Drafts can be found here:
+http://tools.ietf.org/id/{{ doc.name }}

+ +

Abstract

+ +

{{ doc.abstract|escape }}

+ +

Authors

+ +

+ {% for author in doc.documentauthor_set.all %} + {{ author.author.person }} {% if not author.author.invalid_address %}<{{ author.author.address }}>{% endif %} + {% if not forloop.last %}
{% endif %}{% endfor %} +

+ +

(Note: The e-mail addresses provided for the authors of this Internet-Draft may no longer be valid)

+ +{% endif %} + +{% endblock %} + diff --git a/ietf/templates/idrfc/document_history.html b/ietf/templates/doc/document_history.html similarity index 91% rename from ietf/templates/idrfc/document_history.html rename to ietf/templates/doc/document_history.html index 283689a63..43442c227 100644 --- a/ietf/templates/idrfc/document_history.html +++ b/ietf/templates/doc/document_history.html @@ -6,13 +6,14 @@ {% block pagehead %} + {% endblock %} {% block content %} {{ top|safe }} {% if diff_revisions and diff_revisions|length > 1 %} -
+

Diffs

@@ -55,7 +56,7 @@

Document history

{% if user|has_role:"Area Director,Secretariat,IANA,RFC Editor" %} {% endif %} diff --git a/ietf/templates/idrfc/document_top.html b/ietf/templates/doc/document_top.html similarity index 100% rename from ietf/templates/idrfc/document_top.html rename to ietf/templates/doc/document_top.html diff --git a/ietf/templates/idrfc/document_writeup.html b/ietf/templates/doc/document_writeup.html similarity index 56% rename from ietf/templates/idrfc/document_writeup.html rename to ietf/templates/doc/document_writeup.html index f1deda666..aae69b4c9 100644 --- a/ietf/templates/idrfc/document_writeup.html +++ b/ietf/templates/doc/document_writeup.html @@ -9,16 +9,20 @@ {% block content %} {{ top|safe }} -{% for title, text, url in writeups %} -
+{% for title, subtitle, writeups in sections %}

{{ title }}

-{% if can_edit %}{% if text %}Edit{% else %}Generate{% endif %} {{ title }}{% endif %} +{% if subtitle %}

{{ subtitle|safe }}

{% endif %} + +{% for name, text, url in writeups %} +
+{% if can_edit %}{% if text %}Edit{% else %}Generate{% endif %} {{ name }}{% endif %}
 {{ text }}
 
-{% endfor%} +{% endfor %} +{% endfor %} {% endblock content %} diff --git a/ietf/templates/doc/shepherd_writeup.html b/ietf/templates/doc/shepherd_writeup.html new file mode 100644 index 000000000..cd658e706 --- /dev/null +++ b/ietf/templates/doc/shepherd_writeup.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block morecss %} +form #id_content { + width: 40em; + height: 450px; +} +{% endblock %} + +{% block title %} +Shepherd writeup for {{ doc.canonical_name }}-{{ doc.rev }} +{% endblock %} + +{% block content %} +

Shepherd writeup for {{ doc.canonical_name }}-{{ doc.rev }}

+ +
{{writeup}}
+ +Back + +{% if can_edit %} +{% url doc_edit_shepherd_writeup name=doc.name as doc_edit_url %}{% if doc_edit_url %}Edit{% endif %} +{% endif %} + +{% endblock %} diff --git a/ietf/templates/idrfc/ballot.tsv b/ietf/templates/idrfc/ballot.tsv deleted file mode 100644 index 2db5be005..000000000 --- a/ietf/templates/idrfc/ballot.tsv +++ /dev/null @@ -1,2 +0,0 @@ -#Discuss Yes No Objection Abstain Recuse No Record -{% for p in ballot.get_discuss %}{{p.ad_name}}, {% endfor %} {% for p in ballot.get_yes %}{{p.ad_name}}, {% endfor %} {% for p in ballot.get_no_objection %}{{p.ad_name}}, {% endfor %} {% for p in ballot.get_abstain %}{{p.ad_name}}, {% endfor %} {% for p in ballot.get_recuse %}{{p.ad_name}}, {% endfor %} {% for p in ballot.get_no_record %}{{p.ad_name}}, {% endfor %} diff --git a/ietf/templates/idrfc/date_column.html b/ietf/templates/idrfc/date_column.html deleted file mode 100644 index 7852469cf..000000000 --- a/ietf/templates/idrfc/date_column.html +++ /dev/null @@ -1,37 +0,0 @@ -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} -{% load ietf_filters %}{% if not doc.rfc %}{{ doc.publication_date }}{% else %}{{ doc.publication_date|date:"Y-m" }}{% endif %} -{% if doc.publication_date|timesince_days|new_enough:request %}
{% if not doc.rfc%}new{%else%}new{%endif%}{%endif%} -{% if doc.id and doc.id.expected_expiration_date and doc.id.expected_expiration_date|timesince_days|expires_soon:request %}
expires soon{%endif%} - diff --git a/ietf/templates/idrfc/doc_ballot.html b/ietf/templates/idrfc/doc_ballot.html deleted file mode 100644 index fcc3bbe8b..000000000 --- a/ietf/templates/idrfc/doc_ballot.html +++ /dev/null @@ -1,96 +0,0 @@ -{% comment %}{% endcomment %} -{% load ietf_filters %} - -
-{% if doc_ballot_edit_button and user|in_group:"Area_Director,Secretariat" %} -{% if user|in_group:"Area_Director" %} - -{% endif %} - -{% if doc.active_defer_event %} - -
Ballot deferred by {{ doc.active_defer_event.by }} on {{ doc.active_defer_event.time|date:"Y-m-d" }}.
-{% else %} - -{% endif %} - -{% endif %} - -{% if doc_ballot_edit_button and user|in_group:"Secretariat" %} - -{% endif %} - -

Discuss
-{% with bw.get_discuss as positions %}{% include "idrfc/doc_ballot_list.html" %}{% endwith %}

- -

Yes
-{% with bw.get_yes as positions %}{% include "idrfc/doc_ballot_list.html" %}{% endwith %}

- -

No Objection
-{% with bw.get_no_objection as positions %}{% include "idrfc/doc_ballot_list.html" %}{% endwith %}

- -

Abstain
-{% with bw.get_abstain as positions %}{% include "idrfc/doc_ballot_list.html" %}{% endwith %}

- -

Recuse
-{% with bw.get_recuse as positions %}{% include "idrfc/doc_ballot_list.html" %}{% endwith %}

- -

No Record
-{% with bw.get_no_record as positions %}{% include "idrfc/doc_ballot_list.html" %}{% endwith %}

- -
- -

Discusses and other comments

- -{% if bw.is_ballot_set %}

Other documents in this ballot set: {% for x in bw.ballot_set_other%}{{x|urlize_ietf_docs}}{% if not forloop.last %}, {% endif %}{% endfor %}

{% endif %} - -{% if dw.in_ietf_process and dw.ietf_process.has_active_iesg_ballot %} -

Summary: {{ dw.ietf_process.iesg_ballot_needed }}

-{% endif %} - -{% for pos in bw.get_texts|dictsort:"is_old_ad" %} -

{% if pos.is_old_ad %}[{%endif%}{{pos.ad_name|escape}}{% if pos.is_old_ad %}]{%endif%}

- -{% ifequal pos.position "Discuss" %} -

Discuss ({{pos.discuss_date}})

-
{{pos.discuss_text|wrap_text:80|escape }}
-{% endifequal %} - -{% if pos.comment_text %} -

Comment ({{pos.comment_date}})

-
{{pos.comment_text|wrap_text:80|escape }}
-{% endif %} -{% endfor %} -
diff --git a/ietf/templates/idrfc/doc_ballot_list.html b/ietf/templates/idrfc/doc_ballot_list.html deleted file mode 100644 index 5e2c1f342..000000000 --- a/ietf/templates/idrfc/doc_ballot_list.html +++ /dev/null @@ -1,7 +0,0 @@ -{% load ietf_filters %} -{% for p in positions %} -{% if p.is_old_ad %}[{%endif%}{{p.ad_name}}{% if p.is_old_ad %}]{%endif%}{% if p.has_text %} *{% endif %}
-{% if p.old_positions %}(was {{p.old_positions|join:", "}})
{%endif%} -{% empty %} -none -{% endfor %} diff --git a/ietf/templates/idrfc/doc_description.html b/ietf/templates/idrfc/doc_description.html deleted file mode 100644 index 83934e4af..000000000 --- a/ietf/templates/idrfc/doc_description.html +++ /dev/null @@ -1,6 +0,0 @@ -{{ doc.title }} -{% if info.is_rfc %} -({{doc.maturity_level}}, {{doc.publication_date|date:"Y"}}){% if doc.obsoleted_by %}; Obsoleted by {{doc.obsoleted_by}}{% endif %} -{% else %} -({{info.type}}; {{doc.publication_date|date:"Y"}}) -{% endif %} diff --git a/ietf/templates/idrfc/doc_diffs.html b/ietf/templates/idrfc/doc_diffs.html deleted file mode 100644 index 18a1608b2..000000000 --- a/ietf/templates/idrfc/doc_diffs.html +++ /dev/null @@ -1,68 +0,0 @@ -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} - -
-

Diffs

- - - - - - - - -
- - -Format: - -
- - -
- -
diff --git a/ietf/templates/idrfc/doc_history.html b/ietf/templates/idrfc/doc_history.html deleted file mode 100644 index a6a052303..000000000 --- a/ietf/templates/idrfc/doc_history.html +++ /dev/null @@ -1,80 +0,0 @@ -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} - -{% load ietf_filters %} - - - -{% for c in history %} - - - -{% if c.is_rev %} - - - -{% endif %} - -{% if c.is_text %} - - - -{% endif %} - -{% if c.is_com %} - - - -{% endif %} - - -{% endfor %} - -
DateVersionByText
{{ c.date|date:"Y-m-d" }}{{ c.revision }}(System)New version available: {{c.draft_name}}-{{c.revision}} {% ifnotequal c.revision "00" %}(diff from -{{c.revision|add:"-1"|stringformat:"02d"}}){% endifnotequal %} (System){{ c.text|safe }}{{ c.comment.version }}{{ c.info.by|escape }}{% if c.comment.ballot %} -[Ballot {{ c.comment.get_ballot_display }}]
-{% endif %} -{% if c.info.snipped %} -
{{ c.info.textSnippet|safe|sanitize_html|safe }}
-[show all] - -{% else %} -{% if c.info.dontmolest %} -{{ c.info.text|safe }} -{% else %} -{{ c.info.text|safe|urlize|linebreaksbr|keep_spacing|sanitize_html|safe }} -{% endif %} -{% endif %} -
diff --git a/ietf/templates/idrfc/doc_main.html b/ietf/templates/idrfc/doc_main.html deleted file mode 100644 index 85da04715..000000000 --- a/ietf/templates/idrfc/doc_main.html +++ /dev/null @@ -1,126 +0,0 @@ -{% extends "base.html" %} -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} - -{% load ietf_filters %} -{% block morecss %} -.metabox { width: 99%; margin-top:8px; padding:4px; margin-bottom:1em; } -#metatable { border: 0; border-spacing: 0; } -#metatable tr { vertical-align:top ;} -#metatable .post-rfc { font-style: italic; color: #004000; } -.comment_toggle { text-decoration: underline; color: blue; } -.comment_date { white-space: nowrap; } - -div.diffTool { padding: 8px 4px; margin: 8px 0;} -.diffTool label { float:left; width:50px; } - -.markup_draft pre {line-height: 1.2em; margin: 0; } -.m_hdr, .m_ftr { color: #808080; } -.m_ftr { border-bottom: 1px solid #a0a0a0; } -.m_h { font-family: arial; font-weight:bold;} - -a.editlink { - background-image: url("/images/pencil.png"); - background-size:10px; - background-position: right top; - background-attachment: scroll; - background-repeat: no-repeat; - padding-right: 12px; -} - -a.editlink:link {text-decoration:none; color:inherit;} -a.editlink:visited {text-decoration:none; color:inherit;} -a.editlink:hover {text-decoration:underline;} -a.editlink:active {text-decoration:underline;} - -{% endblock %} - -{% block pagehead %} -{% if doc.in_ietf_process %} - -{% endif %} - -{% comment %} -## See ticket #545 -- this is commented out because the versions -## that we currently serve is not sufficiently cachable, so the -## prefetched version is actually thrown away. -## Once the content becomes cacheable, these links should -## be reinserted into the document. -{% ifequal tab "document" %} -{% if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot %} - - -{% endif %} - -{% endifequal %} -{% endcomment %} -{% endblock %} - -{% block title %}{% include "idrfc/doc_title.html" %}{% endblock title %} - -{% block content %} -

{{ doc.title }}
{% include "idrfc/doc_title.html" %}

- -
- -
- -{% block tab_content %}{% endblock %} - -
-
- -{% endblock content %} - -{% block scripts %} -function toggleComment(n) { - var el = document.getElementById("commentF"+n); - var el2 = document.getElementById("commentS"+n); - var el3 = document.getElementById("commentT"+n); - if (el.style.display == 'none') { - el.style.display = 'block'; - el2.style.display = 'none'; - el3.innerHTML = ""; //[hide]"; - } else { - el.style.display = 'none'; - el2.style.display= 'block'; - el3.innerHTML = "[show all]"; - } -} -{% endblock scripts %} diff --git a/ietf/templates/idrfc/doc_tab_ballot.html b/ietf/templates/idrfc/doc_tab_ballot.html deleted file mode 100644 index 543fe1bd2..000000000 --- a/ietf/templates/idrfc/doc_tab_ballot.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends "idrfc/doc_main.html" %} -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} - -{% load ietf_filters %} - -{% block tab_content %} -{% if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot %} -{% with 1 as doc_ballot_edit_button %} -{% with doc.ietf_process.iesg_ballot as ballot %} -{% include "idrfc/doc_ballot.html" %} -{% endwith %} -{% endwith %} -{% endif %} -{% endblock tab_content %} - diff --git a/ietf/templates/idrfc/doc_tab_document.html b/ietf/templates/idrfc/doc_tab_document.html deleted file mode 100644 index 7ec5f7bc8..000000000 --- a/ietf/templates/idrfc/doc_tab_document.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends "idrfc/doc_main.html" %} -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} - -{% load ietf_filters %} - -{% block tab_content %} -
- -{% block doc_metatable %}{% endblock %} -
- -
-{% block doc_metalinks %}{% endblock %} -
- -{% block doc_metabuttons %} -{% if user|in_group:"Area_Director,Secretariat" %} -
- {% ifequal doc.draft_status "Expired" %} - {% if not doc.resurrect_requested_by and user|in_group:"Area_Director" %} - Request resurrect - {% endif %} - {% if user|in_group:"Secretariat" %} - Resurrect - {% endif %} - {% else %} {# not expired #} - {% if stream_info.stream.name == 'ISE' or stream_info.stream.name == 'IRTF' %} - {% if user|in_group:"Secretariat" and not info.conflict_reviews %} - {% url conflict_review_start name=doc.draft_name as start_review_url %}{% if start_review_url %}Begin IETF Conflict Review {% if not doc.underlying_document.intended_std_level %}(note that intended status is not set){% endif %}{% endif %} - {% endif %} - {% else %} {# Not a candidate for conflict review #} - {% if stream_info.stream.name == 'IETF' and not doc.in_ietf_process %} - {% url doc_edit_info name=doc.draft_name as doc_edit_url %}{% if doc_edit_url %}Begin IESG Processing{% endif %} - {% else %} - {% if doc.underlying_document.get_state_slug == 'rfc' %} - {% url start_rfc_status_change name=doc.underlying_document.canonical_name as start_url %}{% if start_url %}Start {% if info.proposed_status_changes %}An Additional {% endif %}Status Change{% endif %} - {% endif %} - {% endif %} - {% endif %} - {% endifequal %} -
-{% endif %}{# if user in group #} -{% endblock doc_metabuttons %} -
- -
-{% block doc_text1 %}{% endblock %} -
-{% endblock tab_content %} - -{% block content_end %} -
-{% if include_text %} -{% block doc_text2 %}{% endblock %} -{% else %} -[include full document text] -{% endif %} -
-{% endblock content_end %} diff --git a/ietf/templates/idrfc/doc_tab_document_id.html b/ietf/templates/idrfc/doc_tab_document_id.html deleted file mode 100644 index 83858fa25..000000000 --- a/ietf/templates/idrfc/doc_tab_document_id.html +++ /dev/null @@ -1,238 +0,0 @@ -{% extends "idrfc/doc_tab_document.html" %} -{% comment %}{% endcomment %} - -{% load ietf_filters ietf_streams %} - -{% block doc_metatable %} -{{ doc.draft_status }} {% ifnotequal doc.draft_status "RFC" %}Internet-Draft ({{ doc.submission|safe }}){% endifnotequal %} -{% ifequal doc.draft_status "Expired" %} -{% if doc.resurrect_requested_by %}(resurrect requested by {{ doc.resurrect_requested_by }}){% endif %} -{% endifequal %} - -Document Stream: -{% with user|in_group:"Area_Director,Secretariat" as add_link %} -{% if add_link %}{% endif %} -{% if stream_info.stream %}{{ stream_info.stream.name|default:"No stream defined" }}{% else %}No stream defined{% endif %} -{% if add_link %}{% endif %} -{% endwith %} - - -Last updated: {{ doc.publication_date|default:"(data missing)" }} - -{% with doc.replaces as r %}{% if r %}Replaces: {% filter urlize_ietf_docs %}{{ r|join:", "}}{% endfilter %}{% endif %}{% endwith %} -{% with doc.replaced_by as r %}{% if r %}Replaced by: {% filter urlize_ietf_docs %}{{ r|join:", "}}{% endfilter %}{% endif %}{% endwith %} - -{% with info.conflict_reviews as r %}{% if r %}IETF Conflict Review: {% filter urlize_ietf_docs %}{{ r|join:","}}{% endfilter %}{% endif %} {% endwith %} - -Intended RFC status: - -{{ doc.underlying_document.intended_std_level|default:"(None)" }} - - - -Other versions: -{% ifequal doc.draft_status "Active" %} - -plain text, -{% for ext in doc.file_types %} -{% ifnotequal ext ".txt" %} -{{ext|cut:"."}}, -{% endifnotequal %} -{% endfor %} -{% if not info.has_pdf %} -pdf, -{% endif %} -html - -{% else %} - -(expired, archived): -plain text, -{% for ext in doc.file_types %} -{% ifnotequal ext ".txt" %} -{{ext|cut:"."}}, -{% endifnotequal %} -{% endfor %} -{% if not info.has_pdf %} -pdf, -{% endif %} -html - - -{% endifequal %} - -
- -{% if stream_info.state %} -{% ifequal stream_info.stream.name "IETF" %} - - IETF State: - {{ stream_info.state.name }} ({{ stream_info.streamed.get_group }}) - {% if stream_info.tags %}
{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %} - {% if milestones %}{% for m in milestones %}{{ m.due|date:"M Y" }}{% endfor %}{% endif %} - - -{% else %} - {% if stream_info.stream %} - - {{ stream_info.stream.name }} status: - - {{ stream_info.state.name }} {% if stream_info.streamed.get_group %}({{ stream_info.streamed.get_group }}) {% endif %} - {% if stream_info.tags %}
{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %} - - - {% endif %} -{% endifequal %} -{% endif %} -Document shepherd:{{ doc.underlying_document.shepherd|default:"(None)" }} -Shepherd writeup - -{% if info.consensus %} -Consensus:{{ info.consensus }} -{% endif %} - -
- - -IESG State: -{% if doc.in_ietf_process and user|in_group:"Area_Director,Secretariat" %} - {{ doc.friendly_state|safe }} -{% else %} - {{ doc.friendly_state|safe }} -{% endif %} -{% if info.iana_review_state %}
IANA Review State: {{ info.iana_review_state.name|escape }} {% endif %} -{% if info.iana_action_state %}
IANA Action State: {{ info.iana_action_state.name|escape }} {% endif %} -{% if info.rfc_editor_state %}
RFC Editor State: {{ info.rfc_editor_state|escape }}{% endif %} -{% ifequal doc.draft_status "Expired" %} -{% if doc.resurrect_requested_by %}(resurrect requested by {{ doc.resurrect_requested_by }}){% endif %} -{% endifequal %} -{% with user|in_group:"Area_Director,Secretariat" as add_link %} -{% if add_link %}{% endif %} -{% if doc.underlying_document.telechat_date %}
On agenda of {{ doc.underlying_document.telechat_date }} IESG telechat {% if doc.underlying_document.returning_item %} (returning item){% endif %}{% else %}{%if add_link %}
Not on an upcoming telechat agenda{% endif %}{% endif %} -{% if add_link %}
{% endif %} -{% if doc.ietf_process.has_active_iesg_ballot %}
({{ doc.ietf_process.iesg_ballot_needed }}){%endif%} -{% endwith %} - -{# Submission:{{ doc.submission|safe }} #} -Responsible AD: -{% with user|in_group:"Area_Director,Secretariat" as add_link %} -{% if add_link %}{% endif %} -{% if doc.in_ietf_process %}{{ doc.ietf_process.ad_name|default:"(None)"|escape }}{%else%}(None){%endif%} -{% if add_link %}{% endif %} -{% endwith %} - -{% with user|in_group:"Area_Director,Secretariat" as add_link %} -{% if doc.in_ietf_process %}{% if doc.ietf_process.iesg_note or add_link %} -IESG Note: -{% if add_link %}{% endif %} -{{ doc.ietf_process.iesg_note|format_textarea|safe }} -{% if add_link %}{% endif %} -{% endif %}{% endif %} -{% endwith %} -Send notices to: -{% with user|in_group:"Area_Director,Secretariat" as add_link %} -{% if add_link %}{% endif %} -{{ doc.underlying_document.notify|default:"No addresses provided"}} -{% if add_link %}{% endif %} -{% endwith %} - - -
- - -{% endblock doc_metatable %} - -{% block doc_metalinks %} -{% edit_actions doc %} -
- Email Authors -| IPR Disclosures -| Dependencies to this draft -| Check nits -{% if doc.in_ietf_process %}| Comments feed{% endif %} -| Search Mailing Lists -{% if user|in_group:"Area_Director" %} -| Search Mailing Lists (ARO) -{% endif %} -
-{% if user|in_group:"Area_Director,Secretariat" %} -
-{% if doc.in_ietf_process %} - Last Call Text -| Ballot Text -| Announcement Text -{% endif %} -
-{% endif %} -{% endblock %} - -{% block doc_text1 %} -{% ifequal doc.draft_status "Active" %} -
-{{ content1|safe }} -
-{% else %} -

This Internet-Draft is no longer active. Unofficial copies of old Internet-Drafts can be found here:
-http://tools.ietf.org/id/{{doc.draft_name}}.

- -

Abstract:
{{ doc.abstract|escape }}

- -

Authors:
-{% for author in doc.authors.all %} - -{% if author.email %} -{{ author.person }} <{{author.email}}> -{% else %} -{% if author.person %} -{{ author.person }} -{% else %} -Missing author info #{{ author.person_id }} -{% endif %} -{% endif %}
- -{% endfor %}

- -

(Note: The e-mail addresses provided for the authors of this Internet-Draft may no longer be valid)

- -{% endifequal %} -{% endblock %}{# doc_text1 #} - -{% block doc_text2 %} -{% ifequal doc.draft_status "Active" %} -
-{{ content2|safe }} -
-{% endifequal %} -{% endblock %} {# doc_text2 #} diff --git a/ietf/templates/idrfc/doc_tab_document_rfc.html b/ietf/templates/idrfc/doc_tab_document_rfc.html deleted file mode 100644 index 8d595dd64..000000000 --- a/ietf/templates/idrfc/doc_tab_document_rfc.html +++ /dev/null @@ -1,112 +0,0 @@ -{% extends "idrfc/doc_tab_document.html" %} -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} - -{% load ietf_filters %} - -{% block doc_metatable %} -Document type:RFC - {{ doc.maturity_level }} {% if doc.stream_name %}({{ doc.stream_name }}){% endif %} -{% if doc.obsoleted_by %}
Obsoleted by {{ doc.obsoleted_by|urlize_ietf_docs }}{%endif %} -{% if doc.updated_by %}
Updated by {{ doc.updated_by|urlize_ietf_docs }}{%endif %} -{% if doc.obsoletes %}
Obsoletes {{ doc.obsoletes|urlize_ietf_docs }}{%endif %} -{% if doc.updates %}
Updates {{ doc.updates|urlize_ietf_docs }}{%endif %} -{% if info.status_changes %}
Status changed by {{ info.status_changes|urlize_ietf_docs }}{% endif %} -{% if info.proposed_status_changes %}
Proposed status changes by {{ info.proposed_status_changes|urlize_ietf_docs }}{% endif %} -{% if doc.also %}
Also Known As {{ doc.also|urlize_ietf_docs }}{%endif %} -{% if doc.draft_name %}
Was {{doc.draft_name}}{% endif %} -{% if doc.has_errata %}
Errata{% endif %} - -Published: {{ doc.publication_date|date:"Y-m"|default:"(data missing)" }} -{% if doc.in_ietf_process %} -* This information refers to IESG processing after the RFC was initially published -State: -{{ doc.friendly_state|safe }} (IESG: {{ doc.ietf_process.state}}) -{% if doc.ietf_process.telechat_date %}
On agenda of {{ doc.ietf_process.telechat_date }} IESG telechat {% if doc.ietf_process.telechat_returning_item %} (returning item){%endif%}{%endif%} - -Intended status:{% if doc.in_ietf_process %}{{ doc.ietf_process.intended_maturity_level|default:"-" }}{% else %}-{%endif%} -Responsible AD:{{ doc.ietf_process.ad_name|default:"-"|escape }} -{% if doc.ietf_process.iesg_note %}IESG Note:{{ doc.ietf_process.iesg_note|escape }}{% endif %} -{% if user|in_group:"Area_Director,Secretariat" %} -Send notices to:{{ doc.ietf_process.state_change_notice_to}} -{% endif %}{# if user|in_group:... #} - -{% endif %} -Other versions: -{% if info.has_txt or info.has_ps or info.has_pdf %} -{% if info.has_txt %} -plain text, -{% endif %} -{% if info.has_ps %} -ps, -{% endif %} -{% if info.has_pdf %} -pdf, -{% else %} -{% if info.has_txt %} -pdf, -{% endif %} -{% endif %} -{% if info.has_txt %} -html -{% endif %} -{% else %} -(not online) -{% endif %} - -{% endblock doc_metatable %} - -{% block doc_metalinks %} - IPR Disclosures -| Dependencies to this RFC -{% endblock %} - -{% block doc_text1 %} -{% if info.has_txt %} -
-{{ content1|safe }} -
-{% else %} -{% if info.has_pdf or info.has_ps %} -

This RFC is not available in plain text format.

-{% else %} -

This RFC is not currently available online.

-{% endif %} -{% endif %} -{% endblock %}{# doc_text1 #} - -{% block doc_text2 %} -
-{{ content2|safe }} -
-{% endblock %}{# doc_text2 #} diff --git a/ietf/templates/idrfc/doc_tab_history.html b/ietf/templates/idrfc/doc_tab_history.html deleted file mode 100644 index 11b45965d..000000000 --- a/ietf/templates/idrfc/doc_tab_history.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends "idrfc/doc_main.html" %} - - -{% load ietf_filters %} - -{% block tab_content %} -{% if not info.is_rfc %} -{% include "idrfc/doc_diffs.html" %} -{% endif %} -

Document history

-{% if user|in_group:"Area_Director,Secretariat,IANA,RFC Editor" and doc.in_ietf_process %} - -{% endif %} - -{% include "idrfc/doc_history.html" %} -{% endblock tab_content %} diff --git a/ietf/templates/idrfc/doc_tab_writeup.html b/ietf/templates/idrfc/doc_tab_writeup.html deleted file mode 100644 index f9ffb89d4..000000000 --- a/ietf/templates/idrfc/doc_tab_writeup.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends "idrfc/doc_main.html" %} -{% comment %} -Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). -All rights reserved. Contact: Pasi Eronen - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the Nokia Corporation and/or its - subsidiary(-ies) nor the names of its contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endcomment %} - -{% load ietf_filters %} - -{% block tab_content %} ----- following is a DRAFT of message to be sent AFTER approval --- -{% if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot %} -{% if user|in_group:"Area_Director,Secretariat" %} -
-

- -Edit Announcement Text - -

-{% endif %} -{% endif %} -
-{{ doc.ietf_process.iesg_ballot.approval_text|escape|urlize }}
-
-{% if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot %} -{% if user|in_group:"Area_Director,Secretariat" %} -
- -
-

- -Edit Ballot Text - -

-{% endif %} -{% endif %} -
-{{ doc.ietf_process.iesg_ballot.ballot_writeup|escape|urlize }}
-
-{% if doc.in_ietf_process and doc.ietf_process.has_iesg_ballot %} -{% if user|in_group:"Area_Director,Secretariat" %} -
-{% endif %} -{% endif %} -{% endblock tab_content %} diff --git a/ietf/templates/idrfc/doc_title.html b/ietf/templates/idrfc/doc_title.html deleted file mode 100644 index 7fc425c12..000000000 --- a/ietf/templates/idrfc/doc_title.html +++ /dev/null @@ -1,5 +0,0 @@ -{% if info.is_rfc %} -RFC {{ doc.rfc_number }} -{% else %} -{{ doc.draft_name_and_revision }} -{% endif %} diff --git a/ietf/templates/idrfc/main.html b/ietf/templates/idrfc/main.html index eadd46981..15c0a4b19 100644 --- a/ietf/templates/idrfc/main.html +++ b/ietf/templates/idrfc/main.html @@ -77,11 +77,6 @@ about: {% endif %}
{% endblock content %} -{% block scripts %} -YAHOO.util.Event.onContentReady("search_submit_button", function () { - var oButton = new YAHOO.widget.Button("search_submit_button", {}); -}); -{% endblock scripts %} {% block js %} diff --git a/ietf/templates/idrfc/search_form.html b/ietf/templates/idrfc/search_form.html index e2b179f11..9592ce186 100644 --- a/ietf/templates/idrfc/search_form.html +++ b/ietf/templates/idrfc/search_form.html @@ -32,54 +32,46 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% endcomment %} -
+
{{ form.name }}
- - - - - -
{{ form.rfcs }} RFCs
{{ form.activeDrafts }} Internet-Drafts (active)
{{ form.oldDrafts }} Internet-Drafts (expired/replaced/withdrawn)
+ + + + + +
- Advanced + Advanced -
+
Additional search criteria:
- {{ form.author }} + {{ form.author }}
- {{ form.group }} + {{ form.group }}
- {{ form.area }} + {{ form.area }}
- {{ form.ad }} + {{ form.ad }}
- {{ form.state }} :: {{ form.subState }} + {{ form.state }} :: {{ form.subState }}
-{% comment %} -
- {{ form.positionAd }} has position {{ form.positionValue }} -
-{% endcomment %}
-
-{# #} - - - +
+
diff --git a/ietf/templates/idrfc/search_main.html b/ietf/templates/idrfc/search_main.html index a2fc630ff..9c235d03a 100644 --- a/ietf/templates/idrfc/search_main.html +++ b/ietf/templates/idrfc/search_main.html @@ -38,7 +38,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% block content %}

Internet-Drafts and RFCs

-
+
{% include "idrfc/search_form.html" %}
@@ -48,32 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% endif %}
{% endblock content %} -{% block scripts %} -YAHOO.util.Event.onContentReady("search_submit_button", function () { - var oButton = new YAHOO.widget.Button("search_submit_button", {}); -}); -{% if meta.searching %} -(function ($) { - $(document).ready(function () { - $('.addtolist a').click(function() { - var trigger = $(this); - $.ajax({ - url: trigger.attr('href'), - type: 'GET', - cache: false, - dataType: 'json', - success: function(response){ - if (response.success) { - trigger.replaceWith('added'); - } - } - }); - return false; - }); - }); -})(jQuery); -{% endif %} -{% endblock scripts %} {% block js %} diff --git a/ietf/templates/idrfc/search_result_row.html b/ietf/templates/idrfc/search_result_row.html index 1f7332588..097e73180 100644 --- a/ietf/templates/idrfc/search_result_row.html +++ b/ietf/templates/idrfc/search_result_row.html @@ -35,7 +35,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% load ietf_filters %} {% load ballot_icon %} -{% if user.is_authenticated and show_add_to_list %} +{% if show_add_to_list and user.is_authenticated %} {% if doc.id %} Add to your personal ID list @@ -51,7 +51,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% endif %} {{ doc.title }} -{% include "idrfc/date_column.html" %} + +{% if not doc.rfc %}{{ doc.publication_date }}{% else %}{{ doc.publication_date|date:"Y-m" }}{% endif %} +{% if doc.publication_date|timesince_days|new_enough:request %}
{% if not doc.rfc%}new{%else%}new{%endif%}{%endif%} +{% if doc.id and doc.id.expected_expiration_date and doc.id.expected_expiration_date|timesince_days|expires_soon:request %}
expires soon{%endif%} + + {% include "idrfc/status_columns.html" %} {% include "idrfc/ipr_column.html" %} {# {% if doc.ad_name %}{{ doc.ad_name }}{% else %} {% endif %} #} diff --git a/ietf/templates/idrfc/search_results.html b/ietf/templates/idrfc/search_results.html index b9aa08780..ae7918688 100644 --- a/ietf/templates/idrfc/search_results.html +++ b/ietf/templates/idrfc/search_results.html @@ -38,19 +38,27 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% if not docs %}

No documents match your query.

{% else %} -{% regroup docs by view_sort_group as grouped_docs %} + -{% if user.is_authenticated %}{% endif %} - {% for hdr in meta.hdrs %} - {% include "idrfc/table_header.html" %} - {% endfor %} + + {% if user.is_authenticated %}{% endif %} + + {% for hdr in meta.hdrs %} + + {% endfor %} -{% for doc_group in grouped_docs %} - +{% regroup docs by view_sort_group as grouped_docs %} +{% for doc_group in grouped_docs %} + + {% with 1 as show_add_to_list %} {% for doc in doc_group.list %} -{% include "idrfc/search_result_row.html" %} + {% include "idrfc/search_result_row.html" %} {% endfor %} {% endwith %} diff --git a/ietf/templates/idrfc/table_header.html b/ietf/templates/idrfc/table_header.html deleted file mode 100644 index 604a92536..000000000 --- a/ietf/templates/idrfc/table_header.html +++ /dev/null @@ -1,15 +0,0 @@ -{# Copyright The IETF Trust 2011, All Rights Reserved #} - - diff --git a/ietf/templates/idtracker/document_entry.html b/ietf/templates/idtracker/document_entry.html index 56a7e06e8..d7b8f5f6c 100644 --- a/ietf/templates/idtracker/document_entry.html +++ b/ietf/templates/idtracker/document_entry.html @@ -20,6 +20,6 @@ {% if doc.primary_flag %} {% if doc.note %} - + {% endif %} {% endif %} diff --git a/ietf/templates/iesg/agenda_conflict_doc.html b/ietf/templates/iesg/agenda_conflict_doc.html index ff9afbbe9..920644010 100644 --- a/ietf/templates/iesg/agenda_conflict_doc.html +++ b/ietf/templates/iesg/agenda_conflict_doc.html @@ -83,7 +83,7 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. [txt]
{{ doc.obj.conflictdoc.title|escape }} ({{doc.obj.conflictdoc.stream}}: {{ doc.obj.conflictdoc.intended_std_level }}) {% if doc.obj.conflictdoc.note %} -
Note: {{ doc.obj.conflictdoc.note|unescape }} +
Note: {{ doc.obj.conflictdoc.note|linebreaksbr }} {% endif %} {% if doc.obj.conflictdoc.ipr %} diff --git a/ietf/templates/iesg/agenda_doc.html b/ietf/templates/iesg/agenda_doc.html index ecb2a45e4..d7e2b11f8 100644 --- a/ietf/templates/iesg/agenda_doc.html +++ b/ietf/templates/iesg/agenda_doc.html @@ -85,7 +85,7 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. {% if doc.obj.note %} -
Note: {{ doc.obj.note|unescape }} +
Note: {{ doc.obj.note|linebreaksbr }} {% endif %} {% if doc.obj.ipr %} diff --git a/ietf/templates/iesg/scribe_conflict_doc.html b/ietf/templates/iesg/scribe_conflict_doc.html index d1800b9fb..8c618f9aa 100644 --- a/ietf/templates/iesg/scribe_conflict_doc.html +++ b/ietf/templates/iesg/scribe_conflict_doc.html @@ -51,8 +51,8 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. {{ doc.obj.conflictdoc.title|escape }} ({{doc.obj.conflictdoc.stream}}: {{ doc.obj.conflictdoc.intended_std_level }})
{{doc.obj.conflictdoc.canonical_name}}[txt] - {% if doc.obj.conflictdoc.note %}{# note: note is not escaped #} -
Note: {{ doc.obj.conflictdoc.note|safe }} + {% if doc.obj.conflictdoc.note %} +
Note: {{ doc.obj.conflictdoc.note|linebreaksbr }} {% endif %} {% for ipr in doc.obj.conflictdoc.ipr %} {% ifequal ipr.ipr.status 1 %} diff --git a/ietf/templates/iesg/scribe_doc.html b/ietf/templates/iesg/scribe_doc.html index cb2126750..22491e7ea 100644 --- a/ietf/templates/iesg/scribe_doc.html +++ b/ietf/templates/iesg/scribe_doc.html @@ -55,7 +55,7 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. {% endwith %}
Token: {{ doc.obj.ad.plain_name|escape }} ({{doc.obj.area_acronym}} area) {% if doc.obj.note %} -
Note: {{ doc.obj.note }} +
Note: {{ doc.obj.note|linebreaksbr }} {% endif %} {% for ipr in doc.obj.ipr %} {% ifequal ipr.ipr.status 1 %} diff --git a/ietf/templates/ipr/search.html b/ietf/templates/ipr/search.html index cdc7bfef5..eefa93114 100644 --- a/ietf/templates/ipr/search.html +++ b/ietf/templates/ipr/search.html @@ -10,7 +10,7 @@ label { float:left; width: 200px; }

IPR Search

Document Search

-
+
diff --git a/ietf/templates/wginfo/wg_charter.html b/ietf/templates/wginfo/wg_charter.html index be23cbead..e4797950b 100644 --- a/ietf/templates/wginfo/wg_charter.html +++ b/ietf/templates/wginfo/wg_charter.html @@ -77,7 +77,7 @@ is occasionally incorrect. {% else %} none {% if user|has_role:"Area Director,Secretariat" %} - - Submit Charter + - Submit Charter {% endif %} {% endif %} diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 5308d22fb..46c425297 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -411,17 +411,16 @@ def make_test_data(): # an independent submission before review doc = Document.objects.create(name='draft-imaginary-independent-submission',type_id='draft') doc.set_state(State.objects.get(used=True, type="draft", slug="active")) - DocAlias.objects.create( name='draft-imaginary-independent-submission',document=doc) + DocAlias.objects.create(name=doc.name, document=doc) # an irtf submission mid review - doc = Document.objects.create( name='draft-imaginary-irtf-submission',type_id='draft') - docalias = DocAlias.objects.create(name='draft-imaginary-irtf-submission',document=doc) + doc = Document.objects.create(name='draft-imaginary-irtf-submission', type_id='draft') + docalias = DocAlias.objects.create(name=doc.name, document=doc) doc.stream = StreamName.objects.get(slug='irtf') doc.save() - crdoc = Document.objects.create(name='conflict-review-imaginary-irtf-submission',type_id='conflrev',rev='00',notify="fsm@ietf.org") - DocAlias.objects.create( name='conflict-review-imaginary-irtf-submission',document=crdoc) - crdoc.set_state(State.objects.get(used=True, name='Needs Shepherd',type__slug='conflrev')) - crdoc.save() + crdoc = Document.objects.create(name='conflict-review-imaginary-irtf-submission', type_id='conflrev', rev='00', notify="fsm@ietf.org") + DocAlias.objects.create(name=crdoc.name, document=crdoc) + crdoc.set_state(State.objects.get(name='Needs Shepherd', type__slug='conflrev')) crdoc.relateddocument_set.create(target=docalias,relationship_id='conflrev') # A status change mid review diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py index 802b762ab..3bcd6e026 100644 --- a/ietf/wgchairs/views.py +++ b/ietf/wgchairs/views.py @@ -4,7 +4,6 @@ from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext from django.http import HttpResponseForbidden, Http404 -from ietf.idrfc.views_search import SearchForm, search_query from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory, workflow_form_factory, TransitionFormSet, WriteUpEditForm, assign_shepherd) @@ -186,15 +185,7 @@ def wg_shepherd_documents(request, acronym): return HttpResponseForbidden('You have no permission to access this view') current_person = get_person_for_user(user) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - base_qs = InternetDraft.objects.filter(group=wg, states__type="draft", states__slug="active").select_related("status").order_by('title') - else: - form = SearchForm({'by': 'group', 'group': str(wg.group_acronym.acronym), - 'activeDrafts': 'on'}) - if not form.is_valid(): - raise ValueError("form did not validate") - (docs, meta) = search_query(form.cleaned_data) - base_qs = InternetDraft.objects.filter(pk__in=[i.id._draft.pk for i in docs if i.id]).select_related('status') + base_qs = InternetDraft.objects.filter(group=wg, states__type="draft", states__slug="active").select_related("status").order_by('title') documents_no_shepherd = base_qs.filter(shepherd=None) documents_my = base_qs.filter(shepherd=current_person) documents_other = base_qs.exclude(shepherd=None).exclude(shepherd__pk__in=[current_person.pk, 0]) diff --git a/static/css/base2.css b/static/css/base2.css index 924854330..7b8bddf2a 100644 --- a/static/css/base2.css +++ b/static/css/base2.css @@ -82,13 +82,16 @@ body { margin: 0; } .ietf-stream .entry-title .entry-date { float: right; } .ietf-stream .entry-comment { background: #eeeeee; margin: 1em 0px; padding: 1em; } -.search_form_box {width: 99.5%; margin-top:8px; padding:4px; margin-bottom:1em; padding-left:8px;} +.search-form-box { width: 99.5%; margin-top:8px; padding:4px; margin-bottom:1em; padding-left:8px; } form#search_form { padding-top: 4px; padding-bottom: 4px; } +#search_form .submit { padding-top: 0.5em; } +#search_form .submit .button { padding: 0.2em 0.5em; border: 1px solid #999; } #search_form input { padding: 0; padding-left: 2px; border: 1px solid #89d;} -#search_form input.radio { padding-left: 0; border: 0; } +#search_form input.radio { padding-left: 0; border: 0; } #search_form select { border: 1px solid #89d; } #search_form div.search_field { margin-top:2px; clear:both;} -#search_form label { width: 170px; float: left; } +#search_form .search_field > label { width: 170px; float: left; } +#search_form #search_advanced { margin-top: 1em; } /* checkboxes for document types */ #search_form table#search_types { border-collapse:collapse;} #search_form #search_types td { padding:0; } @@ -106,12 +109,13 @@ table.ietf-table { border-collapse:collapse; border:1px solid #7f7f7f; } .ietf-table tr.evenrow { background-color: #EDF5FF; } .ietf-table tr.oddrow { background-color: white; } .ietf-table td { border-right: 1px solid #cbcbcb; padding:3px 6px; vertical-align: top; } -.ietf-table th { color:white; background: #2647A0; text-align:left; padding:3px 6px; border-right: 1px solid #7f7f7f; } +.ietf-table th { color: #fff; background: #2647A0; text-align: left; padding:3px 6px; border-right: 1px solid #7f7f7f; } .ietf-doctable tr.header { border-top: 1px solid #7f7f7f; border-bottom: 1px solid #7f7f7f; border-left: 1px solid white; border-right:2px solid white;} .ietf-doctable tr.header td {padding: 6px 6px; font-weight: bold; } .ietf-doctable table { max-width: 1200px; } -.ietf-doctable th { cursor: pointer } +.ietf-doctable th { cursor: pointer; white-space: nowrap; } +.ietf-doctable th img { border-style: none; vertical-align: top; } .ietf-doctable th.doc, .ietf-doctable td.doc { min-width:20em; max-width: 35em; } .ietf-doctable th.title, .ietf-doctable td.title { min-width: 20em; max-width: 35em; } .ietf-doctable th.date, .ietf-doctable td.date { white-space:nowrap; min-width: 6em;} @@ -209,7 +213,13 @@ table.milestones .doc { display: block; padding-left: 1em; } .stream-state .milestone { display: inline-block; font-size: smaller; background-color: #d5dde6; padding: 0 0.2em; margin-left: 0.3em; } -.button { display: inline-block; font-weight: normal; background: #eee; border: 1px solid #bbb; border-radius: 3px; color: #333; padding: 2px 8px; text-align: center; text-decoration: none; outline: none; transition-duration: 0.2s; cursor: pointer } -.button:hover { background: #ddd; color: #222; } -.button:active { background: #ccc; color: #000; } - +.button, .button:hover:disabled { + display: inline-block; padding: 4px 12px; margin-right: 0.3em; + color: #222; font-weight: normal; text-align: center; text-decoration: none; outline: none; cursor: pointer; + background: #eee; background: linear-gradient(#fff, #e0e0e0); background: -webkit-linear-gradient(#fff, #e0e0e0); background: -moz-linear-gradient(#fff, #e0e0e0); + border: 1px solid #666; border-radius: 3px; + transition-duration: 0.2s; +} +.button:hover { color: #111; background: #ddd; background: linear-gradient(#eee, #ccc); background: -webkit-linear-gradient(#eee, #ccc); background: -moz-linear-gradient(#eee, #ccc); } +.button:active { color: #000; background: #ccc; } +.button:disabled, .button:hover:disabled { color: #999; cursor: default; } diff --git a/static/css/doc.css b/static/css/doc.css index 8d1e4211e..deaa737d9 100644 --- a/static/css/doc.css +++ b/static/css/doc.css @@ -3,26 +3,46 @@ #metatable tr { vertical-align: top; } #metatable tr:first-child td:first-child { width: 15em; } -.markup_draft pre { line-height: 1.2em; margin: 0; } -.m_hdr, .m_ftr { color: #808080; } -.m_ftr { border-bottom: 1px solid #a0a0a0; } -.m_h { font-family: arial; font-weight:bold;} +.stream-tags { font-style: italic; } + +.document-markup pre { line-height: 1.2em; margin: 0; } +.document-markup .m_hdr, .m_ftr { color: #808080; } +.document-markup .m_ftr { border-bottom: 1px solid #a0a0a0; } +.document-markup .m_h { font-family: arial; font-weight:bold;} .snapshots { margin: 0.5em 0; } .snapshots .revisions a:last-child { font-weight: bold; } -.metabox .actions a { display: inline-block; margin-right: 0.4em; } +.metabox .links { margin-bottom: 0.2em; } +.metabox .actions { margin-top: 0.5em; } .metabox .ballot-summary { font-style: italic; } .metabox .telechat { margin-top: 0.2em; } -.diffTool { padding: 8px 4px; margin: 8px 0;} -.diffTool h2 { margin-top:0;margin-bottom:4px; } -.diffTool label { display: inline-block; width: 3em; padding: 0 0.5em; } -.diffTool form { margin: 0; } +.diff-tool { padding: 8px 4px; margin: 8px 0;} +.diff-tool h2 { margin-top:0;margin-bottom:4px; } +.diff-tool label { display: inline-block; width: 3em; padding: 0 0.5em; } +.diff-tool form { margin: 0; } .history-actions { margin-bottom: 1em; padding-left: 1px; } -.writeup pre.editable { background-color: #efefff; min-height: 3em; } -.writeup a.edit { float: right; } +.writeup pre.editable { background-color: #efefff; min-height: 3em; padding: 4px; } +.writeup a.edit { float: right; margin: 4px; } + +a.editlink { + background-image: url("/images/pencil.png"); + background-size: 10px; + background-position: right top; + background-attachment: scroll; + background-repeat: no-repeat; + padding-right: 12px; +} + +a.editlink:link {text-decoration:none; color:inherit;} +a.editlink:visited {text-decoration:none; color:inherit;} +a.editlink:hover {text-decoration:underline;} +a.editlink:active {text-decoration:underline;} h3 a.edit { font-weight: normal; font-size: 13px; display: inline-block; margin-left: 0.5em;} + +h4 { margin-bottom: 0; } +h4 + p { margin-top: 0; max-width: 400px; } diff --git a/static/js/doc-search.js b/static/js/doc-search.js index 313958572..5f999f38c 100644 --- a/static/js/doc-search.js +++ b/static/js/doc-search.js @@ -1,86 +1,77 @@ $(function () { var form = jQuery("#search_form"); - form.find(".search_field input[name=by]").parents("label").click(changeBy); - - form.find(".search_field").find("input,select") - .change(toggleSubmit).click(toggleSubmit).keyup(toggleSubmit); - - form.find(".toggle_advanced").click(function () { - togglePlusMinus("search_advanced"); - form.find('.search_field input[type="radio"]').attr("checked", false); - changeBy(); - }); - - changeBy(); - - // we want to disable our submit button if we have no search text, + // we want to disable our submit button if we have no search text, // and we have no advanced options selected function toggleSubmit() { - var button = document.getElementById("id_search_submit"); - var by = findCheckedSearchBy(); - var value = findSearchByValue(by); - var text = document.getElementById("id_name"); - if ((value == "") && (text.value == "")) { - button.disabled = true; - } else { - button.disabled = false; - } + var nameSearch = $.trim($("#id_name").val()); + + var noAdvanced = true; + + var by = form.find("input[name=by]:checked"); + if (by.length > 0) + by.closest(".search_field").find("input,select").not("input[name=by]").each(function () { + if ($.trim(this.value)) + noAdvanced = false; + }); + + form.find("input[type=submit]").get(0).disabled = !nameSearch && noAdvanced; } - function togglePlusMinus(id) { - var el = document.getElementById(id); - var imgEl = document.getElementById(id+"-img"); - if (el.style.display == 'none') { - el.style.display = 'block'; - imgEl.src = "/images/minus.png"; + function togglePlusMinus(toggler, toggled) { + var img = toggler.find("img").get(0); + if (toggled.is(":hidden")) { + toggled.show(); + img.src = "/images/minus.png"; } else { - el.style.display = 'none'; - imgEl.src = "/images/plus.png"; + toggled.hide(); + img.src = "/images/plus.png"; } } - function findCheckedSearchBy() { - var by=''; - var f = document.search_form; - for (var i = 0; i < f.by.length; i++) { - if (f.by[i].checked) { - by = f.by[i].value; - break; - } - } - return by; - } + function updateBy() { + form.find("input[name=by]:checked").closest(".search_field").find("input,select").not("input[name=by]").each(function () { + this.disabled = false; + }); - function findSearchByValue(by) { - if (by == 'author') { return document.getElementById("id_author").value; } - if (by == 'group') { return document.getElementById("id_group").value; } - if (by == 'area') { return document.getElementById("id_area").value; } - if (by == 'ad') { return document.getElementById("id_ad").value; } - if (by == 'state') { - // state might be state... - state_value = document.getElementById("id_state").value; - if (state_value) { return state_value; } - // ...or sub-state - return document.getElementById("id_subState").value; - } - return ''; - } - - function changeBy() { - var by = findCheckedSearchBy(); - var f = document.search_form; - f.author.disabled=true; - f.group.disabled=true; - f.area.disabled=true; - f.ad.disabled=true; - f.state.disabled=true; f.subState.disabled=true; - if (by=='author') { f.author.disabled=false;} - if (by=='group') { f.group.disabled=false;} - if (by=='area') { f.area.disabled=false;} - if (by=='ad') { f.ad.disabled=false; } - if (by=='state') { f.state.disabled=false; f.subState.disabled=false; } + form.find("input[name=by]").not(":checked").closest(".search_field").find("input,select").not("input[name=by]").each(function () { + this.disabled = true; + }); toggleSubmit(); } + + form.find(".search_field input[name=by]").closest("label").click(updateBy); + + form.find(".search_field input,select") + .change(toggleSubmit).click(toggleSubmit).keyup(toggleSubmit); + + form.find(".toggle_advanced").click(function () { + var advanced = $(this).next(); + advanced.find('.search_field input[type="radio"]').attr("checked", false); + togglePlusMinus($(this), advanced); + updateBy(); + }); + + updateBy(); + + $("#search_results th").click(function (e) { + window.location = $(this).find("a").attr("href"); + }) + + $('#search_results .addtolist a').click(function(e) { + e.preventDefault(); + var trigger = $(this); + $.ajax({ + url: trigger.attr('href'), + type: 'POST', + cache: false, + dataType: 'json', + success: function(response){ + if (response.success) { + trigger.replaceWith('added'); + } + } + }); + }); });
+ {{ hdr.htitle }} + + +
{{doc_group.grouper}}s
{{ doc_group.grouper }}s
- - - {% if hdr.selected %} - - {% else %} - - {% endif %} - -
Token:{{ doc.token_name }}
Note:{{ doc.note|safe|urlize }}
Note:{{ doc.note|linebreaksbr|urlize }}