diff --git a/ietf/doc/admin.py b/ietf/doc/admin.py index cb36a0f4b..f25036074 100644 --- a/ietf/doc/admin.py +++ b/ietf/doc/admin.py @@ -111,6 +111,8 @@ class DocAliasAdmin(admin.ModelAdmin): admin.site.register(DocAlias, DocAliasAdmin) +admin.site.register(BallotType) + # events class DocEventAdmin(admin.ModelAdmin): @@ -124,12 +126,13 @@ class DocEventAdmin(admin.ModelAdmin): admin.site.register(DocEvent, DocEventAdmin) admin.site.register(NewRevisionDocEvent, DocEventAdmin) +admin.site.register(BallotDocEvent, DocEventAdmin) admin.site.register(WriteupDocEvent, DocEventAdmin) admin.site.register(LastCallDocEvent, DocEventAdmin) admin.site.register(TelechatDocEvent, DocEventAdmin) class BallotPositionDocEventAdmin(DocEventAdmin): - raw_id_fields = ["doc", "by", "ad"] + raw_id_fields = ["doc", "by", "ad", "ballot"] admin.site.register(BallotPositionDocEvent, BallotPositionDocEventAdmin) diff --git a/ietf/doc/models.py b/ietf/doc/models.py index acd256222..45808240e 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -344,6 +344,7 @@ class BallotType(models.Model): question = models.TextField(blank=True) used = models.BooleanField(default=True) order = models.IntegerField(default=0) + positions = models.ManyToManyField(BallotPositionName, blank=True) def __unicode__(self): return self.name @@ -355,7 +356,7 @@ class BallotDocEvent(DocEvent): ballot_type = models.ForeignKey(BallotType) class BallotPositionDocEvent(DocEvent): -# ballot = models.ForeignKey(BallotDocEvent, null=True) + ballot = models.ForeignKey(BallotDocEvent, null=True) ad = models.ForeignKey(Person) pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord") discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True) diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index 025889c29..7b926c93e 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -68,7 +68,7 @@ def needed_ballot_positions(doc, active_positions): answer.append("Needs a YES.") if blocking: if blocking: - answer.append("Has a %s." % blocking[0].name.upper()) + answer.append("Has a %s." % blocking[0].pos.name.upper()) else: answer.append("Has %d %s." % (len(blocking), blocking[0].name.upper())) needed = 1 @@ -125,6 +125,3 @@ def augment_with_telechat_date(docs): seen.add(e.doc_id) return docs - - - diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py index ed2f6d4b6..9079479bc 100644 --- a/ietf/idrfc/urls.py +++ b/ietf/idrfc/urls.py @@ -44,7 +44,10 @@ urlpatterns = patterns('', url(r'^(?P[A-Za-z0-9.-]+)/((?P[0-9-]+)/)?$', views_doc.document_main, name="doc_view"), url(r'^(?P[A-Za-z0-9.-]+)/history/$', views_doc.document_history, name="doc_history"), url(r'^(?P[A-Za-z0-9.-]+)/writeup/$', views_doc.document_writeup, name="doc_writeup"), - url(r'^(?P[A-Za-z0-9.-]+)/ballot/((?P[A-Za-z0-9.-]+)/)?$', views_doc.document_ballot, name="doc_ballot"), + url(r'^(?P[A-Za-z0-9.-]+)/ballot/(?P[A-Za-z0-9.-]+)/position/$', views_ballot.edit_position, name='doc_edit_position'), + url(r'^(?P[A-Za-z0-9.-]+)/ballot/(?P[A-Za-z0-9.-]+)/emailposition/$', views_ballot.send_ballot_comment, name='doc_send_ballot_comment'), + url(r'^(?P[A-Za-z0-9.-]+)/ballot/(?P[A-Za-z0-9.-]+)/$', views_doc.document_ballot, name="doc_ballot"), + url(r'^(?P[A-Za-z0-9.-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"), (r'^(?P[A-Za-z0-9.-]+)/doc.json$', views_doc.document_debug), (r'^(?P[A-Za-z0-9.-]+)/_ballot.data$', views_doc.ballot_html), # why is this url so weird instead of just ballot.html? (r'^(?P[A-Za-z0-9.-]+)/ballot.tsv$', views_doc.ballot_tsv), @@ -55,10 +58,8 @@ urlpatterns = patterns('', url(r'^(?P[A-Za-z0-9.-]+)/edit/requestresurrect/$', views_edit.request_resurrect, name='doc_request_resurrect'), url(r'^(?P[A-Za-z0-9.-]+)/edit/resurrect/$', views_edit.resurrect, name='doc_resurrect'), url(r'^(?P[A-Za-z0-9.-]+)/edit/addcomment/$', views_edit.add_comment, name='doc_add_comment'), - url(r'^(?P[A-Za-z0-9.-]+)/edit/position/$', views_ballot.edit_position, name='doc_edit_position'), url(r'^(?P[A-Za-z0-9.-]+)/edit/deferballot/$', views_ballot.defer_ballot, name='doc_defer_ballot'), url(r'^(?P[A-Za-z0-9.-]+)/edit/undeferballot/$', views_ballot.undefer_ballot, name='doc_undefer_ballot'), - url(r'^(?P[A-Za-z0-9.-]+)/edit/sendballotcomment/$', views_ballot.send_ballot_comment, name='doc_send_ballot_comment'), url(r'^(?P[A-Za-z0-9.-]+)/edit/lastcalltext/$', views_ballot.lastcalltext, name='doc_ballot_lastcall'), url(r'^(?P[A-Za-z0-9.-]+)/edit/ballotwriteupnotes/$', views_ballot.ballot_writeupnotes, name='doc_ballot_writeupnotes'), url(r'^(?P[A-Za-z0-9.-]+)/edit/approvaltext/$', views_ballot.ballot_approvaltext, name='doc_ballot_approvaltext'), diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py index 2e4d96271..2cdb2c3ed 100644 --- a/ietf/idrfc/views_ballot.py +++ b/ietf/idrfc/views_ballot.py @@ -10,10 +10,11 @@ from django.template.loader import render_to_string from django.template import RequestContext from django import forms from django.utils.html import strip_tags +from django.utils import simplejson from django.conf import settings from ietf.utils.mail import send_mail_text, send_mail_preformatted -from ietf.ietfauth.decorators import group_required +from ietf.ietfauth.decorators import group_required, role_required from ietf.idtracker.templatetags.ietf_filters import in_group from ietf.ietfauth.decorators import has_role from ietf.idtracker.models import * @@ -27,6 +28,7 @@ from ietf.idrfc.idrfc_wrapper import BallotWrapper from ietf.doc.models import * from ietf.name.models import BallotPositionName +from ietf.person.models import Person BALLOT_CHOICES = (("yes", "Yes"), @@ -215,6 +217,11 @@ class EditPositionFormREDESIGN(forms.Form): comment = forms.CharField(required=False, widget=forms.Textarea) return_to_url = forms.CharField(required=False, widget=forms.HiddenInput) + def __init__(self, *args, **kwargs): + ballot_type = kwargs.pop("ballot_type") + super(EditPositionForm, self).__init__(*args, **kwargs) + self.fields['position'].queryset = ballot_type.positions.order_by('order') + def clean_discuss(self): entered_discuss = self.cleaned_data["discuss"] entered_pos = self.cleaned_data["position"] @@ -222,20 +229,18 @@ class EditPositionFormREDESIGN(forms.Form): raise forms.ValidationError("You must enter a non-empty discuss") return entered_discuss -@group_required('Area_Director','Secretariat') -def edit_positionREDESIGN(request, name): - """Vote and edit discuss and comment on Internet Draft as Area Director.""" +@role_required('Area Director','Secretariat') +def edit_positionREDESIGN(request, name, ballot_id): + """Vote and edit discuss and comment on document as Area Director.""" doc = get_object_or_404(Document, docalias__name=name) - started_process = doc.latest_event(type="started_iesg_process") - if not doc.get_state("draft-iesg") or not started_process: - raise Http404() + ballot = get_object_or_404(BallotDocEvent, type="created_ballot", pk=ballot_id, doc=doc) ad = login = request.user.get_profile() if 'HTTP_REFERER' in request.META: return_to_url = request.META['HTTP_REFERER'] else: - return_to_url = doc.get_absolute_url() + return_to_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot=ballot_id)) # if we're in the Secretariat, we can select an AD to act as stand-in for if not has_role(request.user, "Area Director"): @@ -245,12 +250,11 @@ def edit_positionREDESIGN(request, name): from ietf.person.models import Person ad = get_object_or_404(Person, pk=ad_id) - old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) + old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot) if request.method == 'POST': - form = EditPositionForm(request.POST) + form = EditPositionForm(request.POST, ballot_type=ballot.ballot_type) if form.is_valid(): - # save the vote clean = form.cleaned_data @@ -259,6 +263,7 @@ def edit_positionREDESIGN(request, name): pos = BallotPositionDocEvent(doc=doc, by=login) pos.type = "changed_ballot_position" + pos.ballot = ballot pos.ad = ad pos.pos = clean["position"] pos.comment = clean["comment"].strip() @@ -269,7 +274,7 @@ def edit_positionREDESIGN(request, name): changes = [] added_events = [] # possibly add discuss/comment comments to history trail - # so it's easy to see + # so it's easy to see what's happened old_comment = old_pos.comment if old_pos else "" if pos.comment != old_comment: pos.comment_time = pos.time @@ -291,7 +296,8 @@ def edit_positionREDESIGN(request, name): e = DocEvent(doc=doc, by=login) e.by = ad # otherwise we can't see who's saying it e.type = "added_comment" - e.desc = "[Ballot discuss]\n" + pos.discuss + e.desc = "[Ballot %s]\n" % pos.pos.name.lower() + e.desc += pos.discuss added_events.append(e) # figure out a description @@ -311,13 +317,13 @@ def edit_positionREDESIGN(request, name): pos.save() for e in added_events: - e.save() # save them after the position is saved to get later id + e.save() # save them after the position is saved to get later id for sorting order if request.POST.get("send_mail"): qstr = "?return_to_url=%s" % return_to_url if request.GET.get('ad'): qstr += "&ad=%s" % request.GET.get('ad') - return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.name)) + qstr) + return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.name, ballot_id=ballot_id)) + qstr) elif request.POST.get("Defer"): return HttpResponseRedirect(urlreverse("doc_defer_ballot", kwargs=dict(name=doc))) elif request.POST.get("Undefer"): @@ -334,10 +340,12 @@ def edit_positionREDESIGN(request, name): if return_to_url: initial['return_to_url'] = return_to_url - form = EditPositionForm(initial=initial) + 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) ballot_deferred = None - if doc.get_state_slug("draft-iesg") == "defer": + if doc.get_state_slug("%s-iesg" % doc.type_id) == "defer": ballot_deferred = doc.latest_event(type="changed_document", desc__startswith="State changed to IESG Evaluation - Defer") return render_to_response('idrfc/edit_positionREDESIGN.html', @@ -347,6 +355,8 @@ def edit_positionREDESIGN(request, name): return_to_url=return_to_url, old_pos=old_pos, ballot_deferred=ballot_deferred, + show_discuss_text=old_pos and old_pos.pos.blocking, + blocking_positions=simplejson.dumps(blocking_positions), ), context_instance=RequestContext(request)) @@ -427,42 +437,39 @@ def send_ballot_comment(request, name): ), context_instance=RequestContext(request)) -@group_required('Area_Director','Secretariat') -def send_ballot_commentREDESIGN(request, name): - """Email Internet Draft ballot discuss/comment for area director.""" +@role_required('Area Director','Secretariat') +def send_ballot_commentREDESIGN(request, name, ballot_id): + """Email document ballot position discuss/comment for Area Director.""" doc = get_object_or_404(Document, docalias__name=name) - started_process = doc.latest_event(type="started_iesg_process") - if not started_process: - raise Http404() + ballot = get_object_or_404(BallotDocEvent, type="created_ballot", pk=ballot_id, doc=doc) ad = login = request.user.get_profile() return_to_url = request.GET.get('return_to_url') if not return_to_url: - return_to_url = doc.get_absolute_url() + return_to_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot=ballot_id)) if 'HTTP_REFERER' in request.META: back_url = request.META['HTTP_REFERER'] else: - back_url = doc.get_absolute_url() + back_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot=ballot_id)) # if we're in the Secretariat, we can select an AD to act as stand-in for if not has_role(request.user, "Area Director"): ad_id = request.GET.get('ad') if not ad_id: raise Http404() - from ietf.person.models import Person ad = get_object_or_404(Person, pk=ad_id) - pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time) + pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot) if not pos: raise Http404() subj = [] d = "" - if pos.pos == "discuss" and pos.discuss: + if pos.pos.blocking and pos.discuss: d = pos.discuss - subj.append("DISCUSS") + subj.append(pos.pos.name.upper()) c = "" if pos.comment: c = pos.comment diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py index f635c815c..85a6175c0 100644 --- a/ietf/idrfc/views_doc.py +++ b/ietf/idrfc/views_doc.py @@ -239,15 +239,14 @@ def document_writeup(request, name): ), context_instance=RequestContext(request)) -def document_ballot_content(request, name, ballot, editable=True): - doc = get_object_or_404(Document, docalias__name=name) - - if ballot != None: - b = doc.latest_event(BallotDocEvent, type="created_ballot", id=ballot) +def document_ballot_content(request, doc, ballot_id, editable=True): + """Render HTML string with content of ballot page.""" + if ballot_id != None: + ballot = doc.latest_event(BallotDocEvent, type="created_ballot", pk=ballot) else: - b = doc.latest_event(BallotDocEvent, type="created_ballot") + ballot = doc.latest_event(BallotDocEvent, type="created_ballot") - if not b: + if not ballot: raise Http404() deferred = None @@ -260,13 +259,12 @@ def document_ballot_content(request, name, ballot, editable=True): positions = [] seen = {} - # FIXME: restrict on ballot - for e in BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position").select_related('ad', 'pos').order_by("-time", '-id'): + for e in BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", ballot=ballot).select_related('ad', 'pos').order_by("-time", '-id'): if e.ad not in seen: - e.old_ad = e.ad in active_ads + e.old_ad = e.ad not in active_ads e.old_positions = [] positions.append(e) - seen[e.ad] = pos + seen[e.ad] = e else: latest = seen[e.ad] if latest.old_positions: @@ -275,7 +273,7 @@ def document_ballot_content(request, name, ballot, editable=True): prev = latest if e.pos != prev.pos: - latest.old_positions.append(pos) + latest.old_positions.append(e) # add any missing ADs through fake No Record events for ad in active_ads: @@ -290,6 +288,7 @@ def document_ballot_content(request, name, ballot, editable=True): position_groups = [] for n in BallotPositionName.objects.filter(slug__in=[p.pos_id for p in positions]).order_by('order'): g = (n, [p for p in positions if p.pos_id == n.slug]) + g[1].sort(key=lambda p: (p.old_ad, p.ad.plain_name())) if n.blocking: position_groups.insert(0, g) else: @@ -297,25 +296,28 @@ def document_ballot_content(request, name, ballot, editable=True): summary = needed_ballot_positions(doc, [p for p in positions if not p.old_ad]) + text_positions = [p for p in positions if p.discuss or p.comment] + text_positions.sort(key=lambda p: (p.old_ad, p.ad.plain_name())) + return render_to_string("idrfc/document_ballot_content.html", dict(doc=doc, - ballot=b, + ballot=ballot, position_groups=position_groups, - positions=positions, + text_positions=text_positions, editable=editable, deferred=deferred, summary=summary, ), context_instance=RequestContext(request)) -def document_ballot(request, name, ballot=None): +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") - c = document_ballot_content(request, name, ballot, editable=True) + c = document_ballot_content(request, doc, ballot_id, editable=True) return render_to_response("idrfc/document_ballot.html", dict(doc=doc, diff --git a/ietf/name/migrations/0002_auto__add_field_ballotpositionname_blocking.py b/ietf/name/migrations/0002_auto__add_field_ballotpositionname_blocking.py index 85f7eb68c..5355e726d 100644 --- a/ietf/name/migrations/0002_auto__add_field_ballotpositionname_blocking.py +++ b/ietf/name/migrations/0002_auto__add_field_ballotpositionname_blocking.py @@ -11,23 +11,12 @@ class Migration(SchemaMigration): # Adding field 'BallotPositionName.blocking' db.add_column('name_ballotpositionname', 'blocking', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) - # Adding M2M table for field valid_document_types on 'BallotPositionName' - db.create_table('name_ballotpositionname_valid_document_types', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('ballotpositionname', models.ForeignKey(orm['name.ballotpositionname'], null=False)), - ('doctypename', models.ForeignKey(orm['name.doctypename'], null=False)) - )) - db.create_unique('name_ballotpositionname_valid_document_types', ['ballotpositionname_id', 'doctypename_id']) - def backwards(self, orm): # Deleting field 'BallotPositionName.blocking' db.delete_column('name_ballotpositionname', 'blocking') - # Removing M2M table for field valid_document_types on 'BallotPositionName' - db.delete_table('name_ballotpositionname_valid_document_types') - models = { 'name.ballotpositionname': { @@ -37,8 +26,7 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), - 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'valid_document_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTypeName']", 'symmetrical': 'False', 'blank': 'True'}) + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) }, 'name.constraintname': { 'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'}, diff --git a/ietf/name/models.py b/ietf/name/models.py index b502ede45..75ffa1195 100644 --- a/ietf/name/models.py +++ b/ietf/name/models.py @@ -44,7 +44,6 @@ class DocReminderTypeName(NameModel): class BallotPositionName(NameModel): """ Yes, No Objection, Abstain, Discuss, Block, Recuse """ blocking = models.BooleanField(default=False) - valid_document_types = models.ManyToManyField(DocTypeName, blank=True) class GroupBallotPositionName(NameModel): """ Yes, No, Block, Abstain """ class MeetingTypeName(NameModel): diff --git a/ietf/templates/idrfc/document_ballot_content.html b/ietf/templates/idrfc/document_ballot_content.html index 8a40368a6..4c6319674 100644 --- a/ietf/templates/idrfc/document_ballot_content.html +++ b/ietf/templates/idrfc/document_ballot_content.html @@ -4,7 +4,7 @@ {% if editable and user|has_role:"Area Director,Secretariat" %}
{% if user|has_role:"Area Director" %} - + {% endif %}
@@ -23,7 +23,7 @@
{{ n.name }}
{% for p in positions %} -
{% if p.old_ad %}[{% endif %}{{ p.ad.plain_name }}{% if p.old_ad %}]{% endif %}{% if p.comment_text or p.discuss_text %} *{% endif %}
+
{% if p.old_ad %}[{% endif %}{{ p.ad.plain_name }}{% if p.old_ad %}]{% endif %}{% if p.comment_text or p.discuss_text %} *{% endif %}
{% if p.old_positions %}
(was {{ p.old_positions|join:", " }})
{% endif %} {% empty %} none @@ -37,20 +37,16 @@

Summary: {{ summary }}

-{% for p in positions|dictsort:"old_ad" %} -{% if p.comment_text or p.discuss_text %} +{% for p in text_positions %} +

{% if p.old_ad %}[{% endif %}{{ p.ad.plain_name }}{% if p.old_ad %}]{% endif %}

-

{% if p.old_ad %}[{%endif%}{{ p.plain_name }}{% if p.old_ad %}]{%endif%}

- -{% if p.discuss_text %} -

{{ p.pos.name }} ({{ p.discuss_date }})

-
{{ p.discuss_text|fill:"80"|escape }}
-{% endif %} - -{% if p.comment_text %} -

Comment ({{ p.comment_date }})

-
{{ p.comment_text|fill:"80"|escape }}
+{% if p.discuss %} +

{{ p.pos.name }} ({{ p.discuss_time|date:"Y-m-d" }})

+
{{ p.discuss|fill:"80"|escape }}
{% endif %} +{% if p.comment %} +

Comment ({{ p.comment_time|date:"Y-m-d" }})

+
{{ p.comment|fill:"80"|escape }}
{% endif %} {% endfor %} diff --git a/ietf/templates/idrfc/edit_positionREDESIGN.html b/ietf/templates/idrfc/edit_positionREDESIGN.html index daab1574b..f8f96610c 100644 --- a/ietf/templates/idrfc/edit_positionREDESIGN.html +++ b/ietf/templates/idrfc/edit_positionREDESIGN.html @@ -51,7 +51,7 @@ form.position-form .comment-text {
-
+