Skip to content

Commit c90a26c

Browse files
committed
Merged [4772] from rjsparks@nostrum.com:
Liasion Manager: * Patch from Yaco to avoid resetting the From field when changing other items on the form * Gave the secretariat the ability to find/approve any unapproved liaisons * Changed all the email code to use ietf.mail.utils (and removed the fake-mail concept) Charter documents and the Agenda pages: * Added charter documents to iesg/agenda and iesg/agenda/documents * Synced the ordering of drafts on iesg/agenda and iesg/agenda/documents * Allow setting a responsible AD for charter documents * Changed the UI of the charter page to use editlink for changing attributes and buttons for actions (to align with drafts and conflict-reviews) Moderator package: * Refactor: Simplified access to the current BallotDocEvent from a Document * Added functions to BallotDocEvents? to faciliate access to BallotPositionDocEvents?, both for all positions, and current AD postions. * Updated the moderator package to use the Documents from _agenda_data. * Added a filter to assist with rendering the moderator package. * Fixed a bug where different functions in idrfc/views_ballot were using log_state_changed expecting different implementations (a cleanup task should reconcile the _three_ implementations in the codebase of that function). Cleanup from codesprint: * Removed some duplication between doc/util and doc/models by moving things into doc/models * Do not show non-empty discuss text when the ballot position is not blocking * Added a migration to update non-blocking ballot positions that have non-empty discuss text DEPLOYMENT NOTES Please be aware that migration step will take a few minutes to complete. Fixes bug 865 - Legacy-Id: 4780 Note: SVN reference [4772] has been migrated to Git commit e5c3a5a
2 parents 9bde5b9 + e5c3a5a commit c90a26c

33 files changed

Lines changed: 745 additions & 280 deletions

ietf/doc/migrations/0004_clean_stale_discusses.py

Lines changed: 372 additions & 0 deletions
Large diffs are not rendered by default.

ietf/doc/models.py

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -261,29 +261,21 @@ def active_defer_event(self):
261261
return self.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
262262
return None
263263

264+
# This, and several other ballot related functions here, assume that there is only one active ballot for a document at any point in time.
265+
# If that assumption is violated, they will only expose the most recently created ballot
266+
def ballot_open(self, ballot_type_slug):
267+
e = self.latest_event(BallotDocEvent, ballot_type__slug=ballot_type_slug)
268+
return e and not e.type == "closed_ballot"
269+
264270
def active_ballot(self):
271+
"""Returns the most recently created ballot if it isn't closed."""
265272
ballot = self.latest_event(BallotDocEvent, type="created_ballot")
266-
e = self.latest_event(BallotDocEvent, ballot_type__slug=ballot.ballot_type.slug) if ballot else None
267-
open = e and not e.type == "closed_ballot"
268-
return ballot.ballot_type if open else None
273+
open = self.ballot_open(ballot.ballot_type.slug) if ballot else False
274+
return ballot if open else None
269275

270-
def active_ballot_positions(self):
271-
"""Return dict mapping each active AD to a current ballot position (or None if they haven't voted)."""
272-
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active"))
273-
res = {}
274-
275-
ballot = self.latest_event(BallotDocEvent, type="created_ballot")
276-
positions = BallotPositionDocEvent.objects.filter(doc=self, type="changed_ballot_position", ad__in=active_ads, ballot=ballot).select_related('ad', 'pos').order_by("-time", "-id")
277-
278-
for pos in positions:
279-
if pos.ad not in res:
280-
res[pos.ad] = pos
281-
282-
for ad in active_ads:
283-
if ad not in res:
284-
res[ad] = None
285-
286-
return res.values()
276+
def most_recent_ietflc(self):
277+
"""Returns the most recent IETF LastCallDocEvent for this document"""
278+
return self.latest_event(LastCallDocEvent,type="sent_last_call")
287279

288280
def displayname_with_link(self):
289281
return '<a href="%s">%s-%s</a>' % (self.get_absolute_url(), self.name , self.rev)
@@ -528,6 +520,59 @@ class Meta:
528520
class BallotDocEvent(DocEvent):
529521
ballot_type = models.ForeignKey(BallotType)
530522

523+
def active_ad_positions(self):
524+
"""Return dict mapping each active AD to a current ballot position (or None if they haven't voted)."""
525+
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active"))
526+
res = {}
527+
528+
if self.doc.latest_event(BallotDocEvent, type="created_ballot") == self:
529+
530+
positions = BallotPositionDocEvent.objects.filter(type="changed_ballot_position",ad__in=active_ads, ballot=self).select_related('ad', 'pos').order_by("-time", "-id")
531+
532+
for pos in positions:
533+
if pos.ad not in res:
534+
res[pos.ad] = pos
535+
536+
for ad in active_ads:
537+
if ad not in res:
538+
res[ad] = None
539+
return res
540+
541+
def all_positions(self):
542+
"""Return array holding the current and past positions per AD"""
543+
544+
positions = []
545+
seen = {}
546+
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active").distinct())
547+
for e in BallotPositionDocEvent.objects.filter(type="changed_ballot_position", ballot=self).select_related('ad', 'pos').order_by("-time", '-id'):
548+
if e.ad not in seen:
549+
e.old_ad = e.ad not in active_ads
550+
e.old_positions = []
551+
positions.append(e)
552+
seen[e.ad] = e
553+
else:
554+
latest = seen[e.ad]
555+
if latest.old_positions:
556+
prev = latest.old_positions[-1]
557+
else:
558+
prev = latest.pos
559+
560+
if e.pos != prev:
561+
latest.old_positions.append(e.pos)
562+
563+
# add any missing ADs through fake No Record events
564+
norecord = BallotPositionName.objects.get(slug="norecord")
565+
for ad in active_ads:
566+
if ad not in seen:
567+
e = BallotPositionDocEvent(type="changed_ballot_position", doc=self.doc, ad=ad)
568+
e.pos = norecord
569+
e.old_ad = False
570+
e.old_positions = []
571+
positions.append(e)
572+
573+
positions.sort(key=lambda p: (p.old_ad, p.ad.last_name()))
574+
return positions
575+
531576
class BallotPositionDocEvent(DocEvent):
532577
ballot = models.ForeignKey(BallotDocEvent, null=True, default=None) # default=None is a temporary migration period fix, should be removed when charter branch is live
533578
ad = models.ForeignKey(Person)

ietf/doc/proxy.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,10 +550,9 @@ def ballot_issued(self):
550550
def active_positions(self):
551551
"""Returns a list of dicts, with AD and Position tuples"""
552552
from ietf.person.proxy import IESGLogin as IESGLoginProxy
553-
from ietf.doc.utils import active_ballot_positions
554553

555554
res = []
556-
for ad, pos in active_ballot_positions(self).iteritems():
555+
for ad, pos in self.active_ballot().active_ad_positions().iteritems():
557556
res.append(dict(ad=IESGLoginProxy().from_object(ad), pos=Position().from_object(pos) if pos else None))
558557

559558
res.sort(key=lambda x: x["ad"].last_name)

ietf/doc/tests_conflict_review.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from ietf.utils.test_utils import login_testing_unauthorized
1414
from ietf.utils.test_data import make_test_data
1515
from ietf.utils.mail import outbox
16-
from ietf.doc.utils import active_ballot, create_ballot_if_not_open, ballot_open
16+
from ietf.doc.utils import create_ballot_if_not_open
1717
from ietf.doc.views_conflict_review import default_approval_text
1818

1919
from ietf.doc.models import Document,DocEvent,NewRevisionDocEvent,BallotPositionDocEvent,TelechatDocEvent,DocAlias,State
@@ -105,7 +105,7 @@ def test_change_state(self):
105105
review_doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
106106
self.assertEquals(review_doc.get_state('conflrev').slug,'adrev')
107107
self.assertTrue(review_doc.latest_event(DocEvent,type="added_comment").desc.startswith('RDNK84ZD'))
108-
self.assertFalse(active_ballot(review_doc))
108+
self.assertFalse(review_doc.active_ballot())
109109

110110
# successful change to IESG Evaluation
111111
iesgeval_pk = str(State.objects.get(slug='iesgeval',type__slug='conflrev').pk)
@@ -114,7 +114,7 @@ def test_change_state(self):
114114
review_doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
115115
self.assertEquals(review_doc.get_state('conflrev').slug,'iesgeval')
116116
self.assertTrue(review_doc.latest_event(DocEvent,type="added_comment").desc.startswith('TGmZtEjt'))
117-
self.assertTrue(active_ballot(review_doc))
117+
self.assertTrue(review_doc.active_ballot())
118118
self.assertEquals(review_doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position").pos_id,'yes')
119119

120120

@@ -234,7 +234,7 @@ def approve_test_helper(self,approve_type):
234234

235235
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
236236
self.assertEquals(doc.get_state_slug(),approve_type+'-sent')
237-
self.assertFalse(ballot_open(doc, "conflrev"))
237+
self.assertFalse(doc.ballot_open("conflrev"))
238238

239239
self.assertEquals(len(outbox), messages_before + 1)
240240
self.assertTrue('Results of IETF-conflict review' in outbox[-1]['Subject'])

ietf/doc/utils.py

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,6 @@ def get_tags_for_stream_id(stream_id):
3636
else:
3737
return []
3838

39-
# This, and several other utilities here, assume that there is only one active ballot for a document at any point in time.
40-
# If that assumption is violated, they will only expose the most recently created ballot
41-
def active_ballot(doc):
42-
"""Returns the most recently created ballot if it isn't closed."""
43-
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
44-
open = ballot_open(doc,ballot.ballot_type.slug) if ballot else False
45-
return ballot if open else None
46-
47-
48-
def active_ballot_positions(doc, ballot=None):
49-
"""Return dict mapping each active AD to a current ballot position (or None if they haven't voted)."""
50-
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active"))
51-
res = {}
52-
53-
if not ballot:
54-
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
55-
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ad__in=active_ads, ballot=ballot).select_related('ad', 'pos').order_by("-time", "-id")
56-
57-
for pos in positions:
58-
if pos.ad not in res:
59-
res[pos.ad] = pos
60-
61-
for ad in active_ads:
62-
if ad not in res:
63-
res[ad] = None
64-
65-
return res
66-
6739
def needed_ballot_positions(doc, active_positions):
6840
'''Returns text answering the question "what does this document
6941
need to pass?". The return value is only useful if the document
@@ -102,20 +74,16 @@ def needed_ballot_positions(doc, active_positions):
10274

10375
return " ".join(answer)
10476

105-
def ballot_open(doc, ballot_type_slug):
106-
e = doc.latest_event(BallotDocEvent, ballot_type__slug=ballot_type_slug)
107-
return e and not e.type == "closed_ballot"
108-
10977
def create_ballot_if_not_open(doc, by, ballot_type_slug):
110-
if not ballot_open(doc, ballot_type_slug):
78+
if not doc.ballot_open(ballot_type_slug):
11179
e = BallotDocEvent(type="created_ballot", by=by, doc=doc)
11280
e.ballot_type = BallotType.objects.get(doc_type=doc.type, slug=ballot_type_slug)
11381
e.desc = u'Created "%s" ballot' % e.ballot_type.name
11482
e.save()
11583

11684
def close_open_ballots(doc, by):
11785
for t in BallotType.objects.filter(doc_type=doc.type_id):
118-
if ballot_open(doc, t.slug):
86+
if doc.ballot_open(t.slug):
11987
e = BallotDocEvent(type="closed_ballot", doc=doc, by=by)
12088
e.ballot_type = t
12189
e.desc = 'Closed "%s" ballot' % t.name

ietf/idrfc/templatetags/ballot_icon.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
from ietf.idrfc.idrfc_wrapper import position_to_string, BALLOT_ACTIVE_STATES
3838
from ietf.idtracker.templatetags.ietf_filters import in_group, timesince_days
3939
from ietf.ietfauth.decorators import has_role
40-
from ietf.doc.utils import active_ballot_positions
4140
from ietf.doc.models import BallotDocEvent
4241

4342
register = template.Library()
@@ -83,7 +82,7 @@ def sort_key(t):
8382
else:
8483
return (1, pos.pos.order)
8584

86-
positions = list(active_ballot_positions(doc, ballot).items())
85+
positions = list(doc.active_ballot().active_ad_positions().items())
8786
positions.sort(key=sort_key)
8887

8988
cm = ""

ietf/idrfc/templatetags/ballot_icon_redesign.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
from ietf.idrfc.idrfc_wrapper import position_to_string, BALLOT_ACTIVE_STATES
3939
from ietf.idtracker.templatetags.ietf_filters import in_group, timesince_days
4040
from ietf.ietfauth.decorators import has_role
41-
from ietf.doc.utils import active_ballot_positions, active_ballot
4241
from ietf.doc.models import BallotDocEvent, BallotPositionDocEvent
4342

4443
from datetime import date
@@ -90,7 +89,7 @@ def sort_key(t):
9089
else:
9190
return (1, pos.pos.order)
9291

93-
positions = list(active_ballot_positions(doc, ballot).items())
92+
positions = list(doc.active_ballot().active_ad_positions().items())
9493
positions.sort(key=sort_key)
9594

9695
cm = ""
@@ -144,7 +143,7 @@ def my_position(doc, user):
144143
return None
145144
if not in_group(user, "Area_Director"):
146145
return None
147-
ballot = active_ballot(doc)
146+
ballot = doc.active_ballot()
148147
pos = "No Record"
149148
if ballot:
150149
changed_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad__name=user_name, ballot=ballot)

ietf/idrfc/views_ballot.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
from ietf.message.utils import infer_message
3636
from ietf.person.models import Person
3737

38-
from ietf.doc.utils import log_state_changed
38+
from ietf.doc.utils import log_state_changed as docutil_log_state_changed
39+
from ietf.idrfc.utils import log_state_changed as idrfcutil_log_state_changed
3940

4041
BALLOT_CHOICES = (("yes", "Yes"),
4142
("noobj", "No Objection"),
@@ -431,7 +432,7 @@ def defer_ballotREDESIGN(request, name):
431432
elif doc.type_id == 'conflrev':
432433
doc.set_state(State.objects.get(type='conflrev', slug='defer'))
433434

434-
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
435+
e = docutil_log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
435436

436437
doc.time = e.time
437438
doc.save()
@@ -481,7 +482,7 @@ def undefer_ballotREDESIGN(request, name):
481482
elif doc.type_id == 'conflrev':
482483
doc.set_state(State.objects.get(type='conflrev',slug='iesgeval'))
483484

484-
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
485+
e = docutil_log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
485486

486487
doc.time = e.time
487488
doc.save()
@@ -557,7 +558,7 @@ def lastcalltext(request, name):
557558
if "send_last_call_request" in request.POST:
558559
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.LAST_CALL_REQUESTED), None)
559560

560-
change = log_state_changed(request, doc, login)
561+
change = idrfcutil_log_state_changed(request, doc, login)
561562
email_owner(request, doc, doc.idinternal.job_owner, login, change)
562563
request_last_call(request, doc)
563564

@@ -646,7 +647,7 @@ def lastcalltextREDESIGN(request, name):
646647
if prev_tag:
647648
doc.tags.remove(prev_tag)
648649

649-
e = log_state_changed(request, doc, login, prev, prev_tag)
650+
e = idrfcutil_log_state_changed(request, doc, login, prev, prev_tag)
650651

651652
doc.time = e.time
652653
doc.save()
@@ -1093,7 +1094,7 @@ def approve_ballotREDESIGN(request, name):
10931094

10941095
change_description = e.desc + " and state has been changed to %s" % doc.get_state("draft-iesg").name
10951096

1096-
e = log_state_changed(request, doc, login, prev, prev_tag)
1097+
e = idrfcutil_log_state_changed(request, doc, login, prev, prev_tag)
10971098

10981099
doc.time = e.time
10991100
doc.save()
@@ -1153,7 +1154,7 @@ def make_last_call(request, name):
11531154
doc.idinternal.event_date = date.today()
11541155
doc.idinternal.save()
11551156

1156-
log_state_changed(request, doc, login)
1157+
idrfcutil_log_state_changed(request, doc, login)
11571158

11581159
doc.lc_sent_date = form.cleaned_data['last_call_sent_date']
11591160
doc.lc_expiration_date = form.cleaned_data['last_call_expiration_date']
@@ -1216,7 +1217,7 @@ def make_last_callREDESIGN(request, name):
12161217
if prev_tag:
12171218
doc.tags.remove(prev_tag)
12181219

1219-
e = log_state_changed(request, doc, login, prev, prev_tag)
1220+
e = idrfcutil_log_state_changed(request, doc, login, prev, prev_tag)
12201221

12211222
doc.time = e.time
12221223
doc.save()

ietf/idrfc/views_doc.py

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def document_main(request, name, rev=None):
143143

144144
ballot_summary = None
145145
if doc.get_state_slug() in ("intrev", "iesgrev"):
146-
ballot_summary = needed_ballot_positions(doc, active_ballot_positions(doc).values())
146+
ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values())
147147

148148
return render_to_response("idrfc/document_charter.html",
149149
dict(doc=doc,
@@ -171,7 +171,7 @@ def document_main(request, name, rev=None):
171171

172172
ballot_summary = None
173173
if doc.get_state_slug() in ("iesgeval"):
174-
ballot_summary = needed_ballot_positions(doc, active_ballot_positions(doc).values())
174+
ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values())
175175

176176
return render_to_response("idrfc/document_conflict_review.html",
177177
dict(doc=doc,
@@ -292,36 +292,7 @@ def document_ballot_content(request, doc, ballot_id, editable=True):
292292

293293
deferred = doc.active_defer_event()
294294

295-
# collect positions
296-
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active").distinct())
297-
298-
positions = []
299-
seen = {}
300-
for e in BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ballot=ballot).select_related('ad', 'pos').order_by("-time", '-id'):
301-
if e.ad not in seen:
302-
e.old_ad = e.ad not in active_ads
303-
e.old_positions = []
304-
positions.append(e)
305-
seen[e.ad] = e
306-
else:
307-
latest = seen[e.ad]
308-
if latest.old_positions:
309-
prev = latest.old_positions[-1]
310-
else:
311-
prev = latest.pos.name
312-
313-
if e.pos.name != prev:
314-
latest.old_positions.append(e.pos.name)
315-
316-
# add any missing ADs through fake No Record events
317-
norecord = BallotPositionName.objects.get(slug="norecord")
318-
for ad in active_ads:
319-
if ad not in seen:
320-
e = BallotPositionDocEvent(type="changed_ballot_position", doc=doc, ad=ad)
321-
e.pos = norecord
322-
e.old_ad = False
323-
e.old_positions = []
324-
positions.append(e)
295+
positions = doc.active_ballot().all_positions() if doc.active_ballot() else []
325296

326297
# put into position groups
327298
position_groups = []

ietf/idtracker/templatetags/ietf_filters.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ def square_brackets(value):
146146
else:
147147
return "[ ]"
148148

149+
@register.filter(name='bracketpos')
150+
def bracketpos(pos,posslug):
151+
if pos.pos.slug==posslug:
152+
return "[ X ]"
153+
elif posslug in [x.slug for x in pos.old_positions]:
154+
return "[ . ]"
155+
else:
156+
return "[ ]"
157+
149158
@register.filter(name='fill')
150159
def fill(text, width):
151160
"""Wraps each paragraph in text (a string) so every line

0 commit comments

Comments
 (0)