Removed the description field from Position. Simplified the Position list and the Position edit form. Tweaked several places to make the pages more self documenting. Added a page to help nomcom chairs through setting up a new nomcom. Fixes #1867 and #1768.

- Legacy-Id: 10565
This commit is contained in:
Robert Sparks 2015-12-09 23:24:46 +00:00
parent f566a83d1d
commit b653e8fe8a
15 changed files with 173 additions and 36 deletions

View file

@ -125,7 +125,6 @@ class PositionFactory(factory.DjangoModelFactory):
model = Position
name = factory.Faker('sentence',nb_words=10)
description = factory.Faker('paragraph',nb_sentences=4)
is_open = True
class NomineeFactory(factory.DjangoModelFactory):

View file

@ -17,7 +17,6 @@ from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEM
get_user_email, validate_private_key, validate_public_key,
get_or_create_nominee, create_feedback_email)
from ietf.person.models import Email
from ietf.person.fields import SearchableEmailField
from ietf.utils.fields import MultiEmailField
from ietf.utils.mail import send_mail
from ietf.mailtrigger.utils import gather_address_lists
@ -566,13 +565,11 @@ class NomComTemplateForm(BaseNomcomForm, DBTemplateForm):
class PositionForm(BaseNomcomForm, forms.ModelForm):
fieldsets = [('Position', ('name', 'description', 'is_open' ))]
incumbent = SearchableEmailField(required=False)
fieldsets = [('Position', ('name', 'is_open' ))]
class Meta:
model = Position
fields = ('name', 'description', 'is_open')
fields = ('name', 'is_open')
def __init__(self, *args, **kwargs):
self.nomcom = kwargs.pop('nomcom', None)

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('nomcom', '0007_feedbacklastseen'),
]
operations = [
migrations.RemoveField(
model_name='position',
name='description',
),
migrations.AlterField(
model_name='position',
name='name',
field=models.CharField(help_text=b'This short description will appear on the Nomination and Feedback pages. Be as descriptive as necessary. Past examples: "Transport AD", "IAB Member"', max_length=255, verbose_name=b'Name'),
preserve_default=True,
),
]

View file

@ -164,8 +164,7 @@ class NomineePosition(models.Model):
class Position(models.Model):
nomcom = models.ForeignKey('NomCom')
name = models.CharField(verbose_name='Name', max_length=255)
description = models.TextField(verbose_name='Description')
name = models.CharField(verbose_name='Name', max_length=255, help_text='This short description will appear on the Nomination and Feedback pages. Be as descriptive as necessary. Past examples: "Transport AD", "IAB Member"')
requirement = models.ForeignKey(DBTemplate, related_name='requirement', null=True, editable=False)
questionnaire = models.ForeignKey(DBTemplate, related_name='questionnaire', null=True, editable=False)
is_open = models.BooleanField(verbose_name='Is open', default=False)

View file

@ -37,7 +37,6 @@ class PositionResource(ModelResource):
filtering = {
"id": ALL,
"name": ALL,
"description": ALL,
"is_open": ALL,
"nomcom": ALL_WITH_RELATIONS,
"requirement": ALL_WITH_RELATIONS,

View file

@ -21,19 +21,19 @@ SECRETARIAT_USER = 'secretary'
EMAIL_DOMAIN = '@example.com'
NOMCOM_YEAR = "2013"
POSITIONS = {
"GEN": "IETF Chair/Gen AD",
"APP": "APP Area Director",
"INT": "INT Area Director",
"OAM": "OPS Area Director",
"OPS": "OPS Area Director",
"RAI": "RAI Area Director",
"RTG": "RTG Area Director",
"SEC": "SEC Area Director",
"TSV": "TSV Area Director",
"IAB": "IAB Member",
"IAOC": "IAOC Member",
}
POSITIONS = [
"GEN",
"APP",
"INT",
"OAM",
"OPS",
"RAI",
"RTG",
"SEC",
"TSV",
"IAB",
"IAOC"
]
def generate_cert():
@ -127,10 +127,9 @@ def nomcom_test_data():
nominee, _ = Nominee.objects.get_or_create(email=email, nomcom=nomcom)
# positions
for name, description in POSITIONS.iteritems():
for name in POSITIONS:
position, created = Position.objects.get_or_create(nomcom=nomcom,
name=name,
description=description,
is_open=True)
ChangeStateGroupEvent.objects.get_or_create(group=group,

View file

@ -430,7 +430,7 @@ class NomcomViewsTest(TestCase):
nomcom = get_nomcom_by_year(self.year)
count = nomcom.position_set.all().count()
login_testing_unauthorized(self, CHAIR_USER, self.edit_position_url)
test_data = {"action" : "add", "name": "testpos", "description": "test description"}
test_data = {"action" : "add", "name": "testpos" }
r = self.client.post(self.edit_position_url, test_data)
self.assertEqual(r.status_code, 302)
self.assertEqual(nomcom.position_set.all().count(), count+1)
@ -1181,3 +1181,15 @@ class FeedbackLastSeenTests(TestCase):
self.assertEqual(response.status_code,200)
q = PyQuery(response.content)
self.assertEqual( len(q('.label-success')), 0 )
class NewActiveNomComTests(TestCase):
def setUp(self):
self.nc = NomComFactory.create(**nomcom_kwargs_for_year(group__state_id='conclude'))
self.chair = self.nc.group.role_set.filter(name='chair').first().person
def test_help(self):
url = reverse('nomcom_chair_help',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)

View file

@ -7,6 +7,7 @@ urlpatterns = patterns('ietf.nomcom.views',
url(r'^ann/$', 'announcements'),
url(r'^(?P<year>\d{4})/private/$', 'private_index', name='nomcom_private_index'),
url(r'^(?P<year>\d{4})/private/key/$', 'private_key', name='nomcom_private_key'),
url(r'^(?P<year>\d{4})/private/help/$', 'configuration_help', name='nomcom_chair_help'),
url(r'^(?P<year>\d{4})/private/nominate/$', 'private_nominate', name='nomcom_private_nominate'),
url(r'^(?P<year>\d{4})/private/feedback/$', 'private_feedback', name='nomcom_private_feedback'),
url(r'^(?P<year>\d{4})/private/feedback-email/$', 'private_feedback_email', name='nomcom_private_feedback_email'),

View file

@ -874,7 +874,7 @@ def edit_template(request, year, template_id):
@role_required("Nomcom Chair", "Nomcom Advisor")
def list_positions(request, year):
nomcom = get_nomcom_by_year(year)
positions = nomcom.position_set.all()
positions = nomcom.position_set.order_by('-is_open')
return render_to_response('nomcom/list_positions.html',
{'positions': positions,
@ -936,3 +936,7 @@ def edit_position(request, year, position_id=None):
'nomcom': nomcom,
'is_chair_task' : True,
}, RequestContext(request))
@role_required("Nomcom Chair", "Nomcom Advisor")
def configuration_help(request, year):
return render(request,'nomcom/chair_help.html',{'year':year})

View file

@ -0,0 +1,101 @@
{% extends "nomcom/nomcom_private_base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load bootstrap3 %}
{% load ietf_filters %}
{% block bodyAttrs %}data-spy="scroll" data-target="#nav-instructions"{% endblock %}
{% block subtitle %} - Configuration Help {% endblock %}
{% block nomcom_content %}
{% origin %}
{% comment %} Why isn't this part of the base templates? {% endcomment %}
{% bootstrap_messages %}
<div class="col-sm-2 col-sm-offset-10 hidden-xs hidden-print bs-docs-sidebar" id="nav-instructions">
<ul class="nav nav-pills nav-stacked small" data-spy="affix">
<li><a href="#keys">Keypair</a></li>
<li><a href="#configure">Configuration</a></li>
<li><a href="#positions">Positions</a></li>
<li><a href="#templates">Templates</a></li>
</ul>
</div>
<div id="instructions" class="col-sm-10">
<h2>Help for Configuring a New NomCom</h2>
<h3 class="anchor-target" id="keys">Generate a keypair for the nomcom</h3>
<p> The Datatracker uses a public/private keypair to encrypt any feedback entered by the community
before storing it in the database. Only persons with the private key can view this feedback.
The private key is provided by using a datatracker page to store a blowfish-encrypted cookie in a browser session.
The blowfish-encrypted private key is sent to the server and used to decrypt feedback. The private key is never
stored on the server, but if the server is compromised, it would be possible for an attacker to grab the private key
by modifying the datatracker code. The NomCom chair generates the keypair for each NomCom and manages its secure
distribution.
</p>
<p>To generate the keypair:
<ol>
<li>
Create a config file for openssl, named nomcom-config.cnf, with the following contents:
<pre>[ req ]
distinguished_name = req_distinguished_name
string_mask = utf8only
x509_extensions = ss_v3_ca
[ req_distinguished_name ]
commonName = Common Name (e.g. NomComYY)
commonName_default = NomCom{{year|slice:"2:"}}
[ ss_v3_ca ]
subjectKeyIdentifier = hash
keyUsage = critical, digitalSignature, keyEncipherment, dataEncipherment
basicConstraints = critical, CA:true
subjectAltName = email:nomcom{{year|slice:"2:"}}@ietf.org
extendedKeyUsage= emailProtection
</pre></li>
<li>Generate a private key and corresponding certificate:
<pre>openssl req -config nomcom-config.cnf -x509 -new -newkey rsa:2048 -sha256 -days 730 -nodes -keyout privateKey-nomcom{{year|slice:"2:"}}.pem -out nomcom{{year|slice:"2:"}}.cert</pre>
(Just press Enter when presented with "Common Name (e.g. NomComYY) [NomCom15]:")
</li>
</ol>
<p>
You will upload the certificate to the datatracker (and make it available to people wishing to send mail) in the steps below.
</p>
<p>Securely distribute privateKey-nomcom{{year|slice:"2:"}} to your NomCom advisor(s), liaisons, and members, as they become known.</p>
<h3 class="anchor-target" id="configure">Configure the Datatracker NomCom</h3>
<p>Sign into the datatracker and go to the <a href="{% url 'nomcom_edit_nomcom' year %}">NomCom Configuration Page</a>.</p>
<p>Use the Browse button to select the public nomcom{{year|slice:"2:"}}.cert file created above.</p>
<p>Enter any special instructions you want to appear on the nomination entry form in the "Help text for nomination form" box. These will appear on the form immediately below the field labeled "Candidate's qualifications for the position".</p>
<p>Choose whether to have the datatracker send questionnares, and whether to automatically remind people to accept nominations and return questionnaires, according to the instructions on the form.</p>
<p>Press the save button.</p>
<p>You can return to this page and change your mind on any of the settings, even towards the end of your nomcom cycle. However, be wary of uploading a new public key once one feedback has been received. That step should only be taken in the case of a compromised keypair. Old feedback will remain encrypted with the old key, and will not be accessible through the datatracker.</p>
<h3 class="anchor-target" id="positions">Configure the Positions to be filled</h3>
<p>Add the positions this nomcom needs to fill.</p>
<p>Only create one Position for those roles having multiple seats to fill,
such as the IAB, or the IESG areas where multiple ADs in that area are at the end of their term. </p>
<p>Note the "Is open" checkbox. When this is set to True, the Position will appear on the Nomination and Feedback pages. You will set this to False when you do not want any more feedback (that is, the position is filled, or otherwise closed for some reason). It is a good idea to start with the "Is open" value set to False. After you edit the templates for the position and are ready for the community to provide nominations and feedback, set the "Is open" value to True.</p>
<p>You might need to close some positions and open others as your nomcom progresses. For example, the 2014 Nomcom was called back after it had finished work on its usual selections to fill a IAOC position that had been vacated mid-term. Before making the call for nominations and feedback for this additional IAOC position, the chair would mark the already filled positions as not open, leaving only the new IAOC position open for consideration. At that point, only that IAOC position would be available on the Nomination and Feedback pages. </p>
<h3 class="anchor-target" id="templates">Customize the web-form and email templates</h3>
<p>Edit each of the templates at {% url 'nomcom_list_templates' year %}. The "Home page of group" template is where to put information about the current nomcom members and policies. It is also a good place to list incumbents in positions, and information about whether the incumbents will stand again. See the home page of past nomcoms for examples.</p>
<h3 class="anchor-target" id="test">Test the results</h3>
<p> Before advertising that your nomcom pages are ready for the community to use, test your configuration. Create a dummy nominee for at least one position, and give it some feedback. You will be able to move this out of the way later. Once you've marked positions as open, ask your nomcom members to look over the expertise and questionnaires tab (which show rendered view of each of the templates for each position) to ensure they contain what you want the community to see. Please don't assume that everything is all right without looking. It's a good idea to give the secretariat and the tools team a heads up a few (preferably 3 to 5) days notice before announcing that your pages are ready for community use.
</div>
{% endblock %}

View file

@ -10,16 +10,18 @@
{% if nomcom.group.state_id == 'active' %}
<a class="btn btn-default" href="{% url "nomcom_add_position" year %}">Add new position</a>
<p></p>
{% endif %}
{% if positions %}
{% for position in positions %}
<h3>{{ position.name }}</h3>
{% regroup positions by is_open as posgroups %}
{% for group in posgroups %}
<div class="panel panel-default">
<div class="panel-heading"><h3>{{ group.grouper| yesno:"Open Positions,Closed Positions"}}</h3></div>
<div class="panel-body">
{% for position in group.list %}
<h4>{{ position.name }}</h4>
<dl class="dl-horizontal">
<dt>Description</dt>
<dd>{{ position.description }}</dd>
<dt>Is open</dt>
<dd>{{ position.is_open }}</dd>
<dt>Templates</dt>
<dd>
{% for template in position.get_templates %}
@ -35,6 +37,9 @@
{% endif %}
</dl>
{% endfor %}
</div>
</div>
{% endfor %}
{% else %}
<p>There are no positions defined.</p>
{% endif %}

View file

@ -42,6 +42,7 @@
{% if nomcom.group.state_id == 'active' %}
<li {% if selected == "edit_members" %}class="active"{% endif %}><a href="{% url "nomcom_edit_members" year %}">Edit Members</a></li>
{% endif %}
<li {% if selected == "help" %}class="active"{% endif %}><a href="{% url "nomcom_chair_help" year %}">Configuration Help</a></li>
</ul>
</li>

View file

@ -19,7 +19,6 @@
<div class="tab-content">
{% for position in positions %}
<div class="tab-pane {% if forloop.first %}active{% endif %}" id="{{ position.name|slugify }}">
<h3>{{ position.description }}</h3>
{{ position.get_questionnaire|linebreaks}}
</div>
{% endfor %}

View file

@ -8,8 +8,6 @@
{% origin %}
<h2>Position: {{ position }}</h2>
<dl>
<dt>Description:</dt>
<dd>{{ position.description }}</dd>
<dt>Is open:</dt>
<dd>{{ position.is_open }}</dd>
</dl>

View file

@ -26,7 +26,6 @@
<div class="tab-content">
{% for position in positions %}
<div class="tab-pane {% if forloop.first %}active{% endif %}" id="{{ position.name|slugify }}">
<h3>{{ position.description }}</h3>
{{ position.get_requirement|linebreaks}}
</div>
{% endfor %}