From 1ff4bc0121aa3c606cf753887d5f983d8449bb1b Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Mon, 16 Apr 2012 13:19:47 +0000 Subject: [PATCH] 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 --- ietf/doc/proxy.py | 26 ----- ietf/doc/utils.py | 41 +++---- ietf/idrfc/idrfc_wrapper.py | 4 +- ietf/idrfc/mails.py | 5 +- ietf/idrfc/templatetags/ballot_icon.py | 154 +++++++++---------------- ietf/idrfc/views_ballot.py | 13 ++- ietf/idrfc/views_doc.py | 9 +- ietf/wgcharter/migrate.py | 102 ++++++++++++++++ static/css/base2.css | 6 +- 9 files changed, 191 insertions(+), 169 deletions(-) create mode 100644 ietf/wgcharter/migrate.py diff --git a/ietf/doc/proxy.py b/ietf/doc/proxy.py index 00e4b265a..cb42553a8 100644 --- a/ietf/doc/proxy.py +++ b/ietf/doc/proxy.py @@ -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)) diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index 2e1dfe4ac..e1bfd882f 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -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.""" diff --git a/ietf/idrfc/idrfc_wrapper.py b/ietf/idrfc/idrfc_wrapper.py index 53a18a571..290e54084 100644 --- a/ietf/idrfc/idrfc_wrapper.py +++ b/ietf/idrfc/idrfc_wrapper.py @@ -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 diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index 3bac6bafc..8ae3ec17a 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -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 = [] diff --git a/ietf/idrfc/templatetags/ballot_icon.py b/ietf/idrfc/templatetags/ballot_icon.py index 12f6891a6..6182d5529 100644 --- a/ietf/idrfc/templatetags/ballot_icon.py +++ b/ietf/idrfc/templatetags/ballot_icon.py @@ -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)+"" -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 = '' - for y in range(3): - res = res + "" - 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 + '' - res = res + '
' - my = None - else: - res = res + '' - res = res + '
' - 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 = [''] + + res.append("") + + for i, (ad, pos) in enumerate(positions): + if i > 0 and i % 5 == 0: + res.append("") + res.append("") + + c = "position-%s" % (pos.pos.slug if pos else "norecord") + + if ad == user.get_profile(): + c += " my" + + res.append('") + res.append("
' % c) + + res.append("
") + + 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: diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 5f3f1067e..b7b2906e1 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -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) diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index 68caeaf10..762ec3c62 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -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") diff --git a/ietf/wgcharter/migrate.py b/ietf/wgcharter/migrate.py new file mode 100644 index 000000000..fca8910fe --- /dev/null +++ b/ietf/wgcharter/migrate.py @@ -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 diff --git a/static/css/base2.css b/static/css/base2.css index 473069dc0..669c5955d 100644 --- a/static/css/base2.css +++ b/static/css/base2.css @@ -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;}