diff --git a/ietf/nomcom/factories.py b/ietf/nomcom/factories.py index 02791b872..72bff7355 100644 --- a/ietf/nomcom/factories.py +++ b/ietf/nomcom/factories.py @@ -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): diff --git a/ietf/nomcom/forms.py b/ietf/nomcom/forms.py index 11c7f9c43..82559716b 100644 --- a/ietf/nomcom/forms.py +++ b/ietf/nomcom/forms.py @@ -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) diff --git a/ietf/nomcom/migrations/0008_auto_20151209_1423.py b/ietf/nomcom/migrations/0008_auto_20151209_1423.py new file mode 100644 index 000000000..8d8861a6b --- /dev/null +++ b/ietf/nomcom/migrations/0008_auto_20151209_1423.py @@ -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, + ), + ] diff --git a/ietf/nomcom/models.py b/ietf/nomcom/models.py index 97247d860..521cbe233 100644 --- a/ietf/nomcom/models.py +++ b/ietf/nomcom/models.py @@ -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) diff --git a/ietf/nomcom/resources.py b/ietf/nomcom/resources.py index 19583bb12..0ab7115a5 100644 --- a/ietf/nomcom/resources.py +++ b/ietf/nomcom/resources.py @@ -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, diff --git a/ietf/nomcom/test_data.py b/ietf/nomcom/test_data.py index f680809dc..bee016d35 100644 --- a/ietf/nomcom/test_data.py +++ b/ietf/nomcom/test_data.py @@ -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, diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index 9de16385c..30299c12c 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -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) diff --git a/ietf/nomcom/urls.py b/ietf/nomcom/urls.py index 15004d79f..e213f8871 100644 --- a/ietf/nomcom/urls.py +++ b/ietf/nomcom/urls.py @@ -7,6 +7,7 @@ urlpatterns = patterns('ietf.nomcom.views', url(r'^ann/$', 'announcements'), url(r'^(?P\d{4})/private/$', 'private_index', name='nomcom_private_index'), url(r'^(?P\d{4})/private/key/$', 'private_key', name='nomcom_private_key'), + url(r'^(?P\d{4})/private/help/$', 'configuration_help', name='nomcom_chair_help'), url(r'^(?P\d{4})/private/nominate/$', 'private_nominate', name='nomcom_private_nominate'), url(r'^(?P\d{4})/private/feedback/$', 'private_feedback', name='nomcom_private_feedback'), url(r'^(?P\d{4})/private/feedback-email/$', 'private_feedback_email', name='nomcom_private_feedback_email'), diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index 3df144ba1..05aa4a8c1 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -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}) diff --git a/ietf/templates/nomcom/chair_help.html b/ietf/templates/nomcom/chair_help.html new file mode 100644 index 000000000..aab6c6092 --- /dev/null +++ b/ietf/templates/nomcom/chair_help.html @@ -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 %} + + + +
+

Help for Configuring a New NomCom

+ + + + +

Generate a keypair for the nomcom

+ +

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. +

+ +

To generate the keypair: +

    +
  1. +Create a config file for openssl, named nomcom-config.cnf, with the following contents: +
    [ 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
    +
  2. +
  3. Generate a private key and corresponding certificate: +
    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
    +(Just press Enter when presented with "Common Name (e.g. NomComYY) [NomCom15]:") +
  4. +
+

+You will upload the certificate to the datatracker (and make it available to people wishing to send mail) in the steps below. +

+

Securely distribute privateKey-nomcom{{year|slice:"2:"}} to your NomCom advisor(s), liaisons, and members, as they become known.

+ + +

Configure the Datatracker NomCom

+ +

Sign into the datatracker and go to the NomCom Configuration Page.

+

Use the Browse button to select the public nomcom{{year|slice:"2:"}}.cert file created above.

+

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".

+

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.

+

Press the save button.

+

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.

+ +

Configure the Positions to be filled

+

Add the positions this nomcom needs to fill.

+

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.

+ +

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.

+

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.

+ +

Customize the web-form and email templates

+ +

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.

+ +

Test the results

+

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. +

+{% endblock %} + diff --git a/ietf/templates/nomcom/list_positions.html b/ietf/templates/nomcom/list_positions.html index b0106e9be..a53a16c60 100644 --- a/ietf/templates/nomcom/list_positions.html +++ b/ietf/templates/nomcom/list_positions.html @@ -10,16 +10,18 @@ {% if nomcom.group.state_id == 'active' %} Add new position +

{% endif %} {% if positions %} - {% for position in positions %} -

{{ position.name }}

+ {% regroup positions by is_open as posgroups %} + {% for group in posgroups %} +
+

{{ group.grouper| yesno:"Open Positions,Closed Positions"}}

+
+ {% for position in group.list %} +

{{ position.name }}

-
Description
-
{{ position.description }}
-
Is open
-
{{ position.is_open }}
Templates
{% for template in position.get_templates %} @@ -35,6 +37,9 @@ {% endif %}
{% endfor %} +
+
+ {% endfor %} {% else %}

There are no positions defined.

{% endif %} diff --git a/ietf/templates/nomcom/nomcom_private_base.html b/ietf/templates/nomcom/nomcom_private_base.html index a2464294b..20c4779c6 100644 --- a/ietf/templates/nomcom/nomcom_private_base.html +++ b/ietf/templates/nomcom/nomcom_private_base.html @@ -42,6 +42,7 @@ {% if nomcom.group.state_id == 'active' %}
  • Edit Members
  • {% endif %} +
  • Configuration Help
  • diff --git a/ietf/templates/nomcom/questionnaires.html b/ietf/templates/nomcom/questionnaires.html index 1716cf9a7..931d331c8 100644 --- a/ietf/templates/nomcom/questionnaires.html +++ b/ietf/templates/nomcom/questionnaires.html @@ -19,7 +19,6 @@
    {% for position in positions %}
    -

    {{ position.description }}

    {{ position.get_questionnaire|linebreaks}}
    {% endfor %} diff --git a/ietf/templates/nomcom/remove_position.html b/ietf/templates/nomcom/remove_position.html index 82b8f8235..166f5bf04 100644 --- a/ietf/templates/nomcom/remove_position.html +++ b/ietf/templates/nomcom/remove_position.html @@ -8,8 +8,6 @@ {% origin %}

    Position: {{ position }}

    -
    Description:
    -
    {{ position.description }}
    Is open:
    {{ position.is_open }}
    diff --git a/ietf/templates/nomcom/requirements.html b/ietf/templates/nomcom/requirements.html index df3b8a210..d6f2edcad 100644 --- a/ietf/templates/nomcom/requirements.html +++ b/ietf/templates/nomcom/requirements.html @@ -26,7 +26,6 @@
    {% for position in positions %}
    -

    {{ position.description }}

    {{ position.get_requirement|linebreaks}}
    {% endfor %}