Skip to content

Commit a9817e5

Browse files
committed
Some fixes to generic doc ballot view, fix edit position and send
ballot comment email views, moving them to /doc/foo-bar/ballot/[id]/position/ and ../emailposition/. - Legacy-Id: 4249
1 parent 4fc98cf commit a9817e5

11 files changed

Lines changed: 86 additions & 87 deletions

File tree

ietf/doc/admin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ class DocAliasAdmin(admin.ModelAdmin):
111111
admin.site.register(DocAlias, DocAliasAdmin)
112112

113113

114+
admin.site.register(BallotType)
115+
114116
# events
115117

116118
class DocEventAdmin(admin.ModelAdmin):
@@ -124,12 +126,13 @@ def by_raw(self, instance):
124126
admin.site.register(DocEvent, DocEventAdmin)
125127

126128
admin.site.register(NewRevisionDocEvent, DocEventAdmin)
129+
admin.site.register(BallotDocEvent, DocEventAdmin)
127130
admin.site.register(WriteupDocEvent, DocEventAdmin)
128131
admin.site.register(LastCallDocEvent, DocEventAdmin)
129132
admin.site.register(TelechatDocEvent, DocEventAdmin)
130133

131134
class BallotPositionDocEventAdmin(DocEventAdmin):
132-
raw_id_fields = ["doc", "by", "ad"]
135+
raw_id_fields = ["doc", "by", "ad", "ballot"]
133136

134137
admin.site.register(BallotPositionDocEvent, BallotPositionDocEventAdmin)
135138

ietf/doc/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ class BallotType(models.Model):
344344
question = models.TextField(blank=True)
345345
used = models.BooleanField(default=True)
346346
order = models.IntegerField(default=0)
347+
positions = models.ManyToManyField(BallotPositionName, blank=True)
347348

348349
def __unicode__(self):
349350
return self.name
@@ -355,7 +356,7 @@ class BallotDocEvent(DocEvent):
355356
ballot_type = models.ForeignKey(BallotType)
356357

357358
class BallotPositionDocEvent(DocEvent):
358-
# ballot = models.ForeignKey(BallotDocEvent, null=True)
359+
ballot = models.ForeignKey(BallotDocEvent, null=True)
359360
ad = models.ForeignKey(Person)
360361
pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord")
361362
discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True)

ietf/doc/utils.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def needed_ballot_positions(doc, active_positions):
6868
answer.append("Needs a YES.")
6969
if blocking:
7070
if blocking:
71-
answer.append("Has a %s." % blocking[0].name.upper())
71+
answer.append("Has a %s." % blocking[0].pos.name.upper())
7272
else:
7373
answer.append("Has %d %s." % (len(blocking), blocking[0].name.upper()))
7474
needed = 1
@@ -125,6 +125,3 @@ def augment_with_telechat_date(docs):
125125
seen.add(e.doc_id)
126126

127127
return docs
128-
129-
130-

ietf/idrfc/urls.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@
4444
url(r'^(?P<name>[A-Za-z0-9.-]+)/((?P<rev>[0-9-]+)/)?$', views_doc.document_main, name="doc_view"),
4545
url(r'^(?P<name>[A-Za-z0-9.-]+)/history/$', views_doc.document_history, name="doc_history"),
4646
url(r'^(?P<name>[A-Za-z0-9.-]+)/writeup/$', views_doc.document_writeup, name="doc_writeup"),
47-
url(r'^(?P<name>[A-Za-z0-9.-]+)/ballot/((?P<ballot>[A-Za-z0-9.-]+)/)?$', views_doc.document_ballot, name="doc_ballot"),
47+
url(r'^(?P<name>[A-Za-z0-9.-]+)/ballot/(?P<ballot_id>[A-Za-z0-9.-]+)/position/$', views_ballot.edit_position, name='doc_edit_position'),
48+
url(r'^(?P<name>[A-Za-z0-9.-]+)/ballot/(?P<ballot_id>[A-Za-z0-9.-]+)/emailposition/$', views_ballot.send_ballot_comment, name='doc_send_ballot_comment'),
49+
url(r'^(?P<name>[A-Za-z0-9.-]+)/ballot/(?P<ballot_id>[A-Za-z0-9.-]+)/$', views_doc.document_ballot, name="doc_ballot"),
50+
url(r'^(?P<name>[A-Za-z0-9.-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"),
4851
(r'^(?P<name>[A-Za-z0-9.-]+)/doc.json$', views_doc.document_debug),
4952
(r'^(?P<name>[A-Za-z0-9.-]+)/_ballot.data$', views_doc.ballot_html), # why is this url so weird instead of just ballot.html?
5053
(r'^(?P<name>[A-Za-z0-9.-]+)/ballot.tsv$', views_doc.ballot_tsv),
@@ -55,10 +58,8 @@
5558
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/requestresurrect/$', views_edit.request_resurrect, name='doc_request_resurrect'),
5659
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/resurrect/$', views_edit.resurrect, name='doc_resurrect'),
5760
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/addcomment/$', views_edit.add_comment, name='doc_add_comment'),
58-
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/position/$', views_ballot.edit_position, name='doc_edit_position'),
5961
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/deferballot/$', views_ballot.defer_ballot, name='doc_defer_ballot'),
6062
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/undeferballot/$', views_ballot.undefer_ballot, name='doc_undefer_ballot'),
61-
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/sendballotcomment/$', views_ballot.send_ballot_comment, name='doc_send_ballot_comment'),
6263
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/lastcalltext/$', views_ballot.lastcalltext, name='doc_ballot_lastcall'),
6364
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/ballotwriteupnotes/$', views_ballot.ballot_writeupnotes, name='doc_ballot_writeupnotes'),
6465
url(r'^(?P<name>[A-Za-z0-9.-]+)/edit/approvaltext/$', views_ballot.ballot_approvaltext, name='doc_ballot_approvaltext'),

ietf/idrfc/views_ballot.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
from django.template import RequestContext
1111
from django import forms
1212
from django.utils.html import strip_tags
13+
from django.utils import simplejson
1314
from django.conf import settings
1415

1516
from ietf.utils.mail import send_mail_text, send_mail_preformatted
16-
from ietf.ietfauth.decorators import group_required
17+
from ietf.ietfauth.decorators import group_required, role_required
1718
from ietf.idtracker.templatetags.ietf_filters import in_group
1819
from ietf.ietfauth.decorators import has_role
1920
from ietf.idtracker.models import *
@@ -27,6 +28,7 @@
2728

2829
from ietf.doc.models import *
2930
from ietf.name.models import BallotPositionName
31+
from ietf.person.models import Person
3032

3133

3234
BALLOT_CHOICES = (("yes", "Yes"),
@@ -215,27 +217,30 @@ class EditPositionFormREDESIGN(forms.Form):
215217
comment = forms.CharField(required=False, widget=forms.Textarea)
216218
return_to_url = forms.CharField(required=False, widget=forms.HiddenInput)
217219

220+
def __init__(self, *args, **kwargs):
221+
ballot_type = kwargs.pop("ballot_type")
222+
super(EditPositionForm, self).__init__(*args, **kwargs)
223+
self.fields['position'].queryset = ballot_type.positions.order_by('order')
224+
218225
def clean_discuss(self):
219226
entered_discuss = self.cleaned_data["discuss"]
220227
entered_pos = self.cleaned_data["position"]
221228
if entered_pos.slug == "discuss" and not entered_discuss:
222229
raise forms.ValidationError("You must enter a non-empty discuss")
223230
return entered_discuss
224231

225-
@group_required('Area_Director','Secretariat')
226-
def edit_positionREDESIGN(request, name):
227-
"""Vote and edit discuss and comment on Internet Draft as Area Director."""
232+
@role_required('Area Director','Secretariat')
233+
def edit_positionREDESIGN(request, name, ballot_id):
234+
"""Vote and edit discuss and comment on document as Area Director."""
228235
doc = get_object_or_404(Document, docalias__name=name)
229-
started_process = doc.latest_event(type="started_iesg_process")
230-
if not doc.get_state("draft-iesg") or not started_process:
231-
raise Http404()
236+
ballot = get_object_or_404(BallotDocEvent, type="created_ballot", pk=ballot_id, doc=doc)
232237

233238
ad = login = request.user.get_profile()
234239

235240
if 'HTTP_REFERER' in request.META:
236241
return_to_url = request.META['HTTP_REFERER']
237242
else:
238-
return_to_url = doc.get_absolute_url()
243+
return_to_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot=ballot_id))
239244

240245
# if we're in the Secretariat, we can select an AD to act as stand-in for
241246
if not has_role(request.user, "Area Director"):
@@ -245,12 +250,11 @@ def edit_positionREDESIGN(request, name):
245250
from ietf.person.models import Person
246251
ad = get_object_or_404(Person, pk=ad_id)
247252

248-
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time)
253+
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
249254

250255
if request.method == 'POST':
251-
form = EditPositionForm(request.POST)
256+
form = EditPositionForm(request.POST, ballot_type=ballot.ballot_type)
252257
if form.is_valid():
253-
254258
# save the vote
255259
clean = form.cleaned_data
256260

@@ -259,6 +263,7 @@ def edit_positionREDESIGN(request, name):
259263

260264
pos = BallotPositionDocEvent(doc=doc, by=login)
261265
pos.type = "changed_ballot_position"
266+
pos.ballot = ballot
262267
pos.ad = ad
263268
pos.pos = clean["position"]
264269
pos.comment = clean["comment"].strip()
@@ -269,7 +274,7 @@ def edit_positionREDESIGN(request, name):
269274
changes = []
270275
added_events = []
271276
# possibly add discuss/comment comments to history trail
272-
# so it's easy to see
277+
# so it's easy to see what's happened
273278
old_comment = old_pos.comment if old_pos else ""
274279
if pos.comment != old_comment:
275280
pos.comment_time = pos.time
@@ -291,7 +296,8 @@ def edit_positionREDESIGN(request, name):
291296
e = DocEvent(doc=doc, by=login)
292297
e.by = ad # otherwise we can't see who's saying it
293298
e.type = "added_comment"
294-
e.desc = "[Ballot discuss]\n" + pos.discuss
299+
e.desc = "[Ballot %s]\n" % pos.pos.name.lower()
300+
e.desc += pos.discuss
295301
added_events.append(e)
296302

297303
# figure out a description
@@ -311,13 +317,13 @@ def edit_positionREDESIGN(request, name):
311317
pos.save()
312318

313319
for e in added_events:
314-
e.save() # save them after the position is saved to get later id
320+
e.save() # save them after the position is saved to get later id for sorting order
315321

316322
if request.POST.get("send_mail"):
317323
qstr = "?return_to_url=%s" % return_to_url
318324
if request.GET.get('ad'):
319325
qstr += "&ad=%s" % request.GET.get('ad')
320-
return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.name)) + qstr)
326+
return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.name, ballot_id=ballot_id)) + qstr)
321327
elif request.POST.get("Defer"):
322328
return HttpResponseRedirect(urlreverse("doc_defer_ballot", kwargs=dict(name=doc)))
323329
elif request.POST.get("Undefer"):
@@ -334,10 +340,12 @@ def edit_positionREDESIGN(request, name):
334340
if return_to_url:
335341
initial['return_to_url'] = return_to_url
336342

337-
form = EditPositionForm(initial=initial)
343+
form = EditPositionForm(initial=initial, ballot_type=ballot.ballot_type)
344+
345+
blocking_positions = dict((p.pk, p.name) for p in form.fields["position"].queryset.all() if p.blocking)
338346

339347
ballot_deferred = None
340-
if doc.get_state_slug("draft-iesg") == "defer":
348+
if doc.get_state_slug("%s-iesg" % doc.type_id) == "defer":
341349
ballot_deferred = doc.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
342350

343351
return render_to_response('idrfc/edit_positionREDESIGN.html',
@@ -347,6 +355,8 @@ def edit_positionREDESIGN(request, name):
347355
return_to_url=return_to_url,
348356
old_pos=old_pos,
349357
ballot_deferred=ballot_deferred,
358+
show_discuss_text=old_pos and old_pos.pos.blocking,
359+
blocking_positions=simplejson.dumps(blocking_positions),
350360
),
351361
context_instance=RequestContext(request))
352362

@@ -427,42 +437,39 @@ def send_ballot_comment(request, name):
427437
),
428438
context_instance=RequestContext(request))
429439

430-
@group_required('Area_Director','Secretariat')
431-
def send_ballot_commentREDESIGN(request, name):
432-
"""Email Internet Draft ballot discuss/comment for area director."""
440+
@role_required('Area Director','Secretariat')
441+
def send_ballot_commentREDESIGN(request, name, ballot_id):
442+
"""Email document ballot position discuss/comment for Area Director."""
433443
doc = get_object_or_404(Document, docalias__name=name)
434-
started_process = doc.latest_event(type="started_iesg_process")
435-
if not started_process:
436-
raise Http404()
444+
ballot = get_object_or_404(BallotDocEvent, type="created_ballot", pk=ballot_id, doc=doc)
437445

438446
ad = login = request.user.get_profile()
439447

440448
return_to_url = request.GET.get('return_to_url')
441449
if not return_to_url:
442-
return_to_url = doc.get_absolute_url()
450+
return_to_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot=ballot_id))
443451

444452
if 'HTTP_REFERER' in request.META:
445453
back_url = request.META['HTTP_REFERER']
446454
else:
447-
back_url = doc.get_absolute_url()
455+
back_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot=ballot_id))
448456

449457
# if we're in the Secretariat, we can select an AD to act as stand-in for
450458
if not has_role(request.user, "Area Director"):
451459
ad_id = request.GET.get('ad')
452460
if not ad_id:
453461
raise Http404()
454-
from ietf.person.models import Person
455462
ad = get_object_or_404(Person, pk=ad_id)
456463

457-
pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time)
464+
pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
458465
if not pos:
459466
raise Http404()
460467

461468
subj = []
462469
d = ""
463-
if pos.pos == "discuss" and pos.discuss:
470+
if pos.pos.blocking and pos.discuss:
464471
d = pos.discuss
465-
subj.append("DISCUSS")
472+
subj.append(pos.pos.name.upper())
466473
c = ""
467474
if pos.comment:
468475
c = pos.comment

ietf/idrfc/views_doc.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,14 @@ def document_writeup(request, name):
239239
),
240240
context_instance=RequestContext(request))
241241

242-
def document_ballot_content(request, name, ballot, editable=True):
243-
doc = get_object_or_404(Document, docalias__name=name)
244-
245-
if ballot != None:
246-
b = doc.latest_event(BallotDocEvent, type="created_ballot", id=ballot)
242+
def document_ballot_content(request, doc, ballot_id, editable=True):
243+
"""Render HTML string with content of ballot page."""
244+
if ballot_id != None:
245+
ballot = doc.latest_event(BallotDocEvent, type="created_ballot", pk=ballot)
247246
else:
248-
b = doc.latest_event(BallotDocEvent, type="created_ballot")
247+
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
249248

250-
if not b:
249+
if not ballot:
251250
raise Http404()
252251

253252
deferred = None
@@ -260,13 +259,12 @@ def document_ballot_content(request, name, ballot, editable=True):
260259

261260
positions = []
262261
seen = {}
263-
# FIXME: restrict on ballot
264-
for e in BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position").select_related('ad', 'pos').order_by("-time", '-id'):
262+
for e in BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ballot=ballot).select_related('ad', 'pos').order_by("-time", '-id'):
265263
if e.ad not in seen:
266-
e.old_ad = e.ad in active_ads
264+
e.old_ad = e.ad not in active_ads
267265
e.old_positions = []
268266
positions.append(e)
269-
seen[e.ad] = pos
267+
seen[e.ad] = e
270268
else:
271269
latest = seen[e.ad]
272270
if latest.old_positions:
@@ -275,7 +273,7 @@ def document_ballot_content(request, name, ballot, editable=True):
275273
prev = latest
276274

277275
if e.pos != prev.pos:
278-
latest.old_positions.append(pos)
276+
latest.old_positions.append(e)
279277

280278
# add any missing ADs through fake No Record events
281279
for ad in active_ads:
@@ -290,32 +288,36 @@ def document_ballot_content(request, name, ballot, editable=True):
290288
position_groups = []
291289
for n in BallotPositionName.objects.filter(slug__in=[p.pos_id for p in positions]).order_by('order'):
292290
g = (n, [p for p in positions if p.pos_id == n.slug])
291+
g[1].sort(key=lambda p: (p.old_ad, p.ad.plain_name()))
293292
if n.blocking:
294293
position_groups.insert(0, g)
295294
else:
296295
position_groups.append(g)
297296

298297
summary = needed_ballot_positions(doc, [p for p in positions if not p.old_ad])
299298

299+
text_positions = [p for p in positions if p.discuss or p.comment]
300+
text_positions.sort(key=lambda p: (p.old_ad, p.ad.plain_name()))
301+
300302
return render_to_string("idrfc/document_ballot_content.html",
301303
dict(doc=doc,
302-
ballot=b,
304+
ballot=ballot,
303305
position_groups=position_groups,
304-
positions=positions,
306+
text_positions=text_positions,
305307
editable=editable,
306308
deferred=deferred,
307309
summary=summary,
308310
),
309311
context_instance=RequestContext(request))
310312

311-
def document_ballot(request, name, ballot=None):
313+
def document_ballot(request, name, ballot_id=None):
312314
if name.lower().startswith("draft") or name.lower().startswith("rfc"):
313315
return document_main_idrfc(request, name, "ballot")
314316

315317
doc = get_object_or_404(Document, docalias__name=name)
316318
top = render_document_top(request, doc, "ballot")
317319

318-
c = document_ballot_content(request, name, ballot, editable=True)
320+
c = document_ballot_content(request, doc, ballot_id, editable=True)
319321

320322
return render_to_response("idrfc/document_ballot.html",
321323
dict(doc=doc,

ietf/name/migrations/0002_auto__add_field_ballotpositionname_blocking.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,12 @@ def forwards(self, orm):
1111
# Adding field 'BallotPositionName.blocking'
1212
db.add_column('name_ballotpositionname', 'blocking', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
1313

14-
# Adding M2M table for field valid_document_types on 'BallotPositionName'
15-
db.create_table('name_ballotpositionname_valid_document_types', (
16-
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
17-
('ballotpositionname', models.ForeignKey(orm['name.ballotpositionname'], null=False)),
18-
('doctypename', models.ForeignKey(orm['name.doctypename'], null=False))
19-
))
20-
db.create_unique('name_ballotpositionname_valid_document_types', ['ballotpositionname_id', 'doctypename_id'])
21-
2214

2315
def backwards(self, orm):
2416

2517
# Deleting field 'BallotPositionName.blocking'
2618
db.delete_column('name_ballotpositionname', 'blocking')
2719

28-
# Removing M2M table for field valid_document_types on 'BallotPositionName'
29-
db.delete_table('name_ballotpositionname_valid_document_types')
30-
3120

3221
models = {
3322
'name.ballotpositionname': {
@@ -37,8 +26,7 @@ def backwards(self, orm):
3726
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
3827
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
3928
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
40-
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
41-
'valid_document_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTypeName']", 'symmetrical': 'False', 'blank': 'True'})
29+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
4230
},
4331
'name.constraintname': {
4432
'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},

0 commit comments

Comments
 (0)