Allow people to volunteer for NomCom via the datatracker. Commit ready for merge.

- Legacy-Id: 19104
This commit is contained in:
Robert Sparks 2021-06-10 20:35:50 +00:00
parent c8ed251ae3
commit 86102b9980
22 changed files with 576 additions and 59 deletions

View file

@ -39,7 +39,7 @@ def permission_names_to_objects(names):
result.append(Permission.objects.get(content_type__app_label=app_label,
codename=codename))
except Permission.DoesNotExist:
print "NO SUCH PERMISSION: %s, %s" % (app_label, codename)
print ("NO SUCH PERMISSION: %s, %s" % (app_label, codename))
raise
return result
@ -61,6 +61,7 @@ def main():
'group.add_groupevent','group.change_groupevent','group.delete_groupevent',
'iesg.add_telechatagendaitem','iesg.change_telechatagendaitem','iesg.delete_telechatagendaitem',
'iesg.add_telechatdate','iesg.change_telechatdate','iesg.delete_telechatdate',
'liaisons.add_liaisonstatementgroupcontacts', 'liaisons.change_liaisonstatementgroupcontacts', 'liaisons.delete_liaisonstatementgroupcontacts',
'mailinglists.add_list','mailinglists.change_list','mailinglists.delete_list',
'mailtrigger.add_mailtrigger','mailtrigger.change_mailtrigger','mailtrigger.delete_mailtrigger',
'mailtrigger.add_recipient','mailtrigger.change_recipient','mailtrigger.delete_recipient',
@ -71,6 +72,7 @@ def main():
'meeting.add_urlresource','meeting.change_urlresource','meeting.delete_urlresource',
'message.add_announcementfrom','message.change_announcementfrom','message.delete_announcementfrom',
'nomcom.add_nomcom','nomcom.change_nomcom','nomcom.delete_nomcom',
'nomcom.add_volunteer','nomcom.change_volunteer','nomcom.delete_volunteer',
'person.add_person','person.change_person','person.delete_person',
'person.add_alias','person.change_alias','person.delete_alias',
'person.add_email','person.change_email','person.delete_email',

View file

@ -1097,7 +1097,7 @@ class RegenerateLastCallTestCase(TestCase):
lc_text = draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text
self.assertTrue("contains these normative down" in lc_text)
self.assertTrue("rfc6666" in lc_text)
self.assertTrue("Independent Submission Editor stream" in lc_text)
self.assertTrue("Independent Submission" in lc_text)
draft.relateddocument_set.create(target=rfc.docalias.get(name='rfc6666'),relationship_id='downref-approval')

View file

@ -35,6 +35,7 @@ from ietf.group.models import Group, Role, RoleName
from ietf.ietfauth.htpasswd import update_htpasswd_file
from ietf.mailinglists.models import Subscribed
from ietf.meeting.factories import MeetingFactory
from ietf.nomcom.factories import NomComFactory
from ietf.person.factories import PersonFactory, EmailFactory
from ietf.person.models import Person, Email, PersonalApiKey
from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory
@ -333,6 +334,33 @@ class IetfAuthTests(TestCase):
self.assertEqual(updated_roles[0].email_id, new_email_address)
def test_nomcom_dressing_on_profile(self):
url = urlreverse('ietf.ietfauth.views.profile')
nobody = PersonFactory()
login_testing_unauthorized(self, nobody.user.username, url)
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertFalse(q('#volunteer-button'))
self.assertFalse(q('#volunteered'))
year = datetime.date.today().year
nomcom = NomComFactory(group__acronym=f'nomcom{year}',is_accepting_volunteers=True)
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertTrue(q('#volunteer-button'))
self.assertFalse(q('#volunteered'))
nomcom.volunteer_set.create(person=nobody)
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertFalse(q('#volunteer-button'))
self.assertTrue(q('#volunteered'))
def test_reset_password(self):
url = urlreverse(ietf.ietfauth.views.password_reset)

View file

@ -67,6 +67,7 @@ from ietf.ietfauth.htpasswd import update_htpasswd_file
from ietf.ietfauth.utils import role_required, has_role
from ietf.mailinglists.models import Subscribed, Whitelisted
from ietf.name.models import ExtResourceName
from ietf.nomcom.models import NomCom
from ietf.person.models import Person, Email, Alias, PersonalApiKey, PERSON_API_KEY_VALUES
from ietf.review.models import ReviewerSettings, ReviewWish, ReviewAssignment
from ietf.review.utils import unavailable_periods_to_list, get_default_filter_re
@ -210,6 +211,14 @@ def profile(request):
emails = Email.objects.filter(person=person).exclude(address__startswith='unknown-email-').order_by('-active','-time')
new_email_forms = []
nc = NomCom.objects.filter(group__acronym__icontains=Date.today().year).first()
if nc and nc.volunteer_set.filter(person=person).exists():
volunteer_status = 'volunteered'
elif nc and nc.is_accepting_volunteers:
volunteer_status = 'allow'
else:
volunteer_status = 'deny'
if request.method == 'POST':
person_form = get_person_form(request.POST, instance=person)
for r in roles:
@ -287,6 +296,8 @@ def profile(request):
'roles': roles,
'emails': emails,
'new_email_forms': new_email_forms,
'nomcom': nc,
'volunteer_status': volunteer_status,
'settings':settings,
})

View file

@ -2499,10 +2499,10 @@
"is_schedulable": true,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"lead\",\n \"delegate\",\n \"matman\"\n]",
"need_parent": false,
"parent_types": [
"ietf"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"lead\",\n \"delegate\",\n \"matman\"\n]",
"show_on_agenda": true
@ -2536,8 +2536,8 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\"\n]",
"parent_types": [],
"need_parent": false,
"parent_types": [],
"req_subm_approval": false,
"role_order": "[\n \"chair\"\n]",
"show_on_agenda": false
@ -2571,11 +2571,11 @@
"is_schedulable": true,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [
"area",
"ietf"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": true
@ -2609,10 +2609,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": true,
"parent_types": [
"ietf"
],
"need_parent": true,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -2646,10 +2646,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": true,
"parent_types": [
"area"
],
"need_parent": true,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -2683,10 +2683,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\"\n]",
"need_parent": false,
"parent_types": [
"ietf"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": true
@ -2720,8 +2720,8 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\"\n]",
"parent_types": [],
"need_parent": false,
"parent_types": [],
"req_subm_approval": false,
"role_order": "[\n \"chair\"\n]",
"show_on_agenda": false
@ -2755,8 +2755,8 @@
"is_schedulable": false,
"material_types": "\"[]\"",
"matman_roles": "[\n \"chair\",\n \"delegate\",\n \"member\"\n]",
"parent_types": [],
"need_parent": false,
"parent_types": [],
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"delegate\",\n \"member\"\n]",
"show_on_agenda": false
@ -2790,10 +2790,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\"\n]",
"need_parent": false,
"parent_types": [
"ietf"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -2827,10 +2827,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[]",
"need_parent": true,
"parent_types": [
"area"
],
"need_parent": true,
"req_subm_approval": false,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -2864,10 +2864,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [
"irtf"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -2901,8 +2901,8 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\"\n]",
"parent_types": [],
"need_parent": false,
"parent_types": [],
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"delegate\"\n]",
"show_on_agenda": false
@ -2936,10 +2936,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [
"isoc"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -2973,10 +2973,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\"\n]",
"need_parent": false,
"parent_types": [
"area"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"member\",\n \"advisor\"\n]",
"show_on_agenda": false
@ -3010,10 +3010,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"lead\",\n \"chair\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [
"ietf"
],
"need_parent": false,
"req_subm_approval": false,
"role_order": "[\n \"lead\",\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -3047,10 +3047,10 @@
"is_schedulable": true,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [
"irtf"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": true
@ -3063,7 +3063,7 @@
"about_page": "ietf.group.views.group_about",
"acts_like_wg": false,
"admin_roles": "[\n \"chair\",\n \"secr\"\n]",
"agenda_type": null,
"agenda_type": "ietf",
"create_wiki": true,
"custom_group_roles": true,
"customize_workflow": false,
@ -3076,7 +3076,7 @@
"has_chartering_process": false,
"has_default_jabber": false,
"has_documents": false,
"has_meetings": false,
"has_meetings": true,
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": true,
@ -3084,10 +3084,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"secr\"\n]",
"need_parent": true,
"parent_types": [
"area"
],
"need_parent": true,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -3121,8 +3121,8 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[]",
"parent_types": [],
"need_parent": false,
"parent_types": [],
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false
@ -3156,10 +3156,10 @@
"is_schedulable": true,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": true,
"parent_types": [
"irtf"
],
"need_parent": true,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"show_on_agenda": true
@ -3193,11 +3193,11 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[]",
"need_parent": false,
"parent_types": [
"area",
"sdo"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"liaiman\"\n]",
"show_on_agenda": false
@ -3231,10 +3231,10 @@
"is_schedulable": false,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"matman\"\n]",
"need_parent": false,
"parent_types": [
"area"
],
"need_parent": false,
"req_subm_approval": false,
"role_order": "[\n \"chair\",\n \"member\",\n \"matman\"\n]",
"show_on_agenda": false
@ -3268,10 +3268,10 @@
"is_schedulable": true,
"material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [
"area"
],
"need_parent": false,
"req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\",\n \"delegate\"\n]",
"show_on_agenda": true
@ -4165,6 +4165,7 @@
"fields": {
"cc": [
"liaison_cc",
"liaison_coordinators",
"liaison_response_contacts",
"liaison_technical_contacts"
],
@ -5503,6 +5504,14 @@
"model": "mailtrigger.recipient",
"pk": "liaison_cc"
},
{
"fields": {
"desc": "The IAB liaison coordination team members",
"template": "<liaison-coordination@iab.org>"
},
"model": "mailtrigger.recipient",
"pk": "liaison_coordinators"
},
{
"fields": {
"desc": "The assigned liaison manager for an external group ",
@ -10003,6 +10012,7 @@
"auth",
"aut-appr",
"grp-appr",
"ad-appr",
"manual",
"cancel"
],
@ -12050,7 +12060,7 @@
"fields": {
"desc": "",
"name": "Liaison CC Contact",
"order": 0,
"order": 9,
"used": true
},
"model": "name.rolename",
@ -12060,7 +12070,7 @@
"fields": {
"desc": "",
"name": "Liaison Contact",
"order": 0,
"order": 8,
"used": true
},
"model": "name.rolename",
@ -12488,7 +12498,7 @@
},
{
"fields": {
"desc": "IAB stream",
"desc": "Internet Architecture Board (IAB)",
"name": "IAB",
"order": 4,
"used": true
@ -12498,7 +12508,7 @@
},
{
"fields": {
"desc": "IETF stream",
"desc": "Internet Engineering Task Force (IETF)",
"name": "IETF",
"order": 1,
"used": true
@ -12508,7 +12518,7 @@
},
{
"fields": {
"desc": "IRTF Stream",
"desc": "Internet Research Task Force (IRTF)",
"name": "IRTF",
"order": 3,
"used": true
@ -12518,7 +12528,7 @@
},
{
"fields": {
"desc": "Independent Submission Editor stream",
"desc": "Independent Submission",
"name": "ISE",
"order": 2,
"used": true
@ -15243,9 +15253,9 @@
"fields": {
"command": "xym",
"switch": "--version",
"time": "2020-11-14T00:10:15.888",
"time": "2021-06-08T00:12:46.324",
"used": true,
"version": "xym 0.4.8"
"version": "xym 0.5"
},
"model": "utils.versioninfo",
"pk": 1
@ -15254,7 +15264,7 @@
"fields": {
"command": "pyang",
"switch": "--version",
"time": "2020-11-14T00:10:17.069",
"time": "2021-06-08T00:12:48.137",
"used": true,
"version": "pyang 2.4.0"
},
@ -15265,7 +15275,7 @@
"fields": {
"command": "yanglint",
"switch": "--version",
"time": "2020-11-14T00:10:17.405",
"time": "2021-06-08T00:12:48.465",
"used": true,
"version": "yanglint SO 1.6.7"
},
@ -15276,9 +15286,9 @@
"fields": {
"command": "xml2rfc",
"switch": "--version",
"time": "2020-11-14T00:10:19.405",
"time": "2021-06-08T00:12:51.318",
"used": true,
"version": "xml2rfc 3.4.0"
"version": "xml2rfc 3.8.0"
},
"model": "utils.versioninfo",
"pk": 4

View file

@ -1,11 +1,12 @@
# Copyright The IETF Trust 2012-2020, All Rights Reserved
# Copyright The IETF Trust 2012-2021, All Rights Reserved
# -*- coding: utf-8 -*-
from django.contrib import admin
from ietf.nomcom.models import ( ReminderDates, NomCom, Nomination, Nominee, NomineePosition,
Position, Feedback, FeedbackLastSeen, TopicFeedbackLastSeen)
Position, Feedback, FeedbackLastSeen, TopicFeedbackLastSeen,
Volunteer, )
class ReminderDatesAdmin(admin.ModelAdmin):
@ -68,4 +69,10 @@ class TopicFeedbackLastSeenAdmin(admin.ModelAdmin):
raw_id_fields = ['reviewer']
admin.site.register(TopicFeedbackLastSeen, TopicFeedbackLastSeenAdmin)
class VolunteerAdmin(admin.ModelAdmin):
list_display = ['nomcom','person','affiliation']
list_filter = ['nomcom']
raw_id_fields = ['person']
admin.site.register(Volunteer, VolunteerAdmin)

View file

@ -13,7 +13,7 @@ from django.forms.widgets import FileInput
from ietf.dbtemplate.forms import DBTemplateForm
from ietf.name.models import FeedbackTypeName, NomineePositionStateName
from ietf.nomcom.models import ( NomCom, Nomination, Nominee, NomineePosition,
Position, Feedback, ReminderDates, Topic )
Position, Feedback, ReminderDates, Topic, Volunteer )
from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE,
get_user_email, validate_private_key, validate_public_key,
make_nomineeposition, make_nomineeposition_for_newperson,
@ -833,3 +833,22 @@ class EditNomineeForm(forms.ModelForm):
class NominationResponseCommentForm(forms.Form):
comments = forms.CharField(widget=forms.Textarea,required=False,help_text="Any comments provided will be encrypted and will only be visible to the NomCom.", strip=False)
class NomcomVolunteerMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
year = obj.year()
return f'Volunteer for the {year}/{year+1} Nominating Committee'
class VolunteerForm(forms.ModelForm):
class Meta:
model = Volunteer
fields = ('affiliation',)
nomcoms = NomcomVolunteerMultipleChoiceField(queryset=NomCom.objects.none(),widget=forms.CheckboxSelectMultiple)
field_order = ('nomcoms','affiliation')
def __init__(self, person, *args, **kargs):
super().__init__(*args, **kargs)
self.fields['nomcoms'].queryset = NomCom.objects.filter(is_accepting_volunteers=True).exclude(volunteer__person=person)
self.fields['nomcoms'].help_text = 'You may volunteer even if the datatracker does not currently calculate that you are eligible. Eligibility will be assessed when the selection process is performed.'
self.fields['affiliation'].help_text = 'Affiliation to show in the volunteer list'
self.fields['affiliation'].required = True

View file

@ -0,0 +1,30 @@
# Generated by Django 2.2.24 on 2021-06-09 12:15
from django.db import migrations, models
import django.db.models.deletion
import ietf.utils.models
class Migration(migrations.Migration):
dependencies = [
('person', '0019_auto_20210604_1443'),
('nomcom', '0010_nomcom_first_call_for_volunteers'),
]
operations = [
migrations.AddField(
model_name='nomcom',
name='is_accepting_volunteers',
field=models.BooleanField(default=False, help_text='Is this nomcom is currently accepting volunteers?', verbose_name='Accepting volunteers'),
),
migrations.CreateModel(
name='Volunteer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('affiliation', models.CharField(blank=True, max_length=255)),
('nomcom', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nomcom.NomCom')),
('person', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='person.Person')),
],
),
]

View file

@ -0,0 +1,102 @@
# Copyright The IETF Trust 2021 All Rights Reserved
# Generated by Django 2.2.24 on 2021-06-10 12:50
from django.db import migrations
def forward(apps, schema_editor):
Volunteer = apps.get_model('nomcom','Volunteer')
NomCom = apps.get_model('nomcom','NomCom')
nc = NomCom.objects.get(group__acronym='nomcom2021')
nc.is_accepting_volunteers = True
nc.save()
volunteers = [
(21684, 'Futurewei Technologies'), # Barry Leiba
(117988, 'Google LLC'), # Benjamin M. Schwartz
(122671, 'Fastmail Pty Ltd'), # Bron Gondwana
(124329, 'Huawei'), # Cheng Li
(113580, 'China Mobile'), # Weiqiang Cheng
(22933, 'LabN Consulting'), # Christian Hopps
(102391, 'Futurewei Technologies, Inc'), # Donald E. Eastlake 3rd
(111477, 'Huawei'), # Dhruv Dhody
(12695, 'Mozilla'), # Eric Rescorla
(17141, 'APNIC'), # George G. Michaelson
(108833, 'Huawei'), # Luigi Iannone
(118908, 'Huawei Technologies'), # Giuseppe Fioccola
(5376, 'Vigil Security, LLC'), # Russ Housley
(118100, 'Equinix'), # Ignas Bagdonas
(107287, 'rtfm llp'), # Jim Reid
(123344, 'Netflix'), # Theresa Enghardt
(109226, 'Huawei'), # Italo Busi
(113152, 'Ericsson'), # Jaime Jimenez
(111354, 'Juniper Networks'), # John Drake
(112342, 'Cisco Systems, Inc.'), # Jakob Heitz
(109207, 'Huawei Technologies Co., Ltd.'), # 江元龙
(110737, 'Huawei Technologies'), # Jie Dong
(109330, 'Ericsson'), # John Preuß Mattsson
(123589, 'Nokia'), # Julien Maisonneuve
(124655, 'UK National Cyber Security Centre (NCSC)'), # Kirsty Paine
(119463, 'Akamai Technologies, Inc.'), # Kyle Rose
(109983, 'Huawei Technologies Co.,Ltd.'), # Bo Wu
(2097, 'Cisco Systems'), # Eliot Lear
(567, 'UCLA'), # Lixia Zhang
(107762, 'Huawei'), # Mach Chen
(125198, 'Juniper Networks'), # Melchior Aelmans
(104294, 'Ericsson'), # Magnus Westerlund
(104495, 'Impedance Mismatch LLC'), # Marc Petit-Huguenin
(119947, 'Painless Security, LLC'), # Margaret Cullen
(102830, 'Independent'), # Mary Barnes
(116593, 'Fastly'), # Patrick McManus
(102254, 'Sandelman Software Works'), # Michael Richardson
(20356, 'Apple'), # Ted Lemon
(103881, 'Fastly'), # Mark Nottingham
(106741, 'NthPermutation Security'), # Michael StJohns
(116323, 'Moulay Ismail University of Meknes, Morocco'), # Nabil Benamar
(20106, 'W3C/MIT'), # Samuel Weiler
(105691, 'cisco'), # Ole Trøan
(121160, 'Independent, Amazon'), # Padma Pillay-Esnault
(115824, 'Cisco Systems'), # Pascal Thubert
(122637, 'Huawei Technologies Co.,Ltd.'), # Shuping Peng
(112952, 'Huawei'), # Haibo Wang
(5234, 'IIJ Research Lab & Arrcus Inc'), # Randy Bush
(101568, 'Juniper Networks'), # Ron Bonica
(123443, 'Huawei Technologies Co.,Ltd.'), # Bing Liu (Remy)
(18321, 'Episteme Technology Consulting LLC'), # Pete Resnick
(18427, 'Akamai'), # Rich Salz
(126259, 'Huawei Technologies Co.,Ltd.'), # Fan Yang
(115724, 'Juniper Networks'), # Shraddha Hegde
(125509, 'Tencent'), # Shuai Zhao
(110614, 'Huawei'), # Tal Mizrahi
(123395, 'APNIC'), # Tom Harrison
(116516, 'Juniper Networks'), # Tarek Saad
(11834, 'Futurewei USA'), # Toerless Eckert
(123962, 'Open-Xchange'), # Vittorio Bertola
(126530, 'Huawei'), # Yali Wang
(106199, 'Ericsson'), # Wassim Haddad
(125173, 'Huawei'), # Wei Pan
(111299, 'ZTE Corporation'), # Xiao Min
(113285, 'Huawei Technologies'), # XiPeng Xiao
(116337, 'Futurewei Technologies Inc.'), # Yingzhen Qu
(123974, 'ZTE'), # Zheng Zhang
(117500, 'Huawei'), # Guangying Zheng
(115934, 'Huawei Technologies'), # Haomian Zheng
(110966, 'Juniper'), # Zhaohui (Jeffrey) Zhang
]
for pk, affiliation in volunteers:
nc.volunteer_set.create(person_id=pk, affiliation=affiliation)
def reverse(apps, schema_editor):
Volunteer = apps.get_model('nomcom','Volunteer')
Volunteer.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('nomcom', '0011_volunteers'),
]
operations = [
migrations.RunPython(forward,reverse)
]

View file

@ -59,6 +59,8 @@ class NomCom(models.Model):
help_text='Display pictures of each nominee (if available) on the feedback pages')
show_accepted_nominees = models.BooleanField(verbose_name='Show accepted nominees', default=True,
help_text='Show accepted nominees on the public nomination page')
is_accepting_volunteers = models.BooleanField(verbose_name="Accepting volunteers", default=False,
help_text='Is this nomcom is currently accepting volunteers?')
first_call_for_volunteers = models.DateField(verbose_name='Date of the first call for volunteers', blank=True, null=True)
class Meta:
@ -311,3 +313,11 @@ class TopicFeedbackLastSeen(models.Model):
topic = ForeignKey(Topic)
time = models.DateTimeField(auto_now=True)
class Volunteer(models.Model):
nomcom = ForeignKey('NomCom')
person = ForeignKey(Person)
affiliation = models.CharField(blank=True, max_length=255)
def __str__(self):
return f'{self.person} for {self.nomcom}'

View file

@ -12,7 +12,7 @@ from tastypie.cache import SimpleCache
from ietf import api
from ietf.nomcom.models import (NomCom, Position, Nominee, ReminderDates, NomineePosition,
Feedback, Nomination, FeedbackLastSeen, Topic, TopicFeedbackLastSeen, )
Feedback, Nomination, FeedbackLastSeen, Topic, TopicFeedbackLastSeen, Volunteer, )
from ietf.group.resources import GroupResource
class NomComResource(ModelResource):
@ -226,3 +226,22 @@ class TopicFeedbackLastSeenResource(ModelResource):
"topic": ALL_WITH_RELATIONS,
}
api.nomcom.register(TopicFeedbackLastSeenResource())
from ietf.person.resources import PersonResource
class VolunteerResource(ModelResource):
nomcom = ToOneField(NomComResource, 'nomcom')
person = ToOneField(PersonResource, 'person')
class Meta:
queryset = Volunteer.objects.all()
serializer = api.Serializer()
cache = SimpleCache()
#resource_name = 'volunteer'
ordering = ['id', ]
filtering = {
"id": ALL,
"affiliation": ALL,
"nomcom": ALL_WITH_RELATIONS,
"person": ALL_WITH_RELATIONS,
}
api.nomcom.register(VolunteerResource())

View file

@ -23,7 +23,8 @@ import debug # pyflakes:ignore
from ietf.dbtemplate.factories import DBTemplateFactory
from ietf.dbtemplate.models import DBTemplate
from ietf.doc.factories import DocEventFactory, WgDocumentAuthorFactory
from ietf.doc.factories import DocEventFactory, WgDocumentAuthorFactory, \
NewRevisionDocEventFactory, DocumentAuthorFactory
from ietf.group.factories import GroupFactory, GroupHistoryFactory, RoleFactory, RoleHistoryFactory
from ietf.group.models import Group, Role
from ietf.meeting.factories import MeetingFactory
@ -40,7 +41,7 @@ from ietf.nomcom.factories import NomComFactory, FeedbackFactory, TopicFactory,
key
from ietf.nomcom.utils import get_nomcom_by_year, make_nomineeposition, \
get_hash_nominee_position, is_eligible, list_eligible, \
get_eligibility_date
get_eligibility_date, suggest_affiliation
from ietf.person.factories import PersonFactory, EmailFactory
from ietf.person.models import Email, Person
from ietf.stats.models import MeetingRegistration
@ -1833,11 +1834,42 @@ Junk body for testing
continue
first_name, last_name = ascii.rsplit(None, 1)
MeetingRegistration.objects.create(meeting=meeting, first_name=first_name, last_name=last_name, person=person, country_code='WO', email=email, attended=True)
# test the page
url = reverse('ietf.nomcom.views.eligible',kwargs={'year':self.nc.year()})
login_testing_unauthorized(self,self.chair.user.username,url)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
for view in ('public_eligible','private_eligible'):
url = reverse(f'ietf.nomcom.views.{view}',kwargs={'year':self.nc.year()})
for username in (self.chair.user.username,'secretary'):
login_testing_unauthorized(self,username,url)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.client.logout()
self.client.login(username='plain',password='plain+password')
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
def test_volunteers(self):
year = self.nc.year()
def first_meeting_of_year(year):
assert isinstance(year, int)
assert year >= 1991
return (year-1985)*3+2
people = PersonFactory.create_batch(10)
meeting_start = first_meeting_of_year(year-2)
for number in range(meeting_start, meeting_start+8):
m = MeetingFactory.create(type_id='ietf', number=number)
for p in people:
m.meetingregistration_set.create(person=p)
for p in people:
self.nc.volunteer_set.create(person=p,affiliation='something')
for view in ('public_volunteers','private_volunteers'):
url = reverse(f'ietf.nomcom.views.{view}', kwargs=dict(year=self.nc.year()))
for username in (self.chair.user.username,'secretary'):
login_testing_unauthorized(self,username,url)
response = self.client.get(url)
self.assertContains(response,people[-1].email(),status_code=200)
self.client.logout()
self.client.login(username='plain',password='plain+password')
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
class NomComIndexTests(TestCase):
@ -2424,3 +2456,76 @@ class rfc8989EligibilityTests(TestCase):
self.assertFalse(is_eligible(person,self.nomcom))
self.assertEqual(set(list_eligible(nomcom=self.nomcom)),set(eligible))
class VolunteerTests(TestCase):
def test_volunteer(self):
url = reverse('ietf.nomcom.views.volunteer')
person = PersonFactory()
login_testing_unauthorized(self, person.user.username, url)
r = self.client.get(url)
self.assertContains(r, 'NomCom is not accepting volunteers at this time', status_code=200)
year = datetime.date.today().year
nomcom = NomComFactory(group__acronym=f'nomcom{year}', is_accepting_volunteers=False)
r = self.client.get(url)
self.assertContains(r, 'NomCom is not accepting volunteers at this time', status_code=200)
nomcom.is_accepting_volunteers = True
nomcom.save()
MeetingRegistrationFactory(person=person, affiliation='mtg_affiliation')
r = self.client.get(url)
self.assertContains(r, 'Volunteer for NomCom', status_code=200)
self.assertContains(r, 'mtg_affiliation')
r=self.client.post(url, dict(nomcoms=[nomcom.pk], affiliation=''))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(q('form div.has-error #id_affiliation'))
r=self.client.post(url, dict(nomcoms=[], affiliation='something'))
q = PyQuery(r.content)
self.assertTrue(q('form div.has-error #id_nomcoms'))
r=self.client.post(url, dict(nomcoms=[nomcom.pk], affiliation='something'))
self.assertRedirects(r, reverse('ietf.ietfauth.views.profile'))
self.assertEqual(person.volunteer_set.get(nomcom=nomcom).affiliation, 'something')
r=self.client.get(url)
self.assertContains(r, 'already volunteered', status_code=200)
person.volunteer_set.all().delete()
nomcom2 = NomComFactory(group__acronym=f'nomcom{year-1}', is_accepting_volunteers=True)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('#id_nomcoms div.checkbox')), 2)
r = self.client.post(url, dict(nomcoms=[nomcom.pk, nomcom2.pk], affiliation='something'))
self.assertRedirects(r, reverse('ietf.ietfauth.views.profile'))
self.assertEqual(person.volunteer_set.count(), 2)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertFalse(q('form div#id_nomcoms'))
self.assertIn(f'{nomcom.year()}/', q('#already-volunteered').text())
self.assertIn(f'{nomcom2.year()}/', q('#already-volunteered').text())
person.volunteer_set.all().delete()
r=self.client.post(url, dict(nomcoms=[nomcom2.pk], affiliation='something'))
self.assertRedirects(r, reverse('ietf.ietfauth.views.profile'))
self.assertEqual(person.volunteer_set.count(), 1)
self.assertEqual(person.volunteer_set.first().nomcom, nomcom2)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('#id_nomcoms div.checkbox')), 1)
self.assertNotIn(f'{nomcom.year()}/', q('#already-volunteered').text())
self.assertIn(f'{nomcom2.year()}/', q('#already-volunteered').text())
def test_suggest_affiliation(self):
person = PersonFactory()
self.assertEqual(suggest_affiliation(person), '')
da = DocumentAuthorFactory(person=person,affiliation='auth_affil')
NewRevisionDocEventFactory(doc=da.document)
self.assertEqual(suggest_affiliation(person), 'auth_affil')
nc = NomComFactory()
nc.volunteer_set.create(person=person,affiliation='volunteer_affil')
self.assertEqual(suggest_affiliation(person), 'volunteer_affil')
MeetingRegistrationFactory(person=person, affiliation='meeting_affil')
self.assertEqual(suggest_affiliation(person), 'meeting_affil')

View file

@ -7,6 +7,7 @@ urlpatterns = [
url(r'^$', views.index),
url(r'^ann/$', views.announcements),
url(r'^history/$', views.history),
url(r'^volunteer/$', views.volunteer),
url(r'^(?P<year>\d{4})/private/$', views.private_index),
url(r'^(?P<year>\d{4})/private/key/$', views.private_key),
url(r'^(?P<year>\d{4})/private/help/$', views.configuration_help),
@ -37,7 +38,8 @@ urlpatterns = [
url(r'^(?P<year>\d{4})/private/chair/topic/add/$', views.edit_topic),
url(r'^(?P<year>\d{4})/private/chair/topic/(?P<topic_id>\d+)/$', views.edit_topic),
url(r'^(?P<year>\d{4})/private/chair/topic/(?P<topic_id>\d+)/remove/$', views.remove_topic),
url(r'^(?P<year>\d{4})/private/chair/eligible/$', views.eligible ),
url(r'^(?P<year>\d{4})/private/chair/eligible/$', views.private_eligible),
url(r'^(?P<year>\d{4})/private/chair/volunteers/$', views.private_volunteers),
url(r'^(?P<year>\d{4})/$', views.year_index),
url(r'^(?P<year>\d{4})/requirements/$', views.requirements),
@ -47,6 +49,8 @@ urlpatterns = [
url(r'^(?P<year>\d{4})/nominate/$', views.public_nominate),
url(r'^(?P<year>\d{4})/nominate/newperson$', views.public_nominate_newperson),
url(r'^(?P<year>\d{4})/process-nomination-status/(?P<nominee_position_id>\d+)/(?P<state>[\w]+)/(?P<date>[\d]+)/(?P<hash>[a-f0-9]+)/$', views.process_nomination_status),
url(r'^(?P<year>\d{4})/eligible/$', views.public_eligible),
url(r'^(?P<year>\d{4})/volunteers/$', views.public_volunteers),
# use the generic view from message
url(r'^ann/(?P<message_id>\d+)/$', message_views.message, {'group_type': "nomcom" }),
]

View file

@ -22,7 +22,7 @@ from django.template.loader import render_to_string
from django.shortcuts import get_object_or_404
from ietf.dbtemplate.models import DBTemplate
from ietf.doc.models import DocEvent
from ietf.doc.models import DocEvent, NewRevisionDocEvent
from ietf.group.models import Group, Role
from ietf.person.models import Email, Person
from ietf.mailtrigger.utils import gather_address_lists
@ -589,4 +589,16 @@ def three_of_five_eligible(previous_five, queryset=None):
queryset = Person.objects.all()
return queryset.filter(meetingregistration__meeting__in=list(previous_five),meetingregistration__attended=True).annotate(mtg_count=Count('meetingregistration')).filter(mtg_count__gte=3)
def suggest_affiliation(person):
recent_meeting = person.meetingregistration_set.order_by('-meeting__date').first()
affiliation = recent_meeting.affiliation if recent_meeting else ''
if not affiliation:
recent_volunteer = person.volunteer_set.order_by('-nomcom__group__acronym').first()
if recent_volunteer:
affiliation = recent_volunteer.affiliation
if not affiliation:
recent_draft_revision = NewRevisionDocEvent.objects.filter(doc__type_id='draft',doc__documentauthor__person=person).order_by('-time').first()
if recent_draft_revision:
affiliation = recent_draft_revision.doc.documentauthor_set.filter(person=person).first().affiliation
return affiliation

View file

@ -31,10 +31,10 @@ from ietf.nomcom.forms import (NominateForm, NominateNewPersonForm, FeedbackForm
PrivateKeyForm, EditNomcomForm, EditNomineeForm,
PendingFeedbackForm, ReminderDatesForm, FullFeedbackFormSet,
FeedbackEmailForm, NominationResponseCommentForm, TopicForm,
NewEditMembersForm,)
NewEditMembersForm, VolunteerForm, )
from ietf.nomcom.models import (Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates,
FeedbackLastSeen, Topic, TopicFeedbackLastSeen, )
from ietf.nomcom.utils import (get_nomcom_by_year, store_nomcom_private_key,
from ietf.nomcom.utils import (get_nomcom_by_year, store_nomcom_private_key, suggest_affiliation,
get_hash_nominee_position, send_reminder_to_nominees, list_eligible,
HOME_TEMPLATE, NOMINEE_ACCEPT_REMINDER_TEMPLATE,NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE, )
@ -1269,8 +1269,33 @@ def extract_email_lists(request, year):
'bypos': bypos,
})
@login_required
def volunteer(request):
nomcoms = NomCom.objects.filter(is_accepting_volunteers=True)
if not nomcoms.exists():
return render(request, 'nomcom/volunteers_not_accepted.html')
person = request.user.person
already_volunteered = nomcoms.filter(volunteer__person=person)
can_volunteer = nomcoms.exclude(volunteer__person=person)
if request.method=='POST':
form = VolunteerForm(person=person, data=request.POST)
if form.is_valid():
for nc in form.cleaned_data['nomcoms']:
nc.volunteer_set.create(person=person, affiliation=form.cleaned_data['affiliation'])
return redirect('ietf.ietfauth.views.profile')
else:
form = VolunteerForm(person=person,initial=dict(nomcoms=can_volunteer, affiliation=suggest_affiliation(person)))
return render(request, 'nomcom/volunteer.html', {'form':form, 'can_volunteer': can_volunteer, 'already_volunteered': already_volunteered})
def public_eligible(request, year):
return eligible(request=request, year=year, public=True)
def private_eligible(request, year):
return eligible(request=request, year=year, public=False)
@role_required("Nomcom Chair", "Nomcom Advisor", "Secretariat")
def eligible(request, year):
def eligible(request, year, public=False):
nomcom = get_nomcom_by_year(year)
eligible_persons = list(list_eligible(nomcom=nomcom))
@ -1281,3 +1306,23 @@ def eligible(request, year):
'year':year,
'eligible_persons':eligible_persons,
})
def public_volunteers(request, year):
return volunteers(request=request, year=year, public=True)
def private_volunteers(request, year):
return volunteers(request=request, year=year, public=False)
@role_required("Nomcom Chair", "Nomcom Advisor", "Secretariat")
def volunteers(request, year, public=False):
nomcom = get_nomcom_by_year(year)
# pull list of volunteers
# get queryset of all eligible (from utils)
# decorate members of the list with eligibility
volunteers = nomcom.volunteer_set.all()
eligible = list_eligible(nomcom)
for v in volunteers:
v.eligible = v.person in eligible
volunteers = sorted(volunteers,key=lambda v:(not v.eligible,v.person.last_name()))
return render(request, 'nomcom/volunteers.html', dict(year=year, nomcom=nomcom, volunteers=volunteers, public=public))

View file

@ -1,4 +1,4 @@
{% extends "nomcom/nomcom_private_base.html" %}
{% extends public|yesno:"nomcom/nomcom_public_base.html,nomcom/nomcom_private_base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load bootstrap3 %}

View file

@ -3,6 +3,7 @@
{% load origin %}
{% load nomcom_tags %}
{% load ietf_filters %}
{% block title %}NomCom {{ year }} Private{% block subtitle %}{% endblock %}{% endblock %}
@ -46,7 +47,6 @@
{% if nomcom.group.state_id == 'active' %}
<li {% if selected == "edit_members" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.edit_members' year %}">Edit Members</a></li>
{% endif %}
<li {% if selected == "view_eligible" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.eligible' year %}">View Eligible</a></li>
<li {% if selected == "help" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.configuration_help' year %}">Configuration Help</a></li>
<li role="presentation" class="dropdown-header">Other Tools</li>
<li {% if selected == "announce" %}class="active"{% endif %}><a href="{% url 'ietf.secr.announcement.views.main'%}">Announcement Tool</a></li>
@ -55,6 +55,15 @@
</ul>
</li>
{% endif %}
{% if user|is_chair_or_advisor:year or user|has_role:"Secretariat" %}
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Volunteers<span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li {% if selected == "view_eligible" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.private_eligible' year %}">View Eligible</a></li>
<li {% if selected == "view_volunteers" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.private_volunteers' year %}">View Volunteers</a></li>
</ul>
</li>
{% endif %}
</ul>
{% block nomcom_content %}

View file

@ -3,6 +3,7 @@
{% load origin %}
{% load nomcom_tags %}
{% load ietf_filters %}
{% block title %}NomCom {{ year }}{% block subtitle %}{% endblock %}{% endblock %}
@ -19,6 +20,15 @@
{% endif %}
<li {% if selected == "requirements" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.requirements' year %}">Desired expertise</a></li>
<li {% if selected == "questionnaires" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.questionnaires' year %}">Questionnaires</a></li>
{% if user|is_chair_or_advisor:year or user|has_role:"Secretariat" %}
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Volunteers<span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li {% if selected == "view_eligible" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.public_eligible' year %}">View Eligible</a></li>
<li {% if selected == "view_volunteers" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.public_volunteers' year %}">View Volunteers</a></li>
</ul>
</li>
{% endif %}
</ul>
{% block nomcom_content %}

View file

@ -0,0 +1,32 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2021, All Rights Reserved #}
{% load origin %}
{% load bootstrap3 %}
{% block title %}Volunteer for NomCom{% endblock %}
{% block content %}
{% origin %}
<h1>Volunteer for NomCom</h1>
{% if can_volunteer.exists %}
<form method="post" >
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
{% endbuttons %}
</form>
{% endif %}
{% if already_volunteered.exists %}
<div class="alert alert-info" id="already-volunteered">
You have already volunteered for the {% for nc in already_volunteered %}{{nc.year}}/{{nc.year|add:1}}{% if not forloop.last %}, {% endif %}{% endfor %} Nominating Committee{{already_volunteered|pluralize}}.
To modify your volunteer status, contact the NomCom chair.
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,40 @@
{% extends public|yesno:"nomcom/nomcom_public_base.html,nomcom/nomcom_private_base.html" %}
{# Copyright The IETF Trust 2021, All Rights Reserved #}
{% load origin %}
{% load bootstrap3 %}
{% load static %}
{% block subtitle %} - Volunteers{% endblock %}
{% block pagehead %}
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
{% endblock %}
{% block nomcom_content %}
{% origin %}
<h2>Volunteers for {{ nomcom.group }}</h2>
<table class="table table-condensed table-striped tablesorter">
<thead>
<th>Eligible</th>
<th>Last Name</th>
<th>First Name</th>
<th>Affiliation</th>
<th>Email Addresses</th>
</thead>
{% for v in volunteers %}
<tr>
<td>{{v.eligible|yesno}}
<td><a href="{% url 'ietf.person.views.profile' v.person.name %}">{{v.person.last_name}}</a></td>
<td>{{v.person.first_name}}</td>
<td>{{v.affiliation}}</td>
<td>{% for e in v.person.email_set.all %}{{e.address}}{% if not forloop.last %}, {% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</table>
{% endblock nomcom_content %}
{% block js %}
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
{% endblock %}

View file

@ -0,0 +1,10 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2021, All Rights Reserved #}
{% load origin %}
{% block title %}Not Accepting Volunteers{% endblock %}
{% block content %}
{% origin %}
<h1> NomCom is not accepting volunteers at this time</h1>
{% endblock %}

View file

@ -66,7 +66,14 @@
<div class="form-group">
<label class="col-sm-2 control-label">Nomcom Eligible</label>
<div class="col-sm-1 form-control-static">{{person|is_nomcom_eligible|yesno:'Yes,No,No'}}</div>
<div class="col-sm-1 form-control-static">
{{person|is_nomcom_eligible|yesno:'Yes,No,No'}}
{% if volunteer_status == 'allow' %}
<br>
<a id="volunteer-button" class="btn btn-primary" href="{% url "ietf.nomcom.views.volunteer" %}">Volunteer</a>
{% endif %}
</div>
<div class="col-sm-9">
<p class="alert alert-info form-control-static">
If you believe this calculation is incorrect, make sure you've added all the
@ -80,6 +87,9 @@
for eligibility requirements.
For the 2021 nomcom, see also <a href="{% url 'ietf.doc.views_doc.document_main' name='rfc8989' %}">RFC 8989</a>.
{% if volunteer_status == 'volunteered' %}
<br/><strong id='volunteered'>You have volunteered for the {{nomcom.group.name}}.</strong> To modify your volunteer status, contact the NomCom chair.
{% endif %}
</p>
</div>
</div>
@ -89,7 +99,9 @@
<div class="col-sm-10 form-control-static">
{% for extres in person.personextresource_set.all %}
<div class="row">
<div class="col-sm-1"><span title="{{ extres.name.name}}">{% firstof extres.display_name extres.name.name %}</span></div>
<div class="col-sm-1">
<span title="{{ extres.name.name}}">{% firstof extres.display_name extres.name.name %}</span>
</div>
<div class="col-sm-11">{{extres.value}}
{% if forloop.first %}&nbsp;<a href="{% url 'ietf.ietfauth.views.edit_person_externalresources' %}"><span class="fa fa-pencil"></span></a>{% endif %}
</div>