Port idrfc code to use new ballot setup with an explicit ballot on

positions, port and clean up ballot icon renderer, add preliminary migration
script for inserting extra events in drafts
 - Legacy-Id: 4274
This commit is contained in:
Ole Laursen 2012-04-16 13:19:47 +00:00
parent 6524aba8dc
commit 1ff4bc0121
9 changed files with 191 additions and 169 deletions

View file

@ -175,32 +175,6 @@ class InternetDraft(Document):
def expired_tombstone(self):
return False
def calc_process_start_end(self):
import datetime
start, end = datetime.datetime.min, datetime.datetime.max
e = self.latest_event(type="started_iesg_process")
if e:
start = e.time
if self.get_state_slug() == "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
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
#shepherd = BrokenForeignKey('PersonOrOrgInfo', null=True, blank=True, null_values=(0, )) # same name
#idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))

View file

@ -35,14 +35,10 @@ def active_ballot_positions(doc, ballot=None):
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active"))
res = {}
# FIXME: do something with ballot
if not ballot:
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
start = datetime.datetime.min
e = doc.latest_event(type="started_iesg_process")
if e:
start = e.time
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ad__in=active_ads, time__gte=start).select_related('ad').order_by("-time", "-id")
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ad__in=active_ads, ballot=ballot).select_related('ad', 'pos').order_by("-time", "-id")
for pos in positions:
if pos.ad not in res:
@ -75,7 +71,7 @@ def needed_ballot_positions(doc, active_positions):
if doc.type_id == "draft" and doc.intended_std_level_id in ("bcp", "ps", "ds", "std"):
# For standards-track, need positions from 2/3 of the
# non-recused current IESG.
needed = len(active_positions) - recuse * 2 / 3
needed = len(active_positions) - len(recuse) * 2 / 3
have = len(yes) + len(noobj) + len(blocking)
if have < needed:
@ -92,21 +88,6 @@ def needed_ballot_positions(doc, active_positions):
return " ".join(answer)
def get_rfc_number(doc):
qs = doc.docalias_set.filter(name__startswith='rfc')
return qs[0].name[3:] if qs else None
def get_chartering_type(doc):
chartering = ""
if doc.get_state_slug() not in ("notrev", "approved"):
if doc.group.state_id == "proposed":
chartering = "initial"
elif doc.group.state_id == "active":
chartering = "rechartering"
return chartering
def ballot_open(doc, ballot_type_slug):
e = doc.latest_event(BallotDocEvent, ballot_type__slug=ballot_type_slug)
return e and not e.type == "closed_ballot"
@ -126,6 +107,20 @@ def close_open_ballots(doc, by):
e.desc = 'Closed "%s" ballot' % t.name
e.save()
def get_rfc_number(doc):
qs = doc.docalias_set.filter(name__startswith='rfc')
return qs[0].name[3:] if qs else None
def get_chartering_type(doc):
chartering = ""
if doc.get_state_slug() not in ("notrev", "approved"):
if doc.group.state_id == "proposed":
chartering = "initial"
elif doc.group.state_id == "active":
chartering = "rechartering"
return chartering
def augment_with_telechat_date(docs):
"""Add a telechat_date attribute to each document with the
scheduled telechat or None if it's not scheduled."""

View file

@ -733,7 +733,9 @@ class BallotWrapper:
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'):
ballot = self.ballot.latest_event(BallotDocEvent, type="created_ballot")
for pos in BallotPositionDocEvent.objects.filter(doc=self.ballot, type="changed_ballot_position", ballot=ballot).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

View file

@ -532,14 +532,13 @@ def generate_issue_ballot_mail(request, doc):
)
)
def generate_issue_ballot_mailREDESIGN(request, doc):
def generate_issue_ballot_mailREDESIGN(request, doc, ballot):
full_status = full_intended_status(doc.intended_std_level.name)
status = full_status.replace("a ", "").replace("an ", "")
active_ads = Person.objects.filter(role__name="ad", role__group__state="active").distinct()
e = doc.latest_event(type="started_iesg_process")
positions = BallotPositionDocEvent.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", ballot=ballot).order_by("-time", '-id').select_related('ad')
# format positions and setup discusses and comments
ad_feedback = []

View file

@ -36,17 +36,12 @@ 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 ietf.ietfauth.decorators import has_role
from ietf.doc.utils import active_ballot_positions
from ietf.doc.models import BallotDocEvent
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:
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:
@ -60,105 +55,64 @@ def get_user_name(context):
if person:
return str(person)
return None
def render_ballot_icon(context, doc):
if isinstance(doc,IDInternal):
try:
ballot = doc.ballot
if not ballot.ballot_issued:
return ""
except BallotInfo.DoesNotExist:
return ""
if str(doc.cur_state) not in BALLOT_ACTIVE_STATES:
return ""
if doc.rfc_flag and not settings.USE_DB_REDESIGN_PROXY_CLASSES:
name = doc.document().filename()
else:
name = doc.document().filename
else:
if doc.in_ietf_process() and doc.ietf_process.has_active_iesg_ballot():
ballot = doc._idinternal.ballot
else:
return ""
if doc.is_rfc_wrapper:
name = "rfc"+str(doc.rfc_number)
else:
name = doc.draft_name
adId = get_user_adid(context)
red = 0
green = 0
yellow = 0
gray = 0
blank = 0
my = None
for p in ballot.active_positions():
if not p['pos']:
blank = blank + 1
elif (p['pos'].yes > 0) or (p['pos'].noobj > 0):
green = green + 1
elif (p['pos'].discuss > 0):
red = red + 1
elif (p['pos'].abstain > 0):
yellow = yellow + 1
elif (p['pos'].recuse > 0):
gray = gray + 1
else:
blank = blank + 1
if adId and (p['ad'].id == adId):
my = position_to_string(p['pos'])
return render_ballot_icon2(name, red,yellow,green,gray,blank, my, adId)+"<!-- adId="+str(adId)+" my="+str(my)+"-->"
def render_ballot_icon2(draft_name, red,yellow,green,gray,blank, my,adId):
from ietf.doc.models import BallotDocEvent
ballots = BallotDocEvent.objects.filter(doc__docalias__name=draft_name).order_by("-time", "-id")
if ballots:
edit_position_url = urlreverse('doc_edit_position', kwargs=dict(name=draft_name, ballot_id=ballots[0].pk))
else:
edit_position_url = ""
if adId:
res_cm = ' oncontextmenu="editBallot(\''+str(edit_position_url)+'\');return false;"'
else:
res_cm = ''
res = '<table class="ballot_icon" title="IESG Evaluation Record (click to show more, right-click to edit position)" onclick="showBallot(\'' + draft_name + '\',\'' + str(edit_position_url) + '\')"'+res_cm+'>'
for y in range(3):
res = res + "<tr>"
for x in range(5):
myMark = False
if red > 0:
c = "ballot_icon_red"
red = red - 1
myMark = (my == "Discuss")
elif yellow > 0:
c = "ballot_icon_yellow"
yellow = yellow - 1
myMark = (my == "Abstain")
elif green > 0:
c = "ballot_icon_green"
green = green - 1
myMark = (my == "Yes") or (my == "No Objection")
elif gray > 0:
c = "ballot_icon_gray"
gray = gray - 1
myMark = (my == "Recuse")
else:
c = ""
myMark = (y == 2) and (x == 4) and (my == "No Record")
if myMark:
res = res + '<td class="'+c+' ballot_icon_my" />'
my = None
else:
res = res + '<td class="'+c+'" />'
res = res + '</tr>'
res = res + '</table>'
return res
def render_ballot_icon(user, doc):
if not doc:
return ""
s = doc.get_state("draft-iesg")
if s and s.name not in BALLOT_ACTIVE_STATES:
return ""
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
if not ballot:
return ""
edit_position_url = urlreverse('doc_edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk))
def sort_key(t):
_, pos = t
if not pos:
return (2, 0)
elif pos.pos.blocking:
return (0, pos.pos.order)
else:
return (1, pos.pos.order)
positions = list(active_ballot_positions(doc, ballot).items())
positions.sort(key=sort_key)
cm = ""
if has_role(user, "Area Director"):
cm = ' oncontextmenu="editBallot(\''+str(edit_position_url)+'\');return false;"'
res = ['<table class="ballot_icon" title="IESG Evaluation Record (click to show more, right-click to edit position)" onclick="showBallot(\'' + doc.name + '\',\'' + str(edit_position_url) + '\')"' + cm + '>']
res.append("<tr>")
for i, (ad, pos) in enumerate(positions):
if i > 0 and i % 5 == 0:
res.append("</tr>")
res.append("<tr>")
c = "position-%s" % (pos.pos.slug if pos else "norecord")
if ad == user.get_profile():
c += " my"
res.append('<td class="%s" />' % c)
res.append("</tr>")
res.append("</table>")
return "".join(res)
class BallotIconNode(template.Node):
def __init__(self, doc_var):
self.doc_var = doc_var
def render(self, context):
doc = template.resolve_variable(self.doc_var, context)
return render_ballot_icon(context, doc)
return render_ballot_icon(context.get("user"), doc._idinternal)
def do_ballot_icon(parser, token):
try:

View file

@ -26,6 +26,7 @@ from ietf.idrfc.utils import *
from ietf.idrfc.lastcall import request_last_call
from ietf.idrfc.idrfc_wrapper import BallotWrapper
from ietf.doc.utils import *
from ietf.doc.models import *
from ietf.name.models import BallotPositionName
from ietf.person.models import Person
@ -343,6 +344,7 @@ def edit_positionREDESIGN(request, name, ballot_id):
form = EditPositionForm(initial=initial, ballot_type=ballot.ballot_type)
blocking_positions = dict((p.pk, p.name) for p in form.fields["position"].queryset.all() if p.blocking)
print blocking_positions, form.fields["position"].queryset.all()
ballot_deferred = None
if doc.get_state_slug("%s-iesg" % doc.type_id) == "defer":
@ -933,9 +935,6 @@ 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)
started_process = doc.latest_event(type="started_iesg_process")
if not started_process:
raise Http404()
login = request.user.get_profile()
@ -958,9 +957,13 @@ def ballot_writeupnotesREDESIGN(request, name):
e.save()
if "issue_ballot" in request.POST:
if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionDocEvent, ad=login, time__gte=started_process.time):
create_ballot_if_not_open(doc, login, "approve")
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot):
# sending the ballot counts as a yes
pos = BallotPositionDocEvent(doc=doc, by=login)
pos.ballot = ballot
pos.type = "changed_ballot_position"
pos.ad = login
pos.pos_id = "yes"
@ -971,7 +974,7 @@ def ballot_writeupnotesREDESIGN(request, name):
if not approval:
approval = generate_approval_mail(request, doc)
msg = generate_issue_ballot_mail(request, doc)
msg = generate_issue_ballot_mail(request, doc, ballot)
send_mail_preformatted(request, msg)
email_iana(request, doc, 'drafts-eval@icann.org', msg)

View file

@ -271,10 +271,10 @@ def document_ballot_content(request, doc, ballot_id, editable=True):
if latest.old_positions:
prev = latest.old_positions[-1]
else:
prev = latest
prev = latest.pos.name
if e.pos != prev.pos:
latest.old_positions.append(e)
if e.pos.name != prev:
latest.old_positions.append(e.pos.name)
# add any missing ADs through fake No Record events
norecord = BallotPositionName.objects.get(slug="norecord")
@ -316,9 +316,6 @@ def document_ballot_content(request, doc, ballot_id, editable=True):
context_instance=RequestContext(request))
def document_ballot(request, name, ballot_id=None):
if name.lower().startswith("draft") or name.lower().startswith("rfc"):
return document_main_idrfc(request, name, "ballot")
doc = get_object_or_404(Document, docalias__name=name)
top = render_document_top(request, doc, "ballot")

102
ietf/wgcharter/migrate.py Normal file
View file

@ -0,0 +1,102 @@
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
from django.core import management
management.setup_environ(settings)
from ietf.doc.models import *
# make sure ballot positions and types are right
BallotPositionName.objects.get_or_create(slug="block",
order=3,
name="Block",
blocking=True,
)
BallotPositionName.objects.filter(slug="discuss").update(blocking=True)
charter_positions = BallotPositionName.objects.filter(slug__in=["yes", "noobj", "block", "abstain", "norecord" ])
o,_ = BallotType.objects.get_or_create(doc_type_id="charter",
slug="r-extrev",
name="Ready for external review",
question="Is this charter ready for external review?",
order=1,
)
o.positions = charter_positions
o,_ = BallotType.objects.get_or_create(doc_type_id="charter",
slug="r-wo-ext",
name="Ready w/o external review",
question="Is this charter ready for external review? Is this charter ready for approval without external review?",
order=2,
)
o.positions = charter_positions
o,_ = BallotType.objects.get_or_create(doc_type_id="charter",
slug="approve",
name="Approve",
question="Do we approve of this charter?",
order=3,
)
o.positions = charter_positions
draft_ballot,_ = BallotType.objects.get_or_create(doc_type_id="draft",
slug="approve",
name="Approve",
question="",
order=1,
)
draft_ballot.positions = BallotPositionName.objects.filter(slug__in=["yes", "noobj", "discuss", "abstain", "recuse", "norecord"])
# add events for drafts
# prevent memory from leaking when settings.DEBUG=True
from django.db import connection
class DontSaveQueries(object):
def append(self, x):
pass
connection.queries = DontSaveQueries()
relevant_docs = Document.objects.filter(type="draft", docevent__type__in=("changed_ballot_position", "sent_ballot_announcement")).distinct()
for d in relevant_docs[:1].iterator():
# print ""
# print d.name
# for e in d.docevent_set.order_by("time", "id").select_related("ballotpositiondocevent"):
# print e.time, e.type, "BINGO" if e.type == "sent_ballot_announcement" else ""
ballot = None
for e in d.docevent_set.order_by("time", "id").select_related("ballotpositiondocevent"):
if e.type == "created_ballot":
ballot = e
if e.type == "closed_ballot":
ballot = None
if not ballot and e.type in ("sent_ballot_announcement", "changed_ballot_position"):
ballot = BallotDocEvent(doc=e.doc, by=e.by)
ballot.type = "created_ballot"
ballot.ballot_type = draft_ballot
# place new event just before
ballot.time = e.time - datetime.timedelta(seconds=1)
ballot.desc = u'Created "%s" ballot' % ballot.ballot_type.name
ballot.save()
if e.type == "sent_ballot_announcement":
print "added ballot for", d.name
else:
print "MISSING ballot issue event, added ballot for", d.name
if e.type == "changed_ballot_position" and not e.ballotpositiondocevent.ballot:
e.ballotpositiondocevent.ballot = ballot
e.ballotpositiondocevent.save()
# FIXME: close ballot

View file

@ -122,11 +122,7 @@ table.ietf-table { border-collapse:collapse; border:1px solid #7f7f7f; }
table.ballot_icon { empty-cells: show; padding: 0; border-spacing: 0; border: 1px solid black; border-collapse: collapse; table-layout:fixed; min-width:35px; background:white; }
table.ballot_icon td { border: 1px solid black; height: 7px; width: 6px; padding: 0;}
td.ballot_icon_green { background:#80ff80; }
td.ballot_icon_red { background: #c00000; color: yellow; }
td.ballot_icon_gray { background: #c0c0c0; }
td.ballot_icon_yellow { background: #ffff00; }
table.ballot_icon td.ballot_icon_my { border: 3px outset black;}
table.ballot_icon td.my { border: 3px outset black;}
.ietf-small { font-size:85%; }
.ietf-highlight-y { padding:0 2px;background:yellow;}