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)+"<!-- 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: 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;}