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, result.append(Permission.objects.get(content_type__app_label=app_label,
codename=codename)) codename=codename))
except Permission.DoesNotExist: except Permission.DoesNotExist:
print "NO SUCH PERMISSION: %s, %s" % (app_label, codename) print ("NO SUCH PERMISSION: %s, %s" % (app_label, codename))
raise raise
return result return result
@ -61,6 +61,7 @@ def main():
'group.add_groupevent','group.change_groupevent','group.delete_groupevent', 'group.add_groupevent','group.change_groupevent','group.delete_groupevent',
'iesg.add_telechatagendaitem','iesg.change_telechatagendaitem','iesg.delete_telechatagendaitem', 'iesg.add_telechatagendaitem','iesg.change_telechatagendaitem','iesg.delete_telechatagendaitem',
'iesg.add_telechatdate','iesg.change_telechatdate','iesg.delete_telechatdate', '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', 'mailinglists.add_list','mailinglists.change_list','mailinglists.delete_list',
'mailtrigger.add_mailtrigger','mailtrigger.change_mailtrigger','mailtrigger.delete_mailtrigger', 'mailtrigger.add_mailtrigger','mailtrigger.change_mailtrigger','mailtrigger.delete_mailtrigger',
'mailtrigger.add_recipient','mailtrigger.change_recipient','mailtrigger.delete_recipient', 'mailtrigger.add_recipient','mailtrigger.change_recipient','mailtrigger.delete_recipient',
@ -71,6 +72,7 @@ def main():
'meeting.add_urlresource','meeting.change_urlresource','meeting.delete_urlresource', 'meeting.add_urlresource','meeting.change_urlresource','meeting.delete_urlresource',
'message.add_announcementfrom','message.change_announcementfrom','message.delete_announcementfrom', 'message.add_announcementfrom','message.change_announcementfrom','message.delete_announcementfrom',
'nomcom.add_nomcom','nomcom.change_nomcom','nomcom.delete_nomcom', '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_person','person.change_person','person.delete_person',
'person.add_alias','person.change_alias','person.delete_alias', 'person.add_alias','person.change_alias','person.delete_alias',
'person.add_email','person.change_email','person.delete_email', '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 lc_text = draft.latest_event(WriteupDocEvent, type="changed_last_call_text").text
self.assertTrue("contains these normative down" in lc_text) self.assertTrue("contains these normative down" in lc_text)
self.assertTrue("rfc6666" 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') 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.ietfauth.htpasswd import update_htpasswd_file
from ietf.mailinglists.models import Subscribed from ietf.mailinglists.models import Subscribed
from ietf.meeting.factories import MeetingFactory from ietf.meeting.factories import MeetingFactory
from ietf.nomcom.factories import NomComFactory
from ietf.person.factories import PersonFactory, EmailFactory from ietf.person.factories import PersonFactory, EmailFactory
from ietf.person.models import Person, Email, PersonalApiKey from ietf.person.models import Person, Email, PersonalApiKey
from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory
@ -333,6 +334,33 @@ class IetfAuthTests(TestCase):
self.assertEqual(updated_roles[0].email_id, new_email_address) 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): def test_reset_password(self):
url = urlreverse(ietf.ietfauth.views.password_reset) 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.ietfauth.utils import role_required, has_role
from ietf.mailinglists.models import Subscribed, Whitelisted from ietf.mailinglists.models import Subscribed, Whitelisted
from ietf.name.models import ExtResourceName 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.person.models import Person, Email, Alias, PersonalApiKey, PERSON_API_KEY_VALUES
from ietf.review.models import ReviewerSettings, ReviewWish, ReviewAssignment from ietf.review.models import ReviewerSettings, ReviewWish, ReviewAssignment
from ietf.review.utils import unavailable_periods_to_list, get_default_filter_re 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') emails = Email.objects.filter(person=person).exclude(address__startswith='unknown-email-').order_by('-active','-time')
new_email_forms = [] 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': if request.method == 'POST':
person_form = get_person_form(request.POST, instance=person) person_form = get_person_form(request.POST, instance=person)
for r in roles: for r in roles:
@ -287,6 +296,8 @@ def profile(request):
'roles': roles, 'roles': roles,
'emails': emails, 'emails': emails,
'new_email_forms': new_email_forms, 'new_email_forms': new_email_forms,
'nomcom': nc,
'volunteer_status': volunteer_status,
'settings':settings, 'settings':settings,
}) })

View file

@ -2499,10 +2499,10 @@
"is_schedulable": true, "is_schedulable": true,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"lead\",\n \"delegate\",\n \"matman\"\n]", "matman_roles": "[\n \"chair\",\n \"lead\",\n \"delegate\",\n \"matman\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"ietf" "ietf"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"lead\",\n \"delegate\",\n \"matman\"\n]", "role_order": "[\n \"chair\",\n \"lead\",\n \"delegate\",\n \"matman\"\n]",
"show_on_agenda": true "show_on_agenda": true
@ -2536,8 +2536,8 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\"\n]", "matman_roles": "[\n \"chair\"\n]",
"parent_types": [],
"need_parent": false, "need_parent": false,
"parent_types": [],
"req_subm_approval": false, "req_subm_approval": false,
"role_order": "[\n \"chair\"\n]", "role_order": "[\n \"chair\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2571,11 +2571,11 @@
"is_schedulable": true, "is_schedulable": true,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]", "matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"area", "area",
"ietf" "ietf"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": true "show_on_agenda": true
@ -2609,10 +2609,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]", "matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": true,
"parent_types": [ "parent_types": [
"ietf" "ietf"
], ],
"need_parent": true,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2646,10 +2646,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]", "matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": true,
"parent_types": [ "parent_types": [
"area" "area"
], ],
"need_parent": true,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2683,10 +2683,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\"\n]", "matman_roles": "[\n \"chair\",\n \"delegate\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"ietf" "ietf"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": true "show_on_agenda": true
@ -2720,8 +2720,8 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\"\n]", "matman_roles": "[\n \"chair\"\n]",
"parent_types": [],
"need_parent": false, "need_parent": false,
"parent_types": [],
"req_subm_approval": false, "req_subm_approval": false,
"role_order": "[\n \"chair\"\n]", "role_order": "[\n \"chair\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2755,8 +2755,8 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "\"[]\"", "material_types": "\"[]\"",
"matman_roles": "[\n \"chair\",\n \"delegate\",\n \"member\"\n]", "matman_roles": "[\n \"chair\",\n \"delegate\",\n \"member\"\n]",
"parent_types": [],
"need_parent": false, "need_parent": false,
"parent_types": [],
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"delegate\",\n \"member\"\n]", "role_order": "[\n \"chair\",\n \"delegate\",\n \"member\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2790,10 +2790,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\"\n]", "matman_roles": "[\n \"chair\",\n \"delegate\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"ietf" "ietf"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2827,10 +2827,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[]", "matman_roles": "[]",
"need_parent": true,
"parent_types": [ "parent_types": [
"area" "area"
], ],
"need_parent": true,
"req_subm_approval": false, "req_subm_approval": false,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2864,10 +2864,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]", "matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"irtf" "irtf"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2901,8 +2901,8 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\"\n]", "matman_roles": "[\n \"chair\",\n \"delegate\"\n]",
"parent_types": [],
"need_parent": false, "need_parent": false,
"parent_types": [],
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"delegate\"\n]", "role_order": "[\n \"chair\",\n \"delegate\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2936,10 +2936,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"secr\"\n]", "matman_roles": "[\n \"chair\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"isoc" "isoc"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -2973,10 +2973,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\"\n]", "matman_roles": "[\n \"chair\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"area" "area"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"member\",\n \"advisor\"\n]", "role_order": "[\n \"chair\",\n \"member\",\n \"advisor\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -3010,10 +3010,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"lead\",\n \"chair\",\n \"secr\"\n]", "matman_roles": "[\n \"lead\",\n \"chair\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"ietf" "ietf"
], ],
"need_parent": false,
"req_subm_approval": false, "req_subm_approval": false,
"role_order": "[\n \"lead\",\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"lead\",\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -3047,10 +3047,10 @@
"is_schedulable": true, "is_schedulable": true,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]", "matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"irtf" "irtf"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": true "show_on_agenda": true
@ -3063,7 +3063,7 @@
"about_page": "ietf.group.views.group_about", "about_page": "ietf.group.views.group_about",
"acts_like_wg": false, "acts_like_wg": false,
"admin_roles": "[\n \"chair\",\n \"secr\"\n]", "admin_roles": "[\n \"chair\",\n \"secr\"\n]",
"agenda_type": null, "agenda_type": "ietf",
"create_wiki": true, "create_wiki": true,
"custom_group_roles": true, "custom_group_roles": true,
"customize_workflow": false, "customize_workflow": false,
@ -3076,7 +3076,7 @@
"has_chartering_process": false, "has_chartering_process": false,
"has_default_jabber": false, "has_default_jabber": false,
"has_documents": false, "has_documents": false,
"has_meetings": false, "has_meetings": true,
"has_milestones": false, "has_milestones": false,
"has_nonsession_materials": false, "has_nonsession_materials": false,
"has_reviews": true, "has_reviews": true,
@ -3084,10 +3084,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"secr\"\n]", "matman_roles": "[\n \"ad\",\n \"secr\"\n]",
"need_parent": true,
"parent_types": [ "parent_types": [
"area" "area"
], ],
"need_parent": true,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -3121,8 +3121,8 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[]", "matman_roles": "[]",
"parent_types": [],
"need_parent": false, "need_parent": false,
"parent_types": [],
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"secr\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -3156,10 +3156,10 @@
"is_schedulable": true, "is_schedulable": true,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]", "matman_roles": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": true,
"parent_types": [ "parent_types": [
"irtf" "irtf"
], ],
"need_parent": true,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]", "role_order": "[\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"show_on_agenda": true "show_on_agenda": true
@ -3193,11 +3193,11 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[]", "matman_roles": "[]",
"need_parent": false,
"parent_types": [ "parent_types": [
"area", "area",
"sdo" "sdo"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"liaiman\"\n]", "role_order": "[\n \"liaiman\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -3231,10 +3231,10 @@
"is_schedulable": false, "is_schedulable": false,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"chair\",\n \"matman\"\n]", "matman_roles": "[\n \"chair\",\n \"matman\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"area" "area"
], ],
"need_parent": false,
"req_subm_approval": false, "req_subm_approval": false,
"role_order": "[\n \"chair\",\n \"member\",\n \"matman\"\n]", "role_order": "[\n \"chair\",\n \"member\",\n \"matman\"\n]",
"show_on_agenda": false "show_on_agenda": false
@ -3268,10 +3268,10 @@
"is_schedulable": true, "is_schedulable": true,
"material_types": "[\n \"slides\"\n]", "material_types": "[\n \"slides\"\n]",
"matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]", "matman_roles": "[\n \"ad\",\n \"chair\",\n \"delegate\",\n \"secr\"\n]",
"need_parent": false,
"parent_types": [ "parent_types": [
"area" "area"
], ],
"need_parent": false,
"req_subm_approval": true, "req_subm_approval": true,
"role_order": "[\n \"chair\",\n \"secr\",\n \"delegate\"\n]", "role_order": "[\n \"chair\",\n \"secr\",\n \"delegate\"\n]",
"show_on_agenda": true "show_on_agenda": true
@ -4165,6 +4165,7 @@
"fields": { "fields": {
"cc": [ "cc": [
"liaison_cc", "liaison_cc",
"liaison_coordinators",
"liaison_response_contacts", "liaison_response_contacts",
"liaison_technical_contacts" "liaison_technical_contacts"
], ],
@ -5503,6 +5504,14 @@
"model": "mailtrigger.recipient", "model": "mailtrigger.recipient",
"pk": "liaison_cc" "pk": "liaison_cc"
}, },
{
"fields": {
"desc": "The IAB liaison coordination team members",
"template": "<liaison-coordination@iab.org>"
},
"model": "mailtrigger.recipient",
"pk": "liaison_coordinators"
},
{ {
"fields": { "fields": {
"desc": "The assigned liaison manager for an external group ", "desc": "The assigned liaison manager for an external group ",
@ -10003,6 +10012,7 @@
"auth", "auth",
"aut-appr", "aut-appr",
"grp-appr", "grp-appr",
"ad-appr",
"manual", "manual",
"cancel" "cancel"
], ],
@ -12050,7 +12060,7 @@
"fields": { "fields": {
"desc": "", "desc": "",
"name": "Liaison CC Contact", "name": "Liaison CC Contact",
"order": 0, "order": 9,
"used": true "used": true
}, },
"model": "name.rolename", "model": "name.rolename",
@ -12060,7 +12070,7 @@
"fields": { "fields": {
"desc": "", "desc": "",
"name": "Liaison Contact", "name": "Liaison Contact",
"order": 0, "order": 8,
"used": true "used": true
}, },
"model": "name.rolename", "model": "name.rolename",
@ -12488,7 +12498,7 @@
}, },
{ {
"fields": { "fields": {
"desc": "IAB stream", "desc": "Internet Architecture Board (IAB)",
"name": "IAB", "name": "IAB",
"order": 4, "order": 4,
"used": true "used": true
@ -12498,7 +12508,7 @@
}, },
{ {
"fields": { "fields": {
"desc": "IETF stream", "desc": "Internet Engineering Task Force (IETF)",
"name": "IETF", "name": "IETF",
"order": 1, "order": 1,
"used": true "used": true
@ -12508,7 +12518,7 @@
}, },
{ {
"fields": { "fields": {
"desc": "IRTF Stream", "desc": "Internet Research Task Force (IRTF)",
"name": "IRTF", "name": "IRTF",
"order": 3, "order": 3,
"used": true "used": true
@ -12518,7 +12528,7 @@
}, },
{ {
"fields": { "fields": {
"desc": "Independent Submission Editor stream", "desc": "Independent Submission",
"name": "ISE", "name": "ISE",
"order": 2, "order": 2,
"used": true "used": true
@ -15243,9 +15253,9 @@
"fields": { "fields": {
"command": "xym", "command": "xym",
"switch": "--version", "switch": "--version",
"time": "2020-11-14T00:10:15.888", "time": "2021-06-08T00:12:46.324",
"used": true, "used": true,
"version": "xym 0.4.8" "version": "xym 0.5"
}, },
"model": "utils.versioninfo", "model": "utils.versioninfo",
"pk": 1 "pk": 1
@ -15254,7 +15264,7 @@
"fields": { "fields": {
"command": "pyang", "command": "pyang",
"switch": "--version", "switch": "--version",
"time": "2020-11-14T00:10:17.069", "time": "2021-06-08T00:12:48.137",
"used": true, "used": true,
"version": "pyang 2.4.0" "version": "pyang 2.4.0"
}, },
@ -15265,7 +15275,7 @@
"fields": { "fields": {
"command": "yanglint", "command": "yanglint",
"switch": "--version", "switch": "--version",
"time": "2020-11-14T00:10:17.405", "time": "2021-06-08T00:12:48.465",
"used": true, "used": true,
"version": "yanglint SO 1.6.7" "version": "yanglint SO 1.6.7"
}, },
@ -15276,9 +15286,9 @@
"fields": { "fields": {
"command": "xml2rfc", "command": "xml2rfc",
"switch": "--version", "switch": "--version",
"time": "2020-11-14T00:10:19.405", "time": "2021-06-08T00:12:51.318",
"used": true, "used": true,
"version": "xml2rfc 3.4.0" "version": "xml2rfc 3.8.0"
}, },
"model": "utils.versioninfo", "model": "utils.versioninfo",
"pk": 4 "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 -*- # -*- coding: utf-8 -*-
from django.contrib import admin from django.contrib import admin
from ietf.nomcom.models import ( ReminderDates, NomCom, Nomination, Nominee, NomineePosition, from ietf.nomcom.models import ( ReminderDates, NomCom, Nomination, Nominee, NomineePosition,
Position, Feedback, FeedbackLastSeen, TopicFeedbackLastSeen) Position, Feedback, FeedbackLastSeen, TopicFeedbackLastSeen,
Volunteer, )
class ReminderDatesAdmin(admin.ModelAdmin): class ReminderDatesAdmin(admin.ModelAdmin):
@ -68,4 +69,10 @@ class TopicFeedbackLastSeenAdmin(admin.ModelAdmin):
raw_id_fields = ['reviewer'] raw_id_fields = ['reviewer']
admin.site.register(TopicFeedbackLastSeen, TopicFeedbackLastSeenAdmin) 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.dbtemplate.forms import DBTemplateForm
from ietf.name.models import FeedbackTypeName, NomineePositionStateName from ietf.name.models import FeedbackTypeName, NomineePositionStateName
from ietf.nomcom.models import ( NomCom, Nomination, Nominee, NomineePosition, 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, from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE,
get_user_email, validate_private_key, validate_public_key, get_user_email, validate_private_key, validate_public_key,
make_nomineeposition, make_nomineeposition_for_newperson, make_nomineeposition, make_nomineeposition_for_newperson,
@ -833,3 +833,22 @@ class EditNomineeForm(forms.ModelForm):
class NominationResponseCommentForm(forms.Form): 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) 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') 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, show_accepted_nominees = models.BooleanField(verbose_name='Show accepted nominees', default=True,
help_text='Show accepted nominees on the public nomination page') 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) first_call_for_volunteers = models.DateField(verbose_name='Date of the first call for volunteers', blank=True, null=True)
class Meta: class Meta:
@ -310,4 +312,12 @@ class TopicFeedbackLastSeen(models.Model):
reviewer = ForeignKey(Person) reviewer = ForeignKey(Person)
topic = ForeignKey(Topic) topic = ForeignKey(Topic)
time = models.DateTimeField(auto_now=True) 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 import api
from ietf.nomcom.models import (NomCom, Position, Nominee, ReminderDates, NomineePosition, 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 from ietf.group.resources import GroupResource
class NomComResource(ModelResource): class NomComResource(ModelResource):
@ -226,3 +226,22 @@ class TopicFeedbackLastSeenResource(ModelResource):
"topic": ALL_WITH_RELATIONS, "topic": ALL_WITH_RELATIONS,
} }
api.nomcom.register(TopicFeedbackLastSeenResource()) 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.factories import DBTemplateFactory
from ietf.dbtemplate.models import DBTemplate 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.factories import GroupFactory, GroupHistoryFactory, RoleFactory, RoleHistoryFactory
from ietf.group.models import Group, Role from ietf.group.models import Group, Role
from ietf.meeting.factories import MeetingFactory from ietf.meeting.factories import MeetingFactory
@ -40,7 +41,7 @@ from ietf.nomcom.factories import NomComFactory, FeedbackFactory, TopicFactory,
key key
from ietf.nomcom.utils import get_nomcom_by_year, make_nomineeposition, \ from ietf.nomcom.utils import get_nomcom_by_year, make_nomineeposition, \
get_hash_nominee_position, is_eligible, list_eligible, \ 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.factories import PersonFactory, EmailFactory
from ietf.person.models import Email, Person from ietf.person.models import Email, Person
from ietf.stats.models import MeetingRegistration from ietf.stats.models import MeetingRegistration
@ -1833,11 +1834,42 @@ Junk body for testing
continue continue
first_name, last_name = ascii.rsplit(None, 1) 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) 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 for view in ('public_eligible','private_eligible'):
url = reverse('ietf.nomcom.views.eligible',kwargs={'year':self.nc.year()}) url = reverse(f'ietf.nomcom.views.{view}',kwargs={'year':self.nc.year()})
login_testing_unauthorized(self,self.chair.user.username,url) for username in (self.chair.user.username,'secretary'):
response = self.client.get(url) login_testing_unauthorized(self,username,url)
self.assertEqual(response.status_code, 200) 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): class NomComIndexTests(TestCase):
@ -2424,3 +2456,76 @@ class rfc8989EligibilityTests(TestCase):
self.assertFalse(is_eligible(person,self.nomcom)) self.assertFalse(is_eligible(person,self.nomcom))
self.assertEqual(set(list_eligible(nomcom=self.nomcom)),set(eligible)) 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'^$', views.index),
url(r'^ann/$', views.announcements), url(r'^ann/$', views.announcements),
url(r'^history/$', views.history), 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/$', views.private_index),
url(r'^(?P<year>\d{4})/private/key/$', views.private_key), url(r'^(?P<year>\d{4})/private/key/$', views.private_key),
url(r'^(?P<year>\d{4})/private/help/$', views.configuration_help), 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/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+)/$', 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/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})/$', views.year_index),
url(r'^(?P<year>\d{4})/requirements/$', views.requirements), 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/$', views.public_nominate),
url(r'^(?P<year>\d{4})/nominate/newperson$', views.public_nominate_newperson), 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})/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 # use the generic view from message
url(r'^ann/(?P<message_id>\d+)/$', message_views.message, {'group_type': "nomcom" }), 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 django.shortcuts import get_object_or_404
from ietf.dbtemplate.models import DBTemplate 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.group.models import Group, Role
from ietf.person.models import Email, Person from ietf.person.models import Email, Person
from ietf.mailtrigger.utils import gather_address_lists 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() 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) 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

@ -11,7 +11,7 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.forms.models import modelformset_factory, inlineformset_factory from django.forms.models import modelformset_factory, inlineformset_factory
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -31,10 +31,10 @@ from ietf.nomcom.forms import (NominateForm, NominateNewPersonForm, FeedbackForm
PrivateKeyForm, EditNomcomForm, EditNomineeForm, PrivateKeyForm, EditNomcomForm, EditNomineeForm,
PendingFeedbackForm, ReminderDatesForm, FullFeedbackFormSet, PendingFeedbackForm, ReminderDatesForm, FullFeedbackFormSet,
FeedbackEmailForm, NominationResponseCommentForm, TopicForm, FeedbackEmailForm, NominationResponseCommentForm, TopicForm,
NewEditMembersForm,) NewEditMembersForm, VolunteerForm, )
from ietf.nomcom.models import (Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates, from ietf.nomcom.models import (Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates,
FeedbackLastSeen, Topic, TopicFeedbackLastSeen, ) 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, get_hash_nominee_position, send_reminder_to_nominees, list_eligible,
HOME_TEMPLATE, NOMINEE_ACCEPT_REMINDER_TEMPLATE,NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE, ) HOME_TEMPLATE, NOMINEE_ACCEPT_REMINDER_TEMPLATE,NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE, )
@ -1269,8 +1269,33 @@ def extract_email_lists(request, year):
'bypos': bypos, '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") @role_required("Nomcom Chair", "Nomcom Advisor", "Secretariat")
def eligible(request, year): def eligible(request, year, public=False):
nomcom = get_nomcom_by_year(year) nomcom = get_nomcom_by_year(year)
eligible_persons = list(list_eligible(nomcom=nomcom)) eligible_persons = list(list_eligible(nomcom=nomcom))
@ -1281,3 +1306,23 @@ def eligible(request, year):
'year':year, 'year':year,
'eligible_persons':eligible_persons, '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 #} {# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %} {% load origin %}
{% load bootstrap3 %} {% load bootstrap3 %}

View file

@ -3,6 +3,7 @@
{% load origin %} {% load origin %}
{% load nomcom_tags %} {% load nomcom_tags %}
{% load ietf_filters %}
{% block title %}NomCom {{ year }} Private{% block subtitle %}{% endblock %}{% endblock %} {% block title %}NomCom {{ year }} Private{% block subtitle %}{% endblock %}{% endblock %}
@ -46,7 +47,6 @@
{% if nomcom.group.state_id == 'active' %} {% 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> <li {% if selected == "edit_members" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.edit_members' year %}">Edit Members</a></li>
{% endif %} {% 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 {% 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 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> <li {% if selected == "announce" %}class="active"{% endif %}><a href="{% url 'ietf.secr.announcement.views.main'%}">Announcement Tool</a></li>
@ -55,6 +55,15 @@
</ul> </ul>
</li> </li>
{% endif %} {% 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> </ul>
{% block nomcom_content %} {% block nomcom_content %}

View file

@ -3,6 +3,7 @@
{% load origin %} {% load origin %}
{% load nomcom_tags %} {% load nomcom_tags %}
{% load ietf_filters %}
{% block title %}NomCom {{ year }}{% block subtitle %}{% endblock %}{% endblock %} {% block title %}NomCom {{ year }}{% block subtitle %}{% endblock %}{% endblock %}
@ -19,6 +20,15 @@
{% endif %} {% endif %}
<li {% if selected == "requirements" %}class="active"{% endif %}><a href="{% url 'ietf.nomcom.views.requirements' year %}">Desired expertise</a></li> <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> <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> </ul>
{% block nomcom_content %} {% 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"> <div class="form-group">
<label class="col-sm-2 control-label">Nomcom Eligible</label> <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"> <div class="col-sm-9">
<p class="alert alert-info form-control-static"> <p class="alert alert-info form-control-static">
If you believe this calculation is incorrect, make sure you've added all the If you believe this calculation is incorrect, make sure you've added all the
@ -80,6 +87,9 @@
for eligibility requirements. for eligibility requirements.
For the 2021 nomcom, see also <a href="{% url 'ietf.doc.views_doc.document_main' name='rfc8989' %}">RFC 8989</a>. 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> </p>
</div> </div>
</div> </div>
@ -89,7 +99,9 @@
<div class="col-sm-10 form-control-static"> <div class="col-sm-10 form-control-static">
{% for extres in person.personextresource_set.all %} {% for extres in person.personextresource_set.all %}
<div class="row"> <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}} <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 %} {% if forloop.first %}&nbsp;<a href="{% url 'ietf.ietfauth.views.edit_person_externalresources' %}"><span class="fa fa-pencil"></span></a>{% endif %}
</div> </div>