Skip to content

Commit e9f5b41

Browse files
committed
Extract ballot views from views_edit.py to prevent the file from getting too large, moving common helpers into utils.py
- Legacy-Id: 2283
1 parent 85481ed commit e9f5b41

4 files changed

Lines changed: 323 additions & 299 deletions

File tree

branch/iesg-tracker/ietf/idrfc/urls.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3232

3333
from django.conf.urls.defaults import patterns, url
34-
from ietf.idrfc import views_doc, views_search, views_edit, views
34+
from ietf.idrfc import views_doc, views_search, views_edit, views_ballot, views
3535

3636
urlpatterns = patterns('',
3737
(r'^/?$', views_search.search_main),
@@ -47,8 +47,8 @@
4747
url(r'^(?P<name>[^/]+)/edit/info/$', views_edit.edit_info, {}, name='doc_edit_info'),
4848
url(r'^(?P<name>[^/]+)/edit/resurrect/$', views_edit.request_resurrect, {}, name='doc_request_resurrect'),
4949
url(r'^(?P<name>[^/]+)/edit/addcomment/$', views_edit.add_comment, {}, name='doc_add_comment'),
50-
url(r'^(?P<name>[^/]+)/edit/position/$', views_edit.edit_position, {}, name='doc_edit_position'),
51-
url(r'^(?P<name>[^/]+)/edit/deferballot/$', views_edit.defer_ballot, {}, name='doc_defer_ballot'),
52-
url(r'^(?P<name>[^/]+)/edit/undeferballot/$', views_edit.undefer_ballot, {}, name='doc_undefer_ballot'),
53-
url(r'^(?P<name>[^/]+)/edit/sendballotcomment/$', views_edit.send_ballot_comment, {}, name='doc_send_ballot_comment'),
50+
url(r'^(?P<name>[^/]+)/edit/position/$', views_ballot.edit_position, {}, name='doc_edit_position'),
51+
url(r'^(?P<name>[^/]+)/edit/deferballot/$', views_ballot.defer_ballot, {}, name='doc_defer_ballot'),
52+
url(r'^(?P<name>[^/]+)/edit/undeferballot/$', views_ballot.undefer_ballot, {}, name='doc_undefer_ballot'),
53+
url(r'^(?P<name>[^/]+)/edit/sendballotcomment/$', views_ballot.send_ballot_comment, {}, name='doc_send_ballot_comment'),
5454
)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from ietf.idtracker.models import DocumentComment, BallotInfo, IESGLogin
2+
from ietf.idrfc.mails import *
3+
4+
def add_document_comment(request, doc, text, include_by=True, ballot=None):
5+
login = IESGLogin.objects.get(login_name=request.user.username)
6+
if include_by:
7+
text += " by %s" % login
8+
9+
c = DocumentComment()
10+
c.document = doc.idinternal
11+
c.public_flag = True
12+
c.version = doc.revision_display()
13+
c.comment_text = text
14+
c.created_by = login
15+
if ballot:
16+
c.ballot = ballot
17+
c.rfc_flag = doc.idinternal.rfc_flag
18+
c.save()
19+
20+
def make_last_call(request, doc):
21+
try:
22+
ballot = doc.idinternal.ballot
23+
except BallotInfo.DoesNotExist:
24+
ballot = BallotInfo()
25+
ballot.ballot = doc.idinternal.ballot_id
26+
ballot.active = False
27+
ballot.last_call_text = generate_last_call_announcement(request, doc)
28+
ballot.approval_text = generate_approval_mail(request, doc)
29+
ballot.ballot_writeup = render_to_string("idrfc/ballot_writeup.txt")
30+
ballot.save()
31+
32+
send_last_call_request(request, doc, ballot)
33+
add_document_comment(request, doc, "Last Call was requested")
34+
35+
def log_state_changed(request, doc, by):
36+
change = u"State changed to <b>%s</b> from <b>%s</b> by <b>%s</b>" % (
37+
doc.idinternal.docstate(),
38+
format_document_state(doc.idinternal.prev_state, doc.
39+
idinternal.prev_sub_state),
40+
by)
41+
42+
c = DocumentComment()
43+
c.document = doc.idinternal
44+
c.public_flag = True
45+
c.version = doc.revision_display()
46+
c.comment_text = change
47+
c.created_by = by
48+
c.result_state = doc.idinternal.cur_state
49+
c.origin_state = doc.idinternal.prev_state
50+
c.rfc_flag = doc.idinternal.rfc_flag
51+
c.save()
52+
53+
email_state_changed(request, doc, strip_tags(change))
54+
55+
return change
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# ballot management (voting, commenting, ...) for IESG members
2+
3+
import re, os
4+
from datetime import datetime, date, time, timedelta
5+
from django.http import HttpResponse, HttpResponseRedirect, Http404
6+
from django.shortcuts import render_to_response, get_object_or_404
7+
from django.core.urlresolvers import reverse as urlreverse
8+
from django.template.loader import render_to_string
9+
from django.template import RequestContext
10+
from django import forms
11+
from django.utils.html import strip_tags
12+
13+
from ietf.utils.mail import send_mail_text
14+
from ietf.ietfauth.decorators import group_required
15+
from ietf.idtracker.templatetags.ietf_filters import in_group
16+
from ietf.idtracker.models import *
17+
from ietf.iesg.models import *
18+
from ietf import settings
19+
from ietf.idrfc.mails import *
20+
from ietf.idrfc.utils import *
21+
22+
23+
BALLOT_CHOICES = (("yes", "Yes"),
24+
("noobj", "No Objection"),
25+
("discuss", "Discuss"),
26+
("abstain", "Abstain"),
27+
("recuse", "Recuse"),
28+
("", "No Record"),
29+
)
30+
31+
def position_to_ballot_choice(position):
32+
for v, label in BALLOT_CHOICES:
33+
if v and getattr(position, v):
34+
return v
35+
return ""
36+
37+
def position_label(position_value):
38+
return dict(BALLOT_CHOICES).get(position_value, "")
39+
40+
def get_ballot_info(ballot, area_director):
41+
pos = Position.objects.filter(ballot=ballot, ad=area_director)
42+
pos = pos[0] if pos else None
43+
44+
discuss = IESGDiscuss.objects.filter(ballot=ballot, ad=area_director)
45+
discuss = discuss[0] if discuss else None
46+
47+
comment = IESGComment.objects.filter(ballot=ballot, ad=area_director)
48+
comment = comment[0] if comment else None
49+
50+
return (pos, discuss, comment)
51+
52+
class EditPositionForm(forms.Form):
53+
position = forms.ChoiceField(choices=BALLOT_CHOICES, widget=forms.RadioSelect)
54+
discuss_text = forms.CharField(required=False, widget=forms.Textarea)
55+
comment_text = forms.CharField(required=False, widget=forms.Textarea)
56+
57+
@group_required('Area_Director','Secretariat')
58+
def edit_position(request, name):
59+
"""Vote and edit discuss and comment on Internet Draft as Area Director."""
60+
doc = get_object_or_404(InternetDraft, filename=name)
61+
if not doc.idinternal:
62+
raise Http404()
63+
64+
login = IESGLogin.objects.get(login_name=request.user.username)
65+
66+
pos, discuss, comment = get_ballot_info(doc.idinternal.ballot, login)
67+
68+
if request.method == 'POST':
69+
form = EditPositionForm(request.POST)
70+
if form.is_valid():
71+
# save the vote
72+
clean = form.cleaned_data
73+
vote = clean['position']
74+
if pos:
75+
# mark discuss as cleared (quirk from old system)
76+
if pos.discuss:
77+
pos.discuss = -1
78+
else:
79+
pos = Position(ballot=doc.idinternal.ballot, ad=login)
80+
pos.discuss = 0
81+
82+
old_vote = position_to_ballot_choice(pos)
83+
84+
pos.yes = pos.noobj = pos.abstain = pos.recuse = 0
85+
if vote:
86+
setattr(pos, vote, 1)
87+
88+
if pos.id:
89+
pos.save()
90+
if vote != old_vote:
91+
add_document_comment(request, doc, "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad, position_label(vote), position_label(old_vote)))
92+
elif vote:
93+
pos.save()
94+
add_document_comment(request, doc, "[Ballot Position Update] New position, %s, has been recorded" % position_label(vote))
95+
96+
# save discuss
97+
if (discuss and clean['discuss_text'] != discuss.text) or (clean['discuss_text'] and not discuss):
98+
if not discuss:
99+
discuss = IESGDiscuss(ballot=doc.idinternal.ballot, ad=login)
100+
101+
discuss.text = clean['discuss_text']
102+
discuss.date = date.today()
103+
discuss.revision = doc.revision_display()
104+
discuss.active = True
105+
discuss.save()
106+
107+
if discuss.text:
108+
add_document_comment(request, doc, discuss.text,
109+
ballot=DocumentComment.BALLOT_DISCUSS)
110+
111+
if pos.discuss < 1:
112+
IESGDiscuss.objects.filter(ballot=doc.idinternal.ballot, ad=pos.ad).update(active=False)
113+
114+
# similar for comment (could share code with discuss, but
115+
# it's maybe better to coalesce them in the model instead
116+
# than doing a clever hack here)
117+
if (comment and clean['comment_text'] != comment.text) or (clean['comment_text'] and not comment):
118+
if not comment:
119+
comment = IESGComment(ballot=doc.idinternal.ballot, ad=login)
120+
121+
comment.text = clean['comment_text']
122+
comment.date = date.today()
123+
comment.revision = doc.revision_display()
124+
comment.active = True
125+
comment.save()
126+
127+
if comment.text:
128+
add_document_comment(request, doc, comment.text,
129+
ballot=DocumentComment.BALLOT_COMMENT)
130+
131+
doc.idinternal.event_date = date.today()
132+
doc.idinternal.save()
133+
if request.POST.get("send_mail"):
134+
return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.filename)))
135+
else:
136+
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
137+
else:
138+
initial = {}
139+
if pos:
140+
initial['position'] = position_to_ballot_choice(pos)
141+
142+
if discuss:
143+
initial['discuss_text'] = discuss.text
144+
145+
if comment:
146+
initial['comment_text'] = comment.text
147+
148+
form = EditPositionForm(initial=initial)
149+
150+
return render_to_response('idrfc/edit_position.html',
151+
dict(doc=doc,
152+
form=form,
153+
discuss=discuss,
154+
comment=comment),
155+
context_instance=RequestContext(request))
156+
157+
@group_required('Area_Director','Secretariat')
158+
def send_ballot_comment(request, name):
159+
"""Email Internet Draft ballot discuss/comment for area director."""
160+
doc = get_object_or_404(InternetDraft, filename=name)
161+
if not doc.idinternal:
162+
raise Http404()
163+
164+
login = IESGLogin.objects.get(login_name=request.user.username)
165+
pos, discuss, comment = get_ballot_info(doc.idinternal.ballot, login)
166+
167+
subj = []
168+
d = ""
169+
if pos and pos.discuss == 1 and discuss and discuss.text:
170+
d = discuss.text
171+
subj.append("DISCUSS")
172+
c = ""
173+
if comment and comment.text:
174+
c = comment.text
175+
subj.append("COMMENT")
176+
177+
subject = "%s: %s" % (" and ".join(subj), doc.file_tag())
178+
body = render_to_string("idrfc/ballot_comment_mail.txt",
179+
dict(discuss=d, comment=c))
180+
frm = u"%s <%s>" % login.person.email()
181+
to = "iesg@ietf.org"
182+
183+
if request.method == 'POST':
184+
cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()]
185+
if request.POST.get("cc_state_change") and doc.idinternal.state_change_notice_to:
186+
cc.extend(doc.idinternal.state_change_notice_to.split(','))
187+
188+
send_mail_text(request, to, frm, subject, body, cc=", ".join(cc))
189+
190+
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
191+
192+
return render_to_response('idrfc/send_ballot_comment.html',
193+
dict(doc=doc,
194+
subject=subject,
195+
body=body,
196+
frm=frm,
197+
to=to,
198+
can_send=d or c),
199+
context_instance=RequestContext(request))
200+
201+
202+
@group_required('Area_Director','Secretariat')
203+
def defer_ballot(request, name):
204+
"""Signal post-pone of Internet Draft ballot, notifying relevant parties."""
205+
doc = get_object_or_404(InternetDraft, filename=name)
206+
if not doc.idinternal:
207+
raise Http404()
208+
209+
login = IESGLogin.objects.get(login_name=request.user.username)
210+
telechat_date = TelechatDates.objects.all()[0].date2
211+
212+
if request.method == 'POST':
213+
doc.idinternal.ballot.defer = True
214+
doc.idinternal.ballot.defer_by = login
215+
doc.idinternal.ballot.defer_date = date.today()
216+
doc.idinternal.ballot.save()
217+
218+
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.IESG_EVALUATION_DEFER), None)
219+
doc.idinternal.agenda = True
220+
doc.idinternal.telechat_date = telechat_date
221+
doc.idinternal.event_date = date.today()
222+
doc.idinternal.save()
223+
224+
email_ballot_deferred(request, doc, login, telechat_date)
225+
226+
log_state_changed(request, doc, login)
227+
228+
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
229+
230+
return render_to_response('idrfc/defer_ballot.html',
231+
dict(doc=doc,
232+
telechat_date=telechat_date),
233+
context_instance=RequestContext(request))
234+
235+
@group_required('Area_Director','Secretariat')
236+
def undefer_ballot(request, name):
237+
"""Delete deferral of Internet Draft ballot."""
238+
doc = get_object_or_404(InternetDraft, filename=name)
239+
if not doc.idinternal:
240+
raise Http404()
241+
242+
login = IESGLogin.objects.get(login_name=request.user.username)
243+
244+
if request.method == 'POST':
245+
doc.idinternal.ballot.defer = False
246+
doc.idinternal.ballot.save()
247+
248+
doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.IESG_EVALUATION), None)
249+
doc.idinternal.event_date = date.today()
250+
doc.idinternal.save()
251+
252+
log_state_changed(request, doc, login)
253+
254+
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
255+
256+
return render_to_response('idrfc/undefer_ballot.html',
257+
dict(doc=doc),
258+
context_instance=RequestContext(request))
259+

0 commit comments

Comments
 (0)