# Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). # All rights reserved. Contact: Pasi Eronen # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # * Neither the name of the Nokia Corporation and/or its # subsidiary(-ies) nor the names of its contributors may be used # to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from ietf.idtracker.models import InternetDraft, IDInternal, BallotInfo, IESGDiscuss, IESGLogin, DocumentComment, Acronym, IDState from ietf.idrfc.models import RfcEditorQueue from ietf.ipr.models import IprRfc, IprDraft, IprDetail import re from datetime import date from django.utils import simplejson as json from django.db.models import Q from django.db import models from django.core.urlresolvers import reverse from django.conf import settings import types BALLOT_ACTIVE_STATES = ['In Last Call', 'Waiting for Writeup', 'Waiting for AD Go-Ahead', 'IESG Evaluation', 'IESG Evaluation - Defer'] def jsonify_helper(obj, keys): result = {} for k in keys: if hasattr(obj, k): v = getattr(obj, k) if callable(v): v = v() if v == None: pass elif isinstance(v, (types.StringType, types.IntType, types.BooleanType, types.LongType, types.ListType, types.UnicodeType)): result[k] = v elif isinstance(v, date): result[k] = str(v) else: result[k] = 'Unknown type '+str(type(v)) return result # Wrappers to make writing templates less painful # --------------------------------------------------------------------------- class IdWrapper: _draft = None _idinternal = None is_id_wrapper = True is_rfc_wrapper = False draft_name = None # Active/Expired/RFC/Withdrawn by Submitter/Replaced/Withdrawn by IETF draft_status = None # Revision is sometimes incorrect (+1 too large) if status != Active latest_revision = None # Set if and only if draft_status is "RFC" rfc_number = None title = None tracker_id = None publication_date = None ietf_process = None def __init__(self, draft): self.id = self if isinstance(draft, IDInternal) and not settings.USE_DB_REDESIGN_PROXY_CLASSES: self._idinternal = draft self._draft = self._idinternal.draft else: self._draft = draft if draft.idinternal: self._idinternal = draft.idinternal if self._idinternal: self.ietf_process = IetfProcessData(self._idinternal) self.draft_name = self._draft.filename self.draft_status = str(self._draft.status) if self.draft_status == "RFC": if self._draft.rfc_number: self.rfc_number = self._draft.rfc_number else: # Handle incorrect database entries self.draft_status = "Expired" self.latest_revision = self._draft.revision_display() self.title = self._draft.title self.tracker_id = self._draft.id_document_tag self.resurrect_requested_by = self._idinternal.resurrect_requested_by if self._idinternal else None self.publication_date = self._draft.revision_date if not self.publication_date: # should never happen -- but unfortunately it does. Return an # obviously bogus date self.publication_date = date(1990,1,1) def rfc_editor_state(self): if settings.USE_DB_REDESIGN_PROXY_CLASSES: s = self._draft.get_state("draft-rfceditor") if s: # extract possible extra states tags = self._draft.tags.filter(slug__in=("iana-crd", "ref", "missref")) return " ".join([s.name] + [t.slug.replace("-crd", "").upper() for t in tags]) else: return None try: qs = self._draft.rfc_editor_queue_state return qs.state except RfcEditorQueue.DoesNotExist: pass return None def replaced_by(self): try: if self._draft.replaced_by: return [self._draft.replaced_by.filename] except InternetDraft.DoesNotExist: pass return None def replaces(self): r = [str(r.filename) for r in self._draft.replaces_set.all()] if len(r) > 0: return r else: return None def in_ietf_process(self): return self.ietf_process != None def submission(self): if self._idinternal and self._idinternal.via_rfc_editor: return "Via IRTF or RFC Editor" if self._draft.group_id == Acronym.INDIVIDUAL_SUBMITTER: return "Individual" a = self.group_acronym() if a: return "%s WG" % (a, a) return "" submission.allow_tags = True def search_archive(self): if self._idinternal and self._idinternal.via_rfc_editor: return "www.ietf.org/mail-archive/web/" if self._draft.group_id == Acronym.INDIVIDUAL_SUBMITTER: return "www.ietf.org/mail-archive/web/" a = self._draft.group_ml_archive() if a: return a return "" def file_types(self): return self._draft.file_type.split(",") def group_acronym(self): if self._draft.group_id != 0 and self._draft.group != None and str(self._draft.group) != "none": return str(self._draft.group) else: return None # TODO: Returning integers here isn't nice # 0=Unknown, 1=IETF, 2=IAB, 3=IRTF, 4=Independent def stream_id(self): if self.draft_name.startswith("draft-iab-"): return 2 elif self.draft_name.startswith("draft-irtf-"): return 3 elif self._idinternal: if self._idinternal.via_rfc_editor > 0: return 4 else: return 1 elif self.group_acronym(): return 1 else: return 0 def draft_name_and_revision(self): return self.draft_name+"-"+self.latest_revision def friendly_state(self): if self.draft_status == "RFC": return "RFC %d" % (reverse('doc_view', args=['rfc%d' % self.rfc_number]), self.rfc_number) elif self.draft_status == "Replaced": rs = self.replaced_by() if rs: return "Replaced by %s" % (reverse('doc_view', args=[rs[0]]),rs[0], ) else: return "Replaced" elif self.draft_status == "Active": if self.in_ietf_process(): if self.ietf_process.main_state == "Dead": # Many drafts in "Dead" state are not dead; they're # just not currently under IESG processing. Show # them as "I-D Exists (IESG: Dead)" instead... return "I-D Exists (IESG: "+self.ietf_process.state+")" elif self.ietf_process.main_state == "In Last Call": return self.ietf_process.state + " (ends "+str(self._idinternal.document().lc_expiration_date)+")" else: return self.ietf_process.state else: return "I-D Exists" else: if self.in_ietf_process() and self.ietf_process.main_state == "Dead": return self.draft_status+" (IESG: "+self.ietf_process.state+")" # Expired/Withdrawn by Submitter/IETF return self.draft_status def abstract(self): return self._draft.clean_abstract() # TODO: ugly hack def authors(self): return self._draft.authors def expected_expiration_date(self): if self.draft_status == "Active" and self._draft.can_expire(): return self._draft.expiration() else: return None def ad_name(self): if self.in_ietf_process(): return self.ietf_process.ad_name() else: return None def get_absolute_url(self): return "/doc/"+self.draft_name+"/" def displayname_with_link(self): return '%s' % (self.get_absolute_url(), self.draft_name_and_revision()) def to_json(self): result = jsonify_helper(self, ['draft_name', 'draft_status', 'latest_revision', 'rfc_number', 'title', 'tracker_id', 'publication_date','rfc_editor_state', 'replaced_by', 'replaces', 'in_ietf_process', 'file_types', 'group_acronym', 'stream_id','friendly_state', 'abstract', 'ad_name']) if self.in_ietf_process(): result['ietf_process'] = self.ietf_process.dict() return json.dumps(result, indent=2) # --------------------------------------------------------------------------- class RfcWrapper: _rfc = None _rfcindex = None _idinternal = None is_id_wrapper = False is_rfc_wrapper = True rfc_number = None title = None publication_date = None maturity_level = None ietf_process = None draft_name = None def __init__(self, rfcindex, rfc=None, idinternal=None): self._rfcindex = rfcindex self._rfc = rfc self._idinternal = idinternal self.rfc = self if not self._idinternal: if settings.USE_DB_REDESIGN_PROXY_CLASSES: 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: 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) self.rfc_number = self._rfcindex.rfc_number self.title = self._rfcindex.title self.publication_date = self._rfcindex.rfc_published_date self.maturity_level = self._rfcindex.current_status if not self.maturity_level: self.maturity_level = "Unknown" 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) if len(ids) >= 1: self.draft_name = ids[0].filename elif self._rfcindex and self._rfcindex.draft: # rfcindex occasionally includes drafts that were not # really submitted to IETF (e.g. April 1st) ids = InternetDraft.objects.filter(filename=self._rfcindex.draft) if len(ids) > 0: self.draft_name = self._rfcindex.draft def _rfc_doc_list(self, name): if (not self._rfcindex) or (not getattr(self._rfcindex, name)): return None else: s = getattr(self._rfcindex, name) s = s.replace(",", ", ") s = re.sub("([A-Z])([0-9])", "\\1 \\2", s) return s def obsoleted_by(self): return self._rfc_doc_list("obsoleted_by") def obsoletes(self): return self._rfc_doc_list("obsoletes") def updated_by(self): return self._rfc_doc_list("updated_by") def updates(self): return self._rfc_doc_list("updates") def also(self): return self._rfc_doc_list("also") def has_errata(self): return self._rfcindex and (self._rfcindex.has_errata > 0) def stream_name(self): if not self._rfcindex: return None else: x = self._rfcindex.stream if x == "INDEPENDENT": return "Independent Submission Stream" elif x == "LEGACY": return "Legacy Stream" else: return x+" Stream" def in_ietf_process(self): return self.ietf_process != None def file_types(self): types = self._rfcindex.file_formats types = types.replace("ascii","txt") return ["."+x for x in types.split(",")] def friendly_state(self): if self.in_ietf_process(): s = self.ietf_process.main_state if not s in ["RFC Published", "AD is watching", "Dead"]: return "RFC %d (%s)
%s (to %s)" % (self.rfc_number, self.maturity_level, self.ietf_process.state, self.ietf_process.intended_maturity_level()) return "RFC %d (%s)" % (self.rfc_number, self.maturity_level) def ad_name(self): if self.in_ietf_process(): return self.ietf_process.ad_name() else: # TODO: get AD name of the draft return None @models.permalink def get_absolute_url(self): return ('ietf.idrfc.views_doc.document_main', ['rfc%s' % (str(self.rfc_number))]) def displayname_with_link(self): return 'RFC %d' % (self.get_absolute_url(), self.rfc_number) def to_json(self): result = jsonify_helper(self, ['rfc_number', 'title', 'publication_date', 'maturity_level', 'obsoleted_by','obsoletes','updated_by','updates','also','has_errata','stream_name','file_types','in_ietf_process', 'friendly_state']) if self.in_ietf_process(): result['ietf_process'] = self.ietf_process.dict() return json.dumps(result, indent=2) # --------------------------------------------------------------------------- class IetfProcessData: _idinternal = None main_state = None sub_state = None state = None _ballot = None def __init__(self, idinternal): self._idinternal = idinternal i = self._idinternal self.main_state = str(i.cur_state) if i.cur_sub_state_id > 0: self.sub_state = str(i.cur_sub_state) self.state = self.main_state + "::" + self.sub_state else: self.sub_state = None self.state = self.main_state def has_iesg_ballot(self): try: if self._idinternal.ballot.ballot_issued: return True except BallotInfo.DoesNotExist: pass return False def has_active_iesg_ballot(self): if not self.has_iesg_ballot(): return False if not self.main_state in BALLOT_ACTIVE_STATES: return False if (not self._idinternal.rfc_flag) and self._idinternal.draft.status_id != 1: # Active return False return True # don't call this unless has_[active_]iesg_ballot returns True def iesg_ballot(self): if not self._ballot: self._ballot = BallotWrapper(self._idinternal) return self._ballot # 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() in ("BCP", "Best Current Practice") return self.iesg_ballot().ballot.needed( standardsTrack ) def ad_name(self): return str(self._idinternal.job_owner) def iesg_note(self): if self._idinternal.note: n = self._idinternal.note # Hide unnecessary note of form "RFC 1234" if re.match("^RFC\s*\d+$", n): return None return n else: return None def state_date(self): try: if settings.USE_DB_REDESIGN_PROXY_CLASSES: 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 ")| 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 ")| 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 ")| Q(comment_text__istartswith="State has been changed to ")| Q(comment_text__istartswith="IESG has approved and state has been changed to")).order_by('-id')[0].date except IndexError: # should never happen -- return an obviously bogus date return date(1990,1,1) def dict(self): result = {'main_state':self.main_state, 'sub_state':self.sub_state, 'state':self.state, 'state_date':str(self.state_date()), 'has_iesg_ballot':self.has_iesg_ballot(), 'has_active_iesg_ballot':self.has_active_iesg_ballot(), 'ad_name':self.ad_name(), 'intended_maturity_level':self.intended_maturity_level(), 'telechat_date':self.telechat_date()} if result['telechat_date']: result['telechat_date'] = str(result['telechat_date']) result['telechat_returning_item'] = self.telechat_returning_item() if self.iesg_note(): result['iesg_note'] = self.iesg_note() if self.has_iesg_ballot(): result['iesg_ballot'] = self.iesg_ballot().dict() return result def intended_maturity_level(self): if self._idinternal.rfc_flag: s = str(self._idinternal.document().intended_status) # rfc_intend_status table uses different names, argh! if s == "Proposed": s = "Proposed Standard" elif s == "Draft": s = "Draft Standard" elif s == "None": s = None else: s = str(self._idinternal.draft.intended_status) if s == "None": s = None elif s == "Request": s = None return s def telechat_date(self): # return date only if it's on upcoming agenda if self._idinternal.agenda: return self._idinternal.telechat_date else: return None def telechat_returning_item(self): # should be called only if telechat_date() returns non-None return bool(self._idinternal.returning_item) def state_change_notice_to(self): return self._idinternal.state_change_notice_to # comment_log? # --------------------------------------------------------------------------- class IdRfcWrapper: rfc = None id = None iprCount = None iprUrl = None def __init__(self, id, rfc): self.id = id self.rfc = rfc if id: iprs = IprDraft.objects.filter(document=self.id.tracker_id, ipr__status__in=[1,3]) self.iprUrl = "/ipr/search?option=document_search&id_document_tag=" + str(self.id.tracker_id) elif rfc: iprs = IprRfc.objects.filter(document=self.rfc.rfc_number, ipr__status__in=[1,3]) self.iprUrl = "/ipr/search?option=rfc_search&rfc_search=" + str(self.rfc.rfc_number) else: raise ValueError("Construction with null id and rfc") # iprs is a list of docs which contain IPR self.iprCount = len(iprs) def title(self): if self.rfc: return self.rfc.title else: return self.id.title def friendly_state(self): if self.rfc: return self.rfc.friendly_state() else: return self.id.friendly_state() def get_absolute_url(self): if self.rfc: return self.rfc.get_absolute_url() else: return self.id.get_absolute_url() def comment_count(self): if self.rfc: return DocumentComment.objects.filter(document=self.rfc.rfc_number,rfc_flag=1).count() else: return DocumentComment.objects.filter(document=self.id.tracker_id).exclude(rfc_flag=1).count() def ad_name(self): if self.rfc: s = self.rfc.ad_name() if s: return s if self.id: return self.id.ad_name() return None def publication_date(self): if self.rfc: return self.rfc.publication_date else: return self.id.publication_date def telechat_date(self): if self.rfc and self.rfc.in_ietf_process(): return self.rfc.ietf_process.telechat_date() elif self.id and self.id.in_ietf_process(): return self.id.ietf_process.telechat_date() else: return None def view_sort_group(self): if self.rfc: return 'RFC' elif self.id.draft_status == "Active": return 'Active Internet-Draft' else: return 'Old Internet-Draft' def view_sort_group_byad(self): if self.rfc: return 'RFC' elif self.id.draft_status == "Active": if self.id.in_ietf_process(): if self.id.ietf_process._idinternal.cur_state_id == IDState.DEAD: return 'IESG Dead Internet-Draft' else: return "%s Internet-Draft" % self.id.ietf_process._idinternal.cur_state else: return 'Active Internet-Draft' else: return 'Old Internet-Draft' def view_sort_key(self, sort_by=None): if sort_by is None: if self.rfc: return "2%04d" % self.rfc.rfc_number elif self.id.draft_status == "Active": return "1"+self.id.draft_name else: return "3"+self.id.draft_name else: if self.rfc: sort_key = "2" elif self.id.draft_status == "Active": sort_key = "1" else: sort_key = "3" # Depending on what we're sorting on, we may # need to do some conversion. if sort_by == "title": sort_key += self.title() elif sort_by == "date": sort_key = sort_key + str(self.publication_date()) elif sort_by == "status": if self.rfc: sort_key += "%04d" % self.rfc.rfc_number else: sort_key += self.id.draft_status elif sort_by == "ipr": sort_key += self.iprUrl elif sort_by == "ad": return self.view_sort_key_byad() else: # sort default or unknown sort value, revert to default if self.rfc: sort_key += "%04d" % self.rfc.rfc_number else: sort_key += self.id.draft_name return sort_key def view_sort_key_byad(self): if self.rfc: return "2%04d" % self.rfc.rfc_number elif self.id.draft_status == "Active": if self.id.in_ietf_process(): return "11%02d" % (self.id.ietf_process._idinternal.cur_state_id) else: return "10" else: return "3" # --------------------------------------------------------------------------- class BallotWrapper: _idinternal = None ballot = None ballot_active = False _positions = None position_values = ["Discuss", "Yes", "No Objection", "Abstain", "Recuse", "No Record"] def __init__(self, idinternal): self._idinternal = idinternal self.ballot = idinternal.ballot if not idinternal.rfc_flag: self.ballot_active = self.ballot.ballot_issued and (str(idinternal.cur_state) in BALLOT_ACTIVE_STATES) and str(idinternal.draft.status)=="Active"; else: self.ballot_active = self.ballot.ballot_issued and (str(idinternal.cur_state) in BALLOT_ACTIVE_STATES) self._ballot_set = None def approval_text(self): return self.ballot.approval_text def ballot_writeup(self): return self.ballot.ballot_writeup def is_active(self): return self.ballot_active def ballot_id(self): return self._idinternal.ballot_id def was_deferred(self): return self.ballot.defer def deferred_by(self): return self.ballot.defer_by def deferred_date(self): return self.ballot.defer_date def is_ballot_set(self): if not self._ballot_set: self._ballot_set = self._idinternal.ballot_set() return len(list(self._ballot_set)) > 1 def ballot_set_other(self): if not self.is_ballot_set(): 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 ietf.person.models import Person from ietf.doc.models import BallotPositionDocEvent, NewRevisionDocEvent active_ads = Person.objects.filter(role__name="ad", role__group__state="active").distinct() positions = [] seen = {} new_revisions = list(NewRevisionDocEvent.objects.filter(doc=self.ballot, type="new_revision").order_by('-time', '-id')) 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.plain_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, old_positions=[]) rev = pos.doc.rev for n in new_revisions: if n.time <= pos.time: rev = n.rev break if pos.pos.slug == "discuss": p["has_text"] = True p["discuss_text"] = pos.discuss p["discuss_date"] = pos.discuss_time.date() p["discuss_revision"] = rev if pos.comment: p["has_text"] = True p["comment_text"] = pos.comment p["comment_date"] = pos.comment_time.date() p["comment_revision"] = rev 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.plain_name(), ad_username=ad.pk, position="No Record", ) positions.append(d) self._positions = positions def old_init(self): try: ads = set() except NameError: # for Python 2.3 from sets import Set as set ads = set() positions = [] all_comments = self.ballot.comments.all().select_related('ad') for p in self.ballot.positions.all().select_related('ad'): po = create_position_object(self.ballot, p, all_comments) #if not self.ballot_active: # if 'is_old_ad' in po: # del po['is_old_ad'] ads.add(str(p.ad)) positions.append(po) for c in all_comments: if (str(c.ad) not in ads) and c.ad.is_current_ad(): positions.append({'has_text':True, 'comment_text':c.text, 'comment_date':c.date, 'comment_revision':str(c.revision), 'ad_name':str(c.ad), 'ad_username': c.ad.login_name, 'position':'No Record', 'is_old_ad':False}) ads.add(str(c.ad)) if self.ballot_active: for ad in IESGLogin.active_iesg(): if str(ad) not in ads: positions.append(dict(ad_name=str(ad), ad_username=ad.login_name, position="No Record")) self._positions = positions def position_for_ad(self, ad_name): pl = self.position_list() for p in pl: if p["ad_name"] == ad_name: return p["position"] return None def position_list(self): if not self._positions: self._init() return self._positions def get(self, v): return [p for p in self.position_list() if p['position']==v] def get_discuss(self): return self.get("Discuss") def get_yes(self): return self.get("Yes") def get_no_objection(self): return self.get("No Objection") def get_abstain(self): return self.get("Abstain") def get_recuse(self): return self.get("Recuse") def get_no_record(self): return self.get("No Record") def get_texts(self): return [p for p in self.position_list() if ('has_text' in p) and p['has_text']] def dict(self): summary = {} for key in self.position_values: tag = key.lower().replace(" ", "_") summary[tag] = [ pos["ad_name"] for pos in self.get(key) ] positions = self.position_list() for i in range(len(positions)): for key in ["comment_date", "discuss_date", ]: if key in positions[i]: positions[i][key] = positions[i][key].strftime("%Y-%m-%d") return { "active": self.is_active(), "approval_text": self.approval_text(), "ballot_writeup": self.ballot_writeup(), "ballot_id": self.ballot_id(), "deferred_by": unicode(self.deferred_by()), "deferred_date": self.deferred_date() and self.deferred_date().strftime("%Y-%m-%d") , "positions": positions, "summary": summary, "was_deferred": self.was_deferred(), } def position_to_string(position): positions = {"yes":"Yes", "noobj":"No Objection", "discuss":"Discuss", "abstain":"Abstain", "recuse":"Recuse"} if not position: return "No Record" p = None for k,v in positions.iteritems(): if getattr(position, k) > 0: p = v if not p: p = "No Record" return p def create_position_object(ballot, position, all_comments): positions = {"yes":"Yes", "noobj":"No Objection", "discuss":"Discuss", "abstain":"Abstain", "recuse":"Recuse"} p = None for k,v in positions.iteritems(): if position.__dict__[k] > 0: p = v if not p: p = "No Record" r = dict(ad_name=str(position.ad), ad_username=position.ad.login_name, position=p) if not position.ad.is_current_ad(): r['is_old_ad'] = True else: r['is_old_ad'] = False was = [v for k,v in positions.iteritems() if position.__dict__[k] < 0] if len(was) > 0: r['old_positions'] = was comment = None for c in all_comments: if c.ad == position.ad: comment = c break if comment and comment.text: r['has_text'] = True r['comment_text'] = comment.text r['comment_date'] = comment.date r['comment_revision'] = str(comment.revision) if p == "Discuss": try: discuss = ballot.discusses.get(ad=position.ad) if discuss.text: r['discuss_text'] = discuss.text else: r['discuss_text'] = '(empty)' r['discuss_revision'] = str(discuss.revision) r['discuss_date'] = discuss.date except IESGDiscuss.DoesNotExist: # this should never happen, but unfortunately it does # fill in something to keep other parts of the code happy r['discuss_text'] = "(error: discuss text not found)" r['discuss_revision'] = "00" r['discuss_date'] = date(2000, 1,1) r['has_text'] = True return r