From c71f1d9f97b6fa583cb0509301e2bea8d26e8cc7 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 19 Nov 2010 19:21:15 +0000
Subject: [PATCH 01/75] Added redesign work-in-progress - Legacy-Id: 2686
---
django/shortcuts/__init__.py | 2 +-
ietf/idrfc/idrfc_wrapper.py | 59 +++-
ietf/idrfc/views_doc.py | 39 ++-
ietf/idtracker/admin.py | 30 +-
ietf/idtracker/models.py | 5 +
ietf/settings.py | 13 +-
redesign/__init__.py | 0
redesign/doc/__init__.py | 0
redesign/doc/admin.py | 45 +++
redesign/doc/models.py | 211 ++++++++++++
redesign/doc/proxy.py | 529 ++++++++++++++++++++++++++++++
redesign/group/__init__.py | 0
redesign/group/admin.py | 13 +
redesign/group/models.py | 53 +++
redesign/group/proxy.py | 46 +++
redesign/import-document-state.py | 320 ++++++++++++++++++
redesign/import-groups.py | 82 +++++
redesign/name/__init__.py | 0
redesign/name/admin.py | 22 ++
redesign/name/models.py | 57 ++++
redesign/name/proxy.py | 20 ++
redesign/person/__init__.py | 0
redesign/person/admin.py | 28 ++
redesign/person/models.py | 67 ++++
redesign/person/proxy.py | 0
redesign/proxy_utils.py | 215 ++++++++++++
redesign/util.py | 45 +++
27 files changed, 1871 insertions(+), 30 deletions(-)
create mode 100644 redesign/__init__.py
create mode 100644 redesign/doc/__init__.py
create mode 100644 redesign/doc/admin.py
create mode 100644 redesign/doc/models.py
create mode 100644 redesign/doc/proxy.py
create mode 100644 redesign/group/__init__.py
create mode 100644 redesign/group/admin.py
create mode 100644 redesign/group/models.py
create mode 100644 redesign/group/proxy.py
create mode 100755 redesign/import-document-state.py
create mode 100755 redesign/import-groups.py
create mode 100644 redesign/name/__init__.py
create mode 100644 redesign/name/admin.py
create mode 100644 redesign/name/models.py
create mode 100644 redesign/name/proxy.py
create mode 100644 redesign/person/__init__.py
create mode 100644 redesign/person/admin.py
create mode 100644 redesign/person/models.py
create mode 100644 redesign/person/proxy.py
create mode 100644 redesign/proxy_utils.py
create mode 100644 redesign/util.py
diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py
index 823c6bb5e..36a046792 100644
--- a/django/shortcuts/__init__.py
+++ b/django/shortcuts/__init__.py
@@ -98,4 +98,4 @@ def get_list_or_404(klass, *args, **kwargs):
obj_list = list(queryset.filter(*args, **kwargs))
if not obj_list:
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
- return obj_list
\ No newline at end of file
+ return obj_list
diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py
index 64712665a..04a74654f 100644
--- a/ietf/idrfc/idrfc_wrapper.py
+++ b/ietf/idrfc/idrfc_wrapper.py
@@ -36,6 +36,7 @@ import re
from datetime import date
from django.utils import simplejson as json
from django.db.models import Q
+from django.conf import settings
import types
BALLOT_ACTIVE_STATES = ['In Last Call',
@@ -602,8 +603,64 @@ class BallotWrapper:
return []
else:
return self._ballot_set.exclude(draft=self._idinternal)
-
+
def _init(self):
+ if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ self.old_init()
+ return
+
+ from redesign.person.models import Email
+ active_ads = Email.objects.filter(role__name="ad", role__group__state="active")
+
+ positions = []
+ seen = {}
+
+ for pos in self.ballot.event_set.filter(type="changed_ballot_position").select_related('pos', 'ad').order_by("-time", '-id'):
+ pos = pos.ballotposition
+ if pos.ad not in seen:
+ p = dict(ad_name=pos.ad.get_name(),
+ ad_username="", # FIXME: don't seem to have username at the moment
+ position=pos.pos.name,
+ is_old_ad=pos.ad in active_ads,
+ old_positions=[])
+
+ if pos.pos.slug == "discuss":
+ p["has_text"] = True
+ p["discuss_text"] = pos.discuss
+ p["discuss_date"] = pos.discuss_time
+ p["discuss_revision"] = pos.doc.rev # FIXME: wrong
+
+ if pos.comment:
+ p["has_text"] = True
+ p["comment_text"] = pos.comment
+ p["comment_date"] = pos.comment_time
+ p["comment_revision"] = pos.doc.rev # FIXME: wrong
+
+ positions.append(p)
+ seen[pos.ad] = p
+ else:
+ latest = seen[pos.ad]
+ if latest["old_positions"]:
+ prev = latest["old_positions"][-1]
+ else:
+ prev = latest["position"]
+
+ if prev != pos.pos.name:
+ seen[pos.ad]["old_positions"].append(pos.pos.name)
+
+ # add any missing ADs as No Record
+ if self.ballot_active:
+ for ad in active_ads:
+ if ad not in seen:
+ d = dict(ad_name=ad.get_name(),
+ ad_username="", # FIXME: don't seem to have username at the moment
+ position="No Record",
+ )
+ positions.append(d)
+
+ self._positions = positions
+
+ def old_init(self):
try:
ads = set()
except NameError:
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index ca29b988a..29795c58e 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -147,21 +147,32 @@ def document_main(request, name):
# doc is either IdWrapper or RfcWrapper
def _get_history(doc, versions):
results = []
- if doc.is_id_wrapper:
- comments = DocumentComment.objects.filter(document=doc.tracker_id).exclude(rfc_flag=1)
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ for e in doc._draft.event_set.all().select_related('by').order_by('-time'):
+ info = {}
+ info['text'] = e.desc
+ info['by'] = e.by.get_name()
+ info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25)
+ info['snipped'] = info['textSnippet'][-3:] == "..."
+ e.version = e.doc.rev
+ results.append({'comment':e, 'info':info, 'date':e.time, 'is_com':True})
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:
+ 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:
diff --git a/ietf/idtracker/admin.py b/ietf/idtracker/admin.py
index 43f9abdb6..340928383 100644
--- a/ietf/idtracker/admin.py
+++ b/ietf/idtracker/admin.py
@@ -1,14 +1,17 @@
#coding: utf-8
from django.contrib import admin
+from django.conf import settings
from ietf.idtracker.models import *
-
-class AcronymAdmin(admin.ModelAdmin):
- list_display=('acronym', 'name')
-admin.site.register(Acronym, AcronymAdmin)
-class AreaAdmin(admin.ModelAdmin):
- list_display=('area_acronym', 'status')
-admin.site.register(Area, AreaAdmin)
+if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ class AcronymAdmin(admin.ModelAdmin):
+ list_display=('acronym', 'name')
+ admin.site.register(Acronym, AcronymAdmin)
+
+if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ class AreaAdmin(admin.ModelAdmin):
+ list_display=('area_acronym', 'status')
+ admin.site.register(Area, AreaAdmin)
class AreaDirectorAdmin(admin.ModelAdmin):
raw_id_fields=['person']
@@ -96,12 +99,13 @@ class IRTFAdmin(admin.ModelAdmin):
pass
admin.site.register(IRTF, IRTFAdmin)
-class InternetDraftAdmin(admin.ModelAdmin):
- list_display=('filename', 'revision', 'title', 'status')
- search_fields=['filename', 'title']
- list_filter=['status']
- raw_id_fields=['replaced_by']
-admin.site.register(InternetDraft, InternetDraftAdmin)
+if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ class InternetDraftAdmin(admin.ModelAdmin):
+ list_display=('filename', 'revision', 'title', 'status')
+ search_fields=['filename', 'title']
+ list_filter=['status']
+ raw_id_fields=['replaced_by']
+ admin.site.register(InternetDraft, InternetDraftAdmin)
class PersonOrOrgInfoAdmin(admin.ModelAdmin):
fieldsets=((None, {'fields': (('first_name', 'middle_initial', 'last_name'), ('name_suffix', 'modified_by'))}), ('Obsolete Info', {'fields': ('record_type', 'created_by', 'address_type'), 'classes': 'collapse'}))
diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py
index 74db9f3ca..7a01878f1 100644
--- a/ietf/idtracker/models.py
+++ b/ietf/idtracker/models.py
@@ -1087,6 +1087,11 @@ class DocumentWrapper(object):
def __init__(self, document):
self.document = document
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ from redesign.doc.proxy import InternetDraft
+ from redesign.group.proxy import Area
+ from redesign.group.proxy import Acronym
+
# changes done by convert-096.py:changed maxlength to max_length
# removed core
diff --git a/ietf/settings.py b/ietf/settings.py
index aecc2fb73..f4c4027fd 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -8,9 +8,12 @@ import os
import syslog
syslog.openlog("django", syslog.LOG_PID, syslog.LOG_LOCAL0)
-
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+import sys
+sys.path.append(os.path.abspath(BASE_DIR + "/.."))
+sys.path.append(os.path.abspath(BASE_DIR + "/../redesign"))
+
DEBUG = True
TEMPLATE_DEBUG = DEBUG
@@ -118,6 +121,11 @@ INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.humanize',
'south',
+ 'redesign.person',
+ 'redesign.name',
+ 'redesign.group',
+ 'redesign.doc',
+ 'redesign.issue',
'ietf.announcements',
'ietf.idindex',
'ietf.idtracker',
@@ -187,6 +195,9 @@ LIAISON_UNIVERSAL_FROM = 'Liaison Statement Management Tool fields["time"]:
+ snap.time = fields["time"]
+ snap.save()
+ except DocHistory.DoesNotExist:
+ snap = DocHistory(**fields)
+ snap.save()
+ for m in many2many:
+ #print "m2m:", m, many2many[m]
+ rel = getattr(snap, m)
+ for item in many2many[m]:
+ rel.add(item)
+ except DocHistory.MultipleObjectsReturned:
+ list = DocHistory.objects.filter(**dict((k,v) for k,v in fields.items() if k != 'time'))
+ list.delete()
+ snap = DocHistory(**fields)
+ snap.save()
+ print "Deleted list:", snap
+ super(Document, self).save(force_insert, force_update)
+
+class DocHistory(DocumentInfo):
+ doc = models.ForeignKey(Document) # ID of the Document this relates to
+ def __unicode__(self):
+ return unicode(self.doc.name)
+
+class DocAlias(models.Model):
+ """This is used for documents that may appear under multiple names,
+ and in particular for RFCs, which for continuity still keep the
+ same immutable Document.name, in the tables, but will be referred
+ to by RFC number, primarily, after achieving RFC status.
+ """
+ document = models.ForeignKey(Document)
+ name = models.CharField(max_length=255)
+ def __unicode__(self):
+ return "%s-->%s" % (self.name, self.document.name)
+ document_link = admin_link("document")
+ class Meta:
+ verbose_name_plural="Aliases"
+
+class SendQueue(models.Model):
+ time = models.DateTimeField() # Scheduled at this time
+ agent = models.ForeignKey(Email) # Scheduled by this person
+ comment = models.TextField()
+ #
+ msg = models.ForeignKey('Message')
+ to = models.ForeignKey(Email, related_name='to_messages')
+ cc = models.ManyToManyField(Email, related_name='cc_messages')
+ send = models.DateTimeField() # Send message at this time
+
+# class Ballot(models.Model): # A collection of ballot positions
+# """A collection of ballot positions, and the actions taken during the
+# lifetime of the ballot.
+
+# The actual ballot positions are found by searching Messages for
+# BallotPositions for this document between the dates indicated by
+# self.initiated.time and (self.closed.time or now)
+# """
+# initiated = models.ForeignKey(Message, related_name="initiated_ballots")
+# deferred = models.ForeignKey(Message, null=True, blank=True, related_name="deferred_ballots")
+# last_call = models.ForeignKey(Message, null=True, blank=True, related_name="lastcalled_ballots")
+# closed = models.ForeignKey(Message, null=True, blank=True, related_name="closed_ballots")
+# announced = models.ForeignKey(Message, null=True, blank=True, related_name="announced_ballots")
+
+
+EVENT_TYPES = [
+ # misc document events
+ ("new_revision", "Added new revision"),
+ ("document_changed", "Changed document"),
+ ("tombstone_added", "Added tombstone"),
+ ("requested_resurrect", "Requested resurrect"),
+
+ # IESG events
+ ("sent_ballot_announcement", "Sent ballot announcement"),
+ ("deferred_ballot", "Deferred ballot"),
+ ("approved_ballot", "Approved ballot"),
+ ("changed_ballot_position", "Changed ballot position"),
+ ("changed_ballot_approval_text", "Changed ballot approval text"),
+ ("changed_ballot_writeup_text", "Changed ballot writeup text"),
+
+ ("changed_last_call_text", "Changed last call text"),
+ ("sent_last_call", "Sent last call"),
+
+ ("scheduled_for_telechat", "Scheduled for telechat"),
+
+ ("resolved_to_do_not_publish", "Resolved to 'do not publish'"),
+ ("resolved_to_no_problem", "Resolved to 'no problem'"),
+
+ ("approved_in_minute", "Approved in minute"),
+ ]
+
+class Event(models.Model):
+ """An occurrence in connection with a document."""
+ time = models.DateTimeField()
+ type = models.CharField(max_length=50, choices=EVENT_TYPES)
+ by = models.ForeignKey(Email, blank=True, null=True)
+ doc = models.ForeignKey('doc.Document')
+ desc = models.TextField()
+
+ def __unicode__(self):
+ return u"%s %s at %s" % (self.by.get_name(), self.get_type_display().lower(), self.time)
+
+ class Meta:
+ ordering = ['-time']
+
+class Message(Event):
+ subj = models.CharField(max_length=255)
+ body = models.TextField()
+
+class Text(Event):
+ content = models.TextField(blank=True)
+
+# IESG events
+class BallotPosition(Event):
+ ad = models.ForeignKey(Email)
+ pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norec")
+ discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True)
+ discuss_time = models.DateTimeField(help_text="Time discuss text was written", blank=True, null=True)
+ comment = models.TextField(help_text="Optional comment", blank=True)
+ comment_time = models.DateTimeField(help_text="Time optional comment was written", blank=True, null=True)
+
+class Expiration(Event):
+ expires = models.DateTimeField()
+
+class Telechat(Event):
+ telechat_date = models.DateField()
+ returning_item = models.BooleanField(default=False)
+
+
+
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
new file mode 100644
index 000000000..d381b29f9
--- /dev/null
+++ b/redesign/doc/proxy.py
@@ -0,0 +1,529 @@
+from models import *
+from redesign.person.models import Email
+from redesign.proxy_utils import TranslatingManager
+
+
+class InternetDraft(Document):
+ objects = TranslatingManager(dict(filename="name",
+ id_document_tag="id",
+ status="state"))
+
+ DAYS_TO_EXPIRE=185
+
+ # things from InternetDraft
+
+# id_document_tag = models.AutoField(primary_key=True)
+ @property
+ def id_document_tag(self):
+ return self.name # Will only work for some use cases
+# title = models.CharField(max_length=255, db_column='id_document_name') # same name
+# id_document_key = models.CharField(max_length=255, editable=False)
+ @property
+ def id_document_key(self):
+ return self.title.upper()
+# group = models.ForeignKey(Acronym, db_column='group_acronym_id')
+ @property
+ def group(self):
+ return super(self.__class__, self).group
+# filename = models.CharField(max_length=255, unique=True)
+ @property
+ def filename(self):
+ return self.name
+# revision = models.CharField(max_length=2)
+ @property
+ def revision(self):
+ return self.rev
+# revision_date = models.DateField()
+ @property
+ def revision_date(self):
+ e = self.latest_event(type="new_revision")
+ return e.time if e else None
+# file_type = models.CharField(max_length=20)
+ @property
+ def file_type(self):
+ return ".txt" # FIXME XXX should look in the repository to see what's available
+# txt_page_count = models.IntegerField()
+ @property
+ def txt_page_count(self):
+ return self.pages
+# local_path = models.CharField(max_length=255, blank=True) # unused
+# start_date = models.DateField()
+ @property
+ def start_date(self):
+ return self.dochistory_set.dates("time","day","ASC")[0]
+# expiration_date = models.DateField()
+ @property
+ def expiration_date(self):
+ return self.expiration()
+# abstract = models.TextField() # same name
+# dunn_sent_date = models.DateField(null=True, blank=True) # unused
+# extension_date = models.DateField(null=True, blank=True) # unused
+# status = models.ForeignKey(IDStatus)
+ @property
+ def status(self):
+ from redesign.name.proxy import IDStatus
+ return IDStatus(self.state)
+
+ @property
+ def status_id(self):
+ from redesign.name.proxy import IDStatus
+ return { 'active': 1, 'repl': 5, 'expired': 2, 'rfc': 3, 'auth-rm': 4, 'ietf-rm': 6 }[self.state_id]
+
+# intended_status = models.ForeignKey(IDIntendedStatus)
+ @property
+ def intended_status(self):
+ return self.intended_std_level
+
+# lc_sent_date = models.DateField(null=True, blank=True)
+ @property
+ def lc_sent_date(self):
+ e = self.latest_event(type="sent_last_call")
+ return e.time if e else None
+
+# lc_changes = models.CharField(max_length=3) # used in DB, unused in Django code?
+
+# lc_expiration_date = models.DateField(null=True, blank=True)
+ @property
+ def lc_expiration_date(self):
+ e = self.latest_event(type="sent_last_call")
+ return e.expiration.expires if e else None
+
+# b_sent_date = models.DateField(null=True, blank=True)
+ @property
+ def b_sent_date(self):
+ e = self.latest_event(type="sent_ballot_announcement")
+ return e.time if e else None
+
+# b_discussion_date = models.DateField(null=True, blank=True) # unused
+
+# b_approve_date = models.DateField(null=True, blank=True)
+ @property
+ def b_approve_date(self):
+ e = self.latest_event(type="approved_ballot")
+ return e.time if e else None
+
+# wgreturn_date = models.DateField(null=True, blank=True) # unused
+
+# rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
+ @property
+ def rfc_number(self):
+ try:
+ self.docalias_set.filter(name__startswith="rfc")[0].name[3:]
+ except IndexError:
+ return None
+
+# comments = models.TextField(blank=True) # unused
+
+# last_modified_date = models.DateField()
+ @property
+ def last_modified_date(self):
+ return self.time
+
+# replaced_by = models.ForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
+ @property
+ def replaced_by(self):
+ r = InternetDraft.objects.filter(docalias__relateddoc__relationship="replaces", docalias__relateddoc__related_document_set=self)
+ return r[0] if r else None
+
+# replaces = FKAsOneToOne('replaces', reverse=True)
+ @property
+ def replaces(self):
+ r = InternetDraft.objects.filter(related__doc_alias__document=self, related__relationship="replaces")
+ return r[0] if r else Non
+
+
+ @property
+ def replaces_set(self):
+ # this is replaced_by
+ return InternetDraft.objects.filter(docalias__relateddoc__relationship="replaces", docalias__relateddoc__related_document_set=self)
+
+# review_by_rfc_editor = models.BooleanField()
+ @property
+ def review_by_rfc_editor(self): raise NotImplemented # should use tag
+
+# expired_tombstone = models.BooleanField()
+ @property
+ def expired_tombstone(self):
+ # FIXME: this is probably not perfect, what happens when we delete it again
+ return self.latest_event(type="added_tombstone")
+
+# idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
+ @property
+ def idinternal(self):
+ return self if self.iesg_state else None
+
+ # methods from InternetDraft
+ def displayname(self):
+ return self.name
+ def file_tag(self):
+ return "<%s-%s.txt>" % (self.name, self.revision_display())
+ def group_acronym(self):
+ return self.group.acronym
+ def idstate(self):
+ return self.docstate()
+ def revision_display(self):
+ r = int(self.revision)
+ if self.state_id != 'active' and not self.expired_tombstone:
+ r = max(r - 1, 0)
+ return "%02d" % r
+ def expiration(self):
+ return self.revision_date + datetime.timedelta(self.DAYS_TO_EXPIRE)
+ def can_expire(self):
+ # Copying the logic from expire-ids-1 without thinking
+ # much about it.
+ if self.review_by_rfc_editor:
+ return False
+ idinternal = self.idinternal
+ if idinternal:
+ cur_state_id = idinternal.cur_state_id
+ # 42 is "AD is Watching"; this matches what's in the
+ # expire-ids-1 perl script.
+ # A better way might be to add a column to the table
+ # saying whether or not a document is prevented from
+ # expiring.
+ if cur_state_id < 42:
+ return False
+ return True
+
+ def clean_abstract(self):
+ # Cleaning based on what "id-abstracts-text" script does
+ import re
+ a = self.abstract
+ a = re.sub(" *\r\n *", "\n", a) # get rid of DOS line endings
+ a = re.sub(" *\r *", "\n", a) # get rid of MAC line endings
+ a = re.sub("(\n *){3,}", "\n\n", a) # get rid of excessive vertical whitespace
+ a = re.sub("\f[\n ]*[^\n]*\n", "", a) # get rid of page headers
+ # Get rid of 'key words' boilerplate and anything which follows it:
+ # (No way that is part of the abstract...)
+ a = re.sub("(?s)(Conventions [Uu]sed in this [Dd]ocument|Requirements [Ll]anguage)?[\n ]*The key words \"MUST\", \"MUST NOT\",.*$", "", a)
+ # Get rid of status/copyright boilerplate
+ a = re.sub("(?s)\nStatus of [tT]his Memo\n.*$", "", a)
+ # wrap long lines without messing up formatting of Ok paragraphs:
+ while re.match("([^\n]{72,}?) +", a):
+ a = re.sub("([^\n]{72,}?) +([^\n ]*)(\n|$)", "\\1\n\\2 ", a)
+ # Remove leading and trailing whitespace
+ a = a.strip()
+ return a
+
+
+ # things from IDInternal
+
+ #draft = models.ForeignKey(InternetDraft, primary_key=True, unique=True, db_column='id_document_tag')
+ @property
+ def draft(self):
+ return self
+
+ #rfc_flag = models.IntegerField(null=True)
+ @property
+ def rfc_flag(self):
+ return self.state_id == "rfc"
+
+ #ballot = models.ForeignKey(BallotInfo, related_name='drafts', db_column="ballot_id")
+ @property
+ def ballot(self):
+ return self # FIXME: raise BallotInfo.DoesNotExist?
+
+ #primary_flag = models.IntegerField(blank=True, null=True)
+ @property
+ def primary(self):
+ return True # left-over from multi-ballot documents?
+
+ #group_flag = models.IntegerField(blank=True, default=0) # unused?
+
+ #token_name = models.CharField(blank=True, max_length=25)
+ @property
+ def token_name(self):
+ e = self.latest_event()
+ return e.by.person.name if e else None
+
+ #token_email = models.CharField(blank=True, max_length=255)
+ @property
+ def token_email(self):
+ e = self.latest_event()
+ return e.by.address if e else None
+
+ #note = models.TextField(blank=True) # same name
+
+ #status_date = models.DateField(blank=True,null=True)
+ @property
+ def status_date(self): raise NotImplemented # FIXME
+
+ #email_display = models.CharField(blank=True, max_length=50) # unused
+ #agenda = models.IntegerField(null=True, blank=True)
+ @property
+ def agenda(self):
+ return bool(self.latest_event(type="scheduled_for_telechat"))
+
+ #cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs')
+ @property
+ def cur_state(self):
+ return self.iesg_state
+
+ #prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
+ @property
+ def prev_state(self):
+ ds = self.dochistory_set.all().order_by('-time')[:1]
+ return ds[0].iesg_state if ds else None
+
+ #assigned_to = models.CharField(blank=True, max_length=25) # unused
+
+ #mark_by = models.ForeignKey(IESGLogin, db_column='mark_by', related_name='marked')
+ @property
+ def mark_by(self):
+ e = self.latest_event()
+ return e.by if e else None
+
+ # job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
+ @property
+ def job_owner(self):
+ return self.ad
+
+ #event_date = models.DateField(null=True)
+ @property
+ def event_date(self):
+ e = self.latest_event()
+ return e.time if e else None
+
+ #area_acronym = models.ForeignKey(Area)
+ @property
+ def area_acronym(self):
+ if self.group:
+ return self.group.parent
+ elif self.ad:
+ # return area for AD
+ return ad.role_set.get(type="ad", group__state="active").group
+ else:
+ return None
+
+ #cur_sub_state = BrokenForeignKey(IDSubState, related_name='docs', null=True, blank=True, null_values=(0, -1))
+ @property
+ def cur_sub_state(self):
+ return ", ".join(self.tags.all())
+ @property
+ def cur_sub_state_id(self):
+ return 0
+
+ #prev_sub_state = BrokenForeignKey(IDSubState, related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
+ @property
+ def prev_sub_state(self):
+ ds = self.dochistory_set.all().order_by('-time')[:1]
+ return "|".join(ds[0].tags.all()) if ds else None
+ @property
+ def prev_sub_state_id(self):
+ return 0
+
+ #returning_item = models.IntegerField(null=True, blank=True)
+ @property
+ def returning_item(self):
+ e = self.latest_event(type="scheduled_for_telechat")
+ return e.telechat.returning_item if e else None
+
+ #telechat_date = models.DateField(null=True, blank=True)
+ @property
+ def telechat_date(self):
+ e = self.latest_event(type="scheduled_for_telechat")
+ return e.telechat.telechat_date if e else None
+
+ #via_rfc_editor = models.IntegerField(null=True, blank=True)
+ @property
+ def via_rfc_editor(self):
+ return bool(self.tags.filter(slug='viarfceditor'))
+
+ #state_change_notice_to = models.CharField(blank=True, max_length=255)
+ @property
+ def state_change_notice_to(self):
+ return self.notify
+
+ #dnp = models.IntegerField(null=True, blank=True)
+ @property
+ def dnp(self):
+ return self.latest_event(type="resolved_to_do_not_publish")
+
+ #dnp_date = models.DateField(null=True, blank=True)
+ @property
+ def dnp_date(self):
+ e = self.latest_event(type="resolved_to_do_not_publish")
+ return e.time if e else None
+
+ #noproblem = models.IntegerField(null=True, blank=True)
+ @property
+ def noproblem(self):
+ return self.latest_event(type="resolved_to_no_problem")
+
+ #resurrect_requested_by = BrokenForeignKey(IESGLogin, db_column='resurrect_requested_by', related_name='docsresurrected', null=True, blank=True)
+ @property
+ def resurrect_requested_by(self):
+ return self.latest_event(type="requested_resurrect")
+
+ #approved_in_minute = models.IntegerField(null=True, blank=True)
+ @property
+ def approved_in_minute(self):
+ return self.latest_event(type="approved_in_minute")
+
+
+ def get_absolute_url(self):
+ if self.rfc_flag:
+ return "/doc/rfc%d/" % self.rfc_number
+ else:
+ return "/doc/%s/" % self.name
+
+ def document(self):
+ return self
+
+ def comments(self):
+ return self.event_set.all().order_by('-time')
+
+ def ballot_set(self):
+ return [self]
+ def ballot_primary(self):
+ return [self]
+ def ballot_others(self):
+ return []
+ def docstate(self):
+ if self.iesg_state:
+ return self.iesg_state.name
+ else:
+ return "I-D Exists"
+ def change_state(self, state, sub_state):
+ self.iesg_state = state
+
+
+ # things from BallotInfo
+ #active = models.BooleanField()
+ @property
+ def active(self):
+ return self.iesg_state and self.iesg_state.name in ['In Last Call', 'Waiting for Writeup', 'Waiting for AD Go-Ahead', 'IESG Evaluation', 'IESG Evaluation - Defer']
+
+ #an_sent = models.BooleanField()
+ @property
+ def an_sent(self):
+ return bool(self.latest_event(type="approved_ballot"))
+
+ #an_sent_date = models.DateField(null=True, blank=True)
+ @property
+ def an_sent_date(self):
+ e = self.latest_event(type="approved_ballot")
+ return e.time if e else None
+
+ #an_sent_by = models.ForeignKey(IESGLogin, db_column='an_sent_by', related_name='ansent', null=True)
+ @property
+ def an_sent_by(self):
+ e = self.latest_event(type="approved_ballot")
+ return e.by if e else None
+
+ #defer = models.BooleanField()
+ @property
+ def defer(self):
+ return bool(self.latest_event(type="deferred_ballot"))
+
+ #defer_by = models.ForeignKey(IESGLogin, db_column='defer_by', related_name='deferred', null=True)
+ @property
+ def defer_by(self):
+ e = self.latest_event(type="deferred_ballot")
+ return e.by if e else None
+
+ #defer_date = models.DateField(null=True, blank=True)
+ @property
+ def defer_date(self):
+ e = self.latest_event(type="deferred_ballot")
+ return e.time if e else None
+
+ #approval_text = models.TextField(blank=True)
+ @property
+ def approval_text(self):
+ e = self.latest_event(type="changed_ballot_approval_text")
+ return e.text.content if e else ""
+
+ #last_call_text = models.TextField(blank=True)
+ @property
+ def last_call_text(self):
+ e = self.latest_event(type="changed_last_call_text")
+ return e.text.content if e else ""
+
+ #ballot_writeup = models.TextField(blank=True)
+ @property
+ def ballot_writeup(self):
+ e = self.latest_event(type="changed_ballot_writeup_text")
+ return e.text.content if e else ""
+
+ #ballot_issued = models.IntegerField(null=True, blank=True)
+ @property
+ def ballot_issued(self):
+ return bool(self.latest_event(type="sent_ballot_announcement"))
+
+ # def remarks(self): # apparently not used
+ # remarks = list(self.discusses.all()) + list(self.comments.all())
+ # return remarks
+ def active_positions(self):
+ """Returns a list of dicts, with AD and Position tuples"""
+ active_ads = Email.objects.filter(role__name="ad", role__group__state="active")
+
+ res = []
+ def add(ad, pos):
+ # FIXME: ad and pos don't emulate old interface
+ res.append(dict(ad=ad, pos=pos))
+
+ found = set()
+ for pos in self.event_set.filter(type="changed_ballot_position", ballotposition__ad__in=active_ads).select_related('ad').order_by("-time"):
+ if not pos.ballotposition.ad in found:
+ found.add(pos.ballotposition.ad)
+ add(pos.ballotposition.ad, pos)
+
+ for ad in active_ads:
+ if ad not in found:
+ add(ad, None)
+
+ return res
+
+ def needed(self, standardsTrack=True):
+ """Returns text answering the question what does this document
+ need to pass?. The return value is only useful if the document
+ is currently in IESG evaluation."""
+ tmp = self.active_positions()
+ positions = [x["pos"] for x in tmp if x["pos"]]
+ ads = [x["ad"] for x in tmp]
+
+ yes = noobj = discuss = recuse = 0
+ for position in positions:
+ p = position.pos_id
+ if p == "yes":
+ yes += 1
+ if p == "noobj":
+ noobj += 1
+ if p == "discuss":
+ discuss += 1
+ if p == "recuse":
+ recuse += 1
+ answer = ''
+ if yes < 1:
+ answer += "Needs a YES. "
+ if discuss > 0:
+ if discuss == 1:
+ answer += "Has a DISCUSS. "
+ else:
+ answer += "Has %d DISCUSSes. " % discuss
+ if standardsTrack:
+ # For standards-track, need positions from 2/3 of the
+ # non-recused current IESG.
+ needed = (len(ads) - recuse) * 2 / 3
+ else:
+ # Info and experimental only need one position.
+ needed = 1
+ have = yes + noobj + discuss
+ if have < needed:
+ more = needed - have
+ if more == 1:
+ answer += "Needs %d more position. " % more
+ else:
+ answer += "Needs %d more positions. " % more
+ else:
+ answer += "Has enough positions to pass"
+ if discuss:
+ answer += " once DISCUSSes are resolved"
+ answer += ". "
+
+ return answer.rstrip()
+
+
+ class Meta:
+ proxy = True
diff --git a/redesign/group/__init__.py b/redesign/group/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/redesign/group/admin.py b/redesign/group/admin.py
new file mode 100644
index 000000000..46f324cd7
--- /dev/null
+++ b/redesign/group/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+from models import *
+
+class GroupAdmin(admin.ModelAdmin):
+ list_display = ["acronym", "name", "type"]
+ search_fields = ["name"]
+ ordering = ["name"]
+ raw_id_fields = ["charter"]
+
+admin.site.register(Group, GroupAdmin)
+admin.site.register(GroupHistory)
+
+admin.site.register(Role)
diff --git a/redesign/group/models.py b/redesign/group/models.py
new file mode 100644
index 000000000..8d5311b3e
--- /dev/null
+++ b/redesign/group/models.py
@@ -0,0 +1,53 @@
+# Copyright The IETF Trust 2007, All Rights Reserved
+
+from django.db import models
+from redesign.name.models import *
+from redesign.person.models import Email
+
+class Group(models.Model):
+ name = models.CharField(max_length=80)
+ acronym = models.CharField(max_length=16)
+ state = models.ForeignKey(GroupStateName, null=True)
+ type = models.ForeignKey(GroupTypeName, null=True)
+ charter = models.OneToOneField('doc.Document', related_name='chartered_group', blank=True, null=True)
+ parent = models.ForeignKey('Group', blank=True, null=True)
+ list_email = models.CharField(max_length=64, blank=True)
+ list_pages = models.CharField(max_length=64, blank=True)
+ comments = models.TextField(blank=True)
+ def __unicode__(self):
+ return self.name
+
+# This will actually be extended from Groups, but that requires Django 1.0
+# This will record the new state and the date it occurred for any changes
+# to a group. The group acronym must be unique and is the invariant used
+# to select group history from this table.
+class GroupHistory(models.Model):
+ group = models.ForeignKey('Group', related_name='group_history')
+ # Event related
+ time = models.DateTimeField()
+ comment = models.TextField()
+ who = models.ForeignKey(Email, related_name='group_changes')
+ # inherited from Group:
+ name = models.CharField(max_length=64)
+ acronym = models.CharField(max_length=16)
+ state = models.ForeignKey(GroupStateName)
+ type = models.ForeignKey(GroupTypeName)
+ charter = models.ForeignKey('doc.Document', related_name='chartered_group_history')
+ parent = models.ForeignKey('Group')
+ chairs = models.ManyToManyField(Email, related_name='chaired_groups_history')
+ list_email = models.CharField(max_length=64)
+ list_pages = models.CharField(max_length=64)
+ comments = models.TextField(blank=True)
+ def __unicode__(self):
+ return self.group.name
+ class Meta:
+ verbose_name_plural="Doc histories"
+
+class Role(models.Model):
+ name = models.ForeignKey(RoleName)
+ group = models.ForeignKey(Group)
+ email = models.ForeignKey(Email)
+ auth = models.CharField(max_length=255, blank=True)
+ def __unicode__(self):
+ return self.name
+
diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py
new file mode 100644
index 000000000..ed2f08d77
--- /dev/null
+++ b/redesign/group/proxy.py
@@ -0,0 +1,46 @@
+from models import *
+
+class Acronym(Group):
+ def __init__(self, base):
+ for f in base._meta.fields:
+ setattr(self, f.name, getattr(base, f.name))
+
+ #acronym_id = models.AutoField(primary_key=True)
+ @property
+ def acronym_id(self):
+ raise NotImplemented
+ #acronym = models.CharField(max_length=12) # same name
+ #name = models.CharField(max_length=100) # same name
+ #name_key = models.CharField(max_length=50, editable=False)
+ @property
+ def name_key(self):
+ return self.name.upper()
+
+ class Meta:
+ proxy = True
+
+class Area(Group):
+ ACTIVE=1
+ #area_acronym = models.OneToOneField(Acronym, primary_key=True)
+ @property
+ def area_acronym(self):
+ return Acronym(self)
+
+ #start_date = models.DateField(auto_now_add=True)
+ #concluded_date = models.DateField(null=True, blank=True)
+ #status = models.ForeignKey(AreaStatus)
+ #comments = models.TextField(blank=True)
+ #last_modified_date = models.DateField(auto_now=True)
+ #extra_email_addresses = models.TextField(blank=True,null=True)
+
+ #def additional_urls(self):
+ # return AreaWGURL.objects.filter(name=self.area_acronym.name)
+ #def active_wgs(self):
+ # return IETFWG.objects.filter(group_type=1,status=IETFWG.ACTIVE,areagroup__area=self).order_by('group_acronym__acronym')
+
+ @staticmethod
+ def active_areas():
+ return Area.objects.filter(type="area", state="active")
+
+ class Meta:
+ proxy = True
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
new file mode 100755
index 000000000..1ddeae781
--- /dev/null
+++ b/redesign/import-document-state.py
@@ -0,0 +1,320 @@
+#!/usr/bin/python
+
+import sys, os, re, datetime
+
+basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+sys.path = [ basedir ] + sys.path
+
+from ietf import settings
+settings.USE_DB_REDESIGN_PROXY_CLASSES = False
+
+from django.core import management
+management.setup_environ(settings)
+
+from redesign.doc.models import *
+from redesign.group.models import *
+from redesign.name.models import *
+from ietf.idtracker.models import InternetDraft, IESGLogin, DocumentComment
+
+# assumptions:
+# - groups have been imported
+# - iesglogin emails have been imported
+
+# FIXME: what about RFCs
+
+def name(name_class, slug, name, desc=""):
+ # create if it doesn't exist, set name
+ obj, _ = name_class.objects.get_or_create(slug=slug)
+ obj.name = name
+ obj.desc = desc
+ obj.save()
+ return obj
+
+type_draft = name(DocTypeName, "draft", "Draft")
+stream_ietf = name(DocStreamName, "ietf", "IETF")
+
+intended_status_mapping = {
+ "BCP": name(IntendedStatusName, "bcp", "Best Current Practice"),
+ "Draft Standard": name(IntendedStatusName, "ds", name="Draft Standard"),
+ "Experimental": name(IntendedStatusName, "exp", name="Experimental"),
+ "Historic": name(IntendedStatusName, "hist", name="Historic"),
+ "Informational": name(IntendedStatusName, "inf", name="Informational"),
+ "Proposed Standard": name(IntendedStatusName, "ps", name="Proposed Standard"),
+ "Standard": name(IntendedStatusName, "std", name="Standard"),
+ "None": None,
+ "Request": None, # FIXME: correct? from idrfc_wrapper.py
+ }
+
+status_mapping = {
+ 'Active': name(DocStateName, "active", "Active"),
+ 'Expired': name(DocStateName, "expired", "Expired"),
+ 'RFC': name(DocStateName, "rfc", "RFC"),
+ 'Withdrawn by Submitter': name(DocStateName, "auth-rm", "Withdrawn by Submitter"),
+ 'Replaced': name(DocStateName, "repl", "Replaced"),
+ 'Withdrawn by IETF': name(DocStateName, "ietf-rm", "Withdrawn by IETF"),
+ }
+
+iesg_state_mapping = {
+ 'RFC Published': name(IesgDocStateName, "pub", "RFC Published", 'The ID has been published as an RFC.'),
+ 'Dead': name(IesgDocStateName, "dead", "Dead", 'Document is "dead" and is no longer being tracked. (E.g., it has been replaced by another document with a different name, it has been withdrawn, etc.)'),
+ 'Approved-announcement to be sent': name(IesgDocStateName, "approved", "Approved-announcement to be sent", 'The IESG has approved the document for publication, but the Secretariat has not yet sent out on official approval message.'),
+ 'Approved-announcement sent': name(IesgDocStateName, "ann", "Approved-announcement sent", 'The IESG has approved the document for publication, and the Secretariat has sent out the official approval message to the RFC editor.'),
+ 'AD is watching': name(IesgDocStateName, "watching", "AD is watching", 'An AD is aware of the document and has chosen to place the document in a separate state in order to keep a closer eye on it (for whatever reason). Documents in this state are still not being actively tracked in the sense that no formal request has been made to publish or advance the document. The sole difference between this state and "I-D Exists" is that an AD has chosen to put it in a separate state, to make it easier to keep track of (for the AD\'s own reasons).'),
+ 'IESG Evaluation': name(IesgDocStateName, "iesg-eva", "IESG Evaluation", 'The document is now (finally!) being formally reviewed by the entire IESG. Documents are discussed in email or during a bi-weekly IESG telechat. In this phase, each AD reviews the document and airs any issues they may have. Unresolvable issues are documented as "discuss" comments that can be forwarded to the authors/WG. See the description of substates for additional details about the current state of the IESG discussion.'),
+ 'AD Evaluation': name(IesgDocStateName, "ad-eval", "AD Evaluation", 'A specific AD (e.g., the Area Advisor for the WG) has begun reviewing the document to verify that it is ready for advancement. The shepherding AD is responsible for doing any necessary review before starting an IETF Last Call or sending the document directly to the IESG as a whole.'),
+ 'Last Call Requested': name(IesgDocStateName, "lc-req", "Last Call requested", 'The AD has requested that the Secretariat start an IETF Last Call, but the the actual Last Call message has not been sent yet.'),
+ 'In Last Call': name(IesgDocStateName, "lc", "In Last Call", 'The document is currently waiting for IETF Last Call to complete. Last Calls for WG documents typically last 2 weeks, those for individual submissions last 4 weeks.'),
+ 'Publication Requested': name(IesgDocStateName, "pub-req", "Publication Requested", 'A formal request has been made to advance/publish the document, following the procedures in Section 7.5 of RFC 2418. The request could be from a WG chair, from an individual through the RFC Editor, etc. (The Secretariat (iesg-secretary@ietf.org) is copied on these requests to ensure that the request makes it into the ID tracker.) A document in this state has not (yet) been reviewed by an AD nor has any official action been taken on it yet (other than to note that its publication has been requested.'),
+ 'RFC Ed Queue': name(IesgDocStateName, "rfcqueue", "RFC Ed Queue", 'The document is in the RFC editor Queue (as confirmed by http://www.rfc-editor.org/queue.html).'),
+ 'IESG Evaluation - Defer': name(IesgDocStateName, "defer", "IESG Evaluation - Defer", 'During a telechat, one or more ADs requested an additional 2 weeks to review the document. A defer is designed to be an exception mechanism, and can only be invoked once, the first time the document comes up for discussion during a telechat.'),
+ 'Waiting for Writeup': name(IesgDocStateName, "writeupw", "Waiting for Writeup", 'Before a standards-track or BCP document is formally considered by the entire IESG, the AD must write up a protocol action. The protocol action is included in the approval message that the Secretariat sends out when the document is approved for publication as an RFC.'),
+ 'Waiting for AD Go-Ahead': name(IesgDocStateName, "goaheadw", "Waiting for AD Go-Ahead", 'As a result of the IETF Last Call, comments may need to be responded to and a revision of the ID may be needed as well. The AD is responsible for verifying that all Last Call comments have been adequately addressed and that the (possibly revised) document is in the ID directory and ready for consideration by the IESG as a whole.'),
+ 'Expert Review': name(IesgDocStateName, "review-e", "Expert Review", 'An AD sometimes asks for an external review by an outside party as part of evaluating whether a document is ready for advancement. MIBs, for example, are reviewed by the "MIB doctors". Other types of reviews may also be requested (e.g., security, operations impact, etc.). Documents stay in this state until the review is complete and possibly until the issues raised in the review are addressed. See the "note" field for specific details on the nature of the review.'),
+ 'DNP-waiting for AD note': name(IesgDocStateName, "nopubadw", "DNP-waiting for AD note", 'Do Not Publish: The IESG recommends against publishing the document, but the writeup explaining its reasoning has not yet been produced. DNPs apply primarily to individual submissions received through the RFC editor. See the "note" field for more details on who has the action item.'),
+ 'DNP-announcement to be sent': name(IesgDocStateName, "nopubanw", "DNP-announcement to be sent", 'The IESG recommends against publishing the document, the writeup explaining its reasoning has been produced, but the Secretariat has not yet sent out the official "do not publish" recommendation message.'),
+ None: None, # FIXME: consider introducing the ID-exists state
+ }
+
+ballot_position_mapping = {
+ 'No Objection': name(BallotPositionName, 'noobj', 'No Objection'),
+ 'Yes': name(BallotPositionName, 'yes', 'Yes'),
+ 'Abstain': name(BallotPositionName, 'abstain', 'Abstain'),
+ 'Discuss': name(BallotPositionName, 'discuss', 'Discuss'),
+ 'Recuse': name(BallotPositionName, 'recuse', 'Recuse'),
+ 'Undefined': name(BallotPositionName, 'norecord', 'No record'),
+ None: name(BallotPositionName, 'norecord', 'No record'),
+ }
+
+# regexps for parsing document comments
+
+date_re_str = "(?P[0-9][0-9][0-9][0-9])-(?P[0-9][0-9])-(?P[0-9][0-9])"
+def date_in_match(match):
+ return datetime.date(int(match.group('year')), int(match.group('month')), int(match.group('day')))
+
+re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat - %s by" % date_re_str)
+re_ballot_position = re.compile(r"\[Ballot Position Update\] (New position, (?P.*), has been recorded (|for (?P.*) )|Position (|for (?P.*) )has been changed to (?P.*) from .*)by (?P.*)")
+re_ballot_issued = re.compile(r"Ballot has been issued by")
+
+# helpers for events
+
+def save_event(doc, event, comment):
+ event.time = comment.datetime()
+ event.by = iesg_login_to_email(comment.created_by)
+ event.doc = doc
+ event.desc = comment.comment_text # FIXME: consider unquoting here
+ event.save()
+
+def iesg_login_to_email(l):
+ if not l:
+ return None
+ else:
+ try:
+ return Email.objects.get(address=l.person.email()[1])
+ except Email.DoesNotExist:
+ print "MISSING IESG LOGIN", l.person.email()
+ return None
+
+
+
+all_drafts = InternetDraft.objects.all().select_related()
+all_drafts = all_drafts.filter(filename="draft-arkko-townsley-coexistence")
+#all_drafts = all_drafts[all_drafts.count() - 1000:]
+
+for o in all_drafts:
+ try:
+ d = Document.objects.get(name=o.filename)
+ except Document.DoesNotExist:
+ d = Document(name=o.filename)
+
+ d.time = o.idinternal.event_date if o.idinternal else o.revision_date
+ d.type = type_draft
+ d.title = o.title
+ d.state = status_mapping[o.status.status]
+ d.group = Group.objects.get(acronym=o.group.acronym)
+# d.tags =
+ d.stream = stream_ietf
+ d.wg_state = None
+ d.iesg_state = iesg_state_mapping[o.idinternal.cur_state.state if o.idinternal else None]
+ d.iana_state = None
+# d.rfc_state =
+ d.rev = o.revision
+ d.abstract = o.abstract
+ d.pages = o.txt_page_count
+ d.intended_std_level = intended_status_mapping[o.intended_status.intended_status]
+# d.std_level =
+# d.authors =
+# d.related =
+ d.ad = iesg_login_to_email(o.idinternal.job_owner)
+ d.shepherd = None
+ d.notify = o.idinternal.state_change_notice_to or "" if o.idinternal else ""
+ d.external_url = ""
+ d.note = o.idinternal.note or "" if o.idinternal else ""
+ d.internal_comments = o.comments or "" # FIXME: maybe put these somewhere else
+ d.save()
+
+ if o.idinternal:
+ # clear already imported events
+ d.event_set.all().delete()
+
+ # extract events
+ for c in o.idinternal.documentcomment_set.order_by('date', 'time', 'id'):
+ # telechat agenda schedulings
+ match = re_telechat_agenda.search(c.comment_text)
+ if match:
+ e = Telechat()
+ e.type = "scheduled_for_telechat"
+ e.telechat_date = date_in_match(match)
+ # can't extract this from history so we just take the latest value
+ e.returning_item = bool(o.idinternal.returning_item)
+ save_event(d, e, c)
+
+
+ # ballot issued
+ match = re_ballot_issued.search(c.comment_text)
+ if match:
+ e = Text()
+ e.type = "sent_ballot_announcement"
+ save_event(d, e, c)
+
+ # when you issue a ballot, you also vote yes; add that vote
+ e = BallotPosition()
+ e.type = "changed_ballot_position"
+ e.ad = iesg_login_to_email(c.created_by)
+ last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
+ e.pos = ballot_position_mapping["Yes"]
+ e.discuss = last_pos.ballotposition.discuss if last_pos else ""
+ e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
+ e.comment = last_pos.ballotposition.comment if last_pos else ""
+ e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
+ save_event(d, e, c)
+
+
+ # ballot positions
+ match = re_ballot_position.search(c.comment_text)
+ if match:
+ position = match.group('position') or match.group('position2')
+ ad_name = match.group('for') or match.group('for2') or match.group('by') # some of the old positions don't specify who it's for, in that case assume it's "by", the person who entered the position
+ ad_first, ad_last = ad_name.split(' ')
+
+ e = BallotPosition()
+ e.type = "changed_ballot_position"
+ e.ad = iesg_login_to_email(IESGLogin.objects.get(first_name=ad_first, last_name=ad_last))
+ last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
+ e.pos = ballot_position_mapping[position]
+ e.discuss = last_pos.ballotposition.discuss if last_pos else ""
+ e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
+ e.comment = last_pos.ballotposition.comment if last_pos else ""
+ e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
+ save_event(d, e, c)
+
+
+ # ballot discusses/comments
+ if c.ballot in (DocumentComment.BALLOT_DISCUSS, DocumentComment.BALLOT_COMMENT):
+ e = BallotPosition()
+ e.type = "changed_ballot_position"
+ e.ad = iesg_login_to_email(c.created_by)
+ last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
+ e.pos = last_pos.ballotposition.pos if last_pos else ballot_position_mapping[None]
+ if c.ballot == DocumentComment.BALLOT_DISCUSS:
+ e.discuss = c.comment_text
+ e.discuss_time = c.datetime()
+ e.comment = last_pos.ballotposition.comment if last_pos else ""
+ e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
+ # put header into description
+ c.comment_text = "[Ballot discuss]\n" + c.comment_text
+ else:
+ e.discuss = last_pos.ballotposition.discuss if last_pos else ""
+ e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
+ e.comment = c.comment_text
+ e.comment_time = c.datetime()
+ # put header into description
+ c.comment_text = "[Ballot comment]\n" + c.comment_text
+ save_event(d, e, c)
+
+
+ print "imported", d.name, "state", d.iesg_state
+
+
+
+# checklist of attributes below: handled attributes are commented out
+
+sys.exit(0)
+
+class CheckListInternetDraft(models.Model):
+# id_document_tag = models.AutoField(primary_key=True)
+# title = models.CharField(max_length=255, db_column='id_document_name')
+# id_document_key = models.CharField(max_length=255, editable=False)
+# group = models.ForeignKey(Acronym, db_column='group_acronym_id')
+# filename = models.CharField(max_length=255, unique=True)
+# revision = models.CharField(max_length=2)
+ revision_date = models.DateField()
+ file_type = models.CharField(max_length=20)
+# txt_page_count = models.IntegerField()
+ local_path = models.CharField(max_length=255, blank=True, null=True)
+ start_date = models.DateField()
+ expiration_date = models.DateField(null=True)
+# abstract = models.TextField()
+ dunn_sent_date = models.DateField(null=True, blank=True)
+ extension_date = models.DateField(null=True, blank=True)
+# status = models.ForeignKey(IDStatus)
+# intended_status = models.ForeignKey(IDIntendedStatus)
+ lc_sent_date = models.DateField(null=True, blank=True)
+ lc_changes = models.CharField(max_length=3,null=True)
+ lc_expiration_date = models.DateField(null=True, blank=True)
+ b_sent_date = models.DateField(null=True, blank=True)
+ b_discussion_date = models.DateField(null=True, blank=True)
+ b_approve_date = models.DateField(null=True, blank=True)
+ wgreturn_date = models.DateField(null=True, blank=True)
+ rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
+# comments = models.TextField(blank=True,null=True)
+ last_modified_date = models.DateField()
+ replaced_by = BrokenForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
+ replaces = FKAsOneToOne('replaces', reverse=True)
+ review_by_rfc_editor = models.BooleanField()
+ expired_tombstone = models.BooleanField()
+# idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
+
+class CheckListIDInternal(models.Model):
+# draft = models.ForeignKey(InternetDraft, primary_key=True, unique=True, db_column='id_document_tag')
+ rfc_flag = models.IntegerField(null=True)
+ ballot = models.ForeignKey('BallotInfo', related_name='drafts', db_column="ballot_id")
+ primary_flag = models.IntegerField(blank=True, null=True)
+ group_flag = models.IntegerField(blank=True, default=0)
+ token_name = models.CharField(blank=True, max_length=25)
+ token_email = models.CharField(blank=True, max_length=255)
+# note = models.TextField(blank=True)
+ status_date = models.DateField(blank=True,null=True)
+ email_display = models.CharField(blank=True, max_length=50)
+ agenda = models.IntegerField(null=True, blank=True)
+# cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs')
+ prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
+ assigned_to = models.CharField(blank=True, max_length=25)
+ mark_by = models.ForeignKey('IESGLogin', db_column='mark_by', related_name='marked')
+# job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
+ event_date = models.DateField(null=True)
+ area_acronym = models.ForeignKey('Area')
+ cur_sub_state = BrokenForeignKey('IDSubState', related_name='docs', null=True, blank=True, null_values=(0, -1))
+ prev_sub_state = BrokenForeignKey('IDSubState', related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
+# returning_item = models.IntegerField(null=True, blank=True)
+# telechat_date = models.DateField(null=True, blank=True)
+ via_rfc_editor = models.IntegerField(null=True, blank=True)
+# state_change_notice_to = models.CharField(blank=True, max_length=255)
+ dnp = models.IntegerField(null=True, blank=True)
+ dnp_date = models.DateField(null=True, blank=True)
+ noproblem = models.IntegerField(null=True, blank=True)
+ resurrect_requested_by = BrokenForeignKey('IESGLogin', db_column='resurrect_requested_by', related_name='docsresurrected', null=True, blank=True)
+ approved_in_minute = models.IntegerField(null=True, blank=True)
+
+class CheckListBallotInfo(models.Model):
+ ballot = models.AutoField(primary_key=True, db_column='ballot_id')
+ active = models.BooleanField()
+ an_sent = models.BooleanField()
+ an_sent_date = models.DateField(null=True, blank=True)
+ an_sent_by = models.ForeignKey('IESGLogin', db_column='an_sent_by', related_name='ansent', null=True)
+ defer = models.BooleanField(blank=True)
+ defer_by = models.ForeignKey('IESGLogin', db_column='defer_by', related_name='deferred', null=True)
+ defer_date = models.DateField(null=True, blank=True)
+ approval_text = models.TextField(blank=True)
+ last_call_text = models.TextField(blank=True)
+ ballot_writeup = models.TextField(blank=True)
+ ballot_issued = models.IntegerField(null=True, blank=True)
diff --git a/redesign/import-groups.py b/redesign/import-groups.py
new file mode 100755
index 000000000..5086cf251
--- /dev/null
+++ b/redesign/import-groups.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python
+
+import sys, os
+
+basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+sys.path = [ basedir ] + sys.path
+
+from ietf import settings
+from django.core import management
+management.setup_environ(settings)
+
+
+from redesign.group.models import *
+from redesign.name.models import *
+from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF
+
+# Group replaces IETFWG, Area, AreaGroup, Acronym, IRTF
+
+# make sure we got the names
+GroupStateName.objects.get_or_create(slug="bof", name="BOF") # is this a state?
+GroupStateName.objects.get_or_create(slug="proposed", name="Proposed")
+GroupStateName.objects.get_or_create(slug="active", name="Active")
+GroupStateName.objects.get_or_create(slug="dormant", name="Dormant")
+GroupStateName.objects.get_or_create(slug="conclude", name="Concluded")
+GroupStateName.objects.get_or_create(slug="unknown", name="Unknown")
+
+GroupTypeName.objects.get_or_create(slug="ietf", name="IETF")
+GroupTypeName.objects.get_or_create(slug="area", name="Area")
+GroupTypeName.objects.get_or_create(slug="wg", name="WG")
+GroupTypeName.objects.get_or_create(slug="rg", name="RG")
+GroupTypeName.objects.get_or_create(slug="team", name="Team")
+
+# FIXME: what about AG (area group?)?
+
+
+# Area
+for o in Area.objects.all():
+ group, _ = Group.objects.get_or_create(acronym=o.area_acronym.acronym)
+ group.name = o.area_acronym.name
+ if o.status.status == "Active":
+ s = GroupStateName.objects.get(slug="active")
+ elif o.status.status == "Concluded":
+ s = GroupStateName.objects.get(slug="conclude")
+ elif o.status.status == "Unknown":
+ s = GroupStateName.objects.get(slug="unknown")
+ group.state = s
+ group.type = GroupTypeName.objects.get(slug="area")
+
+ # FIXME: missing fields from old: concluded_date, comments, last_modified_date, extra_email_addresses
+
+ group.save()
+
+# IETFWG, AreaGroup
+for o in IETFWG.objects.all():
+ group, _ = Group.objects.get_or_create(acronym=o.group_acronym.acronym)
+ group.name = o.group_acronym.name
+ # state
+ if o.group_type.type == "BOF":
+ s = GroupStateName.objects.get(slug="bof")
+ elif o.group_type.type == "PWG": # FIXME: right?
+ s = GroupStateName.objects.get(slug="proposed")
+ elif o.status.status == "Active":
+ s = GroupStateName.objects.get(slug="active")
+ elif o.status.status == "Dormant":
+ s = GroupStateName.objects.get(slug="dormant")
+ elif o.status.status == "Concluded":
+ s = GroupStateName.objects.get(slug="conclude")
+ group.state = s
+ # type
+ if o.group_type.type == "team":
+ group.type = GroupTypeName.objects.get(slug="team")
+ else:
+ group.type = GroupTypeName.objects.get(slug="wg")
+
+ if o.area:
+ print "no area for", group.acronym, group.name, group.type, group.state
+ group.parent = Group.objects.get(acronym=o.area.area.area_acronym.acronym)
+
+ # FIXME: missing fields from old: proposed_date, start_date, dormant_date, concluded_date, meeting_scheduled, email_address, email_subscribe, email_keyword, email_archive, comments, last_modified_date, meeting_scheduled_old
+
+ group.save()
+
diff --git a/redesign/name/__init__.py b/redesign/name/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/redesign/name/admin.py b/redesign/name/admin.py
new file mode 100644
index 000000000..59ef3fdd2
--- /dev/null
+++ b/redesign/name/admin.py
@@ -0,0 +1,22 @@
+from django.contrib import admin
+from models import *
+
+class NameAdmin(admin.ModelAdmin):
+ list_display = ["slug", "name", "desc", "used"]
+ prepopulate_from = { "slug": ("name",) }
+
+admin.site.register(GroupTypeName, NameAdmin)
+admin.site.register(GroupStateName, NameAdmin)
+admin.site.register(RoleName, NameAdmin)
+admin.site.register(DocStreamName, NameAdmin)
+admin.site.register(DocStateName, NameAdmin)
+admin.site.register(DocRelationshipName, NameAdmin)
+admin.site.register(WgDocStateName, NameAdmin)
+admin.site.register(IesgDocStateName, NameAdmin)
+admin.site.register(IanaDocStateName, NameAdmin)
+admin.site.register(RfcDocStateName, NameAdmin)
+admin.site.register(DocTypeName, NameAdmin)
+admin.site.register(DocInfoTagName, NameAdmin)
+admin.site.register(IntendedStatusName, NameAdmin)
+admin.site.register(StdStatusName, NameAdmin)
+admin.site.register(BallotPositionName, NameAdmin)
diff --git a/redesign/name/models.py b/redesign/name/models.py
new file mode 100644
index 000000000..fef8b8a4e
--- /dev/null
+++ b/redesign/name/models.py
@@ -0,0 +1,57 @@
+# Copyright The IETF Trust 2007, All Rights Reserved
+
+from django.db import models
+
+class NameModel(models.Model):
+ slug = models.CharField(max_length=8, primary_key=True)
+ name = models.CharField(max_length=32)
+ desc = models.TextField(blank=True)
+ used = models.BooleanField(default=True)
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ abstract = True
+
+class GroupStateName(NameModel):
+ """BOF, Proposed, Active, Dormant, Concluded"""
+class GroupTypeName(NameModel):
+ """IETF, Area, WG, RG, Team, etc."""
+class RoleName(NameModel):
+ """AD, Chair"""
+class DocStreamName(NameModel):
+ """IETF, IAB, IRTF, Independent Submission"""
+class DocStateName(NameModel):
+ """Active, Expired, RFC, Replaced, Withdrawn"""
+class DocRelationshipName(NameModel):
+ """Updates, Replaces, Obsoletes, Reviews, ... The relationship is
+ always recorded in one direction.
+ """
+class WgDocStateName(NameModel):
+ """Not, Candidate, Active, Parked, LastCall, WriteUp, Submitted,
+ Dead"""
+class IesgDocStateName(NameModel):
+ """Pub Request, Ad Eval, Expert Review, Last Call Requested, In
+ Last Call, Waiting for Writeup, Waiting for AD Go-Ahead, IESG
+ Evaluation, Deferred, Approved, Announcement Sent, Do Not Publish,
+ Ad is watching, Dead """
+class IanaDocStateName(NameModel):
+ """ """
+class RfcDocStateName(NameModel):
+ """Missref, Edit, RFC-Editor, Auth48, Auth, Published; ISR,
+ ISR-Auth, ISR-Timeout;"""
+class DocTypeName(NameModel):
+ """Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email,
+ Review, Issue, Wiki"""
+class DocInfoTagName(NameModel):
+ """Waiting for Reference, IANA Coordination, Revised ID Needed,
+ External Party, AD Followup, Point Raised - Writeup Needed"""
+class StdStatusName(NameModel):
+ """Proposed Standard, Draft Standard, Standard, Experimental,
+ Informational, Best Current Practice, Historic, ..."""
+class IntendedStatusName(NameModel):
+ """Standards Track, Experimental, Informational, Best Current
+ Practice, Historic, ..."""
+class BallotPositionName(NameModel):
+ """ Yes, NoObjection, Abstain, Discuss, Recuse """
diff --git a/redesign/name/proxy.py b/redesign/name/proxy.py
new file mode 100644
index 000000000..6d2fb3645
--- /dev/null
+++ b/redesign/name/proxy.py
@@ -0,0 +1,20 @@
+from redesign.proxy_utils import TranslatingManager
+from models import *
+
+class IDStatus(DocStateName):
+ def __init__(self, base):
+ for f in base._meta.fields:
+ setattr(self, f.name, getattr(base, f.name))
+
+ #status_id = models.AutoField(primary_key=True)
+
+ #status = models.CharField(max_length=25, db_column='status_value')
+ @property
+ def status(self):
+ return self.name
+
+ def __unicode__(self):
+ return super(self.__class__, self).__unicode__()
+
+ class Meta:
+ proxy = True
diff --git a/redesign/person/__init__.py b/redesign/person/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/redesign/person/admin.py b/redesign/person/admin.py
new file mode 100644
index 000000000..40cdb8dc9
--- /dev/null
+++ b/redesign/person/admin.py
@@ -0,0 +1,28 @@
+from django.contrib import admin
+from models import *
+
+class EmailAdmin(admin.ModelAdmin):
+ list_display = ["address", "person", "time", "active", ]
+ raw_id_fields = ["person", ]
+ search_fields = ["address", "person__name", ]
+admin.site.register(Email, EmailAdmin)
+
+class EmailInline(admin.TabularInline):
+ model = Email
+
+class AliasAdmin(admin.ModelAdmin):
+ list_display = ["name", "person", ]
+ search_fields = ["name",]
+ raw_id_fields = ["person"]
+admin.site.register(Alias, AliasAdmin)
+
+class AliasInline(admin.StackedInline):
+ model = Alias
+
+class PersonAdmin(admin.ModelAdmin):
+ list_display = ["name", "short", "time", ]
+ search_fields = ["name", "ascii"]
+ inlines = [ EmailInline, AliasInline, ]
+# actions = None
+admin.site.register(Person, PersonAdmin)
+
diff --git a/redesign/person/models.py b/redesign/person/models.py
new file mode 100644
index 000000000..6fc918b3a
--- /dev/null
+++ b/redesign/person/models.py
@@ -0,0 +1,67 @@
+# Copyright The IETF Trust 2007, All Rights Reserved
+
+from django.db import models
+
+class Person(models.Model):
+ time = models.DateTimeField(auto_now_add=True) # When this Person record entered the system
+ name = models.CharField(max_length=255) # The normal unicode form of the name. This must be
+ # set to the same value as the ascii-form if equal.
+ ascii = models.CharField(max_length=255) # The normal ascii-form of the name.
+ ascii_short = models.CharField(max_length=32, null=True, blank=True) # The short ascii-form of the name. Also in alias table if non-null
+ address = models.TextField(max_length=255, blank=True)
+ def __unicode__(self):
+ return self.name
+ def _parts(self, name):
+ prefix, first, middle, last, suffix = "", "", "", "", ""
+ parts = name.split()
+ if parts[0] in ["Mr", "Mr.", "Mrs", "Mrs.", "Ms", "Ms.", "Miss", "Dr.", "Doctor", "Prof", "Prof.", "Professor", "Sir", "Lady", "Dame", ]:
+ prefix = parts[0];
+ parts = parts[1:]
+ if len(parts) > 2:
+ if parts[-1] in ["Jr", "Jr.", "II", "2nd", "III", "3rd", ]:
+ suffix = parts[-1]
+ parts = parts[:-1]
+ if len(parts) > 2:
+ first = parts[0]
+ last = parts[-1]
+ middle = " ".join(parts[1:-1])
+ elif len(parts) == 2:
+ first, last = parts
+ else:
+ last = parts[0]
+ return prefix, first, middle, last, suffix
+ def name_parts(self):
+ return self._parts(self.name)
+ def ascii_parts(self):
+ return self._parts(self.ascii)
+ def short(self):
+ if self.ascii_short:
+ return self.ascii_short
+ else:
+ prefix, first, middle, last, suffix = self.ascii_parts()
+ return (first and first[0]+"." or "")+(middle or "")+" "+last+(suffix and " "+suffix or "")
+
+class Alias(models.Model):
+ """This is used for alternative forms of a name. This is the
+ primary lookup point for names, and should always contain the
+ unicode form (and ascii form, if different) of a name which is
+ recorded in the Person record.
+ """
+ person = models.ForeignKey(Person)
+ name = models.CharField(max_length=255)
+ def __unicode__(self):
+ return self.name
+ class Meta:
+ verbose_name_plural = "Aliases"
+
+class Email(models.Model):
+ address = models.CharField(max_length=64, primary_key=True)
+ person = models.ForeignKey(Person, null=True)
+ time = models.DateTimeField(auto_now_add=True)
+ active = models.BooleanField(default=True) # Old email addresses are *not* purged, as history
+ # information points to persons through these
+ def __unicode__(self):
+ return self.address
+
+ def get_name(self):
+ return self.person.name if self.person else self.address
diff --git a/redesign/person/proxy.py b/redesign/person/proxy.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/redesign/proxy_utils.py b/redesign/proxy_utils.py
new file mode 100644
index 000000000..9c4f7963e
--- /dev/null
+++ b/redesign/proxy_utils.py
@@ -0,0 +1,215 @@
+from django.db.models.manager import Manager
+from django.db.models.query import QuerySet
+
+class TranslatingQuerySet(QuerySet):
+ def translated_kwargs(self, kwargs):
+ trans = self.translated_attrs
+ return dict((trans[k], v) if k in trans else (k, v) for k, v in kwargs.iteritems())
+
+ # overridden methods
+ def _clone(self, *args, **kwargs):
+ c = super(self.__class__, self)._clone(*args, **kwargs)
+ c.translated_attrs = self.translated_attrs
+ return c
+
+ def dates(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).dates(*args, **kwargs)
+
+ def distinct(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).distinct(*args, **kwargs)
+
+ def extra(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).extra(*args, **kwargs)
+
+ def get(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).get(*args, **kwargs)
+
+ def get_or_create(self, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).get_or_create(**kwargs)
+
+ def create(self, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).create(**kwargs)
+
+ def filter(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).filter(*args, **kwargs)
+
+ def aggregate(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).aggregate(*args, **kwargs)
+
+ def annotate(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).annotate(*args, **kwargs)
+
+ def complex_filter(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).complex_filter(*args, **kwargs)
+
+ def exclude(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).exclude(*args, **kwargs)
+
+ def in_bulk(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).in_bulk(*args, **kwargs)
+
+ def iterator(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).iterator(*args, **kwargs)
+
+ def latest(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).latest(*args, **kwargs)
+
+ def order_by(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).order_by(*args, **kwargs)
+
+ def select_related(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).select_related(*args, **kwargs)
+
+ def values(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).values(*args, **kwargs)
+
+ def values_list(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).values_list(*args, **kwargs)
+
+ def update(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).update(*args, **kwargs)
+
+ def reverse(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).reverse(*args, **kwargs)
+
+ def defer(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).defer(*args, **kwargs)
+
+ def only(self, *args, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self).only(*args, **kwargs)
+
+ def _insert(self, values, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return insert_query(self.model, values, **kwargs)
+
+ def _update(self, values, **kwargs):
+ kwargs = self.translated_kwargs(kwargs)
+ return super(self.__class__, self)._update(values, **kwargs)
+
+class TranslatingManager(Manager):
+ def __init__(self, trans):
+ super(self.__class__, self).__init__()
+ self.translated_attrs = trans
+
+ def get_query_set(self):
+ qs = TranslatingQuerySet(self.model)
+ qs.translated_attrs = self.translated_attrs
+ return qs
+
+ # def dates(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().dates(*args, **kwargs)
+
+ # def distinct(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().distinct(*args, **kwargs)
+
+ # def extra(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().extra(*args, **kwargs)
+
+ # def get(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().get(*args, **kwargs)
+
+ # def get_or_create(self, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().get_or_create(**kwargs)
+
+ # def create(self, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().create(**kwargs)
+
+ # def filter(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().filter(*args, **kwargs)
+
+ # def aggregate(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().aggregate(*args, **kwargs)
+
+ # def annotate(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().annotate(*args, **kwargs)
+
+ # def complex_filter(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().complex_filter(*args, **kwargs)
+
+ # def exclude(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().exclude(*args, **kwargs)
+
+ # def in_bulk(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().in_bulk(*args, **kwargs)
+
+ # def iterator(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().iterator(*args, **kwargs)
+
+ # def latest(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().latest(*args, **kwargs)
+
+ # def order_by(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().order_by(*args, **kwargs)
+
+ # def select_related(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().select_related(*args, **kwargs)
+
+ # def values(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().values(*args, **kwargs)
+
+ # def values_list(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().values_list(*args, **kwargs)
+
+ # def update(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().update(*args, **kwargs)
+
+ # def reverse(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().reverse(*args, **kwargs)
+
+ # def defer(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().defer(*args, **kwargs)
+
+ # def only(self, *args, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set().only(*args, **kwargs)
+
+ # def _insert(self, values, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return insert_query(self.model, values, **kwargs)
+
+ # def _update(self, values, **kwargs):
+ # kwargs = self.translated_kwargs(kwargs)
+ # return self.get_query_set()._update(values, **kwargs)
diff --git a/redesign/util.py b/redesign/util.py
new file mode 100644
index 000000000..8e24e462a
--- /dev/null
+++ b/redesign/util.py
@@ -0,0 +1,45 @@
+
+def name(obj):
+ if hasattr(obj, 'abbrev'):
+ return obj.abbrev()
+ elif hasattr(obj, 'name'):
+ if callable(obj.name):
+ return obj.name()
+ else:
+ return unicode(obj.name)
+ else:
+ return unicode(obj)
+
+def admin_link(field, label=None, ordering="", display=name, suffix=""):
+ if not label:
+ label = field.capitalize().replace("_", " ").strip()
+ if ordering == "":
+ ordering = field
+ def _link(self):
+ obj = self
+ for attr in field.split("__"):
+ obj = getattr(obj, attr)
+ if callable(obj):
+ obj = obj()
+ if hasattr(obj, "all"):
+ objects = obj.all()
+ elif callable(obj):
+ objects = obj()
+ if not hasattr(objects, "__iter__"):
+ objects = [ objects ]
+ elif hasattr(obj, "__iter__"):
+ objects = obj
+ else:
+ objects = [ obj ]
+ chunks = []
+ for obj in objects:
+ app = obj._meta.app_label
+ model = obj.__class__.__name__.lower()
+ id = obj.pk
+ chunks += [ u'%(display)s' %
+ {'app':app, "model": model, "id":id, "display": display(obj), "suffix":suffix, } ]
+ return u", ".join(chunks)
+ _link.allow_tags = True
+ _link.short_description = label
+ _link.admin_order_field = ordering
+ return _link
From df9dba63d8b838ca798b6322a5944714c7f50119 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 9 Dec 2010 15:39:48 +0000
Subject: [PATCH 02/75] First stab at importing roles - Legacy-Id: 2711
---
redesign/import-groups.py | 8 ++--
redesign/import-roles.py | 91 +++++++++++++++++++++++++++++++++++++++
2 files changed, 96 insertions(+), 3 deletions(-)
create mode 100755 redesign/import-roles.py
diff --git a/redesign/import-groups.py b/redesign/import-groups.py
index 5086cf251..4eef2e870 100755
--- a/redesign/import-groups.py
+++ b/redesign/import-groups.py
@@ -14,7 +14,9 @@ from redesign.group.models import *
from redesign.name.models import *
from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF
-# Group replaces IETFWG, Area, AreaGroup, Acronym, IRTF
+# imports IETFWG, Area, AreaGroup, Acronym
+
+# FIXME: should also import IRTF
# make sure we got the names
GroupStateName.objects.get_or_create(slug="bof", name="BOF") # is this a state?
@@ -29,10 +31,9 @@ GroupTypeName.objects.get_or_create(slug="area", name="Area")
GroupTypeName.objects.get_or_create(slug="wg", name="WG")
GroupTypeName.objects.get_or_create(slug="rg", name="RG")
GroupTypeName.objects.get_or_create(slug="team", name="Team")
-
# FIXME: what about AG (area group?)?
-
+
# Area
for o in Area.objects.all():
group, _ = Group.objects.get_or_create(acronym=o.area_acronym.acronym)
@@ -80,3 +81,4 @@ for o in IETFWG.objects.all():
group.save()
+# FIXME: IRTF
diff --git a/redesign/import-roles.py b/redesign/import-roles.py
new file mode 100755
index 000000000..97c640299
--- /dev/null
+++ b/redesign/import-roles.py
@@ -0,0 +1,91 @@
+#!/usr/bin/python
+
+import sys, os, re, datetime
+
+basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+sys.path = [ basedir ] + sys.path
+
+from ietf import settings
+settings.USE_DB_REDESIGN_PROXY_CLASSES = False
+
+from django.core import management
+management.setup_environ(settings)
+
+from redesign.person.models import *
+from redesign.group.models import *
+from redesign.name.models import *
+from ietf.idtracker.models import IESGLogin, AreaDirector
+
+# assumptions:
+# - groups have been imported
+
+# PersonOrOrgInfo/PostalAddress/EmailAddress/PhoneNumber are not imported
+
+# imports IESGLogin, AreaDirector
+
+# should import IDAuthor, WGChair, WGEditor,
+# WGSecretary, WGTechAdvisor, Role, ChairsHistory, IRTFChair
+
+# make sure names exist
+def name(name_class, slug, name, desc=""):
+ # create if it doesn't exist, set name
+ obj, _ = name_class.objects.get_or_create(slug=slug)
+ obj.name = name
+ obj.desc = desc
+ obj.save()
+ return obj
+
+area_director_role = name(RoleName, "ad", "Area Director")
+
+
+# helpers for creating the objects
+def get_or_create_email(o, old_person):
+ email = o.person.email()[1]
+ if not email:
+ print "NO EMAIL FOR %s %s" % (o.__class__, o.id)
+ return None
+
+ e, _ = Email.objects.get_or_create(address=email)
+ if not e.person:
+ n = u"%s %s" % (o.person.first_name, o.person.last_name)
+ aliases = Alias.objects.filter(name=n)
+ if aliases:
+ p = aliases[0].person
+ else:
+ p = Person.objects.create(name=n, ascii=n)
+ Alias.objects.create(name=n, person=p)
+ # FIXME: fill in address?
+
+ e.person = p
+ e.save()
+
+ return e
+
+
+# IESGLogin
+for o in IESGLogin.objects.all():
+ print "importing IESGLogin", o.id, o.first_name, o.last_name
+
+ if not o.person:
+ print "NO PERSON", o.person_id
+ continue
+
+ email = get_or_create_email(o, o.person)
+
+ # FIXME: import o.user_level
+ # FIXME: import o.login_name, o.user_level
+
+
+# AreaDirector
+Role.objects.filter(name=area_director_role).delete()
+for o in AreaDirector.objects.all():
+ print "importing AreaDirector", o.area, o.person
+ email = get_or_create_email(o, o.person)
+ if not o.area:
+ print "NO AREA", o.area_id
+ continue
+
+ area = Group.objects.get(acronym=o.area.area_acronym.acronym)
+
+ Role.objects.get_or_create(name=area_director_role, group=area, email=email)
+
From 224e3f686777e127cdddcc1a8ad922e582f129b3 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 9 Dec 2010 15:40:41 +0000
Subject: [PATCH 03/75] Import document state changes, spit out missing
comments - Legacy-Id: 2712
---
redesign/doc/models.py | 8 +++---
redesign/import-document-state.py | 41 ++++++++++++++++++++++++++++---
2 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index 856c611b8..014ea1377 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -145,10 +145,12 @@ class SendQueue(models.Model):
EVENT_TYPES = [
- # misc document events
+ # core events
("new_revision", "Added new revision"),
- ("document_changed", "Changed document"),
- ("tombstone_added", "Added tombstone"),
+ ("changed_document", "Changed document metadata"),
+
+ # misc document events
+ ("added_tombstone", "Added tombstone"),
("requested_resurrect", "Requested resurrect"),
# IESG events
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 1ddeae781..5d4543dc0 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -18,7 +18,7 @@ from ietf.idtracker.models import InternetDraft, IESGLogin, DocumentComment
# assumptions:
# - groups have been imported
-# - iesglogin emails have been imported
+# - roles have been imported
# FIXME: what about RFCs
@@ -94,6 +94,7 @@ def date_in_match(match):
re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat - %s by" % date_re_str)
re_ballot_position = re.compile(r"\[Ballot Position Update\] (New position, (?P.*), has been recorded (|for (?P.*) )|Position (|for (?P.*) )has been changed to (?P.*) from .*)by (?P.*)")
re_ballot_issued = re.compile(r"Ballot has been issued by")
+re_state_changed = re.compile(r"(State (changed|Changes) to (?P.*) from (?P.*) by|Sub state has been changed to (?P.*) from (?P.*))")
# helpers for events
@@ -104,16 +105,30 @@ def save_event(doc, event, comment):
event.desc = comment.comment_text # FIXME: consider unquoting here
event.save()
+iesg_login_cache = {}
+
+# make sure system email exists
+system_email, _ = Email.objects.get_or_create(address="(System)")
+
def iesg_login_to_email(l):
if not l:
- return None
+ return system_email
else:
+ # fix logins without the right person
+ if not l.person:
+ if l.id not in iesg_login_cache:
+ logins = IESGLogin.objects.filter(first_name=l.first_name, last_name=l.last_name).exclude(id=l.id)
+ if logins:
+ iesg_login_cache[l.id] = logins[0]
+ else:
+ iesg_login_cache[l.id] = None
+ l = iesg_login_cache[l.id]
+
try:
return Email.objects.get(address=l.person.email()[1])
except Email.DoesNotExist:
print "MISSING IESG LOGIN", l.person.email()
return None
-
all_drafts = InternetDraft.objects.all().select_related()
@@ -158,6 +173,8 @@ for o in all_drafts:
# extract events
for c in o.idinternal.documentcomment_set.order_by('date', 'time', 'id'):
+ handled = False
+
# telechat agenda schedulings
match = re_telechat_agenda.search(c.comment_text)
if match:
@@ -167,6 +184,7 @@ for o in all_drafts:
# can't extract this from history so we just take the latest value
e.returning_item = bool(o.idinternal.returning_item)
save_event(d, e, c)
+ handled = True
# ballot issued
@@ -187,6 +205,7 @@ for o in all_drafts:
e.comment = last_pos.ballotposition.comment if last_pos else ""
e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
save_event(d, e, c)
+ handled = True
# ballot positions
@@ -206,6 +225,7 @@ for o in all_drafts:
e.comment = last_pos.ballotposition.comment if last_pos else ""
e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
save_event(d, e, c)
+ handled = True
# ballot discusses/comments
@@ -230,6 +250,19 @@ for o in all_drafts:
# put header into description
c.comment_text = "[Ballot comment]\n" + c.comment_text
save_event(d, e, c)
+ handled = True
+
+ # state changes
+ match = re_state_changed.search(c.comment_text)
+ if match:
+ # we currently don't recreate DocumentHistory
+ e = Event(type="changed_document")
+ save_event(d, e, c)
+ handled = True
+
+
+ if not handled:
+ print "couldn't handle %s '%s'" % (c.id, c.comment_text.replace("\n", "").replace("\r", ""))
print "imported", d.name, "state", d.iesg_state
@@ -317,4 +350,4 @@ class CheckListBallotInfo(models.Model):
approval_text = models.TextField(blank=True)
last_call_text = models.TextField(blank=True)
ballot_writeup = models.TextField(blank=True)
- ballot_issued = models.IntegerField(null=True, blank=True)
+# ballot_issued = models.IntegerField(null=True, blank=True)
From d49f761383d24517b78a30c49351deaf7622cf9e Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 9 Dec 2010 15:50:55 +0000
Subject: [PATCH 04/75] Fix missing case in wrapper - Legacy-Id: 2713
---
ietf/idrfc/idrfc_wrapper.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py
index 04a74654f..1690e2ce0 100644
--- a/ietf/idrfc/idrfc_wrapper.py
+++ b/ietf/idrfc/idrfc_wrapper.py
@@ -418,6 +418,7 @@ class IetfProcessData:
return self._idinternal.comments().filter(
Q(comment_text__istartswith="Draft Added by ")|
Q(comment_text__istartswith="Draft Added in state ")|
+ Q(comment_text__istartswith="Draft added in state ")|
Q(comment_text__istartswith="State changed to ")|
Q(comment_text__istartswith="State Changes to ")|
Q(comment_text__istartswith="Sub state has been changed to ")|
From 2dee1bd5ca45cecdd6d5031a95abb50d14d5a68c Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 10 Dec 2010 18:10:02 +0000
Subject: [PATCH 05/75] Import more history and fix import/proxy bugs (enough
to get draft-arkko-townsley-coexistence working), more proxying for
group/person related things (enough to get left base menu working) -
Legacy-Id: 2714
---
ietf/idrfc/idrfc_wrapper.py | 2 +-
ietf/idrfc/views_doc.py | 20 +++-
ietf/idrfc/views_edit.py | 2 +-
ietf/templates/idrfc/doc_history.html | 4 +
redesign/doc/admin.py | 1 +
redesign/doc/models.py | 30 ++++--
redesign/doc/proxy.py | 93 +++++++++-------
redesign/group/proxy.py | 97 ++++++++++++++++-
redesign/import-document-state.py | 149 +++++++++++++++++++++-----
redesign/import-groups.py | 26 ++++-
redesign/import-roles.py | 16 +--
redesign/person/proxy.py | 48 +++++++++
12 files changed, 399 insertions(+), 89 deletions(-)
diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py
index 1690e2ce0..ed6a4b112 100644
--- a/ietf/idrfc/idrfc_wrapper.py
+++ b/ietf/idrfc/idrfc_wrapper.py
@@ -622,7 +622,7 @@ class BallotWrapper:
p = dict(ad_name=pos.ad.get_name(),
ad_username="", # FIXME: don't seem to have username at the moment
position=pos.pos.name,
- is_old_ad=pos.ad in active_ads,
+ is_old_ad=pos.ad not in active_ads,
old_positions=[])
if pos.pos.slug == "discuss":
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index 29795c58e..e215e9999 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -148,14 +148,30 @@ def document_main(request, name):
def _get_history(doc, versions):
results = []
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ versions = [] # clear versions
for e in doc._draft.event_set.all().select_related('by').order_by('-time'):
info = {}
+ if e.type == "new_revision":
+ filename = u"%s-%s" % (e.doc.name, e.newrevision.rev)
+ e.desc = 'New version available: %s' % (filename, filename)
+ if int(e.newrevision.rev) != 0:
+ e.desc += ' (diff from -%02d)' % (filename, int(e.newrevision.rev) - 1)
+ info["dontmolest"] = True
+
info['text'] = e.desc
info['by'] = e.by.get_name()
info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25)
- info['snipped'] = info['textSnippet'][-3:] == "..."
- e.version = e.doc.rev
+ 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"
+ for o in reversed(results):
+ e = o["comment"]
+ if e.type == "new_revision":
+ e.version = e.newrevision.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)
diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py
index 743781a57..282388317 100644
--- a/ietf/idrfc/views_edit.py
+++ b/ietf/idrfc/views_edit.py
@@ -236,7 +236,7 @@ def edit_info(request, name):
setattr(obj, attr, r[attr])
diff(doc, 'intended_status', "Intended Status")
- diff(doc.idinternal, 'status_date', "Status Date")
+ diff(doc.idinternal, 'status_date', "Status date")
if 'area_acronym' in r and r['area_acronym']:
diff(doc.idinternal, 'area_acronym', 'Area acronym')
diff(doc.idinternal, 'job_owner', 'Responsible AD')
diff --git a/ietf/templates/idrfc/doc_history.html b/ietf/templates/idrfc/doc_history.html
index b634f0629..8ec6b72e1 100644
--- a/ietf/templates/idrfc/doc_history.html
+++ b/ietf/templates/idrfc/doc_history.html
@@ -65,8 +65,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{{ c.info.text|fill:"80"|safe|urlize|linebreaksbr|keep_spacing|sanitize_html|safe }}
{% else %}
+{% if c.info.dontmolest %}
+{{ c.info.text|safe }}
+{% else %}
{{ c.info.text|fill:"80"|safe|urlize|linebreaksbr|keep_spacing|sanitize_html|safe }}
{% endif %}
+{% endif %}
{% endif %}
diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py
index c144cb33e..958c82e45 100644
--- a/redesign/doc/admin.py
+++ b/redesign/doc/admin.py
@@ -40,6 +40,7 @@ admin.site.register(Event, EventAdmin)
admin.site.register(Message, EventAdmin)
admin.site.register(Text, EventAdmin)
admin.site.register(BallotPosition, EventAdmin)
+admin.site.register(Status, EventAdmin)
admin.site.register(Expiration, EventAdmin)
admin.site.register(Telechat, EventAdmin)
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index 014ea1377..4413380bb 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -6,6 +6,7 @@ from redesign.name.models import *
from redesign.person.models import Email
from redesign.util import admin_link
+import datetime
class RelatedDoc(models.Model):
relationship = models.ForeignKey(DocRelationshipName)
@@ -49,12 +50,13 @@ class DocumentInfo(models.Model):
abstract = True
def author_list(self):
return ", ".join(email.address for email in self.authors.all())
- def latest_event(self, **filter_args):
- e = self.event_set.filter(**filter_args).order_by('-time')[:1]
- if e:
- return e[0]
- else:
- return None
+ def latest_event(self, *args, **filter_args):
+ """Get latest event with specific requirements, e.g.
+ d.latest_event(type="xyz") returns an Event while
+ d.latest_event(Status, type="xyz") returns a Status event."""
+ model = args[0] if args else Event
+ e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time')[:1]
+ return e[0] if e else None
class Document(DocumentInfo):
name = models.CharField(max_length=255, primary_key=True) # immutable
@@ -150,6 +152,7 @@ EVENT_TYPES = [
("changed_document", "Changed document metadata"),
# misc document events
+ ("added_comment", "Added comment"),
("added_tombstone", "Added tombstone"),
("requested_resurrect", "Requested resurrect"),
@@ -163,6 +166,9 @@ EVENT_TYPES = [
("changed_last_call_text", "Changed last call text"),
("sent_last_call", "Sent last call"),
+ ("requested_last_call", "Requested last call"),
+
+ ("changed_status_date", "Changed status date"),
("scheduled_for_telechat", "Scheduled for telechat"),
@@ -174,9 +180,9 @@ EVENT_TYPES = [
class Event(models.Model):
"""An occurrence in connection with a document."""
- time = models.DateTimeField()
+ time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened")
type = models.CharField(max_length=50, choices=EVENT_TYPES)
- by = models.ForeignKey(Email, blank=True, null=True)
+ by = models.ForeignKey(Email, blank=True, null=True) # FIXME: make NOT NULL?
doc = models.ForeignKey('doc.Document')
desc = models.TextField()
@@ -192,7 +198,10 @@ class Message(Event):
class Text(Event):
content = models.TextField(blank=True)
-
+
+class NewRevision(Event):
+ rev = models.CharField(max_length=16)
+
# IESG events
class BallotPosition(Event):
ad = models.ForeignKey(Email)
@@ -202,6 +211,9 @@ class BallotPosition(Event):
comment = models.TextField(help_text="Optional comment", blank=True)
comment_time = models.DateTimeField(help_text="Time optional comment was written", blank=True, null=True)
+class Status(Event):
+ date = models.DateField()
+
class Expiration(Event):
expires = models.DateTimeField()
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index d381b29f9..b9059dfec 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -2,6 +2,10 @@ from models import *
from redesign.person.models import Email
from redesign.proxy_utils import TranslatingManager
+from django.conf import settings
+
+import glob, os
+
class InternetDraft(Document):
objects = TranslatingManager(dict(filename="name",
@@ -12,53 +16,61 @@ class InternetDraft(Document):
# things from InternetDraft
-# id_document_tag = models.AutoField(primary_key=True)
+ #id_document_tag = models.AutoField(primary_key=True)
@property
def id_document_tag(self):
return self.name # Will only work for some use cases
-# title = models.CharField(max_length=255, db_column='id_document_name') # same name
-# id_document_key = models.CharField(max_length=255, editable=False)
+ #title = models.CharField(max_length=255, db_column='id_document_name') # same name
+ #id_document_key = models.CharField(max_length=255, editable=False)
@property
def id_document_key(self):
return self.title.upper()
-# group = models.ForeignKey(Acronym, db_column='group_acronym_id')
+ #group = models.ForeignKey(Acronym, db_column='group_acronym_id')
@property
def group(self):
- return super(self.__class__, self).group
-# filename = models.CharField(max_length=255, unique=True)
+ from group.proxy import Acronym as AcronymProxy
+ return AcronymProxy(super(self.__class__, self).group)
+ #filename = models.CharField(max_length=255, unique=True)
@property
def filename(self):
return self.name
-# revision = models.CharField(max_length=2)
+ #revision = models.CharField(max_length=2)
@property
def revision(self):
return self.rev
-# revision_date = models.DateField()
+ #revision_date = models.DateField()
@property
def revision_date(self):
e = self.latest_event(type="new_revision")
- return e.time if e else None
-# file_type = models.CharField(max_length=20)
+ return e.time.date() if e else None
+ #file_type = models.CharField(max_length=20)
@property
def file_type(self):
- return ".txt" # FIXME XXX should look in the repository to see what's available
-# txt_page_count = models.IntegerField()
+ matches = glob.glob(os.path.join(settings.INTERNET_DRAFT_PATH, self.filename + "*.*"))
+ possible_types = [".txt", ".pdf", ".xml", ".ps"]
+ res = set()
+ for m in matches:
+ for t in possible_types:
+ if m.endswith(t):
+ res.add(t)
+ return ",".join(res)
+ #txt_page_count = models.IntegerField()
@property
def txt_page_count(self):
return self.pages
-# local_path = models.CharField(max_length=255, blank=True) # unused
-# start_date = models.DateField()
+ #local_path = models.CharField(max_length=255, blank=True) # unused
+ #start_date = models.DateField()
@property
def start_date(self):
return self.dochistory_set.dates("time","day","ASC")[0]
-# expiration_date = models.DateField()
+ #expiration_date = models.DateField()
@property
def expiration_date(self):
return self.expiration()
-# abstract = models.TextField() # same name
-# dunn_sent_date = models.DateField(null=True, blank=True) # unused
-# extension_date = models.DateField(null=True, blank=True) # unused
-# status = models.ForeignKey(IDStatus)
+ #abstract = models.TextField() # same name
+ #dunn_sent_date = models.DateField(null=True, blank=True) # unused
+ #extension_date = models.DateField(null=True, blank=True) # unused
+ #status = models.ForeignKey(IDStatus)
@property
def status(self):
from redesign.name.proxy import IDStatus
@@ -69,42 +81,42 @@ class InternetDraft(Document):
from redesign.name.proxy import IDStatus
return { 'active': 1, 'repl': 5, 'expired': 2, 'rfc': 3, 'auth-rm': 4, 'ietf-rm': 6 }[self.state_id]
-# intended_status = models.ForeignKey(IDIntendedStatus)
+ #intended_status = models.ForeignKey(IDIntendedStatus)
@property
def intended_status(self):
return self.intended_std_level
-# lc_sent_date = models.DateField(null=True, blank=True)
+ #lc_sent_date = models.DateField(null=True, blank=True)
@property
def lc_sent_date(self):
e = self.latest_event(type="sent_last_call")
return e.time if e else None
-# lc_changes = models.CharField(max_length=3) # used in DB, unused in Django code?
+ #lc_changes = models.CharField(max_length=3) # used in DB, unused in Django code?
-# lc_expiration_date = models.DateField(null=True, blank=True)
+ #lc_expiration_date = models.DateField(null=True, blank=True)
@property
def lc_expiration_date(self):
e = self.latest_event(type="sent_last_call")
return e.expiration.expires if e else None
-# b_sent_date = models.DateField(null=True, blank=True)
+ #b_sent_date = models.DateField(null=True, blank=True)
@property
def b_sent_date(self):
e = self.latest_event(type="sent_ballot_announcement")
return e.time if e else None
-# b_discussion_date = models.DateField(null=True, blank=True) # unused
+ #b_discussion_date = models.DateField(null=True, blank=True) # unused
-# b_approve_date = models.DateField(null=True, blank=True)
+ #b_approve_date = models.DateField(null=True, blank=True)
@property
def b_approve_date(self):
e = self.latest_event(type="approved_ballot")
return e.time if e else None
-# wgreturn_date = models.DateField(null=True, blank=True) # unused
+ #wgreturn_date = models.DateField(null=True, blank=True) # unused
-# rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
+ #rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
@property
def rfc_number(self):
try:
@@ -112,20 +124,20 @@ class InternetDraft(Document):
except IndexError:
return None
-# comments = models.TextField(blank=True) # unused
+ #comments = models.TextField(blank=True) # unused
-# last_modified_date = models.DateField()
+ #last_modified_date = models.DateField()
@property
def last_modified_date(self):
return self.time
-# replaced_by = models.ForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
+ #replaced_by = models.ForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
@property
def replaced_by(self):
r = InternetDraft.objects.filter(docalias__relateddoc__relationship="replaces", docalias__relateddoc__related_document_set=self)
return r[0] if r else None
-# replaces = FKAsOneToOne('replaces', reverse=True)
+ #replaces = FKAsOneToOne('replaces', reverse=True)
@property
def replaces(self):
r = InternetDraft.objects.filter(related__doc_alias__document=self, related__relationship="replaces")
@@ -137,17 +149,17 @@ class InternetDraft(Document):
# this is replaced_by
return InternetDraft.objects.filter(docalias__relateddoc__relationship="replaces", docalias__relateddoc__related_document_set=self)
-# review_by_rfc_editor = models.BooleanField()
+ #review_by_rfc_editor = models.BooleanField()
@property
def review_by_rfc_editor(self): raise NotImplemented # should use tag
-# expired_tombstone = models.BooleanField()
+ #expired_tombstone = models.BooleanField()
@property
def expired_tombstone(self):
# FIXME: this is probably not perfect, what happens when we delete it again
return self.latest_event(type="added_tombstone")
-# idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
+ #idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
@property
def idinternal(self):
return self if self.iesg_state else None
@@ -246,7 +258,9 @@ class InternetDraft(Document):
#status_date = models.DateField(blank=True,null=True)
@property
- def status_date(self): raise NotImplemented # FIXME
+ def status_date(self):
+ e = self.latest_event(Status, type="changed_status_date")
+ return e.date if e else None
#email_display = models.CharField(blank=True, max_length=50) # unused
#agenda = models.IntegerField(null=True, blank=True)
@@ -276,7 +290,8 @@ class InternetDraft(Document):
# job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
@property
def job_owner(self):
- return self.ad
+ from person.proxy import IESGLogin as IESGLoginProxy
+ return IESGLoginProxy(self.ad)
#event_date = models.DateField(null=True)
@property
@@ -485,7 +500,7 @@ class InternetDraft(Document):
yes = noobj = discuss = recuse = 0
for position in positions:
- p = position.pos_id
+ p = position.ballotposition.pos_id
if p == "yes":
yes += 1
if p == "noobj":
@@ -505,7 +520,7 @@ class InternetDraft(Document):
if standardsTrack:
# For standards-track, need positions from 2/3 of the
# non-recused current IESG.
- needed = (len(ads) - recuse) * 2 / 3
+ needed = int((len(ads) - recuse) * 2 / 3)
else:
# Info and experimental only need one position.
needed = 1
diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py
index ed2f08d77..b328df51e 100644
--- a/redesign/group/proxy.py
+++ b/redesign/group/proxy.py
@@ -1,6 +1,12 @@
from models import *
class Acronym(Group):
+ class LazyIndividualSubmitter(object):
+ def __get__(self, obj, type=None):
+ return Group.objects.get(acronym="none").id
+
+ INDIVIDUAL_SUBMITTER = LazyIndividualSubmitter()
+
def __init__(self, base):
for f in base._meta.fields:
setattr(self, f.name, getattr(base, f.name))
@@ -15,6 +21,12 @@ class Acronym(Group):
@property
def name_key(self):
return self.name.upper()
+
+ def __str__(self):
+ return self.acronym
+
+ def __unicode__(self):
+ return self.acronym
class Meta:
proxy = True
@@ -35,12 +47,91 @@ class Area(Group):
#def additional_urls(self):
# return AreaWGURL.objects.filter(name=self.area_acronym.name)
- #def active_wgs(self):
- # return IETFWG.objects.filter(group_type=1,status=IETFWG.ACTIVE,areagroup__area=self).order_by('group_acronym__acronym')
+ def active_wgs(self):
+ return IETFWG.objects.filter(type="wg", state="active", parent=self).order_by("acronym")
@staticmethod
def active_areas():
- return Area.objects.filter(type="area", state="active")
+ return Area.objects.filter(type="area", state="active").order_by('acronym')
+
+ class Meta:
+ proxy = True
+
+
+class IETFWG(Group):
+ ACTIVE=1
+ #group_acronym = models.OneToOneField(Acronym, primary_key=True, editable=False)
+ @property
+ def group_acronym(self):
+ return Acronym(self)
+
+ #group_type = models.ForeignKey(WGType)
+ #proposed_date = models.DateField(null=True, blank=True)
+ #start_date = models.DateField(null=True, blank=True)
+ #dormant_date = models.DateField(null=True, blank=True)
+ #concluded_date = models.DateField(null=True, blank=True)
+ #status = models.ForeignKey(WGStatus)
+ #area_director = models.ForeignKey(AreaDirector, null=True)
+ #meeting_scheduled = models.CharField(blank=True, max_length=3)
+ #email_address = models.CharField(blank=True, max_length=60)
+ #email_subscribe = models.CharField(blank=True, max_length=120)
+ #email_keyword = models.CharField(blank=True, max_length=50)
+ #email_archive = models.CharField(blank=True, max_length=95)
+ #comments = models.TextField(blank=True)
+ #last_modified_date = models.DateField()
+ #meeting_scheduled_old = models.CharField(blank=True, max_length=3)
+ #area = FKAsOneToOne('areagroup', reverse=True)
+ def __str__(self):
+ return self.group_acronym.acronym
+
+ def __unicode__(self):
+ return self.group_acronym.acronym
+
+ # everything below here is not fixed yet
+ def active_drafts(self):
+ return self.group_acronym.internetdraft_set.all().filter(status__status="Active")
+ def choices():
+ return [(wg.group_acronym_id, wg.group_acronym.acronym) for wg in IETFWG.objects.all().filter(group_type__type='WG').select_related().order_by('acronym.acronym')]
+ choices = staticmethod(choices)
+ def area_acronym(self):
+ areas = AreaGroup.objects.filter(group__exact=self.group_acronym)
+ if areas:
+ return areas[areas.count()-1].area.area_acronym
+ else:
+ return None
+ def area_directors(self):
+ areas = AreaGroup.objects.filter(group__exact=self.group_acronym)
+ if areas:
+ return areas[areas.count()-1].area.areadirector_set.all()
+ else:
+ return None
+ def chairs(self): # return a set of WGChair objects for this work group
+ return WGChair.objects.filter(group_acronym__exact=self.group_acronym)
+ def secretaries(self): # return a set of WGSecretary objects for this group
+ return WGSecretary.objects.filter(group_acronym__exact=self.group_acronym)
+ def milestones(self): # return a set of GoalMilestone objects for this group
+ return GoalMilestone.objects.filter(group_acronym__exact=self.group_acronym)
+ def rfcs(self): # return a set of Rfc objects for this group
+ return Rfc.objects.filter(group_acronym__exact=self.group_acronym)
+ def drafts(self): # return a set of Rfc objects for this group
+ return InternetDraft.objects.filter(group__exact=self.group_acronym)
+ def charter_text(self): # return string containing WG description read from file
+ # get file path from settings. Syntesize file name from path, acronym, and suffix
+ try:
+ filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, self.group_acronym.acronym) + ".desc.txt"
+ desc_file = open(filename)
+ desc = desc_file.read()
+ except BaseException:
+ desc = 'Error Loading Work Group Description'
+ return desc
+
+ def additional_urls(self):
+ return AreaWGURL.objects.filter(name=self.group_acronym.acronym)
+ def clean_email_archive(self):
+ x = self.email_archive
+ # remove "current/" and "maillist.html"
+ x = re.sub("^(http://www\.ietf\.org/mail-archive/web/)([^/]+/)(current/)?([a-z]+\.html)?$", "\\1\\2", x)
+ return x
class Meta:
proxy = True
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 5d4543dc0..262fe02d7 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -14,14 +14,27 @@ management.setup_environ(settings)
from redesign.doc.models import *
from redesign.group.models import *
from redesign.name.models import *
-from ietf.idtracker.models import InternetDraft, IESGLogin, DocumentComment
+from ietf.idtracker.models import InternetDraft, IESGLogin, DocumentComment, PersonOrOrgInfo
+from ietf.idrfc.models import DraftVersions
+
+import sys
+
+draft_name_to_import = None
+if len(sys.argv) > 1:
+ draft_name_to_import = sys.argv[1]
# assumptions:
# - groups have been imported
-# - roles have been imported
+# - IESG login emails/roles have been imported
# FIXME: what about RFCs
+# Regarding history, we currently don't try to create DocumentHistory
+# objects, we just import the comments as events.
+
+# imports InternetDraft, IDInternal, BallotInfo, Position,
+# IESGComment, IESGDiscuss, DocumentComment, idrfc.DraftVersions
+
def name(name_class, slug, name, desc=""):
# create if it doesn't exist, set name
obj, _ = name_class.objects.get_or_create(slug=slug)
@@ -85,16 +98,6 @@ ballot_position_mapping = {
None: name(BallotPositionName, 'norecord', 'No record'),
}
-# regexps for parsing document comments
-
-date_re_str = "(?P[0-9][0-9][0-9][0-9])-(?P[0-9][0-9])-(?P[0-9][0-9])"
-def date_in_match(match):
- return datetime.date(int(match.group('year')), int(match.group('month')), int(match.group('day')))
-
-re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat - %s by" % date_re_str)
-re_ballot_position = re.compile(r"\[Ballot Position Update\] (New position, (?P.*), has been recorded (|for (?P.*) )|Position (|for (?P.*) )has been changed to (?P.*) from .*)by (?P.*)")
-re_ballot_issued = re.compile(r"Ballot has been issued by")
-re_state_changed = re.compile(r"(State (changed|Changes) to (?P.*) from (?P.*) by|Sub state has been changed to (?P.*) from (?P.*))")
# helpers for events
@@ -102,10 +105,11 @@ def save_event(doc, event, comment):
event.time = comment.datetime()
event.by = iesg_login_to_email(comment.created_by)
event.doc = doc
- event.desc = comment.comment_text # FIXME: consider unquoting here
+ if not event.desc:
+ event.desc = comment.comment_text # FIXME: consider unquoting here
event.save()
-iesg_login_cache = {}
+buggy_iesg_logins_cache = {}
# make sure system email exists
system_email, _ = Email.objects.get_or_create(address="(System)")
@@ -116,13 +120,18 @@ def iesg_login_to_email(l):
else:
# fix logins without the right person
if not l.person:
- if l.id not in iesg_login_cache:
+ if l.id not in buggy_iesg_logins_cache:
logins = IESGLogin.objects.filter(first_name=l.first_name, last_name=l.last_name).exclude(id=l.id)
if logins:
- iesg_login_cache[l.id] = logins[0]
+ buggy_iesg_logins_cache[l.id] = logins[0]
else:
- iesg_login_cache[l.id] = None
- l = iesg_login_cache[l.id]
+ persons = PersonOrOrgInfo.objects.filter(first_name=l.first_name, last_name=l.last_name)
+ if persons:
+ l.person = persons[0]
+ buggy_iesg_logins_cache[l.id] = l
+ else:
+ buggy_iesg_logins_cache[l.id] = None
+ l = buggy_iesg_logins_cache[l.id]
try:
return Email.objects.get(address=l.person.email()[1])
@@ -130,9 +139,25 @@ def iesg_login_to_email(l):
print "MISSING IESG LOGIN", l.person.email()
return None
+# regexps for parsing document comments
+
+date_re_str = "(?P[0-9][0-9][0-9][0-9])-(?P[0-9][0-9])-(?P[0-9][0-9])"
+def date_in_match(match):
+ return datetime.date(int(match.group('year')), int(match.group('month')), int(match.group('day')))
+
+re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat - %s by" % date_re_str)
+re_ballot_position = re.compile(r"\[Ballot Position Update\] (New position, (?P.*), has been recorded (|for (?P.*) )|Position (|for (?P.*) )has been changed to (?P.*) from .*)by (?P.*)")
+re_ballot_issued = re.compile(r"Ballot has been issued by")
+re_state_changed = re.compile(r"(State (changed|Changes) to (?P.*) from (?P.*) by|Sub state has been changed to (?P.*) from (?P.*))")
+re_note_field_cleared = re.compile(r"Note field has been cleared by")
+re_draft_added = re.compile(r"Draft [Aa]dded (by .*)? in state (?P.*)")
+re_last_call_requested = re.compile(r"Last Call was requested")
+re_status_date_changed = re.compile(r"Status [dD]ate has been changed to ()?" + date_re_str)
+
all_drafts = InternetDraft.objects.all().select_related()
-all_drafts = all_drafts.filter(filename="draft-arkko-townsley-coexistence")
+if draft_name_to_import:
+ all_drafts = all_drafts.filter(filename=draft_name_to_import)
#all_drafts = all_drafts[all_drafts.count() - 1000:]
for o in all_drafts:
@@ -198,6 +223,7 @@ for o in all_drafts:
e = BallotPosition()
e.type = "changed_ballot_position"
e.ad = iesg_login_to_email(c.created_by)
+ e.desc = "[Ballot Position Update] New position, Yes, has been recorded by %s" % e.ad.get_name()
last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
e.pos = ballot_position_mapping["Yes"]
e.discuss = last_pos.ballotposition.discuss if last_pos else ""
@@ -251,21 +277,96 @@ for o in all_drafts:
c.comment_text = "[Ballot comment]\n" + c.comment_text
save_event(d, e, c)
handled = True
-
+
+ # last call requested
+ match = re_last_call_requested.search(c.comment_text)
+ if match:
+ e = Event(type="requested_last_call")
+ save_event(d, e, c)
+ handled = True
+
# state changes
match = re_state_changed.search(c.comment_text)
if match:
- # we currently don't recreate DocumentHistory
e = Event(type="changed_document")
save_event(d, e, c)
handled = True
+ # note cleared
+ match = re_note_field_cleared.search(c.comment_text)
+ if match:
+ e = Event(type="changed_document")
+ save_event(d, e, c)
+ handled = True
+
+ # note cleared
+ match = re_draft_added.search(c.comment_text)
+ if match:
+ e = Event(type="changed_document")
+ save_event(d, e, c)
+ handled = True
+
+ # status date changed
+ match = re_status_date_changed.search(c.comment_text)
+ if match:
+ # FIXME: handle multiple comments in one
+ e = Status(type="changed_status_date", date=date_in_match(match))
+ save_event(d, e, c)
+ handled = True
+ # new version
+ if c.comment_text == "New version available":
+ e = NewRevision(type="new_revision", rev=c.version)
+ save_event(d, e, c)
+ handled = True
+
+ # all others are added as comments
+ if not handled:
+ e = Event(type="added_comment")
+ save_event(d, e, c)
+
+ # stop typical comments from being output
+ typical_comments = ["Who is the Document Shepherd for this document",
+ "We understand that this document doesn't require any IANA actions"]
+ for t in typical_comments:
+ if t in c.comment_text:
+ handled = True
+ break
+
if not handled:
print "couldn't handle %s '%s'" % (c.id, c.comment_text.replace("\n", "").replace("\r", ""))
+
+ # import new revision changes from DraftVersions
+ known_revisions = set(e.newrevision.rev for e in d.event_set.filter(type="new_revision").select_related('newrevision'))
+ for v in DraftVersions.objects.filter(filename=d.name).order_by("revision"):
+ if v.revision not in known_revisions:
+ e = NewRevision(type="new_revision")
+ e.rev = v.revision
+ # we don't have time information in this source, so
+ # hack the seconds to include the revision to ensure
+ # they're ordered correctly
+ e.time = datetime.datetime.combine(v.revision_date, datetime.time(0, 0, int(v.revision)))
+ e.by = system_email
+ e.doc = d
+ e.desc = "New version available"
+ e.save()
+ known_revisions.add(v.revision)
-
- print "imported", d.name, "state", d.iesg_state
+ # import events that might be missing, we don't know where to
+ # place them but if we don't generate them, we'll be missing
+ # the information completely
+ e = d.latest_event(Status, type="changed_status_date")
+ status_date = e.date if e else None
+ if o.idinternal.status_date != status_date:
+ e = Status(type="changed_status_date", date=o.idinternal.status_date)
+ e.by = system_email
+ e.doc = d
+ e.desc = "Status date has been changed to %s from %s" % (o.idinternal.status_date, status_date)
+ e.save()
+
+ # FIXME: import writeups
+
+ print "imported", d.name, "S:", d.iesg_state
@@ -316,7 +417,7 @@ class CheckListIDInternal(models.Model):
token_name = models.CharField(blank=True, max_length=25)
token_email = models.CharField(blank=True, max_length=255)
# note = models.TextField(blank=True)
- status_date = models.DateField(blank=True,null=True)
+# status_date = models.DateField(blank=True,null=True)
email_display = models.CharField(blank=True, max_length=50)
agenda = models.IntegerField(null=True, blank=True)
# cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs')
diff --git a/redesign/import-groups.py b/redesign/import-groups.py
index 4eef2e870..1c2655ad1 100755
--- a/redesign/import-groups.py
+++ b/redesign/import-groups.py
@@ -6,6 +6,8 @@ basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path = [ basedir ] + sys.path
from ietf import settings
+settings.USE_DB_REDESIGN_PROXY_CLASSES = False
+
from django.core import management
management.setup_environ(settings)
@@ -31,6 +33,7 @@ GroupTypeName.objects.get_or_create(slug="area", name="Area")
GroupTypeName.objects.get_or_create(slug="wg", name="WG")
GroupTypeName.objects.get_or_create(slug="rg", name="RG")
GroupTypeName.objects.get_or_create(slug="team", name="Team")
+GroupTypeName.objects.get_or_create(slug="individ", name="Individual")
# FIXME: what about AG (area group?)?
@@ -53,7 +56,11 @@ for o in Area.objects.all():
# IETFWG, AreaGroup
for o in IETFWG.objects.all():
- group, _ = Group.objects.get_or_create(acronym=o.group_acronym.acronym)
+ try:
+ group = Group.objects.get(acronym=o.group_acronym.acronym)
+ except Group.DoesNotExist:
+ group = Group(acronym=o.group_acronym.acronym)
+
group.name = o.group_acronym.name
# state
if o.group_type.type == "BOF":
@@ -68,14 +75,25 @@ for o in IETFWG.objects.all():
s = GroupStateName.objects.get(slug="conclude")
group.state = s
# type
- if o.group_type.type == "team":
+ if o.group_type.type == "TEAM":
group.type = GroupTypeName.objects.get(slug="team")
- else:
+ elif o.group_type.type == "AG":
+ # this contains groups like
+ #apptsv, none, saag, iesg, iab, tsvdir, apples, usac, secdir, apparea, null, opsarea, rtgarea, usvarea, genarea, tsvarea, raiarea, dirdir
+ # which we currently just ignore
+ if o.group_acronym.acronym == "none":
+ group.type = GroupTypeName.objects.get(slug="individ")
+ else:
+ if group.id:
+ group.delete()
+ continue
+ else: # PWG/BOF/WG
group.type = GroupTypeName.objects.get(slug="wg")
if o.area:
- print "no area for", group.acronym, group.name, group.type, group.state
group.parent = Group.objects.get(acronym=o.area.area.area_acronym.acronym)
+ else:
+ print "no area for", group.acronym, group.name, group.type, group.state
# FIXME: missing fields from old: proposed_date, start_date, dormant_date, concluded_date, meeting_scheduled, email_address, email_subscribe, email_keyword, email_archive, comments, last_modified_date, meeting_scheduled_old
diff --git a/redesign/import-roles.py b/redesign/import-roles.py
index 97c640299..99914d291 100755
--- a/redesign/import-roles.py
+++ b/redesign/import-roles.py
@@ -14,7 +14,7 @@ management.setup_environ(settings)
from redesign.person.models import *
from redesign.group.models import *
from redesign.name.models import *
-from ietf.idtracker.models import IESGLogin, AreaDirector
+from ietf.idtracker.models import IESGLogin, AreaDirector, PersonOrOrgInfo
# assumptions:
# - groups have been imported
@@ -39,7 +39,7 @@ area_director_role = name(RoleName, "ad", "Area Director")
# helpers for creating the objects
-def get_or_create_email(o, old_person):
+def get_or_create_email(o):
email = o.person.email()[1]
if not email:
print "NO EMAIL FOR %s %s" % (o.__class__, o.id)
@@ -67,10 +67,14 @@ for o in IESGLogin.objects.all():
print "importing IESGLogin", o.id, o.first_name, o.last_name
if not o.person:
- print "NO PERSON", o.person_id
- continue
+ persons = PersonOrOrgInfo.objects.filter(first_name=o.first_name, last_name=o.last_name)
+ if persons:
+ o.person = persons[0]
+ else:
+ print "NO PERSON", o.person_id
+ continue
- email = get_or_create_email(o, o.person)
+ email = get_or_create_email(o)
# FIXME: import o.user_level
# FIXME: import o.login_name, o.user_level
@@ -80,7 +84,7 @@ for o in IESGLogin.objects.all():
Role.objects.filter(name=area_director_role).delete()
for o in AreaDirector.objects.all():
print "importing AreaDirector", o.area, o.person
- email = get_or_create_email(o, o.person)
+ email = get_or_create_email(o)
if not o.area:
print "NO AREA", o.area_id
continue
diff --git a/redesign/person/proxy.py b/redesign/person/proxy.py
index e69de29bb..2b080d3ea 100644
--- a/redesign/person/proxy.py
+++ b/redesign/person/proxy.py
@@ -0,0 +1,48 @@
+from models import *
+
+class IESGLogin(Email):
+ def __init__(self, base):
+ for f in base._meta.fields:
+ setattr(self, f.name, getattr(base, f.name))
+ SECRETARIAT_LEVEL = 0
+ AD_LEVEL = 1
+ INACTIVE_AD_LEVEL = 2
+
+ #login_name = models.CharField(blank=True, max_length=255)
+ @property
+ def login_name(self): raise NotImplemented
+ #password = models.CharField(max_length=25)
+ @property
+ def password(self): raise NotImplemented
+ #user_level = models.IntegerField(choices=USER_LEVEL_CHOICES)
+ @property
+ def user_level(self): raise NotImplemented
+
+ #first_name = models.CharField(blank=True, max_length=25)
+ @property
+ def first_name(self):
+ return self.get_name().split(" ")[0]
+
+ #last_name = models.CharField(blank=True, max_length=25)
+ @property
+ def last_name(self):
+ return self.get_name().split(" ")[1]
+
+ # FIXME: person isn't wrapped yet
+ #person = BrokenForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', unique=True, null_values=(0, 888888), null=True)
+
+ # apparently unused
+ #pgp_id = models.CharField(blank=True, null=True, max_length=20)
+ #default_search = models.NullBooleanField()
+
+ def __str__(self):
+ return self.get_name()
+ def is_current_ad(self):
+ return self in Email.objects.filter(role__name="ad", role__group__state="active")
+ @staticmethod
+ def active_iesg():
+ raise NotImplemented
+ #return IESGLogin.objects.filter(user_level=1,id__gt=1).order_by('last_name')
+
+ class Meta:
+ proxy = True
From 518a0387cd195d603c53b2bb54d7849e0ffb97d6 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 13 Dec 2010 14:50:59 +0000
Subject: [PATCH 06/75] Import more history (enough for
draft-ietf-mboned-ssm232), handle multiple-changes-in-one comments gracefully
- Legacy-Id: 2715
---
redesign/doc/models.py | 2 +-
redesign/doc/proxy.py | 8 +-
redesign/import-document-state.py | 144 +++++++++++++++++++++---------
3 files changed, 106 insertions(+), 48 deletions(-)
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index 4413380bb..6265d71cc 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -159,7 +159,6 @@ EVENT_TYPES = [
# IESG events
("sent_ballot_announcement", "Sent ballot announcement"),
("deferred_ballot", "Deferred ballot"),
- ("approved_ballot", "Approved ballot"),
("changed_ballot_position", "Changed ballot position"),
("changed_ballot_approval_text", "Changed ballot approval text"),
("changed_ballot_writeup_text", "Changed ballot writeup text"),
@@ -174,6 +173,7 @@ EVENT_TYPES = [
("resolved_to_do_not_publish", "Resolved to 'do not publish'"),
("resolved_to_no_problem", "Resolved to 'no problem'"),
+ ("iesg_approved", "IESG approved document"),
("approved_in_minute", "Approved in minute"),
]
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index b9059dfec..a5ed43cdd 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -111,7 +111,7 @@ class InternetDraft(Document):
#b_approve_date = models.DateField(null=True, blank=True)
@property
def b_approve_date(self):
- e = self.latest_event(type="approved_ballot")
+ e = self.latest_event(type="iesg_approved")
return e.time if e else None
#wgreturn_date = models.DateField(null=True, blank=True) # unused
@@ -412,18 +412,18 @@ class InternetDraft(Document):
#an_sent = models.BooleanField()
@property
def an_sent(self):
- return bool(self.latest_event(type="approved_ballot"))
+ return bool(self.latest_event(type="iesg_approved"))
#an_sent_date = models.DateField(null=True, blank=True)
@property
def an_sent_date(self):
- e = self.latest_event(type="approved_ballot")
+ e = self.latest_event(type="iesg_approved")
return e.time if e else None
#an_sent_by = models.ForeignKey(IESGLogin, db_column='an_sent_by', related_name='ansent', null=True)
@property
def an_sent_by(self):
- e = self.latest_event(type="approved_ballot")
+ e = self.latest_event(type="iesg_approved")
return e.by if e else None
#defer = models.BooleanField()
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 262fe02d7..054f4a5c9 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -148,12 +148,16 @@ def date_in_match(match):
re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat - %s by" % date_re_str)
re_ballot_position = re.compile(r"\[Ballot Position Update\] (New position, (?P.*), has been recorded (|for (?P.*) )|Position (|for (?P.*) )has been changed to (?P.*) from .*)by (?P.*)")
re_ballot_issued = re.compile(r"Ballot has been issued by")
-re_state_changed = re.compile(r"(State (changed|Changes) to (?P.*) from (?P.*) by|Sub state has been changed to (?P.*) from (?P.*))")
-re_note_field_cleared = re.compile(r"Note field has been cleared by")
-re_draft_added = re.compile(r"Draft [Aa]dded (by .*)? in state (?P.*)")
+re_state_changed = re.compile(r"(State (has been changed|changed|Changes) to (?P.*) from (?P.*) by|Sub state has been changed to (?P.*) from (?P.*))")
+re_note_changed = re.compile(r"(\[Note\]: .*'.*'|Note field has been cleared)")
+re_draft_added = re.compile(r"Draft [Aa]dded (by .*)?( in state (?P.*))?")
re_last_call_requested = re.compile(r"Last Call was requested")
-re_status_date_changed = re.compile(r"Status [dD]ate has been changed to ()?" + date_re_str)
+re_document_approved = re.compile(r"IESG has approved and state has been changed to")
+re_status_date_changed = re.compile(r"Status [dD]ate has been changed to ()?" + date_re_str)
+re_responsible_ad_changed = re.compile(r"(Responsible AD|Shepherding AD) has been changed to ()?")
+re_intended_status_changed = re.compile(r"Intended [sS]tatus has been changed to ()?")
+re_state_change_notice = re.compile(r"State Change Notice email list (have been change|has been changed) ()?")
all_drafts = InternetDraft.objects.all().select_related()
if draft_name_to_import:
@@ -192,10 +196,10 @@ for o in all_drafts:
d.internal_comments = o.comments or "" # FIXME: maybe put these somewhere else
d.save()
+ # clear already imported events
+ d.event_set.all().delete()
+
if o.idinternal:
- # clear already imported events
- d.event_set.all().delete()
-
# extract events
for c in o.idinternal.documentcomment_set.order_by('date', 'time', 'id'):
handled = False
@@ -210,7 +214,6 @@ for o in all_drafts:
e.returning_item = bool(o.idinternal.returning_item)
save_event(d, e, c)
handled = True
-
# ballot issued
match = re_ballot_issued.search(c.comment_text)
@@ -233,7 +236,6 @@ for o in all_drafts:
save_event(d, e, c)
handled = True
-
# ballot positions
match = re_ballot_position.search(c.comment_text)
if match:
@@ -253,7 +255,6 @@ for o in all_drafts:
save_event(d, e, c)
handled = True
-
# ballot discusses/comments
if c.ballot in (DocumentComment.BALLOT_DISCUSS, DocumentComment.BALLOT_COMMENT):
e = BallotPosition()
@@ -292,34 +293,80 @@ for o in all_drafts:
save_event(d, e, c)
handled = True
- # note cleared
- match = re_note_field_cleared.search(c.comment_text)
+ # note changed
+ match = re_note_changed.search(c.comment_text)
if match:
e = Event(type="changed_document")
save_event(d, e, c)
handled = True
- # note cleared
+ # draft added
match = re_draft_added.search(c.comment_text)
if match:
e = Event(type="changed_document")
save_event(d, e, c)
handled = True
- # status date changed
- match = re_status_date_changed.search(c.comment_text)
- if match:
- # FIXME: handle multiple comments in one
- e = Status(type="changed_status_date", date=date_in_match(match))
- save_event(d, e, c)
- handled = True
-
# new version
if c.comment_text == "New version available":
e = NewRevision(type="new_revision", rev=c.version)
save_event(d, e, c)
handled = True
+ # approved document
+ match = re_document_approved.search(c.comment_text)
+ if match:
+ e = Event(type="iesg_approved")
+ save_event(d, e, c)
+ handled = True
+
+
+ # some changes can be bundled - this is not entirely
+ # convenient, especially since it makes it hard to give
+ # each a type, so unbundle them
+ if not handled:
+ unhandled_lines = []
+ for line in c.comment_text.split(" "):
+ # status date changed
+ match = re_status_date_changed.search(line)
+ if match:
+ e = Status(type="changed_status_date", date=date_in_match(match))
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # AD/job owner changed
+ match = re_responsible_ad_changed.search(line)
+ if match:
+ e = Event(type="changed_draft")
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # intended standard level changed
+ match = re_intended_status_changed.search(line)
+ if match:
+ e = Event(type="changed_draft")
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # state change notice
+ match = re_state_change_notice.search(line)
+ if match:
+ e = Event(type="changed_draft")
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # multiline change bundles end with a single "by xyz" that we skip
+ if not handled and not line.startswith("by "):
+ unhandled_lines.append(line)
+
+ if handled:
+ c.comment_text = " ".join(unhandled_lines)
+
+
# all others are added as comments
if not handled:
e = Event(type="added_comment")
@@ -327,7 +374,8 @@ for o in all_drafts:
# stop typical comments from being output
typical_comments = ["Who is the Document Shepherd for this document",
- "We understand that this document doesn't require any IANA actions"]
+ "We understand that this document doesn't require any IANA actions",
+ ]
for t in typical_comments:
if t in c.comment_text:
handled = True
@@ -336,22 +384,6 @@ for o in all_drafts:
if not handled:
print "couldn't handle %s '%s'" % (c.id, c.comment_text.replace("\n", "").replace("\r", ""))
- # import new revision changes from DraftVersions
- known_revisions = set(e.newrevision.rev for e in d.event_set.filter(type="new_revision").select_related('newrevision'))
- for v in DraftVersions.objects.filter(filename=d.name).order_by("revision"):
- if v.revision not in known_revisions:
- e = NewRevision(type="new_revision")
- e.rev = v.revision
- # we don't have time information in this source, so
- # hack the seconds to include the revision to ensure
- # they're ordered correctly
- e.time = datetime.datetime.combine(v.revision_date, datetime.time(0, 0, int(v.revision)))
- e.by = system_email
- e.doc = d
- e.desc = "New version available"
- e.save()
- known_revisions.add(v.revision)
-
# import events that might be missing, we don't know where to
# place them but if we don't generate them, we'll be missing
# the information completely
@@ -364,7 +396,33 @@ for o in all_drafts:
e.desc = "Status date has been changed to %s from %s" % (o.idinternal.status_date, status_date)
e.save()
+ e = d.latest_event(Event, type="iesg_approved")
+ approved_date = e.time.date() if e else None
+ if o.b_approve_date != approved_date:
+ e = Event(type="iesg_approved")
+ e.time = o.idinternal.b_approve_date
+ e.by = system_email
+ e.doc = d
+ e.desc = "IESG has approved"
+ e.save()
+
# FIXME: import writeups
+
+ # import missing revision changes from DraftVersions
+ known_revisions = set(e.newrevision.rev for e in d.event_set.filter(type="new_revision").select_related('newrevision'))
+ for v in DraftVersions.objects.filter(filename=d.name).order_by("revision"):
+ if v.revision not in known_revisions:
+ e = NewRevision(type="new_revision")
+ e.rev = v.revision
+ # we don't have time information in this source, so
+ # hack the seconds to include the revision to ensure
+ # they're ordered correctly
+ e.time = datetime.datetime.combine(v.revision_date, datetime.time(0, 0, int(v.revision)))
+ e.by = system_email
+ e.doc = d
+ e.desc = "New version available"
+ e.save()
+ known_revisions.add(v.revision)
print "imported", d.name, "S:", d.iesg_state
@@ -397,7 +455,7 @@ class CheckListInternetDraft(models.Model):
lc_expiration_date = models.DateField(null=True, blank=True)
b_sent_date = models.DateField(null=True, blank=True)
b_discussion_date = models.DateField(null=True, blank=True)
- b_approve_date = models.DateField(null=True, blank=True)
+# b_approve_date = models.DateField(null=True, blank=True)
wgreturn_date = models.DateField(null=True, blank=True)
rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
# comments = models.TextField(blank=True,null=True)
@@ -426,7 +484,7 @@ class CheckListIDInternal(models.Model):
mark_by = models.ForeignKey('IESGLogin', db_column='mark_by', related_name='marked')
# job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
event_date = models.DateField(null=True)
- area_acronym = models.ForeignKey('Area')
+# area_acronym = models.ForeignKey('Area')
cur_sub_state = BrokenForeignKey('IDSubState', related_name='docs', null=True, blank=True, null_values=(0, -1))
prev_sub_state = BrokenForeignKey('IDSubState', related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
# returning_item = models.IntegerField(null=True, blank=True)
@@ -442,9 +500,9 @@ class CheckListIDInternal(models.Model):
class CheckListBallotInfo(models.Model):
ballot = models.AutoField(primary_key=True, db_column='ballot_id')
active = models.BooleanField()
- an_sent = models.BooleanField()
- an_sent_date = models.DateField(null=True, blank=True)
- an_sent_by = models.ForeignKey('IESGLogin', db_column='an_sent_by', related_name='ansent', null=True)
+# an_sent = models.BooleanField()
+# an_sent_date = models.DateField(null=True, blank=True)
+# an_sent_by = models.ForeignKey('IESGLogin', db_column='an_sent_by', related_name='ansent', null=True)
defer = models.BooleanField(blank=True)
defer_by = models.ForeignKey('IESGLogin', db_column='defer_by', related_name='deferred', null=True)
defer_date = models.DateField(null=True, blank=True)
From c5b0b9ccf1c369d4988c0e215cb9856601f2791b Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Wed, 22 Dec 2010 17:15:01 +0000
Subject: [PATCH 07/75] Figure out some missing groups, spit it those the
script doesn't handle - Legacy-Id: 2728
---
redesign/import-groups.py | 93 ++++++++++++++++++++++++++-------------
1 file changed, 62 insertions(+), 31 deletions(-)
diff --git a/redesign/import-groups.py b/redesign/import-groups.py
index 1c2655ad1..479d25224 100755
--- a/redesign/import-groups.py
+++ b/redesign/import-groups.py
@@ -21,20 +21,38 @@ from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, A
# FIXME: should also import IRTF
# make sure we got the names
-GroupStateName.objects.get_or_create(slug="bof", name="BOF") # is this a state?
-GroupStateName.objects.get_or_create(slug="proposed", name="Proposed")
-GroupStateName.objects.get_or_create(slug="active", name="Active")
-GroupStateName.objects.get_or_create(slug="dormant", name="Dormant")
-GroupStateName.objects.get_or_create(slug="conclude", name="Concluded")
-GroupStateName.objects.get_or_create(slug="unknown", name="Unknown")
+def name(name_class, slug, name, desc=""):
+ # create if it doesn't exist, set name and desc
+ obj, _ = name_class.objects.get_or_create(slug=slug)
+ obj.name = name
+ obj.desc = desc
+ obj.save()
+ return obj
-GroupTypeName.objects.get_or_create(slug="ietf", name="IETF")
-GroupTypeName.objects.get_or_create(slug="area", name="Area")
-GroupTypeName.objects.get_or_create(slug="wg", name="WG")
-GroupTypeName.objects.get_or_create(slug="rg", name="RG")
-GroupTypeName.objects.get_or_create(slug="team", name="Team")
-GroupTypeName.objects.get_or_create(slug="individ", name="Individual")
-# FIXME: what about AG (area group?)?
+state_names = dict(
+ bof=name(GroupStateName, slug="bof", name="BOF"),
+ proposed=name(GroupStateName, slug="proposed", name="Proposed"),
+ active=name(GroupStateName, slug="active", name="Active"),
+ dormant=name(GroupStateName, slug="dormant", name="Dormant"),
+ conclude=name(GroupStateName, slug="conclude", name="Concluded"),
+ unknown=name(GroupStateName, slug="unknown", name="Unknown"),
+ )
+
+type_names = dict(
+ ietf=name(GroupTypeName, slug="ietf", name="IETF"),
+ area=name(GroupTypeName, slug="area", name="Area"),
+ wg=name(GroupTypeName, slug="wg", name="WG"),
+ rg=name(GroupTypeName, slug="rg", name="RG"),
+ team=name(GroupTypeName, slug="team", name="Team"),
+ individ=name(GroupTypeName, slug="individ", name="Individual"),
+ )
+
+# make sure we got the IESG so we can use it as parent for areas
+iesg_group, _ = Group.objects.get_or_create(acronym="iesg")
+iesg_group.name = "IESG"
+iesg_group.state = state_names["active"]
+iesg_group.type = type_names["ietf"]
+iesg_group.save()
# Area
@@ -42,13 +60,14 @@ for o in Area.objects.all():
group, _ = Group.objects.get_or_create(acronym=o.area_acronym.acronym)
group.name = o.area_acronym.name
if o.status.status == "Active":
- s = GroupStateName.objects.get(slug="active")
+ s = state_names["active"]
elif o.status.status == "Concluded":
- s = GroupStateName.objects.get(slug="conclude")
+ s = state_names["conclude"]
elif o.status.status == "Unknown":
- s = GroupStateName.objects.get(slug="unknown")
+ s = state_names["unknown"]
group.state = s
- group.type = GroupTypeName.objects.get(slug="area")
+ group.type = type_names["area"]
+ group.parent = iesg_group
# FIXME: missing fields from old: concluded_date, comments, last_modified_date, extra_email_addresses
@@ -64,36 +83,48 @@ for o in IETFWG.objects.all():
group.name = o.group_acronym.name
# state
if o.group_type.type == "BOF":
- s = GroupStateName.objects.get(slug="bof")
- elif o.group_type.type == "PWG": # FIXME: right?
- s = GroupStateName.objects.get(slug="proposed")
+ s = state_names["bof"]
+ elif o.group_type.type == "PWG":
+ s = state_names["proposed"]
elif o.status.status == "Active":
- s = GroupStateName.objects.get(slug="active")
+ s = state_names["active"]
elif o.status.status == "Dormant":
- s = GroupStateName.objects.get(slug="dormant")
+ s = state_names["dormant"]
elif o.status.status == "Concluded":
- s = GroupStateName.objects.get(slug="conclude")
+ s = state_names["conclude"]
group.state = s
# type
if o.group_type.type == "TEAM":
- group.type = GroupTypeName.objects.get(slug="team")
+ group.type = type_names["team"]
elif o.group_type.type == "AG":
- # this contains groups like
- #apptsv, none, saag, iesg, iab, tsvdir, apples, usac, secdir, apparea, null, opsarea, rtgarea, usvarea, genarea, tsvarea, raiarea, dirdir
- # which we currently just ignore
if o.group_acronym.acronym == "none":
- group.type = GroupTypeName.objects.get(slug="individ")
+ # none means individual
+ group.type = type_names["individ"]
+ elif o.group_acronym.acronym == "iab":
+ group.type = type_names["ietf"]
+ group.parent = None
+ elif o.group_acronym.acronym in ("tsvdir", "secdir", "saag"):
+ group.type = type_names["team"]
+ elif o.group_acronym.acronym == "iesg":
+ pass # we already treated iesg
+ elif o.group_acronym.acronym in ('apparea', 'opsarea', 'rtgarea', 'usvarea', 'genarea', 'tsvarea', 'raiarea'):
+ pass # we already treated areas
else:
+ # the remaining groups are
+ # apptsv, apples, usac, null, dirdir
+ # for now, we don't transfer them
if group.id:
group.delete()
+ print "not transferring", o.group_acronym.acronym, o.group_acronym.name
continue
else: # PWG/BOF/WG
- group.type = GroupTypeName.objects.get(slug="wg")
+ # some BOFs aren't WG-forming but we currently classify all as WGs
+ group.type = type_names["wg"]
if o.area:
group.parent = Group.objects.get(acronym=o.area.area.area_acronym.acronym)
- else:
- print "no area for", group.acronym, group.name, group.type, group.state
+ elif not group.parent:
+ print "no area/parent for", group.acronym, group.name, group.type, group.state
# FIXME: missing fields from old: proposed_date, start_date, dormant_date, concluded_date, meeting_scheduled, email_address, email_subscribe, email_keyword, email_archive, comments, last_modified_date, meeting_scheduled_old
From 7deeb609a176b349470abecb6fa331286c427ddd Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Wed, 22 Dec 2010 19:09:56 +0000
Subject: [PATCH 08/75] Review and import more attributes from InternetDraft,
fix proxy bugs - Legacy-Id: 2729
---
redesign/doc/models.py | 7 ++-
redesign/doc/proxy.py | 9 ++-
redesign/import-document-state.py | 91 +++++++++++++++++++++++--------
3 files changed, 75 insertions(+), 32 deletions(-)
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index 6265d71cc..da2830560 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -119,7 +119,7 @@ class DocAlias(models.Model):
return "%s-->%s" % (self.name, self.document.name)
document_link = admin_link("document")
class Meta:
- verbose_name_plural="Aliases"
+ verbose_name_plural = "aliases"
class SendQueue(models.Model):
time = models.DateTimeField() # Scheduled at this time
@@ -154,6 +154,7 @@ EVENT_TYPES = [
# misc document events
("added_comment", "Added comment"),
("added_tombstone", "Added tombstone"),
+ ("expired_document", "Expired document"),
("requested_resurrect", "Requested resurrect"),
# IESG events
@@ -164,8 +165,8 @@ EVENT_TYPES = [
("changed_ballot_writeup_text", "Changed ballot writeup text"),
("changed_last_call_text", "Changed last call text"),
- ("sent_last_call", "Sent last call"),
("requested_last_call", "Requested last call"),
+ ("sent_last_call", "Sent last call"),
("changed_status_date", "Changed status date"),
@@ -218,7 +219,7 @@ class Expiration(Event):
expires = models.DateTimeField()
class Telechat(Event):
- telechat_date = models.DateField()
+ telechat_date = models.DateField(blank=True, null=True)
returning_item = models.BooleanField(default=False)
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index a5ed43cdd..7380c3e0d 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -62,7 +62,8 @@ class InternetDraft(Document):
#start_date = models.DateField()
@property
def start_date(self):
- return self.dochistory_set.dates("time","day","ASC")[0]
+ e = NewRevision.objects.filter(doc=self).order_by("time")[:1]
+ return e[0].time.date() if e else None
#expiration_date = models.DateField()
@property
def expiration_date(self):
@@ -119,10 +120,8 @@ class InternetDraft(Document):
#rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
@property
def rfc_number(self):
- try:
- self.docalias_set.filter(name__startswith="rfc")[0].name[3:]
- except IndexError:
- return None
+ aliases = self.docalias_set.filter(name__startswith="rfc")
+ return int(aliases[0].name[3:]) if aliases else None
#comments = models.TextField(blank=True) # unused
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 054f4a5c9..c801f616e 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -36,7 +36,7 @@ if len(sys.argv) > 1:
# IESGComment, IESGDiscuss, DocumentComment, idrfc.DraftVersions
def name(name_class, slug, name, desc=""):
- # create if it doesn't exist, set name
+ # create if it doesn't exist, set name and desc
obj, _ = name_class.objects.get_or_create(slug=slug)
obj.name = name
obj.desc = desc
@@ -147,7 +147,7 @@ def date_in_match(match):
re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat - %s by" % date_re_str)
re_ballot_position = re.compile(r"\[Ballot Position Update\] (New position, (?P.*), has been recorded (|for (?P.*) )|Position (|for (?P.*) )has been changed to (?P.*) from .*)by (?P.*)")
-re_ballot_issued = re.compile(r"Ballot has been issued by")
+re_ballot_issued = re.compile(r"Ballot has been issued(| by)")
re_state_changed = re.compile(r"(State (has been changed|changed|Changes) to (?P.*) from (?P.*) by|Sub state has been changed to (?P.*) from (?P.*))")
re_note_changed = re.compile(r"(\[Note\]: .*'.*'|Note field has been cleared)")
re_draft_added = re.compile(r"Draft [Aa]dded (by .*)?( in state (?P.*))?")
@@ -158,7 +158,10 @@ re_status_date_changed = re.compile(r"Status [dD]ate has been changed to ()?"
re_responsible_ad_changed = re.compile(r"(Responsible AD|Shepherding AD) has been changed to ()?")
re_intended_status_changed = re.compile(r"Intended [sS]tatus has been changed to ()?")
re_state_change_notice = re.compile(r"State Change Notice email list (have been change|has been changed) ()?")
-
+
+
+made_up_date = datetime.datetime(2030, 1, 1, 0, 0, 0)
+
all_drafts = InternetDraft.objects.all().select_related()
if draft_name_to_import:
all_drafts = all_drafts.filter(filename=draft_name_to_import)
@@ -209,7 +212,7 @@ for o in all_drafts:
if match:
e = Telechat()
e.type = "scheduled_for_telechat"
- e.telechat_date = date_in_match(match)
+ e.telechat_date = date_in_match(match) if "Placed on" in c.comment_text else None
# can't extract this from history so we just take the latest value
e.returning_item = bool(o.idinternal.returning_item)
save_event(d, e, c)
@@ -313,6 +316,12 @@ for o in all_drafts:
save_event(d, e, c)
handled = True
+ # document expiration
+ if c.comment_text == "Document is expired by system":
+ e = Event(type="expired_document")
+ save_event(d, e, c)
+ handled = True
+
# approved document
match = re_document_approved.search(c.comment_text)
if match:
@@ -384,13 +393,15 @@ for o in all_drafts:
if not handled:
print "couldn't handle %s '%s'" % (c.id, c.comment_text.replace("\n", "").replace("\r", ""))
- # import events that might be missing, we don't know where to
- # place them but if we don't generate them, we'll be missing
- # the information completely
+
+ # import events that might be missing, we can't be sure where
+ # to place them but if we don't generate them, we'll be
+ # missing the information completely
e = d.latest_event(Status, type="changed_status_date")
status_date = e.date if e else None
if o.idinternal.status_date != status_date:
e = Status(type="changed_status_date", date=o.idinternal.status_date)
+ e.time = made_up_date
e.by = system_email
e.doc = d
e.desc = "Status date has been changed to %s from %s" % (o.idinternal.status_date, status_date)
@@ -406,8 +417,40 @@ for o in all_drafts:
e.desc = "IESG has approved"
e.save()
- # FIXME: import writeups
+ if o.lc_expiration_date:
+ e = Expiration(type="sent_last_call", expires=o.lc_expiration_date)
+ e.time = o.lc_sent_date
+ # let's try to figure out who did it
+ events = d.event_set.filter(type="changed_document", desc__contains=" to In Last Call").order_by('-time')[:1]
+ e.by = events[0].by if events else system_email
+ e.doc = d
+ e.desc = "Last call sent"
+ e.save()
+ if o.idinternal:
+ e = d.latest_event(Telechat, type="scheduled_for_telechat")
+ telechat_date = e.telechat_date if e else None
+ if not o.idinternal.agenda:
+ o.idinternal.telechat_date = None # normalize
+
+ if telechat_date != o.idinternal.telechat_date:
+ e = Telechat(type="scheduled_for_telechat",
+ telechat_date=o.idinternal.telechat_date,
+ returning_item=bool(o.idinternal.returning_item))
+ e.time = made_up_date
+ e.by = system_email
+ args = ("Placed on", o.idinternal.telechat_date) if o.idinternal.telechat_date else ("Removed from", telechat_date)
+ e.doc = d
+ e.desc = "%s agenda for telechat - %s by system" % args
+ e.save()
+
+ # FIXME: import writeups
+
+ # RFC alias
+ if o.rfc_number:
+ rfc_name = "rfc%s" % o.rfc_number
+ DocAlias.objects.get_or_create(document=d, name=rfc_name)
+
# import missing revision changes from DraftVersions
known_revisions = set(e.newrevision.rev for e in d.event_set.filter(type="new_revision").select_related('newrevision'))
for v in DraftVersions.objects.filter(filename=d.name).order_by("revision"):
@@ -424,7 +467,7 @@ for o in all_drafts:
e.save()
known_revisions.add(v.revision)
- print "imported", d.name, "S:", d.iesg_state
+ print "imported", d.name, " - ", d.iesg_state
@@ -439,27 +482,27 @@ class CheckListInternetDraft(models.Model):
# group = models.ForeignKey(Acronym, db_column='group_acronym_id')
# filename = models.CharField(max_length=255, unique=True)
# revision = models.CharField(max_length=2)
- revision_date = models.DateField()
- file_type = models.CharField(max_length=20)
+# revision_date = models.DateField()
+# file_type = models.CharField(max_length=20)
# txt_page_count = models.IntegerField()
- local_path = models.CharField(max_length=255, blank=True, null=True)
- start_date = models.DateField()
- expiration_date = models.DateField(null=True)
+# local_path = models.CharField(max_length=255, blank=True, null=True)
+# start_date = models.DateField()
+# expiration_date = models.DateField(null=True)
# abstract = models.TextField()
- dunn_sent_date = models.DateField(null=True, blank=True)
- extension_date = models.DateField(null=True, blank=True)
+# dunn_sent_date = models.DateField(null=True, blank=True)
+# extension_date = models.DateField(null=True, blank=True)
# status = models.ForeignKey(IDStatus)
# intended_status = models.ForeignKey(IDIntendedStatus)
- lc_sent_date = models.DateField(null=True, blank=True)
- lc_changes = models.CharField(max_length=3,null=True)
- lc_expiration_date = models.DateField(null=True, blank=True)
- b_sent_date = models.DateField(null=True, blank=True)
- b_discussion_date = models.DateField(null=True, blank=True)
+# lc_sent_date = models.DateField(null=True, blank=True)
+# lc_changes = models.CharField(max_length=3,null=True)
+# lc_expiration_date = models.DateField(null=True, blank=True)
+# b_sent_date = models.DateField(null=True, blank=True)
+# b_discussion_date = models.DateField(null=True, blank=True)
# b_approve_date = models.DateField(null=True, blank=True)
- wgreturn_date = models.DateField(null=True, blank=True)
- rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
+# wgreturn_date = models.DateField(null=True, blank=True)
+# rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
# comments = models.TextField(blank=True,null=True)
- last_modified_date = models.DateField()
+# last_modified_date = models.DateField()
replaced_by = BrokenForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
replaces = FKAsOneToOne('replaces', reverse=True)
review_by_rfc_editor = models.BooleanField()
From 0ada0780fa21e052b2fc00d3aee9ab5317273324 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 23 Dec 2010 18:09:59 +0000
Subject: [PATCH 09/75] Import document replaces, revamp document relationships
a little bit to remove a model, fix bugs - Legacy-Id: 2730
---
redesign/doc/admin.py | 6 --
redesign/doc/models.py | 26 ++++--
redesign/doc/proxy.py | 10 +--
redesign/import-document-state.py | 144 +++++++++++++++++-------------
4 files changed, 101 insertions(+), 85 deletions(-)
diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py
index 958c82e45..bf6029fa4 100644
--- a/redesign/doc/admin.py
+++ b/redesign/doc/admin.py
@@ -2,12 +2,6 @@ from django.contrib import admin
from models import *
from person.models import *
-class RelatedDocAdmin(admin.ModelAdmin):
- list_display = ["source", "relationship", "target"]
- search_fields = ["doc_alias__name", "related_document_set__name", ]
- list_display_links = ["relationship", ]
-admin.site.register(RelatedDoc, RelatedDocAdmin)
-
class DocumentAdmin(admin.ModelAdmin):
list_display = ['name', 'rev', 'state', 'group', 'pages', 'intended_std_level', 'author_list', 'time']
search_fields = ['name']
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index da2830560..27f3c0720 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -8,14 +8,6 @@ from redesign.util import admin_link
import datetime
-class RelatedDoc(models.Model):
- relationship = models.ForeignKey(DocRelationshipName)
- doc_alias = models.ForeignKey('DocAlias')
- def __unicode__(self):
- return "%s %s" % (self.relationship.name, self.doc_alias.name)
- source = admin_link("related_document_set")
- target = admin_link("doc_alias__document")
-
class DocumentInfo(models.Model):
"""Any kind of document. Draft, RFC, Charter, IPR Statement, Liaison Statement"""
time = models.DateTimeField() # should probably have auto_now=True
@@ -38,7 +30,6 @@ class DocumentInfo(models.Model):
intended_std_level = models.ForeignKey(IntendedStatusName, blank=True, null=True)
std_level = models.ForeignKey(StdStatusName, blank=True, null=True)
authors = models.ManyToManyField(Email, blank=True, null=True)
- related = models.ManyToManyField(RelatedDoc, related_name='related_%(class)s_set', blank=True)
ad = models.ForeignKey(Email, related_name='ad_%(class)s_set', blank=True, null=True)
shepherd = models.ForeignKey(Email, related_name='shepherd_%(class)s_set', blank=True, null=True)
notify = models.CharField(max_length=255, blank=True)
@@ -58,8 +49,16 @@ class DocumentInfo(models.Model):
e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time')[:1]
return e[0] if e else None
+class RelatedDocument(models.Model):
+ document = models.ForeignKey('Document') # source
+ doc_alias = models.ForeignKey('DocAlias') # target
+ relationship = models.ForeignKey(DocRelationshipName)
+ def __unicode__(self):
+ return u"%s %s %s" % (self.document.name, self.relationship.name.lower(), self.doc_alias.name)
+
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")
def __unicode__(self):
return self.name
def values(self):
@@ -90,6 +89,7 @@ class Document(DocumentInfo):
snap = DocHistory(**fields)
snap.save()
for m in many2many:
+ # FIXME: check that this works with related
#print "m2m:", m, many2many[m]
rel = getattr(snap, m)
for item in many2many[m]:
@@ -102,8 +102,16 @@ class Document(DocumentInfo):
print "Deleted list:", snap
super(Document, self).save(force_insert, force_update)
+class RelatedDocHistory(models.Model):
+ document = models.ForeignKey('DocHistory')
+ doc_alias = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set")
+ relationship = models.ForeignKey(DocRelationshipName)
+ def __unicode__(self):
+ return u"%s %s %s" % (self.document.name, self.relationship.name.lower(), self.doc_alias.name)
+
class DocHistory(DocumentInfo):
doc = models.ForeignKey(Document) # ID of the Document this relates to
+ related = models.ManyToManyField('DocAlias', through=RelatedDocHistory, blank=True)
def __unicode__(self):
return unicode(self.doc.name)
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index 7380c3e0d..647edad59 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -133,20 +133,18 @@ class InternetDraft(Document):
#replaced_by = models.ForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
@property
def replaced_by(self):
- r = InternetDraft.objects.filter(docalias__relateddoc__relationship="replaces", docalias__relateddoc__related_document_set=self)
+ r = InternetDraft.objects.filter(related__document=self, related__relateddocument__relationship="replaces")
return r[0] if r else None
#replaces = FKAsOneToOne('replaces', reverse=True)
@property
def replaces(self):
- r = InternetDraft.objects.filter(related__doc_alias__document=self, related__relationship="replaces")
- return r[0] if r else Non
+ r = self.replaces_set()
+ return r[0] if r else None
-
@property
def replaces_set(self):
- # this is replaced_by
- return InternetDraft.objects.filter(docalias__relateddoc__relationship="replaces", docalias__relateddoc__related_document_set=self)
+ return InternetDraft.objects.filter(docalias__relateddocument__relationship="replaces", docalias__relateddocument__document=self)
#review_by_rfc_editor = models.BooleanField()
@property
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index c801f616e..683dc1e3d 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -46,6 +46,8 @@ def name(name_class, slug, name, desc=""):
type_draft = name(DocTypeName, "draft", "Draft")
stream_ietf = name(DocStreamName, "ietf", "IETF")
+relationship_replaces = name(DocRelationshipName, "replaces", "Replaces")
+
intended_status_mapping = {
"BCP": name(IntendedStatusName, "bcp", "Best Current Practice"),
"Draft Standard": name(IntendedStatusName, "ds", name="Draft Standard"),
@@ -145,7 +147,7 @@ date_re_str = "(?P[0-9][0-9][0-9][0-9])-(?P[0-9][0-9])-(?P[0-9
def date_in_match(match):
return datetime.date(int(match.group('year')), int(match.group('month')), int(match.group('day')))
-re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat - %s by" % date_re_str)
+re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat(| - %s) by" % date_re_str)
re_ballot_position = re.compile(r"\[Ballot Position Update\] (New position, (?P.*), has been recorded (|for (?P.*) )|Position (|for (?P.*) )has been changed to (?P.*) from .*)by (?P.*)")
re_ballot_issued = re.compile(r"Ballot has been issued(| by)")
re_state_changed = re.compile(r"(State (has been changed|changed|Changes) to (?P.*) from (?P.*) by|Sub state has been changed to (?P.*) from (?P.*))")
@@ -191,7 +193,7 @@ for o in all_drafts:
# d.std_level =
# d.authors =
# d.related =
- d.ad = iesg_login_to_email(o.idinternal.job_owner)
+ d.ad = iesg_login_to_email(o.idinternal.job_owner) if o.idinternal else None
d.shepherd = None
d.notify = o.idinternal.state_change_notice_to or "" if o.idinternal else ""
d.external_url = ""
@@ -199,6 +201,10 @@ for o in all_drafts:
d.internal_comments = o.comments or "" # FIXME: maybe put these somewhere else
d.save()
+ # make sure our alias is updated
+ DocAlias.objects.filter(name=d.name).exclude(document=d).delete()
+ d_alias, _ = DocAlias.objects.get_or_create(name=d.name, document=d)
+
# clear already imported events
d.event_set.all().delete()
@@ -382,8 +388,10 @@ for o in all_drafts:
save_event(d, e, c)
# stop typical comments from being output
- typical_comments = ["Who is the Document Shepherd for this document",
- "We understand that this document doesn't require any IANA actions",
+ typical_comments = [
+ "Document Shepherd Write-up for %s" % d.name,
+ "Who is the Document Shepherd for this document",
+ "We understand that this document doesn't require any IANA actions",
]
for t in typical_comments:
if t in c.comment_text:
@@ -391,66 +399,9 @@ for o in all_drafts:
break
if not handled:
- print "couldn't handle %s '%s'" % (c.id, c.comment_text.replace("\n", "").replace("\r", ""))
+ print "couldn't handle %s '%s'" % (c.id, c.comment_text.replace("\n", "").replace("\r", "")[0:80])
- # import events that might be missing, we can't be sure where
- # to place them but if we don't generate them, we'll be
- # missing the information completely
- e = d.latest_event(Status, type="changed_status_date")
- status_date = e.date if e else None
- if o.idinternal.status_date != status_date:
- e = Status(type="changed_status_date", date=o.idinternal.status_date)
- e.time = made_up_date
- e.by = system_email
- e.doc = d
- e.desc = "Status date has been changed to %s from %s" % (o.idinternal.status_date, status_date)
- e.save()
-
- e = d.latest_event(Event, type="iesg_approved")
- approved_date = e.time.date() if e else None
- if o.b_approve_date != approved_date:
- e = Event(type="iesg_approved")
- e.time = o.idinternal.b_approve_date
- e.by = system_email
- e.doc = d
- e.desc = "IESG has approved"
- e.save()
-
- if o.lc_expiration_date:
- e = Expiration(type="sent_last_call", expires=o.lc_expiration_date)
- e.time = o.lc_sent_date
- # let's try to figure out who did it
- events = d.event_set.filter(type="changed_document", desc__contains=" to In Last Call").order_by('-time')[:1]
- e.by = events[0].by if events else system_email
- e.doc = d
- e.desc = "Last call sent"
- e.save()
-
- if o.idinternal:
- e = d.latest_event(Telechat, type="scheduled_for_telechat")
- telechat_date = e.telechat_date if e else None
- if not o.idinternal.agenda:
- o.idinternal.telechat_date = None # normalize
-
- if telechat_date != o.idinternal.telechat_date:
- e = Telechat(type="scheduled_for_telechat",
- telechat_date=o.idinternal.telechat_date,
- returning_item=bool(o.idinternal.returning_item))
- e.time = made_up_date
- e.by = system_email
- args = ("Placed on", o.idinternal.telechat_date) if o.idinternal.telechat_date else ("Removed from", telechat_date)
- e.doc = d
- e.desc = "%s agenda for telechat - %s by system" % args
- e.save()
-
- # FIXME: import writeups
-
- # RFC alias
- if o.rfc_number:
- rfc_name = "rfc%s" % o.rfc_number
- DocAlias.objects.get_or_create(document=d, name=rfc_name)
-
# import missing revision changes from DraftVersions
known_revisions = set(e.newrevision.rev for e in d.event_set.filter(type="new_revision").select_related('newrevision'))
for v in DraftVersions.objects.filter(filename=d.name).order_by("revision"):
@@ -466,6 +417,71 @@ for o in all_drafts:
e.desc = "New version available"
e.save()
known_revisions.add(v.revision)
+
+ # import events that might be missing, we can't be sure where
+ # to place them but if we don't generate them, we'll be
+ # missing the information completely
+ e = d.latest_event(Event, type="iesg_approved")
+ approved_date = e.time.date() if e else None
+ if o.b_approve_date != approved_date:
+ e = Event(type="iesg_approved")
+ e.time = o.idinternal.b_approve_date
+ e.by = system_email
+ e.doc = d
+ e.desc = "IESG has approved"
+ e.save()
+
+ if o.lc_expiration_date:
+ e = Expiration(type="sent_last_call", expires=o.lc_expiration_date)
+ e.time = o.lc_sent_date
+ # let's try to figure out who did it
+ events = d.event_set.filter(type="changed_document", desc__contains=" to In Last Call").order_by('-time')[:1]
+ e.by = events[0].by if events else system_email
+ e.doc = d
+ e.desc = "Last call sent"
+ e.save()
+
+ if o.idinternal:
+ e = d.latest_event(Status, type="changed_status_date")
+ status_date = e.date if e else None
+ if o.idinternal.status_date != status_date:
+ e = Status(type="changed_status_date", date=o.idinternal.status_date)
+ e.time = made_up_date
+ e.by = system_email
+ e.doc = d
+ e.desc = "Status date has been changed to %s from %s" % (o.idinternal.status_date, status_date)
+ e.save()
+
+ e = d.latest_event(Telechat, type="scheduled_for_telechat")
+ telechat_date = e.telechat_date if e else None
+ if not o.idinternal.agenda:
+ o.idinternal.telechat_date = None # normalize
+
+ if telechat_date != o.idinternal.telechat_date:
+ e = Telechat(type="scheduled_for_telechat",
+ telechat_date=o.idinternal.telechat_date,
+ returning_item=bool(o.idinternal.returning_item))
+ e.time = made_up_date # FIXME: this isn't good, will override new values, estimate one instead
+ e.by = system_email
+ args = ("Placed on", o.idinternal.telechat_date) if o.idinternal.telechat_date else ("Removed from", telechat_date)
+ e.doc = d
+ e.desc = "%s agenda for telechat - %s by system" % args
+ e.save()
+
+ # FIXME: import writeups
+
+ # import other attributes
+
+ # RFC alias
+ if o.rfc_number:
+ rfc_name = "rfc%s" % o.rfc_number
+ DocAlias.objects.get_or_create(document=d, name=rfc_name)
+ # FIXME: some RFCs seem to be called rfc1234bis?
+
+ if o.replaced_by:
+ replacement, _ = Document.objects.get_or_create(name=o.replaced_by.filename)
+ RelatedDocument.objects.get_or_create(document=replacement, doc_alias=d_alias, relationship=relationship_replaces)
+
print "imported", d.name, " - ", d.iesg_state
@@ -503,8 +519,8 @@ class CheckListInternetDraft(models.Model):
# rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
# comments = models.TextField(blank=True,null=True)
# last_modified_date = models.DateField()
- replaced_by = BrokenForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
- replaces = FKAsOneToOne('replaces', reverse=True)
+# replaced_by = BrokenForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
+# replaces = FKAsOneToOne('replaces', reverse=True)
review_by_rfc_editor = models.BooleanField()
expired_tombstone = models.BooleanField()
# idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
From daf08da04be75a6dfa2ed2fe41496cc60a4c0e62 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 4 Jan 2011 19:25:20 +0000
Subject: [PATCH 10/75] Import remaining missing attributes in InternetDraft,
IDInternal and Ballot, fix bugs - Legacy-Id: 2736
---
ietf/idrfc/views_doc.py | 3 +-
redesign/doc/models.py | 11 +-
redesign/doc/proxy.py | 84 +++++-----
redesign/import-document-state.py | 254 ++++++++++++++++++++++--------
redesign/import-roles.py | 6 +-
redesign/person/proxy.py | 2 +
6 files changed, 253 insertions(+), 107 deletions(-)
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index e215e9999..dcacbc238 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -165,7 +165,8 @@ def _get_history(doc, versions):
results.append({'comment':e, 'info':info, 'date':e.time, 'is_com':True})
prev_rev = "00"
- for o in reversed(results):
+ results.sort(key=lambda x: x['date'])
+ for o in results:
e = o["comment"]
if e.type == "new_revision":
e.version = e.newrevision.rev
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index 27f3c0720..0b73053e5 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -164,10 +164,10 @@ EVENT_TYPES = [
("added_tombstone", "Added tombstone"),
("expired_document", "Expired document"),
("requested_resurrect", "Requested resurrect"),
+ ("completed_resurrect", "Completed resurrect"),
# IESG events
("sent_ballot_announcement", "Sent ballot announcement"),
- ("deferred_ballot", "Deferred ballot"),
("changed_ballot_position", "Changed ballot position"),
("changed_ballot_approval_text", "Changed ballot approval text"),
("changed_ballot_writeup_text", "Changed ballot writeup text"),
@@ -180,9 +180,8 @@ EVENT_TYPES = [
("scheduled_for_telechat", "Scheduled for telechat"),
- ("resolved_to_do_not_publish", "Resolved to 'do not publish'"),
- ("resolved_to_no_problem", "Resolved to 'no problem'"),
- ("iesg_approved", "IESG approved document"),
+ ("iesg_approved", "IESG approved document (no problem)"),
+ ("iesg_disapproved", "IESG disapproved document (do not publish)"),
("approved_in_minute", "Approved in minute"),
]
@@ -221,10 +220,10 @@ class BallotPosition(Event):
comment_time = models.DateTimeField(help_text="Time optional comment was written", blank=True, null=True)
class Status(Event):
- date = models.DateField()
+ date = models.DateField(blank=True, null=True)
class Expiration(Event):
- expires = models.DateTimeField()
+ expires = models.DateTimeField(blank=True, null=True)
class Telechat(Event):
telechat_date = models.DateField(blank=True, null=True)
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index 647edad59..7f7836952 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -53,7 +53,7 @@ class InternetDraft(Document):
for t in possible_types:
if m.endswith(t):
res.add(t)
- return ",".join(res)
+ return ",".join(res) or ".txt"
#txt_page_count = models.IntegerField()
@property
def txt_page_count(self):
@@ -113,7 +113,7 @@ class InternetDraft(Document):
@property
def b_approve_date(self):
e = self.latest_event(type="iesg_approved")
- return e.time if e else None
+ return e.time.date() if e else None
#wgreturn_date = models.DateField(null=True, blank=True) # unused
@@ -148,13 +148,13 @@ class InternetDraft(Document):
#review_by_rfc_editor = models.BooleanField()
@property
- def review_by_rfc_editor(self): raise NotImplemented # should use tag
+ def review_by_rfc_editor(self):
+ return bool(self.tags.filter(slug='rfc-rev'))
#expired_tombstone = models.BooleanField()
@property
def expired_tombstone(self):
- # FIXME: this is probably not perfect, what happens when we delete it again
- return self.latest_event(type="added_tombstone")
+ return bool(self.tags.filter(slug='exp-tomb'))
#idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
@property
@@ -176,7 +176,8 @@ class InternetDraft(Document):
r = max(r - 1, 0)
return "%02d" % r
def expiration(self):
- return self.revision_date + datetime.timedelta(self.DAYS_TO_EXPIRE)
+ e = self.latest_event(type__in=("completed_resurrect", "new_revision"))
+ return e.time.date() + datetime.timedelta(self.DAYS_TO_EXPIRE)
def can_expire(self):
# Copying the logic from expire-ids-1 without thinking
# much about it.
@@ -234,22 +235,22 @@ class InternetDraft(Document):
#primary_flag = models.IntegerField(blank=True, null=True)
@property
- def primary(self):
- return True # left-over from multi-ballot documents?
+ def primary_flag(self):
+ # left-over from multi-ballot documents which we don't really
+ # support anymore, just pretend we're always primary
+ return True
- #group_flag = models.IntegerField(blank=True, default=0) # unused?
+ #group_flag = models.IntegerField(blank=True, default=0) # not used anymore, contained the group acronym_id once upon a time (so it wasn't a flag)
#token_name = models.CharField(blank=True, max_length=25)
@property
def token_name(self):
- e = self.latest_event()
- return e.by.person.name if e else None
+ return self.ad.get_name()
#token_email = models.CharField(blank=True, max_length=255)
@property
def token_email(self):
- e = self.latest_event()
- return e.by.address if e else None
+ return self.ad.address
#note = models.TextField(blank=True) # same name
@@ -273,7 +274,7 @@ class InternetDraft(Document):
#prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
@property
def prev_state(self):
- ds = self.dochistory_set.all().order_by('-time')[:1]
+ ds = self.dochistory_set.exclude(iesg_state=self.iesg_state).order_by('-time')[:1]
return ds[0].iesg_state if ds else None
#assigned_to = models.CharField(blank=True, max_length=25) # unused
@@ -282,7 +283,8 @@ class InternetDraft(Document):
@property
def mark_by(self):
e = self.latest_event()
- return e.by if e else None
+ from person.proxy import IESGLogin as IESGLoginProxy
+ return IESGLoginProxy(e.by) if e else None
# job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
@property
@@ -310,10 +312,12 @@ class InternetDraft(Document):
#cur_sub_state = BrokenForeignKey(IDSubState, related_name='docs', null=True, blank=True, null_values=(0, -1))
@property
def cur_sub_state(self):
- return ", ".join(self.tags.all())
+ s = self.tags.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point'])
+ return s[0] if s else None
@property
def cur_sub_state_id(self):
- return 0
+ s = self.cur_sub_state
+ return 1 if s else 0 # need to return something numeric
#prev_sub_state = BrokenForeignKey(IDSubState, related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
@property
@@ -339,7 +343,7 @@ class InternetDraft(Document):
#via_rfc_editor = models.IntegerField(null=True, blank=True)
@property
def via_rfc_editor(self):
- return bool(self.tags.filter(slug='viarfceditor'))
+ return bool(self.tags.filter(slug='via-rfc'))
#state_change_notice_to = models.CharField(blank=True, max_length=255)
@property
@@ -349,23 +353,27 @@ class InternetDraft(Document):
#dnp = models.IntegerField(null=True, blank=True)
@property
def dnp(self):
- return self.latest_event(type="resolved_to_do_not_publish")
+ e = self.latest_event(type__in=("iesg_disapproved", "iesg_approved"))
+ return e != None and e.type == "iesg_disapproved"
#dnp_date = models.DateField(null=True, blank=True)
@property
def dnp_date(self):
- e = self.latest_event(type="resolved_to_do_not_publish")
- return e.time if e else None
+ e = self.latest_event(type__in=("iesg_disapproved", "iesg_approved"))
+ return e.time.date() if e != None and e.type == "iesg_disapproved" else None
#noproblem = models.IntegerField(null=True, blank=True)
@property
def noproblem(self):
- return self.latest_event(type="resolved_to_no_problem")
+ e = self.latest_event(type__in=("iesg_disapproved", "iesg_approved"))
+ return e != None and e.type == "iesg_approved"
#resurrect_requested_by = BrokenForeignKey(IESGLogin, db_column='resurrect_requested_by', related_name='docsresurrected', null=True, blank=True)
@property
def resurrect_requested_by(self):
- return self.latest_event(type="requested_resurrect")
+ e = self.latest_event(type__in=("requested_resurrect", "completed_resurrect"))
+ from person.proxy import IESGLogin as IESGLoginProxy
+ return IESGLoginProxy(e.by) if e and e.type == "requested_resurrect" else None
#approved_in_minute = models.IntegerField(null=True, blank=True)
@property
@@ -404,7 +412,8 @@ class InternetDraft(Document):
#active = models.BooleanField()
@property
def active(self):
- return self.iesg_state and self.iesg_state.name in ['In Last Call', 'Waiting for Writeup', 'Waiting for AD Go-Ahead', 'IESG Evaluation', 'IESG Evaluation - Defer']
+ # taken from BallotWrapper
+ return self.latest_event(type="sent_ballot_announcement") and self.iesg_state and self.iesg_state.name in ['In Last Call', 'Waiting for Writeup', 'Waiting for AD Go-Ahead', 'IESG Evaluation', 'IESG Evaluation - Defer'] and (self.state_id == "rfc" or self.state_id == "active")
#an_sent = models.BooleanField()
@property
@@ -421,42 +430,45 @@ class InternetDraft(Document):
@property
def an_sent_by(self):
e = self.latest_event(type="iesg_approved")
- return e.by if e else None
+ from person.proxy import IESGLogin as IESGLoginProxy
+ return IESGLoginProxy(e.by) if e else None
#defer = models.BooleanField()
@property
def defer(self):
- return bool(self.latest_event(type="deferred_ballot"))
+ # we're deferred if we're in the deferred state
+ return self.iesg_state and self.iesg_state.name == "IESG Evaluation - Defer"
#defer_by = models.ForeignKey(IESGLogin, db_column='defer_by', related_name='deferred', null=True)
@property
def defer_by(self):
- e = self.latest_event(type="deferred_ballot")
- return e.by if e else None
+ e = self.latest_event(type="changed_document", desc__startswith="State changed to IESG Evaluation - Defer")
+ from person.proxy import IESGLogin as IESGLoginProxy
+ return IESGLoginProxy(e.by) if e else None
#defer_date = models.DateField(null=True, blank=True)
@property
def defer_date(self):
- e = self.latest_event(type="deferred_ballot")
- return e.time if e else None
+ e = self.latest_event(type="changed_document", desc__startswith="State changed to IESG Evaluation - Defer")
+ return e.time.date() if e else None
#approval_text = models.TextField(blank=True)
@property
def approval_text(self):
- e = self.latest_event(type="changed_ballot_approval_text")
- return e.text.content if e else ""
+ e = self.latest_event(Text, type="changed_ballot_approval_text")
+ return e.content if e else ""
#last_call_text = models.TextField(blank=True)
@property
def last_call_text(self):
- e = self.latest_event(type="changed_last_call_text")
- return e.text.content if e else ""
+ e = self.latest_event(Text, type="changed_last_call_text")
+ return e.content if e else ""
#ballot_writeup = models.TextField(blank=True)
@property
def ballot_writeup(self):
- e = self.latest_event(type="changed_ballot_writeup_text")
- return e.text.content if e else ""
+ e = self.latest_event(Text, type="changed_ballot_writeup_text")
+ return e.content if e else ""
#ballot_issued = models.IntegerField(null=True, blank=True)
@property
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 683dc1e3d..5254b7e08 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -100,6 +100,23 @@ ballot_position_mapping = {
None: name(BallotPositionName, 'norecord', 'No record'),
}
+substate_mapping = {
+ "External Party": name(DocInfoTagName, 'extpty', "External Party", 'The document is awaiting review or input from an external party (i.e, someone other than the shepherding AD, the authors, or the WG). See the "note" field for more details on who has the action.'),
+ "Revised ID Needed": name(DocInfoTagName, 'need-rev', "Revised ID Needed", 'An updated ID is needed to address the issues that have been raised.'),
+ "AD Followup": name(DocInfoTagName, 'ad-f-up', "AD Followup", """A generic substate indicating that the shepherding AD has the action item to determine appropriate next steps. In particular, the appropriate steps (and the corresponding next state or substate) depend entirely on the nature of the issues that were raised and can only be decided with active involvement of the shepherding AD. Examples include:
+
+- if another AD raises an issue, the shepherding AD may first iterate with the other AD to get a better understanding of the exact issue. Or, the shepherding AD may attempt to argue that the issue is not serious enough to bring to the attention of the authors/WG.
+
+- if a documented issue is forwarded to a WG, some further iteration may be needed before it can be determined whether a new revision is needed or whether the WG response to an issue clarifies the issue sufficiently.
+
+- when a new revision appears, the shepherding AD will first look at the changes to determine whether they believe all outstanding issues have been raised satisfactorily, prior to asking the ADs who raised the original issues to verify the changes."""),
+ "Point Raised - writeup needed": name(DocInfoTagName, 'point', "Point Raised - writeup needed", 'IESG discussions on the document have raised some issues that need to be brought to the attention of the authors/WG, but those issues have not been written down yet. (It is common for discussions during a telechat to result in such situations. An AD may raise a possible issue during a telechat and only decide as a result of that discussion whether the issue is worth formally writing up and bringing to the attention of the authors/WG). A document stays in the "Point Raised - Writeup Needed" state until *ALL* IESG comments that have been raised have been documented.')
+ }
+
+tag_review_by_rfc_editor = name(DocInfoTagName, 'rfc-rev', "Review by RFC Editor")
+tag_via_rfc_editor = name(DocInfoTagName, 'via-rfc', "Via RFC Editor")
+tag_expired_tombstone = name(DocInfoTagName, 'exp-tomb', "Expired tombstone")
+tag_approved_in_minute = name(DocInfoTagName, 'app-min', "Approved in minute")
# helpers for events
@@ -120,6 +137,11 @@ def iesg_login_to_email(l):
if not l:
return system_email
else:
+ # there's a bunch of old weird comments made by "IESG
+ # Member", transform these into "System" instead
+ if l.id == 2:
+ return system_email
+
# fix logins without the right person
if not l.person:
if l.id not in buggy_iesg_logins_cache:
@@ -138,8 +160,11 @@ def iesg_login_to_email(l):
try:
return Email.objects.get(address=l.person.email()[1])
except Email.DoesNotExist:
- print "MISSING IESG LOGIN", l.person.email()
- return None
+ try:
+ return Email.objects.get(person__name="%s %s" % (l.person.first_name, l.person.last_name))
+ except Email.DoesNotExist:
+ print "MISSING IESG LOGIN", l.person.email()
+ return None
# regexps for parsing document comments
@@ -148,21 +173,23 @@ def date_in_match(match):
return datetime.date(int(match.group('year')), int(match.group('month')), int(match.group('day')))
re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat(| - %s) by" % date_re_str)
+re_telechat_changed = re.compile(r"Telechat date (was|has been) changed to ()?%s()? from" % date_re_str)
re_ballot_position = re.compile(r"\[Ballot Position Update\] (New position, (?P.*), has been recorded (|for (?P.*) )|Position (|for (?P.*) )has been changed to (?P.*) from .*)by (?P.*)")
re_ballot_issued = re.compile(r"Ballot has been issued(| by)")
re_state_changed = re.compile(r"(State (has been changed|changed|Changes) to (?P.*) from (?P.*) by|Sub state has been changed to (?P.*) from (?P.*))")
-re_note_changed = re.compile(r"(\[Note\]: .*'.*'|Note field has been cleared)")
+re_note_changed = re.compile(r"(\[Note\]: .*'.*'|Note field has been cleared)", re.DOTALL)
re_draft_added = re.compile(r"Draft [Aa]dded (by .*)?( in state (?P.*))?")
re_last_call_requested = re.compile(r"Last Call was requested")
re_document_approved = re.compile(r"IESG has approved and state has been changed to")
+re_document_disapproved = re.compile(r"(Do Not Publish|DNP) note has been sent to RFC Editor and state has been changed to")
+re_resurrection_requested = re.compile(r"(I-D |)Resurrection was requested by")
+re_completed_resurrect = re.compile(r"(This document has been resurrected|This document has been resurrected per RFC Editor's request|Resurrection was completed)")
re_status_date_changed = re.compile(r"Status [dD]ate has been changed to ()?" + date_re_str)
-re_responsible_ad_changed = re.compile(r"(Responsible AD|Shepherding AD) has been changed to ()?")
+re_responsible_ad_changed = re.compile(r"(Responsible AD|Shepherding AD|responsible) has been changed to ()?")
re_intended_status_changed = re.compile(r"Intended [sS]tatus has been changed to ()?")
re_state_change_notice = re.compile(r"State Change Notice email list (have been change|has been changed) ()?")
-
-
-made_up_date = datetime.datetime(2030, 1, 1, 0, 0, 0)
+re_area_acronym_changed = re.compile(r"Area acronymn? has been changed to \w+ from \w+()?")
all_drafts = InternetDraft.objects.all().select_related()
if draft_name_to_import:
@@ -180,10 +207,10 @@ for o in all_drafts:
d.title = o.title
d.state = status_mapping[o.status.status]
d.group = Group.objects.get(acronym=o.group.acronym)
-# d.tags =
d.stream = stream_ietf
d.wg_state = None
d.iesg_state = iesg_state_mapping[o.idinternal.cur_state.state if o.idinternal else None]
+ # we currently ignore the previous IESG state prev_state
d.iana_state = None
# d.rfc_state =
d.rev = o.revision
@@ -209,12 +236,14 @@ for o in all_drafts:
d.event_set.all().delete()
if o.idinternal:
+ last_note_change_text = ""
+
# extract events
for c in o.idinternal.documentcomment_set.order_by('date', 'time', 'id'):
handled = False
# telechat agenda schedulings
- match = re_telechat_agenda.search(c.comment_text)
+ match = re_telechat_agenda.search(c.comment_text) or re_telechat_changed.search(c.comment_text)
if match:
e = Telechat()
e.type = "scheduled_for_telechat"
@@ -305,8 +334,11 @@ for o in all_drafts:
# note changed
match = re_note_changed.search(c.comment_text)
if match:
- e = Event(type="changed_document")
- save_event(d, e, c)
+ # watch out for duplicates of which the old data's got many
+ if c.comment_text != last_note_change_text:
+ last_note_change_text = c.comment_text
+ e = Event(type="changed_document")
+ save_event(d, e, c)
handled = True
# draft added
@@ -322,6 +354,20 @@ for o in all_drafts:
save_event(d, e, c)
handled = True
+ # resurrect requested
+ match = re_resurrection_requested.search(c.comment_text)
+ if match:
+ e = Event(type="requested_resurrect")
+ save_event(d, e, c)
+ handled = True
+
+ # completed resurrect
+ match = re_completed_resurrect.search(c.comment_text)
+ if match:
+ e = Event(type="completed_resurrect")
+ save_event(d, e, c)
+ handled = True
+
# document expiration
if c.comment_text == "Document is expired by system":
e = Event(type="expired_document")
@@ -334,6 +380,13 @@ for o in all_drafts:
e = Event(type="iesg_approved")
save_event(d, e, c)
handled = True
+
+ # disapproved document
+ match = re_document_disapproved.search(c.comment_text)
+ if match:
+ e = Event(type="iesg_disapproved")
+ save_event(d, e, c)
+ handled = True
# some changes can be bundled - this is not entirely
@@ -353,7 +406,7 @@ for o in all_drafts:
# AD/job owner changed
match = re_responsible_ad_changed.search(line)
if match:
- e = Event(type="changed_draft")
+ e = Event(type="changed_document")
e.desc = line
save_event(d, e, c)
handled = True
@@ -361,7 +414,7 @@ for o in all_drafts:
# intended standard level changed
match = re_intended_status_changed.search(line)
if match:
- e = Event(type="changed_draft")
+ e = Event(type="changed_document")
e.desc = line
save_event(d, e, c)
handled = True
@@ -369,7 +422,15 @@ for o in all_drafts:
# state change notice
match = re_state_change_notice.search(line)
if match:
- e = Event(type="changed_draft")
+ e = Event(type="changed_document")
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # area acronym
+ match = re_area_acronym_changed.search(line)
+ if match:
+ e = Event(type="changed_document")
e.desc = line
save_event(d, e, c)
handled = True
@@ -380,7 +441,9 @@ for o in all_drafts:
if handled:
c.comment_text = " ".join(unhandled_lines)
-
+
+ if c.comment_text:
+ print "couldn't handle multi-line comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
# all others are added as comments
if not handled:
@@ -392,6 +455,11 @@ for o in all_drafts:
"Document Shepherd Write-up for %s" % d.name,
"Who is the Document Shepherd for this document",
"We understand that this document doesn't require any IANA actions",
+ "IANA questions",
+ "IANA has questions",
+ "IANA comments",
+ "IANA Comments",
+ "IANA Evaluation Comments",
]
for t in typical_comments:
if t in c.comment_text:
@@ -399,7 +467,7 @@ for o in all_drafts:
break
if not handled:
- print "couldn't handle %s '%s'" % (c.id, c.comment_text.replace("\n", "").replace("\r", "")[0:80])
+ print "couldn't handle comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
# import missing revision changes from DraftVersions
@@ -418,30 +486,37 @@ for o in all_drafts:
e.save()
known_revisions.add(v.revision)
- # import events that might be missing, we can't be sure where
- # to place them but if we don't generate them, we'll be
- # missing the information completely
- e = d.latest_event(Event, type="iesg_approved")
- approved_date = e.time.date() if e else None
- if o.b_approve_date != approved_date:
- e = Event(type="iesg_approved")
- e.time = o.idinternal.b_approve_date
+ # import events that might be missing, we can't be sure who did
+ # them or when but if we don't generate them, we'll be missing the
+ # information completely
+
+ # make sure last decision is recorded
+ e = d.latest_event(Event, type__in=("iesg_approved", "iesg_disapproved"))
+ decision_date = e.time.date() if e else None
+ if o.b_approve_date != decision_date:
+ disapproved = o.idinternal and o.idinternal.dnp
+ e = Event(type="iesg_disapproved" if disapproved else "iesg_approved")
+ e.time = o.b_approve_date
e.by = system_email
e.doc = d
- e.desc = "IESG has approved"
+ e.desc = "Do Not Publish note has been sent to RFC Editor" if disapproved else "IESG has approved"
e.save()
if o.lc_expiration_date:
e = Expiration(type="sent_last_call", expires=o.lc_expiration_date)
- e.time = o.lc_sent_date
- # let's try to figure out who did it
+ # let's try to find the actual change
events = d.event_set.filter(type="changed_document", desc__contains=" to In Last Call").order_by('-time')[:1]
+ # event time is more accurate with actual time instead of just
+ # date, gives better sorting
+ e.time = events[0].time if events else o.lc_sent_date
e.by = events[0].by if events else system_email
e.doc = d
e.desc = "Last call sent"
e.save()
if o.idinternal:
+ made_up_date = d.latest_event().time + datetime.timedelta(seconds=1) # datetime.datetime(2030, 1, 1, 0, 0, 0)
+
e = d.latest_event(Status, type="changed_status_date")
status_date = e.date if e else None
if o.idinternal.status_date != status_date:
@@ -461,17 +536,72 @@ for o in all_drafts:
e = Telechat(type="scheduled_for_telechat",
telechat_date=o.idinternal.telechat_date,
returning_item=bool(o.idinternal.returning_item))
- e.time = made_up_date # FIXME: this isn't good, will override new values, estimate one instead
+ # a common case is that it has been removed from the
+ # agenda automatically by a script without a notice in the
+ # comments, in that case the time is simply the day after
+ # the telechat
+ e.time = telechat_date + datetime.timedelta(days=1) if telechat_date and not o.idinternal.telechat_date else made_up_date
e.by = system_email
args = ("Placed on", o.idinternal.telechat_date) if o.idinternal.telechat_date else ("Removed from", telechat_date)
e.doc = d
e.desc = "%s agenda for telechat - %s by system" % args
e.save()
+
+ if o.idinternal.ballot:
+ text_date = made_up_date
+
+ # if any of these events have happened, they're closer to
+ # the real time
+ e = d.event_set.filter(type__in=("requested_last_call", "sent_last_call", "sent_ballot_announcement", "iesg_approved", "iesg_disapproved")).order_by('time')[:1]
+ if e:
+ text_date = e[0].time - datetime.timedelta(seconds=1)
- # FIXME: import writeups
+ if o.idinternal.ballot.approval_text:
+ e = Text(type="changed_ballot_approval_text", content=o.idinternal.ballot.approval_text)
+ e.time = text_date
+ e.by = system_email
+ e.doc = d
+ e.desc = "Ballot approval text was added"
+ e.save()
+
+ if o.idinternal.ballot.last_call_text:
+ e = Text(type="changed_last_call_text", content=o.idinternal.ballot.last_call_text)
+ e.time = text_date
+ e.by = system_email
+ e.doc = d
+ e.desc = "Last call text was added"
+ e.save()
+
+ if o.idinternal.ballot.ballot_writeup:
+ e = Text(type="changed_ballot_writeup_text", content=o.idinternal.ballot.ballot_writeup)
+ e.time = text_date
+ e.by = system_email
+ e.doc = d
+ e.desc = "Ballot writeup text was added"
+ e.save()
# import other attributes
+ # tags
+ tags = d.tags.all()
+ def sync_tag(include, tag):
+ if include and tag not in tags:
+ d.tags.add(tag)
+ if not include and tag in tags:
+ d.tags.remove(tag)
+
+ sync_tag(o.review_by_rfc_editor, tag_review_by_rfc_editor)
+ sync_tag(o.expired_tombstone, tag_expired_tombstone)
+ sync_tag(o.idinternal and o.idinternal.via_rfc_editor, tag_via_rfc_editor)
+
+ n = o.idinternal and o.idinternal.cur_sub_state and o.idinternal.cur_sub_state.sub_state
+ for k, v in substate_mapping.iteritems():
+ sync_tag(k == n, v)
+ # currently we ignore prev_sub_state
+
+ sync_tag(o.idinternal and o.idinternal.approved_in_minute, tag_approved_in_minute)
+
+
# RFC alias
if o.rfc_number:
rfc_name = "rfc%s" % o.rfc_number
@@ -491,7 +621,7 @@ for o in all_drafts:
sys.exit(0)
-class CheckListInternetDraft(models.Model):
+#class CheckListInternetDraft(models.Model):
# id_document_tag = models.AutoField(primary_key=True)
# title = models.CharField(max_length=255, db_column='id_document_name')
# id_document_key = models.CharField(max_length=255, editable=False)
@@ -521,51 +651,51 @@ class CheckListInternetDraft(models.Model):
# last_modified_date = models.DateField()
# replaced_by = BrokenForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
# replaces = FKAsOneToOne('replaces', reverse=True)
- review_by_rfc_editor = models.BooleanField()
- expired_tombstone = models.BooleanField()
+# review_by_rfc_editor = models.BooleanField()
+# expired_tombstone = models.BooleanField()
# idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
-class CheckListIDInternal(models.Model):
+#class CheckListIDInternal(models.Model):
# draft = models.ForeignKey(InternetDraft, primary_key=True, unique=True, db_column='id_document_tag')
- rfc_flag = models.IntegerField(null=True)
- ballot = models.ForeignKey('BallotInfo', related_name='drafts', db_column="ballot_id")
- primary_flag = models.IntegerField(blank=True, null=True)
- group_flag = models.IntegerField(blank=True, default=0)
- token_name = models.CharField(blank=True, max_length=25)
- token_email = models.CharField(blank=True, max_length=255)
+# rfc_flag = models.IntegerField(null=True)
+# ballot = models.ForeignKey('BallotInfo', related_name='drafts', db_column="ballot_id")
+# primary_flag = models.IntegerField(blank=True, null=True)
+# group_flag = models.IntegerField(blank=True, default=0)
+# token_name = models.CharField(blank=True, max_length=25)
+# token_email = models.CharField(blank=True, max_length=255)
# note = models.TextField(blank=True)
# status_date = models.DateField(blank=True,null=True)
- email_display = models.CharField(blank=True, max_length=50)
- agenda = models.IntegerField(null=True, blank=True)
+# email_display = models.CharField(blank=True, max_length=50)
+# agenda = models.IntegerField(null=True, blank=True)
# cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs')
- prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
- assigned_to = models.CharField(blank=True, max_length=25)
- mark_by = models.ForeignKey('IESGLogin', db_column='mark_by', related_name='marked')
+# prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
+# assigned_to = models.CharField(blank=True, max_length=25)
+# mark_by = models.ForeignKey('IESGLogin', db_column='mark_by', related_name='marked')
# job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
- event_date = models.DateField(null=True)
+# event_date = models.DateField(null=True)
# area_acronym = models.ForeignKey('Area')
- cur_sub_state = BrokenForeignKey('IDSubState', related_name='docs', null=True, blank=True, null_values=(0, -1))
- prev_sub_state = BrokenForeignKey('IDSubState', related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
+# cur_sub_state = BrokenForeignKey('IDSubState', related_name='docs', null=True, blank=True, null_values=(0, -1))
+# prev_sub_state = BrokenForeignKey('IDSubState', related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
# returning_item = models.IntegerField(null=True, blank=True)
# telechat_date = models.DateField(null=True, blank=True)
- via_rfc_editor = models.IntegerField(null=True, blank=True)
+# via_rfc_editor = models.IntegerField(null=True, blank=True)
# state_change_notice_to = models.CharField(blank=True, max_length=255)
- dnp = models.IntegerField(null=True, blank=True)
- dnp_date = models.DateField(null=True, blank=True)
- noproblem = models.IntegerField(null=True, blank=True)
- resurrect_requested_by = BrokenForeignKey('IESGLogin', db_column='resurrect_requested_by', related_name='docsresurrected', null=True, blank=True)
- approved_in_minute = models.IntegerField(null=True, blank=True)
+# dnp = models.IntegerField(null=True, blank=True)
+# dnp_date = models.DateField(null=True, blank=True)
+# noproblem = models.IntegerField(null=True, blank=True)
+# resurrect_requested_by = BrokenForeignKey('IESGLogin', db_column='resurrect_requested_by', related_name='docsresurrected', null=True, blank=True)
+# approved_in_minute = models.IntegerField(null=True, blank=True)
-class CheckListBallotInfo(models.Model):
- ballot = models.AutoField(primary_key=True, db_column='ballot_id')
- active = models.BooleanField()
+#class CheckListBallotInfo(models.Model):
+# ballot = models.AutoField(primary_key=True, db_column='ballot_id')
+# active = models.BooleanField()
# an_sent = models.BooleanField()
# an_sent_date = models.DateField(null=True, blank=True)
# an_sent_by = models.ForeignKey('IESGLogin', db_column='an_sent_by', related_name='ansent', null=True)
- defer = models.BooleanField(blank=True)
- defer_by = models.ForeignKey('IESGLogin', db_column='defer_by', related_name='deferred', null=True)
- defer_date = models.DateField(null=True, blank=True)
- approval_text = models.TextField(blank=True)
- last_call_text = models.TextField(blank=True)
- ballot_writeup = models.TextField(blank=True)
+# defer = models.BooleanField(blank=True)
+# defer_by = models.ForeignKey('IESGLogin', db_column='defer_by', related_name='deferred', null=True)
+# defer_date = models.DateField(null=True, blank=True)
+# approval_text = models.TextField(blank=True)
+# last_call_text = models.TextField(blank=True)
+# ballot_writeup = models.TextField(blank=True)
# ballot_issued = models.IntegerField(null=True, blank=True)
diff --git a/redesign/import-roles.py b/redesign/import-roles.py
index 99914d291..cb465b258 100755
--- a/redesign/import-roles.py
+++ b/redesign/import-roles.py
@@ -40,9 +40,11 @@ area_director_role = name(RoleName, "ad", "Area Director")
# helpers for creating the objects
def get_or_create_email(o):
- email = o.person.email()[1]
+ hardcoded_emails = { 'Dinara Suleymanova': "dinaras@ietf.org" }
+
+ email = o.person.email()[1] or hardcoded_emails.get("%s %s" % (o.person.first_name, o.person.last_name))
if not email:
- print "NO EMAIL FOR %s %s" % (o.__class__, o.id)
+ print "NO EMAIL FOR %s %s %s %s" % (o.__class__, o.id, o.person.first_name, o.person.last_name)
return None
e, _ = Email.objects.get_or_create(address=email)
diff --git a/redesign/person/proxy.py b/redesign/person/proxy.py
index 2b080d3ea..009ba0742 100644
--- a/redesign/person/proxy.py
+++ b/redesign/person/proxy.py
@@ -37,6 +37,8 @@ class IESGLogin(Email):
def __str__(self):
return self.get_name()
+ def __unicode__(self):
+ return self.get_name()
def is_current_ad(self):
return self in Email.objects.filter(role__name="ad", role__group__state="active")
@staticmethod
From 34d9f5f890ffa375f6c55b192124365c286f20c7 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Wed, 5 Jan 2011 19:26:09 +0000
Subject: [PATCH 11/75] Import some attributes, aliases and relationships from
the rfc_index_mirror table - Legacy-Id: 2737
---
ietf/idrfc/idrfc_wrapper.py | 4 +-
ietf/idrfc/models.py | 6 +-
ietf/idrfc/views_doc.py | 3 +-
redesign/doc/models.py | 10 +-
redesign/doc/proxy.py | 74 ++++++++++-
redesign/import-document-state.py | 212 ++++++++++++++++--------------
redesign/proxy_utils.py | 18 ++-
7 files changed, 216 insertions(+), 111 deletions(-)
diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py
index ed6a4b112..6b9e46359 100644
--- a/ietf/idrfc/idrfc_wrapper.py
+++ b/ietf/idrfc/idrfc_wrapper.py
@@ -287,10 +287,10 @@ class RfcWrapper:
self.draft_name = self._rfcindex.draft
def _rfc_doc_list(self, name):
- if (not self._rfcindex) or (not self._rfcindex.__dict__[name]):
+ if (not self._rfcindex) or (not getattr(self._rfcindex, name)):
return None
else:
- s = self._rfcindex.__dict__[name]
+ s = getattr(self._rfcindex, name)
s = s.replace(",", ", ")
s = re.sub("([A-Z])([0-9])", "\\1 \\2", s)
return s
diff --git a/ietf/idrfc/models.py b/ietf/idrfc/models.py
index 046f68cd8..514b4a09d 100644
--- a/ietf/idrfc/models.py
+++ b/ietf/idrfc/models.py
@@ -96,4 +96,8 @@ class DraftVersions(models.Model):
return "DraftVersions"+self.filename+self.revision+str(self.revision_date)
class Meta:
db_table = "draft_versions_mirror"
-
+
+
+from django.conf import settings
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ from redesign.doc.proxy import RfcIndex
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index dcacbc238..fad15926f 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -149,7 +149,8 @@ def _get_history(doc, versions):
results = []
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
versions = [] # clear versions
- for e in doc._draft.event_set.all().select_related('by').order_by('-time'):
+ event_holder = doc._draft if hasattr(doc, "_draft") else doc._rfcindex
+ for e in event_holder.event_set.all().select_related('by').order_by('-time'):
info = {}
if e.type == "new_revision":
filename = u"%s-%s" % (e.doc.name, e.newrevision.rev)
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index 0b73053e5..3330628a1 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -10,7 +10,7 @@ import datetime
class DocumentInfo(models.Model):
"""Any kind of document. Draft, RFC, Charter, IPR Statement, Liaison Statement"""
- time = models.DateTimeField() # should probably have auto_now=True
+ time = models.DateTimeField(default=datetime.datetime.now) # should probably have auto_now=True
# Document related
type = models.ForeignKey(DocTypeName, blank=True, null=True) # Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email, Review, Issue, Wiki, External ...
title = models.CharField(max_length=255)
@@ -103,8 +103,8 @@ class Document(DocumentInfo):
super(Document, self).save(force_insert, force_update)
class RelatedDocHistory(models.Model):
- document = models.ForeignKey('DocHistory')
- doc_alias = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set")
+ document = models.ForeignKey('DocHistory') # source
+ doc_alias = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set") # target
relationship = models.ForeignKey(DocRelationshipName)
def __unicode__(self):
return u"%s %s %s" % (self.document.name, self.relationship.name.lower(), self.doc_alias.name)
@@ -127,7 +127,8 @@ class DocAlias(models.Model):
return "%s-->%s" % (self.name, self.document.name)
document_link = admin_link("document")
class Meta:
- verbose_name_plural = "aliases"
+ verbose_name = "document alias"
+ verbose_name_plural = "document aliases"
class SendQueue(models.Model):
time = models.DateTimeField() # Scheduled at this time
@@ -165,6 +166,7 @@ EVENT_TYPES = [
("expired_document", "Expired document"),
("requested_resurrect", "Requested resurrect"),
("completed_resurrect", "Completed resurrect"),
+ ("published_rfc", "Published RFC"),
# IESG events
("sent_ballot_announcement", "Sent ballot announcement"),
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index 7f7836952..8039c27ac 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -10,7 +10,8 @@ import glob, os
class InternetDraft(Document):
objects = TranslatingManager(dict(filename="name",
id_document_tag="id",
- status="state"))
+ status="state",
+ rfc_number=lambda v: ("docalias__name", "rfc%s" % v)))
DAYS_TO_EXPIRE=185
@@ -46,7 +47,7 @@ class InternetDraft(Document):
#file_type = models.CharField(max_length=20)
@property
def file_type(self):
- matches = glob.glob(os.path.join(settings.INTERNET_DRAFT_PATH, self.filename + "*.*"))
+ matches = glob.glob(os.path.join(settings.INTERNET_DRAFT_PATH, self.name + "*.*"))
possible_types = [".txt", ".pdf", ".xml", ".ps"]
res = set()
for m in matches:
@@ -133,7 +134,7 @@ class InternetDraft(Document):
#replaced_by = models.ForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
@property
def replaced_by(self):
- r = InternetDraft.objects.filter(related__document=self, related__relateddocument__relationship="replaces")
+ r = InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="replaces")
return r[0] if r else None
#replaces = FKAsOneToOne('replaces', reverse=True)
@@ -144,7 +145,7 @@ class InternetDraft(Document):
@property
def replaces_set(self):
- return InternetDraft.objects.filter(docalias__relateddocument__relationship="replaces", docalias__relateddocument__document=self)
+ return InternetDraft.objects.filter(docalias__relateddocument__document=self, docalias__relateddocument__relationship="replaces")
#review_by_rfc_editor = models.BooleanField()
@property
@@ -548,6 +549,71 @@ class InternetDraft(Document):
return answer.rstrip()
+
+ # things from RfcIndex
+ #rfc_number = models.IntegerField(primary_key=True) # already taken care of
+ #title = models.CharField(max_length=250) # same name
+ #authors = models.CharField(max_length=250) FIXME
+ #rfc_published_date = models.DateField()
+ @property
+ def rfc_published_date(self):
+ e = self.latest_event(type="published_rfc")
+ return e.time.date() if e else None
+
+ #current_status = models.CharField(max_length=50,null=True)
+ @property
+ def current_status(self):
+ return self.std_level
+
+ #updates = models.CharField(max_length=200,blank=True,null=True)
+ @property
+ def updates(self):
+ return ",".join(sorted("RFC%s" % d.rfc_number for d in InternetDraft.objects.filter(docalias__relateddocument__document=self, docalias__relateddocument__relationship="updates")))
+
+ #updated_by = models.CharField(max_length=200,blank=True,null=True)
+ @property
+ def updated_by(self):
+ return ",".join(sorted("RFC%s" % d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="updates")))
+
+ #obsoletes = models.CharField(max_length=200,blank=True,null=True)
+ @property
+ def obsoletes(self):
+ return ",".join(sorted("RFC%s" % d.rfc_number for d in InternetDraft.objects.filter(docalias__relateddocument__document=self, docalias__relateddocument__relationship="obs")))
+
+ #obsoleted_by = models.CharField(max_length=200,blank=True,null=True)
+ @property
+ def obsoleted_by(self):
+ return ",".join(sorted("RFC%s" % d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="obs")))
+
+ #also = models.CharField(max_length=50,blank=True,null=True)
+ @property
+ def also(self):
+ aliases = self.docalias_set.filter(models.Q(name__startswith="bcp") |
+ models.Q(name__startswith="std") |
+ models.Q(name__startswith="bcp"))
+ return aliases[0].name.upper() if aliases else None
+
+ #draft = models.CharField(max_length=200,null=True)
+ @property
+ def draft(self):
+ if not self.name.startswith("rfc"):
+ return self.name
+ else:
+ return None
+
+ #has_errata = models.BooleanField() FIXME
+ #stream = models.CharField(max_length=15,blank=True,null=True)
+ @property
+ def stream(self):
+ return super(InternetDraft, self).stream.name
+ #wg = models.CharField(max_length=15,blank=True,null=True) FIXME
+ #file_formats = models.CharField(max_length=20,blank=True,null=True)
+ @property
+ def file_formats(self):
+ return self.file_type.replace(".", "").replace("txt", "ascii")
+
class Meta:
proxy = True
+
+RfcIndex = InternetDraft
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 5254b7e08..00cacf06c 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -14,14 +14,14 @@ management.setup_environ(settings)
from redesign.doc.models import *
from redesign.group.models import *
from redesign.name.models import *
-from ietf.idtracker.models import InternetDraft, IESGLogin, DocumentComment, PersonOrOrgInfo
-from ietf.idrfc.models import DraftVersions
+from ietf.idtracker.models import InternetDraft, IESGLogin, DocumentComment, PersonOrOrgInfo, RfcObsolete
+from ietf.idrfc.models import RfcIndex, DraftVersions
import sys
-draft_name_to_import = None
+document_name_to_import = None
if len(sys.argv) > 1:
- draft_name_to_import = sys.argv[1]
+ document_name_to_import = sys.argv[1]
# assumptions:
# - groups have been imported
@@ -33,7 +33,8 @@ if len(sys.argv) > 1:
# objects, we just import the comments as events.
# imports InternetDraft, IDInternal, BallotInfo, Position,
-# IESGComment, IESGDiscuss, DocumentComment, idrfc.DraftVersions
+# IESGComment, IESGDiscuss, DocumentComment, RfcObsolete,
+# idrfc.RfcIndex, idrfc.DraftVersions
def name(name_class, slug, name, desc=""):
# create if it doesn't exist, set name and desc
@@ -43,10 +44,17 @@ def name(name_class, slug, name, desc=""):
obj.save()
return obj
+def alias_doc(name, doc):
+ DocAlias.objects.filter(name=name).exclude(document=doc).delete()
+ alias, _ = DocAlias.objects.get_or_create(name=name, document=doc)
+ return alias
+
type_draft = name(DocTypeName, "draft", "Draft")
stream_ietf = name(DocStreamName, "ietf", "IETF")
relationship_replaces = name(DocRelationshipName, "replaces", "Replaces")
+relationship_updates = name(DocRelationshipName, "updates", "Updates")
+relationship_obsoletes = name(DocRelationshipName, "obs", "Obsoletes")
intended_status_mapping = {
"BCP": name(IntendedStatusName, "bcp", "Best Current Practice"),
@@ -165,7 +173,7 @@ def iesg_login_to_email(l):
except Email.DoesNotExist:
print "MISSING IESG LOGIN", l.person.email()
return None
-
+
# regexps for parsing document comments
date_re_str = "(?P[0-9][0-9][0-9][0-9])-(?P[0-9][0-9])-(?P[0-9][0-9])"
@@ -192,8 +200,8 @@ re_state_change_notice = re.compile(r"State Change Notice email list (have been
re_area_acronym_changed = re.compile(r"Area acronymn? has been changed to \w+ from \w+()?")
all_drafts = InternetDraft.objects.all().select_related()
-if draft_name_to_import:
- all_drafts = all_drafts.filter(filename=draft_name_to_import)
+if document_name_to_import:
+ all_drafts = all_drafts.filter(filename=document_name_to_import)
#all_drafts = all_drafts[all_drafts.count() - 1000:]
for o in all_drafts:
@@ -212,14 +220,14 @@ for o in all_drafts:
d.iesg_state = iesg_state_mapping[o.idinternal.cur_state.state if o.idinternal else None]
# we currently ignore the previous IESG state prev_state
d.iana_state = None
-# d.rfc_state =
+# d.rfc_state = # FIXME
d.rev = o.revision
d.abstract = o.abstract
d.pages = o.txt_page_count
d.intended_std_level = intended_status_mapping[o.intended_status.intended_status]
-# d.std_level =
+# d.std_level = # FIXME
# d.authors =
-# d.related =
+# d.related = # FIXME
d.ad = iesg_login_to_email(o.idinternal.job_owner) if o.idinternal else None
d.shepherd = None
d.notify = o.idinternal.state_change_notice_to or "" if o.idinternal else ""
@@ -229,8 +237,7 @@ for o in all_drafts:
d.save()
# make sure our alias is updated
- DocAlias.objects.filter(name=d.name).exclude(document=d).delete()
- d_alias, _ = DocAlias.objects.get_or_create(name=d.name, document=d)
+ alias_doc(d.name, d)
# clear already imported events
d.event_set.all().delete()
@@ -601,101 +608,110 @@ for o in all_drafts:
sync_tag(o.idinternal and o.idinternal.approved_in_minute, tag_approved_in_minute)
-
# RFC alias
if o.rfc_number:
- rfc_name = "rfc%s" % o.rfc_number
- DocAlias.objects.get_or_create(document=d, name=rfc_name)
+ alias_doc("rfc%s" % o.rfc_number, d)
# FIXME: some RFCs seem to be called rfc1234bis?
-
+
if o.replaced_by:
replacement, _ = Document.objects.get_or_create(name=o.replaced_by.filename)
RelatedDocument.objects.get_or_create(document=replacement, doc_alias=d_alias, relationship=relationship_replaces)
-
+
+ # the RFC-related attributes are imported when we handle the RFCs below
print "imported", d.name, " - ", d.iesg_state
-
-# checklist of attributes below: handled attributes are commented out
+# now process RFCs
+
+def get_or_create_rfc_document(rfc_number):
+ name = "rfc%s" % rfc_number
+
+ # try to find a draft that can form the base of the document
+ draft = None
+
+ ids = InternetDraft.objects.filter(rfc_number=rfc_number)[:1]
+ if ids:
+ draft = ids[0]
+ else:
+ r = RfcIndex.objects.get(rfc_number=rfc_number)
+ # rfcindex occasionally includes drafts that were not
+ # really submitted to IETF (e.g. April 1st)
+ if r.draft:
+ ids = InternetDraft.objects.filter(filename=r.draft)[:1]
+ if ids:
+ draft = ids[0]
+
+ if draft:
+ name = draft.filename
+
+ d, _ = Document.objects.get_or_create(name=name)
+ if not name.startswith('rfc'):
+ # make sure draft also got an alias
+ alias_doc(name, d)
+
+ alias = alias_doc("rfc%s" % rfc_number, d)
+
+ return (d, alias)
+
+all_rfcs = RfcIndex.objects.all()
+
+if all_drafts.count() != InternetDraft.objects.count():
+ if document_name_to_import.startswith("rfc"):
+ # we wanted to import just an RFC, great
+ all_rfcs = all_rfcs.filter(rfc_number=document_name_to_import[3:])
+ else:
+ # if we didn't process all drafts, limit the RFCs to the ones we
+ # did process
+ all_rfcs = all_rfcs.filter(rfc_number__in=set(d.rfc_number for d in all_drafts if d.rfc_number))
+
+for o in all_rfcs:
+ d, d_alias = get_or_create_rfc_document(o.rfc_number)
+
+ # import obsoletes/updates
+ def make_relation(other_rfc, rel_type, reverse):
+ other_number = int(other_rfc.replace("RFC", ""))
+ other, other_alias = get_or_create_rfc_document(other_number)
+ if reverse:
+ RelatedDocument.objects.get_or_create(document=other, doc_alias=d_alias, relationship=rel_type)
+ else:
+ RelatedDocument.objects.get_or_create(document=d, doc_alias=other_alias, relationship=rel_type)
+
+ if o.obsoletes:
+ for x in o.obsoletes.split(','):
+ make_relation(x, relationship_obsoletes, False)
+ if o.obsoleted_by:
+ for x in o.obsoleted_by.split(','):
+ make_relation(x, relationship_obsoletes, True)
+ if o.updates:
+ for x in o.updates.split(','):
+ make_relation(x, relationship_updates, False)
+ if o.updated_by:
+ for x in o.updated_by.split(','):
+ make_relation(x, relationship_updates, True)
+
+ if o.also:
+ print o.also
+ alias_doc(o.also.lower(), d)
+
+ print "imported", d_alias.name, " - ", d.rfc_state
+
sys.exit(0)
-
-#class CheckListInternetDraft(models.Model):
-# id_document_tag = models.AutoField(primary_key=True)
-# title = models.CharField(max_length=255, db_column='id_document_name')
-# id_document_key = models.CharField(max_length=255, editable=False)
-# group = models.ForeignKey(Acronym, db_column='group_acronym_id')
-# filename = models.CharField(max_length=255, unique=True)
-# revision = models.CharField(max_length=2)
-# revision_date = models.DateField()
-# file_type = models.CharField(max_length=20)
-# txt_page_count = models.IntegerField()
-# local_path = models.CharField(max_length=255, blank=True, null=True)
-# start_date = models.DateField()
-# expiration_date = models.DateField(null=True)
-# abstract = models.TextField()
-# dunn_sent_date = models.DateField(null=True, blank=True)
-# extension_date = models.DateField(null=True, blank=True)
-# status = models.ForeignKey(IDStatus)
-# intended_status = models.ForeignKey(IDIntendedStatus)
-# lc_sent_date = models.DateField(null=True, blank=True)
-# lc_changes = models.CharField(max_length=3,null=True)
-# lc_expiration_date = models.DateField(null=True, blank=True)
-# b_sent_date = models.DateField(null=True, blank=True)
-# b_discussion_date = models.DateField(null=True, blank=True)
-# b_approve_date = models.DateField(null=True, blank=True)
-# wgreturn_date = models.DateField(null=True, blank=True)
-# rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
-# comments = models.TextField(blank=True,null=True)
-# last_modified_date = models.DateField()
-# replaced_by = BrokenForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
-# replaces = FKAsOneToOne('replaces', reverse=True)
-# review_by_rfc_editor = models.BooleanField()
-# expired_tombstone = models.BooleanField()
-# idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
-
-#class CheckListIDInternal(models.Model):
-# draft = models.ForeignKey(InternetDraft, primary_key=True, unique=True, db_column='id_document_tag')
-# rfc_flag = models.IntegerField(null=True)
-# ballot = models.ForeignKey('BallotInfo', related_name='drafts', db_column="ballot_id")
-# primary_flag = models.IntegerField(blank=True, null=True)
-# group_flag = models.IntegerField(blank=True, default=0)
-# token_name = models.CharField(blank=True, max_length=25)
-# token_email = models.CharField(blank=True, max_length=255)
-# note = models.TextField(blank=True)
-# status_date = models.DateField(blank=True,null=True)
-# email_display = models.CharField(blank=True, max_length=50)
-# agenda = models.IntegerField(null=True, blank=True)
-# cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs')
-# prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
-# assigned_to = models.CharField(blank=True, max_length=25)
-# mark_by = models.ForeignKey('IESGLogin', db_column='mark_by', related_name='marked')
-# job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
-# event_date = models.DateField(null=True)
-# area_acronym = models.ForeignKey('Area')
-# cur_sub_state = BrokenForeignKey('IDSubState', related_name='docs', null=True, blank=True, null_values=(0, -1))
-# prev_sub_state = BrokenForeignKey('IDSubState', related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
-# returning_item = models.IntegerField(null=True, blank=True)
-# telechat_date = models.DateField(null=True, blank=True)
-# via_rfc_editor = models.IntegerField(null=True, blank=True)
-# state_change_notice_to = models.CharField(blank=True, max_length=255)
-# dnp = models.IntegerField(null=True, blank=True)
-# dnp_date = models.DateField(null=True, blank=True)
-# noproblem = models.IntegerField(null=True, blank=True)
-# resurrect_requested_by = BrokenForeignKey('IESGLogin', db_column='resurrect_requested_by', related_name='docsresurrected', null=True, blank=True)
-# approved_in_minute = models.IntegerField(null=True, blank=True)
-#class CheckListBallotInfo(models.Model):
-# ballot = models.AutoField(primary_key=True, db_column='ballot_id')
-# active = models.BooleanField()
-# an_sent = models.BooleanField()
-# an_sent_date = models.DateField(null=True, blank=True)
-# an_sent_by = models.ForeignKey('IESGLogin', db_column='an_sent_by', related_name='ansent', null=True)
-# defer = models.BooleanField(blank=True)
-# defer_by = models.ForeignKey('IESGLogin', db_column='defer_by', related_name='deferred', null=True)
-# defer_date = models.DateField(null=True, blank=True)
-# approval_text = models.TextField(blank=True)
-# last_call_text = models.TextField(blank=True)
-# ballot_writeup = models.TextField(blank=True)
-# ballot_issued = models.IntegerField(null=True, blank=True)
+class RfcIndex(models.Model):
+# rfc_number = models.IntegerField(primary_key=True)
+ title = models.CharField(max_length=250)
+ authors = models.CharField(max_length=250)
+ rfc_published_date = models.DateField()
+ current_status = models.CharField(max_length=50,null=True)
+# updates = models.CharField(max_length=200,blank=True,null=True)
+# updated_by = models.CharField(max_length=200,blank=True,null=True)
+# obsoletes = models.CharField(max_length=200,blank=True,null=True)
+# obsoleted_by = models.CharField(max_length=200,blank=True,null=True)
+# also = models.CharField(max_length=50,blank=True,null=True)
+ draft = models.CharField(max_length=200,null=True)
+ has_errata = models.BooleanField()
+ stream = models.CharField(max_length=15,blank=True,null=True)
+ wg = models.CharField(max_length=15,blank=True,null=True)
+ file_formats = models.CharField(max_length=20,blank=True,null=True)
diff --git a/redesign/proxy_utils.py b/redesign/proxy_utils.py
index 9c4f7963e..a42bd3f34 100644
--- a/redesign/proxy_utils.py
+++ b/redesign/proxy_utils.py
@@ -4,7 +4,17 @@ from django.db.models.query import QuerySet
class TranslatingQuerySet(QuerySet):
def translated_kwargs(self, kwargs):
trans = self.translated_attrs
- return dict((trans[k], v) if k in trans else (k, v) for k, v in kwargs.iteritems())
+ res = dict()
+ for k, v in kwargs.iteritems():
+ if k in trans:
+ t = trans[k]
+ if callable(t):
+ t, v = t(v)
+
+ res[t] = v
+ else:
+ res[k] = v
+ return res
# overridden methods
def _clone(self, *args, **kwargs):
@@ -109,6 +119,12 @@ class TranslatingQuerySet(QuerySet):
return super(self.__class__, self)._update(values, **kwargs)
class TranslatingManager(Manager):
+ """Translates keyword arguments for the ORM, for use in proxy
+ wrapping, e.g. given trans={'foo': 'bar'} it will transform a
+ lookup of the field foo to a lookup on the field bar. The right
+ hand side can either be a string or a function which is called
+ with the right-hand side to transform it."""
+
def __init__(self, trans):
super(self.__class__, self).__init__()
self.translated_attrs = trans
From 754469cd3dc5f5fa076d961348bbdf97d957995e Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 6 Jan 2011 20:14:15 +0000
Subject: [PATCH 12/75] Import remaining RfcIndex attributes, import IDAuthor,
bug fixes - Legacy-Id: 2738
---
ietf/idrfc/idrfc_wrapper.py | 22 +-
ietf/idrfc/views_doc.py | 2 +-
ietf/idtracker/admin.py | 21 +-
ietf/idtracker/models.py | 24 +-
redesign/doc/models.py | 41 +-
redesign/doc/proxy.py | 77 ++-
redesign/import-document-state.py | 851 +++++++++++++++++-------------
redesign/import-roles.py | 31 +-
redesign/name/admin.py | 4 +-
redesign/name/models.py | 6 +-
redesign/proxy_utils.py | 3 +-
redesign/unaccent.py | 144 +++++
12 files changed, 772 insertions(+), 454 deletions(-)
create mode 100644 redesign/unaccent.py
diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py
index 6b9e46359..489ed1b88 100644
--- a/ietf/idrfc/idrfc_wrapper.py
+++ b/ietf/idrfc/idrfc_wrapper.py
@@ -87,7 +87,7 @@ class IdWrapper:
def __init__(self, draft):
self.id = self
- if isinstance(draft, IDInternal):
+ if isinstance(draft, IDInternal) and not settings.USE_DB_REDESIGN_PROXY_CLASSES:
self._idinternal = draft
self._draft = self._idinternal.draft
else:
@@ -261,10 +261,16 @@ class RfcWrapper:
self.rfc = self
if not self._idinternal:
- try:
- self._idinternal = IDInternal.objects.get(rfc_flag=1, draft=self._rfcindex.rfc_number)
- except IDInternal.DoesNotExist:
- pass
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ pub = rfcindex.latest_event(type="published_rfc")
+ started = rfcindex.latest_event(type="started_iesg_process")
+ if pub and started and pub.time < started.time:
+ self._idinternal = rfcindex
+ else:
+ try:
+ self._idinternal = IDInternal.objects.get(rfc_flag=1, draft=self._rfcindex.rfc_number)
+ except IDInternal.DoesNotExist:
+ pass
if self._idinternal:
self.ietf_process = IetfProcessData(self._idinternal)
@@ -275,7 +281,10 @@ class RfcWrapper:
self.maturity_level = self._rfcindex.current_status
if not self.maturity_level:
self.maturity_level = "Unknown"
-
+
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES and rfcindex.filename.startswith('rfc'):
+ return # we've already done the lookup while importing so skip the rest
+
ids = InternetDraft.objects.filter(rfc_number=self.rfc_number)
if len(ids) >= 1:
self.draft_name = ids[0].filename
@@ -658,7 +667,6 @@ class BallotWrapper:
position="No Record",
)
positions.append(d)
-
self._positions = positions
def old_init(self):
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index fad15926f..4e9c32439 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -199,7 +199,7 @@ def _get_history(doc, versions):
results.insert(0, v)
if 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 doc.is_rfc_wrapper:
+ if not settings.USE_DB_REDESIGN_PROXY_CLASSES and doc.is_rfc_wrapper:
if doc.draft_name:
text = 'RFC Published (see %s for earlier history)' % (doc.draft_name,doc.draft_name)
else:
diff --git a/ietf/idtracker/admin.py b/ietf/idtracker/admin.py
index 340928383..5c90f0261 100644
--- a/ietf/idtracker/admin.py
+++ b/ietf/idtracker/admin.py
@@ -8,7 +8,6 @@ if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
list_display=('acronym', 'name')
admin.site.register(Acronym, AcronymAdmin)
-if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
class AreaAdmin(admin.ModelAdmin):
list_display=('area_acronym', 'status')
admin.site.register(Area, AreaAdmin)
@@ -25,9 +24,10 @@ class AreaWGURLAdmin(admin.ModelAdmin):
pass
admin.site.register(AreaWGURL, AreaWGURLAdmin)
-class BallotInfoAdmin(admin.ModelAdmin):
- pass
-admin.site.register(BallotInfo, BallotInfoAdmin)
+if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ class BallotInfoAdmin(admin.ModelAdmin):
+ pass
+ admin.site.register(BallotInfo, BallotInfoAdmin)
class ChairsHistoryAdmin(admin.ModelAdmin):
list_display=('person', 'chair_type', 'start_year', 'end_year')
@@ -52,12 +52,13 @@ class IDIntendedStatusAdmin(admin.ModelAdmin):
pass
admin.site.register(IDIntendedStatus, IDIntendedStatusAdmin)
-class IDInternalAdmin(admin.ModelAdmin):
- ordering=['draft']
- list_display=['draft', 'token_email', 'note']
- search_fields=['draft__filename']
- raw_id_fields=['draft','ballot']
-admin.site.register(IDInternal, IDInternalAdmin)
+if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ class IDInternalAdmin(admin.ModelAdmin):
+ ordering=['draft']
+ list_display=['draft', 'token_email', 'note']
+ search_fields=['draft__filename']
+ raw_id_fields=['draft','ballot']
+ admin.site.register(IDInternal, IDInternalAdmin)
class IDNextStateAdmin(admin.ModelAdmin):
pass
diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py
index 7a01878f1..11136a801 100644
--- a/ietf/idtracker/models.py
+++ b/ietf/idtracker/models.py
@@ -264,19 +264,12 @@ class PersonOrOrgInfo(models.Model):
if self.first_name == '' and self.last_name == '':
return u"(Person #%s)" % self.person_or_org_tag
return u"%s %s" % ( self.first_name or u"", self.last_name or u"")
- def email(self, priority=1, type=None):
- name = str(self)
+ def email(self, priority=1):
+ name = unicode(self)
email = ''
- types = type and [ type ] or [ "INET", "Prim", None ]
- for type in types:
- try:
- if type:
- email = self.emailaddress_set.get(priority=priority, type=type).address
- else:
- email = self.emailaddress_set.get(priority=priority).address
- break
- except (EmailAddress.DoesNotExist, AssertionError):
- pass
+ addresses = self.emailaddress_set.filter(address__contains="@").order_by('priority')[:1]
+ if addresses:
+ email = addresses[0].address.replace('<', '').replace('>', '')
return (name, email)
# Added by Sunny Lee to display person's affiliation - 5/26/2007
def affiliation(self, priority=1):
@@ -1088,7 +1081,12 @@ class DocumentWrapper(object):
self.document = document
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
- from redesign.doc.proxy import InternetDraft
+ InternetDraftOld = InternetDraft
+ IDInternalOld = IDInternal
+ BallotInfoOld = BallotInfo
+ AreaOld = Area
+ AcronymOld = Acronym
+ from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo
from redesign.group.proxy import Area
from redesign.group.proxy import Acronym
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index 3330628a1..0664a5969 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -27,9 +27,8 @@ class DocumentInfo(models.Model):
abstract = models.TextField()
rev = models.CharField(max_length=16)
pages = models.IntegerField(blank=True, null=True)
- intended_std_level = models.ForeignKey(IntendedStatusName, blank=True, null=True)
- std_level = models.ForeignKey(StdStatusName, blank=True, null=True)
- authors = models.ManyToManyField(Email, blank=True, null=True)
+ intended_std_level = models.ForeignKey(IntendedStdLevelName, blank=True, null=True)
+ std_level = models.ForeignKey(StdLevelName, blank=True, null=True)
ad = models.ForeignKey(Email, related_name='ad_%(class)s_set', blank=True, null=True)
shepherd = models.ForeignKey(Email, related_name='shepherd_%(class)s_set', blank=True, null=True)
notify = models.CharField(max_length=255, blank=True)
@@ -42,9 +41,10 @@ class DocumentInfo(models.Model):
def author_list(self):
return ", ".join(email.address for email in self.authors.all())
def latest_event(self, *args, **filter_args):
- """Get latest event with specific requirements, e.g.
- d.latest_event(type="xyz") returns an Event while
- d.latest_event(Status, type="xyz") returns a Status event."""
+ """Get latest event of optional Python type and with filter
+ arguments, e.g. d.latest_event(type="xyz") returns an Event
+ while d.latest_event(Status, type="xyz") returns a Status
+ event."""
model = args[0] if args else Event
e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time')[:1]
return e[0] if e else None
@@ -56,9 +56,21 @@ class RelatedDocument(models.Model):
def __unicode__(self):
return u"%s %s %s" % (self.document.name, self.relationship.name.lower(), self.doc_alias.name)
+class DocumentAuthor(models.Model):
+ document = models.ForeignKey('Document')
+ author = models.ForeignKey(Email)
+ order = models.IntegerField()
+
+ def __unicode__(self):
+ return u"%s %s (%s)" % (self.document.name, self.email.get_name(), self.order)
+
+ class Meta:
+ ordering = ["document", "order"]
+
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")
+ authors = models.ManyToManyField(Email, through=DocumentAuthor, blank=True)
def __unicode__(self):
return self.name
def values(self):
@@ -89,7 +101,7 @@ class Document(DocumentInfo):
snap = DocHistory(**fields)
snap.save()
for m in many2many:
- # FIXME: check that this works with related
+ # FIXME: check that this works with related/authors
#print "m2m:", m, many2many[m]
rel = getattr(snap, m)
for item in many2many[m]:
@@ -107,11 +119,23 @@ class RelatedDocHistory(models.Model):
doc_alias = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set") # target
relationship = models.ForeignKey(DocRelationshipName)
def __unicode__(self):
- return u"%s %s %s" % (self.document.name, self.relationship.name.lower(), self.doc_alias.name)
+ return u"%s %s %s" % (self.document.doc.name, self.relationship.name.lower(), self.doc_alias.name)
+class DocHistoryAuthor(models.Model):
+ document = models.ForeignKey('DocHistory')
+ author = models.ForeignKey(Email)
+ order = models.IntegerField()
+
+ def __unicode__(self):
+ return u"%s %s (%s)" % (self.document.doc.name, self.email.get_name(), self.order)
+
+ class Meta:
+ ordering = ["document", "order"]
+
class DocHistory(DocumentInfo):
doc = models.ForeignKey(Document) # ID of the Document this relates to
related = models.ManyToManyField('DocAlias', through=RelatedDocHistory, blank=True)
+ authors = models.ManyToManyField(Email, through=DocHistoryAuthor, blank=True)
def __unicode__(self):
return unicode(self.doc.name)
@@ -169,6 +193,7 @@ EVENT_TYPES = [
("published_rfc", "Published RFC"),
# IESG events
+ ("started_iesg_process", "Started IESG process on document"),
("sent_ballot_announcement", "Sent ballot announcement"),
("changed_ballot_position", "Changed ballot position"),
("changed_ballot_approval_text", "Changed ballot approval text"),
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index 8039c27ac..8c7668849 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -44,17 +44,19 @@ class InternetDraft(Document):
def revision_date(self):
e = self.latest_event(type="new_revision")
return e.time.date() if e else None
+ # helper function
+ def get_file_type_matches_from(self, glob_path):
+ possible_types = [".txt", ".pdf", ".xml", ".ps"]
+ res = []
+ for m in glob.glob(glob_path):
+ for t in possible_types:
+ if m.endswith(t):
+ res.append(t)
+ return ",".join(res)
#file_type = models.CharField(max_length=20)
@property
def file_type(self):
- matches = glob.glob(os.path.join(settings.INTERNET_DRAFT_PATH, self.name + "*.*"))
- possible_types = [".txt", ".pdf", ".xml", ".ps"]
- res = set()
- for m in matches:
- for t in possible_types:
- if m.endswith(t):
- res.add(t)
- return ",".join(res) or ".txt"
+ return self.get_file_type_matches_from(os.path.join(settings.INTERNET_DRAFT_PATH, self.name + "-" + self.rev + ".*")) or ".txt"
#txt_page_count = models.IntegerField()
@property
def txt_page_count(self):
@@ -160,8 +162,15 @@ class InternetDraft(Document):
#idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
@property
def idinternal(self):
+ print self.iesg_state
return self if self.iesg_state else None
+ # reverse relationship
+ @property
+ def authors(self):
+ from person.models import Person
+ return IDAuthor.objects.filter(document=self)
+
# methods from InternetDraft
def displayname(self):
return self.name
@@ -554,7 +563,7 @@ class InternetDraft(Document):
#rfc_number = models.IntegerField(primary_key=True) # already taken care of
#title = models.CharField(max_length=250) # same name
- #authors = models.CharField(max_length=250) FIXME
+ #authors = models.CharField(max_length=250) # exists already
#rfc_published_date = models.DateField()
@property
def rfc_published_date(self):
@@ -564,7 +573,7 @@ class InternetDraft(Document):
#current_status = models.CharField(max_length=50,null=True)
@property
def current_status(self):
- return self.std_level
+ return self.std_level.name
#updates = models.CharField(max_length=200,blank=True,null=True)
@property
@@ -594,26 +603,54 @@ class InternetDraft(Document):
models.Q(name__startswith="bcp"))
return aliases[0].name.upper() if aliases else None
- #draft = models.CharField(max_length=200,null=True)
- @property
- def draft(self):
- if not self.name.startswith("rfc"):
- return self.name
- else:
- return None
+ #draft = models.CharField(max_length=200,null=True) # have to ignore this, it's already implemented
- #has_errata = models.BooleanField() FIXME
+ #has_errata = models.BooleanField()
+ @property
+ def has_errata(self):
+ return bool(self.tags.filter(slug="errata"))
+
#stream = models.CharField(max_length=15,blank=True,null=True)
@property
def stream(self):
return super(InternetDraft, self).stream.name
- #wg = models.CharField(max_length=15,blank=True,null=True) FIXME
+
+ #wg = models.CharField(max_length=15,blank=True,null=True)
+ @property
+ def wg(self):
+ return self.group.acronym
+
#file_formats = models.CharField(max_length=20,blank=True,null=True)
@property
def file_formats(self):
- return self.file_type.replace(".", "").replace("txt", "ascii")
+ return self.get_file_type_matches_from(os.path.join(settings.RFC_PATH, "rfc" + str(self.rfc_number) + ".*")).replace(".", "").replace("txt", "ascii")
class Meta:
proxy = True
+IDInternal = InternetDraft
+BallotInfo = InternetDraft
RfcIndex = InternetDraft
+
+
+class IDAuthor(DocumentAuthor):
+ #document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name='authors') # same name
+ #person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
+ @property
+ def person(self):
+ return self.author.person
+
+ #author_order = models.IntegerField()
+ @property
+ def author_order(self):
+ return self.order
+
+ def email(self):
+ return self.author.address
+
+ def final_author_order(self):
+ return self.order
+
+ class Meta:
+ proxy = True
+
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 00cacf06c..69e14c2e8 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -14,7 +14,7 @@ management.setup_environ(settings)
from redesign.doc.models import *
from redesign.group.models import *
from redesign.name.models import *
-from ietf.idtracker.models import InternetDraft, IESGLogin, DocumentComment, PersonOrOrgInfo, RfcObsolete
+from ietf.idtracker.models import InternetDraft, IDInternal, IESGLogin, DocumentComment, PersonOrOrgInfo, Rfc, IESGComment, IESGDiscuss, BallotInfo
from ietf.idrfc.models import RfcIndex, DraftVersions
import sys
@@ -26,15 +26,14 @@ if len(sys.argv) > 1:
# assumptions:
# - groups have been imported
# - IESG login emails/roles have been imported
-
-# FIXME: what about RFCs
+# - IDAuthor emails/persons have been imported
# Regarding history, we currently don't try to create DocumentHistory
# objects, we just import the comments as events.
# imports InternetDraft, IDInternal, BallotInfo, Position,
-# IESGComment, IESGDiscuss, DocumentComment, RfcObsolete,
-# idrfc.RfcIndex, idrfc.DraftVersions
+# IESGComment, IESGDiscuss, DocumentComment, IDAuthor, idrfc.RfcIndex,
+# idrfc.DraftVersions
def name(name_class, slug, name, desc=""):
# create if it doesn't exist, set name and desc
@@ -50,25 +49,47 @@ def alias_doc(name, doc):
return alias
type_draft = name(DocTypeName, "draft", "Draft")
-stream_ietf = name(DocStreamName, "ietf", "IETF")
+
+stream_mapping = {
+ "Legacy": name(DocStreamName, "legacy", "Legacy"),
+ "IETF": name(DocStreamName, "ietf", "IETF"),
+ "INDEPENDENT": name(DocStreamName, "indie", "Independent Submission"),
+ "IAB": name(DocStreamName, "iab", "IAB"),
+ "IRTF": name(DocStreamName, "irtf", "IRTF"),
+ }
relationship_replaces = name(DocRelationshipName, "replaces", "Replaces")
relationship_updates = name(DocRelationshipName, "updates", "Updates")
relationship_obsoletes = name(DocRelationshipName, "obs", "Obsoletes")
-intended_status_mapping = {
- "BCP": name(IntendedStatusName, "bcp", "Best Current Practice"),
- "Draft Standard": name(IntendedStatusName, "ds", name="Draft Standard"),
- "Experimental": name(IntendedStatusName, "exp", name="Experimental"),
- "Historic": name(IntendedStatusName, "hist", name="Historic"),
- "Informational": name(IntendedStatusName, "inf", name="Informational"),
- "Proposed Standard": name(IntendedStatusName, "ps", name="Proposed Standard"),
- "Standard": name(IntendedStatusName, "std", name="Standard"),
+intended_std_level_mapping = {
+ "BCP": name(IntendedStdLevelName, "bcp", "Best Current Practice"),
+ "Draft Standard": name(IntendedStdLevelName, "ds", name="Draft Standard"),
+ "Experimental": name(IntendedStdLevelName, "exp", name="Experimental"),
+ "Historic": name(IntendedStdLevelName, "hist", name="Historic"),
+ "Informational": name(IntendedStdLevelName, "inf", name="Informational"),
+ "Proposed Standard": name(IntendedStdLevelName, "ps", name="Proposed Standard"),
+ "Standard": name(IntendedStdLevelName, "std", name="Standard"),
"None": None,
- "Request": None, # FIXME: correct? from idrfc_wrapper.py
+ "Request": None,
}
-status_mapping = {
+# add aliases from rfc_intend_status
+intended_std_level_mapping["Proposed"] = intended_std_level_mapping["Proposed Standard"]
+intended_std_level_mapping["Draft"] = intended_std_level_mapping["Draft Standard"]
+
+std_level_mapping = {
+ "Standard": name(StdLevelName, "std", "Standard"),
+ "Draft Standard": name(StdLevelName, "ds", "Draft Standard"),
+ "Proposed Standard": name(StdLevelName, "ps", "Proposed Standard"),
+ "Informational": name(StdLevelName, "inf", "Informational"),
+ "Experimental": name(StdLevelName, "exp", "Experimental"),
+ "Best Current Practice": name(StdLevelName, "bcp", "Best Current Practice"),
+ "Historic": name(StdLevelName, "hist", "Historic"),
+ "Unknown": name(StdLevelName, "unkn", "Unknown"),
+ }
+
+state_mapping = {
'Active': name(DocStateName, "active", "Active"),
'Expired': name(DocStateName, "expired", "Expired"),
'RFC': name(DocStateName, "rfc", "RFC"),
@@ -125,9 +146,9 @@ tag_review_by_rfc_editor = name(DocInfoTagName, 'rfc-rev', "Review by RFC Editor
tag_via_rfc_editor = name(DocInfoTagName, 'via-rfc', "Via RFC Editor")
tag_expired_tombstone = name(DocInfoTagName, 'exp-tomb', "Expired tombstone")
tag_approved_in_minute = name(DocInfoTagName, 'app-min', "Approved in minute")
+tag_has_errata = name(DocInfoTagName, 'errata', "Has errata")
-# helpers for events
-
+# helpers
def save_event(doc, event, comment):
event.time = comment.datetime()
event.by = iesg_login_to_email(comment.created_by)
@@ -136,6 +157,12 @@ def save_event(doc, event, comment):
event.desc = comment.comment_text # FIXME: consider unquoting here
event.save()
+def sync_tag(d, include, tag):
+ if include:
+ d.tags.add(tag)
+ else:
+ d.tags.remove(tag)
+
buggy_iesg_logins_cache = {}
# make sure system email exists
@@ -199,6 +226,354 @@ re_intended_status_changed = re.compile(r"Intended [sS]tatus has been changed to
re_state_change_notice = re.compile(r"State Change Notice email list (have been change|has been changed) ()?")
re_area_acronym_changed = re.compile(r"Area acronymn? has been changed to \w+ from \w+()?")
+
+def import_from_idinternal(d, idinternal):
+ d.time = idinternal.event_date
+ d.iesg_state = iesg_state_mapping[idinternal.cur_state.state]
+ d.ad = iesg_login_to_email(idinternal.job_owner)
+ d.notify = idinternal.state_change_notice_to or ""
+ d.note = idinternal.note or ""
+ d.save()
+
+ # extract events
+ last_note_change_text = ""
+
+ for c in idinternal.documentcomment_set.order_by('date', 'time', 'id'):
+ handled = False
+
+ # telechat agenda schedulings
+ match = re_telechat_agenda.search(c.comment_text) or re_telechat_changed.search(c.comment_text)
+ if match:
+ e = Telechat()
+ e.type = "scheduled_for_telechat"
+ e.telechat_date = date_in_match(match) if "Placed on" in c.comment_text else None
+ # can't extract this from history so we just take the latest value
+ e.returning_item = bool(idinternal.returning_item)
+ save_event(d, e, c)
+ handled = True
+
+ # ballot issued
+ match = re_ballot_issued.search(c.comment_text)
+ if match:
+ e = Text()
+ e.type = "sent_ballot_announcement"
+ save_event(d, e, c)
+
+ # when you issue a ballot, you also vote yes; add that vote
+ e = BallotPosition()
+ e.type = "changed_ballot_position"
+ e.ad = iesg_login_to_email(c.created_by)
+ e.desc = "[Ballot Position Update] New position, Yes, has been recorded by %s" % e.ad.get_name()
+ last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
+ e.pos = ballot_position_mapping["Yes"]
+ e.discuss = last_pos.ballotposition.discuss if last_pos else ""
+ e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
+ e.comment = last_pos.ballotposition.comment if last_pos else ""
+ e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
+ save_event(d, e, c)
+ handled = True
+
+ # ballot positions
+ match = re_ballot_position.search(c.comment_text)
+ if match:
+ position = match.group('position') or match.group('position2')
+ ad_name = match.group('for') or match.group('for2') or match.group('by') # some of the old positions don't specify who it's for, in that case assume it's "by", the person who entered the position
+ ad_first, ad_last = ad_name.split(' ')
+
+ e = BallotPosition()
+ e.type = "changed_ballot_position"
+ e.ad = iesg_login_to_email(IESGLogin.objects.get(first_name=ad_first, last_name=ad_last))
+ last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
+ e.pos = ballot_position_mapping[position]
+ e.discuss = last_pos.ballotposition.discuss if last_pos else ""
+ e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
+ e.comment = last_pos.ballotposition.comment if last_pos else ""
+ e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
+ save_event(d, e, c)
+ handled = True
+
+ # ballot discusses/comments
+ if c.ballot in (DocumentComment.BALLOT_DISCUSS, DocumentComment.BALLOT_COMMENT):
+ e = BallotPosition()
+ e.type = "changed_ballot_position"
+ e.ad = iesg_login_to_email(c.created_by)
+ last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
+ e.pos = last_pos.ballotposition.pos if last_pos else ballot_position_mapping[None]
+ if c.ballot == DocumentComment.BALLOT_DISCUSS:
+ e.discuss = c.comment_text
+ e.discuss_time = c.datetime()
+ e.comment = last_pos.ballotposition.comment if last_pos else ""
+ e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
+ # put header into description
+ c.comment_text = "[Ballot discuss]\n" + c.comment_text
+ else:
+ e.discuss = last_pos.ballotposition.discuss if last_pos else ""
+ e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
+ e.comment = c.comment_text
+ e.comment_time = c.datetime()
+ # put header into description
+ c.comment_text = "[Ballot comment]\n" + c.comment_text
+ save_event(d, e, c)
+ handled = True
+
+ # last call requested
+ match = re_last_call_requested.search(c.comment_text)
+ if match:
+ e = Event(type="requested_last_call")
+ save_event(d, e, c)
+ handled = True
+
+ # state changes
+ match = re_state_changed.search(c.comment_text)
+ if match:
+ e = Event(type="changed_document")
+ save_event(d, e, c)
+ handled = True
+
+ # note changed
+ match = re_note_changed.search(c.comment_text)
+ if match:
+ # watch out for duplicates of which the old data's got many
+ if c.comment_text != last_note_change_text:
+ last_note_change_text = c.comment_text
+ e = Event(type="changed_document")
+ save_event(d, e, c)
+ handled = True
+
+ # draft added
+ match = re_draft_added.search(c.comment_text)
+ if match:
+ e = Event(type="started_iesg_process")
+ save_event(d, e, c)
+ handled = True
+
+ # new version
+ if c.comment_text == "New version available":
+ e = NewRevision(type="new_revision", rev=c.version)
+ save_event(d, e, c)
+ handled = True
+
+ # resurrect requested
+ match = re_resurrection_requested.search(c.comment_text)
+ if match:
+ e = Event(type="requested_resurrect")
+ save_event(d, e, c)
+ handled = True
+
+ # completed resurrect
+ match = re_completed_resurrect.search(c.comment_text)
+ if match:
+ e = Event(type="completed_resurrect")
+ save_event(d, e, c)
+ handled = True
+
+ # document expiration
+ if c.comment_text == "Document is expired by system":
+ e = Event(type="expired_document")
+ save_event(d, e, c)
+ handled = True
+
+ # approved document
+ match = re_document_approved.search(c.comment_text)
+ if match:
+ e = Event(type="iesg_approved")
+ save_event(d, e, c)
+ handled = True
+
+ # disapproved document
+ match = re_document_disapproved.search(c.comment_text)
+ if match:
+ e = Event(type="iesg_disapproved")
+ save_event(d, e, c)
+ handled = True
+
+
+ # some changes can be bundled - this is not entirely
+ # convenient, especially since it makes it hard to give
+ # each a type, so unbundle them
+ if not handled:
+ unhandled_lines = []
+ for line in c.comment_text.split(" "):
+ # status date changed
+ match = re_status_date_changed.search(line)
+ if match:
+ e = Status(type="changed_status_date", date=date_in_match(match))
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # AD/job owner changed
+ match = re_responsible_ad_changed.search(line)
+ if match:
+ e = Event(type="changed_document")
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # intended standard level changed
+ match = re_intended_status_changed.search(line)
+ if match:
+ e = Event(type="changed_document")
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # state change notice
+ match = re_state_change_notice.search(line)
+ if match:
+ e = Event(type="changed_document")
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # area acronym
+ match = re_area_acronym_changed.search(line)
+ if match:
+ e = Event(type="changed_document")
+ e.desc = line
+ save_event(d, e, c)
+ handled = True
+
+ # multiline change bundles end with a single "by xyz" that we skip
+ if not handled and not line.startswith("by "):
+ unhandled_lines.append(line)
+
+ if handled:
+ c.comment_text = " ".join(unhandled_lines)
+
+ if c.comment_text:
+ print "COULDN'T HANDLE multi-line comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
+
+ # all others are added as comments
+ if not handled:
+ e = Event(type="added_comment")
+ save_event(d, e, c)
+
+ # stop typical comments from being output
+ typical_comments = [
+ "Document Shepherd Write-up for %s" % d.name,
+ "Who is the Document Shepherd for this document",
+ "We understand that this document doesn't require any IANA actions",
+ "IANA questions",
+ "IANA has questions",
+ "IANA comments",
+ "IANA Comments",
+ "IANA Evaluation Comments",
+ "Published as RFC",
+ ]
+ for t in typical_comments:
+ if t in c.comment_text:
+ handled = True
+ break
+
+ if not handled:
+ print "couldn't handle comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
+
+ made_up_date = d.latest_event().time + datetime.timedelta(seconds=1)
+
+ e = d.latest_event(Status, type="changed_status_date")
+ status_date = e.date if e else None
+ if idinternal.status_date != status_date:
+ e = Status(type="changed_status_date", date=idinternal.status_date)
+ e.time = made_up_date
+ e.by = system_email
+ e.doc = d
+ e.desc = "Status date has been changed to %s from %s" % (idinternal.status_date, status_date)
+ e.save()
+
+ e = d.latest_event(Telechat, type="scheduled_for_telechat")
+ telechat_date = e.telechat_date if e else None
+ if not idinternal.agenda:
+ idinternal.telechat_date = None # normalize
+
+ if telechat_date != idinternal.telechat_date:
+ e = Telechat(type="scheduled_for_telechat",
+ telechat_date=idinternal.telechat_date,
+ returning_item=bool(idinternal.returning_item))
+ # a common case is that it has been removed from the
+ # agenda automatically by a script without a notice in the
+ # comments, in that case the time is simply the day after
+ # the telechat
+ e.time = telechat_date + datetime.timedelta(days=1) if telechat_date and not idinternal.telechat_date else made_up_date
+ e.by = system_email
+ args = ("Placed on", idinternal.telechat_date) if idinternal.telechat_date else ("Removed from", telechat_date)
+ e.doc = d
+ e.desc = "%s agenda for telechat - %s by system" % args
+ e.save()
+
+ try:
+ # sad fact: some ballots haven't been generated yet
+ ballot = idinternal.ballot
+ except BallotInfo.DoesNotExist:
+ ballot = None
+
+ if ballot:
+ # make sure the comments and discusses are updated
+ positions = list(BallotPosition.objects.filter(doc=d).order_by("-time"))
+ for c in IESGComment.objects.filter(ballot=idinternal.ballot):
+ ad = iesg_login_to_email(c.ad)
+ for p in positions:
+ if p.ad == ad:
+ if p.comment != c.text:
+ p.comment = c.text
+ p.comment_time = c.date if p.time.date() != c.date else p.time
+ p.save()
+ break
+
+ for c in IESGDiscuss.objects.filter(ballot=idinternal.ballot):
+ ad = iesg_login_to_email(c.ad)
+ for p in positions:
+ if p.ad == ad:
+ if p.discuss != c.text:
+ p.discuss = c.text
+ p.discuss_time = c.date if p.time.date() != c.date else p.time
+ p.save()
+ break
+
+ # if any of these events have happened, they're closer to
+ # the real time
+ e = d.event_set.filter(type__in=("requested_last_call", "sent_last_call", "sent_ballot_announcement", "iesg_approved", "iesg_disapproved")).order_by('time')[:1]
+ if e:
+ text_date = e[0].time - datetime.timedelta(seconds=1)
+ else:
+ text_date = made_up_date
+
+ if idinternal.ballot.approval_text:
+ e, _ = Text.objects.get_or_create(type="changed_ballot_approval_text", doc=d)
+ e.content = idinternal.ballot.approval_text
+ e.time = text_date
+ e.by = system_email
+ e.desc = "Ballot approval text was added"
+ e.save()
+
+ if idinternal.ballot.last_call_text:
+ e, _ = Text.objects.get_or_create(type="changed_last_call_text", doc=d)
+ e.content = idinternal.ballot.last_call_text
+ e.time = text_date
+ e.by = system_email
+ e.desc = "Last call text was added"
+ e.save()
+
+ if idinternal.ballot.ballot_writeup:
+ e, _ = Text.objects.get_or_create(type="changed_ballot_writeup_text", doc=d)
+ e.content = idinternal.ballot.ballot_writeup
+ e.time = text_date
+ e.by = system_email
+ e.desc = "Ballot writeup text was added"
+ e.save()
+
+ # fix tags
+ sync_tag(d, idinternal.via_rfc_editor, tag_via_rfc_editor)
+
+ n = idinternal.cur_sub_state and idinternal.cur_sub_state.sub_state
+ for k, v in substate_mapping.iteritems():
+ sync_tag(d, k == n, v)
+ # currently we ignore prev_sub_state
+
+ sync_tag(d, idinternal.approved_in_minute, tag_approved_in_minute)
+
+
+
all_drafts = InternetDraft.objects.all().select_related()
if document_name_to_import:
all_drafts = all_drafts.filter(filename=document_name_to_import)
@@ -209,277 +584,62 @@ for o in all_drafts:
d = Document.objects.get(name=o.filename)
except Document.DoesNotExist:
d = Document(name=o.filename)
-
- d.time = o.idinternal.event_date if o.idinternal else o.revision_date
+
+ d.time = o.revision_date
d.type = type_draft
d.title = o.title
- d.state = status_mapping[o.status.status]
+ d.state = state_mapping[o.status.status]
d.group = Group.objects.get(acronym=o.group.acronym)
- d.stream = stream_ietf
+ if o.filename.startswith("draft-iab-"):
+ d.stream = stream_mapping["IAB"]
+ elif o.filename.startswith("draft-irtf-"):
+ d.stream = stream_mapping["IRTF"]
+ elif o.idinternal and o.idinternal.via_rfc_editor:
+ d.stream = stream_mapping["INDEPENDENT"] # FIXME: correct?
+ else:
+ d.stream = stream_mapping["IETF"] # FIXME: correct?
d.wg_state = None
- d.iesg_state = iesg_state_mapping[o.idinternal.cur_state.state if o.idinternal else None]
- # we currently ignore the previous IESG state prev_state
+ d.iesg_state = iesg_state_mapping[None]
d.iana_state = None
-# d.rfc_state = # FIXME
+ d.rfc_state = None
d.rev = o.revision
d.abstract = o.abstract
d.pages = o.txt_page_count
- d.intended_std_level = intended_status_mapping[o.intended_status.intended_status]
-# d.std_level = # FIXME
-# d.authors =
-# d.related = # FIXME
- d.ad = iesg_login_to_email(o.idinternal.job_owner) if o.idinternal else None
+ d.intended_std_level = intended_std_level_mapping[o.intended_status.intended_status]
+ d.ad = None
d.shepherd = None
- d.notify = o.idinternal.state_change_notice_to or "" if o.idinternal else ""
+ d.notify = ""
d.external_url = ""
- d.note = o.idinternal.note or "" if o.idinternal else ""
- d.internal_comments = o.comments or "" # FIXME: maybe put these somewhere else
+ d.note = ""
+ d.internal_comments = o.comments or ""
d.save()
# make sure our alias is updated
- alias_doc(d.name, d)
-
- # clear already imported events
+ d_alias = alias_doc(d.name, d)
+
+ d.authors.clear()
+ for i, a in enumerate(o.authors.all().select_related("person").order_by('author_order', 'person')):
+ try:
+ e = Email.objects.get(address=a.person.email()[1])
+ # renumber since old numbers may be a bit borked
+ DocumentAuthor.objects.create(document=d, author=e, order=i)
+ except Email.DoesNotExist:
+ print "SKIPPED author", unicode(a.person).encode('utf-8')
+
+ # clear any already imported events as the event importer isn't
+ # clever enough to do a diff
d.event_set.all().delete()
if o.idinternal:
- last_note_change_text = ""
-
- # extract events
- for c in o.idinternal.documentcomment_set.order_by('date', 'time', 'id'):
- handled = False
-
- # telechat agenda schedulings
- match = re_telechat_agenda.search(c.comment_text) or re_telechat_changed.search(c.comment_text)
- if match:
- e = Telechat()
- e.type = "scheduled_for_telechat"
- e.telechat_date = date_in_match(match) if "Placed on" in c.comment_text else None
- # can't extract this from history so we just take the latest value
- e.returning_item = bool(o.idinternal.returning_item)
- save_event(d, e, c)
- handled = True
-
- # ballot issued
- match = re_ballot_issued.search(c.comment_text)
- if match:
- e = Text()
- e.type = "sent_ballot_announcement"
- save_event(d, e, c)
+ # import attributes and events
+ import_from_idinternal(d, o.idinternal)
- # when you issue a ballot, you also vote yes; add that vote
- e = BallotPosition()
- e.type = "changed_ballot_position"
- e.ad = iesg_login_to_email(c.created_by)
- e.desc = "[Ballot Position Update] New position, Yes, has been recorded by %s" % e.ad.get_name()
- last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
- e.pos = ballot_position_mapping["Yes"]
- e.discuss = last_pos.ballotposition.discuss if last_pos else ""
- e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
- e.comment = last_pos.ballotposition.comment if last_pos else ""
- e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
- save_event(d, e, c)
- handled = True
-
- # ballot positions
- match = re_ballot_position.search(c.comment_text)
- if match:
- position = match.group('position') or match.group('position2')
- ad_name = match.group('for') or match.group('for2') or match.group('by') # some of the old positions don't specify who it's for, in that case assume it's "by", the person who entered the position
- ad_first, ad_last = ad_name.split(' ')
-
- e = BallotPosition()
- e.type = "changed_ballot_position"
- e.ad = iesg_login_to_email(IESGLogin.objects.get(first_name=ad_first, last_name=ad_last))
- last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
- e.pos = ballot_position_mapping[position]
- e.discuss = last_pos.ballotposition.discuss if last_pos else ""
- e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
- e.comment = last_pos.ballotposition.comment if last_pos else ""
- e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
- save_event(d, e, c)
- handled = True
-
- # ballot discusses/comments
- if c.ballot in (DocumentComment.BALLOT_DISCUSS, DocumentComment.BALLOT_COMMENT):
- e = BallotPosition()
- e.type = "changed_ballot_position"
- e.ad = iesg_login_to_email(c.created_by)
- last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
- e.pos = last_pos.ballotposition.pos if last_pos else ballot_position_mapping[None]
- if c.ballot == DocumentComment.BALLOT_DISCUSS:
- e.discuss = c.comment_text
- e.discuss_time = c.datetime()
- e.comment = last_pos.ballotposition.comment if last_pos else ""
- e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
- # put header into description
- c.comment_text = "[Ballot discuss]\n" + c.comment_text
- else:
- e.discuss = last_pos.ballotposition.discuss if last_pos else ""
- e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
- e.comment = c.comment_text
- e.comment_time = c.datetime()
- # put header into description
- c.comment_text = "[Ballot comment]\n" + c.comment_text
- save_event(d, e, c)
- handled = True
-
- # last call requested
- match = re_last_call_requested.search(c.comment_text)
- if match:
- e = Event(type="requested_last_call")
- save_event(d, e, c)
- handled = True
-
- # state changes
- match = re_state_changed.search(c.comment_text)
- if match:
- e = Event(type="changed_document")
- save_event(d, e, c)
- handled = True
-
- # note changed
- match = re_note_changed.search(c.comment_text)
- if match:
- # watch out for duplicates of which the old data's got many
- if c.comment_text != last_note_change_text:
- last_note_change_text = c.comment_text
- e = Event(type="changed_document")
- save_event(d, e, c)
- handled = True
-
- # draft added
- match = re_draft_added.search(c.comment_text)
- if match:
- e = Event(type="changed_document")
- save_event(d, e, c)
- handled = True
-
- # new version
- if c.comment_text == "New version available":
- e = NewRevision(type="new_revision", rev=c.version)
- save_event(d, e, c)
- handled = True
-
- # resurrect requested
- match = re_resurrection_requested.search(c.comment_text)
- if match:
- e = Event(type="requested_resurrect")
- save_event(d, e, c)
- handled = True
-
- # completed resurrect
- match = re_completed_resurrect.search(c.comment_text)
- if match:
- e = Event(type="completed_resurrect")
- save_event(d, e, c)
- handled = True
-
- # document expiration
- if c.comment_text == "Document is expired by system":
- e = Event(type="expired_document")
- save_event(d, e, c)
- handled = True
-
- # approved document
- match = re_document_approved.search(c.comment_text)
- if match:
- e = Event(type="iesg_approved")
- save_event(d, e, c)
- handled = True
-
- # disapproved document
- match = re_document_disapproved.search(c.comment_text)
- if match:
- e = Event(type="iesg_disapproved")
- save_event(d, e, c)
- handled = True
-
-
- # some changes can be bundled - this is not entirely
- # convenient, especially since it makes it hard to give
- # each a type, so unbundle them
- if not handled:
- unhandled_lines = []
- for line in c.comment_text.split(" "):
- # status date changed
- match = re_status_date_changed.search(line)
- if match:
- e = Status(type="changed_status_date", date=date_in_match(match))
- e.desc = line
- save_event(d, e, c)
- handled = True
-
- # AD/job owner changed
- match = re_responsible_ad_changed.search(line)
- if match:
- e = Event(type="changed_document")
- e.desc = line
- save_event(d, e, c)
- handled = True
-
- # intended standard level changed
- match = re_intended_status_changed.search(line)
- if match:
- e = Event(type="changed_document")
- e.desc = line
- save_event(d, e, c)
- handled = True
-
- # state change notice
- match = re_state_change_notice.search(line)
- if match:
- e = Event(type="changed_document")
- e.desc = line
- save_event(d, e, c)
- handled = True
-
- # area acronym
- match = re_area_acronym_changed.search(line)
- if match:
- e = Event(type="changed_document")
- e.desc = line
- save_event(d, e, c)
- handled = True
-
- # multiline change bundles end with a single "by xyz" that we skip
- if not handled and not line.startswith("by "):
- unhandled_lines.append(line)
-
- if handled:
- c.comment_text = " ".join(unhandled_lines)
-
- if c.comment_text:
- print "couldn't handle multi-line comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
-
- # all others are added as comments
- if not handled:
- e = Event(type="added_comment")
- save_event(d, e, c)
-
- # stop typical comments from being output
- typical_comments = [
- "Document Shepherd Write-up for %s" % d.name,
- "Who is the Document Shepherd for this document",
- "We understand that this document doesn't require any IANA actions",
- "IANA questions",
- "IANA has questions",
- "IANA comments",
- "IANA Comments",
- "IANA Evaluation Comments",
- ]
- for t in typical_comments:
- if t in c.comment_text:
- handled = True
- break
-
- if not handled:
- print "couldn't handle comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
-
-
# import missing revision changes from DraftVersions
known_revisions = set(e.newrevision.rev for e in d.event_set.filter(type="new_revision").select_related('newrevision'))
- for v in DraftVersions.objects.filter(filename=d.name).order_by("revision"):
+ draft_versions = list(DraftVersions.objects.filter(filename=d.name).order_by("revision"))
+ # DraftVersions is not entirely accurate, make sure we got the current one
+ draft_versions.insert(0, DraftVersions(filename=d.name, revision=o.revision_display(), revision_date=o.revision_date))
+ for v in draft_versions:
if v.revision not in known_revisions:
e = NewRevision(type="new_revision")
e.rev = v.revision
@@ -521,104 +681,22 @@ for o in all_drafts:
e.desc = "Last call sent"
e.save()
- if o.idinternal:
- made_up_date = d.latest_event().time + datetime.timedelta(seconds=1) # datetime.datetime(2030, 1, 1, 0, 0, 0)
-
- e = d.latest_event(Status, type="changed_status_date")
- status_date = e.date if e else None
- if o.idinternal.status_date != status_date:
- e = Status(type="changed_status_date", date=o.idinternal.status_date)
- e.time = made_up_date
- e.by = system_email
- e.doc = d
- e.desc = "Status date has been changed to %s from %s" % (o.idinternal.status_date, status_date)
- e.save()
-
- e = d.latest_event(Telechat, type="scheduled_for_telechat")
- telechat_date = e.telechat_date if e else None
- if not o.idinternal.agenda:
- o.idinternal.telechat_date = None # normalize
-
- if telechat_date != o.idinternal.telechat_date:
- e = Telechat(type="scheduled_for_telechat",
- telechat_date=o.idinternal.telechat_date,
- returning_item=bool(o.idinternal.returning_item))
- # a common case is that it has been removed from the
- # agenda automatically by a script without a notice in the
- # comments, in that case the time is simply the day after
- # the telechat
- e.time = telechat_date + datetime.timedelta(days=1) if telechat_date and not o.idinternal.telechat_date else made_up_date
- e.by = system_email
- args = ("Placed on", o.idinternal.telechat_date) if o.idinternal.telechat_date else ("Removed from", telechat_date)
- e.doc = d
- e.desc = "%s agenda for telechat - %s by system" % args
- e.save()
-
- if o.idinternal.ballot:
- text_date = made_up_date
-
- # if any of these events have happened, they're closer to
- # the real time
- e = d.event_set.filter(type__in=("requested_last_call", "sent_last_call", "sent_ballot_announcement", "iesg_approved", "iesg_disapproved")).order_by('time')[:1]
- if e:
- text_date = e[0].time - datetime.timedelta(seconds=1)
-
- if o.idinternal.ballot.approval_text:
- e = Text(type="changed_ballot_approval_text", content=o.idinternal.ballot.approval_text)
- e.time = text_date
- e.by = system_email
- e.doc = d
- e.desc = "Ballot approval text was added"
- e.save()
-
- if o.idinternal.ballot.last_call_text:
- e = Text(type="changed_last_call_text", content=o.idinternal.ballot.last_call_text)
- e.time = text_date
- e.by = system_email
- e.doc = d
- e.desc = "Last call text was added"
- e.save()
-
- if o.idinternal.ballot.ballot_writeup:
- e = Text(type="changed_ballot_writeup_text", content=o.idinternal.ballot.ballot_writeup)
- e.time = text_date
- e.by = system_email
- e.doc = d
- e.desc = "Ballot writeup text was added"
- e.save()
-
# import other attributes
# tags
- tags = d.tags.all()
- def sync_tag(include, tag):
- if include and tag not in tags:
- d.tags.add(tag)
- if not include and tag in tags:
- d.tags.remove(tag)
-
- sync_tag(o.review_by_rfc_editor, tag_review_by_rfc_editor)
- sync_tag(o.expired_tombstone, tag_expired_tombstone)
- sync_tag(o.idinternal and o.idinternal.via_rfc_editor, tag_via_rfc_editor)
-
- n = o.idinternal and o.idinternal.cur_sub_state and o.idinternal.cur_sub_state.sub_state
- for k, v in substate_mapping.iteritems():
- sync_tag(k == n, v)
- # currently we ignore prev_sub_state
-
- sync_tag(o.idinternal and o.idinternal.approved_in_minute, tag_approved_in_minute)
+ sync_tag(d, o.review_by_rfc_editor, tag_review_by_rfc_editor)
+ sync_tag(d, o.expired_tombstone, tag_expired_tombstone)
# RFC alias
if o.rfc_number:
alias_doc("rfc%s" % o.rfc_number, d)
- # FIXME: some RFCs seem to be called rfc1234bis?
if o.replaced_by:
replacement, _ = Document.objects.get_or_create(name=o.replaced_by.filename)
RelatedDocument.objects.get_or_create(document=replacement, doc_alias=d_alias, relationship=relationship_replaces)
# the RFC-related attributes are imported when we handle the RFCs below
-
+
print "imported", d.name, " - ", d.iesg_state
@@ -653,12 +731,13 @@ def get_or_create_rfc_document(rfc_number):
alias = alias_doc("rfc%s" % rfc_number, d)
return (d, alias)
+
all_rfcs = RfcIndex.objects.all()
if all_drafts.count() != InternetDraft.objects.count():
- if document_name_to_import.startswith("rfc"):
- # we wanted to import just an RFC, great
+ if document_name_to_import and document_name_to_import.startswith("rfc"):
+ # we wanted to import an RFC
all_rfcs = all_rfcs.filter(rfc_number=document_name_to_import[3:])
else:
# if we didn't process all drafts, limit the RFCs to the ones we
@@ -667,7 +746,38 @@ if all_drafts.count() != InternetDraft.objects.count():
for o in all_rfcs:
d, d_alias = get_or_create_rfc_document(o.rfc_number)
+ #if d.name.startswith('rfc'):
+ d.time = datetime.datetime.now()
+ d.title = o.title
+ d.std_level = std_level_mapping[o.current_status]
+ d.stream = stream_mapping[o.stream]
+ if not d.group and o.wg:
+ d.group = Group.objects.get(acronym=o.wg)
+
+ # get some values from the rfc table
+ rfcs = Rfc.objects.filter(rfc_number=o.rfc_number).select_related()
+ if rfcs:
+ r = rfcs[0]
+ d.intended_std_level = intended_std_level_mapping[r.intended_status.status]
+ d.save()
+
+ # a few RFCs have an IDInternal so we may have to import the
+ # events and attributes
+ internals = IDInternal.objects.filter(rfc_flag=1, draft=o.rfc_number)
+ if internals:
+ if d.name.startswith("rfc"):
+ # clear any already imported events as the event importer isn't
+ # clever enough to do a diff
+ d.event_set.all().delete()
+ import_from_idinternal(d, internals[0])
+ # publication date
+ e, _ = Event.objects.get_or_create(doc=d, type="published_rfc")
+ e.time = o.rfc_published_date
+ e.by = system_email
+ e.desc = "RFC published"
+ e.save()
+
# import obsoletes/updates
def make_relation(other_rfc, rel_type, reverse):
other_number = int(other_rfc.replace("RFC", ""))
@@ -691,27 +801,10 @@ for o in all_rfcs:
make_relation(x, relationship_updates, True)
if o.also:
- print o.also
alias_doc(o.also.lower(), d)
-
+
+ sync_tag(d, o.has_errata, tag_has_errata)
+
+ # FIXME: import RFC authors?
+
print "imported", d_alias.name, " - ", d.rfc_state
-
-
-sys.exit(0)
-
-class RfcIndex(models.Model):
-# rfc_number = models.IntegerField(primary_key=True)
- title = models.CharField(max_length=250)
- authors = models.CharField(max_length=250)
- rfc_published_date = models.DateField()
- current_status = models.CharField(max_length=50,null=True)
-# updates = models.CharField(max_length=200,blank=True,null=True)
-# updated_by = models.CharField(max_length=200,blank=True,null=True)
-# obsoletes = models.CharField(max_length=200,blank=True,null=True)
-# obsoleted_by = models.CharField(max_length=200,blank=True,null=True)
-# also = models.CharField(max_length=50,blank=True,null=True)
- draft = models.CharField(max_length=200,null=True)
- has_errata = models.BooleanField()
- stream = models.CharField(max_length=15,blank=True,null=True)
- wg = models.CharField(max_length=15,blank=True,null=True)
- file_formats = models.CharField(max_length=20,blank=True,null=True)
diff --git a/redesign/import-roles.py b/redesign/import-roles.py
index cb465b258..d8a72f891 100755
--- a/redesign/import-roles.py
+++ b/redesign/import-roles.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
import sys, os, re, datetime
+import unaccent
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path = [ basedir ] + sys.path
@@ -14,17 +15,18 @@ management.setup_environ(settings)
from redesign.person.models import *
from redesign.group.models import *
from redesign.name.models import *
-from ietf.idtracker.models import IESGLogin, AreaDirector, PersonOrOrgInfo
+from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo
# assumptions:
# - groups have been imported
-# PersonOrOrgInfo/PostalAddress/EmailAddress/PhoneNumber are not imported
+# PersonOrOrgInfo/PostalAddress/EmailAddress/PhoneNumber are not
+# imported, although some information is retrieved from those
-# imports IESGLogin, AreaDirector
+# imports IESGLogin, AreaDirector and persons from IDAuthor
-# should import IDAuthor, WGChair, WGEditor,
-# WGSecretary, WGTechAdvisor, Role, ChairsHistory, IRTFChair
+# should probably import WGChair, WGEditor, WGSecretary,
+# WGTechAdvisor, Role, ChairsHistory, IRTFChair
# make sure names exist
def name(name_class, slug, name, desc=""):
@@ -44,25 +46,27 @@ def get_or_create_email(o):
email = o.person.email()[1] or hardcoded_emails.get("%s %s" % (o.person.first_name, o.person.last_name))
if not email:
- print "NO EMAIL FOR %s %s %s %s" % (o.__class__, o.id, o.person.first_name, o.person.last_name)
+ print "NO EMAIL FOR %s %s %s %s %s" % (o.__class__, o.id, o.person.pk, o.person.first_name, o.person.last_name)
return None
e, _ = Email.objects.get_or_create(address=email)
if not e.person:
n = u"%s %s" % (o.person.first_name, o.person.last_name)
- aliases = Alias.objects.filter(name=n)
+ asciified = unaccent.asciify(n)
+ aliases = Alias.objects.filter(name__in=(n, asciified))
if aliases:
p = aliases[0].person
else:
- p = Person.objects.create(name=n, ascii=n)
- Alias.objects.create(name=n, person=p)
+ p = Person.objects.create(name=n, ascii=asciified)
# FIXME: fill in address?
+ Alias.objects.create(name=n, person=p)
+ if asciified != n:
+ Alias.objects.create(name=asciified, person=p)
e.person = p
e.save()
return e
-
# IESGLogin
for o in IESGLogin.objects.all():
@@ -95,3 +99,10 @@ for o in AreaDirector.objects.all():
Role.objects.get_or_create(name=area_director_role, group=area, email=email)
+# IDAuthor persons
+for o in IDAuthor.objects.all().order_by('id').select_related('person'):
+ print "importing IDAuthor", o.id, o.person_id, o.person.first_name.encode('utf-8'), o.person.last_name.encode('utf-8')
+ email = get_or_create_email(o)
+
+ # FIXME: we lack email addresses for some, need to do something
+
diff --git a/redesign/name/admin.py b/redesign/name/admin.py
index 59ef3fdd2..0a3cedb72 100644
--- a/redesign/name/admin.py
+++ b/redesign/name/admin.py
@@ -17,6 +17,6 @@ admin.site.register(IanaDocStateName, NameAdmin)
admin.site.register(RfcDocStateName, NameAdmin)
admin.site.register(DocTypeName, NameAdmin)
admin.site.register(DocInfoTagName, NameAdmin)
-admin.site.register(IntendedStatusName, NameAdmin)
-admin.site.register(StdStatusName, NameAdmin)
+admin.site.register(StdLevelName, NameAdmin)
+admin.site.register(IntendedStdLevelName, NameAdmin)
admin.site.register(BallotPositionName, NameAdmin)
diff --git a/redesign/name/models.py b/redesign/name/models.py
index fef8b8a4e..da57c22ce 100644
--- a/redesign/name/models.py
+++ b/redesign/name/models.py
@@ -21,7 +21,7 @@ class GroupTypeName(NameModel):
class RoleName(NameModel):
"""AD, Chair"""
class DocStreamName(NameModel):
- """IETF, IAB, IRTF, Independent Submission"""
+ """IETF, IAB, IRTF, Independent Submission, Legacy"""
class DocStateName(NameModel):
"""Active, Expired, RFC, Replaced, Withdrawn"""
class DocRelationshipName(NameModel):
@@ -47,10 +47,10 @@ class DocTypeName(NameModel):
class DocInfoTagName(NameModel):
"""Waiting for Reference, IANA Coordination, Revised ID Needed,
External Party, AD Followup, Point Raised - Writeup Needed"""
-class StdStatusName(NameModel):
+class StdLevelName(NameModel):
"""Proposed Standard, Draft Standard, Standard, Experimental,
Informational, Best Current Practice, Historic, ..."""
-class IntendedStatusName(NameModel):
+class IntendedStdLevelName(NameModel):
"""Standards Track, Experimental, Informational, Best Current
Practice, Historic, ..."""
class BallotPositionName(NameModel):
diff --git a/redesign/proxy_utils.py b/redesign/proxy_utils.py
index a42bd3f34..7189b67dd 100644
--- a/redesign/proxy_utils.py
+++ b/redesign/proxy_utils.py
@@ -11,7 +11,8 @@ class TranslatingQuerySet(QuerySet):
if callable(t):
t, v = t(v)
- res[t] = v
+ if t:
+ res[t] = v
else:
res[k] = v
return res
diff --git a/redesign/unaccent.py b/redesign/unaccent.py
new file mode 100644
index 000000000..265e764de
--- /dev/null
+++ b/redesign/unaccent.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+# use a dynamically populated translation dictionary to remove accents
+# from a string
+# (by Chris Mulligan, http://chmullig.com/2009/12/python-unicode-ascii-ifier/)
+
+import unicodedata, sys
+
+class unaccented_map(dict):
+# Translation dictionary. Translation entries are added to this dictionary as needed.
+ CHAR_REPLACEMENT = {
+ 0xc6: u"AE", # Æ LATIN CAPITAL LETTER AE
+ 0xd0: u"D", # Ð LATIN CAPITAL LETTER ETH
+ 0xd8: u"OE", # Ø LATIN CAPITAL LETTER O WITH STROKE
+ 0xde: u"Th", # Þ LATIN CAPITAL LETTER THORN
+ 0xc4: u'Ae', # Ä LATIN CAPITAL LETTER A WITH DIAERESIS
+ 0xd6: u'Oe', # Ö LATIN CAPITAL LETTER O WITH DIAERESIS
+ 0xdc: u'Ue', # Ü LATIN CAPITAL LETTER U WITH DIAERESIS
+
+ 0xc0: u"A", # À LATIN CAPITAL LETTER A WITH GRAVE
+ 0xc1: u"A", # Á LATIN CAPITAL LETTER A WITH ACUTE
+ 0xc3: u"A", # Ã LATIN CAPITAL LETTER A WITH TILDE
+ 0xc7: u"C", # Ç LATIN CAPITAL LETTER C WITH CEDILLA
+ 0xc8: u"E", # È LATIN CAPITAL LETTER E WITH GRAVE
+ 0xc9: u"E", # É LATIN CAPITAL LETTER E WITH ACUTE
+ 0xca: u"E", # Ê LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ 0xcc: u"I", # Ì LATIN CAPITAL LETTER I WITH GRAVE
+ 0xcd: u"I", # Í LATIN CAPITAL LETTER I WITH ACUTE
+ 0xd2: u"O", # Ò LATIN CAPITAL LETTER O WITH GRAVE
+ 0xd3: u"O", # Ó LATIN CAPITAL LETTER O WITH ACUTE
+ 0xd5: u"O", # Õ LATIN CAPITAL LETTER O WITH TILDE
+ 0xd9: u"U", # Ù LATIN CAPITAL LETTER U WITH GRAVE
+ 0xda: u"U", # Ú LATIN CAPITAL LETTER U WITH ACUTE
+
+ 0xdf: u"ss", # ß LATIN SMALL LETTER SHARP S
+ 0xe6: u"ae", # æ LATIN SMALL LETTER AE
+ 0xf0: u"d", # ð LATIN SMALL LETTER ETH
+ 0xf8: u"oe", # ø LATIN SMALL LETTER O WITH STROKE
+ 0xfe: u"th", # þ LATIN SMALL LETTER THORN,
+ 0xe4: u'ae', # ä LATIN SMALL LETTER A WITH DIAERESIS
+ 0xf6: u'oe', # ö LATIN SMALL LETTER O WITH DIAERESIS
+ 0xfc: u'ue', # ü LATIN SMALL LETTER U WITH DIAERESIS
+
+ 0xe0: u"a", # à LATIN SMALL LETTER A WITH GRAVE
+ 0xe1: u"a", # á LATIN SMALL LETTER A WITH ACUTE
+ 0xe3: u"a", # ã LATIN SMALL LETTER A WITH TILDE
+ 0xe7: u"c", # ç LATIN SMALL LETTER C WITH CEDILLA
+ 0xe8: u"e", # è LATIN SMALL LETTER E WITH GRAVE
+ 0xe9: u"e", # é LATIN SMALL LETTER E WITH ACUTE
+ 0xea: u"e", # ê LATIN SMALL LETTER E WITH CIRCUMFLEX
+ 0xec: u"i", # ì LATIN SMALL LETTER I WITH GRAVE
+ 0xed: u"i", # í LATIN SMALL LETTER I WITH ACUTE
+ 0xf2: u"o", # ò LATIN SMALL LETTER O WITH GRAVE
+ 0xf3: u"o", # ó LATIN SMALL LETTER O WITH ACUTE
+ 0xf5: u"o", # õ LATIN SMALL LETTER O WITH TILDE
+ 0xf9: u"u", # ù LATIN SMALL LETTER U WITH GRAVE
+ 0xfa: u"u", # ú LATIN SMALL LETTER U WITH ACUTE
+
+ 0x2018: u"'", # ‘ LEFT SINGLE QUOTATION MARK
+ 0x2019: u"'", # ’ RIGHT SINGLE QUOTATION MARK
+ 0x201c: u'"', # “ LEFT DOUBLE QUOTATION MARK
+ 0x201d: u'"', # ” RIGHT DOUBLE QUOTATION MARK
+
+ }
+
+ # Maps a unicode character code (the key) to a replacement code
+ # (either a character code or a unicode string).
+ def mapchar(self, key):
+ ch = self.get(key)
+ if ch is not None:
+ return ch
+ try:
+ de = unicodedata.decomposition(unichr(key))
+ p1, p2 = [int(x, 16) for x in de.split(None, 1)]
+ if p2 == 0x308:
+ ch = self.CHAR_REPLACEMENT.get(key)
+ else:
+ ch = int(p1)
+
+ except (IndexError, ValueError):
+ ch = self.CHAR_REPLACEMENT.get(key, key)
+ self[key] = ch
+ return ch
+
+ if sys.version <= "2.5":
+ # use __missing__ where available
+ __missing__ = mapchar
+ else:
+ # otherwise, use standard __getitem__ hook (this is slower,
+ # since it's called for each character)
+ __getitem__ = mapchar
+
+map = unaccented_map()
+
+def asciify(input):
+ try:
+ return input.encode('ascii')
+ except AttributeError:
+ return str(input).encode('ascii')
+ except UnicodeEncodeError:
+ return unicodedata.normalize('NFKD', input.translate(map)).encode('ascii', 'replace')
+
+text = u"""
+
+##Norwegian
+"Jo, når'n da ha gått ett stôck te, så kommer'n te e å,
+å i åa ä e ö."
+"Vasa", sa'n.
+"Å i åa ä e ö", sa ja.
+"Men va i all ti ä dä ni säjer, a, o?", sa'n.
+"D'ä e å, vett ja", skrek ja, för ja ble rasen, "å i åa
+ä e ö, hörer han lite, d'ä e å, å i åa ä e ö."
+"A, o, ö", sa'n å dämmä geck'en.
+Jo, den va nôe te dum den.
+
+(taken from the short story "Dumt fôlk" in Gustaf Fröding's
+"Räggler å paschaser på våra mål tå en bonne" (1895).
+
+##Danish
+
+Nu bliver Mølleren sikkert sur, og dog, han er stadig den største på verdensplan.
+
+Userneeds A/S er en dansk virksomhed, der udfører statistiske undersøgelser på internettet. Den blev etableret i 2001 som et anpartsselskab af David Jensen og Henrik Vincentz.
+Frem til 2004 var det primære fokus på at forbedre hjemmesiderne for andre virksomheder. Herefter blev fokus omlagt, så man også beskæftigede sig med statistiske målinger. Ledelsen vurderede, at dette marked ville vokse betragteligt i de kommende år, hvilket man ønskede at udnytte.
+Siden omlægningen er der blevet fokuseret på at etablere meget store forbrugerpaneler. Således udgjorde det danske panel i 2005 65.000 personer og omfatter per 2008 100.000 personer.
+I 2007 blev Userneeds ApS konverteret til aktieselskabet Userneeds A/S
+Efterhånden er aktiviteterne blevet udvidet til de nordiske lande (med undtagelse af Island) og besidder i 2009 et forbrugerpanel med i alt mere end 250.000 personer bosat i de fire store nordiske lande.
+Selskabet tegnes udadtil af en direktion på tre personer, der foruden Henrik Vincentz tæller Palle Viby Morgen og Simon Andersen.
+De primære konkurrenter er andre analysebureauer som AC Nielsen, Analysedanmark, Gallup, Norstat, Synnovate og Zapera.
+
+##Finnish
+Titus Aurelius Fulvus Boionius Arrius Antoninus eli Antoninus Pius (19. syyskuuta 86 – 7. maaliskuuta 161) oli Rooman keisari vuosina 138–161. Antoninus sai lisänimensä Pius (suom. velvollisuudentuntoinen) noustuaan valtaan vuonna 138. Hän kuului Nerva–Antoninusten hallitsijasukuun ja oli suosittu ja kunnioitettu keisari, joka tunnettiin lempeydestään ja oikeamielisyydestään. Hänen valtakauttaan on usein sanottu Rooman valtakunnan kultakaudeksi, jolloin talous kukoisti, poliittinen tilanne oli vakaa ja armeija vahva. Hän hallitsi pitempään kuin yksikään Rooman keisari Augustuksen jälkeen, ja hänen kautensa tunnetaan erityisen rauhallisena, joskaan ei sodattomana. Antoninus adoptoi Marcus Aureliuksen ja Lucius Veruksen vallanperijöikseen. Hän kuoli vuonna 161.
+
+#German
+So heißt ein altes Märchen: "Der Ehre Dornenpfad", und es handelt von einem Schützen mit Namen Bryde, der wohl zu großen Ehren und Würden kam, aber nicht ohne lange und vielfältige Widerwärtigkeiten und Fährnisse des Lebens durchzumachen. Manch einer von uns hat es gewiß als Kind gehört oder es vielleicht später gelesen und dabei an seinen eigenen stillen Dornenweg und die vielen Widerwärtigkeiten gedacht. Märchen und Wirklichkeit liegen einander so nahe, aber das Märchen hat seine harmonische Lösung hier auf Erden, während die Wirklichkeit sie meist aus dem Erdenleben hinaus in Zeit und Ewigkeit verlegt.
+
+12\xbd inch
+"""
+
+if __name__ == "__main__":
+ for i, line in enumerate(text.splitlines()):
+ line = line.strip()
+ print line
+ if line and not line.startswith('#'):
+ print '\tTrans: ', asciify(line).strip()
From 644c7fc49619178d63e74ca4189d4c3943997175 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 7 Jan 2011 23:16:57 +0000
Subject: [PATCH 13/75] Fixed import problems (all drafts and RFCs from
rfc_index_mirror can now be imported) - Legacy-Id: 2739
---
redesign/doc/models.py | 2 +-
redesign/doc/proxy.py | 7 +-
redesign/import-document-state.py | 194 ++++++++++++++++++++++++------
redesign/import-roles.py | 24 ++--
4 files changed, 176 insertions(+), 51 deletions(-)
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index 0664a5969..ae9eef405 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -146,7 +146,7 @@ class DocAlias(models.Model):
to by RFC number, primarily, after achieving RFC status.
"""
document = models.ForeignKey(Document)
- name = models.CharField(max_length=255)
+ name = models.CharField(max_length=255, db_index=True)
def __unicode__(self):
return "%s-->%s" % (self.name, self.document.name)
document_link = admin_link("document")
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index 8c7668849..64c1f6123 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -162,8 +162,9 @@ class InternetDraft(Document):
#idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
@property
def idinternal(self):
- print self.iesg_state
- return self if self.iesg_state else None
+ # since IDInternal is now merged into the document, we try to
+ # guess here
+ return self if self.iesg_state or self.latest_event(type="changed_ballot_position") else None
# reverse relationship
@property
@@ -646,7 +647,7 @@ class IDAuthor(DocumentAuthor):
return self.order
def email(self):
- return self.author.address
+ return None if self.author.address.startswith("unknown-email") else self.author.address
def final_author_order(self):
return self.order
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 69e14c2e8..12eb5d155 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -14,7 +14,7 @@ management.setup_environ(settings)
from redesign.doc.models import *
from redesign.group.models import *
from redesign.name.models import *
-from ietf.idtracker.models import InternetDraft, IDInternal, IESGLogin, DocumentComment, PersonOrOrgInfo, Rfc, IESGComment, IESGDiscuss, BallotInfo
+from ietf.idtracker.models import InternetDraft, IDInternal, IESGLogin, DocumentComment, PersonOrOrgInfo, Rfc, IESGComment, IESGDiscuss, BallotInfo, Position
from ietf.idrfc.models import RfcIndex, DraftVersions
import sys
@@ -125,9 +125,15 @@ ballot_position_mapping = {
'Abstain': name(BallotPositionName, 'abstain', 'Abstain'),
'Discuss': name(BallotPositionName, 'discuss', 'Discuss'),
'Recuse': name(BallotPositionName, 'recuse', 'Recuse'),
- 'Undefined': name(BallotPositionName, 'norecord', 'No record'),
- None: name(BallotPositionName, 'norecord', 'No record'),
+ 'No Record': name(BallotPositionName, 'norecord', 'No record'),
}
+ballot_position_mapping["no"] = ballot_position_mapping['No Objection']
+ballot_position_mapping["yes"] = ballot_position_mapping['Yes']
+ballot_position_mapping["discuss"] = ballot_position_mapping['Discuss']
+ballot_position_mapping["abstain"] = ballot_position_mapping['Abstain']
+ballot_position_mapping["recuse"] = ballot_position_mapping['Recuse']
+ballot_position_mapping[None] = ballot_position_mapping["No Record"]
+ballot_position_mapping["Undefined"] = ballot_position_mapping["No Record"]
substate_mapping = {
"External Party": name(DocInfoTagName, 'extpty', "External Party", 'The document is awaiting review or input from an external party (i.e, someone other than the shepherding AD, the authors, or the WG). See the "note" field for more details on who has the action.'),
@@ -203,9 +209,14 @@ def iesg_login_to_email(l):
# regexps for parsing document comments
-date_re_str = "(?P[0-9][0-9][0-9][0-9])-(?P[0-9][0-9])-(?P[0-9][0-9])"
+date_re_str = "(?P[0-9][0-9][0-9][0-9])-(?P[0-9][0-9]?)-(?P[0-9][0-9]?)"
def date_in_match(match):
- return datetime.date(int(match.group('year')), int(match.group('month')), int(match.group('day')))
+ y = int(match.group('year'))
+ m = int(match.group('month'))
+ d = int(match.group('day'))
+ if d == 35: # borked status date
+ d = 25
+ return datetime.date(y, m, d)
re_telechat_agenda = re.compile(r"(Placed on|Removed from) agenda for telechat(| - %s) by" % date_re_str)
re_telechat_changed = re.compile(r"Telechat date (was|has been) changed to ()?%s()? from" % date_re_str)
@@ -238,7 +249,7 @@ def import_from_idinternal(d, idinternal):
# extract events
last_note_change_text = ""
- for c in idinternal.documentcomment_set.order_by('date', 'time', 'id'):
+ for c in DocumentComment.objects.filter(document=idinternal.draft_id).order_by('date', 'time', 'id'):
handled = False
# telechat agenda schedulings
@@ -255,7 +266,7 @@ def import_from_idinternal(d, idinternal):
# ballot issued
match = re_ballot_issued.search(c.comment_text)
if match:
- e = Text()
+ e = Event()
e.type = "sent_ballot_announcement"
save_event(d, e, c)
@@ -276,19 +287,41 @@ def import_from_idinternal(d, idinternal):
# ballot positions
match = re_ballot_position.search(c.comment_text)
if match:
- position = match.group('position') or match.group('position2')
+ position = ballot_position_mapping[match.group('position') or match.group('position2')]
ad_name = match.group('for') or match.group('for2') or match.group('by') # some of the old positions don't specify who it's for, in that case assume it's "by", the person who entered the position
ad_first, ad_last = ad_name.split(' ')
+ login = IESGLogin.objects.filter(first_name=ad_first, last_name=ad_last).order_by('user_level')[0]
+ if login.user_level == IESGLogin.SECRETARIAT_LEVEL:
+ # now we're in trouble, a secretariat person isn't an
+ # AD, instead try to find a position object that
+ # matches and that we haven't taken yet
+ positions = Position.objects.filter(ballot=idinternal.ballot)
+ if position.slug == "noobj":
+ positions = positions.filter(noobj=1)
+ elif position.slug == "yes":
+ positions = positions.filter(yes=1)
+ elif position.slug == "abstain":
+ positions = positions.filter(models.Q(abstain=1)|models.Q(abstain=2))
+ elif position.slug == "recuse":
+ positions = positions.filter(recuse=1)
+ elif position.slug == "discuss":
+ positions = positions.filter(models.Q(discuss=1)|models.Q(discuss=2))
+ assert position.slug != "norecord"
+
+ for p in positions:
+ if not d.event_set.filter(type="changed_ballot_position", ballotposition__pos=position, ballotposition__ad=iesg_login_to_email(p.ad)):
+ login = p.ad
+ break
e = BallotPosition()
e.type = "changed_ballot_position"
- e.ad = iesg_login_to_email(IESGLogin.objects.get(first_name=ad_first, last_name=ad_last))
- last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
- e.pos = ballot_position_mapping[position]
- e.discuss = last_pos.ballotposition.discuss if last_pos else ""
- e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
- e.comment = last_pos.ballotposition.comment if last_pos else ""
- e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
+ e.ad = iesg_login_to_email(login)
+ last_pos = d.latest_event(BallotPosition, type="changed_ballot_position", ad=e.ad)
+ e.pos = position
+ e.discuss = last_pos.discuss if last_pos else ""
+ e.discuss_time = last_pos.discuss_time if last_pos else None
+ e.comment = last_pos.comment if last_pos else ""
+ e.comment_time = last_pos.comment_time if last_pos else None
save_event(d, e, c)
handled = True
@@ -394,6 +427,7 @@ def import_from_idinternal(d, idinternal):
if not handled:
unhandled_lines = []
for line in c.comment_text.split(" "):
+ line = line.replace(" ", " ")
# status date changed
match = re_status_date_changed.search(line)
if match:
@@ -442,7 +476,8 @@ def import_from_idinternal(d, idinternal):
c.comment_text = " ".join(unhandled_lines)
if c.comment_text:
- print "COULDN'T HANDLE multi-line comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
+ if "Due date has been changed" not in c.comment_text:
+ print "COULDN'T HANDLE multi-line comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
# all others are added as comments
if not handled:
@@ -458,8 +493,17 @@ def import_from_idinternal(d, idinternal):
"IANA has questions",
"IANA comments",
"IANA Comments",
- "IANA Evaluation Comments",
- "Published as RFC",
+ "IANA Evaluation Comment",
+ "IANA Last Call Comments",
+ "ublished as RFC",
+ "A new comment added",
+ "Due date has been changed",
+ "Due date has been changed",
+ "by ",
+ "AD-review comments",
+ "IANA Last Call",
+ "Subject:",
+ "Merged with",
]
for t in typical_comments:
if t in c.comment_text:
@@ -467,9 +511,14 @@ def import_from_idinternal(d, idinternal):
break
if not handled:
- print "couldn't handle comment %s '%s'" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80])
+ print (u"COULDN'T HANDLE comment %s '%s' by %s" % (c.id, c.comment_text.replace("\n", " ").replace("\r", "")[0:80], c.created_by)).encode("utf-8")
- made_up_date = d.latest_event().time + datetime.timedelta(seconds=1)
+ e = d.latest_event()
+ if e:
+ made_up_date = e.time
+ else:
+ made_up_date = d.time
+ made_up_date += datetime.timedelta(seconds=1)
e = d.latest_event(Status, type="changed_status_date")
status_date = e.date if e else None
@@ -508,6 +557,70 @@ def import_from_idinternal(d, idinternal):
ballot = None
if ballot:
+ e = d.event_set.filter(type__in=("changed_ballot_position", "sent_ballot_announcement", "requested_last_call")).order_by('-time')[:1]
+ if e:
+ position_date = e[0].time + datetime.timedelta(seconds=1)
+ else:
+ position_date = made_up_date
+
+ # make sure we got all the positions
+ existing = BallotPosition.objects.filter(doc=d, type="changed_ballot_position").order_by("-time")
+
+ for p in Position.objects.filter(ballot=ballot):
+ found = False
+ ad = iesg_login_to_email(p.ad)
+ if p.noobj > 0:
+ pos = ballot_position_mapping["No Objection"]
+ elif p.yes > 0:
+ pos = ballot_position_mapping["Yes"]
+ elif p.abstain > 0:
+ pos = ballot_position_mapping["Abstain"]
+ elif p.recuse > 0:
+ pos = ballot_position_mapping["Recuse"]
+ elif p.discuss > 0:
+ pos = ballot_position_mapping["Discuss"]
+ else:
+ pos = ballot_position_mapping[None]
+ for x in existing:
+ if x.ad == ad and x.pos == pos:
+ found = True
+ break
+
+ if not found:
+ e = BallotPosition()
+ e.type = "changed_ballot_position"
+ e.doc = d
+ e.time = position_date
+ e.by = system_email
+ e.ad = ad
+ last_pos = d.latest_event(BallotPosition, type="changed_ballot_position", ad=e.ad)
+ e.pos = pos
+ e.discuss = last_pos.discuss if last_pos else ""
+ e.discuss_time = last_pos.discuss_time if last_pos else None
+ e.comment = last_pos.comment if last_pos else ""
+ e.comment_time = last_pos.comment_time if last_pos else None
+ if last_pos:
+ e.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (ad.get_name(), pos.name, last_pos.pos.name)
+ else:
+ e.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.name, ad.get_name())
+ e.save()
+
+ # make sure we got the ballot issued event
+ if ballot.ballot_issued and not d.event_set.filter(type="sent_ballot_announcement"):
+ position = d.event_set.filter(type=("changed_ballot_position")).order_by('time')[:1]
+ if position:
+ sent_date = position[0].time
+ else:
+ sent_date = made_up_date
+
+ e = Event()
+ e.type = "sent_ballot_announcement"
+ e.doc = d
+ e.time = sent_date
+ e.by = system_email
+ e.desc = "Ballot has been issued"
+ e.save()
+
# make sure the comments and discusses are updated
positions = list(BallotPosition.objects.filter(doc=d).order_by("-time"))
for c in IESGComment.objects.filter(ballot=idinternal.ballot):
@@ -578,8 +691,17 @@ all_drafts = InternetDraft.objects.all().select_related()
if document_name_to_import:
all_drafts = all_drafts.filter(filename=document_name_to_import)
#all_drafts = all_drafts[all_drafts.count() - 1000:]
+
+# prevent memory from leaking from debug setting
+from django.db import connection
+class DummyQueries(object):
+ def append(self, x):
+ pass
+connection.queries = DummyQueries()
-for o in all_drafts:
+for index, o in enumerate(all_drafts.iterator()):
+ print "importing", o.filename, index
+
try:
d = Document.objects.get(name=o.filename)
except Document.DoesNotExist:
@@ -620,14 +742,13 @@ for o in all_drafts:
d.authors.clear()
for i, a in enumerate(o.authors.all().select_related("person").order_by('author_order', 'person')):
try:
- e = Email.objects.get(address=a.person.email()[1])
+ e = Email.objects.get(address=a.person.email()[1] or u"unknown-email-%s-%s" % (a.person.first_name, a.person.last_name))
# renumber since old numbers may be a bit borked
DocumentAuthor.objects.create(document=d, author=e, order=i)
except Email.DoesNotExist:
print "SKIPPED author", unicode(a.person).encode('utf-8')
- # clear any already imported events as the event importer isn't
- # clever enough to do a diff
+ # clear any already imported events
d.event_set.all().delete()
if o.idinternal:
@@ -646,7 +767,7 @@ for o in all_drafts:
# we don't have time information in this source, so
# hack the seconds to include the revision to ensure
# they're ordered correctly
- e.time = datetime.datetime.combine(v.revision_date, datetime.time(0, 0, int(v.revision)))
+ e.time = datetime.datetime.combine(v.revision_date, datetime.time(0, 0, 0)) + datetime.timedelta(seconds=int(v.revision))
e.by = system_email
e.doc = d
e.desc = "New version available"
@@ -658,7 +779,7 @@ for o in all_drafts:
# information completely
# make sure last decision is recorded
- e = d.latest_event(Event, type__in=("iesg_approved", "iesg_disapproved"))
+ e = d.latest_event(type__in=("iesg_approved", "iesg_disapproved"))
decision_date = e.time.date() if e else None
if o.b_approve_date != decision_date:
disapproved = o.idinternal and o.idinternal.dnp
@@ -697,9 +818,6 @@ for o in all_drafts:
# the RFC-related attributes are imported when we handle the RFCs below
- print "imported", d.name, " - ", d.iesg_state
-
-
# now process RFCs
def get_or_create_rfc_document(rfc_number):
@@ -744,9 +862,10 @@ if all_drafts.count() != InternetDraft.objects.count():
# did process
all_rfcs = all_rfcs.filter(rfc_number__in=set(d.rfc_number for d in all_drafts if d.rfc_number))
-for o in all_rfcs:
+for index, o in enumerate(all_rfcs.iterator()):
+ print "importing rfc%s" % o.rfc_number, index
+
d, d_alias = get_or_create_rfc_document(o.rfc_number)
- #if d.name.startswith('rfc'):
d.time = datetime.datetime.now()
d.title = o.title
d.std_level = std_level_mapping[o.current_status]
@@ -758,7 +877,9 @@ for o in all_rfcs:
rfcs = Rfc.objects.filter(rfc_number=o.rfc_number).select_related()
if rfcs:
r = rfcs[0]
- d.intended_std_level = intended_std_level_mapping[r.intended_status.status]
+ l = intended_std_level_mapping[r.intended_status.status]
+ if l:
+ d.intended_std_level = l
d.save()
# a few RFCs have an IDInternal so we may have to import the
@@ -766,8 +887,8 @@ for o in all_rfcs:
internals = IDInternal.objects.filter(rfc_flag=1, draft=o.rfc_number)
if internals:
if d.name.startswith("rfc"):
- # clear any already imported events as the event importer isn't
- # clever enough to do a diff
+ # clear any already imported events, we don't do it for
+ # drafts as they've already been cleared above
d.event_set.all().delete()
import_from_idinternal(d, internals[0])
@@ -780,6 +901,9 @@ for o in all_rfcs:
# import obsoletes/updates
def make_relation(other_rfc, rel_type, reverse):
+ if other_rfc.startswith("NIC") or other_rfc.startswith("IEN") or other_rfc.startswith("STD") or other_rfc.startswith("RTR"):
+ return # we currently have no good way of importing these
+
other_number = int(other_rfc.replace("RFC", ""))
other, other_alias = get_or_create_rfc_document(other_number)
if reverse:
@@ -806,5 +930,3 @@ for o in all_rfcs:
sync_tag(d, o.has_errata, tag_has_errata)
# FIXME: import RFC authors?
-
- print "imported", d_alias.name, " - ", d.rfc_state
diff --git a/redesign/import-roles.py b/redesign/import-roles.py
index d8a72f891..cc39eebe9 100755
--- a/redesign/import-roles.py
+++ b/redesign/import-roles.py
@@ -41,13 +41,17 @@ area_director_role = name(RoleName, "ad", "Area Director")
# helpers for creating the objects
-def get_or_create_email(o):
+def get_or_create_email(o, create_fake):
hardcoded_emails = { 'Dinara Suleymanova': "dinaras@ietf.org" }
email = o.person.email()[1] or hardcoded_emails.get("%s %s" % (o.person.first_name, o.person.last_name))
if not email:
- print "NO EMAIL FOR %s %s %s %s %s" % (o.__class__, o.id, o.person.pk, o.person.first_name, o.person.last_name)
- return None
+ if create_fake:
+ email = u"unknown-email-%s-%s" % (o.person.first_name, o.person.last_name)
+ print ("USING FAKE EMAIL %s for %s %s %s" % (email, o.person.pk, o.person.first_name, o.person.last_name)).encode('utf-8')
+ else:
+ print ("NO EMAIL FOR %s %s %s %s %s" % (o.__class__, o.id, o.person.pk, o.person.first_name, o.person.last_name)).encode('utf-8')
+ return None
e, _ = Email.objects.get_or_create(address=email)
if not e.person:
@@ -80,21 +84,21 @@ for o in IESGLogin.objects.all():
print "NO PERSON", o.person_id
continue
- email = get_or_create_email(o)
+ email = get_or_create_email(o, create_fake=False)
- # FIXME: import o.user_level
# FIXME: import o.login_name, o.user_level
# AreaDirector
Role.objects.filter(name=area_director_role).delete()
for o in AreaDirector.objects.all():
- print "importing AreaDirector", o.area, o.person
- email = get_or_create_email(o)
if not o.area:
- print "NO AREA", o.area_id
+ print "NO AREA", o.person, o.area_id
continue
+ print "importing AreaDirector", o.area, o.person
+ email = get_or_create_email(o, create_fake=False)
+
area = Group.objects.get(acronym=o.area.area_acronym.acronym)
Role.objects.get_or_create(name=area_director_role, group=area, email=email)
@@ -102,7 +106,5 @@ for o in AreaDirector.objects.all():
# IDAuthor persons
for o in IDAuthor.objects.all().order_by('id').select_related('person'):
print "importing IDAuthor", o.id, o.person_id, o.person.first_name.encode('utf-8'), o.person.last_name.encode('utf-8')
- email = get_or_create_email(o)
-
- # FIXME: we lack email addresses for some, need to do something
+ email = get_or_create_email(o, create_fake=True)
From 380c24d302e53edfe585fbada83d2de8db43632a Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 10 Jan 2011 12:01:05 +0000
Subject: [PATCH 14/75] Fix some import/proxy bugs that have turned up after a
comparison test between non-proxied and proxied JSON generation, mostly
relating to ballots registered by the Secretariat - Legacy-Id: 2740
---
ietf/idrfc/idrfc_wrapper.py | 17 ++-
ietf/idrfc/models.py | 1 +
ietf/idrfc/views_doc.py | 18 ++-
redesign/doc/models.py | 2 +-
redesign/doc/proxy.py | 70 +++++++++--
redesign/import-document-state.py | 200 +++++++++++++++++++++---------
redesign/proxy_utils.py | 24 ++++
7 files changed, 254 insertions(+), 78 deletions(-)
diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py
index 489ed1b88..146ad5aa4 100644
--- a/ietf/idrfc/idrfc_wrapper.py
+++ b/ietf/idrfc/idrfc_wrapper.py
@@ -406,7 +406,7 @@ class IetfProcessData:
# don't call this unless has_[active_]iesg_ballot returns True
def iesg_ballot_needed( self ):
standardsTrack = 'Standard' in self.intended_maturity_level() or \
- self.intended_maturity_level() == "BCP"
+ self.intended_maturity_level() in ("BCP", "Best Current Practice")
return self.iesg_ballot().ballot.needed( standardsTrack )
def ad_name(self):
@@ -424,6 +424,17 @@ class IetfProcessData:
def state_date(self):
try:
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ return self._idinternal.event_set.filter(
+ Q(desc__istartswith="Draft Added by ")|
+ Q(desc__istartswith="Draft Added in state ")|
+ Q(desc__istartswith="Draft added in state ")|
+ Q(desc__istartswith="State changed to ")|
+ Q(desc__istartswith="State Changes to ")|
+ Q(desc__istartswith="Sub state has been changed to ")|
+ Q(desc__istartswith="State has been changed to ")|
+ Q(desc__istartswith="IESG has approved and state has been changed to")).order_by('-time')[0].time.date()
+
return self._idinternal.comments().filter(
Q(comment_text__istartswith="Draft Added by ")|
Q(comment_text__istartswith="Draft Added in state ")|
@@ -625,8 +636,8 @@ class BallotWrapper:
positions = []
seen = {}
- for pos in self.ballot.event_set.filter(type="changed_ballot_position").select_related('pos', 'ad').order_by("-time", '-id'):
- pos = pos.ballotposition
+ from doc.models import BallotPosition
+ for pos in BallotPosition.objects.filter(doc=self.ballot, type="changed_ballot_position").select_related('ad').order_by("-time", '-id'):
if pos.ad not in seen:
p = dict(ad_name=pos.ad.get_name(),
ad_username="", # FIXME: don't seem to have username at the moment
diff --git a/ietf/idrfc/models.py b/ietf/idrfc/models.py
index 514b4a09d..c6fb882f0 100644
--- a/ietf/idrfc/models.py
+++ b/ietf/idrfc/models.py
@@ -100,4 +100,5 @@ class DraftVersions(models.Model):
from django.conf import settings
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ RfcIndexOld = RfcIndex
from redesign.doc.proxy import RfcIndex
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index 4e9c32439..939198b2b 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -150,7 +150,7 @@ def _get_history(doc, versions):
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.event_set.all().select_related('by').order_by('-time'):
+ for e in event_holder.event_set.all().select_related('by').order_by('-time', '-id'):
info = {}
if e.type == "new_revision":
filename = u"%s-%s" % (e.doc.name, e.newrevision.rev)
@@ -166,6 +166,9 @@ def _get_history(doc, versions):
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"]
@@ -236,6 +239,19 @@ def _get_versions(draft, include_replaced=True):
def get_ballot(name):
r = re.compile("^rfc([1-9][0-9]*)$")
m = r.match(name)
+
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ from doc.models import DocAlias
+ alias = get_object_or_404(DocAlias, name=name)
+ d = get_object_or_404(InternetDraft, name=alias.document.name)
+ try:
+ if not d.ballot.ballot_issued:
+ raise Http404
+ except BallotInfo.DoesNotExist:
+ raise Http404
+
+ return (BallotWrapper(d), RfcWrapper(d) if m else IdWrapper(d))
+
if m:
rfc_number = int(m.group(1))
rfci = get_object_or_404(RfcIndex, rfc_number=rfc_number)
diff --git a/redesign/doc/models.py b/redesign/doc/models.py
index ae9eef405..de14f3054 100644
--- a/redesign/doc/models.py
+++ b/redesign/doc/models.py
@@ -46,7 +46,7 @@ class DocumentInfo(models.Model):
while d.latest_event(Status, type="xyz") returns a Status
event."""
model = args[0] if args else Event
- e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time')[:1]
+ e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time', '-id')[:1]
return e[0] if e else None
class RelatedDocument(models.Model):
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index 64c1f6123..adb89f0bb 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -10,8 +10,10 @@ import glob, os
class InternetDraft(Document):
objects = TranslatingManager(dict(filename="name",
id_document_tag="id",
- status="state",
- rfc_number=lambda v: ("docalias__name", "rfc%s" % v)))
+ status=lambda v: ("state", { 1: 'active', 2: 'expired', 3: 'rfc', 4: 'auth-rm', 5: 'repl', 6: 'ietf-rm'}),
+ job_owner="ad",
+ rfc_number=lambda v: ("docalias__name", "rfc%s" % v),
+ ))
DAYS_TO_EXPIRE=185
@@ -94,7 +96,7 @@ class InternetDraft(Document):
@property
def lc_sent_date(self):
e = self.latest_event(type="sent_last_call")
- return e.time if e else None
+ return e.time.date() if e else None
#lc_changes = models.CharField(max_length=3) # used in DB, unused in Django code?
@@ -108,7 +110,7 @@ class InternetDraft(Document):
@property
def b_sent_date(self):
e = self.latest_event(type="sent_ballot_announcement")
- return e.time if e else None
+ return e.time.date() if e else None
#b_discussion_date = models.DateField(null=True, blank=True) # unused
@@ -142,7 +144,7 @@ class InternetDraft(Document):
#replaces = FKAsOneToOne('replaces', reverse=True)
@property
def replaces(self):
- r = self.replaces_set()
+ r = self.replaces_set
return r[0] if r else None
@property
@@ -242,7 +244,12 @@ class InternetDraft(Document):
#ballot = models.ForeignKey(BallotInfo, related_name='drafts', db_column="ballot_id")
@property
def ballot(self):
- return self # FIXME: raise BallotInfo.DoesNotExist?
+ if not self.idinternal:
+ raise BallotInfo.DoesNotExist()
+ return self
+ @property
+ def ballot_id(self):
+ return self.ballot.name
#primary_flag = models.IntegerField(blank=True, null=True)
@property
@@ -402,7 +409,7 @@ class InternetDraft(Document):
return self
def comments(self):
- return self.event_set.all().order_by('-time')
+ return DocumentComment.objects.filter(doc=self).order_by('-time')
def ballot_set(self):
return [self]
@@ -579,22 +586,22 @@ class InternetDraft(Document):
#updates = models.CharField(max_length=200,blank=True,null=True)
@property
def updates(self):
- return ",".join(sorted("RFC%s" % d.rfc_number for d in InternetDraft.objects.filter(docalias__relateddocument__document=self, docalias__relateddocument__relationship="updates")))
+ return ",".join("RFC%s" % n for n in sorted(d.rfc_number for d in InternetDraft.objects.filter(docalias__relateddocument__document=self, docalias__relateddocument__relationship="updates")))
#updated_by = models.CharField(max_length=200,blank=True,null=True)
@property
def updated_by(self):
- return ",".join(sorted("RFC%s" % d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="updates")))
+ return ",".join("RFC%s" % n for n in sorted(d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="updates")))
#obsoletes = models.CharField(max_length=200,blank=True,null=True)
@property
def obsoletes(self):
- return ",".join(sorted("RFC%s" % d.rfc_number for d in InternetDraft.objects.filter(docalias__relateddocument__document=self, docalias__relateddocument__relationship="obs")))
+ return ",".join("RFC%s" % n for n in sorted(d.rfc_number for d in InternetDraft.objects.filter(docalias__relateddocument__document=self, docalias__relateddocument__relationship="obs")))
#obsoleted_by = models.CharField(max_length=200,blank=True,null=True)
@property
def obsoleted_by(self):
- return ",".join(sorted("RFC%s" % d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="obs")))
+ return ",".join("RFC%s" % n for n in sorted(d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="obs")))
#also = models.CharField(max_length=50,blank=True,null=True)
@property
@@ -655,3 +662,44 @@ class IDAuthor(DocumentAuthor):
class Meta:
proxy = True
+class DocumentComment(Event):
+ objects = TranslatingManager(dict(comment_text="desc",
+ ))
+
+ BALLOT_DISCUSS = 1
+ BALLOT_COMMENT = 2
+ BALLOT_CHOICES = (
+ (BALLOT_DISCUSS, 'discuss'),
+ (BALLOT_COMMENT, 'comment'),
+ )
+ #document = models.ForeignKey(IDInternal)
+ @property
+ def document(self):
+ return self.doc
+ #rfc_flag = models.IntegerField(null=True, blank=True)
+ #public_flag = models.BooleanField() #unused
+ #date = models.DateField(db_column='comment_date', default=datetime.date.today)
+ @property
+ def date(self):
+ return self.time.date()
+ #time = models.CharField(db_column='comment_time', max_length=20, default=lambda: datetime.datetime.now().strftime("%H:%M:%S"))
+ #version = models.CharField(blank=True, max_length=3)
+ #comment_text = models.TextField(blank=True)
+ #created_by = BrokenForeignKey(IESGLogin, db_column='created_by', null=True, null_values=(0, 999))
+ #result_state = BrokenForeignKey(IDState, db_column='result_state', null=True, related_name="comments_leading_to_state", null_values=(0, 99))
+ #origin_state = models.ForeignKey(IDState, db_column='origin_state', null=True, related_name="comments_coming_from_state")
+ #ballot = models.IntegerField(null=True, choices=BALLOT_CHOICES)
+ def get_absolute_url(self):
+ return "/idtracker/%d/comment/%d/" % (self.doc.name, self.id)
+ def get_author(self):
+ return unicode(self.by)
+ def get_username(self):
+ return unicode(self.by)
+ def get_fullname(self):
+ return unicode(self.by)
+ def datetime(self):
+ return self.time
+
+ class Meta:
+ proxy = True
+
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 12eb5d155..8c1276ea7 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -23,6 +23,14 @@ document_name_to_import = None
if len(sys.argv) > 1:
document_name_to_import = sys.argv[1]
+# prevent memory from leaking when settings.DEBUG=True
+from django.db import connection
+class DummyQueries(object):
+ def append(self, x):
+ pass
+connection.queries = DummyQueries()
+
+
# assumptions:
# - groups have been imported
# - IESG login emails/roles have been imported
@@ -207,6 +215,10 @@ def iesg_login_to_email(l):
print "MISSING IESG LOGIN", l.person.email()
return None
+def iesg_login_is_secretary(l):
+ # Amy has two users, for some reason
+ return l.user_level == IESGLogin.SECRETARIAT_LEVEL or l.first_name == "Amy" and l.last_name == "Vezza"
+
# regexps for parsing document comments
date_re_str = "(?P[0-9][0-9][0-9][0-9])-(?P[0-9][0-9]?)-(?P[0-9][0-9]?)"
@@ -237,6 +249,7 @@ re_intended_status_changed = re.compile(r"Intended [sS]tatus has been changed to
re_state_change_notice = re.compile(r"State Change Notice email list (have been change|has been changed) ()?")
re_area_acronym_changed = re.compile(r"Area acronymn? has been changed to \w+ from \w+()?")
+re_comment_discuss_by_tag = re.compile(r" by [\w-]+ [\w-]+$")
def import_from_idinternal(d, idinternal):
d.time = idinternal.event_date
@@ -244,12 +257,14 @@ def import_from_idinternal(d, idinternal):
d.ad = iesg_login_to_email(idinternal.job_owner)
d.notify = idinternal.state_change_notice_to or ""
d.note = idinternal.note or ""
+ d.note = d.note.replace(' ', '\n').strip().replace('\n', ' ')
d.save()
# extract events
last_note_change_text = ""
-
- for c in DocumentComment.objects.filter(document=idinternal.draft_id).order_by('date', 'time', 'id'):
+
+ document_comments = DocumentComment.objects.filter(document=idinternal.draft_id).order_by('date', 'time', 'id')
+ for c in document_comments:
handled = False
# telechat agenda schedulings
@@ -269,21 +284,24 @@ def import_from_idinternal(d, idinternal):
e = Event()
e.type = "sent_ballot_announcement"
save_event(d, e, c)
-
- # when you issue a ballot, you also vote yes; add that vote
- e = BallotPosition()
- e.type = "changed_ballot_position"
- e.ad = iesg_login_to_email(c.created_by)
- e.desc = "[Ballot Position Update] New position, Yes, has been recorded by %s" % e.ad.get_name()
- last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
- e.pos = ballot_position_mapping["Yes"]
- e.discuss = last_pos.ballotposition.discuss if last_pos else ""
- e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
- e.comment = last_pos.ballotposition.comment if last_pos else ""
- e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
- save_event(d, e, c)
handled = True
+ ad = iesg_login_to_email(c.created_by)
+ last_pos = d.latest_event(BallotPosition, type="changed_ballot_position", ad=ad)
+ if not last_pos and not iesg_login_is_secretary(c.created_by):
+ # when you issue a ballot, you also vote yes; add that vote
+ e = BallotPosition()
+ e.type = "changed_ballot_position"
+ e.ad = ad
+ e.desc = "[Ballot Position Update] New position, Yes, has been recorded by %s" % e.ad.get_name()
+
+ e.pos = ballot_position_mapping["Yes"]
+ e.discuss = last_pos.discuss if last_pos else ""
+ e.discuss_time = last_pos.discuss_time if last_pos else None
+ e.comment = last_pos.comment if last_pos else ""
+ e.comment_time = last_pos.comment_time if last_pos else None
+ save_event(d, e, c)
+
# ballot positions
match = re_ballot_position.search(c.comment_text)
if match:
@@ -291,7 +309,7 @@ def import_from_idinternal(d, idinternal):
ad_name = match.group('for') or match.group('for2') or match.group('by') # some of the old positions don't specify who it's for, in that case assume it's "by", the person who entered the position
ad_first, ad_last = ad_name.split(' ')
login = IESGLogin.objects.filter(first_name=ad_first, last_name=ad_last).order_by('user_level')[0]
- if login.user_level == IESGLogin.SECRETARIAT_LEVEL:
+ if iesg_login_is_secretary(login):
# now we're in trouble, a secretariat person isn't an
# AD, instead try to find a position object that
# matches and that we haven't taken yet
@@ -307,12 +325,33 @@ def import_from_idinternal(d, idinternal):
elif position.slug == "discuss":
positions = positions.filter(models.Q(discuss=1)|models.Q(discuss=2))
assert position.slug != "norecord"
-
+
+ found = False
for p in positions:
if not d.event_set.filter(type="changed_ballot_position", ballotposition__pos=position, ballotposition__ad=iesg_login_to_email(p.ad)):
login = p.ad
+ found = True
break
+ if not found:
+ # in even more trouble, we can try and see if it
+ # belongs to a nearby discuss
+ if position.slug == "discuss":
+ index_c = list(document_comments).index(c)
+ start = c.datetime()
+ end = c.datetime() + datetime.timedelta(seconds=30 * 60)
+ for i, x in enumerate(document_comments):
+ if (x.ballot == DocumentComment.BALLOT_DISCUSS
+ and (c.datetime() <= x.datetime() <= end
+ or abs(index_c - i) <= 2)
+ and not iesg_login_is_secretary(x.created_by)):
+ login = x.created_by
+ found = True
+
+ if not found:
+ print "BALLOT BY SECRETARIAT", login
+
+
e = BallotPosition()
e.type = "changed_ballot_position"
e.ad = iesg_login_to_email(login)
@@ -320,6 +359,10 @@ def import_from_idinternal(d, idinternal):
e.pos = position
e.discuss = last_pos.discuss if last_pos else ""
e.discuss_time = last_pos.discuss_time if last_pos else None
+ if e.pos_id == "discuss" and not e.discuss_time:
+ # in a few cases, we don't have the discuss
+ # text/time, fudge the time so it's not null
+ e.discuss_time = c.datetime()
e.comment = last_pos.comment if last_pos else ""
e.comment_time = last_pos.comment_time if last_pos else None
save_event(d, e, c)
@@ -330,23 +373,35 @@ def import_from_idinternal(d, idinternal):
e = BallotPosition()
e.type = "changed_ballot_position"
e.ad = iesg_login_to_email(c.created_by)
- last_pos = d.latest_event(type="changed_ballot_position", ballotposition__ad=e.ad)
- e.pos = last_pos.ballotposition.pos if last_pos else ballot_position_mapping[None]
+ last_pos = d.latest_event(BallotPosition, type="changed_ballot_position", ad=e.ad)
+ e.pos = last_pos.pos if last_pos else ballot_position_mapping[None]
+ c.comment_text = re_comment_discuss_by_tag.sub("", c.comment_text)
if c.ballot == DocumentComment.BALLOT_DISCUSS:
e.discuss = c.comment_text
e.discuss_time = c.datetime()
- e.comment = last_pos.ballotposition.comment if last_pos else ""
- e.comment_time = last_pos.ballotposition.comment_time if last_pos else None
+ e.comment = last_pos.comment if last_pos else ""
+ e.comment_time = last_pos.comment_time if last_pos else None
# put header into description
c.comment_text = "[Ballot discuss]\n" + c.comment_text
else:
- e.discuss = last_pos.ballotposition.discuss if last_pos else ""
- e.discuss_time = last_pos.ballotposition.discuss_time if last_pos else None
+ e.discuss = last_pos.discuss if last_pos else ""
+ e.discuss_time = last_pos.discuss_time if last_pos else None
+ if e.pos_id == "discuss" and not e.discuss_time:
+ # in a few cases, we don't have the discuss
+ # text/time, fudge the time so it's not null
+ e.discuss_time = c.datetime()
e.comment = c.comment_text
e.comment_time = c.datetime()
# put header into description
c.comment_text = "[Ballot comment]\n" + c.comment_text
- save_event(d, e, c)
+
+ # there are some bogus copies where a secretary has the
+ # same discuss comment as an AD, skip saving if this is
+ # one of those
+ if not (iesg_login_is_secretary(c.created_by)
+ and DocumentComment.objects.filter(ballot=c.ballot, document=c.document).exclude(created_by=c.created_by)):
+ save_event(d, e, c)
+
handled = True
# last call requested
@@ -564,10 +619,13 @@ def import_from_idinternal(d, idinternal):
position_date = made_up_date
# make sure we got all the positions
- existing = BallotPosition.objects.filter(doc=d, type="changed_ballot_position").order_by("-time")
+ existing = BallotPosition.objects.filter(doc=d, type="changed_ballot_position").order_by("-time", '-id')
for p in Position.objects.filter(ballot=ballot):
- found = False
+ # there are some bogus ones
+ if iesg_login_is_secretary(p.ad):
+ continue
+
ad = iesg_login_to_email(p.ad)
if p.noobj > 0:
pos = ballot_position_mapping["No Objection"]
@@ -581,6 +639,8 @@ def import_from_idinternal(d, idinternal):
pos = ballot_position_mapping["Discuss"]
else:
pos = ballot_position_mapping[None]
+
+ found = False
for x in existing:
if x.ad == ad and x.pos == pos:
found = True
@@ -597,6 +657,10 @@ def import_from_idinternal(d, idinternal):
e.pos = pos
e.discuss = last_pos.discuss if last_pos else ""
e.discuss_time = last_pos.discuss_time if last_pos else None
+ if e.pos_id == "discuss" and not e.discuss_time:
+ # in a few cases, we don't have the discuss
+ # text/time, fudge the time so it's not null
+ e.discuss_time = e.time
e.comment = last_pos.comment if last_pos else ""
e.comment_time = last_pos.comment_time if last_pos else None
if last_pos:
@@ -607,7 +671,7 @@ def import_from_idinternal(d, idinternal):
# make sure we got the ballot issued event
if ballot.ballot_issued and not d.event_set.filter(type="sent_ballot_announcement"):
- position = d.event_set.filter(type=("changed_ballot_position")).order_by('time')[:1]
+ position = d.event_set.filter(type=("changed_ballot_position")).order_by('time', 'id')[:1]
if position:
sent_date = position[0].time
else:
@@ -622,8 +686,8 @@ def import_from_idinternal(d, idinternal):
e.save()
# make sure the comments and discusses are updated
- positions = list(BallotPosition.objects.filter(doc=d).order_by("-time"))
- for c in IESGComment.objects.filter(ballot=idinternal.ballot):
+ positions = list(BallotPosition.objects.filter(doc=d).order_by("-time", '-id'))
+ for c in IESGComment.objects.filter(ballot=ballot):
ad = iesg_login_to_email(c.ad)
for p in positions:
if p.ad == ad:
@@ -633,7 +697,7 @@ def import_from_idinternal(d, idinternal):
p.save()
break
- for c in IESGDiscuss.objects.filter(ballot=idinternal.ballot):
+ for c in IESGDiscuss.objects.filter(ballot=ballot):
ad = iesg_login_to_email(c.ad)
for p in positions:
if p.ad == ad:
@@ -689,16 +753,13 @@ def import_from_idinternal(d, idinternal):
all_drafts = InternetDraft.objects.all().select_related()
if document_name_to_import:
- all_drafts = all_drafts.filter(filename=document_name_to_import)
+ if document_name_to_import.startswith("rfc"):
+ all_drafts = all_drafts.filter(rfc_number=document_name_to_import[3:])
+ else:
+ all_drafts = all_drafts.filter(filename=document_name_to_import)
#all_drafts = all_drafts[all_drafts.count() - 1000:]
+#all_drafts = all_drafts.none()
-# prevent memory from leaking from debug setting
-from django.db import connection
-class DummyQueries(object):
- def append(self, x):
- pass
-connection.queries = DummyQueries()
-
for index, o in enumerate(all_drafts.iterator()):
print "importing", o.filename, index
@@ -717,9 +778,9 @@ for index, o in enumerate(all_drafts.iterator()):
elif o.filename.startswith("draft-irtf-"):
d.stream = stream_mapping["IRTF"]
elif o.idinternal and o.idinternal.via_rfc_editor:
- d.stream = stream_mapping["INDEPENDENT"] # FIXME: correct?
+ d.stream = stream_mapping["INDEPENDENT"]
else:
- d.stream = stream_mapping["IETF"] # FIXME: correct?
+ d.stream = stream_mapping["IETF"]
d.wg_state = None
d.iesg_state = iesg_state_mapping[None]
d.iana_state = None
@@ -739,6 +800,10 @@ for index, o in enumerate(all_drafts.iterator()):
# make sure our alias is updated
d_alias = alias_doc(d.name, d)
+ # RFC alias
+ if o.rfc_number:
+ alias_doc("rfc%s" % o.rfc_number, d)
+
d.authors.clear()
for i, a in enumerate(o.authors.all().select_related("person").order_by('author_order', 'person')):
try:
@@ -807,13 +872,10 @@ for index, o in enumerate(all_drafts.iterator()):
# tags
sync_tag(d, o.review_by_rfc_editor, tag_review_by_rfc_editor)
sync_tag(d, o.expired_tombstone, tag_expired_tombstone)
-
- # RFC alias
- if o.rfc_number:
- alias_doc("rfc%s" % o.rfc_number, d)
-
+
+ # replacements
if o.replaced_by:
- replacement, _ = Document.objects.get_or_create(name=o.replaced_by.filename)
+ replacement, _ = Document.objects.get_or_create(name=o.replaced_by.filename, defaults=dict(time=datetime.datetime(1970, 1, 1, 0, 0, 0)))
RelatedDocument.objects.get_or_create(document=replacement, doc_alias=d_alias, relationship=relationship_replaces)
# the RFC-related attributes are imported when we handle the RFCs below
@@ -878,7 +940,7 @@ for index, o in enumerate(all_rfcs.iterator()):
if rfcs:
r = rfcs[0]
l = intended_std_level_mapping[r.intended_status.status]
- if l:
+ if l: # skip some bogus None values
d.intended_std_level = l
d.save()
@@ -901,9 +963,6 @@ for index, o in enumerate(all_rfcs.iterator()):
# import obsoletes/updates
def make_relation(other_rfc, rel_type, reverse):
- if other_rfc.startswith("NIC") or other_rfc.startswith("IEN") or other_rfc.startswith("STD") or other_rfc.startswith("RTR"):
- return # we currently have no good way of importing these
-
other_number = int(other_rfc.replace("RFC", ""))
other, other_alias = get_or_create_rfc_document(other_number)
if reverse:
@@ -911,18 +970,35 @@ for index, o in enumerate(all_rfcs.iterator()):
else:
RelatedDocument.objects.get_or_create(document=d, doc_alias=other_alias, relationship=rel_type)
- if o.obsoletes:
- for x in o.obsoletes.split(','):
- make_relation(x, relationship_obsoletes, False)
- if o.obsoleted_by:
- for x in o.obsoleted_by.split(','):
- make_relation(x, relationship_obsoletes, True)
- if o.updates:
- for x in o.updates.split(','):
- make_relation(x, relationship_updates, False)
- if o.updated_by:
- for x in o.updated_by.split(','):
- make_relation(x, relationship_updates, True)
+ def parse_relation_list(s):
+ if not s:
+ return []
+ res = []
+ for x in s.split(","):
+ if x[:3] in ("NIC", "IEN", "STD", "RTR"):
+ # try translating this to RFC numbers that we can
+ # handle sensibly; otherwise we'll have to ignore them
+ l = ["RFC%s" % y.rfc_number for y in RfcIndex.objects.filter(also=x).order_by('rfc_number')]
+ if l:
+ print "translated", x, "to", ", ".join(l)
+ for y in l:
+ if y not in res:
+ res.append(y)
+ else:
+ print "SKIPPED relation to", x
+ else:
+ res.append(x)
+ return res
+
+ RelatedDocument.objects.filter(document=d).delete()
+ for x in parse_relation_list(o.obsoletes):
+ make_relation(x, relationship_obsoletes, False)
+ for x in parse_relation_list(o.obsoleted_by):
+ make_relation(x, relationship_obsoletes, True)
+ for x in parse_relation_list(o.updates):
+ make_relation(x, relationship_updates, False)
+ for x in parse_relation_list(o.updated_by):
+ make_relation(x, relationship_updates, True)
if o.also:
alias_doc(o.also.lower(), d)
diff --git a/redesign/proxy_utils.py b/redesign/proxy_utils.py
index 7189b67dd..2f55c379b 100644
--- a/redesign/proxy_utils.py
+++ b/redesign/proxy_utils.py
@@ -2,6 +2,27 @@ from django.db.models.manager import Manager
from django.db.models.query import QuerySet
class TranslatingQuerySet(QuerySet):
+ def translated_args(self, args):
+ trans = self.translated_attrs
+ res = []
+ for a in args:
+ if a.startswith("-"):
+ prefix = "-"
+ a = a[1:]
+ else:
+ prefix = ""
+
+ if a in trans:
+ t = trans[a]
+ if callable(t):
+ t, _ = t(None)
+
+ if t:
+ res.append(prefix + t)
+ else:
+ res.append(prefix + a)
+ return res
+
def translated_kwargs(self, kwargs):
trans = self.translated_attrs
res = dict()
@@ -80,6 +101,7 @@ class TranslatingQuerySet(QuerySet):
return super(self.__class__, self).latest(*args, **kwargs)
def order_by(self, *args, **kwargs):
+ args = self.translated_args(args)
kwargs = self.translated_kwargs(kwargs)
return super(self.__class__, self).order_by(*args, **kwargs)
@@ -88,10 +110,12 @@ class TranslatingQuerySet(QuerySet):
return super(self.__class__, self).select_related(*args, **kwargs)
def values(self, *args, **kwargs):
+ args = self.translated_args(args)
kwargs = self.translated_kwargs(kwargs)
return super(self.__class__, self).values(*args, **kwargs)
def values_list(self, *args, **kwargs):
+ args = self.translated_args(args)
kwargs = self.translated_kwargs(kwargs)
return super(self.__class__, self).values_list(*args, **kwargs)
From 5c1fc8ac08d62419b58aa7e422530513fa2b9e13 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 13 Jan 2011 16:58:59 +0000
Subject: [PATCH 15/75] Always set state of RFC documents to 'RFC' -
Legacy-Id: 2741
---
redesign/import-document-state.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 8c1276ea7..849ca4c5a 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -931,6 +931,7 @@ for index, o in enumerate(all_rfcs.iterator()):
d.time = datetime.datetime.now()
d.title = o.title
d.std_level = std_level_mapping[o.current_status]
+ d.state = state_mapping['RFC']
d.stream = stream_mapping[o.stream]
if not d.group and o.wg:
d.group = Group.objects.get(acronym=o.wg)
From 145fa558d4f056e61da9bf2029eaeff3bbf97dcb Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 13 Jan 2011 17:54:47 +0000
Subject: [PATCH 16/75] Fix problem with RFC2604 and RFC3025 that share drafts
with other non-obsolete RFCs - Legacy-Id: 2742
---
redesign/import-document-state.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 849ca4c5a..a7be1e046 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -900,6 +900,12 @@ def get_or_create_rfc_document(rfc_number):
if ids:
draft = ids[0]
+ if rfc_number in (2604, 3025):
+ # prevent merge for some botched RFCs that are obsoleted by
+ # another RFC coming from the same draft, in practice this is
+ # just these two, so we hardcode rather than querying for it
+ draft = None
+
if draft:
name = draft.filename
From d7c6d28f2e585ad501b66079c85a2ec7941b51a2 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 21 Jan 2011 20:37:09 +0000
Subject: [PATCH 17/75] Add comment to history for multi-document ballots to
compensate for the lack of support for those in the new schema - Legacy-Id:
2755
---
ietf/idrfc/views_doc.py | 7 +++++++
redesign/import-document-state.py | 11 ++++++++++-
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index 939198b2b..a1966fd27 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -41,6 +41,7 @@ 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
from ietf import settings
from ietf.idtracker.models import InternetDraft, IDInternal, BallotInfo, DocumentComment
@@ -158,6 +159,12 @@ def _get_history(doc, versions):
if int(e.newrevision.rev) != 0:
e.desc += ' (diff from -%02d)' % (filename, int(e.newrevision.rev) - 1)
info["dontmolest"] = True
+
+ multiset_ballot_text = "This was part of a ballot set with: "
+ if e.desc.startswith(multiset_ballot_text):
+ names = 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.get_name()
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index a7be1e046..70c785922 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -739,6 +739,15 @@ def import_from_idinternal(d, idinternal):
e.desc = "Ballot writeup text was added"
e.save()
+ ballot_set = idinternal.ballot_set()
+ if len(ballot_set) > 1:
+ others = sorted(b.draft.filename for b in ballot_set if b != idinternal)
+ desc = u"This was part of a ballot set with: %s" % ",".join(others)
+ e, _ = Event.objects.get_or_create(type="added_comment", doc=d, desc=desc)
+ e.time = made_up_date
+ e.by = system_email
+ e.save()
+
# fix tags
sync_tag(d, idinternal.via_rfc_editor, tag_via_rfc_editor)
@@ -759,7 +768,7 @@ if document_name_to_import:
all_drafts = all_drafts.filter(filename=document_name_to_import)
#all_drafts = all_drafts[all_drafts.count() - 1000:]
#all_drafts = all_drafts.none()
-
+
for index, o in enumerate(all_drafts.iterator()):
print "importing", o.filename, index
From 19b572b285abce12af0fae9dc67b41ceffb3f09d Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 24 Jan 2011 20:20:50 +0000
Subject: [PATCH 18/75] Fix problem with multiple ballot rounds on a draft that
later becomes an RFC - Legacy-Id: 2759
---
ietf/idrfc/idrfc_wrapper.py | 2 +-
ietf/idrfc/views_doc.py | 1 +
redesign/doc/proxy.py | 28 +++++++++++++++++++++++++++-
redesign/import-document-state.py | 9 +++++++--
4 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py
index 146ad5aa4..8d3beb75c 100644
--- a/ietf/idrfc/idrfc_wrapper.py
+++ b/ietf/idrfc/idrfc_wrapper.py
@@ -637,7 +637,7 @@ class BallotWrapper:
seen = {}
from doc.models import BallotPosition
- for pos in BallotPosition.objects.filter(doc=self.ballot, type="changed_ballot_position").select_related('ad').order_by("-time", '-id'):
+ for pos in BallotPosition.objects.filter(doc=self.ballot, type="changed_ballot_position", time__gte=self.ballot.process_start, time__lte=self.ballot.process_end).select_related('ad').order_by("-time", '-id'):
if pos.ad not in seen:
p = dict(ad_name=pos.ad.get_name(),
ad_username="", # FIXME: don't seem to have username at the moment
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index a1966fd27..15cdc20fc 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -77,6 +77,7 @@ def _get_html(key, filename):
def document_main_rfc(request, rfc_number):
rfci = get_object_or_404(RfcIndex, rfc_number=rfc_number)
+ rfci.viewing_as_rfc = True
doc = RfcWrapper(rfci)
info = {}
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index adb89f0bb..619952981 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -160,7 +160,33 @@ class InternetDraft(Document):
@property
def expired_tombstone(self):
return bool(self.tags.filter(slug='exp-tomb'))
-
+
+ def calc_process_start_end(self):
+ import datetime
+ start, end = datetime.datetime.min, datetime.datetime.max
+ e = self.ballot.latest_event(type="started_iesg_process")
+ if e:
+ start = e.time
+ if self.ballot.state_id == "rfc" and self.ballot.name.startswith("draft") and not hasattr(self.ballot, "viewing_as_rfc"):
+ previous_process = self.ballot.latest_event(type="started_iesg_process", time__lt=e.time)
+ if previous_process:
+ start = previous_process.time
+ end = e.time
+ self._process_start = start
+ self._process_end = end
+
+ @property
+ def process_start(self):
+ if not hasattr(self, "_process_start"):
+ self.calc_process_start_end()
+ return self._process_start
+
+ @property
+ def process_end(self):
+ if not hasattr(self, "_process_end"):
+ self.calc_process_start_end()
+ return self._process_end
+
#idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
@property
def idinternal(self):
diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py
index 70c785922..33a2fae54 100755
--- a/redesign/import-document-state.py
+++ b/redesign/import-document-state.py
@@ -262,6 +262,7 @@ def import_from_idinternal(d, idinternal):
# extract events
last_note_change_text = ""
+ started_iesg_process = ""
document_comments = DocumentComment.objects.filter(document=idinternal.draft_id).order_by('date', 'time', 'id')
for c in document_comments:
@@ -431,8 +432,12 @@ def import_from_idinternal(d, idinternal):
# draft added
match = re_draft_added.search(c.comment_text)
if match:
- e = Event(type="started_iesg_process")
- save_event(d, e, c)
+ # watch out for extraneous starts, the old data contains
+ # some phony ones
+ if not started_iesg_process:
+ started_iesg_process = c.comment_text
+ e = Event(type="started_iesg_process")
+ save_event(d, e, c)
handled = True
# new version
From 9f1bd35ca90ed26a96eaa55602cea4a0097ee3c3 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 1 Feb 2011 10:59:20 +0000
Subject: [PATCH 19/75] Rewrite search query building to support new schema,
extend proxy support to search pages, fix a couple of bugs - Legacy-Id: 2780
---
ietf/idrfc/idrfc_wrapper.py | 12 +-
ietf/idrfc/templatetags/ballot_icon.py | 5 +
ietf/idrfc/views_ballot.py | 2 +-
ietf/idrfc/views_doc.py | 4 +-
ietf/idrfc/views_edit.py | 2 +-
ietf/idrfc/views_search.py | 219 ++++++++++++++++++++++++-
redesign/doc/proxy.py | 104 ++++++++++--
redesign/group/proxy.py | 4 +-
redesign/person/proxy.py | 4 +
9 files changed, 322 insertions(+), 34 deletions(-)
diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py
index 8d3beb75c..7049ec0aa 100644
--- a/ietf/idrfc/idrfc_wrapper.py
+++ b/ietf/idrfc/idrfc_wrapper.py
@@ -262,9 +262,9 @@ class RfcWrapper:
if not self._idinternal:
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
- pub = rfcindex.latest_event(type="published_rfc")
- started = rfcindex.latest_event(type="started_iesg_process")
- if pub and started and pub.time < started.time:
+ pub = rfcindex.rfc_published_date
+ started = rfcindex.started_iesg_process if hasattr(rfcindex, 'started_iesg_process') else rfcindex.latest_event(type="started_iesg_process")
+ if pub and started and pub < started.time.date():
self._idinternal = rfcindex
else:
try:
@@ -282,7 +282,9 @@ class RfcWrapper:
if not self.maturity_level:
self.maturity_level = "Unknown"
- if settings.USE_DB_REDESIGN_PROXY_CLASSES and rfcindex.filename.startswith('rfc'):
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ if not rfcindex.name.startswith('rfc'):
+ self.draft_name = rfcindex.name
return # we've already done the lookup while importing so skip the rest
ids = InternetDraft.objects.filter(rfc_number=self.rfc_number)
@@ -779,7 +781,7 @@ def position_to_string(position):
return "No Record"
p = None
for k,v in positions.iteritems():
- if position.__dict__[k] > 0:
+ if getattr(position, k) > 0:
p = v
if not p:
p = "No Record"
diff --git a/ietf/idrfc/templatetags/ballot_icon.py b/ietf/idrfc/templatetags/ballot_icon.py
index e75e265fd..6a03415eb 100644
--- a/ietf/idrfc/templatetags/ballot_icon.py
+++ b/ietf/idrfc/templatetags/ballot_icon.py
@@ -40,6 +40,11 @@ register = template.Library()
def get_user_adid(context):
if 'user' in context and in_group(context['user'], "Area_Director"):
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ # with the new design, we need the email address (FIXME:
+ # we shouldn't go through the old classes though,
+ # IESGLogin needs revamping)
+ return context['user'].get_profile().person().email()[1]
return context['user'].get_profile().iesg_login_id()
else:
return None
diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py
index 2112530b9..50e67e019 100644
--- a/ietf/idrfc/views_ballot.py
+++ b/ietf/idrfc/views_ballot.py
@@ -10,8 +10,8 @@ from django.template.loader import render_to_string
from django.template import RequestContext
from django import forms
from django.utils.html import strip_tags
+from django.conf import settings
-from ietf import settings
from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index 15cdc20fc..b0269b57d 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -42,8 +42,8 @@ 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
-
-from ietf import settings
+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
diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py
index 282388317..42f036c56 100644
--- a/ietf/idrfc/views_edit.py
+++ b/ietf/idrfc/views_edit.py
@@ -11,8 +11,8 @@ from django.template import RequestContext
from django import forms
from django.utils.html import strip_tags
from django.db.models import Max
+from django.conf import settings
-from ietf import settings
from ietf.utils.mail import send_mail_text
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py
index 32c4dd1a8..e650ab2a1 100644
--- a/ietf/idrfc/views_search.py
+++ b/ietf/idrfc/views_search.py
@@ -41,6 +41,7 @@ from ietf.idrfc.models import RfcIndex
from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect
from ietf.idrfc.idrfc_wrapper import IdWrapper,RfcWrapper,IdRfcWrapper
from ietf.utils import normalize_draftname
+from django.conf import settings
class SearchForm(forms.Form):
name = forms.CharField(required=False)
@@ -252,6 +253,206 @@ def search_query(query_original):
meta['advanced'] = True
return (results,meta)
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ from doc.models import *
+ from person.models import *
+ from group.models import *
+
+ 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)
+
+ 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)
+ # FIXME: state needs a sort
+ state = forms.ModelChoiceField(IesgDocStateName.objects.all(), 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(Email.objects.filter(role__name="ad",
+ role__group__type="area",
+ role__group__state="active")
+ .select_related('person'))
+ inactive_ads = list(Email.objects.filter(pk__in=responsible)
+ .exclude(pk__in=[x.pk for x in active_ads])
+ .select_related('person'))
+ extract_last_name = lambda x: x.get_name().split(' ')[-1]
+ active_ads.sort(key=extract_last_name)
+ inactive_ads.sort(key=extract_last_name)
+
+ # FIXME: -99
+ self.fields['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.get_name()) for ad in active_ads] + [('-99', '------------------')] + [(ad.pk, ad.get_name()) for ad in inactive_ads]
+ self.fields['subState'].choices = [('', 'any substate'), ('0', 'no substate')] + [(n.slug, n.name) for n in DocInfoTagName.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
+ for k in ('author','group','area','ad'):
+ if (q['by'] == k) and not q[k]:
+ q['by'] = None
+ if (q['by'] == 'state') and 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):
+ 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 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 = []
+ disallowed = []
+
+ def add(allow, states):
+ l = allowed if allow else disallowed
+ l.extend(states)
+
+ add(query["rfcs"], ['rfc'])
+ add(query["activeDrafts"], ['active'])
+ add(query["oldDrafts"], ['repl', 'expired', 'auth-rm', 'ietf-rm'])
+
+ docs = docs.filter(state__in=allowed).exclude(state__in=disallowed)
+
+ # 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__parent=query["area"]) |
+ Q(ad__role__name="ad",
+ ad__role__group=query["area"]))
+ elif by == "ad":
+ docs = docs.filter(ad=query["ad"])
+ elif by == "state":
+ if query["state"]:
+ docs = docs.filter(iesg_state=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("state", "iesg_state", "ad", "ad__person", "std_level", "intended_std_level", "group")[: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:
+ r.canonical_name = rfc_aliases[r.pk]
+ else:
+ r.canonical_name = 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 Event.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(doc_alias__name__in=rfc_aliases.values(), relationship__in=("obs", "updates")).select_related('doc_alias__document_id')
+ rel_rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[rel.document_id for rel in xed_by]).values_list('document_id', 'name'))
+ for rel in xed_by:
+ r = result_map[rel.doc_alias.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.document_id][3:]))
+
+
+ # sort
+ def sort_key(d):
+ if d.canonical_name.startswith('rfc'):
+ return (2, "%06d" % int(d.canonical_name[3:]))
+ elif d.state_id == "active":
+ return (1, d.canonical_name)
+ else:
+ return (3, d.canonical_name)
+
+ 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)
+
+
def search_results(request):
if len(request.REQUEST.items()) == 0:
return search_main(request)
@@ -288,6 +489,8 @@ def by_ad(request, name):
break
if not ad_id:
raise Http404
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ ad_id = i.person.email()[1]
form = SearchForm({'by':'ad','ad':ad_id,
'rfcs':'on', 'activeDrafts':'on', 'oldDrafts':'on'})
if not form.is_valid():
@@ -298,11 +501,17 @@ def by_ad(request, name):
@cache_page(15*60) # 15 minutes
def all(request):
- active = InternetDraft.objects.all().filter(status=1).order_by("filename").values('filename')
- rfc1 = InternetDraft.objects.all().filter(status=3).order_by("filename").values('filename','rfc_number')
- rfc_numbers1 = InternetDraft.objects.all().filter(status=3).values_list('rfc_number', flat=True)
- rfc2 = RfcIndex.objects.all().exclude(rfc_number__in=rfc_numbers1).order_by('rfc_number').values('rfc_number','draft')
- dead = InternetDraft.objects.all().exclude(status__in=[1,3]).order_by("filename").select_related('status__status')
+ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ active = (dict(filename=n) for n in InternetDraft.objects.filter(state="active").order_by("name").values_list('name', flat=True))
+ rfc1 = (dict(filename=d, rfc_number=int(n[3:])) for d, n in DocAlias.objects.filter(document__state="rfc", name__startswith="rfc").exclude(document__name__startswith="rfc").order_by("document__name").values_list('document__name','name').distinct())
+ rfc2 = (dict(rfc_number=r, draft=None) for r in sorted(int(n[3:]) for n in Document.objects.filter(name__startswith="rfc").values_list('name', flat=True)))
+ dead = InternetDraft.objects.exclude(state__in=("active", "rfc")).select_related("state").order_by("name")
+ else:
+ active = InternetDraft.objects.all().filter(status=1).order_by("filename").values('filename')
+ rfc1 = InternetDraft.objects.all().filter(status=3).order_by("filename").values('filename','rfc_number')
+ rfc_numbers1 = InternetDraft.objects.all().filter(status=3).values_list('rfc_number', flat=True)
+ rfc2 = RfcIndex.objects.all().exclude(rfc_number__in=rfc_numbers1).order_by('rfc_number').values('rfc_number','draft')
+ dead = InternetDraft.objects.all().exclude(status__in=[1,3]).order_by("filename").select_related('status__status')
return render_to_response('idrfc/all.html', {'active':active, 'rfc1':rfc1, 'rfc2':rfc2, 'dead':dead}, context_instance=RequestContext(request))
@cache_page(15*60) # 15 minutes
diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py
index 619952981..7f984aeb0 100644
--- a/redesign/doc/proxy.py
+++ b/redesign/doc/proxy.py
@@ -10,7 +10,7 @@ import glob, os
class InternetDraft(Document):
objects = TranslatingManager(dict(filename="name",
id_document_tag="id",
- status=lambda v: ("state", { 1: 'active', 2: 'expired', 3: 'rfc', 4: 'auth-rm', 5: 'repl', 6: 'ietf-rm'}),
+ status=lambda v: ("state", { 1: 'active', 2: 'expired', 3: 'rfc', 4: 'auth-rm', 5: 'repl', 6: 'ietf-rm'}[v]),
job_owner="ad",
rfc_number=lambda v: ("docalias__name", "rfc%s" % v),
))
@@ -32,7 +32,8 @@ class InternetDraft(Document):
@property
def group(self):
from group.proxy import Acronym as AcronymProxy
- return AcronymProxy(super(self.__class__, self).group)
+ g = super(self.__class__, self).group
+ return AcronymProxy(g) if g else None
#filename = models.CharField(max_length=255, unique=True)
@property
def filename(self):
@@ -44,7 +45,10 @@ class InternetDraft(Document):
#revision_date = models.DateField()
@property
def revision_date(self):
- e = self.latest_event(type="new_revision")
+ if hasattr(self, "new_revision"):
+ e = self.new_revision
+ else:
+ e = self.latest_event(type="new_revision")
return e.time.date() if e else None
# helper function
def get_file_type_matches_from(self, glob_path):
@@ -80,11 +84,10 @@ class InternetDraft(Document):
@property
def status(self):
from redesign.name.proxy import IDStatus
- return IDStatus(self.state)
+ return IDStatus(self.state) if self.state else None
@property
def status_id(self):
- from redesign.name.proxy import IDStatus
return { 'active': 1, 'repl': 5, 'expired': 2, 'rfc': 3, 'auth-rm': 4, 'ietf-rm': 6 }[self.state_id]
#intended_status = models.ForeignKey(IDIntendedStatus)
@@ -125,6 +128,10 @@ class InternetDraft(Document):
#rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
@property
def rfc_number(self):
+ # simple optimization for search results
+ if hasattr(self, "canonical_name"):
+ return int(self.canonical_name[3:]) if self.canonical_name.startswith('rfc') else None
+
aliases = self.docalias_set.filter(name__startswith="rfc")
return int(aliases[0].name[3:]) if aliases else None
@@ -192,7 +199,11 @@ class InternetDraft(Document):
def idinternal(self):
# since IDInternal is now merged into the document, we try to
# guess here
- return self if self.iesg_state or self.latest_event(type="changed_ballot_position") else None
+ if hasattr(self, "changed_ballot_position"):
+ e = self.changed_ballot_position
+ else:
+ e = self.latest_event(type="changed_ballot_position")
+ return self if self.iesg_state or e else None
# reverse relationship
@property
@@ -216,7 +227,7 @@ class InternetDraft(Document):
return "%02d" % r
def expiration(self):
e = self.latest_event(type__in=("completed_resurrect", "new_revision"))
- return e.time.date() + datetime.timedelta(self.DAYS_TO_EXPIRE)
+ return e.time.date() + datetime.timedelta(days=self.DAYS_TO_EXPIRE)
def can_expire(self):
# Copying the logic from expire-ids-1 without thinking
# much about it.
@@ -315,6 +326,11 @@ class InternetDraft(Document):
def cur_state(self):
return self.iesg_state
+ @property
+ def cur_state_id(self):
+ # FIXME: results in wrong sort order
+ return abs(hash(self.iesg_state.slug))
+
#prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
@property
def prev_state(self):
@@ -334,7 +350,7 @@ class InternetDraft(Document):
@property
def job_owner(self):
from person.proxy import IESGLogin as IESGLoginProxy
- return IESGLoginProxy(self.ad)
+ return IESGLoginProxy(self.ad) if self.ad else None
#event_date = models.DateField(null=True)
@property
@@ -528,14 +544,14 @@ class InternetDraft(Document):
res = []
def add(ad, pos):
- # FIXME: ad and pos don't emulate old interface
- res.append(dict(ad=ad, pos=pos))
+ from person.proxy import IESGLogin as IESGLoginProxy
+ res.append(dict(ad=IESGLoginProxy(ad), pos=Position(pos) if pos else None))
found = set()
- for pos in self.event_set.filter(type="changed_ballot_position", ballotposition__ad__in=active_ads).select_related('ad').order_by("-time"):
- if not pos.ballotposition.ad in found:
- found.add(pos.ballotposition.ad)
- add(pos.ballotposition.ad, pos)
+ for pos in BallotPosition.objects.filter(doc=self, type="changed_ballot_position", ad__in=active_ads).select_related('ad').order_by("-time", "-id"):
+ if pos.ad not in found:
+ found.add(pos.ad)
+ add(pos.ad, pos)
for ad in active_ads:
if ad not in found:
@@ -553,7 +569,7 @@ class InternetDraft(Document):
yes = noobj = discuss = recuse = 0
for position in positions:
- p = position.ballotposition.pos_id
+ p = position.pos_id
if p == "yes":
yes += 1
if p == "noobj":
@@ -601,7 +617,10 @@ class InternetDraft(Document):
#rfc_published_date = models.DateField()
@property
def rfc_published_date(self):
- e = self.latest_event(type="published_rfc")
+ if hasattr(self, 'published_rfc'):
+ e = self.published_rfc
+ else:
+ e = self.latest_event(type="published_rfc")
return e.time.date() if e else None
#current_status = models.CharField(max_length=50,null=True)
@@ -617,7 +636,9 @@ class InternetDraft(Document):
#updated_by = models.CharField(max_length=200,blank=True,null=True)
@property
def updated_by(self):
- return ",".join("RFC%s" % n for n in sorted(d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="updates")))
+ if not hasattr(self, "updated_by_list"):
+ self.updated_by_list = [d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="updates")]
+ return ",".join("RFC%s" % n for n in sorted(self.updated_by_list))
#obsoletes = models.CharField(max_length=200,blank=True,null=True)
@property
@@ -627,7 +648,9 @@ class InternetDraft(Document):
#obsoleted_by = models.CharField(max_length=200,blank=True,null=True)
@property
def obsoleted_by(self):
- return ",".join("RFC%s" % n for n in sorted(d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="obs")))
+ if not hasattr(self, "obsoleted_by_list"):
+ self.obsoleted_by_list = [d.rfc_number for d in InternetDraft.objects.filter(relateddocument__doc_alias__document=self, relateddocument__relationship="obs")]
+ return ",".join("RFC%s" % n for n in sorted(self.obsoleted_by_list))
#also = models.CharField(max_length=50,blank=True,null=True)
@property
@@ -729,3 +752,48 @@ class DocumentComment(Event):
class Meta:
proxy = True
+
+class Position(BallotPosition):
+ def __init__(self, base):
+ for f in base._meta.fields:
+ if not f.name in ('discuss',): # don't overwrite properties
+ setattr(self, f.name, getattr(base, f.name))
+
+ #ballot = models.ForeignKey(BallotInfo, related_name='positions')
+ @property
+ def ballot(self):
+ return self.doc # FIXME: doesn't emulate old interface
+
+ # ad = models.ForeignKey(IESGLogin) # same name
+ #yes = models.IntegerField(db_column='yes_col')
+ @property
+ def yes(self):
+ return self.pos_id == "yes"
+ #noobj = models.IntegerField(db_column='no_col')
+ @property
+ def noobj(self):
+ return self.pos_id == "noobj"
+ #abstain = models.IntegerField()
+ @property
+ def abstain(self):
+ return self.pos_id == "abstain"
+ #approve = models.IntegerField(default=0) # unused
+ #discuss = models.IntegerField()
+ @property
+ def discuss(self):
+ return self.pos_id == "discuss"
+ #recuse = models.IntegerField()
+ @property
+ def recuse(self):
+ return self.pos_id == "recuse"
+ def __str__(self):
+ return "Position for %s on %s" % ( self.ad, self.ballot )
+ def abstain_ind(self):
+ if self.recuse:
+ return 'R'
+ if self.abstain:
+ return 'X'
+ else:
+ return ' '
+ class Meta:
+ proxy = True
diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py
index b328df51e..d52792905 100644
--- a/redesign/group/proxy.py
+++ b/redesign/group/proxy.py
@@ -48,11 +48,11 @@ class Area(Group):
#def additional_urls(self):
# return AreaWGURL.objects.filter(name=self.area_acronym.name)
def active_wgs(self):
- return IETFWG.objects.filter(type="wg", state="active", parent=self).order_by("acronym")
+ return IETFWG.objects.filter(type="wg", state="active", parent=self).select_related('type', 'state', 'parent').order_by("acronym")
@staticmethod
def active_areas():
- return Area.objects.filter(type="area", state="active").order_by('acronym')
+ return Area.objects.filter(type="area", state="active").select_related('type', 'state', 'parent').order_by('acronym')
class Meta:
proxy = True
diff --git a/redesign/person/proxy.py b/redesign/person/proxy.py
index 009ba0742..bc12de1a4 100644
--- a/redesign/person/proxy.py
+++ b/redesign/person/proxy.py
@@ -4,10 +4,14 @@ class IESGLogin(Email):
def __init__(self, base):
for f in base._meta.fields:
setattr(self, f.name, getattr(base, f.name))
+
SECRETARIAT_LEVEL = 0
AD_LEVEL = 1
INACTIVE_AD_LEVEL = 2
+ @property
+ def id(self):
+ return self.pk # this is not really backwards-compatible
#login_name = models.CharField(blank=True, max_length=255)
@property
def login_name(self): raise NotImplemented
From e45aab49dbcfaa80541049821187934d1c2fc38a Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 1 Feb 2011 12:28:29 +0000
Subject: [PATCH 20/75] Add missing import - Legacy-Id: 2781
---
ietf/idrfc/templatetags/ballot_icon.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/ietf/idrfc/templatetags/ballot_icon.py b/ietf/idrfc/templatetags/ballot_icon.py
index 6a03415eb..7e73d2d95 100644
--- a/ietf/idrfc/templatetags/ballot_icon.py
+++ b/ietf/idrfc/templatetags/ballot_icon.py
@@ -32,6 +32,7 @@
from django import template
from django.core.urlresolvers import reverse as urlreverse
+from django.conf import settings
from ietf.idtracker.models import IDInternal, BallotInfo
from ietf.idrfc.idrfc_wrapper import position_to_string, BALLOT_ACTIVE_STATES
from ietf.idtracker.templatetags.ietf_filters import in_group, timesince_days
From 294740d270000927991e164a67befd63c5c8312e Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 3 Feb 2011 19:37:59 +0000
Subject: [PATCH 21/75] Import WG list email addresses and comments -
Legacy-Id: 2807
---
redesign/import-groups.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/redesign/import-groups.py b/redesign/import-groups.py
index 479d25224..98529fded 100755
--- a/redesign/import-groups.py
+++ b/redesign/import-groups.py
@@ -68,8 +68,9 @@ for o in Area.objects.all():
group.state = s
group.type = type_names["area"]
group.parent = iesg_group
+ group.comments = o.comments.strip() if o.comments else ""
- # FIXME: missing fields from old: concluded_date, comments, last_modified_date, extra_email_addresses
+ # FIXME: missing fields from old: concluded_date, last_modified_date, extra_email_addresses
group.save()
@@ -125,8 +126,10 @@ for o in IETFWG.objects.all():
group.parent = Group.objects.get(acronym=o.area.area.area_acronym.acronym)
elif not group.parent:
print "no area/parent for", group.acronym, group.name, group.type, group.state
-
- # FIXME: missing fields from old: proposed_date, start_date, dormant_date, concluded_date, meeting_scheduled, email_address, email_subscribe, email_keyword, email_archive, comments, last_modified_date, meeting_scheduled_old
+
+ group.list_email = o.email_address if o.email_address else ""
+ group.comments = o.comments.strip() if o.comments else ""
+ # FIXME: missing fields from old: proposed_date, start_date, dormant_date, concluded_date, meeting_scheduled, email_subscribe, email_keyword, email_archive, last_modified_date, meeting_scheduled_old
group.save()
From d0f3b5e628ca0ef6b46f2f3bb973bfa991ac9a37 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 3 Feb 2011 19:40:38 +0000
Subject: [PATCH 22/75] Port change_state view + helpers + tests to new schema,
retaining the old view via settings; adjusted relationship model to use
source/target as attribute names - Legacy-Id: 2808
---
ietf/idrfc/fixtures/names.xml | 414 +++++
ietf/idrfc/generate_fixturesREDESIGN.py | 40 +
ietf/idrfc/lastcall.py | 23 +
ietf/idrfc/mails.py | 186 ++-
ietf/idrfc/tests.py | 2 +
ietf/idrfc/testsREDESIGN.py | 1428 +++++++++++++++++
ietf/idrfc/views_edit.py | 78 +-
ietf/idrfc/views_search.py | 8 +-
ietf/idtracker/models.py | 4 +-
ietf/ietfauth/models.py | 5 +
.../templates/idrfc/change_stateREDESIGN.html | 64 +
ietf/templates/idrfc/last_call_requested.html | 2 +-
redesign/doc/admin.py | 2 +-
redesign/doc/models.py | 127 +-
redesign/doc/proxy.py | 12 +-
redesign/group/models.py | 2 +-
redesign/import-document-state.py | 6 +-
redesign/name/models.py | 28 +
redesign/person/models.py | 7 +
19 files changed, 2363 insertions(+), 75 deletions(-)
create mode 100644 ietf/idrfc/fixtures/names.xml
create mode 100755 ietf/idrfc/generate_fixturesREDESIGN.py
create mode 100644 ietf/idrfc/testsREDESIGN.py
create mode 100644 ietf/templates/idrfc/change_stateREDESIGN.html
diff --git a/ietf/idrfc/fixtures/names.xml b/ietf/idrfc/fixtures/names.xml
new file mode 100644
index 000000000..a7fedc301
--- /dev/null
+++ b/ietf/idrfc/fixtures/names.xml
@@ -0,0 +1,414 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ietf/idrfc/generate_fixturesREDESIGN.py b/ietf/idrfc/generate_fixturesREDESIGN.py
new file mode 100755
index 000000000..abd47b430
--- /dev/null
+++ b/ietf/idrfc/generate_fixturesREDESIGN.py
@@ -0,0 +1,40 @@
+#!/usr/bin/python
+
+# boiler plate
+import os, sys
+
+one_dir_up = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../'))
+
+sys.path.insert(0, one_dir_up)
+
+from django.core.management import setup_environ
+import settings
+setup_environ(settings)
+
+# script
+from django.core.serializers import serialize
+from django.db.models import Q
+
+def output(name, qs):
+ try:
+ f = open(os.path.join(settings.BASE_DIR, "idrfc/fixtures/%s.xml" % name), 'w')
+ f.write(serialize("xml", qs, indent=4))
+ f.close()
+ except:
+ from django.db import connection
+ from pprint import pprint
+ pprint(connection.queries)
+ raise
+
+# pick all name models directly out of the module
+names = []
+
+import name.models
+for n in dir(name.models):
+ if n[:1].upper() == n[:1] and n.endswith("Name"):
+ model = getattr(name.models, n)
+ if not model._meta.abstract:
+ names.extend(model.objects.all())
+
+output("names", names)
+
diff --git a/ietf/idrfc/lastcall.py b/ietf/idrfc/lastcall.py
index 450b5d42c..63e937e43 100644
--- a/ietf/idrfc/lastcall.py
+++ b/ietf/idrfc/lastcall.py
@@ -2,9 +2,12 @@
import datetime
+from django.conf import settings
+
from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin
from ietf.idrfc.mails import *
from ietf.idrfc.utils import *
+from doc.models import Event
def request_last_call(request, doc):
try:
@@ -15,6 +18,26 @@ def request_last_call(request, doc):
send_last_call_request(request, doc, ballot)
add_document_comment(request, doc, "Last Call was requested")
+def request_last_callREDESIGN(request, doc):
+ if not doc.latest_event(type="changed_ballot_writeup_text"):
+ generate_ballot_writeup(request, doc)
+ if not doc.latest_event(type="changed_ballot_approval_text"):
+ generate_approval_mail(request, doc)
+ if not doc.latest_event(type="changed_last_call_text"):
+ generate_last_call_announcement(request, doc)
+
+ send_last_call_request(request, doc)
+
+ e = Event()
+ e.type = "requested_last_call"
+ e.by = request.user.get_profile().email()
+ e.doc = doc
+ e.desc = "Last call was requested by %s" % e.by.get_name()
+ e.save()
+
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ request_last_call = request_last_callREDESIGN
+
def get_expired_last_calls():
return InternetDraft.objects.filter(lc_expiration_date__lte=datetime.date.today(),
idinternal__cur_state__document_state_id=IDState.IN_LAST_CALL)
diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py
index 25d7b9127..d7210c231 100644
--- a/ietf/idrfc/mails.py
+++ b/ietf/idrfc/mails.py
@@ -9,6 +9,8 @@ from django.conf import settings
from ietf.utils.mail import send_mail, send_mail_text
from ietf.idtracker.models import *
+from doc.models import Text
+from person.models import Email
def email_state_changed(request, doc, text):
to = [x.strip() for x in doc.idinternal.state_change_notice_to.replace(';', ',').split(',')]
@@ -18,6 +20,17 @@ def email_state_changed(request, doc, text):
dict(text=text,
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
+def email_state_changedREDESIGN(request, doc, text):
+ to = [x.strip() for x in doc.notify.replace(';', ',').split(',')]
+ send_mail(request, to, None,
+ "ID Tracker State Update Notice: %s" % doc.file_tag(),
+ "idrfc/state_changed_email.txt",
+ dict(text=text,
+ url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
+
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ email_state_changed = email_state_changedREDESIGN
+
def html_to_text(html):
return strip_tags(html.replace("<", "<").replace(">", ">").replace("&", "&").replace(" ", "\n"))
@@ -34,6 +47,23 @@ def email_owner(request, doc, owner, changed_by, text, subject=None):
doc=doc,
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
+def email_ownerREDESIGN(request, doc, owner, changed_by, text, subject=None):
+ if not owner or not changed_by or owner == changed_by:
+ return
+
+ to = owner.formatted_email()
+ send_mail(request, to,
+ "DraftTracker Mail System ",
+ "%s updated by %s" % (doc.file_tag(), changed_by),
+ "idrfc/change_notice.txt",
+ dict(text=html_to_text(text),
+ doc=doc,
+ url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
+
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ email_owner = email_ownerREDESIGN
+
+
def full_intended_status(intended_status):
s = str(intended_status)
# FIXME: this should perhaps be defined in the db
@@ -54,6 +84,15 @@ def full_intended_status(intended_status):
else:
return "a %s" % s
+def generate_ballot_writeup(request, doc):
+ e = Text()
+ e.type = "changed_ballot_writeup_text"
+ e.by = request.user.get_profile().email()
+ e.doc = doc
+ e.desc = u"Ballot writeup was generated by %s" % e.by.get_name()
+ e.content = unicode(render_to_string("idrfc/ballot_writeup.txt"))
+ e.save()
+
def generate_last_call_announcement(request, doc):
status = full_intended_status(doc.intended_status).replace("a ", "").replace("an ", "")
@@ -86,6 +125,49 @@ def generate_last_call_announcement(request, doc):
)
)
+def generate_last_call_announcementREDESIGN(request, doc):
+ doc.full_status = full_intended_status(doc.intended_std_level)
+ status = doc.full_status.replace("a ", "").replace("an ", "")
+
+ expiration_date = date.today() + timedelta(days=14)
+ cc = []
+ if doc.group.type_id == "individ":
+ group = "an individual submitter"
+ expiration_date += timedelta(days=14)
+ else:
+ group = "the %s WG (%s)" % (doc.group.name, doc.group.acronym)
+ if doc.group.list_email:
+ cc.append(doc.group.list_email)
+
+ doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3)
+ url = settings.IDTRACKER_BASE_URL + doc.get_absolute_url()
+
+ mail = render_to_string("idrfc/last_call_announcement.txt",
+ dict(doc=doc,
+ doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
+ expiration_date=expiration_date.strftime("%Y-%m-%d"), #.strftime("%B %-d, %Y"),
+ cc=", ".join("<%s>" % e for e in cc),
+ group=group,
+ docs=[doc],
+ urls=[url],
+ status=status,
+ impl_report="Draft" in status or "Full" in status,
+ )
+ )
+
+ from doc.models import Text
+ e = Text()
+ e.type = "changed_last_call_text"
+ e.by = request.user.get_profile().email()
+ e.doc = doc
+ e.desc = u"Last call announcement was generated by %s" % e.by.get_name()
+ e.content = unicode(mail)
+ e.save()
+
+
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ generate_last_call_announcement = generate_last_call_announcementREDESIGN
+
def generate_approval_mail(request, doc):
if doc.idinternal.cur_state_id in IDState.DO_NOT_PUBLISH_STATES or doc.idinternal.via_rfc_editor:
return generate_approval_mail_rfc_editor(request, doc)
@@ -114,7 +196,7 @@ def generate_approval_mail(request, doc):
if len(docs) > 1:
made_by = "These documents have been reviewed in the IETF but are not the products of an IETF Working Group."
else:
- made_by = "This document has been reviewed in the IETF but is not the product of an IETF Working Group.";
+ made_by = "This document has been reviewed in the IETF but is not the product of an IETF Working Group."
else:
if len(docs) > 1:
made_by = "These documents are products of the %s." % doc.group.name_with_wg
@@ -159,6 +241,95 @@ def generate_approval_mail_rfc_editor(request, doc):
)
)
+DO_NOT_PUBLISH_IESG_STATES = ("nopubadw", "nopubanw")
+
+def generate_approval_mailREDESIGN(request, doc):
+ if doc.iesg_state_id in DO_NOT_PUBLISH_IESG_STATES or doc.tags.filter(slug='via-rfc'):
+ mail = generate_approval_mail_rfc_editor(request, doc)
+ else:
+ mail = generate_approval_mail_approved(request, doc)
+
+ from doc.models import Text
+ e = Text()
+ e.type = "changed_ballot_approval_text"
+ e.by = request.user.get_profile().email()
+ e.doc = doc
+ e.desc = u"Ballot approval text was generated by %s" % e.by.get_name()
+ e.content = unicode(mail)
+ e.save()
+
+def generate_approval_mail_approved(request, doc):
+ doc.full_status = full_intended_status(doc.intended_std_level.name)
+ status = doc.full_status.replace("a ", "").replace("an ", "")
+
+ if "an " in status:
+ action_type = "Document"
+ else:
+ action_type = "Protocol"
+
+ cc = ["Internet Architecture Board ", "RFC Editor "]
+
+ # the second check catches some area working groups (like
+ # Transport Area Working Group)
+ if doc.group.type_id != "area" and not doc.group.name.endswith("Working Group"):
+ doc.group.name_with_wg = doc.group.name + " Working Group"
+ if doc.group.list_email:
+ cc.append("%s mailing list <%s>" % (doc.group.acronym, doc.group.list_email))
+ cc.append("%s chair <%s-chairs@tools.ietf.org>" % (doc.group.acronym, doc.group.acronym))
+ else:
+ doc.group.name_with_wg = doc.group.name
+
+ doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3)
+
+ if doc.group.type_id == "individ":
+ made_by = "This document has been reviewed in the IETF but is not the product of an IETF Working Group."
+ else:
+ made_by = "This document is the product of the %s." % doc.group.name_with_wg
+
+ director = doc.ad
+ other_director = Email.objects.filter(role__group__role__email=director, role__group__role__name="ad").exclude(pk=director.pk)
+
+ if doc.group.type_id != "individ" and other_director:
+ contacts = "The IESG contact persons are %s and %s." % (director.get_name(), other_director[0].get_name())
+ else:
+ contacts = "The IESG contact person is %s." % director.get_name()
+
+ doc_type = "RFC" if doc.state_id == "rfc" else "Internet Draft"
+
+ return render_to_string("idrfc/approval_mail.txt",
+ dict(doc=doc,
+ docs=[doc],
+ doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
+ cc=",\n ".join(cc),
+ doc_type=doc_type,
+ made_by=made_by,
+ contacts=contacts,
+ status=status,
+ action_type=action_type,
+ )
+ )
+
+def generate_approval_mail_rfc_editorREDESIGN(request, doc):
+ full_status = full_intended_status(doc.intended_std_level.name)
+ status = full_status.replace("a ", "").replace("an ", "")
+ disapproved = doc.iesg_state in DO_NOT_PUBLISH_IESG_STATES
+ doc_type = "RFC" if doc.state_id == "rfc" else "Internet Draft"
+
+ return render_to_string("idrfc/approval_mail_rfc_editor.txt",
+ dict(doc=doc,
+ doc_url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url(),
+ doc_type=doc_type,
+ status=status,
+ full_status=full_status,
+ disapproved=disapproved,
+ )
+ )
+
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ generate_approval_mail = generate_approval_mailREDESIGN
+ generate_approval_mail_rfc_editor = generate_approval_mail_rfc_editorREDESIGN
+
+
def send_last_call_request(request, doc, ballot):
to = "iesg-secretary@ietf.org"
frm = '"DraftTracker Mail System" '
@@ -170,6 +341,19 @@ def send_last_call_request(request, doc, ballot):
dict(docs=docs,
doc_url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
+def send_last_call_requestREDESIGN(request, doc):
+ to = "iesg-secretary@ietf.org"
+ frm = '"DraftTracker Mail System" '
+
+ send_mail(request, to, frm,
+ "Last Call: %s" % doc.file_tag(),
+ "idrfc/last_call_request.txt",
+ dict(docs=[doc],
+ doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
+
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ send_last_call_request = send_last_call_requestREDESIGN
+
def email_resurrect_requested(request, doc, by):
to = "I-D Administrator "
frm = u"%s <%s>" % by.person.email()
diff --git a/ietf/idrfc/tests.py b/ietf/idrfc/tests.py
index 0710508e9..c51a5b19d 100644
--- a/ietf/idrfc/tests.py
+++ b/ietf/idrfc/tests.py
@@ -1277,3 +1277,5 @@ class MirrorScriptTestCases(unittest.TestCase,RealDatabaseTest):
self.assertEquals(len(refs), 3)
print "OK"
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ from testsREDESIGN import *
diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py
new file mode 100644
index 000000000..1a5fe84e6
--- /dev/null
+++ b/ietf/idrfc/testsREDESIGN.py
@@ -0,0 +1,1428 @@
+# 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
+
+import django.test
+from django.core.urlresolvers import reverse as urlreverse
+from django.conf import settings
+
+from pyquery import PyQuery
+
+#from ietf.idrfc.models import *
+from ietf.idtracker.models import IESGLogin, PersonOrOrgInfo, EmailAddress
+from doc.models import *
+from name.models import *
+from group.models import *
+from person.models import *
+from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest, login_testing_unauthorized
+from ietf.utils.test_runner import mail_outbox
+
+class IdRfcUrlTestCase(SimpleUrlTestCase):
+ def testUrls(self):
+ self.doTestUrls(__file__)
+
+def make_test_data():
+ # groups
+ area = Group.objects.create(
+ name="Far Future",
+ acronym="farfut",
+ state_id="active",
+ type_id="area",
+ parent=None)
+ group = Group.objects.create(
+ name="Martian Special Interest Group",
+ acronym="mars",
+ state_id="active",
+ type_id="wg",
+ parent=area,
+ )
+
+ # persons
+ p = Person.objects.create(
+ name="Aread Irector",
+ ascii="Aread Irector",
+ )
+ ad = Email.objects.create(
+ address="aread@ietf.org",
+ person=p)
+ Role.objects.create(
+ name_id="ad",
+ group=area,
+ email=ad)
+ porg = PersonOrOrgInfo.objects.create(
+ first_name="Aread",
+ last_name="Irector",
+ middle_initial="",
+ )
+ EmailAddress.objects.create(
+ person_or_org=porg,
+ priority=1,
+ address=ad.address,
+ )
+ IESGLogin.objects.create(
+ login_name="ad",
+ password="foo",
+ user_level=1,
+ first_name="Aread",
+ last_name="Irector",
+ person=porg,
+ )
+
+ p = Person.objects.create(
+ name="Ano Therdir",
+ ascii="Ano Therdir",
+ )
+ ad = Email.objects.create(
+ address="ano@ietf.org",
+ person=p)
+ Role.objects.create(
+ name_id="ad",
+ group=area,
+ email=ad)
+ porg = PersonOrOrgInfo.objects.create(
+ first_name="Ano",
+ last_name="Therdir",
+ middle_initial="",
+ )
+ EmailAddress.objects.create(
+ person_or_org=porg,
+ priority=1,
+ address=ad.address,
+ )
+ IESGLogin.objects.create(
+ login_name="ad2",
+ password="foo",
+ user_level=1,
+ first_name="Ano",
+ last_name="Therdir",
+ person=porg,
+ )
+
+ p = Person.objects.create(
+ name="Sec Retary",
+ ascii="Sec Retary",
+ )
+ Email.objects.create(
+ address="sec.retary@ietf.org",
+ person=p)
+ porg = PersonOrOrgInfo.objects.create(
+ first_name="Sec",
+ last_name="Retary",
+ middle_initial="",
+ )
+ EmailAddress.objects.create(
+ person_or_org=porg,
+ priority=1,
+ address="sec.retary@ietf.org",
+ )
+ IESGLogin.objects.create(
+ login_name="secretary",
+ password="foo",
+ user_level=0,
+ first_name="Sec",
+ last_name="Retary",
+ person=porg,
+ )
+
+ # draft
+ draft = Document.objects.create(
+ name="ietf-test",
+ time=datetime.datetime.now(),
+ type_id="draft",
+ title="Optimizing Martian Network Topologies",
+ state_id="active",
+ stream_id="ietf",
+ group=group,
+ abstract="Techniques for achieving near-optimal Martian networks.",
+ rev="01",
+ pages=2,
+ intended_std_level_id="ps",
+ ad=ad,
+ notify="aliens@example.mars"
+ )
+
+ DocAlias.objects.create(
+ document=draft,
+ name=draft.name,
+ )
+
+ Event.objects.create(
+ type="started_iesg_process",
+ by=ad,
+ doc=draft,
+ desc="Added draft",
+ )
+
+ return draft
+
+class ChangeStateTestCase(django.test.TestCase):
+ fixtures = ['names']
+
+ def test_change_state(self):
+ draft = make_test_data()
+ draft.iesg_state = IesgDocStateName.objects.get(slug="ad-eval")
+ draft.save()
+
+ url = urlreverse('doc_change_state', kwargs=dict(name=draft.name))
+ login_testing_unauthorized(self, "secretary", url)
+
+ first_state = draft.iesg_state
+ next_states = get_next_iesg_states(first_state)
+
+ # 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="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.iesg_state, first_state)
+
+
+ # change state
+ history_before = draft.event_set.count()
+ mailbox_before = len(mail_outbox)
+
+ r = self.client.post(url, dict(state="review-e"))
+ self.assertEquals(r.status_code, 302)
+
+ draft = Document.objects.get(name=draft.name)
+ self.assertEquals(draft.iesg_state_id, "review-e")
+ self.assertEquals(draft.event_set.count(), history_before + 1)
+ self.assertTrue("State changed" in draft.event_set.all()[0].desc)
+ self.assertEquals(len(mail_outbox), mailbox_before + 2)
+ self.assertTrue("State Update Notice" in mail_outbox[-2]['Subject'])
+ self.assertTrue(draft.name in mail_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"][value="ad-eval"]')), 1)
+
+
+ def test_request_last_call(self):
+ draft = make_test_data()
+ draft.iesg_state = IesgDocStateName.objects.get(slug="ad-eval")
+ draft.save()
+
+ self.client.login(remote_user="secretary")
+ url = urlreverse('doc_change_state', kwargs=dict(name=draft.name))
+
+ mailbox_before = len(mail_outbox)
+
+ self.assertTrue(not draft.latest_event(type="changed_ballot_writeup_text"))
+ r = self.client.post(url, dict(state="lc-req"))
+ self.assertContains(r, "Your request to issue the Last Call")
+
+ # last call text
+ e = draft.latest_event(Text, type="changed_last_call_text")
+ self.assertTrue(e)
+ self.assertTrue("The IESG has received" in e.content)
+ self.assertTrue(draft.title in e.content)
+ self.assertTrue(draft.get_absolute_url() in e.content)
+
+ # approval text
+ e = draft.latest_event(Text, type="changed_ballot_approval_text")
+ self.assertTrue(e)
+ self.assertTrue("The IESG has approved" in e.content)
+ self.assertTrue(draft.title in e.content)
+ self.assertTrue(draft.get_absolute_url() in e.content)
+
+ # ballot writeup
+ e = draft.latest_event(Text, type="changed_ballot_writeup_text")
+ self.assertTrue(e)
+ self.assertTrue("Technical Summary" in e.content)
+
+ # mail notice
+ self.assertTrue(len(mail_outbox) > mailbox_before)
+ self.assertTrue("Last Call:" in mail_outbox[-1]['Subject'])
+
+ # comment
+ self.assertTrue("Last call was requested" in draft.event_set.all()[0].desc)
+
+ # FIXME: test regeneration of announcement when it's not approved/via rfc editor
+
+
+class EditInfoTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft']
+
+ def test_edit_info(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_edit_info', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", 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_status]')), 1)
+ self.assertEquals(len(q('form input[name=via_rfc_editor]')), 1)
+
+ prev_job_owner = draft.idinternal.job_owner
+ # faulty post
+ r = self.client.post(url, dict(job_owner="123456789"))
+ self.assertEquals(r.status_code, 200)
+ q = PyQuery(r.content)
+ self.assertTrue(len(q('form ul.errorlist')) > 0)
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertEquals(draft.idinternal.job_owner, prev_job_owner)
+
+ # edit info
+ history_before = draft.idinternal.comments().count()
+ mailbox_before = len(mail_outbox)
+ draft.group = Acronym.objects.get(acronym_id=Acronym.INDIVIDUAL_SUBMITTER)
+ draft.save()
+ new_job_owner = IESGLogin.objects.exclude(id__in=[IESGLogin.objects.get(login_name="klm").id, draft.idinternal.job_owner_id])[0]
+ new_area = Area.active_areas()[0]
+
+ r = self.client.post(url,
+ dict(intended_status=str(draft.intended_status_id),
+ status_date=str(date.today() + timedelta(2)),
+ area_acronym=str(new_area.area_acronym_id),
+ via_rfc_editor="1",
+ job_owner=new_job_owner.id,
+ state_change_notice_to="test@example.com",
+ note="",
+ telechat_date="",
+ ))
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertEquals(draft.idinternal.area_acronym, new_area)
+ self.assertTrue(draft.idinternal.via_rfc_editor)
+ self.assertEquals(draft.idinternal.job_owner, new_job_owner)
+ self.assertEquals(draft.idinternal.note, "")
+ self.assertTrue(not draft.idinternal.agenda)
+ self.assertEquals(draft.idinternal.comments().count(), history_before + 3)
+ self.assertEquals(len(mail_outbox), mailbox_before + 1)
+ self.assertTrue(draft.filename in mail_outbox[-1]['Subject'])
+
+ def test_edit_telechat_date(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ draft.idinternal.agenda = False
+ draft.idinternal.save()
+ url = urlreverse('doc_edit_info', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", url)
+
+ data = dict(intended_status=str(draft.intended_status_id),
+ status_date=str(date.today() + timedelta(2)),
+ area_acronym=str(draft.idinternal.area_acronym_id),
+ via_rfc_editor="1",
+ job_owner=str(draft.idinternal.job_owner_id),
+ state_change_notice_to="test@example.com",
+ note="",
+ )
+
+ from ietf.iesg.models import TelechatDates
+
+ # add to telechat
+ data["telechat_date"] = TelechatDates.objects.all()[0].date1.isoformat()
+ r = self.client.post(url, data)
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue(draft.idinternal.agenda)
+ self.assertEquals(draft.idinternal.telechat_date, TelechatDates.objects.all()[0].date1)
+
+ # change telechat
+ data["telechat_date"] = TelechatDates.objects.all()[0].date2.isoformat()
+ r = self.client.post(url, data)
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue(draft.idinternal.agenda)
+ self.assertEquals(draft.idinternal.telechat_date, TelechatDates.objects.all()[0].date2)
+
+ # remove from agenda
+ data["telechat_date"] = ""
+ r = self.client.post(url, data)
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue(not draft.idinternal.agenda)
+
+
+ def test_add_draft(self):
+ draft = InternetDraft.objects.get(filename="draft-ah-rfc2141bis-urn")
+ url = urlreverse('doc_edit_info', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", 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_status]')), 1)
+ self.assertEquals(len(q('form input[name=via_rfc_editor]')), 1)
+ self.assertTrue('@' in q('form input[name=state_change_notice_to]')[0].get('value'))
+
+ # add
+ mailbox_before = len(mail_outbox)
+
+ job_owner = IESGLogin.objects.filter(user_level=1)[0]
+ area = Area.active_areas()[0]
+
+ r = self.client.post(url,
+ dict(intended_status=str(draft.intended_status_id),
+ status_date=str(date.today() + timedelta(2)),
+ area_acronym=str(area.area_acronym_id),
+ via_rfc_editor="1",
+ job_owner=job_owner.id,
+ state_change_notice_to="test@example.com",
+ note="This is a note",
+ telechat_date="",
+ ))
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ah-rfc2141bis-urn")
+ self.assertEquals(draft.idinternal.area_acronym, area)
+ self.assertTrue(draft.idinternal.via_rfc_editor)
+ self.assertEquals(draft.idinternal.job_owner, job_owner)
+ self.assertEquals(draft.idinternal.note, "This is a note")
+ self.assertTrue(not draft.idinternal.agenda)
+ self.assertEquals(draft.idinternal.comments().count(), 2)
+ self.assertTrue("Draft added" in draft.idinternal.comments()[0].comment_text)
+ self.assertTrue("This is a note" in draft.idinternal.comments()[1].comment_text)
+ self.assertEquals(len(mail_outbox), mailbox_before)
+
+
+class ResurrectTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft']
+
+ def test_request_resurrect(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mip6-cn-ipsec")
+ self.assertEquals(draft.status.status, "Expired")
+ self.assertTrue(not draft.idinternal.resurrect_requested_by)
+
+ url = urlreverse('doc_request_resurrect', kwargs=dict(name=draft.filename))
+ login_as = "rhousley"
+
+ login_testing_unauthorized(self, login_as, 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
+ history_before = draft.idinternal.comments().count()
+ mailbox_before = len(mail_outbox)
+
+ r = self.client.post(url, dict())
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mip6-cn-ipsec")
+ self.assertEquals(draft.idinternal.resurrect_requested_by, IESGLogin.objects.get(login_name=login_as))
+ self.assertEquals(draft.idinternal.comments().count(), history_before + 1)
+ self.assertTrue("Resurrection" in draft.idinternal.comments()[0].comment_text)
+ self.assertEquals(len(mail_outbox), mailbox_before + 1)
+ self.assertTrue("Resurrection" in mail_outbox[-1]['Subject'])
+
+ def test_resurrect(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mip6-cn-ipsec")
+ self.assertEquals(draft.status.status, "Expired")
+ draft.idinternal.resurrect_requested_by = IESGLogin.objects.get(login_name="rhousley")
+ draft.idinternal.save()
+
+ url = urlreverse('doc_resurrect', kwargs=dict(name=draft.filename))
+
+ login_testing_unauthorized(self, "klm", 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
+ history_before = draft.idinternal.comments().count()
+ mailbox_before = len(mail_outbox)
+
+ r = self.client.post(url, dict())
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mip6-cn-ipsec")
+ self.assertEquals(draft.idinternal.resurrect_requested_by, None)
+ self.assertEquals(draft.idinternal.comments().count(), history_before + 1)
+ self.assertTrue("completed" in draft.idinternal.comments()[0].comment_text)
+ self.assertEquals(draft.status.status, "Active")
+ self.assertEquals(len(mail_outbox), mailbox_before + 1)
+
+class AddCommentTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft']
+
+ def test_add_comment(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_add_comment', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", 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
+ history_before = draft.idinternal.comments().count()
+ mailbox_before = len(mail_outbox)
+
+ r = self.client.post(url, dict(comment="This is a test."))
+ self.assertEquals(r.status_code, 302)
+
+ self.assertEquals(draft.idinternal.comments().count(), history_before + 1)
+ self.assertTrue("This is a test." in draft.idinternal.comments()[0].comment_text)
+ self.assertEquals(len(mail_outbox), mailbox_before + 1)
+ self.assertTrue("updated" in mail_outbox[-1]['Subject'])
+ self.assertTrue(draft.filename in mail_outbox[-1]['Subject'])
+
+class EditPositionTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft', 'ballot']
+
+ def test_edit_position(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_edit_position', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "rhousley", 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)
+ self.assertEquals(len(q('form textarea[name=comment_text]')), 1)
+
+ # vote
+ history_before = draft.idinternal.comments().count()
+ self.assertTrue(not Position.objects.filter(ballot=draft.idinternal.ballot, ad__login_name="rhousley"))
+
+ r = self.client.post(url, dict(position="discuss",
+ discuss_text="This is a discussion test.",
+ comment_text="This is a test."))
+ self.assertEquals(r.status_code, 302)
+
+ pos = Position.objects.get(ballot=draft.idinternal.ballot, ad__login_name="rhousley")
+ self.assertTrue("This is a discussion test." in IESGDiscuss.objects.get(ballot=draft.idinternal.ballot, ad__login_name="rhousley").text)
+ self.assertTrue("This is a test." in IESGComment.objects.get(ballot=draft.idinternal.ballot, ad__login_name="rhousley").text)
+ self.assertTrue(pos.discuss)
+ self.assertTrue(not (pos.yes or pos.noobj or pos.abstain or pos.recuse))
+
+ self.assertEquals(draft.idinternal.comments().count(), history_before + 3)
+ self.assertTrue("New position" in draft.idinternal.comments()[2].comment_text)
+
+ # recast vote
+ history_before = draft.idinternal.comments().count()
+ r = self.client.post(url, dict(position="noobj"))
+ self.assertEquals(r.status_code, 302)
+
+ pos = Position.objects.filter(ballot=draft.idinternal.ballot, ad__login_name="rhousley")[0]
+ self.assertTrue(pos.noobj)
+ self.assertTrue(not (pos.yes or pos.abstain or pos.recuse))
+ self.assertTrue(pos.discuss == -1)
+ self.assertEquals(draft.idinternal.comments().count(), history_before + 1)
+ self.assertTrue("Position" in draft.idinternal.comments()[0].comment_text)
+
+ # clear vote
+ history_before = draft.idinternal.comments().count()
+ r = self.client.post(url, dict(position=""))
+ self.assertEquals(r.status_code, 302)
+
+ pos = Position.objects.filter(ballot=draft.idinternal.ballot, ad__login_name="rhousley")
+ self.assertEquals(len(pos), 0)
+ self.assertEquals(draft.idinternal.comments().count(), history_before + 1)
+ self.assertTrue("Position" in draft.idinternal.comments()[0].comment_text)
+ def test_edit_position_as_secretary(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_edit_position', kwargs=dict(name=draft.filename))
+ url += "?ad=rhousley"
+ login_testing_unauthorized(self, "klm", 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 for rhousley
+ history_before = draft.idinternal.comments().count()
+ self.assertTrue(not Position.objects.filter(ballot=draft.idinternal.ballot, ad__login_name="rhousley"))
+
+ r = self.client.post(url, dict(position="discuss"))
+ self.assertEquals(r.status_code, 302)
+
+ pos = Position.objects.get(ballot=draft.idinternal.ballot, ad__login_name="rhousley")
+ self.assertTrue(pos.discuss)
+ self.assertTrue(not (pos.yes or pos.noobj or pos.abstain or pos.recuse))
+
+
+ def test_send_ballot_comment(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_send_ballot_comment', kwargs=dict(name=draft.filename))
+ login_as = "rhousley"
+ login_testing_unauthorized(self, login_as, 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(mail_outbox)
+ IESGComment.objects.create(ballot=draft.idinternal.ballot,
+ ad=IESGLogin.objects.get(login_name=login_as),
+ text="Test!", date=date.today(),
+ revision=draft.revision_display(), active=1)
+
+ r = self.client.post(url, dict(cc="test@example.com", cc_state_change="1"))
+ self.assertEquals(r.status_code, 302)
+
+ self.assertEquals(len(mail_outbox), mailbox_before + 1)
+ self.assertTrue("COMMENT" in mail_outbox[-1]['Subject'])
+
+
+class DeferBallotTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft', 'ballot']
+
+ def test_defer_ballot(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_defer_ballot', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "rhousley", url)
+
+ # normal get
+ r = self.client.get(url)
+ self.assertEquals(r.status_code, 200)
+
+ # defer
+ self.assertTrue(not draft.idinternal.ballot.defer)
+ mailbox_before = len(mail_outbox)
+
+ r = self.client.post(url, dict())
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue(draft.idinternal.ballot.defer)
+ self.assertTrue(draft.idinternal.cur_state_id == IDState.IESG_EVALUATION_DEFER)
+
+ self.assertEquals(len(mail_outbox), mailbox_before + 2)
+ self.assertTrue("Deferred" in mail_outbox[-2]['Subject'])
+ self.assertTrue(draft.file_tag() in mail_outbox[-2]['Subject'])
+
+ def test_undefer_ballot(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_undefer_ballot', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "rhousley", url)
+
+ draft.idinternal.ballot.defer = True
+ draft.idinternal.ballot.save()
+
+ # normal get
+ r = self.client.get(url)
+ self.assertEquals(r.status_code, 200)
+
+ # undefer
+ self.assertTrue(draft.idinternal.ballot.defer)
+
+ r = self.client.post(url, dict())
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue(not draft.idinternal.ballot.defer)
+ self.assertEquals(draft.idinternal.cur_state_id, IDState.IESG_EVALUATION)
+
+class BallotWriteupsTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft', 'ballot']
+
+ def test_edit_last_call_text(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", 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)
+
+ # 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 = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue("This is a simple test" in draft.idinternal.ballot.last_call_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 = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue("Subject: Last Call" in draft.idinternal.ballot.last_call_text)
+
+ def test_request_last_call(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", url)
+
+ mailbox_before = len(mail_outbox)
+
+ r = self.client.post(url, dict(
+ last_call_text=draft.idinternal.ballot.last_call_text,
+ send_last_call_request="1"))
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertEquals(draft.idinternal.cur_state_id, IDState.LAST_CALL_REQUESTED)
+
+ self.assertEquals(len(mail_outbox), mailbox_before + 3)
+
+ self.assertTrue("Last Call" in mail_outbox[-1]['Subject'])
+
+ def test_edit_ballot_writeup(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", url)
+
+ # 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)
+
+ # 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 = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue("This is a simple test" in draft.idinternal.ballot.ballot_writeup)
+
+ def test_issue_ballot(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "rhousley", url)
+
+ draft.idinternal.ballot.ballot_issued = False
+ draft.idinternal.ballot.save()
+ active = IESGLogin.objects.filter(user_level=1)
+ Position.objects.create(ad=active[0], yes=1, noobj=0, discuss=0, abstain=0, recuse=0, ballot=draft.idinternal.ballot)
+ Position.objects.create(ad=active[1], yes=0, noobj=1, discuss=0, abstain=0, recuse=0, ballot=draft.idinternal.ballot)
+ Position.objects.create(ad=active[2], yes=0, noobj=1, discuss=-1, abstain=0, recuse=0, ballot=draft.idinternal.ballot)
+ Position.objects.create(ad=active[3], yes=0, noobj=0, discuss=1, abstain=0, recuse=0, ballot=draft.idinternal.ballot)
+ Position.objects.create(ad=active[4], yes=0, noobj=0, discuss=0, abstain=1, recuse=0, ballot=draft.idinternal.ballot)
+ Position.objects.create(ad=active[5], yes=0, noobj=0, discuss=0, abstain=0, recuse=1, ballot=draft.idinternal.ballot)
+ inactive = IESGLogin.objects.filter(user_level=2)
+ Position.objects.create(ad=inactive[0], yes=1, noobj=0, discuss=0, abstain=0, recuse=0, ballot=draft.idinternal.ballot)
+ IESGDiscuss.objects.create(ad=active[1], active=True, date=datetime.date.today(), text="test " * 20, ballot=draft.idinternal.ballot)
+ IESGComment.objects.create(ad=active[2], active=True, date=datetime.date.today(), text="test " * 20, ballot=draft.idinternal.ballot)
+ IESGDiscuss.objects.create(ad=active[3], active=True, date=datetime.date.today(), text="test " * 20, ballot=draft.idinternal.ballot)
+ IESGComment.objects.create(ad=active[3], active=True, date=datetime.date.today(), text="test " * 20, ballot=draft.idinternal.ballot)
+
+ mailbox_before = len(mail_outbox)
+
+ r = self.client.post(url, dict(
+ ballot_writeup=draft.idinternal.ballot.ballot_writeup,
+ approval_text=draft.idinternal.ballot.approval_text,
+ issue_ballot="1"))
+ self.assertEquals(r.status_code, 200)
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+
+ self.assertTrue(draft.idinternal.ballot.ballot_issued)
+ self.assertEquals(len(mail_outbox), mailbox_before + 2)
+ self.assertTrue("Evaluation:" in mail_outbox[-2]['Subject'])
+
+
+ def test_edit_approval_text(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_ballot_approvaltext', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", 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)
+
+ # 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(
+ approval_text="This is a simple test.",
+ save_approval_text="1"))
+ self.assertEquals(r.status_code, 200)
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue("This is a simple test" in draft.idinternal.ballot.approval_text)
+
+ # test regenerate
+ r = self.client.post(url, dict(
+ approval_text="This is a simple test.",
+ regenerate_approval_text="1"))
+ self.assertEquals(r.status_code, 200)
+ q = PyQuery(r.content)
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertTrue("Subject: Protocol Action" in draft.idinternal.ballot.approval_text)
+
+class ApproveBallotTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft', 'ballot']
+
+ def test_approve_ballot(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", 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('input[type=submit]')[0].get('value'))
+ self.assertEquals(len(q('pre')), 1)
+
+ # approve
+ mailbox_before = len(mail_outbox)
+
+ r = self.client.post(url, dict())
+ self.assertEquals(r.status_code, 302)
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertEquals(draft.idinternal.cur_state_id, IDState.APPROVED_ANNOUNCEMENT_SENT)
+
+ self.assertEquals(len(mail_outbox), mailbox_before + 4)
+
+ self.assertTrue("Protocol Action" in mail_outbox[-2]['Subject'])
+ # the IANA copy
+ self.assertTrue("Protocol Action" in mail_outbox[-1]['Subject'])
+
+class MakeLastCallTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft', 'ballot']
+
+ def test_make_last_call(self):
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ draft.idinternal.cur_state = IDState.objects.get(document_state_id=IDState.LAST_CALL_REQUESTED)
+ draft.idinternal.save()
+ draft.lc_expiration_date = None
+ draft.save()
+
+ url = urlreverse('doc_make_last_call', kwargs=dict(name=draft.filename))
+ login_testing_unauthorized(self, "klm", 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(mail_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 = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertEquals(draft.idinternal.cur_state_id, IDState.IN_LAST_CALL)
+ self.assertEquals(draft.lc_expiration_date.strftime("%Y-%m-%d"), expire_date)
+ self.assertEquals(len(mail_outbox), mailbox_before + 4)
+
+ self.assertTrue("Last Call" in mail_outbox[-4]['Subject'])
+ # the IANA copy
+ self.assertTrue("Last Call" in mail_outbox[-3]['Subject'])
+
+class ExpireIDsTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft']
+
+ 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
+
+ self.assertTrue(not in_id_expire_freeze(datetime.datetime(2010, 7, 11, 0, 0)))
+ self.assertTrue(not in_id_expire_freeze(datetime.datetime(2010, 7, 12, 8, 0)))
+ self.assertTrue(in_id_expire_freeze(datetime.datetime(2010, 7, 12, 10, 0)))
+ self.assertTrue(in_id_expire_freeze(datetime.datetime(2010, 7, 25, 0, 0)))
+ self.assertTrue(not in_id_expire_freeze(datetime.datetime(2010, 7, 26, 0, 0)))
+
+ def test_expire_ids(self):
+ from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id
+
+ # hack into expirable state
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ draft.status = IDStatus.objects.get(status="Active")
+ draft.review_by_rfc_editor = 0
+ draft.revision_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1)
+ draft.idinternal.cur_state_id = IDState.AD_WATCHING
+ draft.idinternal.save()
+ draft.save()
+
+ draft = InternetDraft.objects.get(filename="draft-ah-rfc2141bis-urn")
+ self.assertTrue(draft.idinternal == None)
+ draft.status = IDStatus.objects.get(status="Active")
+ draft.review_by_rfc_editor = 0
+ draft.revision_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1)
+ draft.save()
+
+ # test query
+ documents = get_expired_ids()
+ self.assertEquals(len(documents), 2)
+
+ for d in documents:
+ # test notice
+ mailbox_before = len(mail_outbox)
+
+ send_expire_notice_for_id(d)
+
+ self.assertEquals(InternetDraft.objects.get(filename=d.filename).dunn_sent_date, datetime.date.today())
+ if d.idinternal:
+ self.assertEquals(len(mail_outbox), mailbox_before + 1)
+ self.assertTrue("expired" in mail_outbox[-1]["Subject"])
+
+ # test expiry
+ txt = "%s-%s.txt" % (d.filename, d.revision_display())
+ self.write_id_file(txt, 5000)
+
+ revision_before = d.revision
+
+ expire_id(d)
+
+ draft = InternetDraft.objects.get(filename=d.filename)
+ self.assertEquals(draft.status.status, "Expired")
+ self.assertEquals(int(draft.revision), int(revision_before) + 1)
+ 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)))
+ new_txt = "%s-%s.txt" % (draft.filename, draft.revision)
+ self.assertTrue(os.path.exists(os.path.join(self.id_dir, new_txt)))
+
+ def test_clean_up_id_files(self):
+ 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-ietf-mipshop-pfmipv6.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 = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ draft.status_id = 3
+ draft.save()
+
+ txt = "%s-%s.txt" % (draft.filename, draft.revision)
+ self.write_id_file(txt, 5000)
+ pdf = "%s-%s.pdf" % (draft.filename, draft.revision)
+ 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)))
+
+
+ # expired without tombstone
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ draft.status_id = 2
+ draft.expiration_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1)
+ draft.save()
+
+ txt = "%s-%s.txt" % (draft.filename, draft.revision)
+ 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
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ draft.status_id = 2
+ draft.expiration_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1)
+ draft.expired_tombstone = False
+ draft.save()
+
+ revision_before = draft.revision
+
+ txt = "%s-%s.txt" % (draft.filename, draft.revision)
+ self.write_id_file(txt, 1000)
+
+ 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)))
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertEquals(int(draft.revision), int(revision_before) - 1)
+ self.assertTrue(draft.expired_tombstone)
+
+class ExpireLastCallTestCase(django.test.TestCase):
+ fixtures = ['base', 'draft']
+
+ def test_expire_last_call(self):
+ from ietf.idrfc.lastcall import get_expired_last_calls, expire_last_call
+
+ # check that not expired drafts aren't expired
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ draft.idinternal.cur_state = IDState.objects.get(document_state_id=IDState.IN_LAST_CALL)
+ draft.idinternal.cur_substate = None
+ draft.idinternal.save()
+ draft.lc_expiration_date = datetime.date.today() + datetime.timedelta(days=2)
+ draft.save()
+
+ self.assertEquals(len(get_expired_last_calls()), 0)
+
+ draft.lc_expiration_date = None
+ draft.save()
+
+ self.assertEquals(len(get_expired_last_calls()), 0)
+
+ # test expired
+ draft.lc_expiration_date = datetime.date.today()
+ draft.save()
+
+ drafts = get_expired_last_calls()
+ self.assertEquals(len(drafts), 1)
+
+ mailbox_before = len(mail_outbox)
+ history_before = draft.idinternal.comments().count()
+
+ expire_last_call(drafts[0])
+
+ draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
+ self.assertEquals(draft.idinternal.cur_state.document_state_id, IDState.WAITING_FOR_WRITEUP)
+ self.assertEquals(draft.idinternal.comments().count(), history_before + 1)
+ self.assertEquals(len(mail_outbox), mailbox_before + 1)
+ self.assertTrue("Last Call Expired" in mail_outbox[-1]["Subject"])
+
+
+
+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.
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
+
+
+
+'''
+
+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
+
+
+
+
+
+
+
+
+
+'''
+
+class MirrorScriptTestCases(unittest.TestCase,RealDatabaseTest):
+
+ def setUp(self):
+ self.setUpRealDatabase()
+ def tearDown(self):
+ self.tearDownRealDatabase()
+
+ def testRfcIndex(self):
+ print " Testing rfc-index.xml parsing"
+ from ietf.idrfc.mirror_rfc_index import parse
+ data = parse(StringIO.StringIO(TEST_RFC_INDEX))
+ self.assertEquals(len(data), 6)
+ print "OK"
+
+ def testRfcEditorQueue(self):
+ print " Testing queue2.xml parsing"
+ from ietf.idrfc.mirror_rfc_editor_queue import parse_all
+ (drafts,refs) = parse_all(StringIO.StringIO(TEST_QUEUE))
+ self.assertEquals(len(drafts), 3)
+ self.assertEquals(len(refs), 3)
+ print "OK"
+
diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py
index 42f036c56..788fa02d5 100644
--- a/ietf/idrfc/views_edit.py
+++ b/ietf/idrfc/views_edit.py
@@ -22,6 +22,8 @@ from ietf.idrfc.mails import *
from ietf.idrfc.utils import *
from ietf.idrfc.lastcall import request_last_call
+from doc.models import Document, Event, save_document_in_history, DocHistory
+from name.models import IesgDocStateName, get_next_iesg_states
class ChangeStateForm(forms.Form):
state = forms.ModelChoiceField(IDState.objects.all(), empty_label=None, required=True)
@@ -56,7 +58,8 @@ def change_state(request, name):
request_last_call(request, doc)
return render_to_response('idrfc/last_call_requested.html',
- dict(doc=doc),
+ dict(doc=doc,
+ url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
return HttpResponseRedirect(internal.get_absolute_url())
@@ -77,6 +80,79 @@ def change_state(request, name):
next_states=next_states),
context_instance=RequestContext(request))
+class ChangeStateFormREDESIGN(forms.Form):
+ state = forms.ModelChoiceField(IesgDocStateName.objects.all(), empty_label=None, required=True)
+ # FIXME: no tags yet
+ #substate = forms.ModelChoiceField(IDSubState.objects.all(), required=False)
+
+@group_required('Area_Director','Secretariat')
+def change_stateREDESIGN(request, name):
+ """Change state of Internet Draft, notifying parties as necessary
+ and logging the change as a comment."""
+ doc = get_object_or_404(Document, docalias__name=name)
+ if not doc.latest_event(type="started_iesg_process") or doc.state_id == "expired":
+ raise Http404()
+
+ login = request.user.get_profile().email()
+
+ if request.method == 'POST':
+ form = ChangeStateForm(request.POST)
+ if form.is_valid():
+ state = form.cleaned_data['state']
+ if state != doc.iesg_state:
+ save_document_in_history(doc)
+
+ prev_state = doc.iesg_state
+ doc.iesg_state = state
+
+ e = Event()
+ e.type = "changed_document"
+ e.by = login
+ e.doc = doc
+ e.desc = u"State changed to %s from %s by %s" % (
+ doc.iesg_state.name,
+ prev_state.name if prev_state else "None",
+ login.get_name())
+ e.save()
+
+ doc.time = e.time
+ doc.save()
+
+ email_state_changed(request, doc, strip_tags(e.desc))
+ email_owner(request, doc, doc.ad, login, e.desc)
+
+ if doc.iesg_state_id == "lc-req":
+ request_last_call(request, doc)
+
+ return render_to_response('idrfc/last_call_requested.html',
+ dict(doc=doc,
+ url=doc.get_absolute_url()),
+ context_instance=RequestContext(request))
+
+ return HttpResponseRedirect(doc.get_absolute_url())
+
+ else:
+ form = ChangeStateForm(initial=dict(state=doc.iesg_state_id))
+
+ next_states = get_next_iesg_states(doc.iesg_state)
+ prev_state = None
+
+ hists = DocHistory.objects.filter(doc=doc).exclude(iesg_state=doc.iesg_state).order_by("-time")[:1]
+ if hists:
+ prev_state = hists[0].iesg_state
+
+ return render_to_response('idrfc/change_stateREDESIGN.html',
+ dict(form=form,
+ doc=doc,
+ prev_state=prev_state,
+ next_states=next_states),
+ context_instance=RequestContext(request))
+
+if settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ change_state = change_stateREDESIGN
+ ChangeStateForm = ChangeStateFormREDESIGN
+
+
def dehtmlify_textarea_text(s):
return s.replace(" ", "\n").replace("", "").replace("", "").replace(" ", " ")
diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py
index e650ab2a1..d23227f93 100644
--- a/ietf/idrfc/views_search.py
+++ b/ietf/idrfc/views_search.py
@@ -409,16 +409,16 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
r.obsoleted_by_list = []
r.updated_by_list = []
- xed_by = RelatedDocument.objects.filter(doc_alias__name__in=rfc_aliases.values(), relationship__in=("obs", "updates")).select_related('doc_alias__document_id')
- rel_rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[rel.document_id for rel in xed_by]).values_list('document_id', 'name'))
+ 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.doc_alias.document_id]
+ 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.document_id][3:]))
+ getattr(r, attr).append(int(rel_rfc_aliases[rel.source_id][3:]))
# sort
diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py
index 11136a801..3664b9c27 100644
--- a/ietf/idtracker/models.py
+++ b/ietf/idtracker/models.py
@@ -249,11 +249,11 @@ class PersonOrOrgInfo(models.Model):
date_created = models.DateField(auto_now_add=True, null=True)
created_by = models.CharField(blank=True, null=True, max_length=8)
address_type = models.CharField(blank=True, null=True, max_length=4)
- def save(self):
+ def save(self, **kwargs):
self.first_name_key = self.first_name.upper()
self.middle_initial_key = self.middle_initial.upper()
self.last_name_key = self.last_name.upper()
- super(PersonOrOrgInfo, self).save()
+ super(PersonOrOrgInfo, self).save(**kwargs)
def __str__(self):
# For django.VERSION 0.96
if self.first_name == '' and self.last_name == '':
diff --git a/ietf/ietfauth/models.py b/ietf/ietfauth/models.py
index 680b540ea..080f9767b 100644
--- a/ietf/ietfauth/models.py
+++ b/ietf/ietfauth/models.py
@@ -61,6 +61,11 @@ class IetfUserProfile(models.Model):
except:
return None
+ def email(self):
+ # quick hack to bind new and old schema together for the time being
+ from person.models import Email
+ return Email.objects.get(address=self.person().email()[1])
+
def __str__(self):
return "IetfUserProfile(%s)" % (self.user,)
diff --git a/ietf/templates/idrfc/change_stateREDESIGN.html b/ietf/templates/idrfc/change_stateREDESIGN.html
new file mode 100644
index 000000000..9e88a309b
--- /dev/null
+++ b/ietf/templates/idrfc/change_stateREDESIGN.html
@@ -0,0 +1,64 @@
+{% extends "base.html" %}
+
+{% block title %}Change state of {{ doc }}{% endblock %}
+
+{% block morecss %}
+form.change-state select {
+ width: 22em;
+}
+form.change-state .actions {
+ text-align: right;
+ padding-top: 10px;
+}
+.next-states,
+.prev-state {
+ margin-bottom: 30px;
+}
+.next-states form,
+.prev-state form {
+ display: inline;
+ margin-right: 10px;
+}
+{% endblock %}
+
+{% block content %}
+
+{% endif %}
+{% endblock %}
diff --git a/ietf/templates/idrfc/last_call_requested.html b/ietf/templates/idrfc/last_call_requested.html
index d61884ff5..f7b3c571b 100644
--- a/ietf/templates/idrfc/last_call_requested.html
+++ b/ietf/templates/idrfc/last_call_requested.html
@@ -12,6 +12,6 @@ secretariat takes appropriate steps. This may take up to one business
day, as it involves a person taking action.