Merged in ^/branch/akayla/irsg/6.113.1.dev0 from peter@akayla.com.
This provides support for IRSG ballots, similar to the IESG ballots support which has been in use for quite some time. The IRSG ballots differ from IESG ballots in a number of ways, described in detail in the RFP and SoW for this work and implemented here. - Legacy-Id: 17164
This commit is contained in:
commit
6482254a03
|
@ -12,7 +12,7 @@ from .models import (StateType, State, RelatedDocument, DocumentAuthor, Document
|
|||
StateDocEvent, ConsensusDocEvent, BallotType, BallotDocEvent, WriteupDocEvent, LastCallDocEvent,
|
||||
TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
|
||||
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL,
|
||||
ReviewAssignmentDocEvent, IanaExpertDocEvent )
|
||||
ReviewAssignmentDocEvent, IanaExpertDocEvent, IRSGBallotDocEvent )
|
||||
|
||||
|
||||
class StateTypeAdmin(admin.ModelAdmin):
|
||||
|
@ -164,8 +164,12 @@ class DeletedEventAdmin(admin.ModelAdmin):
|
|||
admin.site.register(DeletedEvent, DeletedEventAdmin)
|
||||
|
||||
class BallotPositionDocEventAdmin(DocEventAdmin):
|
||||
raw_id_fields = ["doc", "by", "ad", "ballot"]
|
||||
raw_id_fields = ["doc", "by", "balloter", "ballot"]
|
||||
admin.site.register(BallotPositionDocEvent, BallotPositionDocEventAdmin)
|
||||
|
||||
class IRSGBallotDocEventAdmin(DocEventAdmin):
|
||||
raw_id_fields = ["doc", "by"]
|
||||
admin.site.register(IRSGBallotDocEvent, IRSGBallotDocEventAdmin)
|
||||
|
||||
class DocumentUrlAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'doc', 'tag', 'url', 'desc', ]
|
||||
|
|
|
@ -14,7 +14,8 @@ if six.PY3:
|
|||
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.doc.models import Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor, StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType
|
||||
from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor,
|
||||
StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, )
|
||||
from ietf.group.models import Group
|
||||
|
||||
def draft_name_generator(type_id,group,n):
|
||||
|
@ -192,6 +193,26 @@ class RgDraftFactory(BaseDocumentFactory):
|
|||
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
|
||||
|
||||
|
||||
class RgRfcFactory(RgDraftFactory):
|
||||
|
||||
alias2 = factory.RelatedFactory('ietf.doc.factories.DocAliasFactory','document',name=factory.Sequence(lambda n: 'rfc%04d'%(n+1000)))
|
||||
|
||||
std_level_id = 'inf'
|
||||
|
||||
@factory.post_generation
|
||||
def states(obj, create, extracted, **kwargs):
|
||||
if not create:
|
||||
return
|
||||
if extracted:
|
||||
for (state_type_id,state_slug) in extracted:
|
||||
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
|
||||
if not obj.get_state('draft-stream-irtf'):
|
||||
obj.set_state(State.objects.get(type_id='draft-stream-irtf', slug='pub'))
|
||||
else:
|
||||
obj.set_state(State.objects.get(type_id='draft',slug='rfc'))
|
||||
obj.set_state(State.objects.get(type_id='draft-stream-irtf', slug='pub'))
|
||||
|
||||
|
||||
class CharterFactory(BaseDocumentFactory):
|
||||
|
||||
type_id = 'charter'
|
||||
|
@ -296,6 +317,7 @@ class StateDocEventFactory(DocEventFactory):
|
|||
class BallotTypeFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = BallotType
|
||||
django_get_or_create = ('slug','doc_type_id')
|
||||
|
||||
doc_type_id = 'draft'
|
||||
slug = 'approve'
|
||||
|
@ -308,6 +330,13 @@ class BallotDocEventFactory(DocEventFactory):
|
|||
ballot_type = factory.SubFactory(BallotTypeFactory)
|
||||
type = 'created_ballot'
|
||||
|
||||
class IRSGBallotDocEventFactory(BallotDocEventFactory):
|
||||
class Meta:
|
||||
model = IRSGBallotDocEvent
|
||||
|
||||
duedate = datetime.datetime.now() + datetime.timedelta(days=14)
|
||||
ballot_type = factory.SubFactory(BallotTypeFactory, slug='irsg-approve')
|
||||
|
||||
class BallotPositionDocEventFactory(DocEventFactory):
|
||||
class Meta:
|
||||
model = BallotPositionDocEvent
|
||||
|
@ -319,6 +348,6 @@ class BallotPositionDocEventFactory(DocEventFactory):
|
|||
# separately and passing the same doc into thier factories.
|
||||
ballot = factory.SubFactory(BallotDocEventFactory)
|
||||
|
||||
ad = factory.SubFactory('ietf.person.factories.PersonFactory')
|
||||
balloter = factory.SubFactory('ietf.person.factories.PersonFactory')
|
||||
pos_id = 'discuss'
|
||||
|
||||
|
|
|
@ -382,7 +382,7 @@ def generate_issue_ballot_mail(request, doc, ballot):
|
|||
last_call_has_expired=last_call_has_expired,
|
||||
needed_ballot_positions=
|
||||
needed_ballot_positions(doc,
|
||||
list(doc.active_ballot().active_ad_positions().values())
|
||||
list(doc.active_ballot().active_balloter_positions().values())
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
62
ietf/doc/migrations/0027_add_irsg_doc_positions.py
Normal file
62
ietf/doc/migrations/0027_add_irsg_doc_positions.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.22 on 2019-08-03 10:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
# forward, reverse initially copied from migration 0004
|
||||
def forward(apps, schema_editor):
|
||||
State = apps.get_model('doc','State')
|
||||
State.objects.create(type_id='draft-stream-irtf',
|
||||
slug='irsg_review',
|
||||
name='IRSG Review',
|
||||
desc='IRSG Review',
|
||||
used=True,
|
||||
)
|
||||
BallotPositionName = apps.get_model('name','BallotPositionName')
|
||||
# desc, used, order, and blocking all have suitable defaults
|
||||
BallotPositionName.objects.create(slug="moretime",
|
||||
name="Need More Time",
|
||||
)
|
||||
BallotPositionName.objects.create(slug="notready",
|
||||
name="Not Ready",
|
||||
)
|
||||
|
||||
# Create a new ballot type for IRSG ballot
|
||||
# include positions for the ballot type
|
||||
BallotType = apps.get_model('doc','BallotType')
|
||||
bt = BallotType.objects.create(doc_type_id="draft",
|
||||
slug="irsg-approve",
|
||||
name="IRSG Approve",
|
||||
question="Is this draft ready for publication in the IRTF stream?",
|
||||
)
|
||||
bt.positions.set(['yes','noobj','recuse','notready','moretime'])
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
State = apps.get_model('doc','State')
|
||||
State.objects.filter(type_id__in=('draft-stream-irtf',), slug='irsg_review').delete()
|
||||
|
||||
Position = apps.get_model('name','BallotPositionName')
|
||||
for pos in ("moretime", "notready"):
|
||||
Position.objects.filter(slug=pos).delete()
|
||||
|
||||
IRSGBallot = apps.get_model('doc','BallotType')
|
||||
IRSGBallot.objects.filter(slug="irsg-approve").delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('doc', '0026_add_draft_rfceditor_state'),
|
||||
('name', '0007_fix_m2m_slug_id_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward,reverse),
|
||||
migrations.RenameField(
|
||||
model_name='ballotpositiondocevent',
|
||||
old_name='ad',
|
||||
new_name='balloter',
|
||||
),
|
||||
|
||||
]
|
25
ietf/doc/migrations/0028_irsgballotdocevent.py
Normal file
25
ietf/doc/migrations/0028_irsgballotdocevent.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.25 on 2019-10-10 10:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('doc', '0027_add_irsg_doc_positions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='IRSGBallotDocEvent',
|
||||
fields=[
|
||||
('ballotdocevent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='doc.BallotDocEvent')),
|
||||
('duedate', models.DateTimeField(blank=True, null=True)),
|
||||
],
|
||||
bases=('doc.ballotdocevent',),
|
||||
),
|
||||
]
|
|
@ -27,7 +27,7 @@ from ietf.name.models import ( DocTypeName, DocTagName, StreamName, IntendedStdL
|
|||
DocRelationshipName, DocReminderTypeName, BallotPositionName, ReviewRequestStateName, ReviewAssignmentStateName, FormalLanguageName,
|
||||
DocUrlTagName)
|
||||
from ietf.person.models import Email, Person
|
||||
from ietf.person.utils import get_active_ads
|
||||
from ietf.person.utils import get_active_balloters
|
||||
from ietf.utils import log
|
||||
from ietf.utils.admin import admin_link
|
||||
from ietf.utils.decorators import memoize
|
||||
|
@ -656,7 +656,7 @@ class Document(DocumentInfo):
|
|||
|
||||
def latest_event(self, *args, **filter_args):
|
||||
"""Get latest event of optional Python type and with filter
|
||||
arguments, e.g. d.latest_event(type="xyz") returns an DocEvent
|
||||
arguments, e.g. d.latest_event(type="xyz") returns a DocEvent
|
||||
while d.latest_event(WriteupDocEvent, type="xyz") returns a
|
||||
WriteupDocEvent event."""
|
||||
model = args[0] if args else DocEvent
|
||||
|
@ -1082,20 +1082,20 @@ class BallotType(models.Model):
|
|||
class BallotDocEvent(DocEvent):
|
||||
ballot_type = ForeignKey(BallotType)
|
||||
|
||||
def active_ad_positions(self):
|
||||
"""Return dict mapping each active AD to a current ballot position (or None if they haven't voted)."""
|
||||
def active_balloter_positions(self):
|
||||
"""Return dict mapping each active AD or IRSG member to a current ballot position (or None if they haven't voted)."""
|
||||
res = {}
|
||||
|
||||
active_ads = get_active_ads()
|
||||
positions = BallotPositionDocEvent.objects.filter(type="changed_ballot_position",ad__in=active_ads, ballot=self).select_related('ad', 'pos').order_by("-time", "-id")
|
||||
active_balloters = get_active_balloters(self.ballot_type)
|
||||
positions = BallotPositionDocEvent.objects.filter(type="changed_ballot_position",balloter__in=active_balloters, ballot=self).select_related('balloter', 'pos').order_by("-time", "-id")
|
||||
|
||||
for pos in positions:
|
||||
if pos.ad not in res:
|
||||
res[pos.ad] = pos
|
||||
if pos.balloter not in res:
|
||||
res[pos.balloter] = pos
|
||||
|
||||
for ad in active_ads:
|
||||
if ad not in res:
|
||||
res[ad] = None
|
||||
for balloter in active_balloters:
|
||||
if balloter not in res:
|
||||
res[balloter] = None
|
||||
return res
|
||||
|
||||
def all_positions(self):
|
||||
|
@ -1103,15 +1103,15 @@ class BallotDocEvent(DocEvent):
|
|||
|
||||
positions = []
|
||||
seen = {}
|
||||
active_ads = get_active_ads()
|
||||
for e in BallotPositionDocEvent.objects.filter(type="changed_ballot_position", ballot=self).select_related('ad', 'pos').order_by("-time", '-id'):
|
||||
if e.ad not in seen:
|
||||
e.old_ad = e.ad not in active_ads
|
||||
active_balloters = get_active_balloters(self.ballot_type)
|
||||
for e in BallotPositionDocEvent.objects.filter(type="changed_ballot_position", ballot=self).select_related('balloter', 'pos').order_by("-time", '-id'):
|
||||
if e.balloter not in seen:
|
||||
e.is_old_pos = e.balloter not in active_balloters
|
||||
e.old_positions = []
|
||||
positions.append(e)
|
||||
seen[e.ad] = e
|
||||
seen[e.balloter] = e
|
||||
else:
|
||||
latest = seen[e.ad]
|
||||
latest = seen[e.balloter]
|
||||
if latest.old_positions:
|
||||
prev = latest.old_positions[-1]
|
||||
else:
|
||||
|
@ -1126,25 +1126,27 @@ class BallotDocEvent(DocEvent):
|
|||
while p.old_positions and p.old_positions[-1].slug == "norecord":
|
||||
p.old_positions.pop()
|
||||
|
||||
# add any missing ADs through fake No Record events
|
||||
# add any missing ADs/IRSGers through fake No Record events
|
||||
if self.doc.active_ballot() == self:
|
||||
norecord = BallotPositionName.objects.get(slug="norecord")
|
||||
for ad in active_ads:
|
||||
if ad not in seen:
|
||||
e = BallotPositionDocEvent(type="changed_ballot_position", doc=self.doc, rev=self.doc.rev, ad=ad)
|
||||
e.by = ad
|
||||
for balloter in active_balloters:
|
||||
if balloter not in seen:
|
||||
e = BallotPositionDocEvent(type="changed_ballot_position", doc=self.doc, rev=self.doc.rev, balloter=balloter)
|
||||
e.by = balloter
|
||||
e.pos = norecord
|
||||
e.old_ad = False
|
||||
e.is_old_pos = False
|
||||
e.old_positions = []
|
||||
positions.append(e)
|
||||
|
||||
positions.sort(key=lambda p: (p.old_ad, p.ad.last_name()))
|
||||
positions.sort(key=lambda p: (p.is_old_pos, p.balloter.last_name()))
|
||||
return positions
|
||||
|
||||
class IRSGBallotDocEvent(BallotDocEvent):
|
||||
duedate = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
class BallotPositionDocEvent(DocEvent):
|
||||
ballot = ForeignKey(BallotDocEvent, null=True, default=None) # default=None is a temporary migration period fix, should be removed when charter branch is live
|
||||
ad = ForeignKey(Person)
|
||||
balloter = ForeignKey(Person)
|
||||
pos = ForeignKey(BallotPositionName, verbose_name="position", default="norecord")
|
||||
discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True)
|
||||
discuss_time = models.DateTimeField(help_text="Time discuss text was written", blank=True, null=True)
|
||||
|
|
|
@ -17,7 +17,7 @@ from ietf.doc.models import (BallotType, DeletedEvent, StateType, State, Documen
|
|||
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
|
||||
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
|
||||
ReviewRequestDocEvent, ReviewAssignmentDocEvent, EditedAuthorsDocEvent, DocumentURL,
|
||||
IanaExpertDocEvent )
|
||||
IanaExpertDocEvent, IRSGBallotDocEvent )
|
||||
|
||||
from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
|
||||
class BallotTypeResource(ModelResource):
|
||||
|
@ -531,7 +531,7 @@ class BallotPositionDocEventResource(ModelResource):
|
|||
doc = ToOneField(DocumentResource, 'doc')
|
||||
docevent_ptr = ToOneField(DocEventResource, 'docevent_ptr')
|
||||
ballot = ToOneField(BallotDocEventResource, 'ballot', null=True)
|
||||
ad = ToOneField(PersonResource, 'ad')
|
||||
balloter = ToOneField(PersonResource, 'balloter')
|
||||
pos = ToOneField(BallotPositionNameResource, 'pos')
|
||||
class Meta:
|
||||
cache = SimpleCache()
|
||||
|
@ -553,7 +553,7 @@ class BallotPositionDocEventResource(ModelResource):
|
|||
"doc": ALL_WITH_RELATIONS,
|
||||
"docevent_ptr": ALL_WITH_RELATIONS,
|
||||
"ballot": ALL_WITH_RELATIONS,
|
||||
"ad": ALL_WITH_RELATIONS,
|
||||
"balloter": ALL_WITH_RELATIONS,
|
||||
"pos": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.doc.register(BallotPositionDocEventResource())
|
||||
|
@ -738,3 +738,32 @@ class IanaExpertDocEventResource(ModelResource):
|
|||
"docevent_ptr": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.doc.register(IanaExpertDocEventResource())
|
||||
|
||||
|
||||
from ietf.person.resources import PersonResource
|
||||
class IRSGBallotDocEventResource(ModelResource):
|
||||
by = ToOneField(PersonResource, 'by')
|
||||
doc = ToOneField(DocumentResource, 'doc')
|
||||
docevent_ptr = ToOneField(DocEventResource, 'docevent_ptr')
|
||||
ballot_type = ToOneField(BallotTypeResource, 'ballot_type')
|
||||
ballotdocevent_ptr = ToOneField(BallotDocEventResource, 'ballotdocevent_ptr')
|
||||
class Meta:
|
||||
queryset = IRSGBallotDocEvent.objects.all()
|
||||
serializer = api.Serializer()
|
||||
cache = SimpleCache()
|
||||
#resource_name = 'irsgballotdocevent'
|
||||
ordering = ['ballotdocevent_ptr', ]
|
||||
filtering = {
|
||||
"id": ALL,
|
||||
"time": ALL,
|
||||
"type": ALL,
|
||||
"rev": ALL,
|
||||
"desc": ALL,
|
||||
"duedate": ALL,
|
||||
"by": ALL_WITH_RELATIONS,
|
||||
"doc": ALL_WITH_RELATIONS,
|
||||
"docevent_ptr": ALL_WITH_RELATIONS,
|
||||
"ballot_type": ALL_WITH_RELATIONS,
|
||||
"ballotdocevent_ptr": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.doc.register(IRSGBallotDocEventResource())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2019, All rights reserved.
|
||||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
|
||||
#
|
||||
|
@ -91,7 +91,7 @@ def ballot_icon(context, doc):
|
|||
else:
|
||||
return (1, pos.pos.order)
|
||||
|
||||
positions = list(ballot.active_ad_positions().items())
|
||||
positions = list(ballot.active_balloter_positions().items())
|
||||
positions.sort(key=sort_key)
|
||||
|
||||
right_click_string = ''
|
||||
|
@ -99,8 +99,8 @@ def ballot_icon(context, doc):
|
|||
right_click_string = 'oncontextmenu="window.location.href=\'%s\';return false;"' % urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk))
|
||||
|
||||
my_blocking = False
|
||||
for i, (ad, pos) in enumerate(positions):
|
||||
if user_is_person(user,ad) and pos and pos.pos.blocking:
|
||||
for i, (balloter, pos) in enumerate(positions):
|
||||
if user_is_person(user,balloter) and pos and pos.pos.blocking:
|
||||
my_blocking = True
|
||||
break
|
||||
|
||||
|
@ -153,7 +153,7 @@ def ballotposition(doc, user):
|
|||
if not ballot:
|
||||
return None
|
||||
|
||||
changed_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad__user=user, ballot=ballot)
|
||||
changed_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", balloter__user=user, ballot=ballot)
|
||||
if changed_pos:
|
||||
pos = changed_pos.pos
|
||||
else:
|
||||
|
|
|
@ -25,6 +25,7 @@ import debug # pyflakes:ignore
|
|||
from ietf.doc.models import ConsensusDocEvent
|
||||
from ietf.utils.text import wordwrap, fill, wrap_text_if_unwrapped
|
||||
from ietf.utils.html import sanitize_fragment
|
||||
from ietf.doc.models import BallotDocEvent
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -539,3 +540,20 @@ def charter_major_rev(rev):
|
|||
@stringfilter
|
||||
def charter_minor_rev(rev):
|
||||
return rev[3:5]
|
||||
|
||||
@register.filter()
|
||||
def can_defer(user,doc):
|
||||
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
|
||||
if ballot and (doc.type_id == "draft" or doc.type_id == "conflrev") and doc.stream_id == 'ietf' and has_role(user, 'Area Director,Secretariat'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@register.filter()
|
||||
def can_ballot(user,doc):
|
||||
if doc.stream_id == 'ietf' and user.person.role_set.filter(name="ad", group__type="area", group__state="active"):
|
||||
return True
|
||||
elif doc.stream_id == 'irtf' and has_role(user,'IRSG Member'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -240,7 +240,7 @@ class SearchTests(TestCase):
|
|||
ballot_type = BallotType.objects.get(doc_type_id='draft',slug='approve')
|
||||
ballot = BallotDocEventFactory(ballot_type=ballot_type, doc__states=[('draft-iesg','iesg-eva')])
|
||||
discuss_pos = BallotPositionName.objects.get(slug='discuss')
|
||||
discuss_other = BallotPositionDocEventFactory(ballot=ballot, doc=ballot.doc, ad=ad, pos=discuss_pos)
|
||||
discuss_other = BallotPositionDocEventFactory(ballot=ballot, doc=ballot.doc, balloter=ad, pos=discuss_pos)
|
||||
|
||||
r = self.client.get(urlreverse('ietf.doc.views_search.docs_for_ad', kwargs=dict(name=ad.full_name_as_key())))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
@ -743,7 +743,7 @@ class DocTestCase(TestCase):
|
|||
pos_id="yes",
|
||||
comment="Looks fine to me",
|
||||
comment_time=datetime.datetime.now(),
|
||||
ad=Person.objects.get(user__username="ad"),
|
||||
balloter=Person.objects.get(user__username="ad"),
|
||||
by=Person.objects.get(name="(System)"))
|
||||
|
||||
r = self.client.get(urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name)))
|
||||
|
@ -1119,7 +1119,7 @@ expand-draft-ietf-ames-test.all@virtual.ietf.org ames-author@example.ames, ames
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, 'draft-ietf-mars-test.all@ietf.org')
|
||||
self.assertContains(r, 'ballot_saved')
|
||||
self.assertContains(r, 'iesg_ballot_saved')
|
||||
|
||||
class DocumentMeetingTests(TestCase):
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2013-2019, All Rights Reserved
|
||||
#ad Copyright The IETF Trust 2013-2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -28,7 +28,7 @@ from ietf.utils.text import unwrap
|
|||
class EditPositionTests(TestCase):
|
||||
def test_edit_position(self):
|
||||
ad = Person.objects.get(user__username="ad")
|
||||
draft = IndividualDraftFactory(ad=ad)
|
||||
draft = IndividualDraftFactory(ad=ad,stream_id='ietf')
|
||||
ballot = create_ballot_if_not_open(None, draft, ad, 'approve')
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=draft.name,
|
||||
ballot_id=ballot.pk))
|
||||
|
@ -51,7 +51,7 @@ class EditPositionTests(TestCase):
|
|||
comment=" This is a test. \n "))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "discuss")
|
||||
self.assertTrue(" This is a discussion test." in pos.discuss)
|
||||
self.assertTrue(pos.discuss_time != None)
|
||||
|
@ -66,7 +66,7 @@ class EditPositionTests(TestCase):
|
|||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "noobj")
|
||||
self.assertEqual(draft.docevent_set.count(), events_before + 1)
|
||||
self.assertTrue("Position for" in pos.desc)
|
||||
|
@ -77,7 +77,7 @@ class EditPositionTests(TestCase):
|
|||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "norecord")
|
||||
self.assertEqual(draft.docevent_set.count(), events_before + 1)
|
||||
self.assertTrue("Position for" in pos.desc)
|
||||
|
@ -88,7 +88,7 @@ class EditPositionTests(TestCase):
|
|||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "norecord")
|
||||
self.assertEqual(draft.docevent_set.count(), events_before + 2)
|
||||
self.assertTrue("Ballot comment text updated" in pos.desc)
|
||||
|
@ -115,7 +115,7 @@ class EditPositionTests(TestCase):
|
|||
)
|
||||
self.assertContains(r, "Done")
|
||||
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "discuss")
|
||||
self.assertTrue(" This is a discussion test." in pos.discuss)
|
||||
self.assertTrue(pos.discuss_time != None)
|
||||
|
@ -132,7 +132,7 @@ class EditPositionTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "noobj")
|
||||
self.assertEqual(draft.docevent_set.count(), events_before + 1)
|
||||
self.assertTrue("Position for" in pos.desc)
|
||||
|
@ -150,7 +150,7 @@ class EditPositionTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "norecord")
|
||||
self.assertEqual(draft.docevent_set.count(), events_before + 1)
|
||||
self.assertTrue("Position for" in pos.desc)
|
||||
|
@ -165,7 +165,7 @@ class EditPositionTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "norecord")
|
||||
self.assertEqual(draft.docevent_set.count(), events_before + 2)
|
||||
self.assertTrue("Ballot comment text updated" in pos.desc)
|
||||
|
@ -181,7 +181,7 @@ class EditPositionTests(TestCase):
|
|||
ballot = create_ballot_if_not_open(None, draft, ad, 'approve')
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=draft.name, ballot_id=ballot.pk))
|
||||
ad = Person.objects.get(name="Areað Irector")
|
||||
url += "?ad=%s" % ad.pk
|
||||
url += "?balloter=%s" % ad.pk
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
|
@ -195,7 +195,7 @@ class EditPositionTests(TestCase):
|
|||
r = self.client.post(url, dict(position="discuss", discuss="Test discuss text"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
pos = draft.latest_event(BallotPositionDocEvent, ad=ad)
|
||||
pos = draft.latest_event(BallotPositionDocEvent, balloter=ad)
|
||||
self.assertEqual(pos.pos.slug, "discuss")
|
||||
self.assertEqual(pos.discuss, "Test discuss text")
|
||||
self.assertTrue("New position" in pos.desc)
|
||||
|
@ -229,7 +229,7 @@ class EditPositionTests(TestCase):
|
|||
|
||||
BallotPositionDocEvent.objects.create(
|
||||
doc=draft, rev=draft.rev, type="changed_ballot_position",
|
||||
by=ad, ad=ad, ballot=ballot, pos=BallotPositionName.objects.get(slug="discuss"),
|
||||
by=ad, balloter=ad, ballot=ballot, pos=BallotPositionName.objects.get(slug="discuss"),
|
||||
discuss="This draft seems to be lacking a clearer title?",
|
||||
discuss_time=datetime.datetime.now(),
|
||||
comment="Test!",
|
||||
|
|
519
ietf/doc/tests_irsg_ballot.py
Normal file
519
ietf/doc/tests_irsg_ballot.py
Normal file
|
@ -0,0 +1,519 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
# import datetime
|
||||
# from pyquery import PyQuery
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
import datetime
|
||||
|
||||
from django.urls import reverse as urlreverse
|
||||
|
||||
from ietf.utils.mail import outbox, empty_outbox, get_payload
|
||||
from ietf.utils.test_utils import TestCase, unicontent, login_testing_unauthorized
|
||||
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory, RgDraftFactory, RgRfcFactory, BallotDocEventFactory, IRSGBallotDocEventFactory, BallotPositionDocEventFactory
|
||||
from ietf.doc.models import BallotDocEvent, BallotPositionDocEvent
|
||||
from ietf.doc.utils import create_ballot_if_not_open, close_ballot
|
||||
from ietf.person.utils import get_active_irsg, get_active_ads
|
||||
from ietf.group.factories import RoleFactory
|
||||
from ietf.person.models import Person
|
||||
|
||||
|
||||
class IssueIRSGBallotTests(TestCase):
|
||||
|
||||
def test_issue_ballot_button(self):
|
||||
|
||||
# creates empty drafts with lots of values filled in
|
||||
individual_draft = IndividualDraftFactory()
|
||||
wg_draft = WgDraftFactory()
|
||||
rg_draft = RgDraftFactory()
|
||||
rg_rfc = RgRfcFactory()
|
||||
|
||||
# login as an IRTF chair
|
||||
self.client.login(username='irtf-chair', password='irtf-chair+password')
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=individual_draft.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertNotIn("Issue IRSG ballot", unicontent(r))
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=wg_draft.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertNotIn("Issue IRSG ballot", unicontent(r))
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=rg_draft.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertIn("Issue IRSG ballot", unicontent(r))
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=rg_rfc.name))
|
||||
r = self.client.get(url, follow = True)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertNotIn("Issue IRSG ballot", unicontent(r))
|
||||
|
||||
self.client.logout()
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=rg_draft.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertNotIn("Issue IRSG ballot", unicontent(r))
|
||||
|
||||
def test_close_ballot_button(self):
|
||||
|
||||
# creates empty drafts with lots of values filled in
|
||||
rg_draft1 = RgDraftFactory()
|
||||
rg_draft2 = RgDraftFactory()
|
||||
rg_rfc = RgRfcFactory()
|
||||
iesgmember = get_active_ads()[0]
|
||||
|
||||
# Login as the IRTF chair
|
||||
self.client.login(username='irtf-chair', password='irtf-chair+password')
|
||||
|
||||
# Set the two IRTF ballots in motion
|
||||
|
||||
# Get the page with the Issue IRSG Ballot Yes/No buttons
|
||||
url = urlreverse('ietf.doc.views_ballot.issue_irsg_ballot',kwargs=dict(name=rg_draft1.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# Press the Yes button
|
||||
r = self.client.post(url,dict(irsg_button="Yes", duedate="2038-01-19"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertTrue(rg_draft1.ballot_open('irsg-approve'))
|
||||
|
||||
# Get the page with the Issue IRSG Ballot Yes/No buttons
|
||||
url = urlreverse('ietf.doc.views_ballot.issue_irsg_ballot',kwargs=dict(name=rg_draft2.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# Press the Yes button
|
||||
r = self.client.post(url,dict(irsg_button="Yes", duedate="2038-01-18"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertTrue(rg_draft2.ballot_open('irsg-approve'))
|
||||
|
||||
# Logout - the Close button should not be available
|
||||
self.client.logout()
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=rg_draft1.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertNotIn("Close IRSG ballot", unicontent(r))
|
||||
|
||||
# Login as an IESG member to see if the ballot close button appears
|
||||
self.client.login(username=iesgmember.user.username, password=iesgmember.user.username+"password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertNotIn("Close IRSG ballot", unicontent(r))
|
||||
|
||||
# Try to get the ballot closing page directly
|
||||
url = urlreverse('ietf.doc.views_ballot.close_irsg_ballot',kwargs=dict(name=rg_draft1.name))
|
||||
r = self.client.get(url)
|
||||
self.assertNotEqual(r.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
|
||||
# Login again as the IRTF chair
|
||||
self.client.login(username='irtf-chair', password='irtf-chair+password')
|
||||
|
||||
# The close button should now be available
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=rg_draft1.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertIn("Close IRSG ballot", unicontent(r))
|
||||
|
||||
# Get the page with the Close IRSG Ballot Yes/No buttons
|
||||
url = urlreverse('ietf.doc.views_ballot.close_irsg_ballot',kwargs=dict(name=rg_draft1.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# Press the Yes button
|
||||
r = self.client.post(url,dict(irsg_button="Yes"))
|
||||
self.assertEqual(r.status_code,302)
|
||||
# Expect the draft not to have an open IRSG ballot anymore
|
||||
self.assertFalse(rg_draft1.ballot_open('irsg-approve'))
|
||||
|
||||
# Login as the Secretariat
|
||||
self.client.login(username='secretary', password='secretary+password')
|
||||
|
||||
# The close button should now be available
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=rg_draft2.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertIn("Close IRSG ballot", unicontent(r))
|
||||
|
||||
# Get the page with the Close IRSG Ballot Yes/No buttons
|
||||
url = urlreverse('ietf.doc.views_ballot.close_irsg_ballot',kwargs=dict(name=rg_draft2.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# Press the Yes button
|
||||
r = self.client.post(url,dict(irsg_button="Yes"))
|
||||
self.assertEqual(r.status_code,302)
|
||||
# Expect the draft not to have an open IRSG ballot anymore
|
||||
self.assertFalse(rg_draft2.ballot_open('irsg-approve'))
|
||||
|
||||
# Individual, IETF, and RFC docs should not show the Close button. Sample test using IRTF RFC:
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=rg_rfc.name))
|
||||
r = self.client.get(url, follow = True)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertNotIn("Close IRSG ballot", unicontent(r))
|
||||
|
||||
|
||||
def test_issue_ballot(self):
|
||||
|
||||
# Just testing IRTF drafts
|
||||
rg_draft1 = RgDraftFactory()
|
||||
rg_draft2 = RgDraftFactory()
|
||||
iesgmember = get_active_ads()[0]
|
||||
|
||||
# login as an IRTF chair (who is a user who can issue an IRSG ballot)
|
||||
self.client.login(username='irtf-chair', password='irtf-chair+password')
|
||||
|
||||
# Get the page with the Issue IRSG Ballot Yes/No buttons
|
||||
url = urlreverse('ietf.doc.views_ballot.issue_irsg_ballot',kwargs=dict(name=rg_draft1.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
# Buttons present?
|
||||
self.assertIn("irsg_button", unicontent(r))
|
||||
|
||||
# Press the No button - expect nothing but a redirect back to the draft's main page
|
||||
r = self.client.post(url,dict(irsg_button="No"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
# Press the Yes button
|
||||
r = self.client.post(url,dict(irsg_button="Yes", duedate="2038-01-19"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
ballot_created = list(BallotDocEvent.objects.filter(doc=rg_draft1,
|
||||
type="created_ballot"))
|
||||
self.assertNotEqual(len(ballot_created), 0)
|
||||
|
||||
# Having issued a ballot, the Issue IRSG ballot button should be gone
|
||||
url = urlreverse('ietf.doc.views_doc.document_main',kwargs=dict(name=rg_draft1.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertNotIn("Issue IRSG ballot", unicontent(r))
|
||||
|
||||
# The IRSG evaluation record tab should exist
|
||||
self.assertIn("IRSG evaluation record", unicontent(r))
|
||||
# The IRSG evaluation record tab should not indicate unavailability
|
||||
self.assertNotIn("IRSG Evaluation Ballot has not been created yet", unicontent(r))
|
||||
|
||||
# We should find an IRSG member's name on the IRSG evaluation tab regardless of any positions taken or not
|
||||
url = urlreverse('ietf.doc.views_doc.document_irsg_ballot',kwargs=dict(name=rg_draft1.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
irsgmembers = get_active_irsg()
|
||||
self.assertNotEqual(len(irsgmembers), 0)
|
||||
self.assertIn(irsgmembers[0].name, unicontent(r))
|
||||
|
||||
# Having issued a ballot, it should appear on the IRSG Ballot Status page
|
||||
url = urlreverse('ietf.doc.views_ballot.irsg_ballot_status')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
# Does the draft name appear on the page?
|
||||
self.assertIn(rg_draft1.name, unicontent(r))
|
||||
|
||||
self.client.logout()
|
||||
|
||||
# Test that an IESG member cannot issue an IRSG ballot
|
||||
self.client.login(username=iesgmember.user.username, password=iesgmember.user.username+"password")
|
||||
|
||||
url = urlreverse('ietf.doc.views_ballot.issue_irsg_ballot',kwargs=dict(name=rg_draft2.name))
|
||||
r = self.client.get(url)
|
||||
self.assertNotEqual(r.status_code, 200)
|
||||
# Buttons present?
|
||||
self.assertNotIn("irsg_button", unicontent(r))
|
||||
|
||||
# Attempt to press the Yes button anyway
|
||||
r = self.client.post(url,dict(irsg_button="Yes", duedate="2038-01-19"))
|
||||
self.assertTrue(r.status_code == 302 and "/accounts/login" in r['Location'])
|
||||
|
||||
self.client.logout()
|
||||
|
||||
# Test that the Secretariat can issue an IRSG ballot
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
# Buttons present?
|
||||
self.assertIn("irsg_button", unicontent(r))
|
||||
|
||||
# Press the Yes button
|
||||
r = self.client.post(url,dict(irsg_button="Yes", duedate="2038-01-19"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
self.client.logout()
|
||||
|
||||
|
||||
def test_edit_ballot_position_permissions(self):
|
||||
rg_draft = RgDraftFactory()
|
||||
wg_draft = WgDraftFactory()
|
||||
ad = RoleFactory(group__type_id='area',name_id='ad')
|
||||
pre_ad = RoleFactory(group__type_id='area',name_id='pre-ad')
|
||||
irsgmember = get_active_irsg()[0]
|
||||
secr = RoleFactory(group__acronym='secretariat',name_id='secr')
|
||||
wg_ballot = create_ballot_if_not_open(None, wg_draft, ad.person, 'approve')
|
||||
due = datetime.date.today()+datetime.timedelta(days=14)
|
||||
rg_ballot = create_ballot_if_not_open(None, rg_draft, secr.person, 'irsg-approve', due)
|
||||
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=wg_draft.name, ballot_id=wg_ballot.pk))
|
||||
|
||||
# Pre-ADs can see
|
||||
login_testing_unauthorized(self, pre_ad.person.user.username, url)
|
||||
|
||||
# But Pre-ADs cannot take a position
|
||||
r = self.client.post(url, dict(position="discuss", discuss="Test discuss text"))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
self.client.logout()
|
||||
|
||||
# ADs can see and take a position
|
||||
login_testing_unauthorized(self, ad.person.user.username, url)
|
||||
r = self.client.post(url, dict(position="discuss", discuss="Test discuss text"))
|
||||
self.assertTrue(r.status_code == 302 and "/accounts/login" not in r['Location'])
|
||||
|
||||
# IESG members should not be able to take positions on IRSG ballots
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=rg_draft.name, ballot_id=rg_ballot.pk))
|
||||
r = self.client.post(url, dict(position="yes"))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
self.client.logout()
|
||||
|
||||
# IRSG members should be able to enter a position on IRSG ballots
|
||||
login_testing_unauthorized(self, irsgmember.user.username, url)
|
||||
r = self.client.post(url, dict(position="yes"))
|
||||
self.assertTrue(r.status_code == 302 and "/accounts/login" not in r['Location'])
|
||||
|
||||
|
||||
def test_iesg_ballot_no_irsg_actions(self):
|
||||
ad = Person.objects.get(user__username="ad")
|
||||
wg_draft = IndividualDraftFactory(ad=ad)
|
||||
irsgmember = get_active_irsg()[0]
|
||||
|
||||
url = urlreverse('ietf.doc.views_ballot.ballot_writeupnotes', kwargs=dict(name=wg_draft.name))
|
||||
|
||||
# IRSG members should not be able to issue IESG ballots
|
||||
login_testing_unauthorized(self, irsgmember.user.username, url)
|
||||
r = self.client.post(url, dict(
|
||||
ballot_writeup="This is a test.",
|
||||
issue_ballot="1"))
|
||||
self.assertNotEqual(r.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
login_testing_unauthorized(self, "ad", url)
|
||||
|
||||
# But IESG members can
|
||||
r = self.client.post(url, dict(
|
||||
ballot_writeup="This is a test.",
|
||||
issue_ballot="1"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
|
||||
# Now that the ballot is issued, see if an IRSG member can take a position or close the ballot
|
||||
ballot = wg_draft.active_ballot()
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=wg_draft.name, ballot_id=ballot.pk))
|
||||
login_testing_unauthorized(self, irsgmember.user.username, url)
|
||||
|
||||
r = self.client.post(url, dict(position="discuss", discuss="Test discuss text"))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
class BaseManipulationTests():
|
||||
|
||||
def test_issue_ballot(self):
|
||||
draft = RgDraftFactory()
|
||||
url = urlreverse('ietf.doc.views_ballot.issue_irsg_ballot',kwargs=dict(name=draft.name))
|
||||
due = datetime.date.today()+datetime.timedelta(days=14)
|
||||
empty_outbox()
|
||||
|
||||
login_testing_unauthorized(self, self.username , url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url,{'irsg_button':'No', 'duedate':due })
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertIsNone(draft.ballot_open('irsg-approve'))
|
||||
|
||||
r = self.client.post(url,{'irsg_button':'Yes', 'duedate':due })
|
||||
self.assertEqual(r.status_code,302)
|
||||
self.assertIsNotNone(draft.ballot_open('irsg-approve'))
|
||||
self.assertEqual(len(outbox),0)
|
||||
|
||||
def test_take_and_email_position(self):
|
||||
draft = RgDraftFactory()
|
||||
ballot = IRSGBallotDocEventFactory(doc=draft)
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=draft.name, ballot_id=ballot.pk)) + self.balloter
|
||||
empty_outbox()
|
||||
|
||||
login_testing_unauthorized(self, self.username, url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url, dict(position='yes', comment='oib239sb', send_mail='Save and send email'))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
e = draft.latest_event(BallotPositionDocEvent)
|
||||
self.assertEqual(e.pos.slug,'yes')
|
||||
self.assertEqual(e.comment, 'oib239sb')
|
||||
|
||||
url = urlreverse('ietf.doc.views_ballot.send_ballot_comment', kwargs=dict(name=draft.name, ballot_id=ballot.pk)) + self.balloter
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url, dict(cc_choices=['doc_authors','doc_group_chairs','doc_group_mail_list'], body="Stuff"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(outbox),1)
|
||||
self.assertNotIn('discuss-criteria', get_payload(outbox[0]))
|
||||
|
||||
def test_close_ballot(self):
|
||||
draft = RgDraftFactory()
|
||||
IRSGBallotDocEventFactory(doc=draft)
|
||||
url = urlreverse('ietf.doc.views_ballot.close_irsg_ballot', kwargs=dict(name=draft.name))
|
||||
empty_outbox()
|
||||
|
||||
login_testing_unauthorized(self, self.username, url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url,dict(irsg_button='No'))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertIsNotNone(draft.ballot_open('irsg-approve'))
|
||||
|
||||
r = self.client.post(url,dict(irsg_button='Yes'))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertIsNone(draft.ballot_open('irsg-approve'))
|
||||
|
||||
self.assertEqual(len(outbox), 0)
|
||||
|
||||
def test_view_outstanding_ballots(self):
|
||||
draft = RgDraftFactory()
|
||||
IRSGBallotDocEventFactory(doc=draft)
|
||||
url = urlreverse('ietf.doc.views_ballot.irsg_ballot_status')
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIn(draft.name, unicontent(r))
|
||||
|
||||
close_ballot(draft, Person.objects.get(user__username=self.username), 'irsg-approve')
|
||||
r = self.client.get(url)
|
||||
self.assertNotIn(draft.name, unicontent(r))
|
||||
|
||||
|
||||
class IRTFChairTests(BaseManipulationTests, TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.username = 'irtf-chair'
|
||||
self.balloter = ''
|
||||
|
||||
class SecretariatTests(BaseManipulationTests, TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.username = 'secretary'
|
||||
self.balloter = '?balloter={}'.format(Person.objects.get(user__username='irtf-chair').pk)
|
||||
|
||||
|
||||
class IRSGMemberTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.username = get_active_irsg()[0].user.username
|
||||
|
||||
def test_cant_issue_irsg_ballot(self):
|
||||
draft = RgDraftFactory()
|
||||
due = datetime.date.today()+datetime.timedelta(days=14)
|
||||
url = urlreverse('ietf.doc.views_ballot.close_irsg_ballot', kwargs=dict(name=draft.name))
|
||||
|
||||
self.client.login(username = self.username, password = self.username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
r = self.client.post(url,{'irsg_button':'Yes', 'duedate':due })
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
def test_cant_close_irsg_ballot(self):
|
||||
draft = RgDraftFactory()
|
||||
IRSGBallotDocEventFactory(doc=draft)
|
||||
url = urlreverse('ietf.doc.views_ballot.close_irsg_ballot', kwargs=dict(name=draft.name))
|
||||
|
||||
self.client.login(username = self.username, password = self.username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
r = self.client.post(url,dict(irsg_button='Yes'))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
def test_cant_take_position_on_iesg_ballot(self):
|
||||
draft = WgDraftFactory()
|
||||
ballot = BallotDocEventFactory(doc=draft)
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=draft.name, ballot_id=ballot.pk))
|
||||
|
||||
self.client.login(username = self.username, password = self.username+'+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url, dict(position='yes', comment='oib239sb', send_mail='Save and send email'))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
def test_take_and_email_position(self):
|
||||
draft = RgDraftFactory()
|
||||
ballot = IRSGBallotDocEventFactory(doc=draft)
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=draft.name, ballot_id=ballot.pk))
|
||||
empty_outbox()
|
||||
|
||||
login_testing_unauthorized(self, self.username, url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url, dict(position='yes', comment='oib239sb', send_mail='Save and send email'))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
e = draft.latest_event(BallotPositionDocEvent)
|
||||
self.assertEqual(e.pos.slug,'yes')
|
||||
self.assertEqual(e.comment, 'oib239sb')
|
||||
|
||||
url = urlreverse('ietf.doc.views_ballot.send_ballot_comment', kwargs=dict(name=draft.name, ballot_id=ballot.pk))
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url, dict(cc_choices=['doc_authors','doc_group_chairs','doc_group_mail_list'], body="Stuff"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(outbox),1)
|
||||
|
||||
class IESGMemberTests(TestCase):
|
||||
|
||||
def test_cant_take_position_on_irtf_ballot(self):
|
||||
draft = RgDraftFactory()
|
||||
ballot = IRSGBallotDocEventFactory(doc=draft)
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=draft.name, ballot_id=ballot.pk))
|
||||
|
||||
self.assertEqual(self.client.login(username = 'ad', password = 'ad+password'), True)
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url, dict(position='yes', comment='oib239sb', send_mail='Save and send email'))
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
class NobodyTests(TestCase):
|
||||
|
||||
def can_see_IRSG_tab(self):
|
||||
draft=RgDraftFactory()
|
||||
ballot = IRSGBallotDocEventFactory(doc=draft)
|
||||
BallotPositionDocEventFactory(ballot=ballot, by=get_active_irsg()[0], pos_id='yes', comment='b2390sn3')
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.document_irsg_ballot',kwargs=dict(name=draft.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertIn('b2390sn3',unicontent(r))
|
||||
|
||||
def test_cant_take_position_on_irtf_ballot(self):
|
||||
draft = RgDraftFactory()
|
||||
ballot = IRSGBallotDocEventFactory(doc=draft)
|
||||
url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=draft.name, ballot_id=ballot.pk))
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertIn('/accounts/login', r['Location'])
|
||||
|
||||
r = self.client.post(url, dict(position='yes', comment='oib239sb', send_mail='Save and send email'))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertIn('/accounts/login', r['Location'])
|
|
@ -71,6 +71,7 @@ urlpatterns = [
|
|||
url(r'^active/?$', views_search.index_active_drafts),
|
||||
url(r'^recent/?$', views_search.recent_drafts),
|
||||
url(r'^select2search/(?P<model_name>(document|docalias))/(?P<doc_type>draft)/$', views_search.ajax_select2_search_docs),
|
||||
url(r'^ballots/irsg/$', views_ballot.irsg_ballot_status),
|
||||
|
||||
url(r'^%(name)s(?:/%(rev)s)?/$' % settings.URL_REGEXPS, views_doc.document_main),
|
||||
url(r'^%(name)s(?:/%(rev)s)?/bibtex/$' % settings.URL_REGEXPS, views_doc.document_bibtex),
|
||||
|
@ -82,7 +83,8 @@ urlpatterns = [
|
|||
url(r'^%(name)s/shepherdwriteup/$' % settings.URL_REGEXPS, views_doc.document_shepherd_writeup),
|
||||
url(r'^%(name)s/references/$' % settings.URL_REGEXPS, views_doc.document_references),
|
||||
url(r'^%(name)s/referencedby/$' % settings.URL_REGEXPS, views_doc.document_referenced_by),
|
||||
url(r'^%(name)s/ballot/$' % settings.URL_REGEXPS, views_doc.document_ballot),
|
||||
url(r'^%(name)s/ballot/(iesg/)?$' % settings.URL_REGEXPS, views_doc.document_ballot),
|
||||
url(r'^%(name)s/ballot/irsg/$' % settings.URL_REGEXPS, views_doc.document_irsg_ballot),
|
||||
url(r'^%(name)s/ballot/(?P<ballot_id>[0-9]+)/$' % settings.URL_REGEXPS, views_doc.document_ballot),
|
||||
url(r'^%(name)s/ballot/(?P<ballot_id>[0-9]+)/position/$' % settings.URL_REGEXPS, views_ballot.edit_position),
|
||||
url(r'^%(name)s/ballot/(?P<ballot_id>[0-9]+)/emailposition/$' % settings.URL_REGEXPS, views_ballot.send_ballot_comment),
|
||||
|
@ -129,7 +131,9 @@ urlpatterns = [
|
|||
url(r'^%(name)s/edit/approvedownrefs/$' % settings.URL_REGEXPS, views_ballot.approve_downrefs),
|
||||
url(r'^%(name)s/edit/makelastcall/$' % settings.URL_REGEXPS, views_ballot.make_last_call),
|
||||
url(r'^%(name)s/edit/urls/$' % settings.URL_REGEXPS, views_draft.edit_document_urls),
|
||||
|
||||
url(r'^%(name)s/edit/issueballot/irsg/$' % settings.URL_REGEXPS, views_ballot.issue_irsg_ballot),
|
||||
url(r'^%(name)s/edit/closeballot/irsg/$' % settings.URL_REGEXPS, views_ballot.close_irsg_ballot),
|
||||
|
||||
url(r'^help/state/(?P<type>[\w-]+)/$', views_help.state_help),
|
||||
url(r'^help/relationships/$', views_help.relationship_help),
|
||||
url(r'^help/relationships/(?P<subset>\w+)/$', views_help.relationship_help),
|
||||
|
|
|
@ -28,7 +28,7 @@ from ietf.community.utils import docs_tracked_by_community_list
|
|||
|
||||
from ietf.doc.models import Document, DocHistory, State, DocumentAuthor, DocHistoryAuthor
|
||||
from ietf.doc.models import DocAlias, RelatedDocument, RelatedDocHistory, BallotType, DocReminder
|
||||
from ietf.doc.models import DocEvent, ConsensusDocEvent, BallotDocEvent, NewRevisionDocEvent, StateDocEvent
|
||||
from ietf.doc.models import DocEvent, ConsensusDocEvent, BallotDocEvent, IRSGBallotDocEvent, NewRevisionDocEvent, StateDocEvent
|
||||
from ietf.doc.models import TelechatDocEvent
|
||||
from ietf.name.models import DocReminderTypeName, DocRelationshipName
|
||||
from ietf.group.models import Role, Group
|
||||
|
@ -210,6 +210,34 @@ def needed_ballot_positions(doc, active_positions):
|
|||
|
||||
return " ".join(answer)
|
||||
|
||||
# Not done yet - modified version of above needed_ballot_positions
|
||||
def irsg_needed_ballot_positions(doc, active_positions):
|
||||
'''Returns text answering the question "what does this document
|
||||
need to pass?". The return value is only useful if the document
|
||||
is currently in IRSG evaluation.'''
|
||||
yes = [p for p in active_positions if p and p.pos_id == "yes"]
|
||||
needmoretime = [p for p in active_positions if p and p.pos_id == "moretime"]
|
||||
notready = [p for p in active_positions if p and p.pos_id == "notready"]
|
||||
|
||||
answer = []
|
||||
needed = 2
|
||||
|
||||
have = len(yes)
|
||||
if len(notready) > 0:
|
||||
answer.append("Has a Not Ready position.")
|
||||
if have < needed:
|
||||
more = needed - have
|
||||
if more == 1:
|
||||
answer.append("Needs one more YES position to pass.")
|
||||
else:
|
||||
answer.append("Needs %d more YES positions to pass." % more)
|
||||
else:
|
||||
answer.append("Has enough positions to pass.")
|
||||
if len(needmoretime) > 0:
|
||||
answer.append("Has a Need More Time position.")
|
||||
|
||||
return " ".join(answer)
|
||||
|
||||
def create_ballot(request, doc, by, ballot_slug, time=None):
|
||||
closed = close_open_ballots(doc, by)
|
||||
for e in closed:
|
||||
|
@ -222,13 +250,19 @@ def create_ballot(request, doc, by, ballot_slug, time=None):
|
|||
e.desc = 'Created "%s" ballot' % e.ballot_type.name
|
||||
e.save()
|
||||
|
||||
def create_ballot_if_not_open(request, doc, by, ballot_slug, time=None):
|
||||
def create_ballot_if_not_open(request, doc, by, ballot_slug, time=None, duedate=None):
|
||||
ballot_type = BallotType.objects.get(doc_type=doc.type, slug=ballot_slug)
|
||||
if not doc.ballot_open(ballot_slug):
|
||||
if time:
|
||||
e = BallotDocEvent(type="created_ballot", by=by, doc=doc, rev=doc.rev, time=time)
|
||||
if duedate:
|
||||
e = IRSGBallotDocEvent(type="created_ballot", by=by, doc=doc, rev=doc.rev, time=time, duedate=duedate)
|
||||
else:
|
||||
e = BallotDocEvent(type="created_ballot", by=by, doc=doc, rev=doc.rev, time=time)
|
||||
else:
|
||||
e = BallotDocEvent(type="created_ballot", by=by, doc=doc, rev=doc.rev)
|
||||
if duedate:
|
||||
e = IRSGBallotDocEvent(type="created_ballot", by=by, doc=doc, rev=doc.rev, duedate=duedate)
|
||||
else:
|
||||
e = BallotDocEvent(type="created_ballot", by=by, doc=doc, rev=doc.rev)
|
||||
e.ballot_type = ballot_type
|
||||
e.desc = 'Created "%s" ballot' % e.ballot_type.name
|
||||
e.save()
|
||||
|
|
|
@ -20,8 +20,9 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import ( Document, State, DocEvent, BallotDocEvent, BallotPositionDocEvent,
|
||||
LastCallDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS, RelatedDocument )
|
||||
from ietf.doc.models import ( Document, State, DocEvent, BallotDocEvent,
|
||||
IRSGBallotDocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent,
|
||||
IESG_SUBSTATE_TAGS, RelatedDocument, BallotType )
|
||||
from ietf.doc.utils import ( add_state_change_event, close_ballot, close_open_ballots,
|
||||
create_ballot_if_not_open, update_telechat )
|
||||
from ietf.doc.mails import ( email_ballot_deferred, email_ballot_undeferred,
|
||||
|
@ -34,17 +35,20 @@ from ietf.ietfauth.utils import has_role, role_required, is_authorized_in_doc_st
|
|||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.mailtrigger.forms import CcSelectForm
|
||||
from ietf.message.utils import infer_message
|
||||
from ietf.name.models import BallotPositionName
|
||||
from ietf.name.models import BallotPositionName, DocTypeName
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils import log
|
||||
from ietf.utils.mail import send_mail_text, send_mail_preformatted
|
||||
from ietf.utils.decorators import require_api_key
|
||||
from ietf.doc.templatetags.ietf_filters import can_ballot
|
||||
|
||||
BALLOT_CHOICES = (("yes", "Yes"),
|
||||
("noobj", "No Objection"),
|
||||
("discuss", "Discuss"),
|
||||
("abstain", "Abstain"),
|
||||
("recuse", "Recuse"),
|
||||
("moretime", "Need More Time"),
|
||||
("notready", "Not Ready"),
|
||||
("", "No Record"),
|
||||
)
|
||||
|
||||
|
@ -112,22 +116,22 @@ class EditPositionForm(forms.Form):
|
|||
|
||||
def clean_discuss(self):
|
||||
entered_discuss = self.cleaned_data["discuss"]
|
||||
entered_pos = self.cleaned_data.get("position", "norecord")
|
||||
entered_pos = self.cleaned_data.get("position", BallotPositionName.objects.get(slug="norecord"))
|
||||
if entered_pos.blocking and not entered_discuss:
|
||||
raise forms.ValidationError("You must enter a non-empty discuss")
|
||||
return entered_discuss
|
||||
|
||||
def save_position(form, doc, ballot, ad, login=None, send_email=False):
|
||||
def save_position(form, doc, ballot, balloter, login=None, send_email=False):
|
||||
# save the vote
|
||||
if login is None:
|
||||
login = ad
|
||||
login = balloter
|
||||
clean = form.cleaned_data
|
||||
|
||||
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
|
||||
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", balloter=balloter, ballot=ballot)
|
||||
pos = BallotPositionDocEvent(doc=doc, rev=doc.rev, by=login)
|
||||
pos.type = "changed_ballot_position"
|
||||
pos.ballot = ballot
|
||||
pos.ad = ad
|
||||
pos.balloter = balloter
|
||||
pos.pos = clean["position"]
|
||||
pos.comment = clean["comment"].rstrip()
|
||||
pos.comment_time = old_pos.comment_time if old_pos else None
|
||||
|
@ -147,7 +151,7 @@ def save_position(form, doc, ballot, ad, login=None, send_email=False):
|
|||
changes.append("comment")
|
||||
|
||||
if pos.comment:
|
||||
e = DocEvent(doc=doc, rev=doc.rev, by=ad)
|
||||
e = DocEvent(doc=doc, rev=doc.rev, by=balloter)
|
||||
e.type = "added_comment"
|
||||
e.desc = "[Ballot comment]\n" + pos.comment
|
||||
|
||||
|
@ -159,7 +163,7 @@ def save_position(form, doc, ballot, ad, login=None, send_email=False):
|
|||
changes.append("discuss")
|
||||
|
||||
if pos.pos.blocking:
|
||||
e = DocEvent(doc=doc, rev=doc.rev, by=ad)
|
||||
e = DocEvent(doc=doc, rev=doc.rev, by=balloter)
|
||||
e.type = "added_comment"
|
||||
e.desc = "[Ballot %s]\n" % pos.pos.name.lower()
|
||||
e.desc += pos.discuss
|
||||
|
@ -167,16 +171,16 @@ def save_position(form, doc, ballot, ad, login=None, send_email=False):
|
|||
|
||||
# figure out a description
|
||||
if not old_pos and pos.pos.slug != "norecord":
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.balloter.plain_name())
|
||||
elif old_pos and pos.pos != old_pos.pos:
|
||||
pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad.plain_name(), pos.pos.name, old_pos.pos.name)
|
||||
pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.balloter.plain_name(), pos.pos.name, old_pos.pos.name)
|
||||
|
||||
if not pos.desc and changes:
|
||||
pos.desc = "Ballot %s text updated for %s" % (" and ".join(changes), ad.plain_name())
|
||||
pos.desc = "Ballot %s text updated for %s" % (" and ".join(changes), balloter.plain_name())
|
||||
|
||||
# only add new event if we actually got a change
|
||||
if pos.desc:
|
||||
if login != ad:
|
||||
if login != balloter:
|
||||
pos.desc += " by %s" % login.plain_name()
|
||||
|
||||
pos.save()
|
||||
|
@ -186,13 +190,13 @@ def save_position(form, doc, ballot, ad, login=None, send_email=False):
|
|||
|
||||
return pos
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
@role_required('Area Director','Secretariat','IRSG Member')
|
||||
def edit_position(request, name, ballot_id):
|
||||
"""Vote and edit discuss and comment on document as Area Director."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
ballot = get_object_or_404(BallotDocEvent, type="created_ballot", pk=ballot_id, doc=doc)
|
||||
|
||||
ad = login = request.user.person
|
||||
balloter = login = request.user.person
|
||||
|
||||
if 'ballot_edit_return_point' in request.session:
|
||||
return_to_url = request.session['ballot_edit_return_point']
|
||||
|
@ -200,37 +204,38 @@ def edit_position(request, name, ballot_id):
|
|||
return_to_url = urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name, ballot_id=ballot_id))
|
||||
|
||||
# if we're in the Secretariat, we can select an AD to act as stand-in for
|
||||
# or we can select an IRSG member
|
||||
if has_role(request.user, "Secretariat"):
|
||||
ad_id = request.GET.get('ad')
|
||||
if not ad_id:
|
||||
balloter_id = request.GET.get('balloter')
|
||||
if not balloter_id:
|
||||
raise Http404
|
||||
ad = get_object_or_404(Person, pk=ad_id)
|
||||
balloter = get_object_or_404(Person, pk=balloter_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
old_pos = None
|
||||
if not has_role(request.user, "Secretariat") and not ad.role_set.filter(name="ad", group__type="area", group__state="active"):
|
||||
if not has_role(request.user, "Secretariat") and not can_ballot(request.user, doc):
|
||||
# prevent pre-ADs from voting
|
||||
return HttpResponseForbidden("Must be a proper Area Director in an active area to cast ballot")
|
||||
return HttpResponseForbidden("Must be a proper Area Director in an active area or IRSG Member to cast ballot")
|
||||
|
||||
form = EditPositionForm(request.POST, ballot_type=ballot.ballot_type)
|
||||
if form.is_valid():
|
||||
send_mail = True if request.POST.get("send_mail") else False
|
||||
save_position(form, doc, ballot, ad, login, send_mail)
|
||||
|
||||
save_position(form, doc, ballot, balloter, login, send_mail)
|
||||
|
||||
if send_mail:
|
||||
qstr=""
|
||||
if request.GET.get('ad'):
|
||||
qstr += "?ad=%s" % request.GET.get('ad')
|
||||
if request.GET.get('balloter'):
|
||||
qstr += "?balloter=%s" % request.GET.get('balloter')
|
||||
return HttpResponseRedirect(urlreverse('ietf.doc.views_ballot.send_ballot_comment', kwargs=dict(name=doc.name, ballot_id=ballot_id)) + qstr)
|
||||
elif request.POST.get("Defer"):
|
||||
elif request.POST.get("Defer") and doc.stream.slug != "irtf":
|
||||
return redirect('ietf.doc.views_ballot.defer_ballot', name=doc)
|
||||
elif request.POST.get("Undefer"):
|
||||
elif request.POST.get("Undefer") and doc.stream.slug != "irtf":
|
||||
return redirect('ietf.doc.views_ballot.undefer_ballot', name=doc)
|
||||
else:
|
||||
return HttpResponseRedirect(return_to_url)
|
||||
else:
|
||||
initial = {}
|
||||
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
|
||||
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", balloter=balloter, ballot=ballot)
|
||||
if old_pos:
|
||||
initial['position'] = old_pos.pos.slug
|
||||
initial['discuss'] = old_pos.discuss
|
||||
|
@ -245,7 +250,7 @@ def edit_position(request, name, ballot_id):
|
|||
return render(request, 'doc/ballot/edit_position.html',
|
||||
dict(doc=doc,
|
||||
form=form,
|
||||
ad=ad,
|
||||
balloter=balloter,
|
||||
return_to_url=return_to_url,
|
||||
old_pos=old_pos,
|
||||
ballot_deferred=ballot_deferred,
|
||||
|
@ -295,7 +300,7 @@ def api_set_position(request):
|
|||
return HttpResponse("Done", status=200, content_type='text/plain')
|
||||
|
||||
|
||||
def build_position_email(ad, doc, pos):
|
||||
def build_position_email(balloter, doc, pos):
|
||||
subj = []
|
||||
d = ""
|
||||
blocking_name = "DISCUSS"
|
||||
|
@ -308,32 +313,41 @@ def build_position_email(ad, doc, pos):
|
|||
c = pos.comment
|
||||
subj.append("COMMENT")
|
||||
|
||||
ad_name_genitive = ad.plain_name() + "'" if ad.plain_name().endswith('s') else ad.plain_name() + "'s"
|
||||
subject = "%s %s on %s" % (ad_name_genitive, pos.pos.name if pos.pos else "No Position", doc.name + "-" + doc.rev)
|
||||
balloter_name_genitive = balloter.plain_name() + "'" if balloter.plain_name().endswith('s') else balloter.plain_name() + "'s"
|
||||
subject = "%s %s on %s" % (balloter_name_genitive, pos.pos.name if pos.pos else "No Position", doc.name + "-" + doc.rev)
|
||||
if subj:
|
||||
subject += ": (with %s)" % " and ".join(subj)
|
||||
|
||||
body = render_to_string("doc/ballot/ballot_comment_mail.txt",
|
||||
dict(discuss=d,
|
||||
comment=c,
|
||||
ad=ad.plain_name(),
|
||||
balloter=balloter.plain_name(),
|
||||
doc=doc,
|
||||
pos=pos.pos,
|
||||
blocking_name=blocking_name,
|
||||
settings=settings))
|
||||
frm = ad.role_email("ad").formatted_email()
|
||||
|
||||
addrs = gather_address_lists('ballot_saved',doc=doc)
|
||||
frm = balloter.role_email("ad").formatted_email()
|
||||
|
||||
if doc.stream_id == "irtf":
|
||||
addrs = gather_address_lists('irsg_ballot_saved',doc=doc)
|
||||
else:
|
||||
addrs = gather_address_lists('iesg_ballot_saved',doc=doc)
|
||||
|
||||
return addrs, frm, subject, body
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
@role_required('Area Director','Secretariat','IRSG Member')
|
||||
def send_ballot_comment(request, name, ballot_id):
|
||||
"""Email document ballot position discuss/comment for Area Director."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
ballot = get_object_or_404(BallotDocEvent, type="created_ballot", pk=ballot_id, doc=doc)
|
||||
|
||||
ad = request.user.person
|
||||
if not has_role(request.user, 'Secretariat'):
|
||||
if doc.stream_id == 'irtf' and not has_role(request.user, 'IRSG Member'):
|
||||
raise Http404
|
||||
if doc.stream_id == 'ietf' and not has_role(request.user, 'Area Director'):
|
||||
raise Http404
|
||||
|
||||
balloter = request.user.person
|
||||
|
||||
if 'ballot_edit_return_point' in request.session:
|
||||
return_to_url = request.session['ballot_edit_return_point']
|
||||
|
@ -346,21 +360,26 @@ def send_ballot_comment(request, name, ballot_id):
|
|||
back_url = urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name, ballot_id=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:
|
||||
if has_role(request.user, "Secretariat"):
|
||||
balloter_id = request.GET.get('balloter')
|
||||
if not balloter_id:
|
||||
raise Http404
|
||||
ad = get_object_or_404(Person, pk=ad_id)
|
||||
balloter = get_object_or_404(Person, pk=balloter_id)
|
||||
|
||||
pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
|
||||
pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", balloter=balloter, ballot=ballot)
|
||||
if not pos:
|
||||
raise Http404
|
||||
|
||||
addrs, frm, subject, body = build_position_email(ad, doc, pos)
|
||||
addrs, frm, subject, body = build_position_email(balloter, doc, pos)
|
||||
|
||||
if doc.stream_id == 'irtf':
|
||||
mailtrigger_slug='irsg_ballot_saved'
|
||||
else:
|
||||
mailtrigger_slug='iesg_ballot_saved'
|
||||
|
||||
if request.method == 'POST':
|
||||
cc = []
|
||||
cc_select_form = CcSelectForm(data=request.POST,mailtrigger_slug='ballot_saved',mailtrigger_context={'doc':doc})
|
||||
cc_select_form = CcSelectForm(data=request.POST,mailtrigger_slug=mailtrigger_slug,mailtrigger_context={'doc':doc})
|
||||
if cc_select_form.is_valid():
|
||||
cc.extend(cc_select_form.get_selected_addresses())
|
||||
extra_cc = [x.strip() for x in request.POST.get("extra_cc","").split(',') if x.strip()]
|
||||
|
@ -373,7 +392,7 @@ def send_ballot_comment(request, name, ballot_id):
|
|||
|
||||
else:
|
||||
|
||||
cc_select_form = CcSelectForm(mailtrigger_slug='ballot_saved',mailtrigger_context={'doc':doc})
|
||||
cc_select_form = CcSelectForm(mailtrigger_slug=mailtrigger_slug,mailtrigger_context={'doc':doc})
|
||||
|
||||
return render(request, 'doc/ballot/send_ballot_comment.html',
|
||||
dict(doc=doc,
|
||||
|
@ -381,7 +400,7 @@ def send_ballot_comment(request, name, ballot_id):
|
|||
body=body,
|
||||
frm=frm,
|
||||
to=addrs.as_strings().to,
|
||||
ad=ad,
|
||||
balloter=balloter,
|
||||
back_url=back_url,
|
||||
cc_select_form = cc_select_form,
|
||||
))
|
||||
|
@ -599,17 +618,17 @@ def ballot_writeupnotes(request, name):
|
|||
if "issue_ballot" in request.POST:
|
||||
e = create_ballot_if_not_open(request, doc, login, "approve") # pyflakes:ignore
|
||||
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):
|
||||
if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionDocEvent, balloter=login, ballot=ballot):
|
||||
# sending the ballot counts as a yes
|
||||
pos = BallotPositionDocEvent(doc=doc, rev=doc.rev, by=login)
|
||||
pos.ballot = ballot
|
||||
pos.type = "changed_ballot_position"
|
||||
pos.ad = login
|
||||
pos.balloter = login
|
||||
pos.pos_id = "yes"
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.balloter.plain_name())
|
||||
pos.save()
|
||||
|
||||
# Consider mailing this position to 'ballot_saved'
|
||||
# Consider mailing this position to 'iesg_ballot_saved'
|
||||
|
||||
approval = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
|
||||
if not approval:
|
||||
|
@ -1051,3 +1070,88 @@ def make_last_call(request, name):
|
|||
form=form,
|
||||
announcement=announcement,
|
||||
))
|
||||
|
||||
@role_required('Secretariat', 'IRTF Chair')
|
||||
def issue_irsg_ballot(request, name):
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if doc.stream.slug != "irtf" or doc.type != DocTypeName.objects.get(slug="draft"):
|
||||
raise Http404
|
||||
|
||||
by = request.user.person
|
||||
fillerdate = datetime.date.today() + datetime.timedelta(weeks=2)
|
||||
|
||||
if request.method == 'POST':
|
||||
button = request.POST.get("irsg_button")
|
||||
if button == 'Yes':
|
||||
duedate = request.POST.get("duedate")
|
||||
e = IRSGBallotDocEvent(doc=doc, rev=doc.rev, by=request.user.person)
|
||||
if (duedate == None or duedate==""):
|
||||
duedate = str(fillerdate)
|
||||
e.duedate = datetime.datetime.strptime(duedate, '%Y-%m-%d')
|
||||
e.type = "created_ballot"
|
||||
e.desc = "Created IRSG Ballot"
|
||||
ballot_type = BallotType.objects.get(doc_type=doc.type, slug="irsg-approve")
|
||||
e.ballot_type = ballot_type
|
||||
e.save()
|
||||
new_state = doc.get_state()
|
||||
prev_tags = []
|
||||
new_tags = []
|
||||
|
||||
if doc.type_id == 'draft':
|
||||
new_state = State.objects.get(used=True, type="draft-stream-irtf", slug='irsgpoll')
|
||||
|
||||
prev_state = doc.get_state(new_state.type_id if new_state else None)
|
||||
|
||||
doc.set_state(new_state)
|
||||
doc.tags.remove(*prev_tags)
|
||||
|
||||
events = []
|
||||
state_change_event = add_state_change_event(doc, by, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags)
|
||||
if state_change_event:
|
||||
events.append(state_change_event)
|
||||
|
||||
if events:
|
||||
doc.save_with_history(events)
|
||||
|
||||
return HttpResponseRedirect(doc.get_absolute_url())
|
||||
else:
|
||||
templ = 'doc/ballot/irsg_ballot_approve.html'
|
||||
|
||||
question = "Confirm issuing a ballot for " + name + "?"
|
||||
return render(request, templ, dict(doc=doc,
|
||||
question=question, fillerdate=fillerdate))
|
||||
|
||||
@role_required('Secretariat', 'IRTF Chair')
|
||||
def close_irsg_ballot(request, name):
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if doc.stream.slug != "irtf" or doc.type != DocTypeName.objects.get(slug="draft"):
|
||||
raise Http404
|
||||
|
||||
by = request.user.person
|
||||
|
||||
if request.method == 'POST':
|
||||
button = request.POST.get("irsg_button")
|
||||
if button == 'Yes':
|
||||
close_ballot(doc, by, "irsg-approve")
|
||||
|
||||
return HttpResponseRedirect(doc.get_absolute_url())
|
||||
|
||||
templ = 'doc/ballot/irsg_ballot_close.html'
|
||||
|
||||
question = "Confirm closing the ballot for " + name + "?"
|
||||
return render(request, templ, dict(doc=doc,
|
||||
question=question))
|
||||
|
||||
def irsg_ballot_status(request):
|
||||
possible_docs = Document.objects.filter(docevent__ballotdocevent__irsgballotdocevent__isnull=False)
|
||||
docs = []
|
||||
for doc in possible_docs:
|
||||
if doc.ballot_open("irsg-approve"):
|
||||
ballot = doc.active_ballot()
|
||||
if ballot:
|
||||
doc.ballot = ballot
|
||||
doc.duedate=datetime.datetime.strftime(ballot.irsgballotdocevent.duedate, '%Y-%m-%d')
|
||||
|
||||
docs.append(doc)
|
||||
|
||||
return render(request, 'doc/irsg_ballot_status.html', {'docs':docs})
|
||||
|
|
|
@ -665,15 +665,15 @@ def ballot_writeupnotes(request, name):
|
|||
existing.save()
|
||||
|
||||
if "send_ballot" in request.POST and approval:
|
||||
if has_role(request.user, "Area Director") and not charter.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=by, ballot=ballot):
|
||||
if has_role(request.user, "Area Director") and not charter.latest_event(BallotPositionDocEvent, type="changed_ballot_position", balloter=by, ballot=ballot):
|
||||
# sending the ballot counts as a yes
|
||||
pos = BallotPositionDocEvent(doc=charter, rev=charter.rev, by=by)
|
||||
pos.type = "changed_ballot_position"
|
||||
pos.ad = by
|
||||
pos.balloter = by
|
||||
pos.pos_id = "yes"
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
|
||||
pos.save()
|
||||
# Consider mailing this position to 'ballot_saved'
|
||||
# Consider mailing this position to 'iesg_ballot_saved'
|
||||
|
||||
msg = generate_issue_ballot_mail(request, charter, ballot)
|
||||
send_mail_preformatted(request, msg)
|
||||
|
|
|
@ -69,17 +69,17 @@ def change_state(request, name, option=None):
|
|||
e = create_ballot_if_not_open(request, review, login, "conflrev") # pyflakes:ignore
|
||||
ballot = review.latest_event(BallotDocEvent, type="created_ballot")
|
||||
log.assertion('ballot == e')
|
||||
if has_role(request.user, "Area Director") and not review.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot, type="changed_ballot_position"):
|
||||
if has_role(request.user, "Area Director") and not review.latest_event(BallotPositionDocEvent, balloter=login, ballot=ballot, type="changed_ballot_position"):
|
||||
|
||||
# The AD putting a conflict review into iesgeval who doesn't already have a position is saying "yes"
|
||||
pos = BallotPositionDocEvent(doc=review, rev=review.rev, by=login)
|
||||
pos.ballot = ballot
|
||||
pos.type = "changed_ballot_position"
|
||||
pos.ad = login
|
||||
pos.balloter = login
|
||||
pos.pos_id = "yes"
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.balloter.plain_name())
|
||||
pos.save()
|
||||
# Consider mailing that position to 'ballot_saved'
|
||||
# Consider mailing that position to 'iesg_ballot_saved'
|
||||
send_conflict_eval_email(request,review)
|
||||
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ from ietf.doc.utils import (add_links_in_new_revision_events, augment_events_wit
|
|||
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
|
||||
get_initial_notify, make_notify_changed_event, make_rev_history, default_consensus,
|
||||
add_events_message_info, get_unicode_document_content, build_doc_meta_block,
|
||||
augment_docs_and_user_with_user_info)
|
||||
augment_docs_and_user_with_user_info, irsg_needed_ballot_positions )
|
||||
from ietf.group.models import Role, Group
|
||||
from ietf.group.utils import can_manage_group_type, can_manage_materials, group_features_role_filter
|
||||
from ietf.ietfauth.utils import ( has_role, is_authorized_in_doc_stream, user_is_person,
|
||||
|
@ -86,12 +86,16 @@ def render_document_top(request, doc, tab, name):
|
|||
tabs = []
|
||||
tabs.append(("Status", "status", urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=name)), True, None))
|
||||
|
||||
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
|
||||
if doc.type_id in ("draft","conflrev", "statchg"):
|
||||
tabs.append(("IESG Evaluation Record", "ballot", urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=name)), ballot, None if ballot else "IESG Evaluation Ballot has not been created yet"))
|
||||
elif doc.type_id == "charter" and doc.group.type_id == "wg":
|
||||
tabs.append(("IESG Review", "ballot", urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=name)), ballot, None if ballot else "IESG Review Ballot has not been created yet"))
|
||||
iesg_ballot = doc.latest_event(BallotDocEvent, type="created_ballot", ballot_type__slug='approve')
|
||||
irsg_ballot = doc.latest_event(BallotDocEvent, type="created_ballot", ballot_type__slug='irsg-approve')
|
||||
|
||||
if doc.type_id == "draft" and doc.get_state("draft-stream-irtf"):
|
||||
tabs.append(("IRSG Evaluation Record", "irsgballot", urlreverse("ietf.doc.views_doc.document_irsg_ballot", kwargs=dict(name=name)), irsg_ballot, None if irsg_ballot else "IRSG Evaluation Ballot has not been created yet"))
|
||||
if doc.type_id in ("draft","conflrev", "statchg"):
|
||||
tabs.append(("IESG Evaluation Record", "ballot", urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=name)), iesg_ballot, None if iesg_ballot else "IESG Evaluation Ballot has not been created yet"))
|
||||
elif doc.type_id == "charter" and doc.group.type_id == "wg":
|
||||
tabs.append(("IESG Review", "ballot", urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=name)), iesg_ballot, None if iesg_ballot else "IESG Review Ballot has not been created yet"))
|
||||
|
||||
if doc.type_id == "draft" or (doc.type_id == "charter" and doc.group.type_id == "wg"):
|
||||
tabs.append(("IESG Writeups", "writeup", urlreverse('ietf.doc.views_doc.document_writeup', kwargs=dict(name=name)), True, None))
|
||||
|
||||
|
@ -170,6 +174,8 @@ def document_main(request, name, rev=None):
|
|||
|
||||
iesg_state = doc.get_state("draft-iesg")
|
||||
iesg_state_summary = doc.friendly_state()
|
||||
irsg_state = doc.get_state("draft-stream-irtf")
|
||||
|
||||
can_edit = has_role(request.user, ("Area Director", "Secretariat"))
|
||||
stream_slugs = StreamName.objects.values_list("slug", flat=True)
|
||||
# For some reason, AnonymousUser has __iter__, but is not iterable,
|
||||
|
@ -260,11 +266,17 @@ def document_main(request, name, rev=None):
|
|||
file_urls.append(("bibtex", "bibtex"))
|
||||
|
||||
# ballot
|
||||
ballot_summary = None
|
||||
if iesg_state and iesg_state.slug in IESG_BALLOT_ACTIVE_STATES:
|
||||
iesg_ballot_summary = None
|
||||
irsg_ballot_summary = None
|
||||
due_date = None
|
||||
if (iesg_state and iesg_state.slug in IESG_BALLOT_ACTIVE_STATES) or irsg_state:
|
||||
active_ballot = doc.active_ballot()
|
||||
if active_ballot:
|
||||
ballot_summary = needed_ballot_positions(doc, list(active_ballot.active_ad_positions().values()))
|
||||
if irsg_state:
|
||||
irsg_ballot_summary = irsg_needed_ballot_positions(doc, list(active_ballot.active_balloter_positions().values()))
|
||||
due_date=active_ballot.irsgballotdocevent.duedate
|
||||
else:
|
||||
iesg_ballot_summary = needed_ballot_positions(doc, list(active_ballot.active_balloter_positions().values()))
|
||||
|
||||
# submission
|
||||
submission = ""
|
||||
|
@ -365,6 +377,13 @@ def document_main(request, name, rev=None):
|
|||
|
||||
if doc.get_state_slug() == "expired" and has_role(request.user, ("Secretariat",)) and not snapshot:
|
||||
actions.append(("Resurrect", urlreverse('ietf.doc.views_draft.resurrect', kwargs=dict(name=doc.name))))
|
||||
|
||||
if (doc.get_state_slug() not in ["rfc", "expired"] and doc.stream_id in ("irtf",) and not snapshot and not doc.ballot_open('irsg-approve') and can_edit_stream_info):
|
||||
label = "Issue IRSG Ballot"
|
||||
actions.append((label, urlreverse('ietf.doc.views_ballot.issue_irsg_ballot', kwargs=dict(name=doc.name))))
|
||||
if (doc.get_state_slug() not in ["rfc", "expired"] and doc.stream_id in ("irtf",) and not snapshot and doc.ballot_open('irsg-approve') and can_edit_stream_info):
|
||||
label = "Close IRSG Ballot"
|
||||
actions.append((label, urlreverse('ietf.doc.views_ballot.close_irsg_ballot', kwargs=dict(name=doc.name))))
|
||||
|
||||
if (doc.get_state_slug() not in ["rfc", "expired"] and doc.stream_id in ("ise", "irtf")
|
||||
and can_edit_stream_info and not conflict_reviews and not snapshot):
|
||||
|
@ -433,7 +452,9 @@ def document_main(request, name, rev=None):
|
|||
rfc_number=rfc_number,
|
||||
draft_name=draft_name,
|
||||
telechat=telechat,
|
||||
ballot_summary=ballot_summary,
|
||||
iesg_ballot_summary=iesg_ballot_summary,
|
||||
# PEY: Currently not using irsg_ballot_summary in the template, but it should be. That will take a new box for IRSG data.
|
||||
irsg_ballot_summary=irsg_ballot_summary,
|
||||
submission=submission,
|
||||
resurrected_by=resurrected_by,
|
||||
|
||||
|
@ -471,6 +492,7 @@ def document_main(request, name, rev=None):
|
|||
presentations=presentations,
|
||||
review_assignments=review_assignments,
|
||||
no_review_from_teams=no_review_from_teams,
|
||||
due_date=due_date,
|
||||
))
|
||||
|
||||
if doc.type_id == "charter":
|
||||
|
@ -481,7 +503,7 @@ def document_main(request, name, rev=None):
|
|||
if doc.get_state_slug() in ("intrev", "iesgrev"):
|
||||
active_ballot = doc.active_ballot()
|
||||
if active_ballot:
|
||||
ballot_summary = needed_ballot_positions(doc, list(active_ballot.active_ad_positions().values()))
|
||||
ballot_summary = needed_ballot_positions(doc, list(active_ballot.active_balloter_positions().values()))
|
||||
else:
|
||||
ballot_summary = "No active ballot found."
|
||||
|
||||
|
@ -523,7 +545,7 @@ def document_main(request, name, rev=None):
|
|||
|
||||
ballot_summary = None
|
||||
if doc.get_state_slug() in ("iesgeval") and doc.active_ballot():
|
||||
ballot_summary = needed_ballot_positions(doc, list(doc.active_ballot().active_ad_positions().values()))
|
||||
ballot_summary = needed_ballot_positions(doc, list(doc.active_ballot().active_balloter_positions().values()))
|
||||
|
||||
return render(request, "doc/document_conflict_review.html",
|
||||
dict(doc=doc,
|
||||
|
@ -550,7 +572,7 @@ def document_main(request, name, rev=None):
|
|||
|
||||
ballot_summary = None
|
||||
if doc.get_state_slug() in ("iesgeval"):
|
||||
ballot_summary = needed_ballot_positions(doc, list(doc.active_ballot().active_ad_positions().values()))
|
||||
ballot_summary = needed_ballot_positions(doc, list(doc.active_ballot().active_balloter_positions().values()))
|
||||
|
||||
if isinstance(doc,Document):
|
||||
sorted_relations=doc.relateddocument_set.all().order_by('relationship__name')
|
||||
|
@ -998,16 +1020,19 @@ def document_ballot_content(request, doc, ballot_id, 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()))
|
||||
g[1].sort(key=lambda p: (p.is_old_pos, p.balloter.plain_name()))
|
||||
if n.blocking:
|
||||
position_groups.insert(0, g)
|
||||
else:
|
||||
position_groups.append(g)
|
||||
|
||||
summary = needed_ballot_positions(doc, [p for p in positions if not p.old_ad])
|
||||
if (ballot.ballot_type.slug == "irsg-approve"):
|
||||
summary = irsg_needed_ballot_positions(doc, [p for p in positions if not p.is_old_pos])
|
||||
else:
|
||||
summary = needed_ballot_positions(doc, [p for p in positions if not p.is_old_pos])
|
||||
|
||||
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()))
|
||||
text_positions.sort(key=lambda p: (p.is_old_pos, p.balloter.plain_name()))
|
||||
|
||||
ballot_open = not BallotDocEvent.objects.filter(doc=doc,
|
||||
type__in=("closed_ballot", "created_ballot"),
|
||||
|
@ -1031,7 +1056,49 @@ def document_ballot_content(request, doc, ballot_id, editable=True):
|
|||
|
||||
def document_ballot(request, name, ballot_id=None):
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
top = render_document_top(request, doc, "ballot", name)
|
||||
all_ballots = list(BallotDocEvent.objects.filter(doc=doc, type="created_ballot").order_by("time"))
|
||||
if not ballot_id:
|
||||
if all_ballots:
|
||||
ballot = all_ballots[-1]
|
||||
else:
|
||||
raise Http404("Ballot not found for: %s" % name)
|
||||
ballot_id = ballot.id
|
||||
else:
|
||||
ballot_id = int(ballot_id)
|
||||
for b in all_ballots:
|
||||
if b.id == ballot_id:
|
||||
ballot = b
|
||||
break
|
||||
|
||||
if not ballot_id or not ballot:
|
||||
raise Http404("Ballot not found for: %s" % name)
|
||||
|
||||
if ballot.ballot_type.slug == "approve":
|
||||
ballot_tab = "ballot"
|
||||
elif ballot.ballot_type.slug == "irsg-approve":
|
||||
ballot_tab = "irsgballot"
|
||||
else:
|
||||
ballot_tab = None
|
||||
|
||||
top = render_document_top(request, doc, ballot_tab, name)
|
||||
|
||||
c = document_ballot_content(request, doc, ballot_id, editable=True)
|
||||
request.session['ballot_edit_return_point'] = request.path_info
|
||||
|
||||
return render(request, "doc/document_ballot.html",
|
||||
dict(doc=doc,
|
||||
top=top,
|
||||
ballot_content=c,
|
||||
# ballot_type_slug=ballot.ballot_type.slug,
|
||||
))
|
||||
|
||||
def document_irsg_ballot(request, name, ballot_id=None):
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
top = render_document_top(request, doc, "irsgballot", name)
|
||||
if not ballot_id:
|
||||
ballot = doc.latest_event(BallotDocEvent, type="created_ballot", ballot_type__slug='irsg-approve')
|
||||
if ballot:
|
||||
ballot_id = ballot.id
|
||||
|
||||
c = document_ballot_content(request, doc, ballot_id, editable=True)
|
||||
|
||||
|
@ -1041,6 +1108,7 @@ def document_ballot(request, name, ballot_id=None):
|
|||
dict(doc=doc,
|
||||
top=top,
|
||||
ballot_content=c,
|
||||
# ballot_type_slug=ballot.ballot_type.slug,
|
||||
))
|
||||
|
||||
def ballot_popup(request, name, ballot_id):
|
||||
|
|
|
@ -425,7 +425,7 @@ def docs_for_ad(request, name):
|
|||
Q(states__type__in=("statchg", "conflrev"),
|
||||
states__slug__in=("iesgeval", "defer")),
|
||||
docevent__ballotpositiondocevent__pos__blocking=True,
|
||||
docevent__ballotpositiondocevent__ad=ad).distinct()
|
||||
docevent__ballotpositiondocevent__balloter=ad).distinct()
|
||||
for doc in possible_docs:
|
||||
ballot = doc.active_ballot()
|
||||
if not ballot:
|
||||
|
@ -433,7 +433,7 @@ def docs_for_ad(request, name):
|
|||
|
||||
blocking_positions = [p for p in ballot.all_positions() if p.pos.blocking]
|
||||
|
||||
if not blocking_positions or not any( p.ad==ad for p in blocking_positions ):
|
||||
if not blocking_positions or not any( p.balloter==ad for p in blocking_positions ):
|
||||
continue
|
||||
|
||||
augment_events_with_revision(doc, blocking_positions)
|
||||
|
@ -445,7 +445,7 @@ def docs_for_ad(request, name):
|
|||
|
||||
# latest first
|
||||
if blocked_docs:
|
||||
blocked_docs.sort(key=lambda d: min(p.time for p in d.blocking_positions if p.ad==ad), reverse=True)
|
||||
blocked_docs.sort(key=lambda d: min(p.time for p in d.blocking_positions if p.balloter==ad), reverse=True)
|
||||
|
||||
return render(request, 'doc/drafts_for_ad.html', {
|
||||
'form':form, 'docs':results, 'meta':meta, 'ad_name': ad.plain_name(), 'blocked_docs': blocked_docs
|
||||
|
|
|
@ -73,15 +73,15 @@ def change_state(request, name, option=None):
|
|||
if new_state.slug == "iesgeval":
|
||||
e = create_ballot_if_not_open(request, status_change, login, "statchg", status_change.time) # pyflakes:ignore
|
||||
ballot = status_change.latest_event(BallotDocEvent, type="created_ballot")
|
||||
if has_role(request.user, "Area Director") and not status_change.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot, type="changed_ballot_position"):
|
||||
if has_role(request.user, "Area Director") and not status_change.latest_event(BallotPositionDocEvent, balloter=login, ballot=ballot, type="changed_ballot_position"):
|
||||
|
||||
# The AD putting a status change into iesgeval who doesn't already have a position is saying "yes"
|
||||
pos = BallotPositionDocEvent(doc=status_change, rev=status_change.rev, by=login)
|
||||
pos.ballot = ballot
|
||||
pos.type = "changed_ballot_position"
|
||||
pos.ad = login
|
||||
pos.balloter = login
|
||||
pos.pos_id = "yes"
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.balloter.plain_name())
|
||||
pos.save()
|
||||
|
||||
send_status_change_eval_email(request,status_change)
|
||||
|
|
|
@ -42,14 +42,14 @@ class IESGTests(TestCase):
|
|||
pos.type = "changed_ballot_position"
|
||||
pos.doc = draft
|
||||
pos.rev = draft.rev
|
||||
pos.ad = pos.by = Person.objects.get(user__username="ad")
|
||||
pos.balloter = pos.by = Person.objects.get(user__username="ad")
|
||||
pos.save()
|
||||
|
||||
r = self.client.get(urlreverse("ietf.iesg.views.discusses"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.assertContains(r, draft.name)
|
||||
self.assertContains(r, pos.ad.plain_name())
|
||||
self.assertContains(r, pos.balloter.plain_name())
|
||||
|
||||
def test_milestones_needing_review(self):
|
||||
draft = WgDraftFactory()
|
||||
|
|
|
@ -428,7 +428,7 @@ def past_documents(request):
|
|||
if blocking_positions:
|
||||
augment_events_with_revision(doc, blocking_positions)
|
||||
|
||||
doc.by_me = bool([p for p in blocking_positions if user_is_person(request.user, p.ad)])
|
||||
doc.by_me = bool([p for p in blocking_positions if user_is_person(request.user, p.balloter)])
|
||||
doc.for_me = user_is_person(request.user, doc.ad)
|
||||
doc.milestones = doc.groupmilestone_set.filter(state="active").order_by("time").select_related("group")
|
||||
doc.blocking_positions = blocking_positions
|
||||
|
@ -506,7 +506,7 @@ def discusses(request):
|
|||
|
||||
augment_events_with_revision(doc, blocking_positions)
|
||||
|
||||
doc.by_me = bool([p for p in blocking_positions if user_is_person(request.user, p.ad)])
|
||||
doc.by_me = bool([p for p in blocking_positions if user_is_person(request.user, p.balloter)])
|
||||
doc.for_me = user_is_person(request.user, doc.ad)
|
||||
doc.milestones = doc.groupmilestone_set.filter(state="active").order_by("time").select_related("group")
|
||||
doc.blocking_positions = blocking_positions
|
||||
|
|
|
@ -80,6 +80,7 @@ def has_role(user, role_names, *args, **kwargs):
|
|||
"Recording Manager": Q(person=person,name="recman",group__type="ietf",group__state="active", ),
|
||||
"Reviewer": Q(person=person, name="reviewer", group__state="active"),
|
||||
"Review Team Secretary": Q(person=person, name="secr", group__reviewteamsettings__isnull=False,group__state="active", ),
|
||||
"IRSG Member": (Q(person=person, name="member", group__acronym="irsg") | Q(person=person, name="chair", group__acronym="irtf")),
|
||||
|
||||
}
|
||||
|
||||
|
|
52
ietf/mailtrigger/migrations/0013_add_irsg_ballot_saved.py
Normal file
52
ietf/mailtrigger/migrations/0013_add_irsg_ballot_saved.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.25 on 2019-10-04 13:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def forward(apps, shema_editor):
|
||||
Recipient = apps.get_model('mailtrigger','Recipient')
|
||||
|
||||
irsg = Recipient.objects.create(
|
||||
slug = 'irsg',
|
||||
desc = 'The IRSG',
|
||||
template = 'The IRSG <irsg@irtf.org>'
|
||||
)
|
||||
|
||||
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
|
||||
slug = 'irsg_ballot_saved'
|
||||
desc = 'Recipients when a new IRSG ballot position with comments is saved'
|
||||
irsg_ballot_saved = MailTrigger.objects.create(
|
||||
slug=slug,
|
||||
desc=desc
|
||||
)
|
||||
irsg_ballot_saved.to.add(irsg)
|
||||
irsg_ballot_saved.cc.set(Recipient.objects.filter(slug__in=['doc_affecteddoc_authors','doc_affecteddoc_group_chairs','doc_affecteddoc_notify','doc_authors','doc_group_chairs','doc_group_mail_list','doc_notify','doc_shepherd']))
|
||||
|
||||
# We cannot just change the slug of the existing ballot_saved table,
|
||||
# because that will loose all the m2m entries in .to and .cc
|
||||
ballot_saved = MailTrigger.objects.get(slug='ballot_saved')
|
||||
iesg_ballot_saved = MailTrigger.objects.create(slug='iesg_ballot_saved')
|
||||
iesg_ballot_saved.to.set(ballot_saved.to.all())
|
||||
iesg_ballot_saved.cc.set(ballot_saved.cc.all())
|
||||
iesg_ballot_saved.desc = ballot_saved.desc
|
||||
iesg_ballot_saved.save()
|
||||
|
||||
def reverse(apps, shema_editor):
|
||||
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
|
||||
MailTrigger.objects.filter(slug='irsg_ballot_saved').delete()
|
||||
MailTrigger.objects.filter(slug='iesg_ballot_saved').delete()
|
||||
#
|
||||
Recipient = apps.get_model('mailtrigger','Recipient')
|
||||
Recipient.objects.filter(slug='irsg').delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailtrigger', '0012_dont_last_call_early_reviews'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse),
|
||||
]
|
|
@ -304,9 +304,9 @@ class Recipient(models.Model):
|
|||
doc = kwargs['doc']
|
||||
active_ballot = doc.active_ballot()
|
||||
if active_ballot:
|
||||
for ad, pos in active_ballot.active_ad_positions().items():
|
||||
for balloter, pos in active_ballot.active_balloter_positions().items():
|
||||
if pos and pos.pos_id == "discuss":
|
||||
addrs.append(ad.role_email("ad").address)
|
||||
addrs.append(balloter.role_email("ad").address)
|
||||
return addrs
|
||||
|
||||
def gather_ipr_updatedipr_contacts(self, **kwargs):
|
||||
|
|
|
@ -15,12 +15,12 @@ class EventMailTests(TestCase):
|
|||
url = urlreverse('ietf.mailtrigger.views.show_triggers')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, 'ballot_saved')
|
||||
self.assertContains(r, 'iesg_ballot_saved')
|
||||
|
||||
url = urlreverse('ietf.mailtrigger.views.show_triggers',kwargs=dict(mailtrigger_slug='ballot_saved'))
|
||||
url = urlreverse('ietf.mailtrigger.views.show_triggers',kwargs=dict(mailtrigger_slug='iesg_ballot_saved'))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, 'ballot_saved')
|
||||
self.assertContains(r, 'iesg_ballot_saved')
|
||||
|
||||
def test_show_recipients(self):
|
||||
|
||||
|
|
|
@ -68,7 +68,8 @@ def gather_relevant_expansions(**kwargs):
|
|||
|
||||
doc = kwargs['doc']
|
||||
|
||||
relevant.update(['doc_state_edited','doc_telechat_details_changed','ballot_deferred','ballot_saved'])
|
||||
# PEY: does this need to include irsg_ballot_saved as well?
|
||||
relevant.update(['doc_state_edited','doc_telechat_details_changed','ballot_deferred','iesg_ballot_saved'])
|
||||
|
||||
if doc.type_id in ['draft','statchg']:
|
||||
relevant.update(starts_with('last_call_'))
|
||||
|
|
|
@ -128,6 +128,25 @@
|
|||
"model": "doc.ballottype",
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"doc_type": "draft",
|
||||
"name": "IRSG Approve",
|
||||
"order": 0,
|
||||
"positions": [
|
||||
"moretime",
|
||||
"notready",
|
||||
"yes",
|
||||
"noobj",
|
||||
"recuse"
|
||||
],
|
||||
"question": "Is this draft ready for publication in the IRTF stream?",
|
||||
"slug": "irsg-approve",
|
||||
"used": true
|
||||
},
|
||||
"model": "doc.ballottype",
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
|
@ -2260,6 +2279,19 @@
|
|||
"model": "doc.state",
|
||||
"pk": 155
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "IRSG Review",
|
||||
"name": "IRSG Review",
|
||||
"next_states": [],
|
||||
"order": 0,
|
||||
"slug": "irsg_review",
|
||||
"type": "draft-stream-irtf",
|
||||
"used": true
|
||||
},
|
||||
"model": "doc.state",
|
||||
"pk": 156
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"label": "State"
|
||||
|
@ -3598,6 +3630,27 @@
|
|||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "group_personnel_change"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [
|
||||
"conflict_review_stream_manager",
|
||||
"doc_affecteddoc_authors",
|
||||
"doc_affecteddoc_group_chairs",
|
||||
"doc_affecteddoc_notify",
|
||||
"doc_authors",
|
||||
"doc_group_chairs",
|
||||
"doc_group_mail_list",
|
||||
"doc_notify",
|
||||
"doc_shepherd"
|
||||
],
|
||||
"desc": "Recipients when a new ballot position (with discusses, other blocking positions, or comments) is saved",
|
||||
"to": [
|
||||
"iesg"
|
||||
]
|
||||
},
|
||||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "iesg_ballot_saved"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [
|
||||
|
@ -3689,6 +3742,26 @@
|
|||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "ipr_posting_confirmation"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [
|
||||
"doc_affecteddoc_authors",
|
||||
"doc_affecteddoc_group_chairs",
|
||||
"doc_affecteddoc_notify",
|
||||
"doc_authors",
|
||||
"doc_group_chairs",
|
||||
"doc_group_mail_list",
|
||||
"doc_notify",
|
||||
"doc_shepherd"
|
||||
],
|
||||
"desc": "Recipients when a new IRSG ballot position with comments is saved",
|
||||
"to": [
|
||||
"irsg"
|
||||
]
|
||||
},
|
||||
"model": "mailtrigger.mailtrigger",
|
||||
"pk": "irsg_ballot_saved"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"cc": [
|
||||
|
@ -5052,6 +5125,14 @@
|
|||
"model": "mailtrigger.recipient",
|
||||
"pk": "ipr_updatedipr_holders"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The IRSG",
|
||||
"template": "The IRSG <irsg@irtf.org>"
|
||||
},
|
||||
"model": "mailtrigger.recipient",
|
||||
"pk": "irsg"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Alias for secretariat liaison administration",
|
||||
|
@ -5405,6 +5486,17 @@
|
|||
"model": "name.ballotpositionname",
|
||||
"pk": "discuss"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"blocking": false,
|
||||
"desc": "",
|
||||
"name": "Need More Time",
|
||||
"order": 0,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.ballotpositionname",
|
||||
"pk": "moretime"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"blocking": false,
|
||||
|
@ -5427,6 +5519,17 @@
|
|||
"model": "name.ballotpositionname",
|
||||
"pk": "norecord"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"blocking": false,
|
||||
"desc": "",
|
||||
"name": "Not Ready",
|
||||
"order": 0,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.ballotpositionname",
|
||||
"pk": "notready"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"blocking": false,
|
||||
|
@ -10769,7 +10872,7 @@
|
|||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Least recently used",
|
||||
"order": 2,
|
||||
"order": 0,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.reviewerqueuepolicyname",
|
||||
|
@ -10779,7 +10882,7 @@
|
|||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Rotate alphabetically",
|
||||
"order": 1,
|
||||
"order": 0,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.reviewerqueuepolicyname",
|
||||
|
@ -11625,6 +11728,16 @@
|
|||
"model": "name.timeslottypename",
|
||||
"pk": "reg"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Regular",
|
||||
"order": 0,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
"pk": "regular"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "A room has been reserved for use by another body the timeslot indicated",
|
||||
|
@ -11635,16 +11748,6 @@
|
|||
"model": "name.timeslottypename",
|
||||
"pk": "reserved"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Session",
|
||||
"order": 0,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
"pk": "regular"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "A room was not booked for the timeslot indicated",
|
||||
|
@ -14113,7 +14216,7 @@
|
|||
"fields": {
|
||||
"command": "xym",
|
||||
"switch": "--version",
|
||||
"time": "2019-11-20T00:13:04.660",
|
||||
"time": "2019-12-18T00:13:51.087",
|
||||
"used": true,
|
||||
"version": "xym 0.4"
|
||||
},
|
||||
|
@ -14124,7 +14227,7 @@
|
|||
"fields": {
|
||||
"command": "pyang",
|
||||
"switch": "--version",
|
||||
"time": "2019-11-20T00:13:05.501",
|
||||
"time": "2019-12-18T00:13:52.218",
|
||||
"used": true,
|
||||
"version": "pyang 2.1"
|
||||
},
|
||||
|
@ -14135,7 +14238,7 @@
|
|||
"fields": {
|
||||
"command": "yanglint",
|
||||
"switch": "--version",
|
||||
"time": "2019-11-20T00:13:05.643",
|
||||
"time": "2019-12-18T00:13:52.367",
|
||||
"used": true,
|
||||
"version": "yanglint 0.14.80"
|
||||
},
|
||||
|
@ -14146,9 +14249,9 @@
|
|||
"fields": {
|
||||
"command": "xml2rfc",
|
||||
"switch": "--version",
|
||||
"time": "2019-11-20T00:13:06.676",
|
||||
"time": "2019-12-18T00:13:53.390",
|
||||
"used": true,
|
||||
"version": "xml2rfc 2.35.0"
|
||||
"version": "xml2rfc 2.36.0"
|
||||
},
|
||||
"model": "utils.versioninfo",
|
||||
"pk": 4
|
||||
|
|
|
@ -59,7 +59,8 @@ class FormalLanguageName(NameModel):
|
|||
class DocReminderTypeName(NameModel):
|
||||
"Stream state"
|
||||
class BallotPositionName(NameModel):
|
||||
""" Yes, No Objection, Abstain, Discuss, Block, Recuse """
|
||||
""" Yes, No Objection, Abstain, Discuss, Block, Recuse, Need More Time,
|
||||
Not Ready """
|
||||
blocking = models.BooleanField(default=False)
|
||||
class MeetingTypeName(NameModel):
|
||||
"""IETF, Interim"""
|
||||
|
|
|
@ -189,6 +189,13 @@ def determine_merge_order(source,target):
|
|||
source,target = sorted([source,target],key=lambda a: a.user.last_login if a.user.last_login else datetime.datetime.min)
|
||||
return source,target
|
||||
|
||||
def get_active_balloters(ballot_type):
|
||||
if (ballot_type.slug != "irsg-approve"):
|
||||
active_balloters = get_active_ads()
|
||||
else:
|
||||
active_balloters = get_active_irsg()
|
||||
return active_balloters
|
||||
|
||||
def get_active_ads():
|
||||
from ietf.person.models import Person
|
||||
cache_key = "doc:active_ads"
|
||||
|
@ -197,3 +204,13 @@ def get_active_ads():
|
|||
active_ads = list(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type="area").distinct())
|
||||
cache.set(cache_key, active_ads)
|
||||
return active_ads
|
||||
|
||||
def get_active_irsg():
|
||||
from ietf.person.models import Person
|
||||
cache_key = "doc:active_irsg_balloters"
|
||||
active_irsg_balloters = cache.get(cache_key)
|
||||
if not active_irsg_balloters:
|
||||
active_irsg_balloters = list(Person.objects.filter(role__group__acronym='irsg',role__name__in=['chair','member','atlarge']).distinct())
|
||||
cache.set(cache_key, active_irsg_balloters)
|
||||
return active_irsg_balloters
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ class SecrTelechatTestCase(TestCase):
|
|||
}
|
||||
)
|
||||
self.assertEqual(response.status_code,302)
|
||||
self.assertTrue(BallotPositionDocEvent.objects.filter(doc=charter, ad_id=13, pos__slug='noobj').exists())
|
||||
self.assertTrue(BallotPositionDocEvent.objects.filter(doc=charter, balloter_id=13, pos__slug='noobj').exists())
|
||||
|
||||
def test_doc_detail_post_update_state(self):
|
||||
by=Person.objects.get(name="(System)")
|
||||
|
|
|
@ -35,7 +35,7 @@ x consolidate views (get rid of get_group_header,group,group_navigate)
|
|||
'''
|
||||
active_ballot_positions: takes one argument, doc. returns a dictionary with a key for each ad Person object
|
||||
NOTE: this function has been deprecated as of Datatracker 4.34. Should now use methods on the Document.
|
||||
For example: doc.active_ballot().active_ad_positions()
|
||||
For example: doc.active_ballot().active_balloter_positions()
|
||||
|
||||
agenda_data: takes a date string in the format YYYY-MM-DD.
|
||||
'''
|
||||
|
@ -191,7 +191,7 @@ def doc_detail(request, date, name):
|
|||
login = request.user.person
|
||||
|
||||
if doc.active_ballot():
|
||||
ballots = doc.active_ballot().active_ad_positions() # returns dict of ad:ballotpositiondocevent
|
||||
ballots = doc.active_ballot().active_balloter_positions() # returns dict of ad:ballotpositiondocevent
|
||||
else:
|
||||
ballots = []
|
||||
|
||||
|
@ -246,16 +246,16 @@ def doc_detail(request, date, name):
|
|||
if form.is_valid() and form.changed_data:
|
||||
# create new BallotPositionDocEvent
|
||||
clean = form.cleaned_data
|
||||
ad = Person.objects.get(id=clean['id'])
|
||||
balloter = Person.objects.get(id=clean['id'])
|
||||
pos = BallotPositionDocEvent(doc=doc, rev=doc.rev, by=login)
|
||||
pos.type = "changed_ballot_position"
|
||||
pos.ad = ad
|
||||
pos.balloter = balloter
|
||||
pos.ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
|
||||
pos.pos = clean['position']
|
||||
if form.initial['position'] == None:
|
||||
pos.desc = '[Ballot Position Update] New position, %s, has been recorded for %s by %s' % (pos.pos.name, ad.name, login.name)
|
||||
pos.desc = '[Ballot Position Update] New position, %s, has been recorded for %s by %s' % (pos.pos.name, balloter.name, login.name)
|
||||
else:
|
||||
pos.desc = '[Ballot Position Update] Position for %s has been changed to %s by %s' % (ad.name, pos.pos.name, login.name)
|
||||
pos.desc = '[Ballot Position Update] Position for %s has been changed to %s by %s' % (balloter.name, pos.pos.name, login.name)
|
||||
pos.save()
|
||||
has_changed = True
|
||||
|
||||
|
|
|
@ -466,7 +466,7 @@ td.area-director div { border-bottom: solid #ccc 1px; }
|
|||
|
||||
.milestone { font-style: italic; }
|
||||
|
||||
.areadirector-name { padding-bottom: .5em; line-height: 1em;}
|
||||
.balloter-name { padding-bottom: .5em; line-height: 1em;}
|
||||
|
||||
.changebar { width: 0.3em; }
|
||||
|
||||
|
|
|
@ -365,7 +365,7 @@ class SubmitTests(TestCase):
|
|||
ballot_position.type = "changed_ballot_position"
|
||||
ballot_position.doc = draft
|
||||
ballot_position.rev = draft.rev
|
||||
ballot_position.ad = ballot_position.by = Person.objects.get(user__username="ad2")
|
||||
ballot_position.balloter = ballot_position.by = Person.objects.get(user__username="ad2")
|
||||
ballot_position.save()
|
||||
|
||||
elif stream_type == 'irtf':
|
||||
|
@ -508,7 +508,7 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue(interesting_address in force_text(outbox[-2].as_string()))
|
||||
if draft.stream_id == 'ietf':
|
||||
self.assertTrue(draft.ad.role_email("ad").address in force_text(outbox[-2].as_string()))
|
||||
self.assertTrue(ballot_position.ad.role_email("ad").address in force_text(outbox[-2].as_string()))
|
||||
self.assertTrue(ballot_position.balloter.role_email("ad").address in force_text(outbox[-2].as_string()))
|
||||
self.assertTrue("New Version Notification" in outbox[-1]["Subject"])
|
||||
self.assertTrue(name in get_payload(outbox[-1]))
|
||||
r = self.client.get(urlreverse('ietf.doc.views_search.recent_drafts'))
|
||||
|
|
|
@ -55,6 +55,9 @@
|
|||
<li><a href="{% url 'ietf.doc.views_status_change.rfc_status_changes' %}">RFC status changes</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
|
||||
<li><a href="{% url 'ietf.doc.views_ballot.irsg_ballot_status' %}">IRSG ballot status</a></li>
|
||||
|
||||
{% if user|has_role:"WG Chair,RG Chair" %}
|
||||
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
|
||||
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>WG chair</li>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{% load ietf_filters %}{% autoescape off %}{{ ad }} has entered the following ballot position for
|
||||
{% load ietf_filters %}{% autoescape off %}{{ balloter }} has entered the following ballot position for
|
||||
{{ doc.name }}-{{ doc.rev }}: {{ pos.name }}
|
||||
|
||||
When responding, please keep the subject line intact and reply to all
|
||||
email addresses included in the To and CC lines. (Feel free to cut this
|
||||
introductory paragraph, however.)
|
||||
|
||||
{% if doc.type_id == "draft" %}
|
||||
{% if doc.type_id == "draft" and doc.stream_id != "irtf" %}
|
||||
Please refer to https://www.ietf.org/iesg/statement/discuss-criteria.html
|
||||
for more information about IESG DISCUSS and COMMENT positions.
|
||||
{% endif %}
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
{% load origin %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Change position for {{ ad.plain_name }}{% endblock %}
|
||||
{% block title %}Change position for {{ balloter.plain_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Change position for {{ ad.plain_name }} regarding <br><small><a href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">{{ doc }}</a></small></h1>
|
||||
<h1>Change position for {{ balloter.plain_name }} regarding <br><small><a href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">{{ doc }}</a></small></h1>
|
||||
|
||||
<div class="question">{{ ballot.ballot_type.question }}</div>
|
||||
|
||||
|
@ -36,11 +36,13 @@
|
|||
<input type="submit" class="btn btn-default" value="Save">
|
||||
|
||||
{% if doc.type_id == "draft" or doc.type_id == "conflrev" %}
|
||||
{% if doc.stream.slug != "irtf" %}
|
||||
{% if ballot_deferred %}
|
||||
<input type="submit" class="btn btn-warning" name="Undefer" value="Undefer ballot">
|
||||
{% else %}
|
||||
<input type="submit" class="btn btn-danger" name="Defer" value="Defer ballot">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
|
26
ietf/templates/doc/ballot/irsg_ballot_approve.html
Normal file
26
ietf/templates/doc/ballot/irsg_ballot_approve.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2019, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Issue ballot for {{ doc }}?{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>{{ question }}</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{# curly percent bootstrap_form approval_text_form curly percent #}
|
||||
|
||||
Due date for this ballot:
|
||||
<input type="text" placeholder={{ fillerdate }} name="duedate">
|
||||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary" name="irsg_button" value="Yes">Yes</button>
|
||||
<button type="submit" class="btn btn-default" name="irsg_button" value="No">No</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
23
ietf/templates/doc/ballot/irsg_ballot_close.html
Normal file
23
ietf/templates/doc/ballot/irsg_ballot_close.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2019, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Close ballot for {{ doc }}?{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>{{ question }}</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{# curly percent bootstrap_form approval_text_form curly percent #}
|
||||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary" name="irsg_button" value="Yes">Yes</button>
|
||||
<button type="submit" class="btn btn-default" name="irsg_button" value="No">No</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block title %}Send ballot position for {{ ad }}{% endblock %}
|
||||
{% block title %}Send ballot position for {{ balloter }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Send ballot position for {{ ad }} on <a href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">{{ doc }}</a></h1>
|
||||
<h1>Send ballot position for {{ balloter }} on <a href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">{{ doc }}</a></h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -25,19 +25,19 @@
|
|||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
{% if editable and user|has_role:"Area Director,Secretariat" %}
|
||||
{% if user|has_role:"Area Director" %}
|
||||
{% if editable and user|has_role:"Area Director,Secretariat,IRSG Member" %}
|
||||
{% if user|can_ballot:doc %}
|
||||
<a class="btn btn-default" href="{% url "ietf.doc.views_ballot.edit_position" name=doc.name ballot_id=ballot_id %}">Edit position</a>
|
||||
{% endif %}
|
||||
|
||||
{% if doc.type_id == "draft" or doc.type_id == "conflrev" %}
|
||||
{% if user|can_defer:doc %}
|
||||
{% if deferred %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.doc.views_ballot.undefer_ballot' name=doc.name %}">Undefer ballot</a>
|
||||
{% else %}
|
||||
<a class="btn btn-warning" href="{% url 'ietf.doc.views_ballot.defer_ballot' name=doc.name %}">Defer ballot</a>
|
||||
{% endif %}
|
||||
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
{% if user|has_role:"Secretariat" and ballot_type_slug != "irsg-approve" %}
|
||||
<a class="btn btn-danger" href="{% url 'ietf.doc.views_ballot.clear_ballot' name=doc.name ballot_type_slug=ballot_type_slug %}">Clear ballot</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
{% for n, positions in position_groups %}
|
||||
<h4><span class="label label-{{ n|pos_to_label }}"> {{ n.name }}</span></h4>
|
||||
{% for p in positions|dictsort:"ad.last_name" %}
|
||||
<div class="areadirector-name">
|
||||
{% if p.old_ad %}<span class="text-muted">({% endif %}{% if p.comment or p.discuss %}<a href="#{{ p.ad.plain_name|slugify }}">{% endif %}{{ p.ad.plain_name }}{% if p.comment or p.discuss %}</a>{% endif %}{% if p.old_ad %})</span>{% endif %}
|
||||
{% for p in positions|dictsort:"balloter.last_name" %}
|
||||
<div class="balloter-name">
|
||||
{% if p.is_old_pos %}<span class="text-muted">({% endif %}{% if p.comment or p.discuss %}<a href="#{{ p.balloter.plain_name|slugify }}">{% endif %}{{ p.balloter.plain_name }}{% if p.comment or p.discuss %}</a>{% endif %}{% if p.is_old_pos %})</span>{% endif %}
|
||||
</div>
|
||||
{% empty %}
|
||||
(None)
|
||||
|
@ -48,12 +48,12 @@
|
|||
<p class="well"><b>Ballot question:</b> "{{ ballot.ballot_type.question }}"</p>
|
||||
{% endif %}
|
||||
|
||||
{% if editable and user|has_role:"Area Director,Secretariat" %}
|
||||
{% if editable and user|has_role:"Area Director,Secretariat,IRSG Member" %}
|
||||
<a class="btn btn-default"
|
||||
href="https://mailarchive.ietf.org/arch/search/?q=subject:{{doc.name}}+AND+subject:(discuss+OR+comment+OR+review)">
|
||||
Search Mailarchive</a>
|
||||
|
||||
{% if user|has_role:"Area Director" %}
|
||||
{% if user|can_ballot:doc %}
|
||||
<a class="btn btn-primary" href="{% url "ietf.doc.views_ballot.edit_position" name=doc.name ballot_id=ballot.pk %}">Edit position</a>
|
||||
{% endif %}
|
||||
|
||||
|
@ -66,23 +66,23 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if user|has_role:"Area Director,Secretariat" %}
|
||||
{% if user|has_role:"Area Director,Secretariat" and ballot.ballot_type.slug != "irsg-approve" %}
|
||||
<a class="btn btn-danger" href="{% url 'ietf.doc.views_ballot.clear_ballot' name=doc.name ballot_type_slug=ballot.ballot_type.slug%}">Clear ballot</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% for n, positions in position_groups %}
|
||||
{% for p in positions|dictsort:"ad.last_name" %}
|
||||
<h4 class="anchor-target" id="{{ p.ad.plain_name|slugify }}">
|
||||
{% if p.old_ad %}<span class="text-muted">({% endif %}{{ p.ad.plain_name }}{% if p.old_ad %})</span>{% endif %}
|
||||
{% for p in positions|dictsort:"balloter.last_name" %}
|
||||
<h4 class="anchor-target" id="{{ p.balloter.plain_name|slugify }}">
|
||||
{% if p.is_old_pos %}<span class="text-muted">({% endif %}{{ p.balloter.plain_name }}{% if p.is_old_pos %})</span>{% endif %}
|
||||
<span class="pull-right">
|
||||
{% if p.old_positions %}
|
||||
<span class="text-muted small">(was {{ p.old_positions|join:", " }})</span>
|
||||
{% endif %}
|
||||
<span class="label label-{{ p.pos|pos_to_label }}">{{p.pos}}</span>
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<a href="{% url "ietf.doc.views_ballot.edit_position" name=doc.name ballot_id=ballot.pk %}?ad={{ p.ad.pk }}" title="Click to edit the position of {{ p.ad.plain_name }}" class="btn btn-default btn-xs">
|
||||
<a href="{% url "ietf.doc.views_ballot.edit_position" name=doc.name ballot_id=ballot.pk %}?balloter={{ p.balloter.pk }}" title="Click to edit the position of {{ p.balloter.plain_name }}" class="btn btn-default btn-xs">
|
||||
Edit</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
|
|
@ -305,6 +305,8 @@
|
|||
{% if stream_tags %}
|
||||
<div class="stream-tags">{% for tag in stream_tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}</div>
|
||||
{% endif %}
|
||||
{# PEY: Move this to the IRSG section when built #}
|
||||
{% if due_date %} [Due date: {{ due_date }}] {% endif %}
|
||||
</td>
|
||||
{% else %}
|
||||
<th>Stream state</th>
|
||||
|
@ -467,8 +469,8 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if ballot_summary %}
|
||||
<br><i>{{ ballot_summary }}</i>
|
||||
{% if iesg_ballot_summary %}
|
||||
<br><i>{{ iesg_ballot_summary }}</i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -31,11 +31,11 @@
|
|||
<td>{{ doc.ad|default:"" }}</td>
|
||||
<td>
|
||||
{% for p in doc.blocking_positions %}
|
||||
{{ p.ad }}
|
||||
{{ p.balloter }}
|
||||
({% if p.discuss_time %}{{ p.discuss_time|timesince_days }}{% endif %}
|
||||
days ago{% if doc.get_state_url != "rfc" and p.rev != doc.rev %}
|
||||
for -{{ p.rev }}{% endif %})<br>
|
||||
{% if p.old_ad %}
|
||||
{% if p.is_old_pos %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
42
ietf/templates/doc/irsg_ballot_status.html
Normal file
42
ietf/templates/doc/irsg_ballot_status.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2019, All Rights Reserved #}
|
||||
{% load origin staticfiles %}
|
||||
|
||||
{% load ballot_icon %}
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}IRSG ballot status{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>IRSG ballot status</h1>
|
||||
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Document</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for doc in docs %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ doc.displayname_with_link }}
|
||||
</td>
|
||||
{% include "doc/search/status_columns.html" %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
|
@ -42,6 +42,11 @@
|
|||
{{ doc.intended_std_level }}
|
||||
{% endif %}
|
||||
|
||||
{% if doc.duedate %}
|
||||
<br>
|
||||
Due date: {{ doc.duedate }}
|
||||
{%endif %}
|
||||
|
||||
{% if doc.reviewed_by_teams %}
|
||||
<br>Reviews:
|
||||
{% for acronym in doc.reviewed_by_teams %}
|
||||
|
|
|
@ -59,14 +59,14 @@
|
|||
<td>{{ doc.ad|default:"" }}</td>
|
||||
<td>
|
||||
{% for p in doc.blocking_positions %}
|
||||
{% if p.old_ad %}
|
||||
{% if p.is_old_pos %}
|
||||
<span class="text-muted">
|
||||
{% endif %}
|
||||
{{ p.ad }}
|
||||
{{ p.balloter }}
|
||||
({% if p.discuss_time %}{{ p.discuss_time|timesince_days }}{% endif %}
|
||||
days ago{% if doc.get_state_url != "rfc" and p.rev != doc.rev %}
|
||||
for -{{ p.rev }}{% endif %})<br>
|
||||
{% if p.old_ad %}
|
||||
{% if p.is_old_pos %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -64,7 +64,7 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
|
|||
{% if ballot %}
|
||||
<small><pre>
|
||||
Yes No-Objection Discuss Abstain Recuse
|
||||
{% for pos in ballot.all_positions %}{% if pos.old_ad %}{{pos.ad.plain_name|bracket|ljust:"22"}}{%else%}{{pos.ad.plain_name|ljust:"22"}}{%endif%} {{pos|bracketpos:"yes"}} {{pos|bracketpos:"noobj"}} {{pos|bracketpos:"discuss"}} {{pos|bracketpos:"abstain"}} {{pos|bracketpos:"recuse"}}
|
||||
{% for pos in ballot.all_positions %}{% if pos.is_old_pos %}{{pos.balloter.plain_name|bracket|ljust:"22"}}{%else%}{{pos.balloter.plain_name|ljust:"22"}}{%endif%} {{pos|bracketpos:"yes"}} {{pos|bracketpos:"noobj"}} {{pos|bracketpos:"discuss"}} {{pos|bracketpos:"abstain"}} {{pos|bracketpos:"recuse"}}
|
||||
{% endfor %}
|
||||
</pre></small>
|
||||
|
||||
|
|
|
@ -66,14 +66,14 @@
|
|||
<td>{{ doc.ad|default:"" }}</td>
|
||||
<td>
|
||||
{% for p in doc.blocking_positions %}
|
||||
{% if p.old_ad %}
|
||||
{% if p.is_old_pos %}
|
||||
<span class="text-muted">
|
||||
{% endif %}
|
||||
{{ p.ad }}
|
||||
{{ p.balloter }}
|
||||
({% if p.discuss_time %}{{ p.discuss_time|timesince_days }}{% endif %}
|
||||
days ago{% if doc.get_state_url != "rfc" and p.rev != doc.rev %}
|
||||
for -{{ p.rev }}{% endif %})<br>
|
||||
{% if p.old_ad %}
|
||||
{% if p.is_old_pos %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
{% if ballot %}
|
||||
<br><b>Discusses/comments</b> <a href="https://datatracker.ietf.org/doc/{{doc.canonical_name}}/ballot/">[ballot]</a>:
|
||||
<ul>
|
||||
{% for p in ballot.active_ad_positions.values %}
|
||||
{% for p in ballot.active_balloter_positions.values %}
|
||||
{% if p.pos %}
|
||||
{% if p.discuss %}
|
||||
<li>
|
||||
<a href="#{{ doc.name }}+{{ p.ad|slugify }}+discuss">{{ p.ad }}: Discuss [{{ p.discuss_time }}]</a>:
|
||||
<a href="#{{ doc.name }}+{{ p.balloter|slugify }}+discuss">{{ p.balloter }}: Discuss [{{ p.discuss_time }}]</a>:
|
||||
<br>{{ p.discuss }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if p.comment %}
|
||||
<li>
|
||||
<a href="#{{ doc.name }}+{{ p.ad|slugify }}+comment">{{ p.ad }}: Comment [{{ p.comment_time }}]</a>:
|
||||
<a href="#{{ doc.name }}+{{ p.balloter|slugify }}+comment">{{ p.balloter }}: Comment [{{ p.comment_time }}]</a>:
|
||||
<br>{{ p.comment }}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
|
|
@ -67,16 +67,16 @@ Some parts Copyright (c) 2009 The IETF Trust, all rights reserved.
|
|||
|
||||
{% with doc.active_ballot as ballot %}
|
||||
{% if ballot %}
|
||||
<ul>{% for p in ballot.active_ad_positions.values %}
|
||||
<ul>{% for p in ballot.active_balloter_positions.values %}
|
||||
{% if p.pos and p.discuss %}
|
||||
<li>
|
||||
<a name="{{ doc.name }}+{{ p.ad.plain_name|slugify }}+discuss">{{ p.ad.plain_name }}: Discuss [{{ p.discuss_time }}]</a>:
|
||||
<a name="{{ doc.name }}+{{ p.balloter.plain_name|slugify }}+discuss">{{ p.balloter.plain_name }}: Discuss [{{ p.discuss_time }}]</a>:
|
||||
<br><pre>{{ p.discuss|wordwrap:80 }}</pre>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if p.pos and p.comment %}
|
||||
<li>
|
||||
<a name="{{ doc.name }}+{{ p.ad.plain_name|slugify }}+comment">{{ p.ad.plain_name }}: Comment [{{ p.comment_time }}]</a>:
|
||||
<a name="{{ doc.name }}+{{ p.balloter.plain_name|slugify }}+comment">{{ p.balloter.plain_name }}: Comment [{{ p.comment_time }}]</a>:
|
||||
<br><pre>{{ p.comment|wordwrap:80 }}</pre>
|
||||
</li>
|
||||
{% endif %}{% endfor %}
|
||||
|
|
|
@ -95,6 +95,7 @@ def make_immutable_base_data():
|
|||
create_person(rfc_editor, "auth", name="Rfc Editor", username="rfc", email_address="rfc@edit.or")
|
||||
|
||||
iesg = create_group(name="Internet Engineering Steering Group", acronym="iesg", type_id="ietf", parent=ietf) # pyflakes:ignore
|
||||
irsg = create_group(name="Internet Research Steering Group", acronym="irsg", type_id="irtf", parent=irtf) # pyflakes:ignore
|
||||
|
||||
individ = create_group(name="Individual submissions", acronym="none", type_id="individ") # pyflakes:ignore
|
||||
|
||||
|
@ -134,6 +135,17 @@ def make_immutable_base_data():
|
|||
person=p,
|
||||
email=email)
|
||||
|
||||
# Create some IRSG members (really should add some atlarge and the chair,
|
||||
# but this isn't currently essential)
|
||||
create_person(irsg, "member", name="R. Searcher", username="rsearcher", email_address="rsearcher@example.org")
|
||||
|
||||
# Create a bunch of IRSG members for swarm tests
|
||||
for i in range(1, 5):
|
||||
u = User.objects.create(username="irsgmember%s" % i)
|
||||
p = Person.objects.create(name="IRSG Member No%s" % i, ascii="IRSG Member No%s" % i, user=u)
|
||||
email = Email.objects.create(address="irsgmember%s@example.org" % i, person=p, origin=u.username)
|
||||
Role.objects.create(name_id="member", group=irsg, person=p, email=email)
|
||||
|
||||
def make_test_data():
|
||||
area = Group.objects.get(acronym="farfut")
|
||||
ad = Person.objects.get(user__username="ad")
|
||||
|
|
Loading…
Reference in a new issue