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 @@ + + + + Yes + + 1 + + + No Objection + + 1 + + + Abstain + + 1 + + + Discuss + + 1 + + + Recuse + + 1 + + + No record + + 1 + + + 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. + 1 + + + Revised ID Needed + An updated ID is needed to address the issues that have been raised. + 1 + + + IANA-coord + + 1 + + + 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. + 1 + + + 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. + 1 + + + MissingRef + + 1 + + + FastTrack + + 1 + + + Review by RFC Editor + + 1 + + + Via RFC Editor + + 1 + + + Expired tombstone + + 1 + + + Approved in minute + + 1 + + + Has errata + + 1 + + + Updates + + 1 + + + Replaces + + 1 + + + Obsoletes + + 1 + + + Reviews + + 1 + + + References + + 1 + + + RFC + + 1 + + + Expired + + 1 + + + Replaced + + 1 + + + Active + + 1 + + + Withdrawn by Submitter + + 1 + + + Withdrawn by IETF + + 1 + + + IETF + + 1 + + + Independent Submission + + 1 + + + Legacy + + 1 + + + IAB + + 1 + + + IRTF + + 1 + + + Draft + + 1 + + + External + + 1 + + + BOF + + 1 + + + Proposed + + 1 + + + Active + + 1 + + + Dormant + + 1 + + + Concluded + + 1 + + + Unknown + + 1 + + + IETF + + 1 + + + Area + + 1 + + + WG + + 1 + + + RG + + 1 + + + Team + + 1 + + + Individual + + 1 + + + RFC Published + The ID has been published as an RFC. + 1 + + + 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.) + 1 + + + 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. + 1 + + + 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). + 1 + + + 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. + 1 + + + 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. + 1 + + + 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. + 1 + + + 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. + 1 + + + RFC Ed Queue + The document is in the RFC editor Queue (as confirmed by http://www.rfc-editor.org/queue.html). + 1 + + + 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. + 1 + + + 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. + 1 + + + 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. + 1 + + + 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. + 1 + + + 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. + 1 + + + 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. + 1 + + + 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. + 1 + + + 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. + 1 + + + Best Current Practice + + 1 + + + Draft Standard + + 1 + + + Experimental + + 1 + + + Historic + + 1 + + + Informational + + 1 + + + Proposed Standard + + 1 + + + Standard + + 1 + + + Area Director + + 1 + + + Standard + + 1 + + + Draft Standard + + 1 + + + Proposed Standard + + 1 + + + Informational + + 1 + + + Experimental + + 1 + + + Best Current Practice + + 1 + + + Historic + + 1 + + + Unknown + + 1 + + \ 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.

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

This document describes a method to improve the bandwidth utilization of RTP streams over network paths that carry multiple Real-time Transport Protocol (RTP) streams in parallel between two endpoints, as in voice trunking. The method combines standard protocols that provide compression, multiplexing, and tunneling over a network path for the purpose of reducing the bandwidth used when multiple RTP streams are carried over that path. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.

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

This memo provides guidelines for authors and reviewers of IETF standards-track specifications containing MIB modules. Applicable portions may be used as a basis for reviews of other MIB documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.

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

This document updates RFC 4181, "Guidelines for Authors and Reviewers of MIB Documents", to recognize the creation of the IETF Trust. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.

+ draft-heard-rfc4181-update-00 + + RFC4181 + + + BCP0111 + + BEST CURRENT PRACTICE + BEST CURRENT PRACTICE + IETF + NON WORKING GROUP +
+ + STD0061 + A One-Time Password System + + RFC2289 + + +
+''' + +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 %} +

Change state of {{ doc }}

+ +

For help on the states, see the state table.

+ +
+ + {{ form.as_table }} + + + +
+ Back + +
+
+ +{% if next_states %} +

Or jump directly to

+ +
+ {% for n in next_states %} +
+ + +
+ {% endfor %} +
+{% endif %} + +{% if prev_state %} +

Or revert to previous state

+ +
+
+ + +
+
+{% 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.

- Back + Back
{% endblock %} diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py index bf6029fa4..d560ecff0 100644 --- a/redesign/doc/admin.py +++ b/redesign/doc/admin.py @@ -12,7 +12,7 @@ class DocHistoryAdmin(admin.ModelAdmin): list_display = ['doc', 'rev', 'state', 'group', 'pages', 'intended_std_level', 'author_list', 'time'] search_fields = ['doc__name'] ordering = ['time', 'doc', 'rev'] - raw_id_fields = ['authors', 'related'] + raw_id_fields = ['doc', 'authors', 'related', 'group', 'shepherd', 'ad'] admin.site.register(DocHistory, DocHistoryAdmin) class DocAliasAdmin(admin.ModelAdmin): diff --git a/redesign/doc/models.py b/redesign/doc/models.py index de14f3054..608a10987 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -1,6 +1,8 @@ # Copyright The IETF Trust 2007, All Rights Reserved from django.db import models +from django.core.urlresolvers import reverse as urlreverse + from redesign.group.models import * from redesign.name.models import * from redesign.person.models import Email @@ -19,17 +21,17 @@ class DocumentInfo(models.Model): tags = models.ManyToManyField(DocInfoTagName, blank=True, null=True) # Revised ID Needed, ExternalParty, AD Followup, ... stream = models.ForeignKey(DocStreamName, blank=True, null=True) # IETF, IAB, IRTF, Independent Submission group = models.ForeignKey(Group, blank=True, null=True) # WG, RG, IAB, IESG, Edu, Tools - wg_state = models.ForeignKey(WgDocStateName, blank=True, null=True) # Not/Candidate/Active/Parked/LastCall/WriteUp/Submitted/Dead - iesg_state = models.ForeignKey(IesgDocStateName, blank=True, null=True) # - iana_state = models.ForeignKey(IanaDocStateName, blank=True, null=True) - rfc_state = models.ForeignKey(RfcDocStateName, blank=True, null=True) + wg_state = models.ForeignKey(WgDocStateName, verbose_name="WG state", blank=True, null=True) # Not/Candidate/Active/Parked/LastCall/WriteUp/Submitted/Dead + iesg_state = models.ForeignKey(IesgDocStateName, verbose_name="IESG state", blank=True, null=True) # + iana_state = models.ForeignKey(IanaDocStateName, verbose_name="IANA state", blank=True, null=True) + rfc_state = models.ForeignKey(RfcDocStateName, verbose_name="RFC state", blank=True, null=True) # Other abstract = models.TextField() - rev = models.CharField(max_length=16) + rev = models.CharField(verbose_name="revision", max_length=16) pages = models.IntegerField(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) + ad = models.ForeignKey(Email, verbose_name="area director", 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) external_url = models.URLField(blank=True) # Should be set for documents with type 'External'. @@ -50,11 +52,11 @@ class DocumentInfo(models.Model): return e[0] if e else None class RelatedDocument(models.Model): - document = models.ForeignKey('Document') # source - doc_alias = models.ForeignKey('DocAlias') # target + source = models.ForeignKey('Document') + target = models.ForeignKey('DocAlias') 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.source.name, self.relationship.name.lower(), self.target.name) class DocumentAuthor(models.Model): document = models.ForeignKey('Document') @@ -62,7 +64,7 @@ class DocumentAuthor(models.Model): order = models.IntegerField() def __unicode__(self): - return u"%s %s (%s)" % (self.document.name, self.email.get_name(), self.order) + return u"%s %s (%s)" % (self.document.name, self.author.get_name(), self.order) class Meta: ordering = ["document", "order"] @@ -71,55 +73,28 @@ 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): - try: - fields = dict([(field.name, getattr(self, field.name)) - for field in self._meta.fields - if field is not self._meta.pk]) - except: - for field in self._meta.fields: - print "* %24s"%field.name, - print getattr(self, field.name) - raise - many2many = dict([(field.name, getattr(self, field.name).all()) - for field in self._meta.many_to_many ]) - return fields, many2many - - def save_with_history(self, force_insert=False, force_update=False): - fields, many2many = self.values() - fields["doc"] = self - try: - snap = DocHistory.objects.get(**dict((k,v) for k,v in fields.items() if k != 'time')) - # FIXME: what if there are two with the same set of values - # at different points in time? - if snap.time > fields["time"]: - snap.time = fields["time"] - snap.save() - except DocHistory.DoesNotExist: - snap = DocHistory(**fields) - snap.save() - for m in many2many: - # FIXME: check that this works with related/authors - #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) + + def get_absolute_url(self): + name = self.name + if self.state == "rfc": + aliases = self.docalias_set.filter(name__startswith="rfc") + if aliases: + name = aliases[0].name + return urlreverse('doc_view', kwargs={ 'name': name }) + + def file_tag(self): + # FIXME: compensate for tombstones? + return u"<%s-%s.txt>" % (self.name, self.rev) class RelatedDocHistory(models.Model): - document = models.ForeignKey('DocHistory') # source - doc_alias = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set") # target + source = models.ForeignKey('DocHistory') + target = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set") relationship = models.ForeignKey(DocRelationshipName) def __unicode__(self): - return u"%s %s %s" % (self.document.doc.name, self.relationship.name.lower(), self.doc_alias.name) + return u"%s %s %s" % (self.source.doc.name, self.relationship.name.lower(), self.target.name) class DocHistoryAuthor(models.Model): document = models.ForeignKey('DocHistory') @@ -127,18 +102,58 @@ class DocHistoryAuthor(models.Model): order = models.IntegerField() def __unicode__(self): - return u"%s %s (%s)" % (self.document.doc.name, self.email.get_name(), self.order) + return u"%s %s (%s)" % (self.document.doc.name, self.author.get_name(), self.order) class Meta: ordering = ["document", "order"] - + class DocHistory(DocumentInfo): doc = models.ForeignKey(Document) # ID of the Document this relates to + # Django won't let us define these in the base class, so we have + # to repeat them related = models.ManyToManyField('DocAlias', through=RelatedDocHistory, blank=True) authors = models.ManyToManyField(Email, through=DocHistoryAuthor, blank=True) def __unicode__(self): return unicode(self.doc.name) +def save_document_in_history(doc): + def get_model_fields_as_dict(obj): + return dict((field.name, getattr(obj, field.name)) + for field in obj._meta.fields + if field is not obj._meta.pk) + + # copy fields + fields = get_model_fields_as_dict(doc) + fields["doc"] = doc + + dochist = DocHistory(**fields) + dochist.save() + + # copy many to many + for field in doc._meta.many_to_many: + if not field.rel.through: + # just add the attributes + rel = getattr(dochist, field.name) + for item in getattr(doc, field.name).all(): + rel.add(item) + + # copy remaining tricky many to many + def transfer_fields(obj, HistModel): + mfields = get_model_fields_as_dict(item) + # map doc -> dochist + for k, v in mfields.iteritems(): + if v == doc: + mfields[k] = dochist + HistModel.objects.create(**mfields) + + for item in RelatedDocument.objects.filter(source=doc): + transfer_fields(item, RelatedDocHistory) + + for item in DocumentAuthor.objects.filter(document=doc): + transfer_fields(item, DocHistoryAuthor) + + return dochist + 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 @@ -194,8 +209,10 @@ EVENT_TYPES = [ # 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"), ("changed_ballot_writeup_text", "Changed ballot writeup text"), diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 7f984aeb0..91835e8f8 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -145,7 +145,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(relateddocument__doc_alias__document=self, relateddocument__relationship="replaces") + r = InternetDraft.objects.filter(relateddocument__target__document=self, relateddocument__relationship="replaces") return r[0] if r else None #replaces = FKAsOneToOne('replaces', reverse=True) @@ -156,7 +156,7 @@ class InternetDraft(Document): @property def replaces_set(self): - return InternetDraft.objects.filter(docalias__relateddocument__document=self, docalias__relateddocument__relationship="replaces") + return InternetDraft.objects.filter(docalias__relateddocument__source=self, docalias__relateddocument__relationship="replaces") #review_by_rfc_editor = models.BooleanField() @property @@ -631,25 +631,25 @@ class InternetDraft(Document): #updates = models.CharField(max_length=200,blank=True,null=True) @property def updates(self): - 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"))) + return ",".join("RFC%s" % n for n in sorted(d.rfc_number for d in InternetDraft.objects.filter(docalias__relateddocument__source=self, docalias__relateddocument__relationship="updates"))) #updated_by = models.CharField(max_length=200,blank=True,null=True) @property def updated_by(self): 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")] + self.updated_by_list = [d.rfc_number for d in InternetDraft.objects.filter(relateddocument__target__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 def obsoletes(self): - 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"))) + return ",".join("RFC%s" % n for n in sorted(d.rfc_number for d in InternetDraft.objects.filter(docalias__relateddocument__source=self, docalias__relateddocument__relationship="obs"))) #obsoleted_by = models.CharField(max_length=200,blank=True,null=True) @property def obsoleted_by(self): 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")] + self.obsoleted_by_list = [d.rfc_number for d in InternetDraft.objects.filter(relateddocument__target__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) diff --git a/redesign/group/models.py b/redesign/group/models.py index 8d5311b3e..c25d91c55 100644 --- a/redesign/group/models.py +++ b/redesign/group/models.py @@ -49,5 +49,5 @@ class Role(models.Model): email = models.ForeignKey(Email) auth = models.CharField(max_length=255, blank=True) def __unicode__(self): - return self.name + return u"%s is %s in %s" % (self.email.get_name(), self.name.name, self.grop.acronym) diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py index 33a2fae54..3de8c6fdd 100755 --- a/redesign/import-document-state.py +++ b/redesign/import-document-state.py @@ -890,7 +890,7 @@ for index, o in enumerate(all_drafts.iterator()): # replacements if o.replaced_by: 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) + RelatedDocument.objects.get_or_create(source=replacement, target=d_alias, relationship=relationship_replaces) # the RFC-related attributes are imported when we handle the RFCs below @@ -987,9 +987,9 @@ for index, o in enumerate(all_rfcs.iterator()): 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) + RelatedDocument.objects.get_or_create(source=other, target=d_alias, relationship=rel_type) else: - RelatedDocument.objects.get_or_create(document=d, doc_alias=other_alias, relationship=rel_type) + RelatedDocument.objects.get_or_create(source=d, target=other_alias, relationship=rel_type) def parse_relation_list(s): if not s: diff --git a/redesign/name/models.py b/redesign/name/models.py index da57c22ce..badadfa89 100644 --- a/redesign/name/models.py +++ b/redesign/name/models.py @@ -55,3 +55,31 @@ class IntendedStdLevelName(NameModel): Practice, Historic, ...""" class BallotPositionName(NameModel): """ Yes, NoObjection, Abstain, Discuss, Recuse """ + + +def get_next_iesg_states(iesg_state): + if not iesg_state: + return () + + next = { + "pub-req": ("ad-eval", "watching", "dead"), + "ad-eval": ("watching", "lc-req", "review-e", "iesg-eva"), + "review-e": ("ad-eval", ), + "lc-req": ("lc", ), + "lc": ("writeupw", "goaheadw"), + "writeupw": ("goaheadw", ), + "goaheadw": ("iesg-eva", ), + "iesg-eva": ("nopubadw", "defer", "ann"), + "defer": ("iesg-eva", ), + "ann": ("approved", ), + "approved": ("rfcqueue", ), + "rfcqueue": ("pub", ), + "pub": ("dead", ), + "nopubadw": ("nopubanw", ), + "nopubanw": ("dead", ), + "watching": ("pub-req", ), + "dead": ("pub-req", ), + } + + return IesgDocStateName.objects.filter(slug__in=next.get(iesg_state.slug, ())) + diff --git a/redesign/person/models.py b/redesign/person/models.py index 6fc918b3a..1fbce34f6 100644 --- a/redesign/person/models.py +++ b/redesign/person/models.py @@ -65,3 +65,10 @@ class Email(models.Model): def get_name(self): return self.person.name if self.person else self.address + + def formatted_email(self): + if self.person and self.person.name: + return u"%s <%s>" % (self.person.name, self.address) + else: + return self.address + From 4b15644a1ed9bb9da5300185f13db1be5a81aaa3 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 7 Feb 2011 17:05:18 +0000 Subject: [PATCH 23/75] Import WGEditor too, fixup AreaDirector import - Legacy-Id: 2816 --- redesign/import-roles.py | 44 +++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/redesign/import-roles.py b/redesign/import-roles.py index cc39eebe9..4bcaa0388 100755 --- a/redesign/import-roles.py +++ b/redesign/import-roles.py @@ -15,7 +15,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, IDAuthor, PersonOrOrgInfo +from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo, WGEditor # assumptions: # - groups have been imported @@ -23,9 +23,9 @@ from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrg # PersonOrOrgInfo/PostalAddress/EmailAddress/PhoneNumber are not # imported, although some information is retrieved from those -# imports IESGLogin, AreaDirector and persons from IDAuthor +# imports IESGLogin, AreaDirector, WGEditor and persons from IDAuthor -# should probably import WGChair, WGEditor, WGSecretary, +# should probably import WGChair, WGSecretary, # WGTechAdvisor, Role, ChairsHistory, IRTFChair # make sure names exist @@ -38,7 +38,8 @@ def name(name_class, slug, name, desc=""): return obj area_director_role = name(RoleName, "ad", "Area Director") - +inactive_area_director_role = name(RoleName, "ex-ad", "Ex-Area Director", desc="In-active Area Director") +wg_editor_role = name(RoleName, "wgeditor", "Working Group Editor") # helpers for creating the objects def get_or_create_email(o, create_fake): @@ -86,11 +87,14 @@ for o in IESGLogin.objects.all(): email = get_or_create_email(o, create_fake=False) + if o.user_level == IESGLogin.INACTIVE_AD_LEVEL: + if not Role.objects.filter(name=inactive_area_director_role, email=email): + # connect them directly to the IESG as we don't really know where they belong + Role.objects.create(name=inactive_area_director_role, group=Group.objects.get(acronym="iesg"), email=email) + # FIXME: import o.login_name, o.user_level - # AreaDirector -Role.objects.filter(name=area_director_role).delete() for o in AreaDirector.objects.all(): if not o.area: print "NO AREA", o.person, o.area_id @@ -101,8 +105,34 @@ for o in AreaDirector.objects.all(): area = Group.objects.get(acronym=o.area.area_acronym.acronym) - Role.objects.get_or_create(name=area_director_role, group=area, email=email) + if area.state_id == "active": + role_type = area_director_role + else: + # can't be active area director in an inactive area + role_type = inactive_area_director_role + r = Role.objects.filter(name__in=(area_director_role, inactive_area_director_role), + email=email) + if r and r[0].group == "iesg": + r[0].group = area + r[0].name = role_type + r[0].save() + else: + Role.objects.get_or_create(name=role_type, group=area, email=email) + +# WGEditor +for o in WGEditor.objects.all(): + # if not o.group_acronym: + # print "NO GROUP", o.person, o.group_acronym_id + # continue + + print "importing WGEditor", o.group_acronym, o.person + email = get_or_create_email(o, create_fake=False) + + group = Group.objects.get(acronym=o.group_acronym.group_acronym.acronym) + + Role.objects.get_or_create(name=wg_editor_role, group=group, 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') From d56e446833847122a7ef0e9c3800ebe816093669 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 8 Feb 2011 14:17:53 +0000 Subject: [PATCH 24/75] Port edit info view with tests to new schema - Legacy-Id: 2823 --- ietf/idrfc/fixtures/names.xml | 5 + ietf/idrfc/mails.py | 2 +- ietf/idrfc/testsREDESIGN.py | 202 +++++++++-------- ietf/idrfc/utils.py | 56 +++++ ietf/idrfc/views_doc.py | 2 +- ietf/idrfc/views_edit.py | 234 +++++++++++++++++++- ietf/templates/idrfc/edit_infoREDESIGN.html | 47 ++++ redesign/doc/admin.py | 1 + redesign/doc/models.py | 2 +- redesign/group/models.py | 4 +- 10 files changed, 451 insertions(+), 104 deletions(-) create mode 100644 ietf/templates/idrfc/edit_infoREDESIGN.html diff --git a/ietf/idrfc/fixtures/names.xml b/ietf/idrfc/fixtures/names.xml index a7fedc301..6cc9e577d 100644 --- a/ietf/idrfc/fixtures/names.xml +++ b/ietf/idrfc/fixtures/names.xml @@ -371,6 +371,11 @@ 1 + + Ex-Area Director + In-active Area Director + 1 + Standard diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index d7210c231..ac179df93 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -54,7 +54,7 @@ def email_ownerREDESIGN(request, doc, owner, changed_by, text, subject=None): to = owner.formatted_email() send_mail(request, to, "DraftTracker Mail System ", - "%s updated by %s" % (doc.file_tag(), changed_by), + "%s updated by %s" % (doc.file_tag(), changed_by.get_name()), "idrfc/change_notice.txt", dict(text=html_to_text(text), doc=doc, diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index 1a5fe84e6..9fffc0fc8 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -47,6 +47,7 @@ from doc.models import * from name.models import * from group.models import * from person.models import * +from ietf.iesg.models import TelechatDates from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest, login_testing_unauthorized from ietf.utils.test_runner import mail_outbox @@ -105,13 +106,13 @@ def make_test_data(): name="Ano Therdir", ascii="Ano Therdir", ) - ad = Email.objects.create( + email = Email.objects.create( address="ano@ietf.org", person=p) Role.objects.create( name_id="ad", group=area, - email=ad) + email=email) porg = PersonOrOrgInfo.objects.create( first_name="Ano", last_name="Therdir", @@ -164,6 +165,7 @@ def make_test_data(): type_id="draft", title="Optimizing Martian Network Topologies", state_id="active", + iesg_state_id="pub-req", stream_id="ietf", group=group, abstract="Techniques for achieving near-optimal Martian networks.", @@ -171,7 +173,8 @@ def make_test_data(): pages=2, intended_std_level_id="ps", ad=ad, - notify="aliens@example.mars" + notify="aliens@example.mars", + note="", ) DocAlias.objects.create( @@ -179,12 +182,21 @@ def make_test_data(): name=draft.name, ) + # draft has only one event Event.objects.create( type="started_iesg_process", by=ad, doc=draft, desc="Added draft", ) + + t = datetime.date.today() + dates = TelechatDates(date1=t, + date2=t + datetime.timedelta(days=7), + date3=t + datetime.timedelta(days=14), + date4=t + datetime.timedelta(days=21), + ) + super(dates.__class__, dates).save(force_insert=True) return draft @@ -222,7 +234,7 @@ class ChangeStateTestCase(django.test.TestCase): # change state - history_before = draft.event_set.count() + events_before = draft.event_set.count() mailbox_before = len(mail_outbox) r = self.client.post(url, dict(state="review-e")) @@ -230,7 +242,7 @@ class ChangeStateTestCase(django.test.TestCase): 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.assertEquals(draft.event_set.count(), events_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']) @@ -288,149 +300,147 @@ class ChangeStateTestCase(django.test.TestCase): class EditInfoTestCase(django.test.TestCase): - fixtures = ['base', 'draft'] + fixtures = ['names'] 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) + draft = make_test_data() + url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) self.assertEquals(r.status_code, 200) q = PyQuery(r.content) - self.assertEquals(len(q('form select[name=intended_status]')), 1) + self.assertEquals(len(q('form select[name=intended_std_level]')), 1) self.assertEquals(len(q('form input[name=via_rfc_editor]')), 1) - prev_job_owner = draft.idinternal.job_owner + prev_ad = draft.ad # faulty post - r = self.client.post(url, dict(job_owner="123456789")) + r = self.client.post(url, dict(ad="123456789")) self.assertEquals(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(len(q('form ul.errorlist')) > 0) - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") - self.assertEquals(draft.idinternal.job_owner, prev_job_owner) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.ad, prev_ad) # edit info - history_before = draft.idinternal.comments().count() + events_before = draft.event_set.all().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] + + new_ad = Email.objects.get(address="ano@ietf.org") r = self.client.post(url, - dict(intended_status=str(draft.intended_status_id), + dict(intended_std_level=str(draft.intended_std_level.pk), 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="", + ad=str(new_ad.pk), + notify="test@example.com", + note="New 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) + draft = Document.objects.get(name=draft.name) + self.assertTrue(draft.tags.filter(slug="via-rfc")) + self.assertEquals(draft.ad, new_ad) + self.assertEquals(draft.note, "New note") + self.assertTrue(not draft.latest_event(Telechat, type="telechat_date")) + self.assertEquals(draft.event_set.count(), events_before + 4) self.assertEquals(len(mail_outbox), mailbox_before + 1) - self.assertTrue(draft.filename in mail_outbox[-1]['Subject']) + self.assertTrue(draft.name 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) + draft = make_test_data() + + url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) - data = dict(intended_status=str(draft.intended_status_id), + data = dict(intended_std_level=str(draft.intended_std_level_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", + ad=str(draft.ad_id), + notify="test@example.com", note="", ) from ietf.iesg.models import TelechatDates # add to telechat + self.assertTrue(not draft.latest_event(Telechat, "scheduled_for_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) + draft = Document.objects.get(name=draft.name) + self.assertTrue(draft.latest_event(Telechat, "scheduled_for_telechat")) + self.assertEquals(draft.latest_event(Telechat, "scheduled_for_telechat").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) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.latest_event(Telechat, "scheduled_for_telechat").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) + draft = Document.objects.get(name=draft.name) + self.assertTrue(not draft.latest_event(Telechat, "scheduled_for_telechat").telechat_date) + + for e in draft.event_set.all(): + print e.desc - 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) + def test_start_iesg_process_on_draft(self): + draft = make_test_data() + draft.ad = None + draft.iesg_state = None + draft.save() + draft.event_set.all().delete() + + url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) self.assertEquals(r.status_code, 200) q = PyQuery(r.content) - self.assertEquals(len(q('form select[name=intended_status]')), 1) + self.assertEquals(len(q('form select[name=intended_std_level]')), 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')) + self.assertTrue('@' in q('form input[name=notify]')[0].get('value')) # add mailbox_before = len(mail_outbox) - job_owner = IESGLogin.objects.filter(user_level=1)[0] - area = Area.active_areas()[0] + ad = Email.objects.get(address="aread@ietf.org") r = self.client.post(url, - dict(intended_status=str(draft.intended_status_id), + dict(intended_std_level=str(draft.intended_std_level_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", + ad=ad, + notify="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) + draft = Document.objects.get(name=draft.name) + self.assertTrue(draft.tags.filter(slug="via-rfc")) + self.assertEquals(draft.ad, ad) + self.assertEquals(draft.note, "This is a note") + self.assertTrue(not draft.latest_event(Telechat, type="scheduled_for_telechat")) + self.assertEquals(draft.event_set.count(), 4) + self.assertEquals(draft.event_set.order_by('time', '-id')[0].type, "started_iesg_process") self.assertEquals(len(mail_outbox), mailbox_before) class ResurrectTestCase(django.test.TestCase): - fixtures = ['base', 'draft'] + fixtures = ['names'] def test_request_resurrect(self): draft = InternetDraft.objects.get(filename="draft-ietf-mip6-cn-ipsec") @@ -450,7 +460,7 @@ class ResurrectTestCase(django.test.TestCase): # request resurrect - history_before = draft.idinternal.comments().count() + events_before = draft.idinternal.comments().count() mailbox_before = len(mail_outbox) r = self.client.post(url, dict()) @@ -458,7 +468,7 @@ class ResurrectTestCase(django.test.TestCase): 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.assertEquals(draft.idinternal.comments().count(), events_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']) @@ -471,7 +481,7 @@ class ResurrectTestCase(django.test.TestCase): url = urlreverse('doc_resurrect', kwargs=dict(name=draft.filename)) - login_testing_unauthorized(self, "klm", url) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) @@ -480,7 +490,7 @@ class ResurrectTestCase(django.test.TestCase): self.assertEquals(len(q('form input[type=submit]')), 1) # request resurrect - history_before = draft.idinternal.comments().count() + events_before = draft.idinternal.comments().count() mailbox_before = len(mail_outbox) r = self.client.post(url, dict()) @@ -488,18 +498,18 @@ class ResurrectTestCase(django.test.TestCase): 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.assertEquals(draft.idinternal.comments().count(), events_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'] + fixtures = ['names'] 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) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) @@ -508,13 +518,13 @@ class AddCommentTestCase(django.test.TestCase): self.assertEquals(len(q('form textarea[name=comment]')), 1) # request resurrect - history_before = draft.idinternal.comments().count() + events_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.assertEquals(draft.idinternal.comments().count(), events_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']) @@ -536,7 +546,7 @@ class EditPositionTestCase(django.test.TestCase): self.assertEquals(len(q('form textarea[name=comment_text]')), 1) # vote - history_before = draft.idinternal.comments().count() + events_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", @@ -550,11 +560,11 @@ class EditPositionTestCase(django.test.TestCase): 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.assertEquals(draft.idinternal.comments().count(), events_before + 3) self.assertTrue("New position" in draft.idinternal.comments()[2].comment_text) # recast vote - history_before = draft.idinternal.comments().count() + events_before = draft.idinternal.comments().count() r = self.client.post(url, dict(position="noobj")) self.assertEquals(r.status_code, 302) @@ -562,23 +572,23 @@ class EditPositionTestCase(django.test.TestCase): 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.assertEquals(draft.idinternal.comments().count(), events_before + 1) self.assertTrue("Position" in draft.idinternal.comments()[0].comment_text) # clear vote - history_before = draft.idinternal.comments().count() + events_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.assertEquals(draft.idinternal.comments().count(), events_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) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) @@ -587,7 +597,7 @@ class EditPositionTestCase(django.test.TestCase): self.assertTrue(len(q('form input[name=position]')) > 0) # vote for rhousley - history_before = draft.idinternal.comments().count() + events_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")) @@ -679,7 +689,7 @@ class BallotWriteupsTestCase(django.test.TestCase): 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) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) @@ -716,7 +726,7 @@ class BallotWriteupsTestCase(django.test.TestCase): 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) + login_testing_unauthorized(self, "secretary", url) mailbox_before = len(mail_outbox) @@ -733,7 +743,7 @@ class BallotWriteupsTestCase(django.test.TestCase): 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) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) @@ -788,7 +798,7 @@ class BallotWriteupsTestCase(django.test.TestCase): 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) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) @@ -828,7 +838,7 @@ class ApproveBallotTestCase(django.test.TestCase): 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) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) @@ -863,7 +873,7 @@ class MakeLastCallTestCase(django.test.TestCase): draft.save() url = urlreverse('doc_make_last_call', kwargs=dict(name=draft.filename)) - login_testing_unauthorized(self, "klm", url) + login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) @@ -1082,13 +1092,13 @@ class ExpireLastCallTestCase(django.test.TestCase): self.assertEquals(len(drafts), 1) mailbox_before = len(mail_outbox) - history_before = draft.idinternal.comments().count() + events_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(draft.idinternal.comments().count(), events_before + 1) self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue("Last Call Expired" in mail_outbox[-1]["Subject"]) diff --git a/ietf/idrfc/utils.py b/ietf/idrfc/utils.py index 28dbec74a..a950c8a01 100644 --- a/ietf/idrfc/utils.py +++ b/ietf/idrfc/utils.py @@ -1,3 +1,5 @@ +from django.conf import settings + from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin from ietf.idrfc.mails import * @@ -92,3 +94,57 @@ def update_telechat(request, idinternal, new_telechat_date, new_returning_item=N (new_telechat_date, idinternal.telechat_date)) idinternal.telechat_date = new_telechat_date + +def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_item=None): + on_agenda = bool(new_telechat_date) + + from doc.models import Telechat + prev = doc.latest_event(Telechat, type="scheduled_for_telechat") + prev_returning = bool(prev and prev.returning_item) + prev_telechat = prev.telechat_date if prev else None + prev_agenda = bool(prev_telechat) + + returning_item_changed = bool(new_returning_item != None and new_returning_item != prev_returning) + + if new_returning_item == None: + returning = prev_returning + else: + returning = new_returning_item + + if returning == prev_returning and new_telechat_date == prev_telechat: + # fully updated, nothing to do + return + + # auto-update returning item + if (not returning_item_changed and on_agenda and prev_agenda + and new_telechat_date != prev_telechat): + returning = True + + e = Telechat() + e.type = "scheduled_for_telechat" + e.by = by + e.doc = doc + e.returning_item = returning + e.telechat_date = new_telechat_date + + if on_agenda != prev_agenda: + if on_agenda: + e.desc = "Placed on agenda for telechat - %s by %s" % ( + new_telechat_date, by.get_name()) + else: + e.desc = "Removed from agenda for telechat by %s" % by.get_name() + elif on_agenda and new_telechat_date != prev_telechat: + e.desc = "Telechat date has been changed to %s from %s by %s" % ( + new_telechat_date, prev_telechat, by.get_name()) + else: + # we didn't reschedule but flipped returning item bit - let's + # just explain that + if returning: + e.desc = "Added as returning item on telechat by %s" % by.get_name() + else: + e.desc = "Removed as returning item on telechat by %s" % by.get_name() + + e.save() + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + update_telechat = update_telechatREDESIGN diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index b0269b57d..741adb4fe 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -152,7 +152,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', '-id'): + 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) diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index 788fa02d5..15e82eda8 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -22,8 +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 +from doc.models import Document, Event, Status, Telechat, save_document_in_history, DocHistory +from name.models import IesgDocStateName, IntendedStdLevelName, DocInfoTagName, get_next_iesg_states class ChangeStateForm(forms.Form): state = forms.ModelChoiceField(IDState.objects.all(), empty_label=None, required=True) @@ -90,7 +90,7 @@ 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": + if (not doc.latest_event(type="started_iesg_process")) or doc.state_id == "expired": raise Http404() login = request.user.get_profile().email() @@ -379,6 +379,234 @@ def edit_info(request, name): login=login), context_instance=RequestContext(request)) +class NameFromEmailModelChoiceField(forms.ModelChoiceField): + def label_from_instance(self, obj): + return obj.get_name() + +class EditInfoFormREDESIGN(forms.Form): + intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.all(), empty_label=None, required=True) + status_date = forms.DateField(required=False, help_text="Format is YYYY-MM-DD") + via_rfc_editor = forms.BooleanField(required=False, label="Via IRTF or RFC Editor") + ad = NameFromEmailModelChoiceField(Email.objects.filter(role__name__in=("ad", "ex-ad")).order_by('role__name', 'person__name'), label="Responsible AD", empty_label=None, required=True) + notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False) + note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False) + telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False) + returning_item = forms.BooleanField(required=False) + + def __init__(self, *args, **kwargs): + old_ads = kwargs.pop('old_ads') + + super(self.__class__, self).__init__(*args, **kwargs) + + # fix up ad field + choices = self.fields['ad'].choices + ex_ads = dict((e.pk, e) for e in Email.objects.filter(role__name="ex-ad")) + if old_ads: + # separate active ADs from inactive + for i, t in enumerate(choices): + if t[0] in ex_ads: + choices.insert(i, ("", "----------------")) + break + else: + # remove old ones + self.fields['ad'].choices = [t for t in choices if t[0] not in ex_ads] + + # telechat choices + dates = TelechatDates.objects.all()[0].dates() + init = kwargs['initial']['telechat_date'] + if init and init not in dates: + dates.insert(0, init) + + choices = [("", "(not on agenda)")] + for d in dates: + choices.append((d, d.strftime("%Y-%m-%d"))) + + self.fields['telechat_date'].choices = choices + + # returning item is rendered non-standard + self.standard_fields = [x for x in self.visible_fields() if x.name not in ('returning_item',)] + + def clean_status_date(self): + d = self.cleaned_data['status_date'] + if d: + if d < date.today(): + raise forms.ValidationError("Date must not be in the past.") + if d >= date.today() + timedelta(days=365 * 2): + raise forms.ValidationError("Date must be within two years.") + + return d + + def clean_note(self): + # note is stored munged in the database + return self.cleaned_data['note'].replace('\n', '
').replace('\r', '').replace(' ', '  ') + + +def get_initial_notify(doc): + # set change state notice to something sensible + receivers = [] + if doc.group.type_id == "individ": + for a in doc.authors.all(): + receivers.append(e.address) + else: + receivers.append("%s-chairs@%s" % (doc.group.acronym, settings.TOOLS_SERVER)) + for editor in Email.objects.filter(role__name="wgeditor", role__group=doc.group): + receivers.append(e.address) + + receivers.append("%s@%s" % (doc.name, settings.TOOLS_SERVER)) + return ", ".join(receivers) + +@group_required('Area_Director','Secretariat') +def edit_infoREDESIGN(request, name): + """Edit various Internet Draft attributes, notifying parties as + necessary and logging changes as document events.""" + doc = get_object_or_404(Document, name=name) + if doc.state_id == "expired": + raise Http404() + + login = request.user.get_profile().email() + + new_document = False + if not doc.iesg_state: # FIXME: should probably get this as argument to view + new_document = True + doc.iesg_state = IesgDocStateName.objects.get(slug="pub-req") + doc.notify = get_initial_notify(doc) + + e = doc.latest_event(Telechat, type="scheduled_for_telechat") + initial_telechat_date = e.telechat_date if e else None + initial_returning_item = bool(e and e.returning_item) + + if request.method == 'POST': + form = EditInfoForm(request.POST, + old_ads=False, + initial=dict(telechat_date=initial_telechat_date)) + if form.is_valid(): + save_document_in_history(doc) + + r = form.cleaned_data + if new_document: + # fix so Django doesn't barf in the diff below because these + # fields can't be NULL + doc.ad = r['ad'] + + replaces = Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces") + if replaces: + # this should perhaps be somewhere else, e.g. the + # place where the replace relationship is established + e = Event() + e.type = "added_comment" + e.by = Email.objects.get(address="(System)") + e.doc = doc + e.desc = "Earlier history may be found in the Comment Log for %s" % (replaces[0], replaces[0].get_absolute_url()) + e.save() + + e = Event() + e.type = "started_iesg_process" + e.by = login + e.doc = doc + e.desc = "IESG process started in state %s" % doc.iesg_state.name + e.save() + + orig_ad = doc.ad + + changes = [] + + def desc(attr, new, old): + entry = "%(attr)s has been changed to %(new)s from %(old)s" + if new_document: + entry = "%(attr)s has been changed to %(new)s" + + return entry % dict(attr=attr, new=new, old=old) + + def diff(attr, name): + v = getattr(doc, attr) + if r[attr] != v: + changes.append(desc(name, r[attr], v)) + setattr(doc, attr, r[attr]) + + # update the attributes, keeping track of what we're doing + diff('intended_std_level', "Intended Status") + diff('ad', "Responsible AD") + diff('notify', "State Change Notice email list") + + if r['note'] != doc.note: + if not r['note']: + if doc.note: + changes.append("Note field has been cleared") + else: + if doc.note: + changes.append("Note changed to '%s'" % r['note']) + else: + changes.append("Note added '%s'" % r['note']) + + doc.note = r['note'] + + for c in changes: + e = Event() + e.type = "changed_document" + e.by = login + e.doc = doc + e.desc = c + " by %s" % login.get_name() + e.save() + + update_telechat(request, doc, login, + r['telechat_date'], r['returning_item']) + + e = doc.latest_event(Status, type="changed_status_date") + status_date = e.date if e else None + if r["status_date"] != status_date: + e = Status() + e.type ="changed_status_date" + e.by = login + e.doc = doc + d = desc("Status date", r["status_date"], status_date) + changes.append(d) + e.desc = d + " by %s" % login.get_name() + e.date = r["status_date"] + e.save() + + if in_group(request.user, 'Secretariat'): + via_rfc = DocInfoTagName.objects.get(slug="via-rfc") + if r['via_rfc_editor']: + doc.tags.add(via_rfc) + else: + doc.tags.remove(via_rfc) + + doc.time = datetime.datetime.now() + + if changes and not new_document: + email_owner(request, doc, orig_ad, login, "\n".join(changes)) + + doc.save() + return HttpResponseRedirect(doc.get_absolute_url()) + else: + e = doc.latest_event(Status) + status = e.date if e else None + init = dict(intended_std_level=doc.intended_std_level, + status_date=status, + ad=doc.ad, + notify=doc.notify, + note=dehtmlify_textarea_text(doc.note), + telechat_date=initial_telechat_date, + returning_item=initial_returning_item, + ) + + form = EditInfoForm(old_ads=False, initial=init) + + if not in_group(request.user, 'Secretariat'): + # filter out Via RFC Editor + form.standard_fields = [x for x in form.standard_fields if x.name != "via_rfc_editor"] + + return render_to_response('idrfc/edit_infoREDESIGN.html', + dict(doc=doc, + form=form, + user=request.user, + login=login), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + EditInfoForm = EditInfoFormREDESIGN + edit_info = edit_infoREDESIGN + @group_required('Area_Director','Secretariat') def request_resurrect(request, name): diff --git a/ietf/templates/idrfc/edit_infoREDESIGN.html b/ietf/templates/idrfc/edit_infoREDESIGN.html new file mode 100644 index 000000000..59d1ddfcb --- /dev/null +++ b/ietf/templates/idrfc/edit_infoREDESIGN.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% block title %}Edit info on {{ doc }}{% endblock %} + +{% block morecss %} +form.edit-info #id_state_change_notice_to { + width: 600px; +} +form.edit-info #id_note { + width: 600px; + height: 150px; +} +form.edit-info .actions { + padding-top: 20px; +} +{% endblock %} + +{% block content %} +{% load ietf_filters %} +

Edit info on {{ doc }}

+ +
+ + {% for field in form.standard_fields %} + + + + + {% endfor %} + + + + +
{{ field.label_tag }}:{{ field }} + {% ifequal field.name "telechat_date" %}{{ form.returning_item }} {{ form.returning_item.label_tag }} {{ form.returning_item.errors }}{% endifequal %} + {% ifequal field.name "ad" %} + {% if user|in_group:"Area_Director" %} + + {% endif %} + {% endifequal %} + {% if field.help_text %}
{{ field.help_text }}
{% endif %} + {{ field.errors }}
+ Back + +
+
+{% endblock %} diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py index d560ecff0..f6d85477b 100644 --- a/redesign/doc/admin.py +++ b/redesign/doc/admin.py @@ -28,6 +28,7 @@ admin.site.register(SendQueue, SendQueueAdmin) # events class EventAdmin(admin.ModelAdmin): + list_display = ["doc", "type", "by", "time"] raw_id_fields = ["doc", "by"] admin.site.register(Event, EventAdmin) diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 608a10987..5937c3d74 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -242,7 +242,7 @@ class Event(models.Model): return u"%s %s at %s" % (self.by.get_name(), self.get_type_display().lower(), self.time) class Meta: - ordering = ['-time'] + ordering = ['-time', 'id'] class Message(Event): subj = models.CharField(max_length=255) diff --git a/redesign/group/models.py b/redesign/group/models.py index c25d91c55..83efbdda0 100644 --- a/redesign/group/models.py +++ b/redesign/group/models.py @@ -6,7 +6,7 @@ from redesign.person.models import Email class Group(models.Model): name = models.CharField(max_length=80) - acronym = models.CharField(max_length=16) + acronym = models.CharField(max_length=16, db_index=True) 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) @@ -49,5 +49,5 @@ class Role(models.Model): email = models.ForeignKey(Email) auth = models.CharField(max_length=255, blank=True) def __unicode__(self): - return u"%s is %s in %s" % (self.email.get_name(), self.name.name, self.grop.acronym) + return u"%s is %s in %s" % (self.email.get_name(), self.name.name, self.group.acronym) From e9236da77b7c476edd71f50edf3008fcc7e5ed6a Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 8 Feb 2011 15:03:07 +0000 Subject: [PATCH 25/75] Added order field to names so it's possible to supply a sensible default ordering, add one to IESG state name - Legacy-Id: 2824 --- redesign/import-document-state.py | 37 ++++++++++++++++--------------- redesign/name/models.py | 2 ++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py index 3de8c6fdd..2929ab27b 100755 --- a/redesign/import-document-state.py +++ b/redesign/import-document-state.py @@ -43,11 +43,12 @@ connection.queries = DummyQueries() # IESGComment, IESGDiscuss, DocumentComment, IDAuthor, idrfc.RfcIndex, # idrfc.DraftVersions -def name(name_class, slug, name, desc=""): +def name(name_class, slug, name, desc="", order=0): # 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.order = order obj.save() return obj @@ -107,23 +108,23 @@ state_mapping = { } 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.'), + 'RFC Published': name(IesgDocStateName, "pub", "RFC Published", 'The ID has been published as an RFC.', order=32), + '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.)', order=99), + '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.', order=27), + '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.', order=30), + '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).', order=42), + '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.', order=20), + '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.', order=11), + '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.', order=15), + '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.', order=16), + '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.', order=10), + '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).', order=31), + '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.', order=21), + '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.', order=18), + '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.', order=19), + '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.', order=12), + '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.', order=33), + '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.', order=34), None: None, # FIXME: consider introducing the ID-exists state } diff --git a/redesign/name/models.py b/redesign/name/models.py index badadfa89..5051c0fd5 100644 --- a/redesign/name/models.py +++ b/redesign/name/models.py @@ -7,12 +7,14 @@ class NameModel(models.Model): name = models.CharField(max_length=32) desc = models.TextField(blank=True) used = models.BooleanField(default=True) + order = models.IntegerField(default=0) def __unicode__(self): return self.name class Meta: abstract = True + ordering = ['order'] class GroupStateName(NameModel): """BOF, Proposed, Active, Dormant, Concluded""" From 90e46a0f287e9e53803d0efa4eefce7ab95c9cea Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 8 Feb 2011 15:05:01 +0000 Subject: [PATCH 26/75] Get rid of a couple of FIXMEs - Legacy-Id: 2825 --- ietf/idrfc/views_search.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index d23227f93..f7c7145a1 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -270,7 +270,6 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: 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) @@ -288,8 +287,7 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: 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['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.get_name()) for ad in active_ads] + [('', '------------------')] + [(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','') @@ -322,7 +320,7 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: # 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 + # FIXME: this should be fixed with MySQL if it's still a problem? for k in ['name','author','group']: try: tmp = str(query.get(k, '')) From d6c1f3c3bb8542f84d3dc4ddfabbe3f1389c1de5 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 8 Feb 2011 15:52:17 +0000 Subject: [PATCH 27/75] Fix spelling mistake - Legacy-Id: 2829 --- ietf/templates/idrfc/resurrect_completed_email.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/templates/idrfc/resurrect_completed_email.txt b/ietf/templates/idrfc/resurrect_completed_email.txt index 5e40f41b5..2cbbb14ee 100644 --- a/ietf/templates/idrfc/resurrect_completed_email.txt +++ b/ietf/templates/idrfc/resurrect_completed_email.txt @@ -1,4 +1,4 @@ -As you requsted, the Internet Draft {{ doc.file_tag|safe }} +As you requested, the Internet Draft {{ doc.file_tag|safe }} has been resurrected. ID Tracker URL: {{ url|safe }} From 2d9636e7fabc9f0cb7260cfad50eccde20244668 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 8 Feb 2011 16:37:44 +0000 Subject: [PATCH 28/75] Fix event admin to spit out raw values to speed it up a bit - Legacy-Id: 2830 --- redesign/doc/admin.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py index f6d85477b..27dee0bb7 100644 --- a/redesign/doc/admin.py +++ b/redesign/doc/admin.py @@ -28,12 +28,17 @@ admin.site.register(SendQueue, SendQueueAdmin) # events class EventAdmin(admin.ModelAdmin): - list_display = ["doc", "type", "by", "time"] + list_display = ["doc", "type", "by_raw", "time"] raw_id_fields = ["doc", "by"] + + def by_raw(self, instance): + return instance.by_id + admin.site.register(Event, EventAdmin) admin.site.register(Message, EventAdmin) admin.site.register(Text, EventAdmin) +admin.site.register(NewRevision, EventAdmin) admin.site.register(BallotPosition, EventAdmin) admin.site.register(Status, EventAdmin) admin.site.register(Expiration, EventAdmin) From 0de6066e78f01e6b7835437dc744bcb24ee9472b Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 8 Feb 2011 16:38:45 +0000 Subject: [PATCH 29/75] Hack the temporary login fix to support Secretariat people too - Legacy-Id: 2831 --- ietf/ietfauth/models.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ietf/ietfauth/models.py b/ietf/ietfauth/models.py index 080f9767b..59125912d 100644 --- a/ietf/ietfauth/models.py +++ b/ietf/ietfauth/models.py @@ -63,8 +63,17 @@ class IetfUserProfile(models.Model): def email(self): # quick hack to bind new and old schema together for the time being + try: + l = IESGLogin.objects.get(login_name=self.user.username) + if l.person: + person = l.person + else: + person = PersonOrOrgInfo.objects.get(first_name=l.first_name, + last_name=l.last_name) + except IESGLogin.DoesNotExist, PersonOrOrgInfo.DoesNotExist: + person = None from person.models import Email - return Email.objects.get(address=self.person().email()[1]) + return Email.objects.get(address=person.email()[1]) def __str__(self): return "IetfUserProfile(%s)" % (self.user,) From 98296cde44eefaae4886a2fe705286a04731f605 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 8 Feb 2011 16:39:16 +0000 Subject: [PATCH 30/75] Port resurrect and add comment views and tests to new schema - Legacy-Id: 2832 --- ietf/idrfc/mails.py | 26 +++++ ietf/idrfc/testsREDESIGN.py | 83 +++++++------- ietf/idrfc/views_edit.py | 115 +++++++++++++++++--- ietf/templates/idrfc/add_comment.html | 4 +- ietf/templates/idrfc/request_resurrect.html | 2 +- ietf/templates/idrfc/resurrect.html | 2 +- 6 files changed, 175 insertions(+), 57 deletions(-) diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index ac179df93..da840fefc 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -364,6 +364,19 @@ def email_resurrect_requested(request, doc, by): by=frm, url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url())) +def email_resurrect_requestedREDESIGN(request, doc, by): + to = "I-D Administrator " + frm = by.formatted_email() + send_mail(request, to, frm, + "I-D Resurrection Request", + "idrfc/resurrect_request_email.txt", + dict(doc=doc, + by=frm, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + email_resurrect_requested = email_resurrect_requestedREDESIGN + def email_resurrection_completed(request, doc): to = u"%s <%s>" % doc.idinternal.resurrect_requested_by.person.email() frm = "I-D Administrator " @@ -374,6 +387,19 @@ def email_resurrection_completed(request, doc): by=frm, url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url())) +def email_resurrection_completedREDESIGN(request, doc, requester): + to = requester.formatted_email() + frm = "I-D Administrator " + send_mail(request, to, frm, + "I-D Resurrection Completed - %s" % doc.file_tag(), + "idrfc/resurrect_completed_email.txt", + dict(doc=doc, + by=frm, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url())) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + email_resurrection_completed = email_resurrection_completedREDESIGN + def email_ballot_deferred(request, doc, by, telechat_date): to = "iesg@ietf.org" frm = "DraftTracker Mail System " diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index 9fffc0fc8..d3b882173 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -324,7 +324,7 @@ class EditInfoTestCase(django.test.TestCase): self.assertEquals(draft.ad, prev_ad) # edit info - events_before = draft.event_set.all().count() + events_before = draft.event_set.count() mailbox_before = len(mail_outbox) new_ad = Email.objects.get(address="ano@ietf.org") @@ -443,14 +443,13 @@ class ResurrectTestCase(django.test.TestCase): fixtures = ['names'] 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) + draft = make_test_data() + draft.state_id = "expired" + draft.save() + + url = urlreverse('doc_request_resurrect', kwargs=dict(name=draft.name)) - url = urlreverse('doc_request_resurrect', kwargs=dict(name=draft.filename)) - login_as = "rhousley" - - login_testing_unauthorized(self, login_as, url) + login_testing_unauthorized(self, "ad", url) # normal get r = self.client.get(url) @@ -460,26 +459,30 @@ class ResurrectTestCase(django.test.TestCase): # request resurrect - events_before = draft.idinternal.comments().count() + events_before = draft.event_set.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(), events_before + 1) - self.assertTrue("Resurrection" in draft.idinternal.comments()[0].comment_text) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.event_set.count(), events_before + 1) + e = draft.latest_event(type="requested_resurrect") + self.assertTrue(e) + self.assertEquals(e.by, Email.objects.get(address="aread@ietf.org")) + self.assertTrue("Resurrection" in e.desc) 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)) + draft = make_test_data() + draft.state_id = "expired" + draft.save() + Event.objects.create(doc=draft, + type="requested_resurrect", + by=Email.objects.get(address="aread@ietf.org")) + + url = urlreverse('doc_resurrect', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) @@ -490,25 +493,24 @@ class ResurrectTestCase(django.test.TestCase): self.assertEquals(len(q('form input[type=submit]')), 1) # request resurrect - events_before = draft.idinternal.comments().count() + events_before = draft.event_set.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(), events_before + 1) - self.assertTrue("completed" in draft.idinternal.comments()[0].comment_text) - self.assertEquals(draft.status.status, "Active") + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.event_set.count(), events_before + 1) + self.assertEquals(draft.latest_event().type, "completed_resurrect") + self.assertEquals(draft.state_id, "active") self.assertEquals(len(mail_outbox), mailbox_before + 1) class AddCommentTestCase(django.test.TestCase): fixtures = ['names'] def test_add_comment(self): - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") - url = urlreverse('doc_add_comment', kwargs=dict(name=draft.filename)) + draft = make_test_data() + url = urlreverse('doc_add_comment', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) # normal get @@ -518,17 +520,18 @@ class AddCommentTestCase(django.test.TestCase): self.assertEquals(len(q('form textarea[name=comment]')), 1) # request resurrect - events_before = draft.idinternal.comments().count() + events_before = draft.event_set.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(), events_before + 1) - self.assertTrue("This is a test." in draft.idinternal.comments()[0].comment_text) + self.assertEquals(draft.event_set.count(), events_before + 1) + self.assertEquals("This is a test.", draft.latest_event().desc) + self.assertEquals("added_comment", draft.latest_event().type) self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue("updated" in mail_outbox[-1]['Subject']) - self.assertTrue(draft.filename in mail_outbox[-1]['Subject']) + self.assertTrue(draft.name in mail_outbox[-1]['Subject']) class EditPositionTestCase(django.test.TestCase): fixtures = ['base', 'draft', 'ballot'] @@ -546,7 +549,7 @@ class EditPositionTestCase(django.test.TestCase): self.assertEquals(len(q('form textarea[name=comment_text]')), 1) # vote - events_before = draft.idinternal.comments().count() + events_before = draft.event_set.count() self.assertTrue(not Position.objects.filter(ballot=draft.idinternal.ballot, ad__login_name="rhousley")) r = self.client.post(url, dict(position="discuss", @@ -560,11 +563,11 @@ class EditPositionTestCase(django.test.TestCase): self.assertTrue(pos.discuss) self.assertTrue(not (pos.yes or pos.noobj or pos.abstain or pos.recuse)) - self.assertEquals(draft.idinternal.comments().count(), events_before + 3) + self.assertEquals(draft.event_set.count(), events_before + 3) self.assertTrue("New position" in draft.idinternal.comments()[2].comment_text) # recast vote - events_before = draft.idinternal.comments().count() + events_before = draft.event_set.count() r = self.client.post(url, dict(position="noobj")) self.assertEquals(r.status_code, 302) @@ -572,17 +575,17 @@ class EditPositionTestCase(django.test.TestCase): 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(), events_before + 1) + self.assertEquals(draft.event_set.count(), events_before + 1) self.assertTrue("Position" in draft.idinternal.comments()[0].comment_text) # clear vote - events_before = draft.idinternal.comments().count() + events_before = draft.event_set.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(), events_before + 1) + self.assertEquals(draft.event_set.count(), events_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") @@ -597,7 +600,7 @@ class EditPositionTestCase(django.test.TestCase): self.assertTrue(len(q('form input[name=position]')) > 0) # vote for rhousley - events_before = draft.idinternal.comments().count() + events_before = draft.event_set.count() self.assertTrue(not Position.objects.filter(ballot=draft.idinternal.ballot, ad__login_name="rhousley")) r = self.client.post(url, dict(position="discuss")) @@ -1092,13 +1095,13 @@ class ExpireLastCallTestCase(django.test.TestCase): self.assertEquals(len(drafts), 1) mailbox_before = len(mail_outbox) - events_before = draft.idinternal.comments().count() + events_before = draft.event_set.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(), events_before + 1) + self.assertEquals(draft.event_set.count(), events_before + 1) self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue("Last Call Expired" in mail_outbox[-1]["Subject"]) diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index 15e82eda8..0768255bb 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -23,7 +23,7 @@ from ietf.idrfc.utils import * from ietf.idrfc.lastcall import request_last_call from doc.models import Document, Event, Status, Telechat, save_document_in_history, DocHistory -from name.models import IesgDocStateName, IntendedStdLevelName, DocInfoTagName, get_next_iesg_states +from name.models import IesgDocStateName, IntendedStdLevelName, DocInfoTagName, get_next_iesg_states, DocStateName class ChangeStateForm(forms.Form): state = forms.ModelChoiceField(IDState.objects.all(), empty_label=None, required=True) @@ -105,10 +105,8 @@ def change_stateREDESIGN(request, name): prev_state = doc.iesg_state doc.iesg_state = state - e = Event() + e = Event(doc=doc, by=login) 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", @@ -541,10 +539,8 @@ def edit_infoREDESIGN(request, name): doc.note = r['note'] for c in changes: - e = Event() + e = Event(doc=doc, by=login) e.type = "changed_document" - e.by = login - e.doc = doc e.desc = c + " by %s" % login.get_name() e.save() @@ -554,10 +550,8 @@ def edit_infoREDESIGN(request, name): e = doc.latest_event(Status, type="changed_status_date") status_date = e.date if e else None if r["status_date"] != status_date: - e = Status() + e = Status(doc=doc, by=login) e.type ="changed_status_date" - e.by = login - e.doc = doc d = desc("Status date", r["status_date"], status_date) changes.append(d) e.desc = d + " by %s" % login.get_name() @@ -628,9 +622,37 @@ def request_resurrect(request, name): return HttpResponseRedirect(doc.idinternal.get_absolute_url()) return render_to_response('idrfc/request_resurrect.html', - dict(doc=doc), + dict(doc=doc, + back_url=doc.idinternal.get_absolute_url()), context_instance=RequestContext(request)) +@group_required('Area_Director','Secretariat') +def request_resurrectREDESIGN(request, name): + """Request resurrect of expired Internet Draft.""" + doc = get_object_or_404(Document, name=name) + if doc.state_id != "expired": + raise Http404() + + login = request.user.get_profile().email() + + if request.method == 'POST': + email_resurrect_requested(request, doc, login) + + e = Event(doc=doc, by=login) + e.type = "requested_resurrect" + e.desc = "Resurrection was requested by %s" % login.get_name() + e.save() + + return HttpResponseRedirect(doc.get_absolute_url()) + + return render_to_response('idrfc/request_resurrect.html', + dict(doc=doc, + back_url=doc.get_absolute_url()), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + request_resurrect = request_resurrectREDESIGN + @group_required('Secretariat') def resurrect(request, name): """Resurrect expired Internet Draft.""" @@ -655,9 +677,43 @@ def resurrect(request, name): return HttpResponseRedirect(doc.idinternal.get_absolute_url()) return render_to_response('idrfc/resurrect.html', - dict(doc=doc), + dict(doc=doc, + back_url=doc.idinternal.get_absolute_url()), context_instance=RequestContext(request)) +@group_required('Secretariat') +def resurrectREDESIGN(request, name): + """Resurrect expired Internet Draft.""" + doc = get_object_or_404(Document, name=name) + if doc.state_id != "expired": + raise Http404() + + login = request.user.get_profile().email() + + if request.method == 'POST': + e = doc.latest_event(type__in=('requested_resurrect', "completed_resurrect")) + if e and e.type == 'requested_resurrect': + email_resurrection_completed(request, doc, requester=e.by) + + e = Event(doc=doc, by=login) + e.type = "completed_resurrect" + e.desc = "Resurrection was completed by %s" % login.get_name() + e.save() + + doc.state = DocStateName.objects.get(slug="active") + doc.time = datetime.datetime.now() + doc.save() + return HttpResponseRedirect(doc.get_absolute_url()) + + return render_to_response('idrfc/resurrect.html', + dict(doc=doc, + back_url=doc.get_absolute_url()), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + resurrect = resurrectREDESIGN + + class AddCommentForm(forms.Form): comment = forms.CharField(required=True, widget=forms.Textarea) @@ -683,7 +739,40 @@ def add_comment(request, name): return render_to_response('idrfc/add_comment.html', dict(doc=doc, - form=form), + form=form, + back_url=doc.idinternal.get_absolute_url()), context_instance=RequestContext(request)) +@group_required('Area_Director','Secretariat') +def add_commentREDESIGN(request, name): + """Add comment to Internet Draft.""" + doc = get_object_or_404(Document, name=name) + if not doc.iesg_state: + raise Http404() + login = request.user.get_profile().email() + + if request.method == 'POST': + form = AddCommentForm(request.POST) + if form.is_valid(): + c = form.cleaned_data['comment'] + + e = Event(doc=doc, by=login) + e.type = "added_comment" + e.desc = c + e.save() + + email_owner(request, doc, doc.ad, login, + "A new comment added by %s" % login.get_name()) + return HttpResponseRedirect(doc.get_absolute_url()) + else: + form = AddCommentForm() + + return render_to_response('idrfc/add_comment.html', + dict(doc=doc, + form=form, + back_url=doc.get_absolute_url()), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + add_comment = add_commentREDESIGN diff --git a/ietf/templates/idrfc/add_comment.html b/ietf/templates/idrfc/add_comment.html index 38b60b303..54208d886 100644 --- a/ietf/templates/idrfc/add_comment.html +++ b/ietf/templates/idrfc/add_comment.html @@ -16,7 +16,7 @@ form.add-comment .actions { {% block content %}

Add comment on {{ doc }}

-

The comment will be added to the comment trail.

+

The comment will be added to the history trail.

@@ -24,7 +24,7 @@ form.add-comment .actions { diff --git a/ietf/templates/idrfc/request_resurrect.html b/ietf/templates/idrfc/request_resurrect.html index 78145e4c9..caad47463 100644 --- a/ietf/templates/idrfc/request_resurrect.html +++ b/ietf/templates/idrfc/request_resurrect.html @@ -12,7 +12,7 @@ I-D.

- Back + Back
diff --git a/ietf/templates/idrfc/resurrect.html b/ietf/templates/idrfc/resurrect.html index 1879c0b37..fb89feacb 100644 --- a/ietf/templates/idrfc/resurrect.html +++ b/ietf/templates/idrfc/resurrect.html @@ -11,7 +11,7 @@

This will change the status to Active{% if doc.idinternal.resurrect_requested_by %} and email a notice to {{ doc.idinternal.resurrect_requested_by }}{% endif %}.

- Back + Back
From 996b060749105018d0a03c32481b8e8f2d9747d1 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 8 Feb 2011 17:40:23 +0000 Subject: [PATCH 31/75] Fix a couple of details - Legacy-Id: 2834 --- ietf/idrfc/views_edit.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index 0768255bb..f8d0df7a9 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -457,7 +457,7 @@ def get_initial_notify(doc): def edit_infoREDESIGN(request, name): """Edit various Internet Draft attributes, notifying parties as necessary and logging changes as document events.""" - doc = get_object_or_404(Document, name=name) + doc = get_object_or_404(Document, docalias__name=name) if doc.state_id == "expired": raise Http404() @@ -629,7 +629,7 @@ def request_resurrect(request, name): @group_required('Area_Director','Secretariat') def request_resurrectREDESIGN(request, name): """Request resurrect of expired Internet Draft.""" - doc = get_object_or_404(Document, name=name) + doc = get_object_or_404(Document, docalias__name=name) if doc.state_id != "expired": raise Http404() @@ -684,13 +684,15 @@ def resurrect(request, name): @group_required('Secretariat') def resurrectREDESIGN(request, name): """Resurrect expired Internet Draft.""" - doc = get_object_or_404(Document, name=name) + doc = get_object_or_404(Document, docalias__name=name) if doc.state_id != "expired": raise Http404() login = request.user.get_profile().email() if request.method == 'POST': + save_document_in_history(doc) + e = doc.latest_event(type__in=('requested_resurrect', "completed_resurrect")) if e and e.type == 'requested_resurrect': email_resurrection_completed(request, doc, requester=e.by) @@ -746,7 +748,7 @@ def add_comment(request, name): @group_required('Area_Director','Secretariat') def add_commentREDESIGN(request, name): """Add comment to Internet Draft.""" - doc = get_object_or_404(Document, name=name) + doc = get_object_or_404(Document, docalias__name=name) if not doc.iesg_state: raise Http404() From 0e932ffd6f446935549fedbd8aa11b744e53abdd Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 11 Feb 2011 17:29:25 +0000 Subject: [PATCH 32/75] Port edit position and send ballot comment views with tests to new schema - Legacy-Id: 2852 --- ietf/idrfc/idrfc_wrapper.py | 4 +- ietf/idrfc/testsREDESIGN.py | 133 ++++++----- ietf/idrfc/views_ballot.py | 210 +++++++++++++++++- ietf/templates/idrfc/ballot_comment_mail.txt | 4 +- .../idrfc/edit_positionREDESIGN.html | 66 ++++++ .../idrfc/send_ballot_commentREDESIGN.html | 47 ++++ redesign/doc/admin.py | 8 +- redesign/doc/models.py | 2 +- 8 files changed, 406 insertions(+), 68 deletions(-) create mode 100644 ietf/templates/idrfc/edit_positionREDESIGN.html create mode 100644 ietf/templates/idrfc/send_ballot_commentREDESIGN.html diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py index 7049ec0aa..b0431d72b 100644 --- a/ietf/idrfc/idrfc_wrapper.py +++ b/ietf/idrfc/idrfc_wrapper.py @@ -642,7 +642,7 @@ class BallotWrapper: 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 + ad_username=pos.ad.pk, # ought to rename this in doc_ballot_list position=pos.pos.name, is_old_ad=pos.ad not in active_ads, old_positions=[]) @@ -676,7 +676,7 @@ class BallotWrapper: 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 + ad_username=pos.ad.pk, position="No Record", ) positions.append(d) diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index d3b882173..8c709c1f2 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -534,63 +534,74 @@ class AddCommentTestCase(django.test.TestCase): self.assertTrue(draft.name in mail_outbox[-1]['Subject']) class EditPositionTestCase(django.test.TestCase): - fixtures = ['base', 'draft', 'ballot'] + fixtures = ['names'] 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) + draft = make_test_data() + url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "ad", url) + ad = Email.objects.get(address="aread@ietf.org") + # 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) + self.assertEquals(len(q('form textarea[name=comment]')), 1) # vote events_before = draft.event_set.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.")) + discuss="This is a discussion test.", + comment="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)) - + pos = draft.latest_event(BallotPosition, ad=ad) + self.assertEquals(pos.pos.slug, "discuss") + self.assertTrue("This is a discussion test." in pos.discuss) + self.assertTrue(pos.discuss_time != None) + self.assertTrue("This is a test." in pos.comment) + self.assertTrue(pos.comment_time != None) + self.assertTrue("New position" in pos.desc) self.assertEquals(draft.event_set.count(), events_before + 3) - self.assertTrue("New position" in draft.idinternal.comments()[2].comment_text) # recast vote events_before = draft.event_set.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) + pos = draft.latest_event(BallotPosition, ad=ad) + self.assertEquals(pos.pos.slug, "noobj") self.assertEquals(draft.event_set.count(), events_before + 1) - self.assertTrue("Position" in draft.idinternal.comments()[0].comment_text) + self.assertTrue("Position for" in pos.desc) # clear vote events_before = draft.event_set.count() - r = self.client.post(url, dict(position="")) + r = self.client.post(url, dict(position="norecord")) self.assertEquals(r.status_code, 302) - pos = Position.objects.filter(ballot=draft.idinternal.ballot, ad__login_name="rhousley") - self.assertEquals(len(pos), 0) + pos = draft.latest_event(BallotPosition, ad=ad) + self.assertEquals(pos.pos.slug, "norecord") self.assertEquals(draft.event_set.count(), events_before + 1) - self.assertTrue("Position" in draft.idinternal.comments()[0].comment_text) + self.assertTrue("Position for" in pos.desc) + + # change comment + events_before = draft.event_set.count() + r = self.client.post(url, dict(position="norecord", comment="New comment.")) + self.assertEquals(r.status_code, 302) + + pos = draft.latest_event(BallotPosition, ad=ad) + self.assertEquals(pos.pos.slug, "norecord") + self.assertEquals(draft.event_set.count(), events_before + 2) + self.assertTrue("Ballot comment text updated" in pos.desc) + 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" + draft = make_test_data() + url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name)) + ad = Email.objects.get(address="aread@ietf.org") + url += "?ad=%s" % ad.pk login_testing_unauthorized(self, "secretary", url) # normal get @@ -599,23 +610,30 @@ class EditPositionTestCase(django.test.TestCase): q = PyQuery(r.content) self.assertTrue(len(q('form input[name=position]')) > 0) - # vote for rhousley + # vote on behalf of AD events_before = draft.event_set.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)) - + pos = draft.latest_event(BallotPosition, ad=ad) + self.assertEquals(pos.pos.slug, "discuss") + self.assertTrue("New position" in pos.desc) + self.assertTrue("by Sec" in pos.desc) 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) + draft = make_test_data() + draft.notify = "somebody@example.com" + draft.save() + + ad = Email.objects.get(address="aread@ietf.org") + + BallotPosition.objects.create(doc=draft, type="changed_ballot_position", + by=ad, ad=ad, pos=BallotPositionName.objects.get(slug="yes"), + comment="Test!", + comment_time=datetime.datetime.now()) + + url = urlreverse('doc_send_ballot_comment', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "ad", url) # normal get r = self.client.get(url) @@ -625,16 +643,15 @@ class EditPositionTestCase(django.test.TestCase): # 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']) + m = mail_outbox[-1] + self.assertTrue("COMMENT" in m['Subject']) + self.assertTrue(draft.name in m['Subject']) + self.assertTrue("Test!" in str(m)) class DeferBallotTestCase(django.test.TestCase): @@ -642,7 +659,7 @@ class DeferBallotTestCase(django.test.TestCase): def test_defer_ballot(self): draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") - url = urlreverse('doc_defer_ballot', kwargs=dict(name=draft.filename)) + url = urlreverse('doc_defer_ballot', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "rhousley", url) # normal get @@ -666,7 +683,7 @@ class DeferBallotTestCase(django.test.TestCase): def test_undefer_ballot(self): draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") - url = urlreverse('doc_undefer_ballot', kwargs=dict(name=draft.filename)) + url = urlreverse('doc_undefer_ballot', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "rhousley", url) draft.idinternal.ballot.defer = True @@ -691,7 +708,7 @@ class BallotWriteupsTestCase(django.test.TestCase): 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)) + url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) # normal get @@ -728,7 +745,7 @@ class BallotWriteupsTestCase(django.test.TestCase): 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)) + url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) mailbox_before = len(mail_outbox) @@ -745,7 +762,7 @@ class BallotWriteupsTestCase(django.test.TestCase): 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)) + url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) # normal get @@ -765,7 +782,7 @@ class BallotWriteupsTestCase(django.test.TestCase): def test_issue_ballot(self): draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") - url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.filename)) + url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "rhousley", url) draft.idinternal.ballot.ballot_issued = False @@ -800,7 +817,7 @@ class BallotWriteupsTestCase(django.test.TestCase): 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)) + url = urlreverse('doc_ballot_approvaltext', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) # normal get @@ -840,7 +857,7 @@ class ApproveBallotTestCase(django.test.TestCase): def test_approve_ballot(self): draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") - url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.filename)) + url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) # normal get @@ -875,7 +892,7 @@ class MakeLastCallTestCase(django.test.TestCase): draft.lc_expiration_date = None draft.save() - url = urlreverse('doc_make_last_call', kwargs=dict(name=draft.filename)) + url = urlreverse('doc_make_last_call', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) # normal get @@ -984,7 +1001,7 @@ class ExpireIDsTestCase(django.test.TestCase): 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) + new_txt = "%s-%s.txt" % (draft.name, draft.revision) self.assertTrue(os.path.exists(os.path.join(self.id_dir, new_txt))) def test_clean_up_id_files(self): @@ -1015,9 +1032,9 @@ class ExpireIDsTestCase(django.test.TestCase): draft.status_id = 3 draft.save() - txt = "%s-%s.txt" % (draft.filename, draft.revision) + txt = "%s-%s.txt" % (draft.name, draft.revision) self.write_id_file(txt, 5000) - pdf = "%s-%s.pdf" % (draft.filename, draft.revision) + pdf = "%s-%s.pdf" % (draft.name, draft.revision) self.write_id_file(pdf, 5000) clean_up_id_files() @@ -1035,7 +1052,7 @@ class ExpireIDsTestCase(django.test.TestCase): draft.expiration_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE + 1) draft.save() - txt = "%s-%s.txt" % (draft.filename, draft.revision) + txt = "%s-%s.txt" % (draft.name, draft.revision) self.write_id_file(txt, 5000) clean_up_id_files() @@ -1053,7 +1070,7 @@ class ExpireIDsTestCase(django.test.TestCase): revision_before = draft.revision - txt = "%s-%s.txt" % (draft.filename, draft.revision) + txt = "%s-%s.txt" % (draft.name, draft.revision) self.write_id_file(txt, 1000) clean_up_id_files() diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 50e67e019..6e3f0a213 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -22,6 +22,9 @@ from ietf.idrfc.mails import * from ietf.idrfc.utils import * from ietf.idrfc.lastcall import request_last_call +from doc.models import Document, Event, BallotPosition, save_document_in_history +from name.models import BallotPositionName + BALLOT_CHOICES = (("yes", "Yes"), ("noobj", "No Objection"), ("discuss", "Discuss"), @@ -78,14 +81,13 @@ def edit_position(request, name): if not ad_username: raise Http404() ad = get_object_or_404(IESGLogin, login_name=ad_username) - - pos, discuss, comment = get_ballot_info(doc.idinternal.ballot, ad) + + doc.latest_event(BallotPosition, type='changed_ballot_position', ad=ad) if request.method == 'POST': form = EditPositionForm(request.POST) if form.is_valid(): - - # save the vote + # save the vote clean = form.cleaned_data if clean['return_to_url']: @@ -189,6 +191,139 @@ def edit_position(request, name): ), context_instance=RequestContext(request)) +class EditPositionFormREDESIGN(forms.Form): + position = forms.ModelChoiceField(queryset=BallotPositionName.objects.all(), widget=forms.RadioSelect, initial="norecord", required=True) + discuss = forms.CharField(required=False, widget=forms.Textarea) + comment = forms.CharField(required=False, widget=forms.Textarea) + return_to_url = forms.CharField(required=False, widget=forms.HiddenInput) + +@group_required('Area_Director','Secretariat') +def edit_positionREDESIGN(request, name): + """Vote and edit discuss and comment on Internet Draft as Area Director.""" + doc = get_object_or_404(Document, docalias__name=name) + if not doc.iesg_state: + raise Http404() + + ad = login = request.user.get_profile().email() + + if 'HTTP_REFERER' in request.META: + return_to_url = request.META['HTTP_REFERER'] + else: + return_to_url = doc.get_absolute_url() + + # if we're in the Secretariat, we can select an AD to act as stand-in for + if not in_group(request.user, "Area_Director"): + ad_id = request.GET.get('ad') + if not ad_id: + raise Http404() + ad = get_object_or_404(Email, pk=ad_id) + + old_pos = doc.latest_event(BallotPosition, type="changed_ballot_position", ad=ad) + + if request.method == 'POST': + form = EditPositionForm(request.POST) + if form.is_valid(): + + # save the vote + clean = form.cleaned_data + + if clean['return_to_url']: + return_to_url = clean['return_to_url'] + + pos = BallotPosition(doc=doc, by=login) + pos.type = "changed_ballot_position" + pos.ad = ad + pos.pos = clean["position"] + pos.comment = clean["comment"].strip() + pos.comment_time = old_pos.comment_time if old_pos else None + pos.discuss = clean["discuss"].strip() + pos.discuss_time = old_pos.discuss_time if old_pos else None + + changes = [] + added_events = [] + # possibly add discuss/comment comments to history trail + # so it's easy to see + old_comment = old_pos.comment if old_pos else "" + if pos.comment != old_comment: + pos.comment_time = pos.time + changes.append("comment") + + if pos.comment: + e = Event(doc=doc) + e.by = ad # otherwise we can't see who's saying it + e.type = "added_comment" + e.desc = "[Ballot comment]\n" + pos.comment + added_events.append(e) + + old_discuss = old_pos.discuss if old_pos else "" + if pos.discuss != old_discuss: + pos.discuss_time = pos.time + changes.append("discuss") + + if pos.discuss: + e = Event(doc=doc, by=login) + e.by = ad # otherwise we can't see who's saying it + e.type = "added_comment" + e.desc = "[Ballot discuss]\n" + pos.discuss + added_events.append(e) + + # figure out a description + if not old_pos and pos.pos.slug != "norecord": + pos.desc = u"[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.get_name()) + elif old_pos and pos.pos != old_pos.pos: + pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad.get_name(), pos.pos.name, old_pos.pos.name) + + if not pos.desc and changes: + pos.desc = u"Ballot %s text updated for %s" % (u" and ".join(changes), ad.get_name()) + + # only add new event if we actually got a change + if pos.desc: + if login != ad: + pos.desc += u" by %s" % login.get_name() + + pos.save() + + for e in added_events: + e.save() # save them after the position is saved to get later id + + doc.time = pos.time + doc.save() + + # FIXME: test + if request.POST.get("send_mail"): + qstr = "?return_to_url=%s" % return_to_url + if request.GET.get('ad'): + qstr += "&ad=%s" % request.GET.get('ad') + return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.name)) + qstr) + else: + return HttpResponseRedirect(return_to_url) + else: + initial = {} + if old_pos: + initial['position'] = old_pos.pos.slug + initial['discuss'] = old_pos.discuss + initial['comment'] = old_pos.comment + + if return_to_url: + initial['return_to_url'] = return_to_url + + form = EditPositionForm(initial=initial) + + + return render_to_response('idrfc/edit_positionREDESIGN.html', + dict(doc=doc, + form=form, + ad=ad, + return_to_url=return_to_url, + old_pos=old_pos, + ), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + edit_position = edit_positionREDESIGN + EditPositionForm = EditPositionFormREDESIGN + + @group_required('Area_Director','Secretariat') def send_ballot_comment(request, name): """Email Internet Draft ballot discuss/comment for area director.""" @@ -255,6 +390,73 @@ def send_ballot_comment(request, name): ), context_instance=RequestContext(request)) +@group_required('Area_Director','Secretariat') +def send_ballot_commentREDESIGN(request, name): + """Email Internet Draft ballot discuss/comment for area director.""" + doc = get_object_or_404(Document, docalias__name=name) + + ad = login = request.user.get_profile().email() + + return_to_url = request.GET.get('return_to_url') + if not return_to_url: + return_to_url = doc.get_absolute_url() + + if 'HTTP_REFERER' in request.META: + back_url = request.META['HTTP_REFERER'] + else: + back_url = doc.get_absolute_url() + + # if we're in the Secretariat, we can select an AD to act as stand-in for + if not in_group(request.user, "Area_Director"): + ad_id = request.GET.get('ad') + if not ad_id: + raise Http404() + ad = get_object_or_404(Email, pk=ad_id) + + pos = doc.latest_event(BallotPosition, type="changed_ballot_position", ad=ad) + if not pos: + raise Http404() + + subj = [] + d = "" + if pos.pos == "discuss" and pos.discuss: + d = pos.discuss + subj.append("DISCUSS") + c = "" + if pos.comment: + c = pos.comment + subj.append("COMMENT") + + subject = "%s: %s" % (" and ".join(subj), doc.file_tag()) + body = render_to_string("idrfc/ballot_comment_mail.txt", + dict(discuss=d, comment=c)) + frm = ad.formatted_email() + to = "iesg@ietf.org" + + if request.method == 'POST': + cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()] + if request.POST.get("cc_state_change") and doc.notify: + cc.extend(doc.notify.split(',')) + + send_mail_text(request, to, frm, subject, body, cc=", ".join(cc)) + + return HttpResponseRedirect(return_to_url) + + return render_to_response('idrfc/send_ballot_commentREDESIGN.html', + dict(doc=doc, + subject=subject, + body=body, + frm=frm, + to=to, + ad=ad, + can_send=d or c, + back_url=back_url, + ), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + send_ballot_comment = send_ballot_commentREDESIGN + @group_required('Area_Director','Secretariat') def defer_ballot(request, name): diff --git a/ietf/templates/idrfc/ballot_comment_mail.txt b/ietf/templates/idrfc/ballot_comment_mail.txt index 2cdf676c4..b6570d408 100644 --- a/ietf/templates/idrfc/ballot_comment_mail.txt +++ b/ietf/templates/idrfc/ballot_comment_mail.txt @@ -1,6 +1,6 @@ {% if discuss %}Discuss: -{{ discuss|safe }} +{{ discuss|safe|wordwrap:73 }} {% endif %}{% if comment %}Comment: -{{ comment|safe }} +{{ comment|safe|wordwrap:73 }} {% endif %} diff --git a/ietf/templates/idrfc/edit_positionREDESIGN.html b/ietf/templates/idrfc/edit_positionREDESIGN.html new file mode 100644 index 000000000..8b2c6cf2e --- /dev/null +++ b/ietf/templates/idrfc/edit_positionREDESIGN.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} + +{% block title %}Change position for {{ ad.get_name }}{% endblock %} + +{% block morecss %} +form.position-form .position ul { + padding: 0; + margin: 0; +} +form.position-form .position li { + list-style-type: none; + float: left; + padding-right: 10px; +} +form.position-form .last-edited { + font-style: italic; +} +form.position-form .discuss-text { + padding-top: 20px +} +form.position-form #id_discuss_text, +form.position-form #id_comment_text { + width: 700px; + height: 250px; +} +form.position-form .comment-text { + margin-top: 20px; +} +{% endblock %} + +{% block content %} +

Change position for {{ ad.get_name }}

+ +
+
{{ form.position }}
+ +
+ +
+
+ {{ form.discuss.label_tag }}: + {% if old_pos and old_pos.discuss_time %}(last edited {{ old_pos.discuss_time }}){% endif %} +
+ {{ form.discuss }} +
+ +
+ {{ form.comment.label_tag }}: + {% if old_pos and old_pos.comment_time %}(last edited {{ old_pos.comment_time }}){% endif %} +
+ {{ form.comment }} + +
+ Back + + +
+ + {{ form.return_to_url }} + + +{% endblock %} + +{% block content_end %} + +{% endblock %} diff --git a/ietf/templates/idrfc/send_ballot_commentREDESIGN.html b/ietf/templates/idrfc/send_ballot_commentREDESIGN.html new file mode 100644 index 000000000..d9301a678 --- /dev/null +++ b/ietf/templates/idrfc/send_ballot_commentREDESIGN.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% block title %}Email Discuss and Comment text for {{ ad.get_name }} to IESG list{% endblock %} + +{% block morecss %} +form.send-ballot pre { + margin: 0; + padding: 4px; + border-top: 4px solid #eee; + border-bottom: 4px solid #eee; +} +{% endblock %} + +{% block content %} +

Email Discuss and Comment text for {{ ad.get_name }} to IESG list

+ +
+
- Back + Back
+ + + + + + + + + + + + + + + +
From: {{ frm }}
To: {{ to }}
Cc:
+ separated
by comma

+ {% if doc.notify %} + + {% endif %} +
Subject: {{ subject }}
Body:
{{ body }}
+ Back + +
+ +{% endblock %} diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py index 27dee0bb7..32e705c16 100644 --- a/redesign/doc/admin.py +++ b/redesign/doc/admin.py @@ -18,6 +18,7 @@ admin.site.register(DocHistory, DocHistoryAdmin) class DocAliasAdmin(admin.ModelAdmin): list_display = [ 'name', 'document_link', ] search_fields = [ 'name', 'document__name', ] + raw_id_fields = ['document'] admin.site.register(DocAlias, DocAliasAdmin) class SendQueueAdmin(admin.ModelAdmin): @@ -33,14 +34,19 @@ class EventAdmin(admin.ModelAdmin): def by_raw(self, instance): return instance.by_id + by_raw.short_description = "By" admin.site.register(Event, EventAdmin) admin.site.register(Message, EventAdmin) admin.site.register(Text, EventAdmin) admin.site.register(NewRevision, EventAdmin) -admin.site.register(BallotPosition, EventAdmin) admin.site.register(Status, EventAdmin) admin.site.register(Expiration, EventAdmin) admin.site.register(Telechat, EventAdmin) +class BallotPositionAdmin(EventAdmin): + raw_id_fields = ["doc", "by", "ad"] + +admin.site.register(BallotPosition, BallotPositionAdmin) + diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 5937c3d74..bb19228f1 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -257,7 +257,7 @@ class NewRevision(Event): # IESG events class BallotPosition(Event): ad = models.ForeignKey(Email) - pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norec") + pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord") 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) From 6c096cb02ef2b6a65a68fbe376e67d46a2b60c62 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 11 Feb 2011 18:01:51 +0000 Subject: [PATCH 33/75] Fix bug in quoting <> in ballot deferral email - Legacy-Id: 2853 --- ietf/templates/idrfc/ballot_deferred_email.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/templates/idrfc/ballot_deferred_email.txt b/ietf/templates/idrfc/ballot_deferred_email.txt index 7448ed92c..1b69301f4 100644 --- a/ietf/templates/idrfc/ballot_deferred_email.txt +++ b/ietf/templates/idrfc/ballot_deferred_email.txt @@ -1,3 +1,3 @@ -{% filter wordwrap:73 %}Ballot of {{ doc.file_tag }} has been deferred by {{ by }}.{% endfilter %} +{% filter wordwrap:73 %}Ballot of {{ doc.file_tag|safe }} has been deferred by {{ by }}.{% endfilter %} This ballot will be on the IESG agenda of {{ telechat_date }}. From 280080e55ab93e75debb8723697521ecd5da086e Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 11 Feb 2011 18:09:35 +0000 Subject: [PATCH 34/75] Ported defer/undefer ballot views and tests to new schema - Legacy-Id: 2854 --- ietf/idrfc/mails.py | 1 + ietf/idrfc/testsREDESIGN.py | 36 ++++++----- ietf/idrfc/utils.py | 17 +++++- ietf/idrfc/views_ballot.py | 76 +++++++++++++++++++++++- ietf/idrfc/views_edit.py | 14 ++--- ietf/templates/idrfc/defer_ballot.html | 2 +- ietf/templates/idrfc/undefer_ballot.html | 2 +- 7 files changed, 113 insertions(+), 35 deletions(-) diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index da840fefc..cee76ad09 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -22,6 +22,7 @@ def email_state_changed(request, doc, text): def email_state_changedREDESIGN(request, doc, text): to = [x.strip() for x in doc.notify.replace(';', ',').split(',')] + text = strip_tags(text) send_mail(request, to, None, "ID Tracker State Update Notice: %s" % doc.file_tag(), "idrfc/state_changed_email.txt", diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index 8c709c1f2..49224c7c9 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -655,53 +655,51 @@ class EditPositionTestCase(django.test.TestCase): class DeferBallotTestCase(django.test.TestCase): - fixtures = ['base', 'draft', 'ballot'] + fixtures = ['names'] def test_defer_ballot(self): - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") + draft = make_test_data() + draft.iesg_state_id = "iesg-eva" + draft.save() + url = urlreverse('doc_defer_ballot', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "rhousley", url) + login_testing_unauthorized(self, "ad", 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) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.iesg_state_id, "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.name)) - login_testing_unauthorized(self, "rhousley", url) - - draft.idinternal.ballot.defer = True - draft.idinternal.ballot.save() + draft = make_test_data() + draft.iesg_state_id = "defer" + draft.save() + url = urlreverse('doc_undefer_ballot', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "ad", url) + # normal get r = self.client.get(url) self.assertEquals(r.status_code, 200) # undefer - 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) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.iesg_state_id, "iesg-eva") class BallotWriteupsTestCase(django.test.TestCase): fixtures = ['base', 'draft', 'ballot'] @@ -783,7 +781,7 @@ class BallotWriteupsTestCase(django.test.TestCase): def test_issue_ballot(self): draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name)) - login_testing_unauthorized(self, "rhousley", url) + login_testing_unauthorized(self, "ad", url) draft.idinternal.ballot.ballot_issued = False draft.idinternal.ballot.save() diff --git a/ietf/idrfc/utils.py b/ietf/idrfc/utils.py index a950c8a01..c384598f1 100644 --- a/ietf/idrfc/utils.py +++ b/ietf/idrfc/utils.py @@ -3,6 +3,8 @@ from django.conf import settings from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin from ietf.idrfc.mails import * +from doc.models import Telechat, Event + def add_document_comment(request, doc, text, include_by=True, ballot=None): if request: login = IESGLogin.objects.get(login_name=request.user.username) @@ -59,6 +61,20 @@ def log_state_changed(request, doc, by, email_watch_list=True): return change +def log_state_changedREDESIGN(request, doc, by, prev_iesg_state): + e = Event(doc=doc, by=by) + e.type = "changed_document" + e.desc = u"State changed to %s from %s by %s" % ( + doc.iesg_state.name, + prev_iesg_state.name if prev_iesg_state else "None", + by.get_name()) + e.save() + return e + + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + log_state_changed = log_state_changedREDESIGN + def update_telechat(request, idinternal, new_telechat_date, new_returning_item=None): on_agenda = bool(new_telechat_date) @@ -98,7 +114,6 @@ def update_telechat(request, idinternal, new_telechat_date, new_returning_item=N def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_item=None): on_agenda = bool(new_telechat_date) - from doc.models import Telechat prev = doc.latest_event(Telechat, type="scheduled_for_telechat") prev_returning = bool(prev and prev.returning_item) prev_telechat = prev.telechat_date if prev else None diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 6e3f0a213..ad0283a3d 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -23,7 +23,7 @@ from ietf.idrfc.utils import * from ietf.idrfc.lastcall import request_last_call from doc.models import Document, Event, BallotPosition, save_document_in_history -from name.models import BallotPositionName +from name.models import BallotPositionName, IesgDocStateName BALLOT_CHOICES = (("yes", "Yes"), ("noobj", "No Objection"), @@ -488,9 +488,46 @@ def defer_ballot(request, name): return render_to_response('idrfc/defer_ballot.html', dict(doc=doc, - telechat_date=telechat_date), + telechat_date=telechat_date, + back_url=doc.idinternal.get_absolute_url()), context_instance=RequestContext(request)) +@group_required('Area_Director','Secretariat') +def defer_ballotREDESIGN(request, name): + """Signal post-pone of Internet Draft ballot, notifying relevant parties.""" + doc = get_object_or_404(Document, docalias__name=name) + if not doc.iesg_state: + raise Http404() + + login = request.user.get_profile().email() + telechat_date = TelechatDates.objects.all()[0].date2 + + if request.method == 'POST': + save_document_in_history(doc) + + prev = doc.iesg_state + doc.iesg_state = IesgDocStateName.objects.get(slug='defer') + e = log_state_changed(request, doc, login, prev) + + doc.time = e.time + doc.save() + + email_state_changed(request, doc, e.desc) + + update_telechat(request, doc, login, telechat_date) + email_ballot_deferred(request, doc, login.get_name(), telechat_date) + + return HttpResponseRedirect(doc.get_absolute_url()) + + return render_to_response('idrfc/defer_ballot.html', + dict(doc=doc, + telechat_date=telechat_date, + back_url=doc.get_absolute_url()), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + defer_ballot = defer_ballotREDESIGN + @group_required('Area_Director','Secretariat') def undefer_ballot(request, name): """Delete deferral of Internet Draft ballot.""" @@ -513,9 +550,42 @@ def undefer_ballot(request, name): return HttpResponseRedirect(doc.idinternal.get_absolute_url()) return render_to_response('idrfc/undefer_ballot.html', - dict(doc=doc), + dict(doc=doc, + back_url=doc.idinternal.get_absolute_url()), context_instance=RequestContext(request)) +@group_required('Area_Director','Secretariat') +def undefer_ballotREDESIGN(request, name): + """Delete deferral of Internet Draft ballot.""" + doc = get_object_or_404(Document, docalias__name=name) + if not doc.iesg_state: + raise Http404() + + login = request.user.get_profile().email() + + if request.method == 'POST': + save_document_in_history(doc) + + prev = doc.iesg_state + doc.iesg_state = IesgDocStateName.objects.get(slug='iesg-eva') + e = log_state_changed(request, doc, login, prev) + + doc.time = e.time + doc.save() + + email_state_changed(request, doc, e.desc) + + return HttpResponseRedirect(doc.get_absolute_url()) + + return render_to_response('idrfc/undefer_ballot.html', + dict(doc=doc, + back_url=doc.get_absolute_url()), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + undefer_ballot = undefer_ballotREDESIGN + + class LastCallTextForm(forms.ModelForm): def clean_last_call_text(self): lines = self.cleaned_data["last_call_text"].split("\r\n") diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index f8d0df7a9..50dd88f3b 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -102,21 +102,15 @@ def change_stateREDESIGN(request, name): if state != doc.iesg_state: save_document_in_history(doc) - prev_state = doc.iesg_state + prev = doc.iesg_state doc.iesg_state = state - - e = Event(doc=doc, by=login) - e.type = "changed_document" - 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() + + e = log_state_changed(request, doc, login, prev) doc.time = e.time doc.save() - email_state_changed(request, doc, strip_tags(e.desc)) + email_state_changed(request, doc, e.desc) email_owner(request, doc, doc.ad, login, e.desc) if doc.iesg_state_id == "lc-req": diff --git a/ietf/templates/idrfc/defer_ballot.html b/ietf/templates/idrfc/defer_ballot.html index f65ebcd5b..db4d65abc 100644 --- a/ietf/templates/idrfc/defer_ballot.html +++ b/ietf/templates/idrfc/defer_ballot.html @@ -11,7 +11,7 @@

The ballot will then be on the IESG agenda of {{ telechat_date }}.

- Back + Back
diff --git a/ietf/templates/idrfc/undefer_ballot.html b/ietf/templates/idrfc/undefer_ballot.html index 8157a8af5..7eac5b909 100644 --- a/ietf/templates/idrfc/undefer_ballot.html +++ b/ietf/templates/idrfc/undefer_ballot.html @@ -9,7 +9,7 @@

Undefer the ballot for {{ doc.file_tag }}?

- Back + Back
From bf29f1a5f902342960a8b54248498973b51dd6c0 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 18 Feb 2011 18:04:20 +0000 Subject: [PATCH 35/75] Ported last call text and ballot writeup views with friends and tests to new schema - Legacy-Id: 2860 --- ietf/idrfc/mails.py | 101 +++++++++- ietf/idrfc/testsREDESIGN.py | 182 ++++++++++------- ietf/idrfc/views_ballot.py | 186 +++++++++++++++++- ietf/idtracker/models.py | 6 + ietf/templates/idrfc/ballot_issued.html | 2 +- ietf/templates/idrfc/ballot_lastcalltext.html | 4 +- .../idrfc/ballot_writeupnotesREDESIGN.html | 36 ++++ .../idrfc/issue_ballot_mailREDESIGN.txt | 39 ++++ redesign/doc/proxy.py | 10 +- 9 files changed, 479 insertions(+), 87 deletions(-) create mode 100644 ietf/templates/idrfc/ballot_writeupnotesREDESIGN.html create mode 100644 ietf/templates/idrfc/issue_ballot_mailREDESIGN.txt diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index cee76ad09..2ca805d57 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -9,7 +9,7 @@ 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 doc.models import Text, BallotPosition, Expiration from person.models import Email def email_state_changed(request, doc, text): @@ -94,6 +94,8 @@ def generate_ballot_writeup(request, doc): e.content = unicode(render_to_string("idrfc/ballot_writeup.txt")) e.save() + return e + def generate_last_call_announcement(request, doc): status = full_intended_status(doc.intended_status).replace("a ", "").replace("an ", "") @@ -156,7 +158,6 @@ def generate_last_call_announcementREDESIGN(request, doc): ) ) - from doc.models import Text e = Text() e.type = "changed_last_call_text" e.by = request.user.get_profile().email() @@ -164,6 +165,8 @@ def generate_last_call_announcementREDESIGN(request, doc): e.desc = u"Last call announcement was generated by %s" % e.by.get_name() e.content = unicode(mail) e.save() + + return e if settings.USE_DB_REDESIGN_PROXY_CLASSES: @@ -250,7 +253,6 @@ def generate_approval_mailREDESIGN(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() @@ -259,6 +261,8 @@ def generate_approval_mailREDESIGN(request, doc): e.content = unicode(mail) e.save() + return e + 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 ", "") @@ -467,7 +471,79 @@ def generate_issue_ballot_mail(request, doc): ad_feedback=ad_feedback ) ) + +def generate_issue_ballot_mailREDESIGN(request, doc): + full_status = full_intended_status(doc.intended_std_level.name) + status = full_status.replace("a ", "").replace("an ", "") + + active_ads = Email.objects.filter(role__name="ad", role__group__state="active") + e = doc.latest_event(type="started_iesg_process") + positions = BallotPosition.objects.filter(doc=doc, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad') + + # format positions and setup discusses and comments + ad_feedback = [] + seen = set() + active_ad_positions = [] + inactive_ad_positions = [] + for p in positions: + if p.ad in seen: + continue + + seen.add(p.ad) + + def formatted(val): + if val: + return "[ X ]" + else: + return "[ ]" + + fmt = u"%-21s%-10s%-11s%-9s%-10s" % ( + p.ad.get_name()[:21], + formatted(p.pos_id == "yes"), + formatted(p.pos_id == "noobj"), + formatted(p.pos_id == "discuss"), + "[ R ]" if p.pos_id == "recuse" else formatted(p.pos_id == "abstain"), + ) + + if p.ad in active_ads: + active_ad_positions.append(fmt) + if not p.pos_id == "discuss": + p.discuss = "" + if p.comment or p.discuss: + ad_feedback.append(p) + else: + inactive_ad_positions.append(fmt) + + active_ad_positions.sort() + inactive_ad_positions.sort() + ad_feedback.sort(key=lambda p: p.ad.get_name()) + + e = doc.latest_event(Expiration, type="sent_last_call") + last_call_expires = e.expires if e else None + + e = doc.latest_event(Text, type="changed_ballot_approval_text") + approval_text = e.content if e else "" + + e = doc.latest_event(Text, type="changed_ballot_writeup_text") + ballot_writeup = e.content if e else "" + + return render_to_string("idrfc/issue_ballot_mailREDESIGN.txt", + dict(doc=doc, + doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), + status=status, + active_ad_positions=active_ad_positions, + inactive_ad_positions=inactive_ad_positions, + ad_feedback=ad_feedback, + last_call_expires=last_call_expires, + approval_text=approval_text, + ballot_writeup=ballot_writeup, + ) + ) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + generate_issue_ballot_mail = generate_issue_ballot_mailREDESIGN + def email_iana(request, doc, to, msg): # fix up message and send message to IANA for each in ballot set import email @@ -485,6 +561,25 @@ def email_iana(request, doc, to, msg): extra=extra, bcc="fenner@research.att.com") +def email_ianaREDESIGN(request, doc, to, msg): + # fix up message and send it with extra info on doc in headers + import email + parsed_msg = email.message_from_string(msg.encode("utf-8")) + + extra = {} + extra["Reply-To"] = "noreply@ietf.org" + extra["X-IETF-Draft-string"] = doc.name + extra["X-IETF-Draft-revision"] = doc.rev + + send_mail_text(request, "IANA <%s>" % to, + parsed_msg["From"], parsed_msg["Subject"], + parsed_msg.get_payload(), + extra=extra, + bcc="fenner@research.att.com") + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + email_iana = email_ianaREDESIGN + def email_last_call_expired(doc): text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.idinternal.cur_state.state diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index 49224c7c9..eb230d72e 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -97,40 +97,42 @@ def make_test_data(): login_name="ad", password="foo", user_level=1, - first_name="Aread", - last_name="Irector", + first_name=porg.first_name, + last_name=porg.last_name, person=porg, ) - p = Person.objects.create( - name="Ano Therdir", - ascii="Ano Therdir", - ) - email = Email.objects.create( - address="ano@ietf.org", - person=p) - Role.objects.create( - name_id="ad", - group=area, - email=email) - 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, - ) + # create a bunch of ads for swarm tests + for i in range(1, 10): + p = Person.objects.create( + name="Ad No%s" % i, + ascii="Ad No%s" % i, + ) + email = Email.objects.create( + address="ad%s@ietf.org" % i, + person=p) + Role.objects.create( + name_id="ad" if i <= 5 else "ex-ad", + group=area, + email=email) + porg = PersonOrOrgInfo.objects.create( + first_name="Ad", + last_name="No%s" % i, + middle_initial="", + ) + EmailAddress.objects.create( + person_or_org=porg, + priority=1, + address=ad.address, + ) + IESGLogin.objects.create( + login_name="ad%s" % i, + password="foo", + user_level=1, + first_name=porg.first_name, + last_name=porg.last_name, + person=porg, + ) p = Person.objects.create( name="Sec Retary", @@ -153,8 +155,8 @@ def make_test_data(): login_name="secretary", password="foo", user_level=0, - first_name="Sec", - last_name="Retary", + first_name=porg.first_name, + last_name=porg.last_name, person=porg, ) @@ -295,8 +297,6 @@ class ChangeStateTestCase(django.test.TestCase): # 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): @@ -327,7 +327,7 @@ class EditInfoTestCase(django.test.TestCase): events_before = draft.event_set.count() mailbox_before = len(mail_outbox) - new_ad = Email.objects.get(address="ano@ietf.org") + new_ad = Email.objects.get(address="ad1@ietf.org") r = self.client.post(url, dict(intended_std_level=str(draft.intended_std_level.pk), @@ -702,10 +702,10 @@ class DeferBallotTestCase(django.test.TestCase): self.assertEquals(draft.iesg_state_id, "iesg-eva") class BallotWriteupsTestCase(django.test.TestCase): - fixtures = ['base', 'draft', 'ballot'] + fixtures = ['names'] def test_edit_last_call_text(self): - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") + draft = make_test_data() url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) @@ -715,7 +715,9 @@ class BallotWriteupsTestCase(django.test.TestCase): q = PyQuery(r.content) self.assertEquals(len(q('textarea[name=last_call_text]')), 1) self.assertEquals(len(q('input[type=submit][value*="Save Last Call"]')), 1) - + # we're secretariat, so we got The Link + self.assertEquals(len(q('a:contains("Make Last Call")')), 1) + # subject error r = self.client.post(url, dict( last_call_text="Subject: test\r\nhello\r\n\r\n", @@ -729,8 +731,8 @@ class BallotWriteupsTestCase(django.test.TestCase): 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) + draft = Document.objects.get(name=draft.name) + self.assertTrue("This is a simple test" in draft.latest_event(Text, type="changed_last_call_text").content) # test regenerate r = self.client.post(url, dict( @@ -738,28 +740,33 @@ class BallotWriteupsTestCase(django.test.TestCase): 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) + draft = Document.objects.get(name=draft.name) + self.assertTrue("Subject: Last Call" in draft.latest_event(Text, type="changed_last_call_text").content) + def test_request_last_call(self): - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") + draft = make_test_data() url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) - mailbox_before = len(mail_outbox) + # give us an announcement to send + r = self.client.post(url, dict(regenerate_last_call_text="1")) + self.assertEquals(r.status_code, 200) + mailbox_before = len(mail_outbox) + + # send r = self.client.post(url, dict( - last_call_text=draft.idinternal.ballot.last_call_text, + last_call_text=draft.latest_event(Text, type="changed_last_call_text").content, 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) - + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.iesg_state_id, "lc-req") self.assertEquals(len(mail_outbox), mailbox_before + 3) - self.assertTrue("Last Call" in mail_outbox[-1]['Subject']) + self.assertTrue(draft.name in mail_outbox[-1]['Subject']) def test_edit_ballot_writeup(self): - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") + draft = make_test_data() url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) @@ -775,43 +782,66 @@ class BallotWriteupsTestCase(django.test.TestCase): 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) + draft = Document.objects.get(name=draft.name) + self.assertTrue("This is a simple test" in draft.latest_event(Text, type="changed_ballot_writeup_text").content) def test_issue_ballot(self): - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") + draft = make_test_data() url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "ad", 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) + def create_pos(num, vote, comment="", discuss=""): + ad = Email.objects.get(address="ad%s@ietf.org" % num) + e = BallotPosition() + e.doc = draft + e.by = ad + e.ad = ad + e.pos = BallotPositionName.objects.get(slug=vote) + e.type = "changed_ballot_position" + e.comment = comment + if e.comment: + e.comment_time = datetime.datetime.now() + e.discuss = discuss + if e.discuss: + e.discuss_time = datetime.datetime.now() + e.save() + # active + create_pos(1, "yes", discuss="discuss1 " * 20) + create_pos(2, "noobj", comment="comment2 " * 20) + create_pos(3, "discuss", discuss="discuss3 " * 20, comment="comment3 " * 20) + create_pos(4, "abstain") + create_pos(5, "recuse") + + # inactive + create_pos(9, "yes") + + # we need approval text to be able to submit + e = Text() + e.doc = draft + e.by = Email.objects.get(address="aread@ietf.org") + e.type = "changed_ballot_approval_text" + e.content = "The document has been approved." + e.save() + 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, + ballot_writeup="This is a test.", issue_ballot="1")) self.assertEquals(r.status_code, 200) - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") + draft = Document.objects.get(name=draft.name) - self.assertTrue(draft.idinternal.ballot.ballot_issued) + self.assertTrue(draft.latest_event(type="sent_ballot_announcement")) self.assertEquals(len(mail_outbox), mailbox_before + 2) - self.assertTrue("Evaluation:" in mail_outbox[-2]['Subject']) - + issue_email = mail_outbox[-2] + self.assertTrue("Evaluation:" in issue_email['Subject']) + self.assertTrue("comment1" not in str(issue_email)) + self.assertTrue("comment2" in str(issue_email)) + self.assertTrue("comment3" in str(issue_email)) + self.assertTrue("discuss3" in str(issue_email)) + self.assertTrue("This is a test" in str(issue_email)) + self.assertTrue("The document has been approved" in str(issue_email)) def test_edit_approval_text(self): draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") @@ -850,6 +880,8 @@ class BallotWriteupsTestCase(django.test.TestCase): draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") self.assertTrue("Subject: Protocol Action" in draft.idinternal.ballot.approval_text) + # FIXME: test regeneration of announcement when it's not approved/via rfc editor + class ApproveBallotTestCase(django.test.TestCase): fixtures = ['base', 'draft', 'ballot'] diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index ad0283a3d..3bcb65e6c 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -670,6 +670,7 @@ def lastcalltext(request, name): return render_to_response('idrfc/ballot_lastcalltext.html', dict(doc=doc, + back_url=doc.idinternal.get_absolute_url(), ballot=ballot, last_call_form=last_call_form, can_request_last_call=can_request_last_call, @@ -678,6 +679,101 @@ def lastcalltext(request, name): ), context_instance=RequestContext(request)) + +class LastCallTextFormREDESIGN(forms.Form): + last_call_text = forms.CharField(widget=forms.Textarea, required=True) + + def clean_last_call_text(self): + lines = self.cleaned_data["last_call_text"].split("\r\n") + for l, next in zip(lines, lines[1:]): + if l.startswith('Subject:') and next.strip(): + raise forms.ValidationError("Subject line appears to have a line break, please make sure there is no line breaks in the subject line and that it is followed by an empty line.") + + return self.cleaned_data["last_call_text"].replace("\r", "") + +@group_required('Area_Director','Secretariat') +def lastcalltextREDESIGN(request, name): + """Editing of the last call text""" + doc = get_object_or_404(Document, docalias__name=name) + if not doc.iesg_state: + raise Http404() + + login = request.user.get_profile().email() + + existing = doc.latest_event(Text, type="changed_last_call_text") + if not existing: + existing = generate_last_call_announcement(request, doc) + + form = LastCallTextForm(initial=dict(last_call_text=existing.content)) + + if request.method == 'POST': + if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST: + form = LastCallTextForm(request.POST) + if form.is_valid(): + t = form.cleaned_data['last_call_text'] + if t != existing.content: + e = Text(doc=doc, by=login) + e.by = login + e.type = "changed_last_call_text" + e.desc = "Last call announcement was changed by %s" % login.get_name() + e.content = t + e.save() + + doc.time = e.time + doc.save() + + if "send_last_call_request" in request.POST: + save_document_in_history(doc) + + prev = doc.iesg_state + doc.iesg_state = IesgDocStateName.objects.get(slug='lc-req') + e = log_state_changed(request, doc, login, prev) + + doc.time = e.time + doc.save() + + email_state_changed(request, doc, e.desc) + email_owner(request, doc, doc.ad, login, e.desc) + + request_last_call(request, doc) + + return render_to_response('idrfc/last_call_requested.html', + dict(doc=doc), + context_instance=RequestContext(request)) + + if "regenerate_last_call_text" in request.POST: + e = generate_last_call_announcement(request, doc) + + doc.time = e.time + doc.save() + + # make sure form has the updated text + form = LastCallTextForm(initial=dict(last_call_text=e.content)) + + + can_request_last_call = doc.iesg_state.order < 27 + can_make_last_call = doc.iesg_state.order < 20 + can_announce = doc.iesg_state.order > 19 + + need_intended_status = "" + if not doc.intended_std_level: + need_intended_status = doc.file_tag() + + return render_to_response('idrfc/ballot_lastcalltext.html', + dict(doc=doc, + back_url=doc.get_absolute_url(), + last_call_form=form, + can_request_last_call=can_request_last_call, + can_make_last_call=can_make_last_call, + need_intended_status=need_intended_status, + ), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + LastCallTextForm = LastCallTextFormREDESIGN + lastcalltext = lastcalltextREDESIGN + + @group_required('Area_Director','Secretariat') def ballot_writeupnotes(request, name): """Editing of ballot write-up and notes""" @@ -734,7 +830,8 @@ def ballot_writeupnotes(request, name): doc.idinternal.save() return render_to_response('idrfc/ballot_issued.html', - dict(doc=doc), + dict(doc=doc, + back_url=doc.idinternal.get_absolute_url()), context_instance=RequestContext(request)) @@ -752,6 +849,93 @@ def ballot_writeupnotes(request, name): ), context_instance=RequestContext(request)) +class BallotWriteupFormREDESIGN(forms.Form): + ballot_writeup = forms.CharField(widget=forms.Textarea, required=True) + + def clean_ballot_writeup(self): + return self.cleaned_data["ballot_writeup"].replace("\r", "") + +@group_required('Area_Director','Secretariat') +def ballot_writeupnotesREDESIGN(request, name): + """Editing of ballot write-up and notes""" + doc = get_object_or_404(Document, docalias__name=name) + if not doc.iesg_state: + raise Http404() + + login = request.user.get_profile().email() + + approval = doc.latest_event(Text, type="changed_ballot_approval_text") + + existing = doc.latest_event(Text, type="changed_ballot_writeup_text") + if not existing: + existing = generate_ballot_writeup(request, doc) + + form = BallotWriteupForm(initial=dict(ballot_writeup=existing.content)) + + if request.method == 'POST' and "save_ballot_writeup" in request.POST or "issue_ballot" in request.POST: + form = BallotWriteupForm(request.POST) + if form.is_valid(): + t = form.cleaned_data["ballot_writeup"] + if t != existing.content: + e = Text(doc=doc, by=login) + e.by = login + e.type = "changed_ballot_writeup_text" + e.desc = "Ballot writeup was changed by %s" % login.get_name() + e.content = t + e.save() + + doc.time = e.time + doc.save() + + if "issue_ballot" in request.POST and approval: + if in_group(request.user, "Area_Director") and not doc.latest_event(BallotPosition, ad=login): + # sending the ballot counts as a yes + pos = BallotPosition(doc=doc, by=login) + pos.type = "changed_ballot_position" + pos.ad = login + pos.pos_id = "yes" + pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.get_name()) + pos.save() + + msg = generate_issue_ballot_mail(request, doc) + send_mail_preformatted(request, msg) + + email_iana(request, doc, 'drafts-eval@icann.org', msg) + + e = Event(doc=doc, by=login) + e.by = login + e.type = "sent_ballot_announcement" + e.desc = "Ballot has been issued by %s" % login.get_name() + e.save() + + doc.time = e.time + doc.save() + + return render_to_response('idrfc/ballot_issued.html', + dict(doc=doc, + back_url=doc.get_absolute_url()), + context_instance=RequestContext(request)) + + + need_intended_status = "" + if not doc.intended_std_level: + need_intended_status = doc.file_tag() + + return render_to_response('idrfc/ballot_writeupnotesREDESIGN.html', + dict(doc=doc, + back_url=doc.get_absolute_url(), + ballot_issued=bool(doc.latest_event(type="sent_ballot_announcement")), + ballot_writeup_form=form, + need_intended_status=need_intended_status, + approval=approval, + ), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + BallotWriteupForm = BallotWriteupFormREDESIGN + ballot_writeupnotes = ballot_writeupnotesREDESIGN + + @group_required('Area_Director','Secretariat') def ballot_approvaltext(request, name): """Editing of approval text""" diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py index 3664b9c27..69c35cdb1 100644 --- a/ietf/idtracker/models.py +++ b/ietf/idtracker/models.py @@ -179,6 +179,9 @@ class InternetDraft(models.Model): return self.filename def file_tag(self): return "<%s-%s.txt>" % (self.filename, self.revision_display()) + def name(self): + # small hack to make model forward-compatible with new schema + return self.filename def group_acronym(self): return self.group.acronym def idstate(self): @@ -397,6 +400,9 @@ class Rfc(models.Model): return "%s.txt" % ( self.filename() ) def filename(self): return "rfc%d" % ( self.rfc_number ) + def name(self): + # small hack to make model forward-compatible with new schema + return self.filename() def revision(self): return "RFC" def revision_display(self): diff --git a/ietf/templates/idrfc/ballot_issued.html b/ietf/templates/idrfc/ballot_issued.html index 0c62d5f7f..45a8464e3 100644 --- a/ietf/templates/idrfc/ballot_issued.html +++ b/ietf/templates/idrfc/ballot_issued.html @@ -8,6 +8,6 @@

Ballot has been sent out.

{% endblock %} diff --git a/ietf/templates/idrfc/ballot_lastcalltext.html b/ietf/templates/idrfc/ballot_lastcalltext.html index 62861bc14..135af9b64 100644 --- a/ietf/templates/idrfc/ballot_lastcalltext.html +++ b/ietf/templates/idrfc/ballot_lastcalltext.html @@ -25,7 +25,7 @@ form #id_last_call_text { {% endif %}
- Back + Back {% if can_request_last_call and not need_intended_status %} @@ -38,7 +38,7 @@ form #id_last_call_text { {% if user|in_group:"Secretariat" %}

{% if can_make_last_call %} -Make Last Call +Make Last Call {% endif %}

diff --git a/ietf/templates/idrfc/ballot_writeupnotesREDESIGN.html b/ietf/templates/idrfc/ballot_writeupnotesREDESIGN.html new file mode 100644 index 000000000..3cab31e74 --- /dev/null +++ b/ietf/templates/idrfc/ballot_writeupnotesREDESIGN.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block title %}Ballot writeup and notes for {{ doc }}{% endblock %} + +{% block morecss %} +form #id_ballot_writeup { + width: 700px; + height: 600px; +} +{% endblock %} + +{% block content %} +

Ballot writeup and notes for {{ doc }}

+ + +
+ +

(Technical Summary, Working Group Summary, Document Quality, + Personnel, RFC Editor Note, IRTF Note, IESG Note, IANA Note)

+ +

This text will be appended to all announcements and messages to + the IRTF or RFC Editor.

+ + {{ ballot_writeup_form.ballot_writeup }} + + {% if not approval %}

Ballot cannot be issued before announcement text is added.

{% endif %} + +
+ Back + + +
+
+ + +{% endblock%} diff --git a/ietf/templates/idrfc/issue_ballot_mailREDESIGN.txt b/ietf/templates/idrfc/issue_ballot_mailREDESIGN.txt new file mode 100644 index 000000000..c56fbfee9 --- /dev/null +++ b/ietf/templates/idrfc/issue_ballot_mailREDESIGN.txt @@ -0,0 +1,39 @@ +To: Internet Engineering Steering Group +From: IESG Secretary +Reply-To: IESG Secretary +Subject: Evaluation: {{ doc.file_tag|safe }} to {{ status }} + +{% filter wordwrap:73 %}Evaluation for {{ doc.file_tag|safe }} can be found at {{ doc_url }} + +{% if last_call_expires %}Last call to expire on: {{ last_call_expires }} + +{% endif %}{% endfilter %} + Please return the full line with your position. + + Yes No-Objection Discuss Abstain +{% for fmt in active_ad_positions %}{{ fmt|safe }} +{% endfor %}{% if inactive_ad_positions %} + +{% for fmt in inactive_ad_positions %}{{ fmt|safe }} +{% endfor %}{% endif %} + +"Yes" or "No-Objection" positions from 2/3 of non-recused ADs, +with no "Discuss" positions, are needed for approval. + +DISCUSSES AND COMMENTS +====================== +{% filter wordwrap:79 %}{% for pos in ad_feedback %}{{ pos.ad.get_name }}: + +{% if pos.discuss %}Discuss [{{ pos.discuss_time|date:"Y-m-d" }}]: +{{ pos.discuss }} + +{% endif %}{% if pos.comment %}Comment [{{ pos.comment_time|date:"Y-m-d" }}]: +{{ pos.comment }} + +{% endif %} +{% endfor %}{% endfilter %} +---- following is a DRAFT of message to be sent AFTER approval --- +{{ approval_text|safe }}{% if ballot_writeup %} + +{{ ballot_writeup|safe }} +{% endif %} diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 91835e8f8..501f57c50 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -106,8 +106,8 @@ class InternetDraft(Document): #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 + e = self.latest_event(Expiration, type="sent_last_call") + return e.expires if e else None #b_sent_date = models.DateField(null=True, blank=True) @property @@ -171,11 +171,11 @@ class InternetDraft(Document): 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") + e = self.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 self.state_id == "rfc" and self.name.startswith("draft") and not hasattr(self, "viewing_as_rfc"): + previous_process = self.latest_event(type="started_iesg_process", time__lt=e.time) if previous_process: start = previous_process.time end = e.time From fae31940e3f7bd76bf56fbbba4b7e9b225b2ba17 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 21 Feb 2011 18:12:30 +0000 Subject: [PATCH 36/75] Ported edit approval text and approve ballot views with tests to new schema - Legacy-Id: 2862 --- ietf/idrfc/mails.py | 4 +- ietf/idrfc/testsREDESIGN.py | 69 +++++---- ietf/idrfc/views_ballot.py | 141 ++++++++++++++++++ ietf/templates/idrfc/approve_ballot.html | 2 +- ietf/templates/idrfc/ballot_approvaltext.html | 4 +- 5 files changed, 188 insertions(+), 32 deletions(-) diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index 2ca805d57..2d2cdc8ab 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -317,12 +317,12 @@ def generate_approval_mail_approved(request, doc): 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 + disapproved = doc.iesg_state_id 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_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(), doc_type=doc_type, status=status, full_status=full_status, diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index eb230d72e..9806a0deb 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -844,7 +844,7 @@ class BallotWriteupsTestCase(django.test.TestCase): self.assertTrue("The document has been approved" in str(issue_email)) def test_edit_approval_text(self): - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") + draft = make_test_data() url = urlreverse('doc_ballot_approvaltext', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) @@ -855,38 +855,36 @@ class BallotWriteupsTestCase(django.test.TestCase): 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) + draft = Document.objects.get(name=draft.name) + self.assertTrue("This is a simple test" in draft.latest_event(Text, type="changed_ballot_approval_text").content) # test regenerate - r = self.client.post(url, dict( - approval_text="This is a simple test.", - regenerate_approval_text="1")) + r = self.client.post(url, dict(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) - - # FIXME: test regeneration of announcement when it's not approved/via rfc editor + draft = Document.objects.get(name=draft.name) + self.assertTrue("Subject: Protocol Action" in draft.latest_event(Text, type="changed_ballot_approval_text").content) + + # test regenerate when it's a disapprove + draft.iesg_state_id = "nopubadw" + draft.save() + + r = self.client.post(url, dict(regenerate_approval_text="1")) + self.assertEquals(r.status_code, 200) + draft = Document.objects.get(name=draft.name) + self.assertTrue("NOT be published" in draft.latest_event(Text, type="changed_ballot_approval_text").content) class ApproveBallotTestCase(django.test.TestCase): - fixtures = ['base', 'draft', 'ballot'] + fixtures = ['names'] def test_approve_ballot(self): - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") + draft = make_test_data() + draft.iesg_state_id = "iesg-eva" # make sure it's approvable + draft.save() url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) @@ -894,8 +892,8 @@ class ApproveBallotTestCase(django.test.TestCase): 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) + self.assertTrue("Send out the announcement" in q('.actions input[type=submit]')[0].get('value')) + self.assertEquals(len(q('.announcement pre:contains("Subject: Protocol Action")')), 1) # approve mailbox_before = len(mail_outbox) @@ -903,15 +901,32 @@ class ApproveBallotTestCase(django.test.TestCase): 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) - + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.iesg_state_id, "ann") 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']) + def test_disapprove_ballot(self): + draft = make_test_data() + draft.iesg_state_id = "nopubadw" + draft.save() + + url = urlreverse('doc_approve_ballot', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # disapprove (the Martians aren't going to be happy) + mailbox_before = len(mail_outbox) + + r = self.client.post(url, dict()) + self.assertEquals(r.status_code, 302) + + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.iesg_state_id, "dead") + self.assertEquals(len(mail_outbox), mailbox_before + 3) + self.assertTrue("NOT be published" in str(mail_outbox[-1])) + class MakeLastCallTestCase(django.test.TestCase): fixtures = ['base', 'draft', 'ballot'] diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 3bcb65e6c..21ab4b7aa 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -976,6 +976,7 @@ def ballot_approvaltext(request, name): return render_to_response('idrfc/ballot_approvaltext.html', dict(doc=doc, + back_url=doc.idinternal.get_absolute_url(), ballot=ballot, approval_text_form=approval_text_form, can_announce=can_announce, @@ -983,6 +984,69 @@ def ballot_approvaltext(request, name): ), context_instance=RequestContext(request)) +class ApprovalTextFormREDESIGN(forms.Form): + approval_text = forms.CharField(widget=forms.Textarea, required=True) + + def clean_approval_text(self): + return self.cleaned_data["approval_text"].replace("\r", "") + +@group_required('Area_Director','Secretariat') +def ballot_approvaltextREDESIGN(request, name): + """Editing of approval text""" + doc = get_object_or_404(Document, docalias__name=name) + if not doc.iesg_state: + raise Http404() + + login = request.user.get_profile().email() + + existing = doc.latest_event(Text, type="changed_ballot_approval_text") + if not existing: + existing = generate_approval_mail(request, doc) + + form = ApprovalTextForm(initial=dict(approval_text=existing.content)) + + if request.method == 'POST': + if "save_approval_text" in request.POST: + form = ApprovalTextForm(request.POST) + if form.is_valid(): + t = form.cleaned_data['approval_text'] + if t != existing.content: + e = Text(doc=doc, by=login) + e.by = login + e.type = "changed_ballot_approval_text" + e.desc = "Ballot approval text was changed by %s" % login.get_name() + e.content = t + e.save() + + doc.time = e.time + doc.save() + + if "regenerate_approval_text" in request.POST: + e = generate_approval_mail(request, doc) + + doc.time = e.time + doc.save() + + # make sure form has the updated text + form = ApprovalTextForm(initial=dict(approval_text=existing.content)) + + can_announce = doc.iesg_state.order > 19 + need_intended_status = "" + if not doc.intended_std_level: + need_intended_status = doc.file_tag() + + return render_to_response('idrfc/ballot_approvaltext.html', + dict(doc=doc, + back_url=doc.get_absolute_url(), + approval_text_form=form, + can_announce=can_announce, + need_intended_status=need_intended_status, + ), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + ApprovalTextForm = ApprovalTextFormREDESIGN + ballot_approvaltext = ballot_approvaltextREDESIGN @group_required('Secretariat') def approve_ballot(request, name): @@ -1055,6 +1119,83 @@ def approve_ballot(request, name): announcement=announcement), context_instance=RequestContext(request)) +@group_required('Secretariat') +def approve_ballotREDESIGN(request, name): + """Approve ballot, sending out announcement, changing state.""" + doc = get_object_or_404(Document, docalias__name=name) + if not doc.iesg_state: + raise Http404() + + login = request.user.get_profile().email() + + e = doc.latest_event(Text, type="changed_ballot_approval_text") + if not e: + e = generate_approval_mail(request, doc) + approval_text = e.content + + e = doc.latest_event(Text, type="changed_ballot_writeup_text") + if not e: + e = generate_ballot_writeup(request, doc) + ballot_writeup = e.content + + if "NOT be published" in approval_text: + action = "do_not_publish" + elif "To: RFC Editor" in approval_text: + action = "to_rfc_editor" + else: + action = "to_announcement_list" + + announcement = approval_text + "\n\n" + ballot_writeup + + if request.method == 'POST': + if action == "do_not_publish": + new_state = IesgDocStateName.objects.get(slug="dead") + else: + new_state = IesgDocStateName.objects.get(slug="ann") + + # fixup document + save_document_in_history(doc) + + prev = doc.iesg_state + doc.iesg_state = new_state + + e = Event(doc=doc, by=login) + if action == "do_not_publish": + e.type = "iesg_disapproved" + e.desc = "Do Not Publish note has been sent to RFC Editor" + else: + e.type = "iesg_approved" + e.desc = "IESG has approved the document" + + e.save() + + change_description = e.desc + " and state has been changed to %s" % doc.iesg_state.name + + e = log_state_changed(request, doc, login, prev) + + doc.time = e.time + doc.save() + + email_state_changed(request, doc, change_description) + email_owner(request, doc, doc.ad, login, change_description) + + # send announcement + send_mail_preformatted(request, announcement) + + if action == "to_announcement_list": + email_iana(request, doc, "drafts-approval@icann.org", announcement) + + return HttpResponseRedirect(doc.get_absolute_url()) + + return render_to_response('idrfc/approve_ballot.html', + dict(doc=doc, + action=action, + announcement=announcement), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + approve_ballot = approve_ballotREDESIGN + class MakeLastCallForm(forms.Form): last_call_sent_date = forms.DateField(required=True) diff --git a/ietf/templates/idrfc/approve_ballot.html b/ietf/templates/idrfc/approve_ballot.html index 3960f1253..f7a4c8ef1 100644 --- a/ietf/templates/idrfc/approve_ballot.html +++ b/ietf/templates/idrfc/approve_ballot.html @@ -30,7 +30,7 @@ form.approve-ballot .announcement {
- Back + Back {% ifequal action "to_announcement_list" %} {% endifequal %} diff --git a/ietf/templates/idrfc/ballot_approvaltext.html b/ietf/templates/idrfc/ballot_approvaltext.html index d6b4ecadc..f58ae42a6 100644 --- a/ietf/templates/idrfc/ballot_approvaltext.html +++ b/ietf/templates/idrfc/ballot_approvaltext.html @@ -19,7 +19,7 @@ form #id_approval_text { {{ approval_text_form.approval_text }}
- Back + Back
@@ -29,7 +29,7 @@ form #id_approval_text { {% if user|in_group:"Secretariat" %}

{% if can_announce %} -Approve ballot +Approve ballot {% endif %}

{% endif %} From 9511c77dcf0866f73aa27b6a2d19c71470826cfe Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 1 Mar 2011 14:15:40 +0000 Subject: [PATCH 37/75] Port make last call view and with test to new schema - Legacy-Id: 2867 --- ietf/idrfc/testsREDESIGN.py | 14 ++-- ietf/idrfc/views_ballot.py | 78 +++++++++++++++++++ .../idrfc/make_last_callREDESIGN.html | 39 ++++++++++ 3 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 ietf/templates/idrfc/make_last_callREDESIGN.html diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index 9806a0deb..833386c64 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -928,13 +928,11 @@ class ApproveBallotTestCase(django.test.TestCase): self.assertTrue("NOT be published" in str(mail_outbox[-1])) class MakeLastCallTestCase(django.test.TestCase): - fixtures = ['base', 'draft', 'ballot'] + fixtures = ['names'] 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 = make_test_data() + draft.iesg_state_id = "lc-req" draft.save() url = urlreverse('doc_make_last_call', kwargs=dict(name=draft.name)) @@ -957,9 +955,9 @@ class MakeLastCallTestCase(django.test.TestCase): )) 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) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.iesg_state.slug, "lc") + self.assertEquals(draft.latest_event(Expiration, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date) self.assertEquals(len(mail_outbox), mailbox_before + 4) self.assertTrue("Last Call" in mail_outbox[-4]['Subject']) diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 21ab4b7aa..4b7a2eda0 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -1268,3 +1268,81 @@ def make_last_call(request, name): form=form), context_instance=RequestContext(request)) + +@group_required('Secretariat') +def make_last_callREDESIGN(request, name): + """Make last call for Internet Draft, sending out announcement.""" + doc = get_object_or_404(Document, docalias__name=name) + if not doc.iesg_state: + raise Http404() + + login = request.user.get_profile().email() + + e = doc.latest_event(Text, type="changed_last_call_text") + if not e: + e = generate_last_call_announcement(request, doc) + announcement = e.content + + # why cut -4 off name? a better question is probably why these + # tables aren't linked together properly + filename_fragment = doc.name[:-4] + iprs = IprDetail.objects.filter(title__icontains=filename_fragment) + if iprs: + links = [urlreverse("ietf.ipr.views.show", kwargs=dict(ipr_id=i.ipr_id)) + for i in iprs] + + announcement += "\n\n" + announcement += "The following IPR Declarations may be related to this I-D:" + announcement += "\n\n" + announcement += "\n".join(links) + else: + announcement += "\n\n" + announcement += "No IPR declarations were found that appear related to this I-D." + + if request.method == 'POST': + form = MakeLastCallForm(request.POST) + if form.is_valid(): + send_mail_preformatted(request, announcement) + email_iana(request, doc, "drafts-lastcall@icann.org", announcement) + + save_document_in_history(doc) + + prev = doc.iesg_state + doc.iesg_state = IesgDocStateName.objects.get(slug='lc') + e = log_state_changed(request, doc, login, prev) + + doc.time = e.time + doc.save() + + change_description = "Last call has been made for %s and state has been changed to %s" % (doc.name, doc.iesg_state.name) + email_state_changed(request, doc, change_description) + email_owner(request, doc, doc.ad, login, change_description) + + e = Expiration(doc=doc, by=login) + e.type = "sent_last_call" + e.desc = "Last call sent by %s" % login.get_name() + if form.cleaned_data['last_call_sent_date'] != e.time.date(): + e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time()) + e.expires = form.cleaned_data['last_call_expiration_date'] + e.save() + + return HttpResponseRedirect(doc.get_absolute_url()) + else: + initial = {} + initial["last_call_sent_date"] = date.today() + expire_days = 14 + if doc.group.type_id == "individ": + expire_days = 28 + + initial["last_call_expiration_date"] = date.today() + timedelta(days=expire_days) + + form = MakeLastCallForm(initial=initial) + + return render_to_response('idrfc/make_last_callREDESIGN.html', + dict(doc=doc, + form=form), + context_instance=RequestContext(request)) + + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + make_last_call = make_last_callREDESIGN diff --git a/ietf/templates/idrfc/make_last_callREDESIGN.html b/ietf/templates/idrfc/make_last_callREDESIGN.html new file mode 100644 index 000000000..b81d8ad2e --- /dev/null +++ b/ietf/templates/idrfc/make_last_callREDESIGN.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block title %}Make Last Call for {{ doc.name }}{% endblock %} + +{% block morecss %} +form.approve-ballot pre { + margin: 0; + padding: 4px; + border-top: 4px solid #eee; + border-bottom: 4px solid #eee; +} +form.approve-ballot .announcement { + overflow-x: auto; + overflow-y: scroll; + width: 800px; + height: 400px; + border: 1px solid #bbb; +} +{% endblock %} + +{% block content %} +

Make Last Call for {{ doc.name }}

+ +

Make last call for following draft:

+ +
{{ doc.file_tag }} ({{ doc.group.acronym }}) - {{ doc.intended_std_level.name }}
+ +
+ + {{ form.as_table }} +
+ +
+ Back + + +
+
+{% endblock %} From e7788f11f5a291b3712f374d9d0e445db6adeb86 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 1 Mar 2011 18:34:02 +0000 Subject: [PATCH 38/75] Remove debug and fix test - Legacy-Id: 2868 --- ietf/idrfc/testsREDESIGN.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index 833386c64..aeb57e08f 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -391,10 +391,6 @@ class EditInfoTestCase(django.test.TestCase): draft = Document.objects.get(name=draft.name) self.assertTrue(not draft.latest_event(Telechat, "scheduled_for_telechat").telechat_date) - for e in draft.event_set.all(): - print e.desc - - def test_start_iesg_process_on_draft(self): draft = make_test_data() draft.ad = None @@ -679,8 +675,9 @@ class DeferBallotTestCase(django.test.TestCase): self.assertEquals(draft.iesg_state_id, "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']) + self.assertTrue("State Update" in mail_outbox[-2]['Subject']) + self.assertTrue("Deferred" in mail_outbox[-1]['Subject']) + self.assertTrue(draft.file_tag() in mail_outbox[-1]['Subject']) def test_undefer_ballot(self): draft = make_test_data() From 11865dc2492d9a98ea3815d299c827339ec2d7cd Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 1 Mar 2011 18:50:04 +0000 Subject: [PATCH 39/75] Clip the lookup for old ballots at the start of the IESG process - Legacy-Id: 2869 --- ietf/idrfc/views_ballot.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 4b7a2eda0..101be75f5 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -82,7 +82,7 @@ def edit_position(request, name): raise Http404() ad = get_object_or_404(IESGLogin, login_name=ad_username) - doc.latest_event(BallotPosition, type='changed_ballot_position', ad=ad) + pos, discuss, comment = get_ballot_info(doc.idinternal.ballot, ad) if request.method == 'POST': form = EditPositionForm(request.POST) @@ -201,7 +201,8 @@ class EditPositionFormREDESIGN(forms.Form): def edit_positionREDESIGN(request, name): """Vote and edit discuss and comment on Internet Draft as Area Director.""" doc = get_object_or_404(Document, docalias__name=name) - if not doc.iesg_state: + started_process = doc.latest_event(type="started_iesg_process") + if not doc.iesg_state or not started_process: raise Http404() ad = login = request.user.get_profile().email() @@ -218,7 +219,7 @@ def edit_positionREDESIGN(request, name): raise Http404() ad = get_object_or_404(Email, pk=ad_id) - old_pos = doc.latest_event(BallotPosition, type="changed_ballot_position", ad=ad) + old_pos = doc.latest_event(BallotPosition, type="changed_ballot_position", ad=ad, time__gte=started_process.time) if request.method == 'POST': form = EditPositionForm(request.POST) @@ -394,6 +395,9 @@ def send_ballot_comment(request, name): def send_ballot_commentREDESIGN(request, name): """Email Internet Draft ballot discuss/comment for area director.""" doc = get_object_or_404(Document, docalias__name=name) + started_process = doc.latest_event(type="started_iesg_process") + if not started_process: + raise Http404() ad = login = request.user.get_profile().email() @@ -413,7 +417,7 @@ def send_ballot_commentREDESIGN(request, name): raise Http404() ad = get_object_or_404(Email, pk=ad_id) - pos = doc.latest_event(BallotPosition, type="changed_ballot_position", ad=ad) + pos = doc.latest_event(BallotPosition, type="changed_ballot_position", ad=ad, time__gte=started_process.time) if not pos: raise Http404() From b740db33be5f3c87eab2c670407894684457dbf8 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 1 Mar 2011 19:37:04 +0000 Subject: [PATCH 40/75] Make it possible to specify a testurl file to doTestUrls - Legacy-Id: 2870 --- ietf/utils/test_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ietf/utils/test_utils.py b/ietf/utils/test_utils.py index 14461faf2..dce6327d1 100644 --- a/ietf/utils/test_utils.py +++ b/ietf/utils/test_utils.py @@ -38,7 +38,7 @@ import django from django.db import connection from django.test import TestCase from django.test.client import Client -import ietf +import ietf.settings from django.conf import settings from datetime import datetime import urllib2 as urllib @@ -117,7 +117,10 @@ class SimpleUrlTestCase(TestCase,RealDatabaseTest): self.tearDownRealDatabase() def doTestUrls(self, test_filename): - filename = os.path.dirname(os.path.abspath(test_filename))+"/testurl.list" + if test_filename.endswith(".list"): + filename = test_filename + else: + filename = os.path.dirname(os.path.abspath(test_filename))+"/testurl.list" print " Reading "+filename tuples = read_testurls(filename) failures = 0 From dd72649d3d2295cdc1cf4c89c2d552c75893d71d Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 1 Mar 2011 19:37:58 +0000 Subject: [PATCH 41/75] More robust error handling in search, fix the urls tests to reflect the new schema - Legacy-Id: 2871 --- ietf/idrfc/testsREDESIGN.py | 3 +- ietf/idrfc/testurlREDESIGN.list | 88 +++++++++++++++++++++++++++++++++ ietf/idrfc/views_search.py | 8 +-- 3 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 ietf/idrfc/testurlREDESIGN.list diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index aeb57e08f..bff617f09 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -53,7 +53,8 @@ from ietf.utils.test_runner import mail_outbox class IdRfcUrlTestCase(SimpleUrlTestCase): def testUrls(self): - self.doTestUrls(__file__) + #self.doTestUrls(__file__) + self.doTestUrls(os.path.join(os.path.dirname(os.path.abspath(__file__)), "testurlREDESIGN.list")) def make_test_data(): # groups diff --git a/ietf/idrfc/testurlREDESIGN.list b/ietf/idrfc/testurlREDESIGN.list new file mode 100644 index 000000000..6c92a6511 --- /dev/null +++ b/ietf/idrfc/testurlREDESIGN.list @@ -0,0 +1,88 @@ +200 / +200 /doc/ +200,heavy /doc/all/ +200,heavy /doc/active/ + +# draft that's now RFC +200 /doc/draft-ietf-avt-rtp-atrac-family/ +200 /doc/draft-ietf-avt-rtp-atrac-family/doc.json +200 /doc/draft-ietf-avt-rtp-atrac-family/ballot.json +200 /doc/draft-ietf-avt-rtp-atrac-family/_ballot.data + +# replaced draft, never went to IESG +200 /doc/draft-eronen-mobike-mopo/ +404 /doc/draft-eronen-mobike-mopo/ballot.json +404 /doc/draft-eronen-mobike-mopo/_ballot.data + +# expired draft +200 /doc/draft-eronen-eap-sim-aka-80211/ + +# Normal RFC +200 /doc/rfc4739/ +200 /doc/rfc4739/doc.json +200 /doc/rfc4739/ballot.json # has ballot from I-D +200 /doc/rfc4739/_ballot.data + +# RFC that's evaluated in IESG +200 /doc/rfc3852/ +200 /doc/rfc3852/doc.json +200 /doc/rfc3852/ballot.json +200 /doc/rfc3852/_ballot.data + +# old RFC +200 /doc/rfc822/ +200 /doc/rfc822/doc.json + +# ballot sets +200 /doc/rfc3550/ballot.json +200 /doc/rfc3550/_ballot.data +200 /doc/rfc3551/ballot.json +200 /doc/rfc3551/_ballot.data +200 /doc/draft-irtf-dtnrg-ltp/ballot.json +200 /doc/draft-irtf-dtnrg-ltp/_ballot.data + +# file formats +200 /doc/rfc9/ # PDF only +200 /doc/rfc2490/ # TXT+PDF+PS +200 /doc/rfc500/ # not online + +404 /doc/draft-no-such-draft/ +404 /doc/rfc4637/ + +200 /doc/rfc2444/doc.json # foreignkey problem with Django 1.x + +# current AD -- needs to be updated at some point +200 /doc/ad/robert.sparks/ +# former AD +200 /doc/ad/sam.hartman/ +404 /doc/ad/no.body/ + +# ballot exists, but it's not issued +404 /doc/draft-ietf-aaa-diameter-api/ballot.json +404 /doc/draft-ietf-aaa-diameter-api/_ballot.data +# ballot does not exist +404 /doc/draft-zeilenga-cldap/ballot.json +404 /doc/draft-zeilenga-cldap/_ballot.data +# comment with created_by=999 +200 /doc/draft-ietf-l3vpn-2547bis-mcast-bgp/ +# comment with created_by=0 (and no idinternal entry) +200 /doc/draft-ietf-proto-wgdocument-states/ + +200 /doc/search/ +200 /doc/search/?rfcs=on&name=snmp +200 /doc/search/?rfcs=on&name=nfs&by=ad&ad=lars.eggert%40nokia.com +200 /doc/search/?activeDrafts=on&name=sipping +200 /doc/search/?oldDrafts=on&name=tls +200 /doc/search/?activeDrafts=on&oldDrafts=on&ad=lars.eggert%40nokia.com&by=ad +200 /doc/search/?activeDrafts=on&state=iesg-eva&by=state +200 /doc/search/?activeDrafts=on&oldDrafts=on&subState=need-rev&by=state +200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&ad=lars.eggert%40nokia.com&name=nfs&by=ad +200 /doc/search/?rfcs=on&group=tls&by=group +200 /doc/search/?activeDrafts=on&group=tls&by=group +200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&author=eronen&by=author +200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&area=178&name=ldap&by=area +200 /doc/search/?activeDrafts=on&name=asdfsadfsdfasdf +200 /doc/search/?activeDrafts=on&name=%EF%BD%8C #non-ASCII + +# Test case for missing publication date +200 /doc/search/?oldDrafts=on&name=ppvpn diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index f7c7145a1..b91e4d4fd 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -38,7 +38,7 @@ from django.template import RequestContext from django.views.decorators.cache import cache_page from ietf.idtracker.models import IDState, IESGLogin, IDSubState, Area, InternetDraft, Rfc, IDInternal, IETFWG from ietf.idrfc.models import RfcIndex -from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect +from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect from ietf.idrfc.idrfc_wrapper import IdWrapper,RfcWrapper,IdRfcWrapper from ietf.utils import normalize_draftname from django.conf import settings @@ -296,9 +296,9 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: q = self.cleaned_data # Reset query['by'] if needed for k in ('author','group','area','ad'): - if (q['by'] == k) and not q[k]: + if (q['by'] == k) and (k not in q or not q[k]): q['by'] = None - if (q['by'] == 'state') and not (q['state'] or q['subState']): + if (q['by'] == 'state') and (not 'state' in q or not 'subState' in q or not (q['state'] or q['subState'])): q['by'] = None # Reset other fields for k in ('author','group','area','ad'): @@ -456,7 +456,7 @@ def search_results(request): return search_main(request) form = SearchForm(dict(request.REQUEST.items())) if not form.is_valid(): - return HttpResponse("form not valid?", mimetype="text/plain") + return HttpResponseBadRequest("form not valid?", mimetype="text/plain") (results,meta) = search_query(form.cleaned_data) meta['searching'] = True meta['by'] = form.cleaned_data['by'] From 3ecd539f7ba2a58c4cc912a49a0a492c84f6f623 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Wed, 2 Mar 2011 14:59:07 +0000 Subject: [PATCH 42/75] Rename derived event models to be more self-explanatory, fixed a couple of bugs - Legacy-Id: 2872 --- ietf/idrfc/idrfc_wrapper.py | 6 +-- ietf/idrfc/mails.py | 26 +++++------ ietf/idrfc/testsREDESIGN.py | 76 ++++++++++++++++--------------- ietf/idrfc/utils.py | 6 +-- ietf/idrfc/views_ballot.py | 65 +++++++++++++------------- ietf/idrfc/views_doc.py | 8 ++-- ietf/idrfc/views_edit.py | 10 ++-- redesign/doc/admin.py | 14 +++--- redesign/doc/models.py | 24 +++++----- redesign/doc/proxy.py | 32 ++++++------- redesign/import-document-state.py | 56 +++++++++++------------ 11 files changed, 162 insertions(+), 161 deletions(-) diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py index b0431d72b..4408d218f 100644 --- a/ietf/idrfc/idrfc_wrapper.py +++ b/ietf/idrfc/idrfc_wrapper.py @@ -638,8 +638,8 @@ class BallotWrapper: positions = [] seen = {} - from doc.models import BallotPosition - 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'): + from doc.models import BallotPositionEvent + for pos in BallotPositionEvent.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=pos.ad.pk, # ought to rename this in doc_ballot_list @@ -676,7 +676,7 @@ class BallotWrapper: for ad in active_ads: if ad not in seen: d = dict(ad_name=ad.get_name(), - ad_username=pos.ad.pk, + ad_username=ad.pk, position="No Record", ) positions.append(d) diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index 2d2cdc8ab..3233700f1 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -9,7 +9,7 @@ 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, BallotPosition, Expiration +from doc.models import WriteupEvent, BallotPositionEvent, LastCallEvent from person.models import Email def email_state_changed(request, doc, text): @@ -86,12 +86,12 @@ def full_intended_status(intended_status): return "a %s" % s def generate_ballot_writeup(request, doc): - e = Text() + e = WriteupEvent() 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.text = unicode(render_to_string("idrfc/ballot_writeup.txt")) e.save() return e @@ -158,12 +158,12 @@ def generate_last_call_announcementREDESIGN(request, doc): ) ) - e = Text() + e = WriteupEvent() 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.text = unicode(mail) e.save() return e @@ -253,12 +253,12 @@ def generate_approval_mailREDESIGN(request, doc): else: mail = generate_approval_mail_approved(request, doc) - e = Text() + e = WriteupEvent() 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.text = unicode(mail) e.save() return e @@ -479,7 +479,7 @@ def generate_issue_ballot_mailREDESIGN(request, doc): active_ads = Email.objects.filter(role__name="ad", role__group__state="active") e = doc.latest_event(type="started_iesg_process") - positions = BallotPosition.objects.filter(doc=doc, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad') + positions = BallotPositionEvent.objects.filter(doc=doc, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad') # format positions and setup discusses and comments ad_feedback = [] @@ -519,14 +519,14 @@ def generate_issue_ballot_mailREDESIGN(request, doc): inactive_ad_positions.sort() ad_feedback.sort(key=lambda p: p.ad.get_name()) - e = doc.latest_event(Expiration, type="sent_last_call") + e = doc.latest_event(LastCallEvent, type="sent_last_call") last_call_expires = e.expires if e else None - e = doc.latest_event(Text, type="changed_ballot_approval_text") - approval_text = e.content if e else "" + e = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") + approval_text = e.text if e else "" - e = doc.latest_event(Text, type="changed_ballot_writeup_text") - ballot_writeup = e.content if e else "" + e = doc.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + ballot_writeup = e.text if e else "" return render_to_string("idrfc/issue_ballot_mailREDESIGN.txt", dict(doc=doc, diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index bff617f09..6b7741a95 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -274,30 +274,30 @@ class ChangeStateTestCase(django.test.TestCase): self.assertContains(r, "Your request to issue the Last Call") # last call text - e = draft.latest_event(Text, type="changed_last_call_text") + e = draft.latest_event(WriteupEvent, 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) + self.assertTrue("The IESG has received" in e.text) + self.assertTrue(draft.title in e.text) + self.assertTrue(draft.get_absolute_url() in e.text) # approval text - e = draft.latest_event(Text, type="changed_ballot_approval_text") + e = draft.latest_event(WriteupEvent, 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) + self.assertTrue("The IESG has approved" in e.text) + self.assertTrue(draft.title in e.text) + self.assertTrue(draft.get_absolute_url() in e.text) # ballot writeup - e = draft.latest_event(Text, type="changed_ballot_writeup_text") + e = draft.latest_event(WriteupEvent, type="changed_ballot_writeup_text") self.assertTrue(e) - self.assertTrue("Technical Summary" in e.content) + self.assertTrue("Technical Summary" in e.text) # 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) + self.assertTrue("Last call was requested" in draft.latest_event().desc) class EditInfoTestCase(django.test.TestCase): @@ -345,7 +345,7 @@ class EditInfoTestCase(django.test.TestCase): self.assertTrue(draft.tags.filter(slug="via-rfc")) self.assertEquals(draft.ad, new_ad) self.assertEquals(draft.note, "New note") - self.assertTrue(not draft.latest_event(Telechat, type="telechat_date")) + self.assertTrue(not draft.latest_event(TelechatEvent, type="telechat_date")) self.assertEquals(draft.event_set.count(), events_before + 4) self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue(draft.name in mail_outbox[-1]['Subject']) @@ -367,14 +367,14 @@ class EditInfoTestCase(django.test.TestCase): from ietf.iesg.models import TelechatDates # add to telechat - self.assertTrue(not draft.latest_event(Telechat, "scheduled_for_telechat")) + self.assertTrue(not draft.latest_event(TelechatEvent, "scheduled_for_telechat")) data["telechat_date"] = TelechatDates.objects.all()[0].date1.isoformat() r = self.client.post(url, data) self.assertEquals(r.status_code, 302) draft = Document.objects.get(name=draft.name) - self.assertTrue(draft.latest_event(Telechat, "scheduled_for_telechat")) - self.assertEquals(draft.latest_event(Telechat, "scheduled_for_telechat").telechat_date, TelechatDates.objects.all()[0].date1) + self.assertTrue(draft.latest_event(TelechatEvent, "scheduled_for_telechat")) + self.assertEquals(draft.latest_event(TelechatEvent, "scheduled_for_telechat").telechat_date, TelechatDates.objects.all()[0].date1) # change telechat data["telechat_date"] = TelechatDates.objects.all()[0].date2.isoformat() @@ -382,7 +382,7 @@ class EditInfoTestCase(django.test.TestCase): self.assertEquals(r.status_code, 302) draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.latest_event(Telechat, "scheduled_for_telechat").telechat_date, TelechatDates.objects.all()[0].date2) + self.assertEquals(draft.latest_event(TelechatEvent, "scheduled_for_telechat").telechat_date, TelechatDates.objects.all()[0].date2) # remove from agenda data["telechat_date"] = "" @@ -390,7 +390,7 @@ class EditInfoTestCase(django.test.TestCase): self.assertEquals(r.status_code, 302) draft = Document.objects.get(name=draft.name) - self.assertTrue(not draft.latest_event(Telechat, "scheduled_for_telechat").telechat_date) + self.assertTrue(not draft.latest_event(TelechatEvent, "scheduled_for_telechat").telechat_date) def test_start_iesg_process_on_draft(self): draft = make_test_data() @@ -411,6 +411,7 @@ class EditInfoTestCase(django.test.TestCase): self.assertTrue('@' in q('form input[name=notify]')[0].get('value')) # add + events_before = draft.event_set.count() mailbox_before = len(mail_outbox) ad = Email.objects.get(address="aread@ietf.org") @@ -430,9 +431,10 @@ class EditInfoTestCase(django.test.TestCase): self.assertTrue(draft.tags.filter(slug="via-rfc")) self.assertEquals(draft.ad, ad) self.assertEquals(draft.note, "This is a note") - self.assertTrue(not draft.latest_event(Telechat, type="scheduled_for_telechat")) - self.assertEquals(draft.event_set.count(), 4) - self.assertEquals(draft.event_set.order_by('time', '-id')[0].type, "started_iesg_process") + self.assertTrue(not draft.latest_event(TelechatEvent, type="scheduled_for_telechat")) + self.assertEquals(draft.event_set.count(), events_before + 4) + events = list(draft.event_set.order_by('time', 'id')) + self.assertEquals(events[-4].type, "started_iesg_process") self.assertEquals(len(mail_outbox), mailbox_before) @@ -555,7 +557,7 @@ class EditPositionTestCase(django.test.TestCase): comment="This is a test.")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPosition, ad=ad) + pos = draft.latest_event(BallotPositionEvent, ad=ad) self.assertEquals(pos.pos.slug, "discuss") self.assertTrue("This is a discussion test." in pos.discuss) self.assertTrue(pos.discuss_time != None) @@ -569,7 +571,7 @@ class EditPositionTestCase(django.test.TestCase): r = self.client.post(url, dict(position="noobj")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPosition, ad=ad) + pos = draft.latest_event(BallotPositionEvent, ad=ad) self.assertEquals(pos.pos.slug, "noobj") self.assertEquals(draft.event_set.count(), events_before + 1) self.assertTrue("Position for" in pos.desc) @@ -579,7 +581,7 @@ class EditPositionTestCase(django.test.TestCase): r = self.client.post(url, dict(position="norecord")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPosition, ad=ad) + pos = draft.latest_event(BallotPositionEvent, ad=ad) self.assertEquals(pos.pos.slug, "norecord") self.assertEquals(draft.event_set.count(), events_before + 1) self.assertTrue("Position for" in pos.desc) @@ -589,7 +591,7 @@ class EditPositionTestCase(django.test.TestCase): r = self.client.post(url, dict(position="norecord", comment="New comment.")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPosition, ad=ad) + pos = draft.latest_event(BallotPositionEvent, ad=ad) self.assertEquals(pos.pos.slug, "norecord") self.assertEquals(draft.event_set.count(), events_before + 2) self.assertTrue("Ballot comment text updated" in pos.desc) @@ -612,7 +614,7 @@ class EditPositionTestCase(django.test.TestCase): r = self.client.post(url, dict(position="discuss")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPosition, ad=ad) + pos = draft.latest_event(BallotPositionEvent, ad=ad) self.assertEquals(pos.pos.slug, "discuss") self.assertTrue("New position" in pos.desc) self.assertTrue("by Sec" in pos.desc) @@ -624,7 +626,7 @@ class EditPositionTestCase(django.test.TestCase): ad = Email.objects.get(address="aread@ietf.org") - BallotPosition.objects.create(doc=draft, type="changed_ballot_position", + BallotPositionEvent.objects.create(doc=draft, type="changed_ballot_position", by=ad, ad=ad, pos=BallotPositionName.objects.get(slug="yes"), comment="Test!", comment_time=datetime.datetime.now()) @@ -730,7 +732,7 @@ class BallotWriteupsTestCase(django.test.TestCase): save_last_call_text="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(Text, type="changed_last_call_text").content) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupEvent, type="changed_last_call_text").text) # test regenerate r = self.client.post(url, dict( @@ -739,7 +741,7 @@ class BallotWriteupsTestCase(django.test.TestCase): self.assertEquals(r.status_code, 200) q = PyQuery(r.content) draft = Document.objects.get(name=draft.name) - self.assertTrue("Subject: Last Call" in draft.latest_event(Text, type="changed_last_call_text").content) + self.assertTrue("Subject: Last Call" in draft.latest_event(WriteupEvent, type="changed_last_call_text").text) def test_request_last_call(self): @@ -755,7 +757,7 @@ class BallotWriteupsTestCase(django.test.TestCase): # send r = self.client.post(url, dict( - last_call_text=draft.latest_event(Text, type="changed_last_call_text").content, + last_call_text=draft.latest_event(WriteupEvent, type="changed_last_call_text").text, send_last_call_request="1")) draft = Document.objects.get(name=draft.name) self.assertEquals(draft.iesg_state_id, "lc-req") @@ -781,7 +783,7 @@ class BallotWriteupsTestCase(django.test.TestCase): save_ballot_writeup="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(Text, type="changed_ballot_writeup_text").content) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupEvent, type="changed_ballot_writeup_text").text) def test_issue_ballot(self): draft = make_test_data() @@ -790,7 +792,7 @@ class BallotWriteupsTestCase(django.test.TestCase): def create_pos(num, vote, comment="", discuss=""): ad = Email.objects.get(address="ad%s@ietf.org" % num) - e = BallotPosition() + e = BallotPositionEvent() e.doc = draft e.by = ad e.ad = ad @@ -815,11 +817,11 @@ class BallotWriteupsTestCase(django.test.TestCase): create_pos(9, "yes") # we need approval text to be able to submit - e = Text() + e = WriteupEvent() e.doc = draft e.by = Email.objects.get(address="aread@ietf.org") e.type = "changed_ballot_approval_text" - e.content = "The document has been approved." + e.text = "The document has been approved." e.save() mailbox_before = len(mail_outbox) @@ -859,13 +861,13 @@ class BallotWriteupsTestCase(django.test.TestCase): save_approval_text="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(Text, type="changed_ballot_approval_text").content) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupEvent, type="changed_ballot_approval_text").text) # test regenerate r = self.client.post(url, dict(regenerate_approval_text="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("Subject: Protocol Action" in draft.latest_event(Text, type="changed_ballot_approval_text").content) + self.assertTrue("Subject: Protocol Action" in draft.latest_event(WriteupEvent, type="changed_ballot_approval_text").text) # test regenerate when it's a disapprove draft.iesg_state_id = "nopubadw" @@ -874,7 +876,7 @@ class BallotWriteupsTestCase(django.test.TestCase): r = self.client.post(url, dict(regenerate_approval_text="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("NOT be published" in draft.latest_event(Text, type="changed_ballot_approval_text").content) + self.assertTrue("NOT be published" in draft.latest_event(WriteupEvent, type="changed_ballot_approval_text").text) class ApproveBallotTestCase(django.test.TestCase): fixtures = ['names'] @@ -955,7 +957,7 @@ class MakeLastCallTestCase(django.test.TestCase): draft = Document.objects.get(name=draft.name) self.assertEquals(draft.iesg_state.slug, "lc") - self.assertEquals(draft.latest_event(Expiration, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date) + self.assertEquals(draft.latest_event(LastCallEvent, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date) self.assertEquals(len(mail_outbox), mailbox_before + 4) self.assertTrue("Last Call" in mail_outbox[-4]['Subject']) diff --git a/ietf/idrfc/utils.py b/ietf/idrfc/utils.py index c384598f1..d885f3575 100644 --- a/ietf/idrfc/utils.py +++ b/ietf/idrfc/utils.py @@ -3,7 +3,7 @@ from django.conf import settings from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin from ietf.idrfc.mails import * -from doc.models import Telechat, Event +from doc.models import Event, TelechatEvent def add_document_comment(request, doc, text, include_by=True, ballot=None): if request: @@ -114,7 +114,7 @@ def update_telechat(request, idinternal, new_telechat_date, new_returning_item=N def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_item=None): on_agenda = bool(new_telechat_date) - prev = doc.latest_event(Telechat, type="scheduled_for_telechat") + prev = doc.latest_event(TelechatEvent, type="scheduled_for_telechat") prev_returning = bool(prev and prev.returning_item) prev_telechat = prev.telechat_date if prev else None prev_agenda = bool(prev_telechat) @@ -135,7 +135,7 @@ def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_i and new_telechat_date != prev_telechat): returning = True - e = Telechat() + e = TelechatEvent() e.type = "scheduled_for_telechat" e.by = by e.doc = doc diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 101be75f5..023778ca2 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -22,7 +22,7 @@ from ietf.idrfc.mails import * from ietf.idrfc.utils import * from ietf.idrfc.lastcall import request_last_call -from doc.models import Document, Event, BallotPosition, save_document_in_history +from doc.models import Document, Event, BallotPositionEvent, LastCallEvent, save_document_in_history from name.models import BallotPositionName, IesgDocStateName BALLOT_CHOICES = (("yes", "Yes"), @@ -219,7 +219,7 @@ def edit_positionREDESIGN(request, name): raise Http404() ad = get_object_or_404(Email, pk=ad_id) - old_pos = doc.latest_event(BallotPosition, type="changed_ballot_position", ad=ad, time__gte=started_process.time) + old_pos = doc.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) if request.method == 'POST': form = EditPositionForm(request.POST) @@ -231,7 +231,7 @@ def edit_positionREDESIGN(request, name): if clean['return_to_url']: return_to_url = clean['return_to_url'] - pos = BallotPosition(doc=doc, by=login) + pos = BallotPositionEvent(doc=doc, by=login) pos.type = "changed_ballot_position" pos.ad = ad pos.pos = clean["position"] @@ -417,7 +417,7 @@ def send_ballot_commentREDESIGN(request, name): raise Http404() ad = get_object_or_404(Email, pk=ad_id) - pos = doc.latest_event(BallotPosition, type="changed_ballot_position", ad=ad, time__gte=started_process.time) + pos = doc.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) if not pos: raise Http404() @@ -704,23 +704,23 @@ def lastcalltextREDESIGN(request, name): login = request.user.get_profile().email() - existing = doc.latest_event(Text, type="changed_last_call_text") + existing = doc.latest_event(WriteupEvent, type="changed_last_call_text") if not existing: existing = generate_last_call_announcement(request, doc) - form = LastCallTextForm(initial=dict(last_call_text=existing.content)) + form = LastCallTextForm(initial=dict(last_call_text=existing.text)) if request.method == 'POST': if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST: form = LastCallTextForm(request.POST) if form.is_valid(): t = form.cleaned_data['last_call_text'] - if t != existing.content: - e = Text(doc=doc, by=login) + if t != existing.text: + e = WriteupEvent(doc=doc, by=login) e.by = login e.type = "changed_last_call_text" e.desc = "Last call announcement was changed by %s" % login.get_name() - e.content = t + e.text = t e.save() doc.time = e.time @@ -752,7 +752,7 @@ def lastcalltextREDESIGN(request, name): doc.save() # make sure form has the updated text - form = LastCallTextForm(initial=dict(last_call_text=e.content)) + form = LastCallTextForm(initial=dict(last_call_text=e.text)) can_request_last_call = doc.iesg_state.order < 27 @@ -863,38 +863,39 @@ class BallotWriteupFormREDESIGN(forms.Form): def ballot_writeupnotesREDESIGN(request, name): """Editing of ballot write-up and notes""" doc = get_object_or_404(Document, docalias__name=name) - if not doc.iesg_state: + started_process = doc.latest_event(type="started_iesg_process") + if not started_process: raise Http404() login = request.user.get_profile().email() - approval = doc.latest_event(Text, type="changed_ballot_approval_text") + approval = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") - existing = doc.latest_event(Text, type="changed_ballot_writeup_text") + existing = doc.latest_event(WriteupEvent, type="changed_ballot_writeup_text") if not existing: existing = generate_ballot_writeup(request, doc) - form = BallotWriteupForm(initial=dict(ballot_writeup=existing.content)) + form = BallotWriteupForm(initial=dict(ballot_writeup=existing.text)) if request.method == 'POST' and "save_ballot_writeup" in request.POST or "issue_ballot" in request.POST: form = BallotWriteupForm(request.POST) if form.is_valid(): t = form.cleaned_data["ballot_writeup"] - if t != existing.content: - e = Text(doc=doc, by=login) + if t != existing.text: + e = WriteupEvent(doc=doc, by=login) e.by = login e.type = "changed_ballot_writeup_text" e.desc = "Ballot writeup was changed by %s" % login.get_name() - e.content = t + e.text = t e.save() doc.time = e.time doc.save() if "issue_ballot" in request.POST and approval: - if in_group(request.user, "Area_Director") and not doc.latest_event(BallotPosition, ad=login): + if in_group(request.user, "Area_Director") and not doc.latest_event(BallotPositionEvent, ad=login, time__gte=started_process.time): # sending the ballot counts as a yes - pos = BallotPosition(doc=doc, by=login) + pos = BallotPositionEvent(doc=doc, by=login) pos.type = "changed_ballot_position" pos.ad = login pos.pos_id = "yes" @@ -1003,23 +1004,23 @@ def ballot_approvaltextREDESIGN(request, name): login = request.user.get_profile().email() - existing = doc.latest_event(Text, type="changed_ballot_approval_text") + existing = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") if not existing: existing = generate_approval_mail(request, doc) - form = ApprovalTextForm(initial=dict(approval_text=existing.content)) + form = ApprovalTextForm(initial=dict(approval_text=existing.text)) if request.method == 'POST': if "save_approval_text" in request.POST: form = ApprovalTextForm(request.POST) if form.is_valid(): t = form.cleaned_data['approval_text'] - if t != existing.content: - e = Text(doc=doc, by=login) + if t != existing.text: + e = WriteupEvent(doc=doc, by=login) e.by = login e.type = "changed_ballot_approval_text" e.desc = "Ballot approval text was changed by %s" % login.get_name() - e.content = t + e.text = t e.save() doc.time = e.time @@ -1032,7 +1033,7 @@ def ballot_approvaltextREDESIGN(request, name): doc.save() # make sure form has the updated text - form = ApprovalTextForm(initial=dict(approval_text=existing.content)) + form = ApprovalTextForm(initial=dict(approval_text=existing.text)) can_announce = doc.iesg_state.order > 19 need_intended_status = "" @@ -1132,15 +1133,15 @@ def approve_ballotREDESIGN(request, name): login = request.user.get_profile().email() - e = doc.latest_event(Text, type="changed_ballot_approval_text") + e = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") if not e: e = generate_approval_mail(request, doc) - approval_text = e.content + approval_text = e.text - e = doc.latest_event(Text, type="changed_ballot_writeup_text") + e = doc.latest_event(WriteupEvent, type="changed_ballot_writeup_text") if not e: e = generate_ballot_writeup(request, doc) - ballot_writeup = e.content + ballot_writeup = e.text if "NOT be published" in approval_text: action = "do_not_publish" @@ -1282,10 +1283,10 @@ def make_last_callREDESIGN(request, name): login = request.user.get_profile().email() - e = doc.latest_event(Text, type="changed_last_call_text") + e = doc.latest_event(WriteupEvent, type="changed_last_call_text") if not e: e = generate_last_call_announcement(request, doc) - announcement = e.content + announcement = e.text # why cut -4 off name? a better question is probably why these # tables aren't linked together properly @@ -1322,7 +1323,7 @@ def make_last_callREDESIGN(request, name): email_state_changed(request, doc, change_description) email_owner(request, doc, doc.ad, login, change_description) - e = Expiration(doc=doc, by=login) + e = LastCallEvent(doc=doc, by=login) e.type = "sent_last_call" e.desc = "Last call sent by %s" % login.get_name() if form.cleaned_data['last_call_sent_date'] != e.time.date(): diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index 741adb4fe..4aa019825 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -155,10 +155,10 @@ def _get_history(doc, versions): 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) + filename = u"%s-%s" % (e.doc.name, e.newrevisionevent.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) + if int(e.newrevisionevent.rev) != 0: + e.desc += ' (diff from -%02d)' % (filename, int(e.newrevisionevent.rev) - 1) info["dontmolest"] = True multiset_ballot_text = "This was part of a ballot set with: " @@ -181,7 +181,7 @@ def _get_history(doc, versions): for o in results: e = o["comment"] if e.type == "new_revision": - e.version = e.newrevision.rev + e.version = e.newrevisionevent.rev else: e.version = prev_rev prev_rev = e.version diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index 50dd88f3b..c892f097c 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -22,7 +22,7 @@ from ietf.idrfc.mails import * from ietf.idrfc.utils import * from ietf.idrfc.lastcall import request_last_call -from doc.models import Document, Event, Status, Telechat, save_document_in_history, DocHistory +from doc.models import Document, Event, StatusDateEvent, TelechatEvent, save_document_in_history, DocHistory from name.models import IesgDocStateName, IntendedStdLevelName, DocInfoTagName, get_next_iesg_states, DocStateName class ChangeStateForm(forms.Form): @@ -463,7 +463,7 @@ def edit_infoREDESIGN(request, name): doc.iesg_state = IesgDocStateName.objects.get(slug="pub-req") doc.notify = get_initial_notify(doc) - e = doc.latest_event(Telechat, type="scheduled_for_telechat") + e = doc.latest_event(TelechatEvent, type="scheduled_for_telechat") initial_telechat_date = e.telechat_date if e else None initial_returning_item = bool(e and e.returning_item) @@ -541,10 +541,10 @@ def edit_infoREDESIGN(request, name): update_telechat(request, doc, login, r['telechat_date'], r['returning_item']) - e = doc.latest_event(Status, type="changed_status_date") + e = doc.latest_event(StatusDateEvent, type="changed_status_date") status_date = e.date if e else None if r["status_date"] != status_date: - e = Status(doc=doc, by=login) + e = StatusDateEvent(doc=doc, by=login) e.type ="changed_status_date" d = desc("Status date", r["status_date"], status_date) changes.append(d) @@ -567,7 +567,7 @@ def edit_infoREDESIGN(request, name): doc.save() return HttpResponseRedirect(doc.get_absolute_url()) else: - e = doc.latest_event(Status) + e = doc.latest_event(StatusDateEvent) status = e.date if e else None init = dict(intended_std_level=doc.intended_std_level, status_date=status, diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py index 32e705c16..184d942b7 100644 --- a/redesign/doc/admin.py +++ b/redesign/doc/admin.py @@ -39,14 +39,14 @@ class EventAdmin(admin.ModelAdmin): admin.site.register(Event, EventAdmin) admin.site.register(Message, EventAdmin) -admin.site.register(Text, EventAdmin) -admin.site.register(NewRevision, EventAdmin) -admin.site.register(Status, EventAdmin) -admin.site.register(Expiration, EventAdmin) -admin.site.register(Telechat, EventAdmin) +admin.site.register(NewRevisionEvent, EventAdmin) +admin.site.register(WriteupEvent, EventAdmin) +admin.site.register(StatusDateEvent, EventAdmin) +admin.site.register(LastCallEvent, EventAdmin) +admin.site.register(TelechatEvent, EventAdmin) -class BallotPositionAdmin(EventAdmin): +class BallotPositionEventAdmin(EventAdmin): raw_id_fields = ["doc", "by", "ad"] -admin.site.register(BallotPosition, BallotPositionAdmin) +admin.site.register(BallotPositionEvent, BallotPositionEventAdmin) diff --git a/redesign/doc/models.py b/redesign/doc/models.py index bb19228f1..822e66570 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -45,8 +45,8 @@ class DocumentInfo(models.Model): def latest_event(self, *args, **filter_args): """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.""" + while d.latest_event(WriteupEvent, type="xyz") returns a + WriteupEvent event.""" model = args[0] if args else Event e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time', '-id')[:1] return e[0] if e else None @@ -231,7 +231,7 @@ EVENT_TYPES = [ ] class Event(models.Model): - """An occurrence in connection with a document.""" + """An occurrence for a document, used for tracking who, when and what.""" 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) # FIXME: make NOT NULL? @@ -248,14 +248,11 @@ class Message(Event): subj = models.CharField(max_length=255) body = models.TextField() -class Text(Event): - content = models.TextField(blank=True) - -class NewRevision(Event): +class NewRevisionEvent(Event): rev = models.CharField(max_length=16) # IESG events -class BallotPosition(Event): +class BallotPositionEvent(Event): ad = models.ForeignKey(Email) pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord") discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True) @@ -263,15 +260,16 @@ 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): +class WriteupEvent(Event): + text = models.TextField(blank=True) + +class StatusDateEvent(Event): date = models.DateField(blank=True, null=True) -class Expiration(Event): +class LastCallEvent(Event): expires = models.DateTimeField(blank=True, null=True) -class Telechat(Event): +class TelechatEvent(Event): 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 501f57c50..bf069a873 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -1,4 +1,4 @@ -from models import * +from redesign.doc.models import * from redesign.person.models import Email from redesign.proxy_utils import TranslatingManager @@ -71,7 +71,7 @@ class InternetDraft(Document): #start_date = models.DateField() @property def start_date(self): - e = NewRevision.objects.filter(doc=self).order_by("time")[:1] + e = NewRevisionEvent.objects.filter(doc=self).order_by("time")[:1] return e[0].time.date() if e else None #expiration_date = models.DateField() @property @@ -106,7 +106,7 @@ class InternetDraft(Document): #lc_expiration_date = models.DateField(null=True, blank=True) @property def lc_expiration_date(self): - e = self.latest_event(Expiration, type="sent_last_call") + e = self.latest_event(LastCallEvent, type="sent_last_call") return e.expires if e else None #b_sent_date = models.DateField(null=True, blank=True) @@ -312,7 +312,7 @@ class InternetDraft(Document): #status_date = models.DateField(blank=True,null=True) @property def status_date(self): - e = self.latest_event(Status, type="changed_status_date") + e = self.latest_event(StatusDateEvent, type="changed_status_date") return e.date if e else None #email_display = models.CharField(blank=True, max_length=50) # unused @@ -391,14 +391,14 @@ class InternetDraft(Document): #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 + e = self.latest_event(TelechatEvent, type="scheduled_for_telechat") + return e.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 + e = self.latest_event(TelechatEvent, type="scheduled_for_telechat") + return e.telechat_date if e else None #via_rfc_editor = models.IntegerField(null=True, blank=True) @property @@ -515,20 +515,20 @@ class InternetDraft(Document): #approval_text = models.TextField(blank=True) @property def approval_text(self): - e = self.latest_event(Text, type="changed_ballot_approval_text") - return e.content if e else "" + e = self.latest_event(WriteupEvent, type="changed_ballot_approval_text") + return e.text if e else "" #last_call_text = models.TextField(blank=True) @property def last_call_text(self): - e = self.latest_event(Text, type="changed_last_call_text") - return e.content if e else "" + e = self.latest_event(WriteupEvent, type="changed_last_call_text") + return e.text if e else "" #ballot_writeup = models.TextField(blank=True) @property def ballot_writeup(self): - e = self.latest_event(Text, type="changed_ballot_writeup_text") - return e.content if e else "" + e = self.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + return e.text if e else "" #ballot_issued = models.IntegerField(null=True, blank=True) @property @@ -548,7 +548,7 @@ class InternetDraft(Document): res.append(dict(ad=IESGLoginProxy(ad), pos=Position(pos) if pos else None)) found = set() - for pos in BallotPosition.objects.filter(doc=self, type="changed_ballot_position", ad__in=active_ads).select_related('ad').order_by("-time", "-id"): + for pos in BallotPositionEvent.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) @@ -753,7 +753,7 @@ class DocumentComment(Event): proxy = True -class Position(BallotPosition): +class Position(BallotPositionEvent): def __init__(self, base): for f in base._meta.fields: if not f.name in ('discuss',): # don't overwrite properties diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py index 2929ab27b..42fbd824b 100755 --- a/redesign/import-document-state.py +++ b/redesign/import-document-state.py @@ -272,7 +272,7 @@ def import_from_idinternal(d, idinternal): # telechat agenda schedulings match = re_telechat_agenda.search(c.comment_text) or re_telechat_changed.search(c.comment_text) if match: - e = Telechat() + e = TelechatEvent() 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 @@ -289,10 +289,10 @@ def import_from_idinternal(d, idinternal): handled = True ad = iesg_login_to_email(c.created_by) - last_pos = d.latest_event(BallotPosition, type="changed_ballot_position", ad=ad) + last_pos = d.latest_event(BallotPositionEvent, 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 = BallotPositionEvent() 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() @@ -354,10 +354,10 @@ def import_from_idinternal(d, idinternal): print "BALLOT BY SECRETARIAT", login - e = BallotPosition() + e = BallotPositionEvent() e.type = "changed_ballot_position" e.ad = iesg_login_to_email(login) - last_pos = d.latest_event(BallotPosition, type="changed_ballot_position", ad=e.ad) + last_pos = d.latest_event(BallotPositionEvent, 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 @@ -372,10 +372,10 @@ def import_from_idinternal(d, idinternal): # ballot discusses/comments if c.ballot in (DocumentComment.BALLOT_DISCUSS, DocumentComment.BALLOT_COMMENT): - e = BallotPosition() + e = BallotPositionEvent() e.type = "changed_ballot_position" e.ad = iesg_login_to_email(c.created_by) - last_pos = d.latest_event(BallotPosition, type="changed_ballot_position", ad=e.ad) + last_pos = d.latest_event(BallotPositionEvent, 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: @@ -443,7 +443,7 @@ def import_from_idinternal(d, idinternal): # new version if c.comment_text == "New version available": - e = NewRevision(type="new_revision", rev=c.version) + e = NewRevisionEvent(type="new_revision", rev=c.version) save_event(d, e, c) handled = True @@ -492,7 +492,7 @@ def import_from_idinternal(d, idinternal): # status date changed match = re_status_date_changed.search(line) if match: - e = Status(type="changed_status_date", date=date_in_match(match)) + e = StatusDateEvent(type="changed_status_date", date=date_in_match(match)) e.desc = line save_event(d, e, c) handled = True @@ -581,25 +581,25 @@ def import_from_idinternal(d, idinternal): made_up_date = d.time made_up_date += datetime.timedelta(seconds=1) - e = d.latest_event(Status, type="changed_status_date") + e = d.latest_event(StatusDateEvent, 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 = StatusDateEvent(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") + e = d.latest_event(TelechatEvent, 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)) + e = TelechatEvent(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 @@ -625,7 +625,7 @@ 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", '-id') + existing = BallotPositionEvent.objects.filter(doc=d, type="changed_ballot_position").order_by("-time", '-id') for p in Position.objects.filter(ballot=ballot): # there are some bogus ones @@ -653,13 +653,13 @@ def import_from_idinternal(d, idinternal): break if not found: - e = BallotPosition() + e = BallotPositionEvent() 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) + last_pos = d.latest_event(BallotPositionEvent, 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 @@ -692,7 +692,7 @@ 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", '-id')) + positions = list(BallotPositionEvent.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: @@ -722,24 +722,24 @@ def import_from_idinternal(d, idinternal): 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, _ = WriteupEvent.objects.get_or_create(type="changed_ballot_approval_text", doc=d) + e.text = 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, _ = WriteupEvent.objects.get_or_create(type="changed_last_call_text", doc=d) + e.text = 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, _ = WriteupEvent.objects.get_or_create(type="changed_ballot_writeup_text", doc=d) + e.text = idinternal.ballot.ballot_writeup e.time = text_date e.by = system_email e.desc = "Ballot writeup text was added" @@ -836,13 +836,13 @@ for index, o in enumerate(all_drafts.iterator()): import_from_idinternal(d, o.idinternal) # 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')) + known_revisions = set(e.rev for e in NewRevisionEvent.objects.filter(doc=d, type="new_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 = NewRevisionEvent(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 @@ -871,7 +871,7 @@ for index, o in enumerate(all_drafts.iterator()): e.save() if o.lc_expiration_date: - e = Expiration(type="sent_last_call", expires=o.lc_expiration_date) + e = LastCallEvent(type="sent_last_call", expires=o.lc_expiration_date) # 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 From bb2e2b10c5db4169e05e1d9257689eddb76aad20 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 4 Mar 2011 19:07:57 +0000 Subject: [PATCH 43/75] Ported last call and expire scripts with tests to new schema - Legacy-Id: 2878 --- ietf/bin/expire-ids | 2 +- ietf/bin/expire-last-calls | 2 +- ietf/idrfc/expire.py | 128 ++++++++++- ietf/idrfc/fixtures/names.xml | 214 +++++++++++++------ ietf/idrfc/lastcall.py | 37 +++- ietf/idrfc/mails.py | 16 ++ ietf/idrfc/testsREDESIGN.py | 186 ++++++++-------- ietf/settings.py | 2 +- ietf/templates/idrfc/expire_textREDESIGN.txt | 7 + ietf/templates/idrfc/id_expired_email.txt | 2 +- redesign/doc/models.py | 1 - redesign/doc/proxy.py | 3 +- 12 files changed, 445 insertions(+), 155 deletions(-) create mode 100644 ietf/templates/idrfc/expire_textREDESIGN.txt diff --git a/ietf/bin/expire-ids b/ietf/bin/expire-ids index 0096b844b..c95b46365 100755 --- a/ietf/bin/expire-ids +++ b/ietf/bin/expire-ids @@ -15,6 +15,6 @@ if not in_id_expire_freeze(): for doc in get_expired_ids(): send_expire_notice_for_id(doc) expire_id(doc) - syslog.syslog("Expired %s (id=%s)%s" % (doc.file_tag(), doc.id_document_tag, " in the ID Tracker" if doc.idinternal else "")) + syslog.syslog("Expired %s (id=%s)%s" % (doc.file_tag(), doc.pk, " in the ID Tracker" if doc.latest_event(type="started_iesg_process") else "")) clean_up_id_files() diff --git a/ietf/bin/expire-last-calls b/ietf/bin/expire-last-calls index e854f40ac..57b637084 100755 --- a/ietf/bin/expire-last-calls +++ b/ietf/bin/expire-last-calls @@ -14,4 +14,4 @@ from ietf.idrfc.lastcall import * drafts = get_expired_last_calls() for doc in drafts: expire_last_call(doc) - syslog.syslog("Expired last call for %s (id=%s)" % (doc.file_tag(), doc.id_document_tag)) + syslog.syslog("Expired last call for %s (id=%s)" % (doc.file_tag(), doc.pk)) diff --git a/ietf/idrfc/expire.py b/ietf/idrfc/expire.py index a98b484bd..e8b6dbfa8 100644 --- a/ietf/idrfc/expire.py +++ b/ietf/idrfc/expire.py @@ -9,6 +9,11 @@ import datetime, os, shutil, glob, re from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment from ietf.utils.mail import send_mail from ietf.idrfc.utils import log_state_changed, add_document_comment +from doc.models import Document, Event, save_document_in_history +from name.models import IesgDocStateName, DocStateName, DocInfoTagName +from person.models import Email + +INTERNET_DRAFT_DAYS_TO_EXPIRE = 185 def in_id_expire_freeze(when=None): if when == None: @@ -32,6 +37,15 @@ def get_expired_ids(): review_by_rfc_editor=0).filter( Q(idinternal=None) | Q(idinternal__cur_state__document_state_id__gte=42)) +def get_expired_idsREDESIGN(): + cut_off = datetime.date.today() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE) + + docs = Document.objects.filter(state="active").exclude(tags="rfc-rev").filter(Q(iesg_state=None) | Q(iesg_state__order__gte=42)) + for d in docs: + e = d.latest_event(type="new_revision") + if e and e.time.date() <= cut_off: + yield d + def send_expire_notice_for_id(doc): doc.dunn_sent_date = datetime.date.today() doc.save() @@ -45,7 +59,24 @@ def send_expire_notice_for_id(doc): "I-D Expiring System ", u"I-D was expired %s" % doc.file_tag(), "idrfc/id_expired_email.txt", - dict(doc=doc)) + dict(doc=doc, + state=doc.idstate())) + +def send_expire_notice_for_idREDESIGN(doc): + if not doc.ad: + return + + state = doc.iesg_state.name if doc.iesg_state else "I-D Exists" + + request = None + to = doc.ad.formatted_email() + send_mail(request, to, + "I-D Expiring System ", + u"I-D was expired %s" % doc.file_tag(), + "idrfc/id_expired_email.txt", + dict(doc=doc, + state=state, + )) def expire_id(doc): def move_file(f): @@ -82,6 +113,52 @@ def expire_id(doc): add_document_comment(None, doc, "Document is expired by system") +def expire_idREDESIGN(doc): + system_email = Email.objects.get(address="(System)") + + # clean up files + def move_file(f): + src = os.path.join(settings.INTERNET_DRAFT_PATH, f) + dst = os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, f) + + if os.path.exists(src): + shutil.move(src, dst) + + file_types = ['txt', 'ps', 'pdf'] + for t in file_types: + move_file("%s-%s.%s" % (doc.name, doc.rev, t)) + + # make tombstone + new_revision = "%02d" % (int(doc.rev) + 1) + + new_file = open(os.path.join(settings.INTERNET_DRAFT_PATH, "%s-%s.txt" % (doc.name, new_revision)), 'w') + txt = render_to_string("idrfc/expire_textREDESIGN.txt", + dict(doc=doc, + authors=[(e.get_name(), e.address) for e in doc.authors.all()], + expire_days=InternetDraft.DAYS_TO_EXPIRE)) + new_file.write(txt) + new_file.close() + + # now change the states + + save_document_in_history(doc) + if doc.latest_event(type='started_iesg_process'): + dead_state = IesgDocStateName.objects.get(slug="dead") + if doc.iesg_state != dead_state: + prev = doc.iesg_state + doc.iesg_state = dead_state + log_state_changed(None, doc, system_email, prev) + + e = Event(doc=doc, by=system_email) + e.type = "expired_document" + e.desc = "Document has expired" + e.save() + + doc.rev = new_revision # FIXME: incrementing the revision like this is messed up + doc.state = DocStateName.objects.get(slug="expired") + doc.time = datetime.datetime.now() + doc.save() + def clean_up_id_files(): """Move unidentified and old files out of the Internet Draft directory.""" cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE) @@ -120,3 +197,52 @@ def clean_up_id_files(): except InternetDraft.DoesNotExist: move_file_to("unknown_ids") + +def clean_up_id_filesREDESIGN(): + """Move unidentified and old files out of the Internet Draft directory.""" + cut_off = datetime.date.today() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE) + + pattern = os.path.join(settings.INTERNET_DRAFT_PATH, "draft-*.*") + files = [] + filename_re = re.compile('^(.*)-(\d+)$') + for path in glob.glob(pattern): + basename = os.path.basename(path) + stem, ext = os.path.splitext(basename) + match = filename_re.search(stem) + if not match: + filename, revision = ("UNKNOWN", "00") + else: + filename, revision = match.groups() + + def move_file_to(subdir): + shutil.move(path, + os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, subdir, basename)) + + try: + doc = Document.objects.get(name=filename, rev=revision) + + if doc.state_id == "rfc": + if ext != ".txt": + move_file_to("unknown_ids") + elif doc.state_id in ("expired", "auth-rm", "repl", "ietf-rm"): + e = doc.latest_event(type__in=('expired_document', 'new_revision', "completed_resurrect")) + expiration_date = e.time.date() if e and e.type == "expired_document" else None + + if expiration_date and expiration_date < cut_off: + if os.path.getsize(path) < 1500: + move_file_to("deleted_tombstones") + # revert version after having deleted tombstone + doc.rev = "%02d" % (int(revision) - 1) # FIXME: messed up + doc.save() + doc.tags.add(DocInfoTagName.objects.get(slug='exp-tomb')) + else: + move_file_to("expired_without_tombstone") + + except Document.DoesNotExist: + move_file_to("unknown_ids") + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + get_expired_ids = get_expired_idsREDESIGN + send_expire_notice_for_id = send_expire_notice_for_idREDESIGN + expire_id = expire_idREDESIGN + clean_up_id_files = clean_up_id_filesREDESIGN diff --git a/ietf/idrfc/fixtures/names.xml b/ietf/idrfc/fixtures/names.xml index 6cc9e577d..39838b7ee 100644 --- a/ietf/idrfc/fixtures/names.xml +++ b/ietf/idrfc/fixtures/names.xml @@ -4,46 +4,55 @@ Yes 1 + 0
No Objection 1 + 0 Abstain 1 + 0 Discuss 1 + 0 Recuse 1 + 0 No record 1 + 0 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. 1 + 0 Revised ID Needed An updated ID is needed to address the issues that have been raised. 1 + 0 IANA-coord 1 + 0 AD Followup @@ -55,365 +64,444 @@ - 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. 1 + 0 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. 1 + 0 MissingRef 1 + 0 FastTrack 1 + 0 Review by RFC Editor 1 + 0 Via RFC Editor 1 + 0 Expired tombstone 1 + 0 Approved in minute 1 + 0 Has errata 1 + 0 Updates 1 + 0 Replaces 1 + 0 Obsoletes 1 + 0 Reviews 1 + 0 References 1 + 0 RFC 1 + 0 Expired 1 + 0 Replaced 1 + 0 Active 1 + 0 Withdrawn by Submitter 1 + 0 Withdrawn by IETF 1 + 0 IETF 1 + 0 Independent Submission 1 + 0 Legacy 1 + 0 IAB 1 + 0 IRTF 1 + 0 Draft 1 + 0 External 1 + 0 BOF 1 + 0 Proposed 1 + 0 Active 1 + 0 Dormant 1 + 0 Concluded 1 + 0 Unknown 1 + 0 IETF 1 + 0 Area 1 + 0 WG 1 + 0 RG 1 + 0 Team 1 + 0 Individual 1 - - - RFC Published - The ID has been published as an RFC. - 1 - - - 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.) - 1 - - - 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. - 1 - - - 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). - 1 - - - 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. - 1 - - - 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. - 1 - - - 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. - 1 + 0 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. 1 + 10 - - RFC Ed Queue - The document is in the RFC editor Queue (as confirmed by http://www.rfc-editor.org/queue.html). - 1 - - - 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. - 1 - - - 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. - 1 - - - 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. - 1 - - - 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. + + 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. 1 + 11 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. 1 - - - 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. - 1 + 12 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. 1 + 15 + + + 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. + 1 + 16 + + + 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. + 1 + 18 + + + 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. + 1 + 19 + + + 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. + 1 + 20 + + + 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. + 1 + 21 + + + 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. + 1 + 27 + + + 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. + 1 + 30 + + + RFC Ed Queue + The document is in the RFC editor Queue (as confirmed by http://www.rfc-editor.org/queue.html). + 1 + 31 + + + RFC Published + The ID has been published as an RFC. + 1 + 32 + + + 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. + 1 + 33 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. 1 + 34 + + + 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). + 1 + 42 + + + 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.) + 1 + 99 Best Current Practice 1 + 0 Draft Standard 1 + 0 Experimental 1 + 0 Historic 1 + 0 Informational 1 + 0 Proposed Standard 1 + 0 Standard 1 + 0 Area Director 1 + 0 Ex-Area Director In-active Area Director 1 + 0 + + + Working Group Editor + + 1 + 0 Standard 1 + 0 Draft Standard 1 + 0 Proposed Standard 1 + 0 Informational 1 + 0 Experimental 1 + 0 Best Current Practice 1 + 0 Historic 1 + 0 Unknown 1 + 0 \ No newline at end of file diff --git a/ietf/idrfc/lastcall.py b/ietf/idrfc/lastcall.py index 63e937e43..880b001af 100644 --- a/ietf/idrfc/lastcall.py +++ b/ietf/idrfc/lastcall.py @@ -7,7 +7,10 @@ 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 + +from doc.models import Document, Event, LastCallEvent, WriteupEvent, save_document_in_history +from name.models import IesgDocStateName +from person.models import Email def request_last_call(request, doc): try: @@ -42,6 +45,13 @@ 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) +def get_expired_last_callsREDESIGN(): + today = datetime.date.today() + for d in Document.objects.filter(iesg_state="lc"): + e = d.latest_event(LastCallEvent, type="sent_last_call") + if e and e.expires.date() <= today: + yield d + def expire_last_call(doc): state = IDState.WAITING_FOR_WRITEUP @@ -59,3 +69,28 @@ def expire_last_call(doc): log_state_changed(None, doc, by="system", email_watch_list=False) email_last_call_expired(doc) + +def expire_last_callREDESIGN(doc): + state = IesgDocStateName.objects.get(slug="writeupw") + + e = doc.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + if e and "What does this protocol do and why" not in e.text: + # if it boiler-plate text has been removed, we assume the + # write-up has been written + state = IesgDocStateName.objects.get(slug="goaheadw") + + save_document_in_history(doc) + + prev = doc.iesg_state + doc.iesg_state = state + e = log_state_changed(None, doc, Email.objects.get(address="(System)"), prev) + + doc.time = e.time + doc.save() + + email_last_call_expired(doc) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + get_expired_last_calls = get_expired_last_callsREDESIGN + expire_last_call = expire_last_callREDESIGN + diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index 3233700f1..799d7dea3 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -593,3 +593,19 @@ def email_last_call_expired(doc): url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()), cc="iesg-secretary@ietf.org") +def email_last_call_expiredREDESIGN(doc): + text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.iesg_state.name + + send_mail(None, + "iesg@ietf.org", + "DraftTracker Mail System ", + "Last Call Expired: %s" % doc.file_tag(), + "idrfc/change_notice.txt", + dict(text=text, + doc=doc, + url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()), + cc="iesg-secretary@ietf.org") + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + email_last_call_expired = email_last_call_expiredREDESIGN + diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index 6b7741a95..d8831a700 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -42,7 +42,7 @@ from django.conf import settings from pyquery import PyQuery #from ietf.idrfc.models import * -from ietf.idtracker.models import IESGLogin, PersonOrOrgInfo, EmailAddress +from ietf.idtracker.models import IESGLogin, PersonOrOrgInfo, EmailAddress, IDDates from doc.models import * from name.models import * from group.models import * @@ -73,6 +73,8 @@ def make_test_data(): ) # persons + Email.objects.get_or_create(address="(System)") + p = Person.objects.create( name="Aread Irector", ascii="Aread Irector", @@ -163,7 +165,7 @@ def make_test_data(): # draft draft = Document.objects.create( - name="ietf-test", + name="draft-ietf-test", time=datetime.datetime.now(), type_id="draft", title="Optimizing Martian Network Topologies", @@ -193,14 +195,15 @@ def make_test_data(): desc="Added draft", ) + # telechat dates t = datetime.date.today() dates = TelechatDates(date1=t, date2=t + datetime.timedelta(days=7), date3=t + datetime.timedelta(days=14), date4=t + datetime.timedelta(days=21), ) - super(dates.__class__, dates).save(force_insert=True) - + super(dates.__class__, dates).save(force_insert=True) # work-around hard-coded save block + return draft class ChangeStateTestCase(django.test.TestCase): @@ -364,8 +367,6 @@ class EditInfoTestCase(django.test.TestCase): note="", ) - from ietf.iesg.models import TelechatDates - # add to telechat self.assertTrue(not draft.latest_event(TelechatEvent, "scheduled_for_telechat")) data["telechat_date"] = TelechatDates.objects.all()[0].date1.isoformat() @@ -965,7 +966,7 @@ class MakeLastCallTestCase(django.test.TestCase): self.assertTrue("Last Call" in mail_outbox[-3]['Subject']) class ExpireIDsTestCase(django.test.TestCase): - fixtures = ['base', 'draft'] + fixtures = ['names'] def setUp(self): self.id_dir = os.path.abspath("tmp-id-dir") @@ -991,6 +992,10 @@ class ExpireIDsTestCase(django.test.TestCase): def test_in_id_expire_freeze(self): from ietf.idrfc.expire import in_id_expire_freeze + # dummy id dates + IDDates.objects.create(id=IDDates.SECOND_CUT_OFF, date=datetime.date(2010, 7, 12), description="", f_name="") + IDDates.objects.create(id=IDDates.IETF_MONDAY, date=datetime.date(2010, 7, 26), description="", f_name="") + 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))) @@ -998,57 +1003,62 @@ class ExpireIDsTestCase(django.test.TestCase): 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 + from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id, INTERNET_DRAFT_DAYS_TO_EXPIRE + draft = make_test_data() + + self.assertEquals(len(list(get_expired_ids())), 0) + # 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.iesg_state = None 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) + NewRevisionEvent.objects.create( + type="new_revision", + by=Email.objects.get(address="aread@ietf.org"), + doc=draft, + desc="New revision", + time=datetime.datetime.now() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE + 1), + rev="01" + ) + + self.assertEquals(len(list(get_expired_ids())), 1) + + draft.iesg_state = IesgDocStateName.objects.get(slug="watching") draft.save() + + self.assertEquals(len(list(get_expired_ids())), 1) - # test query - documents = get_expired_ids() - self.assertEquals(len(documents), 2) + # test notice + mailbox_before = len(mail_outbox) - for d in documents: - # test notice - mailbox_before = len(mail_outbox) + send_expire_notice_for_id(draft) - send_expire_notice_for_id(d) + self.assertEquals(len(mail_outbox), mailbox_before + 1) + self.assertTrue("expired" in mail_outbox[-1]["Subject"]) - 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" % (draft.name, draft.rev) + self.write_id_file(txt, 5000) - # test expiry - txt = "%s-%s.txt" % (d.filename, d.revision_display()) - self.write_id_file(txt, 5000) + revision_before = draft.rev - revision_before = d.revision - - expire_id(d) + expire_id(draft) - 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.name, draft.revision) - self.assertTrue(os.path.exists(os.path.join(self.id_dir, new_txt))) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.state_id, "expired") + self.assertEquals(int(draft.rev), int(revision_before) + 1) + self.assertEquals(draft.iesg_state_id, "dead") + self.assertTrue(draft.latest_event(type="expired_document")) + self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) + self.assertTrue(os.path.exists(os.path.join(self.archive_dir, txt))) + new_txt = "%s-%s.txt" % (draft.name, draft.rev) + 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 + draft = make_test_data() + + from ietf.idrfc.expire import clean_up_id_files, INTERNET_DRAFT_DAYS_TO_EXPIRE # put unknown file unknown = "draft-i-am-unknown-01.txt" @@ -1061,7 +1071,7 @@ class ExpireIDsTestCase(django.test.TestCase): # put file with malformed name (no revision) - malformed = "draft-ietf-mipshop-pfmipv6.txt" + malformed = draft.name + ".txt" self.write_id_file(malformed, 5000) clean_up_id_files() @@ -1071,13 +1081,12 @@ class ExpireIDsTestCase(django.test.TestCase): # RFC draft - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") - draft.status_id = 3 + draft.state = DocStateName.objects.get(slug="rfc") draft.save() - txt = "%s-%s.txt" % (draft.name, draft.revision) + txt = "%s-%s.txt" % (draft.name, draft.rev) self.write_id_file(txt, 5000) - pdf = "%s-%s.pdf" % (draft.name, draft.revision) + pdf = "%s-%s.pdf" % (draft.name, draft.rev) self.write_id_file(pdf, 5000) clean_up_id_files() @@ -1089,13 +1098,20 @@ class ExpireIDsTestCase(django.test.TestCase): 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) + # expire draft + draft.state = DocStateName.objects.get(slug="expired") draft.save() - txt = "%s-%s.txt" % (draft.name, draft.revision) + e = Event() + e.doc = draft + e.by = Email.objects.get(address="(System)") + e.type = "expired_document" + e.text = "Document has expired" + e.time = datetime.date.today() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE + 1) + e.save() + + # expired without tombstone + txt = "%s-%s.txt" % (draft.name, draft.rev) self.write_id_file(txt, 5000) clean_up_id_files() @@ -1105,62 +1121,64 @@ class ExpireIDsTestCase(django.test.TestCase): # 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.rev - revision_before = draft.revision - - txt = "%s-%s.txt" % (draft.name, draft.revision) - self.write_id_file(txt, 1000) + txt = "%s-%s.txt" % (draft.name, draft.rev) + self.write_id_file(txt, 1000) # < 1500 means tombstone clean_up_id_files() self.assertTrue(not os.path.exists(os.path.join(self.id_dir, txt))) self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "deleted_tombstones", txt))) - draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6") - self.assertEquals(int(draft.revision), int(revision_before) - 1) - self.assertTrue(draft.expired_tombstone) + draft = Document.objects.get(name=draft.name) + self.assertEquals(int(draft.rev), int(revision_before) - 1) + self.assertTrue(draft.tags.filter(slug="exp-tomb")) class ExpireLastCallTestCase(django.test.TestCase): - fixtures = ['base', 'draft'] + fixtures = ['names'] 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 + # check that non-expirable 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 = make_test_data() + draft.iesg_state_id = "lc" draft.save() - self.assertEquals(len(get_expired_last_calls()), 0) + self.assertEquals(len(list(get_expired_last_calls())), 0) + + e = LastCallEvent() + e.doc = draft + e.by = Email.objects.get(address="sec.retary@ietf.org") + e.type = "sent_last_call" + e.text = "Last call sent" + e.expires = datetime.datetime.now() + datetime.timedelta(days=14) + e.save() + + self.assertEquals(len(list(get_expired_last_calls())), 0) # test expired - draft.lc_expiration_date = datetime.date.today() - draft.save() + e = LastCallEvent() + e.doc = draft + e.by = Email.objects.get(address="sec.retary@ietf.org") + e.type = "sent_last_call" + e.text = "Last call sent" + e.expires = datetime.datetime.now() + e.save() - drafts = get_expired_last_calls() + drafts = list(get_expired_last_calls()) self.assertEquals(len(drafts), 1) + # expire it mailbox_before = len(mail_outbox) events_before = draft.event_set.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) + draft = Document.objects.get(name=draft.name) + self.assertEquals(draft.iesg_state.slug, "writeupw") self.assertEquals(draft.event_set.count(), events_before + 1) self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue("Last Call Expired" in mail_outbox[-1]["Subject"]) diff --git a/ietf/settings.py b/ietf/settings.py index f4c4027fd..110773850 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -196,7 +196,7 @@ LIAISON_ATTACH_PATH = '/a/www/ietf-datatracker/documents/LIAISON/' LIAISON_ATTACH_URL = '/documents/LIAISON/' # DB redesign -USE_DB_REDESIGN_PROXY_CLASSES=True +USE_DB_REDESIGN_PROXY_CLASSES = True # Put SECRET_KEY in here, or any other sensitive or site-specific # changes. DO NOT commit settings_local.py to svn. diff --git a/ietf/templates/idrfc/expire_textREDESIGN.txt b/ietf/templates/idrfc/expire_textREDESIGN.txt new file mode 100644 index 000000000..c14c7b5f6 --- /dev/null +++ b/ietf/templates/idrfc/expire_textREDESIGN.txt @@ -0,0 +1,7 @@ +{% filter wordwrap:73 %}This Internet-Draft, {{ doc.name }}-{{ doc.rev }}.txt, has expired, and has been deleted from the Internet-Drafts directory. An Internet-Draft expires {{ expire_days }} days from the date that it is posted unless it is replaced by an updated version, or the Secretariat has been notified that the document is under official review by the IESG or has been passed to the RFC Editor for review and/or publication as an RFC. This Internet-Draft was not published as an RFC. + +Internet-Drafts are not archival documents, and copies of Internet-Drafts that have been deleted from the directory are not available. The Secretariat does not have any information regarding the future plans of the author{{ authors|pluralize}} or working group, if applicable, with respect to this deleted Internet-Draft. For more information, or to request a copy of the document, please contact the author{{ authors|pluralize}} directly.{% endfilter %} + +Draft Author{{ authors|pluralize}}: +{% for name, email in authors %}{{ name }}<{{ email }}> +{% endfor %} diff --git a/ietf/templates/idrfc/id_expired_email.txt b/ietf/templates/idrfc/id_expired_email.txt index 5ea37273c..5eda3f0ac 100644 --- a/ietf/templates/idrfc/id_expired_email.txt +++ b/ietf/templates/idrfc/id_expired_email.txt @@ -1,5 +1,5 @@ {{ doc.file_tag|safe }} was just expired. -This draft is in the state {{ doc.idstate }} in ID Tracker. +This draft is in the state "{{ state }}" in the ID Tracker. Thanks, diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 822e66570..06f4eb349 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -201,7 +201,6 @@ EVENT_TYPES = [ # misc document events ("added_comment", "Added comment"), - ("added_tombstone", "Added tombstone"), ("expired_document", "Expired document"), ("requested_resurrect", "Requested resurrect"), ("completed_resurrect", "Completed resurrect"), diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index bf069a873..58763a13f 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -76,7 +76,8 @@ class InternetDraft(Document): #expiration_date = models.DateField() @property def expiration_date(self): - return self.expiration() + e = self.latest_event(type__in=('expired_document', 'new_revision', "completed_resurrect")) + return e.time.date() if e and e.type == "expired_document" else None #abstract = models.TextField() # same name #dunn_sent_date = models.DateField(null=True, blank=True) # unused #extension_date = models.DateField(null=True, blank=True) # unused From 8762d77fd8f0d3c2a654ad179f7dad7547ce2bef Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 26 Apr 2011 13:29:39 +0000 Subject: [PATCH 44/75] Import NomCom groups, and start/end events for groups - Legacy-Id: 3060 --- redesign/group/models.py | 27 +++++++++++++++ redesign/import-groups.py | 70 ++++++++++++++++++++++++++++++++++++--- redesign/import-roles.py | 22 +++++++++--- 3 files changed, 110 insertions(+), 9 deletions(-) diff --git a/redesign/group/models.py b/redesign/group/models.py index 83efbdda0..6e7ef00ce 100644 --- a/redesign/group/models.py +++ b/redesign/group/models.py @@ -4,6 +4,8 @@ from django.db import models from redesign.name.models import * from redesign.person.models import Email +import datetime + class Group(models.Model): name = models.CharField(max_length=80) acronym = models.CharField(max_length=16, db_index=True) @@ -16,6 +18,31 @@ class Group(models.Model): comments = models.TextField(blank=True) def __unicode__(self): return self.name + def latest_event(self, *args, **filter_args): + """Get latest group event with filter arguments, e.g. + d.latest_event(type="xyz").""" + e = GroupEvent.objects.filter(group=self).filter(**filter_args).order_by('-time', '-id')[:1] + return e[0] if e else None + + +GROUP_EVENT_CHOICES = [("proposed", "Proposed group"), + ("started", "Started group"), + ("concluded", "Concluded group"), + ] + +class GroupEvent(models.Model): + """An occurrence for a group, used for tracking who, when and what.""" + group = models.ForeignKey(Group) + time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened") + type = models.CharField(max_length=50, choices=GROUP_EVENT_CHOICES) + by = models.ForeignKey(Email) + 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', 'id'] # 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 diff --git a/redesign/import-groups.py b/redesign/import-groups.py index 98529fded..fc15c41a5 100755 --- a/redesign/import-groups.py +++ b/redesign/import-groups.py @@ -14,10 +14,12 @@ 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 +from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF, ChairsHistory, Role # imports IETFWG, Area, AreaGroup, Acronym +# also creates nomcom groups + # FIXME: should also import IRTF # make sure we got the names @@ -54,7 +56,38 @@ iesg_group.state = state_names["active"] iesg_group.type = type_names["ietf"] iesg_group.save() +system_email, _ = Email.objects.get_or_create(address="(System)") + +# NomCom +Group.objects.filter(acronym="nomcom").delete() + +for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("start_year"): + group = Group() + group.acronym = "nomcom" + group.name = "IAB/IESG Nominating Committee %s/%s" % (o.start_year, o.end_year) + if o.chair_type.person == o.person: + s = state_names["active"] + else: + s = state_names["conclude"] + group.state = s + group.type = type_names["ietf"] + group.parent = None + group.save() + + # we need start/end year so fudge events + e = GroupEvent(group=group, type="started") + e.time = datetime.datetime(o.start_year, 5, 1, 12, 0, 0) + e.by = system_email + e.desc = e.get_type_display() + e.save() + + e = GroupEvent(group=group, type="concluded") + e.time = datetime.datetime(o.end_year, 5, 1, 12, 0, 0) + e.by = system_email + e.desc = e.get_type_display() + e.save() + # Area for o in Area.objects.all(): group, _ = Group.objects.get_or_create(acronym=o.area_acronym.acronym) @@ -70,9 +103,20 @@ for o in Area.objects.all(): group.parent = iesg_group group.comments = o.comments.strip() if o.comments else "" - # FIXME: missing fields from old: concluded_date, last_modified_date, extra_email_addresses - group.save() + + # import events + group.groupevent_set.all().delete() + + if o.concluded_date: + e = GroupEvent(group=group, type="concluded") + e.time = datetime.datetime.combine(o.concluded_date, datetime.time(12, 0, 0)) + e.by = system_email + e.desc = e.get_type_display() + e.save() + + # FIXME: missing fields from old: last_modified_date, extra_email_addresses + # IETFWG, AreaGroup for o in IETFWG.objects.all(): @@ -129,8 +173,24 @@ for o in IETFWG.objects.all(): 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() -# FIXME: IRTF + # import events + group.groupevent_set.all().delete() + + def import_date_event(name): + d = getattr(o, "%s_date" % name) + if d: + e = GroupEvent(group=group, type=name) + e.time = datetime.datetime.combine(d, datetime.time(12, 0, 0)) + e.by = system_email + e.desc = e.get_type_display() + e.save() + + import_date_event("proposed") + import_date_event("start") + import_date_event("concluded") + # dormant_date is empty on all so don't bother with that + + # FIXME: missing fields from old: meeting_scheduled, email_subscribe, email_keyword, email_archive, last_modified_date, meeting_scheduled_old diff --git a/redesign/import-roles.py b/redesign/import-roles.py index 4bcaa0388..d28c42c10 100755 --- a/redesign/import-roles.py +++ b/redesign/import-roles.py @@ -15,7 +15,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, IDAuthor, PersonOrOrgInfo, WGEditor +from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo, WGEditor, ChairsHistory, Role as OldRole # assumptions: # - groups have been imported @@ -23,10 +23,11 @@ from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrg # PersonOrOrgInfo/PostalAddress/EmailAddress/PhoneNumber are not # imported, although some information is retrieved from those -# imports IESGLogin, AreaDirector, WGEditor and persons from IDAuthor +# imports IESGLogin, AreaDirector, WGEditor, persons from IDAuthor, +# NomCom chairs from ChairsHistory # should probably import WGChair, WGSecretary, -# WGTechAdvisor, Role, ChairsHistory, IRTFChair +# WGTechAdvisor, Role, IRTFChair # make sure names exist def name(name_class, slug, name, desc=""): @@ -38,8 +39,9 @@ def name(name_class, slug, name, desc=""): return obj area_director_role = name(RoleName, "ad", "Area Director") -inactive_area_director_role = name(RoleName, "ex-ad", "Ex-Area Director", desc="In-active Area Director") +inactive_area_director_role = name(RoleName, "ex-ad", "Ex-Area Director", desc="Inactive Area Director") wg_editor_role = name(RoleName, "wgeditor", "Working Group Editor") +chair_role = name(RoleName, "chair", "Chair") # helpers for creating the objects def get_or_create_email(o, create_fake): @@ -73,6 +75,18 @@ def get_or_create_email(o, create_fake): return e +nomcom_groups = list(Group.objects.filter(acronym="nomcom")) +for o in ChairsHistory.objects.filter(chair_type=OldRole.NOMCOM_CHAIR): + print "importing NOMCOM chair", o + for g in nomcom_groups: + if ("%s/%s" % (o.start_year, o.end_year)) in g.name: + break + + email = get_or_create_email(o, create_fake=False) + + Role.objects.get_or_create(name=chair_role, group=g, email=email) + + # IESGLogin for o in IESGLogin.objects.all(): print "importing IESGLogin", o.id, o.first_name, o.last_name From 920b2d25c37bf0cd8476606add199b07496dfdd9 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Wed, 27 Apr 2011 13:06:14 +0000 Subject: [PATCH 45/75] Import IRTF groups, chairs and other roles, port notify-expirations script (and add test) - Legacy-Id: 3068 --- ietf/bin/notify-expirations | 55 +---------- ietf/idrfc/expire.py | 88 +++++++++++++++-- ietf/idrfc/fixtures/names.xml | 24 ++++- ietf/idrfc/tests.py | 28 ++++++ ietf/idrfc/testsREDESIGN.py | 58 +++++++++++- ietf/idrfc/views_edit.py | 2 +- ietf/templates/idrfc/expire_warning_email.txt | 6 ++ ietf/utils/mail.py | 3 +- redesign/doc/proxy.py | 1 - redesign/import-groups.py | 30 +++++- redesign/import-roles.py | 94 +++++++++++++++---- redesign/person/models.py | 2 +- 12 files changed, 301 insertions(+), 90 deletions(-) create mode 100644 ietf/templates/idrfc/expire_warning_email.txt diff --git a/ietf/bin/notify-expirations b/ietf/bin/notify-expirations index 1e582bf4a..030d0611c 100755 --- a/ietf/bin/notify-expirations +++ b/ietf/bin/notify-expirations @@ -6,56 +6,11 @@ from ietf import settings from django.core import management management.setup_environ(settings) -from ietf.idtracker.models import InternetDraft,IDAuthor,WGChair -from ietf.utils.mail import send_mail_subj - -notify_days = 14 # notify about documents that expire within the - # next 2 weeks - -start_date = datetime.date.today() - datetime.timedelta(InternetDraft.DAYS_TO_EXPIRE - 1) -end_date = start_date + datetime.timedelta(notify_days - 1) +from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id -matches = InternetDraft.objects.filter(revision_date__gte=start_date,revision_date__lte=end_date,status__status='Active') +# notify about documents that expire within the next 2 weeks +notify_days = 14 -#For development - focus on one draft -#matches = InternetDraft.objects.filter(filename__icontains='geopriv-http-location-delivery') - -# Todo: -#second_cutoff = IDDates.objects.get(date_id=2) -#ietf_monday = IDDates.objects.get(date_id=3) -#freeze_delta = ietf_monday - second_cutoff - -for draft in matches: - if not draft.can_expire(): - # debugging - #print "%s can't expire, skipping" % draft - continue - expiration = draft.expiration() -# # The I-D expiration job doesn't run while submissions are frozen. -# if ietf_monday > expiration > second_cutoff: -# expiration += freeze_delta - authors = draft.authors.all() - to_addrs = [author.email() for author in authors if author.email()] - cc_addrs = None - if draft.group.acronym != 'none': - cc_addrs = [chair.person.email() for chair in WGChair.objects.filter(group_acronym=draft.group)] - - #For development debugging - """ - print "filename: "+draft.filename - print "to: ", to_addrs - print "cc: ", cc_addrs - print "expires: ", expiration - print "status: ", draft.status.status, "/", draft.idstate() - print - continue - """ - - if to_addrs or cc_addrs: - send_mail_subj(None, to_addrs, None, 'notify_expirations/subject.txt', 'notify_expirations/body.txt', - { - 'draft':draft, - 'expiration':expiration, - }, - cc_addrs) +for doc in get_soon_to_expire_ids(notify_days): + send_expire_warning_for_id(doc) diff --git a/ietf/idrfc/expire.py b/ietf/idrfc/expire.py index e8b6dbfa8..0a0e0b003 100644 --- a/ietf/idrfc/expire.py +++ b/ietf/idrfc/expire.py @@ -6,8 +6,8 @@ from django.db.models import Q import datetime, os, shutil, glob, re -from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment -from ietf.utils.mail import send_mail +from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment, IDAuthor,WGChair +from ietf.utils.mail import send_mail, send_mail_subj from ietf.idrfc.utils import log_state_changed, add_document_comment from doc.models import Document, Event, save_document_in_history from name.models import IesgDocStateName, DocStateName, DocInfoTagName @@ -28,6 +28,33 @@ def in_id_expire_freeze(when=None): return second_cut_off <= when < ietf_monday +def document_expires(doc): + e = doc.latest_event(type__in=("completed_resurrect", "new_revision")) + if e: + return e.time + datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE) + else: + return None + +def expirable_documents(): + return Document.objects.filter(state="active").exclude(tags="rfc-rev").filter(Q(iesg_state=None) | Q(iesg_state__order__gte=42)) + +def get_soon_to_expire_ids(days): + start_date = datetime.date.today() - datetime.timedelta(InternetDraft.DAYS_TO_EXPIRE - 1) + end_date = start_date + datetime.timedelta(days - 1) + + for d in InternetDraft.objects.filter(revision_date__gte=start_date,revision_date__lte=end_date,status__status='Active'): + if d.can_expire(): + yield d + +def get_soon_to_expire_idsREDESIGN(days): + start_date = datetime.date.today() - datetime.timedelta(1) + end_date = start_date + datetime.timedelta(days - 1) + + for d in expirable_documents(): + e = document_expires(d) + if e and start_date <= e.date() <= end_date: + yield d + def get_expired_ids(): cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE) @@ -38,14 +65,59 @@ def get_expired_ids(): Q(idinternal=None) | Q(idinternal__cur_state__document_state_id__gte=42)) def get_expired_idsREDESIGN(): - cut_off = datetime.date.today() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE) + today = datetime.date.today() - docs = Document.objects.filter(state="active").exclude(tags="rfc-rev").filter(Q(iesg_state=None) | Q(iesg_state__order__gte=42)) - for d in docs: - e = d.latest_event(type="new_revision") - if e and e.time.date() <= cut_off: + for d in expirable_documents(): + e = document_expires(d) + if e and e.time.date() <= today: yield d +def send_expire_warning_for_id(doc): + expiration = doc.expiration() + # Todo: + #second_cutoff = IDDates.objects.get(date_id=2) + #ietf_monday = IDDates.objects.get(date_id=3) + #freeze_delta = ietf_monday - second_cutoff + # # The I-D expiration job doesn't run while submissions are frozen. + # if ietf_monday > expiration > second_cutoff: + # expiration += freeze_delta + + authors = doc.authors.all() + to_addrs = [author.email() for author in authors if author.email()] + cc_addrs = None + if doc.group.acronym != 'none': + cc_addrs = [chair.person.email() for chair in WGChair.objects.filter(group_acronym=doc.group)] + + if to_addrs or cc_addrs: + send_mail_subj(None, to_addrs, None, 'notify_expirations/subject.txt', 'notify_expirations/body.txt', + { + 'draft':doc, + 'expiration':expiration, + }, + cc_addrs) + +def send_expire_warning_for_idREDESIGN(doc): + expiration = document_expires(doc).date() + + to = [e.formatted_email() for e in doc.authors.all() if not e.address.startswith("unknown-email")] + cc = None + if doc.group.type_id != "individ": + cc = [e.formatted_email() for e in Email.objects.filter(role__group=doc.group, role__name="chair") if not e.address.startswith("unknown-email")] + + state = doc.iesg_state.name if doc.iesg_state else "I-D Exists" + + frm = None + request = None + if to or cc: + send_mail(request, to, frm, + u"Expiration impending: %s" % doc.file_tag(), + "idrfc/expire_warning_email.txt", + dict(doc=doc, + state=state, + expiration=expiration + ), + cc=cc) + def send_expire_notice_for_id(doc): doc.dunn_sent_date = datetime.date.today() doc.save() @@ -242,7 +314,9 @@ def clean_up_id_filesREDESIGN(): move_file_to("unknown_ids") if settings.USE_DB_REDESIGN_PROXY_CLASSES: + get_soon_to_expire_ids = get_soon_to_expire_idsREDESIGN get_expired_ids = get_expired_idsREDESIGN + send_expire_warning_for_id = send_expire_warning_for_idREDESIGN send_expire_notice_for_id = send_expire_notice_for_idREDESIGN expire_id = expire_idREDESIGN clean_up_id_files = clean_up_id_filesREDESIGN diff --git a/ietf/idrfc/fixtures/names.xml b/ietf/idrfc/fixtures/names.xml index 39838b7ee..904cc2171 100644 --- a/ietf/idrfc/fixtures/names.xml +++ b/ietf/idrfc/fixtures/names.xml @@ -446,12 +446,30 @@ Ex-Area Director - In-active Area Director + Inactive Area Director 1 0 - - Working Group Editor + + Chair + + 1 + 0 + + + Editor + + 1 + 0 + + + Secretary + + 1 + 0 + + + Tech Advisor 1 0 diff --git a/ietf/idrfc/tests.py b/ietf/idrfc/tests.py index c51a5b19d..79386e3cf 100644 --- a/ietf/idrfc/tests.py +++ b/ietf/idrfc/tests.py @@ -774,6 +774,34 @@ class ExpireIDsTestCase(django.test.TestCase): 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_warn_expirable_ids(self): + from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id + + # hack into almost 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 - 7) + draft.idinternal.cur_state_id = IDState.AD_WATCHING + draft.idinternal.save() + draft.save() + + author = PersonOrOrgInfo.objects.all()[0] + IDAuthor.objects.create(document=draft, person=author, author_order=1) + EmailAddress.objects.create(person_or_org=author, type="I-D", priority=draft.pk, address="author@example.com") + + # test query + documents = list(get_soon_to_expire_ids(14)) + self.assertEquals(len(documents), 1) + + # test send warning + mailbox_before = len(mail_outbox) + + send_expire_warning_for_id(documents[0]) + + self.assertEquals(len(mail_outbox), mailbox_before + 1) + self.assertTrue("author@example.com" in str(mail_outbox[-1])) def test_expire_ids(self): from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index d8831a700..dd09d5650 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -74,7 +74,8 @@ def make_test_data(): # persons Email.objects.get_or_create(address="(System)") - + + # ad p = Person.objects.create( name="Aread Irector", ascii="Aread Irector", @@ -137,6 +138,21 @@ def make_test_data(): person=porg, ) + # group chair + p = Person.objects.create( + name="WG Chair Man", + ascii="WG Chair Man", + ) + wgchair = Email.objects.create( + address="wgchairman@ietf.org", + person=p) + Role.objects.create( + name=RoleName.objects.get(slug="chair"), + group=group, + email=wgchair, + ) + + # secretary p = Person.objects.create( name="Sec Retary", ascii="Sec Retary", @@ -187,6 +203,12 @@ def make_test_data(): name=draft.name, ) + DocumentAuthor.objects.create( + document=draft, + author=Email.objects.get(address="aread@ietf.org"), + order=1 + ) + # draft has only one event Event.objects.create( type="started_iesg_process", @@ -194,7 +216,7 @@ def make_test_data(): doc=draft, desc="Added draft", ) - + # telechat dates t = datetime.date.today() dates = TelechatDates(date1=t, @@ -1002,6 +1024,38 @@ class ExpireIDsTestCase(django.test.TestCase): 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_warn_expirable_ids(self): + from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id, INTERNET_DRAFT_DAYS_TO_EXPIRE + + draft = make_test_data() + + self.assertEquals(len(list(get_soon_to_expire_ids(14))), 0) + + # hack into expirable state + draft.iesg_state = None + draft.save() + + NewRevisionEvent.objects.create( + type="new_revision", + by=Email.objects.get(address="aread@ietf.org"), + doc=draft, + desc="New revision", + time=datetime.datetime.now() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE - 7), + rev="01" + ) + + self.assertEquals(len(list(get_soon_to_expire_ids(14))), 1) + + # test send warning + mailbox_before = len(mail_outbox) + + send_expire_warning_for_id(draft) + + print mail_outbox[-1] + self.assertEquals(len(mail_outbox), mailbox_before + 1) + self.assertTrue("aread@ietf.org" in str(mail_outbox[-1])) # author + self.assertTrue("wgchairman@ietf.org" in str(mail_outbox[-1])) + def test_expire_ids(self): from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id, INTERNET_DRAFT_DAYS_TO_EXPIRE diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index c892f097c..d4f75aece 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -441,7 +441,7 @@ def get_initial_notify(doc): receivers.append(e.address) else: receivers.append("%s-chairs@%s" % (doc.group.acronym, settings.TOOLS_SERVER)) - for editor in Email.objects.filter(role__name="wgeditor", role__group=doc.group): + for editor in Email.objects.filter(role__name="editor", role__group=doc.group): receivers.append(e.address) receivers.append("%s@%s" % (doc.name, settings.TOOLS_SERVER)) diff --git a/ietf/templates/idrfc/expire_warning_email.txt b/ietf/templates/idrfc/expire_warning_email.txt new file mode 100644 index 000000000..38b5a9267 --- /dev/null +++ b/ietf/templates/idrfc/expire_warning_email.txt @@ -0,0 +1,6 @@ +The following draft will expire soon: + +Name: {{ doc.name }} +Title: {{ doc.title}} +State: {{ state }} +Expires: {{ expiration }} (in {{ expiration|timeuntil }}) diff --git a/ietf/utils/mail.py b/ietf/utils/mail.py index 272f6a3e2..dffac7513 100644 --- a/ietf/utils/mail.py +++ b/ietf/utils/mail.py @@ -128,7 +128,7 @@ def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=N else: msg = MIMEText(txt) - send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=None, bcc=None) + send_mail_mime(request, to, frm, subject, msg, cc, extra, toUser, bcc) def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=None, bcc=None): """Send MIME message with content already filled in.""" @@ -143,6 +143,7 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=N msg['To'] = to if cc: msg['Cc'] = cc + print cc msg['Subject'] = subject msg['X-Test-IDTracker'] = (settings.SERVER_MODE == 'production') and 'no' or 'yes' if extra: diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 58763a13f..812d96585 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -209,7 +209,6 @@ class InternetDraft(Document): # reverse relationship @property def authors(self): - from person.models import Person return IDAuthor.objects.filter(document=self) # methods from InternetDraft diff --git a/redesign/import-groups.py b/redesign/import-groups.py index fc15c41a5..3faeedb7c 100755 --- a/redesign/import-groups.py +++ b/redesign/import-groups.py @@ -16,12 +16,10 @@ from redesign.group.models import * from redesign.name.models import * from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF, ChairsHistory, Role -# imports IETFWG, Area, AreaGroup, Acronym +# imports IETFWG, Area, AreaGroup, Acronym, IRTF # also creates nomcom groups -# FIXME: should also import IRTF - # make sure we got the names def name(name_class, slug, name, desc=""): # create if it doesn't exist, set name and desc @@ -60,11 +58,11 @@ system_email, _ = Email.objects.get_or_create(address="(System)") # NomCom -Group.objects.filter(acronym="nomcom").delete() +Group.objects.filter(acronym="nominatingcom").delete() for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("start_year"): group = Group() - group.acronym = "nomcom" + group.acronym = "nominatingcom" group.name = "IAB/IESG Nominating Committee %s/%s" % (o.start_year, o.end_year) if o.chair_type.person == o.person: s = state_names["active"] @@ -118,6 +116,28 @@ for o in Area.objects.all(): # FIXME: missing fields from old: last_modified_date, extra_email_addresses +# IRTF +for o in IRTF.objects.all(): + try: + group = Group.objects.get(acronym=o.acronym.lower()) + except Group.DoesNotExist: + group = Group(acronym=o.acronym.lower()) + + group.name = o.name + group.state = state_names["active"] # we assume all to be active + group.type = type_names["rg"] + + # FIXME: who is the parent? + # group.parent = Group.objects.get(acronym=) + print "no parent for", group.acronym, group.name, group.type, group.state + + group.comments = o.charter_text or "" + + group.save() + + # FIXME: missing fields from old: meeting_scheduled + + # IETFWG, AreaGroup for o in IETFWG.objects.all(): try: diff --git a/redesign/import-roles.py b/redesign/import-roles.py index d28c42c10..dc82be4fe 100755 --- a/redesign/import-roles.py +++ b/redesign/import-roles.py @@ -15,7 +15,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, IDAuthor, PersonOrOrgInfo, WGEditor, ChairsHistory, Role as OldRole +from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo, WGChair, WGEditor, WGSecretary, WGTechAdvisor, ChairsHistory, Role as OldRole, Acronym, IRTFChair # assumptions: # - groups have been imported @@ -24,10 +24,10 @@ from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrg # imported, although some information is retrieved from those # imports IESGLogin, AreaDirector, WGEditor, persons from IDAuthor, -# NomCom chairs from ChairsHistory +# NomCom chairs from ChairsHistory, WGChair, IRTFChair, WGSecretary, +# WGTechAdvisor -# should probably import WGChair, WGSecretary, -# WGTechAdvisor, Role, IRTFChair +# FIXME: should probably import Role # make sure names exist def name(name_class, slug, name, desc=""): @@ -40,8 +40,10 @@ def name(name_class, slug, name, desc=""): area_director_role = name(RoleName, "ad", "Area Director") inactive_area_director_role = name(RoleName, "ex-ad", "Ex-Area Director", desc="Inactive Area Director") -wg_editor_role = name(RoleName, "wgeditor", "Working Group Editor") chair_role = name(RoleName, "chair", "Chair") +editor_role = name(RoleName, "editor", "Editor") +secretary_role = name(RoleName, "secr", "Secretary") +techadvisor_role = name(RoleName, "techadv", "Tech Advisor") # helpers for creating the objects def get_or_create_email(o, create_fake): @@ -75,7 +77,74 @@ def get_or_create_email(o, create_fake): return e -nomcom_groups = list(Group.objects.filter(acronym="nomcom")) +# WGEditor +for o in WGEditor.objects.all(): + acronym = Acronym.objects.get(acronym_id=o.group_acronym_id).acronym + print "importing WGEditor", acronym, o.person + + email = get_or_create_email(o, create_fake=True) + group = Group.objects.get(acronym=acronym) + + Role.objects.get_or_create(name=editor_role, group=group, email=email) + +# WGSecretary +for o in WGSecretary.objects.all(): + acronym = Acronym.objects.get(acronym_id=o.group_acronym_id).acronym + print "importing WGSecretary", acronym, o.person + + email = get_or_create_email(o, create_fake=True) + group = Group.objects.get(acronym=acronym) + + Role.objects.get_or_create(name=secretary_role, group=group, email=email) + +# WGTechAdvisor +for o in WGTechAdvisor.objects.all(): + acronym = Acronym.objects.get(acronym_id=o.group_acronym_id).acronym + print "importing WGTechAdvisor", acronym, o.person + + email = get_or_create_email(o, create_fake=True) + group = Group.objects.get(acronym=acronym) + + Role.objects.get_or_create(name=techadvisor_role, group=group, email=email) + +# WGChair +for o in WGChair.objects.all(): + # there's some garbage in this table, so wear double safety belts + try: + acronym = Acronym.objects.get(acronym_id=o.group_acronym_id).acronym + except Acronym.DoesNotExist: + print "SKIPPING WGChair with unknown acronym id", o.group_acronym_id + continue + + try: + person = o.person + except PersonOrOrgInfo.DoesNotExist: + print "SKIPPING WGChair", acronym, "with invalid person id", o.person_id + continue + + if acronym in ("apples", "apptsv", "usac", "null", "dirdir"): + print "SKIPPING WGChair", acronym, o.person + continue + + print "importing WGChair", acronym, o.person + + email = get_or_create_email(o, create_fake=True) + group = Group.objects.get(acronym=acronym) + + Role.objects.get_or_create(name=chair_role, group=group, email=email) + +# IRTFChair +for o in IRTFChair.objects.all(): + acronym = o.irtf.acronym.lower() + print "importing IRTFChair", acronym, o.person + + email = get_or_create_email(o, create_fake=True) + group = Group.objects.get(acronym=acronym) + + Role.objects.get_or_create(name=chair_role, group=group, email=email) + +# NomCom chairs +nomcom_groups = list(Group.objects.filter(acronym="nominatingcom")) for o in ChairsHistory.objects.filter(chair_type=OldRole.NOMCOM_CHAIR): print "importing NOMCOM chair", o for g in nomcom_groups: @@ -86,7 +155,6 @@ for o in ChairsHistory.objects.filter(chair_type=OldRole.NOMCOM_CHAIR): Role.objects.get_or_create(name=chair_role, group=g, email=email) - # IESGLogin for o in IESGLogin.objects.all(): print "importing IESGLogin", o.id, o.first_name, o.last_name @@ -134,18 +202,6 @@ for o in AreaDirector.objects.all(): else: Role.objects.get_or_create(name=role_type, group=area, email=email) -# WGEditor -for o in WGEditor.objects.all(): - # if not o.group_acronym: - # print "NO GROUP", o.person, o.group_acronym_id - # continue - - print "importing WGEditor", o.group_acronym, o.person - email = get_or_create_email(o, create_fake=False) - - group = Group.objects.get(acronym=o.group_acronym.group_acronym.acronym) - - Role.objects.get_or_create(name=wg_editor_role, group=group, email=email) # IDAuthor persons for o in IDAuthor.objects.all().order_by('id').select_related('person'): diff --git a/redesign/person/models.py b/redesign/person/models.py index 1fbce34f6..b2e735867 100644 --- a/redesign/person/models.py +++ b/redesign/person/models.py @@ -68,7 +68,7 @@ class Email(models.Model): def formatted_email(self): if self.person and self.person.name: - return u"%s <%s>" % (self.person.name, self.address) + return u'"%s" <%s>' % (self.person.name, self.address) else: return self.address From 16a2782da0f625b7f028f76e2a9e7a3ce9e8a8cc Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 28 Apr 2011 16:46:38 +0000 Subject: [PATCH 46/75] Fix NomCom naming, put a parent on RG, save ids on groups and output them on documents - Legacy-Id: 3069 --- redesign/import-document-state.py | 2 +- redesign/import-groups.py | 18 +++++++++++------- redesign/import-roles.py | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py index 42fbd824b..7d55a22ca 100755 --- a/redesign/import-document-state.py +++ b/redesign/import-document-state.py @@ -776,7 +776,7 @@ if document_name_to_import: #all_drafts = all_drafts.none() for index, o in enumerate(all_drafts.iterator()): - print "importing", o.filename, index + print "importing", o.id_document_tag, o.filename, index try: d = Document.objects.get(name=o.filename) diff --git a/redesign/import-groups.py b/redesign/import-groups.py index 3faeedb7c..3a4dc051d 100755 --- a/redesign/import-groups.py +++ b/redesign/import-groups.py @@ -54,15 +54,22 @@ iesg_group.state = state_names["active"] iesg_group.type = type_names["ietf"] iesg_group.save() +# make sure we got the IRTF as parent for RGs +irtf_group, _ = Group.objects.get_or_create(acronym="irtf") +irtf_group.name = "IRTF" +irtf_group.state = state_names["active"] +irtf_group.type = type_names["ietf"] +irtf_group.save() + system_email, _ = Email.objects.get_or_create(address="(System)") # NomCom -Group.objects.filter(acronym="nominatingcom").delete() +Group.objects.filter(acronym__startswith="nomcom").exclude(acronym="nomcom").delete() for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("start_year"): group = Group() - group.acronym = "nominatingcom" + group.acronym = "nomcom%s" % o.start_year group.name = "IAB/IESG Nominating Committee %s/%s" % (o.start_year, o.end_year) if o.chair_type.person == o.person: s = state_names["active"] @@ -126,10 +133,7 @@ for o in IRTF.objects.all(): group.name = o.name group.state = state_names["active"] # we assume all to be active group.type = type_names["rg"] - - # FIXME: who is the parent? - # group.parent = Group.objects.get(acronym=) - print "no parent for", group.acronym, group.name, group.type, group.state + group.parent = irtf_group group.comments = o.charter_text or "" @@ -137,13 +141,13 @@ for o in IRTF.objects.all(): # FIXME: missing fields from old: meeting_scheduled - # IETFWG, AreaGroup for o in IETFWG.objects.all(): try: group = Group.objects.get(acronym=o.group_acronym.acronym) except Group.DoesNotExist: group = Group(acronym=o.group_acronym.acronym) + group.id = o.group_acronym_id # transfer id group.name = o.group_acronym.name # state diff --git a/redesign/import-roles.py b/redesign/import-roles.py index dc82be4fe..30e7e34c9 100755 --- a/redesign/import-roles.py +++ b/redesign/import-roles.py @@ -144,7 +144,7 @@ for o in IRTFChair.objects.all(): Role.objects.get_or_create(name=chair_role, group=group, email=email) # NomCom chairs -nomcom_groups = list(Group.objects.filter(acronym="nominatingcom")) +nomcom_groups = list(Group.objects.filter(acronym__startswith="nomcom").exclude(acronym="nomcom")) for o in ChairsHistory.objects.filter(chair_type=OldRole.NOMCOM_CHAIR): print "importing NOMCOM chair", o for g in nomcom_groups: From 53a2370b204166968195b972795aebae653385fb Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 28 Apr 2011 16:49:11 +0000 Subject: [PATCH 47/75] Port idindex, commenting out test views - Legacy-Id: 3070 --- ietf/idindex/tests.py | 60 +++++++++++++++++++-------------------- ietf/idindex/testurl.list | 12 ++++---- ietf/idindex/urls.py | 3 +- ietf/idindex/views.py | 10 +++++++ 4 files changed, 48 insertions(+), 37 deletions(-) diff --git a/ietf/idindex/tests.py b/ietf/idindex/tests.py index f4c543312..b21d781cc 100644 --- a/ietf/idindex/tests.py +++ b/ietf/idindex/tests.py @@ -39,34 +39,34 @@ class IdIndexUrlTestCase(SimpleUrlTestCase): def testUrls(self): self.doTestUrls(__file__) -class IndexTestCase(unittest.TestCase, RealDatabaseTest): - def setUp(self): - self.setUpRealDatabase() - def tearDown(self): - self.tearDownRealDatabase() +# class IndexTestCase(unittest.TestCase, RealDatabaseTest): +# def setUp(self): +# self.setUpRealDatabase() +# def tearDown(self): +# self.tearDownRealDatabase() - def testAllId(self): - print " Testing all_id.txt generation" - c = Client() - response = c.get('/drafts/_test/all_id.txt') - self.assertEquals(response.status_code, 200) - content = response.content - # Test that correct version number is shown for couple of old drafts - self.assert_(content.find("draft-ietf-tls-psk-09") >= 0) - self.assert_(content.find("draft-eronen-eap-sim-aka-80211-00") >= 0) - # Since all_id.txt contains all old drafts, it should never shrink - lines = content.split("\n") - self.assert_(len(lines) > 18000) - # Test that the lines look OK and have correct number of tabs - r = re.compile(r'^(draft-\S*-\d\d)\t(\d\d\d\d-\d\d-\d\d)\t([^\t]+)\t([^\t]*)$') - for line in lines: - if ((line == "") or - (line == "Internet-Drafts Status Summary") or - (line == "Web version is available at") or - (line == "https://datatracker.ietf.org/public/idindex.cgi")): - pass - elif r.match(line): - pass - else: - self.fail("Unexpected line \""+line+"\"") - print "OK (all_id.txt)" +# def testAllId(self): +# print " Testing all_id.txt generation" +# c = Client() +# response = c.get('/drafts/_test/all_id.txt') +# self.assertEquals(response.status_code, 200) +# content = response.content +# # Test that correct version number is shown for couple of old drafts +# self.assert_(content.find("draft-ietf-tls-psk-09") >= 0) +# self.assert_(content.find("draft-eronen-eap-sim-aka-80211-00") >= 0) +# # Since all_id.txt contains all old drafts, it should never shrink +# lines = content.split("\n") +# self.assert_(len(lines) > 18000) +# # Test that the lines look OK and have correct number of tabs +# r = re.compile(r'^(draft-\S*-\d\d)\t(\d\d\d\d-\d\d-\d\d)\t([^\t]+)\t([^\t]*)$') +# for line in lines: +# if ((line == "") or +# (line == "Internet-Drafts Status Summary") or +# (line == "Web version is available at") or +# (line == "https://datatracker.ietf.org/public/idindex.cgi")): +# pass +# elif r.match(line): +# pass +# else: +# self.fail("Unexpected line \""+line+"\"") +# print "OK (all_id.txt)" diff --git a/ietf/idindex/testurl.list b/ietf/idindex/testurl.list index 15810bc1c..5ba17e942 100644 --- a/ietf/idindex/testurl.list +++ b/ietf/idindex/testurl.list @@ -5,16 +5,16 @@ 301 /drafts/current/ 301 /drafts/all/ 301 /drafts/dead/ -301 /drafts/9574/related/ -301 /drafts/9574/ +#301 /drafts/9574/related/ +#301 /drafts/9574/ 301 /drafts/draft-ietf-dnsext-dnssec-protocol/related/ 301 /drafts/draft-ietf-dnsext-dnssec-protocol/ -404 /drafts/987654/ +#404 /drafts/987654/ 301 /drafts/all_id_txt.html 301 /drafts/all_id.html 301 /drafts/ -200,heavy /drafts/_test/all_id.txt +#200,heavy /drafts/_test/all_id.txt # this takes 3 minutes, so disabled by default #200,heavy /drafts/_test/all_id2.txt -200,heavy /drafts/_test/id_index.txt -200,heavy /drafts/_test/id_abstracts.txt +#200,heavy /drafts/_test/id_index.txt +#200,heavy /drafts/_test/id_abstracts.txt diff --git a/ietf/idindex/urls.py b/ietf/idindex/urls.py index 9d9b7e628..8f039501d 100644 --- a/ietf/idindex/urls.py +++ b/ietf/idindex/urls.py @@ -17,7 +17,8 @@ urlpatterns = patterns('', (r'^all_id(?:_txt)?.html$', 'django.views.generic.simple.redirect_to', { 'url': 'http://www.ietf.org/id/all_id.txt' }), ) -if settings.SERVER_MODE != 'production': +if settings.SERVER_MODE != 'production' and not settings.USE_DB_REDESIGN_PROXY_CLASSES: + # these haven't been ported urlpatterns += patterns('', (r'^_test/all_id.txt$', views.test_all_id_txt), (r'^_test/all_id2.txt$', views.test_all_id2_txt), diff --git a/ietf/idindex/views.py b/ietf/idindex/views.py index b219b9788..30b75d834 100644 --- a/ietf/idindex/views.py +++ b/ietf/idindex/views.py @@ -35,6 +35,8 @@ from django.http import HttpResponse, HttpResponsePermanentRedirect from django.template import loader from django.shortcuts import get_object_or_404 +from django.conf import settings + from ietf.idtracker.models import Acronym, IETFWG, InternetDraft, IDInternal,PersonOrOrgInfo, Area from ietf.idtracker.templatetags.ietf_filters import clean_whitespace import re @@ -156,6 +158,9 @@ def test_id_abstracts_txt(request): def redirect_id(request, object_id): '''Redirect from historical document ID to preferred filename url.''' + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + return HttpResponsePermanentRedirect("/doc/") + doc = get_object_or_404(InternetDraft, id_document_tag=object_id) return HttpResponsePermanentRedirect("/doc/"+doc.filename+"/") @@ -163,6 +168,11 @@ def redirect_filename(request, filename): return HttpResponsePermanentRedirect("/doc/"+filename+"/") def wgdocs_redirect_id(request, id): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from group.models import Group + group = get_object_or_404(Group, id=id) + return HttpResponsePermanentRedirect("/wg/%s/" % group.acronym) + group = get_object_or_404(Acronym, acronym_id=id) return HttpResponsePermanentRedirect("/wg/"+group.acronym+"/") From 7d3bef3db41df5e5de1bdf6cd8fa84fe192dff55 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 2 May 2011 15:58:36 +0000 Subject: [PATCH 48/75] Import announcements, port NomCom views - Legacy-Id: 3077 --- ietf/announcements/urls.py | 6 +- ietf/announcements/views.py | 60 +++++++++++++ ietf/settings.py | 1 + .../announcements/message_detail.html | 18 ++++ .../announcements/nomcomREDESIGN.html | 76 ++++++++++++++++ redesign/announcements/__init__.py | 0 redesign/announcements/admin.py | 20 +++++ redesign/announcements/models.py | 37 ++++++++ redesign/doc/admin.py | 5 -- redesign/doc/models.py | 13 --- redesign/import-announcements.py | 86 +++++++++++++++++++ redesign/import-document-state.py | 13 +-- redesign/import-roles.py | 37 ++++---- redesign/importing/__init__.py | 0 redesign/importing/utils.py | 14 +++ 15 files changed, 338 insertions(+), 48 deletions(-) create mode 100644 ietf/templates/announcements/message_detail.html create mode 100644 ietf/templates/announcements/nomcomREDESIGN.html create mode 100644 redesign/announcements/__init__.py create mode 100644 redesign/announcements/admin.py create mode 100644 redesign/announcements/models.py create mode 100755 redesign/import-announcements.py create mode 100644 redesign/importing/__init__.py create mode 100644 redesign/importing/utils.py diff --git a/ietf/announcements/urls.py b/ietf/announcements/urls.py index 78842bf35..8489b5fcc 100644 --- a/ietf/announcements/urls.py +++ b/ietf/announcements/urls.py @@ -3,12 +3,14 @@ from django.conf.urls.defaults import patterns from ietf.announcements.models import Announcement +from django.conf import settings + nomcom_dict = { 'queryset': Announcement.objects.all().filter(nomcom=True) -} + } urlpatterns = patterns('', # (r'^nomcom/$', 'django.views.generic.simple.redirect_to', {'url': 'http://www.ietf.org/nomcom/index.html'} ), (r'^nomcom/$', 'ietf.announcements.views.nomcom'), - (r'^nomcom/(?P\d+)/$', 'django.views.generic.list_detail.object_detail', nomcom_dict) + (r'^nomcom/(?P\d+)/$', 'ietf.announcements.views.message_detail' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'django.views.generic.list_detail.object_detail', nomcom_dict) ) diff --git a/ietf/announcements/views.py b/ietf/announcements/views.py index abd1b0440..286b6182c 100644 --- a/ietf/announcements/views.py +++ b/ietf/announcements/views.py @@ -1,6 +1,11 @@ # Copyright The IETF Trust 2007, All Rights Reserved from django.views.generic.simple import direct_to_template +from django.shortcuts import get_object_or_404 +from django.conf import settings +from django.db.models import Q + +import re from ietf.idtracker.models import ChairsHistory from ietf.idtracker.models import Role @@ -29,3 +34,58 @@ def nomcom(request): { 'curr_chair' : curr_chair, 'regimes' : regimes }) +def nomcomREDESIGN(request): + from person.models import Email + from group.models import Group + from redesign.announcements.models import Message + + address_re = re.compile("<.*>") + + nomcoms = list(Group.objects.filter(acronym__startswith="nomcom").exclude(name="nomcom")) + + regimes = [] + + for n in nomcoms: + e = n.latest_event(type="started") + n.start_year = e.time.year if e else 0 + if n.start_year <= 2003: + continue + e = n.latest_event(type="concluded") + n.end_year = e.time.year if e else "" + + chair = n.role_set.get(name="chair").email + announcements = Message.objects.filter(related_groups=n).order_by('-time') + for a in announcements: + a.to_name = address_re.sub("", a.to) + + regimes.append(dict(chair=chair, + announcements=announcements, + group=n)) + + regimes.sort(key=lambda x: x["group"].start_year, reverse=True) + + return direct_to_template(request, + "announcements/nomcomREDESIGN.html", + { 'curr_chair' : regimes[0]["chair"], + 'regimes' : regimes }) + + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + nomcom = nomcomREDESIGN + + +def message_detail(request, object_id, queryset): + from person.models import Email + from group.models import Group + from redesign.announcements.models import Message + + # restrict to nomcom announcements for the time being + nomcoms = Group.objects.filter(acronym__startswith="nomcom").exclude(acronym="nomcom") + m = get_object_or_404(Message, id=object_id, + related_groups__in=nomcoms) + + return direct_to_template(request, + "announcements/message_detail.html", + dict(message=m)) + + diff --git a/ietf/settings.py b/ietf/settings.py index 110773850..7089cf5f4 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -125,6 +125,7 @@ INSTALLED_APPS = ( 'redesign.name', 'redesign.group', 'redesign.doc', + 'redesign.announcements', 'redesign.issue', 'ietf.announcements', 'ietf.idindex', diff --git a/ietf/templates/announcements/message_detail.html b/ietf/templates/announcements/message_detail.html new file mode 100644 index 000000000..606d84046 --- /dev/null +++ b/ietf/templates/announcements/message_detail.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load ietf_filters %} + +{% block title %}Announcement: {{ message.time|date:"F j, Y" }} -- {{ message.subject|escape }}{% endblock %} + +{% block content %} +

NomCom Message

+

+From: {{ message.frm|escape }}
+To: {{ message.to|escape }}
+Date: {{ message.time|date:"F j, Y" }}
+Subject: {{ message.subject|escape }} +

+
+
+{{ message.text|escape }}
+
+{% endblock %} diff --git a/ietf/templates/announcements/nomcomREDESIGN.html b/ietf/templates/announcements/nomcomREDESIGN.html new file mode 100644 index 000000000..7870db472 --- /dev/null +++ b/ietf/templates/announcements/nomcomREDESIGN.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% load ietf_filters %} +{% block title %}IAB/IESG Nominating Committee{% endblock %} +{% block content %} + +

IAB/IESG Nominating Committee

+ +

Current Committee Chair: {{ curr_chair.get_name }}

+ +{% for regime in regimes %} +
+

Messages from {{ regime.group.start_year }} - {{ regime.group.end_year }}

+

Committee Chair: {{ regime.chair.get_name }}

+ + + + + + + {% for ann in regime.announcements %} + + + + + + {% endfor %} +
DateSubjectSent To
{{ ann.time|date:"Y-M-d" }}{{ ann.subject|escape }}{{ ann.to_name }}
+{% endfor %} + +
+ +{# somebody ought to import these announcements in the DB instead of this mess #} + +

Messages from 2003-2004 NomCom

+Committee Chair: Rich Draves +

  • IETF Nominations Committee Chair Announcement August 25, 2003 +
  • NomCom call for volunteers September 22, 2003 +
  • Selection of the Nominations Committee +
  • NomCom Volunteer List October 06, 2004 +
  • NomCom Selection October 10, 2003 +
  • Call for Nominees October 17, 2003 +
  • NomCom members October 24, 2003 +
  • NomCom at IETF November 07, 2003 +
  • NomCom News November 14, 2003 +
  • Reminder - nominations to replace Randy Bush November 26, 2003 +
  • Randy Bush replacement schedule December 01, 2003 +
  • Randy Bush replacement January 14, 2004 +
  • NomCom results February 13, 2004 +
  • Call for Security AD nominations September 28, 2004 +
  • Steve Bellovin replacement November 07, 2004 + +

    Messages from 2002-2003 NomCom

    + +Committee Chair: Phil Roberts +

    +
  • First Call for Volunteers July 30, 2002 +
  • Selection of the Nominations Committee +
  • Announcement of the Nominations Committee September 18, 2002 +
  • Announcement of IESG and IAB Nominations Requests October 21, 2002 +
  • Announcement of IESG and IAB Nominations Requests November 5, 2002 +
  • Announcement of IESG and IAB Nominations Requests November 12, 2002 +
  • IETF Nomcom Announcement February 27, 2003 +
  • Announcement of IESG and IAB Nominations Request June 11, 2003 +
  • Nomcom result announcement July 15, 2003 + +

    Historical Information

    + +
  • IAB/IESG Nominating Committee Members (by year) + +

    References

    + +
  • The Internet Standards Process (RFC 2026) +
  • IAB and IESG Selection, Confirmation, and Recall Process: Operation of the Nominating and Recall Committees (RFC 3777) (Also BCP10) +
  • Publicly Verifiable Nominations Committee (NomCom) Random Selection (RFC 3797) + +{% endblock %} diff --git a/redesign/announcements/__init__.py b/redesign/announcements/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/redesign/announcements/admin.py b/redesign/announcements/admin.py new file mode 100644 index 000000000..f7ee41c2b --- /dev/null +++ b/redesign/announcements/admin.py @@ -0,0 +1,20 @@ +from django.contrib import admin +from models import * + +class MessageAdmin(admin.ModelAdmin): + list_display = ["time", "by", "subject", "groups"] + search_fields = ["text"] + raw_id_fields = ["by"] + + def groups(self, instance): + return ", ".join(g.acronym for g in related_groups.all()) + +admin.site.register(Message, MessageAdmin) + +class SendQueueAdmin(admin.ModelAdmin): + list_display = ["time", "by", "message", "send_at", "sent_at"] + list_filter = ["time", "send_at", "sent_at"] + search_fields = ["message__text"] + raw_id_fields = ["by"] + +admin.site.register(SendQueue, SendQueueAdmin) diff --git a/redesign/announcements/models.py b/redesign/announcements/models.py new file mode 100644 index 000000000..03c67dbe6 --- /dev/null +++ b/redesign/announcements/models.py @@ -0,0 +1,37 @@ +from django.db import models + +import datetime + +from person.models import Email +from group.models import Group + +class Message(models.Model): + time = models.DateTimeField(default=datetime.datetime.now) + by = models.ForeignKey(Email) + + subject = models.CharField(max_length=255) + frm = models.CharField(max_length=255) + to = models.CharField(max_length=255) + cc = models.CharField(max_length=255, blank=True) + bcc = models.CharField(max_length=255, blank=True) + reply_to = models.CharField(max_length=255, blank=True) + text = models.TextField() + + related_groups = models.ManyToManyField(Group, blank=True) + + class Meta: + ordering = ['time'] + + def __unicode__(self): + return "'%s' %s -> %s" % (self.subject, self.frm, self.to) + +class SendQueue(models.Model): + time = models.DateTimeField(default=datetime.datetime.now) + by = models.ForeignKey(Email) + comment = models.TextField() + message = models.ForeignKey(Message) + send_at = models.DateTimeField(blank=True, null=True) + sent_at = models.DateTimeField(blank=True, null=True) + + class Meta: + ordering = ['time'] diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py index 184d942b7..d3ac4bff3 100644 --- a/redesign/doc/admin.py +++ b/redesign/doc/admin.py @@ -21,10 +21,6 @@ class DocAliasAdmin(admin.ModelAdmin): raw_id_fields = ['document'] admin.site.register(DocAlias, DocAliasAdmin) -class SendQueueAdmin(admin.ModelAdmin): - pass -admin.site.register(SendQueue, SendQueueAdmin) - # events @@ -38,7 +34,6 @@ class EventAdmin(admin.ModelAdmin): admin.site.register(Event, EventAdmin) -admin.site.register(Message, EventAdmin) admin.site.register(NewRevisionEvent, EventAdmin) admin.site.register(WriteupEvent, EventAdmin) admin.site.register(StatusDateEvent, EventAdmin) diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 06f4eb349..4615b1959 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -169,15 +169,6 @@ class DocAlias(models.Model): verbose_name = "document alias" verbose_name_plural = "document 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 @@ -243,10 +234,6 @@ class Event(models.Model): class Meta: ordering = ['-time', 'id'] -class Message(Event): - subj = models.CharField(max_length=255) - body = models.TextField() - class NewRevisionEvent(Event): rev = models.CharField(max_length=16) diff --git a/redesign/import-announcements.py b/redesign/import-announcements.py new file mode 100755 index 000000000..b465a1c22 --- /dev/null +++ b/redesign/import-announcements.py @@ -0,0 +1,86 @@ +#!/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.announcements.models import * +from ietf.announcements.models import Announcement, PersonOrOrgInfo, AnnouncedTo, AnnouncedFrom +from importing.utils import * + +# assumptions: +# - nomcom groups have been imported +# - persons have been imported + +# imports Announcements + +# FIXME: should import ScheduledAnnouncements + +system_email, _ = Email.objects.get_or_create(address="(System)") + +# Announcement +for o in Announcement.objects.all().select_related('announced_to', 'announced_from').order_by('announcement_id').iterator(): + try: + message = Message.objects.get(id=o.announcement_id) + except Message.DoesNotExist: + message = Message(id=o.announcement_id) + + message.time = datetime.datetime.combine(o.announced_date, + datetime.time(*(int(x) for x in o.announced_time.split(":")))) + + try: + x = o.announced_by + except PersonOrOrgInfo.DoesNotExist: + message.by = system_email + else: + if not o.announced_by.first_name and o.announced_by.last_name == 'None': + message.by = system_email + else: + message.by = Email.objects.get(address=person_email(o.announced_by)) + + message.subject = o.subject.strip() + if o.announced_from_id == 99: + message.frm = o.other_val or "" + elif o.announced_from_id == 18 and o.nomcom_chair_id != 0: + message.frm = u"%s <%s>" % o.nomcom_chair.person.email() + else: + if '<' in o.announced_from.announced_from: + message.frm = o.announced_from.announced_from + else: + message.frm = u"%s <%s>" % (o.announced_from.announced_from, o.announced_from.email) + if o.announced_to_id == 99: + message.to = o.other_val or "" + else: + try: + message.to = u"%s <%s>" % (o.announced_to.announced_to, o.announced_to.email) + except AnnouncedTo.DoesNotExist: + message.to = "" + + message.cc = o.cc or "" + for l in (o.extra or "").strip().replace("^", "\n").replace("\r", "").split("\n"): + l = l.strip() + if l.lower().startswith("bcc:"): + message.bcc = l[len("bcc:"):].strip() + elif l.lower().startswith("reply-to:"): + message.reply_to = l[len("reply-to:"):].strip() + message.text = o.text + message.save() + + message.related_groups.clear() + + if o.nomcom: + nomcom = Group.objects.filter(role__name="chair", + role__email__person__id=o.nomcom_chair.person.pk, + acronym__startswith="nomcom").exclude(acronym="nomcom").get() + + message.related_groups.add(nomcom) + diff --git a/redesign/import-document-state.py b/redesign/import-document-state.py index 7d55a22ca..05c712267 100755 --- a/redesign/import-document-state.py +++ b/redesign/import-document-state.py @@ -17,6 +17,8 @@ from redesign.name.models import * from ietf.idtracker.models import InternetDraft, IDInternal, IESGLogin, DocumentComment, PersonOrOrgInfo, Rfc, IESGComment, IESGDiscuss, BallotInfo, Position from ietf.idrfc.models import RfcIndex, DraftVersions +from importing.utils import * + import sys document_name_to_import = None @@ -43,15 +45,6 @@ connection.queries = DummyQueries() # IESGComment, IESGDiscuss, DocumentComment, IDAuthor, idrfc.RfcIndex, # idrfc.DraftVersions -def name(name_class, slug, name, desc="", order=0): - # 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.order = order - 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) @@ -208,7 +201,7 @@ def iesg_login_to_email(l): l = buggy_iesg_logins_cache[l.id] try: - return Email.objects.get(address=l.person.email()[1]) + return Email.objects.get(address=person_email(l.person)) except Email.DoesNotExist: try: return Email.objects.get(person__name="%s %s" % (l.person.first_name, l.person.last_name)) diff --git a/redesign/import-roles.py b/redesign/import-roles.py index 30e7e34c9..2ddbca008 100755 --- a/redesign/import-roles.py +++ b/redesign/import-roles.py @@ -17,27 +17,22 @@ from redesign.group.models import * from redesign.name.models import * from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo, WGChair, WGEditor, WGSecretary, WGTechAdvisor, ChairsHistory, Role as OldRole, Acronym, IRTFChair +from importing.utils import * + # assumptions: # - groups have been imported # PersonOrOrgInfo/PostalAddress/EmailAddress/PhoneNumber are not # imported, although some information is retrieved from those -# imports IESGLogin, AreaDirector, WGEditor, persons from IDAuthor, -# NomCom chairs from ChairsHistory, WGChair, IRTFChair, WGSecretary, -# WGTechAdvisor +# imports IESGLogin, AreaDirector, WGEditor, WGChair, IRTFChair, +# WGSecretary, WGTechAdvisor, NomCom chairs from ChairsHistory, +# +# also imports persons from IDAuthor, announcement originators from +# Announcements # FIXME: should probably import Role -# 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") inactive_area_director_role = name(RoleName, "ex-ad", "Ex-Area Director", desc="Inactive Area Director") chair_role = name(RoleName, "chair", "Chair") @@ -47,18 +42,16 @@ techadvisor_role = name(RoleName, "techadv", "Tech Advisor") # helpers for creating the objects 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)) + email = person_email(o.person) if not email: 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') + print ("NO EMAIL FOR %s %s %s %s %s" % (o.__class__, o.pk, o.person.pk, o.person.first_name, o.person.last_name)).encode('utf-8') return None - e, _ = Email.objects.get_or_create(address=email) + e, _ = Email.objects.select_related("person").get_or_create(address=email) if not e.person: n = u"%s %s" % (o.person.first_name, o.person.last_name) asciified = unaccent.asciify(n) @@ -66,7 +59,7 @@ def get_or_create_email(o, create_fake): if aliases: p = aliases[0].person else: - p = Person.objects.create(name=n, ascii=asciified) + p = Person.objects.create(id=o.person.pk, name=n, ascii=asciified) # FIXME: fill in address? Alias.objects.create(name=n, person=p) if asciified != n: @@ -203,6 +196,14 @@ for o in AreaDirector.objects.all(): Role.objects.get_or_create(name=role_type, group=area, email=email) +# Announcement persons +for o in PersonOrOrgInfo.objects.filter(announcement__announcement_id__gte=1).distinct(): + print "importing Announcement originator", o.person_or_org_tag, o.first_name.encode('utf-8'), o.last_name.encode('utf-8') + + o.person = o # satisfy the get_or_create_email interface + + email = get_or_create_email(o, create_fake=False) + # 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') diff --git a/redesign/importing/__init__.py b/redesign/importing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/redesign/importing/utils.py b/redesign/importing/utils.py new file mode 100644 index 000000000..29ae77809 --- /dev/null +++ b/redesign/importing/utils.py @@ -0,0 +1,14 @@ +def name(name_class, slug, name, desc="", order=0): + # 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.order = order + obj.save() + return obj + +def person_email(person): + hardcoded_emails = { 'Dinara Suleymanova': "dinaras@ietf.org" } + + return person.email()[1] or hardcoded_emails.get("%s %s" % (person.first_name, person.last_name)) + From 7e8b72c9411e0ba1b43c4630119711e7235d9a09 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 5 May 2011 14:58:28 +0000 Subject: [PATCH 49/75] Move import scripts into importing subdirectory, move name() utility from importing/utils.py to name/utils.py - Legacy-Id: 3098 --- redesign/{ => importing}/import-announcements.py | 5 +++-- redesign/{ => importing}/import-document-state.py | 6 +++--- redesign/{ => importing}/import-groups.py | 12 ++---------- redesign/{ => importing}/import-roles.py | 5 +++-- redesign/importing/utils.py | 9 --------- redesign/name/utils.py | 8 ++++++++ 6 files changed, 19 insertions(+), 26 deletions(-) rename redesign/{ => importing}/import-announcements.py (96%) rename redesign/{ => importing}/import-document-state.py (99%) rename redesign/{ => importing}/import-groups.py (96%) rename redesign/{ => importing}/import-roles.py (98%) create mode 100644 redesign/name/utils.py diff --git a/redesign/import-announcements.py b/redesign/importing/import-announcements.py similarity index 96% rename from redesign/import-announcements.py rename to redesign/importing/import-announcements.py index b465a1c22..213b44640 100755 --- a/redesign/import-announcements.py +++ b/redesign/importing/import-announcements.py @@ -2,7 +2,7 @@ import sys, os, re, datetime -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) sys.path = [ basedir ] + sys.path from ietf import settings @@ -14,8 +14,9 @@ management.setup_environ(settings) from redesign.person.models import * from redesign.group.models import * from redesign.announcements.models import * +from redesign.name.utils import name +from redesign.importing.utils import person_email from ietf.announcements.models import Announcement, PersonOrOrgInfo, AnnouncedTo, AnnouncedFrom -from importing.utils import * # assumptions: # - nomcom groups have been imported diff --git a/redesign/import-document-state.py b/redesign/importing/import-document-state.py similarity index 99% rename from redesign/import-document-state.py rename to redesign/importing/import-document-state.py index 05c712267..b8477cdc6 100755 --- a/redesign/import-document-state.py +++ b/redesign/importing/import-document-state.py @@ -2,7 +2,7 @@ import sys, os, re, datetime -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) sys.path = [ basedir ] + sys.path from ietf import settings @@ -14,11 +14,11 @@ management.setup_environ(settings) from redesign.doc.models import * from redesign.group.models import * from redesign.name.models import * +from redesign.importing.utils import person_email +from redesign.name.utils import name from ietf.idtracker.models import InternetDraft, IDInternal, IESGLogin, DocumentComment, PersonOrOrgInfo, Rfc, IESGComment, IESGDiscuss, BallotInfo, Position from ietf.idrfc.models import RfcIndex, DraftVersions -from importing.utils import * - import sys document_name_to_import = None diff --git a/redesign/import-groups.py b/redesign/importing/import-groups.py similarity index 96% rename from redesign/import-groups.py rename to redesign/importing/import-groups.py index 3a4dc051d..48632e966 100755 --- a/redesign/import-groups.py +++ b/redesign/importing/import-groups.py @@ -2,7 +2,7 @@ import sys, os -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) sys.path = [ basedir ] + sys.path from ietf import settings @@ -14,21 +14,13 @@ management.setup_environ(settings) from redesign.group.models import * from redesign.name.models import * +from redesign.name.utils import name from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF, ChairsHistory, Role # imports IETFWG, Area, AreaGroup, Acronym, IRTF # also creates nomcom groups -# make sure we got the names -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 - state_names = dict( bof=name(GroupStateName, slug="bof", name="BOF"), proposed=name(GroupStateName, slug="proposed", name="Proposed"), diff --git a/redesign/import-roles.py b/redesign/importing/import-roles.py similarity index 98% rename from redesign/import-roles.py rename to redesign/importing/import-roles.py index 2ddbca008..e1fe5b118 100755 --- a/redesign/import-roles.py +++ b/redesign/importing/import-roles.py @@ -3,7 +3,7 @@ import sys, os, re, datetime import unaccent -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) sys.path = [ basedir ] + sys.path from ietf import settings @@ -15,9 +15,10 @@ management.setup_environ(settings) from redesign.person.models import * from redesign.group.models import * from redesign.name.models import * +from redesign.name.utils import name +from redesign.importing.utils import person_email from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo, WGChair, WGEditor, WGSecretary, WGTechAdvisor, ChairsHistory, Role as OldRole, Acronym, IRTFChair -from importing.utils import * # assumptions: # - groups have been imported diff --git a/redesign/importing/utils.py b/redesign/importing/utils.py index 29ae77809..92d056f60 100644 --- a/redesign/importing/utils.py +++ b/redesign/importing/utils.py @@ -1,12 +1,3 @@ -def name(name_class, slug, name, desc="", order=0): - # 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.order = order - obj.save() - return obj - def person_email(person): hardcoded_emails = { 'Dinara Suleymanova': "dinaras@ietf.org" } diff --git a/redesign/name/utils.py b/redesign/name/utils.py new file mode 100644 index 000000000..c4c47db94 --- /dev/null +++ b/redesign/name/utils.py @@ -0,0 +1,8 @@ +def name(name_class, slug, name, desc="", order=0): + # 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.order = order + obj.save() + return obj From 1d7d42b22626b36c002526a1c11cd78d5ade3ae9 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 5 May 2011 15:01:03 +0000 Subject: [PATCH 50/75] Port mirror_rfc_editor_queue to new schema, also fix the -??.txt regexp to catch all the current entries in the queue - Legacy-Id: 3099 --- ietf/idrfc/idrfc_wrapper.py | 9 +++ ietf/idrfc/mirror_rfc_editor_queue.py | 79 ++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py index 4408d218f..2ff4f3ab4 100644 --- a/ietf/idrfc/idrfc_wrapper.py +++ b/ietf/idrfc/idrfc_wrapper.py @@ -116,6 +116,15 @@ class IdWrapper: self.publication_date = date(1990,1,1) def rfc_editor_state(self): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + if self._draft.rfc_state: + # extract possible extra states + tags = self._draft.tags.filter(slug__in=("iana-crd", "ref", "missref")) + s = [self._draft.rfc_state.name] + [t.slug.replace("-crd", "").upper() for t in tags] + return " ".join(s) + else: + return None + try: qs = self._draft.rfc_editor_queue_state return qs.state diff --git a/ietf/idrfc/mirror_rfc_editor_queue.py b/ietf/idrfc/mirror_rfc_editor_queue.py index 851bfc858..5ccce2277 100644 --- a/ietf/idrfc/mirror_rfc_editor_queue.py +++ b/ietf/idrfc/mirror_rfc_editor_queue.py @@ -69,8 +69,7 @@ def parse(response): events.expandNode(node) node.normalize() draft_name = getChildText(node, "draft").strip() - if re.search("-\d\d\.txt$", draft_name): - draft_name = draft_name[0:-7] + draft_name = re.sub("(-\d\d)?(.txt){1,2}$", "", draft_name) date_received = getChildText(node, "date-received") states = [] @@ -169,6 +168,9 @@ def parse_all(response): refs.extend(indirect_refs) del(indirect_refs) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: # note: return before id lookup + return (drafts, refs) + # convert filenames to id_document_tags log("connecting to database...") cursor = db.connection.cursor() @@ -190,7 +192,80 @@ def insert_into_database(drafts, refs): cursor.close() db.connection._commit() db.connection.close() + +import django.db.transaction + +def get_rfc_tag_mapping(): + """Return dict with RFC Editor state name -> DocInfoTagName""" + from name.models import DocInfoTagName + from name.utils import name + return { + 'IANA': name(DocInfoTagName, 'iana-crd', 'IANA coordination', "RFC-Editor/IANA Registration Coordination"), + 'REF': name(DocInfoTagName, 'ref', 'Holding for references', "Holding for normative reference"), + 'MISSREF': name(DocInfoTagName, 'missref', 'Missing references', "Awaiting missing normative reference"), + } + +def get_rfc_state_mapping(): + """Return dict with RFC Editor state name -> RfcDocStateName""" + from name.models import RfcDocStateName + from name.utils import name + + return { + 'AUTH': name(RfcDocStateName, 'auth', 'AUTH', "Awaiting author action"), + 'AUTH48': name(RfcDocStateName, 'auth48', "AUTH48", "Awaiting final author approval"), + 'EDIT': name(RfcDocStateName, 'edit', 'EDIT', "Approved by the stream manager (e.g., IESG, IAB, IRSG, ISE), awaiting processing and publishing"), + 'IANA': name(RfcDocStateName, 'iana-crd', 'IANA', "RFC-Editor/IANA Registration Coordination"), + 'IESG': name(RfcDocStateName, 'iesg', 'IESG', "Holding for IESG action"), + 'ISR': name(RfcDocStateName, 'isr', 'ISR', "Independent Submission Review by the ISE "), + 'ISR-AUTH': name(RfcDocStateName, 'isr-auth', 'ISR-AUTH', "Independent Submission awaiting author update, or in discussion between author and ISE"), + 'REF': name(RfcDocStateName, 'ref', 'REF', "Holding for normative reference"), + 'RFC-EDITOR': name(RfcDocStateName, 'rfc-edit', 'RFC-EDITOR', "Awaiting final RFC Editor review before AUTH48"), + 'TO': name(RfcDocStateName, 'timeout', 'TO', "Time-out period during which the IESG reviews document for conflict/concurrence with other IETF working group work"), + 'MISSREF': name(RfcDocStateName, 'missref', 'MISSREF', "Awaiting missing normative reference"), + } + + +@django.db.transaction.commit_on_success +def insert_into_databaseREDESIGN(drafts, refs): + from doc.models import Document + from name.models import DocInfoTagName + + tags = get_rfc_tag_mapping() + states = get_rfc_state_mapping() + + rfc_editor_tags = tags.values() + + log("removing old data...") + for d in Document.objects.exclude(rfc_state=None).filter(tags__in=rfc_editor_tags): + d.tags.remove(*rfc_editor_tags) + + Document.objects.exclude(rfc_state=None).update(rfc_state=None) + + log("inserting new data...") + + for name, date_received, state, stream_id in drafts: + try: + d = Document.objects.get(name=name) + print "known document", name + except Document.DoesNotExist: + log("unknown document %s" % name) + continue + + s = state.split(" ") + if s: + # first is state + d.rfc_state = states[s[0]] + d.save() + + # remainding are tags + for x in s[1:]: + d.tags.add(tags[x]) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + insert_into_database = insert_into_databaseREDESIGN + + if __name__ == '__main__': try: log("output from mirror_rfc_editor_queue.py:\n") From 7343ba8f2885e25555cc03d84d53f9ad542cc572 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 6 May 2011 14:04:16 +0000 Subject: [PATCH 51/75] Ported mirror RFC index to new schema, with the new schema the script updates the Document and DocAlias objects rather than simply dumping the info in a table - Legacy-Id: 3100 --- ietf/idrfc/mirror_rfc_index.py | 169 +++++++++++++++++++- redesign/importing/import-document-state.py | 23 +-- 2 files changed, 173 insertions(+), 19 deletions(-) diff --git a/ietf/idrfc/mirror_rfc_index.py b/ietf/idrfc/mirror_rfc_index.py index e7cbae769..a76ed05d5 100644 --- a/ietf/idrfc/mirror_rfc_index.py +++ b/ietf/idrfc/mirror_rfc_index.py @@ -38,7 +38,7 @@ from django import db from xml.dom import pulldom, Node import re import urllib2 -from datetime import datetime +from datetime import datetime, date, timedelta import socket import sys @@ -147,6 +147,173 @@ def insert_to_database(data): db.connection._commit() db.connection.close() +def get_std_level_mapping(): + from name.models import StdLevelName + from name.utils import name + return { + "Standard": name(StdLevelName, "std", "Standard"), + "Draft Standard": name(StdLevelName, "ds", "Draft Standard"), + "Proposed Standard": name(StdLevelName, "ps", "Proposed Standard"), + "Informational": name(StdLevelName, "inf", "Informational"), + "Experimental": name(StdLevelName, "exp", "Experimental"), + "Best Current Practice": name(StdLevelName, "bcp", "Best Current Practice"), + "Historic": name(StdLevelName, "hist", "Historic"), + "Unknown": name(StdLevelName, "unkn", "Unknown"), + } + +def get_stream_mapping(): + from name.models import DocStreamName + from name.utils import name + + return { + "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"), + } + + +import django.db.transaction + +@django.db.transaction.commit_on_success +def insert_to_databaseREDESIGN(data): + from person.models import Email + from doc.models import Document, DocAlias, Event, RelatedDocument + from group.models import Group + from name.models import DocInfoTagName, DocRelationshipName + from name.utils import name + + system_email = Email.objects.get(address="(System)") + std_level_mapping = get_std_level_mapping() + stream_mapping = get_stream_mapping() + tag_has_errata = name(DocInfoTagName, 'errata', "Has errata") + relationship_obsoletes = name(DocRelationshipName, "obs", "Obsoletes") + relationship_updates = name(DocRelationshipName, "updates", "Updates") + + skip_older_than_date = (date.today() - timedelta(days=365)).strftime("%Y-%m-%d") + + log("updating data...") + for d in data: + rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats = d + + if rfc_published_date < skip_older_than_date: + # speed up the process by skipping old entries + continue + + # we assume two things can happen: we get a new RFC, or an + # attribute has been updated at the RFC Editor (RFC Editor + # attributes currently take precedence) + + # make sure we got the document and alias + created = False + doc = None + name = "rfc%s" % rfc_number + a = DocAlias.objects.filter(name=name) + if a: + doc = a[0].document + else: + if draft: + try: + doc = Document.objects.get(name=draft) + except Document.DoesNotExist: + pass + + if not doc: + created = True + log("created document %s" % name) + doc = Document.objects.create(name=name) + + # add alias + DocAlias.objects.create(name=name, document=doc) + if not created: + created = True + log("created alias %s to %s" % (name, doc.name)) + + + # check attributes + changed = False + if title != doc.title: + doc.title = title + changed = True + + if std_level_mapping[current_status] != doc.std_level: + doc.std_level = std_level_mapping[current_status] + changed = True + + if doc.state_id != "rfc": + doc.state_id = "rfc" + changed = True + + if doc.stream != stream_mapping[stream]: + doc.stream = stream_mapping[stream] + changed = True + + if not doc.group and wg: + doc.group = Group.objects.get(acronym=wg) + changed = True + + pubdate = datetime.strptime(rfc_published_date, "%Y-%m-%d") + if not doc.latest_event(type="published_rfc", time=pubdate): + e = Event(doc=doc, type="published_rfc") + e.time = pubdate + e.by = system_email + e.desc = "RFC published" + e.save() + changed = 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 RFCs that we can handle + # sensibly; otherwise we'll have to ignore them + l = DocAlias.objects.filter(name__startswith="rfc", document__docalias__name=x.lower()) + else: + l = DocAlias.objects.filter(name=x.lower()) + + for a in l: + if a not in res: + res.append(a) + return res + + for x in parse_relation_list(obsoletes): + if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_obsoletes): + RelatedDocument.objects.create(source=doc, target=x, relationship=relationship_obsoletes) + changed = True + + for x in parse_relation_list(updates): + if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_updates): + RelatedDocument.objects.create(source=doc, target=x, relationship=relationship_updates) + changed = True + + if also: + for a in also.lower().split(","): + if not DocAlias.objects.filter(name=a): + DocAlias.objects.create(name=a, document=doc) + changed = True + + if has_errata: + if not doc.tags.filter(pk=tag_has_errata.pk): + doc.tags.add(tag_has_errata) + changed = True + else: + if doc.tags.filter(pk=tag_has_errata.pk): + doc.tags.remove(tag_has_errata) + changed = True + + if changed: + if not created: + log("%s changed" % name) + doc.time = datetime.now() + doc.save() + + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + insert_to_database = insert_to_databaseREDESIGN + if __name__ == '__main__': try: log("output from mirror_rfc_index.py:\n") diff --git a/redesign/importing/import-document-state.py b/redesign/importing/import-document-state.py index b8477cdc6..03e9c2fe5 100755 --- a/redesign/importing/import-document-state.py +++ b/redesign/importing/import-document-state.py @@ -18,6 +18,7 @@ from redesign.importing.utils import person_email from redesign.name.utils import name from ietf.idtracker.models import InternetDraft, IDInternal, IESGLogin, DocumentComment, PersonOrOrgInfo, Rfc, IESGComment, IESGDiscuss, BallotInfo, Position from ietf.idrfc.models import RfcIndex, DraftVersions +from ietf.idrfc.mirror_rfc_index import get_std_level_mapping, get_stream_mapping import sys @@ -52,13 +53,7 @@ def alias_doc(name, doc): type_draft = name(DocTypeName, "draft", "Draft") -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"), - } +stream_mapping = get_stream_mapping() relationship_replaces = name(DocRelationshipName, "replaces", "Replaces") relationship_updates = name(DocRelationshipName, "updates", "Updates") @@ -80,16 +75,7 @@ intended_std_level_mapping = { 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"), - } +std_level_mapping = get_std_level_mapping() state_mapping = { 'Active': name(DocStateName, "active", "Active"), @@ -1016,7 +1002,8 @@ for index, o in enumerate(all_rfcs.iterator()): make_relation(x, relationship_updates, True) if o.also: - alias_doc(o.also.lower(), d) + for a in o.also.lower().split(","): + alias_doc(a, d) sync_tag(d, o.has_errata, tag_has_errata) From 5422874e3eb80cc1acdeb3e5f3ea4d6fbb2ee70a Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 6 May 2011 17:04:16 +0000 Subject: [PATCH 52/75] Proxy state help page - Legacy-Id: 3101 --- ietf/idtracker/models.py | 7 +- redesign/doc/proxy.py | 118 ++++++++++++++++++-- redesign/importing/import-document-state.py | 10 +- 3 files changed, 115 insertions(+), 20 deletions(-) diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py index 69c35cdb1..9f134a11b 100644 --- a/ietf/idtracker/models.py +++ b/ietf/idtracker/models.py @@ -1090,11 +1090,12 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: InternetDraftOld = InternetDraft IDInternalOld = IDInternal BallotInfoOld = BallotInfo + IDStateOld = IDState + IDSubStateOld = IDSubState AreaOld = Area AcronymOld = Acronym - from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo - from redesign.group.proxy import Area - from redesign.group.proxy import Acronym + from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo, IDState, IDSubState + from redesign.group.proxy import Area, Acronym # changes done by convert-096.py:changed maxlength to max_length diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 812d96585..5efb83a40 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -324,18 +324,17 @@ class InternetDraft(Document): #cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs') @property def cur_state(self): - return self.iesg_state + return IDState().from_old_object(self.iesg_state) @property def cur_state_id(self): - # FIXME: results in wrong sort order - return abs(hash(self.iesg_state.slug)) + return self.iesg_state.order if self.iesg_state else None #prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev') @property def prev_state(self): ds = self.dochistory_set.exclude(iesg_state=self.iesg_state).order_by('-time')[:1] - return ds[0].iesg_state if ds else None + return IDState().from_old_object(ds[0].iesg_state) if ds else None #assigned_to = models.CharField(blank=True, max_length=25) # unused @@ -373,20 +372,22 @@ class InternetDraft(Document): @property def cur_sub_state(self): s = self.tags.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point']) - return s[0] if s else None + return IDSubState().from_old_object(s[0]) if s else None @property def cur_sub_state_id(self): s = self.cur_sub_state - return 1 if s else 0 # need to return something numeric + return s.order if s else None #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 + substates = ds[0].tags.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point']) if ds else None + return IDSubState().from_old_object(substates[0]) if substates else None @property def prev_sub_state_id(self): - return 0 + s = self.prev_sub_state + return s.order if s else None #returning_item = models.IntegerField(null=True, blank=True) @property @@ -545,7 +546,7 @@ class InternetDraft(Document): res = [] def add(ad, pos): from person.proxy import IESGLogin as IESGLoginProxy - res.append(dict(ad=IESGLoginProxy(ad), pos=Position(pos) if pos else None)) + res.append(dict(ad=IESGLoginProxy(ad), pos=Position().from_old_object(pos) if pos else None)) found = set() for pos in BallotPositionEvent.objects.filter(doc=self, type="changed_ballot_position", ad__in=active_ads).select_related('ad').order_by("-time", "-id"): @@ -754,10 +755,11 @@ class DocumentComment(Event): class Position(BallotPositionEvent): - def __init__(self, base): + def from_old_object(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)) + return self #ballot = models.ForeignKey(BallotInfo, related_name='positions') @property @@ -779,9 +781,12 @@ class Position(BallotPositionEvent): return self.pos_id == "abstain" #approve = models.IntegerField(default=0) # unused #discuss = models.IntegerField() - @property - def discuss(self): + # needs special treatment because of clash with attribute on base class + def get_discuss(self): return self.pos_id == "discuss" + def set_discuss(self, x): + pass + discuss = property(get_discuss, set_discuss) #recuse = models.IntegerField() @property def recuse(self): @@ -797,3 +802,92 @@ class Position(BallotPositionEvent): return ' ' class Meta: proxy = True + +class IDState(IesgDocStateName): + PUBLICATION_REQUESTED = 10 + LAST_CALL_REQUESTED = 15 + IN_LAST_CALL = 16 + WAITING_FOR_WRITEUP = 18 + WAITING_FOR_AD_GO_AHEAD = 19 + IESG_EVALUATION = 20 + IESG_EVALUATION_DEFER = 21 + APPROVED_ANNOUNCEMENT_SENT = 30 + AD_WATCHING = 42 + DEAD = 99 + DO_NOT_PUBLISH_STATES = (33, 34) + + def from_old_object(self, base): + for f in base._meta.fields: + setattr(self, f.name, getattr(base, f.name)) + return self + + #document_state_id = models.AutoField(primary_key=True) + @property + def document_state_id(self): + return self.order + + #state = models.CharField(max_length=50, db_column='document_state_val') + @property + def state(self): + return self.name + + #equiv_group_flag = models.IntegerField(null=True, blank=True) # unused + #description = models.TextField(blank=True, db_column='document_desc') + @property + def description(self): + return self.desc + + @property + def nextstate(self): + # simulate related queryset + from name.models import get_next_iesg_states + return IDState.objects.filter(pk__in=[x.pk for x in get_next_iesg_states(self)]) + + @property + def next_state(self): + # simulate IDNextState + return self + + def __str__(self): + return self.state + + @staticmethod + def choices(): + return [(state.slug, state.name) for state in IDState.objects.all()] + + class Meta: + proxy = True + + +class IDSubStateManager(models.Manager): + def all(self): + return self.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point']) + +class IDSubState(DocInfoTagName): + objects = IDSubStateManager() + + def from_old_object(self, base): + for f in base._meta.fields: + setattr(self, f.name, getattr(base, f.name)) + return self + + #sub_state_id = models.AutoField(primary_key=True) + @property + def sub_state_id(self): + return self.order + + #sub_state = models.CharField(max_length=55, db_column='sub_state_val') + @property + def sub_state(self): + return self.name + + #description = models.TextField(blank=True, db_column='sub_state_desc') + @property + def description(self): + return self.desc + + def __str__(self): + return self.sub_state + + class Meta: + proxy = True diff --git a/redesign/importing/import-document-state.py b/redesign/importing/import-document-state.py index 03e9c2fe5..15c2d8f0f 100755 --- a/redesign/importing/import-document-state.py +++ b/redesign/importing/import-document-state.py @@ -94,7 +94,7 @@ iesg_state_mapping = { '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).', order=42), '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.', order=20), '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.', order=11), - '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.', order=15), + '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.', order=15), '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.', order=16), '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.', order=10), '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).', order=31), @@ -124,16 +124,16 @@ 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.'), - "Revised ID Needed": name(DocInfoTagName, 'need-rev', "Revised ID Needed", 'An updated ID is needed to address the issues that have been raised.'), + "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.', 3), + "Revised ID Needed": name(DocInfoTagName, 'need-rev', "Revised ID Needed", 'An updated ID is needed to address the issues that have been raised.', 5), "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.') +- 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.""", 2), + "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.', 1) } tag_review_by_rfc_editor = name(DocInfoTagName, 'rfc-rev', "Review by RFC Editor") From 8764a1db11cc25ab71e3ad84c159b6ad4a66d5ef Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Wed, 11 May 2011 18:08:20 +0000 Subject: [PATCH 53/75] Port idtracker/views.py, fixing some bugs in the proxy classes - Legacy-Id: 3116 --- ietf/idtracker/views.py | 24 +++++++++- redesign/doc/proxy.py | 38 ++++++++++------ redesign/group/proxy.py | 12 +++-- redesign/importing/import-document-state.py | 2 +- redesign/proxy_utils.py | 50 ++++++++++----------- 5 files changed, 83 insertions(+), 43 deletions(-) diff --git a/ietf/idtracker/views.py b/ietf/idtracker/views.py index 825ab0f62..d525274d8 100644 --- a/ietf/idtracker/views.py +++ b/ietf/idtracker/views.py @@ -2,11 +2,12 @@ # Create your views here. from django.http import HttpResponsePermanentRedirect, Http404 +from django.conf import settings from django.template import RequestContext from django.shortcuts import get_object_or_404, render_to_response from django.views.generic.list_detail import object_detail, object_list from ietf.idtracker.models import InternetDraft, IDInternal, IDState, IDSubState, BallotInfo, DocumentComment -import re +import re, datetime def state_desc(request, state, is_substate=0): if int(state) == 100: @@ -27,15 +28,30 @@ IESG to do anything with the document. context_instance=RequestContext(request)) def status(request): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + drafts = list(IDInternal.objects.exclude(iesg_state=None).exclude(iesg_state__in=('pub', 'dead', 'watching', 'rfcqueue')).order_by('iesg_state__order')) + drafts.sort(key=lambda d: (d.cur_state_id, d.status_date or datetime.date.min, d.b_sent_date or datetime.date.min)) + # sadly we can't use the generic view because it only works with a queryset... + return render_to_response('idtracker/status_of_items.html', dict(object_list=drafts, title="IESG Status of Items"), context_instance=RequestContext(request)) + queryset = IDInternal.objects.filter(primary_flag=1).exclude(cur_state__state__in=('RFC Ed Queue', 'RFC Published', 'AD is watching', 'Dead')).order_by('cur_state', 'status_date', 'ballot') return object_list(request, template_name="idtracker/status_of_items.html", queryset=queryset, extra_context={'title': 'IESG Status of Items'}) def last_call(request): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + drafts = list(IDInternal.objects.exclude(iesg_state=None).filter(iesg_state__in=('lc', 'writeupw', 'goaheadw')).order_by('iesg_state__order')) + drafts.sort(key=lambda d: (d.cur_state_id, d.status_date or datetime.date.min, d.b_sent_date or datetime.date.min)) + # sadly we can't use the generic view because it only works with a queryset... + return render_to_response('idtracker/status_of_items.html', dict(object_list=drafts, title="Documents in Last Call", lastcall=1), context_instance=RequestContext(request)) + queryset = IDInternal.objects.filter(primary_flag=1).filter(cur_state__state__in=('In Last Call', 'Waiting for Writeup', 'Waiting for AD Go-Ahead')).order_by('cur_state', 'status_date', 'ballot') return object_list(request, template_name="idtracker/status_of_items.html", queryset=queryset, extra_context={'title': 'Documents in Last Call', 'lastcall': 1}) def redirect_id(request, object_id): '''Redirect from historical document ID to preferred filename url.''' + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + raise Http404() # we don't store the numbers anymore + doc = get_object_or_404(InternetDraft, id_document_tag=object_id) return HttpResponsePermanentRedirect("/doc/"+doc.filename+"/") @@ -46,6 +62,9 @@ def redirect_filename(request, filename): return HttpResponsePermanentRedirect("/doc/"+filename+"/") def redirect_ballot(request, object_id): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + raise Http404() # we don't store the numbers anymore + ballot = get_object_or_404(BallotInfo, pk=object_id) ids = ballot.drafts.filter(primary_flag=1) if len(ids) == 0: @@ -57,6 +76,9 @@ def redirect_ballot(request, object_id): return HttpResponsePermanentRedirect("/doc/"+id.draft.filename+"/#ballot") def redirect_comment(request, object_id): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + raise Http404() # we don't store the numbers anymore + comment = get_object_or_404(DocumentComment, pk=object_id) id = comment.document if id.rfc_flag: diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 5efb83a40..ae7ba9909 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -13,6 +13,7 @@ class InternetDraft(Document): 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), + cur_state="iesg_state__order", )) DAYS_TO_EXPIRE=185 @@ -324,7 +325,7 @@ class InternetDraft(Document): #cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs') @property def cur_state(self): - return IDState().from_old_object(self.iesg_state) + return IDState().from_object(self.iesg_state) if self.iesg_state else None @property def cur_state_id(self): @@ -334,7 +335,7 @@ class InternetDraft(Document): @property def prev_state(self): ds = self.dochistory_set.exclude(iesg_state=self.iesg_state).order_by('-time')[:1] - return IDState().from_old_object(ds[0].iesg_state) if ds else None + return IDState().from_object(ds[0].iesg_state) if ds else None #assigned_to = models.CharField(blank=True, max_length=25) # unused @@ -360,11 +361,17 @@ class InternetDraft(Document): #area_acronym = models.ForeignKey(Area) @property def area_acronym(self): - if self.group: - return self.group.parent + from group.proxy import Area + g = super(self.__class__, self).group # be careful with group which is proxied + if g and g.type_id != "individ": + return Area().from_object(g.parent) elif self.ad: # return area for AD - return ad.role_set.get(type="ad", group__state="active").group + try: + area = Group.objects.get(role__name="ad", role__email=self.ad, state="active") + return Area().from_object(area) + except Group.DoesNotExist: + return None else: return None @@ -372,7 +379,7 @@ class InternetDraft(Document): @property def cur_sub_state(self): s = self.tags.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point']) - return IDSubState().from_old_object(s[0]) if s else None + return IDSubState().from_object(s[0]) if s else None @property def cur_sub_state_id(self): s = self.cur_sub_state @@ -383,7 +390,7 @@ class InternetDraft(Document): def prev_sub_state(self): ds = self.dochistory_set.all().order_by('-time')[:1] substates = ds[0].tags.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point']) if ds else None - return IDSubState().from_old_object(substates[0]) if substates else None + return IDSubState().from_object(substates[0]) if substates else None @property def prev_sub_state_id(self): s = self.prev_sub_state @@ -546,7 +553,7 @@ class InternetDraft(Document): res = [] def add(ad, pos): from person.proxy import IESGLogin as IESGLoginProxy - res.append(dict(ad=IESGLoginProxy(ad), pos=Position().from_old_object(pos) if pos else None)) + res.append(dict(ad=IESGLoginProxy(ad), pos=Position().from_object(pos) if pos else None)) found = set() for pos in BallotPositionEvent.objects.filter(doc=self, type="changed_ballot_position", ad__in=active_ads).select_related('ad').order_by("-time", "-id"): @@ -755,7 +762,7 @@ class DocumentComment(Event): class Position(BallotPositionEvent): - def from_old_object(self, base): + def from_object(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)) @@ -816,7 +823,9 @@ class IDState(IesgDocStateName): DEAD = 99 DO_NOT_PUBLISH_STATES = (33, 34) - def from_old_object(self, base): + objects = TranslatingManager(dict(pk="order")) + + def from_object(self, base): for f in base._meta.fields: setattr(self, f.name, getattr(base, f.name)) return self @@ -859,14 +868,17 @@ class IDState(IesgDocStateName): proxy = True -class IDSubStateManager(models.Manager): +class IDSubStateManager(TranslatingManager): + def __init__(self, *args): + super(IDSubStateManager, self).__init__(*args) + def all(self): return self.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point']) class IDSubState(DocInfoTagName): - objects = IDSubStateManager() + objects = IDSubStateManager(dict(pk="order")) - def from_old_object(self, base): + def from_object(self, base): for f in base._meta.fields: setattr(self, f.name, getattr(base, f.name)) return self diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py index d52792905..428b3d25b 100644 --- a/redesign/group/proxy.py +++ b/redesign/group/proxy.py @@ -7,9 +7,10 @@ class Acronym(Group): INDIVIDUAL_SUBMITTER = LazyIndividualSubmitter() - def __init__(self, base): + def from_object(self, base): for f in base._meta.fields: setattr(self, f.name, getattr(base, f.name)) + return self #acronym_id = models.AutoField(primary_key=True) @property @@ -32,11 +33,16 @@ class Acronym(Group): proxy = True class Area(Group): + def from_object(self, base): + for f in base._meta.fields: + setattr(self, f.name, getattr(base, f.name)) + return self + ACTIVE=1 #area_acronym = models.OneToOneField(Acronym, primary_key=True) @property def area_acronym(self): - return Acronym(self) + return Acronym().from_object(self) #start_date = models.DateField(auto_now_add=True) #concluded_date = models.DateField(null=True, blank=True) @@ -63,7 +69,7 @@ class IETFWG(Group): #group_acronym = models.OneToOneField(Acronym, primary_key=True, editable=False) @property def group_acronym(self): - return Acronym(self) + return Acronym().from_object(self) #group_type = models.ForeignKey(WGType) #proposed_date = models.DateField(null=True, blank=True) diff --git a/redesign/importing/import-document-state.py b/redesign/importing/import-document-state.py index 15c2d8f0f..961635054 100755 --- a/redesign/importing/import-document-state.py +++ b/redesign/importing/import-document-state.py @@ -755,7 +755,7 @@ if document_name_to_import: #all_drafts = all_drafts.none() for index, o in enumerate(all_drafts.iterator()): - print "importing", o.id_document_tag, o.filename, index + print "importing", o.id_document_tag, o.filename, index, "ballot %s" % o.idinternal.ballot_id if o.idinternal and o.idinternal.ballot_id else "" try: d = Document.objects.get(name=o.filename) diff --git a/redesign/proxy_utils.py b/redesign/proxy_utils.py index 2f55c379b..6dd1493a8 100644 --- a/redesign/proxy_utils.py +++ b/redesign/proxy_utils.py @@ -40,100 +40,100 @@ class TranslatingQuerySet(QuerySet): # overridden methods def _clone(self, *args, **kwargs): - c = super(self.__class__, self)._clone(*args, **kwargs) + c = super(TranslatingQuerySet, 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) + return super(TranslatingQuerySet, self).dates(*args, **kwargs) def distinct(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).distinct(*args, **kwargs) + return super(TranslatingQuerySet, self).distinct(*args, **kwargs) def extra(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).extra(*args, **kwargs) + return super(TranslatingQuerySet, self).extra(*args, **kwargs) def get(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).get(*args, **kwargs) + return super(TranslatingQuerySet, self).get(*args, **kwargs) def get_or_create(self, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).get_or_create(**kwargs) + return super(TranslatingQuerySet, self).get_or_create(**kwargs) def create(self, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).create(**kwargs) + return super(TranslatingQuerySet, self).create(**kwargs) def filter(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).filter(*args, **kwargs) + return super(TranslatingQuerySet, self).filter(*args, **kwargs) def aggregate(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).aggregate(*args, **kwargs) + return super(TranslatingQuerySet, self).aggregate(*args, **kwargs) def annotate(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).annotate(*args, **kwargs) + return super(TranslatingQuerySet, self).annotate(*args, **kwargs) def complex_filter(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).complex_filter(*args, **kwargs) + return super(TranslatingQuerySet, self).complex_filter(*args, **kwargs) def exclude(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).exclude(*args, **kwargs) + return super(TranslatingQuerySet, self).exclude(*args, **kwargs) def in_bulk(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).in_bulk(*args, **kwargs) + return super(TranslatingQuerySet, self).in_bulk(*args, **kwargs) def iterator(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).iterator(*args, **kwargs) + return super(TranslatingQuerySet, self).iterator(*args, **kwargs) def latest(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).latest(*args, **kwargs) + return super(TranslatingQuerySet, 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) + return super(TranslatingQuerySet, 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) + return super(TranslatingQuerySet, 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) + return super(TranslatingQuerySet, 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) + return super(TranslatingQuerySet, self).values_list(*args, **kwargs) def update(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).update(*args, **kwargs) + return super(TranslatingQuerySet, self).update(*args, **kwargs) def reverse(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).reverse(*args, **kwargs) + return super(TranslatingQuerySet, self).reverse(*args, **kwargs) def defer(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).defer(*args, **kwargs) + return super(TranslatingQuerySet, self).defer(*args, **kwargs) def only(self, *args, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self).only(*args, **kwargs) + return super(TranslatingQuerySet, self).only(*args, **kwargs) def _insert(self, values, **kwargs): kwargs = self.translated_kwargs(kwargs) @@ -141,7 +141,7 @@ class TranslatingQuerySet(QuerySet): def _update(self, values, **kwargs): kwargs = self.translated_kwargs(kwargs) - return super(self.__class__, self)._update(values, **kwargs) + return super(TranslatingQuerySet, self)._update(values, **kwargs) class TranslatingManager(Manager): """Translates keyword arguments for the ORM, for use in proxy @@ -151,7 +151,7 @@ class TranslatingManager(Manager): with the right-hand side to transform it.""" def __init__(self, trans): - super(self.__class__, self).__init__() + super(TranslatingManager, self).__init__() self.translated_attrs = trans def get_query_set(self): From e12cd7d662d1494fec636754f65b86827eb645b3 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Wed, 11 May 2011 18:50:47 +0000 Subject: [PATCH 54/75] Port idtracker/feeds.py - Legacy-Id: 3119 --- ietf/idtracker/feeds.py | 15 ++++++++++++--- redesign/doc/proxy.py | 25 +++++++++++++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/ietf/idtracker/feeds.py b/ietf/idtracker/feeds.py index be373032f..00bf8fff5 100644 --- a/ietf/idtracker/feeds.py +++ b/ietf/idtracker/feeds.py @@ -1,5 +1,6 @@ # Copyright The IETF Trust 2007, All Rights Reserved +from django.conf import settings from django.contrib.syndication.feeds import Feed, FeedDoesNotExist from django.utils.feedgenerator import Atom1Feed from ietf.idtracker.models import IDInternal @@ -12,6 +13,9 @@ class DocumentComments(Feed): if len(bits) != 1: raise IDInternal.DoesNotExist rfc = re.match('rfc(\d+)', bits[0]) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + return IDInternal.objects.get(docalias__name=bits[0]) + if rfc: return IDInternal.objects.get(draft=int(rfc.group(1)), rfc_flag=1) else: @@ -21,6 +25,9 @@ class DocumentComments(Feed): # filename is a function for RFCs and an attribute for I-Ds. # This works transparently for templates but is not transparent # for python. + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + return "I-D Tracker comments for %s" % obj.filename + if obj.rfc_flag: filename = obj.document().filename() else: @@ -39,8 +46,7 @@ class DocumentComments(Feed): return obj.public_comments().order_by("-date","-id") def item_pubdate(self, item): - time = datetime.time(*[(t and int(t) or 0) for t in item.time.split(":")]) - return datetime.datetime.combine(item.date, time) + return item.datetime() def item_author_name(self, item): return item.get_author() @@ -52,7 +58,10 @@ class InLastCall(Feed): link = "/idtracker/status/last-call/" def items(self): - ret = list(IDInternal.objects.filter(primary_flag=1).filter(cur_state__state='In Last Call')) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + ret = list(IDInternal.objects.filter(iesg_state='lc')) + else: + ret = list(IDInternal.objects.filter(primary_flag=1).filter(cur_state__state='In Last Call')) ret.sort(key=lambda item: (item.document().lc_expiration_date or datetime.date.today())) return ret diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index ae7ba9909..57123c0fe 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -33,7 +33,7 @@ class InternetDraft(Document): @property def group(self): from group.proxy import Acronym as AcronymProxy - g = super(self.__class__, self).group + g = super(InternetDraft, self).group return AcronymProxy(g) if g else None #filename = models.CharField(max_length=255, unique=True) @property @@ -138,7 +138,7 @@ class InternetDraft(Document): return int(aliases[0].name[3:]) if aliases else None #comments = models.TextField(blank=True) # unused - + #last_modified_date = models.DateField() @property def last_modified_date(self): @@ -362,7 +362,7 @@ class InternetDraft(Document): @property def area_acronym(self): from group.proxy import Area - g = super(self.__class__, self).group # be careful with group which is proxied + g = super(InternetDraft, self).group # be careful with group which is proxied if g and g.type_id != "individ": return Area().from_object(g.parent) elif self.ad: @@ -461,6 +461,9 @@ class InternetDraft(Document): def comments(self): return DocumentComment.objects.filter(doc=self).order_by('-time') + def public_comments(self): + return self.comments() + def ballot_set(self): return [self] def ballot_primary(self): @@ -721,6 +724,7 @@ class IDAuthor(DocumentAuthor): class DocumentComment(Event): objects = TranslatingManager(dict(comment_text="desc", + date="time" )) BALLOT_DISCUSS = 1 @@ -741,21 +745,30 @@ class DocumentComment(Event): 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) + @property + def version(self): + e = self.doc.latest_event(NewRevisionEvent, type="new_revision", time__lte=self.time) + return e.rev if e else "0" #comment_text = models.TextField(blank=True) + @property + def comment_text(self): + return self.desc #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) + return "/doc/%s/" % self.doc.name def get_author(self): - return unicode(self.by) + return self.by.get_name() def get_username(self): return unicode(self.by) def get_fullname(self): - return unicode(self.by) + return self.by.get_name() def datetime(self): return self.time + def __str__(self): + return "\"%s...\" by %s" % (self.comment_text[:20], self.get_author()) class Meta: proxy = True From f22e864d62a532c71a77c444bd32008594f4aec6 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 13 May 2011 15:08:40 +0000 Subject: [PATCH 55/75] Port idtracker/sitemaps.py to new schema - Legacy-Id: 3125 --- ietf/idtracker/sitemaps.py | 6 +++++- ietf/urls.py | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ietf/idtracker/sitemaps.py b/ietf/idtracker/sitemaps.py index 7281c4cdc..650a4a94a 100644 --- a/ietf/idtracker/sitemaps.py +++ b/ietf/idtracker/sitemaps.py @@ -1,12 +1,16 @@ # Copyright The IETF Trust 2007, All Rights Reserved # from django.contrib.sitemaps import Sitemap +from django.conf import settings from ietf.idtracker.models import IDInternal, InternetDraft class IDTrackerMap(Sitemap): changefreq = "always" def items(self): - return IDInternal.objects.exclude(draft=999999) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + return IDInternal.objects.all() + else: + return IDInternal.objects.exclude(draft=999999) class DraftMap(Sitemap): changefreq = "always" diff --git a/ietf/urls.py b/ietf/urls.py index 41ce897bc..215b981bd 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -36,6 +36,9 @@ sitemaps = { 'nomcom-announcements': NOMCOMAnnouncementsMap, } +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + del sitemaps['drafts'] # not needed, overlaps sitemaps['idtracker'] + urlpatterns = patterns('', (r'^feed/(?P.*)/$', 'django.contrib.syndication.views.feed', { 'feed_dict': feeds}), From 6a927fbed4d386c5305c2ef9d4f8ce4174defc32 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 13 May 2011 17:45:29 +0000 Subject: [PATCH 56/75] Port iesg.feeds - Legacy-Id: 3126 --- ietf/iesg/feeds.py | 18 +++++++++++++++++- redesign/doc/proxy.py | 14 +++++++------- redesign/person/proxy.py | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/ietf/iesg/feeds.py b/ietf/iesg/feeds.py index bd326f9d8..356f2d697 100644 --- a/ietf/iesg/feeds.py +++ b/ietf/iesg/feeds.py @@ -1,5 +1,6 @@ # Copyright The IETF Trust 2007, 2008, All Rights Reserved +from django.conf import settings from django.contrib.syndication.feeds import Feed from django.utils.feedgenerator import Atom1Feed from ietf.idtracker.models import IDInternal @@ -11,12 +12,24 @@ class IESGAgenda(Feed): feed_type = Atom1Feed def items(self): - return IDInternal.objects.filter(agenda=1).order_by('telechat_date') + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import TelechatEvent + drafts = IDInternal.objects.filter(event__telechatevent__telechat_date__gte=datetime.date.min).distinct() + for d in drafts: + d.latest_telechat_event = d.latest_event(TelechatEvent, type="scheduled_for_telechat") + drafts = [d for d in drafts if d.latest_telechat_event.telechat_date] + drafts.sort(key=lambda d: d.latest_telechat_event.telechat_date) + return drafts + + return IDInternal.objects.filter(agenda=1).order_by('telechat_date') def item_categories(self, item): return [ str(item.telechat_date) ] def item_pubdate(self, item): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + return item.latest_telechat_event.time + f = item.comments().filter(comment_text__startswith='Placed on agenda for telechat') try: comment = f[0] @@ -28,4 +41,7 @@ class IESGAgenda(Feed): def item_author_name(self, item): return str( item.job_owner ) def item_author_email(self, item): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + return item.ad.address + return item.job_owner.person.email()[1] diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 57123c0fe..1c744f28e 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -344,13 +344,13 @@ class InternetDraft(Document): def mark_by(self): e = self.latest_event() from person.proxy import IESGLogin as IESGLoginProxy - return IESGLoginProxy(e.by) if e else None + return IESGLoginProxy().from_object(e.by) if e else None # job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents') @property def job_owner(self): from person.proxy import IESGLogin as IESGLoginProxy - return IESGLoginProxy(self.ad) if self.ad else None + return IESGLoginProxy().from_object(self.ad) if self.ad else None #event_date = models.DateField(null=True) @property @@ -441,7 +441,7 @@ class InternetDraft(Document): def resurrect_requested_by(self): 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 + return IESGLoginProxy().from_object(e.by) if e and e.type == "requested_resurrect" else None #approved_in_minute = models.IntegerField(null=True, blank=True) @property @@ -450,7 +450,7 @@ class InternetDraft(Document): def get_absolute_url(self): - if self.rfc_flag: + if self.rfc_flag and self.rfc_number: return "/doc/rfc%d/" % self.rfc_number else: return "/doc/%s/" % self.name @@ -502,7 +502,7 @@ class InternetDraft(Document): def an_sent_by(self): e = self.latest_event(type="iesg_approved") from person.proxy import IESGLogin as IESGLoginProxy - return IESGLoginProxy(e.by) if e else None + return IESGLoginProxy().from_object(e.by) if e else None #defer = models.BooleanField() @property @@ -515,7 +515,7 @@ class InternetDraft(Document): def defer_by(self): 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 + return IESGLoginProxy().from_object(e.by) if e else None #defer_date = models.DateField(null=True, blank=True) @property @@ -556,7 +556,7 @@ class InternetDraft(Document): res = [] def add(ad, pos): from person.proxy import IESGLogin as IESGLoginProxy - res.append(dict(ad=IESGLoginProxy(ad), pos=Position().from_object(pos) if pos else None)) + res.append(dict(ad=IESGLoginProxy().from_object(ad), pos=Position().from_object(pos) if pos else None)) found = set() for pos in BallotPositionEvent.objects.filter(doc=self, type="changed_ballot_position", ad__in=active_ads).select_related('ad').order_by("-time", "-id"): diff --git a/redesign/person/proxy.py b/redesign/person/proxy.py index bc12de1a4..42a5cd7e2 100644 --- a/redesign/person/proxy.py +++ b/redesign/person/proxy.py @@ -1,7 +1,7 @@ from models import * class IESGLogin(Email): - def __init__(self, base): + def from_object(self, base): for f in base._meta.fields: setattr(self, f.name, getattr(base, f.name)) From 9f03d0ecd697696de96636a3f3b579031df2a7f9 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 13 May 2011 18:31:58 +0000 Subject: [PATCH 57/75] Port iesg.views.inddocs - Legacy-Id: 3127 --- ietf/iesg/views.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index 3e7323ef2..65c72845a 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -59,6 +59,18 @@ def date_threshold(): return ret def inddocs(request): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + queryset_list_ind = [d for d in InternetDraft.objects.filter(tags__slug="via-rfc", event__type="iesg_approved").distinct() if d.latest_event(type__in=("iesg_disapproved", "iesg_approved")).type == "iesg_approved"] + queryset_list_ind.sort(key=lambda d: d.b_approve_date, reverse=True) + + queryset_list_ind_dnp = [d for d in IDInternal.objects.filter(tags__slug="via-rfc", event__type="iesg_disapproved").distinct() if d.latest_event(type__in=("iesg_disapproved", "iesg_approved")).type == "iesg_disapproved"] + queryset_list_ind_dnp.sort(key=lambda d: d.dnp_date, reverse=True) + + return render_to_response('iesg/independent_doc.html', + dict(object_list=queryset_list_ind, + object_list_dnp=queryset_list_ind_dnp), + context_instance=RequestContext(request)) + queryset_list_ind = InternetDraft.objects.filter(idinternal__via_rfc_editor=1, idinternal__rfc_flag=0, idinternal__noproblem=1, idinternal__dnp=0).order_by('-b_approve_date') queryset_list_ind_dnp = IDInternal.objects.filter(via_rfc_editor = 1,rfc_flag=0,dnp=1).order_by('-dnp_date') return object_list(request, queryset=queryset_list_ind, template_name='iesg/independent_doc.html', allow_empty=True, extra_context={'object_list_dnp':queryset_list_ind_dnp }) From c04f1071770dff9be043436967d8530a885f3172 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 16 May 2011 17:59:43 +0000 Subject: [PATCH 58/75] Port a good deal of the agenda views in iesg.views - Legacy-Id: 3128 --- ietf/idrfc/templatetags/ballot_icon.py | 2 +- ietf/iesg/models.py | 1 + ietf/iesg/views.py | 145 +++++++++++++++++++++++-- ietf/ipr/models.py | 24 +++- ietf/templates/iesg/scribe_doc.html | 11 +- redesign/doc/proxy.py | 32 +++++- redesign/group/proxy.py | 5 + redesign/person/proxy.py | 3 +- 8 files changed, 200 insertions(+), 23 deletions(-) diff --git a/ietf/idrfc/templatetags/ballot_icon.py b/ietf/idrfc/templatetags/ballot_icon.py index 7e73d2d95..ffd642384 100644 --- a/ietf/idrfc/templatetags/ballot_icon.py +++ b/ietf/idrfc/templatetags/ballot_icon.py @@ -67,7 +67,7 @@ def render_ballot_icon(context, doc): return "" if str(doc.cur_state) not in BALLOT_ACTIVE_STATES: return "" - if doc.rfc_flag: + if doc.rfc_flag and not settings.USE_DB_REDESIGN_PROXY_CLASSES: name = doc.document().filename() else: name = doc.document().filename diff --git a/ietf/iesg/models.py b/ietf/iesg/models.py index f93fd7f28..899aaee1c 100644 --- a/ietf/iesg/models.py +++ b/ietf/iesg/models.py @@ -107,6 +107,7 @@ class WGAction(models.Model): (22, "WG Rechartering::Under evaluation for IETF Review"), (23, "WG Rechartering::Proposed for Approval") ) + # note that with the new schema, Acronym is monkey-patched and is really Group group_acronym = models.ForeignKey(Acronym, db_column='group_acronym_id', primary_key=True, unique=True) note = models.TextField(blank=True,null=True) status_date = models.DateField() diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index 65c72845a..f29e0983c 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -106,6 +106,61 @@ def wgdocs(request,cat): queryset_list_doc.append(sub_item2) return render_to_response( 'iesg/ietf_doc.html', {'object_list': queryset_list, 'object_list_doc':queryset_list_doc, 'is_recent':is_recent}, context_instance=RequestContext(request) ) +def wgdocsREDESIGN(request,cat): + is_recent = 0 + proto_actions = [] + doc_actions = [] + threshold = date_threshold() + + proto_levels = ["bcp", "ds", "ps", "std"] + doc_levels = ["exp", "inf"] + + if cat == 'new': + is_recent = 1 + + drafts = InternetDraft.objects.filter(event__type="iesg_approved", event__time__gte=threshold, intended_std_level__in=proto_levels + doc_levels).exclude(tags__slug="via-rfc").distinct() + for d in drafts: + if d.b_approve_date and d.b_approve_date >= threshold: + if d.intended_std_level_id in proto_levels: + proto_actions.append(d) + elif d.intended_std_level_id in doc_levels: + doc_actions.append(d) + + elif cat == 'prev': + # proto + start_date = datetime.date(1997, 12, 1) + + drafts = InternetDraft.objects.filter(event__type="iesg_approved", event__time__lt=threshold, event__time__gte=start_date, intended_std_level__in=proto_levels).exclude(tags__slug="via-rfc").distinct() + + for d in drafts: + if d.b_approve_date and start_date <= d.b_approve_date < threshold: + proto_actions.append(d) + + # doc + start_date = datetime.date(1998, 10, 15) + + drafts = InternetDraft.objects.filter(event__type="iesg_approved", event__time__lt=threshold, event__time__gte=start_date, intended_std_level__in=doc_levels).exclude(tags__slug="via-rfc").distinct() + + for d in drafts: + if d.b_approve_date and start_date <= d.b_approve_date < threshold: + doc_actions.append(d) + else: + raise Http404 + + proto_actions.sort(key=lambda d: d.b_approve_date, reverse=True) + doc_actions.sort(key=lambda d: d.b_approve_date, reverse=True) + + return render_to_response('iesg/ietf_doc.html', + dict(object_list=proto_actions, + object_list_doc=doc_actions, + is_recent=is_recent, + title_prefix="Recent" if is_recent else "Previous"), + context_instance=RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + wgdocs = wgdocsREDESIGN + + def get_doc_section(id): states = [16,17,18,19,20,21] if id.document().intended_status.intended_status_id in [1,2,6,7]: @@ -130,14 +185,63 @@ def get_doc_section(id): s = s + "1" return s -def agenda_docs(date, next_agenda): - if next_agenda: - matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1) +def get_doc_sectionREDESIGN(id): + states = [16,17,18,19,20,21] + if id.intended_std_level_id in ["bcp", "ds", "ps", "std"]: + s = "2" else: - matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1) - idmatches = matches.filter(rfc_flag=0).order_by('ballot') - rfcmatches = matches.filter(rfc_flag=1).order_by('ballot') - res = {} + s = "3" + + g = id.document().group_acronym() + if g and str(g) != 'none': + s = s + "1" + elif (s == "3") and id.via_rfc_editor: + s = s + "3" + else: + s = s + "2" + if not id.rfc_flag and id.cur_state.document_state_id not in states: + s = s + "3" + elif id.returning_item: + s = s + "2" + else: + s = s + "1" + return s + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + get_doc_section = get_doc_sectionREDESIGN + +def agenda_docs(date, next_agenda): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import TelechatEvent + + matches = IDInternal.objects.filter(event__telechatevent__telechat_date=date) + + idmatches = [] + rfcmatches = [] + + for m in matches: + if m.latest_event(TelechatEvent, type="scheduled_for_telechat").telechat_date != date: + continue + + if next_agenda and not m.agenda: + continue + + if m.docalias_set.filter(name__startswith="rfc"): + rfcmatches.append(m) + else: + idmatches.append(m) + + idmatches.sort(key=lambda d: d.start_date or datetime.date.min) + rfcmatches.sort(key=lambda d: d.start_date or datetime.date.min) + else: + if next_agenda: + matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1) + else: + matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1) + idmatches = matches.filter(rfc_flag=0).order_by('ballot') + rfcmatches = matches.filter(rfc_flag=1).order_by('ballot') + + res = dict(("s%s%s%s" % (i, j, k), []) for i in range(2, 5) for j in range (1, 4) for k in range(1, 4)) for id in list(idmatches)+list(rfcmatches): section_key = "s"+get_doc_section(id) if section_key not in res: @@ -200,7 +304,7 @@ def agenda_txt(request): def agenda_scribe_template(request): date = TelechatDates.objects.all()[0].date1 docs = agenda_docs(date, True) - return render_to_response('iesg/scribe_template.html', {'date':str(date), 'docs':docs}, context_instance=RequestContext(request) ) + return render_to_response('iesg/scribe_template.html', {'date':str(date), 'docs':docs, 'USE_DB_REDESIGN_PROXY_CLASSES': settings.USE_DB_REDESIGN_PROXY_CLASSES}, context_instance=RequestContext(request) ) def _agenda_moderator_package(request): data = _agenda_data(request) @@ -235,7 +339,13 @@ def agenda_documents_txt(request): dates = TelechatDates.objects.all()[0].dates() docs = [] for date in dates: - docs.extend(IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import TelechatEvent + for d in IDInternal.objects.filter(event__telechatevent__telechat_date=date): + if d.latest_event(TelechatEvent, type="scheduled_for_telechat").telechat_date == date: + docs.append(d) + else: + docs.extend(IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)) t = loader.get_template('iesg/agenda_documents.txt') c = Context({'docs':docs}) return HttpResponse(t.render(c), mimetype='text/plain') @@ -284,8 +394,18 @@ def handle_reschedule_form(request, idinternal, dates): def agenda_documents(request): dates = TelechatDates.objects.all()[0].dates() - idinternals = list(IDInternal.objects.filter(telechat_date__in=dates,primary_flag=1,agenda=1).order_by('rfc_flag', 'ballot')) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import TelechatEvent + idinternals = [] + for d in IDInternal.objects.filter(event__telechatevent__telechat_date__in=dates): + if d.latest_event(TelechatEvent, type="scheduled_for_telechat").telechat_date in dates: + idinternals.append(d) + + idinternals.sort(key=lambda d: (d.rfc_flag, d.start_date)) + else: + idinternals = list(IDInternal.objects.filter(telechat_date__in=dates,primary_flag=1,agenda=1).order_by('rfc_flag', 'ballot')) for i in idinternals: + # FIXME: this isn't ported, apparently disabled i.reschedule_form = handle_reschedule_form(request, i, dates) # some may have been taken off the schedule by the reschedule form @@ -302,7 +422,10 @@ def agenda_documents(request): if not i.rfc_flag: w = IdWrapper(draft=i) else: - ri = RfcIndex.objects.get(rfc_number=i.draft_id) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + ri = i + else: + ri = RfcIndex.objects.get(rfc_number=i.draft_id) w = RfcWrapper(ri) w.reschedule_form = i.reschedule_form res[section_key].append(w) diff --git a/ietf/ipr/models.py b/ietf/ipr/models.py index d25d78193..adf07d595 100644 --- a/ietf/ipr/models.py +++ b/ietf/ipr/models.py @@ -1,6 +1,7 @@ # Copyright The IETF Trust 2007, All Rights Reserved from django.db import models +from django.conf import settings #from django import newforms as forms from ietf.idtracker.views import InternetDraft from ietf.idtracker.models import Rfc @@ -149,8 +150,8 @@ class IprContact(models.Model): class IprDraft(models.Model): - ipr = models.ForeignKey(IprDetail, related_name='drafts') - document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name="ipr") + ipr = models.ForeignKey(IprDetail, related_name='drafts_old' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'drafts') + document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name="ipr_old" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "ipr") revision = models.CharField(max_length=2) def __str__(self): return "%s which applies to %s-%s" % ( self.ipr, self.document, self.revision ) @@ -168,7 +169,7 @@ class IprNotification(models.Model): db_table = 'ipr_notifications' class IprRfc(models.Model): - ipr = models.ForeignKey(IprDetail, related_name='rfcs') + ipr = models.ForeignKey(IprDetail, related_name='rfcs_old' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'rfcs') document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr") def __str__(self): return "%s applies to RFC%04d" % ( self.ipr, self.document_id ) @@ -184,6 +185,23 @@ class IprUpdate(models.Model): class Meta: db_table = 'ipr_updates' + + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import Document + + class IprDocument(models.Model): + ipr = models.ForeignKey(IprDetail, related_name='documents') + document = models.ForeignKey(Document, related_name="ipr") + rev = models.CharField(max_length=2) + def __unicode__(self): + if self.rev != None: + return u"%s which applies to %s-%s" % (self.ipr, self.document, self.revision) + else: + return u"%s which applies to %s" % (self.ipr, self.document) + + + # changes done by convert-096.py:changed maxlength to max_length # removed core # removed edit_inline diff --git a/ietf/templates/iesg/scribe_doc.html b/ietf/templates/iesg/scribe_doc.html index b395a0ab6..dc072c10b 100644 --- a/ietf/templates/iesg/scribe_doc.html +++ b/ietf/templates/iesg/scribe_doc.html @@ -48,11 +48,20 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. {% endif %}{% for ipr in doc.obj.draft.ipr.all %}{% ifequal ipr.ipr.status 1 %}
    IPR: {{ ipr.ipr.title|escape }}{% endifequal %} {% endfor %} {% if doc.obj.ballot.active %}
    Discusses/comments (from ballot {{doc.obj.ballot.ballot }}):
      +{% if USE_DB_REDESIGN_PROXY_CLASSES %} +{% for p in doc.obj.active_positions|dictsort:"ad.get_name" %}{% if p.pos %}{% ifequal p.pos.pos_id "discuss" %}
    1. {{ p.pos.ad.get_name }}: Discuss [{{ p.pos.discuss_time.date }}]: +
      ... +{% endifequal %}{% if p.pos.comment %}
    2. {{ p.pos.ad.get_name }}: Comment [{{ p.pos.comment_time.date }}]: +
      ... +{% endif %}{% endif %}{% endfor %} +{% else %} {% for position in doc.obj.ballot.positions.all|dictsort:"ad.last_name" %}{% ifequal position.discuss 1 %}
    3. {{ position.ad }}:{% for item in doc.obj.ballot.discusses.all %}{% ifequal position.ad item.ad %} Discuss [{{ item.date }}]:
      ... {% endifequal %}{% endfor %}{% endifequal %}{% for item in doc.obj.ballot.comments.all %}{% ifequal position.ad item.ad %}
    4. {{ position.ad }}: Comment [{{ item.date }}]:
      ... -{% endifequal %}{% endfor %}{% endfor %}
    +{% endifequal %}{% endfor %}{% endfor %} +{% endif%} + {%endif %}

    Telechat:

    • ... diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 1c744f28e..47bf13d69 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -109,7 +109,7 @@ class InternetDraft(Document): @property def lc_expiration_date(self): e = self.latest_event(LastCallEvent, type="sent_last_call") - return e.expires if e else None + return e.expires.date() if e else None #b_sent_date = models.DateField(null=True, blank=True) @property @@ -142,7 +142,7 @@ class InternetDraft(Document): #last_modified_date = models.DateField() @property def last_modified_date(self): - return self.time + return self.time.date() #replaced_by = models.ForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set') @property @@ -218,7 +218,7 @@ class InternetDraft(Document): def file_tag(self): return "<%s-%s.txt>" % (self.name, self.revision_display()) def group_acronym(self): - return self.group.acronym + return super(Document, self).group.acronym def idstate(self): return self.docstate() def revision_display(self): @@ -273,6 +273,10 @@ class InternetDraft(Document): @property def draft(self): return self + + @property + def draft_id(self): + return self.name #rfc_flag = models.IntegerField(null=True) @property @@ -320,7 +324,8 @@ class InternetDraft(Document): #agenda = models.IntegerField(null=True, blank=True) @property def agenda(self): - return bool(self.latest_event(type="scheduled_for_telechat")) + e = self.latest_event(TelechatEvent, type="scheduled_for_telechat") + return bool(e and e.telechat_date) #cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs') @property @@ -552,7 +557,6 @@ class InternetDraft(Document): 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): from person.proxy import IESGLogin as IESGLoginProxy @@ -567,7 +571,9 @@ class InternetDraft(Document): for ad in active_ads: if ad not in found: add(ad, None) - + + res.sort(key=lambda x: x["ad"].last_name) + return res def needed(self, standardsTrack=True): @@ -693,6 +699,20 @@ class InternetDraft(Document): def file_formats(self): return self.get_file_type_matches_from(os.path.join(settings.RFC_PATH, "rfc" + str(self.rfc_number) + ".*")).replace(".", "").replace("txt", "ascii") + @property + def positions(self): + res = [] + found = set() + for pos in Position.objects.filter(doc=self, type="changed_ballot_position").select_related('ad').order_by("-time", "-id"): + if pos.ad not in found: + found.add(pos.ad) + res.append(pos) + + class Dummy: pass + d = Dummy() + d.all = res + return d + class Meta: proxy = True diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py index 428b3d25b..0149e3a3b 100644 --- a/redesign/group/proxy.py +++ b/redesign/group/proxy.py @@ -59,6 +59,11 @@ class Area(Group): @staticmethod def active_areas(): return Area.objects.filter(type="area", state="active").select_related('type', 'state', 'parent').order_by('acronym') + + def __str__(self): + return self.acronym + def __unicode__(self): + return self.acronym class Meta: proxy = True diff --git a/redesign/person/proxy.py b/redesign/person/proxy.py index 42a5cd7e2..e48030b5d 100644 --- a/redesign/person/proxy.py +++ b/redesign/person/proxy.py @@ -4,6 +4,7 @@ class IESGLogin(Email): def from_object(self, base): for f in base._meta.fields: setattr(self, f.name, getattr(base, f.name)) + return self SECRETARIAT_LEVEL = 0 AD_LEVEL = 1 @@ -30,7 +31,7 @@ class IESGLogin(Email): #last_name = models.CharField(blank=True, max_length=25) @property def last_name(self): - return self.get_name().split(" ")[1] + 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) From 1e4b5ee74cea7bacff08884411a2c095f46d2a71 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 17 May 2011 12:38:44 +0000 Subject: [PATCH 59/75] Port rest of iesg.views and test cases, move redesign announcement models so tests can run - Legacy-Id: 3129 --- ietf/announcements/admin.py | 20 ++ ietf/announcements/models.py | 37 ++++ ietf/announcements/views.py | 4 +- ietf/idrfc/expire.py | 8 +- ietf/idrfc/mirror_rfc_editor_queue.py | 1 - ietf/idrfc/testsREDESIGN.py | 174 +----------------- ietf/iesg/tests.py | 201 +++++++++++++++++++++ ietf/iesg/views.py | 30 ++- ietf/settings.py | 1 - ietf/utils/mail.py | 1 - ietf/utils/test_data.py | 196 ++++++++++++++++++++ redesign/announcements/__init__.py | 0 redesign/announcements/admin.py | 20 -- redesign/announcements/models.py | 37 ---- redesign/importing/import-announcements.py | 2 +- 15 files changed, 484 insertions(+), 248 deletions(-) create mode 100644 ietf/utils/test_data.py delete mode 100644 redesign/announcements/__init__.py delete mode 100644 redesign/announcements/admin.py delete mode 100644 redesign/announcements/models.py diff --git a/ietf/announcements/admin.py b/ietf/announcements/admin.py index 0ee7208aa..6f9c1dad4 100644 --- a/ietf/announcements/admin.py +++ b/ietf/announcements/admin.py @@ -1,5 +1,6 @@ #coding: utf-8 from django.contrib import admin +from django.conf import settings from ietf.announcements.models import * class AnnouncedFromAdmin(admin.ModelAdmin): @@ -21,3 +22,22 @@ class ScheduledAnnouncementAdmin(admin.ModelAdmin): pass admin.site.register(ScheduledAnnouncement, ScheduledAnnouncementAdmin) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + class MessageAdmin(admin.ModelAdmin): + list_display = ["time", "by", "subject", "groups"] + search_fields = ["text"] + raw_id_fields = ["by"] + + def groups(self, instance): + return ", ".join(g.acronym for g in related_groups.all()) + + admin.site.register(Message, MessageAdmin) + + class SendQueueAdmin(admin.ModelAdmin): + list_display = ["time", "by", "message", "send_at", "sent_at"] + list_filter = ["time", "send_at", "sent_at"] + search_fields = ["message__text"] + raw_id_fields = ["by"] + + admin.site.register(SendQueue, SendQueueAdmin) diff --git a/ietf/announcements/models.py b/ietf/announcements/models.py index cb3c216bf..75ca19aad 100644 --- a/ietf/announcements/models.py +++ b/ietf/announcements/models.py @@ -1,6 +1,7 @@ # Copyright The IETF Trust 2007, All Rights Reserved from django.db import models +from django.conf import settings from ietf.idtracker.models import PersonOrOrgInfo, ChairsHistory #from django.contrib.auth.models import Permission @@ -87,3 +88,39 @@ class ScheduledAnnouncement(models.Model): db_table = 'scheduled_announcements' +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + import datetime + + from person.models import Email + from group.models import Group + + class Message(models.Model): + time = models.DateTimeField(default=datetime.datetime.now) + by = models.ForeignKey(Email) + + subject = models.CharField(max_length=255) + frm = models.CharField(max_length=255) + to = models.CharField(max_length=255) + cc = models.CharField(max_length=255, blank=True) + bcc = models.CharField(max_length=255, blank=True) + reply_to = models.CharField(max_length=255, blank=True) + text = models.TextField() + + related_groups = models.ManyToManyField(Group, blank=True) + + class Meta: + ordering = ['time'] + + def __unicode__(self): + return "'%s' %s -> %s" % (self.subject, self.frm, self.to) + + class SendQueue(models.Model): + time = models.DateTimeField(default=datetime.datetime.now) + by = models.ForeignKey(Email) + comment = models.TextField() + message = models.ForeignKey(Message) + send_at = models.DateTimeField(blank=True, null=True) + sent_at = models.DateTimeField(blank=True, null=True) + + class Meta: + ordering = ['time'] diff --git a/ietf/announcements/views.py b/ietf/announcements/views.py index 286b6182c..1e2fad1d8 100644 --- a/ietf/announcements/views.py +++ b/ietf/announcements/views.py @@ -37,7 +37,7 @@ def nomcom(request): def nomcomREDESIGN(request): from person.models import Email from group.models import Group - from redesign.announcements.models import Message + from ietf.announcements.models import Message address_re = re.compile("<.*>") @@ -77,7 +77,7 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: def message_detail(request, object_id, queryset): from person.models import Email from group.models import Group - from redesign.announcements.models import Message + from ietf.announcements.models import Message # restrict to nomcom announcements for the time being nomcoms = Group.objects.filter(acronym__startswith="nomcom").exclude(acronym="nomcom") diff --git a/ietf/idrfc/expire.py b/ietf/idrfc/expire.py index 0a0e0b003..73f3ea1b3 100644 --- a/ietf/idrfc/expire.py +++ b/ietf/idrfc/expire.py @@ -51,8 +51,8 @@ def get_soon_to_expire_idsREDESIGN(days): end_date = start_date + datetime.timedelta(days - 1) for d in expirable_documents(): - e = document_expires(d) - if e and start_date <= e.date() <= end_date: + t = document_expires(d) + if t and start_date <= t.date() <= end_date: yield d def get_expired_ids(): @@ -68,8 +68,8 @@ def get_expired_idsREDESIGN(): today = datetime.date.today() for d in expirable_documents(): - e = document_expires(d) - if e and e.time.date() <= today: + t = document_expires(d) + if t and t.date() <= today: yield d def send_expire_warning_for_id(doc): diff --git a/ietf/idrfc/mirror_rfc_editor_queue.py b/ietf/idrfc/mirror_rfc_editor_queue.py index 5ccce2277..6ec2239b2 100644 --- a/ietf/idrfc/mirror_rfc_editor_queue.py +++ b/ietf/idrfc/mirror_rfc_editor_queue.py @@ -247,7 +247,6 @@ def insert_into_databaseREDESIGN(drafts, refs): for name, date_received, state, stream_id in drafts: try: d = Document.objects.get(name=name) - print "known document", name except Document.DoesNotExist: log("unknown document %s" % name) continue diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index dd09d5650..c7d4cedee 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -41,7 +41,6 @@ from django.conf import settings from pyquery import PyQuery -#from ietf.idrfc.models import * from ietf.idtracker.models import IESGLogin, PersonOrOrgInfo, EmailAddress, IDDates from doc.models import * from name.models import * @@ -50,183 +49,13 @@ from person.models import * from ietf.iesg.models import TelechatDates from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest, login_testing_unauthorized from ietf.utils.test_runner import mail_outbox +from ietf.utils.test_data import make_test_data class IdRfcUrlTestCase(SimpleUrlTestCase): def testUrls(self): #self.doTestUrls(__file__) self.doTestUrls(os.path.join(os.path.dirname(os.path.abspath(__file__)), "testurlREDESIGN.list")) -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 - Email.objects.get_or_create(address="(System)") - - # ad - 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=porg.first_name, - last_name=porg.last_name, - person=porg, - ) - - # create a bunch of ads for swarm tests - for i in range(1, 10): - p = Person.objects.create( - name="Ad No%s" % i, - ascii="Ad No%s" % i, - ) - email = Email.objects.create( - address="ad%s@ietf.org" % i, - person=p) - Role.objects.create( - name_id="ad" if i <= 5 else "ex-ad", - group=area, - email=email) - porg = PersonOrOrgInfo.objects.create( - first_name="Ad", - last_name="No%s" % i, - middle_initial="", - ) - EmailAddress.objects.create( - person_or_org=porg, - priority=1, - address=ad.address, - ) - IESGLogin.objects.create( - login_name="ad%s" % i, - password="foo", - user_level=1, - first_name=porg.first_name, - last_name=porg.last_name, - person=porg, - ) - - # group chair - p = Person.objects.create( - name="WG Chair Man", - ascii="WG Chair Man", - ) - wgchair = Email.objects.create( - address="wgchairman@ietf.org", - person=p) - Role.objects.create( - name=RoleName.objects.get(slug="chair"), - group=group, - email=wgchair, - ) - - # secretary - 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=porg.first_name, - last_name=porg.last_name, - person=porg, - ) - - # draft - draft = Document.objects.create( - name="draft-ietf-test", - time=datetime.datetime.now(), - type_id="draft", - title="Optimizing Martian Network Topologies", - state_id="active", - iesg_state_id="pub-req", - 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", - note="", - ) - - DocAlias.objects.create( - document=draft, - name=draft.name, - ) - - DocumentAuthor.objects.create( - document=draft, - author=Email.objects.get(address="aread@ietf.org"), - order=1 - ) - - # draft has only one event - Event.objects.create( - type="started_iesg_process", - by=ad, - doc=draft, - desc="Added draft", - ) - - # telechat dates - t = datetime.date.today() - dates = TelechatDates(date1=t, - date2=t + datetime.timedelta(days=7), - date3=t + datetime.timedelta(days=14), - date4=t + datetime.timedelta(days=21), - ) - super(dates.__class__, dates).save(force_insert=True) # work-around hard-coded save block - - return draft class ChangeStateTestCase(django.test.TestCase): fixtures = ['names'] @@ -1051,7 +880,6 @@ class ExpireIDsTestCase(django.test.TestCase): send_expire_warning_for_id(draft) - print mail_outbox[-1] self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue("aread@ietf.org" in str(mail_outbox[-1])) # author self.assertTrue("wgchairman@ietf.org" in str(mail_outbox[-1])) diff --git a/ietf/iesg/tests.py b/ietf/iesg/tests.py index 3177265ae..f40c92858 100644 --- a/ietf/iesg/tests.py +++ b/ietf/iesg/tests.py @@ -61,6 +61,62 @@ class RescheduleOnAgendaTestCase(django.test.TestCase): self.assertEquals(draft.idinternal.comments().count(), comments_before + 1) self.assertTrue("Telechat" in draft.idinternal.comments()[0].comment_text) +class RescheduleOnAgendaTestCaseREDESIGN(django.test.TestCase): + fixtures = ['names'] + + def test_reschedule(self): + from ietf.utils.test_data import make_test_data + from redesign.person.models import Email + from doc.models import TelechatEvent + + draft = make_test_data() + + # add to schedule + e = TelechatEvent(type="scheduled_for_telechat") + e.doc = draft + e.by = Email.objects.get(address="aread@ietf.org") + e.telechat_date = TelechatDates.objects.all()[0].date1 + e.returning_item = True + e.save() + + form_id = draft.pk + telechat_date_before = e.telechat_date + + url = urlreverse('ietf.iesg.views.agenda_documents') + + self.client.login(remote_user="secretary") + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + # FIXME + #self.assertEquals(len(q('form select[name=%s-telechat_date]' % form_id)), 1) + #self.assertEquals(len(q('form input[name=%s-clear_returning_item]' % form_id)), 1) + + # reschedule + events_before = draft.event_set.count() + d = TelechatDates.objects.all()[0].dates()[2] + + r = self.client.post(url, { '%s-telechat_date' % form_id: d.strftime("%Y-%m-%d"), + '%s-clear_returning_item' % form_id: "1" }) + + self.assertEquals(r.status_code, 200) + + # check that it moved below the right header in the DOM on the + # agenda docs page + d_header_pos = r.content.find("IESG telechat %s" % d.strftime("%Y-%m-%d")) + draft_pos = r.content.find(draft.name) + self.assertTrue(d_header_pos < draft_pos) + + self.assertTrue(draft.latest_event(TelechatEvent, "scheduled_for_telechat")) + self.assertEquals(draft.latest_event(TelechatEvent, "scheduled_for_telechat").telechat_date, d) + self.assertTrue(not draft.latest_event(TelechatEvent, "scheduled_for_telechat").returning_item) + self.assertEquals(draft.event_set.count(), events_before + 1) + + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + RescheduleOnAgendaTestCase = RescheduleOnAgendaTestCaseREDESIGN class ManageTelechatDatesTestCase(django.test.TestCase): fixtures = ['base', 'draft'] @@ -212,7 +268,152 @@ class WorkingGroupActionsTestCase(django.test.TestCase): self.assertEquals(r.status_code, 200) self.assertTrue('(sieve)' not in r.content) + +class WorkingGroupActionsTestCaseREDESIGN(django.test.TestCase): + fixtures = ['names'] + + def setUp(self): + super(self.__class__, self).setUp() + + curdir = os.path.dirname(os.path.abspath(__file__)) + self.evaldir = os.path.join(curdir, "tmp-testdir") + os.mkdir(self.evaldir) + src = os.path.join(curdir, "fixtures", "sieve-charter.txt") + shutil.copy(src, self.evaldir) + + settings.IESG_WG_EVALUATION_DIR = self.evaldir + + def tearDown(self): + super(self.__class__, self).tearDown() + shutil.rmtree(self.evaldir) + + + def test_working_group_actions(self): + from ietf.utils.test_data import make_test_data + + make_test_data() + + url = urlreverse('iesg_working_group_actions') + login_testing_unauthorized(self, "secretary", url) + + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + for wga in WGAction.objects.all(): + self.assertTrue(wga.group_acronym.name in r.content) + + self.assertTrue('(sieve)' in r.content) + + def test_delete_wgaction(self): + from ietf.utils.test_data import make_test_data + + make_test_data() + + wga = WGAction.objects.all()[0] + url = urlreverse('iesg_edit_working_group_action', kwargs=dict(wga_id=wga.pk)) + login_testing_unauthorized(self, "secretary", url) + + r = self.client.post(url, dict(delete="1")) + self.assertEquals(r.status_code, 302) + self.assertTrue(not WGAction.objects.filter(pk=wga.pk)) + + def test_edit_wgaction(self): + from ietf.utils.test_data import make_test_data + from redesign.person.models import Email + + make_test_data() + + wga = WGAction.objects.all()[0] + url = urlreverse('iesg_edit_working_group_action', kwargs=dict(wga_id=wga.pk)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form select[name=token_name]')), 1) + self.assertEquals(len(q('form select[name=telechat_date]')), 1) + + # change + dates = TelechatDates.objects.all()[0] + token_name = Email.objects.get(address="ad1@ietf.org").get_name().split(" ")[0] + old = wga.pk + r = self.client.post(url, dict(status_date=dates.date1.isoformat(), + token_name=token_name, + category="23", + note="Testing.", + telechat_date=dates.date4.isoformat())) + self.assertEquals(r.status_code, 302) + + wga = WGAction.objects.get(pk=old) + self.assertEquals(wga.status_date, dates.date1) + self.assertEquals(wga.token_name, token_name) + self.assertEquals(wga.category, 23) + self.assertEquals(wga.note, "Testing.") + self.assertEquals(wga.telechat_date, dates.date4) + + def test_add_possible_wg(self): + from ietf.utils.test_data import make_test_data + from redesign.person.models import Email + from redesign.group.models import Group + + make_test_data() + + url = urlreverse('iesg_working_group_actions') + login_testing_unauthorized(self, "secretary", url) + + r = self.client.post(url, dict(add="1", + filename='sieve-charter.txt')) + self.assertEquals(r.status_code, 302) + + # now we got back a URL we can use for adding, but first make + # sure we got a proposed group with the acronym + group = Group.objects.create( + name="Sieve test test", + acronym="sieve", + state_id="proposed", + type_id="wg", + parent=None + ) + + add_url = r['Location'] + r = self.client.get(add_url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue('(sieve)' in r.content) + self.assertEquals(len(q('form select[name=token_name]')), 1) + self.assertEquals(q('form input[name=status_date]')[0].get("value"), "2010-05-07") + self.assertEquals(len(q('form select[name=telechat_date]')), 1) + + wgas_before = WGAction.objects.all().count() + dates = TelechatDates.objects.all()[0] + token_name = Email.objects.get(address="ad1@ietf.org").get_name().split(" ")[0] + r = self.client.post(add_url, + dict(status_date=dates.date1.isoformat(), + token_name=token_name, + category="23", + note="Testing.", + telechat_date=dates.date4.isoformat())) + self.assertEquals(r.status_code, 302) + self.assertEquals(wgas_before + 1, WGAction.objects.all().count()) + + def test_delete_possible_wg(self): + from ietf.utils.test_data import make_test_data + + make_test_data() + + url = urlreverse('iesg_working_group_actions') + login_testing_unauthorized(self, "secretary", url) + + r = self.client.post(url, dict(delete="1", + filename='sieve-charter.txt')) + self.assertEquals(r.status_code, 200) + + self.assertTrue('(sieve)' not in r.content) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + WorkingGroupActionsTestCase = WorkingGroupActionsTestCaseREDESIGN class IesgUrlTestCase(SimpleUrlTestCase): diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index f29e0983c..ed8680613 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -380,12 +380,20 @@ def handle_reschedule_form(request, idinternal, dates): if request.method == 'POST': form = RescheduleForm(request.POST, **formargs) if form.is_valid(): - update_telechat(request, idinternal, - form.cleaned_data['telechat_date']) - if form.cleaned_data['clear_returning_item']: - idinternal.returning_item = False - idinternal.event_date = datetime.date.today() - idinternal.save() + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + login = request.user.get_profile().email() + update_telechat(request, idinternal, login, + form.cleaned_data['telechat_date'], + False if form.cleaned_data['clear_returning_item'] else None) + idinternal.time = datetime.datetime.now() + idinternal.save() + else: + update_telechat(request, idinternal, + form.cleaned_data['telechat_date']) + if form.cleaned_data['clear_returning_item']: + idinternal.returning_item = False + idinternal.event_date = datetime.date.today() + idinternal.save() else: form = RescheduleForm(**formargs) @@ -405,7 +413,6 @@ def agenda_documents(request): else: idinternals = list(IDInternal.objects.filter(telechat_date__in=dates,primary_flag=1,agenda=1).order_by('rfc_flag', 'ballot')) for i in idinternals: - # FIXME: this isn't ported, apparently disabled i.reschedule_form = handle_reschedule_form(request, i, dates) # some may have been taken off the schedule by the reschedule form @@ -435,7 +442,14 @@ def agenda_documents(request): def telechat_docs_tarfile(request,year,month,day): from tempfile import mkstemp date=datetime.date(int(year),int(month),int(day)) - docs= IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import TelechatEvent + docs = [] + for d in IDInternal.objects.filter(event__telechatevent__telechat_date=date): + if d.latest_event(TelechatEvent, type="scheduled_for_telechat").telechat_date == date: + docs.append(d) + else: + docs= IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1) response = HttpResponse(mimetype='application/octet-stream') response['Content-Disposition'] = 'attachment; filename=telechat-%s-%s-%s-docs.tgz'%(year, month, day) tarstream = tarfile.open('','w:gz',response) diff --git a/ietf/settings.py b/ietf/settings.py index 7089cf5f4..110773850 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -125,7 +125,6 @@ INSTALLED_APPS = ( 'redesign.name', 'redesign.group', 'redesign.doc', - 'redesign.announcements', 'redesign.issue', 'ietf.announcements', 'ietf.idindex', diff --git a/ietf/utils/mail.py b/ietf/utils/mail.py index dffac7513..f531384ec 100644 --- a/ietf/utils/mail.py +++ b/ietf/utils/mail.py @@ -143,7 +143,6 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=N msg['To'] = to if cc: msg['Cc'] = cc - print cc msg['Subject'] = subject msg['X-Test-IDTracker'] = (settings.SERVER_MODE == 'production') and 'no' or 'yes' if extra: diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py new file mode 100644 index 000000000..bb74100ea --- /dev/null +++ b/ietf/utils/test_data.py @@ -0,0 +1,196 @@ +from ietf.idtracker.models import IESGLogin, PersonOrOrgInfo, EmailAddress +from ietf.iesg.models import TelechatDates, WGAction +from redesign.doc.models import * +from redesign.name.models import * +from redesign.group.models import * +from redesign.person.models import * + +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 + Email.objects.get_or_create(address="(System)") + + # ad + 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=porg.first_name, + last_name=porg.last_name, + person=porg, + ) + + # create a bunch of ads for swarm tests + for i in range(1, 10): + p = Person.objects.create( + name="Ad No%s" % i, + ascii="Ad No%s" % i, + ) + email = Email.objects.create( + address="ad%s@ietf.org" % i, + person=p) + Role.objects.create( + name_id="ad" if i <= 5 else "ex-ad", + group=area, + email=email) + porg = PersonOrOrgInfo.objects.create( + first_name="Ad", + last_name="No%s" % i, + middle_initial="", + ) + EmailAddress.objects.create( + person_or_org=porg, + priority=1, + address=ad.address, + ) + IESGLogin.objects.create( + login_name="ad%s" % i, + password="foo", + user_level=1, + first_name=porg.first_name, + last_name=porg.last_name, + person=porg, + ) + + # group chair + p = Person.objects.create( + name="WG Chair Man", + ascii="WG Chair Man", + ) + wgchair = Email.objects.create( + address="wgchairman@ietf.org", + person=p) + Role.objects.create( + name=RoleName.objects.get(slug="chair"), + group=group, + email=wgchair, + ) + + # secretary + 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=porg.first_name, + last_name=porg.last_name, + person=porg, + ) + + # draft + draft = Document.objects.create( + name="draft-ietf-test", + time=datetime.datetime.now(), + type_id="draft", + title="Optimizing Martian Network Topologies", + state_id="active", + iesg_state_id="pub-req", + 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", + note="", + ) + + DocAlias.objects.create( + document=draft, + name=draft.name, + ) + + DocumentAuthor.objects.create( + document=draft, + author=Email.objects.get(address="aread@ietf.org"), + order=1 + ) + + # draft has only one event + Event.objects.create( + type="started_iesg_process", + by=ad, + doc=draft, + desc="Added draft", + ) + + # telechat dates + t = datetime.date.today() + dates = TelechatDates(date1=t, + date2=t + datetime.timedelta(days=7), + date3=t + datetime.timedelta(days=14), + date4=t + datetime.timedelta(days=21), + ) + super(dates.__class__, dates).save(force_insert=True) # work-around hard-coded save block + + # WG Actions + group = Group.objects.create( + name="Asteroid Mining Equipment Standardization Group", + acronym="ames", + state_id="proposed", + type_id="wg", + parent=area, + ) + WGAction.objects.create( + pk=group.pk, + note="", + status_date=datetime.date.today(), + agenda=1, + token_name="Aread", + category=13, + telechat_date=dates.date2 + ) + + return draft diff --git a/redesign/announcements/__init__.py b/redesign/announcements/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/redesign/announcements/admin.py b/redesign/announcements/admin.py deleted file mode 100644 index f7ee41c2b..000000000 --- a/redesign/announcements/admin.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.contrib import admin -from models import * - -class MessageAdmin(admin.ModelAdmin): - list_display = ["time", "by", "subject", "groups"] - search_fields = ["text"] - raw_id_fields = ["by"] - - def groups(self, instance): - return ", ".join(g.acronym for g in related_groups.all()) - -admin.site.register(Message, MessageAdmin) - -class SendQueueAdmin(admin.ModelAdmin): - list_display = ["time", "by", "message", "send_at", "sent_at"] - list_filter = ["time", "send_at", "sent_at"] - search_fields = ["message__text"] - raw_id_fields = ["by"] - -admin.site.register(SendQueue, SendQueueAdmin) diff --git a/redesign/announcements/models.py b/redesign/announcements/models.py deleted file mode 100644 index 03c67dbe6..000000000 --- a/redesign/announcements/models.py +++ /dev/null @@ -1,37 +0,0 @@ -from django.db import models - -import datetime - -from person.models import Email -from group.models import Group - -class Message(models.Model): - time = models.DateTimeField(default=datetime.datetime.now) - by = models.ForeignKey(Email) - - subject = models.CharField(max_length=255) - frm = models.CharField(max_length=255) - to = models.CharField(max_length=255) - cc = models.CharField(max_length=255, blank=True) - bcc = models.CharField(max_length=255, blank=True) - reply_to = models.CharField(max_length=255, blank=True) - text = models.TextField() - - related_groups = models.ManyToManyField(Group, blank=True) - - class Meta: - ordering = ['time'] - - def __unicode__(self): - return "'%s' %s -> %s" % (self.subject, self.frm, self.to) - -class SendQueue(models.Model): - time = models.DateTimeField(default=datetime.datetime.now) - by = models.ForeignKey(Email) - comment = models.TextField() - message = models.ForeignKey(Message) - send_at = models.DateTimeField(blank=True, null=True) - sent_at = models.DateTimeField(blank=True, null=True) - - class Meta: - ordering = ['time'] diff --git a/redesign/importing/import-announcements.py b/redesign/importing/import-announcements.py index 213b44640..4a0984bdf 100755 --- a/redesign/importing/import-announcements.py +++ b/redesign/importing/import-announcements.py @@ -13,9 +13,9 @@ management.setup_environ(settings) from redesign.person.models import * from redesign.group.models import * -from redesign.announcements.models import * from redesign.name.utils import name from redesign.importing.utils import person_email +from ietf.announcements.models import Message from ietf.announcements.models import Announcement, PersonOrOrgInfo, AnnouncedTo, AnnouncedFrom # assumptions: From 0b637a156d3a4b986d5309bbae8d82f52e7c2c77 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 17 May 2011 16:13:05 +0000 Subject: [PATCH 60/75] Port iesg.views.discusses too - Legacy-Id: 3130 --- ietf/iesg/views.py | 24 ++++++++++++++++++++++++ redesign/doc/proxy.py | 6 ++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index ed8680613..a068a560d 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -472,6 +472,30 @@ def telechat_docs_tarfile(request,year,month,day): return response def discusses(request): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + res = [] + + print IDInternal.objects.filter(iesg_state__in=("pub-req", "ad-eval", "review-e", "lc-req", "lc", "writeupw", "goaheadw", "iesg-eva", "defer", "watching"), event__ballotpositionevent__pos="discuss").distinct().count() + for d in IDInternal.objects.filter(iesg_state__in=("pub-req", "ad-eval", "review-e", "lc-req", "lc", "writeupw", "goaheadw", "iesg-eva", "defer", "watching"), event__ballotpositionevent__pos="discuss").distinct(): + found = False + for p in d.positions.all(): + if p.discuss: + found = True + break + + if not found: + continue + + if d.rfc_flag: + doc = RfcWrapper(d) + else: + doc = IdWrapper(draft=d) + + if doc.in_ietf_process() and doc.ietf_process.has_active_iesg_ballot(): + res.append(doc) + + return direct_to_template(request, 'iesg/discusses.html', {'docs':res}) + positions = Position.objects.filter(discuss=1) res = [] try: diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 47bf13d69..c7e75465d 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -708,9 +708,11 @@ class InternetDraft(Document): found.add(pos.ad) res.append(pos) - class Dummy: pass + class Dummy: + def all(self): + return self.res d = Dummy() - d.all = res + d.res = res return d class Meta: From 43c028814622c34e90b0d629fa9f5f894c9cf2eb Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 17 May 2011 16:14:05 +0000 Subject: [PATCH 61/75] Remove accidentally committed debug print - Legacy-Id: 3131 --- ietf/iesg/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index a068a560d..d8dcf241e 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -475,7 +475,6 @@ def discusses(request): if settings.USE_DB_REDESIGN_PROXY_CLASSES: res = [] - print IDInternal.objects.filter(iesg_state__in=("pub-req", "ad-eval", "review-e", "lc-req", "lc", "writeupw", "goaheadw", "iesg-eva", "defer", "watching"), event__ballotpositionevent__pos="discuss").distinct().count() for d in IDInternal.objects.filter(iesg_state__in=("pub-req", "ad-eval", "review-e", "lc-req", "lc", "writeupw", "goaheadw", "iesg-eva", "defer", "watching"), event__ballotpositionevent__pos="discuss").distinct(): found = False for p in d.positions.all(): From 1bea94eb12ffaac57d55087906b8ad7c3489e9ec Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Wed, 25 May 2011 12:33:24 +0000 Subject: [PATCH 62/75] Change auth model for new schema to use Person rather than Email where email is not necessary, use Person for user profiles rather than the old unused profile class, use REMOTE_USER backend directly instead of custom backend, use group roles for authorization rather than Django groups, port/proxy code from using IESGLogin to the new model - Legacy-Id: 3151 --- ietf/announcements/models.py | 9 +- ietf/announcements/views.py | 2 - ietf/idrfc/expire.py | 8 +- ietf/idrfc/idrfc_wrapper.py | 8 +- ietf/idrfc/lastcall.py | 10 +- ietf/idrfc/mails.py | 28 ++--- ietf/idrfc/mirror_rfc_index.py | 9 +- ietf/idrfc/templatetags/ballot_icon.py | 12 ++- ietf/idrfc/testsREDESIGN.py | 34 +++--- ietf/idrfc/utils.py | 12 +-- ietf/idrfc/views_ballot.py | 53 ++++----- ietf/idrfc/views_doc.py | 4 +- ietf/idrfc/views_edit.py | 36 +++---- ietf/idrfc/views_search.py | 37 ++++--- ietf/idtracker/admin.py | 3 +- ietf/idtracker/models.py | 2 + ietf/idtracker/templatetags/ietf_filters.py | 11 ++ ietf/iesg/feeds.py | 2 +- ietf/iesg/tests.py | 62 +++++++++-- ietf/iesg/views.py | 3 +- ietf/ietfauth/decorators.py | 47 ++++++++ ietf/ietfauth/tests.py | 10 +- ietf/ietfauth/views.py | 17 +++ ietf/settings.py | 4 + .../idrfc/edit_positionREDESIGN.html | 4 +- .../idrfc/send_ballot_commentREDESIGN.html | 4 +- ietf/templates/iesg/scribe_doc.html | 4 +- .../registration/profileREDESIGN.html | 31 ++++++ ietf/utils/test_data.py | 101 +++++++----------- redesign/doc/models.py | 16 +-- redesign/doc/proxy.py | 12 +-- redesign/group/models.py | 10 +- redesign/importing/import-announcements.py | 11 +- redesign/importing/import-document-state.py | 93 ++++++++-------- redesign/importing/import-groups.py | 28 +++-- redesign/importing/import-persons.py | 44 ++++++++ redesign/importing/import-roles.py | 36 +++++-- redesign/importing/utils.py | 16 ++- redesign/person/admin.py | 2 +- redesign/person/models.py | 25 ++++- redesign/person/proxy.py | 24 +++-- 41 files changed, 576 insertions(+), 308 deletions(-) create mode 100644 ietf/templates/registration/profileREDESIGN.html create mode 100755 redesign/importing/import-persons.py diff --git a/ietf/announcements/models.py b/ietf/announcements/models.py index 75ca19aad..ab8fa5645 100644 --- a/ietf/announcements/models.py +++ b/ietf/announcements/models.py @@ -88,15 +88,15 @@ class ScheduledAnnouncement(models.Model): db_table = 'scheduled_announcements' -if settings.USE_DB_REDESIGN_PROXY_CLASSES: +if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_ANNOUNCEMENTS"): import datetime - from person.models import Email + from person.models import Email, Person from group.models import Group class Message(models.Model): time = models.DateTimeField(default=datetime.datetime.now) - by = models.ForeignKey(Email) + by = models.ForeignKey(Person) subject = models.CharField(max_length=255) frm = models.CharField(max_length=255) @@ -116,7 +116,8 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: class SendQueue(models.Model): time = models.DateTimeField(default=datetime.datetime.now) - by = models.ForeignKey(Email) + by = models.ForeignKey(Person) + comment = models.TextField() message = models.ForeignKey(Message) send_at = models.DateTimeField(blank=True, null=True) diff --git a/ietf/announcements/views.py b/ietf/announcements/views.py index 1e2fad1d8..d13b9a2a1 100644 --- a/ietf/announcements/views.py +++ b/ietf/announcements/views.py @@ -35,7 +35,6 @@ def nomcom(request): 'regimes' : regimes }) def nomcomREDESIGN(request): - from person.models import Email from group.models import Group from ietf.announcements.models import Message @@ -75,7 +74,6 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: def message_detail(request, object_id, queryset): - from person.models import Email from group.models import Group from ietf.announcements.models import Message diff --git a/ietf/idrfc/expire.py b/ietf/idrfc/expire.py index 73f3ea1b3..cb8c49ff5 100644 --- a/ietf/idrfc/expire.py +++ b/ietf/idrfc/expire.py @@ -11,7 +11,7 @@ from ietf.utils.mail import send_mail, send_mail_subj from ietf.idrfc.utils import log_state_changed, add_document_comment from doc.models import Document, Event, save_document_in_history from name.models import IesgDocStateName, DocStateName, DocInfoTagName -from person.models import Email +from person.models import Person, Email INTERNET_DRAFT_DAYS_TO_EXPIRE = 185 @@ -186,7 +186,7 @@ def expire_id(doc): add_document_comment(None, doc, "Document is expired by system") def expire_idREDESIGN(doc): - system_email = Email.objects.get(address="(System)") + system = Person.objects.get(name="(System)") # clean up files def move_file(f): @@ -219,9 +219,9 @@ def expire_idREDESIGN(doc): if doc.iesg_state != dead_state: prev = doc.iesg_state doc.iesg_state = dead_state - log_state_changed(None, doc, system_email, prev) + log_state_changed(None, doc, system, prev) - e = Event(doc=doc, by=system_email) + e = Event(doc=doc, by=system) e.type = "expired_document" e.desc = "Document has expired" e.save() diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py index 2ff4f3ab4..9ca33df4d 100644 --- a/ietf/idrfc/idrfc_wrapper.py +++ b/ietf/idrfc/idrfc_wrapper.py @@ -641,8 +641,8 @@ class BallotWrapper: self.old_init() return - from redesign.person.models import Email - active_ads = Email.objects.filter(role__name="ad", role__group__state="active") + from redesign.person.models import Person + active_ads = Person.objects.filter(email__role__name="ad", email__role__group__state="active").distinct() positions = [] seen = {} @@ -650,7 +650,7 @@ class BallotWrapper: from doc.models import BallotPositionEvent for pos in BallotPositionEvent.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(), + p = dict(ad_name=pos.ad.name, ad_username=pos.ad.pk, # ought to rename this in doc_ballot_list position=pos.pos.name, is_old_ad=pos.ad not in active_ads, @@ -684,7 +684,7 @@ class BallotWrapper: if self.ballot_active: for ad in active_ads: if ad not in seen: - d = dict(ad_name=ad.get_name(), + d = dict(ad_name=ad.name, ad_username=ad.pk, position="No Record", ) diff --git a/ietf/idrfc/lastcall.py b/ietf/idrfc/lastcall.py index 880b001af..96b30e1b4 100644 --- a/ietf/idrfc/lastcall.py +++ b/ietf/idrfc/lastcall.py @@ -4,13 +4,13 @@ import datetime from django.conf import settings -from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin +from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo from ietf.idrfc.mails import * from ietf.idrfc.utils import * from doc.models import Document, Event, LastCallEvent, WriteupEvent, save_document_in_history from name.models import IesgDocStateName -from person.models import Email +from person.models import Person def request_last_call(request, doc): try: @@ -33,9 +33,9 @@ def request_last_callREDESIGN(request, doc): e = Event() e.type = "requested_last_call" - e.by = request.user.get_profile().email() + e.by = request.user.get_profile() e.doc = doc - e.desc = "Last call was requested by %s" % e.by.get_name() + e.desc = "Last call was requested by %s" % e.by.name e.save() if settings.USE_DB_REDESIGN_PROXY_CLASSES: @@ -83,7 +83,7 @@ def expire_last_callREDESIGN(doc): prev = doc.iesg_state doc.iesg_state = state - e = log_state_changed(None, doc, Email.objects.get(address="(System)"), prev) + e = log_state_changed(None, doc, Person.objects.get(name="(System)"), prev) doc.time = e.time doc.save() diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index 799d7dea3..2041d6a5e 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -10,7 +10,7 @@ from django.conf import settings from ietf.utils.mail import send_mail, send_mail_text from ietf.idtracker.models import * from doc.models import WriteupEvent, BallotPositionEvent, LastCallEvent -from person.models import Email +from person.models import Person def email_state_changed(request, doc, text): to = [x.strip() for x in doc.idinternal.state_change_notice_to.replace(';', ',').split(',')] @@ -55,7 +55,7 @@ def email_ownerREDESIGN(request, doc, owner, changed_by, text, subject=None): to = owner.formatted_email() send_mail(request, to, "DraftTracker Mail System ", - "%s updated by %s" % (doc.file_tag(), changed_by.get_name()), + "%s updated by %s" % (doc.file_tag(), changed_by.name), "idrfc/change_notice.txt", dict(text=html_to_text(text), doc=doc, @@ -88,9 +88,9 @@ def full_intended_status(intended_status): def generate_ballot_writeup(request, doc): e = WriteupEvent() e.type = "changed_ballot_writeup_text" - e.by = request.user.get_profile().email() + e.by = request.user.get_profile() e.doc = doc - e.desc = u"Ballot writeup was generated by %s" % e.by.get_name() + e.desc = u"Ballot writeup was generated by %s" % e.by.name e.text = unicode(render_to_string("idrfc/ballot_writeup.txt")) e.save() @@ -160,9 +160,9 @@ def generate_last_call_announcementREDESIGN(request, doc): e = WriteupEvent() e.type = "changed_last_call_text" - e.by = request.user.get_profile().email() + e.by = request.user.get_profile() e.doc = doc - e.desc = u"Last call announcement was generated by %s" % e.by.get_name() + e.desc = u"Last call announcement was generated by %s" % e.by.name e.text = unicode(mail) e.save() @@ -255,9 +255,9 @@ def generate_approval_mailREDESIGN(request, doc): e = WriteupEvent() e.type = "changed_ballot_approval_text" - e.by = request.user.get_profile().email() + e.by = request.user.get_profile() e.doc = doc - e.desc = u"Ballot approval text was generated by %s" % e.by.get_name() + e.desc = u"Ballot approval text was generated by %s" % e.by.name e.text = unicode(mail) e.save() @@ -292,12 +292,12 @@ def generate_approval_mail_approved(request, doc): 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) + other_director = Person.objects.filter(email__role__group__role__email__person=director, email__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()) + contacts = "The IESG contact persons are %s and %s." % (director.name, other_director[0].name) else: - contacts = "The IESG contact person is %s." % director.get_name() + contacts = "The IESG contact person is %s." % director.name doc_type = "RFC" if doc.state_id == "rfc" else "Internet Draft" @@ -476,7 +476,7 @@ def generate_issue_ballot_mailREDESIGN(request, doc): full_status = full_intended_status(doc.intended_std_level.name) status = full_status.replace("a ", "").replace("an ", "") - active_ads = Email.objects.filter(role__name="ad", role__group__state="active") + active_ads = Person.objects.filter(email__role__name="ad", email__role__group__state="active").distinct() e = doc.latest_event(type="started_iesg_process") positions = BallotPositionEvent.objects.filter(doc=doc, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad') @@ -499,7 +499,7 @@ def generate_issue_ballot_mailREDESIGN(request, doc): return "[ ]" fmt = u"%-21s%-10s%-11s%-9s%-10s" % ( - p.ad.get_name()[:21], + p.ad.name[:21], formatted(p.pos_id == "yes"), formatted(p.pos_id == "noobj"), formatted(p.pos_id == "discuss"), @@ -517,7 +517,7 @@ def generate_issue_ballot_mailREDESIGN(request, doc): active_ad_positions.sort() inactive_ad_positions.sort() - ad_feedback.sort(key=lambda p: p.ad.get_name()) + ad_feedback.sort(key=lambda p: p.ad.name) e = doc.latest_event(LastCallEvent, type="sent_last_call") last_call_expires = e.expires if e else None diff --git a/ietf/idrfc/mirror_rfc_index.py b/ietf/idrfc/mirror_rfc_index.py index a76ed05d5..9def4094d 100644 --- a/ietf/idrfc/mirror_rfc_index.py +++ b/ietf/idrfc/mirror_rfc_index.py @@ -178,13 +178,13 @@ import django.db.transaction @django.db.transaction.commit_on_success def insert_to_databaseREDESIGN(data): - from person.models import Email + from person.models import Person from doc.models import Document, DocAlias, Event, RelatedDocument from group.models import Group from name.models import DocInfoTagName, DocRelationshipName from name.utils import name - system_email = Email.objects.get(address="(System)") + system = Person.objects.get(name="(System)") std_level_mapping = get_std_level_mapping() stream_mapping = get_stream_mapping() tag_has_errata = name(DocInfoTagName, 'errata', "Has errata") @@ -203,7 +203,8 @@ def insert_to_databaseREDESIGN(data): # we assume two things can happen: we get a new RFC, or an # attribute has been updated at the RFC Editor (RFC Editor - # attributes currently take precedence) + # attributes currently take precedence over our local + # attributes) # make sure we got the document and alias created = False @@ -257,7 +258,7 @@ def insert_to_databaseREDESIGN(data): if not doc.latest_event(type="published_rfc", time=pubdate): e = Event(doc=doc, type="published_rfc") e.time = pubdate - e.by = system_email + e.by = system e.desc = "RFC published" e.save() changed = True diff --git a/ietf/idrfc/templatetags/ballot_icon.py b/ietf/idrfc/templatetags/ballot_icon.py index ffd642384..8a804d435 100644 --- a/ietf/idrfc/templatetags/ballot_icon.py +++ b/ietf/idrfc/templatetags/ballot_icon.py @@ -42,16 +42,20 @@ 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().id return context['user'].get_profile().iesg_login_id() else: return None def get_user_name(context): if 'user' in context and context['user'].is_authenticated(): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from person.models import Person + try: + return context['user'].get_profile().name + except Person.DoesNotExist: + return None + person = context['user'].get_profile().person() if person: return str(person) diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index c7d4cedee..ab2d47f11 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -41,7 +41,7 @@ from django.conf import settings from pyquery import PyQuery -from ietf.idtracker.models import IESGLogin, PersonOrOrgInfo, EmailAddress, IDDates +from ietf.idtracker.models import IDDates from doc.models import * from name.models import * from group.models import * @@ -182,7 +182,7 @@ class EditInfoTestCase(django.test.TestCase): events_before = draft.event_set.count() mailbox_before = len(mail_outbox) - new_ad = Email.objects.get(address="ad1@ietf.org") + new_ad = Person.objects.get(name="Ad No1") r = self.client.post(url, dict(intended_std_level=str(draft.intended_std_level.pk), @@ -266,13 +266,13 @@ class EditInfoTestCase(django.test.TestCase): events_before = draft.event_set.count() mailbox_before = len(mail_outbox) - ad = Email.objects.get(address="aread@ietf.org") + ad = Person.objects.get(name="Aread Irector") r = self.client.post(url, dict(intended_std_level=str(draft.intended_std_level_id), status_date=str(date.today() + timedelta(2)), via_rfc_editor="1", - ad=ad, + ad=ad.pk, notify="test@example.com", note="This is a note", telechat_date="", @@ -320,7 +320,7 @@ class ResurrectTestCase(django.test.TestCase): self.assertEquals(draft.event_set.count(), events_before + 1) e = draft.latest_event(type="requested_resurrect") self.assertTrue(e) - self.assertEquals(e.by, Email.objects.get(address="aread@ietf.org")) + self.assertEquals(e.by, Person.objects.get(name="Aread Irector")) self.assertTrue("Resurrection" in e.desc) self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue("Resurrection" in mail_outbox[-1]['Subject']) @@ -331,7 +331,7 @@ class ResurrectTestCase(django.test.TestCase): draft.save() Event.objects.create(doc=draft, type="requested_resurrect", - by=Email.objects.get(address="aread@ietf.org")) + by=Person.objects.get(name="Aread Irector")) url = urlreverse('doc_resurrect', kwargs=dict(name=draft.name)) @@ -392,7 +392,7 @@ class EditPositionTestCase(django.test.TestCase): url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "ad", url) - ad = Email.objects.get(address="aread@ietf.org") + ad = Person.objects.get(name="Aread Irector") # normal get r = self.client.get(url) @@ -451,7 +451,7 @@ class EditPositionTestCase(django.test.TestCase): def test_edit_position_as_secretary(self): draft = make_test_data() url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name)) - ad = Email.objects.get(address="aread@ietf.org") + ad = Person.objects.get(name="Aread Irector") url += "?ad=%s" % ad.pk login_testing_unauthorized(self, "secretary", url) @@ -476,7 +476,7 @@ class EditPositionTestCase(django.test.TestCase): draft.notify = "somebody@example.com" draft.save() - ad = Email.objects.get(address="aread@ietf.org") + ad = Person.objects.get(name="Aread Irector") BallotPositionEvent.objects.create(doc=draft, type="changed_ballot_position", by=ad, ad=ad, pos=BallotPositionName.objects.get(slug="yes"), @@ -643,7 +643,7 @@ class BallotWriteupsTestCase(django.test.TestCase): login_testing_unauthorized(self, "ad", url) def create_pos(num, vote, comment="", discuss=""): - ad = Email.objects.get(address="ad%s@ietf.org" % num) + ad = Person.objects.get(name="Ad No%s" % num) e = BallotPositionEvent() e.doc = draft e.by = ad @@ -671,7 +671,7 @@ class BallotWriteupsTestCase(django.test.TestCase): # we need approval text to be able to submit e = WriteupEvent() e.doc = draft - e.by = Email.objects.get(address="aread@ietf.org") + e.by = Person.objects.get(name="Aread Irector") e.type = "changed_ballot_approval_text" e.text = "The document has been approved." e.save() @@ -866,7 +866,7 @@ class ExpireIDsTestCase(django.test.TestCase): NewRevisionEvent.objects.create( type="new_revision", - by=Email.objects.get(address="aread@ietf.org"), + by=Person.objects.get(name="Aread Irector"), doc=draft, desc="New revision", time=datetime.datetime.now() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE - 7), @@ -897,7 +897,7 @@ class ExpireIDsTestCase(django.test.TestCase): NewRevisionEvent.objects.create( type="new_revision", - by=Email.objects.get(address="aread@ietf.org"), + by=Person.objects.get(name="Aread Irector"), doc=draft, desc="New revision", time=datetime.datetime.now() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE + 1), @@ -986,7 +986,7 @@ class ExpireIDsTestCase(django.test.TestCase): e = Event() e.doc = draft - e.by = Email.objects.get(address="(System)") + e.by = Person.objects.get(name="(System)") e.type = "expired_document" e.text = "Document has expired" e.time = datetime.date.today() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE + 1) @@ -1028,12 +1028,14 @@ class ExpireLastCallTestCase(django.test.TestCase): draft = make_test_data() draft.iesg_state_id = "lc" draft.save() + + secretary = Person.objects.get(name="Sec Retary") self.assertEquals(len(list(get_expired_last_calls())), 0) e = LastCallEvent() e.doc = draft - e.by = Email.objects.get(address="sec.retary@ietf.org") + e.by = secretary e.type = "sent_last_call" e.text = "Last call sent" e.expires = datetime.datetime.now() + datetime.timedelta(days=14) @@ -1044,7 +1046,7 @@ class ExpireLastCallTestCase(django.test.TestCase): # test expired e = LastCallEvent() e.doc = draft - e.by = Email.objects.get(address="sec.retary@ietf.org") + e.by = secretary e.type = "sent_last_call" e.text = "Last call sent" e.expires = datetime.datetime.now() diff --git a/ietf/idrfc/utils.py b/ietf/idrfc/utils.py index d885f3575..d33b6f422 100644 --- a/ietf/idrfc/utils.py +++ b/ietf/idrfc/utils.py @@ -67,7 +67,7 @@ def log_state_changedREDESIGN(request, doc, by, prev_iesg_state): e.desc = u"State changed to %s from %s by %s" % ( doc.iesg_state.name, prev_iesg_state.name if prev_iesg_state else "None", - by.get_name()) + by.name) e.save() return e @@ -145,19 +145,19 @@ def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_i if on_agenda != prev_agenda: if on_agenda: e.desc = "Placed on agenda for telechat - %s by %s" % ( - new_telechat_date, by.get_name()) + new_telechat_date, by.name) else: - e.desc = "Removed from agenda for telechat by %s" % by.get_name() + e.desc = "Removed from agenda for telechat by %s" % by.name elif on_agenda and new_telechat_date != prev_telechat: e.desc = "Telechat date has been changed to %s from %s by %s" % ( - new_telechat_date, prev_telechat, by.get_name()) + new_telechat_date, prev_telechat, by.name) else: # we didn't reschedule but flipped returning item bit - let's # just explain that if returning: - e.desc = "Added as returning item on telechat by %s" % by.get_name() + e.desc = "Added as returning item on telechat by %s" % by.name else: - e.desc = "Removed as returning item on telechat by %s" % by.get_name() + e.desc = "Removed as returning item on telechat by %s" % by.name e.save() diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 023778ca2..b8827b176 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -15,6 +15,7 @@ from django.conf 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 +from ietf.ietfauth.decorators import has_role from ietf.idtracker.models import * from ietf.iesg.models import * from ietf.ipr.models import IprDetail @@ -205,7 +206,7 @@ def edit_positionREDESIGN(request, name): if not doc.iesg_state or not started_process: raise Http404() - ad = login = request.user.get_profile().email() + ad = login = request.user.get_profile() if 'HTTP_REFERER' in request.META: return_to_url = request.META['HTTP_REFERER'] @@ -213,11 +214,12 @@ def edit_positionREDESIGN(request, name): return_to_url = doc.get_absolute_url() # if we're in the Secretariat, we can select an AD to act as stand-in for - if not in_group(request.user, "Area_Director"): + if not has_role(request.user, "Area Director"): ad_id = request.GET.get('ad') if not ad_id: raise Http404() - ad = get_object_or_404(Email, pk=ad_id) + from person.models import Person + ad = get_object_or_404(Person, pk=ad_id) old_pos = doc.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) @@ -270,17 +272,17 @@ def edit_positionREDESIGN(request, name): # figure out a description if not old_pos and pos.pos.slug != "norecord": - pos.desc = u"[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.get_name()) + pos.desc = u"[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.name) elif old_pos and pos.pos != old_pos.pos: - pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad.get_name(), pos.pos.name, old_pos.pos.name) + pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad.name, pos.pos.name, old_pos.pos.name) if not pos.desc and changes: - pos.desc = u"Ballot %s text updated for %s" % (u" and ".join(changes), ad.get_name()) + pos.desc = u"Ballot %s text updated for %s" % (u" and ".join(changes), ad.name) # only add new event if we actually got a change if pos.desc: if login != ad: - pos.desc += u" by %s" % login.get_name() + pos.desc += u" by %s" % login.name pos.save() @@ -399,7 +401,7 @@ def send_ballot_commentREDESIGN(request, name): if not started_process: raise Http404() - ad = login = request.user.get_profile().email() + ad = login = request.user.get_profile() return_to_url = request.GET.get('return_to_url') if not return_to_url: @@ -411,11 +413,12 @@ def send_ballot_commentREDESIGN(request, name): back_url = doc.get_absolute_url() # if we're in the Secretariat, we can select an AD to act as stand-in for - if not in_group(request.user, "Area_Director"): + if not has_role(request.user, "Area Director"): ad_id = request.GET.get('ad') if not ad_id: raise Http404() - ad = get_object_or_404(Email, pk=ad_id) + from person.models import Person + ad = get_object_or_404(Person, pk=ad_id) pos = doc.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) if not pos: @@ -503,7 +506,7 @@ def defer_ballotREDESIGN(request, name): if not doc.iesg_state: raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() telechat_date = TelechatDates.objects.all()[0].date2 if request.method == 'POST': @@ -519,7 +522,7 @@ def defer_ballotREDESIGN(request, name): email_state_changed(request, doc, e.desc) update_telechat(request, doc, login, telechat_date) - email_ballot_deferred(request, doc, login.get_name(), telechat_date) + email_ballot_deferred(request, doc, login.name, telechat_date) return HttpResponseRedirect(doc.get_absolute_url()) @@ -565,7 +568,7 @@ def undefer_ballotREDESIGN(request, name): if not doc.iesg_state: raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() if request.method == 'POST': save_document_in_history(doc) @@ -702,7 +705,7 @@ def lastcalltextREDESIGN(request, name): if not doc.iesg_state: raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() existing = doc.latest_event(WriteupEvent, type="changed_last_call_text") if not existing: @@ -719,7 +722,7 @@ def lastcalltextREDESIGN(request, name): e = WriteupEvent(doc=doc, by=login) e.by = login e.type = "changed_last_call_text" - e.desc = "Last call announcement was changed by %s" % login.get_name() + e.desc = "Last call announcement was changed by %s" % login.name e.text = t e.save() @@ -867,7 +870,7 @@ def ballot_writeupnotesREDESIGN(request, name): if not started_process: raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() approval = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") @@ -885,7 +888,7 @@ def ballot_writeupnotesREDESIGN(request, name): e = WriteupEvent(doc=doc, by=login) e.by = login e.type = "changed_ballot_writeup_text" - e.desc = "Ballot writeup was changed by %s" % login.get_name() + e.desc = "Ballot writeup was changed by %s" % login.name e.text = t e.save() @@ -893,13 +896,13 @@ def ballot_writeupnotesREDESIGN(request, name): doc.save() if "issue_ballot" in request.POST and approval: - if in_group(request.user, "Area_Director") and not doc.latest_event(BallotPositionEvent, ad=login, time__gte=started_process.time): + if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionEvent, ad=login, time__gte=started_process.time): # sending the ballot counts as a yes pos = BallotPositionEvent(doc=doc, by=login) pos.type = "changed_ballot_position" pos.ad = login pos.pos_id = "yes" - pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.get_name()) + pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.name) pos.save() msg = generate_issue_ballot_mail(request, doc) @@ -910,7 +913,7 @@ def ballot_writeupnotesREDESIGN(request, name): e = Event(doc=doc, by=login) e.by = login e.type = "sent_ballot_announcement" - e.desc = "Ballot has been issued by %s" % login.get_name() + e.desc = "Ballot has been issued by %s" % login.name e.save() doc.time = e.time @@ -1002,7 +1005,7 @@ def ballot_approvaltextREDESIGN(request, name): if not doc.iesg_state: raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() existing = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") if not existing: @@ -1019,7 +1022,7 @@ def ballot_approvaltextREDESIGN(request, name): e = WriteupEvent(doc=doc, by=login) e.by = login e.type = "changed_ballot_approval_text" - e.desc = "Ballot approval text was changed by %s" % login.get_name() + e.desc = "Ballot approval text was changed by %s" % login.name e.text = t e.save() @@ -1131,7 +1134,7 @@ def approve_ballotREDESIGN(request, name): if not doc.iesg_state: raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() e = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") if not e: @@ -1281,7 +1284,7 @@ def make_last_callREDESIGN(request, name): if not doc.iesg_state: raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() e = doc.latest_event(WriteupEvent, type="changed_last_call_text") if not e: @@ -1325,7 +1328,7 @@ def make_last_callREDESIGN(request, name): e = LastCallEvent(doc=doc, by=login) e.type = "sent_last_call" - e.desc = "Last call sent by %s" % login.get_name() + e.desc = "Last call sent by %s" % login.name if form.cleaned_data['last_call_sent_date'] != e.time.date(): e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time()) e.expires = form.cleaned_data['last_call_expiration_date'] diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index 4aa019825..da4450685 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -109,7 +109,7 @@ def document_main(request, name): return document_main_rfc(request, int(m.group(1))) id = get_object_or_404(InternetDraft, filename=name) doc = IdWrapper(id) - + info = {} stream_id = doc.stream_id() if stream_id == 2: @@ -168,7 +168,7 @@ def _get_history(doc, versions): info["dontmolest"] = True info['text'] = e.desc - info['by'] = e.by.get_name() + info['by'] = e.by.name info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25) info['snipped'] = info['textSnippet'][-3:] == "..." and e.type != "new_revision" results.append({'comment':e, 'info':info, 'date':e.time, 'is_com':True}) diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index d4f75aece..6fe8cfbd7 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -16,6 +16,7 @@ from django.conf 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 +from ietf.ietfauth.decorators import has_role from ietf.idtracker.models import * from ietf.iesg.models import * from ietf.idrfc.mails import * @@ -24,6 +25,7 @@ from ietf.idrfc.lastcall import request_last_call from doc.models import Document, Event, StatusDateEvent, TelechatEvent, save_document_in_history, DocHistory from name.models import IesgDocStateName, IntendedStdLevelName, DocInfoTagName, get_next_iesg_states, DocStateName +from person.models import Person, Email class ChangeStateForm(forms.Form): state = forms.ModelChoiceField(IDState.objects.all(), empty_label=None, required=True) @@ -93,7 +95,7 @@ def change_stateREDESIGN(request, name): if (not doc.latest_event(type="started_iesg_process")) or doc.state_id == "expired": raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() if request.method == 'POST': form = ChangeStateForm(request.POST) @@ -371,15 +373,11 @@ def edit_info(request, name): login=login), context_instance=RequestContext(request)) -class NameFromEmailModelChoiceField(forms.ModelChoiceField): - def label_from_instance(self, obj): - return obj.get_name() - class EditInfoFormREDESIGN(forms.Form): intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.all(), empty_label=None, required=True) status_date = forms.DateField(required=False, help_text="Format is YYYY-MM-DD") via_rfc_editor = forms.BooleanField(required=False, label="Via IRTF or RFC Editor") - ad = NameFromEmailModelChoiceField(Email.objects.filter(role__name__in=("ad", "ex-ad")).order_by('role__name', 'person__name'), label="Responsible AD", empty_label=None, required=True) + ad = forms.ModelChoiceField(Person.objects.filter(email__role__name__in=("ad", "ex-ad")).order_by('email__role__name', 'name'), label="Responsible AD", empty_label=None, required=True) notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False) note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False) telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False) @@ -392,7 +390,7 @@ class EditInfoFormREDESIGN(forms.Form): # fix up ad field choices = self.fields['ad'].choices - ex_ads = dict((e.pk, e) for e in Email.objects.filter(role__name="ex-ad")) + ex_ads = dict((e.pk, e) for e in Person.objects.filter(email__role__name="ex-ad").distinct()) if old_ads: # separate active ADs from inactive for i, t in enumerate(choices): @@ -455,7 +453,7 @@ def edit_infoREDESIGN(request, name): if doc.state_id == "expired": raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() new_document = False if not doc.iesg_state: # FIXME: should probably get this as argument to view @@ -486,7 +484,7 @@ def edit_infoREDESIGN(request, name): # place where the replace relationship is established e = Event() e.type = "added_comment" - e.by = Email.objects.get(address="(System)") + e.by = Person.objects.get(name="(System)") e.doc = doc e.desc = "Earlier history may be found in the Comment Log for %s" % (replaces[0], replaces[0].get_absolute_url()) e.save() @@ -535,7 +533,7 @@ def edit_infoREDESIGN(request, name): for c in changes: e = Event(doc=doc, by=login) e.type = "changed_document" - e.desc = c + " by %s" % login.get_name() + e.desc = c + " by %s" % login.name e.save() update_telechat(request, doc, login, @@ -548,11 +546,11 @@ def edit_infoREDESIGN(request, name): e.type ="changed_status_date" d = desc("Status date", r["status_date"], status_date) changes.append(d) - e.desc = d + " by %s" % login.get_name() + e.desc = d + " by %s" % login.name e.date = r["status_date"] e.save() - if in_group(request.user, 'Secretariat'): + if has_role(request.user, 'Secretariat'): via_rfc = DocInfoTagName.objects.get(slug="via-rfc") if r['via_rfc_editor']: doc.tags.add(via_rfc) @@ -580,7 +578,7 @@ def edit_infoREDESIGN(request, name): form = EditInfoForm(old_ads=False, initial=init) - if not in_group(request.user, 'Secretariat'): + if not has_role(request.user, 'Secretariat'): # filter out Via RFC Editor form.standard_fields = [x for x in form.standard_fields if x.name != "via_rfc_editor"] @@ -627,14 +625,14 @@ def request_resurrectREDESIGN(request, name): if doc.state_id != "expired": raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() if request.method == 'POST': email_resurrect_requested(request, doc, login) e = Event(doc=doc, by=login) e.type = "requested_resurrect" - e.desc = "Resurrection was requested by %s" % login.get_name() + e.desc = "Resurrection was requested by %s" % login.name e.save() return HttpResponseRedirect(doc.get_absolute_url()) @@ -682,7 +680,7 @@ def resurrectREDESIGN(request, name): if doc.state_id != "expired": raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() if request.method == 'POST': save_document_in_history(doc) @@ -693,7 +691,7 @@ def resurrectREDESIGN(request, name): e = Event(doc=doc, by=login) e.type = "completed_resurrect" - e.desc = "Resurrection was completed by %s" % login.get_name() + e.desc = "Resurrection was completed by %s" % login.name e.save() doc.state = DocStateName.objects.get(slug="active") @@ -746,7 +744,7 @@ def add_commentREDESIGN(request, name): if not doc.iesg_state: raise Http404() - login = request.user.get_profile().email() + login = request.user.get_profile() if request.method == 'POST': form = AddCommentForm(request.POST) @@ -759,7 +757,7 @@ def add_commentREDESIGN(request, name): e.save() email_owner(request, doc, doc.ad, login, - "A new comment added by %s" % login.get_name()) + "A new comment added by %s" % login.name) return HttpResponseRedirect(doc.get_absolute_url()) else: form = AddCommentForm() diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index b91e4d4fd..4207acbd4 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -276,18 +276,16 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: 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 = list(Person.objects.filter(email__role__name="ad", + email__role__group__type="area", + email__role__group__state="active").distinct()) + inactive_ads = list(Person.objects.filter(pk__in=responsible) + .exclude(pk__in=[x.pk for x in active_ads])) + extract_last_name = lambda x: x.name_parts()[3] active_ads.sort(key=extract_last_name) inactive_ads.sort(key=extract_last_name) - self.fields['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.get_name()) for ad in active_ads] + [('', '------------------')] + [(ad.pk, ad.get_name()) for ad in inactive_ads] + self.fields['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.name) for ad in active_ads] + [('', '------------------')] + [(ad.pk, ad.name) for ad in inactive_ads] self.fields['subState'].choices = [('', 'any substate'), ('0', 'no substate')] + [(n.slug, n.name) for n in DocInfoTagName.objects.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))] def clean_name(self): value = self.cleaned_data.get('name','') @@ -479,16 +477,21 @@ def search_main(request): def by_ad(request, name): ad_id = None ad_name = None - for i in IESGLogin.objects.filter(user_level__in=[1,2]): - iname = str(i).lower().replace(' ','.') - if name == iname: - ad_id = i.id - ad_name = str(i) - break + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + for p in Person.objects.filter(email__role__name__in=("ad", "ex-ad")): + if name == p.name.lower().replace(" ", "."): + ad_id = p.id + ad_name = p.name + break + else: + for i in IESGLogin.objects.filter(user_level__in=[1,2]): + iname = str(i).lower().replace(' ','.') + if name == iname: + ad_id = i.id + ad_name = str(i) + 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(): diff --git a/ietf/idtracker/admin.py b/ietf/idtracker/admin.py index 5c90f0261..26bca3ae6 100644 --- a/ietf/idtracker/admin.py +++ b/ietf/idtracker/admin.py @@ -88,7 +88,8 @@ class IESGLoginAdmin(admin.ModelAdmin): ordering=['user_level', 'last_name'] list_display=('login_name', 'first_name', 'last_name', 'user_level') raw_id_fields=['person'] -admin.site.register(IESGLogin, IESGLoginAdmin) +if not settings.USE_DB_REDESIGN_PROXY_CLASSES: + admin.site.register(IESGLogin, IESGLoginAdmin) class IETFWGAdmin(admin.ModelAdmin): list_display=('group_acronym', 'group_type', 'status', 'area_acronym', 'start_date', 'concluded_date') diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py index 9f134a11b..125123cb0 100644 --- a/ietf/idtracker/models.py +++ b/ietf/idtracker/models.py @@ -1094,8 +1094,10 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: IDSubStateOld = IDSubState AreaOld = Area AcronymOld = Acronym + IESGLoginOld = IESGLogin from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo, IDState, IDSubState from redesign.group.proxy import Area, Acronym + from redesign.person.proxy import IESGLogin # changes done by convert-096.py:changed maxlength to max_length diff --git a/ietf/idtracker/templatetags/ietf_filters.py b/ietf/idtracker/templatetags/ietf_filters.py index 03c262c78..3f9e1a4d3 100644 --- a/ietf/idtracker/templatetags/ietf_filters.py +++ b/ietf/idtracker/templatetags/ietf_filters.py @@ -2,6 +2,7 @@ import textwrap from django import template +from django.conf import settings from django.utils.html import escape, fix_ampersands from django.template.defaultfilters import linebreaksbr, wordwrap, stringfilter from django.template import resolve_variable @@ -358,8 +359,18 @@ def startswith(x, y): # based on http://www.djangosnippets.org/snippets/847/ by 'whiteinge' @register.filter def in_group(user, groups): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + return has_role(user, groups.replace("Area_Director", "Area Director")) + return user and user.is_authenticated() and bool(user.groups.filter(name__in=groups.split(',')).values('name')) +@register.filter +def has_role(user, role_names): + from ietf.ietfauth.decorators import has_role + if not user: + return False + return has_role(user, role_names.split(',')) + @register.filter def stable_dictsort(value, arg): """ diff --git a/ietf/iesg/feeds.py b/ietf/iesg/feeds.py index 356f2d697..66d49719b 100644 --- a/ietf/iesg/feeds.py +++ b/ietf/iesg/feeds.py @@ -42,6 +42,6 @@ class IESGAgenda(Feed): return str( item.job_owner ) def item_author_email(self, item): if settings.USE_DB_REDESIGN_PROXY_CLASSES: - return item.ad.address + return item.ad.email_address() return item.job_owner.person.email()[1] diff --git a/ietf/iesg/tests.py b/ietf/iesg/tests.py index f40c92858..8486eab48 100644 --- a/ietf/iesg/tests.py +++ b/ietf/iesg/tests.py @@ -66,7 +66,7 @@ class RescheduleOnAgendaTestCaseREDESIGN(django.test.TestCase): def test_reschedule(self): from ietf.utils.test_data import make_test_data - from redesign.person.models import Email + from redesign.person.models import Person from doc.models import TelechatEvent draft = make_test_data() @@ -74,7 +74,7 @@ class RescheduleOnAgendaTestCaseREDESIGN(django.test.TestCase): # add to schedule e = TelechatEvent(type="scheduled_for_telechat") e.doc = draft - e.by = Email.objects.get(address="aread@ietf.org") + e.by = Person.objects.get(name="Aread Irector") e.telechat_date = TelechatDates.objects.all()[0].date1 e.returning_item = True e.save() @@ -159,6 +159,56 @@ class ManageTelechatDatesTestCase(django.test.TestCase): self.assertTrue(dates.date4 == new_date) self.assertTrue(dates.date1 == old_date2) +class ManageTelechatDatesTestCaseREDESIGN(django.test.TestCase): + fixtures = ['names'] + + def test_set_dates(self): + from ietf.utils.test_data import make_test_data + make_test_data() + + dates = TelechatDates.objects.all()[0] + url = urlreverse('ietf.iesg.views.telechat_dates') + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form input[name=date1]')), 1) + + # post + new_date = dates.date1 + timedelta(days=7) + + r = self.client.post(url, dict(date1=new_date.isoformat(), + date2=new_date.isoformat(), + date3=new_date.isoformat(), + date4=new_date.isoformat(), + )) + self.assertEquals(r.status_code, 200) + + dates = TelechatDates.objects.all()[0] + self.assertTrue(dates.date1 == new_date) + + def test_rollup_dates(self): + from ietf.utils.test_data import make_test_data + make_test_data() + + dates = TelechatDates.objects.all()[0] + url = urlreverse('ietf.iesg.views.telechat_dates') + login_testing_unauthorized(self, "secretary", url) + + old_date2 = dates.date2 + new_date = dates.date4 + timedelta(days=14) + r = self.client.post(url, dict(rollup_dates="1")) + self.assertEquals(r.status_code, 200) + + dates = TelechatDates.objects.all()[0] + self.assertTrue(dates.date4 == new_date) + self.assertTrue(dates.date1 == old_date2) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + ManageTelechatDatesTestCase = ManageTelechatDatesTestCaseREDESIGN + class WorkingGroupActionsTestCase(django.test.TestCase): fixtures = ['base', 'wgactions'] @@ -320,7 +370,7 @@ class WorkingGroupActionsTestCaseREDESIGN(django.test.TestCase): def test_edit_wgaction(self): from ietf.utils.test_data import make_test_data - from redesign.person.models import Email + from redesign.person.models import Person make_test_data() @@ -337,7 +387,7 @@ class WorkingGroupActionsTestCaseREDESIGN(django.test.TestCase): # change dates = TelechatDates.objects.all()[0] - token_name = Email.objects.get(address="ad1@ietf.org").get_name().split(" ")[0] + token_name = Person.objects.get(name="Ad No1").name_parts()[1] old = wga.pk r = self.client.post(url, dict(status_date=dates.date1.isoformat(), token_name=token_name, @@ -355,7 +405,7 @@ class WorkingGroupActionsTestCaseREDESIGN(django.test.TestCase): def test_add_possible_wg(self): from ietf.utils.test_data import make_test_data - from redesign.person.models import Email + from redesign.person.models import Person from redesign.group.models import Group make_test_data() @@ -388,7 +438,7 @@ class WorkingGroupActionsTestCaseREDESIGN(django.test.TestCase): wgas_before = WGAction.objects.all().count() dates = TelechatDates.objects.all()[0] - token_name = Email.objects.get(address="ad1@ietf.org").get_name().split(" ")[0] + token_name = Person.objects.get(name="Ad No1").name_parts()[1] r = self.client.post(add_url, dict(status_date=dates.date1.isoformat(), token_name=token_name, diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index d8dcf241e..51602e77e 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -309,6 +309,7 @@ def agenda_scribe_template(request): def _agenda_moderator_package(request): data = _agenda_data(request) data['ad_names'] = [str(x) for x in IESGLogin.active_iesg()] + data['ad_names'].sort(key=lambda x: x.split(' ')[-1]) return render_to_response("iesg/moderator_package.html", data, context_instance=RequestContext(request)) @group_required('Area_Director','Secretariat') @@ -381,7 +382,7 @@ def handle_reschedule_form(request, idinternal, dates): form = RescheduleForm(request.POST, **formargs) if form.is_valid(): if settings.USE_DB_REDESIGN_PROXY_CLASSES: - login = request.user.get_profile().email() + login = request.user.get_profile() update_telechat(request, idinternal, login, form.cleaned_data['telechat_date'], False if form.cleaned_data['clear_returning_item'] else None) diff --git a/ietf/ietfauth/decorators.py b/ietf/ietfauth/decorators.py index a1c3db2fc..c1b1d66a6 100644 --- a/ietf/ietfauth/decorators.py +++ b/ietf/ietfauth/decorators.py @@ -31,6 +31,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from django.utils.http import urlquote +from django.conf import settings +from django.db.models import Q from django.contrib.auth.decorators import _CheckLogin from django.http import HttpResponseRedirect, HttpResponseForbidden @@ -62,3 +64,48 @@ def group_required(*group_names): def decorate(view_func): return _CheckLogin403(view_func, lambda u: bool(u.groups.filter(name__in=group_names)), "Restricted to group%s %s" % ("s" if len(group_names) != 1 else "", ",".join(group_names))) return decorate + + +def has_role(user, role_names): + """Determines whether user has any of the given standard roles + given. Role names must be a list or, in case of a single value, a + string.""" + if isinstance(role_names, str) or isinstance(role_names, unicode): + role_names = [ role_names ] + + if not user or not user.is_authenticated(): + return False + + from redesign.person.models import Person + + try: + person = user.get_profile() + except Person.DoesNotExist: + return False + + role_qs = { + "Area Director": Q(email__person=person, name="ad"), + "Secretariat": Q(email__person=person, name="secr", group__acronym="secretariat") + } + + filter_expr = Q() + for r in role_names: + filter_expr |= role_qs[r] + + from redesign.group.models import Role + return bool(Role.objects.filter(filter_expr)[:1]) + +def role_required(*role_names): + """View decorator for checking that the user is logged in and + belongs to (at least) one of the listed roles. Users who are not + logged in are redirected to the login page; users who don't have + one of the roles get a "403" page. + """ + def decorate(view_func): + return _CheckLogin403(view_func, + lambda u: has_role(u, role_names), + "Restricted to role%s %s" % ("s" if len(role_names) != 1 else "", ",".join(role_names))) + return decorate + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + group_required = lambda *group_names: role_required(*[n.replace("Area_Director", "Area Director") for n in group_names]) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index e4c6d6675..c9e2d8371 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -31,6 +31,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import unittest +from django.conf import settings from django.contrib.auth.models import User from django.test.client import Client from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest @@ -41,6 +42,8 @@ class IetfAuthUrlTestCase(SimpleUrlTestCase): def testUrls(self): self.doTestUrls(__file__) +# this test case should really work on a test database instead of the +# real one class IetfAuthTestCase(unittest.TestCase,RealDatabaseTest): def setUp(self): self.setUpRealDatabase() @@ -61,7 +64,7 @@ class IetfAuthTestCase(unittest.TestCase,RealDatabaseTest): response = c.get(nexturl[2], {}, False, REMOTE_USER=username) self.assertEquals(response.status_code, 200) - self.assert_("Roles/Groups:" in response.content) + self.assert_("User name" in response.content) return response def testLogin(self): @@ -95,4 +98,7 @@ class IetfAuthTestCase(unittest.TestCase,RealDatabaseTest): self.assert_("IETF_Chair" in groups) print "OK" - + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + # this test doesn't make any sense anymore + IetfAuthTestCase.testGroups = lambda x: None diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index e350791a8..5750d23cc 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -69,4 +69,21 @@ def ietf_loggedin(request): @login_required def profile(request): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from person.models import Person + from group.models import Role + + roles = [] + person = None + try: + person = request.user.get_profile() + roles = Role.objects.filter(email__person=person).distinct() + except Person.DoesNotExist: + pass + + return render_to_response('registration/profileREDESIGN.html', + dict(roles=roles, + person=person), + context_instance=RequestContext(request)) + return render_to_response('registration/profile.html', context_instance=RequestContext(request)) diff --git a/ietf/settings.py b/ietf/settings.py index 110773850..bd3f06720 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -198,6 +198,10 @@ LIAISON_ATTACH_URL = '/documents/LIAISON/' # DB redesign USE_DB_REDESIGN_PROXY_CLASSES = True +if USE_DB_REDESIGN_PROXY_CLASSES: + AUTH_PROFILE_MODULE = 'person.Person' + AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.RemoteUserBackend', ) + # Put SECRET_KEY in here, or any other sensitive or site-specific # changes. DO NOT commit settings_local.py to svn. from settings_local import * diff --git a/ietf/templates/idrfc/edit_positionREDESIGN.html b/ietf/templates/idrfc/edit_positionREDESIGN.html index 8b2c6cf2e..a81b8e738 100644 --- a/ietf/templates/idrfc/edit_positionREDESIGN.html +++ b/ietf/templates/idrfc/edit_positionREDESIGN.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}Change position for {{ ad.get_name }}{% endblock %} +{% block title %}Change position for {{ ad.name }}{% endblock %} {% block morecss %} form.position-form .position ul { @@ -29,7 +29,7 @@ form.position-form .comment-text { {% endblock %} {% block content %} -

      Change position for {{ ad.get_name }}

      +

      Change position for {{ ad.name }}

      {{ form.position }}
      diff --git a/ietf/templates/idrfc/send_ballot_commentREDESIGN.html b/ietf/templates/idrfc/send_ballot_commentREDESIGN.html index d9301a678..1de2fca67 100644 --- a/ietf/templates/idrfc/send_ballot_commentREDESIGN.html +++ b/ietf/templates/idrfc/send_ballot_commentREDESIGN.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}Email Discuss and Comment text for {{ ad.get_name }} to IESG list{% endblock %} +{% block title %}Email Discuss and Comment text for {{ ad.name }} to IESG list{% endblock %} {% block morecss %} form.send-ballot pre { @@ -12,7 +12,7 @@ form.send-ballot pre { {% endblock %} {% block content %} -

      Email Discuss and Comment text for {{ ad.get_name }} to IESG list

      +

      Email Discuss and Comment text for {{ ad.name }} to IESG list

      diff --git a/ietf/templates/iesg/scribe_doc.html b/ietf/templates/iesg/scribe_doc.html index dc072c10b..2945f6f9c 100644 --- a/ietf/templates/iesg/scribe_doc.html +++ b/ietf/templates/iesg/scribe_doc.html @@ -49,9 +49,9 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved. {% endfor %} {% if doc.obj.ballot.active %}
      Discusses/comments (from ballot {{doc.obj.ballot.ballot }}):
        {% if USE_DB_REDESIGN_PROXY_CLASSES %} -{% for p in doc.obj.active_positions|dictsort:"ad.get_name" %}{% if p.pos %}{% ifequal p.pos.pos_id "discuss" %}
      1. {{ p.pos.ad.get_name }}: Discuss [{{ p.pos.discuss_time.date }}]: +{% for p in doc.obj.active_positions|dictsort:"ad.name" %}{% if p.pos %}{% ifequal p.pos.pos_id "discuss" %}
      2. {{ p.pos.ad.name }}: Discuss [{{ p.pos.discuss_time.date }}]:
        ... -{% endifequal %}{% if p.pos.comment %}
      3. {{ p.pos.ad.get_name }}: Comment [{{ p.pos.comment_time.date }}]: +{% endifequal %}{% if p.pos.comment %}
      4. {{ p.pos.ad.name }}: Comment [{{ p.pos.comment_time.date }}]:
        ... {% endif %}{% endif %}{% endfor %} {% else %} diff --git a/ietf/templates/registration/profileREDESIGN.html b/ietf/templates/registration/profileREDESIGN.html new file mode 100644 index 000000000..5c6083d6c --- /dev/null +++ b/ietf/templates/registration/profileREDESIGN.html @@ -0,0 +1,31 @@ +{# Copyright The IETF Trust 2007, All Rights Reserved #} +{% extends "base.html" %} + +{% block morecss %} +table.userProfile th { + text-align: left; +} +{% endblock %} + +{% block title %}Profile for {{ user }}{% endblock %} + +{% block content %} +

        User information

        + +
      + + + + + + + + + {% for role in roles %} + + + + + {% endfor %} +
      User name:{{ user.username }}
      Person:{{ person.name|default:"?" }}
      {% if forloop.first %}Roles:{% endif %}{{ role.name }} in {{ role.group.name }} ({{ role.group.type }})
      +{% endblock %} diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index bb74100ea..2bdcf3fdb 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -1,4 +1,5 @@ -from ietf.idtracker.models import IESGLogin, PersonOrOrgInfo, EmailAddress +from django.contrib.auth.models import User + from ietf.iesg.models import TelechatDates, WGAction from redesign.doc.models import * from redesign.name.models import * @@ -7,6 +8,12 @@ from redesign.person.models import * def make_test_data(): # groups + secretariat = Group.objects.create( + name="Secretariat", + acronym="secretariat", + state_id="active", + type_id="ietf", + parent=None) area = Group.objects.create( name="Far Future", acronym="farfut", @@ -22,45 +29,43 @@ def make_test_data(): ) # persons - Email.objects.get_or_create(address="(System)") + + # system + system_person = Person.objects.create( + id=0, # special value + name="(System)", + ascii="(System)", + address="", + ) + + if system_person.id != 0: # work around bug in Django + Person.objects.filter(id=system_person.id).update(id=0) + system_person = Person.objects.get(id=0) + + Alias.objects.get_or_create(person=system_person, name=system_person.name) + Email.objects.get_or_create(address="", person=system_person) # ad - p = Person.objects.create( + u = User.objects.create(username="ad") + ad = p = Person.objects.create( name="Aread Irector", ascii="Aread Irector", - ) - ad = Email.objects.create( + user=u) + email = 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=porg.first_name, - last_name=porg.last_name, - person=porg, - ) + email=email) # create a bunch of ads for swarm tests for i in range(1, 10): + u = User.objects.create(username="ad%s" % i) p = Person.objects.create( name="Ad No%s" % i, ascii="Ad No%s" % i, - ) + user=u) email = Email.objects.create( address="ad%s@ietf.org" % i, person=p) @@ -68,24 +73,6 @@ def make_test_data(): name_id="ad" if i <= 5 else "ex-ad", group=area, email=email) - porg = PersonOrOrgInfo.objects.create( - first_name="Ad", - last_name="No%s" % i, - middle_initial="", - ) - EmailAddress.objects.create( - person_or_org=porg, - priority=1, - address=ad.address, - ) - IESGLogin.objects.create( - login_name="ad%s" % i, - password="foo", - user_level=1, - first_name=porg.first_name, - last_name=porg.last_name, - person=porg, - ) # group chair p = Person.objects.create( @@ -96,36 +83,24 @@ def make_test_data(): address="wgchairman@ietf.org", person=p) Role.objects.create( - name=RoleName.objects.get(slug="chair"), + name_id="chair", group=group, email=wgchair, ) # secretary + u = User.objects.create(username="secretary") p = Person.objects.create( name="Sec Retary", ascii="Sec Retary", - ) - Email.objects.create( + user=u) + email = 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=porg.first_name, - last_name=porg.last_name, - person=porg, + Role.objects.create( + name_id="secr", + group=secretariat, + email=email, ) # draft diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 4615b1959..ea7969584 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse as urlreverse from redesign.group.models import * from redesign.name.models import * -from redesign.person.models import Email +from redesign.person.models import Email, Person from redesign.util import admin_link import datetime @@ -31,8 +31,8 @@ class DocumentInfo(models.Model): pages = models.IntegerField(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, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True) - shepherd = models.ForeignKey(Email, related_name='shepherd_%(class)s_set', blank=True, null=True) + ad = models.ForeignKey(Person, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True) + shepherd = models.ForeignKey(Person, related_name='shepherd_%(class)s_set', blank=True, null=True) notify = models.CharField(max_length=255, blank=True) external_url = models.URLField(blank=True) # Should be set for documents with type 'External'. note = models.TextField(blank=True) @@ -60,8 +60,8 @@ class RelatedDocument(models.Model): class DocumentAuthor(models.Model): document = models.ForeignKey('Document') - author = models.ForeignKey(Email) - order = models.IntegerField() + author = models.ForeignKey(Email, help_text="Email address used by author for submission") + order = models.IntegerField(default=1) def __unicode__(self): return u"%s %s (%s)" % (self.document.name, self.author.get_name(), self.order) @@ -224,12 +224,12 @@ class Event(models.Model): """An occurrence for a document, used for tracking who, when and what.""" 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) # FIXME: make NOT NULL? + by = models.ForeignKey(Person) 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) + return u"%s %s at %s" % (self.by.name, self.get_type_display().lower(), self.time) class Meta: ordering = ['-time', 'id'] @@ -239,7 +239,7 @@ class NewRevisionEvent(Event): # IESG events class BallotPositionEvent(Event): - ad = models.ForeignKey(Email) + ad = models.ForeignKey(Person) pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord") 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) diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index c7e75465d..2c2b0ae28 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -305,12 +305,12 @@ class InternetDraft(Document): #token_name = models.CharField(blank=True, max_length=25) @property def token_name(self): - return self.ad.get_name() + return self.ad.name #token_email = models.CharField(blank=True, max_length=255) @property def token_email(self): - return self.ad.address + return self.ad.email_address() #note = models.TextField(blank=True) # same name @@ -556,7 +556,7 @@ class InternetDraft(Document): # 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") + active_ads = Person.objects.filter(email__role__name="ad", email__role__group__state="active") res = [] def add(ad, pos): from person.proxy import IESGLogin as IESGLoginProxy @@ -638,7 +638,7 @@ class InternetDraft(Document): e = self.published_rfc else: e = self.latest_event(type="published_rfc") - return e.time.date() if e else None + return e.time.date() if e else datetime.date(1990,1,1) #current_status = models.CharField(max_length=50,null=True) @property @@ -782,11 +782,11 @@ class DocumentComment(Event): def get_absolute_url(self): return "/doc/%s/" % self.doc.name def get_author(self): - return self.by.get_name() + return self.by.name def get_username(self): return unicode(self.by) def get_fullname(self): - return self.by.get_name() + return self.by.name def datetime(self): return self.time def __str__(self): diff --git a/redesign/group/models.py b/redesign/group/models.py index 6e7ef00ce..327970f4a 100644 --- a/redesign/group/models.py +++ b/redesign/group/models.py @@ -2,7 +2,7 @@ from django.db import models from redesign.name.models import * -from redesign.person.models import Email +from redesign.person.models import Email, Person import datetime @@ -35,11 +35,11 @@ class GroupEvent(models.Model): group = models.ForeignKey(Group) time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened") type = models.CharField(max_length=50, choices=GROUP_EVENT_CHOICES) - by = models.ForeignKey(Email) + by = models.ForeignKey(Person) desc = models.TextField() def __unicode__(self): - return u"%s %s at %s" % (self.by.get_name(), self.get_type_display().lower(), self.time) + return u"%s %s at %s" % (self.by.name, self.get_type_display().lower(), self.time) class Meta: ordering = ['-time', 'id'] @@ -73,8 +73,8 @@ class GroupHistory(models.Model): class Role(models.Model): name = models.ForeignKey(RoleName) group = models.ForeignKey(Group) - email = models.ForeignKey(Email) - auth = models.CharField(max_length=255, blank=True) + email = models.ForeignKey(Email, help_text="Email address used by person for this role") + auth = models.CharField(max_length=255, blank=True) # unused? def __unicode__(self): return u"%s is %s in %s" % (self.email.get_name(), self.name.name, self.group.acronym) diff --git a/redesign/importing/import-announcements.py b/redesign/importing/import-announcements.py index 4a0984bdf..f423565cc 100755 --- a/redesign/importing/import-announcements.py +++ b/redesign/importing/import-announcements.py @@ -7,6 +7,7 @@ sys.path = [ basedir ] + sys.path from ietf import settings settings.USE_DB_REDESIGN_PROXY_CLASSES = False +settings.IMPORTING_ANNOUNCEMENTS = True from django.core import management management.setup_environ(settings) @@ -14,7 +15,7 @@ management.setup_environ(settings) from redesign.person.models import * from redesign.group.models import * from redesign.name.utils import name -from redesign.importing.utils import person_email +from redesign.importing.utils import old_person_to_person from ietf.announcements.models import Message from ietf.announcements.models import Announcement, PersonOrOrgInfo, AnnouncedTo, AnnouncedFrom @@ -26,7 +27,7 @@ from ietf.announcements.models import Announcement, PersonOrOrgInfo, AnnouncedTo # FIXME: should import ScheduledAnnouncements -system_email, _ = Email.objects.get_or_create(address="(System)") +system = Person.objects.get(name="(System)") # Announcement for o in Announcement.objects.all().select_related('announced_to', 'announced_from').order_by('announcement_id').iterator(): @@ -41,12 +42,12 @@ for o in Announcement.objects.all().select_related('announced_to', 'announced_fr try: x = o.announced_by except PersonOrOrgInfo.DoesNotExist: - message.by = system_email + message.by = system else: if not o.announced_by.first_name and o.announced_by.last_name == 'None': - message.by = system_email + message.by = system else: - message.by = Email.objects.get(address=person_email(o.announced_by)) + message.by = old_person_to_person(o.announced_by) message.subject = o.subject.strip() if o.announced_from_id == 99: diff --git a/redesign/importing/import-document-state.py b/redesign/importing/import-document-state.py index 961635054..b7c9fff0a 100755 --- a/redesign/importing/import-document-state.py +++ b/redesign/importing/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 redesign.importing.utils import person_email +from redesign.importing.utils import old_person_to_person from redesign.name.utils import name from ietf.idtracker.models import InternetDraft, IDInternal, IESGLogin, DocumentComment, PersonOrOrgInfo, Rfc, IESGComment, IESGDiscuss, BallotInfo, Position from ietf.idrfc.models import RfcIndex, DraftVersions @@ -145,7 +145,7 @@ tag_has_errata = name(DocInfoTagName, 'errata', "Has errata") # helpers def save_event(doc, event, comment): event.time = comment.datetime() - event.by = iesg_login_to_email(comment.created_by) + event.by = iesg_login_to_person(comment.created_by) event.doc = doc if not event.desc: event.desc = comment.comment_text # FIXME: consider unquoting here @@ -159,17 +159,16 @@ def sync_tag(d, include, tag): buggy_iesg_logins_cache = {} -# make sure system email exists -system_email, _ = Email.objects.get_or_create(address="(System)") +system = Person.objects.get(name="(System)") -def iesg_login_to_email(l): +def iesg_login_to_person(l): if not l: - return system_email + return system 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 + return system # fix logins without the right person if not l.person: @@ -185,19 +184,22 @@ def iesg_login_to_email(l): else: buggy_iesg_logins_cache[l.id] = None l = buggy_iesg_logins_cache[l.id] + + if not l: + return system try: - return Email.objects.get(address=person_email(l.person)) + return old_person_to_person(l.person) except Email.DoesNotExist: 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 Person.objects.get(name="%s %s" % (l.person.first_name, l.person.last_name)) + except Person.DoesNotExist: + print "MISSING IESG LOGIN", l.person, 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" + # Amy has two users, for some reason, we sometimes get the wrong one + return l.user_level == IESGLogin.SECRETARIAT_LEVEL or (l.first_name == "Amy" and l.last_name == "Vezza") # regexps for parsing document comments @@ -234,10 +236,9 @@ re_comment_discuss_by_tag = re.compile(r" by [\w-]+ [\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.ad = iesg_login_to_person(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.note = (idinternal.note or "").replace('
      ', '\n').strip().replace('\n', '
      ') d.save() # extract events @@ -267,14 +268,14 @@ def import_from_idinternal(d, idinternal): save_event(d, e, c) handled = True - ad = iesg_login_to_email(c.created_by) + ad = iesg_login_to_person(c.created_by) last_pos = d.latest_event(BallotPositionEvent, 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 = BallotPositionEvent() 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.desc = "[Ballot Position Update] New position, Yes, has been recorded by %s" % e.ad.name e.pos = ballot_position_mapping["Yes"] e.discuss = last_pos.discuss if last_pos else "" @@ -309,7 +310,7 @@ def import_from_idinternal(d, idinternal): 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)): + if not d.event_set.filter(type="changed_ballot_position", ballotposition__pos=position, ballotposition__ad=iesg_login_to_person(p.ad)): login = p.ad found = True break @@ -335,7 +336,7 @@ def import_from_idinternal(d, idinternal): e = BallotPositionEvent() e.type = "changed_ballot_position" - e.ad = iesg_login_to_email(login) + e.ad = iesg_login_to_person(login) last_pos = d.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=e.ad) e.pos = position e.discuss = last_pos.discuss if last_pos else "" @@ -353,7 +354,7 @@ def import_from_idinternal(d, idinternal): if c.ballot in (DocumentComment.BALLOT_DISCUSS, DocumentComment.BALLOT_COMMENT): e = BallotPositionEvent() e.type = "changed_ballot_position" - e.ad = iesg_login_to_email(c.created_by) + e.ad = iesg_login_to_person(c.created_by) last_pos = d.latest_event(BallotPositionEvent, 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) @@ -565,7 +566,7 @@ def import_from_idinternal(d, idinternal): if idinternal.status_date != status_date: e = StatusDateEvent(type="changed_status_date", date=idinternal.status_date) e.time = made_up_date - e.by = system_email + e.by = system e.doc = d e.desc = "Status date has been changed to %s from %s" % (idinternal.status_date, status_date) e.save() @@ -584,7 +585,7 @@ def import_from_idinternal(d, idinternal): # 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 + e.by = system 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 @@ -611,7 +612,7 @@ def import_from_idinternal(d, idinternal): if iesg_login_is_secretary(p.ad): continue - ad = iesg_login_to_email(p.ad) + ad = iesg_login_to_person(p.ad) if p.noobj > 0: pos = ballot_position_mapping["No Objection"] elif p.yes > 0: @@ -636,7 +637,7 @@ def import_from_idinternal(d, idinternal): e.type = "changed_ballot_position" e.doc = d e.time = position_date - e.by = system_email + e.by = system e.ad = ad last_pos = d.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=e.ad) e.pos = pos @@ -649,9 +650,9 @@ def import_from_idinternal(d, idinternal): 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) + e.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (ad.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.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.name, ad.name) e.save() # make sure we got the ballot issued event @@ -666,14 +667,14 @@ def import_from_idinternal(d, idinternal): e.type = "sent_ballot_announcement" e.doc = d e.time = sent_date - e.by = system_email + e.by = system e.desc = "Ballot has been issued" e.save() # make sure the comments and discusses are updated positions = list(BallotPositionEvent.objects.filter(doc=d).order_by("-time", '-id')) for c in IESGComment.objects.filter(ballot=ballot): - ad = iesg_login_to_email(c.ad) + ad = iesg_login_to_person(c.ad) for p in positions: if p.ad == ad: if p.comment != c.text: @@ -683,7 +684,7 @@ def import_from_idinternal(d, idinternal): break for c in IESGDiscuss.objects.filter(ballot=ballot): - ad = iesg_login_to_email(c.ad) + ad = iesg_login_to_person(c.ad) for p in positions: if p.ad == ad: if p.discuss != c.text: @@ -701,26 +702,26 @@ def import_from_idinternal(d, idinternal): text_date = made_up_date if idinternal.ballot.approval_text: - e, _ = WriteupEvent.objects.get_or_create(type="changed_ballot_approval_text", doc=d) + e, _ = WriteupEvent.objects.get_or_create(type="changed_ballot_approval_text", doc=d, + defaults=dict(by=system)) e.text = 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, _ = WriteupEvent.objects.get_or_create(type="changed_last_call_text", doc=d) + e, _ = WriteupEvent.objects.get_or_create(type="changed_last_call_text", doc=d, + defaults=dict(by=system)) e.text = 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, _ = WriteupEvent.objects.get_or_create(type="changed_ballot_writeup_text", doc=d) + e, _ = WriteupEvent.objects.get_or_create(type="changed_ballot_writeup_text", doc=d, + defaults=dict(by=system)) e.text = idinternal.ballot.ballot_writeup e.time = text_date - e.by = system_email e.desc = "Ballot writeup text was added" e.save() @@ -728,10 +729,9 @@ def import_from_idinternal(d, idinternal): 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() + Event.objects.get_or_create(type="added_comment", doc=d, desc=desc, + defaults=dict(time=made_up_date, + by=system)) # fix tags sync_tag(d, idinternal.via_rfc_editor, tag_via_rfc_editor) @@ -798,10 +798,11 @@ for index, o in enumerate(all_drafts.iterator()): if o.rfc_number: alias_doc("rfc%s" % o.rfc_number, d) + # authors 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] or u"unknown-email-%s-%s" % (a.person.first_name, a.person.last_name)) + e = Email.objects.get(address__iexact=a.email() or 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: @@ -827,7 +828,7 @@ for index, o in enumerate(all_drafts.iterator()): # 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, 0)) + datetime.timedelta(seconds=int(v.revision)) - e.by = system_email + e.by = system e.doc = d e.desc = "New version available" e.save() @@ -844,7 +845,7 @@ for index, o in enumerate(all_drafts.iterator()): 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.by = system e.doc = d e.desc = "Do Not Publish note has been sent to RFC Editor" if disapproved else "IESG has approved" e.save() @@ -856,7 +857,7 @@ for index, o in enumerate(all_drafts.iterator()): # 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.by = events[0].by if events else system e.doc = d e.desc = "Last call sent" e.save() @@ -956,9 +957,9 @@ for index, o in enumerate(all_rfcs.iterator()): import_from_idinternal(d, internals[0]) # publication date - e, _ = Event.objects.get_or_create(doc=d, type="published_rfc") + e, _ = Event.objects.get_or_create(doc=d, type="published_rfc", + defaults=dict(by=system)) e.time = o.rfc_published_date - e.by = system_email e.desc = "RFC published" e.save() diff --git a/redesign/importing/import-groups.py b/redesign/importing/import-groups.py index 48632e966..080064539 100755 --- a/redesign/importing/import-groups.py +++ b/redesign/importing/import-groups.py @@ -21,6 +21,8 @@ from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, A # also creates nomcom groups +# assumptions: persons have been imported + state_names = dict( bof=name(GroupStateName, slug="bof", name="BOF"), proposed=name(GroupStateName, slug="proposed", name="Proposed"), @@ -33,6 +35,7 @@ state_names = dict( type_names = dict( ietf=name(GroupTypeName, slug="ietf", name="IETF"), area=name(GroupTypeName, slug="area", name="Area"), + ag=name(GroupTypeName, slug="ag", name="AG"), wg=name(GroupTypeName, slug="wg", name="WG"), rg=name(GroupTypeName, slug="rg", name="RG"), team=name(GroupTypeName, slug="team", name="Team"), @@ -53,7 +56,14 @@ irtf_group.state = state_names["active"] irtf_group.type = type_names["ietf"] irtf_group.save() -system_email, _ = Email.objects.get_or_create(address="(System)") +# create Secretariat for use with roles +secretariat_group, _ = Group.objects.get_or_create(acronym="secretariat") +secretariat_group.name = "IETF Secretariat" +secretariat_group.state = state_names["active"] +secretariat_group.type = type_names["ietf"] +secretariat_group.save() + +system = Person.objects.get(name="(System)") # NomCom @@ -75,13 +85,13 @@ for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("st # we need start/end year so fudge events e = GroupEvent(group=group, type="started") e.time = datetime.datetime(o.start_year, 5, 1, 12, 0, 0) - e.by = system_email + e.by = system e.desc = e.get_type_display() e.save() e = GroupEvent(group=group, type="concluded") e.time = datetime.datetime(o.end_year, 5, 1, 12, 0, 0) - e.by = system_email + e.by = system e.desc = e.get_type_display() e.save() @@ -108,7 +118,7 @@ for o in Area.objects.all(): if o.concluded_date: e = GroupEvent(group=group, type="concluded") e.time = datetime.datetime.combine(o.concluded_date, datetime.time(12, 0, 0)) - e.by = system_email + e.by = system e.desc = e.get_type_display() e.save() @@ -164,15 +174,15 @@ for o in IETFWG.objects.all(): elif o.group_acronym.acronym == "iab": group.type = type_names["ietf"] group.parent = None - elif o.group_acronym.acronym in ("tsvdir", "secdir", "saag"): + elif o.group_acronym.acronym in ("tsvdir", "secdir", "saag", "usac"): 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 + elif o.group_acronym.acronym in ("apparea", "opsarea", "rtgarea", "usvarea", "genarea", "tsvarea", "raiarea", "apptsv"): + group.type = type_names["ag"] else: # the remaining groups are - # apptsv, apples, usac, null, dirdir + # apples, null, dirdir # for now, we don't transfer them if group.id: group.delete() @@ -200,7 +210,7 @@ for o in IETFWG.objects.all(): if d: e = GroupEvent(group=group, type=name) e.time = datetime.datetime.combine(d, datetime.time(12, 0, 0)) - e.by = system_email + e.by = system e.desc = e.get_type_display() e.save() diff --git a/redesign/importing/import-persons.py b/redesign/importing/import-persons.py new file mode 100755 index 000000000..6c593bf82 --- /dev/null +++ b/redesign/importing/import-persons.py @@ -0,0 +1,44 @@ +#!/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 * + +# creates system person and email + +# should probably also import the old person/email tables + +try: + system_person = Person.objects.get(name="(System)") +except Person.DoesNotExist: + system_person = Person.objects.create( + id=0, # special value + name="(System)", + ascii="(System)", + address="", + ) + + if system_person.id != 0: # work around bug in Django + Person.objects.filter(id=system_person.id).update(id=0) + system_person = Person.objects.get(id=0) + + +system_alias = Alias.objects.get_or_create( + person=system_person, + name=system_person.name + ) + +system_email = Email.objects.get_or_create( + address="", + person=system_person, + active=True + ) diff --git a/redesign/importing/import-roles.py b/redesign/importing/import-roles.py index e1fe5b118..a13ce61e0 100755 --- a/redesign/importing/import-roles.py +++ b/redesign/importing/import-roles.py @@ -1,7 +1,6 @@ #!/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 @@ -12,11 +11,12 @@ settings.USE_DB_REDESIGN_PROXY_CLASSES = False from django.core import management management.setup_environ(settings) +from redesign import unaccent from redesign.person.models import * from redesign.group.models import * from redesign.name.models import * from redesign.name.utils import name -from redesign.importing.utils import person_email +from redesign.importing.utils import old_person_to_email, clean_email_address from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo, WGChair, WGEditor, WGSecretary, WGTechAdvisor, ChairsHistory, Role as OldRole, Acronym, IRTFChair @@ -43,7 +43,7 @@ techadvisor_role = name(RoleName, "techadv", "Tech Advisor") # helpers for creating the objects def get_or_create_email(o, create_fake): - email = person_email(o.person) + email = old_person_to_email(o.person) if not email: if create_fake: email = u"unknown-email-%s-%s" % (o.person.first_name, o.person.last_name) @@ -62,6 +62,7 @@ def get_or_create_email(o, create_fake): else: p = Person.objects.create(id=o.person.pk, 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) @@ -162,14 +163,22 @@ for o in IESGLogin.objects.all(): continue email = get_or_create_email(o, create_fake=False) + if not email: + continue - if o.user_level == IESGLogin.INACTIVE_AD_LEVEL: + user, _ = User.objects.get_or_create(username=o.login_name) + email.person.user = user + email.person.save() + + # current ADs are imported below + if o.user_level == IESGLogin.SECRETARIAT_LEVEL: + if not Role.objects.filter(name=secretary_role, email=email): + Role.objects.create(name=secretary_role, group=Group.objects.get(acronym="secretariat"), email=email) + elif o.user_level == IESGLogin.INACTIVE_AD_LEVEL: if not Role.objects.filter(name=inactive_area_director_role, email=email): # connect them directly to the IESG as we don't really know where they belong Role.objects.create(name=inactive_area_director_role, group=Group.objects.get(acronym="iesg"), email=email) - # FIXME: import o.login_name, o.user_level - # AreaDirector for o in AreaDirector.objects.all(): if not o.area: @@ -206,7 +215,20 @@ for o in PersonOrOrgInfo.objects.filter(announcement__announcement_id__gte=1).di email = get_or_create_email(o, create_fake=False) # IDAuthor persons -for o in IDAuthor.objects.all().order_by('id').select_related('person'): +for o in IDAuthor.objects.all().order_by('id').select_related('person').iterator(): 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, create_fake=True) + + # we may also need to import email address used specifically for + # the document + addr = clean_email_address(o.email() or "") + if addr and addr.lower() != email.address.lower(): + try: + e = Email.objects.get(address=addr) + if e.person != email.person or e.active != False: + e.person = email.person + e.active = False + e.save() + except Email.DoesNotExist: + Email.objects.create(address=addr, person=email.person, active=False) diff --git a/redesign/importing/utils.py b/redesign/importing/utils.py index 92d056f60..95d25a179 100644 --- a/redesign/importing/utils.py +++ b/redesign/importing/utils.py @@ -1,5 +1,17 @@ -def person_email(person): +from person.models import Person + +def clean_email_address(addr): + addr = addr.replace("<", "").replace(">", "").replace("!", "@").replace("(at)", "@").strip() + if not "@" in addr: + return "" + else: + return addr + +def old_person_to_person(person): + return Person.objects.get(id=person.pk) + +def old_person_to_email(person): hardcoded_emails = { 'Dinara Suleymanova': "dinaras@ietf.org" } - return person.email()[1] or hardcoded_emails.get("%s %s" % (person.first_name, person.last_name)) + return clean_email_address(person.email()[1] or hardcoded_emails.get("%s %s" % (person.first_name, person.last_name)) or "") diff --git a/redesign/person/admin.py b/redesign/person/admin.py index 40cdb8dc9..8bc2b52bd 100644 --- a/redesign/person/admin.py +++ b/redesign/person/admin.py @@ -20,7 +20,7 @@ class AliasInline(admin.StackedInline): model = Alias class PersonAdmin(admin.ModelAdmin): - list_display = ["name", "short", "time", ] + list_display = ["name", "short", "time", "user", ] search_fields = ["name", "ascii"] inlines = [ EmailInline, AliasInline, ] # actions = None diff --git a/redesign/person/models.py b/redesign/person/models.py index b2e735867..22939dd4b 100644 --- a/redesign/person/models.py +++ b/redesign/person/models.py @@ -1,14 +1,18 @@ # Copyright The IETF Trust 2007, All Rights Reserved from django.db import models +from django.contrib.auth.models import User 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 + name = models.CharField(max_length=255, db_index=True) # 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) + + user = models.OneToOneField(User, blank=True, null=True) + def __unicode__(self): return self.name def _parts(self, name): @@ -40,6 +44,23 @@ class Person(models.Model): else: prefix, first, middle, last, suffix = self.ascii_parts() return (first and first[0]+"." or "")+(middle or "")+" "+last+(suffix and " "+suffix or "") + def email_address(self): + e = self.email_set.filter(active=True) + if e: + return e[0] + else: + return "" + def formatted_email(self): + e = self.email_set.order_by("-active") + if e: + return e[0].formatted_email() + else: + return "" + def person(self): # little temporary wrapper to help porting + return self + def full_name_as_key(self): + return self.name.lower().replace(" ", ".") + class Alias(models.Model): """This is used for alternative forms of a name. This is the @@ -48,7 +69,7 @@ class Alias(models.Model): recorded in the Person record. """ person = models.ForeignKey(Person) - name = models.CharField(max_length=255) + name = models.CharField(max_length=255, db_index=True) def __unicode__(self): return self.name class Meta: diff --git a/redesign/person/proxy.py b/redesign/person/proxy.py index e48030b5d..7fbeb9cc3 100644 --- a/redesign/person/proxy.py +++ b/redesign/person/proxy.py @@ -1,6 +1,12 @@ +from redesign.proxy_utils import TranslatingManager + from models import * -class IESGLogin(Email): +class IESGLogin(Person): + objects = TranslatingManager(dict(user_level__in=None, + first_name="name" + )) + def from_object(self, base): for f in base._meta.fields: setattr(self, f.name, getattr(base, f.name)) @@ -10,9 +16,6 @@ class IESGLogin(Email): 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 @@ -26,12 +29,12 @@ class IESGLogin(Email): #first_name = models.CharField(blank=True, max_length=25) @property def first_name(self): - return self.get_name().split(" ")[0] + return self.name_parts()[1] #last_name = models.CharField(blank=True, max_length=25) @property def last_name(self): - return self.get_name().split(" ")[-1] + return self.name_parts()[3] # FIXME: person isn't wrapped yet #person = BrokenForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', unique=True, null_values=(0, 888888), null=True) @@ -41,15 +44,14 @@ class IESGLogin(Email): #default_search = models.NullBooleanField() def __str__(self): - return self.get_name() + return self.name def __unicode__(self): - return self.get_name() + return self.name def is_current_ad(self): - return self in Email.objects.filter(role__name="ad", role__group__state="active") + return self in Person.objects.filter(email__role__name="ad", email__role__group__state="active").distinct() @staticmethod def active_iesg(): - raise NotImplemented - #return IESGLogin.objects.filter(user_level=1,id__gt=1).order_by('last_name') + return IESGLogin.objects.filter(email__role__name="ad", email__role__group__state="active").distinct().order_by('name') class Meta: proxy = True From 7f6f9a35da2f2697c2b15e29c05ba2323907045f Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 26 May 2011 11:26:20 +0000 Subject: [PATCH 63/75] Fix system person hack to actually work on a fresh import - Legacy-Id: 3155 --- redesign/importing/import-persons.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/redesign/importing/import-persons.py b/redesign/importing/import-persons.py index 6c593bf82..b5a3ecadb 100755 --- a/redesign/importing/import-persons.py +++ b/redesign/importing/import-persons.py @@ -27,9 +27,11 @@ except Person.DoesNotExist: address="", ) - if system_person.id != 0: # work around bug in Django - Person.objects.filter(id=system_person.id).update(id=0) - system_person = Person.objects.get(id=0) + system_person = Person.objects.get(name="(System)") + +if system_person.id != 0: # work around bug in Django + Person.objects.filter(id=system_person.id).update(id=0) + system_person = Person.objects.get(id=0) system_alias = Alias.objects.get_or_create( From fd23dc9d5d96541068fa5f6a5451d7f54715989c Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 26 May 2011 12:20:10 +0000 Subject: [PATCH 64/75] Fix email cleaning to catch recent brokenness on the form ">>" - Legacy-Id: 3156 --- redesign/importing/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/redesign/importing/utils.py b/redesign/importing/utils.py index 95d25a179..4b85304ee 100644 --- a/redesign/importing/utils.py +++ b/redesign/importing/utils.py @@ -1,7 +1,9 @@ from person.models import Person def clean_email_address(addr): - addr = addr.replace("<", "").replace(">", "").replace("!", "@").replace("(at)", "@").strip() + addr = addr.replace("!", "@").replace("(at)", "@") # some obvious @ replacements + addr = addr[addr.rfind('<') + 1:addr.find('>')] # whack surrounding <...> + addr = addr.strip() if not "@" in addr: return "" else: From 215d4ff9fd928820e95dc4c6cd3023abc1850bac Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 27 May 2011 11:07:59 +0000 Subject: [PATCH 65/75] Port IPR tool to new schema and fix some bugs - Legacy-Id: 3158 --- ietf/idtracker/admin.py | 3 +- ietf/idtracker/models.py | 3 +- ietf/ipr/admin.py | 7 +- ietf/ipr/models.py | 42 +++++++++--- ietf/ipr/new.py | 43 +++++++++--- ietf/ipr/related.py | 58 ++++++++++++++++ ietf/ipr/search.py | 75 +++++++++++++++------ ietf/ipr/tests.py | 3 + ietf/ipr/views.py | 34 ++++++++++ ietf/templates/ipr/details.html | 2 +- ietf/templates/ipr/search_result.html | 2 +- redesign/doc/models.py | 2 +- redesign/doc/proxy.py | 46 ++++++++++++- redesign/importing/import-document-state.py | 2 +- redesign/importing/import-ipr.py | 59 ++++++++++++++++ 15 files changed, 335 insertions(+), 46 deletions(-) create mode 100755 redesign/importing/import-ipr.py diff --git a/ietf/idtracker/admin.py b/ietf/idtracker/admin.py index 26bca3ae6..70b59fce8 100644 --- a/ietf/idtracker/admin.py +++ b/ietf/idtracker/admin.py @@ -122,7 +122,8 @@ class RfcAdmin(admin.ModelAdmin): fieldsets=((None, {'fields': ('rfc_number', 'title', 'group_acronym', 'area_acronym', 'status', 'comments', 'last_modified_date')}), ('Metadata', {'fields': (('online_version', 'txt_page_count'), ('fyi_number', 'std_number')), 'classes': 'collapse'}), ('Standards Track Dates', {'fields': ('rfc_published_date', ('proposed_date', 'draft_date'), ('standard_date', 'historic_date')), 'classes': 'collapse'}), ('Last Call / Ballot Info', {'fields': ('intended_status', ('lc_sent_date', 'lc_expiration_date'), ('b_sent_date', 'b_approve_date')), 'classes': 'collapse'})) list_display=['rfc_number', 'title'] search_fields=['title'] -admin.site.register(Rfc, RfcAdmin) +if not settings.USE_DB_REDESIGN_PROXY_CLASSES: + admin.site.register(Rfc, RfcAdmin) class RfcIntendedStatusAdmin(admin.ModelAdmin): pass diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py index 125123cb0..3e76b3d42 100644 --- a/ietf/idtracker/models.py +++ b/ietf/idtracker/models.py @@ -1089,13 +1089,14 @@ class DocumentWrapper(object): if settings.USE_DB_REDESIGN_PROXY_CLASSES: InternetDraftOld = InternetDraft IDInternalOld = IDInternal + RfcOld = Rfc BallotInfoOld = BallotInfo IDStateOld = IDState IDSubStateOld = IDSubState AreaOld = Area AcronymOld = Acronym IESGLoginOld = IESGLogin - from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo, IDState, IDSubState + from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo, IDState, IDSubState, Rfc from redesign.group.proxy import Area, Acronym from redesign.person.proxy import IESGLogin diff --git a/ietf/ipr/admin.py b/ietf/ipr/admin.py index 4b732a28e..c430dbcd4 100644 --- a/ietf/ipr/admin.py +++ b/ietf/ipr/admin.py @@ -1,5 +1,6 @@ #coding: utf-8 from django.contrib import admin +from django.conf import settings from ietf.ipr.models import * class IprContactAdmin(admin.ModelAdmin): @@ -12,7 +13,8 @@ admin.site.register(IprDetail, IprDetailAdmin) class IprDraftAdmin(admin.ModelAdmin): pass -admin.site.register(IprDraft, IprDraftAdmin) +if not settings.USE_DB_REDESIGN_PROXY_CLASSES: + admin.site.register(IprDraft, IprDraftAdmin) class IprLicensingAdmin(admin.ModelAdmin): pass @@ -24,7 +26,8 @@ admin.site.register(IprNotification, IprNotificationAdmin) class IprRfcAdmin(admin.ModelAdmin): pass -admin.site.register(IprRfc, IprRfcAdmin) +if not settings.USE_DB_REDESIGN_PROXY_CLASSES: + admin.site.register(IprRfc, IprRfcAdmin) class IprSelecttypeAdmin(admin.ModelAdmin): pass diff --git a/ietf/ipr/models.py b/ietf/ipr/models.py index adf07d595..a71361e55 100644 --- a/ietf/ipr/models.py +++ b/ietf/ipr/models.py @@ -115,6 +115,8 @@ class IprDetail(models.Model): def __str__(self): return self.title def docs(self): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + return list(IprDraftProxy.objects.filter(ipr=self)) return list(self.drafts.all()) + list(self.rfcs.all()) def get_absolute_url(self): return "/ipr/%d/" % self.ipr_id @@ -151,7 +153,7 @@ class IprContact(models.Model): class IprDraft(models.Model): ipr = models.ForeignKey(IprDetail, related_name='drafts_old' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'drafts') - document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name="ipr_old" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "ipr") + document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name="ipr_draft_old" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "ipr") revision = models.CharField(max_length=2) def __str__(self): return "%s which applies to %s-%s" % ( self.ipr, self.document, self.revision ) @@ -170,7 +172,7 @@ class IprNotification(models.Model): class IprRfc(models.Model): ipr = models.ForeignKey(IprDetail, related_name='rfcs_old' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'rfcs') - document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr") + document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr_rfc_old" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "ipr") def __str__(self): return "%s applies to RFC%04d" % ( self.ipr, self.document_id ) class Meta: @@ -186,19 +188,41 @@ class IprUpdate(models.Model): db_table = 'ipr_updates' - -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import Document +if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_IPR"): + from doc.models import DocAlias - class IprDocument(models.Model): + class IprDocAlias(models.Model): ipr = models.ForeignKey(IprDetail, related_name='documents') - document = models.ForeignKey(Document, related_name="ipr") - rev = models.CharField(max_length=2) + doc_alias = models.ForeignKey(DocAlias) + rev = models.CharField(max_length=2, blank=True) def __unicode__(self): - if self.rev != None: + if self.rev: return u"%s which applies to %s-%s" % (self.ipr, self.document, self.revision) else: return u"%s which applies to %s" % (self.ipr, self.document) + + # proxy stuff + IprDraftOld = IprDraft + IprRfcOld = IprRfc + + class IprDraftProxy(IprDocAlias): + # document = models.ForeignKey(InternetDraft, db_column='id_document_tag', "ipr") + # document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr") + @property + def document(self): + from redesign.doc.proxy import DraftLikeDocAlias + return DraftLikeDocAlias.objects.get(pk=self.doc_alias_id) + + #revision = models.CharField(max_length=2) + @property + def revision(self): + return self.rev + + class Meta: + proxy = True + + IprDraft = IprDraftProxy + IprRfc = IprDraftProxy diff --git a/ietf/ipr/new.py b/ietf/ipr/new.py index 0917ef62d..40cd8db33 100644 --- a/ietf/ipr/new.py +++ b/ietf/ipr/new.py @@ -134,7 +134,11 @@ def new(request, type, update=None, submitter=None): rfclist = rfclist.strip().split() for rfc in rfclist: try: - Rfc.objects.get(rfc_number=int(rfc)) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import DocAlias + DocAlias.objects.get(name="rfc%s" % int(rfc)) + else: + Rfc.objects.get(rfc_number=int(rfc)) except: raise forms.ValidationError("Unknown RFC number: %s - please correct this." % rfc) rfclist = " ".join(rfclist) @@ -155,7 +159,13 @@ def new(request, type, update=None, submitter=None): filename = draft rev = None try: - id = InternetDraft.objects.get(filename=filename) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import DocAlias + id = DocAlias.objects.get(name=filename) + # proxy attribute for code below + id.revision = id.document.rev + else: + id = InternetDraft.objects.get(filename=filename) except Exception, e: log("Exception: %s" % e) raise forms.ValidationError("Unknown Internet-Draft: %s - please correct this." % filename) @@ -263,15 +273,32 @@ def new(request, type, update=None, submitter=None): # Save IprDraft(s) for draft in form.cleaned_data["draftlist"].split(): - id = InternetDraft.objects.get(filename=draft[:-3]) - iprdraft = models.IprDraft(document=id, ipr=instance, revision=draft[-2:]) - iprdraft.save() + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + name = draft[:-3] + rev = draft[-2:] + + from doc.models import DocAlias + models.IprDocAlias.objects.create( + doc_alias=DocAlias.objects.get(name=name), + ipr=instance, + rev=rev) + else: + id = InternetDraft.objects.get(filename=draft[:-3]) + iprdraft = models.IprDraft(document=id, ipr=instance, revision=draft[-2:]) + iprdraft.save() # Save IprRfc(s) for rfcnum in form.cleaned_data["rfclist"].split(): - rfc = Rfc.objects.get(rfc_number=int(rfcnum)) - iprrfc = models.IprRfc(document=rfc, ipr=instance) - iprrfc.save() + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from doc.models import DocAlias + models.IprDocAlias.objects.create( + doc_alias=DocAlias.objects.get(name="rfc%s" % int(rfcnum)), + ipr=instance, + rev="") + else: + rfc = Rfc.objects.get(rfc_number=int(rfcnum)) + iprrfc = models.IprRfc(document=rfc, ipr=instance) + iprrfc.save() send_mail(request, settings.IPR_EMAIL_TO, ('IPR Submitter App', 'ietf-ipr@ietf.org'), 'New IPR Submission Notification', "ipr/new_update_email.txt", {"ipr": instance, "update": update}) return render("ipr/submitted.html", {"update": update}, context_instance=RequestContext(request)) diff --git a/ietf/ipr/related.py b/ietf/ipr/related.py index 9c4881771..2b4c68567 100644 --- a/ietf/ipr/related.py +++ b/ietf/ipr/related.py @@ -1,5 +1,7 @@ # Copyright The IETF Trust 2007, All Rights Reserved +from django.conf import settings +from django.db.models import Q from ietf.idtracker.models import InternetDraft, Rfc inverse = { @@ -77,3 +79,59 @@ def related_docs(doc, found = []): set_relation(doc, 'is_draft_of', item) found = related_docs(item, found) return found + +def related_docsREDESIGN(alias, _): + """Get related document aliases to given alias through depth-first search.""" + from redesign.doc.models import RelatedDocument + from redesign.doc.proxy import DraftLikeDocAlias + + mapping = dict( + updates='that updated', + obs='that obsoleted', + replaces='that replaced', + ) + inverse_mapping = dict( + updates='that was updated by', + obs='that was obsoleted by', + replaces='that was replaced by', + ) + + res = [ alias ] + remaining = [ alias ] + while remaining: + a = remaining.pop() + related = RelatedDocument.objects.filter(relationship__in=mapping.keys()).filter(Q(source=a.document) | Q(target=a)) + for r in related: + if r.source == a.document: + found = DraftLikeDocAlias.objects.filter(pk=r.target_id) + inverse = True + else: + found = DraftLikeDocAlias.objects.filter(document=r.source) + inverse = False + + for x in found: + if not x in res: + x.related = a + x.relation = (inverse_mapping if inverse else mapping)[r.relationship_id] + res.append(x) + remaining.append(x) + + # there's one more source of relatedness, a draft can have been published + aliases = DraftLikeDocAlias.objects.filter(document=a.document).exclude(pk__in=[x.pk for x in res]) + for oa in aliases: + rel = None + if a.name.startswith("rfc") and oa.name.startswith("draft"): + rel = "that was published as" + elif a.name.startswith("draft") and oa.name.startswith("rfc"): + rel = "which came from" + + if rel: + oa.related = a + oa.relation = rel + res.append(oa) + remaining.append(oa) + + return res + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + related_docs = related_docsREDESIGN diff --git a/ietf/ipr/search.py b/ietf/ipr/search.py index e5e4a9fe9..c19c86bf4 100644 --- a/ietf/ipr/search.py +++ b/ietf/ipr/search.py @@ -24,7 +24,11 @@ def mark_last_doc(iprs): def iprs_from_docs(docs): iprs = [] for doc in docs: - if isinstance(doc, InternetDraft): + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from ietf.ipr.models import IprDocAlias + disclosures = [ x.ipr for x in IprDocAlias.objects.filter(doc_alias=doc, ipr__status__in=[1,3]) ] + + elif isinstance(doc, InternetDraft): disclosures = [ item.ipr for item in IprDraft.objects.filter(document=doc, ipr__status__in=[1,3]) ] elif isinstance(doc, Rfc): disclosures = [ item.ipr for item in IprRfc.objects.filter(document=doc, ipr__status__in=[1,3]) ] @@ -50,7 +54,11 @@ def patent_file_search(url, q): return False def search(request, type="", q="", id=""): - wgs = IETFWG.objects.filter(group_type__group_type_id=1).exclude(group_acronym__acronym='2000').select_related().order_by('acronym.acronym') + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from group.models import Group + wgs = Group.objects.filter(type="wg").exclude(acronym="2000").select_related().order_by("acronym") + else: + wgs = IETFWG.objects.filter(group_type__group_type_id=1).exclude(group_acronym__acronym='2000').select_related().order_by('acronym.acronym') args = request.REQUEST.items() if args: for key, value in args: @@ -70,20 +78,32 @@ def search(request, type="", q="", id=""): if type == "document_search": if q: q = normalize_draftname(q) - start = InternetDraft.objects.filter(filename__contains=q) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from redesign.doc.proxy import DraftLikeDocAlias + start = DraftLikeDocAlias.objects.filter(name__contains=q, name__startswith="draft") + else: + start = InternetDraft.objects.filter(filename__contains=q) if id: - try: - id = int(id,10) - except: - id = -1 - start = InternetDraft.objects.filter(id_document_tag=id) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from redesign.doc.proxy import DraftLikeDocAlias + start = DraftLikeDocAlias.objects.filter(name=id) + else: + try: + id = int(id,10) + except: + id = -1 + start = InternetDraft.objects.filter(id_document_tag=id) if type == "rfc_search": if q: try: q = int(q, 10) except: q = -1 - start = Rfc.objects.filter(rfc_number=q) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from redesign.doc.proxy import DraftLikeDocAlias + start = DraftLikeDocAlias.objects.filter(name__contains=q, name__startswith="rfc") + else: + start = Rfc.objects.filter(rfc_number=q) if start.count() == 1: first = start[0] doc = str(first) @@ -142,12 +162,20 @@ def search(request, type="", q="", id=""): # Search by wg acronym # Document list with IPRs elif type == "wg_search": - try: - docs = list(InternetDraft.objects.filter(group__acronym=q)) - except: - docs = [] - docs += [ draft.replaced_by for draft in docs if draft.replaced_by_id ] - docs += list(Rfc.objects.filter(group_acronym=q)) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from redesign.doc.proxy import DraftLikeDocAlias + try: + docs = list(DraftLikeDocAlias.objects.filter(document__group__acronym=q)) + docs += list(DraftLikeDocAlias.objects.filter(document__relateddocument__target__in=docs, document__relateddocument__relationship="replaces")) + except: + docs = [] + else: + try: + docs = list(InternetDraft.objects.filter(group__acronym=q)) + except: + docs = [] + docs += [ draft.replaced_by for draft in docs if draft.replaced_by_id ] + docs += list(Rfc.objects.filter(group_acronym=q)) docs = [ doc for doc in docs if doc.ipr.count() ] iprs, docs = iprs_from_docs(docs) @@ -158,11 +186,18 @@ def search(request, type="", q="", id=""): # Search by rfc and id title # Document list with IPRs elif type == "title_search": - try: - docs = list(InternetDraft.objects.filter(title__icontains=q)) - except: - docs = [] - docs += list(Rfc.objects.filter(title__icontains=q)) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from redesign.doc.proxy import DraftLikeDocAlias + try: + docs = list(DraftLikeDocAlias.objects.filter(document__title__icontains=q)) + except: + docs = [] + else: + try: + docs = list(InternetDraft.objects.filter(title__icontains=q)) + except: + docs = [] + docs += list(Rfc.objects.filter(title__icontains=q)) docs = [ doc for doc in docs if doc.ipr.count() ] iprs, docs = iprs_from_docs(docs) diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py index e179a66c9..9cda36100 100644 --- a/ietf/ipr/tests.py +++ b/ietf/ipr/tests.py @@ -48,6 +48,8 @@ class IprUrlTestCase(SimpleUrlTestCase): else: return content +# this test should be ported to run on a test database instead of the +# real database, and possibly expanded class NewIprTestCase(unittest.TestCase,RealDatabaseTest): SPECIFIC_DISCLOSURE = { 'legal_name':'Testing Only Please Ignore', @@ -58,6 +60,7 @@ class NewIprTestCase(unittest.TestCase,RealDatabaseTest): 'ietf_telephone':'555-555-0101', 'ietf_email':'test.participant@example.com', 'rfclist':'1149', + 'draftlist':'draft-burdis-http-sasl-00', 'patents':'none', 'date_applied':'never', 'country':'nowhere', diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index d1ce84129..248d85436 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -48,6 +48,34 @@ def list_drafts(request): context_instance=RequestContext(request)), mimetype="text/plain") +def list_draftsREDESIGN(request): + from ipr.models import IprDocAlias + + docipr = {} + + for o in IprDocAlias.objects.filter(ipr__status=1).select_related("doc_alias"): + name = o.doc_alias.name + if name.startswith("rfc"): + name = name.upper() + + if not name in docipr: + docipr[name] = [] + + docipr[name].append(o.ipr_id) + + docs = [ dict(name=name, iprs=sorted(iprs)) for name, iprs in docipr.iteritems() ] + + # drafts.html is not an HTML file + return HttpResponse(render_to_string("ipr/drafts.html", + dict(docs=docs), + context_instance=RequestContext(request)), + mimetype="text/plain") + + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + list_drafts = list_draftsREDESIGN + + # Details views def show(request, ipr_id=None, removed=None): @@ -93,6 +121,12 @@ def show(request, ipr_id=None, removed=None): except: # if file does not exist, iframe is used instead pass + + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from ietf.ipr.models import IprDraft, IprRfc + ipr.drafts = IprDraft.objects.filter(ipr=ipr).exclude(doc_alias__name__startswith="rfc").order_by("id") + ipr.rfcs = IprRfc.objects.filter(ipr=ipr).filter(doc_alias__name__startswith="rfc").order_by("id") + return render("ipr/details.html", {"ipr": ipr, "section_list": section_list}, context_instance=RequestContext(request)) diff --git a/ietf/templates/ipr/details.html b/ietf/templates/ipr/details.html index 950a6cea8..e3e4896ca 100644 --- a/ietf/templates/ipr/details.html +++ b/ietf/templates/ipr/details.html @@ -170,7 +170,7 @@ Template for {{ section_list.disclosure_type }}" where the submitter provided I-D Filenames (draft-...):{{ ipr.draftlist }} {% else %} {% for doc in ipr.drafts.all %} - Internet-Draft:"{{ doc.document.title }}"
      ({{ doc.document.filename }}-{{ doc.revision }}) + Internet-Draft:"{{ doc.document.title }}"
      ({{ doc.document.filename }}{% if doc.revision %}-{{ doc.revision }}{% endif %}) {% endfor %} {% endif %} {% if ipr.other_designations %} diff --git a/ietf/templates/ipr/search_result.html b/ietf/templates/ipr/search_result.html index 9a36cffba..fa3ab3e1a 100644 --- a/ietf/templates/ipr/search_result.html +++ b/ietf/templates/ipr/search_result.html @@ -24,7 +24,7 @@ {% block intro_prefix %}IPR that was submitted by {{ q }}, and{% endblock %} {% block related %} - {% if not ipr.drafts.count and not ipr.rfcs.count %} + {% if not ipr.docs %} is not related to a specific IETF contribution. {% else %} is related to diff --git a/redesign/doc/models.py b/redesign/doc/models.py index ea7969584..2cea82873 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -27,7 +27,7 @@ class DocumentInfo(models.Model): rfc_state = models.ForeignKey(RfcDocStateName, verbose_name="RFC state", blank=True, null=True) # Other abstract = models.TextField() - rev = models.CharField(verbose_name="revision", max_length=16) + rev = models.CharField(verbose_name="revision", max_length=16, blank=True) pages = models.IntegerField(blank=True, null=True) intended_std_level = models.ForeignKey(IntendedStdLevelName, blank=True, null=True) std_level = models.ForeignKey(StdLevelName, blank=True, null=True) diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 2c2b0ae28..d08816905 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -9,7 +9,8 @@ import glob, os class InternetDraft(Document): objects = TranslatingManager(dict(filename="name", - id_document_tag="id", + filename__contains="name__contains", + id_document_tag="pk", 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), @@ -149,6 +150,11 @@ class InternetDraft(Document): def replaced_by(self): r = InternetDraft.objects.filter(relateddocument__target__document=self, relateddocument__relationship="replaces") return r[0] if r else None + + @property + def replaced_by_id(self): + r = self.replaced_by + return r.id_document_tag if r else None #replaces = FKAsOneToOne('replaces', reverse=True) @property @@ -714,6 +720,11 @@ class InternetDraft(Document): d = Dummy() d.res = res return d + + @property + def ipr(self): + from ipr.models import IprDraftProxy + return IprDraftProxy.objects.filter(doc_alias__document=self.pk) class Meta: proxy = True @@ -721,6 +732,7 @@ class InternetDraft(Document): IDInternal = InternetDraft BallotInfo = InternetDraft RfcIndex = InternetDraft +Rfc = InternetDraft class IDAuthor(DocumentAuthor): @@ -938,3 +950,35 @@ class IDSubState(DocInfoTagName): class Meta: proxy = True + +class DraftLikeDocAlias(DocAlias): + # this class is mostly useful for the IPR part + + def __str__(self): + return str(unicode(self)) + + def __unicode__(self): + if self.name.startswith("rfc"): + return "RFC%04d" % int(self.name[3:]) + else: + return self.name + + @property + def id_document_tag(self): + return self.name + + @property + def title(self): + return self.document.title + + @property + def filename(self): + return self.name + + @property + def ipr(self): + from ipr.models import IprDraftProxy + return IprDraftProxy.objects.filter(doc_alias=self.pk) + + class Meta: + proxy = True diff --git a/redesign/importing/import-document-state.py b/redesign/importing/import-document-state.py index b7c9fff0a..b35c3d50e 100755 --- a/redesign/importing/import-document-state.py +++ b/redesign/importing/import-document-state.py @@ -992,7 +992,7 @@ for index, o in enumerate(all_rfcs.iterator()): res.append(x) return res - RelatedDocument.objects.filter(document=d).delete() + RelatedDocument.objects.filter(source=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): diff --git a/redesign/importing/import-ipr.py b/redesign/importing/import-ipr.py new file mode 100755 index 000000000..07c267b72 --- /dev/null +++ b/redesign/importing/import-ipr.py @@ -0,0 +1,59 @@ +#!/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 +settings.IMPORTING_IPR = True + +from django.core import management +management.setup_environ(settings) + +from ietf.ipr.models import IprDraftOld, IprRfcOld, IprDocAlias, IprDetail +from redesign.doc.models import DocAlias + +# imports IprDraft and IprRfc, converting them to IprDocAlias links to Document + +# assumptions: documents have been imported + +# some links are borked, only import those that reference an existing IprDetail +ipr_ids = IprDetail.objects.all() + +for o in IprDraftOld.objects.filter(ipr__in=ipr_ids).select_related("document").order_by("id").iterator(): + try: + alias = DocAlias.objects.get(name=o.document.filename) + except DocAlias.DoesNotExist: + print "COULDN'T FIND DOCUMENT", o.document.filename + continue + + try: + IprDocAlias.objects.get(ipr=o.ipr_id, doc_alias=alias) + except IprDocAlias.DoesNotExist: + link = IprDocAlias() + link.ipr_id = o.ipr_id + link.doc_alias = alias + link.rev = o.revision or "" + link.save() + + print "importing IprDraft", o.pk, "linking", o.ipr_id, o.document.filename + +for o in IprRfcOld.objects.filter(ipr__in=ipr_ids).select_related("document").order_by("id").iterator(): + try: + alias = DocAlias.objects.get(name="rfc%s" % o.document.rfc_number) + except DocAlias.DoesNotExist: + print "COULDN'T FIND RFC%s", o.document.rfc_number + continue + + try: + IprDocAlias.objects.get(ipr=o.ipr_id, doc_alias=alias) + except IprDocAlias.DoesNotExist: + link = IprDocAlias() + link.ipr_id = o.ipr_id + link.doc_alias = alias + link.rev = "" + link.save() + + print "importing IprRfc", o.pk, "linking", o.ipr_id, o.document.rfc_number From 68d0e4c8010ecd86bc106d5d637cfab88f3a8431 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 27 May 2011 11:46:54 +0000 Subject: [PATCH 66/75] Port mailinglist view, expand group importer a bit - Legacy-Id: 3159 --- ietf/mailinglists/urls.py | 9 ++++++++- redesign/group/models.py | 2 +- redesign/group/proxy.py | 6 ++++++ redesign/importing/import-groups.py | 27 +++++++++++++++++++++------ 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/ietf/mailinglists/urls.py b/ietf/mailinglists/urls.py index acef9834e..05ad54eba 100644 --- a/ietf/mailinglists/urls.py +++ b/ietf/mailinglists/urls.py @@ -1,10 +1,17 @@ # Copyright The IETF Trust 2007, All Rights Reserved from django.conf.urls.defaults import patterns +from django.conf import settings from ietf.idtracker.models import IETFWG +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from redesign.group.proxy import IETFWG + http_archive_wg_queryset = IETFWG.objects.filter(list_pages__startswith='http') +else: + http_archive_wg_queryset = IETFWG.objects.filter(email_archive__startswith='http') + urlpatterns = patterns('django.views.generic.list_detail', - (r'^wg/$', 'object_list', { 'queryset': IETFWG.objects.filter(email_archive__startswith='http'), 'template_name': 'mailinglists/wgwebmail_list.html' }), + (r'^wg/$', 'object_list', { 'queryset': http_archive_wg_queryset, 'template_name': 'mailinglists/wgwebmail_list.html' }), ) urlpatterns += patterns('', (r'^nonwg/$', 'django.views.generic.simple.redirect_to', { 'url': 'http://www.ietf.org/list/nonwg.html'}), diff --git a/redesign/group/models.py b/redesign/group/models.py index 327970f4a..c6a0ec01e 100644 --- a/redesign/group/models.py +++ b/redesign/group/models.py @@ -14,7 +14,7 @@ class Group(models.Model): 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) + list_pages = models.CharField(max_length=255, blank=True) comments = models.TextField(blank=True) def __unicode__(self): return self.name diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py index 0149e3a3b..bfbe67820 100644 --- a/redesign/group/proxy.py +++ b/redesign/group/proxy.py @@ -85,9 +85,15 @@ class IETFWG(Group): #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) + @property + def email_address(self): + return self.list_email #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) + @property + def email_archive(self): + return self.list_pages #comments = models.TextField(blank=True) #last_modified_date = models.DateField() #meeting_scheduled_old = models.CharField(blank=True, max_length=3) diff --git a/redesign/importing/import-groups.py b/redesign/importing/import-groups.py index 080064539..a92ea2d6d 100755 --- a/redesign/importing/import-groups.py +++ b/redesign/importing/import-groups.py @@ -67,11 +67,11 @@ system = Person.objects.get(name="(System)") # NomCom -Group.objects.filter(acronym__startswith="nomcom").exclude(acronym="nomcom").delete() +#Group.objects.filter(acronym__startswith="nomcom").exclude(acronym="nomcom").delete() for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("start_year"): - group = Group() - group.acronym = "nomcom%s" % o.start_year + print "importing ChairsHistory/Nomcom", o.pk, "nomcom%s" % o.start_year + group, _ = Group.objects.get_or_create(acronym="nomcom%s" % o.start_year) group.name = "IAB/IESG Nominating Committee %s/%s" % (o.start_year, o.end_year) if o.chair_type.person == o.person: s = state_names["active"] @@ -97,7 +97,14 @@ for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("st # Area for o in Area.objects.all(): - group, _ = Group.objects.get_or_create(acronym=o.area_acronym.acronym) + print "importing Area", o.pk, o.area_acronym.acronym + + try: + group = Group.objects.get(acronym=o.area_acronym.acronym) + except Group.DoesNotExist: + group = Group(acronym=o.area_acronym.acronym) + group.id = o.area_acronym_id # transfer id + group.name = o.area_acronym.name if o.status.status == "Active": s = state_names["active"] @@ -127,6 +134,8 @@ for o in Area.objects.all(): # IRTF for o in IRTF.objects.all(): + print "importing IRTF", o.pk, o.acronym + try: group = Group.objects.get(acronym=o.acronym.lower()) except Group.DoesNotExist: @@ -144,7 +153,9 @@ for o in IRTF.objects.all(): # FIXME: missing fields from old: meeting_scheduled # IETFWG, AreaGroup -for o in IETFWG.objects.all(): +for o in IETFWG.objects.all().order_by("pk"): + print "importing IETFWG", o.pk, o.group_acronym.acronym + try: group = Group.objects.get(acronym=o.group_acronym.acronym) except Group.DoesNotExist: @@ -198,6 +209,10 @@ for o in IETFWG.objects.all(): print "no area/parent for", group.acronym, group.name, group.type, group.state group.list_email = o.email_address if o.email_address else "" + l = o.email_archive.strip() if o.email_archive else "" + if l in ("none", "not available"): + l = "" + group.list_pages = l group.comments = o.comments.strip() if o.comments else "" group.save() @@ -219,4 +234,4 @@ for o in IETFWG.objects.all(): import_date_event("concluded") # dormant_date is empty on all so don't bother with that - # FIXME: missing fields from old: meeting_scheduled, email_subscribe, email_keyword, email_archive, last_modified_date, meeting_scheduled_old + # FIXME: missing fields from old: meeting_scheduled, email_subscribe, email_keyword, last_modified_date, meeting_scheduled_old From 5dcc94e5a4ce025bbaad23dfed02933171b5db60 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 27 May 2011 21:33:48 +0000 Subject: [PATCH 67/75] Port wginfo views - Legacy-Id: 3160 --- ietf/idrfc/views_search.py | 14 +- ietf/idtracker/admin.py | 3 +- ietf/idtracker/models.py | 3 +- ietf/mailinglists/urls.py | 7 +- .../wginfo/1wg-summary-by-acronym.txt | 11 +- ietf/templates/wginfo/1wg-summary.txt | 7 +- ietf/templates/wginfo/wg-charter.txt | 4 +- ietf/templates/wginfo/wg-charterREDESIGN.txt | 47 ++++++ ietf/templates/wginfo/wg-dirREDESIGN.html | 92 ++++++++++++ ietf/templates/wginfo/wg_charterREDESIGN.html | 137 ++++++++++++++++++ ietf/templates/wginfo/wg_summary.txt | 5 + ietf/wginfo/views.py | 62 +++++++- redesign/doc/models.py | 28 +++- redesign/doc/proxy.py | 105 +------------- redesign/group/models.py | 22 ++- redesign/group/proxy.py | 105 ++++++++++---- redesign/importing/import-groups.py | 67 +++++++-- redesign/importing/import-persons.py | 14 +- redesign/importing/import-roles.py | 34 +---- redesign/importing/utils.py | 33 ++++- redesign/name/proxy.py | 100 ++++++++++++- redesign/person/models.py | 8 + redesign/proxy_utils.py | 5 +- 23 files changed, 685 insertions(+), 228 deletions(-) create mode 100644 ietf/templates/wginfo/wg-charterREDESIGN.txt create mode 100644 ietf/templates/wginfo/wg-dirREDESIGN.html create mode 100644 ietf/templates/wginfo/wg_charterREDESIGN.html create mode 100644 ietf/templates/wginfo/wg_summary.txt diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index 4207acbd4..a2c9806f7 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -378,9 +378,10 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: # canonical name for r in results: if r.pk in rfc_aliases: - r.canonical_name = rfc_aliases[r.pk] + # lambda weirdness works around lambda binding in local for loop scope + r.canonical_name = (lambda x: lambda: x)(rfc_aliases[r.pk]) else: - r.canonical_name = r.name + r.canonical_name = (lambda x: lambda: x)(r.name) result_map = dict((r.pk, r) for r in results) @@ -419,12 +420,13 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: # sort def sort_key(d): - if d.canonical_name.startswith('rfc'): - return (2, "%06d" % int(d.canonical_name[3:])) + n = d.canonical_name() + if n.startswith('rfc'): + return (2, "%06d" % int(n[3:])) elif d.state_id == "active": - return (1, d.canonical_name) + return (1, n) else: - return (3, d.canonical_name) + return (3, n) results.sort(key=sort_key) diff --git a/ietf/idtracker/admin.py b/ietf/idtracker/admin.py index 70b59fce8..fd90125ca 100644 --- a/ietf/idtracker/admin.py +++ b/ietf/idtracker/admin.py @@ -95,7 +95,8 @@ class IETFWGAdmin(admin.ModelAdmin): list_display=('group_acronym', 'group_type', 'status', 'area_acronym', 'start_date', 'concluded_date') search_fields=['group_acronym__acronym', 'group_acronym__name'] list_filter=['status', 'group_type'] -admin.site.register(IETFWG, IETFWGAdmin) +if not settings.USE_DB_REDESIGN_PROXY_CLASSES: + admin.site.register(IETFWG, IETFWGAdmin) class IRTFAdmin(admin.ModelAdmin): pass diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py index 3e76b3d42..ceb1a50ad 100644 --- a/ietf/idtracker/models.py +++ b/ietf/idtracker/models.py @@ -1096,8 +1096,9 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: AreaOld = Area AcronymOld = Acronym IESGLoginOld = IESGLogin + IETFWGOld = IETFWG from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo, IDState, IDSubState, Rfc - from redesign.group.proxy import Area, Acronym + from redesign.group.proxy import Area, Acronym, IETFWG from redesign.person.proxy import IESGLogin diff --git a/ietf/mailinglists/urls.py b/ietf/mailinglists/urls.py index 05ad54eba..2787cf30e 100644 --- a/ietf/mailinglists/urls.py +++ b/ietf/mailinglists/urls.py @@ -1,14 +1,9 @@ # Copyright The IETF Trust 2007, All Rights Reserved from django.conf.urls.defaults import patterns -from django.conf import settings from ietf.idtracker.models import IETFWG -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from redesign.group.proxy import IETFWG - http_archive_wg_queryset = IETFWG.objects.filter(list_pages__startswith='http') -else: - http_archive_wg_queryset = IETFWG.objects.filter(email_archive__startswith='http') +http_archive_wg_queryset = IETFWG.objects.filter(email_archive__startswith='http') urlpatterns = patterns('django.views.generic.list_detail', (r'^wg/$', 'object_list', { 'queryset': http_archive_wg_queryset, 'template_name': 'mailinglists/wgwebmail_list.html' }), diff --git a/ietf/templates/wginfo/1wg-summary-by-acronym.txt b/ietf/templates/wginfo/1wg-summary-by-acronym.txt index a7ea79517..d1a0f46be 100644 --- a/ietf/templates/wginfo/1wg-summary-by-acronym.txt +++ b/ietf/templates/wginfo/1wg-summary-by-acronym.txt @@ -2,14 +2,9 @@ IETF Working Group Summary (By Acronym) -The following Area Abreviations are used in this document +The following Area Abbreviations are used in this document {% for area in area_list %} -{{ area }} - {{ area.area_acronym.name }}{% endfor %} +{{ area|upper }} - {{ area.area_acronym.name }}{% endfor %} {% for wg in wg_list|dictsort:"group_acronym.acronym" %}{% if wg.start_date %} {{ wg.group_acronym.name|safe }} ({{ wg }}) -- {{ wg.area.area|upper }} -{% for chair in wg.wgchair_set.all %}{% if forloop.first %} Chair{{ forloop.revcounter|pluralize:": ,s:" }} {% else %} {% endif %}{{ chair.person|safe }} <{{ chair.person.email.1 }}> -{% endfor %} WG Mail: {{ wg.email_address }} - To Join: {{ wg.email_subscribe }}{%if wg.email_keyword %} - In Body: {{ wg.email_keyword|safe }}{% endif %} - Archive: {{ wg.email_archive }} -{% endif %}{% endfor %} +{% include "wginfo/wg_summary.txt" %}{% endif %}{% endfor %} diff --git a/ietf/templates/wginfo/1wg-summary.txt b/ietf/templates/wginfo/1wg-summary.txt index ffe19f733..55d76d1e2 100644 --- a/ietf/templates/wginfo/1wg-summary.txt +++ b/ietf/templates/wginfo/1wg-summary.txt @@ -5,9 +5,4 @@ {{ ad.person }} <{{ ad.person.email.1 }}>{% endfor %} {% endif %}{% if wg.start_date %} {{ wg.group_acronym.name|safe }} ({{ wg }}) -{% for chair in wg.wgchair_set.all %}{% if forloop.first %} Chair{{ forloop.revcounter|pluralize:": ,s:" }} {% else %} {% endif %}{{ chair.person|safe }} <{{ chair.person.email.1 }}> -{% endfor %} WG Mail: {{ wg.email_address }} - To Join: {{ wg.email_subscribe }}{%if wg.email_keyword %} - In Body: {{ wg.email_keyword|safe }}{% endif %} - Archive: {{ wg.email_archive }} -{% endif %}{% endifequal %}{% endfor %}{% endfor %} +{% include "wginfo/wg_summary.txt" %}{% endif %}{% endifequal %}{% endfor %}{% endfor %} diff --git a/ietf/templates/wginfo/wg-charter.txt b/ietf/templates/wginfo/wg-charter.txt index 83ba033ca..c0d8af0d4 100644 --- a/ietf/templates/wginfo/wg-charter.txt +++ b/ietf/templates/wginfo/wg-charter.txt @@ -1,4 +1,4 @@ -{% load ietf_filters %}{{wg.group_acronym.name|safe}} ({{wg}}) +{% if USE_DB_REDESIGN_PROXY_CLASSES %}{% include "wginfo/wg-charterREDESIGN.txt" %}{% else %}{% load ietf_filters %}{{wg.group_acronym.name|safe}} ({{wg}}) {{ wg.group_acronym.name|dashify }}{{ wg.group_acronym.acronym|dashify }}--- Charter @@ -44,4 +44,4 @@ Internet-Drafts: * {{obs.action}} RFC{{obs.rfc_acted_on_id}}{% endfor %}{% for obs in rfc.obsoleted_by%} * {%ifequal obs.action 'Obsoletes'%}OBSOLETED BY{%else%}Updated by{%endifequal%} RFC{{obs.rfc_id}}{% endfor %} {%endfor%} -{%else%}No Requests for Comments{% endif %} +{%else%}No Requests for Comments{% endif %}{% endif %} diff --git a/ietf/templates/wginfo/wg-charterREDESIGN.txt b/ietf/templates/wginfo/wg-charterREDESIGN.txt new file mode 100644 index 000000000..12ab03af6 --- /dev/null +++ b/ietf/templates/wginfo/wg-charterREDESIGN.txt @@ -0,0 +1,47 @@ +{% load ietf_filters %}{{wg.name|safe}} ({{wg.acronym}}) +{{ wg.name|dashify }}{{ wg.acronym|dashify }}--- + + Charter + Last Modified: {{ wg.time.date }} + + Current Status: {{ wg.state.name }} + + Chair{{ wg.chairs|pluralize }}: +{% for chair in wg.chairs %} {{ chair.person.name|safe }} <{{chair.address}}> +{% endfor %} + {{wg.area.area.area_acronym.name}} Directors: +{% for ad in wg.area_directors %} {{ ad.person|safe }} <{{ad.person.email.1}}> +{% endfor %} + {{wg.area.area.area_acronym.name}} Advisor: + {{ wg.areadirector.person.name|safe }} <{{wg.areadirector.address}}> +{% if wg.techadvisors %} + Tech Advisor{{ wg.techadvisors|pluralize }}: +{% for techadvisor in wg.techadvisors %} {{ techadvisor.person.name|safe }} <{{techadvisor.address}}> +{% endfor %}{% endif %}{% if wg.editors %} + Editor{{ wg.editors|pluralize }}: +{% for editor in wg.editors %} {{ editor.person.name|safe }} <{{editor.person.address}}> +{% endfor %}{% endif %}{% if wg.secretaries %} + Secretar{{ wg.secretaries|pluralize:"y,ies" }}: +{% for secretary in wg.secretaries %} {{ secretary.person.name|safe }} <{{secretary.person.address}}> +{% endfor %}{% endif %} + Mailing Lists: + General Discussion: {{ wg.email_address }} + To Subscribe: {{ wg.email_subscribe }} + Archive: {{ wg.email_archive }} + +Description of Working Group: + + {{ wg.charter_text|indent|safe }} + +Goals and Milestones: +{% for milestone in wg.milestones %} {% if milestone.done %}Done {% else %}{{ milestone.expected_due_date|date:"M Y" }}{% endif %} - {{ milestone.desc|safe }} +{% endfor %} +Internet-Drafts: +{% for alias in wg.drafts %} - {{alias.document.title|safe}} [{{alias.name}}-{{alias.document.rev}}] ({{ alias.document.pages }} pages) +{% endfor %} +{% if wg.rfcs %}Requests for Comments: +{% for alias in wg.rfcs %} {{ alias.name.upper }}: {{ alias.document.title|safe}} ({{ alias.document.pages }} pages){% for r in alias.rel %} + * {{ r.action }} {{ r.target.name|upper }}{% endfor %}{% for r in alias.invrel %} + * {% ifequal r.relationsship "obs" %}{{ r.inverse_action|upper }}{% else %}{{ r.action }}{% endifequal %} {{ r.source.canonical_name|upper }}{% endfor %} +{%endfor%} +{%else%}No Requests for Comments{% endif %} diff --git a/ietf/templates/wginfo/wg-dirREDESIGN.html b/ietf/templates/wginfo/wg-dirREDESIGN.html new file mode 100644 index 000000000..3808faa61 --- /dev/null +++ b/ietf/templates/wginfo/wg-dirREDESIGN.html @@ -0,0 +1,92 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2009, All Rights Reserved #} +{% comment %} +Portion Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +All rights reserved. Contact: Pasi Eronen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the Nokia Corporation and/or its + subsidiary(-ies) nor the names of its contributors may be used + to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +{% endcomment %} + +{% block title %}Active IETF Working Groups{% endblock %} + +{% block morecss %} +.ietf-wg-table { width: 100%; max-width:50em; } +.ietf-wg-table tr { vertical-align:top; } +{% endblock morecss %} + +{% block content %} +

      Active IETF Working Groups

      + +

      See also: Concluded +Working Groups (www.ietf.org), Concluded Working Groups (tools.ietf.org), Historic Charters.

      + +{% for area in areas %} +

      {{ area.name }}

      + +{% for ad in area.ads %} +{% if forloop.first %} +

      Area Director{{ forloop.revcounter|pluralize }}:

      +

      +{% endif %} +{{ ad.person.name }} <{{ ad.address }}>{% if not forloop.last %}
      {% endif %} +{% if forloop.last %} +

      +{% endif %} +{% endfor %} + +{% for url in area.urls %} +{% if forloop.first %} +

      Area Specific Web Page{{ forloop.revcounter|pluralize}}:

      +

      +{% endif %} +{{ url.name }}{% if not forloop.last %}
      {% endif %} +{% if forloop.last %} +

      +{% endif %} +{% endfor %} + +{% for wg in area.wgs %} +{% if forloop.first %} +

      Active Working Groups:

      +
      + +{% endif %} + + +{% if forloop.last %} +
      {{ wg.acronym }}{{ wg.name }}{% for chair in wg.chairs %}{{ chair.person.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
      +
      +{% endif %} +{% empty %} +

      No Active Working Groups

      +{% endfor %}{# wg #} + +{% endfor %}{# area #} +{% endblock %} diff --git a/ietf/templates/wginfo/wg_charterREDESIGN.html b/ietf/templates/wginfo/wg_charterREDESIGN.html new file mode 100644 index 000000000..1fb1e2c5e --- /dev/null +++ b/ietf/templates/wginfo/wg_charterREDESIGN.html @@ -0,0 +1,137 @@ +{% extends "wginfo/wg_base.html" %} +{% comment %} +Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +All rights reserved. Contact: Pasi Eronen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the Nokia Corporation and/or its + subsidiary(-ies) nor the names of its contributors may be used + to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +{% endcomment %} +{% load ietf_filters %} +{% block wg_titledetail %}Charter{% endblock %} + +{% block wg_content %} + +
      +{% if concluded %} +Note: The data for concluded WGs +is occasionally incorrect. +{% endif %} + + + + + + + + + + + +{% if wg.techadvisors %} + + + +{% endif %} +{% if wg.editors %} + + +{% endif %} +{% if wg.secretaries %} + + +{% endif %} + + + + + + + + + +{% if not concluded %} + + + + + + +{% endif %} + +
      +Personnel +
      Chair{{ wg.chairs|pluralize }}: +{% for chair in wg.chairs %} +{{ chair.person.name }} <{{ chair.address }}>
      +{% endfor %} +
      Area Director: +{% if not wg.ad %}?{% else %} +{{ wg.ad.name }} <{{ wg.ad_email }}>{% endif %} +
      Tech Advisor{{ wg.techadvisors|pluralize }}: +{% for techadvisor in wg.techadvisors %} +{{ techadvisor.person.name }} <{{ techadvisor.address }}>
      +{% endfor %} +
      Editor{{ wg.editors|pluralize }}: +{% for editor in wg.editors %} +{{ editor.person.name }} <{{ editor.address }}>
      +{% endfor %} +
      Secretar{{ wg.secretaries|pluralize:"y,ies" }}: +{% for secretary in wg.secretaries %} +{{ secretary.person.name }} <{{ secretary.address }}>
      +{% endfor %} +
      +
      Mailing List +
      Address:{{ wg.email_address|urlize }}
      To Subscribe:{{ wg.email_subscribe|urlize }}
      Archive:{{ wg.clean_email_archive|urlize }}
      +
      Jabber Chat +
      Room Address:xmpp:{{wg}}@jabber.ietf.org
      Logs:http://jabber.ietf.org/logs/{{wg}}/
      +
      + +{% if wg.additional_urls %} +

      In addition to the charter maintained by the IETF Secretariat, there is additional information about this working group on the Web at: +{% for url in wg.additional_urls %} +{{ url.description}}{% if not forloop.last %}, {% endif %} +{% endfor %} +

      +{% endif %} + +

      Description of Working Group

      +

      {{ wg.charter_text|escape|format_charter|safe }}

      + +

      Goals and Milestones

      + +{% for milestone in wg.milestones %} + + + +{% endfor %} +
      + {% if milestone.done %}Done{% else %}{{ milestone.expected_due_date|date:"M Y" }}{% endif %} + {{ milestone.desc|escape }} +
      +{% endblock wg_content %} diff --git a/ietf/templates/wginfo/wg_summary.txt b/ietf/templates/wginfo/wg_summary.txt new file mode 100644 index 000000000..603d9e2f8 --- /dev/null +++ b/ietf/templates/wginfo/wg_summary.txt @@ -0,0 +1,5 @@ +{% for chair in wg.wgchair_set.all %}{% if forloop.first %} Chair{{ forloop.revcounter|pluralize:": ,s:" }} {% else %} {% endif %}{{ chair.person|safe }} <{{ chair.person.email.1 }}> +{% endfor %} WG Mail: {{ wg.email_address }} + To Join: {{ wg.email_subscribe }}{%if wg.email_keyword %} + In Body: {{ wg.email_keyword|safe }}{% endif %} + Archive: {{ wg.email_archive }} diff --git a/ietf/wginfo/views.py b/ietf/wginfo/views.py index f0eb152cb..3a4fc3441 100644 --- a/ietf/wginfo/views.py +++ b/ietf/wginfo/views.py @@ -32,13 +32,37 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from ietf.idtracker.models import Area, IETFWG from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext, loader from django.http import HttpResponse +from django.conf import settings +from ietf.idtracker.models import Area, IETFWG from ietf.idrfc.views_search import SearchForm, search_query from ietf.idrfc.idrfc_wrapper import IdRfcWrapper +def fill_in_charter_info(wg, include_drafts=False): + from redesign.person.models import Email + from redesign.doc.models import DocAlias, RelatedDocument + + wg.areadirector = wg.ad.role_email("ad", wg.parent) if wg.ad else None + wg.chairs = Email.objects.filter(role__group=wg, role__name="chair") + wg.techadvisors = Email.objects.filter(role__group=wg, role__name="techadv") + wg.editors = Email.objects.filter(role__group=wg, role__name="editor") + wg.secretaries = Email.objects.filter(role__group=wg, role__name="secr") + wg.milestones = wg.groupmilestone_set.all().order_by('expected_due_date') + + if include_drafts: + aliases = DocAlias.objects.filter(document__type="draft", document__group=wg).select_related('document').order_by("name") + wg.drafts = [] + wg.rfcs = [] + for a in aliases: + if a.name.startswith("draft"): + wg.drafts.append(a) + else: + wg.rfcs.append(a) + a.rel = RelatedDocument.objects.filter(source=a.document).distinct() + a.invrel = RelatedDocument.objects.filter(target=a).distinct() + def wg_summary_acronym(request): areas = Area.active_areas() wgs = IETFWG.objects.filter(status=IETFWG.ACTIVE) @@ -50,16 +74,39 @@ def wg_summary_area(request): def wg_charters(request): wgs = IETFWG.objects.filter(status='1',start_date__isnull=False) - return HttpResponse(loader.render_to_string('wginfo/1wg-charters.txt', {'wg_list': wgs}),mimetype='text/plain; charset=UTF-8') + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + for wg in wgs: + fill_in_charter_info(wg, include_drafts=True) + return HttpResponse(loader.render_to_string('wginfo/1wg-charters.txt', {'wg_list': wgs, 'USE_DB_REDESIGN_PROXY_CLASSES': settings.USE_DB_REDESIGN_PROXY_CLASSES}),mimetype='text/plain; charset=UTF-8') def wg_charters_by_acronym(request): wgs = IETFWG.objects.filter(status='1',start_date__isnull=False) - return HttpResponse(loader.render_to_string('wginfo/1wg-charters-by-acronym.txt', {'wg_list': wgs}),mimetype='text/plain; charset=UTF-8') + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + for wg in wgs: + fill_in_charter_info(wg, include_drafts=True) + return HttpResponse(loader.render_to_string('wginfo/1wg-charters-by-acronym.txt', {'wg_list': wgs, 'USE_DB_REDESIGN_PROXY_CLASSES': settings.USE_DB_REDESIGN_PROXY_CLASSES}),mimetype='text/plain; charset=UTF-8') def wg_dir(request): areas = Area.active_areas() return render_to_response('wginfo/wg-dir.html', {'areas':areas}, RequestContext(request)) +def wg_dirREDESIGN(request): + from redesign.group.models import Group, GroupURL + from redesign.person.models import Email + + areas = Group.objects.filter(type="area", state="active").order_by("name") + for area in areas: + area.ads = sorted(Email.objects.filter(role__group=area, role__name="ad").select_related("person"), key=lambda e: e.person.name_parts()[3]) + area.wgs = Group.objects.filter(parent=area, type="wg", state="active").order_by("acronym") + area.urls = area.groupurl_set.all().order_by("name") + for wg in area.wgs: + wg.chairs = sorted(Email.objects.filter(role__group=wg, role__name="chair").select_related("person"), key=lambda e: e.person.name_parts()[3]) + + return render_to_response('wginfo/wg-dirREDESIGN.html', {'areas':areas}, RequestContext(request)) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + wg_dir = wg_dirREDESIGN + def wg_documents(request, acronym): wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1) concluded = (wg.status_id != 1) @@ -88,4 +135,13 @@ def wg_documents(request, acronym): def wg_charter(request, acronym): wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1) concluded = (wg.status_id != 1) + + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + fill_in_charter_info(wg) + return render_to_response('wginfo/wg_charterREDESIGN.html', + dict(wg=wg, + concluded=concluded, + selected='charter'), + RequestContext(request)) + return render_to_response('wginfo/wg_charter.html', {'wg': wg, 'concluded':concluded, 'selected':'charter'}, RequestContext(request)) diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 2cea82873..adf8cc76f 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -42,19 +42,16 @@ class DocumentInfo(models.Model): abstract = True def author_list(self): return ", ".join(email.address for email in self.authors.all()) - def latest_event(self, *args, **filter_args): - """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(WriteupEvent, type="xyz") returns a - WriteupEvent event.""" - model = args[0] if args else Event - 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): source = models.ForeignKey('Document') target = models.ForeignKey('DocAlias') relationship = models.ForeignKey(DocRelationshipName) + def action(self): + return self.relationship.name + def inverse_action(): + infinitive = self.relationship.name[:-1] + return u"%sd by" % infinitive def __unicode__(self): return u"%s %s %s" % (self.source.name, self.relationship.name.lower(), self.target.name) @@ -89,6 +86,21 @@ class Document(DocumentInfo): # FIXME: compensate for tombstones? return u"<%s-%s.txt>" % (self.name, self.rev) + def latest_event(self, *args, **filter_args): + """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(WriteupEvent, type="xyz") returns a + WriteupEvent event.""" + model = args[0] if args else Event + e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time', '-id')[:1] + return e[0] if e else None + + def canonical_name(self): + if self.type_id == "draft" and self.state_id == "rfc": + return self.docalias_set.get(name__startswith="rfc").name + else: + return self.name + class RelatedDocHistory(models.Model): source = models.ForeignKey('DocHistory') target = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set") diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index d08816905..4e41cf829 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -1,6 +1,7 @@ from redesign.doc.models import * from redesign.person.models import Email from redesign.proxy_utils import TranslatingManager +from redesign.name.proxy import * from django.conf import settings @@ -86,8 +87,7 @@ class InternetDraft(Document): #status = models.ForeignKey(IDStatus) @property def status(self): - from redesign.name.proxy import IDStatus - return IDStatus(self.state) if self.state else None + return IDStatus().from_object(self.state) if self.state else None @property def status_id(self): @@ -132,11 +132,8 @@ class InternetDraft(Document): @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 + n = self.canonical_name() + return int(n[3:]) if n.startswith("rfc") else None #comments = models.TextField(blank=True) # unused @@ -857,100 +854,6 @@ class Position(BallotPositionEvent): class Meta: proxy = True -class IDState(IesgDocStateName): - PUBLICATION_REQUESTED = 10 - LAST_CALL_REQUESTED = 15 - IN_LAST_CALL = 16 - WAITING_FOR_WRITEUP = 18 - WAITING_FOR_AD_GO_AHEAD = 19 - IESG_EVALUATION = 20 - IESG_EVALUATION_DEFER = 21 - APPROVED_ANNOUNCEMENT_SENT = 30 - AD_WATCHING = 42 - DEAD = 99 - DO_NOT_PUBLISH_STATES = (33, 34) - - objects = TranslatingManager(dict(pk="order")) - - def from_object(self, base): - for f in base._meta.fields: - setattr(self, f.name, getattr(base, f.name)) - return self - - #document_state_id = models.AutoField(primary_key=True) - @property - def document_state_id(self): - return self.order - - #state = models.CharField(max_length=50, db_column='document_state_val') - @property - def state(self): - return self.name - - #equiv_group_flag = models.IntegerField(null=True, blank=True) # unused - #description = models.TextField(blank=True, db_column='document_desc') - @property - def description(self): - return self.desc - - @property - def nextstate(self): - # simulate related queryset - from name.models import get_next_iesg_states - return IDState.objects.filter(pk__in=[x.pk for x in get_next_iesg_states(self)]) - - @property - def next_state(self): - # simulate IDNextState - return self - - def __str__(self): - return self.state - - @staticmethod - def choices(): - return [(state.slug, state.name) for state in IDState.objects.all()] - - class Meta: - proxy = True - - -class IDSubStateManager(TranslatingManager): - def __init__(self, *args): - super(IDSubStateManager, self).__init__(*args) - - def all(self): - return self.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point']) - -class IDSubState(DocInfoTagName): - objects = IDSubStateManager(dict(pk="order")) - - def from_object(self, base): - for f in base._meta.fields: - setattr(self, f.name, getattr(base, f.name)) - return self - - #sub_state_id = models.AutoField(primary_key=True) - @property - def sub_state_id(self): - return self.order - - #sub_state = models.CharField(max_length=55, db_column='sub_state_val') - @property - def sub_state(self): - return self.name - - #description = models.TextField(blank=True, db_column='sub_state_desc') - @property - def description(self): - return self.desc - - def __str__(self): - return self.sub_state - - class Meta: - proxy = True - class DraftLikeDocAlias(DocAlias): # this class is mostly useful for the IPR part diff --git a/redesign/group/models.py b/redesign/group/models.py index c6a0ec01e..0e552f656 100644 --- a/redesign/group/models.py +++ b/redesign/group/models.py @@ -7,14 +7,17 @@ from redesign.person.models import Email, Person import datetime class Group(models.Model): + time = models.DateTimeField(default=datetime.datetime.now) # should probably have auto_now=True name = models.CharField(max_length=80) acronym = models.CharField(max_length=16, db_index=True) 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) + ad = models.ForeignKey(Person, blank=True, null=True) list_email = models.CharField(max_length=64, blank=True) - list_pages = models.CharField(max_length=255, blank=True) + list_subscribe = models.CharField(max_length=255, blank=True) + list_archive = models.CharField(max_length=255, blank=True) comments = models.TextField(blank=True) def __unicode__(self): return self.name @@ -24,6 +27,22 @@ class Group(models.Model): e = GroupEvent.objects.filter(group=self).filter(**filter_args).order_by('-time', '-id')[:1] return e[0] if e else None +class GroupURL(models.Model): + group = models.ForeignKey(Group) + name = models.CharField(max_length=255) + url = models.URLField(verify_exists=False) + +class GroupMilestone(models.Model): + group = models.ForeignKey(Group) + desc = models.TextField() + expected_due_date = models.DateField() + done = models.BooleanField() + done_date = models.DateField(null=True, blank=True) + time = models.DateTimeField(auto_now=True) + def __unicode__(self): + return self.desc[:20] + "..." + class Meta: + ordering = ['expected_due_date'] GROUP_EVENT_CHOICES = [("proposed", "Proposed group"), ("started", "Started group"), @@ -48,6 +67,7 @@ class GroupEvent(models.Model): # 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. +# FIXME: this class needs to be updated class GroupHistory(models.Model): group = models.ForeignKey('Group', related_name='group_history') # Event related diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py index bfbe67820..3553ebcb4 100644 --- a/redesign/group/proxy.py +++ b/redesign/group/proxy.py @@ -1,3 +1,5 @@ +from redesign.proxy_utils import TranslatingManager + from models import * class Acronym(Group): @@ -47,8 +49,14 @@ class Area(Group): #start_date = models.DateField(auto_now_add=True) #concluded_date = models.DateField(null=True, blank=True) #status = models.ForeignKey(AreaStatus) + @property + def status_id(self): + return { "active": 1, "dormant": 2, "conclude": 3 }[self.state_id] #comments = models.TextField(blank=True) #last_modified_date = models.DateField(auto_now=True) + @property + def last_modified_date(self): + return self.time.date() #extra_email_addresses = models.TextField(blank=True,null=True) #def additional_urls(self): @@ -68,8 +76,22 @@ class Area(Group): class Meta: proxy = True +def proxied_role_emails(emails): + for e in emails: + e.person.email = { 1: e } + return emails class IETFWG(Group): + objects = TranslatingManager(dict(group_acronym="id", + group_acronym__acronym="acronym", + email_archive__startswith="list_archive__startswith", + group_type=lambda v: ("type", { 1: "wg" }[int(v)]), + status=lambda v: ("state", { 1: "active" }[int(v)]), + areagroup__area__status=lambda v: ("parent__state", { 1: "active" }[v]), + start_date__isnull=lambda v: None if v else ("groupevent__type", "started") + ), + always_filter=dict(type="wg")) + ACTIVE=1 #group_acronym = models.OneToOneField(Acronym, primary_key=True, editable=False) @property @@ -79,9 +101,17 @@ class IETFWG(Group): #group_type = models.ForeignKey(WGType) #proposed_date = models.DateField(null=True, blank=True) #start_date = models.DateField(null=True, blank=True) + @property + def start_date(self): + e = self.latest_event(type="started") + return e.time.date() if e else None + #dormant_date = models.DateField(null=True, blank=True) #concluded_date = models.DateField(null=True, blank=True) #status = models.ForeignKey(WGStatus) + @property + def status_id(self): + return { "active": 1, "dormant": 2, "conclude": 3 }[self.state_id] #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) @@ -89,66 +119,79 @@ class IETFWG(Group): def email_address(self): return self.list_email #email_subscribe = models.CharField(blank=True, max_length=120) + @property + def email_subscribe(self): + return self.list_subscribe #email_keyword = models.CharField(blank=True, max_length=50) #email_archive = models.CharField(blank=True, max_length=95) @property def email_archive(self): - return self.list_pages + return self.list_archive #comments = models.TextField(blank=True) #last_modified_date = models.DateField() + @property + def last_modified_date(self): + return self.time.date() #meeting_scheduled_old = models.CharField(blank=True, max_length=3) #area = FKAsOneToOne('areagroup', reverse=True) + @property + def area(self): + class AreaGroup: pass + if self.parent: + areagroup = AreaGroup() + areagroup.area = Area().from_object(self.parent) + return areagroup + else: + return None + 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) + from redesign.doc.proxy import InternetDraft + return InternetDraft.objects.filter(group=self, state="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 + return Area().from_object(self.parent) if self.parent else 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: + if not self.parent: return None + return proxied_role_emails(sorted(Email.objects.filter(role__group=self.parent, role__name="ad"), key=lambda e: e.person.name_parts()[3])) 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) + return proxied_role_emails(sorted(Email.objects.filter(role__group=self, role__name="chair"), key=lambda e: e.person.name_parts()[3])) + # 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" + filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, self.acronym) + ".desc.txt" desc_file = open(filename) desc = desc_file.read() except BaseException: - desc = 'Error Loading Work Group Description' + desc = 'Error Loading Work Group Description' return desc def additional_urls(self): - return AreaWGURL.objects.filter(name=self.group_acronym.acronym) + return self.groupurl_set.all().order_by("name") 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 + return self.list_archive + def wgchair_set(self): + # gross hack ... + class Dummy: pass + d = Dummy() + d.all = self.chairs() + return d class Meta: proxy = True diff --git a/redesign/importing/import-groups.py b/redesign/importing/import-groups.py index a92ea2d6d..6e5692b86 100755 --- a/redesign/importing/import-groups.py +++ b/redesign/importing/import-groups.py @@ -1,6 +1,6 @@ #!/usr/bin/python -import sys, os +import sys, os, datetime basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) sys.path = [ basedir ] + sys.path @@ -15,9 +15,10 @@ management.setup_environ(settings) from redesign.group.models import * from redesign.name.models import * from redesign.name.utils import name -from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF, ChairsHistory, Role +from redesign.importing.utils import old_person_to_person +from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF, ChairsHistory, Role, AreaDirector -# imports IETFWG, Area, AreaGroup, Acronym, IRTF +# imports IETFWG, Area, AreaGroup, Acronym, IRTF, AreaWGURL # also creates nomcom groups @@ -67,8 +68,6 @@ system = Person.objects.get(name="(System)") # NomCom -#Group.objects.filter(acronym__startswith="nomcom").exclude(acronym="nomcom").delete() - for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("start_year"): print "importing ChairsHistory/Nomcom", o.pk, "nomcom%s" % o.start_year group, _ = Group.objects.get_or_create(acronym="nomcom%s" % o.start_year) @@ -83,6 +82,8 @@ for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("st group.save() # we need start/end year so fudge events + group.groupevent_set.all().delete() + e = GroupEvent(group=group, type="started") e.time = datetime.datetime(o.start_year, 5, 1, 12, 0, 0) e.by = system @@ -104,7 +105,9 @@ for o in Area.objects.all(): except Group.DoesNotExist: group = Group(acronym=o.area_acronym.acronym) group.id = o.area_acronym_id # transfer id - + + if o.last_modified_date: + group.time = datetime.datetime.combine(o.last_modified_date, datetime.time(12, 0, 0)) group.name = o.area_acronym.name if o.status.status == "Active": s = state_names["active"] @@ -119,6 +122,11 @@ for o in Area.objects.all(): group.save() + for u in o.additional_urls(): + url, _ = GroupURL.objects.get_or_create(group=group, url=u.url) + url.name = u.description.strip() + url.save() + # import events group.groupevent_set.all().delete() @@ -129,7 +137,7 @@ for o in Area.objects.all(): e.desc = e.get_type_display() e.save() - # FIXME: missing fields from old: last_modified_date, extra_email_addresses + # FIXME: missing fields from old: extra_email_addresses # IRTF @@ -162,6 +170,8 @@ for o in IETFWG.objects.all().order_by("pk"): group = Group(acronym=o.group_acronym.acronym) group.id = o.group_acronym_id # transfer id + if o.last_modified_date: + group.time = datetime.datetime.combine(o.last_modified_date, datetime.time(12, 0, 0)) group.name = o.group_acronym.name # state if o.group_type.type == "BOF": @@ -208,30 +218,57 @@ for o in IETFWG.objects.all().order_by("pk"): elif not group.parent: print "no area/parent for", group.acronym, group.name, group.type, group.state + try: + area_director = o.area_director + except AreaDirector.DoesNotExist: + area_director = None + if area_director and not area_director.area_id: + area_director = None # fake TBD guy + + group.ad = old_person_to_person(area_director.person) if area_director else None group.list_email = o.email_address if o.email_address else "" - l = o.email_archive.strip() if o.email_archive else "" + group.list_subscribe = (o.email_subscribe or "").replace("//listinfo", "/listinfo").strip() + l = o.clean_email_archive().strip() if o.email_archive else "" if l in ("none", "not available"): l = "" - group.list_pages = l + group.list_archive = l group.comments = o.comments.strip() if o.comments else "" group.save() + for u in o.additional_urls(): + url, _ = GroupURL.objects.get_or_create(group=group, url=u.url) + url.name = u.description.strip() + url.save() + + for m in o.milestones(): + desc = m.description.strip() + try: + milestone = GroupMilestone.objects.get(group=group, desc=desc) + except GroupMilestone.DoesNotExist: + milestone = GroupMilestone(group=group, desc=desc) + + milestone.expected_due_date = m.expected_due_date + milestone.done = m.done == "Done" + milestone.done_date = m.done_date + milestone.time = datetime.datetime.combine(m.last_modified_date, datetime.time(12, 0, 0)) + milestone.save() + # import events group.groupevent_set.all().delete() - def import_date_event(name): + def import_date_event(name, type_name): d = getattr(o, "%s_date" % name) if d: - e = GroupEvent(group=group, type=name) + e = GroupEvent(group=group, type=type_name) e.time = datetime.datetime.combine(d, datetime.time(12, 0, 0)) e.by = system e.desc = e.get_type_display() e.save() - import_date_event("proposed") - import_date_event("start") - import_date_event("concluded") + import_date_event("proposed", "proposed") + import_date_event("start", "started") + import_date_event("concluded", "concluded") # dormant_date is empty on all so don't bother with that - # FIXME: missing fields from old: meeting_scheduled, email_subscribe, email_keyword, last_modified_date, meeting_scheduled_old + # FIXME: missing fields from old: meeting_scheduled, email_keyword, meeting_scheduled_old diff --git a/redesign/importing/import-persons.py b/redesign/importing/import-persons.py index b5a3ecadb..289a13f8c 100755 --- a/redesign/importing/import-persons.py +++ b/redesign/importing/import-persons.py @@ -11,12 +11,17 @@ settings.USE_DB_REDESIGN_PROXY_CLASSES = False from django.core import management management.setup_environ(settings) +from ietf.idtracker.models import AreaDirector, IETFWG from redesign.person.models import * +from redesign.importing.utils import get_or_create_email # creates system person and email +# imports AreaDirector persons that are connected to an IETFWG + # should probably also import the old person/email tables +print "creating (System) person and email" try: system_person = Person.objects.get(name="(System)") except Person.DoesNotExist: @@ -33,7 +38,6 @@ if system_person.id != 0: # work around bug in Django Person.objects.filter(id=system_person.id).update(id=0) system_person = Person.objects.get(id=0) - system_alias = Alias.objects.get_or_create( person=system_person, name=system_person.name @@ -41,6 +45,10 @@ system_alias = Alias.objects.get_or_create( system_email = Email.objects.get_or_create( address="", - person=system_person, - active=True + defaults=dict(active=True, person=system_person) ) + +for o in AreaDirector.objects.filter(ietfwg__in=IETFWG.objects.all()).exclude(area_acronym=None).distinct().order_by("pk").iterator(): + print "importing AreaDirector (from IETFWG) persons", o.pk + + get_or_create_email(o, create_fake=False) diff --git a/redesign/importing/import-roles.py b/redesign/importing/import-roles.py index a13ce61e0..c2ce32f3c 100755 --- a/redesign/importing/import-roles.py +++ b/redesign/importing/import-roles.py @@ -11,12 +11,12 @@ settings.USE_DB_REDESIGN_PROXY_CLASSES = False from django.core import management management.setup_environ(settings) -from redesign import unaccent from redesign.person.models import * from redesign.group.models import * from redesign.name.models import * from redesign.name.utils import name -from redesign.importing.utils import old_person_to_email, clean_email_address +from redesign.importing.utils import old_person_to_email, clean_email_address, get_or_create_email + from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo, WGChair, WGEditor, WGSecretary, WGTechAdvisor, ChairsHistory, Role as OldRole, Acronym, IRTFChair @@ -41,36 +41,6 @@ editor_role = name(RoleName, "editor", "Editor") secretary_role = name(RoleName, "secr", "Secretary") techadvisor_role = name(RoleName, "techadv", "Tech Advisor") -# helpers for creating the objects -def get_or_create_email(o, create_fake): - email = old_person_to_email(o.person) - if not email: - 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.pk, o.person.pk, o.person.first_name, o.person.last_name)).encode('utf-8') - return None - - e, _ = Email.objects.select_related("person").get_or_create(address=email) - if not e.person: - n = u"%s %s" % (o.person.first_name, o.person.last_name) - asciified = unaccent.asciify(n) - aliases = Alias.objects.filter(name__in=(n, asciified)) - if aliases: - p = aliases[0].person - else: - p = Person.objects.create(id=o.person.pk, 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 # WGEditor for o in WGEditor.objects.all(): diff --git a/redesign/importing/utils.py b/redesign/importing/utils.py index 4b85304ee..ceec5a4a5 100644 --- a/redesign/importing/utils.py +++ b/redesign/importing/utils.py @@ -1,4 +1,5 @@ -from person.models import Person +from redesign import unaccent +from redesign.person.models import Person, Email, Alias def clean_email_address(addr): addr = addr.replace("!", "@").replace("(at)", "@") # some obvious @ replacements @@ -16,4 +17,34 @@ def old_person_to_email(person): hardcoded_emails = { 'Dinara Suleymanova': "dinaras@ietf.org" } return clean_email_address(person.email()[1] or hardcoded_emails.get("%s %s" % (person.first_name, person.last_name)) or "") + +def get_or_create_email(o, create_fake): + # take person on o and get or create new Email and Person objects + email = old_person_to_email(o.person) + if not email: + 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.pk, o.person.pk, o.person.first_name, o.person.last_name)).encode('utf-8') + return None + e, _ = Email.objects.select_related("person").get_or_create(address=email) + if not e.person: + n = u"%s %s" % (o.person.first_name, o.person.last_name) + asciified = unaccent.asciify(n) + aliases = Alias.objects.filter(name__in=(n, asciified)) + if aliases: + p = aliases[0].person + else: + p = Person.objects.create(id=o.person.pk, 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 diff --git a/redesign/name/proxy.py b/redesign/name/proxy.py index 6d2fb3645..7d1c5b128 100644 --- a/redesign/name/proxy.py +++ b/redesign/name/proxy.py @@ -2,10 +2,11 @@ from redesign.proxy_utils import TranslatingManager from models import * class IDStatus(DocStateName): - def __init__(self, base): + def from_object(self, base): for f in base._meta.fields: setattr(self, f.name, getattr(base, f.name)) - + return self + #status_id = models.AutoField(primary_key=True) #status = models.CharField(max_length=25, db_column='status_value') @@ -18,3 +19,98 @@ class IDStatus(DocStateName): class Meta: proxy = True + +class IDState(IesgDocStateName): + PUBLICATION_REQUESTED = 10 + LAST_CALL_REQUESTED = 15 + IN_LAST_CALL = 16 + WAITING_FOR_WRITEUP = 18 + WAITING_FOR_AD_GO_AHEAD = 19 + IESG_EVALUATION = 20 + IESG_EVALUATION_DEFER = 21 + APPROVED_ANNOUNCEMENT_SENT = 30 + AD_WATCHING = 42 + DEAD = 99 + DO_NOT_PUBLISH_STATES = (33, 34) + + objects = TranslatingManager(dict(pk="order")) + + def from_object(self, base): + for f in base._meta.fields: + setattr(self, f.name, getattr(base, f.name)) + return self + + #document_state_id = models.AutoField(primary_key=True) + @property + def document_state_id(self): + return self.order + + #state = models.CharField(max_length=50, db_column='document_state_val') + @property + def state(self): + return self.name + + #equiv_group_flag = models.IntegerField(null=True, blank=True) # unused + #description = models.TextField(blank=True, db_column='document_desc') + @property + def description(self): + return self.desc + + @property + def nextstate(self): + # simulate related queryset + from name.models import get_next_iesg_states + return IDState.objects.filter(pk__in=[x.pk for x in get_next_iesg_states(self)]) + + @property + def next_state(self): + # simulate IDNextState + return self + + def __str__(self): + return self.state + + @staticmethod + def choices(): + return [(state.slug, state.name) for state in IDState.objects.all()] + + class Meta: + proxy = True + + +class IDSubStateManager(TranslatingManager): + def __init__(self, *args): + super(IDSubStateManager, self).__init__(*args) + + def all(self): + return self.filter(slug__in=['extpty', 'need-rev', 'ad-f-up', 'point']) + +class IDSubState(DocInfoTagName): + objects = IDSubStateManager(dict(pk="order")) + + def from_object(self, base): + for f in base._meta.fields: + setattr(self, f.name, getattr(base, f.name)) + return self + + #sub_state_id = models.AutoField(primary_key=True) + @property + def sub_state_id(self): + return self.order + + #sub_state = models.CharField(max_length=55, db_column='sub_state_val') + @property + def sub_state(self): + return self.name + + #description = models.TextField(blank=True, db_column='sub_state_desc') + @property + def description(self): + return self.desc + + def __str__(self): + return self.sub_state + + class Meta: + proxy = True + diff --git a/redesign/person/models.py b/redesign/person/models.py index 22939dd4b..f8d5d139a 100644 --- a/redesign/person/models.py +++ b/redesign/person/models.py @@ -44,6 +44,14 @@ class Person(models.Model): else: prefix, first, middle, last, suffix = self.ascii_parts() return (first and first[0]+"." or "")+(middle or "")+" "+last+(suffix and " "+suffix or "") + def role_email(self, role_name, group): + e = Email.objects.filter(person=self, role__group=group, role__name=role_name) + if e: + return e[0] + e = self.email_set.order("-active") + if e: + return e[0] + return None def email_address(self): e = self.email_set.filter(active=True) if e: diff --git a/redesign/proxy_utils.py b/redesign/proxy_utils.py index 6dd1493a8..780e4a981 100644 --- a/redesign/proxy_utils.py +++ b/redesign/proxy_utils.py @@ -150,13 +150,16 @@ class TranslatingManager(Manager): 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): + def __init__(self, trans, always_filter=None): super(TranslatingManager, self).__init__() self.translated_attrs = trans + self.always_filter = always_filter def get_query_set(self): qs = TranslatingQuerySet(self.model) qs.translated_attrs = self.translated_attrs + if self.always_filter: + qs = qs.filter(**self.always_filter) return qs # def dates(self, *args, **kwargs): From 925233618f6248ee086aa895fda5d8348834c526 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 30 May 2011 09:15:31 +0000 Subject: [PATCH 68/75] Fix import path of some proxy classes - Legacy-Id: 3161 --- ietf/idtracker/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ietf/idtracker/models.py b/ietf/idtracker/models.py index ceb1a50ad..2cf84f05f 100644 --- a/ietf/idtracker/models.py +++ b/ietf/idtracker/models.py @@ -1097,7 +1097,8 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: AcronymOld = Acronym IESGLoginOld = IESGLogin IETFWGOld = IETFWG - from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo, IDState, IDSubState, Rfc + from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo, Rfc + from redesign.name.proxy import IDState, IDSubState from redesign.group.proxy import Area, Acronym, IETFWG from redesign.person.proxy import IESGLogin From 1ca085f91f1be6f23854db242477df7d46da81f4 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 30 May 2011 12:19:08 +0000 Subject: [PATCH 69/75] Fixed some bugs - Legacy-Id: 3162 --- ietf/idrfc/testurlREDESIGN.list | 8 ++++---- ietf/idrfc/views_search.py | 4 ++-- ietf/idtracker/tests.py | 30 +++++++++++++++--------------- ietf/idtracker/testurl.list | 6 ------ ietf/iesg/testurl.list | 3 --- ietf/ipr/new.py | 20 ++++++++++++++------ redesign/doc/models.py | 11 +++++++---- redesign/doc/proxy.py | 1 - 8 files changed, 42 insertions(+), 41 deletions(-) diff --git a/ietf/idrfc/testurlREDESIGN.list b/ietf/idrfc/testurlREDESIGN.list index 6c92a6511..10a54f1cd 100644 --- a/ietf/idrfc/testurlREDESIGN.list +++ b/ietf/idrfc/testurlREDESIGN.list @@ -70,17 +70,17 @@ 200 /doc/search/ 200 /doc/search/?rfcs=on&name=snmp -200 /doc/search/?rfcs=on&name=nfs&by=ad&ad=lars.eggert%40nokia.com +200 /doc/search/?rfcs=on&name=nfs&by=ad&ad=104942 200 /doc/search/?activeDrafts=on&name=sipping 200 /doc/search/?oldDrafts=on&name=tls -200 /doc/search/?activeDrafts=on&oldDrafts=on&ad=lars.eggert%40nokia.com&by=ad +200 /doc/search/?activeDrafts=on&oldDrafts=on&ad=104942&by=ad 200 /doc/search/?activeDrafts=on&state=iesg-eva&by=state 200 /doc/search/?activeDrafts=on&oldDrafts=on&subState=need-rev&by=state -200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&ad=lars.eggert%40nokia.com&name=nfs&by=ad +200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&ad=104942&name=nfs&by=ad 200 /doc/search/?rfcs=on&group=tls&by=group 200 /doc/search/?activeDrafts=on&group=tls&by=group 200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&author=eronen&by=author -200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&area=178&name=ldap&by=area +200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&area=934&name=ldap&by=area 200 /doc/search/?activeDrafts=on&name=asdfsadfsdfasdf 200 /doc/search/?activeDrafts=on&name=%EF%BD%8C #non-ASCII diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index a2c9806f7..06713b8ee 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -360,8 +360,8 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: 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"])) + Q(ad__email__role__name="ad", + ad__email__role__group=query["area"])) elif by == "ad": docs = docs.filter(ad=query["ad"]) elif by == "state": diff --git a/ietf/idtracker/tests.py b/ietf/idtracker/tests.py index 613be5eb7..d9b63ba7a 100644 --- a/ietf/idtracker/tests.py +++ b/ietf/idtracker/tests.py @@ -25,18 +25,18 @@ class IdTrackerUrlTestCase(SimpleUrlTestCase): else: return content -class WGRoleTest(django.test.TestCase): - fixtures = ['wgtest'] - - def setUp(self): - from ietf.idtracker.models import IETFWG - self.xmas = IETFWG.objects.get(group_acronym__acronym='xmas') - self.snow = IETFWG.objects.get(group_acronym__acronym='snow') - - def test_roles(self): - print " Testing WG roles" - self.assertEquals(self.xmas.wgchair_set.all()[0].role(), 'xmas WG Chair') - self.assertEquals(self.snow.wgchair_set.all()[0].role(), 'snow BOF Chair') - self.assertEquals(self.xmas.wgsecretary_set.all()[0].role(), 'xmas WG Secretary') - self.assertEquals(self.xmas.wgtechadvisor_set.all()[0].role(), 'xmas Technical Advisor') - print "OK" +# class WGRoleTest(django.test.TestCase): +# fixtures = ['wgtest'] +# +# def setUp(self): +# from ietf.idtracker.models import IETFWG +# self.xmas = IETFWG.objects.get(group_acronym__acronym='xmas') +# self.snow = IETFWG.objects.get(group_acronym__acronym='snow') +# +# def test_roles(self): +# print " Testing WG roles" +# self.assertEquals(self.xmas.wgchair_set.all()[0].role(), 'xmas WG Chair') +# self.assertEquals(self.snow.wgchair_set.all()[0].role(), 'snow BOF Chair') +# self.assertEquals(self.xmas.wgsecretary_set.all()[0].role(), 'xmas WG Secretary') +# self.assertEquals(self.xmas.wgtechadvisor_set.all()[0].role(), 'xmas Technical Advisor') +# print "OK" diff --git a/ietf/idtracker/testurl.list b/ietf/idtracker/testurl.list index 97e6b3c3e..c2b06ee30 100644 --- a/ietf/idtracker/testurl.list +++ b/ietf/idtracker/testurl.list @@ -6,13 +6,8 @@ 200 /idtracker/status/last-call/ 301 /idtracker/rfc3847/ -301 /idtracker/12689/ 301 /idtracker/draft-ietf-isis-link-attr/ -301 /idtracker/draft-ietf-isis-link-attr/comment/65232/ 301 /idtracker/draft-eronen-tls-psk/ # no IESG information -301 /idtracker/comment/65232/ -301 /idtracker/ballot/1760/ -404 /idtracker/ballot/1723/ # dangling ballot, does not link to any doc 301 /idtracker/ 200 /feed/comments/draft-ietf-isis-link-attr/ 200 /feed/comments/rfc3373/ @@ -27,5 +22,4 @@ # Test case for missing comment time (bug fixed in changeset 1733) 200 /feed/comments/draft-ietf-msec-newtype-keyid/ -200,heavy /sitemap-drafts.xml 200,heavy /sitemap-idtracker.xml diff --git a/ietf/iesg/testurl.list b/ietf/iesg/testurl.list index 47ee7ca6b..1f7734032 100644 --- a/ietf/iesg/testurl.list +++ b/ietf/iesg/testurl.list @@ -20,8 +20,5 @@ 200 /iesg/ann/new/ # This takes ~ 300s: #200 /iesg/ann/prev/ -200 /iesg/ann/2422/ -200 /iesg/ann/1563/ -404 /iesg/ann/567/ 200 /feed/iesg-agenda/ diff --git a/ietf/ipr/new.py b/ietf/ipr/new.py index 40cd8db33..7d360e71e 100644 --- a/ietf/ipr/new.py +++ b/ietf/ipr/new.py @@ -102,11 +102,19 @@ def new(request, type, update=None, submitter=None): setattr(self, contact, ContactForm(prefix=contact[:4], initial=contact_initial.get(contact, {}), *args, **kwnoinit)) rfclist_initial = "" if update: - rfclist_initial = " ".join(["RFC%d" % rfc.document_id for rfc in update.rfcs.all()]) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from ietf.ipr.models import IprDocAlias + rfclist_initial = " ".join(a.doc_alias.name.upper() for a in IprDocAlias.objects.filter(doc_alias__name__startswith="rfc", ipr=update)) + else: + rfclist_initial = " ".join(["RFC%d" % rfc.document_id for rfc in update.rfcs.all()]) self.base_fields["rfclist"] = forms.CharField(required=False, initial=rfclist_initial) draftlist_initial = "" if update: - draftlist_initial = " ".join([draft.document.filename + (draft.revision and "-%s" % draft.revision or "") for draft in update.drafts.all()]) + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from ietf.ipr.models import IprDocAlias + draftlist_initial = " ".join(a.doc_alias.name + ("-%s" % a.rev if a.rev else "") for a in IprDocAlias.objects.filter(ipr=update).exclude(doc_alias__name__startswith="rfc")) + else: + draftlist_initial = " ".join([draft.document.filename + (draft.revision and "-%s" % draft.revision or "") for draft in update.drafts.all()]) self.base_fields["draftlist"] = forms.CharField(required=False, initial=draftlist_initial) if section_list.get("holder_contact", False): self.base_fields["hold_contact_is_submitter"] = forms.BooleanField(required=False) @@ -135,7 +143,7 @@ def new(request, type, update=None, submitter=None): for rfc in rfclist: try: if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import DocAlias + from redesign.doc.models import DocAlias DocAlias.objects.get(name="rfc%s" % int(rfc)) else: Rfc.objects.get(rfc_number=int(rfc)) @@ -160,7 +168,7 @@ def new(request, type, update=None, submitter=None): rev = None try: if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import DocAlias + from redesign.doc.models import DocAlias id = DocAlias.objects.get(name=filename) # proxy attribute for code below id.revision = id.document.rev @@ -277,7 +285,7 @@ def new(request, type, update=None, submitter=None): name = draft[:-3] rev = draft[-2:] - from doc.models import DocAlias + from redesign.doc.models import DocAlias models.IprDocAlias.objects.create( doc_alias=DocAlias.objects.get(name=name), ipr=instance, @@ -290,7 +298,7 @@ def new(request, type, update=None, submitter=None): # Save IprRfc(s) for rfcnum in form.cleaned_data["rfclist"].split(): if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import DocAlias + from redesign.doc.models import DocAlias models.IprDocAlias.objects.create( doc_alias=DocAlias.objects.get(name="rfc%s" % int(rfcnum)), ipr=instance, diff --git a/redesign/doc/models.py b/redesign/doc/models.py index adf8cc76f..7753a996f 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -96,10 +96,13 @@ class Document(DocumentInfo): return e[0] if e else None def canonical_name(self): + name = self.name if self.type_id == "draft" and self.state_id == "rfc": - return self.docalias_set.get(name__startswith="rfc").name - else: - return self.name + a = self.docalias_set.filter(name__startswith="rfc") + if a: + name = a[0].name + return name + class RelatedDocHistory(models.Model): source = models.ForeignKey('DocHistory') @@ -244,7 +247,7 @@ class Event(models.Model): return u"%s %s at %s" % (self.by.name, self.get_type_display().lower(), self.time) class Meta: - ordering = ['-time', 'id'] + ordering = ['-time', '-id'] class NewRevisionEvent(Event): rev = models.CharField(max_length=16) diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index 4e41cf829..a3f429893 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -131,7 +131,6 @@ class InternetDraft(Document): #rfc_number = models.IntegerField(null=True, blank=True, db_index=True) @property def rfc_number(self): - # simple optimization for search results n = self.canonical_name() return int(n[3:]) if n.startswith("rfc") else None From 02873d24d81b5e077bb41fbc68f2a4d57afa96ce Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 30 May 2011 18:28:49 +0000 Subject: [PATCH 70/75] Port send queue script and tests and import ScheduledAnnouncement + some cleanups - Legacy-Id: 3163 --- ietf/announcements/admin.py | 4 +- ietf/announcements/models.py | 11 ++- ietf/announcements/send_scheduled.py | 32 ++++++- ietf/announcements/tests.py | 74 ++++++++++++++++ ietf/bin/send-scheduled-mail | 35 +++++--- ietf/idrfc/views_ballot.py | 1 - .../announcements/message_detail.html | 2 +- redesign/doc/models.py | 15 ---- redesign/importing/import-announcements.py | 88 +++++++++++++++++-- 9 files changed, 218 insertions(+), 44 deletions(-) diff --git a/ietf/announcements/admin.py b/ietf/announcements/admin.py index 6f9c1dad4..3d19dd576 100644 --- a/ietf/announcements/admin.py +++ b/ietf/announcements/admin.py @@ -26,7 +26,7 @@ admin.site.register(ScheduledAnnouncement, ScheduledAnnouncementAdmin) if settings.USE_DB_REDESIGN_PROXY_CLASSES: class MessageAdmin(admin.ModelAdmin): list_display = ["time", "by", "subject", "groups"] - search_fields = ["text"] + search_fields = ["body"] raw_id_fields = ["by"] def groups(self, instance): @@ -37,7 +37,7 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: class SendQueueAdmin(admin.ModelAdmin): list_display = ["time", "by", "message", "send_at", "sent_at"] list_filter = ["time", "send_at", "sent_at"] - search_fields = ["message__text"] + search_fields = ["message__body"] raw_id_fields = ["by"] admin.site.register(SendQueue, SendQueueAdmin) diff --git a/ietf/announcements/models.py b/ietf/announcements/models.py index ab8fa5645..328496f29 100644 --- a/ietf/announcements/models.py +++ b/ietf/announcements/models.py @@ -100,11 +100,12 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_ANNOUN subject = models.CharField(max_length=255) frm = models.CharField(max_length=255) - to = models.CharField(max_length=255) - cc = models.CharField(max_length=255, blank=True) + to = models.CharField(max_length=1024) + cc = models.CharField(max_length=1024, blank=True) bcc = models.CharField(max_length=255, blank=True) reply_to = models.CharField(max_length=255, blank=True) - text = models.TextField() + body = models.TextField() + content_type = models.CharField(max_length=255, blank=True) related_groups = models.ManyToManyField(Group, blank=True) @@ -118,10 +119,12 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_ANNOUN time = models.DateTimeField(default=datetime.datetime.now) by = models.ForeignKey(Person) - comment = models.TextField() message = models.ForeignKey(Message) + send_at = models.DateTimeField(blank=True, null=True) sent_at = models.DateTimeField(blank=True, null=True) + note = models.TextField(blank=True) + class Meta: ordering = ['time'] diff --git a/ietf/announcements/send_scheduled.py b/ietf/announcements/send_scheduled.py index a2fe2a8ac..bb0279867 100644 --- a/ietf/announcements/send_scheduled.py +++ b/ietf/announcements/send_scheduled.py @@ -1,5 +1,7 @@ import re, datetime, email +from django.conf import settings + from ietf.utils.mail import send_mail_text, send_mail_mime first_dot_on_line_re = re.compile(r'^\.', re.MULTILINE) @@ -8,7 +10,6 @@ def send_scheduled_announcement(announcement): # for some reason, the old Perl code base substituted away . on line starts body = first_dot_on_line_re.sub("", announcement.body) - announcement.content_type extra = {} if announcement.replyto: extra['Reply-To'] = announcement.replyto @@ -30,3 +31,32 @@ def send_scheduled_announcement(announcement): announcement.actual_sent_time = str(now.time()) announcement.mail_sent = True announcement.save() + + +def send_scheduled_announcementREDESIGN(send_queue): + message = send_queue.message + + # for some reason, the old Perl code base substituted away . on line starts + body = first_dot_on_line_re.sub("", message.body) + + extra = {} + if message.reply_to: + extra['Reply-To'] = message.reply_to + + content_type = message.content_type.lower() + if not content_type or 'text/plain' in content_type: + send_mail_text(None, message.to, message.frm, message.subject, + body, cc=message.cc, bcc=message.bcc) + elif 'multipart' in content_type: + # make body a real message so we can parse it + body = ("MIME-Version: 1.0\r\nContent-Type: %s\r\n" % content_type) + body + + msg = email.message_from_string(body.encode("utf-8")) + send_mail_mime(None, message.to, message.frm, message.subject, + msg, cc=message.cc, bcc=message.bcc) + + send_queue.sent_at = datetime.datetime.now() + send_queue.save() + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + send_scheduled_announcement = send_scheduled_announcementREDESIGN diff --git a/ietf/announcements/tests.py b/ietf/announcements/tests.py index 8fdc5a22e..198aac9ea 100644 --- a/ietf/announcements/tests.py +++ b/ietf/announcements/tests.py @@ -1,7 +1,11 @@ +import datetime + +from django.conf import settings import django.test from ietf.utils.test_utils import SimpleUrlTestCase, canonicalize_sitemap from ietf.utils.test_runner import mail_outbox +from ietf.utils.test_data import make_test_data from ietf.announcements.models import ScheduledAnnouncement @@ -57,3 +61,73 @@ class SendScheduledAnnouncementsTestCase(django.test.TestCase): self.assertTrue("This is a test" in mail_outbox[-1]["Subject"]) self.assertTrue("--NextPart" in mail_outbox[-1].as_string()) self.assertTrue(ScheduledAnnouncement.objects.get(id=a.id).mail_sent) + + +class SendScheduledAnnouncementsTestCaseREDESIGN(django.test.TestCase): + def test_send_plain_announcement(self): + from ietf.announcements.models import Message, SendQueue + from redesign.person.models import Person + + make_test_data() + + msg = Message.objects.create( + by=Person.objects.get(name="(System)"), + subject="This is a test", + to="test@example.com", + frm="testmonkey@example.com", + cc="cc.a@example.com, cc.b@example.com", + bcc="bcc@example.com", + body="Hello World!", + content_type="", + ) + + q = SendQueue.objects.create( + by=Person.objects.get(name="(System)"), + message=msg, + send_at=datetime.datetime.now() + datetime.timedelta(hours=12) + ) + + mailbox_before = len(mail_outbox) + + from ietf.announcements.send_scheduled import send_scheduled_announcement + send_scheduled_announcement(q) + + self.assertEquals(len(mail_outbox), mailbox_before + 1) + self.assertTrue("This is a test" in mail_outbox[-1]["Subject"]) + self.assertTrue(SendQueue.objects.get(id=q.id).sent_at) + + def test_send_mime_announcement(self): + from ietf.announcements.models import Message, SendQueue + from redesign.person.models import Person + + make_test_data() + + msg = Message.objects.create( + by=Person.objects.get(name="(System)"), + subject="This is a test", + to="test@example.com", + frm="testmonkey@example.com", + cc="cc.a@example.com, cc.b@example.com", + bcc="bcc@example.com", + body='--NextPart\r\n\r\nA New Internet-Draft is available from the on-line Internet-Drafts directories.\r\n--NextPart\r\nContent-Type: Message/External-body;\r\n\tname="draft-huang-behave-bih-01.txt";\r\n\tsite="ftp.ietf.org";\r\n\taccess-type="anon-ftp";\r\n\tdirectory="internet-drafts"\r\n\r\nContent-Type: text/plain\r\nContent-ID: <2010-07-30001541.I-D@ietf.org>\r\n\r\n--NextPart--', + content_type='Multipart/Mixed; Boundary="NextPart"', + ) + + q = SendQueue.objects.create( + by=Person.objects.get(name="(System)"), + message=msg, + send_at=datetime.datetime.now() + datetime.timedelta(hours=12) + ) + + mailbox_before = len(mail_outbox) + + from ietf.announcements.send_scheduled import send_scheduled_announcement + send_scheduled_announcement(q) + + self.assertEquals(len(mail_outbox), mailbox_before + 1) + self.assertTrue("This is a test" in mail_outbox[-1]["Subject"]) + self.assertTrue("--NextPart" in mail_outbox[-1].as_string()) + self.assertTrue(SendQueue.objects.get(id=q.id).sent_at) + +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + SendScheduledAnnouncementsTestCase = SendScheduledAnnouncementsTestCaseREDESIGN diff --git a/ietf/bin/send-scheduled-mail b/ietf/bin/send-scheduled-mail index 4076f9283..d4d46ab41 100755 --- a/ietf/bin/send-scheduled-mail +++ b/ietf/bin/send-scheduled-mail @@ -14,7 +14,7 @@ from ietf.announcements.models import ScheduledAnnouncement from ietf.announcements.send_scheduled import * from django.db.models import Q -if not len(sys.argv) == 2 or sys.argv[1] not in ('all', 'rsync', 'specific'): +if len(sys.argv) != 2 or sys.argv[1] not in ('all', 'rsync', 'specific'): print "USAGE: %s " % os.path.basename(__file__) print "'all' means all not sent" print "'rsync' means all not sent where to-be-sent-date is null" @@ -24,19 +24,28 @@ if not len(sys.argv) == 2 or sys.argv[1] not in ('all', 'rsync', 'specific'): mode = sys.argv[1] now = datetime.datetime.now() -now = datetime.datetime(2010, 8, 5) -announcements = ScheduledAnnouncement.objects.filter(mail_sent=False) -if mode == "rsync": - # include bogus 0000-00-00 entries - announcements = announcements.filter(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min)) -elif mode == "specific": - # exclude null/bogus entries - announcements = announcements.exclude(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min)) - announcements = announcements.filter(to_be_sent_date__lte=now.date(), - to_be_sent_time__lte=now.time()) +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from ietf.announcements.models import SendQueue + announcements = SendQueue.objects.filter(sent_at=None) + if mode == "rsync": + announcements = announcements.filter(send_at=None) + elif mode == "specific": + announcements = announcements.exclude(send_at=None).filter(send_at__lte=now) +else: + announcements = ScheduledAnnouncement.objects.filter(mail_sent=False) + if mode == "rsync": + # include bogus 0000-00-00 entries + announcements = announcements.filter(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min)) + elif mode == "specific": + # exclude null/bogus entries + announcements = announcements.exclude(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min)) + + announcements = announcements.filter(to_be_sent_date__lte=now.date(), + to_be_sent_time__lte=now.time()) for announcement in announcements: send_scheduled_announcement(announcement) - - syslog.syslog('Sent scheduled announcement %s "%s"' % (announcement.id, announcement.subject)) + + subject = announcement.message.subject if settings.USE_DB_REDESIGN_PROXY_CLASSES else announcement.subject + syslog.syslog(u'Sent scheduled announcement %s "%s"' % (announcement.id, subject)) diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index b8827b176..87903661f 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -292,7 +292,6 @@ def edit_positionREDESIGN(request, name): doc.time = pos.time doc.save() - # FIXME: test if request.POST.get("send_mail"): qstr = "?return_to_url=%s" % return_to_url if request.GET.get('ad'): diff --git a/ietf/templates/announcements/message_detail.html b/ietf/templates/announcements/message_detail.html index 606d84046..29dbbb4be 100644 --- a/ietf/templates/announcements/message_detail.html +++ b/ietf/templates/announcements/message_detail.html @@ -13,6 +13,6 @@ Subject: {{ message.subject|escape }}


      -{{ message.text|escape }}
      +{{ message.body|escape }}
       
      {% endblock %} diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 7753a996f..6610479d6 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -185,21 +185,6 @@ class DocAlias(models.Model): verbose_name_plural = "document aliases" -# 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 = [ # core events ("new_revision", "Added new revision"), diff --git a/redesign/importing/import-announcements.py b/redesign/importing/import-announcements.py index f423565cc..51ce26fe8 100755 --- a/redesign/importing/import-announcements.py +++ b/redesign/importing/import-announcements.py @@ -16,21 +16,21 @@ from redesign.person.models import * from redesign.group.models import * from redesign.name.utils import name from redesign.importing.utils import old_person_to_person -from ietf.announcements.models import Message -from ietf.announcements.models import Announcement, PersonOrOrgInfo, AnnouncedTo, AnnouncedFrom +from ietf.announcements.models import Message, SendQueue +from ietf.announcements.models import Announcement, PersonOrOrgInfo, AnnouncedTo, AnnouncedFrom, ScheduledAnnouncement +from ietf.idtracker.models import IESGLogin # assumptions: # - nomcom groups have been imported -# - persons have been imported +# - persons have been imported (Announcement originators and IESGLogins) -# imports Announcements - -# FIXME: should import ScheduledAnnouncements +# imports Announcement, ScheduledAnnouncement system = Person.objects.get(name="(System)") # Announcement for o in Announcement.objects.all().select_related('announced_to', 'announced_from').order_by('announcement_id').iterator(): + print "importing Announcement", o.pk try: message = Message.objects.get(id=o.announcement_id) except Message.DoesNotExist: @@ -74,7 +74,7 @@ for o in Announcement.objects.all().select_related('announced_to', 'announced_fr message.bcc = l[len("bcc:"):].strip() elif l.lower().startswith("reply-to:"): message.reply_to = l[len("reply-to:"):].strip() - message.text = o.text + message.body = o.text message.save() message.related_groups.clear() @@ -86,3 +86,77 @@ for o in Announcement.objects.all().select_related('announced_to', 'announced_fr message.related_groups.add(nomcom) + +# precompute scheduled_by's to speed up the loop a bit +scheduled_by_mapping = {} +for by in ScheduledAnnouncement.objects.all().values_list("scheduled_by", flat=True).distinct(): + logins = IESGLogin.objects.filter(login_name=by) + if logins: + l = logins[0] + person = l.person + if not person: + person = PersonOrOrgInfo.objects.get(first_name=l.first_name, last_name=l.last_name) + found = old_person_to_person(person) + else: + found = system + + print "mapping", by, "to", found + scheduled_by_mapping[by] = found + +# ScheduledAnnouncement +for o in ScheduledAnnouncement.objects.all().order_by('id').iterator(): + print "importing ScheduledAnnouncement", o.pk + try: + q = SendQueue.objects.get(id=o.id) + except SendQueue.DoesNotExist: + q = SendQueue(id=o.id) + # make sure there's no id overlap with ordinary already-imported announcements + q.message = Message(id=o.id + 4000) + + time = datetime.datetime.combine(o.scheduled_date, + datetime.time(*(int(x) for x in o.scheduled_time.split(":")))) + by = scheduled_by_mapping[o.scheduled_by] + + q.message.time = time + q.message.by = by + + q.message.subject = (o.subject or "").strip() + q.message.to = (o.to_val or "").strip() + q.message.frm = (o.from_val or "").strip() + q.message.cc = (o.cc_val or "").strip() + q.message.bcc = (o.bcc_val or "").strip() + q.message.reply_to = (o.replyto or "").strip() + q.message.body = o.body or "" + q.message.content_type = o.content_type or "" + q.message.save() + + q.time = time + q.by = by + + d = None + if o.to_be_sent_date: + try: + t = datetime.time(*(int(x) for x in o.to_be_sent_time.split(":"))) + except ValueError: + t = datetime.time(0, 0, 0) + d = datetime.datetime.combine(o.to_be_sent_date, t) + + q.send_at = d + + d = None + if o.actual_sent_date: + try: + t = datetime.time(*(int(x) for x in o.scheduled_time.split(":"))) + except ValueError: + t = datetime.time(0, 0, 0) + + d = datetime.datetime.combine(o.actual_sent_date, t) + + q.sent_at = d + + n = (o.note or "").strip() + if n.startswith("
      "): + n = n[len("
      "):] + q.note = n + + q.save() From 52b57f182beda7cad0a78859d534d4790371ab49 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Tue, 31 May 2011 19:29:56 +0000 Subject: [PATCH 71/75] Fix bug in role_mail helper - Legacy-Id: 3164 --- redesign/person/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redesign/person/models.py b/redesign/person/models.py index f8d5d139a..e2e177d6b 100644 --- a/redesign/person/models.py +++ b/redesign/person/models.py @@ -48,7 +48,7 @@ class Person(models.Model): e = Email.objects.filter(person=self, role__group=group, role__name=role_name) if e: return e[0] - e = self.email_set.order("-active") + e = self.email_set.order_by("-active") if e: return e[0] return None From 893471df6817a8987bc4e8019f4c126f4368fb34 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 3 Jun 2011 13:59:55 +0000 Subject: [PATCH 72/75] Bug fixes from Martin Qvist - Legacy-Id: 3167 --- ietf/idrfc/views_search.py | 2 +- redesign/doc/proxy.py | 2 +- redesign/group/proxy.py | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index 06713b8ee..6fc03cccb 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -507,7 +507,7 @@ def all(request): 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))) + rfc2 = (dict(rfc_number=r, draft=None) for r in sorted(int(n[3:]) for n in Document.objects.filter(type="draft", 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') diff --git a/redesign/doc/proxy.py b/redesign/doc/proxy.py index a3f429893..9ffd22374 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -16,7 +16,7 @@ class InternetDraft(Document): job_owner="ad", rfc_number=lambda v: ("docalias__name", "rfc%s" % v), cur_state="iesg_state__order", - )) + ), always_filter=dict(type="draft")) DAYS_TO_EXPIRE=185 diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py index 3553ebcb4..6f38227c0 100644 --- a/redesign/group/proxy.py +++ b/redesign/group/proxy.py @@ -173,6 +173,8 @@ class IETFWG(Group): # 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 + import os + from django.conf import settings # get file path from settings. Syntesize file name from path, acronym, and suffix try: filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, self.acronym) + ".desc.txt" From 0e829f975def8355085e2c7b8a7aa8fdc273fb03 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Fri, 3 Jun 2011 14:10:33 +0000 Subject: [PATCH 73/75] Fix another bug that prevented the Individual Submission group from being accesses through IETFWG - Legacy-Id: 3168 --- redesign/group/proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py index 6f38227c0..7294f482f 100644 --- a/redesign/group/proxy.py +++ b/redesign/group/proxy.py @@ -90,7 +90,7 @@ class IETFWG(Group): areagroup__area__status=lambda v: ("parent__state", { 1: "active" }[v]), start_date__isnull=lambda v: None if v else ("groupevent__type", "started") ), - always_filter=dict(type="wg")) + always_filter=dict(type__in=("wg", "individ"))) ACTIVE=1 #group_acronym = models.OneToOneField(Acronym, primary_key=True, editable=False) From a13ef5d3c4a6f4303b1892a896e479c5049682ce Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 27 Jun 2011 13:57:49 +0000 Subject: [PATCH 74/75] Add version number to Django problem so it's easier to understand what's going on in the future - Legacy-Id: 3175 --- redesign/doc/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 6610479d6..2da0cc26a 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -124,7 +124,7 @@ class DocHistoryAuthor(models.Model): class DocHistory(DocumentInfo): doc = models.ForeignKey(Document) # ID of the Document this relates to - # Django won't let us define these in the base class, so we have + # Django 1.2 won't let us define these in the base class, so we have # to repeat them related = models.ManyToManyField('DocAlias', through=RelatedDocHistory, blank=True) authors = models.ManyToManyField(Email, through=DocHistoryAuthor, blank=True) From 4cff73929b5a167b4842491853e61310b86ea669 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 27 Jun 2011 17:31:59 +0000 Subject: [PATCH 75/75] Rename Event to DocEvent to avoid confusion - Legacy-Id: 3178 --- ietf/idrfc/expire.py | 4 +- ietf/idrfc/idrfc_wrapper.py | 6 +- ietf/idrfc/lastcall.py | 8 +- ietf/idrfc/mails.py | 16 +-- ietf/idrfc/mirror_rfc_index.py | 4 +- ietf/idrfc/testsREDESIGN.py | 116 ++++++++-------- ietf/idrfc/utils.py | 8 +- ietf/idrfc/views_ballot.py | 42 +++--- ietf/idrfc/views_doc.py | 10 +- ietf/idrfc/views_edit.py | 22 +-- ietf/idrfc/views_search.py | 2 +- ietf/iesg/feeds.py | 6 +- ietf/iesg/tests.py | 14 +- ietf/iesg/views.py | 36 ++--- ietf/utils/test_data.py | 2 +- redesign/doc/admin.py | 18 +-- redesign/doc/models.py | 22 +-- redesign/doc/proxy.py | 26 ++-- redesign/importing/import-document-state.py | 144 ++++++++++---------- 19 files changed, 253 insertions(+), 253 deletions(-) diff --git a/ietf/idrfc/expire.py b/ietf/idrfc/expire.py index cb8c49ff5..b3b4140b7 100644 --- a/ietf/idrfc/expire.py +++ b/ietf/idrfc/expire.py @@ -9,7 +9,7 @@ import datetime, os, shutil, glob, re from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment, IDAuthor,WGChair from ietf.utils.mail import send_mail, send_mail_subj from ietf.idrfc.utils import log_state_changed, add_document_comment -from doc.models import Document, Event, save_document_in_history +from doc.models import Document, DocEvent, save_document_in_history from name.models import IesgDocStateName, DocStateName, DocInfoTagName from person.models import Person, Email @@ -221,7 +221,7 @@ def expire_idREDESIGN(doc): doc.iesg_state = dead_state log_state_changed(None, doc, system, prev) - e = Event(doc=doc, by=system) + e = DocEvent(doc=doc, by=system) e.type = "expired_document" e.desc = "Document has expired" e.save() diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py index 9ca33df4d..6094fc7c0 100644 --- a/ietf/idrfc/idrfc_wrapper.py +++ b/ietf/idrfc/idrfc_wrapper.py @@ -436,7 +436,7 @@ class IetfProcessData: def state_date(self): try: if settings.USE_DB_REDESIGN_PROXY_CLASSES: - return self._idinternal.event_set.filter( + return self._idinternal.docevent_set.filter( Q(desc__istartswith="Draft Added by ")| Q(desc__istartswith="Draft Added in state ")| Q(desc__istartswith="Draft added in state ")| @@ -647,8 +647,8 @@ class BallotWrapper: positions = [] seen = {} - from doc.models import BallotPositionEvent - for pos in BallotPositionEvent.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'): + from doc.models import BallotPositionDocEvent + for pos in BallotPositionDocEvent.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.name, ad_username=pos.ad.pk, # ought to rename this in doc_ballot_list diff --git a/ietf/idrfc/lastcall.py b/ietf/idrfc/lastcall.py index 96b30e1b4..7b9723370 100644 --- a/ietf/idrfc/lastcall.py +++ b/ietf/idrfc/lastcall.py @@ -8,7 +8,7 @@ from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo from ietf.idrfc.mails import * from ietf.idrfc.utils import * -from doc.models import Document, Event, LastCallEvent, WriteupEvent, save_document_in_history +from doc.models import Document, DocEvent, LastCallDocEvent, WriteupDocEvent, save_document_in_history from name.models import IesgDocStateName from person.models import Person @@ -31,7 +31,7 @@ def request_last_callREDESIGN(request, doc): send_last_call_request(request, doc) - e = Event() + e = DocEvent() e.type = "requested_last_call" e.by = request.user.get_profile() e.doc = doc @@ -48,7 +48,7 @@ def get_expired_last_calls(): def get_expired_last_callsREDESIGN(): today = datetime.date.today() for d in Document.objects.filter(iesg_state="lc"): - e = d.latest_event(LastCallEvent, type="sent_last_call") + e = d.latest_event(LastCallDocEvent, type="sent_last_call") if e and e.expires.date() <= today: yield d @@ -73,7 +73,7 @@ def expire_last_call(doc): def expire_last_callREDESIGN(doc): state = IesgDocStateName.objects.get(slug="writeupw") - e = doc.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") if e and "What does this protocol do and why" not in e.text: # if it boiler-plate text has been removed, we assume the # write-up has been written diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index 2041d6a5e..d1782ed9a 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -9,7 +9,7 @@ from django.conf import settings from ietf.utils.mail import send_mail, send_mail_text from ietf.idtracker.models import * -from doc.models import WriteupEvent, BallotPositionEvent, LastCallEvent +from doc.models import WriteupDocEvent, BallotPositionDocEvent, LastCallDocEvent from person.models import Person def email_state_changed(request, doc, text): @@ -86,7 +86,7 @@ def full_intended_status(intended_status): return "a %s" % s def generate_ballot_writeup(request, doc): - e = WriteupEvent() + e = WriteupDocEvent() e.type = "changed_ballot_writeup_text" e.by = request.user.get_profile() e.doc = doc @@ -158,7 +158,7 @@ def generate_last_call_announcementREDESIGN(request, doc): ) ) - e = WriteupEvent() + e = WriteupDocEvent() e.type = "changed_last_call_text" e.by = request.user.get_profile() e.doc = doc @@ -253,7 +253,7 @@ def generate_approval_mailREDESIGN(request, doc): else: mail = generate_approval_mail_approved(request, doc) - e = WriteupEvent() + e = WriteupDocEvent() e.type = "changed_ballot_approval_text" e.by = request.user.get_profile() e.doc = doc @@ -479,7 +479,7 @@ def generate_issue_ballot_mailREDESIGN(request, doc): active_ads = Person.objects.filter(email__role__name="ad", email__role__group__state="active").distinct() e = doc.latest_event(type="started_iesg_process") - positions = BallotPositionEvent.objects.filter(doc=doc, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad') + positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad') # format positions and setup discusses and comments ad_feedback = [] @@ -519,13 +519,13 @@ def generate_issue_ballot_mailREDESIGN(request, doc): inactive_ad_positions.sort() ad_feedback.sort(key=lambda p: p.ad.name) - e = doc.latest_event(LastCallEvent, type="sent_last_call") + e = doc.latest_event(LastCallDocEvent, type="sent_last_call") last_call_expires = e.expires if e else None - e = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") + e = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") approval_text = e.text if e else "" - e = doc.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") ballot_writeup = e.text if e else "" return render_to_string("idrfc/issue_ballot_mailREDESIGN.txt", diff --git a/ietf/idrfc/mirror_rfc_index.py b/ietf/idrfc/mirror_rfc_index.py index 9def4094d..90a934113 100644 --- a/ietf/idrfc/mirror_rfc_index.py +++ b/ietf/idrfc/mirror_rfc_index.py @@ -179,7 +179,7 @@ import django.db.transaction @django.db.transaction.commit_on_success def insert_to_databaseREDESIGN(data): from person.models import Person - from doc.models import Document, DocAlias, Event, RelatedDocument + from doc.models import Document, DocAlias, DocEvent, RelatedDocument from group.models import Group from name.models import DocInfoTagName, DocRelationshipName from name.utils import name @@ -256,7 +256,7 @@ def insert_to_databaseREDESIGN(data): pubdate = datetime.strptime(rfc_published_date, "%Y-%m-%d") if not doc.latest_event(type="published_rfc", time=pubdate): - e = Event(doc=doc, type="published_rfc") + e = DocEvent(doc=doc, type="published_rfc") e.time = pubdate e.by = system e.desc = "RFC published" diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py index ab2d47f11..e1732d419 100644 --- a/ietf/idrfc/testsREDESIGN.py +++ b/ietf/idrfc/testsREDESIGN.py @@ -91,7 +91,7 @@ class ChangeStateTestCase(django.test.TestCase): # change state - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() mailbox_before = len(mail_outbox) r = self.client.post(url, dict(state="review-e")) @@ -99,8 +99,8 @@ class ChangeStateTestCase(django.test.TestCase): draft = Document.objects.get(name=draft.name) self.assertEquals(draft.iesg_state_id, "review-e") - self.assertEquals(draft.event_set.count(), events_before + 1) - self.assertTrue("State changed" in draft.event_set.all()[0].desc) + self.assertEquals(draft.docevent_set.count(), events_before + 1) + self.assertTrue("State changed" in draft.docevent_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']) @@ -128,21 +128,21 @@ class ChangeStateTestCase(django.test.TestCase): self.assertContains(r, "Your request to issue the Last Call") # last call text - e = draft.latest_event(WriteupEvent, type="changed_last_call_text") + e = draft.latest_event(WriteupDocEvent, type="changed_last_call_text") self.assertTrue(e) self.assertTrue("The IESG has received" in e.text) self.assertTrue(draft.title in e.text) self.assertTrue(draft.get_absolute_url() in e.text) # approval text - e = draft.latest_event(WriteupEvent, type="changed_ballot_approval_text") + e = draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") self.assertTrue(e) self.assertTrue("The IESG has approved" in e.text) self.assertTrue(draft.title in e.text) self.assertTrue(draft.get_absolute_url() in e.text) # ballot writeup - e = draft.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + e = draft.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") self.assertTrue(e) self.assertTrue("Technical Summary" in e.text) @@ -179,7 +179,7 @@ class EditInfoTestCase(django.test.TestCase): self.assertEquals(draft.ad, prev_ad) # edit info - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() mailbox_before = len(mail_outbox) new_ad = Person.objects.get(name="Ad No1") @@ -199,8 +199,8 @@ class EditInfoTestCase(django.test.TestCase): self.assertTrue(draft.tags.filter(slug="via-rfc")) self.assertEquals(draft.ad, new_ad) self.assertEquals(draft.note, "New note") - self.assertTrue(not draft.latest_event(TelechatEvent, type="telechat_date")) - self.assertEquals(draft.event_set.count(), events_before + 4) + self.assertTrue(not draft.latest_event(TelechatDocEvent, type="telechat_date")) + self.assertEquals(draft.docevent_set.count(), events_before + 4) self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue(draft.name in mail_outbox[-1]['Subject']) @@ -219,14 +219,14 @@ class EditInfoTestCase(django.test.TestCase): ) # add to telechat - self.assertTrue(not draft.latest_event(TelechatEvent, "scheduled_for_telechat")) + self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat")) data["telechat_date"] = TelechatDates.objects.all()[0].date1.isoformat() r = self.client.post(url, data) self.assertEquals(r.status_code, 302) draft = Document.objects.get(name=draft.name) - self.assertTrue(draft.latest_event(TelechatEvent, "scheduled_for_telechat")) - self.assertEquals(draft.latest_event(TelechatEvent, "scheduled_for_telechat").telechat_date, TelechatDates.objects.all()[0].date1) + self.assertTrue(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat")) + self.assertEquals(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, TelechatDates.objects.all()[0].date1) # change telechat data["telechat_date"] = TelechatDates.objects.all()[0].date2.isoformat() @@ -234,7 +234,7 @@ class EditInfoTestCase(django.test.TestCase): self.assertEquals(r.status_code, 302) draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.latest_event(TelechatEvent, "scheduled_for_telechat").telechat_date, TelechatDates.objects.all()[0].date2) + self.assertEquals(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, TelechatDates.objects.all()[0].date2) # remove from agenda data["telechat_date"] = "" @@ -242,14 +242,14 @@ class EditInfoTestCase(django.test.TestCase): self.assertEquals(r.status_code, 302) draft = Document.objects.get(name=draft.name) - self.assertTrue(not draft.latest_event(TelechatEvent, "scheduled_for_telechat").telechat_date) + self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date) def test_start_iesg_process_on_draft(self): draft = make_test_data() draft.ad = None draft.iesg_state = None draft.save() - draft.event_set.all().delete() + draft.docevent_set.all().delete() url = urlreverse('doc_edit_info', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) @@ -263,7 +263,7 @@ class EditInfoTestCase(django.test.TestCase): self.assertTrue('@' in q('form input[name=notify]')[0].get('value')) # add - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() mailbox_before = len(mail_outbox) ad = Person.objects.get(name="Aread Irector") @@ -283,9 +283,9 @@ class EditInfoTestCase(django.test.TestCase): self.assertTrue(draft.tags.filter(slug="via-rfc")) self.assertEquals(draft.ad, ad) self.assertEquals(draft.note, "This is a note") - self.assertTrue(not draft.latest_event(TelechatEvent, type="scheduled_for_telechat")) - self.assertEquals(draft.event_set.count(), events_before + 4) - events = list(draft.event_set.order_by('time', 'id')) + self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat")) + self.assertEquals(draft.docevent_set.count(), events_before + 4) + events = list(draft.docevent_set.order_by('time', 'id')) self.assertEquals(events[-4].type, "started_iesg_process") self.assertEquals(len(mail_outbox), mailbox_before) @@ -310,14 +310,14 @@ class ResurrectTestCase(django.test.TestCase): # request resurrect - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() mailbox_before = len(mail_outbox) r = self.client.post(url, dict()) self.assertEquals(r.status_code, 302) draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.event_set.count(), events_before + 1) + self.assertEquals(draft.docevent_set.count(), events_before + 1) e = draft.latest_event(type="requested_resurrect") self.assertTrue(e) self.assertEquals(e.by, Person.objects.get(name="Aread Irector")) @@ -329,7 +329,7 @@ class ResurrectTestCase(django.test.TestCase): draft = make_test_data() draft.state_id = "expired" draft.save() - Event.objects.create(doc=draft, + DocEvent.objects.create(doc=draft, type="requested_resurrect", by=Person.objects.get(name="Aread Irector")) @@ -344,14 +344,14 @@ class ResurrectTestCase(django.test.TestCase): self.assertEquals(len(q('form input[type=submit]')), 1) # request resurrect - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() mailbox_before = len(mail_outbox) r = self.client.post(url, dict()) self.assertEquals(r.status_code, 302) draft = Document.objects.get(name=draft.name) - self.assertEquals(draft.event_set.count(), events_before + 1) + self.assertEquals(draft.docevent_set.count(), events_before + 1) self.assertEquals(draft.latest_event().type, "completed_resurrect") self.assertEquals(draft.state_id, "active") self.assertEquals(len(mail_outbox), mailbox_before + 1) @@ -371,13 +371,13 @@ class AddCommentTestCase(django.test.TestCase): self.assertEquals(len(q('form textarea[name=comment]')), 1) # request resurrect - events_before = draft.event_set.count() + events_before = draft.docevent_set.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.event_set.count(), events_before + 1) + self.assertEquals(draft.docevent_set.count(), events_before + 1) self.assertEquals("This is a test.", draft.latest_event().desc) self.assertEquals("added_comment", draft.latest_event().type) self.assertEquals(len(mail_outbox), mailbox_before + 1) @@ -402,50 +402,50 @@ class EditPositionTestCase(django.test.TestCase): self.assertEquals(len(q('form textarea[name=comment]')), 1) # vote - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() r = self.client.post(url, dict(position="discuss", discuss="This is a discussion test.", comment="This is a test.")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPositionEvent, ad=ad) + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) self.assertEquals(pos.pos.slug, "discuss") self.assertTrue("This is a discussion test." in pos.discuss) self.assertTrue(pos.discuss_time != None) self.assertTrue("This is a test." in pos.comment) self.assertTrue(pos.comment_time != None) self.assertTrue("New position" in pos.desc) - self.assertEquals(draft.event_set.count(), events_before + 3) + self.assertEquals(draft.docevent_set.count(), events_before + 3) # recast vote - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() r = self.client.post(url, dict(position="noobj")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPositionEvent, ad=ad) + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) self.assertEquals(pos.pos.slug, "noobj") - self.assertEquals(draft.event_set.count(), events_before + 1) + self.assertEquals(draft.docevent_set.count(), events_before + 1) self.assertTrue("Position for" in pos.desc) # clear vote - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() r = self.client.post(url, dict(position="norecord")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPositionEvent, ad=ad) + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) self.assertEquals(pos.pos.slug, "norecord") - self.assertEquals(draft.event_set.count(), events_before + 1) + self.assertEquals(draft.docevent_set.count(), events_before + 1) self.assertTrue("Position for" in pos.desc) # change comment - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() r = self.client.post(url, dict(position="norecord", comment="New comment.")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPositionEvent, ad=ad) + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) self.assertEquals(pos.pos.slug, "norecord") - self.assertEquals(draft.event_set.count(), events_before + 2) + self.assertEquals(draft.docevent_set.count(), events_before + 2) self.assertTrue("Ballot comment text updated" in pos.desc) def test_edit_position_as_secretary(self): @@ -462,11 +462,11 @@ class EditPositionTestCase(django.test.TestCase): self.assertTrue(len(q('form input[name=position]')) > 0) # vote on behalf of AD - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() r = self.client.post(url, dict(position="discuss")) self.assertEquals(r.status_code, 302) - pos = draft.latest_event(BallotPositionEvent, ad=ad) + pos = draft.latest_event(BallotPositionDocEvent, ad=ad) self.assertEquals(pos.pos.slug, "discuss") self.assertTrue("New position" in pos.desc) self.assertTrue("by Sec" in pos.desc) @@ -478,7 +478,7 @@ class EditPositionTestCase(django.test.TestCase): ad = Person.objects.get(name="Aread Irector") - BallotPositionEvent.objects.create(doc=draft, type="changed_ballot_position", + BallotPositionDocEvent.objects.create(doc=draft, type="changed_ballot_position", by=ad, ad=ad, pos=BallotPositionName.objects.get(slug="yes"), comment="Test!", comment_time=datetime.datetime.now()) @@ -584,7 +584,7 @@ class BallotWriteupsTestCase(django.test.TestCase): save_last_call_text="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(WriteupEvent, type="changed_last_call_text").text) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text) # test regenerate r = self.client.post(url, dict( @@ -593,7 +593,7 @@ class BallotWriteupsTestCase(django.test.TestCase): self.assertEquals(r.status_code, 200) q = PyQuery(r.content) draft = Document.objects.get(name=draft.name) - self.assertTrue("Subject: Last Call" in draft.latest_event(WriteupEvent, type="changed_last_call_text").text) + self.assertTrue("Subject: Last Call" in draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text) def test_request_last_call(self): @@ -609,7 +609,7 @@ class BallotWriteupsTestCase(django.test.TestCase): # send r = self.client.post(url, dict( - last_call_text=draft.latest_event(WriteupEvent, type="changed_last_call_text").text, + last_call_text=draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text, send_last_call_request="1")) draft = Document.objects.get(name=draft.name) self.assertEquals(draft.iesg_state_id, "lc-req") @@ -635,7 +635,7 @@ class BallotWriteupsTestCase(django.test.TestCase): save_ballot_writeup="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(WriteupEvent, type="changed_ballot_writeup_text").text) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text").text) def test_issue_ballot(self): draft = make_test_data() @@ -644,7 +644,7 @@ class BallotWriteupsTestCase(django.test.TestCase): def create_pos(num, vote, comment="", discuss=""): ad = Person.objects.get(name="Ad No%s" % num) - e = BallotPositionEvent() + e = BallotPositionDocEvent() e.doc = draft e.by = ad e.ad = ad @@ -669,7 +669,7 @@ class BallotWriteupsTestCase(django.test.TestCase): create_pos(9, "yes") # we need approval text to be able to submit - e = WriteupEvent() + e = WriteupDocEvent() e.doc = draft e.by = Person.objects.get(name="Aread Irector") e.type = "changed_ballot_approval_text" @@ -713,13 +713,13 @@ class BallotWriteupsTestCase(django.test.TestCase): save_approval_text="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(WriteupEvent, type="changed_ballot_approval_text").text) + self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) # test regenerate r = self.client.post(url, dict(regenerate_approval_text="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("Subject: Protocol Action" in draft.latest_event(WriteupEvent, type="changed_ballot_approval_text").text) + self.assertTrue("Subject: Protocol Action" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) # test regenerate when it's a disapprove draft.iesg_state_id = "nopubadw" @@ -728,7 +728,7 @@ class BallotWriteupsTestCase(django.test.TestCase): r = self.client.post(url, dict(regenerate_approval_text="1")) self.assertEquals(r.status_code, 200) draft = Document.objects.get(name=draft.name) - self.assertTrue("NOT be published" in draft.latest_event(WriteupEvent, type="changed_ballot_approval_text").text) + self.assertTrue("NOT be published" in draft.latest_event(WriteupDocEvent, type="changed_ballot_approval_text").text) class ApproveBallotTestCase(django.test.TestCase): fixtures = ['names'] @@ -809,7 +809,7 @@ class MakeLastCallTestCase(django.test.TestCase): draft = Document.objects.get(name=draft.name) self.assertEquals(draft.iesg_state.slug, "lc") - self.assertEquals(draft.latest_event(LastCallEvent, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date) + self.assertEquals(draft.latest_event(LastCallDocEvent, "sent_last_call").expires.strftime("%Y-%m-%d"), expire_date) self.assertEquals(len(mail_outbox), mailbox_before + 4) self.assertTrue("Last Call" in mail_outbox[-4]['Subject']) @@ -864,7 +864,7 @@ class ExpireIDsTestCase(django.test.TestCase): draft.iesg_state = None draft.save() - NewRevisionEvent.objects.create( + NewRevisionDocEvent.objects.create( type="new_revision", by=Person.objects.get(name="Aread Irector"), doc=draft, @@ -895,7 +895,7 @@ class ExpireIDsTestCase(django.test.TestCase): draft.iesg_state = None draft.save() - NewRevisionEvent.objects.create( + NewRevisionDocEvent.objects.create( type="new_revision", by=Person.objects.get(name="Aread Irector"), doc=draft, @@ -984,7 +984,7 @@ class ExpireIDsTestCase(django.test.TestCase): draft.state = DocStateName.objects.get(slug="expired") draft.save() - e = Event() + e = DocEvent() e.doc = draft e.by = Person.objects.get(name="(System)") e.type = "expired_document" @@ -1033,7 +1033,7 @@ class ExpireLastCallTestCase(django.test.TestCase): self.assertEquals(len(list(get_expired_last_calls())), 0) - e = LastCallEvent() + e = LastCallDocEvent() e.doc = draft e.by = secretary e.type = "sent_last_call" @@ -1044,7 +1044,7 @@ class ExpireLastCallTestCase(django.test.TestCase): self.assertEquals(len(list(get_expired_last_calls())), 0) # test expired - e = LastCallEvent() + e = LastCallDocEvent() e.doc = draft e.by = secretary e.type = "sent_last_call" @@ -1057,13 +1057,13 @@ class ExpireLastCallTestCase(django.test.TestCase): # expire it mailbox_before = len(mail_outbox) - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() expire_last_call(drafts[0]) draft = Document.objects.get(name=draft.name) self.assertEquals(draft.iesg_state.slug, "writeupw") - self.assertEquals(draft.event_set.count(), events_before + 1) + self.assertEquals(draft.docevent_set.count(), events_before + 1) self.assertEquals(len(mail_outbox), mailbox_before + 1) self.assertTrue("Last Call Expired" in mail_outbox[-1]["Subject"]) diff --git a/ietf/idrfc/utils.py b/ietf/idrfc/utils.py index d33b6f422..aa9986208 100644 --- a/ietf/idrfc/utils.py +++ b/ietf/idrfc/utils.py @@ -3,7 +3,7 @@ from django.conf import settings from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin from ietf.idrfc.mails import * -from doc.models import Event, TelechatEvent +from doc.models import DocEvent, TelechatDocEvent def add_document_comment(request, doc, text, include_by=True, ballot=None): if request: @@ -62,7 +62,7 @@ def log_state_changed(request, doc, by, email_watch_list=True): return change def log_state_changedREDESIGN(request, doc, by, prev_iesg_state): - e = Event(doc=doc, by=by) + e = DocEvent(doc=doc, by=by) e.type = "changed_document" e.desc = u"State changed to %s from %s by %s" % ( doc.iesg_state.name, @@ -114,7 +114,7 @@ def update_telechat(request, idinternal, new_telechat_date, new_returning_item=N def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_item=None): on_agenda = bool(new_telechat_date) - prev = doc.latest_event(TelechatEvent, type="scheduled_for_telechat") + prev = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat") prev_returning = bool(prev and prev.returning_item) prev_telechat = prev.telechat_date if prev else None prev_agenda = bool(prev_telechat) @@ -135,7 +135,7 @@ def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_i and new_telechat_date != prev_telechat): returning = True - e = TelechatEvent() + e = TelechatDocEvent() e.type = "scheduled_for_telechat" e.by = by e.doc = doc diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 87903661f..3fa76ce52 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -23,7 +23,7 @@ from ietf.idrfc.mails import * from ietf.idrfc.utils import * from ietf.idrfc.lastcall import request_last_call -from doc.models import Document, Event, BallotPositionEvent, LastCallEvent, save_document_in_history +from doc.models import Document, DocEvent, BallotPositionDocEvent, LastCallDocEvent, save_document_in_history from name.models import BallotPositionName, IesgDocStateName BALLOT_CHOICES = (("yes", "Yes"), @@ -221,7 +221,7 @@ def edit_positionREDESIGN(request, name): from person.models import Person ad = get_object_or_404(Person, pk=ad_id) - old_pos = doc.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) + old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) if request.method == 'POST': form = EditPositionForm(request.POST) @@ -233,7 +233,7 @@ def edit_positionREDESIGN(request, name): if clean['return_to_url']: return_to_url = clean['return_to_url'] - pos = BallotPositionEvent(doc=doc, by=login) + pos = BallotPositionDocEvent(doc=doc, by=login) pos.type = "changed_ballot_position" pos.ad = ad pos.pos = clean["position"] @@ -252,7 +252,7 @@ def edit_positionREDESIGN(request, name): changes.append("comment") if pos.comment: - e = Event(doc=doc) + e = DocEvent(doc=doc) e.by = ad # otherwise we can't see who's saying it e.type = "added_comment" e.desc = "[Ballot comment]\n" + pos.comment @@ -264,7 +264,7 @@ def edit_positionREDESIGN(request, name): changes.append("discuss") if pos.discuss: - e = Event(doc=doc, by=login) + e = DocEvent(doc=doc, by=login) e.by = ad # otherwise we can't see who's saying it e.type = "added_comment" e.desc = "[Ballot discuss]\n" + pos.discuss @@ -419,7 +419,7 @@ def send_ballot_commentREDESIGN(request, name): from person.models import Person ad = get_object_or_404(Person, pk=ad_id) - pos = doc.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) + pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) if not pos: raise Http404() @@ -706,7 +706,7 @@ def lastcalltextREDESIGN(request, name): login = request.user.get_profile() - existing = doc.latest_event(WriteupEvent, type="changed_last_call_text") + existing = doc.latest_event(WriteupDocEvent, type="changed_last_call_text") if not existing: existing = generate_last_call_announcement(request, doc) @@ -718,7 +718,7 @@ def lastcalltextREDESIGN(request, name): if form.is_valid(): t = form.cleaned_data['last_call_text'] if t != existing.text: - e = WriteupEvent(doc=doc, by=login) + e = WriteupDocEvent(doc=doc, by=login) e.by = login e.type = "changed_last_call_text" e.desc = "Last call announcement was changed by %s" % login.name @@ -871,9 +871,9 @@ def ballot_writeupnotesREDESIGN(request, name): login = request.user.get_profile() - approval = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") + approval = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") - existing = doc.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + existing = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") if not existing: existing = generate_ballot_writeup(request, doc) @@ -884,7 +884,7 @@ def ballot_writeupnotesREDESIGN(request, name): if form.is_valid(): t = form.cleaned_data["ballot_writeup"] if t != existing.text: - e = WriteupEvent(doc=doc, by=login) + e = WriteupDocEvent(doc=doc, by=login) e.by = login e.type = "changed_ballot_writeup_text" e.desc = "Ballot writeup was changed by %s" % login.name @@ -895,9 +895,9 @@ def ballot_writeupnotesREDESIGN(request, name): doc.save() if "issue_ballot" in request.POST and approval: - if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionEvent, ad=login, time__gte=started_process.time): + if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionDocEvent, ad=login, time__gte=started_process.time): # sending the ballot counts as a yes - pos = BallotPositionEvent(doc=doc, by=login) + pos = BallotPositionDocEvent(doc=doc, by=login) pos.type = "changed_ballot_position" pos.ad = login pos.pos_id = "yes" @@ -909,7 +909,7 @@ def ballot_writeupnotesREDESIGN(request, name): email_iana(request, doc, 'drafts-eval@icann.org', msg) - e = Event(doc=doc, by=login) + e = DocEvent(doc=doc, by=login) e.by = login e.type = "sent_ballot_announcement" e.desc = "Ballot has been issued by %s" % login.name @@ -1006,7 +1006,7 @@ def ballot_approvaltextREDESIGN(request, name): login = request.user.get_profile() - existing = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") + existing = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") if not existing: existing = generate_approval_mail(request, doc) @@ -1018,7 +1018,7 @@ def ballot_approvaltextREDESIGN(request, name): if form.is_valid(): t = form.cleaned_data['approval_text'] if t != existing.text: - e = WriteupEvent(doc=doc, by=login) + e = WriteupDocEvent(doc=doc, by=login) e.by = login e.type = "changed_ballot_approval_text" e.desc = "Ballot approval text was changed by %s" % login.name @@ -1135,12 +1135,12 @@ def approve_ballotREDESIGN(request, name): login = request.user.get_profile() - e = doc.latest_event(WriteupEvent, type="changed_ballot_approval_text") + e = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") if not e: e = generate_approval_mail(request, doc) approval_text = e.text - e = doc.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") if not e: e = generate_ballot_writeup(request, doc) ballot_writeup = e.text @@ -1166,7 +1166,7 @@ def approve_ballotREDESIGN(request, name): prev = doc.iesg_state doc.iesg_state = new_state - e = Event(doc=doc, by=login) + e = DocEvent(doc=doc, by=login) if action == "do_not_publish": e.type = "iesg_disapproved" e.desc = "Do Not Publish note has been sent to RFC Editor" @@ -1285,7 +1285,7 @@ def make_last_callREDESIGN(request, name): login = request.user.get_profile() - e = doc.latest_event(WriteupEvent, type="changed_last_call_text") + e = doc.latest_event(WriteupDocEvent, type="changed_last_call_text") if not e: e = generate_last_call_announcement(request, doc) announcement = e.text @@ -1325,7 +1325,7 @@ def make_last_callREDESIGN(request, name): email_state_changed(request, doc, change_description) email_owner(request, doc, doc.ad, login, change_description) - e = LastCallEvent(doc=doc, by=login) + e = LastCallDocEvent(doc=doc, by=login) e.type = "sent_last_call" e.desc = "Last call sent by %s" % login.name if form.cleaned_data['last_call_sent_date'] != e.time.date(): diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index da4450685..d30c6919e 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -152,13 +152,13 @@ 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', 'id'): + for e in event_holder.docevent_set.all().select_related('by').order_by('-time', 'id'): info = {} if e.type == "new_revision": - filename = u"%s-%s" % (e.doc.name, e.newrevisionevent.rev) + filename = u"%s-%s" % (e.doc.name, e.newrevisiondocevent.rev) e.desc = 'New version available: %s' % (filename, filename) - if int(e.newrevisionevent.rev) != 0: - e.desc += ' (diff from -%02d)' % (filename, int(e.newrevisionevent.rev) - 1) + if int(e.newrevisiondocevent.rev) != 0: + e.desc += ' (diff from -%02d)' % (filename, int(e.newrevisiondocevent.rev) - 1) info["dontmolest"] = True multiset_ballot_text = "This was part of a ballot set with: " @@ -181,7 +181,7 @@ def _get_history(doc, versions): for o in results: e = o["comment"] if e.type == "new_revision": - e.version = e.newrevisionevent.rev + e.version = e.newrevisiondocevent.rev else: e.version = prev_rev prev_rev = e.version diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index 6fe8cfbd7..118df1b22 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -23,7 +23,7 @@ from ietf.idrfc.mails import * from ietf.idrfc.utils import * from ietf.idrfc.lastcall import request_last_call -from doc.models import Document, Event, StatusDateEvent, TelechatEvent, save_document_in_history, DocHistory +from doc.models import Document, DocEvent, StatusDateDocEvent, TelechatDocEvent, save_document_in_history, DocHistory from name.models import IesgDocStateName, IntendedStdLevelName, DocInfoTagName, get_next_iesg_states, DocStateName from person.models import Person, Email @@ -461,7 +461,7 @@ def edit_infoREDESIGN(request, name): doc.iesg_state = IesgDocStateName.objects.get(slug="pub-req") doc.notify = get_initial_notify(doc) - e = doc.latest_event(TelechatEvent, type="scheduled_for_telechat") + e = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat") initial_telechat_date = e.telechat_date if e else None initial_returning_item = bool(e and e.returning_item) @@ -482,14 +482,14 @@ def edit_infoREDESIGN(request, name): if replaces: # this should perhaps be somewhere else, e.g. the # place where the replace relationship is established - e = Event() + e = DocEvent() e.type = "added_comment" e.by = Person.objects.get(name="(System)") e.doc = doc e.desc = "Earlier history may be found in the Comment Log for %s" % (replaces[0], replaces[0].get_absolute_url()) e.save() - e = Event() + e = DocEvent() e.type = "started_iesg_process" e.by = login e.doc = doc @@ -531,7 +531,7 @@ def edit_infoREDESIGN(request, name): doc.note = r['note'] for c in changes: - e = Event(doc=doc, by=login) + e = DocEvent(doc=doc, by=login) e.type = "changed_document" e.desc = c + " by %s" % login.name e.save() @@ -539,10 +539,10 @@ def edit_infoREDESIGN(request, name): update_telechat(request, doc, login, r['telechat_date'], r['returning_item']) - e = doc.latest_event(StatusDateEvent, type="changed_status_date") + e = doc.latest_event(StatusDateDocEvent, type="changed_status_date") status_date = e.date if e else None if r["status_date"] != status_date: - e = StatusDateEvent(doc=doc, by=login) + e = StatusDateDocEvent(doc=doc, by=login) e.type ="changed_status_date" d = desc("Status date", r["status_date"], status_date) changes.append(d) @@ -565,7 +565,7 @@ def edit_infoREDESIGN(request, name): doc.save() return HttpResponseRedirect(doc.get_absolute_url()) else: - e = doc.latest_event(StatusDateEvent) + e = doc.latest_event(StatusDateDocEvent) status = e.date if e else None init = dict(intended_std_level=doc.intended_std_level, status_date=status, @@ -630,7 +630,7 @@ def request_resurrectREDESIGN(request, name): if request.method == 'POST': email_resurrect_requested(request, doc, login) - e = Event(doc=doc, by=login) + e = DocEvent(doc=doc, by=login) e.type = "requested_resurrect" e.desc = "Resurrection was requested by %s" % login.name e.save() @@ -689,7 +689,7 @@ def resurrectREDESIGN(request, name): if e and e.type == 'requested_resurrect': email_resurrection_completed(request, doc, requester=e.by) - e = Event(doc=doc, by=login) + e = DocEvent(doc=doc, by=login) e.type = "completed_resurrect" e.desc = "Resurrection was completed by %s" % login.name e.save() @@ -751,7 +751,7 @@ def add_commentREDESIGN(request, name): if form.is_valid(): c = form.cleaned_data['comment'] - e = Event(doc=doc, by=login) + e = DocEvent(doc=doc, by=login) e.type = "added_comment" e.desc = c e.save() diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index 6fc03cccb..770fb9365 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -394,7 +394,7 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: 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'): + for e in DocEvent.objects.filter(doc__in=rfc_aliases.keys(), type__in=event_types).order_by('-time'): r = result_map[e.doc_id] if not getattr(r, e.type): # sets e.g. r.published_date = e for use in proxy wrapper diff --git a/ietf/iesg/feeds.py b/ietf/iesg/feeds.py index 66d49719b..bf0e89353 100644 --- a/ietf/iesg/feeds.py +++ b/ietf/iesg/feeds.py @@ -13,10 +13,10 @@ class IESGAgenda(Feed): def items(self): if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import TelechatEvent - drafts = IDInternal.objects.filter(event__telechatevent__telechat_date__gte=datetime.date.min).distinct() + from doc.models import TelechatDocEvent + drafts = IDInternal.objects.filter(docevent__telechatdocevent__telechat_date__gte=datetime.date.min).distinct() for d in drafts: - d.latest_telechat_event = d.latest_event(TelechatEvent, type="scheduled_for_telechat") + d.latest_telechat_event = d.latest_event(TelechatDocEvent, type="scheduled_for_telechat") drafts = [d for d in drafts if d.latest_telechat_event.telechat_date] drafts.sort(key=lambda d: d.latest_telechat_event.telechat_date) return drafts diff --git a/ietf/iesg/tests.py b/ietf/iesg/tests.py index 8486eab48..7a186dda9 100644 --- a/ietf/iesg/tests.py +++ b/ietf/iesg/tests.py @@ -67,12 +67,12 @@ class RescheduleOnAgendaTestCaseREDESIGN(django.test.TestCase): def test_reschedule(self): from ietf.utils.test_data import make_test_data from redesign.person.models import Person - from doc.models import TelechatEvent + from doc.models import TelechatDocEvent draft = make_test_data() # add to schedule - e = TelechatEvent(type="scheduled_for_telechat") + e = TelechatDocEvent(type="scheduled_for_telechat") e.doc = draft e.by = Person.objects.get(name="Aread Irector") e.telechat_date = TelechatDates.objects.all()[0].date1 @@ -95,7 +95,7 @@ class RescheduleOnAgendaTestCaseREDESIGN(django.test.TestCase): #self.assertEquals(len(q('form input[name=%s-clear_returning_item]' % form_id)), 1) # reschedule - events_before = draft.event_set.count() + events_before = draft.docevent_set.count() d = TelechatDates.objects.all()[0].dates()[2] r = self.client.post(url, { '%s-telechat_date' % form_id: d.strftime("%Y-%m-%d"), @@ -109,10 +109,10 @@ class RescheduleOnAgendaTestCaseREDESIGN(django.test.TestCase): draft_pos = r.content.find(draft.name) self.assertTrue(d_header_pos < draft_pos) - self.assertTrue(draft.latest_event(TelechatEvent, "scheduled_for_telechat")) - self.assertEquals(draft.latest_event(TelechatEvent, "scheduled_for_telechat").telechat_date, d) - self.assertTrue(not draft.latest_event(TelechatEvent, "scheduled_for_telechat").returning_item) - self.assertEquals(draft.event_set.count(), events_before + 1) + self.assertTrue(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat")) + self.assertEquals(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, d) + self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").returning_item) + self.assertEquals(draft.docevent_set.count(), events_before + 1) if settings.USE_DB_REDESIGN_PROXY_CLASSES: diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index 51602e77e..03304590f 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -60,10 +60,10 @@ def date_threshold(): def inddocs(request): if settings.USE_DB_REDESIGN_PROXY_CLASSES: - queryset_list_ind = [d for d in InternetDraft.objects.filter(tags__slug="via-rfc", event__type="iesg_approved").distinct() if d.latest_event(type__in=("iesg_disapproved", "iesg_approved")).type == "iesg_approved"] + queryset_list_ind = [d for d in InternetDraft.objects.filter(tags__slug="via-rfc", docevent__type="iesg_approved").distinct() if d.latest_event(type__in=("iesg_disapproved", "iesg_approved")).type == "iesg_approved"] queryset_list_ind.sort(key=lambda d: d.b_approve_date, reverse=True) - queryset_list_ind_dnp = [d for d in IDInternal.objects.filter(tags__slug="via-rfc", event__type="iesg_disapproved").distinct() if d.latest_event(type__in=("iesg_disapproved", "iesg_approved")).type == "iesg_disapproved"] + queryset_list_ind_dnp = [d for d in IDInternal.objects.filter(tags__slug="via-rfc", docevent__type="iesg_disapproved").distinct() if d.latest_event(type__in=("iesg_disapproved", "iesg_approved")).type == "iesg_disapproved"] queryset_list_ind_dnp.sort(key=lambda d: d.dnp_date, reverse=True) return render_to_response('iesg/independent_doc.html', @@ -118,7 +118,7 @@ def wgdocsREDESIGN(request,cat): if cat == 'new': is_recent = 1 - drafts = InternetDraft.objects.filter(event__type="iesg_approved", event__time__gte=threshold, intended_std_level__in=proto_levels + doc_levels).exclude(tags__slug="via-rfc").distinct() + drafts = InternetDraft.objects.filter(docevent__type="iesg_approved", docevent__time__gte=threshold, intended_std_level__in=proto_levels + doc_levels).exclude(tags__slug="via-rfc").distinct() for d in drafts: if d.b_approve_date and d.b_approve_date >= threshold: if d.intended_std_level_id in proto_levels: @@ -130,7 +130,7 @@ def wgdocsREDESIGN(request,cat): # proto start_date = datetime.date(1997, 12, 1) - drafts = InternetDraft.objects.filter(event__type="iesg_approved", event__time__lt=threshold, event__time__gte=start_date, intended_std_level__in=proto_levels).exclude(tags__slug="via-rfc").distinct() + drafts = InternetDraft.objects.filter(docevent__type="iesg_approved", docevent__time__lt=threshold, docevent__time__gte=start_date, intended_std_level__in=proto_levels).exclude(tags__slug="via-rfc").distinct() for d in drafts: if d.b_approve_date and start_date <= d.b_approve_date < threshold: @@ -139,7 +139,7 @@ def wgdocsREDESIGN(request,cat): # doc start_date = datetime.date(1998, 10, 15) - drafts = InternetDraft.objects.filter(event__type="iesg_approved", event__time__lt=threshold, event__time__gte=start_date, intended_std_level__in=doc_levels).exclude(tags__slug="via-rfc").distinct() + drafts = InternetDraft.objects.filter(docevent__type="iesg_approved", docevent__time__lt=threshold, docevent__time__gte=start_date, intended_std_level__in=doc_levels).exclude(tags__slug="via-rfc").distinct() for d in drafts: if d.b_approve_date and start_date <= d.b_approve_date < threshold: @@ -212,15 +212,15 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: def agenda_docs(date, next_agenda): if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import TelechatEvent + from doc.models import TelechatDocEvent - matches = IDInternal.objects.filter(event__telechatevent__telechat_date=date) + matches = IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date) idmatches = [] rfcmatches = [] for m in matches: - if m.latest_event(TelechatEvent, type="scheduled_for_telechat").telechat_date != date: + if m.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date != date: continue if next_agenda and not m.agenda: @@ -341,9 +341,9 @@ def agenda_documents_txt(request): docs = [] for date in dates: if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import TelechatEvent - for d in IDInternal.objects.filter(event__telechatevent__telechat_date=date): - if d.latest_event(TelechatEvent, type="scheduled_for_telechat").telechat_date == date: + from doc.models import TelechatDocEvent + for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date): + if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date == date: docs.append(d) else: docs.extend(IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)) @@ -404,10 +404,10 @@ def handle_reschedule_form(request, idinternal, dates): def agenda_documents(request): dates = TelechatDates.objects.all()[0].dates() if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import TelechatEvent + from doc.models import TelechatDocEvent idinternals = [] - for d in IDInternal.objects.filter(event__telechatevent__telechat_date__in=dates): - if d.latest_event(TelechatEvent, type="scheduled_for_telechat").telechat_date in dates: + for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date__in=dates): + if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date in dates: idinternals.append(d) idinternals.sort(key=lambda d: (d.rfc_flag, d.start_date)) @@ -444,10 +444,10 @@ def telechat_docs_tarfile(request,year,month,day): from tempfile import mkstemp date=datetime.date(int(year),int(month),int(day)) if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from doc.models import TelechatEvent + from doc.models import TelechatDocEvent docs = [] - for d in IDInternal.objects.filter(event__telechatevent__telechat_date=date): - if d.latest_event(TelechatEvent, type="scheduled_for_telechat").telechat_date == date: + for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date): + if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date == date: docs.append(d) else: docs= IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1) @@ -476,7 +476,7 @@ def discusses(request): if settings.USE_DB_REDESIGN_PROXY_CLASSES: res = [] - for d in IDInternal.objects.filter(iesg_state__in=("pub-req", "ad-eval", "review-e", "lc-req", "lc", "writeupw", "goaheadw", "iesg-eva", "defer", "watching"), event__ballotpositionevent__pos="discuss").distinct(): + for d in IDInternal.objects.filter(iesg_state__in=("pub-req", "ad-eval", "review-e", "lc-req", "lc", "writeupw", "goaheadw", "iesg-eva", "defer", "watching"), docevent__ballotpositiondocevent__pos="discuss").distinct(): found = False for p in d.positions.all(): if p.discuss: diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 2bdcf3fdb..e44fc1053 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -134,7 +134,7 @@ def make_test_data(): ) # draft has only one event - Event.objects.create( + DocEvent.objects.create( type="started_iesg_process", by=ad, doc=draft, diff --git a/redesign/doc/admin.py b/redesign/doc/admin.py index d3ac4bff3..d662bbb2a 100644 --- a/redesign/doc/admin.py +++ b/redesign/doc/admin.py @@ -24,7 +24,7 @@ admin.site.register(DocAlias, DocAliasAdmin) # events -class EventAdmin(admin.ModelAdmin): +class DocEventAdmin(admin.ModelAdmin): list_display = ["doc", "type", "by_raw", "time"] raw_id_fields = ["doc", "by"] @@ -32,16 +32,16 @@ class EventAdmin(admin.ModelAdmin): return instance.by_id by_raw.short_description = "By" -admin.site.register(Event, EventAdmin) +admin.site.register(DocEvent, DocEventAdmin) -admin.site.register(NewRevisionEvent, EventAdmin) -admin.site.register(WriteupEvent, EventAdmin) -admin.site.register(StatusDateEvent, EventAdmin) -admin.site.register(LastCallEvent, EventAdmin) -admin.site.register(TelechatEvent, EventAdmin) +admin.site.register(NewRevisionDocEvent, DocEventAdmin) +admin.site.register(WriteupDocEvent, DocEventAdmin) +admin.site.register(StatusDateDocEvent, DocEventAdmin) +admin.site.register(LastCallDocEvent, DocEventAdmin) +admin.site.register(TelechatDocEvent, DocEventAdmin) -class BallotPositionEventAdmin(EventAdmin): +class BallotPositionDocEventAdmin(DocEventAdmin): raw_id_fields = ["doc", "by", "ad"] -admin.site.register(BallotPositionEvent, BallotPositionEventAdmin) +admin.site.register(BallotPositionDocEvent, BallotPositionDocEventAdmin) diff --git a/redesign/doc/models.py b/redesign/doc/models.py index 2da0cc26a..8160352b2 100644 --- a/redesign/doc/models.py +++ b/redesign/doc/models.py @@ -88,10 +88,10 @@ class Document(DocumentInfo): def latest_event(self, *args, **filter_args): """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(WriteupEvent, type="xyz") returns a - WriteupEvent event.""" - model = args[0] if args else Event + arguments, e.g. d.latest_event(type="xyz") returns an DocEvent + while d.latest_event(WriteupDocEvent, type="xyz") returns a + WriteupDocEvent event.""" + model = args[0] if args else DocEvent e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time', '-id')[:1] return e[0] if e else None @@ -220,7 +220,7 @@ EVENT_TYPES = [ ("approved_in_minute", "Approved in minute"), ] -class Event(models.Model): +class DocEvent(models.Model): """An occurrence for a document, used for tracking who, when and what.""" time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened") type = models.CharField(max_length=50, choices=EVENT_TYPES) @@ -234,11 +234,11 @@ class Event(models.Model): class Meta: ordering = ['-time', '-id'] -class NewRevisionEvent(Event): +class NewRevisionDocEvent(DocEvent): rev = models.CharField(max_length=16) # IESG events -class BallotPositionEvent(Event): +class BallotPositionDocEvent(DocEvent): ad = models.ForeignKey(Person) pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord") discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True) @@ -246,16 +246,16 @@ class BallotPositionEvent(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 WriteupEvent(Event): +class WriteupDocEvent(DocEvent): text = models.TextField(blank=True) -class StatusDateEvent(Event): +class StatusDateDocEvent(DocEvent): date = models.DateField(blank=True, null=True) -class LastCallEvent(Event): +class LastCallDocEvent(DocEvent): expires = models.DateTimeField(blank=True, null=True) -class TelechatEvent(Event): +class TelechatDocEvent(DocEvent): 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 9ffd22374..fbe2c063b 100644 --- a/redesign/doc/proxy.py +++ b/redesign/doc/proxy.py @@ -74,7 +74,7 @@ class InternetDraft(Document): #start_date = models.DateField() @property def start_date(self): - e = NewRevisionEvent.objects.filter(doc=self).order_by("time")[:1] + e = NewRevisionDocEvent.objects.filter(doc=self).order_by("time")[:1] return e[0].time.date() if e else None #expiration_date = models.DateField() @property @@ -109,7 +109,7 @@ class InternetDraft(Document): #lc_expiration_date = models.DateField(null=True, blank=True) @property def lc_expiration_date(self): - e = self.latest_event(LastCallEvent, type="sent_last_call") + e = self.latest_event(LastCallDocEvent, type="sent_last_call") return e.expires.date() if e else None #b_sent_date = models.DateField(null=True, blank=True) @@ -319,14 +319,14 @@ class InternetDraft(Document): #status_date = models.DateField(blank=True,null=True) @property def status_date(self): - e = self.latest_event(StatusDateEvent, type="changed_status_date") + e = self.latest_event(StatusDateDocEvent, 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) @property def agenda(self): - e = self.latest_event(TelechatEvent, type="scheduled_for_telechat") + e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat") return bool(e and e.telechat_date) #cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs') @@ -406,13 +406,13 @@ class InternetDraft(Document): #returning_item = models.IntegerField(null=True, blank=True) @property def returning_item(self): - e = self.latest_event(TelechatEvent, type="scheduled_for_telechat") + e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat") return e.returning_item if e else None #telechat_date = models.DateField(null=True, blank=True) @property def telechat_date(self): - e = self.latest_event(TelechatEvent, type="scheduled_for_telechat") + e = self.latest_event(TelechatDocEvent, type="scheduled_for_telechat") return e.telechat_date if e else None #via_rfc_editor = models.IntegerField(null=True, blank=True) @@ -533,19 +533,19 @@ class InternetDraft(Document): #approval_text = models.TextField(blank=True) @property def approval_text(self): - e = self.latest_event(WriteupEvent, type="changed_ballot_approval_text") + e = self.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") return e.text if e else "" #last_call_text = models.TextField(blank=True) @property def last_call_text(self): - e = self.latest_event(WriteupEvent, type="changed_last_call_text") + e = self.latest_event(WriteupDocEvent, type="changed_last_call_text") return e.text if e else "" #ballot_writeup = models.TextField(blank=True) @property def ballot_writeup(self): - e = self.latest_event(WriteupEvent, type="changed_ballot_writeup_text") + e = self.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") return e.text if e else "" #ballot_issued = models.IntegerField(null=True, blank=True) @@ -565,7 +565,7 @@ class InternetDraft(Document): res.append(dict(ad=IESGLoginProxy().from_object(ad), pos=Position().from_object(pos) if pos else None)) found = set() - for pos in BallotPositionEvent.objects.filter(doc=self, type="changed_ballot_position", ad__in=active_ads).select_related('ad').order_by("-time", "-id"): + for pos in BallotPositionDocEvent.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) @@ -752,7 +752,7 @@ class IDAuthor(DocumentAuthor): class Meta: proxy = True -class DocumentComment(Event): +class DocumentComment(DocEvent): objects = TranslatingManager(dict(comment_text="desc", date="time" )) @@ -777,7 +777,7 @@ class DocumentComment(Event): #version = models.CharField(blank=True, max_length=3) @property def version(self): - e = self.doc.latest_event(NewRevisionEvent, type="new_revision", time__lte=self.time) + e = self.doc.latest_event(NewRevisionDocEvent, type="new_revision", time__lte=self.time) return e.rev if e else "0" #comment_text = models.TextField(blank=True) @property @@ -804,7 +804,7 @@ class DocumentComment(Event): proxy = True -class Position(BallotPositionEvent): +class Position(BallotPositionDocEvent): def from_object(self, base): for f in base._meta.fields: if not f.name in ('discuss',): # don't overwrite properties diff --git a/redesign/importing/import-document-state.py b/redesign/importing/import-document-state.py index b35c3d50e..e9c25c8e6 100755 --- a/redesign/importing/import-document-state.py +++ b/redesign/importing/import-document-state.py @@ -143,7 +143,7 @@ tag_approved_in_minute = name(DocInfoTagName, 'app-min', "Approved in minute") tag_has_errata = name(DocInfoTagName, 'errata', "Has errata") # helpers -def save_event(doc, event, comment): +def save_docevent(doc, event, comment): event.time = comment.datetime() event.by = iesg_login_to_person(comment.created_by) event.doc = doc @@ -252,27 +252,27 @@ def import_from_idinternal(d, idinternal): # telechat agenda schedulings match = re_telechat_agenda.search(c.comment_text) or re_telechat_changed.search(c.comment_text) if match: - e = TelechatEvent() + e = TelechatDocEvent() 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) + save_docevent(d, e, c) handled = True # ballot issued match = re_ballot_issued.search(c.comment_text) if match: - e = Event() + e = DocEvent() e.type = "sent_ballot_announcement" - save_event(d, e, c) + save_docevent(d, e, c) handled = True ad = iesg_login_to_person(c.created_by) - last_pos = d.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=ad) + last_pos = d.latest_event(BallotPositionDocEvent, 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 = BallotPositionEvent() + e = BallotPositionDocEvent() e.type = "changed_ballot_position" e.ad = ad e.desc = "[Ballot Position Update] New position, Yes, has been recorded by %s" % e.ad.name @@ -282,7 +282,7 @@ def import_from_idinternal(d, idinternal): 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) + save_docevent(d, e, c) # ballot positions match = re_ballot_position.search(c.comment_text) @@ -310,7 +310,7 @@ def import_from_idinternal(d, idinternal): found = False for p in positions: - if not d.event_set.filter(type="changed_ballot_position", ballotposition__pos=position, ballotposition__ad=iesg_login_to_person(p.ad)): + if not d.docevent_set.filter(type="changed_ballot_position", ballotposition__pos=position, ballotposition__ad=iesg_login_to_person(p.ad)): login = p.ad found = True break @@ -334,10 +334,10 @@ def import_from_idinternal(d, idinternal): print "BALLOT BY SECRETARIAT", login - e = BallotPositionEvent() + e = BallotPositionDocEvent() e.type = "changed_ballot_position" e.ad = iesg_login_to_person(login) - last_pos = d.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=e.ad) + last_pos = d.latest_event(BallotPositionDocEvent, 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 @@ -347,15 +347,15 @@ def import_from_idinternal(d, idinternal): 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) + save_docevent(d, e, c) handled = True # ballot discusses/comments if c.ballot in (DocumentComment.BALLOT_DISCUSS, DocumentComment.BALLOT_COMMENT): - e = BallotPositionEvent() + e = BallotPositionDocEvent() e.type = "changed_ballot_position" e.ad = iesg_login_to_person(c.created_by) - last_pos = d.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=e.ad) + last_pos = d.latest_event(BallotPositionDocEvent, 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: @@ -382,22 +382,22 @@ def import_from_idinternal(d, idinternal): # 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) + save_docevent(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) + e = DocEvent(type="requested_last_call") + save_docevent(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) + e = DocEvent(type="changed_document") + save_docevent(d, e, c) handled = True # note changed @@ -406,8 +406,8 @@ def import_from_idinternal(d, idinternal): # 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) + e = DocEvent(type="changed_document") + save_docevent(d, e, c) handled = True # draft added @@ -417,48 +417,48 @@ def import_from_idinternal(d, idinternal): # 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) + e = DocEvent(type="started_iesg_process") + save_docevent(d, e, c) handled = True # new version if c.comment_text == "New version available": - e = NewRevisionEvent(type="new_revision", rev=c.version) - save_event(d, e, c) + e = NewRevisionDocEvent(type="new_revision", rev=c.version) + save_docevent(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) + e = DocEvent(type="requested_resurrect") + save_docevent(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) + e = DocEvent(type="completed_resurrect") + save_docevent(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) + e = DocEvent(type="expired_document") + save_docevent(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) + e = DocEvent(type="iesg_approved") + save_docevent(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) + e = DocEvent(type="iesg_disapproved") + save_docevent(d, e, c) handled = True @@ -472,41 +472,41 @@ def import_from_idinternal(d, idinternal): # status date changed match = re_status_date_changed.search(line) if match: - e = StatusDateEvent(type="changed_status_date", date=date_in_match(match)) + e = StatusDateDocEvent(type="changed_status_date", date=date_in_match(match)) e.desc = line - save_event(d, e, c) + save_docevent(d, e, c) handled = True # AD/job owner changed match = re_responsible_ad_changed.search(line) if match: - e = Event(type="changed_document") + e = DocEvent(type="changed_document") e.desc = line - save_event(d, e, c) + save_docevent(d, e, c) handled = True # intended standard level changed match = re_intended_status_changed.search(line) if match: - e = Event(type="changed_document") + e = DocEvent(type="changed_document") e.desc = line - save_event(d, e, c) + save_docevent(d, e, c) handled = True # state change notice match = re_state_change_notice.search(line) if match: - e = Event(type="changed_document") + e = DocEvent(type="changed_document") e.desc = line - save_event(d, e, c) + save_docevent(d, e, c) handled = True # area acronym match = re_area_acronym_changed.search(line) if match: - e = Event(type="changed_document") + e = DocEvent(type="changed_document") e.desc = line - save_event(d, e, c) + save_docevent(d, e, c) handled = True # multiline change bundles end with a single "by xyz" that we skip @@ -522,8 +522,8 @@ def import_from_idinternal(d, idinternal): # all others are added as comments if not handled: - e = Event(type="added_comment") - save_event(d, e, c) + e = DocEvent(type="added_comment") + save_docevent(d, e, c) # stop typical comments from being output typical_comments = [ @@ -561,23 +561,23 @@ def import_from_idinternal(d, idinternal): made_up_date = d.time made_up_date += datetime.timedelta(seconds=1) - e = d.latest_event(StatusDateEvent, type="changed_status_date") + e = d.latest_event(StatusDateDocEvent, type="changed_status_date") status_date = e.date if e else None if idinternal.status_date != status_date: - e = StatusDateEvent(type="changed_status_date", date=idinternal.status_date) + e = StatusDateDocEvent(type="changed_status_date", date=idinternal.status_date) e.time = made_up_date e.by = system 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(TelechatEvent, type="scheduled_for_telechat") + e = d.latest_event(TelechatDocEvent, 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 = TelechatEvent(type="scheduled_for_telechat", + e = TelechatDocEvent(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 @@ -598,14 +598,14 @@ 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] + e = d.docevent_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 = BallotPositionEvent.objects.filter(doc=d, type="changed_ballot_position").order_by("-time", '-id') + existing = BallotPositionDocEvent.objects.filter(doc=d, type="changed_ballot_position").order_by("-time", '-id') for p in Position.objects.filter(ballot=ballot): # there are some bogus ones @@ -633,13 +633,13 @@ def import_from_idinternal(d, idinternal): break if not found: - e = BallotPositionEvent() + e = BallotPositionDocEvent() e.type = "changed_ballot_position" e.doc = d e.time = position_date e.by = system e.ad = ad - last_pos = d.latest_event(BallotPositionEvent, type="changed_ballot_position", ad=e.ad) + last_pos = d.latest_event(BallotPositionDocEvent, 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 @@ -656,14 +656,14 @@ def import_from_idinternal(d, idinternal): 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', 'id')[:1] + if ballot.ballot_issued and not d.docevent_set.filter(type="sent_ballot_announcement"): + position = d.docevent_set.filter(type=("changed_ballot_position")).order_by('time', 'id')[:1] if position: sent_date = position[0].time else: sent_date = made_up_date - e = Event() + e = DocEvent() e.type = "sent_ballot_announcement" e.doc = d e.time = sent_date @@ -672,7 +672,7 @@ def import_from_idinternal(d, idinternal): e.save() # make sure the comments and discusses are updated - positions = list(BallotPositionEvent.objects.filter(doc=d).order_by("-time", '-id')) + positions = list(BallotPositionDocEvent.objects.filter(doc=d).order_by("-time", '-id')) for c in IESGComment.objects.filter(ballot=ballot): ad = iesg_login_to_person(c.ad) for p in positions: @@ -695,14 +695,14 @@ def import_from_idinternal(d, idinternal): # 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] + e = d.docevent_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, _ = WriteupEvent.objects.get_or_create(type="changed_ballot_approval_text", doc=d, + e, _ = WriteupDocEvent.objects.get_or_create(type="changed_ballot_approval_text", doc=d, defaults=dict(by=system)) e.text = idinternal.ballot.approval_text e.time = text_date @@ -710,7 +710,7 @@ def import_from_idinternal(d, idinternal): e.save() if idinternal.ballot.last_call_text: - e, _ = WriteupEvent.objects.get_or_create(type="changed_last_call_text", doc=d, + e, _ = WriteupDocEvent.objects.get_or_create(type="changed_last_call_text", doc=d, defaults=dict(by=system)) e.text = idinternal.ballot.last_call_text e.time = text_date @@ -718,7 +718,7 @@ def import_from_idinternal(d, idinternal): e.save() if idinternal.ballot.ballot_writeup: - e, _ = WriteupEvent.objects.get_or_create(type="changed_ballot_writeup_text", doc=d, + e, _ = WriteupDocEvent.objects.get_or_create(type="changed_ballot_writeup_text", doc=d, defaults=dict(by=system)) e.text = idinternal.ballot.ballot_writeup e.time = text_date @@ -729,7 +729,7 @@ def import_from_idinternal(d, idinternal): 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) - Event.objects.get_or_create(type="added_comment", doc=d, desc=desc, + DocEvent.objects.get_or_create(type="added_comment", doc=d, desc=desc, defaults=dict(time=made_up_date, by=system)) @@ -809,20 +809,20 @@ for index, o in enumerate(all_drafts.iterator()): print "SKIPPED author", unicode(a.person).encode('utf-8') # clear any already imported events - d.event_set.all().delete() + d.docevent_set.all().delete() if o.idinternal: # import attributes and events import_from_idinternal(d, o.idinternal) # import missing revision changes from DraftVersions - known_revisions = set(e.rev for e in NewRevisionEvent.objects.filter(doc=d, type="new_revision")) + known_revisions = set(e.rev for e in NewRevisionDocEvent.objects.filter(doc=d, type="new_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 = NewRevisionEvent(type="new_revision") + e = NewRevisionDocEvent(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 @@ -843,7 +843,7 @@ for index, o in enumerate(all_drafts.iterator()): 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 = DocEvent(type="iesg_disapproved" if disapproved else "iesg_approved") e.time = o.b_approve_date e.by = system e.doc = d @@ -851,9 +851,9 @@ for index, o in enumerate(all_drafts.iterator()): e.save() if o.lc_expiration_date: - e = LastCallEvent(type="sent_last_call", expires=o.lc_expiration_date) + e = LastCallDocEvent(type="sent_last_call", expires=o.lc_expiration_date) # 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] + events = d.docevent_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 @@ -953,11 +953,11 @@ for index, o in enumerate(all_rfcs.iterator()): if d.name.startswith("rfc"): # clear any already imported events, we don't do it for # drafts as they've already been cleared above - d.event_set.all().delete() + d.docevent_set.all().delete() import_from_idinternal(d, internals[0]) # publication date - e, _ = Event.objects.get_or_create(doc=d, type="published_rfc", + e, _ = DocEvent.objects.get_or_create(doc=d, type="published_rfc", defaults=dict(by=system)) e.time = o.rfc_published_date e.desc = "RFC published"